update main projects

This commit is contained in:
Luke Pulverenti
2017-05-24 15:12:55 -04:00
parent 27c3acb2bf
commit f07af448fa
139 changed files with 1949 additions and 1924 deletions

View File

@@ -0,0 +1,17 @@
namespace SocketHttpListener
{
/// <summary>
/// Contains the values that indicate whether the byte order is a Little-endian or Big-endian.
/// </summary>
public enum ByteOrder : byte
{
/// <summary>
/// Indicates a Little-endian.
/// </summary>
Little,
/// <summary>
/// Indicates a Big-endian.
/// </summary>
Big
}
}

View File

@@ -0,0 +1,90 @@
using System;
using System.Text;
namespace SocketHttpListener
{
/// <summary>
/// Contains the event data associated with a <see cref="WebSocket.OnClose"/> event.
/// </summary>
/// <remarks>
/// A <see cref="WebSocket.OnClose"/> event occurs when the WebSocket connection has been closed.
/// If you would like to get the reason for the close, you should access the <see cref="Code"/> or
/// <see cref="Reason"/> property.
/// </remarks>
public class CloseEventArgs : EventArgs
{
#region Private Fields
private bool _clean;
private ushort _code;
private string _reason;
#endregion
#region Internal Constructors
internal CloseEventArgs (PayloadData payload)
{
var data = payload.ApplicationData;
var len = data.Length;
_code = len > 1
? data.SubArray (0, 2).ToUInt16 (ByteOrder.Big)
: (ushort) CloseStatusCode.NoStatusCode;
_reason = len > 2
? GetUtf8String(data.SubArray (2, len - 2))
: String.Empty;
}
private string GetUtf8String(byte[] bytes)
{
return Encoding.UTF8.GetString(bytes, 0, bytes.Length);
}
#endregion
#region Public Properties
/// <summary>
/// Gets the status code for the close.
/// </summary>
/// <value>
/// A <see cref="ushort"/> that represents the status code for the close if any.
/// </value>
public ushort Code {
get {
return _code;
}
}
/// <summary>
/// Gets the reason for the close.
/// </summary>
/// <value>
/// A <see cref="string"/> that represents the reason for the close if any.
/// </value>
public string Reason {
get {
return _reason;
}
}
/// <summary>
/// Gets a value indicating whether the WebSocket connection has been closed cleanly.
/// </summary>
/// <value>
/// <c>true</c> if the WebSocket connection has been closed cleanly; otherwise, <c>false</c>.
/// </value>
public bool WasClean {
get {
return _clean;
}
internal set {
_clean = value;
}
}
#endregion
}
}

View File

@@ -0,0 +1,94 @@
namespace SocketHttpListener
{
/// <summary>
/// Contains the values of the status code for the WebSocket connection close.
/// </summary>
/// <remarks>
/// <para>
/// The values of the status code are defined in
/// <see href="http://tools.ietf.org/html/rfc6455#section-7.4">Section 7.4</see>
/// of RFC 6455.
/// </para>
/// <para>
/// "Reserved value" must not be set as a status code in a close control frame
/// by an endpoint. It's designated for use in applications expecting a status
/// code to indicate that the connection was closed due to the system grounds.
/// </para>
/// </remarks>
public enum CloseStatusCode : ushort
{
/// <summary>
/// Equivalent to close status 1000.
/// Indicates a normal close.
/// </summary>
Normal = 1000,
/// <summary>
/// Equivalent to close status 1001.
/// Indicates that an endpoint is going away.
/// </summary>
Away = 1001,
/// <summary>
/// Equivalent to close status 1002.
/// Indicates that an endpoint is terminating the connection due to a protocol error.
/// </summary>
ProtocolError = 1002,
/// <summary>
/// Equivalent to close status 1003.
/// Indicates that an endpoint is terminating the connection because it has received
/// an unacceptable type message.
/// </summary>
IncorrectData = 1003,
/// <summary>
/// Equivalent to close status 1004.
/// Still undefined. Reserved value.
/// </summary>
Undefined = 1004,
/// <summary>
/// Equivalent to close status 1005.
/// Indicates that no status code was actually present. Reserved value.
/// </summary>
NoStatusCode = 1005,
/// <summary>
/// Equivalent to close status 1006.
/// Indicates that the connection was closed abnormally. Reserved value.
/// </summary>
Abnormal = 1006,
/// <summary>
/// Equivalent to close status 1007.
/// Indicates that an endpoint is terminating the connection because it has received
/// a message that contains a data that isn't consistent with the type of the message.
/// </summary>
InconsistentData = 1007,
/// <summary>
/// Equivalent to close status 1008.
/// Indicates that an endpoint is terminating the connection because it has received
/// a message that violates its policy.
/// </summary>
PolicyViolation = 1008,
/// <summary>
/// Equivalent to close status 1009.
/// Indicates that an endpoint is terminating the connection because it has received
/// a message that is too big to process.
/// </summary>
TooBig = 1009,
/// <summary>
/// Equivalent to close status 1010.
/// Indicates that the client is terminating the connection because it has expected
/// the server to negotiate one or more extension, but the server didn't return them
/// in the handshake response.
/// </summary>
IgnoreExtension = 1010,
/// <summary>
/// Equivalent to close status 1011.
/// Indicates that the server is terminating the connection because it has encountered
/// an unexpected condition that prevented it from fulfilling the request.
/// </summary>
ServerError = 1011,
/// <summary>
/// Equivalent to close status 1015.
/// Indicates that the connection was closed due to a failure to perform a TLS handshake.
/// Reserved value.
/// </summary>
TlsHandshakeFailure = 1015
}
}

View File

@@ -0,0 +1,23 @@
namespace SocketHttpListener
{
/// <summary>
/// Contains the values of the compression method used to compress the message on the WebSocket
/// connection.
/// </summary>
/// <remarks>
/// The values of the compression method are defined in
/// <see href="http://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-09">Compression
/// Extensions for WebSocket</see>.
/// </remarks>
public enum CompressionMethod : byte
{
/// <summary>
/// Indicates non compression.
/// </summary>
None,
/// <summary>
/// Indicates using DEFLATE.
/// </summary>
Deflate
}
}

View File

@@ -0,0 +1,46 @@
using System;
namespace SocketHttpListener
{
/// <summary>
/// Contains the event data associated with a <see cref="WebSocket.OnError"/> event.
/// </summary>
/// <remarks>
/// A <see cref="WebSocket.OnError"/> event occurs when the <see cref="WebSocket"/> gets an error.
/// If you would like to get the error message, you should access the <see cref="Message"/>
/// property.
/// </remarks>
public class ErrorEventArgs : EventArgs
{
#region Private Fields
private string _message;
#endregion
#region Internal Constructors
internal ErrorEventArgs (string message)
{
_message = message;
}
#endregion
#region Public Properties
/// <summary>
/// Gets the error message.
/// </summary>
/// <value>
/// A <see cref="string"/> that represents the error message.
/// </value>
public string Message {
get {
return _message;
}
}
#endregion
}
}

1083
SocketHttpListener/Ext.cs Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,8 @@
namespace SocketHttpListener
{
internal enum Fin : byte
{
More = 0x0,
Final = 0x1
}
}

View File

@@ -0,0 +1,104 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Text;
using System.Threading;
using MediaBrowser.Model.Services;
namespace SocketHttpListener
{
internal abstract class HttpBase
{
#region Private Fields
private QueryParamCollection _headers;
private Version _version;
#endregion
#region Internal Fields
internal byte[] EntityBodyData;
#endregion
#region Protected Fields
protected const string CrLf = "\r\n";
#endregion
#region Protected Constructors
protected HttpBase(Version version, QueryParamCollection headers)
{
_version = version;
_headers = headers;
}
#endregion
#region Public Properties
public string EntityBody
{
get
{
var data = EntityBodyData;
return data != null && data.Length > 0
? getEncoding(_headers["Content-Type"]).GetString(data, 0, data.Length)
: String.Empty;
}
}
public QueryParamCollection Headers
{
get
{
return _headers;
}
}
public Version ProtocolVersion
{
get
{
return _version;
}
}
#endregion
#region Private Methods
private static Encoding getEncoding(string contentType)
{
if (contentType == null || contentType.Length == 0)
return Encoding.UTF8;
var i = contentType.IndexOf("charset=", StringComparison.Ordinal);
if (i == -1)
return Encoding.UTF8;
var charset = contentType.Substring(i + 8);
i = charset.IndexOf(';');
if (i != -1)
charset = charset.Substring(0, i).TrimEnd();
return Encoding.GetEncoding(charset.Trim('"'));
}
#endregion
#region Public Methods
public byte[] ToByteArray()
{
return Encoding.UTF8.GetBytes(ToString());
}
#endregion
}
}

View File

@@ -0,0 +1,161 @@
using System;
using System.Collections.Specialized;
using System.IO;
using System.Net;
using System.Text;
using HttpStatusCode = SocketHttpListener.Net.HttpStatusCode;
using HttpVersion = SocketHttpListener.Net.HttpVersion;
using System.Linq;
using MediaBrowser.Model.Services;
namespace SocketHttpListener
{
internal class HttpResponse : HttpBase
{
#region Private Fields
private string _code;
private string _reason;
#endregion
#region Private Constructors
private HttpResponse(string code, string reason, Version version, QueryParamCollection headers)
: base(version, headers)
{
_code = code;
_reason = reason;
}
#endregion
#region Internal Constructors
internal HttpResponse(HttpStatusCode code)
: this(code, code.GetDescription())
{
}
internal HttpResponse(HttpStatusCode code, string reason)
: this(((int)code).ToString(), reason, HttpVersion.Version11, new QueryParamCollection())
{
Headers["Server"] = "websocket-sharp/1.0";
}
#endregion
#region Public Properties
public CookieCollection Cookies
{
get
{
return Headers.GetCookies(true);
}
}
public bool IsProxyAuthenticationRequired
{
get
{
return _code == "407";
}
}
public bool IsUnauthorized
{
get
{
return _code == "401";
}
}
public bool IsWebSocketResponse
{
get
{
var headers = Headers;
return ProtocolVersion > HttpVersion.Version10 &&
_code == "101" &&
headers.Contains("Upgrade", "websocket") &&
headers.Contains("Connection", "Upgrade");
}
}
public string Reason
{
get
{
return _reason;
}
}
public string StatusCode
{
get
{
return _code;
}
}
#endregion
#region Internal Methods
internal static HttpResponse CreateCloseResponse(HttpStatusCode code)
{
var res = new HttpResponse(code);
res.Headers["Connection"] = "close";
return res;
}
internal static HttpResponse CreateWebSocketResponse()
{
var res = new HttpResponse(HttpStatusCode.SwitchingProtocols);
var headers = res.Headers;
headers["Upgrade"] = "websocket";
headers["Connection"] = "Upgrade";
return res;
}
#endregion
#region Public Methods
public void SetCookies(CookieCollection cookies)
{
if (cookies == null || cookies.Count == 0)
return;
var headers = Headers;
var sorted = cookies.OfType<Cookie>().OrderBy(i => i.Name).ToList();
foreach (var cookie in sorted)
headers.Add("Set-Cookie", cookie.ToString());
}
public override string ToString()
{
var output = new StringBuilder(64);
output.AppendFormat("HTTP/{0} {1} {2}{3}", ProtocolVersion, _code, _reason, CrLf);
var headers = Headers;
foreach (var key in headers.Keys)
output.AppendFormat("{0}: {1}{2}", key, headers[key], CrLf);
output.Append(CrLf);
var entity = EntityBody;
if (entity.Length > 0)
output.Append(entity);
return output.ToString();
}
#endregion
}
}

View File

@@ -0,0 +1,8 @@
namespace SocketHttpListener
{
internal enum Mask : byte
{
Unmask = 0x0,
Mask = 0x1
}
}

View File

@@ -0,0 +1,96 @@
using System;
using System.Text;
namespace SocketHttpListener
{
/// <summary>
/// Contains the event data associated with a <see cref="WebSocket.OnMessage"/> event.
/// </summary>
/// <remarks>
/// A <see cref="WebSocket.OnMessage"/> event occurs when the <see cref="WebSocket"/> receives
/// a text or binary data frame.
/// If you want to get the received data, you access the <see cref="MessageEventArgs.Data"/> or
/// <see cref="MessageEventArgs.RawData"/> property.
/// </remarks>
public class MessageEventArgs : EventArgs
{
#region Private Fields
private string _data;
private Opcode _opcode;
private byte[] _rawData;
#endregion
#region Internal Constructors
internal MessageEventArgs (Opcode opcode, byte[] data)
{
_opcode = opcode;
_rawData = data;
_data = convertToString (opcode, data);
}
internal MessageEventArgs (Opcode opcode, PayloadData payload)
{
_opcode = opcode;
_rawData = payload.ApplicationData;
_data = convertToString (opcode, _rawData);
}
#endregion
#region Public Properties
/// <summary>
/// Gets the received data as a <see cref="string"/>.
/// </summary>
/// <value>
/// A <see cref="string"/> that contains the received data.
/// </value>
public string Data {
get {
return _data;
}
}
/// <summary>
/// Gets the received data as an array of <see cref="byte"/>.
/// </summary>
/// <value>
/// An array of <see cref="byte"/> that contains the received data.
/// </value>
public byte [] RawData {
get {
return _rawData;
}
}
/// <summary>
/// Gets the type of the received data.
/// </summary>
/// <value>
/// One of the <see cref="Opcode"/> values, indicates the type of the received data.
/// </value>
public Opcode Type {
get {
return _opcode;
}
}
#endregion
#region Private Methods
private static string convertToString (Opcode opcode, byte [] data)
{
return data.Length == 0
? String.Empty
: opcode == Opcode.Text
? Encoding.UTF8.GetString (data, 0, data.Length)
: opcode.ToString ();
}
#endregion
}
}

View File

@@ -0,0 +1,6 @@
using System.Net;
namespace SocketHttpListener.Net
{
public delegate AuthenticationSchemes AuthenticationSchemeSelector(HttpListenerRequest httpRequest);
}

View File

@@ -0,0 +1,405 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net;
using System.Text;
namespace SocketHttpListener.Net
{
// Licensed to the .NET Foundation under one or more agreements.
// See the LICENSE file in the project root for more information.
//
// System.Net.ResponseStream
//
// Author:
// Gonzalo Paniagua Javier (gonzalo@novell.com)
//
// Copyright (c) 2005 Novell, Inc. (http://www.novell.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.
//
internal sealed class ChunkStream
{
private enum State
{
None,
PartialSize,
Body,
BodyFinished,
Trailer
}
private class Chunk
{
public byte[] Bytes;
public int Offset;
public Chunk(byte[] chunk)
{
Bytes = chunk;
}
public int Read(byte[] buffer, int offset, int size)
{
int nread = (size > Bytes.Length - Offset) ? Bytes.Length - Offset : size;
Buffer.BlockCopy(Bytes, Offset, buffer, offset, nread);
Offset += nread;
return nread;
}
}
internal WebHeaderCollection _headers;
private int _chunkSize;
private int _chunkRead;
private int _totalWritten;
private State _state;
private StringBuilder _saved;
private bool _sawCR;
private bool _gotit;
private int _trailerState;
private List<Chunk> _chunks;
public ChunkStream(byte[] buffer, int offset, int size, WebHeaderCollection headers)
: this(headers)
{
Write(buffer, offset, size);
}
public ChunkStream(WebHeaderCollection headers)
{
_headers = headers;
_saved = new StringBuilder();
_chunks = new List<Chunk>();
_chunkSize = -1;
_totalWritten = 0;
}
public void ResetBuffer()
{
_chunkSize = -1;
_chunkRead = 0;
_totalWritten = 0;
_chunks.Clear();
}
public void WriteAndReadBack(byte[] buffer, int offset, int size, ref int read)
{
if (offset + read > 0)
Write(buffer, offset, offset + read);
read = Read(buffer, offset, size);
}
public int Read(byte[] buffer, int offset, int size)
{
return ReadFromChunks(buffer, offset, size);
}
private int ReadFromChunks(byte[] buffer, int offset, int size)
{
int count = _chunks.Count;
int nread = 0;
var chunksForRemoving = new List<Chunk>(count);
for (int i = 0; i < count; i++)
{
Chunk chunk = _chunks[i];
if (chunk.Offset == chunk.Bytes.Length)
{
chunksForRemoving.Add(chunk);
continue;
}
nread += chunk.Read(buffer, offset + nread, size - nread);
if (nread == size)
break;
}
foreach (var chunk in chunksForRemoving)
_chunks.Remove(chunk);
return nread;
}
public void Write(byte[] buffer, int offset, int size)
{
if (offset < size)
InternalWrite(buffer, ref offset, size);
}
private void InternalWrite(byte[] buffer, ref int offset, int size)
{
if (_state == State.None || _state == State.PartialSize)
{
_state = GetChunkSize(buffer, ref offset, size);
if (_state == State.PartialSize)
return;
_saved.Length = 0;
_sawCR = false;
_gotit = false;
}
if (_state == State.Body && offset < size)
{
_state = ReadBody(buffer, ref offset, size);
if (_state == State.Body)
return;
}
if (_state == State.BodyFinished && offset < size)
{
_state = ReadCRLF(buffer, ref offset, size);
if (_state == State.BodyFinished)
return;
_sawCR = false;
}
if (_state == State.Trailer && offset < size)
{
_state = ReadTrailer(buffer, ref offset, size);
if (_state == State.Trailer)
return;
_saved.Length = 0;
_sawCR = false;
_gotit = false;
}
if (offset < size)
InternalWrite(buffer, ref offset, size);
}
public bool WantMore
{
get { return (_chunkRead != _chunkSize || _chunkSize != 0 || _state != State.None); }
}
public bool DataAvailable
{
get
{
int count = _chunks.Count;
for (int i = 0; i < count; i++)
{
Chunk ch = _chunks[i];
if (ch == null || ch.Bytes == null)
continue;
if (ch.Bytes.Length > 0 && ch.Offset < ch.Bytes.Length)
return (_state != State.Body);
}
return false;
}
}
public int TotalDataSize
{
get { return _totalWritten; }
}
public int ChunkLeft
{
get { return _chunkSize - _chunkRead; }
}
private State ReadBody(byte[] buffer, ref int offset, int size)
{
if (_chunkSize == 0)
return State.BodyFinished;
int diff = size - offset;
if (diff + _chunkRead > _chunkSize)
diff = _chunkSize - _chunkRead;
byte[] chunk = new byte[diff];
Buffer.BlockCopy(buffer, offset, chunk, 0, diff);
_chunks.Add(new Chunk(chunk));
offset += diff;
_chunkRead += diff;
_totalWritten += diff;
return (_chunkRead == _chunkSize) ? State.BodyFinished : State.Body;
}
private State GetChunkSize(byte[] buffer, ref int offset, int size)
{
_chunkRead = 0;
_chunkSize = 0;
char c = '\0';
while (offset < size)
{
c = (char)buffer[offset++];
if (c == '\r')
{
if (_sawCR)
ThrowProtocolViolation("2 CR found");
_sawCR = true;
continue;
}
if (_sawCR && c == '\n')
break;
if (c == ' ')
_gotit = true;
if (!_gotit)
_saved.Append(c);
if (_saved.Length > 20)
ThrowProtocolViolation("chunk size too long.");
}
if (!_sawCR || c != '\n')
{
if (offset < size)
ThrowProtocolViolation("Missing \\n");
try
{
if (_saved.Length > 0)
{
_chunkSize = Int32.Parse(RemoveChunkExtension(_saved.ToString()), NumberStyles.HexNumber);
}
}
catch (Exception)
{
ThrowProtocolViolation("Cannot parse chunk size.");
}
return State.PartialSize;
}
_chunkRead = 0;
try
{
_chunkSize = Int32.Parse(RemoveChunkExtension(_saved.ToString()), NumberStyles.HexNumber);
}
catch (Exception)
{
ThrowProtocolViolation("Cannot parse chunk size.");
}
if (_chunkSize == 0)
{
_trailerState = 2;
return State.Trailer;
}
return State.Body;
}
private static string RemoveChunkExtension(string input)
{
int idx = input.IndexOf(';');
if (idx == -1)
return input;
return input.Substring(0, idx);
}
private State ReadCRLF(byte[] buffer, ref int offset, int size)
{
if (!_sawCR)
{
if ((char)buffer[offset++] != '\r')
ThrowProtocolViolation("Expecting \\r");
_sawCR = true;
if (offset == size)
return State.BodyFinished;
}
if (_sawCR && (char)buffer[offset++] != '\n')
ThrowProtocolViolation("Expecting \\n");
return State.None;
}
private State ReadTrailer(byte[] buffer, ref int offset, int size)
{
char c = '\0';
// short path
if (_trailerState == 2 && (char)buffer[offset] == '\r' && _saved.Length == 0)
{
offset++;
if (offset < size && (char)buffer[offset] == '\n')
{
offset++;
return State.None;
}
offset--;
}
int st = _trailerState;
string stString = "\r\n\r";
while (offset < size && st < 4)
{
c = (char)buffer[offset++];
if ((st == 0 || st == 2) && c == '\r')
{
st++;
continue;
}
if ((st == 1 || st == 3) && c == '\n')
{
st++;
continue;
}
if (st > 0)
{
_saved.Append(stString.Substring(0, _saved.Length == 0 ? st - 2 : st));
st = 0;
if (_saved.Length > 4196)
ThrowProtocolViolation("Error reading trailer (too long).");
}
}
if (st < 4)
{
_trailerState = st;
if (offset < size)
ThrowProtocolViolation("Error reading trailer.");
return State.Trailer;
}
StringReader reader = new StringReader(_saved.ToString());
string line;
while ((line = reader.ReadLine()) != null && line != "")
_headers.Add(line);
return State.None;
}
private static void ThrowProtocolViolation(string message)
{
WebException we = new WebException(message, null, WebExceptionStatus.ServerProtocolViolation, null);
throw we;
}
}
}

View File

@@ -0,0 +1,172 @@
using System;
using System.IO;
using System.Net;
using System.Runtime.InteropServices;
using SocketHttpListener.Primitives;
namespace SocketHttpListener.Net
{
// Licensed to the .NET Foundation under one or more agreements.
// See the LICENSE file in the project root for more information.
//
// System.Net.ResponseStream
//
// Author:
// Gonzalo Paniagua Javier (gonzalo@novell.com)
//
// Copyright (c) 2005 Novell, Inc. (http://www.novell.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.
//
internal sealed class ChunkedInputStream : HttpRequestStream
{
private ChunkStream _decoder;
private readonly HttpListenerContext _context;
private bool _no_more_data;
private class ReadBufferState
{
public byte[] Buffer;
public int Offset;
public int Count;
public int InitialCount;
public HttpStreamAsyncResult Ares;
public ReadBufferState(byte[] buffer, int offset, int count, HttpStreamAsyncResult ares)
{
Buffer = buffer;
Offset = offset;
Count = count;
InitialCount = count;
Ares = ares;
}
}
public ChunkedInputStream(HttpListenerContext context, Stream stream, byte[] buffer, int offset, int length)
: base(stream, buffer, offset, length)
{
_context = context;
WebHeaderCollection coll = (WebHeaderCollection)context.Request.Headers;
_decoder = new ChunkStream(coll);
}
public ChunkStream Decoder
{
get { return _decoder; }
set { _decoder = value; }
}
protected override int ReadCore(byte[] buffer, int offset, int count)
{
IAsyncResult ares = BeginReadCore(buffer, offset, count, null, null);
return EndRead(ares);
}
protected override IAsyncResult BeginReadCore(byte[] buffer, int offset, int size, AsyncCallback cback, object state)
{
HttpStreamAsyncResult ares = new HttpStreamAsyncResult(this);
ares._callback = cback;
ares._state = state;
if (_no_more_data || size == 0 || _closed)
{
ares.Complete();
return ares;
}
int nread = _decoder.Read(buffer, offset, size);
offset += nread;
size -= nread;
if (size == 0)
{
// got all we wanted, no need to bother the decoder yet
ares._count = nread;
ares.Complete();
return ares;
}
if (!_decoder.WantMore)
{
_no_more_data = nread == 0;
ares._count = nread;
ares.Complete();
return ares;
}
ares._buffer = new byte[8192];
ares._offset = 0;
ares._count = 8192;
ReadBufferState rb = new ReadBufferState(buffer, offset, size, ares);
rb.InitialCount += nread;
base.BeginReadCore(ares._buffer, ares._offset, ares._count, OnRead, rb);
return ares;
}
private void OnRead(IAsyncResult base_ares)
{
ReadBufferState rb = (ReadBufferState)base_ares.AsyncState;
HttpStreamAsyncResult ares = rb.Ares;
try
{
int nread = base.EndRead(base_ares);
_decoder.Write(ares._buffer, ares._offset, nread);
nread = _decoder.Read(rb.Buffer, rb.Offset, rb.Count);
rb.Offset += nread;
rb.Count -= nread;
if (rb.Count == 0 || !_decoder.WantMore || nread == 0)
{
_no_more_data = !_decoder.WantMore && nread == 0;
ares._count = rb.InitialCount - rb.Count;
ares.Complete();
return;
}
ares._offset = 0;
ares._count = Math.Min(8192, _decoder.ChunkLeft + 6);
base.BeginReadCore(ares._buffer, ares._offset, ares._count, OnRead, rb);
}
catch (Exception e)
{
_context.Connection.SendError(e.Message, 400);
ares.Complete(e);
}
}
public override int EndRead(IAsyncResult asyncResult)
{
if (asyncResult == null)
throw new ArgumentNullException(nameof(asyncResult));
HttpStreamAsyncResult ares = asyncResult as HttpStreamAsyncResult;
if (ares == null || !ReferenceEquals(this, ares._parent))
{
throw new ArgumentException("Invalid async result");
}
if (ares._endCalled)
{
throw new InvalidOperationException("Invalid end call");
}
ares._endCalled = true;
if (!asyncResult.IsCompleted)
asyncResult.AsyncWaitHandle.WaitOne();
if (ares._error != null)
throw new HttpListenerException((int)HttpStatusCode.BadRequest, "Bad Request");
return ares._count;
}
}
}

View File

@@ -0,0 +1,144 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
namespace SocketHttpListener.Net
{
public static class CookieHelper
{
internal static CookieCollection Parse(string value, bool response)
{
return response
? parseResponse(value)
: null;
}
private static string[] splitCookieHeaderValue(string value)
{
return new List<string>(value.SplitHeaderValue(',', ';')).ToArray();
}
private static CookieCollection parseResponse(string value)
{
var cookies = new CookieCollection();
Cookie cookie = null;
var pairs = splitCookieHeaderValue(value);
for (int i = 0; i < pairs.Length; i++)
{
var pair = pairs[i].Trim();
if (pair.Length == 0)
continue;
if (pair.StartsWith("version", StringComparison.OrdinalIgnoreCase))
{
if (cookie != null)
cookie.Version = Int32.Parse(pair.GetValueInternal("=").Trim('"'));
}
else if (pair.StartsWith("expires", StringComparison.OrdinalIgnoreCase))
{
var buffer = new StringBuilder(pair.GetValueInternal("="), 32);
if (i < pairs.Length - 1)
buffer.AppendFormat(", {0}", pairs[++i].Trim());
DateTime expires;
if (!DateTime.TryParseExact(
buffer.ToString(),
new[] { "ddd, dd'-'MMM'-'yyyy HH':'mm':'ss 'GMT'", "r" },
new CultureInfo("en-US"),
DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal,
out expires))
expires = DateTime.Now;
if (cookie != null && cookie.Expires == DateTime.MinValue)
cookie.Expires = expires.ToLocalTime();
}
else if (pair.StartsWith("max-age", StringComparison.OrdinalIgnoreCase))
{
var max = Int32.Parse(pair.GetValueInternal("=").Trim('"'));
var expires = DateTime.Now.AddSeconds((double)max);
if (cookie != null)
cookie.Expires = expires;
}
else if (pair.StartsWith("path", StringComparison.OrdinalIgnoreCase))
{
if (cookie != null)
cookie.Path = pair.GetValueInternal("=");
}
else if (pair.StartsWith("domain", StringComparison.OrdinalIgnoreCase))
{
if (cookie != null)
cookie.Domain = pair.GetValueInternal("=");
}
else if (pair.StartsWith("port", StringComparison.OrdinalIgnoreCase))
{
var port = pair.Equals("port", StringComparison.OrdinalIgnoreCase)
? "\"\""
: pair.GetValueInternal("=");
if (cookie != null)
cookie.Port = port;
}
else if (pair.StartsWith("comment", StringComparison.OrdinalIgnoreCase))
{
if (cookie != null)
cookie.Comment = pair.GetValueInternal("=").UrlDecode();
}
else if (pair.StartsWith("commenturl", StringComparison.OrdinalIgnoreCase))
{
if (cookie != null)
cookie.CommentUri = pair.GetValueInternal("=").Trim('"').ToUri();
}
else if (pair.StartsWith("discard", StringComparison.OrdinalIgnoreCase))
{
if (cookie != null)
cookie.Discard = true;
}
else if (pair.StartsWith("secure", StringComparison.OrdinalIgnoreCase))
{
if (cookie != null)
cookie.Secure = true;
}
else if (pair.StartsWith("httponly", StringComparison.OrdinalIgnoreCase))
{
if (cookie != null)
cookie.HttpOnly = true;
}
else
{
if (cookie != null)
cookies.Add(cookie);
string name;
string val = String.Empty;
var pos = pair.IndexOf('=');
if (pos == -1)
{
name = pair;
}
else if (pos == pair.Length - 1)
{
name = pair.Substring(0, pos).TrimEnd(' ');
}
else
{
name = pair.Substring(0, pos).TrimEnd(' ');
val = pair.Substring(pos + 1).TrimStart(' ');
}
cookie = new Cookie(name, val);
}
}
if (cookie != null)
cookies.Add(cookie);
return cookies;
}
}
}

View File

@@ -0,0 +1,391 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Threading;
using MediaBrowser.Model.Cryptography;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.System;
using MediaBrowser.Model.Text;
using SocketHttpListener.Primitives;
namespace SocketHttpListener.Net
{
sealed class EndPointListener
{
HttpListener listener;
IpEndPointInfo endpoint;
IAcceptSocket sock;
Dictionary<ListenerPrefix,HttpListener> prefixes; // Dictionary <ListenerPrefix, HttpListener>
List<ListenerPrefix> unhandled; // List<ListenerPrefix> unhandled; host = '*'
List<ListenerPrefix> all; // List<ListenerPrefix> all; host = '+'
ICertificate cert;
bool secure;
Dictionary<HttpConnection, HttpConnection> unregistered;
private readonly ILogger _logger;
private bool _closed;
private bool _enableDualMode;
private readonly ICryptoProvider _cryptoProvider;
private readonly IStreamFactory _streamFactory;
private readonly ISocketFactory _socketFactory;
private readonly ITextEncoding _textEncoding;
private readonly IMemoryStreamFactory _memoryStreamFactory;
private readonly IFileSystem _fileSystem;
private readonly IEnvironmentInfo _environment;
public EndPointListener(HttpListener listener, IpAddressInfo addr, int port, bool secure, ICertificate cert, ILogger logger, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, ISocketFactory socketFactory, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding, IFileSystem fileSystem, IEnvironmentInfo environment)
{
this.listener = listener;
_logger = logger;
_cryptoProvider = cryptoProvider;
_streamFactory = streamFactory;
_socketFactory = socketFactory;
_memoryStreamFactory = memoryStreamFactory;
_textEncoding = textEncoding;
_fileSystem = fileSystem;
_environment = environment;
this.secure = secure;
this.cert = cert;
_enableDualMode = addr.Equals(IpAddressInfo.IPv6Any);
endpoint = new IpEndPointInfo(addr, port);
prefixes = new Dictionary<ListenerPrefix, HttpListener>();
unregistered = new Dictionary<HttpConnection, HttpConnection>();
CreateSocket();
}
internal HttpListener Listener
{
get
{
return listener;
}
}
private void CreateSocket()
{
try
{
sock = _socketFactory.CreateSocket(endpoint.IpAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp, _enableDualMode);
}
catch (SocketCreateException ex)
{
if (_enableDualMode && endpoint.IpAddress.Equals(IpAddressInfo.IPv6Any) &&
(string.Equals(ex.ErrorCode, "AddressFamilyNotSupported", StringComparison.OrdinalIgnoreCase) ||
// mono on bsd is throwing this
string.Equals(ex.ErrorCode, "ProtocolNotSupported", StringComparison.OrdinalIgnoreCase)))
{
endpoint = new IpEndPointInfo(IpAddressInfo.Any, endpoint.Port);
_enableDualMode = false;
sock = _socketFactory.CreateSocket(endpoint.IpAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp, _enableDualMode);
}
else
{
throw;
}
}
sock.Bind(endpoint);
// This is the number TcpListener uses.
sock.Listen(2147483647);
sock.StartAccept(ProcessAccept, () => _closed);
_closed = false;
}
private async void ProcessAccept(IAcceptSocket accepted)
{
try
{
var listener = this;
if (listener.secure && listener.cert == null)
{
accepted.Close();
return;
}
HttpConnection conn = await HttpConnection.Create(_logger, accepted, listener, listener.secure, listener.cert, _cryptoProvider, _streamFactory, _memoryStreamFactory, _textEncoding, _fileSystem, _environment).ConfigureAwait(false);
//_logger.Debug("Adding unregistered connection to {0}. Id: {1}", accepted.RemoteEndPoint, connectionId);
lock (listener.unregistered)
{
listener.unregistered[conn] = conn;
}
conn.BeginReadRequest();
}
catch (Exception ex)
{
_logger.ErrorException("Error in ProcessAccept", ex);
}
}
internal void RemoveConnection(HttpConnection conn)
{
lock (unregistered)
{
unregistered.Remove(conn);
}
}
public bool BindContext(HttpListenerContext context)
{
HttpListenerRequest req = context.Request;
ListenerPrefix prefix;
HttpListener listener = SearchListener(req.Url, out prefix);
if (listener == null)
return false;
context.Connection.Prefix = prefix;
return true;
}
public void UnbindContext(HttpListenerContext context)
{
if (context == null || context.Request == null)
return;
listener.UnregisterContext(context);
}
HttpListener SearchListener(Uri uri, out ListenerPrefix prefix)
{
prefix = null;
if (uri == null)
return null;
string host = uri.Host;
int port = uri.Port;
string path = WebUtility.UrlDecode(uri.AbsolutePath);
string path_slash = path[path.Length - 1] == '/' ? path : path + "/";
HttpListener best_match = null;
int best_length = -1;
if (host != null && host != "")
{
var p_ro = prefixes;
foreach (ListenerPrefix p in p_ro.Keys)
{
string ppath = p.Path;
if (ppath.Length < best_length)
continue;
if (p.Host != host || p.Port != port)
continue;
if (path.StartsWith(ppath) || path_slash.StartsWith(ppath))
{
best_length = ppath.Length;
best_match = (HttpListener)p_ro[p];
prefix = p;
}
}
if (best_length != -1)
return best_match;
}
List<ListenerPrefix> list = unhandled;
best_match = MatchFromList(host, path, list, out prefix);
if (path != path_slash && best_match == null)
best_match = MatchFromList(host, path_slash, list, out prefix);
if (best_match != null)
return best_match;
list = all;
best_match = MatchFromList(host, path, list, out prefix);
if (path != path_slash && best_match == null)
best_match = MatchFromList(host, path_slash, list, out prefix);
if (best_match != null)
return best_match;
return null;
}
HttpListener MatchFromList(string host, string path, List<ListenerPrefix> list, out ListenerPrefix prefix)
{
prefix = null;
if (list == null)
return null;
HttpListener best_match = null;
int best_length = -1;
foreach (ListenerPrefix p in list)
{
string ppath = p.Path;
if (ppath.Length < best_length)
continue;
if (path.StartsWith(ppath))
{
best_length = ppath.Length;
best_match = p.Listener;
prefix = p;
}
}
return best_match;
}
void AddSpecial(List<ListenerPrefix> coll, ListenerPrefix prefix)
{
if (coll == null)
return;
foreach (ListenerPrefix p in coll)
{
if (p.Path == prefix.Path) //TODO: code
throw new HttpListenerException(400, "Prefix already in use.");
}
coll.Add(prefix);
}
bool RemoveSpecial(List<ListenerPrefix> coll, ListenerPrefix prefix)
{
if (coll == null)
return false;
int c = coll.Count;
for (int i = 0; i < c; i++)
{
ListenerPrefix p = (ListenerPrefix)coll[i];
if (p.Path == prefix.Path)
{
coll.RemoveAt(i);
return true;
}
}
return false;
}
void CheckIfRemove()
{
if (prefixes.Count > 0)
return;
List<ListenerPrefix> list = unhandled;
if (list != null && list.Count > 0)
return;
list = all;
if (list != null && list.Count > 0)
return;
EndPointManager.RemoveEndPoint(this, endpoint);
}
public void Close()
{
_closed = true;
sock.Close();
lock (unregistered)
{
//
// Clone the list because RemoveConnection can be called from Close
//
var connections = new List<HttpConnection>(unregistered.Keys);
foreach (HttpConnection c in connections)
c.Close(true);
unregistered.Clear();
}
}
public void AddPrefix(ListenerPrefix prefix, HttpListener listener)
{
List<ListenerPrefix> current;
List<ListenerPrefix> future;
if (prefix.Host == "*")
{
do
{
current = unhandled;
future = (current != null) ? current.ToList() : new List<ListenerPrefix>();
prefix.Listener = listener;
AddSpecial(future, prefix);
} while (Interlocked.CompareExchange(ref unhandled, future, current) != current);
return;
}
if (prefix.Host == "+")
{
do
{
current = all;
future = (current != null) ? current.ToList() : new List<ListenerPrefix>();
prefix.Listener = listener;
AddSpecial(future, prefix);
} while (Interlocked.CompareExchange(ref all, future, current) != current);
return;
}
Dictionary<ListenerPrefix, HttpListener> prefs;
Dictionary<ListenerPrefix, HttpListener> p2;
do
{
prefs = prefixes;
if (prefs.ContainsKey(prefix))
{
HttpListener other = (HttpListener)prefs[prefix];
if (other != listener) // TODO: code.
throw new HttpListenerException(400, "There's another listener for " + prefix);
return;
}
p2 = new Dictionary<ListenerPrefix, HttpListener>(prefs);
p2[prefix] = listener;
} while (Interlocked.CompareExchange(ref prefixes, p2, prefs) != prefs);
}
public void RemovePrefix(ListenerPrefix prefix, HttpListener listener)
{
List<ListenerPrefix> current;
List<ListenerPrefix> future;
if (prefix.Host == "*")
{
do
{
current = unhandled;
future = (current != null) ? current.ToList() : new List<ListenerPrefix>();
if (!RemoveSpecial(future, prefix))
break; // Prefix not found
} while (Interlocked.CompareExchange(ref unhandled, future, current) != current);
CheckIfRemove();
return;
}
if (prefix.Host == "+")
{
do
{
current = all;
future = (current != null) ? current.ToList() : new List<ListenerPrefix>();
if (!RemoveSpecial(future, prefix))
break; // Prefix not found
} while (Interlocked.CompareExchange(ref all, future, current) != current);
CheckIfRemove();
return;
}
Dictionary<ListenerPrefix, HttpListener> prefs;
Dictionary<ListenerPrefix, HttpListener> p2;
do
{
prefs = prefixes;
if (!prefs.ContainsKey(prefix))
break;
p2 = new Dictionary<ListenerPrefix, HttpListener>(prefs);
p2.Remove(prefix);
} while (Interlocked.CompareExchange(ref prefixes, p2, prefs) != prefs);
CheckIfRemove();
}
}
}

View File

@@ -0,0 +1,166 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Threading.Tasks;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Net;
using SocketHttpListener.Primitives;
namespace SocketHttpListener.Net
{
sealed class EndPointManager
{
// Dictionary<IPAddress, Dictionary<int, EndPointListener>>
static Dictionary<string, Dictionary<int, EndPointListener>> ip_to_endpoints = new Dictionary<string, Dictionary<int, EndPointListener>>();
private EndPointManager()
{
}
public static void AddListener(ILogger logger, HttpListener listener)
{
List<string> added = new List<string>();
try
{
lock (ip_to_endpoints)
{
foreach (string prefix in listener.Prefixes)
{
AddPrefixInternal(logger, prefix, listener);
added.Add(prefix);
}
}
}
catch
{
foreach (string prefix in added)
{
RemovePrefix(logger, prefix, listener);
}
throw;
}
}
public static void AddPrefix(ILogger logger, string prefix, HttpListener listener)
{
lock (ip_to_endpoints)
{
AddPrefixInternal(logger, prefix, listener);
}
}
static void AddPrefixInternal(ILogger logger, string p, HttpListener listener)
{
ListenerPrefix lp = new ListenerPrefix(p);
if (lp.Path.IndexOf('%') != -1)
throw new HttpListenerException(400, "Invalid path.");
if (lp.Path.IndexOf("//", StringComparison.Ordinal) != -1) // TODO: Code?
throw new HttpListenerException(400, "Invalid path.");
// listens on all the interfaces if host name cannot be parsed by IPAddress.
EndPointListener epl = GetEPListener(logger, lp.Host, lp.Port, listener, lp.Secure).Result;
epl.AddPrefix(lp, listener);
}
private static IpAddressInfo GetIpAnyAddress(HttpListener listener)
{
return listener.EnableDualMode ? IpAddressInfo.IPv6Any : IpAddressInfo.Any;
}
static async Task<EndPointListener> GetEPListener(ILogger logger, string host, int port, HttpListener listener, bool secure)
{
var networkManager = listener.NetworkManager;
IpAddressInfo addr;
if (host == "*" || host == "+")
addr = GetIpAnyAddress(listener);
else if (networkManager.TryParseIpAddress(host, out addr) == false)
{
try
{
addr = (await networkManager.GetHostAddressesAsync(host).ConfigureAwait(false)).FirstOrDefault() ??
GetIpAnyAddress(listener);
}
catch
{
addr = GetIpAnyAddress(listener);
}
}
Dictionary<int, EndPointListener> p = null; // Dictionary<int, EndPointListener>
if (!ip_to_endpoints.TryGetValue(addr.Address, out p))
{
p = new Dictionary<int, EndPointListener>();
ip_to_endpoints[addr.Address] = p;
}
EndPointListener epl = null;
if (p.ContainsKey(port))
{
epl = (EndPointListener)p[port];
}
else
{
epl = new EndPointListener(listener, addr, port, secure, listener.Certificate, logger, listener.CryptoProvider, listener.StreamFactory, listener.SocketFactory, listener.MemoryStreamFactory, listener.TextEncoding, listener.FileSystem, listener.EnvironmentInfo);
p[port] = epl;
}
return epl;
}
public static void RemoveEndPoint(EndPointListener epl, IpEndPointInfo ep)
{
lock (ip_to_endpoints)
{
// Dictionary<int, EndPointListener> p
Dictionary<int, EndPointListener> p;
if (ip_to_endpoints.TryGetValue(ep.IpAddress.Address, out p))
{
p.Remove(ep.Port);
if (p.Count == 0)
{
ip_to_endpoints.Remove(ep.IpAddress.Address);
}
}
epl.Close();
}
}
public static void RemoveListener(ILogger logger, HttpListener listener)
{
lock (ip_to_endpoints)
{
foreach (string prefix in listener.Prefixes)
{
RemovePrefixInternal(logger, prefix, listener);
}
}
}
public static void RemovePrefix(ILogger logger, string prefix, HttpListener listener)
{
lock (ip_to_endpoints)
{
RemovePrefixInternal(logger, prefix, listener);
}
}
static void RemovePrefixInternal(ILogger logger, string prefix, HttpListener listener)
{
ListenerPrefix lp = new ListenerPrefix(prefix);
if (lp.Path.IndexOf('%') != -1)
return;
if (lp.Path.IndexOf("//", StringComparison.Ordinal) != -1)
return;
EndPointListener epl = GetEPListener(logger, lp.Host, lp.Port, listener, lp.Secure).Result;
epl.RemovePrefix(lp, listener);
}
}
}

View File

@@ -0,0 +1,547 @@
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using MediaBrowser.Model.Cryptography;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.System;
using MediaBrowser.Model.Text;
using SocketHttpListener.Primitives;
namespace SocketHttpListener.Net
{
sealed class HttpConnection
{
const int BufferSize = 8192;
IAcceptSocket sock;
Stream stream;
EndPointListener epl;
MemoryStream ms;
byte[] buffer;
HttpListenerContext context;
StringBuilder current_line;
ListenerPrefix prefix;
HttpRequestStream i_stream;
Stream o_stream;
bool chunked;
int reuses;
bool context_bound;
bool secure;
int s_timeout = 300000; // 90k ms for first request, 15k ms from then on
IpEndPointInfo local_ep;
HttpListener last_listener;
int[] client_cert_errors;
ICertificate cert;
Stream ssl_stream;
private readonly ILogger _logger;
private readonly ICryptoProvider _cryptoProvider;
private readonly IMemoryStreamFactory _memoryStreamFactory;
private readonly ITextEncoding _textEncoding;
private readonly IStreamFactory _streamFactory;
private readonly IFileSystem _fileSystem;
private readonly IEnvironmentInfo _environment;
private HttpConnection(ILogger logger, IAcceptSocket sock, EndPointListener epl, bool secure, ICertificate cert, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding, IFileSystem fileSystem, IEnvironmentInfo environment)
{
_logger = logger;
this.sock = sock;
this.epl = epl;
this.secure = secure;
this.cert = cert;
_cryptoProvider = cryptoProvider;
_memoryStreamFactory = memoryStreamFactory;
_textEncoding = textEncoding;
_fileSystem = fileSystem;
_environment = environment;
_streamFactory = streamFactory;
}
private async Task InitStream()
{
if (secure == false)
{
stream = _streamFactory.CreateNetworkStream(sock, false);
}
else
{
//ssl_stream = epl.Listener.CreateSslStream(new NetworkStream(sock, false), false, (t, c, ch, e) =>
//{
// if (c == null)
// return true;
// var c2 = c as X509Certificate2;
// if (c2 == null)
// c2 = new X509Certificate2(c.GetRawCertData());
// client_cert = c2;
// client_cert_errors = new int[] { (int)e };
// return true;
//});
//stream = ssl_stream.AuthenticatedStream;
ssl_stream = _streamFactory.CreateSslStream(_streamFactory.CreateNetworkStream(sock, false), false);
await _streamFactory.AuthenticateSslStreamAsServer(ssl_stream, cert).ConfigureAwait(false);
stream = ssl_stream;
}
Init();
}
public static async Task<HttpConnection> Create(ILogger logger, IAcceptSocket sock, EndPointListener epl, bool secure, ICertificate cert, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding, IFileSystem fileSystem, IEnvironmentInfo environment)
{
var connection = new HttpConnection(logger, sock, epl, secure, cert, cryptoProvider, streamFactory, memoryStreamFactory, textEncoding, fileSystem, environment);
await connection.InitStream().ConfigureAwait(false);
return connection;
}
public Stream Stream
{
get
{
return stream;
}
}
internal int[] ClientCertificateErrors
{
get { return client_cert_errors; }
}
void Init()
{
if (ssl_stream != null)
{
//ssl_stream.AuthenticateAsServer(client_cert, true, (SslProtocols)ServicePointManager.SecurityProtocol, false);
//_streamFactory.AuthenticateSslStreamAsServer(ssl_stream, cert);
}
context_bound = false;
i_stream = null;
o_stream = null;
prefix = null;
chunked = false;
ms = _memoryStreamFactory.CreateNew();
position = 0;
input_state = InputState.RequestLine;
line_state = LineState.None;
context = new HttpListenerContext(this, _logger, _cryptoProvider, _memoryStreamFactory, _textEncoding, _fileSystem);
}
public bool IsClosed
{
get { return (sock == null); }
}
public int Reuses
{
get { return reuses; }
}
public IpEndPointInfo LocalEndPoint
{
get
{
if (local_ep != null)
return local_ep;
local_ep = (IpEndPointInfo)sock.LocalEndPoint;
return local_ep;
}
}
public IpEndPointInfo RemoteEndPoint
{
get { return (IpEndPointInfo)sock.RemoteEndPoint; }
}
public bool IsSecure
{
get { return secure; }
}
public ListenerPrefix Prefix
{
get { return prefix; }
set { prefix = value; }
}
public async Task BeginReadRequest()
{
if (buffer == null)
buffer = new byte[BufferSize];
try
{
//if (reuses == 1)
// s_timeout = 15000;
var nRead = await stream.ReadAsync(buffer, 0, BufferSize).ConfigureAwait(false);
OnReadInternal(nRead);
}
catch (Exception ex)
{
OnReadInternalException(ms, ex);
}
}
public HttpRequestStream GetRequestStream(bool chunked, long contentlength)
{
if (i_stream == null)
{
byte[] buffer;
_memoryStreamFactory.TryGetBuffer(ms, out buffer);
int length = (int)ms.Length;
ms = null;
if (chunked)
{
this.chunked = true;
//context.Response.SendChunked = true;
i_stream = new ChunkedInputStream(context, stream, buffer, position, length - position);
}
else
{
i_stream = new HttpRequestStream(stream, buffer, position, length - position, contentlength);
}
}
return i_stream;
}
public Stream GetResponseStream(bool isExpect100Continue = false)
{
// TODO: can we get this stream before reading the input?
if (o_stream == null)
{
//context.Response.DetermineIfChunked();
var supportsDirectSocketAccess = !context.Response.SendChunked && !isExpect100Continue && !secure;
o_stream = new ResponseStream(stream, context.Response, _memoryStreamFactory, _textEncoding, _fileSystem, sock, supportsDirectSocketAccess, _logger, _environment);
}
return o_stream;
}
void OnReadInternal(int nread)
{
ms.Write(buffer, 0, nread);
if (ms.Length > 32768)
{
SendError("Bad request", 400);
Close(true);
return;
}
if (nread == 0)
{
//if (ms.Length > 0)
// SendError (); // Why bother?
CloseSocket();
Unbind();
return;
}
if (ProcessInput(ms))
{
if (!context.HaveError)
context.Request.FinishInitialization();
if (context.HaveError)
{
SendError();
Close(true);
return;
}
if (!epl.BindContext(context))
{
SendError("Invalid host", 400);
Close(true);
return;
}
HttpListener listener = epl.Listener;
if (last_listener != listener)
{
RemoveConnection();
listener.AddConnection(this);
last_listener = listener;
}
context_bound = true;
listener.RegisterContext(context);
return;
}
BeginReadRequest();
}
private void OnReadInternalException(MemoryStream ms, Exception ex)
{
//_logger.ErrorException("Error in HttpConnection.OnReadInternal", ex);
if (ms != null && ms.Length > 0)
SendError();
if (sock != null)
{
CloseSocket();
Unbind();
}
}
void RemoveConnection()
{
if (last_listener == null)
epl.RemoveConnection(this);
else
last_listener.RemoveConnection(this);
}
enum InputState
{
RequestLine,
Headers
}
enum LineState
{
None,
CR,
LF
}
InputState input_state = InputState.RequestLine;
LineState line_state = LineState.None;
int position;
// true -> done processing
// false -> need more input
bool ProcessInput(MemoryStream ms)
{
byte[] buffer;
_memoryStreamFactory.TryGetBuffer(ms, out buffer);
int len = (int)ms.Length;
int used = 0;
string line;
while (true)
{
if (context.HaveError)
return true;
if (position >= len)
break;
try
{
line = ReadLine(buffer, position, len - position, ref used);
position += used;
}
catch
{
context.ErrorMessage = "Bad request";
context.ErrorStatus = 400;
return true;
}
if (line == null)
break;
if (line == "")
{
if (input_state == InputState.RequestLine)
continue;
current_line = null;
ms = null;
return true;
}
if (input_state == InputState.RequestLine)
{
context.Request.SetRequestLine(line);
input_state = InputState.Headers;
}
else
{
try
{
context.Request.AddHeader(line);
}
catch (Exception e)
{
context.ErrorMessage = e.Message;
context.ErrorStatus = 400;
return true;
}
}
}
if (used == len)
{
ms.SetLength(0);
position = 0;
}
return false;
}
string ReadLine(byte[] buffer, int offset, int len, ref int used)
{
if (current_line == null)
current_line = new StringBuilder(128);
int last = offset + len;
used = 0;
for (int i = offset; i < last && line_state != LineState.LF; i++)
{
used++;
byte b = buffer[i];
if (b == 13)
{
line_state = LineState.CR;
}
else if (b == 10)
{
line_state = LineState.LF;
}
else
{
current_line.Append((char)b);
}
}
string result = null;
if (line_state == LineState.LF)
{
line_state = LineState.None;
result = current_line.ToString();
current_line.Length = 0;
}
return result;
}
public void SendError(string msg, int status)
{
try
{
HttpListenerResponse response = context.Response;
response.StatusCode = status;
response.ContentType = "text/html";
string description = HttpListenerResponse.GetStatusDescription(status);
string str;
if (msg != null)
str = String.Format("<h1>{0} ({1})</h1>", description, msg);
else
str = String.Format("<h1>{0}</h1>", description);
byte[] error = context.Response.ContentEncoding.GetBytes(str);
response.ContentLength64 = error.Length;
response.OutputStream.Write(error, 0, (int)error.Length);
response.Close();
}
catch
{
// response was already closed
}
}
public void SendError()
{
SendError(context.ErrorMessage, context.ErrorStatus);
}
void Unbind()
{
if (context_bound)
{
epl.UnbindContext(context);
context_bound = false;
}
}
public void Close()
{
Close(false);
}
private void CloseSocket()
{
if (sock == null)
return;
try
{
sock.Close();
}
catch
{
}
finally
{
sock = null;
}
RemoveConnection();
}
internal void Close(bool force_close)
{
if (sock != null)
{
if (!context.Request.IsWebSocketRequest || force_close)
{
Stream st = GetResponseStream();
if (st != null)
{
st.Dispose();
}
o_stream = null;
}
}
if (sock != null)
{
force_close |= !context.Request.KeepAlive;
if (!force_close)
force_close = (string.Equals(context.Response.Headers["connection"], "close", StringComparison.OrdinalIgnoreCase));
/*
if (!force_close) {
// bool conn_close = (status_code == 400 || status_code == 408 || status_code == 411 ||
// status_code == 413 || status_code == 414 || status_code == 500 ||
// status_code == 503);
force_close |= (context.Request.ProtocolVersion <= HttpVersion.Version10);
}
*/
if (!force_close && context.Request.FlushInput())
{
reuses++;
Unbind();
Init();
BeginReadRequest();
return;
}
IAcceptSocket s = sock;
sock = null;
try
{
if (s != null)
s.Shutdown(true);
}
catch
{
}
finally
{
if (s != null)
s.Close();
}
Unbind();
RemoveConnection();
return;
}
}
}
}

View File

@@ -0,0 +1,293 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Net;
using MediaBrowser.Common.Net;
using MediaBrowser.Model.Cryptography;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.System;
using MediaBrowser.Model.Text;
using SocketHttpListener.Primitives;
namespace SocketHttpListener.Net
{
public sealed class HttpListener : IDisposable
{
internal ICryptoProvider CryptoProvider { get; private set; }
internal IStreamFactory StreamFactory { get; private set; }
internal ISocketFactory SocketFactory { get; private set; }
internal IFileSystem FileSystem { get; private set; }
internal ITextEncoding TextEncoding { get; private set; }
internal IMemoryStreamFactory MemoryStreamFactory { get; private set; }
internal INetworkManager NetworkManager { get; private set; }
internal IEnvironmentInfo EnvironmentInfo { get; private set; }
public bool EnableDualMode { get; set; }
AuthenticationSchemes auth_schemes;
HttpListenerPrefixCollection prefixes;
AuthenticationSchemeSelector auth_selector;
string realm;
bool unsafe_ntlm_auth;
bool listening;
bool disposed;
Dictionary<HttpListenerContext, HttpListenerContext> registry; // Dictionary<HttpListenerContext,HttpListenerContext>
Dictionary<HttpConnection, HttpConnection> connections;
private ILogger _logger;
private ICertificate _certificate;
public Action<HttpListenerContext> OnContext { get; set; }
public HttpListener(ILogger logger, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, ISocketFactory socketFactory, INetworkManager networkManager, ITextEncoding textEncoding, IMemoryStreamFactory memoryStreamFactory, IFileSystem fileSystem, IEnvironmentInfo environmentInfo)
{
_logger = logger;
CryptoProvider = cryptoProvider;
StreamFactory = streamFactory;
SocketFactory = socketFactory;
NetworkManager = networkManager;
TextEncoding = textEncoding;
MemoryStreamFactory = memoryStreamFactory;
FileSystem = fileSystem;
EnvironmentInfo = environmentInfo;
prefixes = new HttpListenerPrefixCollection(logger, this);
registry = new Dictionary<HttpListenerContext, HttpListenerContext>();
connections = new Dictionary<HttpConnection, HttpConnection>();
auth_schemes = AuthenticationSchemes.Anonymous;
}
public HttpListener(ICertificate certificate, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, ISocketFactory socketFactory, INetworkManager networkManager, ITextEncoding textEncoding, IMemoryStreamFactory memoryStreamFactory, IFileSystem fileSystem, IEnvironmentInfo environmentInfo)
:this(new NullLogger(), certificate, cryptoProvider, streamFactory, socketFactory, networkManager, textEncoding, memoryStreamFactory, fileSystem, environmentInfo)
{
}
public HttpListener(ILogger logger, ICertificate certificate, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, ISocketFactory socketFactory, INetworkManager networkManager, ITextEncoding textEncoding, IMemoryStreamFactory memoryStreamFactory, IFileSystem fileSystem, IEnvironmentInfo environmentInfo)
: this(logger, cryptoProvider, streamFactory, socketFactory, networkManager, textEncoding, memoryStreamFactory, fileSystem, environmentInfo)
{
_certificate = certificate;
}
public void LoadCert(ICertificate cert)
{
_certificate = cert;
}
// TODO: Digest, NTLM and Negotiate require ControlPrincipal
public AuthenticationSchemes AuthenticationSchemes
{
get { return auth_schemes; }
set
{
CheckDisposed();
auth_schemes = value;
}
}
public AuthenticationSchemeSelector AuthenticationSchemeSelectorDelegate
{
get { return auth_selector; }
set
{
CheckDisposed();
auth_selector = value;
}
}
public bool IsListening
{
get { return listening; }
}
public static bool IsSupported
{
get { return true; }
}
public HttpListenerPrefixCollection Prefixes
{
get
{
CheckDisposed();
return prefixes;
}
}
// TODO: use this
public string Realm
{
get { return realm; }
set
{
CheckDisposed();
realm = value;
}
}
public bool UnsafeConnectionNtlmAuthentication
{
get { return unsafe_ntlm_auth; }
set
{
CheckDisposed();
unsafe_ntlm_auth = value;
}
}
//internal IMonoSslStream CreateSslStream(Stream innerStream, bool ownsStream, MSI.MonoRemoteCertificateValidationCallback callback)
//{
// lock (registry)
// {
// if (tlsProvider == null)
// tlsProvider = MonoTlsProviderFactory.GetProviderInternal();
// if (tlsSettings == null)
// tlsSettings = MSI.MonoTlsSettings.CopyDefaultSettings();
// if (tlsSettings.RemoteCertificateValidationCallback == null)
// tlsSettings.RemoteCertificateValidationCallback = callback;
// return tlsProvider.CreateSslStream(innerStream, ownsStream, tlsSettings);
// }
//}
internal ICertificate Certificate
{
get { return _certificate; }
}
public void Abort()
{
if (disposed)
return;
if (!listening)
{
return;
}
Close(true);
}
public void Close()
{
if (disposed)
return;
if (!listening)
{
disposed = true;
return;
}
Close(true);
disposed = true;
}
void Close(bool force)
{
CheckDisposed();
EndPointManager.RemoveListener(_logger, this);
Cleanup(force);
}
void Cleanup(bool close_existing)
{
lock (registry)
{
if (close_existing)
{
// Need to copy this since closing will call UnregisterContext
ICollection keys = registry.Keys;
var all = new HttpListenerContext[keys.Count];
keys.CopyTo(all, 0);
registry.Clear();
for (int i = all.Length - 1; i >= 0; i--)
all[i].Connection.Close(true);
}
lock (connections)
{
ICollection keys = connections.Keys;
var conns = new HttpConnection[keys.Count];
keys.CopyTo(conns, 0);
connections.Clear();
for (int i = conns.Length - 1; i >= 0; i--)
conns[i].Close(true);
}
}
}
internal AuthenticationSchemes SelectAuthenticationScheme(HttpListenerContext context)
{
if (AuthenticationSchemeSelectorDelegate != null)
return AuthenticationSchemeSelectorDelegate(context.Request);
else
return auth_schemes;
}
public void Start()
{
CheckDisposed();
if (listening)
return;
EndPointManager.AddListener(_logger, this);
listening = true;
}
public void Stop()
{
CheckDisposed();
listening = false;
Close(false);
}
void IDisposable.Dispose()
{
if (disposed)
return;
Close(true); //TODO: Should we force here or not?
disposed = true;
}
internal void CheckDisposed()
{
if (disposed)
throw new ObjectDisposedException(GetType().ToString());
}
internal void RegisterContext(HttpListenerContext context)
{
if (OnContext != null && IsListening)
{
OnContext(context);
}
lock (registry)
registry[context] = context;
}
internal void UnregisterContext(HttpListenerContext context)
{
lock (registry)
registry.Remove(context);
}
internal void AddConnection(HttpConnection cnc)
{
lock (connections)
{
connections[cnc] = cnc;
}
}
internal void RemoveConnection(HttpConnection cnc)
{
lock (connections)
{
connections.Remove(cnc);
}
}
}
}

View File

@@ -0,0 +1,70 @@
using System.Security.Principal;
namespace SocketHttpListener.Net
{
public class HttpListenerBasicIdentity : GenericIdentity
{
string password;
public HttpListenerBasicIdentity(string username, string password)
: base(username, "Basic")
{
this.password = password;
}
public virtual string Password
{
get { return password; }
}
}
public class GenericIdentity : IIdentity
{
private string m_name;
private string m_type;
public GenericIdentity(string name)
{
if (name == null)
throw new System.ArgumentNullException("name");
m_name = name;
m_type = "";
}
public GenericIdentity(string name, string type)
{
if (name == null)
throw new System.ArgumentNullException("name");
if (type == null)
throw new System.ArgumentNullException("type");
m_name = name;
m_type = type;
}
public virtual string Name
{
get
{
return m_name;
}
}
public virtual string AuthenticationType
{
get
{
return m_type;
}
}
public virtual bool IsAuthenticated
{
get
{
return !m_name.Equals("");
}
}
}
}

View File

@@ -0,0 +1,198 @@
using System;
using System.Net;
using System.Security.Principal;
using MediaBrowser.Model.Cryptography;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Text;
using SocketHttpListener.Net.WebSockets;
using SocketHttpListener.Primitives;
namespace SocketHttpListener.Net
{
public sealed class HttpListenerContext
{
HttpListenerRequest request;
HttpListenerResponse response;
IPrincipal user;
HttpConnection cnc;
string error;
int err_status = 400;
private readonly ICryptoProvider _cryptoProvider;
private readonly IMemoryStreamFactory _memoryStreamFactory;
private readonly ITextEncoding _textEncoding;
internal HttpListenerContext(HttpConnection cnc, ILogger logger, ICryptoProvider cryptoProvider, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding, IFileSystem fileSystem)
{
this.cnc = cnc;
_cryptoProvider = cryptoProvider;
_memoryStreamFactory = memoryStreamFactory;
_textEncoding = textEncoding;
request = new HttpListenerRequest(this, _textEncoding);
response = new HttpListenerResponse(this, logger, _textEncoding, fileSystem);
}
internal int ErrorStatus
{
get { return err_status; }
set { err_status = value; }
}
internal string ErrorMessage
{
get { return error; }
set { error = value; }
}
internal bool HaveError
{
get { return (error != null); }
}
internal HttpConnection Connection
{
get { return cnc; }
}
public HttpListenerRequest Request
{
get { return request; }
}
public HttpListenerResponse Response
{
get { return response; }
}
public IPrincipal User
{
get { return user; }
}
internal void ParseAuthentication(AuthenticationSchemes expectedSchemes)
{
if (expectedSchemes == AuthenticationSchemes.Anonymous)
return;
// TODO: Handle NTLM/Digest modes
string header = request.Headers["Authorization"];
if (header == null || header.Length < 2)
return;
string[] authenticationData = header.Split(new char[] { ' ' }, 2);
if (string.Equals(authenticationData[0], "basic", StringComparison.OrdinalIgnoreCase))
{
user = ParseBasicAuthentication(authenticationData[1]);
}
// TODO: throw if malformed -> 400 bad request
}
internal IPrincipal ParseBasicAuthentication(string authData)
{
try
{
// Basic AUTH Data is a formatted Base64 String
//string domain = null;
string user = null;
string password = null;
int pos = -1;
var authDataBytes = Convert.FromBase64String(authData);
string authString = _textEncoding.GetDefaultEncoding().GetString(authDataBytes, 0, authDataBytes.Length);
// The format is DOMAIN\username:password
// Domain is optional
pos = authString.IndexOf(':');
// parse the password off the end
password = authString.Substring(pos + 1);
// discard the password
authString = authString.Substring(0, pos);
// check if there is a domain
pos = authString.IndexOf('\\');
if (pos > 0)
{
//domain = authString.Substring (0, pos);
user = authString.Substring(pos);
}
else
{
user = authString;
}
HttpListenerBasicIdentity identity = new HttpListenerBasicIdentity(user, password);
// TODO: What are the roles MS sets
return new GenericPrincipal(identity, new string[0]);
}
catch (Exception)
{
// Invalid auth data is swallowed silently
return null;
}
}
public HttpListenerWebSocketContext AcceptWebSocket(string protocol)
{
if (protocol != null)
{
if (protocol.Length == 0)
throw new ArgumentException("An empty string.", "protocol");
if (!protocol.IsToken())
throw new ArgumentException("Contains an invalid character.", "protocol");
}
return new HttpListenerWebSocketContext(this, protocol, _cryptoProvider, _memoryStreamFactory);
}
}
public class GenericPrincipal : IPrincipal
{
private IIdentity m_identity;
private string[] m_roles;
public GenericPrincipal(IIdentity identity, string[] roles)
{
if (identity == null)
throw new ArgumentNullException("identity");
m_identity = identity;
if (roles != null)
{
m_roles = new string[roles.Length];
for (int i = 0; i < roles.Length; ++i)
{
m_roles[i] = roles[i];
}
}
else
{
m_roles = null;
}
}
public virtual IIdentity Identity
{
get
{
return m_identity;
}
}
public virtual bool IsInRole(string role)
{
if (role == null || m_roles == null)
return false;
for (int i = 0; i < m_roles.Length; ++i)
{
if (m_roles[i] != null && String.Compare(m_roles[i], role, StringComparison.OrdinalIgnoreCase) == 0)
return true;
}
return false;
}
}
}

View File

@@ -0,0 +1,97 @@
using System;
using System.Collections;
using System.Collections.Generic;
using MediaBrowser.Model.Logging;
namespace SocketHttpListener.Net
{
public class HttpListenerPrefixCollection : ICollection<string>, IEnumerable<string>, IEnumerable
{
List<string> prefixes = new List<string>();
HttpListener listener;
private ILogger _logger;
internal HttpListenerPrefixCollection(ILogger logger, HttpListener listener)
{
_logger = logger;
this.listener = listener;
}
public int Count
{
get { return prefixes.Count; }
}
public bool IsReadOnly
{
get { return false; }
}
public bool IsSynchronized
{
get { return false; }
}
public void Add(string uriPrefix)
{
listener.CheckDisposed();
ListenerPrefix.CheckUri(uriPrefix);
if (prefixes.Contains(uriPrefix))
return;
prefixes.Add(uriPrefix);
if (listener.IsListening)
EndPointManager.AddPrefix(_logger, uriPrefix, listener);
}
public void Clear()
{
listener.CheckDisposed();
prefixes.Clear();
if (listener.IsListening)
EndPointManager.RemoveListener(_logger, listener);
}
public bool Contains(string uriPrefix)
{
listener.CheckDisposed();
return prefixes.Contains(uriPrefix);
}
public void CopyTo(string[] array, int offset)
{
listener.CheckDisposed();
prefixes.CopyTo(array, offset);
}
public void CopyTo(Array array, int offset)
{
listener.CheckDisposed();
((ICollection)prefixes).CopyTo(array, offset);
}
public IEnumerator<string> GetEnumerator()
{
return prefixes.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return prefixes.GetEnumerator();
}
public bool Remove(string uriPrefix)
{
listener.CheckDisposed();
if (uriPrefix == null)
throw new ArgumentNullException("uriPrefix");
bool result = prefixes.Remove(uriPrefix);
if (result && listener.IsListening)
EndPointManager.RemovePrefix(_logger, uriPrefix, listener);
return result;
}
}
}

View File

@@ -0,0 +1,654 @@
using System;
using System.Collections.Specialized;
using System.Globalization;
using System.IO;
using System.Net;
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 keep_alive;
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 = (ResponseStream)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 { return RemoteEndPoint.IpAddress.Equals(IpAddressInfo.Loopback) || RemoteEndPoint.IpAddress.Equals(IpAddressInfo.IPv6Loopback) || LocalEndPoint.IpAddress.Equals(RemoteEndPoint.IpAddress); }
}
public bool IsSecureConnection
{
get { return context.Connection.IsSecure; }
}
public bool KeepAlive
{
get
{
if (ka_set)
return keep_alive;
ka_set = true;
// 1. Connection header
// 2. Protocol (1.1 == keep-alive by default)
// 3. Keep-Alive header
string cnc = headers["Connection"];
if (!String.IsNullOrEmpty(cnc))
{
keep_alive = (0 == String.Compare(cnc, "keep-alive", StringComparison.OrdinalIgnoreCase));
}
else if (version == HttpVersion.Version11)
{
keep_alive = true;
}
else
{
cnc = headers["keep-alive"];
if (!String.IsNullOrEmpty(cnc))
keep_alive = (0 != String.Compare(cnc, "closed", StringComparison.OrdinalIgnoreCase));
}
return keep_alive;
}
}
public IpEndPointInfo 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 IpEndPointInfo 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;
}
}
public Task<ICertificate> GetClientCertificateAsync()
{
return Task.FromResult<ICertificate>(null);
}
}
}

View File

@@ -0,0 +1,525 @@
using System;
using System.Globalization;
using System.IO;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Text;
using SocketHttpListener.Primitives;
namespace SocketHttpListener.Net
{
public sealed class HttpListenerResponse : IDisposable
{
bool disposed;
Encoding content_encoding;
long content_length;
bool cl_set;
string content_type;
CookieCollection cookies;
WebHeaderCollection headers = new WebHeaderCollection();
bool keep_alive = true;
Stream output_stream;
Version version = HttpVersion.Version11;
string location;
int status_code = 200;
string status_description = "OK";
bool chunked;
HttpListenerContext context;
internal bool HeadersSent;
internal object headers_lock = new object();
private readonly ILogger _logger;
private readonly ITextEncoding _textEncoding;
private readonly IFileSystem _fileSystem;
internal HttpListenerResponse(HttpListenerContext context, ILogger logger, ITextEncoding textEncoding, IFileSystem fileSystem)
{
this.context = context;
_logger = logger;
_textEncoding = textEncoding;
_fileSystem = fileSystem;
}
internal bool CloseConnection
{
get
{
return headers["Connection"] == "close";
}
}
public Encoding ContentEncoding
{
get
{
if (content_encoding == null)
content_encoding = _textEncoding.GetDefaultEncoding();
return content_encoding;
}
set
{
if (disposed)
throw new ObjectDisposedException(GetType().ToString());
content_encoding = value;
}
}
public long ContentLength64
{
get { return content_length; }
set
{
if (disposed)
throw new ObjectDisposedException(GetType().ToString());
if (HeadersSent)
throw new InvalidOperationException("Cannot be changed after headers are sent.");
if (value < 0)
throw new ArgumentOutOfRangeException("Must be >= 0", "value");
cl_set = true;
content_length = value;
}
}
public string ContentType
{
get { return content_type; }
set
{
// TODO: is null ok?
if (disposed)
throw new ObjectDisposedException(GetType().ToString());
content_type = value;
}
}
// RFC 2109, 2965 + the netscape specification at http://wp.netscape.com/newsref/std/cookie_spec.html
public CookieCollection Cookies
{
get
{
if (cookies == null)
cookies = new CookieCollection();
return cookies;
}
set { cookies = value; } // null allowed?
}
public WebHeaderCollection Headers
{
get { return headers; }
set
{
/**
* "If you attempt to set a Content-Length, Keep-Alive, Transfer-Encoding, or
* WWW-Authenticate header using the Headers property, an exception will be
* thrown. Use the KeepAlive or ContentLength64 properties to set these headers.
* You cannot set the Transfer-Encoding or WWW-Authenticate headers manually."
*/
// TODO: check if this is marked readonly after headers are sent.
headers = value;
}
}
public bool KeepAlive
{
get { return keep_alive; }
set
{
if (disposed)
throw new ObjectDisposedException(GetType().ToString());
keep_alive = value;
}
}
public Stream OutputStream
{
get
{
if (output_stream == null)
output_stream = context.Connection.GetResponseStream();
return output_stream;
}
}
public Version ProtocolVersion
{
get { return version; }
set
{
if (disposed)
throw new ObjectDisposedException(GetType().ToString());
if (value == null)
throw new ArgumentNullException("value");
if (value.Major != 1 || (value.Minor != 0 && value.Minor != 1))
throw new ArgumentException("Must be 1.0 or 1.1", "value");
if (disposed)
throw new ObjectDisposedException(GetType().ToString());
version = value;
}
}
public string RedirectLocation
{
get { return location; }
set
{
if (disposed)
throw new ObjectDisposedException(GetType().ToString());
location = value;
}
}
public bool SendChunked
{
get { return chunked; }
set
{
if (disposed)
throw new ObjectDisposedException(GetType().ToString());
chunked = value;
}
}
public int StatusCode
{
get { return status_code; }
set
{
if (disposed)
throw new ObjectDisposedException(GetType().ToString());
if (value < 100 || value > 999)
throw new ProtocolViolationException("StatusCode must be between 100 and 999.");
status_code = value;
status_description = GetStatusDescription(value);
}
}
internal static string GetStatusDescription(int code)
{
switch (code)
{
case 100: return "Continue";
case 101: return "Switching Protocols";
case 102: return "Processing";
case 200: return "OK";
case 201: return "Created";
case 202: return "Accepted";
case 203: return "Non-Authoritative Information";
case 204: return "No Content";
case 205: return "Reset Content";
case 206: return "Partial Content";
case 207: return "Multi-Status";
case 300: return "Multiple Choices";
case 301: return "Moved Permanently";
case 302: return "Found";
case 303: return "See Other";
case 304: return "Not Modified";
case 305: return "Use Proxy";
case 307: return "Temporary Redirect";
case 400: return "Bad Request";
case 401: return "Unauthorized";
case 402: return "Payment Required";
case 403: return "Forbidden";
case 404: return "Not Found";
case 405: return "Method Not Allowed";
case 406: return "Not Acceptable";
case 407: return "Proxy Authentication Required";
case 408: return "Request Timeout";
case 409: return "Conflict";
case 410: return "Gone";
case 411: return "Length Required";
case 412: return "Precondition Failed";
case 413: return "Request Entity Too Large";
case 414: return "Request-Uri Too Long";
case 415: return "Unsupported Media Type";
case 416: return "Requested Range Not Satisfiable";
case 417: return "Expectation Failed";
case 422: return "Unprocessable Entity";
case 423: return "Locked";
case 424: return "Failed Dependency";
case 500: return "Internal Server Error";
case 501: return "Not Implemented";
case 502: return "Bad Gateway";
case 503: return "Service Unavailable";
case 504: return "Gateway Timeout";
case 505: return "Http Version Not Supported";
case 507: return "Insufficient Storage";
}
return "";
}
public string StatusDescription
{
get { return status_description; }
set
{
status_description = value;
}
}
void IDisposable.Dispose()
{
Close(true); //TODO: Abort or Close?
}
public void Abort()
{
if (disposed)
return;
Close(true);
}
public void AddHeader(string name, string value)
{
if (name == null)
throw new ArgumentNullException("name");
if (name == "")
throw new ArgumentException("'name' cannot be empty", "name");
//TODO: check for forbidden headers and invalid characters
if (value.Length > 65535)
throw new ArgumentOutOfRangeException("value");
headers.Set(name, value);
}
public void AppendCookie(Cookie cookie)
{
if (cookie == null)
throw new ArgumentNullException("cookie");
Cookies.Add(cookie);
}
public void AppendHeader(string name, string value)
{
if (name == null)
throw new ArgumentNullException("name");
if (name == "")
throw new ArgumentException("'name' cannot be empty", "name");
if (value.Length > 65535)
throw new ArgumentOutOfRangeException("value");
headers.Add(name, value);
}
private void Close(bool force)
{
if (force)
{
_logger.Debug("HttpListenerResponse force closing HttpConnection");
}
disposed = true;
context.Connection.Close(force);
}
public void Close()
{
if (disposed)
return;
Close(false);
}
public void Redirect(string url)
{
StatusCode = 302; // Found
location = url;
}
bool FindCookie(Cookie cookie)
{
string name = cookie.Name;
string domain = cookie.Domain;
string path = cookie.Path;
foreach (Cookie c in cookies)
{
if (name != c.Name)
continue;
if (domain != c.Domain)
continue;
if (path == c.Path)
return true;
}
return false;
}
public void DetermineIfChunked()
{
if (chunked)
{
return;
}
Version v = context.Request.ProtocolVersion;
if (!cl_set && !chunked && v >= HttpVersion.Version11)
chunked = true;
if (!chunked && string.Equals(headers["Transfer-Encoding"], "chunked"))
{
chunked = true;
}
}
internal void SendHeaders(bool closing, MemoryStream ms)
{
Encoding encoding = content_encoding;
if (encoding == null)
encoding = _textEncoding.GetDefaultEncoding();
if (content_type != null)
{
if (content_encoding != null && content_type.IndexOf("charset=", StringComparison.OrdinalIgnoreCase) == -1)
{
string enc_name = content_encoding.WebName;
headers.SetInternal("Content-Type", content_type + "; charset=" + enc_name);
}
else
{
headers.SetInternal("Content-Type", content_type);
}
}
if (headers["Server"] == null)
headers.SetInternal("Server", "Mono-HTTPAPI/1.0");
CultureInfo inv = CultureInfo.InvariantCulture;
if (headers["Date"] == null)
headers.SetInternal("Date", DateTime.UtcNow.ToString("r", inv));
if (!chunked)
{
if (!cl_set && closing)
{
cl_set = true;
content_length = 0;
}
if (cl_set)
headers.SetInternal("Content-Length", content_length.ToString(inv));
}
Version v = context.Request.ProtocolVersion;
if (!cl_set && !chunked && v >= HttpVersion.Version11)
chunked = true;
/* Apache forces closing the connection for these status codes:
* HttpStatusCode.BadRequest 400
* HttpStatusCode.RequestTimeout 408
* HttpStatusCode.LengthRequired 411
* HttpStatusCode.RequestEntityTooLarge 413
* HttpStatusCode.RequestUriTooLong 414
* HttpStatusCode.InternalServerError 500
* HttpStatusCode.ServiceUnavailable 503
*/
bool conn_close = status_code == 400 || status_code == 408 || status_code == 411 ||
status_code == 413 || status_code == 414 ||
status_code == 500 ||
status_code == 503;
if (conn_close == false)
conn_close = !context.Request.KeepAlive;
// They sent both KeepAlive: true and Connection: close!?
if (!keep_alive || conn_close)
{
headers.SetInternal("Connection", "close");
conn_close = true;
}
if (chunked)
headers.SetInternal("Transfer-Encoding", "chunked");
//int reuses = context.Connection.Reuses;
//if (reuses >= 100)
//{
// _logger.Debug("HttpListenerResponse - keep alive has exceeded 100 uses and will be closed.");
// force_close_chunked = true;
// if (!conn_close)
// {
// headers.SetInternal("Connection", "close");
// conn_close = true;
// }
//}
if (!conn_close)
{
if (context.Request.ProtocolVersion <= HttpVersion.Version10)
headers.SetInternal("Connection", "keep-alive");
}
if (location != null)
headers.SetInternal("Location", location);
if (cookies != null)
{
foreach (Cookie cookie in cookies)
headers.SetInternal("Set-Cookie", cookie.ToString());
}
headers.SetInternal("Status", status_code.ToString(CultureInfo.InvariantCulture));
using (StreamWriter writer = new StreamWriter(ms, encoding, 256, true))
{
writer.Write("HTTP/{0} {1} {2}\r\n", version, status_code, status_description);
string headers_str = headers.ToStringMultiValue();
writer.Write(headers_str);
writer.Flush();
}
int preamble = encoding.GetPreamble().Length;
if (output_stream == null)
output_stream = context.Connection.GetResponseStream();
/* Assumes that the ms was at position 0 */
ms.Position = preamble;
HeadersSent = true;
}
public void SetCookie(Cookie cookie)
{
if (cookie == null)
throw new ArgumentNullException("cookie");
if (cookies != null)
{
if (FindCookie(cookie))
throw new ArgumentException("The cookie already exists.");
}
else
{
cookies = new CookieCollection();
}
cookies.Add(cookie);
}
public Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken)
{
return ((ResponseStream)OutputStream).TransmitFile(path, offset, count, fileShareMode, cancellationToken);
}
}
}

View File

@@ -0,0 +1,196 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.ExceptionServices;
using System.Text;
using System.Threading.Tasks;
namespace SocketHttpListener.Net
{
// Licensed to the .NET Foundation under one or more agreements.
// See the LICENSE file in the project root for more information.
//
// System.Net.ResponseStream
//
// Author:
// Gonzalo Paniagua Javier (gonzalo@novell.com)
//
// Copyright (c) 2005 Novell, Inc. (http://www.novell.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.
//
internal partial class HttpRequestStream : Stream
{
private byte[] _buffer;
private int _offset;
private int _length;
private long _remainingBody;
protected bool _closed;
private Stream _stream;
internal HttpRequestStream(Stream stream, byte[] buffer, int offset, int length)
: this(stream, buffer, offset, length, -1)
{
}
internal HttpRequestStream(Stream stream, byte[] buffer, int offset, int length, long contentlength)
{
_stream = stream;
_buffer = buffer;
_offset = offset;
_length = length;
_remainingBody = contentlength;
}
// Returns 0 if we can keep reading from the base stream,
// > 0 if we read something from the buffer.
// -1 if we had a content length set and we finished reading that many bytes.
private int FillFromBuffer(byte[] buffer, int offset, int count)
{
if (_remainingBody == 0)
return -1;
if (_length == 0)
return 0;
int size = Math.Min(_length, count);
if (_remainingBody > 0)
size = (int)Math.Min(size, _remainingBody);
if (_offset > _buffer.Length - size)
{
size = Math.Min(size, _buffer.Length - _offset);
}
if (size == 0)
return 0;
Buffer.BlockCopy(_buffer, _offset, buffer, offset, size);
_offset += size;
_length -= size;
if (_remainingBody > 0)
_remainingBody -= size;
return size;
}
protected virtual int ReadCore(byte[] buffer, int offset, int size)
{
// Call FillFromBuffer to check for buffer boundaries even when remaining_body is 0
int nread = FillFromBuffer(buffer, offset, size);
if (nread == -1)
{ // No more bytes available (Content-Length)
return 0;
}
else if (nread > 0)
{
return nread;
}
nread = _stream.Read(buffer, offset, size);
if (nread > 0 && _remainingBody > 0)
_remainingBody -= nread;
return nread;
}
protected virtual IAsyncResult BeginReadCore(byte[] buffer, int offset, int size, AsyncCallback cback, object state)
{
if (size == 0 || _closed)
{
HttpStreamAsyncResult ares = new HttpStreamAsyncResult(this);
ares._callback = cback;
ares._state = state;
ares.Complete();
return ares;
}
int nread = FillFromBuffer(buffer, offset, size);
if (nread > 0 || nread == -1)
{
HttpStreamAsyncResult ares = new HttpStreamAsyncResult(this);
ares._buffer = buffer;
ares._offset = offset;
ares._count = size;
ares._callback = cback;
ares._state = state;
ares._synchRead = Math.Max(0, nread);
ares.Complete();
return ares;
}
// Avoid reading past the end of the request to allow
// for HTTP pipelining
if (_remainingBody >= 0 && size > _remainingBody)
{
size = (int)Math.Min(int.MaxValue, _remainingBody);
}
return _stream.BeginRead(buffer, offset, size, cback, state);
}
public override int EndRead(IAsyncResult asyncResult)
{
if (asyncResult == null)
throw new ArgumentNullException(nameof(asyncResult));
var r = asyncResult as HttpStreamAsyncResult;
if (r != null)
{
if (!ReferenceEquals(this, r._parent))
{
throw new ArgumentException("Invalid async result");
}
if (r._endCalled)
{
throw new InvalidOperationException("Invalid end call");
}
r._endCalled = true;
if (!asyncResult.IsCompleted)
{
asyncResult.AsyncWaitHandle.WaitOne();
}
return r._synchRead;
}
if (_closed)
return 0;
int nread = 0;
try
{
nread = _stream.EndRead(asyncResult);
}
catch (IOException e) when (e.InnerException is ArgumentException || e.InnerException is InvalidOperationException)
{
throw e.InnerException;
}
if (_remainingBody > 0 && nread > 0)
{
_remainingBody -= nread;
}
return nread;
}
}
}

View File

@@ -0,0 +1,144 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace SocketHttpListener.Net
{
// Licensed to the .NET Foundation under one or more agreements.
// See the LICENSE file in the project root for more information.
//
// System.Net.ResponseStream
//
// Author:
// Gonzalo Paniagua Javier (gonzalo@novell.com)
//
// Copyright (c) 2005 Novell, Inc. (http://www.novell.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.
//
internal partial class HttpRequestStream : Stream
{
public override bool CanSeek => false;
public override bool CanWrite => false;
public override bool CanRead => true;
public override int Read(byte[] buffer, int offset, int size)
{
if (buffer == null)
{
throw new ArgumentNullException(nameof(buffer));
}
if (offset < 0 || offset > buffer.Length)
{
throw new ArgumentOutOfRangeException(nameof(offset));
}
if (size < 0 || size > buffer.Length - offset)
{
throw new ArgumentOutOfRangeException(nameof(size));
}
if (size == 0 || _closed)
{
return 0;
}
return ReadCore(buffer, offset, size);
}
public override IAsyncResult BeginRead(byte[] buffer, int offset, int size, AsyncCallback callback, object state)
{
if (buffer == null)
{
throw new ArgumentNullException(nameof(buffer));
}
if (offset < 0 || offset > buffer.Length)
{
throw new ArgumentOutOfRangeException(nameof(offset));
}
if (size < 0 || size > buffer.Length - offset)
{
throw new ArgumentOutOfRangeException(nameof(size));
}
return BeginReadCore(buffer, offset, size, callback, state);
}
public override void Flush() { }
public override Task FlushAsync(CancellationToken cancellationToken) => Task.CompletedTask;
public override long Length
{
get
{
throw new NotImplementedException();
}
}
public override long Position
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotImplementedException();
}
public override void SetLength(long value)
{
throw new NotImplementedException();
}
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotImplementedException();
}
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
{
return base.BeginWrite(buffer, offset, count, callback, state);
}
public override void EndWrite(IAsyncResult asyncResult)
{
base.EndWrite(asyncResult);
}
internal bool Closed => _closed;
protected override void Dispose(bool disposing)
{
_closed = true;
base.Dispose(disposing);
}
}
}

View File

@@ -0,0 +1,321 @@
namespace SocketHttpListener.Net
{
/// <summary>
/// Contains the values of the HTTP status codes.
/// </summary>
/// <remarks>
/// The HttpStatusCode enumeration contains the values of the HTTP status codes defined in
/// <see href="http://tools.ietf.org/html/rfc2616#section-10">RFC 2616</see> for HTTP 1.1.
/// </remarks>
public enum HttpStatusCode
{
/// <summary>
/// Equivalent to status code 100.
/// Indicates that the client should continue with its request.
/// </summary>
Continue = 100,
/// <summary>
/// Equivalent to status code 101.
/// Indicates that the server is switching the HTTP version or protocol on the connection.
/// </summary>
SwitchingProtocols = 101,
/// <summary>
/// Equivalent to status code 200.
/// Indicates that the client's request has succeeded.
/// </summary>
OK = 200,
/// <summary>
/// Equivalent to status code 201.
/// Indicates that the client's request has been fulfilled and resulted in a new resource being
/// created.
/// </summary>
Created = 201,
/// <summary>
/// Equivalent to status code 202.
/// Indicates that the client's request has been accepted for processing, but the processing
/// hasn't been completed.
/// </summary>
Accepted = 202,
/// <summary>
/// Equivalent to status code 203.
/// Indicates that the returned metainformation is from a local or a third-party copy instead of
/// the origin server.
/// </summary>
NonAuthoritativeInformation = 203,
/// <summary>
/// Equivalent to status code 204.
/// Indicates that the server has fulfilled the client's request but doesn't need to return
/// an entity-body.
/// </summary>
NoContent = 204,
/// <summary>
/// Equivalent to status code 205.
/// Indicates that the server has fulfilled the client's request, and the user agent should
/// reset the document view which caused the request to be sent.
/// </summary>
ResetContent = 205,
/// <summary>
/// Equivalent to status code 206.
/// Indicates that the server has fulfilled the partial GET request for the resource.
/// </summary>
PartialContent = 206,
/// <summary>
/// <para>
/// Equivalent to status code 300.
/// Indicates that the requested resource corresponds to any of multiple representations.
/// </para>
/// <para>
/// MultipleChoices is a synonym for Ambiguous.
/// </para>
/// </summary>
MultipleChoices = 300,
/// <summary>
/// <para>
/// Equivalent to status code 300.
/// Indicates that the requested resource corresponds to any of multiple representations.
/// </para>
/// <para>
/// Ambiguous is a synonym for MultipleChoices.
/// </para>
/// </summary>
Ambiguous = 300,
/// <summary>
/// <para>
/// Equivalent to status code 301.
/// Indicates that the requested resource has been assigned a new permanent URI and
/// any future references to this resource should use one of the returned URIs.
/// </para>
/// <para>
/// MovedPermanently is a synonym for Moved.
/// </para>
/// </summary>
MovedPermanently = 301,
/// <summary>
/// <para>
/// Equivalent to status code 301.
/// Indicates that the requested resource has been assigned a new permanent URI and
/// any future references to this resource should use one of the returned URIs.
/// </para>
/// <para>
/// Moved is a synonym for MovedPermanently.
/// </para>
/// </summary>
Moved = 301,
/// <summary>
/// <para>
/// Equivalent to status code 302.
/// Indicates that the requested resource is located temporarily under a different URI.
/// </para>
/// <para>
/// Found is a synonym for Redirect.
/// </para>
/// </summary>
Found = 302,
/// <summary>
/// <para>
/// Equivalent to status code 302.
/// Indicates that the requested resource is located temporarily under a different URI.
/// </para>
/// <para>
/// Redirect is a synonym for Found.
/// </para>
/// </summary>
Redirect = 302,
/// <summary>
/// <para>
/// Equivalent to status code 303.
/// Indicates that the response to the request can be found under a different URI and
/// should be retrieved using a GET method on that resource.
/// </para>
/// <para>
/// SeeOther is a synonym for RedirectMethod.
/// </para>
/// </summary>
SeeOther = 303,
/// <summary>
/// <para>
/// Equivalent to status code 303.
/// Indicates that the response to the request can be found under a different URI and
/// should be retrieved using a GET method on that resource.
/// </para>
/// <para>
/// RedirectMethod is a synonym for SeeOther.
/// </para>
/// </summary>
RedirectMethod = 303,
/// <summary>
/// Equivalent to status code 304.
/// Indicates that the client has performed a conditional GET request and access is allowed,
/// but the document hasn't been modified.
/// </summary>
NotModified = 304,
/// <summary>
/// Equivalent to status code 305.
/// Indicates that the requested resource must be accessed through the proxy given by
/// the Location field.
/// </summary>
UseProxy = 305,
/// <summary>
/// Equivalent to status code 306.
/// This status code was used in a previous version of the specification, is no longer used,
/// and is reserved for future use.
/// </summary>
Unused = 306,
/// <summary>
/// <para>
/// Equivalent to status code 307.
/// Indicates that the requested resource is located temporarily under a different URI.
/// </para>
/// <para>
/// TemporaryRedirect is a synonym for RedirectKeepVerb.
/// </para>
/// </summary>
TemporaryRedirect = 307,
/// <summary>
/// <para>
/// Equivalent to status code 307.
/// Indicates that the requested resource is located temporarily under a different URI.
/// </para>
/// <para>
/// RedirectKeepVerb is a synonym for TemporaryRedirect.
/// </para>
/// </summary>
RedirectKeepVerb = 307,
/// <summary>
/// Equivalent to status code 400.
/// Indicates that the client's request couldn't be understood by the server due to
/// malformed syntax.
/// </summary>
BadRequest = 400,
/// <summary>
/// Equivalent to status code 401.
/// Indicates that the client's request requires user authentication.
/// </summary>
Unauthorized = 401,
/// <summary>
/// Equivalent to status code 402.
/// This status code is reserved for future use.
/// </summary>
PaymentRequired = 402,
/// <summary>
/// Equivalent to status code 403.
/// Indicates that the server understood the client's request but is refusing to fulfill it.
/// </summary>
Forbidden = 403,
/// <summary>
/// Equivalent to status code 404.
/// Indicates that the server hasn't found anything matching the request URI.
/// </summary>
NotFound = 404,
/// <summary>
/// Equivalent to status code 405.
/// Indicates that the method specified in the request line isn't allowed for the resource
/// identified by the request URI.
/// </summary>
MethodNotAllowed = 405,
/// <summary>
/// Equivalent to status code 406.
/// Indicates that the server doesn't have the appropriate resource to respond to the Accept
/// headers in the client's request.
/// </summary>
NotAcceptable = 406,
/// <summary>
/// Equivalent to status code 407.
/// Indicates that the client must first authenticate itself with the proxy.
/// </summary>
ProxyAuthenticationRequired = 407,
/// <summary>
/// Equivalent to status code 408.
/// Indicates that the client didn't produce a request within the time that the server was
/// prepared to wait.
/// </summary>
RequestTimeout = 408,
/// <summary>
/// Equivalent to status code 409.
/// Indicates that the client's request couldn't be completed due to a conflict on the server.
/// </summary>
Conflict = 409,
/// <summary>
/// Equivalent to status code 410.
/// Indicates that the requested resource is no longer available at the server and
/// no forwarding address is known.
/// </summary>
Gone = 410,
/// <summary>
/// Equivalent to status code 411.
/// Indicates that the server refuses to accept the client's request without a defined
/// Content-Length.
/// </summary>
LengthRequired = 411,
/// <summary>
/// Equivalent to status code 412.
/// Indicates that the precondition given in one or more of the request headers evaluated to
/// false when it was tested on the server.
/// </summary>
PreconditionFailed = 412,
/// <summary>
/// Equivalent to status code 413.
/// Indicates that the entity of the client's request is larger than the server is willing or
/// able to process.
/// </summary>
RequestEntityTooLarge = 413,
/// <summary>
/// Equivalent to status code 414.
/// Indicates that the request URI is longer than the server is willing to interpret.
/// </summary>
RequestUriTooLong = 414,
/// <summary>
/// Equivalent to status code 415.
/// Indicates that the entity of the client's request is in a format not supported by
/// the requested resource for the requested method.
/// </summary>
UnsupportedMediaType = 415,
/// <summary>
/// Equivalent to status code 416.
/// Indicates that none of the range specifier values in a Range request header overlap
/// the current extent of the selected resource.
/// </summary>
RequestedRangeNotSatisfiable = 416,
/// <summary>
/// Equivalent to status code 417.
/// Indicates that the expectation given in an Expect request header couldn't be met by
/// the server.
/// </summary>
ExpectationFailed = 417,
/// <summary>
/// Equivalent to status code 500.
/// Indicates that the server encountered an unexpected condition which prevented it from
/// fulfilling the client's request.
/// </summary>
InternalServerError = 500,
/// <summary>
/// Equivalent to status code 501.
/// Indicates that the server doesn't support the functionality required to fulfill the client's
/// request.
/// </summary>
NotImplemented = 501,
/// <summary>
/// Equivalent to status code 502.
/// Indicates that a gateway or proxy server received an invalid response from the upstream
/// server.
/// </summary>
BadGateway = 502,
/// <summary>
/// Equivalent to status code 503.
/// Indicates that the server is currently unable to handle the client's request due to
/// a temporary overloading or maintenance of the server.
/// </summary>
ServiceUnavailable = 503,
/// <summary>
/// Equivalent to status code 504.
/// Indicates that a gateway or proxy server didn't receive a timely response from the upstream
/// server or some other auxiliary server.
/// </summary>
GatewayTimeout = 504,
/// <summary>
/// Equivalent to status code 505.
/// Indicates that the server doesn't support the HTTP version used in the client's request.
/// </summary>
HttpVersionNotSupported = 505,
}
}

View File

@@ -0,0 +1,85 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace SocketHttpListener.Net
{
internal class HttpStreamAsyncResult : IAsyncResult
{
private object _locker = new object();
private ManualResetEvent _handle;
private bool _completed;
internal readonly object _parent;
internal byte[] _buffer;
internal int _offset;
internal int _count;
internal AsyncCallback _callback;
internal object _state;
internal int _synchRead;
internal Exception _error;
internal bool _endCalled;
internal HttpStreamAsyncResult(object parent)
{
_parent = parent;
}
public void Complete(Exception e)
{
_error = e;
Complete();
}
public void Complete()
{
lock (_locker)
{
if (_completed)
return;
_completed = true;
if (_handle != null)
_handle.Set();
if (_callback != null)
Task.Run(() => _callback(this));
}
}
public object AsyncState
{
get { return _state; }
}
public WaitHandle AsyncWaitHandle
{
get
{
lock (_locker)
{
if (_handle == null)
_handle = new ManualResetEvent(_completed);
}
return _handle;
}
}
public bool CompletedSynchronously
{
get { return (_synchRead == _count); }
}
public bool IsCompleted
{
get
{
lock (_locker)
{
return _completed;
}
}
}
}
}

View File

@@ -0,0 +1,16 @@
using System;
namespace SocketHttpListener.Net
{
// <remarks>
// </remarks>
public class HttpVersion
{
public static readonly Version Version10 = new Version(1, 0);
public static readonly Version Version11 = new Version(1, 1);
// pretty useless..
public HttpVersion() { }
}
}

View File

@@ -0,0 +1,148 @@
using System;
using System.Net;
using MediaBrowser.Model.Net;
namespace SocketHttpListener.Net
{
sealed class ListenerPrefix
{
string original;
string host;
ushort port;
string path;
bool secure;
IpAddressInfo[] addresses;
public HttpListener Listener;
public ListenerPrefix(string prefix)
{
this.original = prefix;
Parse(prefix);
}
public override string ToString()
{
return original;
}
public IpAddressInfo[] Addresses
{
get { return addresses; }
set { addresses = value; }
}
public bool Secure
{
get { return secure; }
}
public string Host
{
get { return host; }
}
public int Port
{
get { return (int)port; }
}
public string Path
{
get { return path; }
}
// Equals and GetHashCode are required to detect duplicates in HttpListenerPrefixCollection.
public override bool Equals(object o)
{
ListenerPrefix other = o as ListenerPrefix;
if (other == null)
return false;
return (original == other.original);
}
public override int GetHashCode()
{
return original.GetHashCode();
}
void Parse(string uri)
{
ushort default_port = 80;
if (uri.StartsWith("https://"))
{
default_port = 443;
secure = true;
}
int length = uri.Length;
int start_host = uri.IndexOf(':') + 3;
if (start_host >= length)
throw new ArgumentException("No host specified.");
int colon = uri.IndexOf(':', start_host, length - start_host);
int root;
if (colon > 0)
{
host = uri.Substring(start_host, colon - start_host);
root = uri.IndexOf('/', colon, length - colon);
port = (ushort)Int32.Parse(uri.Substring(colon + 1, root - colon - 1));
path = uri.Substring(root);
}
else
{
root = uri.IndexOf('/', start_host, length - start_host);
host = uri.Substring(start_host, root - start_host);
port = default_port;
path = uri.Substring(root);
}
if (path.Length != 1)
path = path.Substring(0, path.Length - 1);
}
public static void CheckUri(string uri)
{
if (uri == null)
throw new ArgumentNullException("uriPrefix");
if (!uri.StartsWith("http://") && !uri.StartsWith("https://"))
throw new ArgumentException("Only 'http' and 'https' schemes are supported.");
int length = uri.Length;
int start_host = uri.IndexOf(':') + 3;
if (start_host >= length)
throw new ArgumentException("No host specified.");
int colon = uri.IndexOf(':', start_host, length - start_host);
if (start_host == colon)
throw new ArgumentException("No host specified.");
int root;
if (colon > 0)
{
root = uri.IndexOf('/', colon, length - colon);
if (root == -1)
throw new ArgumentException("No path specified.");
try
{
int p = Int32.Parse(uri.Substring(colon + 1, root - colon - 1));
if (p <= 0 || p >= 65536)
throw new Exception();
}
catch
{
throw new ArgumentException("Invalid port.");
}
}
else
{
root = uri.IndexOf('/', start_host, length - start_host);
if (root == -1)
throw new ArgumentException("No path specified.");
}
if (uri[uri.Length - 1] != '/')
throw new ArgumentException("The prefix must end with '/'");
}
}
}

View File

@@ -0,0 +1,400 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.System;
using MediaBrowser.Model.Text;
using SocketHttpListener.Primitives;
namespace SocketHttpListener.Net
{
// FIXME: Does this buffer the response until Close?
// Update: we send a single packet for the first non-chunked Write
// What happens when we set content-length to X and write X-1 bytes then close?
// what if we don't set content-length at all?
public class ResponseStream : Stream
{
HttpListenerResponse response;
bool disposed;
bool trailer_sent;
Stream stream;
private readonly IMemoryStreamFactory _memoryStreamFactory;
private readonly ITextEncoding _textEncoding;
private readonly IFileSystem _fileSystem;
private readonly IAcceptSocket _socket;
private readonly bool _supportsDirectSocketAccess;
private readonly ILogger _logger;
private readonly IEnvironmentInfo _environment;
internal ResponseStream(Stream stream, HttpListenerResponse response, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding, IFileSystem fileSystem, IAcceptSocket socket, bool supportsDirectSocketAccess, ILogger logger, IEnvironmentInfo environment)
{
this.response = response;
_memoryStreamFactory = memoryStreamFactory;
_textEncoding = textEncoding;
_fileSystem = fileSystem;
_socket = socket;
_supportsDirectSocketAccess = supportsDirectSocketAccess;
_logger = logger;
_environment = environment;
this.stream = stream;
}
public override bool CanRead
{
get { return false; }
}
public override bool CanSeek
{
get { return false; }
}
public override bool CanWrite
{
get { return true; }
}
public override long Length
{
get { throw new NotSupportedException(); }
}
public override long Position
{
get { throw new NotSupportedException(); }
set { throw new NotSupportedException(); }
}
protected override void Dispose(bool disposing)
{
if (disposed == false)
{
disposed = true;
using (var ms = GetHeaders(response, _memoryStreamFactory, false))
{
if (stream.CanWrite)
{
try
{
bool chunked = response.SendChunked;
if (ms != null)
{
var start = ms.Position;
if (chunked && !trailer_sent)
{
trailer_sent = true;
var bytes = GetChunkSizeBytes(0, true);
ms.Position = ms.Length;
ms.Write(bytes, 0, bytes.Length);
ms.Position = start;
}
ms.CopyTo(stream);
}
else if (chunked && !trailer_sent)
{
trailer_sent = true;
var bytes = GetChunkSizeBytes(0, true);
stream.Write(bytes, 0, bytes.Length);
}
}
catch (IOException ex)
{
// Ignore error due to connection reset by peer
}
}
response.Close();
}
}
base.Dispose(disposing);
}
internal static MemoryStream GetHeaders(HttpListenerResponse response, IMemoryStreamFactory memoryStreamFactory, bool closing)
{
// SendHeaders works on shared headers
lock (response.headers_lock)
{
if (response.HeadersSent)
return null;
var ms = memoryStreamFactory.CreateNew();
response.SendHeaders(closing, ms);
return ms;
}
}
public override void Flush()
{
}
static byte[] crlf = new byte[] { 13, 10 };
byte[] GetChunkSizeBytes(int size, bool final)
{
string str = String.Format("{0:x}\r\n{1}", size, final ? "\r\n" : "");
return _textEncoding.GetASCIIEncoding().GetBytes(str);
}
internal void InternalWrite(byte[] buffer, int offset, int count)
{
stream.Write(buffer, offset, count);
}
const int MsCopyBufferSize = 81920;
const int StreamCopyToBufferSize = 81920;
public override void Write(byte[] buffer, int offset, int count)
{
if (disposed)
throw new ObjectDisposedException(GetType().ToString());
if (count == 0)
{
return;
}
using (var ms = GetHeaders(response, _memoryStreamFactory, false))
{
bool chunked = response.SendChunked;
if (ms != null)
{
long start = ms.Position; // After the possible preamble for the encoding
ms.Position = ms.Length;
if (chunked)
{
var bytes = GetChunkSizeBytes(count, false);
ms.Write(bytes, 0, bytes.Length);
}
ms.Write(buffer, offset, count);
if (chunked)
{
ms.Write(crlf, 0, 2);
}
ms.Position = start;
ms.CopyTo(stream, MsCopyBufferSize);
return;
}
if (chunked)
{
var bytes = GetChunkSizeBytes(count, false);
stream.Write(bytes, 0, bytes.Length);
}
stream.Write(buffer, offset, count);
if (chunked)
stream.Write(crlf, 0, 2);
}
}
public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
if (disposed)
throw new ObjectDisposedException(GetType().ToString());
if (count == 0)
{
return;
}
using (var ms = GetHeaders(response, _memoryStreamFactory, false))
{
bool chunked = response.SendChunked;
if (ms != null)
{
long start = ms.Position; // After the possible preamble for the encoding
ms.Position = ms.Length;
if (chunked)
{
var bytes = GetChunkSizeBytes(count, false);
ms.Write(bytes, 0, bytes.Length);
}
ms.Write(buffer, offset, count);
if (chunked)
{
ms.Write(crlf, 0, 2);
}
ms.Position = start;
await ms.CopyToAsync(stream, MsCopyBufferSize, cancellationToken).ConfigureAwait(false);
return;
}
if (chunked)
{
var bytes = GetChunkSizeBytes(count, false);
stream.Write(bytes, 0, bytes.Length);
}
await stream.WriteAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false);
if (chunked)
stream.Write(crlf, 0, 2);
}
}
public override int Read([In, Out] byte[] buffer, int offset, int count)
{
throw new NotSupportedException();
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException();
}
public override void SetLength(long value)
{
throw new NotSupportedException();
}
private bool EnableSendFileWithSocket
{
get { return false; }
}
public Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken)
{
if (_supportsDirectSocketAccess && offset == 0 && count == 0 && !response.SendChunked && response.ContentLength64 > 8192)
{
if (EnableSendFileWithSocket)
{
return TransmitFileOverSocket(path, offset, count, fileShareMode, cancellationToken);
}
}
return TransmitFileManaged(path, offset, count, fileShareMode, cancellationToken);
}
private readonly byte[] _emptyBuffer = new byte[] { };
private Task TransmitFileOverSocket(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken)
{
var ms = GetHeaders(response, _memoryStreamFactory, false);
byte[] preBuffer;
if (ms != null)
{
using (var msCopy = new MemoryStream())
{
ms.CopyTo(msCopy);
preBuffer = msCopy.ToArray();
}
}
else
{
return TransmitFileManaged(path, offset, count, fileShareMode, cancellationToken);
}
_logger.Info("Socket sending file {0} {1}", path, response.ContentLength64);
return _socket.SendFile(path, preBuffer, _emptyBuffer, cancellationToken);
}
private async Task TransmitFileManaged(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken)
{
var allowAsync = _environment.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows;
var fileOpenOptions = offset > 0
? FileOpenOptions.RandomAccess
: FileOpenOptions.SequentialScan;
if (allowAsync)
{
fileOpenOptions |= FileOpenOptions.Asynchronous;
}
// use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039
using (var fs = _fileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, fileShareMode, fileOpenOptions))
{
if (offset > 0)
{
fs.Position = offset;
}
var targetStream = this;
if (count > 0)
{
if (allowAsync)
{
await CopyToInternalAsync(fs, targetStream, count, cancellationToken).ConfigureAwait(false);
}
else
{
await CopyToInternalAsyncWithSyncRead(fs, targetStream, count, cancellationToken).ConfigureAwait(false);
}
}
else
{
if (allowAsync)
{
await fs.CopyToAsync(targetStream, StreamCopyToBufferSize, cancellationToken).ConfigureAwait(false);
}
else
{
fs.CopyTo(targetStream, StreamCopyToBufferSize);
}
}
}
}
private static async Task CopyToInternalAsyncWithSyncRead(Stream source, Stream destination, long copyLength, CancellationToken cancellationToken)
{
var array = new byte[StreamCopyToBufferSize];
int bytesRead;
while ((bytesRead = source.Read(array, 0, array.Length)) != 0)
{
var bytesToWrite = Math.Min(bytesRead, copyLength);
if (bytesToWrite > 0)
{
await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false);
}
copyLength -= bytesToWrite;
if (copyLength <= 0)
{
break;
}
}
}
private static async Task CopyToInternalAsync(Stream source, Stream destination, long copyLength, CancellationToken cancellationToken)
{
var array = new byte[StreamCopyToBufferSize];
int bytesRead;
while ((bytesRead = await source.ReadAsync(array, 0, array.Length, cancellationToken).ConfigureAwait(false)) != 0)
{
var bytesToWrite = Math.Min(bytesRead, copyLength);
if (bytesToWrite > 0)
{
await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false);
}
copyLength -= bytesToWrite;
if (copyLength <= 0)
{
break;
}
}
}
}
}

View File

@@ -0,0 +1,391 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Net;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Text;
using MediaBrowser.Model.Services;
namespace SocketHttpListener.Net
{
[ComVisible(true)]
public class WebHeaderCollection : QueryParamCollection
{
[Flags]
internal enum HeaderInfo
{
Request = 1,
Response = 1 << 1,
MultiValue = 1 << 10
}
static readonly bool[] allowed_chars = {
false, false, false, false, false, false, false, false, false, false, false, false, false, false,
false, false, false, false, false, false, false, false, false, false, false, false, false, false,
false, false, false, false, false, true, false, true, true, true, true, false, false, false, true,
true, false, true, true, false, true, true, true, true, true, true, true, true, true, true, false,
false, false, false, false, false, false, true, true, true, true, true, true, true, true, true,
true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true,
false, false, false, true, true, true, true, true, true, true, true, true, true, true, true, true,
true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true,
false, true, false
};
static readonly Dictionary<string, HeaderInfo> headers;
HeaderInfo? headerRestriction;
HeaderInfo? headerConsistency;
static WebHeaderCollection()
{
headers = new Dictionary<string, HeaderInfo>(StringComparer.OrdinalIgnoreCase) {
{ "Allow", HeaderInfo.MultiValue },
{ "Accept", HeaderInfo.Request | HeaderInfo.MultiValue },
{ "Accept-Charset", HeaderInfo.MultiValue },
{ "Accept-Encoding", HeaderInfo.MultiValue },
{ "Accept-Language", HeaderInfo.MultiValue },
{ "Accept-Ranges", HeaderInfo.MultiValue },
{ "Age", HeaderInfo.Response },
{ "Authorization", HeaderInfo.MultiValue },
{ "Cache-Control", HeaderInfo.MultiValue },
{ "Cookie", HeaderInfo.MultiValue },
{ "Connection", HeaderInfo.Request | HeaderInfo.MultiValue },
{ "Content-Encoding", HeaderInfo.MultiValue },
{ "Content-Length", HeaderInfo.Request | HeaderInfo.Response },
{ "Content-Type", HeaderInfo.Request },
{ "Content-Language", HeaderInfo.MultiValue },
{ "Date", HeaderInfo.Request },
{ "Expect", HeaderInfo.Request | HeaderInfo.MultiValue},
{ "Host", HeaderInfo.Request },
{ "If-Match", HeaderInfo.MultiValue },
{ "If-Modified-Since", HeaderInfo.Request },
{ "If-None-Match", HeaderInfo.MultiValue },
{ "Keep-Alive", HeaderInfo.Response },
{ "Pragma", HeaderInfo.MultiValue },
{ "Proxy-Authenticate", HeaderInfo.MultiValue },
{ "Proxy-Authorization", HeaderInfo.MultiValue },
{ "Proxy-Connection", HeaderInfo.Request | HeaderInfo.MultiValue },
{ "Range", HeaderInfo.Request | HeaderInfo.MultiValue },
{ "Referer", HeaderInfo.Request },
{ "Set-Cookie", HeaderInfo.MultiValue },
{ "Set-Cookie2", HeaderInfo.MultiValue },
{ "Server", HeaderInfo.Response },
{ "TE", HeaderInfo.MultiValue },
{ "Trailer", HeaderInfo.MultiValue },
{ "Transfer-Encoding", HeaderInfo.Request | HeaderInfo.Response | HeaderInfo.MultiValue },
{ "Translate", HeaderInfo.Request | HeaderInfo.Response },
{ "Upgrade", HeaderInfo.MultiValue },
{ "User-Agent", HeaderInfo.Request },
{ "Vary", HeaderInfo.MultiValue },
{ "Via", HeaderInfo.MultiValue },
{ "Warning", HeaderInfo.MultiValue },
{ "WWW-Authenticate", HeaderInfo.Response | HeaderInfo. MultiValue },
{ "SecWebSocketAccept", HeaderInfo.Response },
{ "SecWebSocketExtensions", HeaderInfo.Request | HeaderInfo.Response | HeaderInfo. MultiValue },
{ "SecWebSocketKey", HeaderInfo.Request },
{ "Sec-WebSocket-Protocol", HeaderInfo.Request | HeaderInfo.Response | HeaderInfo. MultiValue },
{ "SecWebSocketVersion", HeaderInfo.Response | HeaderInfo. MultiValue }
};
}
// Methods
public void Add(string header)
{
if (header == null)
throw new ArgumentNullException("header");
int pos = header.IndexOf(':');
if (pos == -1)
throw new ArgumentException("no colon found", "header");
this.Add(header.Substring(0, pos), header.Substring(pos + 1));
}
public override void Add(string name, string value)
{
if (name == null)
throw new ArgumentNullException("name");
ThrowIfRestricted(name);
this.AddWithoutValidate(name, value);
}
protected void AddWithoutValidate(string headerName, string headerValue)
{
if (!IsHeaderName(headerName))
throw new ArgumentException("invalid header name: " + headerName, "headerName");
if (headerValue == null)
headerValue = String.Empty;
else
headerValue = headerValue.Trim();
if (!IsHeaderValue(headerValue))
throw new ArgumentException("invalid header value: " + headerValue, "headerValue");
AddValue(headerName, headerValue);
}
internal void AddValue(string headerName, string headerValue)
{
base.Add(headerName, headerValue);
}
internal string[] GetValues_internal(string header, bool split)
{
if (header == null)
throw new ArgumentNullException("header");
string[] values = base.GetValues(header);
if (values == null || values.Length == 0)
return null;
if (split && IsMultiValue(header))
{
List<string> separated = null;
foreach (var value in values)
{
if (value.IndexOf(',') < 0)
{
if (separated != null)
separated.Add(value);
continue;
}
if (separated == null)
{
separated = new List<string>(values.Length + 1);
foreach (var v in values)
{
if (v == value)
break;
separated.Add(v);
}
}
var slices = value.Split(',');
var slices_length = slices.Length;
if (value[value.Length - 1] == ',')
--slices_length;
for (int i = 0; i < slices_length; ++i)
{
separated.Add(slices[i].Trim());
}
}
if (separated != null)
return separated.ToArray();
}
return values;
}
public override string[] GetValues(string header)
{
return GetValues_internal(header, true);
}
public override string[] GetValues(int index)
{
string[] values = base.GetValues(index);
if (values == null || values.Length == 0)
{
return null;
}
return values;
}
public static bool IsRestricted(string headerName)
{
return IsRestricted(headerName, false);
}
public static bool IsRestricted(string headerName, bool response)
{
if (headerName == null)
throw new ArgumentNullException("headerName");
if (headerName.Length == 0)
throw new ArgumentException("empty string", "headerName");
if (!IsHeaderName(headerName))
throw new ArgumentException("Invalid character in header");
HeaderInfo info;
if (!headers.TryGetValue(headerName, out info))
return false;
var flag = response ? HeaderInfo.Response : HeaderInfo.Request;
return (info & flag) != 0;
}
public override void Set(string name, string value)
{
if (name == null)
throw new ArgumentNullException("name");
if (!IsHeaderName(name))
throw new ArgumentException("invalid header name");
if (value == null)
value = String.Empty;
else
value = value.Trim();
if (!IsHeaderValue(value))
throw new ArgumentException("invalid header value");
ThrowIfRestricted(name);
base.Set(name, value);
}
internal string ToStringMultiValue()
{
StringBuilder sb = new StringBuilder();
int count = base.Count;
for (int i = 0; i < count; i++)
{
string key = GetKey(i);
if (IsMultiValue(key))
{
foreach (string v in GetValues(i))
{
sb.Append(key)
.Append(": ")
.Append(v)
.Append("\r\n");
}
}
else
{
sb.Append(key)
.Append(": ")
.Append(Get(i))
.Append("\r\n");
}
}
return sb.Append("\r\n").ToString();
}
public override string ToString()
{
StringBuilder sb = new StringBuilder();
int count = base.Count;
for (int i = 0; i < count; i++)
sb.Append(GetKey(i))
.Append(": ")
.Append(Get(i))
.Append("\r\n");
return sb.Append("\r\n").ToString();
}
// Internal Methods
// With this we don't check for invalid characters in header. See bug #55994.
internal void SetInternal(string header)
{
int pos = header.IndexOf(':');
if (pos == -1)
throw new ArgumentException("no colon found", "header");
SetInternal(header.Substring(0, pos), header.Substring(pos + 1));
}
internal void SetInternal(string name, string value)
{
if (value == null)
value = String.Empty;
else
value = value.Trim();
if (!IsHeaderValue(value))
throw new ArgumentException("invalid header value");
if (IsMultiValue(name))
{
base.Add(name, value);
}
else
{
base.Remove(name);
base.Set(name, value);
}
}
// Private Methods
public override int Remove(string name)
{
ThrowIfRestricted(name);
return base.Remove(name);
}
protected void ThrowIfRestricted(string headerName)
{
if (!headerRestriction.HasValue)
return;
HeaderInfo info;
if (!headers.TryGetValue(headerName, out info))
return;
if ((info & headerRestriction.Value) != 0)
throw new ArgumentException("This header must be modified with the appropriate property.");
}
internal static bool IsMultiValue(string headerName)
{
if (headerName == null)
return false;
HeaderInfo info;
return headers.TryGetValue(headerName, out info) && (info & HeaderInfo.MultiValue) != 0;
}
internal static bool IsHeaderValue(string value)
{
// TEXT any 8 bit value except CTL's (0-31 and 127)
// but including \r\n space and \t
// after a newline at least one space or \t must follow
// certain header fields allow comments ()
int len = value.Length;
for (int i = 0; i < len; i++)
{
char c = value[i];
if (c == 127)
return false;
if (c < 0x20 && (c != '\r' && c != '\n' && c != '\t'))
return false;
if (c == '\n' && ++i < len)
{
c = value[i];
if (c != ' ' && c != '\t')
return false;
}
}
return true;
}
internal static bool IsHeaderName(string name)
{
if (name == null || name.Length == 0)
return false;
int len = name.Length;
for (int i = 0; i < len; i++)
{
char c = name[i];
if (c > 126 || !allowed_chars[c])
return false;
}
return true;
}
}
}

View File

@@ -0,0 +1,348 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Net;
using System.Security.Principal;
using MediaBrowser.Model.Cryptography;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.Services;
using SocketHttpListener.Primitives;
namespace SocketHttpListener.Net.WebSockets
{
/// <summary>
/// Provides the properties used to access the information in a WebSocket connection request
/// received by the <see cref="HttpListener"/>.
/// </summary>
/// <remarks>
/// </remarks>
public class HttpListenerWebSocketContext : WebSocketContext
{
#region Private Fields
private HttpListenerContext _context;
private WebSocket _websocket;
#endregion
#region Internal Constructors
internal HttpListenerWebSocketContext(
HttpListenerContext context, string protocol, ICryptoProvider cryptoProvider, IMemoryStreamFactory memoryStreamFactory)
{
_context = context;
_websocket = new WebSocket(this, protocol, cryptoProvider, memoryStreamFactory);
}
#endregion
#region Internal Properties
internal Stream Stream
{
get
{
return _context.Connection.Stream;
}
}
#endregion
#region Public Properties
/// <summary>
/// Gets the HTTP cookies included in the request.
/// </summary>
/// <value>
/// A <see cref="System.Net.CookieCollection"/> that contains the cookies.
/// </value>
public override CookieCollection CookieCollection
{
get
{
return _context.Request.Cookies;
}
}
/// <summary>
/// Gets the HTTP headers included in the request.
/// </summary>
/// <value>
/// A <see cref="QueryParamCollection"/> that contains the headers.
/// </value>
public override QueryParamCollection Headers
{
get
{
return _context.Request.Headers;
}
}
/// <summary>
/// Gets the value of the Host header included in the request.
/// </summary>
/// <value>
/// A <see cref="string"/> that represents the value of the Host header.
/// </value>
public override string Host
{
get
{
return _context.Request.Headers["Host"];
}
}
/// <summary>
/// Gets a value indicating whether the client is authenticated.
/// </summary>
/// <value>
/// <c>true</c> if the client is authenticated; otherwise, <c>false</c>.
/// </value>
public override bool IsAuthenticated
{
get
{
return _context.Request.IsAuthenticated;
}
}
/// <summary>
/// Gets a value indicating whether the client connected from the local computer.
/// </summary>
/// <value>
/// <c>true</c> if the client connected from the local computer; otherwise, <c>false</c>.
/// </value>
public override bool IsLocal
{
get
{
return _context.Request.IsLocal;
}
}
/// <summary>
/// Gets a value indicating whether the WebSocket connection is secured.
/// </summary>
/// <value>
/// <c>true</c> if the connection is secured; otherwise, <c>false</c>.
/// </value>
public override bool IsSecureConnection
{
get
{
return _context.Connection.IsSecure;
}
}
/// <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 override bool IsWebSocketRequest
{
get
{
return _context.Request.IsWebSocketRequest;
}
}
/// <summary>
/// Gets the value of the Origin header included in the request.
/// </summary>
/// <value>
/// A <see cref="string"/> that represents the value of the Origin header.
/// </value>
public override string Origin
{
get
{
return _context.Request.Headers["Origin"];
}
}
/// <summary>
/// Gets the query string included in the request.
/// </summary>
/// <value>
/// A <see cref="QueryParamCollection"/> that contains the query string parameters.
/// </value>
public override QueryParamCollection QueryString
{
get
{
return _context.Request.QueryString;
}
}
/// <summary>
/// Gets the URI requested by the client.
/// </summary>
/// <value>
/// A <see cref="Uri"/> that represents the requested URI.
/// </value>
public override Uri RequestUri
{
get
{
return _context.Request.Url;
}
}
/// <summary>
/// Gets the value of the Sec-WebSocket-Key header included in the request.
/// </summary>
/// <remarks>
/// This property provides a part of the information used by the server to prove that it
/// received a valid WebSocket connection request.
/// </remarks>
/// <value>
/// A <see cref="string"/> that represents the value of the Sec-WebSocket-Key header.
/// </value>
public override string SecWebSocketKey
{
get
{
return _context.Request.Headers["Sec-WebSocket-Key"];
}
}
/// <summary>
/// Gets the values of the Sec-WebSocket-Protocol header included in the request.
/// </summary>
/// <remarks>
/// This property represents the subprotocols requested by the client.
/// </remarks>
/// <value>
/// An <see cref="T:System.Collections.Generic.IEnumerable{string}"/> instance that provides
/// an enumerator which supports the iteration over the values of the Sec-WebSocket-Protocol
/// header.
/// </value>
public override IEnumerable<string> SecWebSocketProtocols
{
get
{
var protocols = _context.Request.Headers["Sec-WebSocket-Protocol"];
if (protocols != null)
foreach (var protocol in protocols.Split(','))
yield return protocol.Trim();
}
}
/// <summary>
/// Gets the value of the Sec-WebSocket-Version header included in the request.
/// </summary>
/// <remarks>
/// This property represents the WebSocket protocol version.
/// </remarks>
/// <value>
/// A <see cref="string"/> that represents the value of the Sec-WebSocket-Version header.
/// </value>
public override string SecWebSocketVersion
{
get
{
return _context.Request.Headers["Sec-WebSocket-Version"];
}
}
/// <summary>
/// Gets the server endpoint as an IP address and a port number.
/// </summary>
/// <value>
/// </value>
public override IpEndPointInfo ServerEndPoint
{
get
{
return _context.Connection.LocalEndPoint;
}
}
/// <summary>
/// Gets the client information (identity, authentication, and security roles).
/// </summary>
/// <value>
/// A <see cref="IPrincipal"/> that represents the client information.
/// </value>
public override IPrincipal User
{
get
{
return _context.User;
}
}
/// <summary>
/// Gets the client endpoint as an IP address and a port number.
/// </summary>
/// <value>
/// </value>
public override IpEndPointInfo UserEndPoint
{
get
{
return _context.Connection.RemoteEndPoint;
}
}
/// <summary>
/// Gets the <see cref="SocketHttpListener.WebSocket"/> instance used for two-way communication
/// between client and server.
/// </summary>
/// <value>
/// A <see cref="SocketHttpListener.WebSocket"/>.
/// </value>
public override WebSocket WebSocket
{
get
{
return _websocket;
}
}
#endregion
#region Internal Methods
internal void Close()
{
try
{
_context.Connection.Close(true);
}
catch
{
// catch errors sending the closing handshake
}
}
internal void Close(HttpStatusCode code)
{
_context.Response.StatusCode = (int)code;
_context.Response.OutputStream.Dispose();
}
#endregion
#region Public Methods
/// <summary>
/// Returns a <see cref="string"/> that represents the current
/// <see cref="HttpListenerWebSocketContext"/>.
/// </summary>
/// <returns>
/// A <see cref="string"/> that represents the current
/// <see cref="HttpListenerWebSocketContext"/>.
/// </returns>
public override string ToString()
{
return _context.Request.ToString();
}
#endregion
}
}

View File

@@ -0,0 +1,183 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Net;
using System.Security.Principal;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.Services;
namespace SocketHttpListener.Net.WebSockets
{
/// <summary>
/// Exposes the properties used to access the information in a WebSocket connection request.
/// </summary>
/// <remarks>
/// The WebSocketContext class is an abstract class.
/// </remarks>
public abstract class WebSocketContext
{
#region Protected Constructors
/// <summary>
/// Initializes a new instance of the <see cref="WebSocketContext"/> class.
/// </summary>
protected WebSocketContext()
{
}
#endregion
#region Public Properties
/// <summary>
/// Gets the HTTP cookies included in the request.
/// </summary>
/// <value>
/// A <see cref="System.Net.CookieCollection"/> that contains the cookies.
/// </value>
public abstract CookieCollection CookieCollection { get; }
/// <summary>
/// Gets the HTTP headers included in the request.
/// </summary>
/// <value>
/// A <see cref="QueryParamCollection"/> that contains the headers.
/// </value>
public abstract QueryParamCollection Headers { get; }
/// <summary>
/// Gets the value of the Host header included in the request.
/// </summary>
/// <value>
/// A <see cref="string"/> that represents the value of the Host header.
/// </value>
public abstract string Host { get; }
/// <summary>
/// Gets a value indicating whether the client is authenticated.
/// </summary>
/// <value>
/// <c>true</c> if the client is authenticated; otherwise, <c>false</c>.
/// </value>
public abstract bool IsAuthenticated { get; }
/// <summary>
/// Gets a value indicating whether the client connected from the local computer.
/// </summary>
/// <value>
/// <c>true</c> if the client connected from the local computer; otherwise, <c>false</c>.
/// </value>
public abstract bool IsLocal { get; }
/// <summary>
/// Gets a value indicating whether the WebSocket connection is secured.
/// </summary>
/// <value>
/// <c>true</c> if the connection is secured; otherwise, <c>false</c>.
/// </value>
public abstract bool IsSecureConnection { get; }
/// <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 abstract bool IsWebSocketRequest { get; }
/// <summary>
/// Gets the value of the Origin header included in the request.
/// </summary>
/// <value>
/// A <see cref="string"/> that represents the value of the Origin header.
/// </value>
public abstract string Origin { get; }
/// <summary>
/// Gets the query string included in the request.
/// </summary>
/// <value>
/// A <see cref="QueryParamCollection"/> that contains the query string parameters.
/// </value>
public abstract QueryParamCollection QueryString { get; }
/// <summary>
/// Gets the URI requested by the client.
/// </summary>
/// <value>
/// A <see cref="Uri"/> that represents the requested URI.
/// </value>
public abstract Uri RequestUri { get; }
/// <summary>
/// Gets the value of the Sec-WebSocket-Key header included in the request.
/// </summary>
/// <remarks>
/// This property provides a part of the information used by the server to prove that it
/// received a valid WebSocket connection request.
/// </remarks>
/// <value>
/// A <see cref="string"/> that represents the value of the Sec-WebSocket-Key header.
/// </value>
public abstract string SecWebSocketKey { get; }
/// <summary>
/// Gets the values of the Sec-WebSocket-Protocol header included in the request.
/// </summary>
/// <remarks>
/// This property represents the subprotocols requested by the client.
/// </remarks>
/// <value>
/// An <see cref="T:System.Collections.Generic.IEnumerable{string}"/> instance that provides
/// an enumerator which supports the iteration over the values of the Sec-WebSocket-Protocol
/// header.
/// </value>
public abstract IEnumerable<string> SecWebSocketProtocols { get; }
/// <summary>
/// Gets the value of the Sec-WebSocket-Version header included in the request.
/// </summary>
/// <remarks>
/// This property represents the WebSocket protocol version.
/// </remarks>
/// <value>
/// A <see cref="string"/> that represents the value of the Sec-WebSocket-Version header.
/// </value>
public abstract string SecWebSocketVersion { get; }
/// <summary>
/// Gets the server endpoint as an IP address and a port number.
/// </summary>
/// <value>
/// A <see cref="System.Net.IPEndPoint"/> that represents the server endpoint.
/// </value>
public abstract IpEndPointInfo ServerEndPoint { get; }
/// <summary>
/// Gets the client information (identity, authentication, and security roles).
/// </summary>
/// <value>
/// A <see cref="IPrincipal"/> that represents the client information.
/// </value>
public abstract IPrincipal User { get; }
/// <summary>
/// Gets the client endpoint as an IP address and a port number.
/// </summary>
/// <value>
/// A <see cref="System.Net.IPEndPoint"/> that represents the client endpoint.
/// </value>
public abstract IpEndPointInfo UserEndPoint { get; }
/// <summary>
/// Gets the <see cref="SocketHttpListener.WebSocket"/> instance used for two-way communication
/// between client and server.
/// </summary>
/// <value>
/// A <see cref="SocketHttpListener.WebSocket"/>.
/// </value>
public abstract WebSocket WebSocket { get; }
#endregion
}
}

View File

@@ -0,0 +1,43 @@
namespace SocketHttpListener
{
/// <summary>
/// Contains the values of the opcode that indicates the type of a WebSocket frame.
/// </summary>
/// <remarks>
/// The values of the opcode are defined in
/// <see href="http://tools.ietf.org/html/rfc6455#section-5.2">Section 5.2</see> of RFC 6455.
/// </remarks>
public enum Opcode : byte
{
/// <summary>
/// Equivalent to numeric value 0.
/// Indicates a continuation frame.
/// </summary>
Cont = 0x0,
/// <summary>
/// Equivalent to numeric value 1.
/// Indicates a text frame.
/// </summary>
Text = 0x1,
/// <summary>
/// Equivalent to numeric value 2.
/// Indicates a binary frame.
/// </summary>
Binary = 0x2,
/// <summary>
/// Equivalent to numeric value 8.
/// Indicates a connection close frame.
/// </summary>
Close = 0x8,
/// <summary>
/// Equivalent to numeric value 9.
/// Indicates a ping frame.
/// </summary>
Ping = 0x9,
/// <summary>
/// Equivalent to numeric value 10.
/// Indicates a pong frame.
/// </summary>
Pong = 0xa
}
}

View File

@@ -0,0 +1,149 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
namespace SocketHttpListener
{
internal class PayloadData : IEnumerable<byte>
{
#region Private Fields
private byte [] _applicationData;
private byte [] _extensionData;
private bool _masked;
#endregion
#region Public Const Fields
public const ulong MaxLength = long.MaxValue;
#endregion
#region Public Constructors
public PayloadData ()
: this (new byte [0], new byte [0], false)
{
}
public PayloadData (byte [] applicationData)
: this (new byte [0], applicationData, false)
{
}
public PayloadData (string applicationData)
: this (new byte [0], Encoding.UTF8.GetBytes (applicationData), false)
{
}
public PayloadData (byte [] applicationData, bool masked)
: this (new byte [0], applicationData, masked)
{
}
public PayloadData (byte [] extensionData, byte [] applicationData, bool masked)
{
_extensionData = extensionData;
_applicationData = applicationData;
_masked = masked;
}
#endregion
#region Internal Properties
internal bool ContainsReservedCloseStatusCode {
get {
return _applicationData.Length > 1 &&
_applicationData.SubArray (0, 2).ToUInt16 (ByteOrder.Big).IsReserved ();
}
}
#endregion
#region Public Properties
public byte [] ApplicationData {
get {
return _applicationData;
}
}
public byte [] ExtensionData {
get {
return _extensionData;
}
}
public bool IsMasked {
get {
return _masked;
}
}
public ulong Length {
get {
return (ulong) (_extensionData.Length + _applicationData.Length);
}
}
#endregion
#region Private Methods
private static void mask (byte [] src, byte [] key)
{
for (long i = 0; i < src.Length; i++)
src [i] = (byte) (src [i] ^ key [i % 4]);
}
#endregion
#region Public Methods
public IEnumerator<byte> GetEnumerator ()
{
foreach (byte b in _extensionData)
yield return b;
foreach (byte b in _applicationData)
yield return b;
}
public void Mask (byte [] maskingKey)
{
if (_extensionData.Length > 0)
mask (_extensionData, maskingKey);
if (_applicationData.Length > 0)
mask (_applicationData, maskingKey);
_masked = !_masked;
}
public byte [] ToByteArray ()
{
return _extensionData.Length > 0
? new List<byte> (this).ToArray ()
: _applicationData;
}
public override string ToString ()
{
return BitConverter.ToString (ToByteArray ());
}
#endregion
#region Explicitly Implemented Interface Members
IEnumerator IEnumerable.GetEnumerator ()
{
return GetEnumerator ();
}
#endregion
}
}

View File

@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SocketHttpListener.Primitives
{
public interface ICertificate
{
}
}

View File

@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MediaBrowser.Model.Net;
namespace SocketHttpListener.Primitives
{
public interface IStreamFactory
{
Stream CreateNetworkStream(IAcceptSocket acceptSocket, bool ownsSocket);
Stream CreateSslStream(Stream innerStream, bool leaveInnerStreamOpen);
Task AuthenticateSslStreamAsServer(Stream stream, ICertificate certificate);
}
}

View File

@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MediaBrowser.Model.Text;
namespace SocketHttpListener.Primitives
{
public static class TextEncodingExtensions
{
public static Encoding GetDefaultEncoding(this ITextEncoding encoding)
{
return Encoding.UTF8;
}
}
}

View File

@@ -0,0 +1,34 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("SocketHttpListener")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("SocketHttpListener")]
[assembly: AssemblyCopyright("Copyright © 2017")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("1d74413b-e7cf-455b-b021-f52bdf881542")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]

View File

@@ -0,0 +1,8 @@
namespace SocketHttpListener
{
internal enum Rsv : byte
{
Off = 0x0,
On = 0x1
}
}

View File

@@ -0,0 +1,111 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{1D74413B-E7CF-455B-B021-F52BDF881542}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>SocketHttpListener</RootNamespace>
<AssemblyName>SocketHttpListener</AssemblyName>
<TargetFrameworkVersion>v4.6.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\SharedVersion.cs">
<Link>Properties\SharedVersion.cs</Link>
</Compile>
<Compile Include="ByteOrder.cs" />
<Compile Include="CloseEventArgs.cs" />
<Compile Include="CloseStatusCode.cs" />
<Compile Include="CompressionMethod.cs" />
<Compile Include="ErrorEventArgs.cs" />
<Compile Include="Ext.cs" />
<Compile Include="Fin.cs" />
<Compile Include="HttpBase.cs" />
<Compile Include="HttpResponse.cs" />
<Compile Include="Mask.cs" />
<Compile Include="MessageEventArgs.cs" />
<Compile Include="Net\AuthenticationSchemeSelector.cs" />
<Compile Include="Net\ChunkedInputStream.cs" />
<Compile Include="Net\ChunkStream.cs" />
<Compile Include="Net\CookieHelper.cs" />
<Compile Include="Net\EndPointListener.cs" />
<Compile Include="Net\EndPointManager.cs" />
<Compile Include="Net\HttpConnection.cs" />
<Compile Include="Net\HttpListener.cs" />
<Compile Include="Net\HttpListenerBasicIdentity.cs" />
<Compile Include="Net\HttpListenerContext.cs" />
<Compile Include="Net\HttpListenerPrefixCollection.cs" />
<Compile Include="Net\HttpListenerRequest.cs" />
<Compile Include="Net\HttpListenerResponse.cs" />
<Compile Include="Net\HttpRequestStream.cs" />
<Compile Include="Net\HttpRequestStream.Managed.cs" />
<Compile Include="Net\HttpStatusCode.cs" />
<Compile Include="Net\HttpStreamAsyncResult.cs" />
<Compile Include="Net\HttpVersion.cs" />
<Compile Include="Net\ListenerPrefix.cs" />
<Compile Include="Net\ResponseStream.cs" />
<Compile Include="Net\WebHeaderCollection.cs" />
<Compile Include="Net\WebSockets\HttpListenerWebSocketContext.cs" />
<Compile Include="Net\WebSockets\WebSocketContext.cs" />
<Compile Include="Opcode.cs" />
<Compile Include="PayloadData.cs" />
<Compile Include="Primitives\ICertificate.cs" />
<Compile Include="Primitives\IStreamFactory.cs" />
<Compile Include="Primitives\ITextEncoding.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Rsv.cs" />
<Compile Include="WebSocket.cs" />
<Compile Include="WebSocketException.cs" />
<Compile Include="WebSocketFrame.cs" />
<Compile Include="WebSocketState.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj">
<Project>{9142eefa-7570-41e1-bfcc-468bb571af2f}</Project>
<Name>MediaBrowser.Common</Name>
</ProjectReference>
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj">
<Project>{7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}</Project>
<Name>MediaBrowser.Model</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@@ -0,0 +1,887 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.Cryptography;
using MediaBrowser.Model.IO;
using SocketHttpListener.Net.WebSockets;
using SocketHttpListener.Primitives;
using HttpStatusCode = SocketHttpListener.Net.HttpStatusCode;
namespace SocketHttpListener
{
/// <summary>
/// Implements the WebSocket interface.
/// </summary>
/// <remarks>
/// The WebSocket class provides a set of methods and properties for two-way communication using
/// the WebSocket protocol (<see href="http://tools.ietf.org/html/rfc6455">RFC 6455</see>).
/// </remarks>
public class WebSocket : IDisposable
{
#region Private Fields
private string _base64Key;
private Action _closeContext;
private CompressionMethod _compression;
private WebSocketContext _context;
private CookieCollection _cookies;
private string _extensions;
private AutoResetEvent _exitReceiving;
private object _forConn;
private object _forEvent;
private object _forMessageEventQueue;
private object _forSend;
private const string _guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
private Func<WebSocketContext, string>
_handshakeRequestChecker;
private Queue<MessageEventArgs> _messageEventQueue;
private uint _nonceCount;
private string _origin;
private bool _preAuth;
private string _protocol;
private string[] _protocols;
private Uri _proxyUri;
private volatile WebSocketState _readyState;
private AutoResetEvent _receivePong;
private bool _secure;
private Stream _stream;
private Uri _uri;
private const string _version = "13";
private readonly IMemoryStreamFactory _memoryStreamFactory;
private readonly ICryptoProvider _cryptoProvider;
#endregion
#region Internal Fields
internal const int FragmentLength = 1016; // Max value is int.MaxValue - 14.
#endregion
#region Internal Constructors
// As server
internal WebSocket(HttpListenerWebSocketContext context, string protocol, ICryptoProvider cryptoProvider, IMemoryStreamFactory memoryStreamFactory)
{
_context = context;
_protocol = protocol;
_cryptoProvider = cryptoProvider;
_memoryStreamFactory = memoryStreamFactory;
_closeContext = context.Close;
_secure = context.IsSecureConnection;
_stream = context.Stream;
init();
}
#endregion
// As server
internal Func<WebSocketContext, string> CustomHandshakeRequestChecker
{
get
{
return _handshakeRequestChecker ?? (context => null);
}
set
{
_handshakeRequestChecker = value;
}
}
internal bool IsConnected
{
get
{
return _readyState == WebSocketState.Open || _readyState == WebSocketState.Closing;
}
}
/// <summary>
/// Gets the state of the WebSocket connection.
/// </summary>
/// <value>
/// One of the <see cref="WebSocketState"/> enum values, indicates the state of the WebSocket
/// connection. The default value is <see cref="WebSocketState.Connecting"/>.
/// </value>
public WebSocketState ReadyState
{
get
{
return _readyState;
}
}
#region Public Events
/// <summary>
/// Occurs when the WebSocket connection has been closed.
/// </summary>
public event EventHandler<CloseEventArgs> OnClose;
/// <summary>
/// Occurs when the <see cref="WebSocket"/> gets an error.
/// </summary>
public event EventHandler<ErrorEventArgs> OnError;
/// <summary>
/// Occurs when the <see cref="WebSocket"/> receives a message.
/// </summary>
public event EventHandler<MessageEventArgs> OnMessage;
/// <summary>
/// Occurs when the WebSocket connection has been established.
/// </summary>
public event EventHandler OnOpen;
#endregion
#region Private Methods
// As server
private bool acceptHandshake()
{
var msg = checkIfValidHandshakeRequest(_context);
if (msg != null)
{
error("An error has occurred while connecting: " + msg);
Close(HttpStatusCode.BadRequest);
return false;
}
if (_protocol != null &&
!_context.SecWebSocketProtocols.Contains(protocol => protocol == _protocol))
_protocol = null;
////var extensions = _context.Headers["Sec-WebSocket-Extensions"];
////if (extensions != null && extensions.Length > 0)
//// processSecWebSocketExtensionsHeader(extensions);
return sendHttpResponse(createHandshakeResponse());
}
// As server
private string checkIfValidHandshakeRequest(WebSocketContext context)
{
var headers = context.Headers;
return context.RequestUri == null
? "Invalid request url."
: !context.IsWebSocketRequest
? "Not WebSocket connection request."
: !validateSecWebSocketKeyHeader(headers["Sec-WebSocket-Key"])
? "Invalid Sec-WebSocket-Key header."
: !validateSecWebSocketVersionClientHeader(headers["Sec-WebSocket-Version"])
? "Invalid Sec-WebSocket-Version header."
: CustomHandshakeRequestChecker(context);
}
private void close(CloseStatusCode code, string reason, bool wait)
{
close(new PayloadData(((ushort)code).Append(reason)), !code.IsReserved(), wait);
}
private void close(PayloadData payload, bool send, bool wait)
{
lock (_forConn)
{
if (_readyState == WebSocketState.Closing || _readyState == WebSocketState.Closed)
{
return;
}
_readyState = WebSocketState.Closing;
}
var e = new CloseEventArgs(payload);
e.WasClean =
closeHandshake(
send ? WebSocketFrame.CreateCloseFrame(Mask.Unmask, payload).ToByteArray() : null,
wait ? 1000 : 0,
closeServerResources);
_readyState = WebSocketState.Closed;
try
{
OnClose.Emit(this, e);
}
catch (Exception ex)
{
error("An exception has occurred while OnClose.", ex);
}
}
private bool closeHandshake(byte[] frameAsBytes, int millisecondsTimeout, Action release)
{
var sent = frameAsBytes != null && writeBytes(frameAsBytes);
var received =
millisecondsTimeout == 0 ||
(sent && _exitReceiving != null && _exitReceiving.WaitOne(millisecondsTimeout));
release();
if (_receivePong != null)
{
_receivePong.Dispose();
_receivePong = null;
}
if (_exitReceiving != null)
{
_exitReceiving.Dispose();
_exitReceiving = null;
}
var result = sent && received;
return result;
}
// As server
private void closeServerResources()
{
if (_closeContext == null)
return;
_closeContext();
_closeContext = null;
_stream = null;
_context = null;
}
private bool concatenateFragmentsInto(Stream dest)
{
while (true)
{
var frame = WebSocketFrame.Read(_stream, true);
if (frame.IsFinal)
{
/* FINAL */
// CONT
if (frame.IsContinuation)
{
dest.WriteBytes(frame.PayloadData.ApplicationData);
break;
}
// PING
if (frame.IsPing)
{
processPingFrame(frame);
continue;
}
// PONG
if (frame.IsPong)
{
processPongFrame(frame);
continue;
}
// CLOSE
if (frame.IsClose)
return processCloseFrame(frame);
}
else
{
/* MORE */
// CONT
if (frame.IsContinuation)
{
dest.WriteBytes(frame.PayloadData.ApplicationData);
continue;
}
}
// ?
return processUnsupportedFrame(
frame,
CloseStatusCode.IncorrectData,
"An incorrect data has been received while receiving fragmented data.");
}
return true;
}
// As server
private HttpResponse createHandshakeCloseResponse(HttpStatusCode code)
{
var res = HttpResponse.CreateCloseResponse(code);
res.Headers["Sec-WebSocket-Version"] = _version;
return res;
}
// As server
private HttpResponse createHandshakeResponse()
{
var res = HttpResponse.CreateWebSocketResponse();
var headers = res.Headers;
headers["Sec-WebSocket-Accept"] = CreateResponseKey(_base64Key);
if (_protocol != null)
headers["Sec-WebSocket-Protocol"] = _protocol;
if (_extensions != null)
headers["Sec-WebSocket-Extensions"] = _extensions;
if (_cookies.Count > 0)
res.SetCookies(_cookies);
return res;
}
private MessageEventArgs dequeueFromMessageEventQueue()
{
lock (_forMessageEventQueue)
return _messageEventQueue.Count > 0
? _messageEventQueue.Dequeue()
: null;
}
private void enqueueToMessageEventQueue(MessageEventArgs e)
{
lock (_forMessageEventQueue)
_messageEventQueue.Enqueue(e);
}
private void error(string message, Exception exception)
{
try
{
if (exception != null)
{
message += ". Exception.Message: " + exception.Message;
}
OnError.Emit(this, new ErrorEventArgs(message));
}
catch (Exception ex)
{
}
}
private void error(string message)
{
try
{
OnError.Emit(this, new ErrorEventArgs(message));
}
catch (Exception ex)
{
}
}
private void init()
{
_compression = CompressionMethod.None;
_cookies = new CookieCollection();
_forConn = new object();
_forEvent = new object();
_forSend = new object();
_messageEventQueue = new Queue<MessageEventArgs>();
_forMessageEventQueue = ((ICollection)_messageEventQueue).SyncRoot;
_readyState = WebSocketState.Connecting;
}
private void open()
{
try
{
startReceiving();
lock (_forEvent)
{
try
{
OnOpen.Emit(this, EventArgs.Empty);
}
catch (Exception ex)
{
processException(ex, "An exception has occurred while OnOpen.");
}
}
}
catch (Exception ex)
{
processException(ex, "An exception has occurred while opening.");
}
}
private bool processCloseFrame(WebSocketFrame frame)
{
var payload = frame.PayloadData;
close(payload, !payload.ContainsReservedCloseStatusCode, false);
return false;
}
private bool processDataFrame(WebSocketFrame frame)
{
var e = frame.IsCompressed
? new MessageEventArgs(
frame.Opcode, frame.PayloadData.ApplicationData.Decompress(_compression))
: new MessageEventArgs(frame.Opcode, frame.PayloadData);
enqueueToMessageEventQueue(e);
return true;
}
private void processException(Exception exception, string message)
{
var code = CloseStatusCode.Abnormal;
var reason = message;
if (exception is WebSocketException)
{
var wsex = (WebSocketException)exception;
code = wsex.Code;
reason = wsex.Message;
}
error(message ?? code.GetMessage(), exception);
if (_readyState == WebSocketState.Connecting)
Close(HttpStatusCode.BadRequest);
else
close(code, reason ?? code.GetMessage(), false);
}
private bool processFragmentedFrame(WebSocketFrame frame)
{
return frame.IsContinuation // Not first fragment
? true
: processFragments(frame);
}
private bool processFragments(WebSocketFrame first)
{
using (var buff = _memoryStreamFactory.CreateNew())
{
buff.WriteBytes(first.PayloadData.ApplicationData);
if (!concatenateFragmentsInto(buff))
return false;
byte[] data;
if (_compression != CompressionMethod.None)
{
data = buff.DecompressToArray(_compression);
}
else
{
data = buff.ToArray();
}
enqueueToMessageEventQueue(new MessageEventArgs(first.Opcode, data));
return true;
}
}
private bool processPingFrame(WebSocketFrame frame)
{
var mask = Mask.Unmask;
return true;
}
private bool processPongFrame(WebSocketFrame frame)
{
_receivePong.Set();
return true;
}
private bool processUnsupportedFrame(WebSocketFrame frame, CloseStatusCode code, string reason)
{
processException(new WebSocketException(code, reason), null);
return false;
}
private bool processWebSocketFrame(WebSocketFrame frame)
{
return frame.IsCompressed && _compression == CompressionMethod.None
? processUnsupportedFrame(
frame,
CloseStatusCode.IncorrectData,
"A compressed data has been received without available decompression method.")
: frame.IsFragmented
? processFragmentedFrame(frame)
: frame.IsData
? processDataFrame(frame)
: frame.IsPing
? processPingFrame(frame)
: frame.IsPong
? processPongFrame(frame)
: frame.IsClose
? processCloseFrame(frame)
: processUnsupportedFrame(frame, CloseStatusCode.PolicyViolation, null);
}
private bool send(Opcode opcode, Stream stream)
{
lock (_forSend)
{
var src = stream;
var compressed = false;
var sent = false;
try
{
if (_compression != CompressionMethod.None)
{
stream = stream.Compress(_compression);
compressed = true;
}
sent = send(opcode, Mask.Unmask, stream, compressed);
if (!sent)
error("Sending a data has been interrupted.");
}
catch (Exception ex)
{
error("An exception has occurred while sending a data.", ex);
}
finally
{
if (compressed)
stream.Dispose();
src.Dispose();
}
return sent;
}
}
private bool send(Opcode opcode, Mask mask, Stream stream, bool compressed)
{
var len = stream.Length;
/* Not fragmented */
if (len == 0)
return send(Fin.Final, opcode, mask, new byte[0], compressed);
var quo = len / FragmentLength;
var rem = (int)(len % FragmentLength);
byte[] buff = null;
if (quo == 0)
{
buff = new byte[rem];
return stream.Read(buff, 0, rem) == rem &&
send(Fin.Final, opcode, mask, buff, compressed);
}
buff = new byte[FragmentLength];
if (quo == 1 && rem == 0)
return stream.Read(buff, 0, FragmentLength) == FragmentLength &&
send(Fin.Final, opcode, mask, buff, compressed);
/* Send fragmented */
// Begin
if (stream.Read(buff, 0, FragmentLength) != FragmentLength ||
!send(Fin.More, opcode, mask, buff, compressed))
return false;
var n = rem == 0 ? quo - 2 : quo - 1;
for (long i = 0; i < n; i++)
if (stream.Read(buff, 0, FragmentLength) != FragmentLength ||
!send(Fin.More, Opcode.Cont, mask, buff, compressed))
return false;
// End
if (rem == 0)
rem = FragmentLength;
else
buff = new byte[rem];
return stream.Read(buff, 0, rem) == rem &&
send(Fin.Final, Opcode.Cont, mask, buff, compressed);
}
private bool send(Fin fin, Opcode opcode, Mask mask, byte[] data, bool compressed)
{
lock (_forConn)
{
if (_readyState != WebSocketState.Open)
{
return false;
}
return writeBytes(
WebSocketFrame.CreateWebSocketFrame(fin, opcode, mask, data, compressed).ToByteArray());
}
}
private Task sendAsync(Opcode opcode, Stream stream)
{
var completionSource = new TaskCompletionSource<bool>();
Task.Run(() =>
{
try
{
send(opcode, stream);
completionSource.TrySetResult(true);
}
catch (Exception ex)
{
completionSource.TrySetException(ex);
}
});
return completionSource.Task;
}
// As server
private bool sendHttpResponse(HttpResponse response)
{
return writeBytes(response.ToByteArray());
}
private void startReceiving()
{
if (_messageEventQueue.Count > 0)
_messageEventQueue.Clear();
_exitReceiving = new AutoResetEvent(false);
_receivePong = new AutoResetEvent(false);
Action receive = null;
receive = () => WebSocketFrame.ReadAsync(
_stream,
true,
frame =>
{
if (processWebSocketFrame(frame) && _readyState != WebSocketState.Closed)
{
receive();
if (!frame.IsData)
return;
lock (_forEvent)
{
try
{
var e = dequeueFromMessageEventQueue();
if (e != null && _readyState == WebSocketState.Open)
OnMessage.Emit(this, e);
}
catch (Exception ex)
{
processException(ex, "An exception has occurred while OnMessage.");
}
}
}
else if (_exitReceiving != null)
{
_exitReceiving.Set();
}
},
ex => processException(ex, "An exception has occurred while receiving a message."));
receive();
}
// As server
private bool validateSecWebSocketKeyHeader(string value)
{
if (value == null || value.Length == 0)
return false;
_base64Key = value;
return true;
}
// As server
private bool validateSecWebSocketVersionClientHeader(string value)
{
return true;
//return value != null && value == _version;
}
private bool writeBytes(byte[] data)
{
try
{
_stream.Write(data, 0, data.Length);
return true;
}
catch (Exception ex)
{
return false;
}
}
#endregion
#region Internal Methods
// As server
internal void Close(HttpResponse response)
{
_readyState = WebSocketState.Closing;
sendHttpResponse(response);
closeServerResources();
_readyState = WebSocketState.Closed;
}
// As server
internal void Close(HttpStatusCode code)
{
Close(createHandshakeCloseResponse(code));
}
// As server
public void ConnectAsServer()
{
try
{
if (acceptHandshake())
{
_readyState = WebSocketState.Open;
open();
}
}
catch (Exception ex)
{
processException(ex, "An exception has occurred while connecting.");
}
}
private string CreateResponseKey(string base64Key)
{
var buff = new StringBuilder(base64Key, 64);
buff.Append(_guid);
var src = _cryptoProvider.ComputeSHA1(Encoding.UTF8.GetBytes(buff.ToString()));
return Convert.ToBase64String(src);
}
#endregion
#region Public Methods
/// <summary>
/// Closes the WebSocket connection, and releases all associated resources.
/// </summary>
public void Close()
{
var msg = _readyState.CheckIfClosable();
if (msg != null)
{
error(msg);
return;
}
var send = _readyState == WebSocketState.Open;
close(new PayloadData(), send, send);
}
/// <summary>
/// Closes the WebSocket connection with the specified <see cref="CloseStatusCode"/>
/// and <see cref="string"/>, and releases all associated resources.
/// </summary>
/// <remarks>
/// This method emits a <see cref="OnError"/> event if the size
/// of <paramref name="reason"/> is greater than 123 bytes.
/// </remarks>
/// <param name="code">
/// One of the <see cref="CloseStatusCode"/> enum values, represents the status code
/// indicating the reason for the close.
/// </param>
/// <param name="reason">
/// A <see cref="string"/> that represents the reason for the close.
/// </param>
public void Close(CloseStatusCode code, string reason)
{
byte[] data = null;
var msg = _readyState.CheckIfClosable() ??
(data = ((ushort)code).Append(reason)).CheckIfValidControlData("reason");
if (msg != null)
{
error(msg);
return;
}
var send = _readyState == WebSocketState.Open && !code.IsReserved();
close(new PayloadData(data), send, send);
}
/// <summary>
/// Sends a binary <paramref name="data"/> asynchronously using the WebSocket connection.
/// </summary>
/// <remarks>
/// This method doesn't wait for the send to be complete.
/// </remarks>
/// <param name="data">
/// An array of <see cref="byte"/> that represents the binary data to send.
/// </param>
/// An Action&lt;bool&gt; delegate that references the method(s) called when the send is
/// complete. A <see cref="bool"/> passed to this delegate is <c>true</c> if the send is
/// complete successfully; otherwise, <c>false</c>.
public Task SendAsync(byte[] data)
{
var msg = _readyState.CheckIfOpen() ?? data.CheckIfValidSendData();
if (msg != null)
{
throw new Exception(msg);
}
return sendAsync(Opcode.Binary, _memoryStreamFactory.CreateNew(data));
}
/// <summary>
/// Sends a text <paramref name="data"/> asynchronously using the WebSocket connection.
/// </summary>
/// <remarks>
/// This method doesn't wait for the send to be complete.
/// </remarks>
/// <param name="data">
/// A <see cref="string"/> that represents the text data to send.
/// </param>
/// An Action&lt;bool&gt; delegate that references the method(s) called when the send is
/// complete. A <see cref="bool"/> passed to this delegate is <c>true</c> if the send is
/// complete successfully; otherwise, <c>false</c>.
public Task SendAsync(string data)
{
var msg = _readyState.CheckIfOpen() ?? data.CheckIfValidSendData();
if (msg != null)
{
throw new Exception(msg);
}
return sendAsync(Opcode.Text, _memoryStreamFactory.CreateNew(Encoding.UTF8.GetBytes(data)));
}
#endregion
#region Explicit Interface Implementation
/// <summary>
/// Closes the WebSocket connection, and releases all associated resources.
/// </summary>
/// <remarks>
/// This method closes the WebSocket connection with <see cref="CloseStatusCode.Away"/>.
/// </remarks>
void IDisposable.Dispose()
{
Close(CloseStatusCode.Away, null);
}
#endregion
}
}

View File

@@ -0,0 +1,60 @@
using System;
namespace SocketHttpListener
{
/// <summary>
/// The exception that is thrown when a <see cref="WebSocket"/> gets a fatal error.
/// </summary>
public class WebSocketException : Exception
{
#region Internal Constructors
internal WebSocketException ()
: this (CloseStatusCode.Abnormal, null, null)
{
}
internal WebSocketException (string message)
: this (CloseStatusCode.Abnormal, message, null)
{
}
internal WebSocketException (CloseStatusCode code)
: this (code, null, null)
{
}
internal WebSocketException (string message, Exception innerException)
: this (CloseStatusCode.Abnormal, message, innerException)
{
}
internal WebSocketException (CloseStatusCode code, string message)
: this (code, message, null)
{
}
internal WebSocketException (CloseStatusCode code, string message, Exception innerException)
: base (message ?? code.GetMessage (), innerException)
{
Code = code;
}
#endregion
#region Public Properties
/// <summary>
/// Gets the status code indicating the cause for the exception.
/// </summary>
/// <value>
/// One of the <see cref="CloseStatusCode"/> enum values, represents the status code indicating
/// the cause for the exception.
/// </value>
public CloseStatusCode Code {
get; private set;
}
#endregion
}
}

View File

@@ -0,0 +1,578 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace SocketHttpListener
{
internal class WebSocketFrame : IEnumerable<byte>
{
#region Private Fields
private byte[] _extPayloadLength;
private Fin _fin;
private Mask _mask;
private byte[] _maskingKey;
private Opcode _opcode;
private PayloadData _payloadData;
private byte _payloadLength;
private Rsv _rsv1;
private Rsv _rsv2;
private Rsv _rsv3;
#endregion
#region Internal Fields
internal static readonly byte[] EmptyUnmaskPingData;
#endregion
#region Static Constructor
static WebSocketFrame()
{
EmptyUnmaskPingData = CreatePingFrame(Mask.Unmask).ToByteArray();
}
#endregion
#region Private Constructors
private WebSocketFrame()
{
}
#endregion
#region Internal Constructors
internal WebSocketFrame(Opcode opcode, PayloadData payload)
: this(Fin.Final, opcode, Mask.Mask, payload, false)
{
}
internal WebSocketFrame(Opcode opcode, Mask mask, PayloadData payload)
: this(Fin.Final, opcode, mask, payload, false)
{
}
internal WebSocketFrame(Fin fin, Opcode opcode, Mask mask, PayloadData payload)
: this(fin, opcode, mask, payload, false)
{
}
internal WebSocketFrame(
Fin fin, Opcode opcode, Mask mask, PayloadData payload, bool compressed)
{
_fin = fin;
_rsv1 = isData(opcode) && compressed ? Rsv.On : Rsv.Off;
_rsv2 = Rsv.Off;
_rsv3 = Rsv.Off;
_opcode = opcode;
_mask = mask;
var len = payload.Length;
if (len < 126)
{
_payloadLength = (byte)len;
_extPayloadLength = new byte[0];
}
else if (len < 0x010000)
{
_payloadLength = (byte)126;
_extPayloadLength = ((ushort)len).ToByteArrayInternally(ByteOrder.Big);
}
else
{
_payloadLength = (byte)127;
_extPayloadLength = len.ToByteArrayInternally(ByteOrder.Big);
}
if (mask == Mask.Mask)
{
_maskingKey = createMaskingKey();
payload.Mask(_maskingKey);
}
else
{
_maskingKey = new byte[0];
}
_payloadData = payload;
}
#endregion
#region Public Properties
public byte[] ExtendedPayloadLength
{
get
{
return _extPayloadLength;
}
}
public Fin Fin
{
get
{
return _fin;
}
}
public bool IsBinary
{
get
{
return _opcode == Opcode.Binary;
}
}
public bool IsClose
{
get
{
return _opcode == Opcode.Close;
}
}
public bool IsCompressed
{
get
{
return _rsv1 == Rsv.On;
}
}
public bool IsContinuation
{
get
{
return _opcode == Opcode.Cont;
}
}
public bool IsControl
{
get
{
return _opcode == Opcode.Close || _opcode == Opcode.Ping || _opcode == Opcode.Pong;
}
}
public bool IsData
{
get
{
return _opcode == Opcode.Binary || _opcode == Opcode.Text;
}
}
public bool IsFinal
{
get
{
return _fin == Fin.Final;
}
}
public bool IsFragmented
{
get
{
return _fin == Fin.More || _opcode == Opcode.Cont;
}
}
public bool IsMasked
{
get
{
return _mask == Mask.Mask;
}
}
public bool IsPerMessageCompressed
{
get
{
return (_opcode == Opcode.Binary || _opcode == Opcode.Text) && _rsv1 == Rsv.On;
}
}
public bool IsPing
{
get
{
return _opcode == Opcode.Ping;
}
}
public bool IsPong
{
get
{
return _opcode == Opcode.Pong;
}
}
public bool IsText
{
get
{
return _opcode == Opcode.Text;
}
}
public ulong Length
{
get
{
return 2 + (ulong)(_extPayloadLength.Length + _maskingKey.Length) + _payloadData.Length;
}
}
public Mask Mask
{
get
{
return _mask;
}
}
public byte[] MaskingKey
{
get
{
return _maskingKey;
}
}
public Opcode Opcode
{
get
{
return _opcode;
}
}
public PayloadData PayloadData
{
get
{
return _payloadData;
}
}
public byte PayloadLength
{
get
{
return _payloadLength;
}
}
public Rsv Rsv1
{
get
{
return _rsv1;
}
}
public Rsv Rsv2
{
get
{
return _rsv2;
}
}
public Rsv Rsv3
{
get
{
return _rsv3;
}
}
#endregion
#region Private Methods
private byte[] createMaskingKey()
{
var key = new byte[4];
var rand = new Random();
rand.NextBytes(key);
return key;
}
private static bool isControl(Opcode opcode)
{
return opcode == Opcode.Close || opcode == Opcode.Ping || opcode == Opcode.Pong;
}
private static bool isData(Opcode opcode)
{
return opcode == Opcode.Text || opcode == Opcode.Binary;
}
private static WebSocketFrame read(byte[] header, Stream stream, bool unmask)
{
/* Header */
// FIN
var fin = (header[0] & 0x80) == 0x80 ? Fin.Final : Fin.More;
// RSV1
var rsv1 = (header[0] & 0x40) == 0x40 ? Rsv.On : Rsv.Off;
// RSV2
var rsv2 = (header[0] & 0x20) == 0x20 ? Rsv.On : Rsv.Off;
// RSV3
var rsv3 = (header[0] & 0x10) == 0x10 ? Rsv.On : Rsv.Off;
// Opcode
var opcode = (Opcode)(header[0] & 0x0f);
// MASK
var mask = (header[1] & 0x80) == 0x80 ? Mask.Mask : Mask.Unmask;
// Payload Length
var payloadLen = (byte)(header[1] & 0x7f);
// Check if correct frame.
var incorrect = isControl(opcode) && fin == Fin.More
? "A control frame is fragmented."
: !isData(opcode) && rsv1 == Rsv.On
? "A non data frame is compressed."
: null;
if (incorrect != null)
throw new WebSocketException(CloseStatusCode.IncorrectData, incorrect);
// Check if consistent frame.
if (isControl(opcode) && payloadLen > 125)
throw new WebSocketException(
CloseStatusCode.InconsistentData,
"The length of payload data of a control frame is greater than 125 bytes.");
var frame = new WebSocketFrame();
frame._fin = fin;
frame._rsv1 = rsv1;
frame._rsv2 = rsv2;
frame._rsv3 = rsv3;
frame._opcode = opcode;
frame._mask = mask;
frame._payloadLength = payloadLen;
/* Extended Payload Length */
var size = payloadLen < 126
? 0
: payloadLen == 126
? 2
: 8;
var extPayloadLen = size > 0 ? stream.ReadBytes(size) : new byte[0];
if (size > 0 && extPayloadLen.Length != size)
throw new WebSocketException(
"The 'Extended Payload Length' of a frame cannot be read from the data source.");
frame._extPayloadLength = extPayloadLen;
/* Masking Key */
var masked = mask == Mask.Mask;
var maskingKey = masked ? stream.ReadBytes(4) : new byte[0];
if (masked && maskingKey.Length != 4)
throw new WebSocketException(
"The 'Masking Key' of a frame cannot be read from the data source.");
frame._maskingKey = maskingKey;
/* Payload Data */
ulong len = payloadLen < 126
? payloadLen
: payloadLen == 126
? extPayloadLen.ToUInt16(ByteOrder.Big)
: extPayloadLen.ToUInt64(ByteOrder.Big);
byte[] data = null;
if (len > 0)
{
// Check if allowable payload data length.
if (payloadLen > 126 && len > PayloadData.MaxLength)
throw new WebSocketException(
CloseStatusCode.TooBig,
"The length of 'Payload Data' of a frame is greater than the allowable length.");
data = payloadLen > 126
? stream.ReadBytes((long)len, 1024)
: stream.ReadBytes((int)len);
//if (data.LongLength != (long)len)
// throw new WebSocketException(
// "The 'Payload Data' of a frame cannot be read from the data source.");
}
else
{
data = new byte[0];
}
var payload = new PayloadData(data, masked);
if (masked && unmask)
{
payload.Mask(maskingKey);
frame._mask = Mask.Unmask;
frame._maskingKey = new byte[0];
}
frame._payloadData = payload;
return frame;
}
#endregion
#region Internal Methods
internal static WebSocketFrame CreateCloseFrame(Mask mask, byte[] data)
{
return new WebSocketFrame(Opcode.Close, mask, new PayloadData(data));
}
internal static WebSocketFrame CreateCloseFrame(Mask mask, PayloadData payload)
{
return new WebSocketFrame(Opcode.Close, mask, payload);
}
internal static WebSocketFrame CreateCloseFrame(Mask mask, CloseStatusCode code, string reason)
{
return new WebSocketFrame(
Opcode.Close, mask, new PayloadData(((ushort)code).Append(reason)));
}
internal static WebSocketFrame CreatePingFrame(Mask mask)
{
return new WebSocketFrame(Opcode.Ping, mask, new PayloadData());
}
internal static WebSocketFrame CreatePingFrame(Mask mask, byte[] data)
{
return new WebSocketFrame(Opcode.Ping, mask, new PayloadData(data));
}
internal static WebSocketFrame CreatePongFrame(Mask mask, PayloadData payload)
{
return new WebSocketFrame(Opcode.Pong, mask, payload);
}
internal static WebSocketFrame CreateWebSocketFrame(
Fin fin, Opcode opcode, Mask mask, byte[] data, bool compressed)
{
return new WebSocketFrame(fin, opcode, mask, new PayloadData(data), compressed);
}
internal static WebSocketFrame Read(Stream stream)
{
return Read(stream, true);
}
internal static WebSocketFrame Read(Stream stream, bool unmask)
{
var header = stream.ReadBytes(2);
if (header.Length != 2)
throw new WebSocketException(
"The header part of a frame cannot be read from the data source.");
return read(header, stream, unmask);
}
internal static async void ReadAsync(
Stream stream, bool unmask, Action<WebSocketFrame> completed, Action<Exception> error)
{
try
{
var header = await stream.ReadBytesAsync(2).ConfigureAwait(false);
if (header.Length != 2)
throw new WebSocketException(
"The header part of a frame cannot be read from the data source.");
var frame = read(header, stream, unmask);
if (completed != null)
completed(frame);
}
catch (Exception ex)
{
if (error != null)
{
error(ex);
}
}
}
#endregion
#region Public Methods
public IEnumerator<byte> GetEnumerator()
{
foreach (var b in ToByteArray())
yield return b;
}
public void Print(bool dumped)
{
//Console.WriteLine(dumped ? dump(this) : print(this));
}
public byte[] ToByteArray()
{
using (var buff = new MemoryStream())
{
var header = (int)_fin;
header = (header << 1) + (int)_rsv1;
header = (header << 1) + (int)_rsv2;
header = (header << 1) + (int)_rsv3;
header = (header << 4) + (int)_opcode;
header = (header << 1) + (int)_mask;
header = (header << 7) + (int)_payloadLength;
buff.Write(((ushort)header).ToByteArrayInternally(ByteOrder.Big), 0, 2);
if (_payloadLength > 125)
buff.Write(_extPayloadLength, 0, _extPayloadLength.Length);
if (_mask == Mask.Mask)
buff.Write(_maskingKey, 0, _maskingKey.Length);
if (_payloadLength > 0)
{
var payload = _payloadData.ToByteArray();
if (_payloadLength < 127)
buff.Write(payload, 0, payload.Length);
else
buff.WriteBytes(payload);
}
return buff.ToArray();
}
}
public override string ToString()
{
return BitConverter.ToString(ToByteArray());
}
#endregion
#region Explicitly Implemented Interface Members
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
}
}

View File

@@ -0,0 +1,35 @@
namespace SocketHttpListener
{
/// <summary>
/// Contains the values of the state of the WebSocket connection.
/// </summary>
/// <remarks>
/// The values of the state are defined in
/// <see href="http://www.w3.org/TR/websockets/#dom-websocket-readystate">The WebSocket
/// API</see>.
/// </remarks>
public enum WebSocketState : ushort
{
/// <summary>
/// Equivalent to numeric value 0.
/// Indicates that the connection has not yet been established.
/// </summary>
Connecting = 0,
/// <summary>
/// Equivalent to numeric value 1.
/// Indicates that the connection is established and the communication is possible.
/// </summary>
Open = 1,
/// <summary>
/// Equivalent to numeric value 2.
/// Indicates that the connection is going through the closing handshake or
/// the <c>WebSocket.Close</c> method has been invoked.
/// </summary>
Closing = 2,
/// <summary>
/// Equivalent to numeric value 3.
/// Indicates that the connection has been closed or couldn't be opened.
/// </summary>
Closed = 3
}
}