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