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