mirror of
				https://github.com/jellyfin/jellyfin.git
				synced 2025-11-03 19:17:24 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			331 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			331 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
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;
 | 
						|
    }
 | 
						|
}
 |