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");