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 static import requests.idna; 10 11 class UriException: Exception { 12 this(string msg, string file = __FILE__, size_t line = __LINE__) @safe pure { 13 super(msg, file, line); 14 } 15 } 16 17 struct URI { 18 import std.string; 19 private { 20 string _uri; 21 string _scheme; 22 string _username; 23 string _password; 24 ushort _port=80; 25 string _host; 26 string _path="/"; 27 string _query; 28 string _original_host; // can differ from _host if host is unicode 29 } 30 this(string uri) @safe pure { 31 _uri = uri; 32 auto parsed = uri_parse(uri); 33 if ( !parsed ) { 34 throw new UriException("Can't parse uri '" ~ _uri ~ "'"); 35 } 36 } 37 38 bool uri_parse(string uri) @safe pure { 39 auto i = uri.findSplit("://"); 40 string rest; 41 if ( i[1].length ) { 42 _scheme = i[0].toLower; 43 rest = i[2]; 44 } else { 45 return false; 46 } 47 // separate Authority from path and query 48 i = rest.findSplit("/"); 49 auto authority = i[0]; 50 auto path_and_query = i[2]; 51 52 // find user/password/host:port in authority 53 i = authority.findSplit("@"); 54 string up; 55 string hp; 56 if ( i[1].length ) { 57 up = i[0]; 58 hp = i[2]; 59 } else { 60 hp = i[0]; 61 } 62 63 i = hp.findSplit(":"); 64 _original_host = i[0]; 65 _host = i[0]; 66 _port = i[2].length ? to!ushort(i[2]) : standard_ports[_scheme]; 67 68 if ( up.length ) { 69 i = up.findSplit(":"); 70 _username = i[0]; 71 _password = i[2]; 72 } 73 // finished with authority 74 // handle path and query 75 if ( path_and_query.length ) { 76 i = path_and_query.findSplit("?"); 77 _path = "/" ~ i[0]; 78 if ( i[2].length) { 79 _query = "?" ~ i[2]; 80 } 81 } 82 // 83 return true; 84 } 85 86 string recalc_uri() const pure @safe { 87 string userinfo; 88 if ( _username ) { 89 userinfo = "%s".format(_username); 90 if ( _password ) { 91 userinfo ~= ":" ~ _password; 92 } 93 userinfo ~= "@"; 94 } 95 string r = "%s://%s%s".format(_scheme, userinfo, _host); 96 if ( _scheme !in standard_ports || standard_ports[_scheme] != _port ) { 97 r ~= ":%d".format(_port); 98 } 99 r ~= _path; 100 if ( _query ) { 101 r ~= _query; 102 } 103 return r; 104 } 105 mixin(Getter_Setter!string("scheme")); 106 mixin(Getter_Setter!string("host")); 107 mixin(Getter_Setter!string("username")); 108 mixin(Getter_Setter!string("password")); 109 mixin(Getter_Setter!ushort("port")); 110 mixin(Getter_Setter!string("path")); 111 mixin(Getter!string("query")); 112 mixin(Getter!string("original_host")); 113 @property void query(string s) { 114 if ( s[0]=='?' ) { 115 _query = s; 116 } 117 else { 118 _query = "?" ~ s; 119 } 120 } 121 // mixin(setter("scheme")); 122 // mixin(setter("host")); 123 // mixin(setter("username")); 124 // mixin(setter("password")); 125 // mixin(setter("port")); 126 // mixin(setter("path")); 127 // mixin(setter("query")); 128 @property auto uri() pure @safe const { 129 return recalc_uri(); 130 } 131 @property void uri(string s) @trusted { 132 _uri = s; 133 // auto parsed = uri_grammar(s); 134 // if ( !parsed.successful || parsed.matches.joiner.count != __uri.length) { 135 // throw new UriException("Can't parse uri '" ~ __uri ~ "'"); 136 // } 137 // traverseTree(parsed); 138 } 139 void idn_encode() @safe { 140 _host = requests.idna.idn_encode(_original_host); 141 } 142 } 143 unittest { 144 import std.exception; 145 import std.experimental.logger; 146 147 globalLogLevel(LogLevel.info); 148 auto a = URI("http://example.com/"); 149 assert(a.scheme == "http"); 150 assert(a.host == "example.com"); 151 assert(a.path == "/"); 152 a = URI("svn+ssh://igor@example.com:1234"); 153 assert(a.scheme == "svn+ssh"); 154 assert(a.host == "example.com"); 155 assert(a.username == "igor"); 156 assert(a.path == "/"); 157 a = URI("http://igor:pass;word@example.com:1234/abc?q=x"); 158 assert(a.password == "pass;word"); 159 assert(a.port == 1234); 160 assert(a.path == "/abc"); 161 assert(a.query == "?q=x"); 162 a.scheme = "https"; 163 a.query = "x=y"; 164 a.port = 345; 165 auto expected = "https://igor:pass;word@example.com:345/abc?x=y"; 166 assert(a.uri == expected, "Expected '%s', got '%s'".format(expected, a.uri)); 167 assertThrown!UriException(URI("@unparsable")); 168 a = URI("http://registrera-domän.se"); 169 a.idn_encode(); 170 assert(a.host == "xn--registrera-domn-elb.se"); 171 } 172