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 }