1 module hunt.http.server.HttpServerRequest;
2 
3 import hunt.http.server.GlobalSettings;
4 import hunt.http.server.Http1ServerConnection;
5 import hunt.http.server.HttpRequestOptions;
6 import hunt.http.server.HttpServerOptions;
7 import hunt.http.server.HttpServerResponse;
8 import hunt.http.server.ServerHttpHandler;
9 
10 import hunt.http.codec.http.decode.HttpParser;
11 import hunt.http.codec.http.decode.MultipartFormParser;
12 import hunt.http.codec.http.model;
13 
14 import hunt.http.Cookie;
15 import hunt.http.HttpFields;
16 import hunt.http.HttpHeader;
17 import hunt.http.HttpMethod;
18 import hunt.http.HttpRequest;
19 import hunt.http.HttpVersion;
20 import hunt.http.MultipartForm;
21 
22 import hunt.collection;
23 import hunt.concurrency.atomic;
24 import hunt.Exceptions;
25 import hunt.Functions;
26 import hunt.io.BufferUtils;
27 import hunt.stream.PipedStream;
28 import hunt.stream.ByteArrayInputStream;
29 import hunt.stream.Common;
30 import hunt.logging.ConsoleLogger;
31 import hunt.net.util.HttpURI;
32 import hunt.net.util.UrlEncoded;
33 import hunt.text.Common;
34 import hunt.util.Common;
35 import hunt.util.MimeTypeUtils;
36 
37 import std.algorithm;
38 import std.array;
39 import std.conv;
40 import std.container.array;
41 import std.json;
42 import std.string : icmp;
43 import std.range;
44 
45 
46 /**
47  * 
48  */
49 class HttpServerRequest : HttpRequest {
50     
51     private string _stringBody; 
52     private string _mimeType;
53     private HttpServerOptions _options;
54 
55     private Cookie[] _cookies;
56     private PipedStream _pipedStream;
57     private MultipartFormParser _multipartFormParser;
58     private UrlEncoded _urlEncodedMap;
59 
60     private Action1!HttpServerRequest _messageCompleteHandler;
61     // private Action1!ByteBuffer _contentHandler;
62     private Action1!HttpServerRequest _contentCompleteHandler;  
63 
64     this(string method, string uri, HttpVersion ver, HttpServerOptions options) {
65         enum string connect = HttpMethod.CONNECT.asString();
66         _options = options;
67 
68         super(method, new HttpURI(icmp(method, connect) == 0 ? "http://" ~ uri : uri), 
69             ver, new HttpFields());   
70         // _originalPath = uri;  
71         _originalPath = getURI().getPath();
72         // super(method, new HttpURI(HttpMethod.fromString(method) == HttpMethod.CONNECT ? "http://" ~ uri : uri), ver, new HttpFields());
73     }
74 
75     @property string host() {
76         return header(HttpHeader.HOST);
77     }
78     
79     string getParameter(string name) {
80         decodeUrl();
81         return _urlEncodedMap.getString(name);
82     }
83 
84     List!(string) getParameterValues(string name) {
85         decodeUrl();
86         return _urlEncodedMap.getValues(name);
87     }
88 
89     Map!(string, List!(string)) getParameterMap() {
90         decodeUrl();
91         return _urlEncodedMap;
92     }
93 
94     string getMimeType() {
95         if(_mimeType is null) {
96             _mimeType = MimeTypeUtils.getContentTypeMIMEType(getContentType());
97         }
98 
99         return _mimeType;
100     }
101     
102     private void decodeUrl() {
103         if(_urlEncodedMap !is null)
104             return;
105 
106         _urlEncodedMap = new UrlEncoded();
107 
108         string queryString = getURI().getQuery();
109         if (!queryString.empty()) {
110             _urlEncodedMap.decode(queryString);
111         }
112 
113         if ("application/x-www-form-urlencoded".equalsIgnoreCase(getMimeType())) {
114             string bodyString = getStringBody(_options.requestOptions.getCharset());
115             _urlEncodedMap.decode(bodyString);
116         }
117     }
118 
119     // void setPipedStream(PipedStream stream) {
120     //     this._pipedStream = stream;
121     // }
122 
123     // void closeOutputStream() {
124     //     if(this._pipedStream !is null)
125     //         this._pipedStream.getOutputStream().close();
126     // }
127 
128     private OutputStream getOutputStream() {
129         return this._pipedStream.getOutputStream();
130     }
131 
132     string getStringBody(string charset) {
133         if (_stringBody !is null) {
134             return _stringBody;
135         } else {
136             if (_pipedStream is null) // no content
137                 return null;
138 
139             InputStream inputStream = this._pipedStream.getInputStream();
140             scope(exit) 
141                 inputStream.close();
142 
143             try  {
144                 // inputStream.position(0); // The InputStream may be read, so reset it before reading it again.
145                 int size = inputStream.available();
146                 version(HUNT_HTTP_DEBUG) tracef("available: %d", size);
147                 // TODO: Tasks pending completion -@zhangxueping at 2019-10-23T15:40:56+08:00
148                 // encoding convertion: to UTF-8
149                 
150                 // skip the read for better performance
151                 byte[] buffer;
152                 ByteArrayInputStream arrayStream = cast(ByteArrayInputStream)inputStream;
153                 if(arrayStream is null) {
154                     buffer = new byte[size];
155                     int number = inputStream.read(buffer);
156                     if(number == -1) {
157                         version(HUNT_DEBUG) warning("no data read");
158                     }
159                 } else {
160                     buffer = arrayStream.getRawBuffer();
161                 }
162                 _stringBody = cast(string)buffer;
163                 return _stringBody;
164             } catch (IOException e) {
165                 version(HUNT_DEBUG) warning("get string body exception: ", e.msg);
166                 version(HUNT_HTTP_DEBUG) warning(e);
167                 return null;
168             }
169         }
170     }
171 
172     string getStringBody() {
173         return getStringBody("UTF-8");
174     }
175 
176     package(hunt.http) void onHeaderComplete() {
177         // _options = options;
178         auto options = _options.requestOptions;
179         if (isChunked()) {
180             this._pipedStream = new ByteArrayPipedStream(4 * 1024);
181         } else {
182             long contentLength = getContentLength();
183            
184             if (contentLength > options.getBodyBufferThreshold()) {
185                 this._pipedStream = new FilePipedStream(options.getTempFilePath());
186             } else if(contentLength > 0) {
187                 this._pipedStream = new ByteArrayPipedStream(cast(int) contentLength);
188             }
189         }
190     }
191 
192     private shared long chunkedEncodingContentLength;
193 
194     package(hunt.http) void onContent(ByteBuffer buffer) {
195         version(HUNT_HTTP_DEBUG) {
196             tracef("received content size -> %s bytes", buffer.remaining());
197         }
198 
199         try {
200             OutputStream outStream = getOutputStream();
201             if (isChunked()) {
202                 auto options = _options.requestOptions;
203                 long length = AtomicHelper.increment(chunkedEncodingContentLength, buffer.remaining());
204                 ByteArrayPipedStream bytesStream = cast(ByteArrayPipedStream)_pipedStream;
205 
206                 // switch PipedStream from ByteArrayPipedStream to FilePipedStream.
207                 if (length > options.getBodyBufferThreshold() && bytesStream !is null) {
208                     // FIXME: Needing refactor or cleanup -@zhangxueping at 2019-10-17T23:05:10+08:00
209                     // better performance.
210                     
211                     version(HUNT_HTTP_DEBUG) {
212                         warningf("dump to temp file, ContentLength:%d, BufferThreshold: %d, file: %s", 
213                             length,
214                             options.getBodyBufferThreshold(), 
215                             options.getTempFilePath());
216                     }
217 
218                     // chunked encoding content dump to temp file
219                     _pipedStream.getOutputStream().close();
220                     FilePipedStream filePipedStream = new FilePipedStream(options.getTempFilePath());
221                     byte[] bufferedBytes = bytesStream.getOutputStream().toByteArray(false);
222                     OutputStream fileOutStream = filePipedStream.getOutputStream();
223                     fileOutStream.write(bufferedBytes);
224                     fileOutStream.write(BufferUtils.toArray(buffer, false));
225                     _pipedStream = filePipedStream;
226                 } else {
227                     outStream.write(BufferUtils.toArray(buffer, false));
228                 }
229             } else {
230                 outStream.write(BufferUtils.toArray(buffer, false));
231             }
232         } catch (IOException e) {
233             version(HUNT_DEBUG) warning("http server receives http body exception: ", e.msg);
234             version(HUNT_HTTP_DEBUG) warning(e);
235         }        
236     }
237 
238     package(hunt.http) void onContentComplete() {
239         if(_pipedStream is null || (getContentLength() <=0 && !isChunked()) )
240             return;
241             
242         try {
243             string mimeType = getMimeType();
244             version(HUNT_HTTP_DEBUG) trace("mimeType: ", mimeType);
245             getOutputStream().close();
246 
247             if ("multipart/form-data".equalsIgnoreCase(mimeType)) {
248                 _multipartFormParser = new MultipartFormParser(
249                         _pipedStream.getInputStream(),
250                         getContentType(),
251                         GlobalSettings.getMultipartOptions(_options.requestOptions),
252                         _options.requestOptions.getTempFilePath());
253             } else if("application/x-www-form-urlencoded".equalsIgnoreCase(mimeType)) {
254                 _isXFormUrlencoded = true;
255             }
256 
257             if(_contentCompleteHandler !is null) {
258                 _contentCompleteHandler(this);
259             }
260         } catch (IOException e) {
261             version(HUNT_DEBUG) warning("http server ends receiving data exception: ", e.msg);
262             version(HUNT_HTTP_DEBUG) warning(e);
263         }
264     } 
265 
266     package(hunt.http) void onMessageComplete() {
267         if(_messageCompleteHandler !is null)
268             _messageCompleteHandler(this);
269     }
270 
271     // HttpRequest onContent(Action1!ByteBuffer handler) {
272     //     _contentHandler = handler;
273     //     return this;
274     // }
275 
276     HttpRequest onContentComplete(Action1!HttpServerRequest handler) {
277         _contentCompleteHandler = handler;
278         return this;
279     }
280 
281     HttpServerRequest onMessageComplete(Action1!HttpServerRequest handler) {
282         _messageCompleteHandler = handler;
283         return this;
284     }
285 
286     
287     HttpServerRequest onBadMessage(Action1!HttpServerRequest handler) {
288         _messageCompleteHandler = handler;
289         return this;
290     }
291     
292 
293     Cookie[] getCookies() {        
294         if (_cookies is null) {
295 
296             Array!(Cookie) list;
297             foreach (string v; getFields().getValuesList(HttpHeader.COOKIE)) {
298                 if (v.empty)
299                     continue;
300                 foreach (Cookie c; parseCookie(v))
301                     list.insertBack(c);
302             }
303             _cookies = list.array();
304             // _cookies = getFields().getValuesList(HttpHeader.COOKIE)
305 			// 		.map!(parseSetCookie).array;
306         }
307 		
308 		return _cookies;
309     }
310 
311     bool isMultipartForm() {
312         return _multipartFormParser !is null;
313     }
314 
315     bool isXFormUrlencoded() {
316         return _isXFormUrlencoded;
317     }
318 
319     Part[] getParts() {
320         if (_multipartFormParser is null) {
321             throw new RuntimeException("The reqest is not a multipart form.");
322             // return null;
323         }
324 
325         // try {
326             return _multipartFormParser.getParts();
327         // } catch (IOException e) {
328         //     version(HUNT_DEBUG) warning("get multi part exception: ", e.msg);
329         //     version(HUNT_HTTP_DEBUG) warning(e);
330         //     return null;
331         // }
332     }
333 
334     Part getPart(string name) {
335         if (_multipartFormParser is null) {
336             throw new RuntimeException("The reqest is not a multipart form.");
337             // return null;
338         }
339 
340         // try {
341             return _multipartFormParser.getPart(name);
342         // } catch (IOException e) {
343         //     version(HUNT_DEBUG) warning("get multi part exception: ", e.msg);
344         //     version(HUNT_HTTP_DEBUG) warning(e);
345         //     return null;
346         // }
347     } 
348     
349 
350 // dfmtoff Some Helpers
351 
352     import std.string;
353 
354     @property string originalPath() {
355         return _originalPath;
356     }
357     private string _originalPath;
358 
359     @property string path() {
360         return getURI().getPath();
361     }
362 
363     @property void path(string value) {
364         getURI().setPath(value);
365     }
366 
367     @property string decodedPath() {
368         return getURI().getDecodedPath();
369     }
370 
371     string opDispatch(string key)() {
372         version(HUNT_HTTP_DEBUG) {
373             tracef("Get the query parameter: %s", key);
374         }
375         return query(key);
376     }
377 
378     /**
379      * Retrieve a query string item from the request.
380      *
381      * @param  string  key
382      * @param  string|array|null  default
383      * @return string|array
384      */
385     string query(string key, string defaults = null) {
386         return queries().get(key, defaults);
387     }
388 
389     /// get a query
390     T get(T = string)(string key, T v = T.init) {
391         version(HUNT_HTTP_DEBUG) {
392             tracef("key: %s, value: %s", key, v);
393         }
394 
395         auto tmp = queries();
396         if (tmp is null) {
397             return v;
398         }
399         
400         static if(is(T == string)) {
401             if(v is null) v = "";
402         }
403 
404         auto _v = tmp.get(key, "");
405         version(HUNT_HTTP_DEBUG) {
406             tracef("key: %s, value: %s, target: %s, default: %s", key, _v, T.stringof, v);
407         }
408 
409         if (_v.length) {
410             try {
411                 return to!T(_v);
412             } catch(Exception ex) {
413                 string msg = format("Failed converting from <%s> to %s", _v, T.stringof);
414                 // warning(msg);
415                 throw new Exception(msg);
416             }
417         }
418         
419         return v;
420     }
421 
422     /**
423      * Retrieve a request payload item from the request.
424      *
425      * @param  string  key
426      * @param  string|array|null  default
427      *
428      * @return string|array
429      */
430     T post(T = string)(string key, T v = T.init) {
431         string[][string] form = xFormData();
432         if (form is null)
433             return v;
434         if(key in form) {
435             string[] _v = form[key];
436             if (_v.length > 0) {
437                 static if(is(T == string))
438                     v = _v[0];
439                 else {
440                     v = to!T(_v[0]);
441                 }
442             } 
443         } 
444 
445         return v;
446     }
447 
448     T[] posts(T = string)(string key, T[] v = null) {
449         string[][string] form = xFormData();
450         if (form is null)
451             return v;
452             
453         if(key in form) {
454             string[] _v = form[key];
455             if (_v.length > 0) {
456                 static if(is(T == string))
457                     v = _v[];
458                 else {
459                     v = new T[_v.length];
460                     for(size i =0; i<v.length; i++) {
461                         v[i] = to!T(_v[i]);
462                     }
463                 }
464             } 
465         } 
466 
467         return v;
468     }
469 
470     // get queries
471     @property ref string[string] queries() {
472         if (!_isQueryParamsSet) {
473             MultiMap!string map = new MultiMap!string();
474             getURI().decodeQueryTo(map);
475             foreach (string key, List!(string) values; map) {
476                 version(HUNT_DEBUG) {
477                     infof("query parameter: key=%s, values=%s", key, values[0]);
478                 }
479                 if(values is null || values.size()<1) {
480                     _queryParams[key] = ""; 
481                 } else {
482                     _queryParams[key] = values[0];
483                 }
484             }
485             _isQueryParamsSet = true;
486         }
487         return _queryParams;
488     }
489 
490     void putQueryParameter(string key, string value) {
491         version(HUNT_HTTP_DEBUG) infof("query parameter: key=%s, values=%s", key, value);
492         _queryParams[key] = value;
493     }
494 
495     /**
496      * Replace the input for the current request.
497      *
498      * @param  array input
499      * @return Request
500      */
501     void replace(string[string] input) {
502         if (isContained(this.getMethod(), ["GET", "HEAD"]))
503             _queryParams = input;
504         else {
505             foreach(string k, string v; input) {
506                 _xFormData[k] ~= v;
507             }
508         }
509     }
510 
511     private static bool isContained(string source, string[] keys) {
512         foreach (string k; keys) {
513             if (canFind(source, k))
514                 return true;
515         }
516         return false;
517     }
518     
519     T bindForm(T)() if(is(T == class) || is(T == struct)) {
520 
521         if(getMethod() != "POST")
522             return T.init;
523         import hunt.serialization.JsonSerializer;
524         // import hunt.util.Serialize;
525 
526         JSONValue jv;
527         string[][string] forms = xFormData();
528         if(forms is null) {
529             static if(is(T == class)) {
530                 return new T();
531             } else {
532                 return T.init;
533             }
534         }
535 
536         foreach(string k, string[] values; forms) {
537             if(values.length > 1) {
538                 jv[k] = JSONValue(values);
539             } else if(values.length == 1) {
540                 jv[k] = JSONValue(values[0]);
541             } else {
542                 warningf("null value for %s in form data: ", k);
543             }
544         }
545         
546         import hunt.serialization.Common;
547         return JsonSerializer.toObject!(T, SerializationOptions.Default.canThrow(false))(jv);
548     }
549 
550     @property string[][string] xFormData() {
551         if (_xFormData is null && _isXFormUrlencoded) {
552             UrlEncoded map = new UrlEncoded();
553             map.decode(getStringBody());
554             foreach (string key; map.byKey()) {
555                 foreach(string v; map.getValues(key)) {
556                     key = key.strip();
557                     _xFormData[key] ~= v.strip();
558                 }
559             }
560         }
561         return _xFormData;
562     }
563 
564     /**
565      * Retrieve a cookie from the request.
566      *
567      * @param  string  key
568      * @param  string|array|null  default
569      * @return string|array
570      */
571     string cookie(string key, string defaultValue = null) {
572         // return cookieManager.get(key, defaultValue);
573         foreach (Cookie c; getCookies()) {
574             if (c.getName == key)
575                 return c.getValue();
576         }
577         return defaultValue;
578     }
579 
580     /**
581      * Retrieve  users' own preferred language.
582      */
583     string locale() {
584         string l;
585         l = cookie("Content-Language");
586         if(l is null)
587             l = _options.requestOptions.defaultLanguage();
588 
589         return toLower(l);
590     }
591     
592     private string[][string] _xFormData;
593     private string[string] _queryParams;
594     
595     private bool _isMultipart = false;
596     private bool _isXFormUrlencoded = false;
597     private bool _isQueryParamsSet = false;
598 
599 // dfmton
600 
601 
602 }