1 module requests.utils;
2 
3 import std.range;
4 import std.string;
5 import std.datetime;
6 import std.algorithm.sorting;
7 import std.experimental.logger;
8 import std.typecons;
9 import std.algorithm;
10 import std.conv;
11 
12 //import requests.streams;
13 
14 __gshared immutable short[string] standard_ports;
15 shared static this() {
16     standard_ports["http"] = 80;
17     standard_ports["https"] = 443;
18     standard_ports["ftp"] = 21;
19 }
20 
21 
22 string Getter_Setter(T)(string name) {
23     return `
24         @property final auto ` ~ name ~ `() pure inout @safe @nogc nothrow {
25             return _` ~ name ~ `;
26         }
27         @property final void ` ~ name ~ `(` ~ T.stringof ~ ` setter_arg) pure @nogc nothrow {
28             _` ~ name ~ `= setter_arg;
29         }
30     `;
31 }
32 
33 string Setter(T)(string name) {
34     return `
35         @property final void ` ~ name ~ `(` ~ T.stringof ~ ` setter_arg) pure @nogc nothrow {
36             _` ~ name ~ `= setter_arg;
37         }
38     `;
39 }
40 
41 string Getter(string name) {
42     return `
43         @property final auto ` ~ name ~ `() pure inout @safe @nogc nothrow {
44             return _` ~ name ~ `;
45         }
46     `;
47 }
48 
49 //auto getter(string name) {
50 //    return `
51 //        @property final auto ` ~ name ~ `() const @safe @nogc {
52 //            return __` ~ name ~ `;
53 //        }
54 //    `;
55 //}
56 //auto setter(string name) {
57 //    string member = "__" ~ name;
58 //    string t = "typeof(this."~member~")";
59 //    return `
60 //        @property final void ` ~ name ~`(` ~ t ~ ` s) pure @nogc nothrow {`~
61 //             member ~`=s;
62 //        }
63 //    `;
64 //}
65 
66 unittest {
67     struct S {
68         private {
69             int    _i;
70             string _s;
71             bool   _b;
72         }
73         mixin(Getter("i"));
74         mixin(Setter!int("i"));
75         mixin(Getter("b"));
76     }
77     S s;
78     assert(s.i == 0);
79     s.i = 1;
80     assert(s.i == 1);
81     assert(s.b == false);
82 }
83 
84 template rank(R) {
85     static if ( isInputRange!R ) {
86         enum size_t rank = 1 + rank!(ElementType!R);
87     } else {
88         enum size_t rank = 0;
89     }
90 }
91 unittest {
92     assert(rank!(char) == 0);
93     assert(rank!(string) == 1);
94     assert(rank!(ubyte[][]) == 2);
95 }
96 // test if p1 is sub-path of p2 (used to find Cookie to send)
97 bool pathMatches(string p1, string p2) pure @safe @nogc {
98     import std.algorithm;
99     return p1.startsWith(p2);
100 }
101 
102 package unittest {
103     assert("/abc/def".pathMatches("/"));
104     assert("/abc/def".pathMatches("/abc"));
105     assert("/abc/def".pathMatches("/abc/def"));
106     assert(!"/def".pathMatches("/abc"));
107 }
108 
109 // test if d1 is subbomain of d2 (used to find Cookie to send)
110 //    Host names can be specified either as an IP address or a HDN string.
111 //    Sometimes we compare one host name with another.  (Such comparisons
112 //    SHALL be case-insensitive.)  Host A's name domain-matches host B's if
113 //
114 //    *  their host name strings string-compare equal; or
115 //
116 //    * A is a HDN string and has the form NB, where N is a non-empty
117 //        name string, B has the form .B', and B' is a HDN string.  (So,
118 //            x.y.com domain-matches .Y.com but not Y.com.)
119 
120 package bool domainMatches(string d1, string d2) pure @safe @nogc {
121     import std.algorithm;
122     return d1==d2 ||
123            (d2.startsWith(".") && d1.endsWith(d2));
124 }
125 
126 package unittest {
127     assert("x.example.com".domainMatches(".example.com"));
128     assert(!"x.example.com".domainMatches("example.com"));
129     assert(!"example.com".domainMatches(".x.example.com"));
130     assert("example.com".domainMatches("example.com"));
131     assert(!"example.com".domainMatches(""));
132     assert(!"".domainMatches("example.com"));
133 }
134 
135 string[] dump(in ubyte[] data) {
136     import std.stdio;
137     import std.range;
138     import std.ascii;
139     import std.format;
140     import std.algorithm;
141 
142     string[] res;
143 
144     foreach(i,chunk; data.chunks(16).enumerate) {
145         string r;
146         r ~= format("%05X  ", i*16);
147         ubyte[] left, right;
148         if ( chunk.length > 8 ) {
149             left = chunk[0..8].dup;
150             right= chunk[8..$].dup;
151         } else {
152             left = chunk.dup;
153         }
154         r ~= format("%-24.24s ", left.map!(c => format("%02X", c)).join(" "));
155         r ~= format("%-24.24s ", right.map!(c => format("%02X", c)).join(" "));
156         r ~= format("|%-16s|", chunk.map!(c => isPrintable(c)?cast(char)c:'.'));
157         res ~= r;
158     }
159     return res;
160 }
161 
162 static string urlEncoded(string p) pure @safe {
163     immutable string[dchar] translationTable = [
164         ' ':  "%20", '!': "%21", '*': "%2A", '\'': "%27", '(': "%28", ')': "%29",
165         ';':  "%3B", ':': "%3A", '@': "%40", '&':  "%26", '=': "%3D", '+': "%2B",
166         '$':  "%24", ',': "%2C", '/': "%2F", '?':  "%3F", '#': "%23", '[': "%5B",
167         ']':  "%5D", '%': "%25",
168     ];
169     return p.translate(translationTable);
170 }
171 package unittest {
172     assert(urlEncoded(`abc !#$&'()*+,/:;=?@[]`) == "abc%20%21%23%24%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D");
173 }
174 
175 private static immutable char[string] hex2chr;
176 shared static this() {
177     foreach(c; 0..255) {
178         hex2chr["%02X".format(c)] = cast(char)c;
179     }
180 }
181 
182 string urlDecode(string p) {
183     import std.string;
184     import std.algorithm;
185     import core.exception;
186 
187     if ( !p.canFind("%") ) {
188         return p.replace("+", " ");
189     }
190     string[] res;
191     auto parts = p.replace("+", " ").split("%");
192     res ~= parts[0];
193     foreach(part; parts[1..$]) {
194         if ( part.length<2 ) {
195             res ~= "%" ~ part;
196             continue;
197         }
198         try {
199             res ~= hex2chr[part[0..2]] ~ part[2..$];
200         } catch (RangeError e) {
201             res ~= "%" ~ part;
202         }
203     }
204     return res.join();
205 }
206 
207 package unittest {
208     assert(urlEncoded(`abc !#$&'()*+,/:;=?@[]`) == "abc%20%21%23%24%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D");
209     assert(urlDecode("a+bc%20%21%23%24%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D") == `a bc !#$&'()*+,/:;=?@[]`);
210 }
211 
212 
213 public alias Cookie     = Tuple!(string, "path", string, "domain", string, "attr", string, "value");
214 public alias QueryParam = Tuple!(string, "key", string, "value");
215 
216 struct Cookies {
217     Cookie[]    _array;
218     alias _array this;
219 }
220 
221 ///
222 /// create QueryParam[] from assoc.array string[string]
223 //
224 QueryParam[] aa2params(string[string] aa)
225 {
226     return aa.byKeyValue.map!(p => QueryParam(p.key, p.value)).array;
227 }
228 
229 ///
230 /// Create QueryParam[] from any args
231 ///
232 auto queryParams(A...)(A args) pure @safe nothrow {
233     QueryParam[] res;
234     static if ( args.length >= 2 ) {
235         res = [QueryParam(args[0].to!string, args[1].to!string)] ~ queryParams(args[2..$]);
236     }
237     return res;
238 }
239 
240 bool responseMustNotIncludeBody(ushort code) pure nothrow @nogc @safe
241 {
242     // https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.4
243     if ((code / 100 == 1) || code == 204 || code == 304 ) return true;
244     return false;
245 }