diff options
Diffstat (limited to 'SocketHttpListener/Net/HttpListenerRequest.Managed.cs')
| -rw-r--r-- | SocketHttpListener/Net/HttpListenerRequest.Managed.cs | 330 |
1 files changed, 330 insertions, 0 deletions
diff --git a/SocketHttpListener/Net/HttpListenerRequest.Managed.cs b/SocketHttpListener/Net/HttpListenerRequest.Managed.cs new file mode 100644 index 000000000..47a6dfcfd --- /dev/null +++ b/SocketHttpListener/Net/HttpListenerRequest.Managed.cs @@ -0,0 +1,330 @@ +using System; +using System.Text; +using System.Collections.Specialized; +using System.Globalization; +using System.IO; +using System.Security.Authentication.ExtendedProtection; +using System.Security.Cryptography.X509Certificates; +using MediaBrowser.Model.Services; +using MediaBrowser.Model.Text; + +namespace SocketHttpListener.Net +{ + public sealed partial class HttpListenerRequest + { + private long _contentLength; + private bool _clSet; + private WebHeaderCollection _headers; + private string _method; + private Stream _inputStream; + private HttpListenerContext _context; + private bool _isChunked; + + private static byte[] s_100continue = Encoding.ASCII.GetBytes("HTTP/1.1 100 Continue\r\n\r\n"); + + internal HttpListenerRequest(HttpListenerContext context) + { + _context = context; + _headers = new WebHeaderCollection(); + _version = HttpVersion.Version10; + } + + private static readonly char[] s_separators = new char[] { ' ' }; + + internal void SetRequestLine(string req) + { + string[] parts = req.Split(s_separators, 3); + if (parts.Length != 3) + { + _context.ErrorMessage = "Invalid request line (parts)."; + return; + } + + _method = parts[0]; + foreach (char c in _method) + { + int ic = (int)c; + + if ((ic >= 'A' && ic <= 'Z') || + (ic > 32 && c < 127 && c != '(' && c != ')' && c != '<' && + c != '<' && c != '>' && c != '@' && c != ',' && c != ';' && + c != ':' && c != '\\' && c != '"' && c != '/' && c != '[' && + c != ']' && c != '?' && c != '=' && c != '{' && c != '}')) + continue; + + _context.ErrorMessage = "(Invalid verb)"; + return; + } + + _rawUrl = parts[1]; + if (parts[2].Length != 8 || !parts[2].StartsWith("HTTP/")) + { + _context.ErrorMessage = "Invalid request line (version)."; + return; + } + + try + { + _version = new Version(parts[2].Substring(5)); + } + 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(':'); + if (p == -1) + return false; + + if (p >= 10) + return false; + + return IsPredefinedScheme(s.Substring(0, p)); + } + + private static bool IsPredefinedScheme(string scheme) + { + if (scheme == null || scheme.Length < 3) + return false; + + char c = scheme[0]; + if (c == 'h') + return (scheme == UriScheme.Http || scheme == UriScheme.Https); + if (c == 'f') + return (scheme == UriScheme.File || scheme == UriScheme.Ftp); + + if (c == 'n') + { + c = scheme[1]; + if (c == 'e') + return (scheme == UriScheme.News || scheme == UriScheme.NetPipe || scheme == UriScheme.NetTcp); + if (scheme == UriScheme.Nntp) + return true; + return false; + } + if ((c == 'g' && scheme == UriScheme.Gopher) || (c == 'm' && scheme == UriScheme.Mailto)) + return true; + + return false; + } + + internal void FinishInitialization() + { + string host = UserHostName; + if (_version > HttpVersion.Version10 && (host == null || host.Length == 0)) + { + _context.ErrorMessage = "Invalid host name"; + return; + } + + string path; + Uri raw_uri = null; + if (MaybeUri(_rawUrl.ToLowerInvariant()) && Uri.TryCreate(_rawUrl, UriKind.Absolute, out raw_uri)) + path = raw_uri.PathAndQuery; + else + path = _rawUrl; + + if ((host == null || host.Length == 0)) + host = UserHostAddress; + + if (raw_uri != null) + host = raw_uri.Host; + + int colon = host.IndexOf(']') == -1 ? host.IndexOf(':') : host.LastIndexOf(':'); + if (colon >= 0) + host = host.Substring(0, colon); + + string base_uri = string.Format("{0}://{1}:{2}", RequestScheme, host, LocalEndPoint.Port); + + if (!Uri.TryCreate(base_uri + path, UriKind.Absolute, out _requestUri)) + { + _context.ErrorMessage = System.Net.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.Compare(Headers[HttpKnownHeaderNames.Expect], "100-continue", StringComparison.OrdinalIgnoreCase) == 0) + { + HttpResponseStream output = _context.Connection.GetResponseStream(); + output.InternalWrite(s_100continue, 0, s_100continue.Length); + } + } + + internal static string Unquote(String str) + { + int start = str.IndexOf('\"'); + int end = str.LastIndexOf('\"'); + if (start >= 0 && end >= 0) + str = str.Substring(start + 1, end - 1); + return str.Trim(); + } + + 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.Substring(0, colon).Trim(); + string val = header.Substring(colon + 1).Trim(); + 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; + } + } + } + + public long ContentLength64 + { + get + { + if (_isChunked) + _contentLength = -1; + + return _contentLength; + } + } + + public bool HasEntityBody => (_contentLength > 0 || _isChunked); + + public QueryParamCollection 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 System.Net.IPEndPoint LocalEndPoint => _context.Connection.LocalEndPoint; + + public System.Net.IPEndPoint RemoteEndPoint => _context.Connection.RemoteEndPoint; + + public Guid RequestTraceIdentifier { get; } = Guid.NewGuid(); + + public string ServiceName => null; + + private Uri RequestUri => _requestUri; + private bool SupportsWebSockets => true; + } +} |
