123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663 |
- using System;
- using System.Collections.Specialized;
- using System.Globalization;
- using System.IO;
- using System.Net;
- using System.Security.Cryptography.X509Certificates;
- using System.Text;
- using System.Threading.Tasks;
- using MediaBrowser.Model.Net;
- using MediaBrowser.Model.Services;
- using MediaBrowser.Model.Text;
- using SocketHttpListener.Primitives;
- namespace SocketHttpListener.Net
- {
- public sealed class HttpListenerRequest
- {
- string[] accept_types;
- Encoding content_encoding;
- long content_length;
- bool cl_set;
- CookieCollection cookies;
- WebHeaderCollection headers;
- string method;
- Stream input_stream;
- Version version;
- QueryParamCollection query_string; // check if null is ok, check if read-only, check case-sensitiveness
- string raw_url;
- Uri url;
- Uri referrer;
- string[] user_languages;
- HttpListenerContext context;
- bool is_chunked;
- bool ka_set;
- bool? _keepAlive;
- private readonly ITextEncoding _textEncoding;
- internal HttpListenerRequest(HttpListenerContext context, ITextEncoding textEncoding)
- {
- this.context = context;
- _textEncoding = textEncoding;
- headers = new WebHeaderCollection();
- version = HttpVersion.Version10;
- }
- static char[] separators = new char[] { ' ' };
- internal void SetRequestLine(string req)
- {
- string[] parts = req.Split(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;
- }
- raw_url = 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));
- if (version.Major < 1)
- throw new Exception();
- }
- catch
- {
- context.ErrorMessage = "Invalid request line (version).";
- return;
- }
- }
- void CreateQueryString(string query)
- {
- if (query == null || query.Length == 0)
- {
- query_string = new QueryParamCollection();
- return;
- }
- query_string = new QueryParamCollection();
- if (query[0] == '?')
- query = query.Substring(1);
- string[] components = query.Split('&');
- foreach (string kv in components)
- {
- int pos = kv.IndexOf('=');
- if (pos == -1)
- {
- query_string.Add(null, WebUtility.UrlDecode(kv));
- }
- else
- {
- string key = WebUtility.UrlDecode(kv.Substring(0, pos));
- string val = WebUtility.UrlDecode(kv.Substring(pos + 1));
- query_string.Add(key, val);
- }
- }
- }
- 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(raw_url.ToLowerInvariant()) && Uri.TryCreate(raw_url, UriKind.Absolute, out raw_uri))
- path = raw_uri.PathAndQuery;
- else
- path = raw_url;
- if ((host == null || host.Length == 0))
- host = UserHostAddress;
- if (raw_uri != null)
- host = raw_uri.Host;
- int colon = host.LastIndexOf(':');
- if (colon >= 0)
- host = host.Substring(0, colon);
- string base_uri = String.Format("{0}://{1}:{2}",
- (IsSecureConnection) ? (IsWebSocketRequest ? "wss" : "https") : (IsWebSocketRequest ? "ws" : "http"),
- host, LocalEndPoint.Port);
- if (!Uri.TryCreate(base_uri + path, UriKind.Absolute, out url))
- {
- context.ErrorMessage = WebUtility.HtmlEncode("Invalid url: " + base_uri + path);
- return; return;
- }
- CreateQueryString(url.Query);
- if (version >= HttpVersion.Version11)
- {
- string t_encoding = Headers["Transfer-Encoding"];
- is_chunked = (t_encoding != null && String.Compare(t_encoding, "chunked", StringComparison.OrdinalIgnoreCase) == 0);
- // 'identity' is not valid!
- if (t_encoding != null && !is_chunked)
- {
- context.Connection.SendError(null, 501);
- return;
- }
- }
- if (!is_chunked && !cl_set)
- {
- if (String.Compare(method, "POST", StringComparison.OrdinalIgnoreCase) == 0 ||
- String.Compare(method, "PUT", StringComparison.OrdinalIgnoreCase) == 0)
- {
- context.Connection.SendError(null, 411);
- return;
- }
- }
- if (String.Compare(Headers["Expect"], "100-continue", StringComparison.OrdinalIgnoreCase) == 0)
- {
- var output = (HttpResponseStream)context.Connection.GetResponseStream(true);
- var _100continue = _textEncoding.GetASCIIEncoding().GetBytes("HTTP/1.1 100 Continue\r\n\r\n");
- output.InternalWrite(_100continue, 0, _100continue.Length);
- }
- }
- 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));
- }
- //
- // Using a simple block of if's is twice as slow as the compiler generated
- // switch statement. But using this tuned code is faster than the
- // compiler generated code, with a million loops on x86-64:
- //
- // With "http": .10 vs .51 (first check)
- // with "https": .16 vs .51 (second check)
- // with "foo": .22 vs .31 (never found)
- // with "mailto": .12 vs .51 (last check)
- //
- //
- static bool IsPredefinedScheme(string scheme)
- {
- if (scheme == null || scheme.Length < 3)
- return false;
- char c = scheme[0];
- if (c == 'h')
- return (scheme == "http" || scheme == "https");
- if (c == 'f')
- return (scheme == "file" || scheme == "ftp");
- if (c == 'n')
- {
- c = scheme[1];
- if (c == 'e')
- return (scheme == "news" || scheme == "net.pipe" || scheme == "net.tcp");
- if (scheme == "nntp")
- return true;
- return false;
- }
- if ((c == 'g' && scheme == "gopher") || (c == 'm' && scheme == "mailto"))
- return true;
- return false;
- }
- 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 = "Bad Request";
- context.ErrorStatus = 400;
- return;
- }
- string name = header.Substring(0, colon).Trim();
- string val = header.Substring(colon + 1).Trim();
- string lower = name.ToLowerInvariant();
- headers.SetInternal(name, val);
- switch (lower)
- {
- case "accept-language":
- user_languages = val.Split(','); // yes, only split with a ','
- break;
- case "accept":
- accept_types = val.Split(','); // yes, only split with a ','
- break;
- case "content-length":
- try
- {
- //TODO: max. content_length?
- content_length = Int64.Parse(val.Trim());
- if (content_length < 0)
- context.ErrorMessage = "Invalid Content-Length.";
- cl_set = true;
- }
- catch
- {
- context.ErrorMessage = "Invalid Content-Length.";
- }
- break;
- case "content-type":
- {
- var contents = val.Split(';');
- foreach (var content in contents)
- {
- var tmp = content.Trim();
- if (tmp.StartsWith("charset"))
- {
- var charset = tmp.GetValue("=");
- if (charset != null && charset.Length > 0)
- {
- try
- {
- // Support upnp/dlna devices - CONTENT-TYPE: text/xml ; charset="utf-8"\r\n
- charset = charset.Trim('"');
- var index = charset.IndexOf('"');
- if (index != -1) charset = charset.Substring(0, index);
- content_encoding = Encoding.GetEncoding(charset);
- }
- catch
- {
- context.ErrorMessage = "Invalid Content-Type header: " + charset;
- }
- }
- break;
- }
- }
- }
- break;
- case "referer":
- try
- {
- referrer = new Uri(val);
- }
- catch
- {
- referrer = new Uri("http://someone.is.screwing.with.the.headers.com/");
- }
- break;
- case "cookie":
- if (cookies == null)
- cookies = new CookieCollection();
- string[] cookieStrings = val.Split(new char[] { ',', ';' });
- Cookie current = null;
- int version = 0;
- foreach (string cookieString in cookieStrings)
- {
- string str = cookieString.Trim();
- if (str.Length == 0)
- continue;
- if (str.StartsWith("$Version"))
- {
- version = Int32.Parse(Unquote(str.Substring(str.IndexOf('=') + 1)));
- }
- else if (str.StartsWith("$Path"))
- {
- if (current != null)
- current.Path = str.Substring(str.IndexOf('=') + 1).Trim();
- }
- else if (str.StartsWith("$Domain"))
- {
- if (current != null)
- current.Domain = str.Substring(str.IndexOf('=') + 1).Trim();
- }
- else if (str.StartsWith("$Port"))
- {
- if (current != null)
- current.Port = str.Substring(str.IndexOf('=') + 1).Trim();
- }
- else
- {
- if (current != null)
- {
- cookies.Add(current);
- }
- current = new Cookie();
- int idx = str.IndexOf('=');
- if (idx > 0)
- {
- current.Name = str.Substring(0, idx).Trim();
- current.Value = str.Substring(idx + 1).Trim();
- }
- else
- {
- current.Name = str.Trim();
- current.Value = String.Empty;
- }
- current.Version = version;
- }
- }
- if (current != null)
- {
- cookies.Add(current);
- }
- break;
- }
- }
- // returns true is the stream could be reused.
- internal bool FlushInput()
- {
- if (!HasEntityBody)
- return true;
- int length = 2048;
- if (content_length > 0)
- length = (int)Math.Min(content_length, (long)length);
- byte[] bytes = new byte[length];
- while (true)
- {
- // TODO: test if MS has a timeout when doing this
- try
- {
- var task = InputStream.ReadAsync(bytes, 0, length);
- var result = Task.WaitAll(new [] { task }, 1000);
- if (!result)
- {
- return false;
- }
- if (task.Result <= 0)
- {
- return true;
- }
- }
- catch (ObjectDisposedException e)
- {
- input_stream = null;
- return true;
- }
- catch
- {
- return false;
- }
- }
- }
- public string[] AcceptTypes
- {
- get { return accept_types; }
- }
- public int ClientCertificateError
- {
- get
- {
- HttpConnection cnc = context.Connection;
- //if (cnc.ClientCertificate == null)
- // throw new InvalidOperationException("No client certificate");
- //int[] errors = cnc.ClientCertificateErrors;
- //if (errors != null && errors.Length > 0)
- // return errors[0];
- return 0;
- }
- }
- public Encoding ContentEncoding
- {
- get
- {
- if (content_encoding == null)
- content_encoding = _textEncoding.GetDefaultEncoding();
- return content_encoding;
- }
- }
- public long ContentLength64
- {
- get { return is_chunked ? -1 : content_length; }
- }
- public string ContentType
- {
- get { return headers["content-type"]; }
- }
- public CookieCollection Cookies
- {
- get
- {
- // TODO: check if the collection is read-only
- if (cookies == null)
- cookies = new CookieCollection();
- return cookies;
- }
- }
- public bool HasEntityBody
- {
- get { return (content_length > 0 || is_chunked); }
- }
- public QueryParamCollection Headers
- {
- get { return headers; }
- }
- public string HttpMethod
- {
- get { return method; }
- }
- public Stream InputStream
- {
- get
- {
- if (input_stream == null)
- {
- if (is_chunked || content_length > 0)
- input_stream = context.Connection.GetRequestStream(is_chunked, content_length);
- else
- input_stream = Stream.Null;
- }
- return input_stream;
- }
- }
- public bool IsAuthenticated
- {
- get { return false; }
- }
- public bool IsLocal
- {
- get
- {
- var remoteEndPoint = RemoteEndPoint;
- return remoteEndPoint.Address.Equals(IPAddress.Loopback) ||
- remoteEndPoint.Address.Equals(IPAddress.IPv6Loopback) ||
- LocalEndPoint.Address.Equals(remoteEndPoint.Address);
- }
- }
- public bool IsSecureConnection
- {
- get { return context.Connection.IsSecure; }
- }
- public bool KeepAlive
- {
- get
- {
- if (!_keepAlive.HasValue)
- {
- string header = Headers["Proxy-Connection"];
- if (string.IsNullOrEmpty(header))
- {
- header = Headers["Connection"];
- }
- if (string.IsNullOrEmpty(header))
- {
- if (ProtocolVersion >= HttpVersion.Version11)
- {
- _keepAlive = true;
- }
- else
- {
- header = Headers["Keep-Alive"];
- _keepAlive = !string.IsNullOrEmpty(header);
- }
- }
- else
- {
- header = header.ToLower(CultureInfo.InvariantCulture);
- _keepAlive =
- header.IndexOf("close", StringComparison.OrdinalIgnoreCase) < 0 ||
- header.IndexOf("keep-alive", StringComparison.OrdinalIgnoreCase) >= 0;
- }
- }
- return _keepAlive.Value;
- }
- }
- public IPEndPoint LocalEndPoint
- {
- get { return context.Connection.LocalEndPoint; }
- }
- public Version ProtocolVersion
- {
- get { return version; }
- }
- public QueryParamCollection QueryString
- {
- get { return query_string; }
- }
- public string RawUrl
- {
- get { return raw_url; }
- }
- public IPEndPoint RemoteEndPoint
- {
- get { return context.Connection.RemoteEndPoint; }
- }
- public Guid RequestTraceIdentifier
- {
- get { return Guid.Empty; }
- }
- public Uri Url
- {
- get { return url; }
- }
- public Uri UrlReferrer
- {
- get { return referrer; }
- }
- public string UserAgent
- {
- get { return headers["user-agent"]; }
- }
- public string UserHostAddress
- {
- get { return LocalEndPoint.ToString(); }
- }
- public string UserHostName
- {
- get { return headers["host"]; }
- }
- public string[] UserLanguages
- {
- get { return user_languages; }
- }
- public string ServiceName
- {
- get
- {
- return null;
- }
- }
- private bool _websocketRequestWasSet;
- private bool _websocketRequest;
- /// <summary>
- /// Gets a value indicating whether the request is a WebSocket connection request.
- /// </summary>
- /// <value>
- /// <c>true</c> if the request is a WebSocket connection request; otherwise, <c>false</c>.
- /// </value>
- public bool IsWebSocketRequest
- {
- get
- {
- if (!_websocketRequestWasSet)
- {
- _websocketRequest = method == "GET" &&
- version > HttpVersion.Version10 &&
- headers.Contains("Upgrade", "websocket") &&
- headers.Contains("Connection", "Upgrade");
- _websocketRequestWasSet = true;
- }
- return _websocketRequest;
- }
- }
- }
- }
|