|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
//
// System.Net.HttpListenerRequest
//
// Authors:
// Gonzalo Paniagua Javier (gonzalo.mono@gmail.com)
// Marek Safar (marek.safar@gmail.com)
//
// Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
// Copyright (c) 2011-2012 Xamarin, Inc. (http://xamarin.com)
//
// 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.
//
using System.Buffers;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Security.Authentication.ExtendedProtection;
using System.Security.Cryptography.X509Certificates;
using System.Text;
namespace System.Net
{
public sealed partial class HttpListenerRequest
{
private sealed class Context : TransportContext
{
public override ChannelBinding? GetChannelBinding(ChannelBindingKind kind)
{
if (kind != ChannelBindingKind.Endpoint)
{
throw new NotSupportedException(SR.Format(SR.net_listener_invalid_cbt_type, kind.ToString()));
}
return null;
}
}
private long _contentLength;
private bool _clSet;
private readonly WebHeaderCollection _headers;
private string? _method;
private Stream? _inputStream;
private readonly HttpListenerContext _context;
private bool _isChunked;
private static readonly SearchValues<char> s_validMethodChars = SearchValues.Create("!#$%&'*+-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ^_`abcdefghijklmnopqrstuvwxyz|~");
private static readonly byte[] s_100continue = "HTTP/1.1 100 Continue\r\n\r\n"u8.ToArray();
internal HttpListenerRequest(HttpListenerContext context)
{
_context = context;
_headers = new WebHeaderCollection();
_version = HttpVersion.Version10;
}
internal void SetRequestLine(string req)
{
Span<Range> parts = stackalloc Range[3];
if (req.AsSpan().Split(parts, ' ') != 3)
{
_context.ErrorMessage = "Invalid request line (parts).";
return;
}
_method = req[parts[0]];
if (_method.AsSpan().ContainsAnyExcept(s_validMethodChars))
{
_context.ErrorMessage = "(Invalid verb)";
return;
}
_rawUrl = req[parts[1]];
ReadOnlySpan<char> version = req.AsSpan(parts[2]);
if (version.Length != 8 || !version.StartsWith("HTTP/", StringComparison.Ordinal))
{
_context.ErrorMessage = "Invalid request line (version).";
return;
}
try
{
_version = Version.Parse(version.Slice("HTTP/".Length));
}
catch
{
_context.ErrorMessage = "Invalid request line (version).";
return;
}
if (_version.Major < 1)
{
_context.ErrorMessage = "Invalid request line (version).";
return;
}
if (_version.Major > 1)
{
_context.ErrorStatus = (int)HttpStatusCode.HttpVersionNotSupported;
_context.ErrorMessage = HttpStatusDescription.Get(HttpStatusCode.HttpVersionNotSupported);
return;
}
}
private static bool MaybeUri(string s)
{
int p = s.IndexOf(':');
return (uint)p < 10 && s.AsSpan(0, p) is
UriScheme.Http or
UriScheme.Https or
UriScheme.File or
UriScheme.Ftp or
UriScheme.News or
UriScheme.NetPipe or
UriScheme.NetTcp or
UriScheme.Nntp or
UriScheme.Gopher or
UriScheme.Mailto;
}
internal void FinishInitialization()
{
ReadOnlySpan<char> host = UserHostName;
if (_version > HttpVersion.Version10 && host.IsEmpty)
{
_context.ErrorMessage = "Invalid host name";
return;
}
string path;
Uri? raw_uri = null;
Debug.Assert(_rawUrl != null);
if (MaybeUri(_rawUrl!.ToLowerInvariant()) && Uri.TryCreate(_rawUrl, UriKind.Absolute, out raw_uri))
path = raw_uri.PathAndQuery;
else
path = _rawUrl;
if (host.IsEmpty)
host = UserHostAddress;
if (raw_uri != null)
host = raw_uri.Host;
int colon = host.IndexOf(':');
if (colon >= 0)
host = host.Slice(0, colon);
string base_uri = $"{RequestScheme}://{host}:{LocalEndPoint!.Port}";
if (!Uri.TryCreate(base_uri + path, UriKind.Absolute, out _requestUri))
{
_context.ErrorMessage = WebUtility.HtmlEncode("Invalid url: " + base_uri + path);
return;
}
_requestUri = HttpListenerRequestUriBuilder.GetRequestUri(_rawUrl, _requestUri.Scheme,
_requestUri.Authority, _requestUri.LocalPath, _requestUri.Query);
if (_version >= HttpVersion.Version11)
{
string? t_encoding = Headers[HttpKnownHeaderNames.TransferEncoding];
_isChunked = (t_encoding != null && string.Equals(t_encoding, "chunked", StringComparison.OrdinalIgnoreCase));
// 'identity' is not valid!
if (t_encoding != null && !_isChunked)
{
_context.Connection.SendError(null, 501);
return;
}
}
if (!_isChunked && !_clSet)
{
if (string.Equals(_method, "POST", StringComparison.OrdinalIgnoreCase) ||
string.Equals(_method, "PUT", StringComparison.OrdinalIgnoreCase))
{
_context.Connection.SendError(null, 411);
return;
}
}
if (string.Equals(Headers[HttpKnownHeaderNames.Expect], "100-continue", StringComparison.OrdinalIgnoreCase))
{
HttpResponseStream output = _context.Connection.GetResponseStream();
output.InternalWrite(s_100continue, 0, s_100continue.Length);
}
}
internal void AddHeader(string header)
{
int colon = header.IndexOf(':');
if (colon == -1 || colon == 0)
{
_context.ErrorMessage = HttpStatusDescription.Get(400);
_context.ErrorStatus = 400;
return;
}
string name = header.AsSpan(0, colon).Trim().ToString();
string val = header.AsSpan(colon + 1).Trim().ToString();
if (name.Equals("content-length", StringComparison.OrdinalIgnoreCase))
{
// To match Windows behavior:
// Content lengths >= 0 and <= long.MaxValue are accepted as is.
// Content lengths > long.MaxValue and <= ulong.MaxValue are treated as 0.
// Content lengths < 0 cause the requests to fail.
// Other input is a failure, too.
long parsedContentLength =
ulong.TryParse(val, out ulong parsedUlongContentLength) ? (parsedUlongContentLength <= long.MaxValue ? (long)parsedUlongContentLength : 0) :
long.Parse(val);
if (parsedContentLength < 0 || (_clSet && parsedContentLength != _contentLength))
{
_context.ErrorMessage = "Invalid Content-Length.";
}
else
{
_contentLength = parsedContentLength;
_clSet = true;
}
}
else if (name.Equals("transfer-encoding", StringComparison.OrdinalIgnoreCase))
{
if (Headers[HttpKnownHeaderNames.TransferEncoding] != null)
{
_context.ErrorStatus = (int)HttpStatusCode.NotImplemented;
_context.ErrorMessage = HttpStatusDescription.Get(HttpStatusCode.NotImplemented);
}
}
if (_context.ErrorMessage == null)
{
_headers.Set(name, val);
}
}
// returns true is the stream could be reused.
internal bool FlushInput()
{
if (!HasEntityBody)
return true;
int length = 2048;
if (_contentLength > 0)
length = (int)Math.Min(_contentLength, (long)length);
byte[] bytes = new byte[length];
while (true)
{
try
{
IAsyncResult ares = InputStream.BeginRead(bytes, 0, length, null, null);
if (!ares.IsCompleted && !ares.AsyncWaitHandle.WaitOne(1000))
return false;
if (InputStream.EndRead(ares) <= 0)
return true;
}
catch (ObjectDisposedException)
{
_inputStream = null;
return true;
}
catch
{
return false;
}
}
}
private X509Certificate2? GetClientCertificateCore() => ClientCertificate = _context.Connection.ClientCertificate;
private int GetClientCertificateErrorCore()
{
HttpConnection cnc = _context.Connection;
if (cnc.ClientCertificate == null)
return 0;
int[]? errors = cnc.ClientCertificateErrors;
if (errors != null && errors.Length > 0)
return errors[0];
return 0;
}
public long ContentLength64
{
get
{
if (_isChunked)
_contentLength = -1;
return _contentLength;
}
}
public bool HasEntityBody => (_contentLength > 0 || _isChunked);
public NameValueCollection Headers => _headers;
public string? HttpMethod => _method;
public Stream InputStream
{
get
{
if (_inputStream == null)
{
if (_isChunked || _contentLength > 0)
_inputStream = _context.Connection.GetRequestStream(_isChunked, _contentLength);
else
_inputStream = Stream.Null;
}
return _inputStream;
}
}
public bool IsAuthenticated => false;
public bool IsSecureConnection => _context.Connection.IsSecure;
public IPEndPoint? LocalEndPoint => _context.Connection.LocalEndPoint;
public IPEndPoint? RemoteEndPoint => _context.Connection.RemoteEndPoint;
public Guid RequestTraceIdentifier { get; } = Guid.NewGuid();
private GetClientCertificateAsyncResult BeginGetClientCertificateCore(AsyncCallback? requestCallback, object? state)
{
var asyncResult = new GetClientCertificateAsyncResult(this, state, requestCallback);
// The certificate is already retrieved by the time this method is called. GetClientCertificateCore() evaluates to
// a simple member access, so this will always complete immediately.
ClientCertState = ListenerClientCertState.Completed;
asyncResult.InvokeCallback(GetClientCertificateCore());
return asyncResult;
}
public X509Certificate2? EndGetClientCertificate(IAsyncResult asyncResult)
{
ArgumentNullException.ThrowIfNull(asyncResult);
GetClientCertificateAsyncResult? clientCertAsyncResult = asyncResult as GetClientCertificateAsyncResult;
if (clientCertAsyncResult == null || clientCertAsyncResult.AsyncObject != this)
{
throw new ArgumentException(SR.net_io_invalidasyncresult, nameof(asyncResult));
}
if (clientCertAsyncResult.EndCalled)
{
throw new InvalidOperationException(SR.Format(SR.net_io_invalidendcall, nameof(EndGetClientCertificate)));
}
clientCertAsyncResult.EndCalled = true;
return (X509Certificate2?)clientCertAsyncResult.Result;
}
public string? ServiceName => null;
public TransportContext TransportContext => new Context();
private Uri? RequestUri => _requestUri;
private static bool SupportsWebSockets => true;
private sealed class GetClientCertificateAsyncResult : LazyAsyncResult
{
public GetClientCertificateAsyncResult(object myObject, object? myState, AsyncCallback? myCallBack) : base(myObject, myState, myCallBack) { }
}
}
}
|