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