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[0] == '.' && 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("example.com"));
130 }
131 
132 string[] dump(in ubyte[] data) {
133     import std.stdio;
134     import std.range;
135     import std.ascii;
136     import std.format;
137     import std.algorithm;
138 
139     string[] res;
140 
141     foreach(i,chunk; data.chunks(16).enumerate) {
142         string r;
143         r ~= format("%05X  ", i*16);
144         ubyte[] left, right;
145         if ( chunk.length > 8 ) {
146             left = chunk[0..8].dup;
147             right= chunk[8..$].dup;
148         } else {
149             left = chunk.dup;
150         }
151         r ~= format("%-24.24s ", left.map!(c => format("%02X", c)).join(" "));
152         r ~= format("%-24.24s ", right.map!(c => format("%02X", c)).join(" "));
153         r ~= format("|%-16s|", chunk.map!(c => isPrintable(c)?cast(char)c:'.'));
154         res ~= r;
155     }
156     return res;
157 }
158 
159 static string urlEncoded(string p) pure @safe {
160     immutable string[dchar] translationTable = [
161         ' ':  "%20", '!': "%21", '*': "%2A", '\'': "%27", '(': "%28", ')': "%29",
162         ';':  "%3B", ':': "%3A", '@': "%40", '&':  "%26", '=': "%3D", '+': "%2B",
163         '$':  "%24", ',': "%2C", '/': "%2F", '?':  "%3F", '#': "%23", '[': "%5B",
164         ']':  "%5D", '%': "%25",
165     ];
166     return p.translate(translationTable);
167 }
168 package unittest {
169     assert(urlEncoded(`abc !#$&'()*+,/:;=?@[]`) == "abc%20%21%23%24%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D");
170 }
171 
172 private static immutable char[string] hex2chr;
173 static this() {
174     foreach(c; 0..255) {
175         hex2chr["%02X".format(c)] = cast(char)c;
176     }
177 }
178 
179 string urlDecode(string p) {
180     import std.string;
181     import std.algorithm;
182     import core.exception;
183 
184     if ( !p.canFind("%") ) {
185         return p.replace("+", " ");
186     }
187     string[] res;
188     auto parts = p.replace("+", " ").split("%");
189     res ~= parts[0];
190     foreach(part; parts[1..$]) {
191         if ( part.length<2 ) {
192             res ~= "%" ~ part;
193             continue;
194         }
195         try {
196             res ~= hex2chr[part[0..2]] ~ part[2..$];
197         } catch (RangeError e) {
198             res ~= "%" ~ part;
199         }
200     }
201     return res.join();
202 }
203 
204 package unittest {
205     assert(urlEncoded(`abc !#$&'()*+,/:;=?@[]`) == "abc%20%21%23%24%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D");
206     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 !#$&'()*+,/:;=?@[]`);
207 }
208 
209 
210 public alias Cookie     = Tuple!(string, "path", string, "domain", string, "attr", string, "value");
211 public alias QueryParam = Tuple!(string, "key", string, "value");
212 
213 struct Cookies {
214     Cookie[]    _array;
215 }
216 
217 ///
218 /// create QueryParam[] from assoc.array string[string]
219 //
220 QueryParam[] aa2params(string[string] aa)
221 {
222     return aa.byKeyValue.map!(p => QueryParam(p.key, p.value)).array;
223 }
224 
225 ///
226 /// Create QueryParam[] from any args
227 ///
228 auto queryParams(A...)(A args) pure @safe nothrow {
229     QueryParam[] res;
230     static if ( args.length >= 2 ) {
231         res = [QueryParam(args[0].to!string, args[1].to!string)] ~ queryParams(args[2..$]);
232     }
233     return res;
234 }