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