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!string("query"));
109     @property void query(string s) {
110         if ( s[0]=='?' ) {
111             _query = s;
112         }
113         else {
114             _query = "?" ~ s;
115         }
116     }
117 //    mixin(setter("scheme"));
118 //    mixin(setter("host"));
119 //    mixin(setter("username"));
120 //    mixin(setter("password"));
121 //    mixin(setter("port"));
122 //    mixin(setter("path"));
123 //    mixin(setter("query"));
124     @property auto uri() pure @safe const {
125         return recalc_uri();
126     }
127     @property void uri(string s) @trusted {
128         _uri = s;
129         //        auto parsed = uri_grammar(s);
130         //        if ( !parsed.successful || parsed.matches.joiner.count != __uri.length) {
131         //            throw new UriException("Can't parse uri '" ~ __uri ~ "'");
132         //        }
133         //        traverseTree(parsed);
134     }
135 }
136 unittest {
137     import std.exception;
138     import std.experimental.logger;
139     
140     globalLogLevel(LogLevel.info);
141     auto a = URI("http://example.com/");
142     assert(a.scheme == "http");
143     assert(a.host == "example.com");
144     assert(a.path == "/");
145     a = URI("svn+ssh://igor@example.com:1234");
146     assert(a.scheme == "svn+ssh");
147     assert(a.host == "example.com");
148     assert(a.username == "igor");
149     assert(a.path == "/");
150     a = URI("http://igor:pass;word@example.com:1234/abc?q=x");
151     assert(a.password == "pass;word");
152     assert(a.port == 1234);
153     assert(a.path == "/abc");
154     assert(a.query == "?q=x");
155     a.scheme = "https";
156     a.query = "x=y";
157     a.port = 345;
158     auto expected = "https://igor:pass;word@example.com:345/abc?x=y";
159     assert(a.uri == expected, "Expected '%s', got '%s'".format(expected, a.uri));
160     assertThrown!UriException(URI("@unparsable"));
161 }
162