mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-03-02 00:12:24 +00:00
Update to 3.5.2 and .net core 2.1
This commit is contained in:
@@ -7,9 +7,8 @@ using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Model.Services;
|
||||
using SocketHttpListener.Net;
|
||||
using HttpListenerResponse = SocketHttpListener.Net.HttpListenerResponse;
|
||||
using HttpStatusCode = SocketHttpListener.Net.HttpStatusCode;
|
||||
using WebSocketState = System.Net.WebSockets.WebSocketState;
|
||||
|
||||
namespace SocketHttpListener
|
||||
{
|
||||
@@ -129,7 +128,7 @@ namespace SocketHttpListener
|
||||
|
||||
internal static string CheckIfClosable(this WebSocketState state)
|
||||
{
|
||||
return state == WebSocketState.Closing
|
||||
return state == WebSocketState.CloseSent
|
||||
? "While closing the WebSocket connection."
|
||||
: state == WebSocketState.Closed
|
||||
? "The WebSocket connection has already been closed."
|
||||
@@ -140,7 +139,7 @@ namespace SocketHttpListener
|
||||
{
|
||||
return state == WebSocketState.Connecting
|
||||
? "A WebSocket connection isn't established."
|
||||
: state == WebSocketState.Closing
|
||||
: state == WebSocketState.CloseSent
|
||||
? "While closing the WebSocket connection."
|
||||
: state == WebSocketState.Closed
|
||||
? "The WebSocket connection has already been closed."
|
||||
@@ -154,20 +153,6 @@ namespace SocketHttpListener
|
||||
: null;
|
||||
}
|
||||
|
||||
internal static string CheckIfValidSendData(this byte[] data)
|
||||
{
|
||||
return data == null
|
||||
? "'data' must not be null."
|
||||
: null;
|
||||
}
|
||||
|
||||
internal static string CheckIfValidSendData(this string data)
|
||||
{
|
||||
return data == null
|
||||
? "'data' must not be null."
|
||||
: null;
|
||||
}
|
||||
|
||||
internal static Stream Compress(this Stream stream, CompressionMethod method)
|
||||
{
|
||||
return method == CompressionMethod.Deflate
|
||||
@@ -631,24 +616,6 @@ namespace SocketHttpListener
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emits the specified <see cref="EventHandler"/> delegate if it isn't <see langword="null"/>.
|
||||
/// </summary>
|
||||
/// <param name="eventHandler">
|
||||
/// A <see cref="EventHandler"/> to emit.
|
||||
/// </param>
|
||||
/// <param name="sender">
|
||||
/// An <see cref="object"/> from which emits this <paramref name="eventHandler"/>.
|
||||
/// </param>
|
||||
/// <param name="e">
|
||||
/// A <see cref="EventArgs"/> that contains no event data.
|
||||
/// </param>
|
||||
public static void Emit(this EventHandler eventHandler, object sender, EventArgs e)
|
||||
{
|
||||
if (eventHandler != null)
|
||||
eventHandler(sender, e);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emits the specified <c>EventHandler<TEventArgs></c> delegate
|
||||
/// if it isn't <see langword="null"/>.
|
||||
@@ -673,27 +640,6 @@ namespace SocketHttpListener
|
||||
eventHandler(sender, e);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collection of the HTTP cookies from the specified HTTP <paramref name="headers"/>.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A <see cref="CookieCollection"/> that receives a collection of the HTTP cookies.
|
||||
/// </returns>
|
||||
/// <param name="headers">
|
||||
/// A <see cref="QueryParamCollection"/> that contains a collection of the HTTP headers.
|
||||
/// </param>
|
||||
/// <param name="response">
|
||||
/// <c>true</c> if <paramref name="headers"/> is a collection of the response headers;
|
||||
/// otherwise, <c>false</c>.
|
||||
/// </param>
|
||||
public static CookieCollection GetCookies(this QueryParamCollection headers, bool response)
|
||||
{
|
||||
var name = response ? "Set-Cookie" : "Cookie";
|
||||
return headers == null || !headers.Contains(name)
|
||||
? new CookieCollection()
|
||||
: CookieHelper.Parse(headers[name], response);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the description of the specified HTTP status <paramref name="code"/>.
|
||||
/// </summary>
|
||||
@@ -708,52 +654,6 @@ namespace SocketHttpListener
|
||||
return ((int)code).GetStatusDescription();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name from the specified <see cref="string"/> that contains a pair of name and
|
||||
/// value separated by a separator string.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A <see cref="string"/> that represents the name if any; otherwise, <c>null</c>.
|
||||
/// </returns>
|
||||
/// <param name="nameAndValue">
|
||||
/// A <see cref="string"/> that contains a pair of name and value separated by a separator
|
||||
/// string.
|
||||
/// </param>
|
||||
/// <param name="separator">
|
||||
/// A <see cref="string"/> that represents a separator string.
|
||||
/// </param>
|
||||
public static string GetName(this string nameAndValue, string separator)
|
||||
{
|
||||
return (nameAndValue != null && nameAndValue.Length > 0) &&
|
||||
(separator != null && separator.Length > 0)
|
||||
? nameAndValue.GetNameInternal(separator)
|
||||
: null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name and value from the specified <see cref="string"/> that contains a pair of
|
||||
/// name and value separated by a separator string.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A <c>KeyValuePair<string, string></c> that represents the name and value if any.
|
||||
/// </returns>
|
||||
/// <param name="nameAndValue">
|
||||
/// A <see cref="string"/> that contains a pair of name and value separated by a separator
|
||||
/// string.
|
||||
/// </param>
|
||||
/// <param name="separator">
|
||||
/// A <see cref="string"/> that represents a separator string.
|
||||
/// </param>
|
||||
public static KeyValuePair<string, string> GetNameAndValue(
|
||||
this string nameAndValue, string separator)
|
||||
{
|
||||
var name = nameAndValue.GetName(separator);
|
||||
var value = nameAndValue.GetValue(separator);
|
||||
return name != null
|
||||
? new KeyValuePair<string, string>(name, value)
|
||||
: new KeyValuePair<string, string>(null, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the description of the specified HTTP status <paramref name="code"/>.
|
||||
/// </summary>
|
||||
@@ -818,28 +718,6 @@ namespace SocketHttpListener
|
||||
return String.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value from the specified <see cref="string"/> that contains a pair of name and
|
||||
/// value separated by a separator string.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A <see cref="string"/> that represents the value if any; otherwise, <c>null</c>.
|
||||
/// </returns>
|
||||
/// <param name="nameAndValue">
|
||||
/// A <see cref="string"/> that contains a pair of name and value separated by a separator
|
||||
/// string.
|
||||
/// </param>
|
||||
/// <param name="separator">
|
||||
/// A <see cref="string"/> that represents a separator string.
|
||||
/// </param>
|
||||
public static string GetValue(this string nameAndValue, string separator)
|
||||
{
|
||||
return (nameAndValue != null && nameAndValue.Length > 0) &&
|
||||
(separator != null && separator.Length > 0)
|
||||
? nameAndValue.GetValueInternal(separator)
|
||||
: null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified <see cref="ByteOrder"/> is host
|
||||
/// (this computer architecture) byte order.
|
||||
|
||||
@@ -7,6 +7,7 @@ using HttpStatusCode = SocketHttpListener.Net.HttpStatusCode;
|
||||
using HttpVersion = SocketHttpListener.Net.HttpVersion;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Model.Services;
|
||||
using SocketHttpListener.Net;
|
||||
|
||||
namespace SocketHttpListener
|
||||
{
|
||||
@@ -51,10 +52,18 @@ namespace SocketHttpListener
|
||||
{
|
||||
get
|
||||
{
|
||||
return Headers.GetCookies(true);
|
||||
return GetCookies(Headers, true);
|
||||
}
|
||||
}
|
||||
|
||||
private CookieCollection GetCookies(QueryParamCollection headers, bool response)
|
||||
{
|
||||
var name = response ? "Set-Cookie" : "Cookie";
|
||||
return headers == null || !headers.Contains(name)
|
||||
? new CookieCollection()
|
||||
: CookieHelper.Parse(headers[name], response);
|
||||
}
|
||||
|
||||
public bool IsProxyAuthenticationRequired
|
||||
{
|
||||
get
|
||||
@@ -111,17 +120,6 @@ namespace SocketHttpListener
|
||||
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
|
||||
|
||||
13
SocketHttpListener/Net/AuthenticationTypes.cs
Normal file
13
SocketHttpListener/Net/AuthenticationTypes.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace SocketHttpListener.Net
|
||||
{
|
||||
internal class AuthenticationTypes
|
||||
{
|
||||
internal const string NTLM = "NTLM";
|
||||
internal const string Negotiate = "Negotiate";
|
||||
internal const string Basic = "Basic";
|
||||
}
|
||||
}
|
||||
@@ -1,433 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
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;
|
||||
using ProtocolType = MediaBrowser.Model.Net.ProtocolType;
|
||||
using SocketType = MediaBrowser.Model.Net.SocketType;
|
||||
|
||||
namespace SocketHttpListener.Net
|
||||
{
|
||||
sealed class EndPointListener
|
||||
{
|
||||
HttpListener listener;
|
||||
IPEndPoint endpoint;
|
||||
Socket sock;
|
||||
Dictionary<ListenerPrefix, HttpListener> prefixes; // Dictionary <ListenerPrefix, HttpListener>
|
||||
List<ListenerPrefix> unhandled; // List<ListenerPrefix> unhandled; host = '*'
|
||||
List<ListenerPrefix> all; // List<ListenerPrefix> all; host = '+'
|
||||
X509Certificate cert;
|
||||
bool secure;
|
||||
Dictionary<HttpConnection, HttpConnection> unregistered;
|
||||
private readonly ILogger _logger;
|
||||
private bool _closed;
|
||||
private bool _enableDualMode;
|
||||
private readonly ICryptoProvider _cryptoProvider;
|
||||
private readonly ISocketFactory _socketFactory;
|
||||
private readonly ITextEncoding _textEncoding;
|
||||
private readonly IMemoryStreamFactory _memoryStreamFactory;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IEnvironmentInfo _environment;
|
||||
|
||||
public EndPointListener(HttpListener listener, IPAddress addr, int port, bool secure, X509Certificate cert, ILogger logger, ICryptoProvider cryptoProvider, ISocketFactory socketFactory, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding, IFileSystem fileSystem, IEnvironmentInfo environment)
|
||||
{
|
||||
this.listener = listener;
|
||||
_logger = logger;
|
||||
_cryptoProvider = cryptoProvider;
|
||||
_socketFactory = socketFactory;
|
||||
_memoryStreamFactory = memoryStreamFactory;
|
||||
_textEncoding = textEncoding;
|
||||
_fileSystem = fileSystem;
|
||||
_environment = environment;
|
||||
|
||||
this.secure = secure;
|
||||
this.cert = cert;
|
||||
|
||||
_enableDualMode = addr.Equals(IPAddress.IPv6Any);
|
||||
endpoint = new IPEndPoint(addr, port);
|
||||
|
||||
prefixes = new Dictionary<ListenerPrefix, HttpListener>();
|
||||
unregistered = new Dictionary<HttpConnection, HttpConnection>();
|
||||
|
||||
CreateSocket();
|
||||
}
|
||||
|
||||
internal HttpListener Listener
|
||||
{
|
||||
get
|
||||
{
|
||||
return listener;
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateSocket()
|
||||
{
|
||||
try
|
||||
{
|
||||
sock = CreateSocket(endpoint.Address.AddressFamily, _enableDualMode);
|
||||
}
|
||||
catch (SocketCreateException ex)
|
||||
{
|
||||
if (_enableDualMode && endpoint.Address.Equals(IPAddress.IPv6Any) &&
|
||||
(string.Equals(ex.ErrorCode, "AddressFamilyNotSupported", StringComparison.OrdinalIgnoreCase) ||
|
||||
// mono on bsd is throwing this
|
||||
string.Equals(ex.ErrorCode, "ProtocolNotSupported", StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
endpoint = new IPEndPoint(IPAddress.Any, endpoint.Port);
|
||||
_enableDualMode = false;
|
||||
sock = CreateSocket(endpoint.Address.AddressFamily, _enableDualMode);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
sock.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
|
||||
}
|
||||
catch (SocketException)
|
||||
{
|
||||
// This is not supported on all operating systems (qnap)
|
||||
}
|
||||
|
||||
sock.Bind(endpoint);
|
||||
|
||||
// This is the number TcpListener uses.
|
||||
sock.Listen(2147483647);
|
||||
|
||||
new SocketAcceptor(_logger, sock, ProcessAccept, () => _closed).StartAccept();
|
||||
_closed = false;
|
||||
}
|
||||
|
||||
private Socket CreateSocket(AddressFamily addressFamily, bool dualMode)
|
||||
{
|
||||
try
|
||||
{
|
||||
var socket = new Socket(addressFamily, System.Net.Sockets.SocketType.Stream, System.Net.Sockets.ProtocolType.Tcp);
|
||||
|
||||
if (dualMode)
|
||||
{
|
||||
socket.DualMode = true;
|
||||
}
|
||||
|
||||
return socket;
|
||||
}
|
||||
catch (SocketException ex)
|
||||
{
|
||||
throw new SocketCreateException(ex.SocketErrorCode.ToString(), ex);
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
if (dualMode)
|
||||
{
|
||||
// Mono for BSD incorrectly throws ArgumentException instead of SocketException
|
||||
throw new SocketCreateException("AddressFamilyNotSupported", ex);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async void ProcessAccept(Socket 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, _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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,167 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
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 IPAddress GetIpAnyAddress(HttpListener listener)
|
||||
{
|
||||
return listener.EnableDualMode ? IPAddress.IPv6Any : IPAddress.Any;
|
||||
}
|
||||
|
||||
static async Task<EndPointListener> GetEPListener(ILogger logger, string host, int port, HttpListener listener, bool secure)
|
||||
{
|
||||
var networkManager = listener.NetworkManager;
|
||||
|
||||
IPAddress addr;
|
||||
if (host == "*" || host == "+")
|
||||
addr = GetIpAnyAddress(listener);
|
||||
else if (IPAddress.TryParse(host, out addr) == false)
|
||||
{
|
||||
try
|
||||
{
|
||||
var all = (await networkManager.GetHostAddressesAsync(host).ConfigureAwait(false));
|
||||
|
||||
addr = (all.Length == 0 ? null : IPAddress.Parse(all[0].Address)) ??
|
||||
GetIpAnyAddress(listener);
|
||||
}
|
||||
catch
|
||||
{
|
||||
addr = GetIpAnyAddress(listener);
|
||||
}
|
||||
}
|
||||
|
||||
Dictionary<int, EndPointListener> p = null; // Dictionary<int, EndPointListener>
|
||||
if (!ip_to_endpoints.TryGetValue(addr.ToString(), out p))
|
||||
{
|
||||
p = new Dictionary<int, EndPointListener>();
|
||||
ip_to_endpoints[addr.ToString()] = 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.SocketFactory, listener.MemoryStreamFactory, listener.TextEncoding, listener.FileSystem, listener.EnvironmentInfo);
|
||||
p[port] = epl;
|
||||
}
|
||||
|
||||
return epl;
|
||||
}
|
||||
|
||||
public static void RemoveEndPoint(EndPointListener epl, IPEndPoint ep)
|
||||
{
|
||||
lock (ip_to_endpoints)
|
||||
{
|
||||
// Dictionary<int, EndPointListener> p
|
||||
Dictionary<int, EndPointListener> p;
|
||||
if (ip_to_endpoints.TryGetValue(ep.Address.ToString(), out p))
|
||||
{
|
||||
p.Remove(ep.Port);
|
||||
if (p.Count == 0)
|
||||
{
|
||||
ip_to_endpoints.Remove(ep.Address.ToString());
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,9 @@ using MediaBrowser.Model.Net;
|
||||
using MediaBrowser.Model.System;
|
||||
using MediaBrowser.Model.Text;
|
||||
using SocketHttpListener.Primitives;
|
||||
using System.Security.Authentication;
|
||||
|
||||
using System.Threading;
|
||||
namespace SocketHttpListener.Net
|
||||
{
|
||||
sealed class HttpConnection
|
||||
@@ -22,7 +24,7 @@ namespace SocketHttpListener.Net
|
||||
const int BufferSize = 8192;
|
||||
Socket _socket;
|
||||
Stream _stream;
|
||||
EndPointListener _epl;
|
||||
HttpEndPointListener _epl;
|
||||
MemoryStream _memoryStream;
|
||||
byte[] _buffer;
|
||||
HttpListenerContext _context;
|
||||
@@ -34,21 +36,21 @@ namespace SocketHttpListener.Net
|
||||
int _reuses;
|
||||
bool _contextBound;
|
||||
bool secure;
|
||||
int _timeout = 300000; // 90k ms for first request, 15k ms from then on
|
||||
int _timeout = 90000; // 90k ms for first request, 15k ms from then on
|
||||
private Timer _timer;
|
||||
IPEndPoint local_ep;
|
||||
HttpListener _lastListener;
|
||||
int[] client_cert_errors;
|
||||
X509Certificate cert;
|
||||
SslStream ssl_stream;
|
||||
|
||||
private readonly ILogger _logger;
|
||||
private readonly ICryptoProvider _cryptoProvider;
|
||||
private readonly IMemoryStreamFactory _memoryStreamFactory;
|
||||
private readonly IStreamHelper _streamHelper;
|
||||
private readonly ITextEncoding _textEncoding;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IEnvironmentInfo _environment;
|
||||
|
||||
private HttpConnection(ILogger logger, Socket socket, EndPointListener epl, bool secure, X509Certificate cert, ICryptoProvider cryptoProvider, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding, IFileSystem fileSystem, IEnvironmentInfo environment)
|
||||
public HttpConnection(ILogger logger, Socket socket, HttpEndPointListener epl, bool secure, X509Certificate cert, ICryptoProvider cryptoProvider, IStreamHelper streamHelper, ITextEncoding textEncoding, IFileSystem fileSystem, IEnvironmentInfo environment)
|
||||
{
|
||||
_logger = logger;
|
||||
this._socket = socket;
|
||||
@@ -56,47 +58,37 @@ namespace SocketHttpListener.Net
|
||||
this.secure = secure;
|
||||
this.cert = cert;
|
||||
_cryptoProvider = cryptoProvider;
|
||||
_memoryStreamFactory = memoryStreamFactory;
|
||||
_streamHelper = streamHelper;
|
||||
_textEncoding = textEncoding;
|
||||
_fileSystem = fileSystem;
|
||||
_environment = environment;
|
||||
}
|
||||
|
||||
private async Task InitStream()
|
||||
{
|
||||
if (secure == false)
|
||||
{
|
||||
_stream = new SocketStream(_socket, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
//ssl_stream = _epl.Listener.CreateSslStream(new NetworkStream(_socket, 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 = new SslStream(new SocketStream(_socket, false), false, (t, c, ch, e) =>
|
||||
{
|
||||
if (c == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
//var c2 = c as X509Certificate2;
|
||||
//if (c2 == null)
|
||||
//{
|
||||
// c2 = new X509Certificate2(c.GetRawCertData());
|
||||
//}
|
||||
|
||||
//_clientCert = c2;
|
||||
//_clientCertErrors = new int[] { (int)e };
|
||||
return true;
|
||||
});
|
||||
|
||||
ssl_stream = new SslStream(new SocketStream(_socket, false), false);
|
||||
await ssl_stream.AuthenticateAsServerAsync(cert).ConfigureAwait(false);
|
||||
_stream = ssl_stream;
|
||||
}
|
||||
Init();
|
||||
}
|
||||
|
||||
public static async Task<HttpConnection> Create(ILogger logger, Socket sock, EndPointListener epl, bool secure, X509Certificate cert, ICryptoProvider cryptoProvider, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding, IFileSystem fileSystem, IEnvironmentInfo environment)
|
||||
{
|
||||
var connection = new HttpConnection(logger, sock, epl, secure, cert, cryptoProvider, memoryStreamFactory, textEncoding, fileSystem, environment);
|
||||
|
||||
await connection.InitStream().ConfigureAwait(false);
|
||||
|
||||
return connection;
|
||||
}
|
||||
|
||||
public Stream Stream
|
||||
@@ -107,12 +99,27 @@ namespace SocketHttpListener.Net
|
||||
}
|
||||
}
|
||||
|
||||
internal int[] ClientCertificateErrors
|
||||
public async Task Init()
|
||||
{
|
||||
get { return client_cert_errors; }
|
||||
_timer = new Timer(OnTimeout, null, Timeout.Infinite, Timeout.Infinite);
|
||||
|
||||
if (ssl_stream != null)
|
||||
{
|
||||
var enableAsync = true;
|
||||
if (enableAsync)
|
||||
{
|
||||
await ssl_stream.AuthenticateAsServerAsync(cert, false, (SslProtocols)ServicePointManager.SecurityProtocol, false).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
ssl_stream.AuthenticateAsServer(cert, false, (SslProtocols)ServicePointManager.SecurityProtocol, false);
|
||||
}
|
||||
}
|
||||
|
||||
InitInternal();
|
||||
}
|
||||
|
||||
void Init()
|
||||
private void InitInternal()
|
||||
{
|
||||
_contextBound = false;
|
||||
_requestStream = null;
|
||||
@@ -123,7 +130,7 @@ namespace SocketHttpListener.Net
|
||||
_position = 0;
|
||||
_inputState = InputState.RequestLine;
|
||||
_lineState = LineState.None;
|
||||
_context = new HttpListenerContext(this, _logger, _cryptoProvider, _memoryStreamFactory, _textEncoding, _fileSystem);
|
||||
_context = new HttpListenerContext(this, _textEncoding);
|
||||
}
|
||||
|
||||
public bool IsClosed
|
||||
@@ -164,6 +171,13 @@ namespace SocketHttpListener.Net
|
||||
set { _prefix = value; }
|
||||
}
|
||||
|
||||
private void OnTimeout(object unused)
|
||||
{
|
||||
//_logger.Info("HttpConnection timer fired");
|
||||
CloseSocket();
|
||||
Unbind();
|
||||
}
|
||||
|
||||
public void BeginReadRequest()
|
||||
{
|
||||
if (_buffer == null)
|
||||
@@ -211,7 +225,7 @@ namespace SocketHttpListener.Net
|
||||
{
|
||||
var supportsDirectSocketAccess = !_context.Response.SendChunked && !isExpect100Continue && !secure;
|
||||
|
||||
_responseStream = new HttpResponseStream(_stream, _context.Response, false, _memoryStreamFactory, _socket, supportsDirectSocketAccess, _environment, _fileSystem, _logger);
|
||||
_responseStream = new HttpResponseStream(_stream, _context.Response, false, _streamHelper, _socket, supportsDirectSocketAccess, _environment, _fileSystem, _logger);
|
||||
}
|
||||
return _responseStream;
|
||||
}
|
||||
@@ -503,14 +517,14 @@ namespace SocketHttpListener.Net
|
||||
// Don't close. Keep working.
|
||||
_reuses++;
|
||||
Unbind();
|
||||
Init();
|
||||
InitInternal();
|
||||
BeginReadRequest();
|
||||
return;
|
||||
}
|
||||
|
||||
_reuses++;
|
||||
Unbind();
|
||||
Init();
|
||||
InitInternal();
|
||||
BeginReadRequest();
|
||||
return;
|
||||
}
|
||||
|
||||
539
SocketHttpListener/Net/HttpEndPointListener.cs
Normal file
539
SocketHttpListener/Net/HttpEndPointListener.cs
Normal file
@@ -0,0 +1,539 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
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;
|
||||
using ProtocolType = MediaBrowser.Model.Net.ProtocolType;
|
||||
using SocketType = MediaBrowser.Model.Net.SocketType;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SocketHttpListener.Net
|
||||
{
|
||||
internal sealed class HttpEndPointListener
|
||||
{
|
||||
private HttpListener _listener;
|
||||
private IPEndPoint _endpoint;
|
||||
private Socket _socket;
|
||||
private Dictionary<ListenerPrefix, HttpListener> _prefixes;
|
||||
private List<ListenerPrefix> _unhandledPrefixes; // host = '*'
|
||||
private List<ListenerPrefix> _allPrefixes; // host = '+'
|
||||
private X509Certificate _cert;
|
||||
private bool _secure;
|
||||
private Dictionary<HttpConnection, HttpConnection> _unregisteredConnections;
|
||||
|
||||
private readonly ILogger _logger;
|
||||
private bool _closed;
|
||||
private bool _enableDualMode;
|
||||
private readonly ICryptoProvider _cryptoProvider;
|
||||
private readonly ISocketFactory _socketFactory;
|
||||
private readonly ITextEncoding _textEncoding;
|
||||
private readonly IStreamHelper _streamHelper;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IEnvironmentInfo _environment;
|
||||
|
||||
public HttpEndPointListener(HttpListener listener, IPAddress addr, int port, bool secure, X509Certificate cert, ILogger logger, ICryptoProvider cryptoProvider, ISocketFactory socketFactory, IStreamHelper streamHelper, ITextEncoding textEncoding, IFileSystem fileSystem, IEnvironmentInfo environment)
|
||||
{
|
||||
this._listener = listener;
|
||||
_logger = logger;
|
||||
_cryptoProvider = cryptoProvider;
|
||||
_socketFactory = socketFactory;
|
||||
_streamHelper = streamHelper;
|
||||
_textEncoding = textEncoding;
|
||||
_fileSystem = fileSystem;
|
||||
_environment = environment;
|
||||
|
||||
this._secure = secure;
|
||||
this._cert = cert;
|
||||
|
||||
_enableDualMode = addr.Equals(IPAddress.IPv6Any);
|
||||
_endpoint = new IPEndPoint(addr, port);
|
||||
|
||||
_prefixes = new Dictionary<ListenerPrefix, HttpListener>();
|
||||
_unregisteredConnections = new Dictionary<HttpConnection, HttpConnection>();
|
||||
|
||||
CreateSocket();
|
||||
}
|
||||
|
||||
internal HttpListener Listener
|
||||
{
|
||||
get
|
||||
{
|
||||
return _listener;
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateSocket()
|
||||
{
|
||||
try
|
||||
{
|
||||
_socket = CreateSocket(_endpoint.Address.AddressFamily, _enableDualMode);
|
||||
}
|
||||
catch (SocketCreateException ex)
|
||||
{
|
||||
if (_enableDualMode && _endpoint.Address.Equals(IPAddress.IPv6Any) &&
|
||||
(string.Equals(ex.ErrorCode, "AddressFamilyNotSupported", StringComparison.OrdinalIgnoreCase) ||
|
||||
// mono 4.8.1 and lower on bsd is throwing this
|
||||
string.Equals(ex.ErrorCode, "ProtocolNotSupported", StringComparison.OrdinalIgnoreCase) ||
|
||||
// mono 5.2 on bsd is throwing this
|
||||
string.Equals(ex.ErrorCode, "OperationNotSupported", StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
_endpoint = new IPEndPoint(IPAddress.Any, _endpoint.Port);
|
||||
_enableDualMode = false;
|
||||
_socket = CreateSocket(_endpoint.Address.AddressFamily, _enableDualMode);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
|
||||
}
|
||||
catch (SocketException)
|
||||
{
|
||||
// This is not supported on all operating systems (qnap)
|
||||
}
|
||||
|
||||
_socket.Bind(_endpoint);
|
||||
|
||||
// This is the number TcpListener uses.
|
||||
_socket.Listen(2147483647);
|
||||
|
||||
Accept();
|
||||
|
||||
_closed = false;
|
||||
}
|
||||
|
||||
private void Accept()
|
||||
{
|
||||
var acceptEventArg = new SocketAsyncEventArgs();
|
||||
acceptEventArg.UserToken = this;
|
||||
acceptEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(OnAccept);
|
||||
|
||||
Accept(acceptEventArg);
|
||||
}
|
||||
|
||||
private static void TryCloseAndDispose(Socket socket)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (socket)
|
||||
{
|
||||
socket.Close();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private static void TryClose(Socket socket)
|
||||
{
|
||||
try
|
||||
{
|
||||
socket.Close();
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void Accept(SocketAsyncEventArgs acceptEventArg)
|
||||
{
|
||||
// acceptSocket must be cleared since the context object is being reused
|
||||
acceptEventArg.AcceptSocket = null;
|
||||
|
||||
try
|
||||
{
|
||||
bool willRaiseEvent = _socket.AcceptAsync(acceptEventArg);
|
||||
|
||||
if (!willRaiseEvent)
|
||||
{
|
||||
ProcessAccept(acceptEventArg);
|
||||
}
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
HttpEndPointListener epl = (HttpEndPointListener)acceptEventArg.UserToken;
|
||||
|
||||
epl._logger.ErrorException("Error in socket.AcceptAsync", ex);
|
||||
}
|
||||
}
|
||||
|
||||
// This method is the callback method associated with Socket.AcceptAsync
|
||||
// operations and is invoked when an accept operation is complete
|
||||
//
|
||||
private static void OnAccept(object sender, SocketAsyncEventArgs e)
|
||||
{
|
||||
ProcessAccept(e);
|
||||
}
|
||||
|
||||
private static async void ProcessAccept(SocketAsyncEventArgs args)
|
||||
{
|
||||
HttpEndPointListener epl = (HttpEndPointListener)args.UserToken;
|
||||
|
||||
if (epl._closed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// http://msdn.microsoft.com/en-us/library/system.net.sockets.acceptSocket.acceptasync%28v=vs.110%29.aspx
|
||||
// Under certain conditions ConnectionReset can occur
|
||||
// Need to attept to re-accept
|
||||
var socketError = args.SocketError;
|
||||
var accepted = args.AcceptSocket;
|
||||
|
||||
epl.Accept(args);
|
||||
|
||||
if (socketError == SocketError.ConnectionReset)
|
||||
{
|
||||
epl._logger.Error("SocketError.ConnectionReset reported. Attempting to re-accept.");
|
||||
return;
|
||||
}
|
||||
|
||||
if(accepted == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (epl._secure && epl._cert == null)
|
||||
{
|
||||
TryClose(accepted);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var remoteEndPointString = accepted.RemoteEndPoint == null ? string.Empty : accepted.RemoteEndPoint.ToString();
|
||||
var localEndPointString = accepted.LocalEndPoint == null ? string.Empty : accepted.LocalEndPoint.ToString();
|
||||
//_logger.Info("HttpEndPointListener Accepting connection from {0} to {1} secure connection requested: {2}", remoteEndPointString, localEndPointString, _secure);
|
||||
|
||||
HttpConnection conn = new HttpConnection(epl._logger, accepted, epl, epl._secure, epl._cert, epl._cryptoProvider, epl._streamHelper, epl._textEncoding, epl._fileSystem, epl._environment);
|
||||
|
||||
await conn.Init().ConfigureAwait(false);
|
||||
|
||||
//_logger.Debug("Adding unregistered connection to {0}. Id: {1}", accepted.RemoteEndPoint, connectionId);
|
||||
lock (epl._unregisteredConnections)
|
||||
{
|
||||
epl._unregisteredConnections[conn] = conn;
|
||||
}
|
||||
conn.BeginReadRequest();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
epl._logger.ErrorException("Error in ProcessAccept", ex);
|
||||
|
||||
TryClose(accepted);
|
||||
epl.Accept();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private Socket CreateSocket(AddressFamily addressFamily, bool dualMode)
|
||||
{
|
||||
try
|
||||
{
|
||||
var socket = new Socket(addressFamily, System.Net.Sockets.SocketType.Stream, System.Net.Sockets.ProtocolType.Tcp);
|
||||
|
||||
if (dualMode)
|
||||
{
|
||||
socket.DualMode = true;
|
||||
}
|
||||
|
||||
return socket;
|
||||
}
|
||||
catch (SocketException ex)
|
||||
{
|
||||
throw new SocketCreateException(ex.SocketErrorCode.ToString(), ex);
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
if (dualMode)
|
||||
{
|
||||
// Mono for BSD incorrectly throws ArgumentException instead of SocketException
|
||||
throw new SocketCreateException("AddressFamilyNotSupported", ex);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void RemoveConnection(HttpConnection conn)
|
||||
{
|
||||
lock (_unregisteredConnections)
|
||||
{
|
||||
_unregisteredConnections.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);
|
||||
}
|
||||
|
||||
private 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 pathSlash = path[path.Length - 1] == '/' ? path : path + "/";
|
||||
|
||||
HttpListener bestMatch = null;
|
||||
int bestLength = -1;
|
||||
|
||||
if (host != null && host != "")
|
||||
{
|
||||
Dictionary<ListenerPrefix, HttpListener> localPrefixes = _prefixes;
|
||||
foreach (ListenerPrefix p in localPrefixes.Keys)
|
||||
{
|
||||
string ppath = p.Path;
|
||||
if (ppath.Length < bestLength)
|
||||
continue;
|
||||
|
||||
if (p.Host != host || p.Port != port)
|
||||
continue;
|
||||
|
||||
if (path.StartsWith(ppath) || pathSlash.StartsWith(ppath))
|
||||
{
|
||||
bestLength = ppath.Length;
|
||||
bestMatch = localPrefixes[p];
|
||||
prefix = p;
|
||||
}
|
||||
}
|
||||
if (bestLength != -1)
|
||||
return bestMatch;
|
||||
}
|
||||
|
||||
List<ListenerPrefix> list = _unhandledPrefixes;
|
||||
bestMatch = MatchFromList(host, path, list, out prefix);
|
||||
|
||||
if (path != pathSlash && bestMatch == null)
|
||||
bestMatch = MatchFromList(host, pathSlash, list, out prefix);
|
||||
|
||||
if (bestMatch != null)
|
||||
return bestMatch;
|
||||
|
||||
list = _allPrefixes;
|
||||
bestMatch = MatchFromList(host, path, list, out prefix);
|
||||
|
||||
if (path != pathSlash && bestMatch == null)
|
||||
bestMatch = MatchFromList(host, pathSlash, list, out prefix);
|
||||
|
||||
if (bestMatch != null)
|
||||
return bestMatch;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private HttpListener MatchFromList(string host, string path, List<ListenerPrefix> list, out ListenerPrefix prefix)
|
||||
{
|
||||
prefix = null;
|
||||
if (list == null)
|
||||
return null;
|
||||
|
||||
HttpListener bestMatch = null;
|
||||
int bestLength = -1;
|
||||
|
||||
foreach (ListenerPrefix p in list)
|
||||
{
|
||||
string ppath = p.Path;
|
||||
if (ppath.Length < bestLength)
|
||||
continue;
|
||||
|
||||
if (path.StartsWith(ppath))
|
||||
{
|
||||
bestLength = ppath.Length;
|
||||
bestMatch = p._listener;
|
||||
prefix = p;
|
||||
}
|
||||
}
|
||||
|
||||
return bestMatch;
|
||||
}
|
||||
|
||||
private void AddSpecial(List<ListenerPrefix> list, ListenerPrefix prefix)
|
||||
{
|
||||
if (list == null)
|
||||
return;
|
||||
|
||||
foreach (ListenerPrefix p in list)
|
||||
{
|
||||
if (p.Path == prefix.Path)
|
||||
throw new Exception("net_listener_already");
|
||||
}
|
||||
list.Add(prefix);
|
||||
}
|
||||
|
||||
private bool RemoveSpecial(List<ListenerPrefix> list, ListenerPrefix prefix)
|
||||
{
|
||||
if (list == null)
|
||||
return false;
|
||||
|
||||
int c = list.Count;
|
||||
for (int i = 0; i < c; i++)
|
||||
{
|
||||
ListenerPrefix p = list[i];
|
||||
if (p.Path == prefix.Path)
|
||||
{
|
||||
list.RemoveAt(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void CheckIfRemove()
|
||||
{
|
||||
if (_prefixes.Count > 0)
|
||||
return;
|
||||
|
||||
List<ListenerPrefix> list = _unhandledPrefixes;
|
||||
if (list != null && list.Count > 0)
|
||||
return;
|
||||
|
||||
list = _allPrefixes;
|
||||
if (list != null && list.Count > 0)
|
||||
return;
|
||||
|
||||
HttpEndPointManager.RemoveEndPoint(this, _endpoint);
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
_closed = true;
|
||||
_socket.Close();
|
||||
lock (_unregisteredConnections)
|
||||
{
|
||||
// Clone the list because RemoveConnection can be called from Close
|
||||
var connections = new List<HttpConnection>(_unregisteredConnections.Keys);
|
||||
|
||||
foreach (HttpConnection c in connections)
|
||||
c.Close(true);
|
||||
_unregisteredConnections.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public void AddPrefix(ListenerPrefix prefix, HttpListener listener)
|
||||
{
|
||||
List<ListenerPrefix> current;
|
||||
List<ListenerPrefix> future;
|
||||
if (prefix.Host == "*")
|
||||
{
|
||||
do
|
||||
{
|
||||
current = _unhandledPrefixes;
|
||||
future = current != null ? new List<ListenerPrefix>(current) : new List<ListenerPrefix>();
|
||||
prefix._listener = listener;
|
||||
AddSpecial(future, prefix);
|
||||
} while (Interlocked.CompareExchange(ref _unhandledPrefixes, future, current) != current);
|
||||
return;
|
||||
}
|
||||
|
||||
if (prefix.Host == "+")
|
||||
{
|
||||
do
|
||||
{
|
||||
current = _allPrefixes;
|
||||
future = current != null ? new List<ListenerPrefix>(current) : new List<ListenerPrefix>();
|
||||
prefix._listener = listener;
|
||||
AddSpecial(future, prefix);
|
||||
} while (Interlocked.CompareExchange(ref _allPrefixes, future, current) != current);
|
||||
return;
|
||||
}
|
||||
|
||||
Dictionary<ListenerPrefix, HttpListener> prefs, p2;
|
||||
do
|
||||
{
|
||||
prefs = _prefixes;
|
||||
if (prefs.ContainsKey(prefix))
|
||||
{
|
||||
throw new Exception("net_listener_already");
|
||||
}
|
||||
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 = _unhandledPrefixes;
|
||||
future = current != null ? new List<ListenerPrefix>(current) : new List<ListenerPrefix>();
|
||||
if (!RemoveSpecial(future, prefix))
|
||||
break; // Prefix not found
|
||||
} while (Interlocked.CompareExchange(ref _unhandledPrefixes, future, current) != current);
|
||||
|
||||
CheckIfRemove();
|
||||
return;
|
||||
}
|
||||
|
||||
if (prefix.Host == "+")
|
||||
{
|
||||
do
|
||||
{
|
||||
current = _allPrefixes;
|
||||
future = current != null ? new List<ListenerPrefix>(current) : new List<ListenerPrefix>();
|
||||
if (!RemoveSpecial(future, prefix))
|
||||
break; // Prefix not found
|
||||
} while (Interlocked.CompareExchange(ref _allPrefixes, future, current) != current);
|
||||
CheckIfRemove();
|
||||
return;
|
||||
}
|
||||
|
||||
Dictionary<ListenerPrefix, HttpListener> prefs, 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
198
SocketHttpListener/Net/HttpEndPointManager.cs
Normal file
198
SocketHttpListener/Net/HttpEndPointManager.cs
Normal file
@@ -0,0 +1,198 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
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
|
||||
{
|
||||
internal sealed class HttpEndPointManager
|
||||
{
|
||||
private static Dictionary<IPAddress, Dictionary<int, HttpEndPointListener>> s_ipEndPoints = new Dictionary<IPAddress, Dictionary<int, HttpEndPointListener>>();
|
||||
|
||||
private HttpEndPointManager()
|
||||
{
|
||||
}
|
||||
|
||||
public static void AddListener(ILogger logger, HttpListener listener)
|
||||
{
|
||||
List<string> added = new List<string>();
|
||||
try
|
||||
{
|
||||
lock ((s_ipEndPoints as ICollection).SyncRoot)
|
||||
{
|
||||
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 ((s_ipEndPoints as ICollection).SyncRoot)
|
||||
{
|
||||
AddPrefixInternal(logger, prefix, listener);
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddPrefixInternal(ILogger logger, string p, HttpListener listener)
|
||||
{
|
||||
int start = p.IndexOf(':') + 3;
|
||||
int colon = p.IndexOf(':', start);
|
||||
if (colon != -1)
|
||||
{
|
||||
// root can't be -1 here, since we've already checked for ending '/' in ListenerPrefix.
|
||||
int root = p.IndexOf('/', colon, p.Length - colon);
|
||||
string portString = p.Substring(colon + 1, root - colon - 1);
|
||||
|
||||
int port;
|
||||
if (!int.TryParse(portString, out port) || port <= 0 || port >= 65536)
|
||||
{
|
||||
throw new HttpListenerException((int)HttpStatusCode.BadRequest, "net_invalid_port");
|
||||
}
|
||||
}
|
||||
|
||||
ListenerPrefix lp = new ListenerPrefix(p);
|
||||
if (lp.Host != "*" && lp.Host != "+" && Uri.CheckHostName(lp.Host) == UriHostNameType.Unknown)
|
||||
throw new HttpListenerException((int)HttpStatusCode.BadRequest, "net_listener_host");
|
||||
|
||||
if (lp.Path.IndexOf('%') != -1)
|
||||
throw new HttpListenerException((int)HttpStatusCode.BadRequest, "net_invalid_path");
|
||||
|
||||
if (lp.Path.IndexOf("//", StringComparison.Ordinal) != -1)
|
||||
throw new HttpListenerException((int)HttpStatusCode.BadRequest, "net_invalid_path");
|
||||
|
||||
// listens on all the interfaces if host name cannot be parsed by IPAddress.
|
||||
HttpEndPointListener epl = GetEPListener(logger, lp.Host, lp.Port, listener, lp.Secure);
|
||||
epl.AddPrefix(lp, listener);
|
||||
}
|
||||
|
||||
private static IPAddress GetIpAnyAddress(HttpListener listener)
|
||||
{
|
||||
return listener.EnableDualMode ? IPAddress.IPv6Any : IPAddress.Any;
|
||||
}
|
||||
|
||||
private static HttpEndPointListener GetEPListener(ILogger logger, string host, int port, HttpListener listener, bool secure)
|
||||
{
|
||||
IPAddress addr;
|
||||
if (host == "*" || host == "+")
|
||||
{
|
||||
addr = GetIpAnyAddress(listener);
|
||||
}
|
||||
else
|
||||
{
|
||||
const int NotSupportedErrorCode = 50;
|
||||
try
|
||||
{
|
||||
addr = Dns.GetHostAddresses(host)[0];
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Throw same error code as windows, request is not supported.
|
||||
throw new HttpListenerException(NotSupportedErrorCode, "net_listener_not_supported");
|
||||
}
|
||||
|
||||
if (IPAddress.Any.Equals(addr))
|
||||
{
|
||||
// Don't support listening to 0.0.0.0, match windows behavior.
|
||||
throw new HttpListenerException(NotSupportedErrorCode, "net_listener_not_supported");
|
||||
}
|
||||
}
|
||||
|
||||
Dictionary<int, HttpEndPointListener> p = null;
|
||||
if (s_ipEndPoints.ContainsKey(addr))
|
||||
{
|
||||
p = s_ipEndPoints[addr];
|
||||
}
|
||||
else
|
||||
{
|
||||
p = new Dictionary<int, HttpEndPointListener>();
|
||||
s_ipEndPoints[addr] = p;
|
||||
}
|
||||
|
||||
HttpEndPointListener epl = null;
|
||||
if (p.ContainsKey(port))
|
||||
{
|
||||
epl = p[port];
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
epl = new HttpEndPointListener(listener, addr, port, secure, listener.Certificate, logger, listener.CryptoProvider, listener.SocketFactory, listener.StreamHelper, listener.TextEncoding, listener.FileSystem, listener.EnvironmentInfo);
|
||||
}
|
||||
catch (SocketException ex)
|
||||
{
|
||||
throw new HttpListenerException(ex.ErrorCode, ex.Message);
|
||||
}
|
||||
p[port] = epl;
|
||||
}
|
||||
|
||||
return epl;
|
||||
}
|
||||
|
||||
public static void RemoveEndPoint(HttpEndPointListener epl, IPEndPoint ep)
|
||||
{
|
||||
lock ((s_ipEndPoints as ICollection).SyncRoot)
|
||||
{
|
||||
Dictionary<int, HttpEndPointListener> p = null;
|
||||
p = s_ipEndPoints[ep.Address];
|
||||
p.Remove(ep.Port);
|
||||
if (p.Count == 0)
|
||||
{
|
||||
s_ipEndPoints.Remove(ep.Address);
|
||||
}
|
||||
epl.Close();
|
||||
}
|
||||
}
|
||||
|
||||
public static void RemoveListener(ILogger logger, HttpListener listener)
|
||||
{
|
||||
lock ((s_ipEndPoints as ICollection).SyncRoot)
|
||||
{
|
||||
foreach (string prefix in listener.Prefixes)
|
||||
{
|
||||
RemovePrefixInternal(logger, prefix, listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void RemovePrefix(ILogger logger, string prefix, HttpListener listener)
|
||||
{
|
||||
lock ((s_ipEndPoints as ICollection).SyncRoot)
|
||||
{
|
||||
RemovePrefixInternal(logger, prefix, listener);
|
||||
}
|
||||
}
|
||||
|
||||
private 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;
|
||||
|
||||
HttpEndPointListener epl = GetEPListener(logger, lp.Host, lp.Port, listener, lp.Secure);
|
||||
epl.RemovePrefix(lp, listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
95
SocketHttpListener/Net/HttpKnownHeaderNames.cs
Normal file
95
SocketHttpListener/Net/HttpKnownHeaderNames.cs
Normal file
@@ -0,0 +1,95 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace SocketHttpListener.Net
|
||||
{
|
||||
internal static partial class HttpKnownHeaderNames
|
||||
{
|
||||
// When adding a new constant, add it to HttpKnownHeaderNames.TryGetHeaderName.cs as well.
|
||||
|
||||
public const string Accept = "Accept";
|
||||
public const string AcceptCharset = "Accept-Charset";
|
||||
public const string AcceptEncoding = "Accept-Encoding";
|
||||
public const string AcceptLanguage = "Accept-Language";
|
||||
public const string AcceptPatch = "Accept-Patch";
|
||||
public const string AcceptRanges = "Accept-Ranges";
|
||||
public const string AccessControlAllowCredentials = "Access-Control-Allow-Credentials";
|
||||
public const string AccessControlAllowHeaders = "Access-Control-Allow-Headers";
|
||||
public const string AccessControlAllowMethods = "Access-Control-Allow-Methods";
|
||||
public const string AccessControlAllowOrigin = "Access-Control-Allow-Origin";
|
||||
public const string AccessControlExposeHeaders = "Access-Control-Expose-Headers";
|
||||
public const string AccessControlMaxAge = "Access-Control-Max-Age";
|
||||
public const string Age = "Age";
|
||||
public const string Allow = "Allow";
|
||||
public const string AltSvc = "Alt-Svc";
|
||||
public const string Authorization = "Authorization";
|
||||
public const string CacheControl = "Cache-Control";
|
||||
public const string Connection = "Connection";
|
||||
public const string ContentDisposition = "Content-Disposition";
|
||||
public const string ContentEncoding = "Content-Encoding";
|
||||
public const string ContentLanguage = "Content-Language";
|
||||
public const string ContentLength = "Content-Length";
|
||||
public const string ContentLocation = "Content-Location";
|
||||
public const string ContentMD5 = "Content-MD5";
|
||||
public const string ContentRange = "Content-Range";
|
||||
public const string ContentSecurityPolicy = "Content-Security-Policy";
|
||||
public const string ContentType = "Content-Type";
|
||||
public const string Cookie = "Cookie";
|
||||
public const string Cookie2 = "Cookie2";
|
||||
public const string Date = "Date";
|
||||
public const string ETag = "ETag";
|
||||
public const string Expect = "Expect";
|
||||
public const string Expires = "Expires";
|
||||
public const string From = "From";
|
||||
public const string Host = "Host";
|
||||
public const string IfMatch = "If-Match";
|
||||
public const string IfModifiedSince = "If-Modified-Since";
|
||||
public const string IfNoneMatch = "If-None-Match";
|
||||
public const string IfRange = "If-Range";
|
||||
public const string IfUnmodifiedSince = "If-Unmodified-Since";
|
||||
public const string KeepAlive = "Keep-Alive";
|
||||
public const string LastModified = "Last-Modified";
|
||||
public const string Link = "Link";
|
||||
public const string Location = "Location";
|
||||
public const string MaxForwards = "Max-Forwards";
|
||||
public const string Origin = "Origin";
|
||||
public const string P3P = "P3P";
|
||||
public const string Pragma = "Pragma";
|
||||
public const string ProxyAuthenticate = "Proxy-Authenticate";
|
||||
public const string ProxyAuthorization = "Proxy-Authorization";
|
||||
public const string ProxyConnection = "Proxy-Connection";
|
||||
public const string PublicKeyPins = "Public-Key-Pins";
|
||||
public const string Range = "Range";
|
||||
public const string Referer = "Referer"; // NB: The spelling-mistake "Referer" for "Referrer" must be matched.
|
||||
public const string RetryAfter = "Retry-After";
|
||||
public const string SecWebSocketAccept = "Sec-WebSocket-Accept";
|
||||
public const string SecWebSocketExtensions = "Sec-WebSocket-Extensions";
|
||||
public const string SecWebSocketKey = "Sec-WebSocket-Key";
|
||||
public const string SecWebSocketProtocol = "Sec-WebSocket-Protocol";
|
||||
public const string SecWebSocketVersion = "Sec-WebSocket-Version";
|
||||
public const string Server = "Server";
|
||||
public const string SetCookie = "Set-Cookie";
|
||||
public const string SetCookie2 = "Set-Cookie2";
|
||||
public const string StrictTransportSecurity = "Strict-Transport-Security";
|
||||
public const string TE = "TE";
|
||||
public const string TSV = "TSV";
|
||||
public const string Trailer = "Trailer";
|
||||
public const string TransferEncoding = "Transfer-Encoding";
|
||||
public const string Upgrade = "Upgrade";
|
||||
public const string UpgradeInsecureRequests = "Upgrade-Insecure-Requests";
|
||||
public const string UserAgent = "User-Agent";
|
||||
public const string Vary = "Vary";
|
||||
public const string Via = "Via";
|
||||
public const string WWWAuthenticate = "WWW-Authenticate";
|
||||
public const string Warning = "Warning";
|
||||
public const string XAspNetVersion = "X-AspNet-Version";
|
||||
public const string XContentDuration = "X-Content-Duration";
|
||||
public const string XContentTypeOptions = "X-Content-Type-Options";
|
||||
public const string XFrameOptions = "X-Frame-Options";
|
||||
public const string XMSEdgeRef = "X-MSEdge-Ref";
|
||||
public const string XPoweredBy = "X-Powered-By";
|
||||
public const string XRequestID = "X-Request-ID";
|
||||
public const string XUACompatible = "X-UA-Compatible";
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ namespace SocketHttpListener.Net
|
||||
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 IStreamHelper StreamHelper { get; private set; }
|
||||
internal INetworkManager NetworkManager { get; private set; }
|
||||
internal IEnvironmentInfo EnvironmentInfo { get; private set; }
|
||||
|
||||
@@ -42,14 +42,14 @@ namespace SocketHttpListener.Net
|
||||
|
||||
public Action<HttpListenerContext> OnContext { get; set; }
|
||||
|
||||
public HttpListener(ILogger logger, ICryptoProvider cryptoProvider, ISocketFactory socketFactory, INetworkManager networkManager, ITextEncoding textEncoding, IMemoryStreamFactory memoryStreamFactory, IFileSystem fileSystem, IEnvironmentInfo environmentInfo)
|
||||
public HttpListener(ILogger logger, ICryptoProvider cryptoProvider, ISocketFactory socketFactory, INetworkManager networkManager, ITextEncoding textEncoding, IStreamHelper streamHelper, IFileSystem fileSystem, IEnvironmentInfo environmentInfo)
|
||||
{
|
||||
_logger = logger;
|
||||
CryptoProvider = cryptoProvider;
|
||||
SocketFactory = socketFactory;
|
||||
NetworkManager = networkManager;
|
||||
TextEncoding = textEncoding;
|
||||
MemoryStreamFactory = memoryStreamFactory;
|
||||
StreamHelper = streamHelper;
|
||||
FileSystem = fileSystem;
|
||||
EnvironmentInfo = environmentInfo;
|
||||
prefixes = new HttpListenerPrefixCollection(logger, this);
|
||||
@@ -58,13 +58,13 @@ namespace SocketHttpListener.Net
|
||||
auth_schemes = AuthenticationSchemes.Anonymous;
|
||||
}
|
||||
|
||||
public HttpListener(X509Certificate certificate, ICryptoProvider cryptoProvider, ISocketFactory socketFactory, INetworkManager networkManager, ITextEncoding textEncoding, IMemoryStreamFactory memoryStreamFactory, IFileSystem fileSystem, IEnvironmentInfo environmentInfo)
|
||||
:this(new NullLogger(), certificate, cryptoProvider, socketFactory, networkManager, textEncoding, memoryStreamFactory, fileSystem, environmentInfo)
|
||||
public HttpListener(X509Certificate certificate, ICryptoProvider cryptoProvider, ISocketFactory socketFactory, INetworkManager networkManager, ITextEncoding textEncoding, IStreamHelper streamHelper, IFileSystem fileSystem, IEnvironmentInfo environmentInfo)
|
||||
:this(new NullLogger(), certificate, cryptoProvider, socketFactory, networkManager, textEncoding, streamHelper, fileSystem, environmentInfo)
|
||||
{
|
||||
}
|
||||
|
||||
public HttpListener(ILogger logger, X509Certificate certificate, ICryptoProvider cryptoProvider, ISocketFactory socketFactory, INetworkManager networkManager, ITextEncoding textEncoding, IMemoryStreamFactory memoryStreamFactory, IFileSystem fileSystem, IEnvironmentInfo environmentInfo)
|
||||
: this(logger, cryptoProvider, socketFactory, networkManager, textEncoding, memoryStreamFactory, fileSystem, environmentInfo)
|
||||
public HttpListener(ILogger logger, X509Certificate certificate, ICryptoProvider cryptoProvider, ISocketFactory socketFactory, INetworkManager networkManager, ITextEncoding textEncoding, IStreamHelper streamHelper, IFileSystem fileSystem, IEnvironmentInfo environmentInfo)
|
||||
: this(logger, cryptoProvider, socketFactory, networkManager, textEncoding, streamHelper, fileSystem, environmentInfo)
|
||||
{
|
||||
_certificate = certificate;
|
||||
}
|
||||
@@ -185,7 +185,7 @@ namespace SocketHttpListener.Net
|
||||
void Close(bool force)
|
||||
{
|
||||
CheckDisposed();
|
||||
EndPointManager.RemoveListener(_logger, this);
|
||||
HttpEndPointManager.RemoveListener(_logger, this);
|
||||
Cleanup(force);
|
||||
}
|
||||
|
||||
@@ -230,7 +230,7 @@ namespace SocketHttpListener.Net
|
||||
if (listening)
|
||||
return;
|
||||
|
||||
EndPointManager.AddListener(_logger, this);
|
||||
HttpEndPointManager.AddListener(_logger, this);
|
||||
listening = true;
|
||||
}
|
||||
|
||||
@@ -248,7 +248,6 @@ namespace SocketHttpListener.Net
|
||||
|
||||
Close(true); //TODO: Should we force here or not?
|
||||
disposed = true;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
internal void CheckDisposed()
|
||||
|
||||
100
SocketHttpListener/Net/HttpListenerContext.Managed.cs
Normal file
100
SocketHttpListener/Net/HttpListenerContext.Managed.cs
Normal file
@@ -0,0 +1,100 @@
|
||||
using System.ComponentModel;
|
||||
using System.Security.Principal;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using MediaBrowser.Model.Text;
|
||||
using SocketHttpListener.Net.WebSockets;
|
||||
|
||||
namespace SocketHttpListener.Net
|
||||
{
|
||||
public sealed unsafe partial class HttpListenerContext
|
||||
{
|
||||
private HttpConnection _connection;
|
||||
|
||||
internal HttpListenerContext(HttpConnection connection, ITextEncoding textEncoding)
|
||||
{
|
||||
_connection = connection;
|
||||
_response = new HttpListenerResponse(this, textEncoding);
|
||||
Request = new HttpListenerRequest(this);
|
||||
ErrorStatus = 400;
|
||||
}
|
||||
|
||||
internal int ErrorStatus { get; set; }
|
||||
|
||||
internal string ErrorMessage { get; set; }
|
||||
|
||||
internal bool HaveError => ErrorMessage != null;
|
||||
|
||||
internal HttpConnection Connection => _connection;
|
||||
|
||||
internal void ParseAuthentication(System.Net.AuthenticationSchemes expectedSchemes)
|
||||
{
|
||||
if (expectedSchemes == System.Net.AuthenticationSchemes.Anonymous)
|
||||
return;
|
||||
|
||||
string header = Request.Headers["Authorization"];
|
||||
if (string.IsNullOrEmpty(header))
|
||||
return;
|
||||
|
||||
if (IsBasicHeader(header))
|
||||
{
|
||||
_user = ParseBasicAuthentication(header.Substring(AuthenticationTypes.Basic.Length + 1));
|
||||
}
|
||||
}
|
||||
|
||||
internal IPrincipal ParseBasicAuthentication(string authData) =>
|
||||
TryParseBasicAuth(authData, out HttpStatusCode errorCode, out string username, out string password) ?
|
||||
new GenericPrincipal(new HttpListenerBasicIdentity(username, password), Array.Empty<string>()) :
|
||||
null;
|
||||
|
||||
internal static bool IsBasicHeader(string header) =>
|
||||
header.Length >= 6 &&
|
||||
header[5] == ' ' &&
|
||||
string.Compare(header, 0, AuthenticationTypes.Basic, 0, 5, StringComparison.OrdinalIgnoreCase) == 0;
|
||||
|
||||
internal static bool TryParseBasicAuth(string headerValue, out HttpStatusCode errorCode, out string username, out string password)
|
||||
{
|
||||
errorCode = HttpStatusCode.OK;
|
||||
username = password = null;
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(headerValue))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
string authString = Encoding.UTF8.GetString(Convert.FromBase64String(headerValue));
|
||||
int colonPos = authString.IndexOf(':');
|
||||
if (colonPos < 0)
|
||||
{
|
||||
// username must be at least 1 char
|
||||
errorCode = HttpStatusCode.BadRequest;
|
||||
return false;
|
||||
}
|
||||
|
||||
username = authString.Substring(0, colonPos);
|
||||
password = authString.Substring(colonPos + 1);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
errorCode = HttpStatusCode.InternalServerError;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public Task<HttpListenerWebSocketContext> AcceptWebSocketAsync(string subProtocol, int receiveBufferSize, TimeSpan keepAliveInterval)
|
||||
{
|
||||
return HttpWebSocket.AcceptWebSocketAsyncCore(this, subProtocol, receiveBufferSize, keepAliveInterval);
|
||||
}
|
||||
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public Task<HttpListenerWebSocketContext> AcceptWebSocketAsync(string subProtocol, int receiveBufferSize, TimeSpan keepAliveInterval, ArraySegment<byte> internalBuffer)
|
||||
{
|
||||
WebSocketValidate.ValidateArraySegment(internalBuffer, nameof(internalBuffer));
|
||||
HttpWebSocket.ValidateOptions(subProtocol, receiveBufferSize, HttpWebSocket.MinSendBufferSize, keepAliveInterval);
|
||||
return HttpWebSocket.AcceptWebSocketAsyncCore(this, subProtocol, receiveBufferSize, keepAliveInterval, internalBuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,145 +7,39 @@ using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.Text;
|
||||
using SocketHttpListener.Net.WebSockets;
|
||||
using SocketHttpListener.Primitives;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SocketHttpListener.Net
|
||||
{
|
||||
public sealed class HttpListenerContext
|
||||
public sealed unsafe partial 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 HttpListener _listener;
|
||||
private HttpListenerResponse _response;
|
||||
private IPrincipal _user;
|
||||
|
||||
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, _textEncoding);
|
||||
}
|
||||
public HttpListenerRequest Request { get; }
|
||||
|
||||
internal int ErrorStatus
|
||||
{
|
||||
get { return err_status; }
|
||||
set { err_status = value; }
|
||||
}
|
||||
public IPrincipal User => _user;
|
||||
|
||||
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; }
|
||||
}
|
||||
// This can be used to cache the results of HttpListener.AuthenticationSchemeSelectorDelegate.
|
||||
internal AuthenticationSchemes AuthenticationSchemes { get; set; }
|
||||
|
||||
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))
|
||||
get
|
||||
{
|
||||
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;
|
||||
return _response;
|
||||
}
|
||||
}
|
||||
|
||||
public HttpListenerWebSocketContext AcceptWebSocket(string protocol)
|
||||
public Task<HttpListenerWebSocketContext> AcceptWebSocketAsync(string subProtocol)
|
||||
{
|
||||
if (protocol != null)
|
||||
{
|
||||
if (protocol.Length == 0)
|
||||
throw new ArgumentException("An empty string.", "protocol");
|
||||
return AcceptWebSocketAsync(subProtocol, HttpWebSocket.DefaultReceiveBufferSize, WebSocket.DefaultKeepAliveInterval);
|
||||
}
|
||||
|
||||
if (!protocol.IsToken())
|
||||
throw new ArgumentException("Contains an invalid character.", "protocol");
|
||||
}
|
||||
|
||||
return new HttpListenerWebSocketContext(this, protocol, _cryptoProvider, _memoryStreamFactory);
|
||||
public Task<HttpListenerWebSocketContext> AcceptWebSocketAsync(string subProtocol, TimeSpan keepAliveInterval)
|
||||
{
|
||||
return AcceptWebSocketAsync(subProtocol, HttpWebSocket.DefaultReceiveBufferSize, keepAliveInterval);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,13 +36,13 @@ namespace SocketHttpListener.Net
|
||||
public void Add(string uriPrefix)
|
||||
{
|
||||
listener.CheckDisposed();
|
||||
ListenerPrefix.CheckUri(uriPrefix);
|
||||
//ListenerPrefix.CheckUri(uriPrefix);
|
||||
if (prefixes.Contains(uriPrefix))
|
||||
return;
|
||||
|
||||
prefixes.Add(uriPrefix);
|
||||
if (listener.IsListening)
|
||||
EndPointManager.AddPrefix(_logger, uriPrefix, listener);
|
||||
HttpEndPointManager.AddPrefix(_logger, uriPrefix, listener);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
@@ -50,7 +50,7 @@ namespace SocketHttpListener.Net
|
||||
listener.CheckDisposed();
|
||||
prefixes.Clear();
|
||||
if (listener.IsListening)
|
||||
EndPointManager.RemoveListener(_logger, listener);
|
||||
HttpEndPointManager.RemoveListener(_logger, listener);
|
||||
}
|
||||
|
||||
public bool Contains(string uriPrefix)
|
||||
@@ -89,7 +89,7 @@ namespace SocketHttpListener.Net
|
||||
|
||||
bool result = prefixes.Remove(uriPrefix);
|
||||
if (result && listener.IsListening)
|
||||
EndPointManager.RemovePrefix(_logger, uriPrefix, listener);
|
||||
HttpEndPointManager.RemovePrefix(_logger, uriPrefix, listener);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
330
SocketHttpListener/Net/HttpListenerRequest.Managed.cs
Normal file
330
SocketHttpListener/Net/HttpListenerRequest.Managed.cs
Normal file
@@ -0,0 +1,330 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Collections.Specialized;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Security.Authentication.ExtendedProtection;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using MediaBrowser.Model.Services;
|
||||
using MediaBrowser.Model.Text;
|
||||
|
||||
namespace SocketHttpListener.Net
|
||||
{
|
||||
public sealed partial class HttpListenerRequest
|
||||
{
|
||||
private long _contentLength;
|
||||
private bool _clSet;
|
||||
private WebHeaderCollection _headers;
|
||||
private string _method;
|
||||
private Stream _inputStream;
|
||||
private HttpListenerContext _context;
|
||||
private bool _isChunked;
|
||||
|
||||
private static byte[] s_100continue = Encoding.ASCII.GetBytes("HTTP/1.1 100 Continue\r\n\r\n");
|
||||
|
||||
internal HttpListenerRequest(HttpListenerContext context)
|
||||
{
|
||||
_context = context;
|
||||
_headers = new WebHeaderCollection();
|
||||
_version = HttpVersion.Version10;
|
||||
}
|
||||
|
||||
private static readonly char[] s_separators = new char[] { ' ' };
|
||||
|
||||
internal void SetRequestLine(string req)
|
||||
{
|
||||
string[] parts = req.Split(s_separators, 3);
|
||||
if (parts.Length != 3)
|
||||
{
|
||||
_context.ErrorMessage = "Invalid request line (parts).";
|
||||
return;
|
||||
}
|
||||
|
||||
_method = parts[0];
|
||||
foreach (char c in _method)
|
||||
{
|
||||
int ic = (int)c;
|
||||
|
||||
if ((ic >= 'A' && ic <= 'Z') ||
|
||||
(ic > 32 && c < 127 && c != '(' && c != ')' && c != '<' &&
|
||||
c != '<' && c != '>' && c != '@' && c != ',' && c != ';' &&
|
||||
c != ':' && c != '\\' && c != '"' && c != '/' && c != '[' &&
|
||||
c != ']' && c != '?' && c != '=' && c != '{' && c != '}'))
|
||||
continue;
|
||||
|
||||
_context.ErrorMessage = "(Invalid verb)";
|
||||
return;
|
||||
}
|
||||
|
||||
_rawUrl = parts[1];
|
||||
if (parts[2].Length != 8 || !parts[2].StartsWith("HTTP/"))
|
||||
{
|
||||
_context.ErrorMessage = "Invalid request line (version).";
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_version = new Version(parts[2].Substring(5));
|
||||
}
|
||||
catch
|
||||
{
|
||||
_context.ErrorMessage = "Invalid request line (version).";
|
||||
return;
|
||||
}
|
||||
|
||||
if (_version.Major < 1)
|
||||
{
|
||||
_context.ErrorMessage = "Invalid request line (version).";
|
||||
return;
|
||||
}
|
||||
if (_version.Major > 1)
|
||||
{
|
||||
_context.ErrorStatus = (int)HttpStatusCode.HttpVersionNotSupported;
|
||||
_context.ErrorMessage = HttpStatusDescription.Get(HttpStatusCode.HttpVersionNotSupported);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool MaybeUri(string s)
|
||||
{
|
||||
int p = s.IndexOf(':');
|
||||
if (p == -1)
|
||||
return false;
|
||||
|
||||
if (p >= 10)
|
||||
return false;
|
||||
|
||||
return IsPredefinedScheme(s.Substring(0, p));
|
||||
}
|
||||
|
||||
private static bool IsPredefinedScheme(string scheme)
|
||||
{
|
||||
if (scheme == null || scheme.Length < 3)
|
||||
return false;
|
||||
|
||||
char c = scheme[0];
|
||||
if (c == 'h')
|
||||
return (scheme == UriScheme.Http || scheme == UriScheme.Https);
|
||||
if (c == 'f')
|
||||
return (scheme == UriScheme.File || scheme == UriScheme.Ftp);
|
||||
|
||||
if (c == 'n')
|
||||
{
|
||||
c = scheme[1];
|
||||
if (c == 'e')
|
||||
return (scheme == UriScheme.News || scheme == UriScheme.NetPipe || scheme == UriScheme.NetTcp);
|
||||
if (scheme == UriScheme.Nntp)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
if ((c == 'g' && scheme == UriScheme.Gopher) || (c == 'm' && scheme == UriScheme.Mailto))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal void FinishInitialization()
|
||||
{
|
||||
string host = UserHostName;
|
||||
if (_version > HttpVersion.Version10 && (host == null || host.Length == 0))
|
||||
{
|
||||
_context.ErrorMessage = "Invalid host name";
|
||||
return;
|
||||
}
|
||||
|
||||
string path;
|
||||
Uri raw_uri = null;
|
||||
if (MaybeUri(_rawUrl.ToLowerInvariant()) && Uri.TryCreate(_rawUrl, UriKind.Absolute, out raw_uri))
|
||||
path = raw_uri.PathAndQuery;
|
||||
else
|
||||
path = _rawUrl;
|
||||
|
||||
if ((host == null || host.Length == 0))
|
||||
host = UserHostAddress;
|
||||
|
||||
if (raw_uri != null)
|
||||
host = raw_uri.Host;
|
||||
|
||||
int colon = host.IndexOf(']') == -1 ? host.IndexOf(':') : host.LastIndexOf(':');
|
||||
if (colon >= 0)
|
||||
host = host.Substring(0, colon);
|
||||
|
||||
string base_uri = string.Format("{0}://{1}:{2}", RequestScheme, host, LocalEndPoint.Port);
|
||||
|
||||
if (!Uri.TryCreate(base_uri + path, UriKind.Absolute, out _requestUri))
|
||||
{
|
||||
_context.ErrorMessage = System.Net.WebUtility.HtmlEncode("Invalid url: " + base_uri + path);
|
||||
return;
|
||||
}
|
||||
|
||||
_requestUri = HttpListenerRequestUriBuilder.GetRequestUri(_rawUrl, _requestUri.Scheme,
|
||||
_requestUri.Authority, _requestUri.LocalPath, _requestUri.Query);
|
||||
|
||||
if (_version >= HttpVersion.Version11)
|
||||
{
|
||||
string t_encoding = Headers[HttpKnownHeaderNames.TransferEncoding];
|
||||
_isChunked = (t_encoding != null && string.Equals(t_encoding, "chunked", StringComparison.OrdinalIgnoreCase));
|
||||
// 'identity' is not valid!
|
||||
if (t_encoding != null && !_isChunked)
|
||||
{
|
||||
_context.Connection.SendError(null, 501);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!_isChunked && !_clSet)
|
||||
{
|
||||
if (string.Equals(_method, "POST", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(_method, "PUT", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_context.Connection.SendError(null, 411);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (String.Compare(Headers[HttpKnownHeaderNames.Expect], "100-continue", StringComparison.OrdinalIgnoreCase) == 0)
|
||||
{
|
||||
HttpResponseStream output = _context.Connection.GetResponseStream();
|
||||
output.InternalWrite(s_100continue, 0, s_100continue.Length);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string Unquote(String str)
|
||||
{
|
||||
int start = str.IndexOf('\"');
|
||||
int end = str.LastIndexOf('\"');
|
||||
if (start >= 0 && end >= 0)
|
||||
str = str.Substring(start + 1, end - 1);
|
||||
return str.Trim();
|
||||
}
|
||||
|
||||
internal void AddHeader(string header)
|
||||
{
|
||||
int colon = header.IndexOf(':');
|
||||
if (colon == -1 || colon == 0)
|
||||
{
|
||||
_context.ErrorMessage = HttpStatusDescription.Get(400);
|
||||
_context.ErrorStatus = 400;
|
||||
return;
|
||||
}
|
||||
|
||||
string name = header.Substring(0, colon).Trim();
|
||||
string val = header.Substring(colon + 1).Trim();
|
||||
if (name.Equals("content-length", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// To match Windows behavior:
|
||||
// Content lengths >= 0 and <= long.MaxValue are accepted as is.
|
||||
// Content lengths > long.MaxValue and <= ulong.MaxValue are treated as 0.
|
||||
// Content lengths < 0 cause the requests to fail.
|
||||
// Other input is a failure, too.
|
||||
long parsedContentLength =
|
||||
ulong.TryParse(val, out ulong parsedUlongContentLength) ? (parsedUlongContentLength <= long.MaxValue ? (long)parsedUlongContentLength : 0) :
|
||||
long.Parse(val);
|
||||
if (parsedContentLength < 0 || (_clSet && parsedContentLength != _contentLength))
|
||||
{
|
||||
_context.ErrorMessage = "Invalid Content-Length.";
|
||||
}
|
||||
else
|
||||
{
|
||||
_contentLength = parsedContentLength;
|
||||
_clSet = true;
|
||||
}
|
||||
}
|
||||
else if (name.Equals("transfer-encoding", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (Headers[HttpKnownHeaderNames.TransferEncoding] != null)
|
||||
{
|
||||
_context.ErrorStatus = (int)HttpStatusCode.NotImplemented;
|
||||
_context.ErrorMessage = HttpStatusDescription.Get(HttpStatusCode.NotImplemented);
|
||||
}
|
||||
}
|
||||
|
||||
if (_context.ErrorMessage == null)
|
||||
{
|
||||
_headers.Set(name, val);
|
||||
}
|
||||
}
|
||||
|
||||
// returns true is the stream could be reused.
|
||||
internal bool FlushInput()
|
||||
{
|
||||
if (!HasEntityBody)
|
||||
return true;
|
||||
|
||||
int length = 2048;
|
||||
if (_contentLength > 0)
|
||||
length = (int)Math.Min(_contentLength, (long)length);
|
||||
|
||||
byte[] bytes = new byte[length];
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
IAsyncResult ares = InputStream.BeginRead(bytes, 0, length, null, null);
|
||||
if (!ares.IsCompleted && !ares.AsyncWaitHandle.WaitOne(1000))
|
||||
return false;
|
||||
if (InputStream.EndRead(ares) <= 0)
|
||||
return true;
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
_inputStream = null;
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public long ContentLength64
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_isChunked)
|
||||
_contentLength = -1;
|
||||
|
||||
return _contentLength;
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasEntityBody => (_contentLength > 0 || _isChunked);
|
||||
|
||||
public QueryParamCollection Headers => _headers;
|
||||
|
||||
public string HttpMethod => _method;
|
||||
|
||||
public Stream InputStream
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_inputStream == null)
|
||||
{
|
||||
if (_isChunked || _contentLength > 0)
|
||||
_inputStream = _context.Connection.GetRequestStream(_isChunked, _contentLength);
|
||||
else
|
||||
_inputStream = Stream.Null;
|
||||
}
|
||||
|
||||
return _inputStream;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsAuthenticated => false;
|
||||
|
||||
public bool IsSecureConnection => _context.Connection.IsSecure;
|
||||
|
||||
public System.Net.IPEndPoint LocalEndPoint => _context.Connection.LocalEndPoint;
|
||||
|
||||
public System.Net.IPEndPoint RemoteEndPoint => _context.Connection.RemoteEndPoint;
|
||||
|
||||
public Guid RequestTraceIdentifier { get; } = Guid.NewGuid();
|
||||
|
||||
public string ServiceName => null;
|
||||
|
||||
private Uri RequestUri => _requestUri;
|
||||
private bool SupportsWebSockets => true;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
445
SocketHttpListener/Net/HttpListenerRequestUriBuilder.cs
Normal file
445
SocketHttpListener/Net/HttpListenerRequestUriBuilder.cs
Normal file
@@ -0,0 +1,445 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using System.Globalization;
|
||||
|
||||
namespace SocketHttpListener.Net
|
||||
{
|
||||
// We don't use the cooked URL because http.sys unescapes all percent-encoded values. However,
|
||||
// we also can't just use the raw Uri, since http.sys supports not only Utf-8, but also ANSI/DBCS and
|
||||
// Unicode code points. System.Uri only supports Utf-8.
|
||||
// The purpose of this class is to convert all ANSI, DBCS, and Unicode code points into percent encoded
|
||||
// Utf-8 characters.
|
||||
internal sealed class HttpListenerRequestUriBuilder
|
||||
{
|
||||
private static readonly Encoding s_utf8Encoding = new UTF8Encoding(false, true);
|
||||
private static readonly Encoding s_ansiEncoding = Encoding.GetEncoding(0, new EncoderExceptionFallback(), new DecoderExceptionFallback());
|
||||
|
||||
private readonly string _rawUri;
|
||||
private readonly string _cookedUriScheme;
|
||||
private readonly string _cookedUriHost;
|
||||
private readonly string _cookedUriPath;
|
||||
private readonly string _cookedUriQuery;
|
||||
|
||||
// This field is used to build the final request Uri string from the Uri parts passed to the ctor.
|
||||
private StringBuilder _requestUriString;
|
||||
|
||||
// The raw path is parsed by looping through all characters from left to right. 'rawOctets'
|
||||
// is used to store consecutive percent encoded octets as actual byte values: e.g. for path /pa%C3%84th%2F/
|
||||
// rawOctets will be set to { 0xC3, 0x84 } when we reach character 't' and it will be { 0x2F } when
|
||||
// we reach the final '/'. I.e. after a sequence of percent encoded octets ends, we use rawOctets as
|
||||
// input to the encoding and percent encode the resulting string into UTF-8 octets.
|
||||
//
|
||||
// When parsing ANSI (Latin 1) encoded path '/pa%C4th/', %C4 will be added to rawOctets and when
|
||||
// we reach 't', the content of rawOctets { 0xC4 } will be fed into the ANSI encoding. The resulting
|
||||
// string 'Ä' will be percent encoded into UTF-8 octets and appended to requestUriString. The final
|
||||
// path will be '/pa%C3%84th/', where '%C3%84' is the UTF-8 percent encoded character 'Ä'.
|
||||
private List<byte> _rawOctets;
|
||||
private string _rawPath;
|
||||
|
||||
// Holds the final request Uri.
|
||||
private Uri _requestUri;
|
||||
|
||||
private HttpListenerRequestUriBuilder(string rawUri, string cookedUriScheme, string cookedUriHost,
|
||||
string cookedUriPath, string cookedUriQuery)
|
||||
{
|
||||
_rawUri = rawUri;
|
||||
_cookedUriScheme = cookedUriScheme;
|
||||
_cookedUriHost = cookedUriHost;
|
||||
_cookedUriPath = AddSlashToAsteriskOnlyPath(cookedUriPath);
|
||||
_cookedUriQuery = cookedUriQuery ?? string.Empty;
|
||||
}
|
||||
|
||||
public static Uri GetRequestUri(string rawUri, string cookedUriScheme, string cookedUriHost,
|
||||
string cookedUriPath, string cookedUriQuery)
|
||||
{
|
||||
HttpListenerRequestUriBuilder builder = new HttpListenerRequestUriBuilder(rawUri,
|
||||
cookedUriScheme, cookedUriHost, cookedUriPath, cookedUriQuery);
|
||||
|
||||
return builder.Build();
|
||||
}
|
||||
|
||||
private Uri Build()
|
||||
{
|
||||
BuildRequestUriUsingRawPath();
|
||||
|
||||
if (_requestUri == null)
|
||||
{
|
||||
BuildRequestUriUsingCookedPath();
|
||||
}
|
||||
|
||||
return _requestUri;
|
||||
}
|
||||
|
||||
private void BuildRequestUriUsingCookedPath()
|
||||
{
|
||||
bool isValid = Uri.TryCreate(_cookedUriScheme + Uri.SchemeDelimiter + _cookedUriHost + _cookedUriPath +
|
||||
_cookedUriQuery, UriKind.Absolute, out _requestUri);
|
||||
|
||||
// Creating a Uri from the cooked Uri should really always work: If not, we log at least.
|
||||
if (!isValid)
|
||||
{
|
||||
//if (NetEventSource.IsEnabled)
|
||||
// NetEventSource.Error(this, SR.Format(SR.net_log_listener_cant_create_uri, _cookedUriScheme, _cookedUriHost, _cookedUriPath, _cookedUriQuery));
|
||||
}
|
||||
}
|
||||
|
||||
private void BuildRequestUriUsingRawPath()
|
||||
{
|
||||
bool isValid = false;
|
||||
|
||||
// Initialize 'rawPath' only if really needed; i.e. if we build the request Uri from the raw Uri.
|
||||
_rawPath = GetPath(_rawUri);
|
||||
|
||||
// Try to check the raw path using first the primary encoding (according to http.sys settings);
|
||||
// if it fails try the secondary encoding.
|
||||
ParsingResult result = BuildRequestUriUsingRawPath(GetEncoding(EncodingType.Primary));
|
||||
if (result == ParsingResult.EncodingError)
|
||||
{
|
||||
Encoding secondaryEncoding = GetEncoding(EncodingType.Secondary);
|
||||
result = BuildRequestUriUsingRawPath(secondaryEncoding);
|
||||
}
|
||||
isValid = (result == ParsingResult.Success) ? true : false;
|
||||
|
||||
// Log that we weren't able to create a Uri from the raw string.
|
||||
if (!isValid)
|
||||
{
|
||||
//if (NetEventSource.IsEnabled)
|
||||
// NetEventSource.Error(this, SR.Format(SR.net_log_listener_cant_create_uri, _cookedUriScheme, _cookedUriHost, _rawPath, _cookedUriQuery));
|
||||
}
|
||||
}
|
||||
|
||||
private static Encoding GetEncoding(EncodingType type)
|
||||
{
|
||||
Debug.Assert((type == EncodingType.Primary) || (type == EncodingType.Secondary),
|
||||
"Unknown 'EncodingType' value: " + type.ToString());
|
||||
|
||||
if (type == EncodingType.Secondary)
|
||||
{
|
||||
return s_ansiEncoding;
|
||||
}
|
||||
else
|
||||
{
|
||||
return s_utf8Encoding;
|
||||
}
|
||||
}
|
||||
|
||||
private ParsingResult BuildRequestUriUsingRawPath(Encoding encoding)
|
||||
{
|
||||
Debug.Assert(encoding != null, "'encoding' must be assigned.");
|
||||
Debug.Assert(!string.IsNullOrEmpty(_rawPath), "'rawPath' must have at least one character.");
|
||||
|
||||
_rawOctets = new List<byte>();
|
||||
_requestUriString = new StringBuilder();
|
||||
_requestUriString.Append(_cookedUriScheme);
|
||||
_requestUriString.Append(Uri.SchemeDelimiter);
|
||||
_requestUriString.Append(_cookedUriHost);
|
||||
|
||||
ParsingResult result = ParseRawPath(encoding);
|
||||
if (result == ParsingResult.Success)
|
||||
{
|
||||
_requestUriString.Append(_cookedUriQuery);
|
||||
|
||||
Debug.Assert(_rawOctets.Count == 0,
|
||||
"Still raw octets left. They must be added to the result path.");
|
||||
|
||||
if (!Uri.TryCreate(_requestUriString.ToString(), UriKind.Absolute, out _requestUri))
|
||||
{
|
||||
// If we can't create a Uri from the string, this is an invalid string and it doesn't make
|
||||
// sense to try another encoding.
|
||||
result = ParsingResult.InvalidString;
|
||||
}
|
||||
}
|
||||
|
||||
if (result != ParsingResult.Success)
|
||||
{
|
||||
//if (NetEventSource.IsEnabled)
|
||||
// NetEventSource.Error(this, SR.Format(SR.net_log_listener_cant_convert_raw_path, _rawPath, encoding.EncodingName));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private ParsingResult ParseRawPath(Encoding encoding)
|
||||
{
|
||||
Debug.Assert(encoding != null, "'encoding' must be assigned.");
|
||||
|
||||
int index = 0;
|
||||
char current = '\0';
|
||||
while (index < _rawPath.Length)
|
||||
{
|
||||
current = _rawPath[index];
|
||||
if (current == '%')
|
||||
{
|
||||
// Assert is enough, since http.sys accepted the request string already. This should never happen.
|
||||
Debug.Assert(index + 2 < _rawPath.Length, "Expected >=2 characters after '%' (e.g. %2F)");
|
||||
|
||||
index++;
|
||||
current = _rawPath[index];
|
||||
if (current == 'u' || current == 'U')
|
||||
{
|
||||
// We found "%u" which means, we have a Unicode code point of the form "%uXXXX".
|
||||
Debug.Assert(index + 4 < _rawPath.Length, "Expected >=4 characters after '%u' (e.g. %u0062)");
|
||||
|
||||
// Decode the content of rawOctets into percent encoded UTF-8 characters and append them
|
||||
// to requestUriString.
|
||||
if (!EmptyDecodeAndAppendRawOctetsList(encoding))
|
||||
{
|
||||
return ParsingResult.EncodingError;
|
||||
}
|
||||
if (!AppendUnicodeCodePointValuePercentEncoded(_rawPath.Substring(index + 1, 4)))
|
||||
{
|
||||
return ParsingResult.InvalidString;
|
||||
}
|
||||
index += 5;
|
||||
}
|
||||
else
|
||||
{
|
||||
// We found '%', but not followed by 'u', i.e. we have a percent encoded octed: %XX
|
||||
if (!AddPercentEncodedOctetToRawOctetsList(encoding, _rawPath.Substring(index, 2)))
|
||||
{
|
||||
return ParsingResult.InvalidString;
|
||||
}
|
||||
index += 2;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// We found a non-'%' character: decode the content of rawOctets into percent encoded
|
||||
// UTF-8 characters and append it to the result.
|
||||
if (!EmptyDecodeAndAppendRawOctetsList(encoding))
|
||||
{
|
||||
return ParsingResult.EncodingError;
|
||||
}
|
||||
// Append the current character to the result.
|
||||
_requestUriString.Append(current);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
// if the raw path ends with a sequence of percent encoded octets, make sure those get added to the
|
||||
// result (requestUriString).
|
||||
if (!EmptyDecodeAndAppendRawOctetsList(encoding))
|
||||
{
|
||||
return ParsingResult.EncodingError;
|
||||
}
|
||||
|
||||
return ParsingResult.Success;
|
||||
}
|
||||
|
||||
private bool AppendUnicodeCodePointValuePercentEncoded(string codePoint)
|
||||
{
|
||||
// http.sys only supports %uXXXX (4 hex-digits), even though unicode code points could have up to
|
||||
// 6 hex digits. Therefore we parse always 4 characters after %u and convert them to an int.
|
||||
int codePointValue;
|
||||
if (!int.TryParse(codePoint, NumberStyles.HexNumber, null, out codePointValue))
|
||||
{
|
||||
//if (NetEventSource.IsEnabled)
|
||||
// NetEventSource.Error(this, SR.Format(SR.net_log_listener_cant_convert_percent_value, codePoint));
|
||||
return false;
|
||||
}
|
||||
|
||||
string unicodeString = null;
|
||||
try
|
||||
{
|
||||
unicodeString = char.ConvertFromUtf32(codePointValue);
|
||||
AppendOctetsPercentEncoded(_requestUriString, s_utf8Encoding.GetBytes(unicodeString));
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (ArgumentOutOfRangeException)
|
||||
{
|
||||
//if (NetEventSource.IsEnabled)
|
||||
// NetEventSource.Error(this, SR.Format(SR.net_log_listener_cant_convert_percent_value, codePoint));
|
||||
}
|
||||
catch (EncoderFallbackException e)
|
||||
{
|
||||
// If utf8Encoding.GetBytes() fails
|
||||
//if (NetEventSource.IsEnabled) NetEventSource.Error(this, SR.Format(SR.net_log_listener_cant_convert_to_utf8, unicodeString, e.Message));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool AddPercentEncodedOctetToRawOctetsList(Encoding encoding, string escapedCharacter)
|
||||
{
|
||||
byte encodedValue;
|
||||
if (!byte.TryParse(escapedCharacter, NumberStyles.HexNumber, null, out encodedValue))
|
||||
{
|
||||
//if (NetEventSource.IsEnabled) NetEventSource.Error(this, SR.Format(SR.net_log_listener_cant_convert_percent_value, escapedCharacter));
|
||||
return false;
|
||||
}
|
||||
|
||||
_rawOctets.Add(encodedValue);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool EmptyDecodeAndAppendRawOctetsList(Encoding encoding)
|
||||
{
|
||||
if (_rawOctets.Count == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
string decodedString = null;
|
||||
try
|
||||
{
|
||||
// If the encoding can get a string out of the byte array, this is a valid string in the
|
||||
// 'encoding' encoding.
|
||||
decodedString = encoding.GetString(_rawOctets.ToArray());
|
||||
|
||||
if (encoding == s_utf8Encoding)
|
||||
{
|
||||
AppendOctetsPercentEncoded(_requestUriString, _rawOctets.ToArray());
|
||||
}
|
||||
else
|
||||
{
|
||||
AppendOctetsPercentEncoded(_requestUriString, s_utf8Encoding.GetBytes(decodedString));
|
||||
}
|
||||
|
||||
_rawOctets.Clear();
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (DecoderFallbackException e)
|
||||
{
|
||||
//if (NetEventSource.IsEnabled) NetEventSource.Error(this, SR.Format(SR.net_log_listener_cant_convert_bytes, GetOctetsAsString(_rawOctets), e.Message));
|
||||
}
|
||||
catch (EncoderFallbackException e)
|
||||
{
|
||||
// If utf8Encoding.GetBytes() fails
|
||||
//if (NetEventSource.IsEnabled) NetEventSource.Error(this, SR.Format(SR.net_log_listener_cant_convert_to_utf8, decodedString, e.Message));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void AppendOctetsPercentEncoded(StringBuilder target, IEnumerable<byte> octets)
|
||||
{
|
||||
foreach (byte octet in octets)
|
||||
{
|
||||
target.Append('%');
|
||||
target.Append(octet.ToString("X2", CultureInfo.InvariantCulture));
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetOctetsAsString(IEnumerable<byte> octets)
|
||||
{
|
||||
StringBuilder octetString = new StringBuilder();
|
||||
|
||||
bool first = true;
|
||||
foreach (byte octet in octets)
|
||||
{
|
||||
if (first)
|
||||
{
|
||||
first = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
octetString.Append(' ');
|
||||
}
|
||||
octetString.Append(octet.ToString("X2", CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
return octetString.ToString();
|
||||
}
|
||||
|
||||
private static string GetPath(string uriString)
|
||||
{
|
||||
Debug.Assert(uriString != null, "uriString must not be null");
|
||||
Debug.Assert(uriString.Length > 0, "uriString must not be empty");
|
||||
|
||||
int pathStartIndex = 0;
|
||||
|
||||
// Perf. improvement: nearly all strings are relative Uris. So just look if the
|
||||
// string starts with '/'. If so, we have a relative Uri and the path starts at position 0.
|
||||
// (http.sys already trimmed leading whitespaces)
|
||||
if (uriString[0] != '/')
|
||||
{
|
||||
// We can't check against cookedUriScheme, since http.sys allows for request http://myserver/ to
|
||||
// use a request line 'GET https://myserver/' (note http vs. https). Therefore check if the
|
||||
// Uri starts with either http:// or https://.
|
||||
int authorityStartIndex = 0;
|
||||
if (uriString.StartsWith("http://", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
authorityStartIndex = 7;
|
||||
}
|
||||
else if (uriString.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
authorityStartIndex = 8;
|
||||
}
|
||||
|
||||
if (authorityStartIndex > 0)
|
||||
{
|
||||
// we have an absolute Uri. Find out where the authority ends and the path begins.
|
||||
// Note that Uris like "http://server?query=value/1/2" are invalid according to RFC2616
|
||||
// and http.sys behavior: If the Uri contains a query, there must be at least one '/'
|
||||
// between the authority and the '?' character: It's safe to just look for the first
|
||||
// '/' after the authority to determine the beginning of the path.
|
||||
pathStartIndex = uriString.IndexOf('/', authorityStartIndex);
|
||||
if (pathStartIndex == -1)
|
||||
{
|
||||
// e.g. for request lines like: 'GET http://myserver' (no final '/')
|
||||
pathStartIndex = uriString.Length;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// RFC2616: Request-URI = "*" | absoluteURI | abs_path | authority
|
||||
// 'authority' can only be used with CONNECT which is never received by HttpListener.
|
||||
// I.e. if we don't have an absolute path (must start with '/') and we don't have
|
||||
// an absolute Uri (must start with http:// or https://), then 'uriString' must be '*'.
|
||||
Debug.Assert((uriString.Length == 1) && (uriString[0] == '*'), "Unknown request Uri string format",
|
||||
"Request Uri string is not an absolute Uri, absolute path, or '*': {0}", uriString);
|
||||
|
||||
// Should we ever get here, be consistent with 2.0/3.5 behavior: just add an initial
|
||||
// slash to the string and treat it as a path:
|
||||
uriString = "/" + uriString;
|
||||
}
|
||||
}
|
||||
|
||||
// Find end of path: The path is terminated by
|
||||
// - the first '?' character
|
||||
// - the first '#' character: This is never the case here, since http.sys won't accept
|
||||
// Uris containing fragments. Also, RFC2616 doesn't allow fragments in request Uris.
|
||||
// - end of Uri string
|
||||
int queryIndex = uriString.IndexOf('?');
|
||||
if (queryIndex == -1)
|
||||
{
|
||||
queryIndex = uriString.Length;
|
||||
}
|
||||
|
||||
// will always return a != null string.
|
||||
return AddSlashToAsteriskOnlyPath(uriString.Substring(pathStartIndex, queryIndex - pathStartIndex));
|
||||
}
|
||||
|
||||
private static string AddSlashToAsteriskOnlyPath(string path)
|
||||
{
|
||||
Debug.Assert(path != null, "'path' must not be null");
|
||||
|
||||
// If a request like "OPTIONS * HTTP/1.1" is sent to the listener, then the request Uri
|
||||
// should be "http[s]://server[:port]/*" to be compatible with pre-4.0 behavior.
|
||||
if ((path.Length == 1) && (path[0] == '*'))
|
||||
{
|
||||
return "/*";
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
private enum ParsingResult
|
||||
{
|
||||
Success,
|
||||
InvalidString,
|
||||
EncodingError
|
||||
}
|
||||
|
||||
private enum EncodingType
|
||||
{
|
||||
Primary,
|
||||
Secondary
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,6 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.Net;
|
||||
using MediaBrowser.Model.System;
|
||||
|
||||
namespace SocketHttpListener.Net
|
||||
@@ -50,18 +49,18 @@ namespace SocketHttpListener.Net
|
||||
private bool _ignore_errors;
|
||||
private bool _trailer_sent;
|
||||
private Stream _stream;
|
||||
private readonly IMemoryStreamFactory _memoryStreamFactory;
|
||||
private readonly IStreamHelper _streamHelper;
|
||||
private readonly Socket _socket;
|
||||
private readonly bool _supportsDirectSocketAccess;
|
||||
private readonly IEnvironmentInfo _environment;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
internal HttpResponseStream(Stream stream, HttpListenerResponse response, bool ignore_errors, IMemoryStreamFactory memoryStreamFactory, Socket socket, bool supportsDirectSocketAccess, IEnvironmentInfo environment, IFileSystem fileSystem, ILogger logger)
|
||||
internal HttpResponseStream(Stream stream, HttpListenerResponse response, bool ignore_errors, IStreamHelper streamHelper, Socket socket, bool supportsDirectSocketAccess, IEnvironmentInfo environment, IFileSystem fileSystem, ILogger logger)
|
||||
{
|
||||
_response = response;
|
||||
_ignore_errors = ignore_errors;
|
||||
_memoryStreamFactory = memoryStreamFactory;
|
||||
_streamHelper = streamHelper;
|
||||
_socket = socket;
|
||||
_supportsDirectSocketAccess = supportsDirectSocketAccess;
|
||||
_environment = environment;
|
||||
@@ -136,7 +135,7 @@ namespace SocketHttpListener.Net
|
||||
//{
|
||||
// if (_response.HeadersSent)
|
||||
// return null;
|
||||
// var ms = _memoryStreamFactory.CreateNew();
|
||||
// var ms = CreateNew();
|
||||
// _response.SendHeaders(closing, ms);
|
||||
// return ms;
|
||||
//}
|
||||
@@ -287,64 +286,9 @@ namespace SocketHttpListener.Net
|
||||
|
||||
public Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken)
|
||||
{
|
||||
//if (_supportsDirectSocketAccess && offset == 0 && count == 0 && !_response.SendChunked)
|
||||
//{
|
||||
// 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(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);
|
||||
}
|
||||
|
||||
_stream.Flush();
|
||||
|
||||
_logger.Info("Socket sending file {0}", path);
|
||||
|
||||
var taskCompletion = new TaskCompletionSource<bool>();
|
||||
|
||||
Action<IAsyncResult> callback = callbackResult =>
|
||||
{
|
||||
try
|
||||
{
|
||||
_socket.EndSendFile(callbackResult);
|
||||
taskCompletion.TrySetResult(true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
taskCompletion.TrySetException(ex);
|
||||
}
|
||||
};
|
||||
|
||||
var result = _socket.BeginSendFile(path, preBuffer, _emptyBuffer, TransmitFileOptions.UseDefaultWorkerThread, new AsyncCallback(callback), null);
|
||||
|
||||
if (result.CompletedSynchronously)
|
||||
{
|
||||
callback(result);
|
||||
}
|
||||
|
||||
cancellationToken.Register(() => taskCompletion.TrySetCanceled());
|
||||
|
||||
return taskCompletion.Task;
|
||||
}
|
||||
|
||||
const int StreamCopyToBufferSize = 81920;
|
||||
private async Task TransmitFileManaged(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -375,71 +319,11 @@ namespace SocketHttpListener.Net
|
||||
|
||||
if (count > 0)
|
||||
{
|
||||
if (allowAsync)
|
||||
{
|
||||
await CopyToInternalAsync(fs, targetStream, count, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await CopyToInternalAsyncWithSyncRead(fs, targetStream, count, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
await _streamHelper.CopyToAsync(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;
|
||||
await fs.CopyToAsync(targetStream, StreamCopyToBufferSize, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,50 +4,50 @@ using MediaBrowser.Model.Net;
|
||||
|
||||
namespace SocketHttpListener.Net
|
||||
{
|
||||
sealed class ListenerPrefix
|
||||
internal sealed class ListenerPrefix
|
||||
{
|
||||
string original;
|
||||
string host;
|
||||
ushort port;
|
||||
string path;
|
||||
bool secure;
|
||||
IPAddress[] addresses;
|
||||
public HttpListener Listener;
|
||||
private string _original;
|
||||
private string _host;
|
||||
private ushort _port;
|
||||
private string _path;
|
||||
private bool _secure;
|
||||
private IPAddress[] _addresses;
|
||||
internal HttpListener _listener;
|
||||
|
||||
public ListenerPrefix(string prefix)
|
||||
{
|
||||
this.original = prefix;
|
||||
_original = prefix;
|
||||
Parse(prefix);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return original;
|
||||
return _original;
|
||||
}
|
||||
|
||||
public IPAddress[] Addresses
|
||||
{
|
||||
get { return addresses; }
|
||||
set { addresses = value; }
|
||||
get { return _addresses; }
|
||||
set { _addresses = value; }
|
||||
}
|
||||
public bool Secure
|
||||
{
|
||||
get { return secure; }
|
||||
get { return _secure; }
|
||||
}
|
||||
|
||||
public string Host
|
||||
{
|
||||
get { return host; }
|
||||
get { return _host; }
|
||||
}
|
||||
|
||||
public int Port
|
||||
{
|
||||
get { return (int)port; }
|
||||
get { return _port; }
|
||||
}
|
||||
|
||||
public string Path
|
||||
{
|
||||
get { return path; }
|
||||
get { return _path; }
|
||||
}
|
||||
|
||||
// Equals and GetHashCode are required to detect duplicates in HttpListenerPrefixCollection.
|
||||
@@ -57,92 +57,46 @@ namespace SocketHttpListener.Net
|
||||
if (other == null)
|
||||
return false;
|
||||
|
||||
return (original == other.original);
|
||||
return (_original == other._original);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return original.GetHashCode();
|
||||
return _original.GetHashCode();
|
||||
}
|
||||
|
||||
void Parse(string uri)
|
||||
private void Parse(string uri)
|
||||
{
|
||||
ushort default_port = 80;
|
||||
if (uri.StartsWith("https://"))
|
||||
{
|
||||
default_port = 443;
|
||||
secure = true;
|
||||
_secure = true;
|
||||
}
|
||||
|
||||
int length = uri.Length;
|
||||
int start_host = uri.IndexOf(':') + 3;
|
||||
if (start_host >= length)
|
||||
throw new ArgumentException("No host specified.");
|
||||
throw new ArgumentException("net_listener_host");
|
||||
|
||||
int colon = uri.IndexOf(':', start_host, length - start_host);
|
||||
int root;
|
||||
if (colon > 0)
|
||||
{
|
||||
host = uri.Substring(start_host, colon - start_host);
|
||||
_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);
|
||||
_port = (ushort)int.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);
|
||||
_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 '/'");
|
||||
if (_path.Length != 1)
|
||||
_path = _path.Substring(0, _path.Length - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
using System;
|
||||
using System.Net.Sockets;
|
||||
using MediaBrowser.Model.Logging;
|
||||
|
||||
namespace SocketHttpListener.Net
|
||||
{
|
||||
public class SocketAcceptor
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly Socket _originalSocket;
|
||||
private readonly Func<bool> _isClosed;
|
||||
private readonly Action<Socket> _onAccept;
|
||||
|
||||
public SocketAcceptor(ILogger logger, Socket originalSocket, Action<Socket> onAccept, Func<bool> isClosed)
|
||||
{
|
||||
if (logger == null)
|
||||
{
|
||||
throw new ArgumentNullException("logger");
|
||||
}
|
||||
if (originalSocket == null)
|
||||
{
|
||||
throw new ArgumentNullException("originalSocket");
|
||||
}
|
||||
if (onAccept == null)
|
||||
{
|
||||
throw new ArgumentNullException("onAccept");
|
||||
}
|
||||
if (isClosed == null)
|
||||
{
|
||||
throw new ArgumentNullException("isClosed");
|
||||
}
|
||||
|
||||
_logger = logger;
|
||||
_originalSocket = originalSocket;
|
||||
_isClosed = isClosed;
|
||||
_onAccept = onAccept;
|
||||
}
|
||||
|
||||
public void StartAccept()
|
||||
{
|
||||
Socket dummy = null;
|
||||
StartAccept(null, ref dummy);
|
||||
}
|
||||
|
||||
public void StartAccept(SocketAsyncEventArgs acceptEventArg, ref Socket accepted)
|
||||
{
|
||||
if (acceptEventArg == null)
|
||||
{
|
||||
acceptEventArg = new SocketAsyncEventArgs();
|
||||
acceptEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(AcceptEventArg_Completed);
|
||||
}
|
||||
else
|
||||
{
|
||||
// acceptSocket must be cleared since the context object is being reused
|
||||
acceptEventArg.AcceptSocket = null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
bool willRaiseEvent = _originalSocket.AcceptAsync(acceptEventArg);
|
||||
|
||||
if (!willRaiseEvent)
|
||||
{
|
||||
ProcessAccept(acceptEventArg);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (accepted != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
#if NET46
|
||||
accepted.Close();
|
||||
#else
|
||||
accepted.Dispose();
|
||||
#endif
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
accepted = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This method is the callback method associated with Socket.AcceptAsync
|
||||
// operations and is invoked when an accept operation is complete
|
||||
//
|
||||
void AcceptEventArg_Completed(object sender, SocketAsyncEventArgs e)
|
||||
{
|
||||
ProcessAccept(e);
|
||||
}
|
||||
|
||||
private void ProcessAccept(SocketAsyncEventArgs e)
|
||||
{
|
||||
if (_isClosed())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// http://msdn.microsoft.com/en-us/library/system.net.sockets.acceptSocket.acceptasync%28v=vs.110%29.aspx
|
||||
// Under certain conditions ConnectionReset can occur
|
||||
// Need to attept to re-accept
|
||||
if (e.SocketError == SocketError.ConnectionReset)
|
||||
{
|
||||
_logger.Error("SocketError.ConnectionReset reported. Attempting to re-accept.");
|
||||
Socket dummy = null;
|
||||
StartAccept(e, ref dummy);
|
||||
return;
|
||||
}
|
||||
|
||||
var acceptSocket = e.AcceptSocket;
|
||||
if (acceptSocket != null)
|
||||
{
|
||||
//ProcessAccept(acceptSocket);
|
||||
_onAccept(acceptSocket);
|
||||
}
|
||||
|
||||
// Accept the next connection request
|
||||
StartAccept(e, ref acceptSocket);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SocketHttpListener.Net
|
||||
{
|
||||
internal class UriScheme
|
||||
internal static class UriScheme
|
||||
{
|
||||
public const string File = "file";
|
||||
public const string Ftp = "ftp";
|
||||
|
||||
@@ -35,8 +35,6 @@ namespace SocketHttpListener.Net
|
||||
};
|
||||
|
||||
static readonly Dictionary<string, HeaderInfo> headers;
|
||||
HeaderInfo? headerRestriction;
|
||||
HeaderInfo? headerConsistency;
|
||||
|
||||
static WebHeaderCollection()
|
||||
{
|
||||
@@ -108,7 +106,6 @@ namespace SocketHttpListener.Net
|
||||
if (name == null)
|
||||
throw new ArgumentNullException("name");
|
||||
|
||||
ThrowIfRestricted(name);
|
||||
this.AddWithoutValidate(name, value);
|
||||
}
|
||||
|
||||
@@ -237,7 +234,6 @@ namespace SocketHttpListener.Net
|
||||
if (!IsHeaderValue(value))
|
||||
throw new ArgumentException("invalid header value");
|
||||
|
||||
ThrowIfRestricted(name);
|
||||
base.Set(name, value);
|
||||
}
|
||||
|
||||
@@ -317,27 +313,6 @@ namespace SocketHttpListener.Net
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
@@ -83,48 +83,5 @@ namespace SocketHttpListener.Net
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
// The normal client header parser just casts bytes to chars (see GetString).
|
||||
// Check if those bytes were actually utf-8 instead of ASCII.
|
||||
// If not, just return the input value.
|
||||
internal static string DecodeUtf8FromString(string input)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(input))
|
||||
{
|
||||
return input;
|
||||
}
|
||||
|
||||
bool possibleUtf8 = false;
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
{
|
||||
if (input[i] > (char)255)
|
||||
{
|
||||
return input; // This couldn't have come from the wire, someone assigned it directly.
|
||||
}
|
||||
else if (input[i] > (char)127)
|
||||
{
|
||||
possibleUtf8 = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (possibleUtf8)
|
||||
{
|
||||
byte[] rawBytes = new byte[input.Length];
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
{
|
||||
if (input[i] > (char)255)
|
||||
{
|
||||
return input; // This couldn't have come from the wire, someone assigned it directly.
|
||||
}
|
||||
rawBytes[i] = (byte)input[i];
|
||||
}
|
||||
try
|
||||
{
|
||||
return s_utf8Decoder.GetString(rawBytes);
|
||||
}
|
||||
catch (ArgumentException) { } // Not actually Utf-8
|
||||
}
|
||||
return input;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,337 +12,87 @@ 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 readonly Uri _requestUri;
|
||||
private readonly QueryParamCollection _headers;
|
||||
private readonly CookieCollection _cookieCollection;
|
||||
private readonly IPrincipal _user;
|
||||
private readonly bool _isAuthenticated;
|
||||
private readonly bool _isLocal;
|
||||
private readonly bool _isSecureConnection;
|
||||
|
||||
private HttpListenerContext _context;
|
||||
private WebSocket _websocket;
|
||||
private readonly string _origin;
|
||||
private readonly IEnumerable<string> _secWebSocketProtocols;
|
||||
private readonly string _secWebSocketVersion;
|
||||
private readonly string _secWebSocketKey;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internal Constructors
|
||||
private readonly WebSocket _webSocket;
|
||||
|
||||
internal HttpListenerWebSocketContext(
|
||||
HttpListenerContext context, string protocol, ICryptoProvider cryptoProvider, IMemoryStreamFactory memoryStreamFactory)
|
||||
Uri requestUri,
|
||||
QueryParamCollection headers,
|
||||
CookieCollection cookieCollection,
|
||||
IPrincipal user,
|
||||
bool isAuthenticated,
|
||||
bool isLocal,
|
||||
bool isSecureConnection,
|
||||
string origin,
|
||||
IEnumerable<string> secWebSocketProtocols,
|
||||
string secWebSocketVersion,
|
||||
string secWebSocketKey,
|
||||
WebSocket webSocket)
|
||||
{
|
||||
_context = context;
|
||||
_websocket = new WebSocket(this, protocol, cryptoProvider, memoryStreamFactory);
|
||||
_cookieCollection = new CookieCollection();
|
||||
_cookieCollection.Add(cookieCollection);
|
||||
|
||||
//_headers = new NameValueCollection(headers);
|
||||
_headers = headers;
|
||||
_user = CopyPrincipal(user);
|
||||
|
||||
_requestUri = requestUri;
|
||||
_isAuthenticated = isAuthenticated;
|
||||
_isLocal = isLocal;
|
||||
_isSecureConnection = isSecureConnection;
|
||||
_origin = origin;
|
||||
_secWebSocketProtocols = secWebSocketProtocols;
|
||||
_secWebSocketVersion = secWebSocketVersion;
|
||||
_secWebSocketKey = secWebSocketKey;
|
||||
_webSocket = webSocket;
|
||||
}
|
||||
|
||||
#endregion
|
||||
public override Uri RequestUri => _requestUri;
|
||||
|
||||
#region Internal Properties
|
||||
public override QueryParamCollection Headers => _headers;
|
||||
|
||||
internal Stream Stream
|
||||
public override string Origin => _origin;
|
||||
|
||||
public override IEnumerable<string> SecWebSocketProtocols => _secWebSocketProtocols;
|
||||
|
||||
public override string SecWebSocketVersion => _secWebSocketVersion;
|
||||
|
||||
public override string SecWebSocketKey => _secWebSocketKey;
|
||||
|
||||
public override CookieCollection CookieCollection => _cookieCollection;
|
||||
|
||||
public override IPrincipal User => _user;
|
||||
|
||||
public override bool IsAuthenticated => _isAuthenticated;
|
||||
|
||||
public override bool IsLocal => _isLocal;
|
||||
|
||||
public override bool IsSecureConnection => _isSecureConnection;
|
||||
|
||||
public override WebSocket WebSocket => _webSocket;
|
||||
|
||||
private static IPrincipal CopyPrincipal(IPrincipal user)
|
||||
{
|
||||
get
|
||||
if (user != null)
|
||||
{
|
||||
return _context.Connection.Stream;
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
#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 IPEndPoint 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 IPEndPoint 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
|
||||
}
|
||||
}
|
||||
|
||||
84
SocketHttpListener/Net/WebSockets/HttpWebSocket.Managed.cs
Normal file
84
SocketHttpListener/Net/WebSockets/HttpWebSocket.Managed.cs
Normal file
@@ -0,0 +1,84 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SocketHttpListener.Net.WebSockets
|
||||
{
|
||||
internal static partial class HttpWebSocket
|
||||
{
|
||||
private const string SupportedVersion = "13";
|
||||
|
||||
internal static async Task<HttpListenerWebSocketContext> AcceptWebSocketAsyncCore(HttpListenerContext context,
|
||||
string subProtocol,
|
||||
int receiveBufferSize,
|
||||
TimeSpan keepAliveInterval,
|
||||
ArraySegment<byte>? internalBuffer = null)
|
||||
{
|
||||
ValidateOptions(subProtocol, receiveBufferSize, MinSendBufferSize, keepAliveInterval);
|
||||
|
||||
// get property will create a new response if one doesn't exist.
|
||||
HttpListenerResponse response = context.Response;
|
||||
HttpListenerRequest request = context.Request;
|
||||
ValidateWebSocketHeaders(context);
|
||||
|
||||
string secWebSocketVersion = request.Headers[HttpKnownHeaderNames.SecWebSocketVersion];
|
||||
|
||||
// Optional for non-browser client
|
||||
string origin = request.Headers[HttpKnownHeaderNames.Origin];
|
||||
|
||||
string[] secWebSocketProtocols = null;
|
||||
string outgoingSecWebSocketProtocolString;
|
||||
bool shouldSendSecWebSocketProtocolHeader =
|
||||
ProcessWebSocketProtocolHeader(
|
||||
request.Headers[HttpKnownHeaderNames.SecWebSocketProtocol],
|
||||
subProtocol,
|
||||
out outgoingSecWebSocketProtocolString);
|
||||
|
||||
if (shouldSendSecWebSocketProtocolHeader)
|
||||
{
|
||||
secWebSocketProtocols = new string[] { outgoingSecWebSocketProtocolString };
|
||||
response.Headers.Add(HttpKnownHeaderNames.SecWebSocketProtocol, outgoingSecWebSocketProtocolString);
|
||||
}
|
||||
|
||||
// negotiate the websocket key return value
|
||||
string secWebSocketKey = request.Headers[HttpKnownHeaderNames.SecWebSocketKey];
|
||||
string secWebSocketAccept = HttpWebSocket.GetSecWebSocketAcceptString(secWebSocketKey);
|
||||
|
||||
response.Headers.Add(HttpKnownHeaderNames.Connection, HttpKnownHeaderNames.Upgrade);
|
||||
response.Headers.Add(HttpKnownHeaderNames.Upgrade, WebSocketUpgradeToken);
|
||||
response.Headers.Add(HttpKnownHeaderNames.SecWebSocketAccept, secWebSocketAccept);
|
||||
|
||||
response.StatusCode = (int)HttpStatusCode.SwitchingProtocols; // HTTP 101
|
||||
response.StatusDescription = HttpStatusDescription.Get(HttpStatusCode.SwitchingProtocols);
|
||||
|
||||
HttpResponseStream responseStream = response.OutputStream as HttpResponseStream;
|
||||
|
||||
// Send websocket handshake headers
|
||||
await responseStream.WriteWebSocketHandshakeHeadersAsync().ConfigureAwait(false);
|
||||
|
||||
//WebSocket webSocket = WebSocket.CreateFromStream(context.Connection.ConnectedStream, isServer: true, subProtocol, keepAliveInterval);
|
||||
WebSocket webSocket = new WebSocket(subProtocol);
|
||||
|
||||
HttpListenerWebSocketContext webSocketContext = new HttpListenerWebSocketContext(
|
||||
request.Url,
|
||||
request.Headers,
|
||||
request.Cookies,
|
||||
context.User,
|
||||
request.IsAuthenticated,
|
||||
request.IsLocal,
|
||||
request.IsSecureConnection,
|
||||
origin,
|
||||
secWebSocketProtocols != null ? secWebSocketProtocols : Array.Empty<string>(),
|
||||
secWebSocketVersion,
|
||||
secWebSocketKey,
|
||||
webSocket);
|
||||
|
||||
webSocket.SetContext(webSocketContext, context.Connection.Close, context.Connection.Stream);
|
||||
|
||||
return webSocketContext;
|
||||
}
|
||||
|
||||
private const bool WebSocketsSupported = true;
|
||||
}
|
||||
}
|
||||
160
SocketHttpListener/Net/WebSockets/HttpWebSocket.cs
Normal file
160
SocketHttpListener/Net/WebSockets/HttpWebSocket.cs
Normal file
@@ -0,0 +1,160 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading;
|
||||
|
||||
namespace SocketHttpListener.Net.WebSockets
|
||||
{
|
||||
internal static partial class HttpWebSocket
|
||||
{
|
||||
internal const string SecWebSocketKeyGuid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||
internal const string WebSocketUpgradeToken = "websocket";
|
||||
internal const int DefaultReceiveBufferSize = 16 * 1024;
|
||||
internal const int DefaultClientSendBufferSize = 16 * 1024;
|
||||
|
||||
[SuppressMessage("Microsoft.Security", "CA5350", Justification = "SHA1 used only for hashing purposes, not for crypto.")]
|
||||
internal static string GetSecWebSocketAcceptString(string secWebSocketKey)
|
||||
{
|
||||
string retVal;
|
||||
|
||||
// SHA1 used only for hashing purposes, not for crypto. Check here for FIPS compat.
|
||||
using (SHA1 sha1 = SHA1.Create())
|
||||
{
|
||||
string acceptString = string.Concat(secWebSocketKey, HttpWebSocket.SecWebSocketKeyGuid);
|
||||
byte[] toHash = Encoding.UTF8.GetBytes(acceptString);
|
||||
retVal = Convert.ToBase64String(sha1.ComputeHash(toHash));
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
// return value here signifies if a Sec-WebSocket-Protocol header should be returned by the server.
|
||||
internal static bool ProcessWebSocketProtocolHeader(string clientSecWebSocketProtocol,
|
||||
string subProtocol,
|
||||
out string acceptProtocol)
|
||||
{
|
||||
acceptProtocol = string.Empty;
|
||||
if (string.IsNullOrEmpty(clientSecWebSocketProtocol))
|
||||
{
|
||||
// client hasn't specified any Sec-WebSocket-Protocol header
|
||||
if (subProtocol != null)
|
||||
{
|
||||
// If the server specified _anything_ this isn't valid.
|
||||
throw new WebSocketException("UnsupportedProtocol");
|
||||
}
|
||||
// Treat empty and null from the server as the same thing here, server should not send headers.
|
||||
return false;
|
||||
}
|
||||
|
||||
// here, we know the client specified something and it's non-empty.
|
||||
|
||||
if (subProtocol == null)
|
||||
{
|
||||
// client specified some protocols, server specified 'null'. So server should send headers.
|
||||
return true;
|
||||
}
|
||||
|
||||
// here, we know that the client has specified something, it's not empty
|
||||
// and the server has specified exactly one protocol
|
||||
|
||||
string[] requestProtocols = clientSecWebSocketProtocol.Split(new char[] { ',' },
|
||||
StringSplitOptions.RemoveEmptyEntries);
|
||||
acceptProtocol = subProtocol;
|
||||
|
||||
// client specified protocols, serverOptions has exactly 1 non-empty entry. Check that
|
||||
// this exists in the list the client specified.
|
||||
for (int i = 0; i < requestProtocols.Length; i++)
|
||||
{
|
||||
string currentRequestProtocol = requestProtocols[i].Trim();
|
||||
if (string.Equals(acceptProtocol, currentRequestProtocol, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
throw new WebSocketException("net_WebSockets_AcceptUnsupportedProtocol");
|
||||
}
|
||||
|
||||
internal static void ValidateOptions(string subProtocol, int receiveBufferSize, int sendBufferSize, TimeSpan keepAliveInterval)
|
||||
{
|
||||
if (subProtocol != null)
|
||||
{
|
||||
WebSocketValidate.ValidateSubprotocol(subProtocol);
|
||||
}
|
||||
|
||||
if (receiveBufferSize < MinReceiveBufferSize)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("net_WebSockets_ArgumentOutOfRange_TooSmall");
|
||||
}
|
||||
|
||||
if (sendBufferSize < MinSendBufferSize)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("net_WebSockets_ArgumentOutOfRange_TooSmall");
|
||||
}
|
||||
|
||||
if (receiveBufferSize > MaxBufferSize)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("net_WebSockets_ArgumentOutOfRange_TooBig");
|
||||
}
|
||||
|
||||
if (sendBufferSize > MaxBufferSize)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("net_WebSockets_ArgumentOutOfRange_TooBig");
|
||||
}
|
||||
|
||||
if (keepAliveInterval < Timeout.InfiniteTimeSpan) // -1 millisecond
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("net_WebSockets_ArgumentOutOfRange_TooSmall");
|
||||
}
|
||||
}
|
||||
|
||||
internal const int MinSendBufferSize = 16;
|
||||
internal const int MinReceiveBufferSize = 256;
|
||||
internal const int MaxBufferSize = 64 * 1024;
|
||||
|
||||
private static void ValidateWebSocketHeaders(HttpListenerContext context)
|
||||
{
|
||||
if (!WebSocketsSupported)
|
||||
{
|
||||
throw new PlatformNotSupportedException("net_WebSockets_UnsupportedPlatform");
|
||||
}
|
||||
|
||||
if (!context.Request.IsWebSocketRequest)
|
||||
{
|
||||
throw new WebSocketException("net_WebSockets_AcceptNotAWebSocket");
|
||||
}
|
||||
|
||||
string secWebSocketVersion = context.Request.Headers[HttpKnownHeaderNames.SecWebSocketVersion];
|
||||
if (string.IsNullOrEmpty(secWebSocketVersion))
|
||||
{
|
||||
throw new WebSocketException("net_WebSockets_AcceptHeaderNotFound");
|
||||
}
|
||||
|
||||
if (!string.Equals(secWebSocketVersion, SupportedVersion, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new WebSocketException("net_WebSockets_AcceptUnsupportedWebSocketVersion");
|
||||
}
|
||||
|
||||
string secWebSocketKey = context.Request.Headers[HttpKnownHeaderNames.SecWebSocketKey];
|
||||
bool isSecWebSocketKeyInvalid = string.IsNullOrWhiteSpace(secWebSocketKey);
|
||||
if (!isSecWebSocketKeyInvalid)
|
||||
{
|
||||
try
|
||||
{
|
||||
// key must be 16 bytes then base64-encoded
|
||||
isSecWebSocketKeyInvalid = Convert.FromBase64String(secWebSocketKey).Length != 16;
|
||||
}
|
||||
catch
|
||||
{
|
||||
isSecWebSocketKeyInvalid = true;
|
||||
}
|
||||
}
|
||||
if (isSecWebSocketKeyInvalid)
|
||||
{
|
||||
throw new WebSocketException("net_WebSockets_AcceptHeaderNotFound");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
31
SocketHttpListener/Net/WebSockets/WebSocketCloseStatus.cs
Normal file
31
SocketHttpListener/Net/WebSockets/WebSocketCloseStatus.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace SocketHttpListener.Net.WebSockets
|
||||
{
|
||||
public enum WebSocketCloseStatus
|
||||
{
|
||||
NormalClosure = 1000,
|
||||
EndpointUnavailable = 1001,
|
||||
ProtocolError = 1002,
|
||||
InvalidMessageType = 1003,
|
||||
Empty = 1005,
|
||||
// AbnormalClosure = 1006, // 1006 is reserved and should never be used by user
|
||||
InvalidPayloadData = 1007,
|
||||
PolicyViolation = 1008,
|
||||
MessageTooBig = 1009,
|
||||
MandatoryExtension = 1010,
|
||||
InternalServerError = 1011
|
||||
// TLSHandshakeFailed = 1015, // 1015 is reserved and should never be used by user
|
||||
|
||||
// 0 - 999 Status codes in the range 0-999 are not used.
|
||||
// 1000 - 1999 Status codes in the range 1000-1999 are reserved for definition by this protocol.
|
||||
// 2000 - 2999 Status codes in the range 2000-2999 are reserved for use by extensions.
|
||||
// 3000 - 3999 Status codes in the range 3000-3999 MAY be used by libraries and frameworks. The
|
||||
// interpretation of these codes is undefined by this protocol. End applications MUST
|
||||
// NOT use status codes in this range.
|
||||
// 4000 - 4999 Status codes in the range 4000-4999 MAY be used by application code. The interpretation
|
||||
// of these codes is undefined by this protocol.
|
||||
}
|
||||
}
|
||||
@@ -8,176 +8,19 @@ 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 QueryParamCollection Headers { get; }
|
||||
public abstract string Origin { get; }
|
||||
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 IPEndPoint 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 string SecWebSocketKey { get; }
|
||||
public abstract CookieCollection CookieCollection { get; }
|
||||
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 IPEndPoint 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 bool IsAuthenticated { get; }
|
||||
public abstract bool IsLocal { get; }
|
||||
public abstract bool IsSecureConnection { get; }
|
||||
public abstract WebSocket WebSocket { get; }
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
143
SocketHttpListener/Net/WebSockets/WebSocketValidate.cs
Normal file
143
SocketHttpListener/Net/WebSockets/WebSocketValidate.cs
Normal file
@@ -0,0 +1,143 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using MediaBrowser.Model.Net;
|
||||
using System.Globalization;
|
||||
using WebSocketState = System.Net.WebSockets.WebSocketState;
|
||||
|
||||
namespace SocketHttpListener.Net.WebSockets
|
||||
{
|
||||
internal static partial class WebSocketValidate
|
||||
{
|
||||
internal const int MaxControlFramePayloadLength = 123;
|
||||
private const int CloseStatusCodeAbort = 1006;
|
||||
private const int CloseStatusCodeFailedTLSHandshake = 1015;
|
||||
private const int InvalidCloseStatusCodesFrom = 0;
|
||||
private const int InvalidCloseStatusCodesTo = 999;
|
||||
private const string Separators = "()<>@,;:\\\"/[]?={} ";
|
||||
|
||||
internal static void ThrowIfInvalidState(WebSocketState currentState, bool isDisposed, WebSocketState[] validStates)
|
||||
{
|
||||
string validStatesText = string.Empty;
|
||||
|
||||
if (validStates != null && validStates.Length > 0)
|
||||
{
|
||||
foreach (WebSocketState validState in validStates)
|
||||
{
|
||||
if (currentState == validState)
|
||||
{
|
||||
// Ordering is important to maintain .NET 4.5 WebSocket implementation exception behavior.
|
||||
if (isDisposed)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(WebSocket));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
validStatesText = string.Join(", ", validStates);
|
||||
}
|
||||
|
||||
throw new WebSocketException("net_WebSockets_InvalidState");
|
||||
}
|
||||
|
||||
internal static void ValidateSubprotocol(string subProtocol)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(subProtocol))
|
||||
{
|
||||
throw new ArgumentException("net_WebSockets_InvalidEmptySubProtocol");
|
||||
}
|
||||
|
||||
string invalidChar = null;
|
||||
int i = 0;
|
||||
while (i < subProtocol.Length)
|
||||
{
|
||||
char ch = subProtocol[i];
|
||||
if (ch < 0x21 || ch > 0x7e)
|
||||
{
|
||||
invalidChar = string.Format(CultureInfo.InvariantCulture, "[{0}]", (int)ch);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!char.IsLetterOrDigit(ch) &&
|
||||
Separators.IndexOf(ch) >= 0)
|
||||
{
|
||||
invalidChar = ch.ToString();
|
||||
break;
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
if (invalidChar != null)
|
||||
{
|
||||
throw new ArgumentException("net_WebSockets_InvalidCharInProtocolString");
|
||||
}
|
||||
}
|
||||
|
||||
internal static void ValidateCloseStatus(WebSocketCloseStatus closeStatus, string statusDescription)
|
||||
{
|
||||
if (closeStatus == WebSocketCloseStatus.Empty && !string.IsNullOrEmpty(statusDescription))
|
||||
{
|
||||
throw new ArgumentException("net_WebSockets_ReasonNotNull");
|
||||
}
|
||||
|
||||
int closeStatusCode = (int)closeStatus;
|
||||
|
||||
if ((closeStatusCode >= InvalidCloseStatusCodesFrom &&
|
||||
closeStatusCode <= InvalidCloseStatusCodesTo) ||
|
||||
closeStatusCode == CloseStatusCodeAbort ||
|
||||
closeStatusCode == CloseStatusCodeFailedTLSHandshake)
|
||||
{
|
||||
// CloseStatus 1006 means Aborted - this will never appear on the wire and is reflected by calling WebSocket.Abort
|
||||
throw new ArgumentException("net_WebSockets_InvalidCloseStatusCode");
|
||||
}
|
||||
|
||||
int length = 0;
|
||||
if (!string.IsNullOrEmpty(statusDescription))
|
||||
{
|
||||
length = Encoding.UTF8.GetByteCount(statusDescription);
|
||||
}
|
||||
|
||||
if (length > MaxControlFramePayloadLength)
|
||||
{
|
||||
throw new ArgumentException("net_WebSockets_InvalidCloseStatusDescription");
|
||||
}
|
||||
}
|
||||
|
||||
internal static void ValidateArraySegment(ArraySegment<byte> arraySegment, string parameterName)
|
||||
{
|
||||
if (arraySegment.Array == null)
|
||||
{
|
||||
throw new ArgumentNullException(parameterName + "." + nameof(arraySegment.Array));
|
||||
}
|
||||
if (arraySegment.Offset < 0 || arraySegment.Offset > arraySegment.Array.Length)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(parameterName + "." + nameof(arraySegment.Offset));
|
||||
}
|
||||
if (arraySegment.Count < 0 || arraySegment.Count > (arraySegment.Array.Length - arraySegment.Offset))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(parameterName + "." + nameof(arraySegment.Count));
|
||||
}
|
||||
}
|
||||
|
||||
internal static void ValidateBuffer(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (buffer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(buffer));
|
||||
}
|
||||
|
||||
if (offset < 0 || offset > buffer.Length)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(offset));
|
||||
}
|
||||
|
||||
if (count < 0 || count > (buffer.Length - offset))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(count));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,5 +12,10 @@ namespace SocketHttpListener.Primitives
|
||||
{
|
||||
return Encoding.UTF8;
|
||||
}
|
||||
|
||||
public static Encoding GetDefaultEncoding()
|
||||
{
|
||||
return Encoding.UTF8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,120 +1,18 @@
|
||||
<?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')" />
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" />
|
||||
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\SharedVersion.cs"/>
|
||||
</ItemGroup>
|
||||
|
||||
<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</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>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
</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>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</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\BoundaryType.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\EntitySendFormat.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.Managed.cs" />
|
||||
<Compile Include="Net\HttpListenerResponse.cs" />
|
||||
<Compile Include="Net\HttpRequestStream.cs" />
|
||||
<Compile Include="Net\HttpRequestStream.Managed.cs" />
|
||||
<Compile Include="Net\HttpResponseStream.cs" />
|
||||
<Compile Include="Net\HttpResponseStream.Managed.cs" />
|
||||
<Compile Include="Net\HttpStatusCode.cs" />
|
||||
<Compile Include="Net\HttpStatusDescription.cs" />
|
||||
<Compile Include="Net\HttpStreamAsyncResult.cs" />
|
||||
<Compile Include="Net\HttpVersion.cs" />
|
||||
<Compile Include="Net\ListenerPrefix.cs" />
|
||||
<Compile Include="Net\SocketAcceptor.cs" />
|
||||
<Compile Include="Net\UriScheme.cs" />
|
||||
<Compile Include="Net\WebHeaderCollection.cs" />
|
||||
<Compile Include="Net\WebHeaderEncoding.cs" />
|
||||
<Compile Include="Net\WebSockets\HttpListenerWebSocketContext.cs" />
|
||||
<Compile Include="Net\WebSockets\WebSocketContext.cs" />
|
||||
<Compile Include="Opcode.cs" />
|
||||
<Compile Include="PayloadData.cs" />
|
||||
<Compile Include="Primitives\ITextEncoding.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Rsv.cs" />
|
||||
<Compile Include="SocketStream.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>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -11,6 +11,8 @@ using MediaBrowser.Model.IO;
|
||||
using SocketHttpListener.Net.WebSockets;
|
||||
using SocketHttpListener.Primitives;
|
||||
using HttpStatusCode = SocketHttpListener.Net.HttpStatusCode;
|
||||
using System.Net.Sockets;
|
||||
using WebSocketState = System.Net.WebSockets.WebSocketState;
|
||||
|
||||
namespace SocketHttpListener
|
||||
{
|
||||
@@ -30,7 +32,6 @@ namespace SocketHttpListener
|
||||
private CompressionMethod _compression;
|
||||
private WebSocketContext _context;
|
||||
private CookieCollection _cookies;
|
||||
private string _extensions;
|
||||
private AutoResetEvent _exitReceiving;
|
||||
private object _forConn;
|
||||
private object _forEvent;
|
||||
@@ -52,9 +53,6 @@ namespace SocketHttpListener
|
||||
private Stream _stream;
|
||||
private Uri _uri;
|
||||
private const string _version = "13";
|
||||
private readonly IMemoryStreamFactory _memoryStreamFactory;
|
||||
|
||||
private readonly ICryptoProvider _cryptoProvider;
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -67,44 +65,30 @@ namespace SocketHttpListener
|
||||
#region Internal Constructors
|
||||
|
||||
// As server
|
||||
internal WebSocket(HttpListenerWebSocketContext context, string protocol, ICryptoProvider cryptoProvider, IMemoryStreamFactory memoryStreamFactory)
|
||||
internal WebSocket(string protocol)
|
||||
{
|
||||
_protocol = protocol;
|
||||
}
|
||||
|
||||
public void SetContext(HttpListenerWebSocketContext context, Action closeContextFn, Stream stream)
|
||||
{
|
||||
_context = context;
|
||||
_protocol = protocol;
|
||||
_cryptoProvider = cryptoProvider;
|
||||
_memoryStreamFactory = memoryStreamFactory;
|
||||
|
||||
_closeContext = context.Close;
|
||||
_closeContext = closeContextFn;
|
||||
_secure = context.IsSecureConnection;
|
||||
_stream = context.Stream;
|
||||
_stream = stream;
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
public static TimeSpan DefaultKeepAliveInterval
|
||||
{
|
||||
// In the .NET Framework, this pulls the value from a P/Invoke. Here we just hardcode it to a reasonable default.
|
||||
get { return TimeSpan.FromSeconds(30); }
|
||||
}
|
||||
|
||||
#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>
|
||||
@@ -146,44 +130,6 @@ namespace SocketHttpListener
|
||||
|
||||
#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);
|
||||
@@ -193,20 +139,19 @@ namespace SocketHttpListener
|
||||
{
|
||||
lock (_forConn)
|
||||
{
|
||||
if (_readyState == WebSocketState.Closing || _readyState == WebSocketState.Closed)
|
||||
if (_readyState == WebSocketState.CloseSent || _readyState == WebSocketState.Closed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_readyState = WebSocketState.Closing;
|
||||
_readyState = WebSocketState.CloseSent;
|
||||
}
|
||||
|
||||
var e = new CloseEventArgs(payload);
|
||||
e.WasClean =
|
||||
closeHandshake(
|
||||
send ? WebSocketFrame.CreateCloseFrame(Mask.Unmask, payload).ToByteArray() : null,
|
||||
wait ? 1000 : 0,
|
||||
closeServerResources);
|
||||
wait ? 1000 : 0);
|
||||
|
||||
_readyState = WebSocketState.Closed;
|
||||
try
|
||||
@@ -219,14 +164,15 @@ namespace SocketHttpListener
|
||||
}
|
||||
}
|
||||
|
||||
private bool closeHandshake(byte[] frameAsBytes, int millisecondsTimeout, Action release)
|
||||
private bool closeHandshake(byte[] frameAsBytes, int millisecondsTimeout)
|
||||
{
|
||||
var sent = frameAsBytes != null && writeBytes(frameAsBytes);
|
||||
var received =
|
||||
millisecondsTimeout == 0 ||
|
||||
(sent && _exitReceiving != null && _exitReceiving.WaitOne(millisecondsTimeout));
|
||||
|
||||
release();
|
||||
closeServerResources();
|
||||
|
||||
if (_receivePong != null)
|
||||
{
|
||||
_receivePong.Dispose();
|
||||
@@ -250,7 +196,15 @@ namespace SocketHttpListener
|
||||
if (_closeContext == null)
|
||||
return;
|
||||
|
||||
_closeContext();
|
||||
try
|
||||
{
|
||||
_closeContext();
|
||||
}
|
||||
catch (SocketException)
|
||||
{
|
||||
// it could be unable to send the handshake response
|
||||
}
|
||||
|
||||
_closeContext = null;
|
||||
_stream = null;
|
||||
_context = null;
|
||||
@@ -321,26 +275,6 @@ namespace SocketHttpListener
|
||||
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)
|
||||
@@ -403,7 +337,10 @@ namespace SocketHttpListener
|
||||
{
|
||||
try
|
||||
{
|
||||
OnOpen.Emit(this, EventArgs.Empty);
|
||||
if (OnOpen != null)
|
||||
{
|
||||
OnOpen(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -463,7 +400,7 @@ namespace SocketHttpListener
|
||||
|
||||
private bool processFragments(WebSocketFrame first)
|
||||
{
|
||||
using (var buff = _memoryStreamFactory.CreateNew())
|
||||
using (var buff = new MemoryStream())
|
||||
{
|
||||
buff.WriteBytes(first.PayloadData.ApplicationData);
|
||||
if (!concatenateFragmentsInto(buff))
|
||||
@@ -691,23 +628,6 @@ namespace SocketHttpListener
|
||||
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
|
||||
@@ -715,7 +635,7 @@ namespace SocketHttpListener
|
||||
_stream.Write(data, 0, data.Length);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -728,9 +648,9 @@ namespace SocketHttpListener
|
||||
// As server
|
||||
internal void Close(HttpResponse response)
|
||||
{
|
||||
_readyState = WebSocketState.Closing;
|
||||
|
||||
_readyState = WebSocketState.CloseSent;
|
||||
sendHttpResponse(response);
|
||||
|
||||
closeServerResources();
|
||||
|
||||
_readyState = WebSocketState.Closed;
|
||||
@@ -747,11 +667,8 @@ namespace SocketHttpListener
|
||||
{
|
||||
try
|
||||
{
|
||||
if (acceptHandshake())
|
||||
{
|
||||
_readyState = WebSocketState.Open;
|
||||
open();
|
||||
}
|
||||
_readyState = WebSocketState.Open;
|
||||
open();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -759,15 +676,6 @@ namespace SocketHttpListener
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
@@ -830,18 +738,20 @@ namespace SocketHttpListener
|
||||
/// <param name="data">
|
||||
/// An array of <see cref="byte"/> that represents the binary data to send.
|
||||
/// </param>
|
||||
/// An Action<bool> 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 (data == null)
|
||||
{
|
||||
throw new ArgumentNullException("data");
|
||||
}
|
||||
|
||||
var msg = _readyState.CheckIfOpen();
|
||||
if (msg != null)
|
||||
{
|
||||
throw new Exception(msg);
|
||||
}
|
||||
|
||||
return sendAsync(Opcode.Binary, _memoryStreamFactory.CreateNew(data));
|
||||
return sendAsync(Opcode.Binary, new MemoryStream(data));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -853,18 +763,20 @@ namespace SocketHttpListener
|
||||
/// <param name="data">
|
||||
/// A <see cref="string"/> that represents the text data to send.
|
||||
/// </param>
|
||||
/// An Action<bool> 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 (data == null)
|
||||
{
|
||||
throw new ArgumentNullException("data");
|
||||
}
|
||||
|
||||
var msg = _readyState.CheckIfOpen();
|
||||
if (msg != null)
|
||||
{
|
||||
throw new Exception(msg);
|
||||
}
|
||||
|
||||
return sendAsync(Opcode.Text, _memoryStreamFactory.CreateNew(Encoding.UTF8.GetBytes(data)));
|
||||
return sendAsync(Opcode.Text, new MemoryStream(Encoding.UTF8.GetBytes(data)));
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -880,7 +792,6 @@ namespace SocketHttpListener
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
Close(CloseStatusCode.Away, null);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user