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