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