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