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