diff options
Diffstat (limited to 'Emby.Server.Implementations/HttpServer/SocketSharp')
| -rw-r--r-- | Emby.Server.Implementations/HttpServer/SocketSharp/RequestMono.cs | 846 | ||||
| -rw-r--r-- | Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs | 620 |
2 files changed, 1466 insertions, 0 deletions
diff --git a/Emby.Server.Implementations/HttpServer/SocketSharp/RequestMono.cs b/Emby.Server.Implementations/HttpServer/SocketSharp/RequestMono.cs new file mode 100644 index 000000000..ec14c32c8 --- /dev/null +++ b/Emby.Server.Implementations/HttpServer/SocketSharp/RequestMono.cs @@ -0,0 +1,846 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Globalization; +using System.IO; +using System.Net; +using System.Text; +using System.Threading.Tasks; +using MediaBrowser.Model.Services; + +namespace Emby.Server.Implementations.HttpServer.SocketSharp +{ + public partial class WebSocketSharpRequest : IHttpRequest + { + static internal string GetParameter(string header, string attr) + { + int ap = header.IndexOf(attr); + if (ap == -1) + return null; + + ap += attr.Length; + if (ap >= header.Length) + return null; + + char ending = header[ap]; + if (ending != '"') + ending = ' '; + + int end = header.IndexOf(ending, ap + 1); + if (end == -1) + return ending == '"' ? null : header.Substring(ap); + + return header.Substring(ap + 1, end - ap - 1); + } + + async Task LoadMultiPart() + { + string boundary = GetParameter(ContentType, "; boundary="); + if (boundary == null) + return; + + using (var requestStream = GetSubStream(InputStream, _memoryStreamProvider)) + { + //DB: 30/01/11 - Hack to get around non-seekable stream and received HTTP request + //Not ending with \r\n? + var ms = _memoryStreamProvider.CreateNew(32 * 1024); + await requestStream.CopyToAsync(ms).ConfigureAwait(false); + + var input = ms; + ms.WriteByte((byte)'\r'); + ms.WriteByte((byte)'\n'); + + input.Position = 0; + + //Uncomment to debug + //var content = new StreamReader(ms).ReadToEnd(); + //Console.WriteLine(boundary + "::" + content); + //input.Position = 0; + + var multi_part = new HttpMultipart(input, boundary, ContentEncoding); + + HttpMultipart.Element e; + while ((e = multi_part.ReadNextElement()) != null) + { + if (e.Filename == null) + { + byte[] copy = new byte[e.Length]; + + input.Position = e.Start; + input.Read(copy, 0, (int)e.Length); + + form.Add(e.Name, (e.Encoding ?? ContentEncoding).GetString(copy, 0, copy.Length)); + } + else + { + // + // We use a substream, as in 2.x we will support large uploads streamed to disk, + // + HttpPostedFile sub = new HttpPostedFile(e.Filename, e.ContentType, input, e.Start, e.Length); + files[e.Name] = sub; + } + } + } + } + + public QueryParamCollection Form + { + get + { + if (form == null) + { + form = new WebROCollection(); + files = new Dictionary<string, HttpPostedFile>(); + + if (IsContentType("multipart/form-data", true)) + { + var task = LoadMultiPart(); + Task.WaitAll(task); + } + else if (IsContentType("application/x-www-form-urlencoded", true)) + { + var task = LoadWwwForm(); + Task.WaitAll(task); + } + + form.Protect(); + } + +#if NET_4_0 + if (validateRequestNewMode && !checked_form) { + // Setting this before calling the validator prevents + // possible endless recursion + checked_form = true; + ValidateNameValueCollection ("Form", query_string_nvc, RequestValidationSource.Form); + } else +#endif + if (validate_form && !checked_form) + { + checked_form = true; + ValidateNameValueCollection("Form", form); + } + + return form; + } + } + + public string Accept + { + get + { + return string.IsNullOrEmpty(request.Headers["Accept"]) ? null : request.Headers["Accept"]; + } + } + + public string Authorization + { + get + { + return string.IsNullOrEmpty(request.Headers["Authorization"]) ? null : request.Headers["Authorization"]; + } + } + + protected bool validate_cookies, validate_query_string, validate_form; + protected bool checked_cookies, checked_query_string, checked_form; + + static void ThrowValidationException(string name, string key, string value) + { + string v = "\"" + value + "\""; + if (v.Length > 20) + v = v.Substring(0, 16) + "...\""; + + string msg = String.Format("A potentially dangerous Request.{0} value was " + + "detected from the client ({1}={2}).", name, key, v); + + throw new Exception(msg); + } + + static void ValidateNameValueCollection(string name, QueryParamCollection coll) + { + if (coll == null) + return; + + foreach (var pair in coll) + { + var key = pair.Name; + var val = pair.Value; + if (val != null && val.Length > 0 && IsInvalidString(val)) + ThrowValidationException(name, key, val); + } + } + + internal static bool IsInvalidString(string val) + { + int validationFailureIndex; + + return IsInvalidString(val, out validationFailureIndex); + } + + internal static bool IsInvalidString(string val, out int validationFailureIndex) + { + validationFailureIndex = 0; + + int len = val.Length; + if (len < 2) + return false; + + char current = val[0]; + for (int idx = 1; idx < len; idx++) + { + char next = val[idx]; + // See http://secunia.com/advisories/14325 + if (current == '<' || current == '\xff1c') + { + if (next == '!' || next < ' ' + || (next >= 'a' && next <= 'z') + || (next >= 'A' && next <= 'Z')) + { + validationFailureIndex = idx - 1; + return true; + } + } + else if (current == '&' && next == '#') + { + validationFailureIndex = idx - 1; + return true; + } + + current = next; + } + + return false; + } + + public void ValidateInput() + { + validate_cookies = true; + validate_query_string = true; + validate_form = true; + } + + bool IsContentType(string ct, bool starts_with) + { + if (ct == null || ContentType == null) return false; + + if (starts_with) + return StrUtils.StartsWith(ContentType, ct, true); + + return string.Equals(ContentType, ct, StringComparison.OrdinalIgnoreCase); + } + + async Task LoadWwwForm() + { + using (Stream input = GetSubStream(InputStream, _memoryStreamProvider)) + { + using (var ms = _memoryStreamProvider.CreateNew()) + { + await input.CopyToAsync(ms).ConfigureAwait(false); + ms.Position = 0; + + using (StreamReader s = new StreamReader(ms, ContentEncoding)) + { + StringBuilder key = new StringBuilder(); + StringBuilder value = new StringBuilder(); + int c; + + while ((c = s.Read()) != -1) + { + if (c == '=') + { + value.Length = 0; + while ((c = s.Read()) != -1) + { + if (c == '&') + { + AddRawKeyValue(key, value); + break; + } + else + value.Append((char)c); + } + if (c == -1) + { + AddRawKeyValue(key, value); + return; + } + } + else if (c == '&') + AddRawKeyValue(key, value); + else + key.Append((char)c); + } + if (c == -1) + AddRawKeyValue(key, value); + } + } + } + } + + void AddRawKeyValue(StringBuilder key, StringBuilder value) + { + string decodedKey = WebUtility.UrlDecode(key.ToString()); + form.Add(decodedKey, + WebUtility.UrlDecode(value.ToString())); + + key.Length = 0; + value.Length = 0; + } + + WebROCollection form; + + Dictionary<string, HttpPostedFile> files; + + class WebROCollection : QueryParamCollection + { + bool got_id; + int id; + + public bool GotID + { + get { return got_id; } + } + + public int ID + { + get { return id; } + set + { + got_id = true; + id = value; + } + } + public void Protect() + { + //IsReadOnly = true; + } + + public void Unprotect() + { + //IsReadOnly = false; + } + + public override string ToString() + { + StringBuilder result = new StringBuilder(); + foreach (var pair in this) + { + if (result.Length > 0) + result.Append('&'); + + var key = pair.Name; + if (key != null && key.Length > 0) + { + result.Append(key); + result.Append('='); + } + result.Append(pair.Value); + } + + return result.ToString(); + } + } + + public sealed class HttpPostedFile + { + string name; + string content_type; + Stream stream; + + class ReadSubStream : Stream + { + Stream s; + long offset; + long end; + long position; + + public ReadSubStream(Stream s, long offset, long length) + { + this.s = s; + this.offset = offset; + this.end = offset + length; + position = offset; + } + + public override void Flush() + { + } + + public override int Read(byte[] buffer, int dest_offset, int count) + { + if (buffer == null) + throw new ArgumentNullException("buffer"); + + if (dest_offset < 0) + throw new ArgumentOutOfRangeException("dest_offset", "< 0"); + + if (count < 0) + throw new ArgumentOutOfRangeException("count", "< 0"); + + int len = buffer.Length; + if (dest_offset > len) + throw new ArgumentException("destination offset is beyond array size"); + // reordered to avoid possible integer overflow + if (dest_offset > len - count) + throw new ArgumentException("Reading would overrun buffer"); + + if (count > end - position) + count = (int)(end - position); + + if (count <= 0) + return 0; + + s.Position = position; + int result = s.Read(buffer, dest_offset, count); + if (result > 0) + position += result; + else + position = end; + + return result; + } + + public override int ReadByte() + { + if (position >= end) + return -1; + + s.Position = position; + int result = s.ReadByte(); + if (result < 0) + position = end; + else + position++; + + return result; + } + + public override long Seek(long d, SeekOrigin origin) + { + long real; + switch (origin) + { + case SeekOrigin.Begin: + real = offset + d; + break; + case SeekOrigin.End: + real = end + d; + break; + case SeekOrigin.Current: + real = position + d; + break; + default: + throw new ArgumentException(); + } + + long virt = real - offset; + if (virt < 0 || virt > Length) + throw new ArgumentException(); + + position = s.Seek(real, SeekOrigin.Begin); + return position; + } + + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotSupportedException(); + } + + public override bool CanRead + { + get { return true; } + } + public override bool CanSeek + { + get { return true; } + } + public override bool CanWrite + { + get { return false; } + } + + public override long Length + { + get { return end - offset; } + } + + public override long Position + { + get + { + return position - offset; + } + set + { + if (value > Length) + throw new ArgumentOutOfRangeException(); + + position = Seek(value, SeekOrigin.Begin); + } + } + } + + internal HttpPostedFile(string name, string content_type, Stream base_stream, long offset, long length) + { + this.name = name; + this.content_type = content_type; + this.stream = new ReadSubStream(base_stream, offset, length); + } + + public string ContentType + { + get + { + return content_type; + } + } + + public int ContentLength + { + get + { + return (int)stream.Length; + } + } + + public string FileName + { + get + { + return name; + } + } + + public Stream InputStream + { + get + { + return stream; + } + } + } + + class Helpers + { + public static readonly CultureInfo InvariantCulture = CultureInfo.InvariantCulture; + } + + internal sealed class StrUtils + { + public static bool StartsWith(string str1, string str2, bool ignore_case) + { + if (string.IsNullOrWhiteSpace(str1)) + { + return false; + } + + var comparison = ignore_case ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; + return str1.IndexOf(str2, comparison) == 0; + } + + public static bool EndsWith(string str1, string str2, bool ignore_case) + { + int l2 = str2.Length; + if (l2 == 0) + return true; + + int l1 = str1.Length; + if (l2 > l1) + return false; + + var comparison = ignore_case ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; + return str1.IndexOf(str2, comparison) == str1.Length - str2.Length - 1; + } + } + + class HttpMultipart + { + + public class Element + { + public string ContentType; + public string Name; + public string Filename; + public Encoding Encoding; + public long Start; + public long Length; + + public override string ToString() + { + return "ContentType " + ContentType + ", Name " + Name + ", Filename " + Filename + ", Start " + + Start.ToString() + ", Length " + Length.ToString(); + } + } + + Stream data; + string boundary; + byte[] boundary_bytes; + byte[] buffer; + bool at_eof; + Encoding encoding; + StringBuilder sb; + + const byte HYPHEN = (byte)'-', LF = (byte)'\n', CR = (byte)'\r'; + + // See RFC 2046 + // In the case of multipart entities, in which one or more different + // sets of data are combined in a single body, a "multipart" media type + // field must appear in the entity's header. The body must then contain + // one or more body parts, each preceded by a boundary delimiter line, + // and the last one followed by a closing boundary delimiter line. + // After its boundary delimiter line, each body part then consists of a + // header area, a blank line, and a body area. Thus a body part is + // similar to an RFC 822 message in syntax, but different in meaning. + + public HttpMultipart(Stream data, string b, Encoding encoding) + { + this.data = data; + //DB: 30/01/11: cannot set or read the Position in HttpListener in Win.NET + //var ms = new MemoryStream(32 * 1024); + //data.CopyTo(ms); + //this.data = ms; + + boundary = b; + boundary_bytes = encoding.GetBytes(b); + buffer = new byte[boundary_bytes.Length + 2]; // CRLF or '--' + this.encoding = encoding; + sb = new StringBuilder(); + } + + string ReadLine() + { + // CRLF or LF are ok as line endings. + bool got_cr = false; + int b = 0; + sb.Length = 0; + while (true) + { + b = data.ReadByte(); + if (b == -1) + { + return null; + } + + if (b == LF) + { + break; + } + got_cr = b == CR; + sb.Append((char)b); + } + + if (got_cr) + sb.Length--; + + return sb.ToString(); + + } + + static string GetContentDispositionAttribute(string l, string name) + { + int idx = l.IndexOf(name + "=\""); + if (idx < 0) + return null; + int begin = idx + name.Length + "=\"".Length; + int end = l.IndexOf('"', begin); + if (end < 0) + return null; + if (begin == end) + return ""; + return l.Substring(begin, end - begin); + } + + string GetContentDispositionAttributeWithEncoding(string l, string name) + { + int idx = l.IndexOf(name + "=\""); + if (idx < 0) + return null; + int begin = idx + name.Length + "=\"".Length; + int end = l.IndexOf('"', begin); + if (end < 0) + return null; + if (begin == end) + return ""; + + string temp = l.Substring(begin, end - begin); + byte[] source = new byte[temp.Length]; + for (int i = temp.Length - 1; i >= 0; i--) + source[i] = (byte)temp[i]; + + return encoding.GetString(source, 0, source.Length); + } + + bool ReadBoundary() + { + try + { + string line = ReadLine(); + while (line == "") + line = ReadLine(); + if (line[0] != '-' || line[1] != '-') + return false; + + if (!StrUtils.EndsWith(line, boundary, false)) + return true; + } + catch + { + } + + return false; + } + + string ReadHeaders() + { + string s = ReadLine(); + if (s == "") + return null; + + return s; + } + + bool CompareBytes(byte[] orig, byte[] other) + { + for (int i = orig.Length - 1; i >= 0; i--) + if (orig[i] != other[i]) + return false; + + return true; + } + + long MoveToNextBoundary() + { + long retval = 0; + bool got_cr = false; + + int state = 0; + int c = data.ReadByte(); + while (true) + { + if (c == -1) + return -1; + + if (state == 0 && c == LF) + { + retval = data.Position - 1; + if (got_cr) + retval--; + state = 1; + c = data.ReadByte(); + } + else if (state == 0) + { + got_cr = c == CR; + c = data.ReadByte(); + } + else if (state == 1 && c == '-') + { + c = data.ReadByte(); + if (c == -1) + return -1; + + if (c != '-') + { + state = 0; + got_cr = false; + continue; // no ReadByte() here + } + + int nread = data.Read(buffer, 0, buffer.Length); + int bl = buffer.Length; + if (nread != bl) + return -1; + + if (!CompareBytes(boundary_bytes, buffer)) + { + state = 0; + data.Position = retval + 2; + if (got_cr) + { + data.Position++; + got_cr = false; + } + c = data.ReadByte(); + continue; + } + + if (buffer[bl - 2] == '-' && buffer[bl - 1] == '-') + { + at_eof = true; + } + else if (buffer[bl - 2] != CR || buffer[bl - 1] != LF) + { + state = 0; + data.Position = retval + 2; + if (got_cr) + { + data.Position++; + got_cr = false; + } + c = data.ReadByte(); + continue; + } + data.Position = retval + 2; + if (got_cr) + data.Position++; + break; + } + else + { + // state == 1 + state = 0; // no ReadByte() here + } + } + + return retval; + } + + public Element ReadNextElement() + { + if (at_eof || ReadBoundary()) + return null; + + Element elem = new Element(); + string header; + while ((header = ReadHeaders()) != null) + { + if (StrUtils.StartsWith(header, "Content-Disposition:", true)) + { + elem.Name = GetContentDispositionAttribute(header, "name"); + elem.Filename = StripPath(GetContentDispositionAttributeWithEncoding(header, "filename")); + } + else if (StrUtils.StartsWith(header, "Content-Type:", true)) + { + elem.ContentType = header.Substring("Content-Type:".Length).Trim(); + elem.Encoding = GetEncoding(elem.ContentType); + } + } + + long start = 0; + start = data.Position; + elem.Start = start; + long pos = MoveToNextBoundary(); + if (pos == -1) + return null; + + elem.Length = pos - start; + return elem; + } + + static string StripPath(string path) + { + if (path == null || path.Length == 0) + return path; + + if (path.IndexOf(":\\") != 1 && !path.StartsWith("\\\\")) + return path; + return path.Substring(path.LastIndexOf('\\') + 1); + } + } + + } +} diff --git a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs new file mode 100644 index 000000000..b3fcde745 --- /dev/null +++ b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs @@ -0,0 +1,620 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Text; +using Emby.Server.Implementations.HttpServer; +using Emby.Server.Implementations.HttpServer.SocketSharp; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Services; +using SocketHttpListener.Net; +using IHttpFile = MediaBrowser.Model.Services.IHttpFile; +using IHttpRequest = MediaBrowser.Model.Services.IHttpRequest; +using IHttpResponse = MediaBrowser.Model.Services.IHttpResponse; +using IResponse = MediaBrowser.Model.Services.IResponse; + +namespace Emby.Server.Implementations.HttpServer.SocketSharp +{ + public partial class WebSocketSharpRequest : IHttpRequest + { + private readonly HttpListenerRequest request; + private readonly IHttpResponse response; + private readonly IMemoryStreamFactory _memoryStreamProvider; + + public WebSocketSharpRequest(HttpListenerContext httpContext, string operationName, ILogger logger, IMemoryStreamFactory memoryStreamProvider) + { + this.OperationName = operationName; + _memoryStreamProvider = memoryStreamProvider; + this.request = httpContext.Request; + this.response = new WebSocketSharpResponse(logger, httpContext.Response, this); + } + + public HttpListenerRequest HttpRequest + { + get { return request; } + } + + public object OriginalRequest + { + get { return request; } + } + + public IResponse Response + { + get { return response; } + } + + public IHttpResponse HttpResponse + { + get { return response; } + } + + public string OperationName { get; set; } + + public object Dto { get; set; } + + public string RawUrl + { + get { return request.RawUrl; } + } + + public string AbsoluteUri + { + get { return request.Url.AbsoluteUri.TrimEnd('/'); } + } + + public string UserHostAddress + { + get { return request.UserHostAddress; } + } + + public string XForwardedFor + { + get + { + return String.IsNullOrEmpty(request.Headers["X-Forwarded-For"]) ? null : request.Headers["X-Forwarded-For"]; + } + } + + public int? XForwardedPort + { + get + { + return string.IsNullOrEmpty(request.Headers["X-Forwarded-Port"]) ? (int?)null : int.Parse(request.Headers["X-Forwarded-Port"]); + } + } + + public string XForwardedProtocol + { + get + { + return string.IsNullOrEmpty(request.Headers["X-Forwarded-Proto"]) ? null : request.Headers["X-Forwarded-Proto"]; + } + } + + public string XRealIp + { + get + { + return String.IsNullOrEmpty(request.Headers["X-Real-IP"]) ? null : request.Headers["X-Real-IP"]; + } + } + + private string remoteIp; + public string RemoteIp + { + get + { + return remoteIp ?? + (remoteIp = (CheckBadChars(XForwardedFor)) ?? + (NormalizeIp(CheckBadChars(XRealIp)) ?? + (request.RemoteEndPoint != null ? NormalizeIp(request.RemoteEndPoint.IpAddress.ToString()) : null))); + } + } + + private static readonly char[] HttpTrimCharacters = new char[] { (char)0x09, (char)0xA, (char)0xB, (char)0xC, (char)0xD, (char)0x20 }; + + // + // CheckBadChars - throws on invalid chars to be not found in header name/value + // + internal static string CheckBadChars(string name) + { + if (name == null || name.Length == 0) + { + return name; + } + + // VALUE check + //Trim spaces from both ends + name = name.Trim(HttpTrimCharacters); + + //First, check for correctly formed multi-line value + //Second, check for absenece of CTL characters + int crlf = 0; + for (int i = 0; i < name.Length; ++i) + { + char c = (char)(0x000000ff & (uint)name[i]); + switch (crlf) + { + case 0: + if (c == '\r') + { + crlf = 1; + } + else if (c == '\n') + { + // Technically this is bad HTTP. But it would be a breaking change to throw here. + // Is there an exploit? + crlf = 2; + } + else if (c == 127 || (c < ' ' && c != '\t')) + { + throw new ArgumentException("net_WebHeaderInvalidControlChars"); + } + break; + + case 1: + if (c == '\n') + { + crlf = 2; + break; + } + throw new ArgumentException("net_WebHeaderInvalidCRLFChars"); + + case 2: + if (c == ' ' || c == '\t') + { + crlf = 0; + break; + } + throw new ArgumentException("net_WebHeaderInvalidCRLFChars"); + } + } + if (crlf != 0) + { + throw new ArgumentException("net_WebHeaderInvalidCRLFChars"); + } + return name; + } + + internal static bool ContainsNonAsciiChars(string token) + { + for (int i = 0; i < token.Length; ++i) + { + if ((token[i] < 0x20) || (token[i] > 0x7e)) + { + return true; + } + } + return false; + } + + private string NormalizeIp(string ip) + { + if (!string.IsNullOrWhiteSpace(ip)) + { + // Handle ipv4 mapped to ipv6 + const string srch = "::ffff:"; + var index = ip.IndexOf(srch, StringComparison.OrdinalIgnoreCase); + if (index == 0) + { + ip = ip.Substring(srch.Length); + } + } + + return ip; + } + + public bool IsSecureConnection + { + get { return request.IsSecureConnection || XForwardedProtocol == "https"; } + } + + public string[] AcceptTypes + { + get { return request.AcceptTypes; } + } + + private Dictionary<string, object> items; + public Dictionary<string, object> Items + { + get { return items ?? (items = new Dictionary<string, object>()); } + } + + private string responseContentType; + public string ResponseContentType + { + get + { + return responseContentType + ?? (responseContentType = GetResponseContentType(this)); + } + set + { + this.responseContentType = value; + HasExplicitResponseContentType = true; + } + } + + public const string FormUrlEncoded = "application/x-www-form-urlencoded"; + public const string MultiPartFormData = "multipart/form-data"; + private static string GetResponseContentType(IRequest httpReq) + { + var specifiedContentType = GetQueryStringContentType(httpReq); + if (!string.IsNullOrEmpty(specifiedContentType)) return specifiedContentType; + + var serverDefaultContentType = "application/json"; + + var acceptContentTypes = httpReq.AcceptTypes; + var defaultContentType = httpReq.ContentType; + if (HasAnyOfContentTypes(httpReq, FormUrlEncoded, MultiPartFormData)) + { + defaultContentType = serverDefaultContentType; + } + + var preferredContentTypes = new string[] {}; + + var acceptsAnything = false; + var hasDefaultContentType = !string.IsNullOrEmpty(defaultContentType); + if (acceptContentTypes != null) + { + var hasPreferredContentTypes = new bool[preferredContentTypes.Length]; + foreach (var acceptsType in acceptContentTypes) + { + var contentType = HttpResultFactory.GetRealContentType(acceptsType); + acceptsAnything = acceptsAnything || contentType == "*/*"; + + for (var i = 0; i < preferredContentTypes.Length; i++) + { + if (hasPreferredContentTypes[i]) continue; + var preferredContentType = preferredContentTypes[i]; + hasPreferredContentTypes[i] = contentType.StartsWith(preferredContentType); + + //Prefer Request.ContentType if it is also a preferredContentType + if (hasPreferredContentTypes[i] && preferredContentType == defaultContentType) + return preferredContentType; + } + } + + for (var i = 0; i < preferredContentTypes.Length; i++) + { + if (hasPreferredContentTypes[i]) return preferredContentTypes[i]; + } + + if (acceptsAnything) + { + if (hasDefaultContentType) + return defaultContentType; + if (serverDefaultContentType != null) + return serverDefaultContentType; + } + } + + if (acceptContentTypes == null && httpReq.ContentType == Soap11) + { + return Soap11; + } + + //We could also send a '406 Not Acceptable', but this is allowed also + return serverDefaultContentType; + } + + public const string Soap11 = "text/xml; charset=utf-8"; + + public static bool HasAnyOfContentTypes(IRequest request, params string[] contentTypes) + { + if (contentTypes == null || request.ContentType == null) return false; + foreach (var contentType in contentTypes) + { + if (IsContentType(request, contentType)) return true; + } + return false; + } + + public static bool IsContentType(IRequest request, string contentType) + { + return request.ContentType.StartsWith(contentType, StringComparison.OrdinalIgnoreCase); + } + + public const string Xml = "application/xml"; + private static string GetQueryStringContentType(IRequest httpReq) + { + var format = httpReq.QueryString["format"]; + if (format == null) + { + const int formatMaxLength = 4; + var pi = httpReq.PathInfo; + if (pi == null || pi.Length <= formatMaxLength) return null; + if (pi[0] == '/') pi = pi.Substring(1); + format = LeftPart(pi, '/'); + if (format.Length > formatMaxLength) return null; + } + + format = LeftPart(format, '.').ToLower(); + if (format.Contains("json")) return "application/json"; + if (format.Contains("xml")) return Xml; + + return null; + } + + public static string LeftPart(string strVal, char needle) + { + if (strVal == null) return null; + var pos = strVal.IndexOf(needle); + return pos == -1 + ? strVal + : strVal.Substring(0, pos); + } + + public bool HasExplicitResponseContentType { get; private set; } + + public static string HandlerFactoryPath; + + private string pathInfo; + public string PathInfo + { + get + { + if (this.pathInfo == null) + { + var mode = HandlerFactoryPath; + + var pos = request.RawUrl.IndexOf("?"); + if (pos != -1) + { + var path = request.RawUrl.Substring(0, pos); + this.pathInfo = GetPathInfo( + path, + mode, + mode ?? ""); + } + else + { + this.pathInfo = request.RawUrl; + } + + this.pathInfo = WebUtility.UrlDecode(pathInfo); + this.pathInfo = NormalizePathInfo(pathInfo, mode); + } + return this.pathInfo; + } + } + + private static string GetPathInfo(string fullPath, string mode, string appPath) + { + var pathInfo = ResolvePathInfoFromMappedPath(fullPath, mode); + if (!string.IsNullOrEmpty(pathInfo)) return pathInfo; + + //Wildcard mode relies on this to work out the handlerPath + pathInfo = ResolvePathInfoFromMappedPath(fullPath, appPath); + if (!string.IsNullOrEmpty(pathInfo)) return pathInfo; + + return fullPath; + } + + + + private static string ResolvePathInfoFromMappedPath(string fullPath, string mappedPathRoot) + { + if (mappedPathRoot == null) return null; + + var sbPathInfo = new StringBuilder(); + var fullPathParts = fullPath.Split('/'); + var mappedPathRootParts = mappedPathRoot.Split('/'); + var fullPathIndexOffset = mappedPathRootParts.Length - 1; + var pathRootFound = false; + + for (var fullPathIndex = 0; fullPathIndex < fullPathParts.Length; fullPathIndex++) + { + if (pathRootFound) + { + sbPathInfo.Append("/" + fullPathParts[fullPathIndex]); + } + else if (fullPathIndex - fullPathIndexOffset >= 0) + { + pathRootFound = true; + for (var mappedPathRootIndex = 0; mappedPathRootIndex < mappedPathRootParts.Length; mappedPathRootIndex++) + { + if (!string.Equals(fullPathParts[fullPathIndex - fullPathIndexOffset + mappedPathRootIndex], mappedPathRootParts[mappedPathRootIndex], StringComparison.OrdinalIgnoreCase)) + { + pathRootFound = false; + break; + } + } + } + } + if (!pathRootFound) return null; + + var path = sbPathInfo.ToString(); + return path.Length > 1 ? path.TrimEnd('/') : "/"; + } + + private Dictionary<string, System.Net.Cookie> cookies; + public IDictionary<string, System.Net.Cookie> Cookies + { + get + { + if (cookies == null) + { + cookies = new Dictionary<string, System.Net.Cookie>(); + foreach (var cookie in this.request.Cookies) + { + var httpCookie = (Cookie) cookie; + cookies[httpCookie.Name] = new System.Net.Cookie(httpCookie.Name, httpCookie.Value, httpCookie.Path, httpCookie.Domain); + } + } + + return cookies; + } + } + + public string UserAgent + { + get { return request.UserAgent; } + } + + public QueryParamCollection Headers + { + get { return request.Headers; } + } + + private QueryParamCollection queryString; + public QueryParamCollection QueryString + { + get { return queryString ?? (queryString = MyHttpUtility.ParseQueryString(request.Url.Query)); } + } + + private QueryParamCollection formData; + public QueryParamCollection FormData + { + get { return formData ?? (formData = this.Form); } + } + + public bool IsLocal + { + get { return request.IsLocal; } + } + + private string httpMethod; + public string HttpMethod + { + get + { + return httpMethod + ?? (httpMethod = request.HttpMethod); + } + } + + public string Verb + { + get { return HttpMethod; } + } + + public string Param(string name) + { + return Headers[name] + ?? QueryString[name] + ?? FormData[name]; + } + + public string ContentType + { + get { return request.ContentType; } + } + + public Encoding contentEncoding; + public Encoding ContentEncoding + { + get { return contentEncoding ?? request.ContentEncoding; } + set { contentEncoding = value; } + } + + public Uri UrlReferrer + { + get { return request.UrlReferrer; } + } + + public static Encoding GetEncoding(string contentTypeHeader) + { + var param = GetParameter(contentTypeHeader, "charset="); + if (param == null) return null; + try + { + return Encoding.GetEncoding(param); + } + catch (ArgumentException) + { + return null; + } + } + + public Stream InputStream + { + get { return request.InputStream; } + } + + public long ContentLength + { + get { return request.ContentLength64; } + } + + private IHttpFile[] httpFiles; + public IHttpFile[] Files + { + get + { + if (httpFiles == null) + { + if (files == null) + return httpFiles = new IHttpFile[0]; + + httpFiles = new IHttpFile[files.Count]; + var i = 0; + foreach (var pair in files) + { + var reqFile = pair.Value; + httpFiles[i] = new HttpFile + { + ContentType = reqFile.ContentType, + ContentLength = reqFile.ContentLength, + FileName = reqFile.FileName, + InputStream = reqFile.InputStream, + }; + i++; + } + } + return httpFiles; + } + } + + static Stream GetSubStream(Stream stream, IMemoryStreamFactory streamProvider) + { + if (stream is MemoryStream) + { + var other = (MemoryStream)stream; + + byte[] buffer; + if (streamProvider.TryGetBuffer(other, out buffer)) + { + return streamProvider.CreateNew(buffer); + } + return streamProvider.CreateNew(other.ToArray()); + } + + return stream; + } + + public static string GetHandlerPathIfAny(string listenerUrl) + { + if (listenerUrl == null) return null; + var pos = listenerUrl.IndexOf("://", StringComparison.OrdinalIgnoreCase); + if (pos == -1) return null; + var startHostUrl = listenerUrl.Substring(pos + "://".Length); + var endPos = startHostUrl.IndexOf('/'); + if (endPos == -1) return null; + var endHostUrl = startHostUrl.Substring(endPos + 1); + return String.IsNullOrEmpty(endHostUrl) ? null : endHostUrl.TrimEnd('/'); + } + + public static string NormalizePathInfo(string pathInfo, string handlerPath) + { + if (handlerPath != null && pathInfo.TrimStart('/').StartsWith( + handlerPath, StringComparison.OrdinalIgnoreCase)) + { + return pathInfo.TrimStart('/').Substring(handlerPath.Length); + } + + return pathInfo; + } + } + + public class HttpFile : IHttpFile + { + public string Name { get; set; } + public string FileName { get; set; } + public long ContentLength { get; set; } + public string ContentType { get; set; } + public Stream InputStream { get; set; } + } +} |
