1 /*
2  * Collie - An asynchronous event-driven network framework using Dlang development
3  *
4  * Copyright (C) 2015-2017  Shanghai Putao Technology Co., Ltd 
5  *
6  * Developer: putao's Dlang team
7  *
8  * Licensed under the Apache-2.0 License.
9  *
10  */
11 module collie.codec.http.httpmessage;
12 
13 import collie.codec.http.headers;
14 import collie.codec.http.exception;
15 
16 import std.typecons;
17 import std.typetuple;
18 import std.socket;
19 import std.variant;
20 import std.conv;
21 import std.exception;
22 import std..string;
23 public 
24 
25 final class HTTPMessage
26 {
27 	this()
28 	{
29 		_version[0] = 1;
30 		_version[1] = 1;
31 	}
32 
33 	/* Setter and getter for the SPDY priority value (0 - 7).  When serialized
34    * to SPDY/2, Codecs will collpase 0,1 -> 0, 2,3 -> 1, etc.
35    *
36    * Negative values of pri are interpreted much like negative array
37    * indexes in python, so -1 will be the largest numerical priority
38    * value for this SPDY version (i.e. 3 for SPDY/2 or 7 for SPDY/3),
39    * -2 the second largest (i.e. 2 for SPDY/2 or 6 for SPDY/3).
40    */
41 	enum byte kMaxPriority = 7;
42 	
43 //	static byte normalizePriority(byte pri) {
44 //		if (pri > kMaxPriority || pri < -kMaxPriority) {
45 //			// outside [-7, 7] => highest priority
46 //			return kMaxPriority;
47 //		} else if (pri < 0) {
48 //			return pri + kMaxPriority + 1;
49 //		}
50 //		return pri;
51 //	}
52 
53 	/**
54    * Is this a chunked message? (fpreq, fpresp)
55    */
56 	@property void chunked(bool chunked) { _chunked = chunked; }
57 	@property bool chunked() const { return _chunked; }
58 
59 	/**
60    * Is this an upgraded message? (fpreq, fpresp)
61    */
62 	@property void upgraded(bool upgraded) { _upgraded = upgraded; }
63 	@property bool upgraded() const { return _upgraded; }
64 
65 	/**
66    * Set/Get client address
67    */
68 	@property void clientAddress(Address addr) {
69 		request()._clientAddress = addr;
70 		request()._clientIP = addr.toAddrString();
71 		request()._clientPort = addr.toPortString;
72 	}
73 	
74 	@property Address clientAddress() {
75 		return request()._clientAddress;
76 	}
77 
78 	string getClientIP()  {
79 		return request()._clientIP;
80 	}
81 	
82 	string getClientPort()  {
83 		return request()._clientPort;
84 	}
85 
86 	/**
87    * Set/Get destination (vip) address
88    */
89 	@property void dstAddress(Address addr) {
90 		_dstAddress = addr;
91 		_dstIP = addr.toAddrString;
92 		_dstPort = addr.toPortString;
93 	}
94 	
95 	@property Address dstAddress()  {
96 		return _dstAddress;
97 	}
98 
99 	string getDstIP()  {
100 		return _dstIP;
101 	}
102 	
103 	string getDstPort()  {
104 		return _dstPort;
105 	}
106 	
107 	/**
108    * Set/Get the local IP address
109    */
110 	@property void localIp(string ip) {
111 		_localIP = ip;
112 	}
113 	@property string localIp()  {
114 		return _localIP;
115 	}
116 
117 	@property void method(HTTPMethod method)
118 	{
119 		request()._method = method;
120 	}
121 
122 	@property HTTPMethod method()
123 	{
124 		return request()._method;
125 	}
126 	//void setMethod(folly::StringPiece method);
127 
128 	string methodString(){
129 		return method_strings[request()._method];
130 	}
131 
132 	void setHTTPVersion(ubyte maj, ubyte min)
133 	{
134 		_version[0] = maj;
135 		_version[1] = min;
136 	}
137 
138 	auto getHTTPVersion()
139 	{
140 		Tuple!(ubyte, "maj", ubyte, "min") tv;
141 		tv.maj = _version[0];
142 		tv.min = _version[1];
143 		return tv;
144 	}
145 
146 	@property void url(string url){ 
147 		auto idx = url.indexOf('?');
148 		if (idx > 0){
149 			request()._path = url[0..idx];
150 			request()._query = url[idx+1..$];
151 		} else {
152 			request()._path = url;
153 		}
154 		request()._url = url;
155 	}
156 
157 	@property string url(){return request()._url;}
158 
159 
160 	@property wantsKeepAlive(){return _wantsKeepalive;}
161 	@property wantsKeepAlive(bool klive){_wantsKeepalive = klive;}
162 	/**
163    * Access the path component (fpreq)
164    */
165 	string getPath()
166 	{
167 		return request()._path;
168 	}
169 	
170 	/**
171    * Access the query component (fpreq)
172    */
173 	string getQueryString()
174 	{
175 		return request()._query;
176 	}
177 
178 	@property void statusMessage(string msg) {
179 		response()._statusMsg = msg;
180 	}
181 	@property string statusMessage()
182 	{
183 		return response()._statusMsg;
184 	}
185 
186 	/**
187    * Access the status code (fpres)
188    */
189 	@property void statusCode(ushort status)
190 	{
191 		response()._status = status;
192 	}
193 
194 	@property ushort statusCode()
195 	{
196 		return response()._status;
197 	}
198 
199 	@property string host()
200 	{
201 		return _headers.getSingleOrEmpty(HTTPHeaderCode.HOST);
202 	}
203 
204 	/**
205    * Access the headers (fpreq, fpres)
206    */
207 	ref HTTPHeaders getHeaders(){ return _headers; }
208 
209 	/**
210    * Decrements Max-Forwards header, when present on OPTIONS or logDebug methods.
211    *
212    * Returns HTTP status code.
213    */
214 	int processMaxForwards()
215 	{
216 		auto m = method();
217 		if (m == HTTPMethod.HTTP_logDebug || m  == HTTPMethod.HTTP_OPTIONS) {
218 			string value = _headers.getSingleOrEmpty(HTTPHeaderCode.MAX_FORWARDS);
219 			if (value.length > 0) {
220 				long max_forwards = -1;
221 
222 				collectException(to!long(value),max_forwards);
223 
224 				if (max_forwards < 0) {
225 					return 400;
226 				} else if (max_forwards == 0) {
227 					return 501;
228 				} else {
229 					_headers.set(HTTPHeaderCode.MAX_FORWARDS,to!string(max_forwards - 1));
230 				}
231 			}
232 		}
233 		return 0;
234 	}
235 	
236 	/**
237    * Returns true if the version of this message is HTTP/1.0
238    */
239 	bool isHTTP1_0() const
240 	{
241 		return _version[0] == 1 && _version[1] == 0;
242 	}
243 	
244 	/**
245    * Returns true if the version of this message is HTTP/1.1
246    */
247 	bool isHTTP1_1() const
248 	{
249 		return _version[0] == 1 && _version[1] == 1;
250 	}
251 
252 	/**
253    * Returns true if this is a 1xx response.
254    */
255 	bool is1xxResponse(){ return (statusCode() / 100) == 1; }
256 
257 	/**
258    * Fill in the fields for a response message header that the server will
259    * send directly to the client.
260    *
261    * @param version           HTTP version (major, minor)
262    * @param statusCode        HTTP status code to respond with
263    * @param msg               textual message to embed in "message" status field
264    * @param contentLength     the length of the data to be written out through
265    *                          this message
266    */
267 	void constructDirectResponse(ubyte maj,ubyte min,const int statucode,string statusMsg,int contentLength = 0)
268 	{
269 		statusCode(cast(ushort)statucode);
270 		statusMessage(statusMsg);
271 		constructDirectResponse(maj,min, contentLength);
272 	}
273 	
274 	/**
275    * Fill in the fields for a response message header that the server will
276    * send directly to the client. This function assumes the status code and
277    * status message have already been set on this HTTPMessage object
278    *
279    * @param version           HTTP version (major, minor)
280    * @param contentLength     the length of the data to be written out through
281    *                          this message
282    */
283 	void constructDirectResponse(ubyte maj,ubyte min,int contentLength = 0)
284 	{
285 		setHTTPVersion(maj,min);
286 		_headers.set(HTTPHeaderCode.CONTENT_LENGTH,to!string(contentLength));
287 		if(!_headers.exists(HTTPHeaderCode.CONTENT_TYPE)){
288 			_headers.add(HTTPHeaderCode.CONTENT_TYPE, "text/plain");
289 		}
290 		chunked(false);
291 		upgraded(false);
292 	}
293 
294 	/**
295    * Check if query parameter with the specified name exists.
296    */
297 	bool hasQueryParam(string name) 
298 	{
299 		parseQueryParams();
300 		return _queryParams.get(name,string.init) != string.init;
301 	}
302 	/**
303    * Get the query parameter with the specified name.
304    *
305    * Returns a reference to the query parameter value, or
306    * proxygen::empty_string if there is no parameter with the
307    * specified name.  The returned value is only valid as long as this
308    * HTTPMessage object.
309    */
310 	string getQueryParam(string name)
311 	{
312 		parseQueryParams();
313 		return _queryParams.get(name,string.init);
314 	}
315 	/**
316    * Get the query parameter with the specified name after percent decoding.
317    *
318    * Returns empty string if parameter is missing or folly::uriUnescape
319    * query param
320    */
321 	string getDecodedQueryParam(string name)
322 	{
323 		import std.uri;
324 		parseQueryParams();
325 		string v = _queryParams.get(name,string.init);
326 		if(v == string.init)
327 			return v;
328 		return decodeComponent(v);
329 	}
330 
331 	/**
332    * Get the query parameter with the specified name after percent decoding.
333    *
334    * Returns empty string if parameter is missing or folly::uriUnescape
335    * query param
336    */
337 	string[string] queryParam(){parseQueryParams();return _queryParams;}
338 
339 	/**
340    * Set the query string to the specified value, and recreate the url_.
341    *
342    */
343 	void setQueryString(string query)
344 	{
345 		unparseQueryParams();
346 		request._query = query;
347 	}
348 	/**
349    * Remove the query parameter with the specified name.
350    *
351    */
352 	void removeQueryParam(string name)
353 	{
354 		parseQueryParams();
355 		_queryParams.remove(name);
356 	}
357 	
358 	/**
359    * Sets the query parameter with the specified name to the specified value.
360    *
361    * Returns true if the query parameter was successfully set.
362    */
363 	void setQueryParam(string name, string value)
364 	{
365 		parseQueryParams();
366 		_queryParams[name] = value;
367 	}
368 
369 
370 	/**
371    * @returns true if this HTTPMessage represents an HTTP request
372    */
373 	bool isRequest() const {
374 		return _isRequest == MegType.Request_;
375 	}
376 	
377 	/**
378    * @returns true if this HTTPMessage represents an HTTP response
379    */
380 	bool isResponse() const {
381 		return _isRequest == MegType.Response_;
382 	}
383 
384 	static string statusText(int code)
385 	{
386 		switch (code)
387 		{
388 			case 100:
389 				return "Continue";
390 			case 101:
391 				return "Switching Protocols";
392 			case 102:
393 				return "Processing"; // RFC2518
394 			case 200:
395 				return "OK";
396 			case 201:
397 				return "Created";
398 			case 202:
399 				return "Accepted";
400 			case 203:
401 				return "Non-Authoritative logInformation";
402 			case 204:
403 				return "No Content";
404 			case 205:
405 				return "Reset Content";
406 			case 206:
407 				return "Partial Content";
408 			case 207:
409 				return "Multi-Status"; // RFC4918
410 			case 208:
411 				return "Already Reported"; // RFC5842
412 			case 226:
413 				return "IM Used"; // RFC3229
414 			case 300:
415 				return "Multiple Choices";
416 			case 301:
417 				return "Moved Permanently";
418 			case 302:
419 				return "Found";
420 			case 303:
421 				return "See Other";
422 			case 304:
423 				return "Not Modified";
424 			case 305:
425 				return "Use Proxy";
426 			case 306:
427 				return "Reserved";
428 			case 307:
429 				return "Temporary Redirect";
430 			case 308:
431 				return "Permanent Redirect"; // RFC7238
432 			case 400:
433 				return "Bad Request";
434 			case 401:
435 				return "Unauthorized";
436 			case 402:
437 				return "Payment Required";
438 			case 403:
439 				return "Forbidden";
440 			case 404:
441 				return "Not Found";
442 			case 405:
443 				return "Method Not Allowed";
444 			case 406:
445 				return "Not Acceptable";
446 			case 407:
447 				return "Proxy Authentication Required";
448 			case 408:
449 				return "Request Timeout";
450 			case 409:
451 				return "Conflict";
452 			case 410:
453 				return "Gone";
454 			case 411:
455 				return "Length Required";
456 			case 412:
457 				return "Precondition Failed";
458 			case 413:
459 				return "Request Entity Too Large";
460 			case 414:
461 				return "Request-URI Too Long";
462 			case 415:
463 				return "Unsupported Media Type";
464 			case 416:
465 				return "Requested Range Not Satisfiable";
466 			case 417:
467 				return "Expectation Failed";
468 			case 418:
469 				return "I\"m a teapot"; // RFC2324
470 			case 422:
471 				return "Unprocessable Entity"; // RFC4918
472 			case 423:
473 				return "Locked"; // RFC4918
474 			case 424:
475 				return "Failed Dependency"; // RFC4918
476 			case 425:
477 				return "Reserved for WebDAV advanced collections expired proposal"; // RFC2817
478 			case 426:
479 				return "Upgrade Required"; // RFC2817
480 			case 428:
481 				return "Precondition Required"; // RFC6585
482 			case 429:
483 				return "Too Many Requests"; // RFC6585
484 			case 431:
485 				return "Request Header Fields Too Large"; // RFC6585
486 			case 500:
487 				return "Internal Server Error";
488 			case 501:
489 				return "Not Implemented";
490 			case 502:
491 				return "Bad Gateway";
492 			case 503:
493 				return "Service Unavailable";
494 			case 504:
495 				return "Gateway Timeout";
496 			case 505:
497 				return "HTTP Version Not Supported";
498 			case 506:
499 				return "Variant Also Negotiates (Experimental)"; // RFC2295
500 			case 507:
501 				return "Insufficient Storage"; // RFC4918
502 			case 508:
503 				return "Loop Detected"; // RFC5842
504 			case 510:
505 				return "Not Extended"; // RFC2774
506 			case 511:
507 				return "Network Authentication Required"; // RFC6585
508 			default:
509 				return "  ";
510 		}
511 	}
512 
513 protected:
514 	/** The 12 standard fields for HTTP messages. Use accessors.
515    * An HTTPMessage is either a Request or Response.
516    * Once an accessor for either is used, that fixes the type of HTTPMessage.
517    * If an access is then used for the other type, a DCHECK will fail.
518    */
519 	struct Request 
520 	{
521 		Address _clientAddress;
522 		string _clientIP;
523 		string _clientPort;
524 		HTTPMethod _method = HTTPMethod.HTTP_INVAILD;
525 		string _path;
526 		string _query;
527 		string _url;
528 			
529 		//ushort _pushStatus;
530 		//string _pushStatusStr;
531 	}
532 	
533 	struct Response 
534 	{
535 		ushort _status = 200;
536 		string _statusStr;
537 		string _statusMsg;
538 	}
539 
540 	ref Request request() 
541 	{
542 		if(_isRequest == MegType.Null_) {
543 			_isRequest = MegType.Request_;
544 			_resreq.req = Request();
545 		} else if(_isRequest == MegType.Response_){
546 			throw new HTTPMessageTypeException("the message type is Response not Request");
547 		}
548 		return _resreq.req;
549 	}
550 
551 	ref Response response()
552 	{
553 		if(_isRequest == MegType.Null_) {
554 			_isRequest = MegType.Response_;
555 			_resreq.res = Response();
556 		} else if(_isRequest == MegType.Request_){
557 			throw new HTTPMessageTypeException("the message type is Request not Response");
558 		}
559 
560 		return _resreq.res;
561 	}
562 
563 protected:
564 	//void parseCookies(){}
565 	
566 	void parseQueryParams(){
567 		import collie.utils..string;
568 		if(_parsedQueryParams) return;
569 		_parsedQueryParams = true;
570 		string query = getQueryString();
571 		if(query.length == 0) return;
572 		splitNameValue(query, '&', '=',(string name,string value){
573 				name = strip(name);
574 				value = strip(value);
575 				_queryParams[name] = value;
576 				return true;
577 			});
578 	}
579 	void unparseQueryParams(){
580 		_queryParams.clear();
581 		_parsedQueryParams = false;
582 	}
583 
584 	union Req_Res
585 	{
586 		Request req;
587 		Response res;
588 	}
589 
590 	enum MegType : ubyte{
591 		Null_,
592 		Request_,
593 		Response_,
594 	}
595 
596 private:
597 	Address _dstAddress;
598 	string _dstIP;
599 	string _dstPort;
600 		
601 	string _localIP;
602 	string _versionStr;
603 	MegType _isRequest = MegType.Null_;
604 	Req_Res _resreq;
605 private:
606 	ubyte[2] _version;
607 	HTTPHeaders _headers;
608 //	string[string] _cookies;
609 	string[string] _queryParams;
610 
611 private:
612 	bool _parsedCookies = false;
613 	bool _parsedQueryParams = false;
614 	bool _chunked = false;
615 	bool _upgraded = false;
616 	bool _wantsKeepalive = true;
617 }
618