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) @safe pure { 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) @safe pure { 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_Setter!string("scheme")); 103 mixin(Getter_Setter!string("host")); 104 mixin(Getter_Setter!string("username")); 105 mixin(Getter_Setter!string("password")); 106 mixin(Getter_Setter!ushort("port")); 107 mixin(Getter_Setter!string("path")); 108 mixin(Getter_Setter!string("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