#region License /* * HttpListenerRequest.cs * * This code is derived from HttpListenerRequest.cs (System.Net) of Mono * (http://www.mono-project.com). * * The MIT License * * Copyright (c) 2005 Novell, Inc. (http://www.novell.com) * Copyright (c) 2012-2018 sta.blockhead * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #endregion #region Authors /* * Authors: * - Gonzalo Paniagua Javier */ #endregion using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Globalization; using System.IO; using System.Security.Cryptography.X509Certificates; using System.Text; namespace WebSocketSharp.Net { /// /// Represents an incoming HTTP request to a /// instance. /// /// /// This class cannot be inherited. /// public sealed class HttpListenerRequest { #region Private Fields private static readonly byte[] _100continue; private string[] _acceptTypes; private bool _chunked; private HttpConnection _connection; private Encoding _contentEncoding; private long _contentLength; private HttpListenerContext _context; private CookieCollection _cookies; private WebHeaderCollection _headers; private string _httpMethod; private Stream _inputStream; private Version _protocolVersion; private NameValueCollection _queryString; private string _rawUrl; private Guid _requestTraceIdentifier; private Uri _url; private Uri _urlReferrer; private bool _urlSet; private string _userHostName; private string[] _userLanguages; #endregion #region Static Constructor static HttpListenerRequest () { _100continue = Encoding.ASCII.GetBytes ("HTTP/1.1 100 Continue\r\n\r\n"); } #endregion #region Internal Constructors internal HttpListenerRequest (HttpListenerContext context) { _context = context; _connection = context.Connection; _contentLength = -1; _headers = new WebHeaderCollection (); _requestTraceIdentifier = Guid.NewGuid (); } #endregion #region Public Properties /// /// Gets the media types that are acceptable for the client. /// /// /// /// An array of that contains the names of the media /// types specified in the value of the Accept header. /// /// /// if the header is not present. /// /// public string[] AcceptTypes { get { var val = _headers["Accept"]; if (val == null) return null; if (_acceptTypes == null) { _acceptTypes = val .SplitHeaderValue (',') .Trim () .ToList () .ToArray (); } return _acceptTypes; } } /// /// Gets an error code that identifies a problem with the certificate /// provided by the client. /// /// /// An that represents an error code. /// /// /// This property is not supported. /// public int ClientCertificateError { get { throw new NotSupportedException (); } } /// /// Gets the encoding for the entity body data included in the request. /// /// /// /// A converted from the charset value of the /// Content-Type header. /// /// /// if the charset value is not available. /// /// public Encoding ContentEncoding { get { if (_contentEncoding == null) _contentEncoding = getContentEncoding () ?? Encoding.UTF8; return _contentEncoding; } } /// /// Gets the length in bytes of the entity body data included in the /// request. /// /// /// /// A converted from the value of the Content-Length /// header. /// /// /// -1 if the header is not present. /// /// public long ContentLength64 { get { return _contentLength; } } /// /// Gets the media type of the entity body data included in the request. /// /// /// /// A that represents the value of the Content-Type /// header. /// /// /// if the header is not present. /// /// public string ContentType { get { return _headers["Content-Type"]; } } /// /// Gets the cookies included in the request. /// /// /// /// A that contains the cookies. /// /// /// An empty collection if not included. /// /// public CookieCollection Cookies { get { if (_cookies == null) _cookies = _headers.GetCookies (false); return _cookies; } } /// /// Gets a value indicating whether the request has the entity body data. /// /// /// true if the request has the entity body data; otherwise, /// false. /// public bool HasEntityBody { get { return _contentLength > 0 || _chunked; } } /// /// Gets the headers included in the request. /// /// /// A that contains the headers. /// public NameValueCollection Headers { get { return _headers; } } /// /// Gets the HTTP method specified by the client. /// /// /// A that represents the HTTP method specified in /// the request line. /// public string HttpMethod { get { return _httpMethod; } } /// /// Gets a stream that contains the entity body data included in /// the request. /// /// /// /// A that contains the entity body data. /// /// /// if the entity body data is not available. /// /// public Stream InputStream { get { if (_inputStream == null) _inputStream = getInputStream () ?? Stream.Null; return _inputStream; } } /// /// Gets a value indicating whether the client is authenticated. /// /// /// true if the client is authenticated; otherwise, false. /// public bool IsAuthenticated { get { return _context.User != null; } } /// /// Gets a value indicating whether the request is sent from the local /// computer. /// /// /// true if the request is sent from the same computer as the server; /// otherwise, false. /// public bool IsLocal { get { return _connection.IsLocal; } } /// /// Gets a value indicating whether a secure connection is used to send /// the request. /// /// /// true if the connection is secure; otherwise, false. /// public bool IsSecureConnection { get { return _connection.IsSecure; } } /// /// Gets a value indicating whether the request is a WebSocket handshake /// request. /// /// /// true if the request is a WebSocket handshake request; otherwise, /// false. /// public bool IsWebSocketRequest { get { return _httpMethod == "GET" && _protocolVersion > HttpVersion.Version10 && _headers.Upgrades ("websocket"); } } /// /// Gets a value indicating whether a persistent connection is requested. /// /// /// true if the request specifies that the connection is kept open; /// otherwise, false. /// public bool KeepAlive { get { return _headers.KeepsAlive (_protocolVersion); } } /// /// Gets the endpoint to which the request is sent. /// /// /// A that represents the server IP /// address and port number. /// public System.Net.IPEndPoint LocalEndPoint { get { return _connection.LocalEndPoint; } } /// /// Gets the HTTP version specified by the client. /// /// /// A that represents the HTTP version specified in /// the request line. /// public Version ProtocolVersion { get { return _protocolVersion; } } /// /// Gets the query string included in the request. /// /// /// /// A that contains the query /// parameters. /// /// /// An empty collection if not included. /// /// public NameValueCollection QueryString { get { if (_queryString == null) { var url = Url; _queryString = QueryStringCollection.Parse ( url != null ? url.Query : null, Encoding.UTF8 ); } return _queryString; } } /// /// Gets the raw URL specified by the client. /// /// /// A that represents the request target specified in /// the request line. /// public string RawUrl { get { return _rawUrl; } } /// /// Gets the endpoint from which the request is sent. /// /// /// A that represents the client IP /// address and port number. /// public System.Net.IPEndPoint RemoteEndPoint { get { return _connection.RemoteEndPoint; } } /// /// Gets the trace identifier of the request. /// /// /// A that represents the trace identifier. /// public Guid RequestTraceIdentifier { get { return _requestTraceIdentifier; } } /// /// Gets the URL requested by the client. /// /// /// /// A that represents the URL parsed from the request. /// /// /// if the URL cannot be parsed. /// /// public Uri Url { get { if (!_urlSet) { _url = HttpUtility.CreateRequestUrl ( _rawUrl, _userHostName ?? UserHostAddress, IsWebSocketRequest, IsSecureConnection ); _urlSet = true; } return _url; } } /// /// Gets the URI of the resource from which the requested URL was obtained. /// /// /// /// A converted from the value of the Referer header. /// /// /// if the header value is not available. /// /// public Uri UrlReferrer { get { var val = _headers["Referer"]; if (val == null) return null; if (_urlReferrer == null) _urlReferrer = val.ToUri (); return _urlReferrer; } } /// /// Gets the user agent from which the request is originated. /// /// /// /// A that represents the value of the User-Agent /// header. /// /// /// if the header is not present. /// /// public string UserAgent { get { return _headers["User-Agent"]; } } /// /// Gets the IP address and port number to which the request is sent. /// /// /// A that represents the server IP address and port /// number. /// public string UserHostAddress { get { return _connection.LocalEndPoint.ToString (); } } /// /// Gets the server host name requested by the client. /// /// /// /// A that represents the value of the Host header. /// /// /// It includes the port number if provided. /// /// /// if the header is not present. /// /// public string UserHostName { get { return _userHostName; } } /// /// Gets the natural languages that are acceptable for the client. /// /// /// /// An array of that contains the names of the /// natural languages specified in the value of the Accept-Language /// header. /// /// /// if the header is not present. /// /// public string[] UserLanguages { get { var val = _headers["Accept-Language"]; if (val == null) return null; if (_userLanguages == null) _userLanguages = val.Split (',').Trim ().ToList ().ToArray (); return _userLanguages; } } #endregion #region Private Methods private void finishInitialization10 () { var transferEnc = _headers["Transfer-Encoding"]; if (transferEnc != null) { _context.ErrorMessage = "Invalid Transfer-Encoding header"; return; } if (_httpMethod == "POST") { if (_contentLength == -1) { _context.ErrorMessage = "Content-Length header required"; return; } if (_contentLength == 0) { _context.ErrorMessage = "Invalid Content-Length header"; return; } } } private Encoding getContentEncoding () { var val = _headers["Content-Type"]; if (val == null) return null; Encoding ret; HttpUtility.TryGetEncoding (val, out ret); return ret; } private RequestStream getInputStream () { return _contentLength > 0 || _chunked ? _connection.GetRequestStream (_contentLength, _chunked) : null; } #endregion #region Internal Methods internal void AddHeader (string headerField) { var start = headerField[0]; if (start == ' ' || start == '\t') { _context.ErrorMessage = "Invalid header field"; return; } var colon = headerField.IndexOf (':'); if (colon < 1) { _context.ErrorMessage = "Invalid header field"; return; } var name = headerField.Substring (0, colon).Trim (); if (name.Length == 0 || !name.IsToken ()) { _context.ErrorMessage = "Invalid header name"; return; } var val = colon < headerField.Length - 1 ? headerField.Substring (colon + 1).Trim () : String.Empty; _headers.InternalSet (name, val, false); var lower = name.ToLower (CultureInfo.InvariantCulture); if (lower == "host") { if (_userHostName != null) { _context.ErrorMessage = "Invalid Host header"; return; } if (val.Length == 0) { _context.ErrorMessage = "Invalid Host header"; return; } _userHostName = val; return; } if (lower == "content-length") { if (_contentLength > -1) { _context.ErrorMessage = "Invalid Content-Length header"; return; } long len; if (!Int64.TryParse (val, out len)) { _context.ErrorMessage = "Invalid Content-Length header"; return; } if (len < 0) { _context.ErrorMessage = "Invalid Content-Length header"; return; } _contentLength = len; return; } } internal void FinishInitialization () { if (_protocolVersion == HttpVersion.Version10) { finishInitialization10 (); return; } if (_userHostName == null) { _context.ErrorMessage = "Host header required"; return; } var transferEnc = _headers["Transfer-Encoding"]; if (transferEnc != null) { var comparison = StringComparison.OrdinalIgnoreCase; if (!transferEnc.Equals ("chunked", comparison)) { _context.ErrorMessage = String.Empty; _context.ErrorStatus = 501; return; } _chunked = true; } if (_httpMethod == "POST" || _httpMethod == "PUT") { if (_contentLength <= 0 && !_chunked) { _context.ErrorMessage = String.Empty; _context.ErrorStatus = 411; return; } } var expect = _headers["Expect"]; if (expect != null) { var comparison = StringComparison.OrdinalIgnoreCase; if (!expect.Equals ("100-continue", comparison)) { _context.ErrorMessage = "Invalid Expect header"; return; } var output = _connection.GetResponseStream (); output.InternalWrite (_100continue, 0, _100continue.Length); } } internal bool FlushInput () { var input = InputStream; if (input == Stream.Null) return true; var len = 2048; if (_contentLength > 0 && _contentLength < len) len = (int) _contentLength; var buff = new byte[len]; while (true) { try { var ares = input.BeginRead (buff, 0, len, null, null); if (!ares.IsCompleted) { var timeout = 100; if (!ares.AsyncWaitHandle.WaitOne (timeout)) return false; } if (input.EndRead (ares) <= 0) return true; } catch { return false; } } } internal bool IsUpgradeRequest (string protocol) { return _headers.Upgrades (protocol); } internal void SetRequestLine (string requestLine) { var parts = requestLine.Split (new[] { ' ' }, 3); if (parts.Length < 3) { _context.ErrorMessage = "Invalid request line (parts)"; return; } var method = parts[0]; if (method.Length == 0) { _context.ErrorMessage = "Invalid request line (method)"; return; } var target = parts[1]; if (target.Length == 0) { _context.ErrorMessage = "Invalid request line (target)"; return; } var rawVer = parts[2]; if (rawVer.Length != 8) { _context.ErrorMessage = "Invalid request line (version)"; return; } if (rawVer.IndexOf ("HTTP/") != 0) { _context.ErrorMessage = "Invalid request line (version)"; return; } Version ver; if (!rawVer.Substring (5).TryCreateVersion (out ver)) { _context.ErrorMessage = "Invalid request line (version)"; return; } if (ver.Major < 1) { _context.ErrorMessage = "Invalid request line (version)"; return; } if (!method.IsHttpMethod (ver)) { _context.ErrorMessage = "Invalid request line (method)"; return; } _httpMethod = method; _rawUrl = target; _protocolVersion = ver; } #endregion #region Public Methods /// /// Begins getting the certificate provided by the client asynchronously. /// /// /// An instance that indicates the status of the /// operation. /// /// /// An delegate that invokes the method called /// when the operation is complete. /// /// /// An that represents a user defined object to pass to /// the callback delegate. /// /// /// This method is not supported. /// public IAsyncResult BeginGetClientCertificate ( AsyncCallback requestCallback, object state ) { throw new NotSupportedException (); } /// /// Ends an asynchronous operation to get the certificate provided by the /// client. /// /// /// A that represents an X.509 certificate /// provided by the client. /// /// /// An instance returned when the operation /// started. /// /// /// This method is not supported. /// public X509Certificate2 EndGetClientCertificate (IAsyncResult asyncResult) { throw new NotSupportedException (); } /// /// Gets the certificate provided by the client. /// /// /// A that represents an X.509 certificate /// provided by the client. /// /// /// This method is not supported. /// public X509Certificate2 GetClientCertificate () { throw new NotSupportedException (); } /// /// Returns a string that represents the current instance. /// /// /// A that contains the request line and headers /// included in the request. /// public override string ToString () { var buff = new StringBuilder (64); buff .AppendFormat ( "{0} {1} HTTP/{2}\r\n", _httpMethod, _rawUrl, _protocolVersion ) .Append (_headers.ToString ()); return buff.ToString (); } #endregion } }