1 // =================================
  2 // Copyright (c) 2021 Seppo Laakko
  3 // Distributed under the MIT license
  4 // =================================
  5 
  6 using System;
  7 using System.IO;
  8 using System.Collections;
  9 
 10 namespace System.Net.Http
 11 {
 12     public class HttpException : Exception
 13     {
 14         public HttpException(const HttpStatus& httpStatus_) : base(httpStatus_.ToString())httpStatus(httpStatus_)
 15         {
 16         }
 17         public nothrow const HttpStatus& Status() const
 18         {
 19             return httpStatus;
 20         }
 21         private HttpStatus httpStatus;
 22     }
 23 
 24     public class HttpVersion
 25     {
 26         public nothrow HttpVersion(const string& version_) : version(version_)
 27         {
 28         }
 29         public virtual default ~HttpVersion();
 30         public inline nothrow const string& Version() const
 31         {
 32             return version;
 33         }
 34         private string version;
 35     }
 36 
 37     public class Http_1_1_Version : HttpVersion
 38     {
 39         public nothrow Http_1_1_Version() : base("HTTP/1.1")
 40         {
 41         }
 42     }
 43 
 44     public class HttpStatus
 45     {
 46         public nothrow HttpStatus()
 47         {
 48         }
 49         public nothrow HttpStatus(const string& httpVersion_ushort statusCode_const string& reasonPhrase_) : 
 50             httpVersion(httpVersion_)statusCode(statusCode_)reasonPhrase(reasonPhrase_)
 51         {
 52         }
 53         public inline nothrow const string& HttpVersion() const
 54         {
 55             return httpVersion;
 56         }
 57         public inline nothrow ushort StatusCode() const
 58         {
 59             return statusCode;
 60         }
 61         public inline nothrow const string& ReasonPhrase() const
 62         {
 63             return reasonPhrase;
 64         }
 65         public string ToString() const
 66         {
 67             return httpVersion + " " + ToString(statusCode) + " " + reasonPhrase;
 68         }
 69         private string httpVersion;
 70         private ushort statusCode;
 71         private string reasonPhrase;
 72     }
 73 
 74     // Status codes:
 75 
 76     public const ushort statusInfoStart = 100u;
 77     public const ushort statusInfoEnd = 199u;
 78     public const ushort statusInfoContinue = 100u;
 79     public const ushort statusInfoSwitchingProtocols = 101u;
 80 
 81     public const ushort statusSuccessStart = 200u;
 82     public const ushort statusSuccessEnd = 299u;
 83     public const ushort statusSuccessOK = 200u;
 84     public const ushort statusSuccessCreated = 201u;
 85     public const ushort statusSuccessAccepted = 202u;
 86     public const ushort statusSuccessNonAuthoritativeInformation = 203u;
 87     public const ushort statusSuccessNoContent = 204u;
 88     public const ushort statusSuccessResetContent = 205u;
 89     public const ushort statusSuccessPartialContent = 206u;
 90 
 91     public const ushort statusRedirectionStart = 300u;
 92     public const ushort statusRedirectionEnd = 399u;
 93     public const ushort statusRedirectionMultipleChoices = 300u;
 94     public const ushort statusRedirectionMovedPermanently = 301u;
 95     public const ushort statusRedirectionFound = 302u;
 96     public const ushort statusRedirectionSeeOther = 303u;
 97     public const ushort statusRedirectionNotModified = 304u;
 98     public const ushort statusRedirectionUseProxy = 305u;
 99     public const ushort statusRedirectionUnused = 306u;
100     public const ushort statusRedirectionTemporaryRedirect = 307u;
101 
102     public const ushort statusClientErrorStart = 400u;
103     public const ushort statusClientErrorEnd = 499u;
104     public const ushort statusClientErrorBadRequest = 400u;
105     public const ushort statusClientErrorUnauthorized = 401u;
106     public const ushort statusClientErrorPaymentRequired = 402u;
107     public const ushort statusClientErrorForbidden = 403u;
108     public const ushort statusClientErrorNotFound = 404u;
109     public const ushort statusClientErrorMethodNotAllowed = 405u;
110     public const ushort statusClientErrorNotAcceptable = 406u;
111     public const ushort statusClientErrorProxyAuthenticationRequired = 407u;
112     public const ushort statusClientErrorRequestTimeout = 408u;
113     public const ushort statusClientErrorConflict = 409u;
114     public const ushort statusClientErrorGone = 410u;
115     public const ushort statusClientErrorLengthRequired = 411u;
116     public const ushort statusClientErrorPreconditionFailed = 412u;
117     public const ushort statusClientErrorRequestEntityTooLarge = 413u;
118     public const ushort statusClientErrorRequestURITooLong = 414u;
119     public const ushort statusClientErrorUnsupportedMediaType = 415u;
120     public const ushort statusClientErrorRequestedRangeNotSatisfiable = 416u;
121     public const ushort statusClientErrorExpectationFailed = 417u;
122 
123     public const ushort statusServerErrorStart = 500u;
124     public const ushort statusServerErrorEnd = 599u;
125     public const ushort statusServerInternalServerError = 500u;
126     public const ushort statusServerNotImplemented = 501u;
127     public const ushort statusServerBadGateway = 502u;
128     public const ushort statusServerServiceUnavailable = 503u;
129     public const ushort statusServerGatewayTimeout = 504u;
130     public const ushort statusServerHttpVersionNotSupported = 505u;
131 
132     public class HttpFieldValue
133     {
134         public HttpFieldValue() : fieldValue()
135         {
136         }
137         public default HttpFieldValue(const HttpFieldValue&);
138         public default void operator=(const HttpFieldValue&);
139         public HttpFieldValue(const string& fieldValue_) : fieldValue(fieldValue_)
140         {
141         }
142         public inline nothrow const string& FieldValue() const
143         {
144             return fieldValue;
145         }
146         public string ToString() const
147         {
148             string value = fieldValue;
149             for (const Pair<stringstring>& p : parameters)
150             {
151                 value.Append(';').Append(p.first);
152                 if (!p.second.IsEmpty())
153                 {
154                     value.Append('=');
155                     string paramValue = p.second;
156                     if (paramValue.Find('"') != -1 || paramValue.Find(' ') != -1)
157                     {
158                         paramValue = MakeStringLiteral(paramValue);
159                     }
160                     value.Append(paramValue);
161                 }
162             }
163             return value;
164         }
165         public void SetFieldValue(const string& fieldValue_)
166         {
167             fieldValue = fieldValue_;
168         }
169         public void SetParameter(const string& paramNameconst string& paramValue)
170         {
171             parameters[ToLower(paramName)] = paramValue;
172         }
173         public string GetParameter(const string& paramName) const
174         {
175             string pn = ToLower(paramName);
176             Map<stringstring>.ConstIterator it = parameters.CFind(pn);
177             if (it != parameters.CEnd())
178             {
179                 return it->second;
180             }
181             return string();
182         }
183         private string fieldValue;
184         private Map<stringstring> parameters;
185     }
186 
187     public class HttpHeader
188     {
189         public nothrow HttpHeader()
190         {
191         }
192         public HttpHeader(const string& fieldName_const string& fieldValue_) : 
193             fieldName(fieldName_)fieldValueList(1HttpFieldValue(Trim(fieldValue_)))combineWithSpace(false)
194         {
195         }
196         public HttpHeader(const string& fieldName_HttpFieldValue&& fieldValue_) : 
197             fieldName(fieldName_)fieldValueList()combineWithSpace(false)
198         {
199             fieldValueList.Add(fieldValue_);
200         }
201         public HttpHeader(const string& fieldName_List<HttpFieldValue>&& fieldValueList_) : 
202             fieldName(fieldName_)fieldValueList(fieldValueList_)combineWithSpace(false)
203         {
204         }
205         public HttpHeader(const string& fieldName_List<HttpFieldValue>&& fieldValueList_bool combineWithSpace_) : 
206             fieldName(fieldName_)fieldValueList(fieldValueList_)combineWithSpace(combineWithSpace_)
207         {
208         }
209         public virtual default ~HttpHeader();
210         public string CombinedFieldValue() const
211         {
212             string combinedFieldValue;
213             bool first = true;
214             for (const HttpFieldValue& fieldValue : fieldValueList)
215             {
216                 if (first)
217                 {
218                     first = false;
219                 }
220                 else
221                 {
222                     if (combineWithSpace)
223                     {
224                         combinedFieldValue.Append(' ');
225                     }
226                     else
227                     {
228                         combinedFieldValue.Append(", ");
229                     }
230                 }
231                 combinedFieldValue.Append(fieldValue.ToString());
232             }
233             return combinedFieldValue;
234         }
235         public string ToString() const
236         {
237             return fieldName + ": " + CombinedFieldValue();
238         }
239         public string FieldName() const
240         {
241             return ToLower(fieldName);
242         }
243         public inline const HttpFieldValue& SingleFieldValue() const
244         {
245             if (fieldValueList.IsEmpty())
246             {
247                 ThrowPreconditionViolationException();
248             }
249             return fieldValueList.Front();
250         }
251         public void AddFieldValue(const HttpFieldValue& fieldValue)
252         {
253             fieldValueList.Add(fieldValue);
254         }
255         public void SetCombineWithSpace()
256         {
257             combineWithSpace = true;
258         }
259         private string fieldName;
260         private List<HttpFieldValue> fieldValueList;
261         private bool combineWithSpace;
262     }
263 
264     public class HttpHeaderCollection
265     {
266         public void Add(UniquePtr<HttpHeader>&& header)
267         {
268             HttpHeader* prev = GetHeader(header->FieldName());
269             if (prev != null)
270             {
271                 prev->AddFieldValue(header->SingleFieldValue());
272             }
273             else
274             {
275                 headerMap[header->FieldName()] = header.Get();
276                 headers.Add(header);
277             }
278         }
279         public HttpHeader* GetHeader(const string& fieldName) const
280         {
281             HashMap<stringHttpHeader*>.ConstIterator it = headerMap.CFind(ToLower(fieldName));
282             if (it != headerMap.CEnd())
283             {
284                 return it->second;
285             }
286             return null;
287         }
288         public inline nothrow const List<UniquePtr<HttpHeader>>& Headers() const
289         {
290             return headers;
291         }
292         public nothrow void Clear()
293         {
294             headers.Clear();
295             headerMap.Clear();
296         }
297         public ulong GetContentLength() const
298         {
299             HttpHeader* contentLengthHeader = GetHeader("content-length");
300             if (contentLengthHeader != null)
301             {
302                 string contentLength = contentLengthHeader->SingleFieldValue().FieldValue();
303                 if (!contentLength.IsEmpty())
304                 {
305                     return ParseULong(contentLength);
306                 }
307             }
308             return 0u;
309         }
310         public MimeType GetContentType() const
311         {
312             HttpHeader* contentTypeHeader = GetHeader("content-type");
313             if (contentTypeHeader != null)
314             {
315                 string fieldValue = contentTypeHeader->SingleFieldValue().FieldValue();
316                 MimeType mimeType = HttpParser.ParseMediaType(fieldValue);
317                 return mimeType;
318             }
319             return MimeType();
320         }
321         public DateTime GetDate() const
322         {
323             HttpHeader* dateHeader = GetHeader("date");
324             if (dateHeader != null)
325             {
326                 string fieldValue = dateHeader->SingleFieldValue().FieldValue();
327                 DateTime date = HttpParser.ParseDate(fieldValue);
328                 return date;
329             }
330             return DateTime();
331         }
332         private List<UniquePtr<HttpHeader>> headers;
333         private HashMap<stringHttpHeader*> headerMap;
334     }
335 
336     public string MakeHttpHostValue(const string& hostint port)
337     {
338         if (port == -1)
339         {
340             port = 80;
341         }
342         if (port != 80)
343         {
344             return host + ":" + ToString(port);
345         }
346         else
347         {
348             return host;
349         }
350     }
351 
352     public class HttpHostHeader : HttpHeader
353     {
354         public HttpHostHeader(const string& host_) : this(host_80)
355         {
356         }
357         public HttpHostHeader(const string& host_int port_) : base("Host"MakeHttpHostValue(host_port_))
358         {
359         }
360     }
361 
362     public class HttpContentLengthHeader : HttpHeader
363     {
364         public HttpContentLengthHeader(int contentLength_) :  base("Content-Length"ToString(contentLength_))
365         {
366         }
367     }
368 
369     public List<HttpFieldValue> MakeProductListValue(const List<string>& products)
370     {
371         List<HttpFieldValue> fieldValueList;
372         for (const string& product : products)
373         {
374             fieldValueList.Add(HttpFieldValue(product));
375         }
376         return fieldValueList;
377     }
378 
379     public class HttpUserAgentHeader : HttpHeader
380     {
381         public HttpUserAgentHeader() : this("Cmajor-http-client/3.3.0")
382         {
383         }
384         public HttpUserAgentHeader(const string& product_) : base("User-Agent"product_)
385         {
386         }
387         public HttpUserAgentHeader(const List<string>& products_) : base("User-Agent"MakeProductListValue(products_)true)
388         {
389         }
390     }
391 
392     public List<HttpFieldValue> MakeMediaRangeListValue(const List<MediaRange>& mediaRanges)
393     {
394         List<HttpFieldValue> fieldValueList;
395         for (const MediaRange& mediaRange : mediaRanges)
396         {
397             fieldValueList.Add(mediaRange.ToHttpFieldValue());
398         }
399         return fieldValueList;
400     }
401 
402     public class HttpAcceptHeader : HttpHeader
403     {
404         public HttpAcceptHeader(const MediaRange& mediaRange) : base("Accept"mediaRange.ToHttpFieldValue())
405         {
406         }
407         public HttpAcceptHeader(const List<MediaRange>& mediaRanges) : base("Accept"MakeMediaRangeListValue(mediaRanges))
408         {
409         }
410     }
411 
412     public class Coding
413     {
414         public nothrow Coding(const string& coding_) : coding(coding_)quality(1)
415         {
416         }
417         public nothrow Coding(const string& coding_double quality_) : coding(coding_)quality(quality_)
418         {
419         }
420         public virtual default ~Coding();
421         public HttpFieldValue ToHttpFieldValue() const
422         {
423             if (quality == 1)
424             {
425                 return HttpFieldValue(coding);
426             }
427             else
428             {
429                 HttpFieldValue result(coding);
430                 result.SetParameter("q"ToString(quality3));
431                 return result;
432             }
433         }
434         private string coding;
435         private double quality;
436     }
437 
438     public class ChunkedTransferCoding : Coding
439     {
440         public nothrow ChunkedTransferCoding() : base("chunked")
441         {
442         }
443     }
444 
445     public class DeflateCoding : Coding
446     {
447         public nothrow DeflateCoding() : base("deflate")
448         {
449         }
450         public nothrow DeflateCoding(double quality) : base("deflate"quality)
451         {
452         }
453     }
454 
455     public List<HttpFieldValue> MakeCodingListValue(const List<Coding>& codings)
456     {
457         List<HttpFieldValue> fieldValueList;
458         for (const Coding& coding : codings)
459         {
460             fieldValueList.Add(coding.ToHttpFieldValue());
461         }
462         return fieldValueList;
463     }
464 
465     public class HttpTEHeader : HttpHeader
466     {
467         public HttpTEHeader(const Coding& transferCoding) : base("TE"transferCoding.ToHttpFieldValue())
468         {
469         }
470         public HttpTEHeader(const List<Coding>& transferCodings) : base("TE"MakeCodingListValue(transferCodings))
471         {
472         }
473     }
474 
475     public class HttpAcceptEncodingHeader : HttpHeader
476     {
477         public HttpAcceptEncodingHeader(const Coding& encoding) : base("Accept-Encoding"encoding.ToHttpFieldValue())
478         {
479         }
480         public HttpAcceptEncodingHeader(const List<Coding>& encodings) : base("Accept-Encoding"MakeCodingListValue(encodings))
481         {
482         }
483     }
484 
485     public class HttpMethod
486     {
487         public nothrow HttpMethod(const string& methodName_) : methodName(methodName_)
488         {
489         }
490         public virtual default ~HttpMethod();
491         public inline nothrow const string& MethodName() const
492         {
493             return methodName;
494         }
495         private string methodName;
496     }
497 
498     public class HttpOptionsMethod : HttpMethod
499     {
500         public nothrow HttpOptionsMethod() : base("OPTIONS")
501         {
502         }
503     }
504 
505     public class HttpGetMethod : HttpMethod
506     {
507         public nothrow HttpGetMethod() : base("GET")
508         {
509         }
510     }
511 
512     public class HttpHeadMethod : HttpMethod
513     {
514         public nothrow HttpHeadMethod() : base("HEAD")
515         {
516         }
517     }
518 
519     public class HttpPostMethod : HttpMethod
520     {
521         public nothrow HttpPostMethod() : base("POST")
522         {
523         }
524     }
525 
526     public class HttpPutMethod : HttpMethod
527     {
528         public nothrow HttpPutMethod() : base("PUT")
529         {
530         }
531     }
532 
533     public class HttpDeleteMethod : HttpMethod
534     {
535         public nothrow HttpDeleteMethod() : base("DELETE")
536         {
537         }
538     }
539 
540     public class HttpTraceMethod : HttpMethod
541     {
542         public nothrow HttpTraceMethod() : base("TRACE")
543         {
544         }
545     }
546 
547     public class HttpConnectMethod : HttpMethod
548     {
549         public nothrow HttpConnectMethod() : base("CONNECT")
550         {
551         }
552     }
553 
554     public class HttpRequest
555     {
556         public HttpRequest(const UriReference& absoluteUri_HttpHeaderCollection& headers_) : 
557             this(UniquePtr<HttpMethod>(new HttpGetMethod())absoluteUri_UniquePtr<HttpVersion>(new Http_1_1_Version())headers_)
558         {
559         }
560         public HttpRequest(UniquePtr<HttpMethod>&& method_const UriReference& absoluteUri_UniquePtr<HttpVersion>&& httpVersion_HttpHeaderCollection& headers_) : 
561             method(method_)absoluteUri(absoluteUri_)httpVersion(httpVersion_)headers(headers_)
562         {
563         }
564         public void Write(StreamWriter& writer)
565         {
566             Write(writernull);
567         }
568         public void Write(StreamWriter& writerStreamWriter* log)
569         {
570             string requestLine = method->MethodName();
571             UriReference requestUri;
572             requestUri.SetPath(absoluteUri.Path());
573             requestLine.Append(' ');
574             requestLine.Append(requestUri.ToString());
575             requestLine.Append(' ');
576             requestLine.Append(httpVersion->Version());
577             if (log != null)
578             {
579                 log->WriteLine("REQUEST:");
580                 log->WriteLine(requestLine);
581             }
582             writer.Write(requestLine);
583             writer.Write("\r\n");
584             HttpHeader* hostHeader = headers.GetHeader("host");
585             if (hostHeader == null)
586             {
587                 headers.Add(UniquePtr<HttpHeader>(new HttpHostHeader(absoluteUri.GetAuthority().Host()absoluteUri.GetAuthority().Port())));
588             }
589             HttpHeader* userAgentHeader = headers.GetHeader("user-agent");
590             if (userAgentHeader == null)
591             {
592                 headers.Add(UniquePtr<HttpHeader>(new HttpUserAgentHeader()));
593             }
594             for (const UniquePtr<HttpHeader>& header : headers.Headers())
595             {
596                 string headerStr = header->ToString();
597                 if (log != null)
598                 {
599                     log->WriteLine(headerStr);
600                 }
601                 writer.Write(headerStr);
602                 writer.Write("\r\n");
603             }
604             writer.Write("\r\n");
605             writer.ContainedStream()->Flush();
606         }
607         private UniquePtr<HttpMethod> method;
608         private UriReference absoluteUri;
609         private UniquePtr<HttpVersion> httpVersion;
610         private HttpHeaderCollection& headers;
611     }
612 }