Begin removing System.Net sources

This commit is contained in:
Claus Vium 2019-02-21 10:07:21 +01:00
parent 968e282c90
commit 4db31acff9
54 changed files with 264 additions and 7013 deletions

View File

@ -327,7 +327,7 @@ namespace Emby.Server.Implementations.HttpClientManager
}
httpWebRequest.ContentType = contentType;
httpWebRequest.ContentLength = bytes.Length;
// httpWebRequest.ContentLength = bytes.Length;
(await httpWebRequest.GetRequestStreamAsync().ConfigureAwait(false)).Write(bytes, 0, bytes.Length);
}
catch (Exception ex)

View File

@ -5,6 +5,7 @@ using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Emby.Server.Implementations.IO;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Services;
using Microsoft.Extensions.Logging;
@ -14,6 +15,7 @@ namespace Emby.Server.Implementations.HttpServer
public class FileWriter : IHttpResult
{
private ILogger Logger { get; set; }
public IFileSystem FileSystem { get; }
private string RangeHeader { get; set; }
private bool IsHeadRequest { get; set; }
@ -51,6 +53,7 @@ namespace Emby.Server.Implementations.HttpServer
Path = path;
Logger = logger;
FileSystem = fileSystem;
RangeHeader = rangeHeader;
Headers["Content-Type"] = contentType;
@ -60,7 +63,8 @@ namespace Emby.Server.Implementations.HttpServer
if (string.IsNullOrWhiteSpace(rangeHeader))
{
Headers["Content-Length"] = TotalContentLength.ToString(UsCulture);
// TODO
//Headers["Content-Length"] = TotalContentLength.ToString(UsCulture);
StatusCode = HttpStatusCode.OK;
}
else
@ -95,7 +99,7 @@ namespace Emby.Server.Implementations.HttpServer
// Content-Length is the length of what we're serving, not the original content
var lengthString = RangeLength.ToString(UsCulture);
Headers["Content-Length"] = lengthString;
// TODO Headers["Content-Length"] = lengthString;
var rangeString = string.Format("bytes {0}-{1}/{2}", RangeStart, RangeEnd, TotalContentLength);
Headers["Content-Range"] = rangeString;
@ -174,12 +178,12 @@ namespace Emby.Server.Implementations.HttpServer
}
//var count = FileShare == FileShareMode.ReadWrite ? TotalContentLength : 0;
await response.TransmitFile(path, 0, 0, FileShare, cancellationToken).ConfigureAwait(false);
// TODO not DI friendly lol
await response.TransmitFile(path, 0, 0, FileShare, FileSystem, new StreamHelper(), cancellationToken).ConfigureAwait(false);
return;
}
await response.TransmitFile(path, RangeStart, RangeLength, FileShare, cancellationToken).ConfigureAwait(false);
// TODO not DI friendly lol
await response.TransmitFile(path, RangeStart, RangeLength, FileShare, FileSystem, new StreamHelper(), cancellationToken).ConfigureAwait(false);
}
finally
{

View File

@ -644,6 +644,9 @@ namespace Emby.Server.Implementations.HttpServer
{
var bOutput = Encoding.UTF8.GetBytes(text);
response.SetContentLength(bOutput.Length);
// TODO
response.Headers.Remove("Content-Length"); // DO NOT SET THIS, IT'S DONE AUTOMATICALLY BECAUSE MS ARE NOT STUPID
response.SendChunked = true;
return response.OutputStream.WriteAsync(bOutput, 0, bOutput.Length);
}

View File

@ -592,7 +592,7 @@ namespace Emby.Server.Implementations.HttpServer
{
if (totalContentLength.HasValue)
{
responseHeaders["Content-Length"] = totalContentLength.Value.ToString(UsCulture);
// TODO responseHeaders["Content-Length"] = totalContentLength.Value.ToString(UsCulture);
}
if (isHeadRequest)

View File

@ -96,7 +96,7 @@ namespace Emby.Server.Implementations.HttpServer
RangeLength = 1 + RangeEnd - RangeStart;
// Content-Length is the length of what we're serving, not the original content
Headers["Content-Length"] = RangeLength.ToString(UsCulture);
// TODO Headers["Content-Length"] = RangeLength.ToString(UsCulture);
Headers["Content-Range"] = string.Format("bytes {0}-{1}/{2}", RangeStart, RangeEnd, TotalContentLength);
if (RangeStart > 0 && SourceStream.CanSeek)

View File

@ -25,7 +25,7 @@ namespace Emby.Server.Implementations.HttpServer
public void FilterResponse(IRequest req, IResponse res, object dto)
{
// Try to prevent compatibility view
res.AddHeader("Access-Control-Allow-Headers", "Accept, Accept-Language, Authorization, Cache-Control, Content-Disposition, Content-Encoding, Content-Language, Content-Length, Content-MD5, Content-Range, Content-Type, Date, Host, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, Origin, OriginToken, Pragma, Range, Slug, Transfer-Encoding, Want-Digest, X-MediaBrowser-Token, X-Emby-Authorization");
res.AddHeader("Access-Control-Allow-Headers", "Accept, Accept-Language, Authorization, Cache-Control, Content-Disposition, Content-Encoding, Content-Language, Content-MD5, Content-Range, Content-Type, Date, Host, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, Origin, OriginToken, Pragma, Range, Slug, Transfer-Encoding, Want-Digest, X-MediaBrowser-Token, X-Emby-Authorization");
res.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS");
res.AddHeader("Access-Control-Allow-Origin", "*");

View File

@ -56,7 +56,7 @@ namespace Emby.Server.Implementations.HttpServer
if (source.CanSeek)
{
Headers["Content-Length"] = source.Length.ToString(UsCulture);
// TODO Headers["Content-Length"] = source.Length.ToString(UsCulture);
}
}
@ -77,7 +77,7 @@ namespace Emby.Server.Implementations.HttpServer
Headers["Content-Type"] = contentType;
Headers["Content-Length"] = contentLength.ToString(UsCulture);
// TODO Headers["Content-Length"] = contentLength.ToString(UsCulture);
}
public async Task WriteToAsync(Stream responseStream, CancellationToken cancellationToken)

View File

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Security.Cryptography.X509Certificates;
using System.Net;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using Emby.Server.Implementations.HttpServer;
@ -13,9 +14,8 @@ using MediaBrowser.Model.Net;
using MediaBrowser.Model.Services;
using MediaBrowser.Model.System;
using Microsoft.Extensions.Logging;
using SocketHttpListener.Net;
namespace Jellyfin.Server.SocketSharp
namespace Jellyfin.Server.SocketSharp
{
public class WebSocketSharpListener : IHttpListener
{
@ -67,24 +67,44 @@ namespace Jellyfin.Server.SocketSharp
public void Start(IEnumerable<string> urlPrefixes)
{
// TODO
//if (_listener == null)
//{
// _listener = new HttpListener(_logger, _cryptoProvider, _socketFactory, _streamHelper, _fileSystem, _environment);
//}
//_listener.EnableDualMode = _enableDualMode;
//if (_certificate != null)
//{
// _listener.LoadCert(_certificate);
//}
//_logger.LogInformation("Adding HttpListener prefixes {Prefixes}", urlPrefixes);
//_listener.Prefixes.AddRange(urlPrefixes);
//_listener.OnContext = async c => await InitTask(c, _disposeCancellationToken).ConfigureAwait(false);
//_listener.Start();
if (_listener == null)
{
_listener = new HttpListener(_logger, _cryptoProvider, _socketFactory, _streamHelper, _fileSystem, _environment);
}
_listener.EnableDualMode = _enableDualMode;
if (_certificate != null)
{
_listener.LoadCert(_certificate);
_listener = new HttpListener();
}
_logger.LogInformation("Adding HttpListener prefixes {Prefixes}", urlPrefixes);
_listener.Prefixes.AddRange(urlPrefixes);
_listener.OnContext = async c => await InitTask(c, _disposeCancellationToken).ConfigureAwait(false);
//foreach (var urlPrefix in urlPrefixes)
//{
// _listener.Prefixes.Add(urlPrefix);
//}
_listener.Prefixes.Add("http://localhost:8096/");
_listener.Start();
// TODO how to do this in netcore?
_listener.BeginGetContext(async c => await InitTask(c, _disposeCancellationToken).ConfigureAwait(false),
null);
}
private static void LogRequest(ILogger logger, HttpListenerRequest request)
@ -98,8 +118,10 @@ namespace Jellyfin.Server.SocketSharp
request.UserAgent ?? string.Empty);
}
private Task InitTask(HttpListenerContext context, CancellationToken cancellationToken)
private Task InitTask(IAsyncResult asyncResult, CancellationToken cancellationToken)
{
var context = _listener.EndGetContext(asyncResult);
_listener.BeginGetContext(async c => await InitTask(c, _disposeCancellationToken).ConfigureAwait(false), null);
IHttpRequest httpReq = null;
var request = context.Request;
@ -134,7 +156,7 @@ namespace Jellyfin.Server.SocketSharp
var endpoint = ctx.Request.RemoteEndPoint.ToString();
var url = ctx.Request.RawUrl;
var queryString = ctx.Request.QueryString;
var queryString = new QueryParamCollection(ctx.Request.QueryString);
var connectingArgs = new WebSocketConnectingEventArgs
{
@ -153,7 +175,7 @@ namespace Jellyfin.Server.SocketSharp
if (WebSocketConnected != null)
{
var socket = new SharpWebSocket(webSocketContext.WebSocket, _logger);
SharpWebSocket socket = null; //new SharpWebSocket(webSocketContext.WebSocket, _logger);
await socket.ConnectAsServerAsync().ConfigureAwait(false);
WebSocketConnected(new WebSocketConnectEventArgs

View File

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net;
using System.Text;
using Emby.Server.Implementations.HttpServer;
using MediaBrowser.Model.Services;
@ -438,7 +439,7 @@ namespace Jellyfin.Server.SocketSharp
public string UserAgent => request.UserAgent;
public QueryParamCollection Headers => request.Headers;
public QueryParamCollection Headers => new QueryParamCollection(request.Headers);
private QueryParamCollection queryString;
public QueryParamCollection QueryString => queryString ?? (queryString = MyHttpUtility.ParseQueryString(request.Url.Query));

View File

@ -3,13 +3,14 @@ using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Emby.Server.Implementations;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Services;
using Microsoft.Extensions.Logging;
using HttpListenerResponse = SocketHttpListener.Net.HttpListenerResponse;
using IHttpResponse = MediaBrowser.Model.Services.IHttpResponse;
using IRequest = MediaBrowser.Model.Services.IRequest;
@ -53,7 +54,7 @@ namespace Jellyfin.Server.SocketSharp
set => _response.ContentType = value;
}
public QueryParamCollection Headers => _response.Headers;
public QueryParamCollection Headers => new QueryParamCollection(_response.Headers);
private static string AsHeaderValue(Cookie cookie)
{
@ -152,7 +153,7 @@ namespace Jellyfin.Server.SocketSharp
// you can happily set the Content-Length header in Asp.Net
// but HttpListener will complain if you do - you have to set ContentLength64 on the response.
// workaround: HttpListener throws "The parameter is incorrect" exceptions when we try to set the Content-Length header
_response.ContentLength64 = contentLength;
//_response.ContentLength64 = contentLength;
}
public void SetCookie(Cookie cookie)
@ -172,10 +173,43 @@ namespace Jellyfin.Server.SocketSharp
public void ClearCookies()
{
}
public Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken)
const int StreamCopyToBufferSize = 81920;
public async Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, IFileSystem fileSystem, IStreamHelper streamHelper, CancellationToken cancellationToken)
{
return _response.TransmitFile(path, offset, count, fileShareMode, cancellationToken);
// TODO
// return _response.TransmitFile(path, offset, count, fileShareMode, cancellationToken);
var allowAsync = !RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
//if (count <= 0)
//{
// allowAsync = true;
//}
var fileOpenOptions = FileOpenOptions.SequentialScan;
if (allowAsync)
{
fileOpenOptions |= FileOpenOptions.Asynchronous;
}
// use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039
using (var fs = fileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, fileShareMode, fileOpenOptions))
{
if (offset > 0)
{
fs.Position = offset;
}
if (count > 0)
{
await streamHelper.CopyToAsync(fs, OutputStream, count, cancellationToken).ConfigureAwait(false);
}
else
{
await fs.CopyToAsync(OutputStream, StreamCopyToBufferSize, cancellationToken).ConfigureAwait(false);
}
}
}
}
}

View File

@ -324,7 +324,8 @@ namespace MediaBrowser.Api.Playback.Progressive
// Seeing cases of -1 here
if (response.ContentLength.HasValue && response.ContentLength.Value >= 0)
{
responseHeaders["Content-Length"] = response.ContentLength.Value.ToString(UsCulture);
// TODO
//responseHeaders["Content-Length"] = response.ContentLength.Value.ToString(UsCulture);
}
if (isHeadRequest)
@ -382,17 +383,18 @@ namespace MediaBrowser.Api.Playback.Progressive
var hasHeaders = streamResult as IHasHeaders;
if (hasHeaders != null)
{
if (contentLength.HasValue)
{
hasHeaders.Headers["Content-Length"] = contentLength.Value.ToString(CultureInfo.InvariantCulture);
}
else
{
// TODO
//if (contentLength.HasValue)
//{
// hasHeaders.Headers["Content-Length"] = contentLength.Value.ToString(CultureInfo.InvariantCulture);
//}
//else
//{
if (hasHeaders.Headers.ContainsKey("Content-Length"))
{
hasHeaders.Headers.Remove("Content-Length");
}
}
//}
}
return streamResult;

View File

@ -152,7 +152,7 @@ namespace MediaBrowser.Model.Services
QueryParamCollection Headers { get; }
Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken);
Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, IFileSystem fileSystem, IStreamHelper streamHelper, CancellationToken cancellationToken);
bool SendChunked { get; set; }
}

View File

@ -1,10 +1,13 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Net;
using MediaBrowser.Model.Dto;
namespace MediaBrowser.Model.Services
{
// Remove this garbage class, it's just a bastard copy of NameValueCollection
public class QueryParamCollection : List<NameValuePair>
{
public QueryParamCollection()
@ -20,6 +23,30 @@ namespace MediaBrowser.Model.Services
}
}
// TODO remove this shit
public QueryParamCollection(WebHeaderCollection webHeaderCollection)
{
foreach (var key in webHeaderCollection.AllKeys)
{
foreach (var value in webHeaderCollection.GetValues(key) ?? Array.Empty<string>())
{
Add(key, value);
}
}
}
// TODO remove this shit
public QueryParamCollection(NameValueCollection nameValueCollection)
{
foreach (var key in nameValueCollection.AllKeys)
{
foreach (var value in nameValueCollection.GetValues(key) ?? Array.Empty<string>())
{
Add(key, value);
}
}
}
private static StringComparison GetStringComparison()
{
return StringComparison.OrdinalIgnoreCase;
@ -50,6 +77,10 @@ namespace MediaBrowser.Model.Services
/// </summary>
public virtual void Add(string key, string value)
{
if (string.Equals(key, "content-length", StringComparison.OrdinalIgnoreCase))
{
return;
}
Add(new NameValuePair(key, value));
}

View File

@ -6,7 +6,6 @@ using System.Net;
using System.Text;
using System.Threading.Tasks;
using MediaBrowser.Model.Services;
using HttpStatusCode = SocketHttpListener.Net.HttpStatusCode;
using WebSocketState = System.Net.WebSockets.WebSocketState;
namespace SocketHttpListener

View File

@ -4,11 +4,10 @@ using System.Net;
using System.Text;
using MediaBrowser.Model.Services;
using SocketHttpListener.Net;
using HttpStatusCode = SocketHttpListener.Net.HttpStatusCode;
using HttpVersion = SocketHttpListener.Net.HttpVersion;
namespace SocketHttpListener
{
// TODO what is the point of this class?
internal class HttpResponse : HttpBase
{
#region Private Fields

View File

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

View File

@ -1,9 +0,0 @@
namespace SocketHttpListener.Net
{
internal class AuthenticationTypes
{
internal const string NTLM = "NTLM";
internal const string Negotiate = "Negotiate";
internal const string Basic = "Basic";
}
}

View File

@ -1,11 +0,0 @@
namespace SocketHttpListener.Net
{
internal enum BoundaryType
{
ContentLength = 0, // Content-Length: XXX
Chunked = 1, // Transfer-Encoding: chunked
Multipart = 3,
None = 4,
Invalid = 5,
}
}

View File

@ -1,385 +0,0 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net;
using System.Text;
namespace SocketHttpListener.Net
{
// Licensed to the .NET Foundation under one or more agreements.
// See the LICENSE file in the project root for more information.
//
// System.Net.ResponseStream
//
// Author:
// Gonzalo Paniagua Javier (gonzalo@novell.com)
//
// Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
internal sealed class ChunkStream
{
private enum State
{
None,
PartialSize,
Body,
BodyFinished,
Trailer
}
private class Chunk
{
public byte[] Bytes;
public int Offset;
public Chunk(byte[] chunk)
{
Bytes = chunk;
}
public int Read(byte[] buffer, int offset, int size)
{
int nread = (size > Bytes.Length - Offset) ? Bytes.Length - Offset : size;
Buffer.BlockCopy(Bytes, Offset, buffer, offset, nread);
Offset += nread;
return nread;
}
}
internal WebHeaderCollection _headers;
private int _chunkSize;
private int _chunkRead;
private int _totalWritten;
private State _state;
private StringBuilder _saved;
private bool _sawCR;
private bool _gotit;
private int _trailerState;
private List<Chunk> _chunks;
public ChunkStream(WebHeaderCollection headers)
{
_headers = headers;
_saved = new StringBuilder();
_chunks = new List<Chunk>();
_chunkSize = -1;
_totalWritten = 0;
}
public void ResetBuffer()
{
_chunkSize = -1;
_chunkRead = 0;
_totalWritten = 0;
_chunks.Clear();
}
public int Read(byte[] buffer, int offset, int size)
{
return ReadFromChunks(buffer, offset, size);
}
private int ReadFromChunks(byte[] buffer, int offset, int size)
{
int count = _chunks.Count;
int nread = 0;
var chunksForRemoving = new List<Chunk>(count);
for (int i = 0; i < count; i++)
{
var chunk = _chunks[i];
if (chunk.Offset == chunk.Bytes.Length)
{
chunksForRemoving.Add(chunk);
continue;
}
nread += chunk.Read(buffer, offset + nread, size - nread);
if (nread == size)
break;
}
foreach (var chunk in chunksForRemoving)
_chunks.Remove(chunk);
return nread;
}
public void Write(byte[] buffer, int offset, int size)
{
// Note, the logic here only works when offset is 0 here.
// Otherwise, it would treat "size" as the end offset instead of an actual byte count from offset.
if (offset < size)
InternalWrite(buffer, ref offset, size);
}
private void InternalWrite(byte[] buffer, ref int offset, int size)
{
if (_state == State.None || _state == State.PartialSize)
{
_state = GetChunkSize(buffer, ref offset, size);
if (_state == State.PartialSize)
return;
_saved.Length = 0;
_sawCR = false;
_gotit = false;
}
if (_state == State.Body && offset < size)
{
_state = ReadBody(buffer, ref offset, size);
if (_state == State.Body)
return;
}
if (_state == State.BodyFinished && offset < size)
{
_state = ReadCRLF(buffer, ref offset, size);
if (_state == State.BodyFinished)
return;
_sawCR = false;
}
if (_state == State.Trailer && offset < size)
{
_state = ReadTrailer(buffer, ref offset, size);
if (_state == State.Trailer)
return;
_saved.Length = 0;
_sawCR = false;
_gotit = false;
}
if (offset < size)
InternalWrite(buffer, ref offset, size);
}
public bool WantMore => (_chunkRead != _chunkSize || _chunkSize != 0 || _state != State.None);
public bool DataAvailable
{
get
{
int count = _chunks.Count;
for (int i = 0; i < count; i++)
{
var ch = _chunks[i];
if (ch == null || ch.Bytes == null)
continue;
if (ch.Bytes.Length > 0 && ch.Offset < ch.Bytes.Length)
return (_state != State.Body);
}
return false;
}
}
public int TotalDataSize => _totalWritten;
public int ChunkLeft => _chunkSize - _chunkRead;
private State ReadBody(byte[] buffer, ref int offset, int size)
{
if (_chunkSize == 0)
return State.BodyFinished;
int diff = size - offset;
if (diff + _chunkRead > _chunkSize)
diff = _chunkSize - _chunkRead;
byte[] chunk = new byte[diff];
Buffer.BlockCopy(buffer, offset, chunk, 0, diff);
_chunks.Add(new Chunk(chunk));
offset += diff;
_chunkRead += diff;
_totalWritten += diff;
return (_chunkRead == _chunkSize) ? State.BodyFinished : State.Body;
}
private State GetChunkSize(byte[] buffer, ref int offset, int size)
{
_chunkRead = 0;
_chunkSize = 0;
char c = '\0';
while (offset < size)
{
c = (char)buffer[offset++];
if (c == '\r')
{
if (_sawCR)
ThrowProtocolViolation("2 CR found");
_sawCR = true;
continue;
}
if (_sawCR && c == '\n')
break;
if (c == ' ')
_gotit = true;
if (!_gotit)
_saved.Append(c);
if (_saved.Length > 20)
ThrowProtocolViolation("chunk size too long.");
}
if (!_sawCR || c != '\n')
{
if (offset < size)
ThrowProtocolViolation("Missing \\n");
try
{
if (_saved.Length > 0)
{
_chunkSize = int.Parse(RemoveChunkExtension(_saved.ToString()), NumberStyles.HexNumber);
}
}
catch (Exception)
{
ThrowProtocolViolation("Cannot parse chunk size.");
}
return State.PartialSize;
}
_chunkRead = 0;
try
{
_chunkSize = int.Parse(RemoveChunkExtension(_saved.ToString()), NumberStyles.HexNumber);
}
catch (Exception)
{
ThrowProtocolViolation("Cannot parse chunk size.");
}
if (_chunkSize == 0)
{
_trailerState = 2;
return State.Trailer;
}
return State.Body;
}
private static string RemoveChunkExtension(string input)
{
int idx = input.IndexOf(';');
if (idx == -1)
return input;
return input.Substring(0, idx);
}
private State ReadCRLF(byte[] buffer, ref int offset, int size)
{
if (!_sawCR)
{
if ((char)buffer[offset++] != '\r')
ThrowProtocolViolation("Expecting \\r");
_sawCR = true;
if (offset == size)
return State.BodyFinished;
}
if (_sawCR && (char)buffer[offset++] != '\n')
ThrowProtocolViolation("Expecting \\n");
return State.None;
}
private State ReadTrailer(byte[] buffer, ref int offset, int size)
{
char c = '\0';
// short path
if (_trailerState == 2 && (char)buffer[offset] == '\r' && _saved.Length == 0)
{
offset++;
if (offset < size && (char)buffer[offset] == '\n')
{
offset++;
return State.None;
}
offset--;
}
int st = _trailerState;
string stString = "\r\n\r";
while (offset < size && st < 4)
{
c = (char)buffer[offset++];
if ((st == 0 || st == 2) && c == '\r')
{
st++;
continue;
}
if ((st == 1 || st == 3) && c == '\n')
{
st++;
continue;
}
if (st > 0)
{
_saved.Append(stString.Substring(0, _saved.Length == 0 ? st - 2 : st));
st = 0;
if (_saved.Length > 4196)
ThrowProtocolViolation("Error reading trailer (too long).");
}
}
if (st < 4)
{
_trailerState = st;
if (offset < size)
ThrowProtocolViolation("Error reading trailer.");
return State.Trailer;
}
var reader = new StringReader(_saved.ToString());
string line;
while ((line = reader.ReadLine()) != null && line != "")
_headers.Add(line);
return State.None;
}
private static void ThrowProtocolViolation(string message)
{
var we = new WebException(message, null, WebExceptionStatus.ServerProtocolViolation, null);
throw we;
}
}
}

View File

@ -1,178 +0,0 @@
using System;
using System.IO;
using System.Net;
namespace SocketHttpListener.Net
{
// Licensed to the .NET Foundation under one or more agreements.
// See the LICENSE file in the project root for more information.
//
// System.Net.ResponseStream
//
// Author:
// Gonzalo Paniagua Javier (gonzalo@novell.com)
//
// Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
internal sealed class ChunkedInputStream : HttpRequestStream
{
private ChunkStream _decoder;
private readonly HttpListenerContext _context;
private bool _no_more_data;
private class ReadBufferState
{
public byte[] Buffer;
public int Offset;
public int Count;
public int InitialCount;
public HttpStreamAsyncResult Ares;
public ReadBufferState(byte[] buffer, int offset, int count, HttpStreamAsyncResult ares)
{
Buffer = buffer;
Offset = offset;
Count = count;
InitialCount = count;
Ares = ares;
}
}
public ChunkedInputStream(HttpListenerContext context, Stream stream, byte[] buffer, int offset, int length)
: base(stream, buffer, offset, length)
{
_context = context;
var coll = (WebHeaderCollection)context.Request.Headers;
_decoder = new ChunkStream(coll);
}
public ChunkStream Decoder
{
get => _decoder;
set => _decoder = value;
}
protected override int ReadCore(byte[] buffer, int offset, int count)
{
IAsyncResult ares = BeginReadCore(buffer, offset, count, null, null);
return EndRead(ares);
}
protected override IAsyncResult BeginReadCore(byte[] buffer, int offset, int size, AsyncCallback cback, object state)
{
var ares = new HttpStreamAsyncResult(this);
ares._callback = cback;
ares._state = state;
if (_no_more_data || size == 0 || _closed)
{
ares.Complete();
return ares;
}
int nread = _decoder.Read(buffer, offset, size);
offset += nread;
size -= nread;
if (size == 0)
{
// got all we wanted, no need to bother the decoder yet
ares._count = nread;
ares.Complete();
return ares;
}
if (!_decoder.WantMore)
{
_no_more_data = nread == 0;
ares._count = nread;
ares.Complete();
return ares;
}
ares._buffer = new byte[8192];
ares._offset = 0;
ares._count = 8192;
var rb = new ReadBufferState(buffer, offset, size, ares);
rb.InitialCount += nread;
base.BeginReadCore(ares._buffer, ares._offset, ares._count, OnRead, rb);
return ares;
}
private void OnRead(IAsyncResult base_ares)
{
ReadBufferState rb = (ReadBufferState)base_ares.AsyncState;
var ares = rb.Ares;
try
{
int nread = base.EndRead(base_ares);
if (nread == 0)
{
_no_more_data = true;
ares._count = rb.InitialCount - rb.Count;
ares.Complete();
return;
}
_decoder.Write(ares._buffer, ares._offset, nread);
nread = _decoder.Read(rb.Buffer, rb.Offset, rb.Count);
rb.Offset += nread;
rb.Count -= nread;
if (rb.Count == 0 || !_decoder.WantMore)
{
_no_more_data = !_decoder.WantMore && nread == 0;
ares._count = rb.InitialCount - rb.Count;
ares.Complete();
return;
}
ares._offset = 0;
ares._count = Math.Min(8192, _decoder.ChunkLeft + 6);
base.BeginReadCore(ares._buffer, ares._offset, ares._count, OnRead, rb);
}
catch (Exception e)
{
_context.Connection.SendError(e.Message, 400);
ares.Complete(e);
}
}
public override int EndRead(IAsyncResult asyncResult)
{
if (asyncResult == null)
throw new ArgumentNullException(nameof(asyncResult));
var ares = asyncResult as HttpStreamAsyncResult;
if (ares == null || !ReferenceEquals(this, ares._parent))
{
throw new ArgumentException("Invalid async result");
}
if (ares._endCalled)
{
throw new InvalidOperationException("Invalid end call");
}
ares._endCalled = true;
if (!asyncResult.IsCompleted)
asyncResult.AsyncWaitHandle.WaitOne();
if (ares._error != null)
throw new HttpListenerException((int)HttpStatusCode.BadRequest, "Operation aborted");
return ares._count;
}
}
}

View File

@ -1,8 +0,0 @@
namespace SocketHttpListener.Net
{
internal enum EntitySendFormat
{
ContentLength = 0, // Content-Length: XXX
Chunked = 1, // Transfer-Encoding: chunked
}
}

View File

@ -1,528 +0,0 @@
using System;
using System.IO;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.Cryptography;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.System;
using Microsoft.Extensions.Logging;
namespace SocketHttpListener.Net
{
sealed class HttpConnection
{
private static AsyncCallback s_onreadCallback = new AsyncCallback(OnRead);
const int BufferSize = 8192;
Socket _socket;
Stream _stream;
HttpEndPointListener _epl;
MemoryStream _memoryStream;
byte[] _buffer;
HttpListenerContext _context;
StringBuilder _currentLine;
ListenerPrefix _prefix;
HttpRequestStream _requestStream;
HttpResponseStream _responseStream;
bool _chunked;
int _reuses;
bool _contextBound;
bool secure;
IPEndPoint local_ep;
HttpListener _lastListener;
X509Certificate cert;
SslStream ssl_stream;
private readonly ILogger _logger;
private readonly ICryptoProvider _cryptoProvider;
private readonly IStreamHelper _streamHelper;
private readonly IFileSystem _fileSystem;
private readonly IEnvironmentInfo _environment;
public HttpConnection(ILogger logger, Socket socket, HttpEndPointListener epl, bool secure,
X509Certificate cert, ICryptoProvider cryptoProvider, IStreamHelper streamHelper, IFileSystem fileSystem,
IEnvironmentInfo environment)
{
_logger = logger;
this._socket = socket;
this._epl = epl;
this.secure = secure;
this.cert = cert;
_cryptoProvider = cryptoProvider;
_streamHelper = streamHelper;
_fileSystem = fileSystem;
_environment = environment;
if (secure == false)
{
_stream = new SocketStream(_socket, false);
}
else
{
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;
});
_stream = ssl_stream;
}
}
public Stream Stream => _stream;
public async Task Init()
{
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();
}
private void InitInternal()
{
_contextBound = false;
_requestStream = null;
_responseStream = null;
_prefix = null;
_chunked = false;
_memoryStream = new MemoryStream();
_position = 0;
_inputState = InputState.RequestLine;
_lineState = LineState.None;
_context = new HttpListenerContext(this);
}
public bool IsClosed => (_socket == null);
public int Reuses => _reuses;
public IPEndPoint LocalEndPoint
{
get
{
if (local_ep != null)
return local_ep;
local_ep = (IPEndPoint)_socket.LocalEndPoint;
return local_ep;
}
}
public IPEndPoint RemoteEndPoint => _socket.RemoteEndPoint as IPEndPoint;
public bool IsSecure => secure;
public ListenerPrefix Prefix
{
get => _prefix;
set => _prefix = value;
}
private void OnTimeout(object unused)
{
//_logger.LogInformation("HttpConnection timer fired");
CloseSocket();
Unbind();
}
public void BeginReadRequest()
{
if (_buffer == null)
_buffer = new byte[BufferSize];
try
{
_stream.BeginRead(_buffer, 0, BufferSize, s_onreadCallback, this);
}
catch
{
CloseSocket();
Unbind();
}
}
public HttpRequestStream GetRequestStream(bool chunked, long contentlength)
{
if (_requestStream == null)
{
byte[] buffer = _memoryStream.GetBuffer();
int length = (int)_memoryStream.Length;
_memoryStream = null;
if (chunked)
{
_chunked = true;
//_context.Response.SendChunked = true;
_requestStream = new ChunkedInputStream(_context, _stream, buffer, _position, length - _position);
}
else
{
_requestStream = new HttpRequestStream(_stream, buffer, _position, length - _position, contentlength);
}
}
return _requestStream;
}
public HttpResponseStream GetResponseStream(bool isExpect100Continue = false)
{
// TODO: can we get this _stream before reading the input?
if (_responseStream == null)
{
var supportsDirectSocketAccess = !_context.Response.SendChunked && !isExpect100Continue && !secure;
_responseStream = new HttpResponseStream(_stream, _context.Response, false, _streamHelper, _socket, supportsDirectSocketAccess, _environment, _fileSystem, _logger);
}
return _responseStream;
}
private static void OnRead(IAsyncResult ares)
{
var cnc = (HttpConnection)ares.AsyncState;
cnc.OnReadInternal(ares);
}
private void OnReadInternal(IAsyncResult ares)
{
int nread = -1;
try
{
nread = _stream.EndRead(ares);
_memoryStream.Write(_buffer, 0, nread);
if (_memoryStream.Length > 32768)
{
SendError("Bad Request", 400);
Close(true);
return;
}
}
catch
{
if (_memoryStream != null && _memoryStream.Length > 0)
SendError();
if (_socket != null)
{
CloseSocket();
Unbind();
}
return;
}
if (nread == 0)
{
CloseSocket();
Unbind();
return;
}
if (ProcessInput(_memoryStream))
{
if (!_context.HaveError)
_context.Request.FinishInitialization();
if (_context.HaveError)
{
SendError();
Close(true);
return;
}
if (!_epl.BindContext(_context))
{
const int NotFoundErrorCode = 404;
SendError(HttpStatusDescription.Get(NotFoundErrorCode), NotFoundErrorCode);
Close(true);
return;
}
HttpListener listener = _epl.Listener;
if (_lastListener != listener)
{
RemoveConnection();
listener.AddConnection(this);
_lastListener = listener;
}
_contextBound = true;
listener.RegisterContext(_context);
return;
}
_stream.BeginRead(_buffer, 0, BufferSize, s_onreadCallback, this);
}
private void RemoveConnection()
{
if (_lastListener == null)
_epl.RemoveConnection(this);
else
_lastListener.RemoveConnection(this);
}
private enum InputState
{
RequestLine,
Headers
}
private enum LineState
{
None,
CR,
LF
}
InputState _inputState = InputState.RequestLine;
LineState _lineState = LineState.None;
int _position;
// true -> done processing
// false -> need more input
private bool ProcessInput(MemoryStream ms)
{
byte[] buffer = ms.GetBuffer();
int len = (int)ms.Length;
int used = 0;
string line;
while (true)
{
if (_context.HaveError)
return true;
if (_position >= len)
break;
try
{
line = ReadLine(buffer, _position, len - _position, ref used);
_position += used;
}
catch
{
_context.ErrorMessage = "Bad request";
_context.ErrorStatus = 400;
return true;
}
if (line == null)
break;
if (line == "")
{
if (_inputState == InputState.RequestLine)
continue;
_currentLine = null;
ms = null;
return true;
}
if (_inputState == InputState.RequestLine)
{
_context.Request.SetRequestLine(line);
_inputState = InputState.Headers;
}
else
{
try
{
_context.Request.AddHeader(line);
}
catch (Exception e)
{
_context.ErrorMessage = e.Message;
_context.ErrorStatus = 400;
return true;
}
}
}
if (used == len)
{
ms.SetLength(0);
_position = 0;
}
return false;
}
private string ReadLine(byte[] buffer, int offset, int len, ref int used)
{
if (_currentLine == null)
_currentLine = new StringBuilder(128);
int last = offset + len;
used = 0;
for (int i = offset; i < last && _lineState != LineState.LF; i++)
{
used++;
byte b = buffer[i];
if (b == 13)
{
_lineState = LineState.CR;
}
else if (b == 10)
{
_lineState = LineState.LF;
}
else
{
_currentLine.Append((char)b);
}
}
string result = null;
if (_lineState == LineState.LF)
{
_lineState = LineState.None;
result = _currentLine.ToString();
_currentLine.Length = 0;
}
return result;
}
public void SendError(string msg, int status)
{
try
{
HttpListenerResponse response = _context.Response;
response.StatusCode = status;
response.ContentType = "text/html";
string description = HttpStatusDescription.Get(status);
string str;
if (msg != null)
str = string.Format("<h1>{0} ({1})</h1>", description, msg);
else
str = string.Format("<h1>{0}</h1>", description);
byte[] error = Encoding.UTF8.GetBytes(str);
response.Close(error, false);
}
catch
{
// response was already closed
}
}
public void SendError()
{
SendError(_context.ErrorMessage, _context.ErrorStatus);
}
private void Unbind()
{
if (_contextBound)
{
_epl.UnbindContext(_context);
_contextBound = false;
}
}
public void Close()
{
Close(false);
}
private void CloseSocket()
{
if (_socket == null)
return;
try
{
_socket.Close();
}
catch { }
finally
{
_socket = null;
}
RemoveConnection();
}
internal void Close(bool force)
{
if (_socket != null)
{
Stream st = GetResponseStream();
if (st != null)
st.Close();
_responseStream = null;
}
if (_socket != null)
{
force |= !_context.Request.KeepAlive;
if (!force)
force = (string.Equals(_context.Response.Headers["connection"], "close", StringComparison.OrdinalIgnoreCase));
if (!force && _context.Request.FlushInput())
{
if (_chunked && _context.Response.ForceCloseChunked == false)
{
// Don't close. Keep working.
_reuses++;
Unbind();
InitInternal();
BeginReadRequest();
return;
}
_reuses++;
Unbind();
InitInternal();
BeginReadRequest();
return;
}
Socket s = _socket;
_socket = null;
try
{
if (s != null)
s.Shutdown(SocketShutdown.Both);
}
catch
{
}
finally
{
if (s != null)
{
try
{
s.Close();
}
catch { }
}
}
Unbind();
RemoveConnection();
return;
}
}
}
}

View File

@ -1,526 +0,0 @@
using System;
using System.Collections.Generic;
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.Net;
using MediaBrowser.Model.System;
using Microsoft.Extensions.Logging;
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 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,
IFileSystem fileSystem, IEnvironmentInfo environment)
{
this._listener = listener;
_logger = logger;
_cryptoProvider = cryptoProvider;
_socketFactory = socketFactory;
_streamHelper = streamHelper;
_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 => _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)
{
// TODO Investigate or properly fix.
}
catch (Exception ex)
{
var epl = (HttpEndPointListener)acceptEventArg.UserToken;
epl._logger.LogError(ex, "Error in socket.AcceptAsync");
}
}
// 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)
{
var 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.LogError("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.LogInformation("HttpEndPointListener Accepting connection from {0} to {1} secure connection requested: {2}", remoteEndPointString, localEndPointString, _secure);
var conn = new HttpConnection(epl._logger, accepted, epl, epl._secure, epl._cert, epl._cryptoProvider, epl._streamHelper, epl._fileSystem, epl._environment);
await conn.Init().ConfigureAwait(false);
//_logger.LogDebug("Adding unregistered connection to {0}. Id: {1}", accepted.RemoteEndPoint, connectionId);
lock (epl._unregisteredConnections)
{
epl._unregisteredConnections[conn] = conn;
}
conn.BeginReadRequest();
}
catch (Exception ex)
{
epl._logger.LogError(ex, "Error in ProcessAccept");
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)
{
var req = context.Request;
HttpListener listener = SearchListener(req.Url, out var 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 (var 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();
}
}
}

View File

@ -1,192 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using Microsoft.Extensions.Logging;
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)
{
var 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);
if (!int.TryParse(portString, out var port) || port <= 0 || port >= 65536)
{
throw new HttpListenerException((int)HttpStatusCode.BadRequest, "net_invalid_port");
}
}
var 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.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)
{
var 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);
}
}
}

View File

@ -1,91 +0,0 @@
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";
}
}

View File

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

View File

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

View File

@ -1,99 +0,0 @@
using System;
using System.ComponentModel;
using System.Security.Principal;
using System.Text;
using System.Threading.Tasks;
using SocketHttpListener.Net.WebSockets;
namespace SocketHttpListener.Net
{
public sealed partial class HttpListenerContext
{
private HttpConnection _connection;
internal HttpListenerContext(HttpConnection connection)
{
_connection = connection;
_response = new HttpListenerResponse(this);
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);
}
}
}

View File

@ -1,74 +0,0 @@
using System;
using System.Net;
using System.Security.Principal;
using System.Threading.Tasks;
using SocketHttpListener.Net.WebSockets;
namespace SocketHttpListener.Net
{
public sealed partial class HttpListenerContext
{
private HttpListenerResponse _response;
private IPrincipal _user;
public HttpListenerRequest Request { get; }
public IPrincipal User => _user;
// This can be used to cache the results of HttpListener.AuthenticationSchemeSelectorDelegate.
internal AuthenticationSchemes AuthenticationSchemes { get; set; }
public HttpListenerResponse Response => _response;
public Task<HttpListenerWebSocketContext> AcceptWebSocketAsync(string subProtocol)
{
return AcceptWebSocketAsync(subProtocol, HttpWebSocket.DefaultReceiveBufferSize, WebSocket.DefaultKeepAliveInterval);
}
public Task<HttpListenerWebSocketContext> AcceptWebSocketAsync(string subProtocol, TimeSpan keepAliveInterval)
{
return AcceptWebSocketAsync(subProtocol, HttpWebSocket.DefaultReceiveBufferSize, keepAliveInterval);
}
}
public class GenericPrincipal : IPrincipal
{
private IIdentity m_identity;
private string[] m_roles;
public GenericPrincipal(IIdentity identity, string[] roles)
{
if (identity == null)
throw new ArgumentNullException(nameof(identity));
m_identity = identity;
if (roles != null)
{
m_roles = new string[roles.Length];
for (int i = 0; i < roles.Length; ++i)
{
m_roles[i] = roles[i];
}
}
else
{
m_roles = null;
}
}
public virtual IIdentity Identity => m_identity;
public virtual bool IsInRole(string role)
{
if (role == null || m_roles == null)
return false;
for (int i = 0; i < m_roles.Length; ++i)
{
if (m_roles[i] != null && string.Compare(m_roles[i], role, StringComparison.OrdinalIgnoreCase) == 0)
return true;
}
return false;
}
}
}

View File

@ -1,117 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Microsoft.Extensions.Logging;
namespace SocketHttpListener.Net
{
public class HttpListenerPrefixCollection : ICollection<string>, IEnumerable<string>, IEnumerable
{
private List<string> _prefixes = new List<string>();
private HttpListener _listener;
private ILogger _logger;
internal HttpListenerPrefixCollection(ILogger logger, HttpListener listener)
{
_logger = logger;
_listener = listener;
}
public int Count => _prefixes.Count;
public bool IsReadOnly => false;
public bool IsSynchronized => false;
public void Add(string uriPrefix)
{
_listener.CheckDisposed();
//ListenerPrefix.CheckUri(uriPrefix);
if (_prefixes.Contains(uriPrefix))
{
return;
}
_prefixes.Add(uriPrefix);
if (_listener.IsListening)
{
HttpEndPointManager.AddPrefix(_logger, uriPrefix, _listener);
}
}
public void AddRange(IEnumerable<string> uriPrefixes)
{
_listener.CheckDisposed();
foreach (var uriPrefix in uriPrefixes)
{
if (_prefixes.Contains(uriPrefix))
{
continue;
}
_prefixes.Add(uriPrefix);
if (_listener.IsListening)
{
HttpEndPointManager.AddPrefix(_logger, uriPrefix, _listener);
}
}
}
public void Clear()
{
_listener.CheckDisposed();
_prefixes.Clear();
if (_listener.IsListening)
{
HttpEndPointManager.RemoveListener(_logger, _listener);
}
}
public bool Contains(string uriPrefix)
{
_listener.CheckDisposed();
return _prefixes.Contains(uriPrefix);
}
public void CopyTo(string[] array, int offset)
{
_listener.CheckDisposed();
_prefixes.CopyTo(array, offset);
}
public void CopyTo(Array array, int offset)
{
_listener.CheckDisposed();
((ICollection)_prefixes).CopyTo(array, offset);
}
public IEnumerator<string> GetEnumerator()
{
return _prefixes.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return _prefixes.GetEnumerator();
}
public bool Remove(string uriPrefix)
{
_listener.CheckDisposed();
if (uriPrefix == null)
{
throw new ArgumentNullException(nameof(uriPrefix));
}
bool result = _prefixes.Remove(uriPrefix);
if (result && _listener.IsListening)
{
HttpEndPointManager.RemovePrefix(_logger, uriPrefix, _listener);
}
return result;
}
}
}

View File

@ -1,325 +0,0 @@
using System;
using System.IO;
using System.Text;
using MediaBrowser.Model.Services;
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;
}
}

View File

@ -1,537 +0,0 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Net;
using System.Text;
using MediaBrowser.Model.Services;
using SocketHttpListener.Net.WebSockets;
namespace SocketHttpListener.Net
{
public sealed partial class HttpListenerRequest
{
private CookieCollection _cookies;
private bool? _keepAlive;
private string _rawUrl;
private Uri _requestUri;
private Version _version;
public string[] AcceptTypes => Helpers.ParseMultivalueHeader(Headers[HttpKnownHeaderNames.Accept]);
public string[] UserLanguages => Helpers.ParseMultivalueHeader(Headers[HttpKnownHeaderNames.AcceptLanguage]);
private static CookieCollection ParseCookies(Uri uri, string setCookieHeader)
{
var cookies = new CookieCollection();
return cookies;
}
public CookieCollection Cookies
{
get
{
if (_cookies == null)
{
string cookieString = Headers[HttpKnownHeaderNames.Cookie];
if (!string.IsNullOrEmpty(cookieString))
{
_cookies = ParseCookies(RequestUri, cookieString);
}
if (_cookies == null)
{
_cookies = new CookieCollection();
}
}
return _cookies;
}
}
public Encoding ContentEncoding
{
get
{
if (UserAgent != null && CultureInfo.InvariantCulture.CompareInfo.IsPrefix(UserAgent, "UP"))
{
string postDataCharset = Headers["x-up-devcap-post-charset"];
if (postDataCharset != null && postDataCharset.Length > 0)
{
try
{
return Encoding.GetEncoding(postDataCharset);
}
catch (ArgumentException)
{
}
}
}
if (HasEntityBody)
{
if (ContentType != null)
{
string charSet = Helpers.GetCharSetValueFromHeader(ContentType);
if (charSet != null)
{
try
{
return Encoding.GetEncoding(charSet);
}
catch (ArgumentException)
{
}
}
}
}
return Encoding.UTF8;
}
}
public string ContentType => Headers[HttpKnownHeaderNames.ContentType];
public bool IsLocal => LocalEndPoint.Address.Equals(RemoteEndPoint.Address);
public bool IsWebSocketRequest
{
get
{
if (!SupportsWebSockets)
{
return false;
}
bool foundConnectionUpgradeHeader = false;
if (string.IsNullOrEmpty(Headers[HttpKnownHeaderNames.Connection]) || string.IsNullOrEmpty(Headers[HttpKnownHeaderNames.Upgrade]))
{
return false;
}
foreach (string connection in Headers.GetValues(HttpKnownHeaderNames.Connection))
{
if (string.Equals(connection, HttpKnownHeaderNames.Upgrade, StringComparison.OrdinalIgnoreCase))
{
foundConnectionUpgradeHeader = true;
break;
}
}
if (!foundConnectionUpgradeHeader)
{
return false;
}
foreach (string upgrade in Headers.GetValues(HttpKnownHeaderNames.Upgrade))
{
if (string.Equals(upgrade, HttpWebSocket.WebSocketUpgradeToken, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
return false;
}
}
public bool KeepAlive
{
get
{
if (!_keepAlive.HasValue)
{
string header = Headers[HttpKnownHeaderNames.ProxyConnection];
if (string.IsNullOrEmpty(header))
{
header = Headers[HttpKnownHeaderNames.Connection];
}
if (string.IsNullOrEmpty(header))
{
if (ProtocolVersion >= HttpVersion.Version11)
{
_keepAlive = true;
}
else
{
header = Headers[HttpKnownHeaderNames.KeepAlive];
_keepAlive = !string.IsNullOrEmpty(header);
}
}
else
{
header = header.ToLowerInvariant();
_keepAlive =
header.IndexOf("close", StringComparison.OrdinalIgnoreCase) < 0 ||
header.IndexOf("keep-alive", StringComparison.OrdinalIgnoreCase) >= 0;
}
}
return _keepAlive.Value;
}
}
public QueryParamCollection QueryString
{
get
{
var queryString = new QueryParamCollection();
Helpers.FillFromString(queryString, Url.Query, true, ContentEncoding);
return queryString;
}
}
public string RawUrl => _rawUrl;
private string RequestScheme => IsSecureConnection ? UriScheme.Https : UriScheme.Http;
public string UserAgent => Headers[HttpKnownHeaderNames.UserAgent];
public string UserHostAddress => LocalEndPoint.ToString();
public string UserHostName => Headers[HttpKnownHeaderNames.Host];
public Uri UrlReferrer
{
get
{
string referrer = Headers[HttpKnownHeaderNames.Referer];
if (referrer == null)
{
return null;
}
bool success = Uri.TryCreate(referrer, UriKind.RelativeOrAbsolute, out var urlReferrer);
return success ? urlReferrer : null;
}
}
public Uri Url => RequestUri;
public Version ProtocolVersion => _version;
private static class Helpers
{
//
// Get attribute off header value
//
internal static string GetCharSetValueFromHeader(string headerValue)
{
const string AttrName = "charset";
if (headerValue == null)
return null;
int l = headerValue.Length;
int k = AttrName.Length;
// find properly separated attribute name
int i = 1; // start searching from 1
while (i < l)
{
i = CultureInfo.InvariantCulture.CompareInfo.IndexOf(headerValue, AttrName, i, CompareOptions.IgnoreCase);
if (i < 0)
break;
if (i + k >= l)
break;
char chPrev = headerValue[i - 1];
char chNext = headerValue[i + k];
if ((chPrev == ';' || chPrev == ',' || char.IsWhiteSpace(chPrev)) && (chNext == '=' || char.IsWhiteSpace(chNext)))
break;
i += k;
}
if (i < 0 || i >= l)
return null;
// skip to '=' and the following whitespace
i += k;
while (i < l && char.IsWhiteSpace(headerValue[i]))
i++;
if (i >= l || headerValue[i] != '=')
return null;
i++;
while (i < l && char.IsWhiteSpace(headerValue[i]))
i++;
if (i >= l)
return null;
// parse the value
string attrValue = null;
int j;
if (i < l && headerValue[i] == '"')
{
if (i == l - 1)
return null;
j = headerValue.IndexOf('"', i + 1);
if (j < 0 || j == i + 1)
return null;
attrValue = headerValue.Substring(i + 1, j - i - 1).Trim();
}
else
{
for (j = i; j < l; j++)
{
if (headerValue[j] == ';')
break;
}
if (j == i)
return null;
attrValue = headerValue.Substring(i, j - i).Trim();
}
return attrValue;
}
internal static string[] ParseMultivalueHeader(string s)
{
if (s == null)
return null;
int l = s.Length;
// collect comma-separated values into list
var values = new List<string>();
int i = 0;
while (i < l)
{
// find next ,
int ci = s.IndexOf(',', i);
if (ci < 0)
ci = l;
// append corresponding server value
values.Add(s.Substring(i, ci - i));
// move to next
i = ci + 1;
// skip leading space
if (i < l && s[i] == ' ')
i++;
}
// return list as array of strings
int n = values.Count;
string[] strings;
// if n is 0 that means s was empty string
if (n == 0)
{
strings = new string[1];
strings[0] = string.Empty;
}
else
{
strings = new string[n];
values.CopyTo(0, strings, 0, n);
}
return strings;
}
private static string UrlDecodeStringFromStringInternal(string s, Encoding e)
{
int count = s.Length;
var helper = new UrlDecoder(count, e);
// go through the string's chars collapsing %XX and %uXXXX and
// appending each char as char, with exception of %XX constructs
// that are appended as bytes
for (int pos = 0; pos < count; pos++)
{
char ch = s[pos];
if (ch == '+')
{
ch = ' ';
}
else if (ch == '%' && pos < count - 2)
{
if (s[pos + 1] == 'u' && pos < count - 5)
{
int h1 = HexToInt(s[pos + 2]);
int h2 = HexToInt(s[pos + 3]);
int h3 = HexToInt(s[pos + 4]);
int h4 = HexToInt(s[pos + 5]);
if (h1 >= 0 && h2 >= 0 && h3 >= 0 && h4 >= 0)
{ // valid 4 hex chars
ch = (char)((h1 << 12) | (h2 << 8) | (h3 << 4) | h4);
pos += 5;
// only add as char
helper.AddChar(ch);
continue;
}
}
else
{
int h1 = HexToInt(s[pos + 1]);
int h2 = HexToInt(s[pos + 2]);
if (h1 >= 0 && h2 >= 0)
{ // valid 2 hex chars
byte b = (byte)((h1 << 4) | h2);
pos += 2;
// don't add as char
helper.AddByte(b);
continue;
}
}
}
if ((ch & 0xFF80) == 0)
helper.AddByte((byte)ch); // 7 bit have to go as bytes because of Unicode
else
helper.AddChar(ch);
}
return helper.GetString();
}
private static int HexToInt(char h)
{
return (h >= '0' && h <= '9') ? h - '0' :
(h >= 'a' && h <= 'f') ? h - 'a' + 10 :
(h >= 'A' && h <= 'F') ? h - 'A' + 10 :
-1;
}
private class UrlDecoder
{
private int _bufferSize;
// Accumulate characters in a special array
private int _numChars;
private char[] _charBuffer;
// Accumulate bytes for decoding into characters in a special array
private int _numBytes;
private byte[] _byteBuffer;
// Encoding to convert chars to bytes
private Encoding _encoding;
private void FlushBytes()
{
if (_numBytes > 0)
{
_numChars += _encoding.GetChars(_byteBuffer, 0, _numBytes, _charBuffer, _numChars);
_numBytes = 0;
}
}
internal UrlDecoder(int bufferSize, Encoding encoding)
{
_bufferSize = bufferSize;
_encoding = encoding;
_charBuffer = new char[bufferSize];
// byte buffer created on demand
}
internal void AddChar(char ch)
{
if (_numBytes > 0)
FlushBytes();
_charBuffer[_numChars++] = ch;
}
internal void AddByte(byte b)
{
{
if (_byteBuffer == null)
_byteBuffer = new byte[_bufferSize];
_byteBuffer[_numBytes++] = b;
}
}
internal string GetString()
{
if (_numBytes > 0)
FlushBytes();
if (_numChars > 0)
return new string(_charBuffer, 0, _numChars);
else
return string.Empty;
}
}
internal static void FillFromString(QueryParamCollection nvc, string s, bool urlencoded, Encoding encoding)
{
int l = (s != null) ? s.Length : 0;
int i = (s.Length > 0 && s[0] == '?') ? 1 : 0;
while (i < l)
{
// find next & while noting first = on the way (and if there are more)
int si = i;
int ti = -1;
while (i < l)
{
char ch = s[i];
if (ch == '=')
{
if (ti < 0)
ti = i;
}
else if (ch == '&')
{
break;
}
i++;
}
// extract the name / value pair
string name = null;
string value = null;
if (ti >= 0)
{
name = s.Substring(si, ti - si);
value = s.Substring(ti + 1, i - ti - 1);
}
else
{
value = s.Substring(si, i - si);
}
// add name / value pair to the collection
if (urlencoded)
nvc.Add(
name == null ? null : UrlDecodeStringFromStringInternal(name, encoding),
UrlDecodeStringFromStringInternal(value, encoding));
else
nvc.Add(name, value);
// trailing '&'
if (i == l - 1 && s[i] == '&')
nvc.Add(null, "");
i++;
}
}
}
}
}

View File

@ -1,443 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Text;
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)
{
var 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.
if (!int.TryParse(codePoint, NumberStyles.HexNumber, null, out var 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)
{
// 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)
{
if (!byte.TryParse(escapedCharacter, NumberStyles.HexNumber, null, out byte 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)
{
//if (NetEventSource.IsEnabled) NetEventSource.Error(this, SR.Format(SR.net_log_listener_cant_convert_bytes, GetOctetsAsString(_rawOctets), e.Message));
}
catch (EncoderFallbackException)
{
// 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)
{
var 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
}
}
}

View File

@ -1,333 +0,0 @@
using System;
using System.Globalization;
using System.IO;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.IO;
namespace SocketHttpListener.Net
{
public sealed partial class HttpListenerResponse : IDisposable
{
private long _contentLength;
private Version _version = HttpVersion.Version11;
private int _statusCode = 200;
internal object _headersLock = new object();
private bool _forceCloseChunked;
internal HttpListenerResponse(HttpListenerContext context)
{
_httpContext = context;
}
internal bool ForceCloseChunked => _forceCloseChunked;
private void EnsureResponseStream()
{
if (_responseStream == null)
{
_responseStream = _httpContext.Connection.GetResponseStream();
}
}
public Version ProtocolVersion
{
get => _version;
set
{
CheckDisposed();
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
if (value.Major != 1 || (value.Minor != 0 && value.Minor != 1))
{
throw new ArgumentException("Wrong version");
}
_version = new Version(value.Major, value.Minor); // match Windows behavior, trimming to just Major.Minor
}
}
public int StatusCode
{
get => _statusCode;
set
{
CheckDisposed();
if (value < 100 || value > 999)
throw new ProtocolViolationException("Invalid status");
_statusCode = value;
}
}
private void Dispose()
{
Close(true);
}
public void Close()
{
if (Disposed)
return;
Close(false);
}
public void Abort()
{
if (Disposed)
return;
Close(true);
}
private void Close(bool force)
{
Disposed = true;
_httpContext.Connection.Close(force);
}
public void Close(byte[] responseEntity, bool willBlock)
{
CheckDisposed();
if (responseEntity == null)
{
throw new ArgumentNullException(nameof(responseEntity));
}
if (!SentHeaders && _boundaryType != BoundaryType.Chunked)
{
ContentLength64 = responseEntity.Length;
}
if (willBlock)
{
try
{
OutputStream.Write(responseEntity, 0, responseEntity.Length);
}
finally
{
Close(false);
}
}
else
{
OutputStream.BeginWrite(responseEntity, 0, responseEntity.Length, iar =>
{
var thisRef = (HttpListenerResponse)iar.AsyncState;
try
{
try
{
thisRef.OutputStream.EndWrite(iar);
}
finally
{
thisRef.Close(false);
}
}
catch (Exception)
{
// In case response was disposed during this time
}
}, this);
}
}
public void CopyFrom(HttpListenerResponse templateResponse)
{
_webHeaders.Clear();
//_webHeaders.Add(templateResponse._webHeaders);
_contentLength = templateResponse._contentLength;
_statusCode = templateResponse._statusCode;
_statusDescription = templateResponse._statusDescription;
_keepAlive = templateResponse._keepAlive;
_version = templateResponse._version;
}
internal void SendHeaders(bool closing, MemoryStream ms, bool isWebSocketHandshake = false)
{
if (!isWebSocketHandshake)
{
if (_webHeaders["Server"] == null)
{
_webHeaders.Set("Server", "Microsoft-NetCore/2.0");
}
if (_webHeaders["Date"] == null)
{
_webHeaders.Set("Date", DateTime.UtcNow.ToString("r", CultureInfo.InvariantCulture));
}
if (_boundaryType == BoundaryType.None)
{
if (HttpListenerRequest.ProtocolVersion <= HttpVersion.Version10)
{
_keepAlive = false;
}
else
{
_boundaryType = BoundaryType.Chunked;
}
if (CanSendResponseBody(_httpContext.Response.StatusCode))
{
_contentLength = -1;
}
else
{
_boundaryType = BoundaryType.ContentLength;
_contentLength = 0;
}
}
if (_boundaryType != BoundaryType.Chunked)
{
if (_boundaryType != BoundaryType.ContentLength && closing)
{
_contentLength = CanSendResponseBody(_httpContext.Response.StatusCode) ? -1 : 0;
}
if (_boundaryType == BoundaryType.ContentLength)
{
_webHeaders.Set("Content-Length", _contentLength.ToString("D", CultureInfo.InvariantCulture));
}
}
/* Apache forces closing the connection for these status codes:
* HttpStatusCode.BadRequest 400
* HttpStatusCode.RequestTimeout 408
* HttpStatusCode.LengthRequired 411
* HttpStatusCode.RequestEntityTooLarge 413
* HttpStatusCode.RequestUriTooLong 414
* HttpStatusCode.InternalServerError 500
* HttpStatusCode.ServiceUnavailable 503
*/
bool conn_close = (_statusCode == (int)HttpStatusCode.BadRequest || _statusCode == (int)HttpStatusCode.RequestTimeout
|| _statusCode == (int)HttpStatusCode.LengthRequired || _statusCode == (int)HttpStatusCode.RequestEntityTooLarge
|| _statusCode == (int)HttpStatusCode.RequestUriTooLong || _statusCode == (int)HttpStatusCode.InternalServerError
|| _statusCode == (int)HttpStatusCode.ServiceUnavailable);
if (!conn_close)
{
conn_close = !_httpContext.Request.KeepAlive;
}
// They sent both KeepAlive: true and Connection: close
if (!_keepAlive || conn_close)
{
_webHeaders.Set("Connection", "Close");
conn_close = true;
}
if (SendChunked)
{
_webHeaders.Set("Transfer-Encoding", "Chunked");
}
int reuses = _httpContext.Connection.Reuses;
if (reuses >= 100)
{
_forceCloseChunked = true;
if (!conn_close)
{
_webHeaders.Set("Connection", "Close");
conn_close = true;
}
}
if (HttpListenerRequest.ProtocolVersion <= HttpVersion.Version10)
{
if (_keepAlive)
{
Headers["Keep-Alive"] = "true";
}
if (!conn_close)
{
_webHeaders.Set("Connection", "Keep-Alive");
}
}
ComputeCookies();
}
var encoding = Encoding.UTF8;
var writer = new StreamWriter(ms, encoding, 256);
writer.Write("HTTP/1.1 {0} ", _statusCode); // "1.1" matches Windows implementation, which ignores the response version
writer.Flush();
byte[] statusDescriptionBytes = WebHeaderEncoding.GetBytes(StatusDescription);
ms.Write(statusDescriptionBytes, 0, statusDescriptionBytes.Length);
writer.Write("\r\n");
writer.Write(FormatHeaders(_webHeaders));
writer.Flush();
int preamble = encoding.GetPreamble().Length;
EnsureResponseStream();
/* Assumes that the ms was at position 0 */
ms.Position = preamble;
SentHeaders = !isWebSocketHandshake;
}
private static bool HeaderCanHaveEmptyValue(string name) =>
!string.Equals(name, "Location", StringComparison.OrdinalIgnoreCase);
private static string FormatHeaders(WebHeaderCollection headers)
{
var sb = new StringBuilder();
for (int i = 0; i < headers.Count; i++)
{
string key = headers.GetKey(i);
string[] values = headers.GetValues(i);
int startingLength = sb.Length;
sb.Append(key).Append(": ");
bool anyValues = false;
for (int j = 0; j < values.Length; j++)
{
string value = values[j];
if (!string.IsNullOrWhiteSpace(value))
{
if (anyValues)
{
sb.Append(", ");
}
sb.Append(value);
anyValues = true;
}
}
if (anyValues || HeaderCanHaveEmptyValue(key))
{
// Complete the header
sb.Append("\r\n");
}
else
{
// Empty header; remove it.
sb.Length = startingLength;
}
}
return sb.Append("\r\n").ToString();
}
private bool Disposed { get; set; }
internal bool SentHeaders { get; set; }
public Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken)
{
return ((HttpResponseStream)OutputStream).TransmitFile(path, offset, count, fileShareMode, cancellationToken);
}
}
}

View File

@ -1,294 +0,0 @@
using System;
using System.IO;
using System.Net;
using System.Text;
namespace SocketHttpListener.Net
{
public sealed partial class HttpListenerResponse : IDisposable
{
private BoundaryType _boundaryType = BoundaryType.None;
private CookieCollection _cookies;
private HttpListenerContext _httpContext;
private bool _keepAlive = true;
private HttpResponseStream _responseStream;
private string _statusDescription;
private WebHeaderCollection _webHeaders = new WebHeaderCollection();
public WebHeaderCollection Headers => _webHeaders;
public Encoding ContentEncoding { get; set; }
public string ContentType
{
get => Headers["Content-Type"];
set
{
CheckDisposed();
if (string.IsNullOrEmpty(value))
{
Headers.Remove("Content-Type");
}
else
{
Headers.Set("Content-Type", value);
}
}
}
private HttpListenerContext HttpListenerContext => _httpContext;
private HttpListenerRequest HttpListenerRequest => HttpListenerContext.Request;
internal EntitySendFormat EntitySendFormat
{
get => (EntitySendFormat)_boundaryType;
set
{
CheckDisposed();
CheckSentHeaders();
if (value == EntitySendFormat.Chunked && HttpListenerRequest.ProtocolVersion.Minor == 0)
{
throw new ProtocolViolationException("net_nochunkuploadonhttp10");
}
_boundaryType = (BoundaryType)value;
if (value != EntitySendFormat.ContentLength)
{
_contentLength = -1;
}
}
}
public bool SendChunked
{
get => EntitySendFormat == EntitySendFormat.Chunked;
set => EntitySendFormat = value ? EntitySendFormat.Chunked : EntitySendFormat.ContentLength;
}
// We MUST NOT send message-body when we send responses with these Status codes
private static readonly int[] s_noResponseBody = { 100, 101, 204, 205, 304 };
private static bool CanSendResponseBody(int responseCode)
{
for (int i = 0; i < s_noResponseBody.Length; i++)
{
if (responseCode == s_noResponseBody[i])
{
return false;
}
}
return true;
}
public long ContentLength64
{
get => _contentLength;
set
{
CheckDisposed();
CheckSentHeaders();
if (value >= 0)
{
_contentLength = value;
_boundaryType = BoundaryType.ContentLength;
}
else
{
throw new ArgumentOutOfRangeException(nameof(value));
}
}
}
public CookieCollection Cookies
{
get => _cookies ?? (_cookies = new CookieCollection());
set => _cookies = value;
}
public bool KeepAlive
{
get => _keepAlive;
set
{
CheckDisposed();
_keepAlive = value;
}
}
public Stream OutputStream
{
get
{
CheckDisposed();
EnsureResponseStream();
return _responseStream;
}
}
public string RedirectLocation
{
get => Headers["Location"];
set
{
// note that this doesn't set the status code to a redirect one
CheckDisposed();
if (string.IsNullOrEmpty(value))
{
Headers.Remove("Location");
}
else
{
Headers.Set("Location", value);
}
}
}
public string StatusDescription
{
get
{
if (_statusDescription == null)
{
// if the user hasn't set this, generated on the fly, if possible.
// We know this one is safe, no need to verify it as in the setter.
_statusDescription = HttpStatusDescription.Get(StatusCode);
}
if (_statusDescription == null)
{
_statusDescription = string.Empty;
}
return _statusDescription;
}
set
{
CheckDisposed();
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
// Need to verify the status description doesn't contain any control characters except HT. We mask off the high
// byte since that's how it's encoded.
for (int i = 0; i < value.Length; i++)
{
char c = (char)(0x000000ff & (uint)value[i]);
if ((c <= 31 && c != (byte)'\t') || c == 127)
{
throw new ArgumentException("net_WebHeaderInvalidControlChars");
}
}
_statusDescription = value;
}
}
public void AddHeader(string name, string value)
{
Headers.Set(name, value);
}
public void AppendHeader(string name, string value)
{
Headers.Add(name, value);
}
public void AppendCookie(Cookie cookie)
{
if (cookie == null)
{
throw new ArgumentNullException(nameof(cookie));
}
Cookies.Add(cookie);
}
private void ComputeCookies()
{
if (_cookies != null)
{
// now go through the collection, and concatenate all the cookies in per-variant strings
//string setCookie2 = null, setCookie = null;
//for (int index = 0; index < _cookies.Count; index++)
//{
// Cookie cookie = _cookies[index];
// string cookieString = cookie.ToServerString();
// if (cookieString == null || cookieString.Length == 0)
// {
// continue;
// }
// if (cookie.IsRfc2965Variant())
// {
// setCookie2 = setCookie2 == null ? cookieString : setCookie2 + ", " + cookieString;
// }
// else
// {
// setCookie = setCookie == null ? cookieString : setCookie + ", " + cookieString;
// }
//}
//if (!string.IsNullOrEmpty(setCookie))
//{
// Headers.Set(HttpKnownHeaderNames.SetCookie, setCookie);
// if (string.IsNullOrEmpty(setCookie2))
// {
// Headers.Remove(HttpKnownHeaderNames.SetCookie2);
// }
//}
//if (!string.IsNullOrEmpty(setCookie2))
//{
// Headers.Set(HttpKnownHeaderNames.SetCookie2, setCookie2);
// if (string.IsNullOrEmpty(setCookie))
// {
// Headers.Remove(HttpKnownHeaderNames.SetCookie);
// }
//}
}
}
public void Redirect(string url)
{
Headers["Location"] = url;
StatusCode = (int)HttpStatusCode.Redirect;
StatusDescription = "Found";
}
public void SetCookie(Cookie cookie)
{
if (cookie == null)
{
throw new ArgumentNullException(nameof(cookie));
}
//Cookie newCookie = cookie.Clone();
//int added = Cookies.InternalAdd(newCookie, true);
//if (added != 1)
//{
// // The Cookie already existed and couldn't be replaced.
// throw new ArgumentException("Cookie exists");
//}
}
void IDisposable.Dispose()
{
Dispose();
}
private void CheckDisposed()
{
if (Disposed)
{
throw new ObjectDisposedException(GetType().FullName);
}
}
private void CheckSentHeaders()
{
if (SentHeaders)
{
throw new InvalidOperationException();
}
}
}
}

View File

@ -1,210 +0,0 @@
using System;
using System.IO;
namespace SocketHttpListener.Net
{
// Licensed to the .NET Foundation under one or more agreements.
// See the LICENSE file in the project root for more information.
//
// System.Net.ResponseStream
//
// Author:
// Gonzalo Paniagua Javier (gonzalo@novell.com)
//
// Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
internal partial class HttpRequestStream : Stream
{
private byte[] _buffer;
private int _offset;
private int _length;
private long _remainingBody;
protected bool _closed;
private Stream _stream;
internal HttpRequestStream(Stream stream, byte[] buffer, int offset, int length)
: this(stream, buffer, offset, length, -1)
{
}
internal HttpRequestStream(Stream stream, byte[] buffer, int offset, int length, long contentlength)
{
_stream = stream;
_buffer = buffer;
_offset = offset;
_length = length;
_remainingBody = contentlength;
}
// Returns 0 if we can keep reading from the base stream,
// > 0 if we read something from the buffer.
// -1 if we had a content length set and we finished reading that many bytes.
private int FillFromBuffer(byte[] buffer, int offset, int count)
{
if (_remainingBody == 0)
return -1;
if (_length == 0)
return 0;
int size = Math.Min(_length, count);
if (_remainingBody > 0)
size = (int)Math.Min(size, _remainingBody);
if (_offset > _buffer.Length - size)
{
size = Math.Min(size, _buffer.Length - _offset);
}
if (size == 0)
return 0;
Buffer.BlockCopy(_buffer, _offset, buffer, offset, size);
_offset += size;
_length -= size;
if (_remainingBody > 0)
_remainingBody -= size;
return size;
}
protected virtual int ReadCore(byte[] buffer, int offset, int size)
{
// Call FillFromBuffer to check for buffer boundaries even when remaining_body is 0
int nread = FillFromBuffer(buffer, offset, size);
if (nread == -1)
{ // No more bytes available (Content-Length)
return 0;
}
else if (nread > 0)
{
return nread;
}
if (_remainingBody > 0)
{
size = (int)Math.Min(_remainingBody, (long)size);
}
nread = _stream.Read(buffer, offset, size);
if (_remainingBody > 0)
{
if (nread == 0)
{
throw new Exception("Bad request");
}
//Debug.Assert(nread <= _remainingBody);
_remainingBody -= nread;
}
return nread;
}
protected virtual IAsyncResult BeginReadCore(byte[] buffer, int offset, int size, AsyncCallback cback, object state)
{
if (size == 0 || _closed)
{
var ares = new HttpStreamAsyncResult(this);
ares._callback = cback;
ares._state = state;
ares.Complete();
return ares;
}
int nread = FillFromBuffer(buffer, offset, size);
if (nread > 0 || nread == -1)
{
var ares = new HttpStreamAsyncResult(this);
ares._buffer = buffer;
ares._offset = offset;
ares._count = size;
ares._callback = cback;
ares._state = state;
ares._synchRead = Math.Max(0, nread);
ares.Complete();
return ares;
}
// Avoid reading past the end of the request to allow
// for HTTP pipelining
if (_remainingBody >= 0 && size > _remainingBody)
{
size = (int)Math.Min(_remainingBody, (long)size);
}
return _stream.BeginRead(buffer, offset, size, cback, state);
}
public override int EndRead(IAsyncResult asyncResult)
{
if (asyncResult == null)
throw new ArgumentNullException(nameof(asyncResult));
var r = asyncResult as HttpStreamAsyncResult;
if (r != null)
{
if (!ReferenceEquals(this, r._parent))
{
throw new ArgumentException("Invalid async result");
}
if (r._endCalled)
{
throw new InvalidOperationException("invalid end call");
}
r._endCalled = true;
if (!asyncResult.IsCompleted)
{
asyncResult.AsyncWaitHandle.WaitOne();
}
return r._synchRead;
}
if (_closed)
return 0;
int nread = 0;
try
{
nread = _stream.EndRead(asyncResult);
}
catch (IOException e) when (e.InnerException is ArgumentException || e.InnerException is InvalidOperationException)
{
throw e.InnerException;
}
if (_remainingBody > 0)
{
if (nread == 0)
{
throw new Exception("Bad request");
}
_remainingBody -= nread;
}
return nread;
}
}
}

View File

@ -1,129 +0,0 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace SocketHttpListener.Net
{
// Licensed to the .NET Foundation under one or more agreements.
// See the LICENSE file in the project root for more information.
//
// System.Net.ResponseStream
//
// Author:
// Gonzalo Paniagua Javier (gonzalo@novell.com)
//
// Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
internal partial class HttpRequestStream : Stream
{
public override bool CanSeek => false;
public override bool CanWrite => false;
public override bool CanRead => true;
public override int Read(byte[] buffer, int offset, int size)
{
if (buffer == null)
{
throw new ArgumentNullException(nameof(buffer));
}
if (offset < 0 || offset > buffer.Length)
{
throw new ArgumentOutOfRangeException(nameof(offset));
}
if (size < 0 || size > buffer.Length - offset)
{
throw new ArgumentOutOfRangeException(nameof(size));
}
if (size == 0 || _closed)
{
return 0;
}
return ReadCore(buffer, offset, size);
}
public override IAsyncResult BeginRead(byte[] buffer, int offset, int size, AsyncCallback callback, object state)
{
if (buffer == null)
{
throw new ArgumentNullException(nameof(buffer));
}
if (offset < 0 || offset > buffer.Length)
{
throw new ArgumentOutOfRangeException(nameof(offset));
}
if (size < 0 || size > buffer.Length - offset)
{
throw new ArgumentOutOfRangeException(nameof(size));
}
return BeginReadCore(buffer, offset, size, callback, state);
}
public override void Flush() { }
public override Task FlushAsync(CancellationToken cancellationToken) => Task.CompletedTask;
public override long Length => throw new NotImplementedException();
public override long Position
{
get => throw new NotImplementedException();
set => throw new NotImplementedException();
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotImplementedException();
}
public override void SetLength(long value)
{
throw new NotImplementedException();
}
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotImplementedException();
}
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
{
return base.BeginWrite(buffer, offset, count, callback, state);
}
public override void EndWrite(IAsyncResult asyncResult)
{
base.EndWrite(asyncResult);
}
internal bool Closed => _closed;
protected override void Dispose(bool disposing)
{
_closed = true;
base.Dispose(disposing);
}
}
}

View File

@ -1,329 +0,0 @@
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.System;
using Microsoft.Extensions.Logging;
namespace SocketHttpListener.Net
{
// Licensed to the .NET Foundation under one or more agreements.
// See the LICENSE file in the project root for more information.
//
// System.Net.ResponseStream
//
// Author:
// Gonzalo Paniagua Javier (gonzalo@novell.com)
//
// Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
internal partial class HttpResponseStream : Stream
{
private HttpListenerResponse _response;
private bool _ignore_errors;
private bool _trailer_sent;
private Stream _stream;
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, IStreamHelper streamHelper, Socket socket, bool supportsDirectSocketAccess, IEnvironmentInfo environment, IFileSystem fileSystem, ILogger logger)
{
_response = response;
_ignore_errors = ignore_errors;
_streamHelper = streamHelper;
_socket = socket;
_supportsDirectSocketAccess = supportsDirectSocketAccess;
_environment = environment;
_fileSystem = fileSystem;
_logger = logger;
_stream = stream;
}
private void DisposeCore()
{
byte[] bytes = null;
MemoryStream ms = GetHeaders(true);
bool chunked = _response.SendChunked;
if (_stream.CanWrite)
{
try
{
if (ms != null)
{
long start = ms.Position;
if (chunked && !_trailer_sent)
{
bytes = GetChunkSizeBytes(0, true);
ms.Position = ms.Length;
ms.Write(bytes, 0, bytes.Length);
}
InternalWrite(ms.GetBuffer(), (int)start, (int)(ms.Length - start));
_trailer_sent = true;
}
else if (chunked && !_trailer_sent)
{
bytes = GetChunkSizeBytes(0, true);
InternalWrite(bytes, 0, bytes.Length);
_trailer_sent = true;
}
}
catch (HttpListenerException)
{
// Ignore error due to connection reset by peer
}
}
_response.Close();
}
internal async Task WriteWebSocketHandshakeHeadersAsync()
{
if (_closed)
throw new ObjectDisposedException(GetType().ToString());
if (_stream.CanWrite)
{
MemoryStream ms = GetHeaders(closing: false, isWebSocketHandshake: true);
bool chunked = _response.SendChunked;
long start = ms.Position;
if (chunked)
{
byte[] bytes = GetChunkSizeBytes(0, true);
ms.Position = ms.Length;
ms.Write(bytes, 0, bytes.Length);
}
await InternalWriteAsync(ms.GetBuffer(), (int)start, (int)(ms.Length - start)).ConfigureAwait(false);
await _stream.FlushAsync().ConfigureAwait(false);
}
}
private MemoryStream GetHeaders(bool closing, bool isWebSocketHandshake = false)
{
//// SendHeaders works on shared headers
//lock (_response.headers_lock)
//{
// if (_response.HeadersSent)
// return null;
// var ms = CreateNew();
// _response.SendHeaders(closing, ms);
// return ms;
//}
// SendHeaders works on shared headers
lock (_response._headersLock)
{
if (_response.SentHeaders)
{
return null;
}
MemoryStream ms = new MemoryStream();
_response.SendHeaders(closing, ms, isWebSocketHandshake);
return ms;
}
}
private static byte[] s_crlf = new byte[] { 13, 10 };
private static byte[] GetChunkSizeBytes(int size, bool final)
{
string str = string.Format("{0:x}\r\n{1}", size, final ? "\r\n" : "");
return Encoding.ASCII.GetBytes(str);
}
internal void InternalWrite(byte[] buffer, int offset, int count)
{
if (_ignore_errors)
{
try
{
_stream.Write(buffer, offset, count);
}
catch { }
}
else
{
_stream.Write(buffer, offset, count);
}
}
internal Task InternalWriteAsync(byte[] buffer, int offset, int count) =>
_ignore_errors ? InternalWriteIgnoreErrorsAsync(buffer, offset, count) : _stream.WriteAsync(buffer, offset, count);
private async Task InternalWriteIgnoreErrorsAsync(byte[] buffer, int offset, int count)
{
try { await _stream.WriteAsync(buffer, offset, count).ConfigureAwait(false); }
catch { }
}
private void WriteCore(byte[] buffer, int offset, int size)
{
if (size == 0)
return;
byte[] bytes = null;
MemoryStream ms = GetHeaders(false);
bool chunked = _response.SendChunked;
if (ms != null)
{
long start = ms.Position; // After the possible preamble for the encoding
ms.Position = ms.Length;
if (chunked)
{
bytes = GetChunkSizeBytes(size, false);
ms.Write(bytes, 0, bytes.Length);
}
int new_count = Math.Min(size, 16384 - (int)ms.Position + (int)start);
ms.Write(buffer, offset, new_count);
size -= new_count;
offset += new_count;
InternalWrite(ms.GetBuffer(), (int)start, (int)(ms.Length - start));
ms.SetLength(0);
ms.Capacity = 0; // 'dispose' the buffer in ms.
}
else if (chunked)
{
bytes = GetChunkSizeBytes(size, false);
InternalWrite(bytes, 0, bytes.Length);
}
if (size > 0)
InternalWrite(buffer, offset, size);
if (chunked)
InternalWrite(s_crlf, 0, 2);
}
private IAsyncResult BeginWriteCore(byte[] buffer, int offset, int size, AsyncCallback cback, object state)
{
if (_closed)
{
var ares = new HttpStreamAsyncResult(this);
ares._callback = cback;
ares._state = state;
ares.Complete();
return ares;
}
byte[] bytes = null;
MemoryStream ms = GetHeaders(false);
bool chunked = _response.SendChunked;
if (ms != null)
{
long start = ms.Position;
ms.Position = ms.Length;
if (chunked)
{
bytes = GetChunkSizeBytes(size, false);
ms.Write(bytes, 0, bytes.Length);
}
ms.Write(buffer, offset, size);
buffer = ms.GetBuffer();
offset = (int)start;
size = (int)(ms.Position - start);
}
else if (chunked)
{
bytes = GetChunkSizeBytes(size, false);
InternalWrite(bytes, 0, bytes.Length);
}
return _stream.BeginWrite(buffer, offset, size, cback, state);
}
private void EndWriteCore(IAsyncResult asyncResult)
{
if (_closed)
return;
if (_ignore_errors)
{
try
{
_stream.EndWrite(asyncResult);
if (_response.SendChunked)
_stream.Write(s_crlf, 0, 2);
}
catch { }
}
else
{
_stream.EndWrite(asyncResult);
if (_response.SendChunked)
_stream.Write(s_crlf, 0, 2);
}
}
public Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken)
{
return TransmitFileManaged(path, offset, count, fileShareMode, cancellationToken);
}
const int StreamCopyToBufferSize = 81920;
private async Task TransmitFileManaged(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken)
{
var allowAsync = _environment.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows;
//if (count <= 0)
//{
// allowAsync = true;
//}
var fileOpenOptions = FileOpenOptions.SequentialScan;
if (allowAsync)
{
fileOpenOptions |= FileOpenOptions.Asynchronous;
}
// use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039
using (var fs = _fileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, fileShareMode, fileOpenOptions))
{
if (offset > 0)
{
fs.Position = offset;
}
var targetStream = this;
if (count > 0)
{
await _streamHelper.CopyToAsync(fs, targetStream, count, cancellationToken).ConfigureAwait(false);
}
else
{
await fs.CopyToAsync(targetStream, StreamCopyToBufferSize, cancellationToken).ConfigureAwait(false);
}
}
}
}
}

View File

@ -1,124 +0,0 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace SocketHttpListener.Net
{
internal sealed partial class HttpResponseStream : Stream
{
private bool _closed;
internal bool Closed => _closed;
public override bool CanRead => false;
public override bool CanSeek => false;
public override bool CanWrite => true;
public override void Flush() { }
public override Task FlushAsync(CancellationToken cancellationToken) => Task.CompletedTask;
public override long Length => throw new NotImplementedException();
public override long Position
{
get => throw new NotImplementedException();
set => throw new NotImplementedException();
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotImplementedException();
}
public override void SetLength(long value)
{
throw new NotImplementedException();
}
public override int Read(byte[] buffer, int offset, int count)
{
throw new NotImplementedException();
}
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
{
return base.BeginRead(buffer, offset, count, callback, state);
}
public override int EndRead(IAsyncResult asyncResult)
{
return base.EndRead(asyncResult);
}
public override void Write(byte[] buffer, int offset, int size)
{
if (buffer == null)
{
throw new ArgumentNullException(nameof(buffer));
}
if (offset < 0 || offset > buffer.Length)
{
throw new ArgumentOutOfRangeException(nameof(offset));
}
if (size < 0 || size > buffer.Length - offset)
{
throw new ArgumentOutOfRangeException(nameof(size));
}
if (_closed)
{
return;
}
WriteCore(buffer, offset, size);
}
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int size, AsyncCallback callback, object state)
{
if (buffer == null)
{
throw new ArgumentNullException(nameof(buffer));
}
if (offset < 0 || offset > buffer.Length)
{
throw new ArgumentOutOfRangeException(nameof(offset));
}
if (size < 0 || size > buffer.Length - offset)
{
throw new ArgumentOutOfRangeException(nameof(size));
}
return BeginWriteCore(buffer, offset, size, callback, state);
}
public override void EndWrite(IAsyncResult asyncResult)
{
if (asyncResult == null)
{
throw new ArgumentNullException(nameof(asyncResult));
}
EndWriteCore(asyncResult);
}
protected override void Dispose(bool disposing)
{
try
{
if (disposing)
{
if (_closed)
{
return;
}
_closed = true;
DisposeCore();
}
}
finally
{
base.Dispose(disposing);
}
}
}
}

View File

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

View File

@ -1,69 +0,0 @@
namespace SocketHttpListener.Net
{
internal static class HttpStatusDescription
{
internal static string Get(HttpStatusCode code)
{
return Get((int)code);
}
internal static string Get(int code)
{
switch (code)
{
case 100: return "Continue";
case 101: return "Switching Protocols";
case 102: return "Processing";
case 200: return "OK";
case 201: return "Created";
case 202: return "Accepted";
case 203: return "Non-Authoritative Information";
case 204: return "No Content";
case 205: return "Reset Content";
case 206: return "Partial Content";
case 207: return "Multi-Status";
case 300: return "Multiple Choices";
case 301: return "Moved Permanently";
case 302: return "Found";
case 303: return "See Other";
case 304: return "Not Modified";
case 305: return "Use Proxy";
case 307: return "Temporary Redirect";
case 400: return "Bad Request";
case 401: return "Unauthorized";
case 402: return "Payment Required";
case 403: return "Forbidden";
case 404: return "Not Found";
case 405: return "Method Not Allowed";
case 406: return "Not Acceptable";
case 407: return "Proxy Authentication Required";
case 408: return "Request Timeout";
case 409: return "Conflict";
case 410: return "Gone";
case 411: return "Length Required";
case 412: return "Precondition Failed";
case 413: return "Request Entity Too Large";
case 414: return "Request-Uri Too Long";
case 415: return "Unsupported Media Type";
case 416: return "Requested Range Not Satisfiable";
case 417: return "Expectation Failed";
case 422: return "Unprocessable Entity";
case 423: return "Locked";
case 424: return "Failed Dependency";
case 426: return "Upgrade Required"; // RFC 2817
case 500: return "Internal Server Error";
case 501: return "Not Implemented";
case 502: return "Bad Gateway";
case 503: return "Service Unavailable";
case 504: return "Gateway Timeout";
case 505: return "Http Version Not Supported";
case 507: return "Insufficient Storage";
}
return null;
}
}
}

View File

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

View File

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

View File

@ -1,89 +0,0 @@
using System;
using System.Net;
namespace SocketHttpListener.Net
{
internal sealed class ListenerPrefix
{
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)
{
_original = prefix;
Parse(prefix);
}
public override string ToString()
{
return _original;
}
public IPAddress[] Addresses
{
get => _addresses;
set => _addresses = value;
}
public bool Secure => _secure;
public string Host => _host;
public int Port => _port;
public string Path => _path;
// Equals and GetHashCode are required to detect duplicates in HttpListenerPrefixCollection.
public override bool Equals(object o)
{
var other = o as ListenerPrefix;
if (other == null)
return false;
return (_original == other._original);
}
public override int GetHashCode()
{
return _original.GetHashCode();
}
private void Parse(string uri)
{
ushort default_port = 80;
if (uri.StartsWith("https://"))
{
default_port = 443;
_secure = true;
}
int length = uri.Length;
int start_host = uri.IndexOf(':') + 3;
if (start_host >= length)
throw new ArgumentException("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);
root = uri.IndexOf('/', colon, length - colon);
_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);
}
if (_path.Length != 1)
_path = _path.Substring(0, _path.Length - 1);
}
}
}

View File

@ -1,20 +0,0 @@
namespace SocketHttpListener.Net
{
internal static class UriScheme
{
public const string File = "file";
public const string Ftp = "ftp";
public const string Gopher = "gopher";
public const string Http = "http";
public const string Https = "https";
public const string News = "news";
public const string NetPipe = "net.pipe";
public const string NetTcp = "net.tcp";
public const string Nntp = "nntp";
public const string Mailto = "mailto";
public const string Ws = "ws";
public const string Wss = "wss";
public const string SchemeDelimiter = "://";
}
}

View File

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

View File

@ -1,84 +0,0 @@
using System.Text;
namespace SocketHttpListener.Net
{
// we use this static class as a helper class to encode/decode HTTP headers.
// what we need is a 1-1 correspondence between a char in the range U+0000-U+00FF
// and a byte in the range 0x00-0xFF (which is the range that can hit the network).
// The Latin-1 encoding (ISO-88591-1) (GetEncoding(28591)) works for byte[] to string, but is a little slow.
// It doesn't work for string -> byte[] because of best-fit-mapping problems.
internal static class WebHeaderEncoding
{
// We don't want '?' replacement characters, just fail.
private static readonly Encoding s_utf8Decoder = Encoding.GetEncoding("utf-8", EncoderFallback.ExceptionFallback, DecoderFallback.ExceptionFallback);
internal static unsafe string GetString(byte[] bytes, int byteIndex, int byteCount)
{
fixed (byte* pBytes = bytes)
return GetString(pBytes + byteIndex, byteCount);
}
internal static unsafe string GetString(byte* pBytes, int byteCount)
{
if (byteCount < 1)
return "";
string s = new string('\0', byteCount);
fixed (char* pStr = s)
{
char* pString = pStr;
while (byteCount >= 8)
{
pString[0] = (char)pBytes[0];
pString[1] = (char)pBytes[1];
pString[2] = (char)pBytes[2];
pString[3] = (char)pBytes[3];
pString[4] = (char)pBytes[4];
pString[5] = (char)pBytes[5];
pString[6] = (char)pBytes[6];
pString[7] = (char)pBytes[7];
pString += 8;
pBytes += 8;
byteCount -= 8;
}
for (int i = 0; i < byteCount; i++)
{
pString[i] = (char)pBytes[i];
}
}
return s;
}
internal static int GetByteCount(string myString)
{
return myString.Length;
}
internal static unsafe void GetBytes(string myString, int charIndex, int charCount, byte[] bytes, int byteIndex)
{
if (myString.Length == 0)
{
return;
}
fixed (byte* bufferPointer = bytes)
{
byte* newBufferPointer = bufferPointer + byteIndex;
int finalIndex = charIndex + charCount;
while (charIndex < finalIndex)
{
*newBufferPointer++ = (byte)myString[charIndex++];
}
}
}
internal static byte[] GetBytes(string myString)
{
byte[] bytes = new byte[myString.Length];
if (myString.Length != 0)
{
GetBytes(myString, 0, myString.Length, bytes, 0);
}
return bytes;
}
}
}

View File

@ -1,92 +0,0 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Security.Principal;
using MediaBrowser.Model.Services;
namespace SocketHttpListener.Net.WebSockets
{
public class HttpListenerWebSocketContext : WebSocketContext
{
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 readonly string _origin;
private readonly IEnumerable<string> _secWebSocketProtocols;
private readonly string _secWebSocketVersion;
private readonly string _secWebSocketKey;
private readonly WebSocket _webSocket;
internal HttpListenerWebSocketContext(
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)
{
_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;
}
public override Uri RequestUri => _requestUri;
public override QueryParamCollection Headers => _headers;
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)
{
if (user != null)
{
throw new NotImplementedException();
}
return null;
}
}
}

View File

@ -1,81 +0,0 @@
using System;
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;
bool shouldSendSecWebSocketProtocolHeader =
ProcessWebSocketProtocolHeader(
request.Headers[HttpKnownHeaderNames.SecWebSocketProtocol],
subProtocol,
out var 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);
var 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);
var webSocket = new WebSocket(subProtocol);
var 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;
}
}

View File

@ -1,159 +0,0 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Security.Cryptography;
using System.Text;
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 (var 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(nameof(receiveBufferSize), "The receiveBufferSize was too small.");
}
if (sendBufferSize < MinSendBufferSize)
{
throw new ArgumentOutOfRangeException(nameof(sendBufferSize), "The sendBufferSize was too small.");
}
if (receiveBufferSize > MaxBufferSize)
{
throw new ArgumentOutOfRangeException(nameof(receiveBufferSize), "The receiveBufferSize was too large.");
}
if (sendBufferSize > MaxBufferSize)
{
throw new ArgumentOutOfRangeException(nameof(sendBufferSize), "The sendBufferSize was too large.");
}
if (keepAliveInterval < Timeout.InfiniteTimeSpan) // -1 millisecond
{
throw new ArgumentOutOfRangeException(nameof(keepAliveInterval), "The keepAliveInterval was too small.");
}
}
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");
}
}
}
}

View File

@ -1,27 +0,0 @@
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.
}
}

View File

@ -1,24 +0,0 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Security.Principal;
using MediaBrowser.Model.Services;
namespace SocketHttpListener.Net.WebSockets
{
public abstract class WebSocketContext
{
public abstract Uri RequestUri { get; }
public abstract QueryParamCollection Headers { get; }
public abstract string Origin { get; }
public abstract IEnumerable<string> SecWebSocketProtocols { get; }
public abstract string SecWebSocketVersion { get; }
public abstract string SecWebSocketKey { get; }
public abstract CookieCollection CookieCollection { get; }
public abstract IPrincipal User { get; }
public abstract bool IsAuthenticated { get; }
public abstract bool IsLocal { get; }
public abstract bool IsSecureConnection { get; }
public abstract WebSocket WebSocket { get; }
}
}

View File

@ -1,141 +0,0 @@
using System;
using System.Globalization;
using System.Text;
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));
}
}
}
}

View File

@ -4,11 +4,10 @@ using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using SocketHttpListener.Net.WebSockets;
using HttpStatusCode = SocketHttpListener.Net.HttpStatusCode;
using WebSocketState = System.Net.WebSockets.WebSocketState;
namespace SocketHttpListener