1 module requests.uri; 2 3 import std.experimental.logger; 4 import std.array; 5 import std.format; 6 import std.algorithm; 7 import std.conv; 8 import requests.utils; 9 10 class UriException: Exception { 11 this(string msg, string file = __FILE__, size_t line = __LINE__) @safe pure { 12 super(msg, file, line); 13 } 14 } 15 16 struct URI { 17 import std.string; 18 private { 19 string __uri; 20 string __scheme; 21 string __username; 22 string __password; 23 ushort __port=80; 24 string __host; 25 string __path="/"; 26 string __query; 27 } 28 this(string uri) { 29 __uri = uri; 30 auto parsed = uri_parse(uri); 31 if ( !parsed ) { 32 throw new UriException("Can't parse uri '" ~ __uri ~ "'"); 33 } 34 } 35 36 bool uri_parse(string uri) { 37 auto i = uri.findSplit("://"); 38 string rest; 39 if ( i[1].length ) { 40 __scheme = i[0].toLower; 41 rest = i[2]; 42 } else { 43 return false; 44 } 45 // separate Authority from path and query 46 i = rest.findSplit("/"); 47 auto authority = i[0]; 48 auto path_and_query = i[2]; 49 50 // find user/password/host:port in authority 51 i = authority.findSplit("@"); 52 string up; 53 string hp; 54 if ( i[1].length ) { 55 up = i[0]; 56 hp = i[2]; 57 } else { 58 hp = i[0]; 59 } 60 i = hp.findSplit(":"); 61 __host = i[0]; 62 if ( i[2].length ) { 63 __port = to!ushort(i[2]); 64 } else { 65 if ( __scheme == "https" ) { 66 __port = 443; 67 } 68 } 69 if ( up.length ) { 70 i = up.findSplit(":"); 71 __username = i[0]; 72 __password = i[2]; 73 } 74 // finished with authority 75 // handle path and query 76 if ( path_and_query.length ) { 77 i = path_and_query.findSplit("?"); 78 __path = "/" ~ i[0]; 79 if ( i[2].length) { 80 __query = "?" ~ i[2]; 81 } 82 } 83 // 84 return true; 85 } 86 87 string recalc_uri() const pure @safe { 88 string userinfo; 89 if ( __username ) { 90 userinfo = "%s".format(__username); 91 if ( __password ) { 92 userinfo ~= ":" ~ __password; 93 } 94 userinfo ~= "@"; 95 } 96 string r = "%s://%s%s".format(__scheme, userinfo, __host); 97 if ( __scheme !in standard_ports || standard_ports[__scheme] != __port ) { 98 r ~= ":%d".format(__port); 99 } 100 r ~= __path; 101 if ( __query ) { 102 r ~= "?" ~ __query; 103 } 104 return r; 105 } 106 mixin(getter("scheme")); 107 mixin(getter("host")); 108 mixin(getter("username")); 109 mixin(getter("password")); 110 mixin(getter("port")); 111 mixin(getter("path")); 112 mixin(getter("query")); 113 114 mixin(setter("scheme")); 115 mixin(setter("host")); 116 mixin(setter("username")); 117 mixin(setter("password")); 118 mixin(setter("port")); 119 mixin(setter("path")); 120 mixin(setter("query")); 121 @property auto uri() pure @safe const { 122 return recalc_uri(); 123 } 124 @property void uri(string s) @trusted { 125 __uri = s; 126 // auto parsed = uri_grammar(s); 127 // if ( !parsed.successful || parsed.matches.joiner.count != __uri.length) { 128 // throw new UriException("Can't parse uri '" ~ __uri ~ "'"); 129 // } 130 // traverseTree(parsed); 131 } 132 } 133 unittest { 134 import std.exception; 135 import std.experimental.logger; 136 137 globalLogLevel(LogLevel.info); 138 auto a = URI("http://example.com/"); 139 assert(a.scheme == "http"); 140 assert(a.host == "example.com"); 141 assert(a.path == "/"); 142 a = URI("svn+ssh://igor@example.com:1234"); 143 assert(a.scheme == "svn+ssh"); 144 assert(a.host == "example.com"); 145 assert(a.username == "igor"); 146 assert(a.path == "/"); 147 a = URI("http://igor:pass;word@example.com:1234/abc?q=x"); 148 assert(a.password == "pass;word"); 149 assert(a.port == 1234); 150 assert(a.path == "/abc"); 151 assert(a.query == "?q=x"); 152 a.scheme = "https"; 153 a.query = "x=y"; 154 a.port = 345; 155 auto expected = "https://igor:pass;word@example.com:345/abc?x=y"; 156 assert(a.uri == expected, "Expected '%s', got '%s'".format(expected, a.uri)); 157 assertThrown!UriException(URI("@unparsable")); 158 } 159 160 //struct sURI { 161 // import pegged.grammar; 162 // mixin(grammar(` 163 // uri_grammar: 164 // Uri <- Scheme '://' Authority ( Path ( '?' Query )? )? 165 // Scheme <- alpha ( alpha / digit / '+' / '-' / '.')* 166 // Authority <- ( Userinfo '@')? Host (':' Port)? 167 // Userinfo <- ( Unreserved / PCT / SubDelims / ':' )* 168 // Path <- ('/' Pchar* )* 169 // Query <- ( Pchar / '/' / '?' )* 170 // Host <- RegName 171 // Port < digit+ 172 // RegName <- ( Unreserved / PCT / SubDelims )+ 173 // Unreserved <- alpha / Alpha / digit / '-' / '.' / '_' / '~' 174 // PCT <- '%' hexDigit hexDigit 175 // Pchar <- Unreserved / PCT / SubDelims / ':' / '@' 176 // SubDelims <- '!' / 177 // '$' / 178 // '&' / 179 // '\'' / 180 // '(' / ')' / 181 // '*' / '+' / ',' / ';' / '=' 182 // `)); 183 // private { 184 // string __uri; 185 // string __scheme; 186 // string __username; 187 // string __password; 188 // ushort __port=80; 189 // string __host; 190 // string __path="/"; 191 // string __query; 192 // } 193 // void traverseTree(ParseTree tree) { 194 // foreach(ref child; tree.children) { 195 // traverseTree(child); 196 // } 197 // switch(tree.name) { 198 // case "uri_grammar.Scheme": 199 // tracef("Scheme '%s'", tree.matches.join); 200 // __scheme = tree.matches.join; 201 // if ( __scheme == "https" ) { 202 // __port = 443; 203 // } 204 // break; 205 // case "uri_grammar.Userinfo": 206 // tracef("Userinfo '%s'", tree.matches.join); 207 // auto p = tree.matches.findSplit([":"]); 208 // __username = p[0].join; 209 // __password = p[2].join; 210 // break; 211 // case "uri_grammar.Host": 212 // tracef("Host '%s'", tree.matches.join); 213 // __host = tree.matches.join; 214 // break; 215 // case "uri_grammar.Port": 216 // tracef("Port '%s'", tree.matches.join); 217 // __port = std.conv.to!ushort(tree.matches.join); 218 // break; 219 // case "uri_grammar.Path": 220 // tracef("Path '%s'", tree.matches.join); 221 // __path = tree.matches.join; 222 // break; 223 // case "uri_grammar.Query": 224 // tracef("Query '%s'", tree.matches.join); 225 // __query = "?" ~ tree.matches.join; 226 // break; 227 // default: 228 // } 229 // } 230 // this(string uri) { 231 // __uri = uri; 232 // auto parsed = uri_grammar(uri); 233 // if ( !parsed.successful || parsed.matches.joiner.count != __uri.length) { 234 // throw new UriException("Can't parse uri '" ~ __uri ~ "'"); 235 // } 236 // traverseTree(parsed); 237 // } 238 // string recalc_uri() const pure @safe { 239 // string userinfo; 240 // if ( __username ) { 241 // userinfo = "%s".format(__username); 242 // if ( __password ) { 243 // userinfo ~= ":" ~ __password; 244 // } 245 // userinfo ~= "@"; 246 // } 247 // string r = "%s://%s%s".format(__scheme, userinfo, __host); 248 // if ( __scheme !in standard_ports || standard_ports[__scheme] != __port ) { 249 // r ~= ":%d".format(__port); 250 // } 251 // r ~= __path; 252 // if ( __query ) { 253 // r ~= "?" ~ __query; 254 // } 255 // return r; 256 // } 257 // mixin(getter("scheme")); 258 // mixin(getter("host")); 259 // mixin(getter("username")); 260 // mixin(getter("password")); 261 // mixin(getter("port")); 262 // mixin(getter("path")); 263 // mixin(getter("query")); 264 // 265 // mixin(setter("scheme")); 266 // mixin(setter("host")); 267 // mixin(setter("username")); 268 // mixin(setter("password")); 269 // mixin(setter("port")); 270 // mixin(setter("path")); 271 // mixin(setter("query")); 272 // @property auto uri() pure @safe const { 273 // return recalc_uri(); 274 // } 275 // @property void uri(string s) @trusted { 276 // __uri = s; 277 // auto parsed = uri_grammar(s); 278 // if ( !parsed.successful || parsed.matches.joiner.count != __uri.length) { 279 // throw new UriException("Can't parse uri '" ~ __uri ~ "'"); 280 // } 281 // traverseTree(parsed); 282 // } 283 //} 284 //unittest { 285 // import std.exception; 286 // import std.experimental.logger; 287 // 288 // globalLogLevel(LogLevel.trace); 289 // auto a = sURI("http://exampe.com/"); 290 // assert(a.scheme == "http"); 291 // a = sURI("svn+ssh://igor@example.com:1234"); 292 // assert(a.scheme == "svn+ssh"); 293 // assert(a.host == "example.com"); 294 // assert(a.username == "igor"); 295 // assert(a.path == "/"); 296 // a = sURI("http://igor:pass;word@example.com:1234/abc?q=x"); 297 // assert(a.password == "pass;word"); 298 // assert(a.port == 1234); 299 // assert(a.path == "/abc"); 300 // assert(a.query == "?q=x"); 301 // a.scheme = "https"; 302 // a.query = "x=y"; 303 // a.port = 345; 304 // auto expected = "https://igor:pass;word@example.com:345/abc?x=y"; 305 // assert(a.uri == expected, "Expected '%s', got '%s'".format(expected, a.uri)); 306 // assertThrown!UriException(URI("@unparsable")); 307 //}