1 module hio.http.common;
2 
3 import std.string;
4 import std.typecons: Tuple;
5 
6 import std.bitmanip;
7 import std.uni;
8 
9 import std.experimental.logger;
10 
11 import ikod.containers.hashmap: HashMap;
12 import ikod.containers.unrolledlist: UnrolledList;
13 import nbuff: SmartPtr, Nbuff, NbuffChunk, smart_ptr;
14 
15 import hio.http.http_parser;
16 
17 package enum SchCode
18 {
19     HTTP = 0,
20     HTTPS = 1,
21     UNKNOWN
22 }
23 
24 struct URL
25 {
26     private
27     {
28         SchCode _schemaCode = SchCode.UNKNOWN;
29         string _url;
30         string _schema;
31         string _host;
32         ushort _port;
33         string _path;
34         string _query;
35         string _fragment;
36         string _userinfo;
37         long   _path_off = -1;
38     }
39     auto url() pure inout @nogc
40     {
41         return _url;
42     }
43     auto schema() pure inout @nogc
44     {
45         return _schema;
46     }
47     auto host() pure inout @nogc
48     {
49         return _host;
50     }
51     auto port() pure inout @nogc
52     {
53         return _port;
54     }
55     auto path() pure inout @nogc
56     {
57         return _path;
58     }
59     long path_off() pure inout @safe @nogc
60     {
61         return _path_off;
62     }
63     auto query() pure inout @nogc
64     {
65         return _query;
66     }
67     auto fragment() pure inout @nogc
68     {
69         return _fragment;
70     }
71     auto userinfo() pure inout @nogc
72     {
73         return _userinfo;
74     }
75     auto schemaCode() pure inout @nogc
76     {
77         return _schemaCode;
78     }
79     long pstrlen() @safe @nogc nothrow
80     {
81         // build path/query/frag lengh
82         long l = 0;
83         if ( _path.length == 0)
84         {
85             l += 1;
86         }
87         else
88         {
89             l += _path.length;
90         }
91         if ( _query.length > 0 )
92         {
93             l += 1 + _query.length;
94         }
95         if ( _fragment.length > 0)
96         {
97             l += 1 + _fragment.length;
98         }
99         return l;
100     }
101 }
102 
103 URL parse_url(string url) @safe
104 {
105     URL                     result;
106     static http_parser_url  parser;
107 
108     result._url = url;
109     http_parser_url_init(&parser);
110     auto l = http_parser_parse_url(&url[0], url.length, 0, &parser);
111     if ( l )
112     {
113         return result;
114     }
115     if ( (1<<http_parser_url_fields.UF_PORT) & parser.field_set )
116     {
117         result._port = parser.port;
118     }
119     if ( (1<<http_parser_url_fields.UF_SCHEMA) & parser.field_set )
120     {
121         auto off = parser.field_data[http_parser_url_fields.UF_SCHEMA].off;
122         auto len = parser.field_data[http_parser_url_fields.UF_SCHEMA].len;
123         if ( off>=0 && len>=0 && off+len <= url.length )
124         {
125             result._schema = url[off..off+len];
126             if (result._schema.toLower == "http")
127             {
128                 result._schemaCode = SchCode.HTTP;
129                 if ( !result._port )
130                 {
131                     result._port = 80;
132                 }
133             }
134             else if (result._schema.toLower == "https")
135             {
136                 result._schemaCode = SchCode.HTTPS;
137                 if ( !result._port )
138                 {
139                     result._port = 443;
140                 }
141             }
142         }
143     }
144     if ( (1<<http_parser_url_fields.UF_HOST) & parser.field_set )
145     {
146         auto off = parser.field_data[http_parser_url_fields.UF_HOST].off;
147         auto len = parser.field_data[http_parser_url_fields.UF_HOST].len;
148         if ( off>=0 && len>=0 && off+len <= url.length )
149         {
150             result._host = url[off..off+len];
151         }
152     }
153     if ( (1<<http_parser_url_fields.UF_PATH) & parser.field_set )
154     {
155         auto off = parser.field_data[http_parser_url_fields.UF_PATH].off;
156         auto len = parser.field_data[http_parser_url_fields.UF_PATH].len;
157         if ( off>=0 && len>=0 && off+len <= url.length )
158         {
159             result._path = url[off..off+len];
160         }
161         result._path_off = off;
162     }
163     if ( (1<<http_parser_url_fields.UF_QUERY) & parser.field_set )
164     {
165         auto off = parser.field_data[http_parser_url_fields.UF_QUERY].off;
166         auto len = parser.field_data[http_parser_url_fields.UF_QUERY].len;
167         if ( off>=0 && len>=0 && off+len <= url.length )
168         {
169             result._query = url[off..off+len];
170         }
171     }
172     if ( (1<<http_parser_url_fields.UF_FRAGMENT) & parser.field_set )
173     {
174         auto off = parser.field_data[http_parser_url_fields.UF_FRAGMENT].off;
175         auto len = parser.field_data[http_parser_url_fields.UF_FRAGMENT].len;
176         if ( off>=0 && len>=0 && off+len <= url.length )
177         {
178             result._fragment = url[off..off+len];
179         }
180     }
181     if ( (1<<http_parser_url_fields.UF_USERINFO) & parser.field_set )
182     {
183         auto off = parser.field_data[http_parser_url_fields.UF_USERINFO].off;
184         auto len = parser.field_data[http_parser_url_fields.UF_USERINFO].len;
185         if ( off>=0 && len>=0 && off+len <= url.length )
186         {
187             result._userinfo = url[off..off+len];
188         }
189     }
190     debug(hiohttp) tracef("url_parser: %s", parser);
191     return result;
192 }
193 
194 unittest
195 {
196     URL u = parse_url("http://example.com/");
197     assert(u._schema == "http");
198     assert(u._host == "example.com");
199     assert(u._path == "/");
200     assert(u.port == 80);
201     u =parse_url("https://a:b@example.com/path?query#frag");
202     assert(u._schema == "https");
203     assert(u._host == "example.com");
204     assert(u._path == "/path");
205     assert(u._query == "query");
206     assert(u._fragment == "frag");
207     assert(u._userinfo == "a:b");
208     assert(u.port == 443);
209 }
210 
211 struct Header
212 {
213     string  FieldName;
214     string  FieldValue;
215 }
216 
217 struct Method
218 {
219     private string _name;
220     string  name() @safe @nogc nothrow
221     {
222         return _name;
223     }
224 }
225 
226 struct _UH {
227     // flags for each important header, added by user using addHeaders
228     mixin(bitfields!(
229     bool, "Host", 1,
230     bool, "UserAgent", 1,
231     bool, "ContentLength", 1,
232     bool, "Connection", 1,
233     bool, "AcceptEncoding", 1,
234     bool, "ContentType", 1,
235     bool, "Cookie", 1,
236     uint, "", 1
237     ));
238 }
239 alias ErrorType = Tuple!(int, "code", string, "msg");
240 enum AsyncHTTPErrors
241 {
242     None    = ErrorType(0, "OK"),
243     Timeout = ErrorType(2, "Timedout"),
244     ResolveFailed
245             = ErrorType(3, "Can't resolve"),
246     ConnFailed
247             = ErrorType(4, "Connection failed"),
248     DataError
249             = ErrorType(5, "Error when receiving data"),
250     MaxRedirectsReached
251             = ErrorType(6, "Max num of redirects reached"),
252 }
253 
254 package struct Request
255 {
256     private
257     {
258         Method                      _method;
259         HashMap!(string, Header)    _user_headers;
260         _UH                         _user_headers_flags;
261         Nbuff                       _body;
262     }
263     ~this()
264     {
265         clear();
266     }
267     this(Method m)
268     {
269         _method = m;
270     }
271     void method(string m) @safe @nogc nothrow
272     {
273         _method = Method(m);
274     }
275     void method(Method m) @safe @nogc nothrow
276     {
277         _method = m;
278     }
279     string method() @safe @nogc nothrow
280     {
281         return _method.name;
282     }
283     ///
284     void addHeader(Header h) @safe
285     {
286         // save header as user supplied
287         // use lower-case'd field as key in hashmap
288         // pro: fast replace, send header as user supplied
289         // contra: - extra `toLower` call
290         string field = h.FieldName.toLower();
291         switch(field)
292         {
293             case "host":
294                 _user_headers_flags.Host = true;
295                 break;
296             case "useragent":
297                 _user_headers_flags.UserAgent = true;
298                 break;
299             case "accept-encoding":
300                 _user_headers_flags.AcceptEncoding = true;
301                 break;
302             case "content-length":
303                 _user_headers_flags.ContentLength = true;
304                 break;
305             case "content-type":
306                 _user_headers_flags.ContentType = true;
307                 break;
308             default:
309                 break;
310         }
311         _user_headers.put(field, h);
312     }
313     auto user_headers() @safe @nogc nothrow
314     {
315         return _user_headers.byValue;
316     }
317     auto user_headers_flags() @safe @nogc nothrow
318     {
319         return _user_headers_flags;
320     }
321     void clear()
322     {
323         _user_headers.clear;
324     }
325 }
326 // alias Request = SmartPtr!_Request;
327 package struct MessageHeader
328 {
329     NbuffChunk  field;
330     NbuffChunk  value;
331 }
332 
333 alias MessageHeaders = UnrolledList!MessageHeader;
334 
335 ushort standard_port(string schema) @safe
336 {
337     switch(schema)
338     {
339         case "http":
340             return 80;
341         case "https":
342             return 443;
343         default:
344             return 0;
345     }
346 }