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) {
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) {
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("scheme"));
103     mixin(getter("host"));
104     mixin(getter("username"));
105     mixin(getter("password"));
106     mixin(getter("port"));
107     mixin(getter("path"));
108     mixin(getter("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 
156 //struct sURI {
157 //    import pegged.grammar;
158 //    mixin(grammar(`
159 //            uri_grammar:
160 //                Uri <- Scheme '://' Authority ( Path ( '?' Query )? )?
161 //                Scheme <- alpha ( alpha / digit / '+' / '-' / '.')*
162 //                Authority <- ( Userinfo '@')? Host (':' Port)?
163 //                Userinfo <- ( Unreserved / PCT / SubDelims / ':' )*
164 //                Path <- ('/' Pchar* )*
165 //                Query <- ( Pchar / '/' / '?' )*
166 //                Host <- RegName
167 //                Port < digit+
168 //                RegName <- ( Unreserved / PCT / SubDelims )+
169 //                Unreserved <- alpha / Alpha / digit / '-' / '.' / '_' / '~'
170 //                PCT <- '%' hexDigit hexDigit
171 //                Pchar <- Unreserved / PCT / SubDelims / ':' / '@'
172 //                SubDelims <- '!' / 
173 //                             '$' / 
174 //                             '&' / 
175 //                             '\'' / 
176 //                             '(' / ')' / 
177 //                             '*' / '+' / ',' / ';' / '='
178 //          `));
179 //    private {
180 //        string __uri;
181 //        string __scheme;
182 //        string __username;
183 //        string __password;
184 //        ushort  __port=80;
185 //        string __host;
186 //        string __path="/";
187 //        string __query;
188 //    }
189 //    void traverseTree(ParseTree tree) {
190 //        foreach(ref child; tree.children) {
191 //            traverseTree(child);
192 //        }
193 //        switch(tree.name) {
194 //            case "uri_grammar.Scheme":
195 //                tracef("Scheme '%s'", tree.matches.join);
196 //                __scheme = tree.matches.join;
197 //                if ( __scheme == "https" ) {
198 //                    __port = 443;
199 //                }
200 //                break;
201 //            case "uri_grammar.Userinfo":
202 //                tracef("Userinfo '%s'", tree.matches.join);
203 //                auto p = tree.matches.findSplit([":"]);
204 //                __username = p[0].join;
205 //                __password = p[2].join;
206 //                break;
207 //            case "uri_grammar.Host":
208 //                tracef("Host '%s'", tree.matches.join);
209 //                __host = tree.matches.join;
210 //                break;
211 //            case "uri_grammar.Port":
212 //                tracef("Port '%s'", tree.matches.join);
213 //                __port = std.conv.to!ushort(tree.matches.join);
214 //                break;
215 //            case "uri_grammar.Path":
216 //                tracef("Path '%s'", tree.matches.join);
217 //                __path = tree.matches.join;
218 //                break;
219 //            case "uri_grammar.Query":
220 //                tracef("Query '%s'", tree.matches.join);
221 //                __query = "?" ~ tree.matches.join;
222 //                break;
223 //            default:
224 //        }
225 //    }
226 //    this(string uri) {
227 //        __uri = uri;
228 //        auto parsed = uri_grammar(uri);
229 //        if ( !parsed.successful || parsed.matches.joiner.count != __uri.length) {
230 //            throw new UriException("Can't parse uri '" ~ __uri ~ "'");
231 //        }
232 //        traverseTree(parsed);
233 //    }
234 //    string recalc_uri() const pure @safe {
235 //        string userinfo;
236 //        if ( __username ) {
237 //            userinfo = "%s".format(__username);
238 //            if ( __password ) {
239 //                userinfo ~= ":" ~ __password;
240 //            }
241 //            userinfo ~= "@";
242 //        }
243 //        string r = "%s://%s%s".format(__scheme, userinfo, __host);
244 //        if ( __scheme !in standard_ports || standard_ports[__scheme] != __port ) {
245 //            r ~= ":%d".format(__port);
246 //        }
247 //        r ~= __path;
248 //        if ( __query ) {
249 //            r ~= "?" ~ __query;
250 //        }
251 //        return r;
252 //    }
253 //    mixin(getter("scheme"));
254 //    mixin(getter("host"));
255 //    mixin(getter("username"));
256 //    mixin(getter("password"));
257 //    mixin(getter("port"));
258 //    mixin(getter("path"));
259 //    mixin(getter("query"));
260 //    
261 //    mixin(setter("scheme"));
262 //    mixin(setter("host"));
263 //    mixin(setter("username"));
264 //    mixin(setter("password"));
265 //    mixin(setter("port"));
266 //    mixin(setter("path"));
267 //    mixin(setter("query"));
268 //    @property auto uri() pure @safe const {
269 //        return recalc_uri();
270 //    }
271 //    @property void uri(string s) @trusted {
272 //        __uri = s;
273 //        auto parsed = uri_grammar(s);
274 //        if ( !parsed.successful || parsed.matches.joiner.count != __uri.length) {
275 //            throw new UriException("Can't parse uri '" ~ __uri ~ "'");
276 //        }
277 //        traverseTree(parsed);
278 //    }
279 //}
280 //unittest {
281 //    import std.exception;
282 //    import std.experimental.logger;
283 //    
284 //    globalLogLevel(LogLevel.trace);
285 //    auto a = sURI("http://exampe.com/");
286 //    assert(a.scheme == "http");
287 //    a = sURI("svn+ssh://igor@example.com:1234");
288 //    assert(a.scheme == "svn+ssh");
289 //    assert(a.host == "example.com");
290 //    assert(a.username == "igor");
291 //    assert(a.path == "/");
292 //    a = sURI("http://igor:pass;word@example.com:1234/abc?q=x");
293 //    assert(a.password == "pass;word");
294 //    assert(a.port == 1234);
295 //    assert(a.path == "/abc");
296 //    assert(a.query == "?q=x");
297 //    a.scheme = "https";
298 //    a.query = "x=y";
299 //    a.port = 345;
300 //    auto expected = "https://igor:pass;word@example.com:345/abc?x=y";
301 //    assert(a.uri == expected, "Expected '%s', got '%s'".format(expected, a.uri));
302 //    assertThrown!UriException(URI("@unparsable"));
303 //}