123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952 |
- #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-2022 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 <gonzalo@novell.com>
- */
- #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
- {
- /// <summary>
- /// Represents an incoming HTTP request to a <see cref="HttpListener"/>
- /// instance.
- /// </summary>
- /// <remarks>
- /// This class cannot be inherited.
- /// </remarks>
- 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 static readonly Encoding _defaultEncoding;
- 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");
- _defaultEncoding = Encoding.UTF8;
- }
- #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
- /// <summary>
- /// Gets the media types that are acceptable for the client.
- /// </summary>
- /// <value>
- /// <para>
- /// An array of <see cref="string"/> or <see langword="null"/>.
- /// </para>
- /// <para>
- /// The array contains the names of the media types specified in
- /// the value of the Accept header.
- /// </para>
- /// <para>
- /// <see langword="null"/> if the header is not present.
- /// </para>
- /// </value>
- public string[] AcceptTypes {
- get {
- var val = _headers["Accept"];
- if (val == null)
- return null;
- if (_acceptTypes == null) {
- _acceptTypes = val
- .SplitHeaderValue (',')
- .TrimEach ()
- .ToList ()
- .ToArray ();
- }
- return _acceptTypes;
- }
- }
- /// <summary>
- /// Gets an error code that identifies a problem with the certificate
- /// provided by the client.
- /// </summary>
- /// <value>
- /// An <see cref="int"/> that represents an error code.
- /// </value>
- /// <exception cref="NotSupportedException">
- /// This property is not supported.
- /// </exception>
- public int ClientCertificateError {
- get {
- throw new NotSupportedException ();
- }
- }
- /// <summary>
- /// Gets the encoding for the entity body data included in the request.
- /// </summary>
- /// <value>
- /// <para>
- /// A <see cref="Encoding"/> converted from the charset value of the
- /// Content-Type header.
- /// </para>
- /// <para>
- /// <see cref="Encoding.UTF8"/> if the charset value is not available.
- /// </para>
- /// </value>
- public Encoding ContentEncoding {
- get {
- if (_contentEncoding == null)
- _contentEncoding = getContentEncoding ();
- return _contentEncoding;
- }
- }
- /// <summary>
- /// Gets the length in bytes of the entity body data included in the
- /// request.
- /// </summary>
- /// <value>
- /// <para>
- /// A <see cref="long"/> converted from the value of the Content-Length
- /// header.
- /// </para>
- /// <para>
- /// -1 if the header is not present.
- /// </para>
- /// </value>
- public long ContentLength64 {
- get {
- return _contentLength;
- }
- }
- /// <summary>
- /// Gets the media type of the entity body data included in the request.
- /// </summary>
- /// <value>
- /// <para>
- /// A <see cref="string"/> or <see langword="null"/>.
- /// </para>
- /// <para>
- /// The string represents the value of the Content-Type header.
- /// </para>
- /// <para>
- /// <see langword="null"/> if the header is not present.
- /// </para>
- /// </value>
- public string ContentType {
- get {
- return _headers["Content-Type"];
- }
- }
- /// <summary>
- /// Gets the cookies included in the request.
- /// </summary>
- /// <value>
- /// <para>
- /// A <see cref="CookieCollection"/> that contains the cookies.
- /// </para>
- /// <para>
- /// An empty collection if not included.
- /// </para>
- /// </value>
- public CookieCollection Cookies {
- get {
- if (_cookies == null)
- _cookies = _headers.GetCookies (false);
- return _cookies;
- }
- }
- /// <summary>
- /// Gets a value indicating whether the request has the entity body data.
- /// </summary>
- /// <value>
- /// <c>true</c> if the request has the entity body data; otherwise,
- /// <c>false</c>.
- /// </value>
- public bool HasEntityBody {
- get {
- return _contentLength > 0 || _chunked;
- }
- }
- /// <summary>
- /// Gets the headers included in the request.
- /// </summary>
- /// <value>
- /// A <see cref="NameValueCollection"/> that contains the headers.
- /// </value>
- public NameValueCollection Headers {
- get {
- return _headers;
- }
- }
- /// <summary>
- /// Gets the HTTP method specified by the client.
- /// </summary>
- /// <value>
- /// A <see cref="string"/> that represents the HTTP method specified in
- /// the request line.
- /// </value>
- public string HttpMethod {
- get {
- return _httpMethod;
- }
- }
- /// <summary>
- /// Gets a stream that contains the entity body data included in
- /// the request.
- /// </summary>
- /// <value>
- /// <para>
- /// A <see cref="Stream"/> that contains the entity body data.
- /// </para>
- /// <para>
- /// <see cref="Stream.Null"/> if the entity body data is not available.
- /// </para>
- /// </value>
- public Stream InputStream {
- get {
- if (_inputStream == null) {
- _inputStream = _contentLength > 0 || _chunked
- ? _connection
- .GetRequestStream (_contentLength, _chunked)
- : Stream.Null;
- }
- return _inputStream;
- }
- }
- /// <summary>
- /// Gets a value indicating whether the client is authenticated.
- /// </summary>
- /// <value>
- /// <c>true</c> if the client is authenticated; otherwise, <c>false</c>.
- /// </value>
- public bool IsAuthenticated {
- get {
- return _context.User != null;
- }
- }
- /// <summary>
- /// Gets a value indicating whether the request is sent from the
- /// local computer.
- /// </summary>
- /// <value>
- /// <c>true</c> if the request is sent from the same computer as
- /// the server; otherwise, <c>false</c>.
- /// </value>
- public bool IsLocal {
- get {
- return _connection.IsLocal;
- }
- }
- /// <summary>
- /// Gets a value indicating whether a secure connection is used to send
- /// the request.
- /// </summary>
- /// <value>
- /// <c>true</c> if the connection is secure; otherwise, <c>false</c>.
- /// </value>
- public bool IsSecureConnection {
- get {
- return _connection.IsSecure;
- }
- }
- /// <summary>
- /// Gets a value indicating whether the request is a WebSocket handshake
- /// request.
- /// </summary>
- /// <value>
- /// <c>true</c> if the request is a WebSocket handshake request; otherwise,
- /// <c>false</c>.
- /// </value>
- public bool IsWebSocketRequest {
- get {
- return _httpMethod == "GET" && _headers.Upgrades ("websocket");
- }
- }
- /// <summary>
- /// Gets a value indicating whether a persistent connection is requested.
- /// </summary>
- /// <value>
- /// <c>true</c> if the request specifies that the connection is kept open;
- /// otherwise, <c>false</c>.
- /// </value>
- public bool KeepAlive {
- get {
- return _headers.KeepsAlive (_protocolVersion);
- }
- }
- /// <summary>
- /// Gets the endpoint to which the request is sent.
- /// </summary>
- /// <value>
- /// A <see cref="System.Net.IPEndPoint"/> that represents the server
- /// IP address and port number.
- /// </value>
- public System.Net.IPEndPoint LocalEndPoint {
- get {
- return _connection.LocalEndPoint;
- }
- }
- /// <summary>
- /// Gets the HTTP version specified by the client.
- /// </summary>
- /// <value>
- /// A <see cref="Version"/> that represents the HTTP version specified in
- /// the request line.
- /// </value>
- public Version ProtocolVersion {
- get {
- return _protocolVersion;
- }
- }
- /// <summary>
- /// Gets the query string included in the request.
- /// </summary>
- /// <value>
- /// <para>
- /// A <see cref="NameValueCollection"/> that contains the query
- /// parameters.
- /// </para>
- /// <para>
- /// Each query parameter is decoded in UTF-8.
- /// </para>
- /// <para>
- /// An empty collection if not included.
- /// </para>
- /// </value>
- public NameValueCollection QueryString {
- get {
- if (_queryString == null) {
- var url = Url;
- var query = url != null ? url.Query : null;
- _queryString = QueryStringCollection.Parse (query, _defaultEncoding);
- }
- return _queryString;
- }
- }
- /// <summary>
- /// Gets the raw URL specified by the client.
- /// </summary>
- /// <value>
- /// A <see cref="string"/> that represents the request target specified in
- /// the request line.
- /// </value>
- public string RawUrl {
- get {
- return _rawUrl;
- }
- }
- /// <summary>
- /// Gets the endpoint from which the request is sent.
- /// </summary>
- /// <value>
- /// A <see cref="System.Net.IPEndPoint"/> that represents the client
- /// IP address and port number.
- /// </value>
- public System.Net.IPEndPoint RemoteEndPoint {
- get {
- return _connection.RemoteEndPoint;
- }
- }
- /// <summary>
- /// Gets the trace identifier of the request.
- /// </summary>
- /// <value>
- /// A <see cref="Guid"/> that represents the trace identifier.
- /// </value>
- public Guid RequestTraceIdentifier {
- get {
- return _requestTraceIdentifier;
- }
- }
- /// <summary>
- /// Gets the URL requested by the client.
- /// </summary>
- /// <value>
- /// <para>
- /// A <see cref="Uri"/> or <see langword="null"/>.
- /// </para>
- /// <para>
- /// The Uri represents the URL parsed from the request.
- /// </para>
- /// <para>
- /// <see langword="null"/> if the URL cannot be parsed.
- /// </para>
- /// </value>
- public Uri Url {
- get {
- if (!_urlSet) {
- _url = HttpUtility
- .CreateRequestUrl (
- _rawUrl,
- _userHostName,
- IsWebSocketRequest,
- IsSecureConnection
- );
- _urlSet = true;
- }
- return _url;
- }
- }
- /// <summary>
- /// Gets the URI of the resource from which the requested URL was obtained.
- /// </summary>
- /// <value>
- /// <para>
- /// A <see cref="Uri"/> or <see langword="null"/>.
- /// </para>
- /// <para>
- /// The Uri represents the value of the Referer header.
- /// </para>
- /// <para>
- /// <see langword="null"/> if the header value is not available.
- /// </para>
- /// </value>
- public Uri UrlReferrer {
- get {
- var val = _headers["Referer"];
- if (val == null)
- return null;
- if (_urlReferrer == null)
- _urlReferrer = val.ToUri ();
- return _urlReferrer;
- }
- }
- /// <summary>
- /// Gets the user agent from which the request is originated.
- /// </summary>
- /// <value>
- /// <para>
- /// A <see cref="string"/> or <see langword="null"/>.
- /// </para>
- /// <para>
- /// The string represents the value of the User-Agent header.
- /// </para>
- /// <para>
- /// <see langword="null"/> if the header is not present.
- /// </para>
- /// </value>
- public string UserAgent {
- get {
- return _headers["User-Agent"];
- }
- }
- /// <summary>
- /// Gets the IP address and port number to which the request is sent.
- /// </summary>
- /// <value>
- /// A <see cref="string"/> that represents the server IP address and
- /// port number.
- /// </value>
- public string UserHostAddress {
- get {
- return _connection.LocalEndPoint.ToString ();
- }
- }
- /// <summary>
- /// Gets the server host name requested by the client.
- /// </summary>
- /// <value>
- /// <para>
- /// A <see cref="string"/> that represents the value of the Host header.
- /// </para>
- /// <para>
- /// It includes the port number if provided.
- /// </para>
- /// </value>
- public string UserHostName {
- get {
- return _userHostName;
- }
- }
- /// <summary>
- /// Gets the natural languages that are acceptable for the client.
- /// </summary>
- /// <value>
- /// <para>
- /// An array of <see cref="string"/> or <see langword="null"/>.
- /// </para>
- /// <para>
- /// The array contains the names of the natural languages specified in
- /// the value of the Accept-Language header.
- /// </para>
- /// <para>
- /// <see langword="null"/> if the header is not present.
- /// </para>
- /// </value>
- public string[] UserLanguages {
- get {
- var val = _headers["Accept-Language"];
- if (val == null)
- return null;
- if (_userLanguages == null)
- _userLanguages = val.Split (',').TrimEach ().ToList ().ToArray ();
- return _userLanguages;
- }
- }
- #endregion
- #region Private Methods
- private Encoding getContentEncoding ()
- {
- var val = _headers["Content-Type"];
- if (val == null)
- return _defaultEncoding;
- Encoding ret;
- return HttpUtility.TryGetEncoding (val, out ret)
- ? ret
- : _defaultEncoding;
- }
- #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 (_userHostName == null) {
- _context.ErrorMessage = "Host header required";
- return;
- }
- var transferEnc = _headers["Transfer-Encoding"];
- if (transferEnc != null) {
- var compType = StringComparison.OrdinalIgnoreCase;
- if (!transferEnc.Equals ("chunked", compType)) {
- _context.ErrorStatusCode = 501;
- _context.ErrorMessage = "Invalid Transfer-Encoding header";
- return;
- }
- _chunked = true;
- }
- if (_httpMethod == "POST" || _httpMethod == "PUT") {
- if (_contentLength == -1 && !_chunked) {
- _context.ErrorStatusCode = 411;
- _context.ErrorMessage = "Content-Length header required";
- return;
- }
- if (_contentLength == 0 && !_chunked) {
- _context.ErrorStatusCode = 411;
- _context.ErrorMessage = "Invalid Content-Length header";
- return;
- }
- }
- var expect = _headers["Expect"];
- if (expect != null) {
- var compType = StringComparison.OrdinalIgnoreCase;
- if (!expect.Equals ("100-continue", compType)) {
- _context.ErrorStatusCode = 417;
- _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;
- }
- if (!method.IsHttpMethod ()) {
- _context.ErrorStatusCode = 501;
- _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.StartsWith ("HTTP/", StringComparison.Ordinal)) {
- _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 != HttpVersion.Version11) {
- _context.ErrorStatusCode = 505;
- _context.ErrorMessage = "Invalid request line (version)";
- return;
- }
- _httpMethod = method;
- _rawUrl = target;
- _protocolVersion = ver;
- }
- #endregion
- #region Public Methods
- /// <summary>
- /// Begins getting the certificate provided by the client asynchronously.
- /// </summary>
- /// <returns>
- /// An <see cref="IAsyncResult"/> instance that indicates the status of
- /// the operation.
- /// </returns>
- /// <param name="requestCallback">
- /// An <see cref="AsyncCallback"/> delegate that invokes the method called
- /// when the operation is complete.
- /// </param>
- /// <param name="state">
- /// An <see cref="object"/> that specifies a user defined object to pass
- /// to the callback delegate.
- /// </param>
- /// <exception cref="NotSupportedException">
- /// This method is not supported.
- /// </exception>
- public IAsyncResult BeginGetClientCertificate (
- AsyncCallback requestCallback, object state
- )
- {
- throw new NotSupportedException ();
- }
- /// <summary>
- /// Ends an asynchronous operation to get the certificate provided by
- /// the client.
- /// </summary>
- /// <returns>
- /// A <see cref="X509Certificate2"/> that represents an X.509 certificate
- /// provided by the client.
- /// </returns>
- /// <param name="asyncResult">
- /// An <see cref="IAsyncResult"/> instance returned when the operation
- /// started.
- /// </param>
- /// <exception cref="NotSupportedException">
- /// This method is not supported.
- /// </exception>
- public X509Certificate2 EndGetClientCertificate (IAsyncResult asyncResult)
- {
- throw new NotSupportedException ();
- }
- /// <summary>
- /// Gets the certificate provided by the client.
- /// </summary>
- /// <returns>
- /// A <see cref="X509Certificate2"/> that represents an X.509 certificate
- /// provided by the client.
- /// </returns>
- /// <exception cref="NotSupportedException">
- /// This method is not supported.
- /// </exception>
- public X509Certificate2 GetClientCertificate ()
- {
- throw new NotSupportedException ();
- }
- /// <summary>
- /// Returns a string that represents the current instance.
- /// </summary>
- /// <returns>
- /// A <see cref="string"/> that contains the request line and headers
- /// included in the request.
- /// </returns>
- public override string ToString ()
- {
- var buff = new StringBuilder (64);
- var fmt = "{0} {1} HTTP/{2}\r\n";
- var headers = _headers.ToString ();
- buff
- .AppendFormat (fmt, _httpMethod, _rawUrl, _protocolVersion)
- .Append (headers);
- return buff.ToString ();
- }
- #endregion
- }
- }
|