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