commit
c84747d88a
|
@ -511,8 +511,6 @@ namespace Emby.Server.Core
|
|||
{
|
||||
var migrations = new List<IVersionMigration>
|
||||
{
|
||||
new LibraryScanMigration(ServerConfigurationManager, TaskManager),
|
||||
new GuideMigration(ServerConfigurationManager, TaskManager)
|
||||
};
|
||||
|
||||
foreach (var task in migrations)
|
||||
|
|
|
@ -182,8 +182,6 @@
|
|||
<Compile Include="Logging\UnhandledExceptionWriter.cs" />
|
||||
<Compile Include="MediaEncoder\EncodingManager.cs" />
|
||||
<Compile Include="Migrations\IVersionMigration.cs" />
|
||||
<Compile Include="Migrations\LibraryScanMigration.cs" />
|
||||
<Compile Include="Migrations\GuideMigration.cs" />
|
||||
<Compile Include="News\NewsEntryPoint.cs" />
|
||||
<Compile Include="News\NewsService.cs" />
|
||||
<Compile Include="Notifications\CoreNotificationTypes.cs" />
|
||||
|
|
|
@ -598,9 +598,10 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
{
|
||||
ErrorHandler(ex, httpReq, false);
|
||||
}
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
ErrorHandler(ex, httpReq);
|
||||
ErrorHandler(ex, httpReq, !string.Equals(ex.GetType().Name, "SocketException", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
|
@ -53,7 +53,7 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
{
|
||||
if (!hasHeaders.Headers.ContainsKey("Server"))
|
||||
{
|
||||
hasHeaders.Headers["Server"] = "Mono-HTTPAPI/1.1, UPnP/1.0 DLNADOC/1.50";
|
||||
hasHeaders.Headers["Server"] = "Microsoft-NetCore/2.0, UPnP/1.0 DLNADOC/1.50";
|
||||
//hasHeaders.Headers["Server"] = "Mono-HTTPAPI/1.1";
|
||||
}
|
||||
|
||||
|
|
|
@ -2505,9 +2505,32 @@ namespace Emby.Server.Implementations.Library
|
|||
|
||||
public NamingOptions GetNamingOptions()
|
||||
{
|
||||
return GetNamingOptions(true);
|
||||
}
|
||||
|
||||
public NamingOptions GetNamingOptions(bool allowOptimisticEpisodeDetection)
|
||||
{
|
||||
if (!allowOptimisticEpisodeDetection)
|
||||
{
|
||||
if (_namingOptionsWithoutOptimisticEpisodeDetection == null)
|
||||
{
|
||||
var namingOptions = new ExtendedNamingOptions();
|
||||
|
||||
InitNamingOptions(namingOptions);
|
||||
namingOptions.EpisodeExpressions = namingOptions.EpisodeExpressions
|
||||
.Where(i => i.IsNamed && !i.IsOptimistic)
|
||||
.ToList();
|
||||
|
||||
_namingOptionsWithoutOptimisticEpisodeDetection = namingOptions;
|
||||
}
|
||||
|
||||
return _namingOptionsWithoutOptimisticEpisodeDetection;
|
||||
}
|
||||
|
||||
return GetNamingOptions(new LibraryOptions());
|
||||
}
|
||||
|
||||
private NamingOptions _namingOptionsWithoutOptimisticEpisodeDetection;
|
||||
private NamingOptions _namingOptions;
|
||||
private string[] _videoFileExtensions;
|
||||
public NamingOptions GetNamingOptions(LibraryOptions libraryOptions)
|
||||
|
@ -2516,23 +2539,8 @@ namespace Emby.Server.Implementations.Library
|
|||
{
|
||||
var options = new ExtendedNamingOptions();
|
||||
|
||||
// These cause apps to have problems
|
||||
options.AudioFileExtensions.Remove(".m3u");
|
||||
options.AudioFileExtensions.Remove(".wpl");
|
||||
InitNamingOptions(options);
|
||||
|
||||
//if (!libraryOptions.EnableArchiveMediaFiles)
|
||||
{
|
||||
options.AudioFileExtensions.Remove(".rar");
|
||||
options.AudioFileExtensions.Remove(".zip");
|
||||
}
|
||||
|
||||
//if (!libraryOptions.EnableArchiveMediaFiles)
|
||||
{
|
||||
options.VideoFileExtensions.Remove(".rar");
|
||||
options.VideoFileExtensions.Remove(".zip");
|
||||
}
|
||||
|
||||
options.VideoFileExtensions.Add(".tp");
|
||||
_namingOptions = options;
|
||||
_videoFileExtensions = _namingOptions.VideoFileExtensions.ToArray();
|
||||
}
|
||||
|
@ -2540,6 +2548,27 @@ namespace Emby.Server.Implementations.Library
|
|||
return _namingOptions;
|
||||
}
|
||||
|
||||
private void InitNamingOptions(NamingOptions options)
|
||||
{
|
||||
// These cause apps to have problems
|
||||
options.AudioFileExtensions.Remove(".m3u");
|
||||
options.AudioFileExtensions.Remove(".wpl");
|
||||
|
||||
//if (!libraryOptions.EnableArchiveMediaFiles)
|
||||
{
|
||||
options.AudioFileExtensions.Remove(".rar");
|
||||
options.AudioFileExtensions.Remove(".zip");
|
||||
}
|
||||
|
||||
//if (!libraryOptions.EnableArchiveMediaFiles)
|
||||
{
|
||||
options.VideoFileExtensions.Remove(".rar");
|
||||
options.VideoFileExtensions.Remove(".zip");
|
||||
}
|
||||
|
||||
options.VideoFileExtensions.Add(".tp");
|
||||
}
|
||||
|
||||
public ItemLookupInfo ParseName(string name)
|
||||
{
|
||||
var resolver = new VideoResolver(GetNamingOptions(), new NullLogger());
|
||||
|
|
|
@ -3,6 +3,7 @@ using MediaBrowser.Controller.Resolvers;
|
|||
using MediaBrowser.Model.Entities;
|
||||
using System;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using System.IO;
|
||||
|
||||
namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
||||
{
|
||||
|
@ -42,6 +43,13 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
|||
|
||||
if (_libraryManager.IsAudioFile(args.Path, libraryOptions))
|
||||
{
|
||||
if (string.Equals(Path.GetExtension(args.Path), ".cue", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// if audio file exists of same name, return null
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
var collectionType = args.GetCollectionType();
|
||||
|
||||
var isMixed = string.IsNullOrWhiteSpace(collectionType);
|
||||
|
|
|
@ -160,15 +160,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
|
|||
return true;
|
||||
}
|
||||
|
||||
var namingOptions = ((LibraryManager)libraryManager).GetNamingOptions();
|
||||
|
||||
// In mixed folders we need to be conservative and avoid expressions that may result in false positives (e.g. movies with numbers in the title)
|
||||
if (!isTvContentType)
|
||||
{
|
||||
namingOptions.EpisodeExpressions = namingOptions.EpisodeExpressions
|
||||
.Where(i => i.IsNamed && !i.IsOptimistic)
|
||||
.ToList();
|
||||
}
|
||||
var allowOptimisticEpisodeDetection = isTvContentType;
|
||||
var namingOptions = ((LibraryManager)libraryManager).GetNamingOptions(allowOptimisticEpisodeDetection);
|
||||
|
||||
var episodeResolver = new MediaBrowser.Naming.TV.EpisodeResolver(namingOptions, new NullLogger());
|
||||
var episodeInfo = episodeResolver.Resolve(fullName, false, false);
|
||||
|
|
|
@ -1,49 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Common.Updates;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
using MediaBrowser.Model.Updates;
|
||||
using System.Linq;
|
||||
|
||||
namespace Emby.Server.Implementations.Migrations
|
||||
{
|
||||
public class GuideMigration : IVersionMigration
|
||||
{
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly ITaskManager _taskManager;
|
||||
|
||||
public GuideMigration(IServerConfigurationManager config, ITaskManager taskManager)
|
||||
{
|
||||
_config = config;
|
||||
_taskManager = taskManager;
|
||||
}
|
||||
|
||||
public Task Run()
|
||||
{
|
||||
var name = "GuideRefresh3";
|
||||
|
||||
if (!_config.Configuration.Migrations.Contains(name, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
_taskManager.QueueScheduledTask(_taskManager.ScheduledTasks.Select(i => i.ScheduledTask)
|
||||
.First(i => string.Equals(i.Key, "RefreshGuide", StringComparison.OrdinalIgnoreCase)));
|
||||
});
|
||||
|
||||
var list = _config.Configuration.Migrations.ToList();
|
||||
list.Add(name);
|
||||
_config.Configuration.Migrations = list.ToArray();
|
||||
_config.SaveConfiguration();
|
||||
}
|
||||
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Common.Updates;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
using MediaBrowser.Model.Updates;
|
||||
using System.Linq;
|
||||
|
||||
namespace Emby.Server.Implementations.Migrations
|
||||
{
|
||||
public class LibraryScanMigration : IVersionMigration
|
||||
{
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly ITaskManager _taskManager;
|
||||
|
||||
public LibraryScanMigration(IServerConfigurationManager config, ITaskManager taskManager)
|
||||
{
|
||||
_config = config;
|
||||
_taskManager = taskManager;
|
||||
}
|
||||
|
||||
public Task Run()
|
||||
{
|
||||
var name = "LibraryScan6";
|
||||
|
||||
if (!_config.Configuration.Migrations.Contains(name, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
_taskManager.QueueScheduledTask(_taskManager.ScheduledTasks.Select(i => i.ScheduledTask)
|
||||
.First(i => string.Equals(i.Key, "RefreshLibrary", StringComparison.OrdinalIgnoreCase)));
|
||||
});
|
||||
|
||||
var list = _config.Configuration.Migrations.ToList();
|
||||
list.Add(name);
|
||||
_config.Configuration.Migrations = list.ToArray();
|
||||
_config.SaveConfiguration();
|
||||
}
|
||||
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -187,7 +187,6 @@ namespace MediaBrowser.Model.Configuration
|
|||
public bool DisplayCollectionsView { get; set; }
|
||||
public string[] LocalNetworkAddresses { get; set; }
|
||||
public string[] CodecsUsed { get; set; }
|
||||
public string[] Migrations { get; set; }
|
||||
public bool EnableChannelView { get; set; }
|
||||
public bool EnableExternalContentInSuggestions { get; set; }
|
||||
|
||||
|
@ -203,7 +202,6 @@ namespace MediaBrowser.Model.Configuration
|
|||
{
|
||||
LocalNetworkAddresses = new string[] { };
|
||||
CodecsUsed = new string[] { };
|
||||
Migrations = new string[] { };
|
||||
ImageExtractionTimeoutMs = 0;
|
||||
EnableLocalizedGuids = true;
|
||||
PathSubstitutions = new PathSubstitution[] { };
|
||||
|
|
|
@ -248,6 +248,22 @@ namespace MediaBrowser.Model.Net
|
|||
{
|
||||
return "audio/ac3";
|
||||
}
|
||||
if (StringHelper.EqualsIgnoreCase(ext, ".dsf"))
|
||||
{
|
||||
return "audio/dsf";
|
||||
}
|
||||
if (StringHelper.EqualsIgnoreCase(ext, ".m4b"))
|
||||
{
|
||||
return "audio/m4b";
|
||||
}
|
||||
if (StringHelper.EqualsIgnoreCase(ext, ".xsp"))
|
||||
{
|
||||
return "audio/xsp";
|
||||
}
|
||||
if (StringHelper.EqualsIgnoreCase(ext, ".dsp"))
|
||||
{
|
||||
return "audio/dsp";
|
||||
}
|
||||
|
||||
// Playlists
|
||||
if (StringHelper.EqualsIgnoreCase(ext, ".m3u8"))
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
using System.Reflection;
|
||||
|
||||
[assembly: AssemblyVersion("3.2.20.3")]
|
||||
[assembly: AssemblyVersion("3.2.20.4")]
|
||||
|
|
17
SocketHttpListener/Net/BoundaryType.cs
Normal file
17
SocketHttpListener/Net/BoundaryType.cs
Normal file
|
@ -0,0 +1,17 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SocketHttpListener.Net
|
||||
{
|
||||
internal enum BoundaryType
|
||||
{
|
||||
ContentLength = 0, // Content-Length: XXX
|
||||
Chunked = 1, // Transfer-Encoding: chunked
|
||||
Multipart = 3,
|
||||
None = 4,
|
||||
Invalid = 5,
|
||||
}
|
||||
}
|
14
SocketHttpListener/Net/EntitySendFormat.cs
Normal file
14
SocketHttpListener/Net/EntitySendFormat.cs
Normal file
|
@ -0,0 +1,14 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SocketHttpListener.Net
|
||||
{
|
||||
internal enum EntitySendFormat
|
||||
{
|
||||
ContentLength = 0, // Content-Length: XXX
|
||||
Chunked = 1, // Transfer-Encoding: chunked
|
||||
}
|
||||
}
|
|
@ -25,7 +25,7 @@ namespace SocketHttpListener.Net
|
|||
StringBuilder _currentLine;
|
||||
ListenerPrefix _prefix;
|
||||
HttpRequestStream _requestStream;
|
||||
Stream _responseStream;
|
||||
HttpResponseStream _responseStream;
|
||||
bool _chunked;
|
||||
int _reuses;
|
||||
bool _contextBound;
|
||||
|
@ -202,7 +202,7 @@ namespace SocketHttpListener.Net
|
|||
return _requestStream;
|
||||
}
|
||||
|
||||
public Stream GetResponseStream(bool isExpect100Continue = false)
|
||||
public HttpResponseStream GetResponseStream(bool isExpect100Continue = false)
|
||||
{
|
||||
// TODO: can we get this _stream before reading the input?
|
||||
if (_responseStream == null)
|
||||
|
@ -423,14 +423,14 @@ namespace SocketHttpListener.Net
|
|||
HttpListenerResponse response = _context.Response;
|
||||
response.StatusCode = status;
|
||||
response.ContentType = "text/html";
|
||||
string description = HttpListenerResponse.GetStatusDescription(status);
|
||||
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.Default.GetBytes(str);
|
||||
byte[] error = _textEncoding.GetDefaultEncoding().GetBytes(str);
|
||||
response.Close(error, false);
|
||||
}
|
||||
catch
|
||||
|
|
|
@ -29,7 +29,7 @@ namespace SocketHttpListener.Net
|
|||
_memoryStreamFactory = memoryStreamFactory;
|
||||
_textEncoding = textEncoding;
|
||||
request = new HttpListenerRequest(this, _textEncoding);
|
||||
response = new HttpListenerResponse(this, logger, _textEncoding, fileSystem);
|
||||
response = new HttpListenerResponse(this, _textEncoding);
|
||||
}
|
||||
|
||||
internal int ErrorStatus
|
||||
|
|
329
SocketHttpListener/Net/HttpListenerResponse.Managed.cs
Normal file
329
SocketHttpListener/Net/HttpListenerResponse.Managed.cs
Normal file
|
@ -0,0 +1,329 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Model.Text;
|
||||
using SocketHttpListener.Primitives;
|
||||
using System.Threading;
|
||||
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;
|
||||
private ITextEncoding _textEncoding;
|
||||
|
||||
internal HttpListenerResponse(HttpListenerContext context, ITextEncoding textEncoding)
|
||||
{
|
||||
_httpContext = context;
|
||||
_textEncoding = textEncoding;
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
thisRef.OutputStream.EndWrite(iar);
|
||||
}
|
||||
finally
|
||||
{
|
||||
thisRef.Close(false);
|
||||
}
|
||||
}, 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();
|
||||
}
|
||||
|
||||
Encoding encoding = _textEncoding.GetDefaultEncoding();
|
||||
StreamWriter 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,149 +1,128 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.Text;
|
||||
using SocketHttpListener.Primitives;
|
||||
using System.Globalization;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
|
||||
namespace SocketHttpListener.Net
|
||||
{
|
||||
public sealed class HttpListenerResponse : IDisposable
|
||||
public sealed unsafe partial class HttpListenerResponse : IDisposable
|
||||
{
|
||||
bool disposed;
|
||||
Encoding content_encoding;
|
||||
long content_length;
|
||||
bool cl_set;
|
||||
string content_type;
|
||||
CookieCollection cookies;
|
||||
WebHeaderCollection headers = new WebHeaderCollection();
|
||||
bool keep_alive = true;
|
||||
Stream output_stream;
|
||||
Version version = HttpVersion.Version11;
|
||||
string location;
|
||||
int status_code = 200;
|
||||
string status_description = "OK";
|
||||
bool chunked;
|
||||
HttpListenerContext context;
|
||||
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();
|
||||
|
||||
internal bool HeadersSent;
|
||||
internal object headers_lock = new object();
|
||||
|
||||
private readonly ILogger _logger;
|
||||
private readonly ITextEncoding _textEncoding;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
|
||||
internal HttpListenerResponse(HttpListenerContext context, ILogger logger, ITextEncoding textEncoding, IFileSystem fileSystem)
|
||||
public WebHeaderCollection Headers
|
||||
{
|
||||
this.context = context;
|
||||
_logger = logger;
|
||||
_textEncoding = textEncoding;
|
||||
_fileSystem = fileSystem;
|
||||
get => _webHeaders;
|
||||
}
|
||||
|
||||
internal bool CloseConnection
|
||||
{
|
||||
get
|
||||
{
|
||||
return headers["Connection"] == "close";
|
||||
}
|
||||
}
|
||||
public Encoding ContentEncoding { get; set; }
|
||||
|
||||
public bool ForceCloseChunked
|
||||
public string ContentType
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public Encoding ContentEncoding
|
||||
{
|
||||
get
|
||||
{
|
||||
if (content_encoding == null)
|
||||
content_encoding = _textEncoding.GetDefaultEncoding();
|
||||
return content_encoding;
|
||||
}
|
||||
get => Headers["Content-Type"];
|
||||
set
|
||||
{
|
||||
if (disposed)
|
||||
throw new ObjectDisposedException(GetType().ToString());
|
||||
|
||||
content_encoding = value;
|
||||
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 { return content_length; }
|
||||
get => _contentLength;
|
||||
set
|
||||
{
|
||||
if (disposed)
|
||||
throw new ObjectDisposedException(GetType().ToString());
|
||||
|
||||
if (HeadersSent)
|
||||
throw new InvalidOperationException("Cannot be changed after headers are sent.");
|
||||
|
||||
if (value < 0)
|
||||
throw new ArgumentOutOfRangeException("Must be >= 0", "value");
|
||||
|
||||
cl_set = true;
|
||||
content_length = value;
|
||||
CheckDisposed();
|
||||
CheckSentHeaders();
|
||||
if (value >= 0)
|
||||
{
|
||||
_contentLength = value;
|
||||
_boundaryType = BoundaryType.ContentLength;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("net_clsmall");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string ContentType
|
||||
{
|
||||
get { return content_type; }
|
||||
set
|
||||
{
|
||||
// TODO: is null ok?
|
||||
if (disposed)
|
||||
throw new ObjectDisposedException(GetType().ToString());
|
||||
|
||||
content_type = value;
|
||||
}
|
||||
}
|
||||
|
||||
// RFC 2109, 2965 + the netscape specification at http://wp.netscape.com/newsref/std/cookie_spec.html
|
||||
public CookieCollection Cookies
|
||||
{
|
||||
get
|
||||
{
|
||||
if (cookies == null)
|
||||
cookies = new CookieCollection();
|
||||
return cookies;
|
||||
}
|
||||
set { cookies = value; } // null allowed?
|
||||
}
|
||||
|
||||
public WebHeaderCollection Headers
|
||||
{
|
||||
get { return headers; }
|
||||
set
|
||||
{
|
||||
/**
|
||||
* "If you attempt to set a Content-Length, Keep-Alive, Transfer-Encoding, or
|
||||
* WWW-Authenticate header using the Headers property, an exception will be
|
||||
* thrown. Use the KeepAlive or ContentLength64 properties to set these headers.
|
||||
* You cannot set the Transfer-Encoding or WWW-Authenticate headers manually."
|
||||
*/
|
||||
// TODO: check if this is marked readonly after headers are sent.
|
||||
headers = value;
|
||||
}
|
||||
get => _cookies ?? (_cookies = new CookieCollection());
|
||||
set => _cookies = value;
|
||||
}
|
||||
|
||||
public bool KeepAlive
|
||||
{
|
||||
get { return keep_alive; }
|
||||
get => _keepAlive;
|
||||
set
|
||||
{
|
||||
if (disposed)
|
||||
throw new ObjectDisposedException(GetType().ToString());
|
||||
|
||||
keep_alive = value;
|
||||
CheckDisposed();
|
||||
_keepAlive = value;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -151,422 +130,173 @@ namespace SocketHttpListener.Net
|
|||
{
|
||||
get
|
||||
{
|
||||
if (output_stream == null)
|
||||
output_stream = context.Connection.GetResponseStream();
|
||||
return output_stream;
|
||||
}
|
||||
}
|
||||
|
||||
public Version ProtocolVersion
|
||||
{
|
||||
get { return version; }
|
||||
set
|
||||
{
|
||||
if (disposed)
|
||||
throw new ObjectDisposedException(GetType().ToString());
|
||||
|
||||
if (value == null)
|
||||
throw new ArgumentNullException("value");
|
||||
|
||||
if (value.Major != 1 || (value.Minor != 0 && value.Minor != 1))
|
||||
throw new ArgumentException("Must be 1.0 or 1.1", "value");
|
||||
|
||||
if (disposed)
|
||||
throw new ObjectDisposedException(GetType().ToString());
|
||||
|
||||
version = value;
|
||||
CheckDisposed();
|
||||
EnsureResponseStream();
|
||||
return _responseStream;
|
||||
}
|
||||
}
|
||||
|
||||
public string RedirectLocation
|
||||
{
|
||||
get { return location; }
|
||||
get => Headers["Location"];
|
||||
set
|
||||
{
|
||||
if (disposed)
|
||||
throw new ObjectDisposedException(GetType().ToString());
|
||||
|
||||
location = value;
|
||||
// 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 bool SendChunked
|
||||
{
|
||||
get { return chunked; }
|
||||
set
|
||||
{
|
||||
if (disposed)
|
||||
throw new ObjectDisposedException(GetType().ToString());
|
||||
|
||||
chunked = value;
|
||||
}
|
||||
}
|
||||
|
||||
public int StatusCode
|
||||
{
|
||||
get { return status_code; }
|
||||
set
|
||||
{
|
||||
if (disposed)
|
||||
throw new ObjectDisposedException(GetType().ToString());
|
||||
|
||||
if (value < 100 || value > 999)
|
||||
throw new ProtocolViolationException("StatusCode must be between 100 and 999.");
|
||||
status_code = value;
|
||||
status_description = GetStatusDescription(value);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string GetStatusDescription(int code)
|
||||
{
|
||||
switch (code)
|
||||
{
|
||||
case 100: return "Continue";
|
||||
case 101: return "Switching Protocols";
|
||||
case 102: return "Processing";
|
||||
case 200: return "OK";
|
||||
case 201: return "Created";
|
||||
case 202: return "Accepted";
|
||||
case 203: return "Non-Authoritative Information";
|
||||
case 204: return "No Content";
|
||||
case 205: return "Reset Content";
|
||||
case 206: return "Partial Content";
|
||||
case 207: return "Multi-Status";
|
||||
case 300: return "Multiple Choices";
|
||||
case 301: return "Moved Permanently";
|
||||
case 302: return "Found";
|
||||
case 303: return "See Other";
|
||||
case 304: return "Not Modified";
|
||||
case 305: return "Use Proxy";
|
||||
case 307: return "Temporary Redirect";
|
||||
case 400: return "Bad Request";
|
||||
case 401: return "Unauthorized";
|
||||
case 402: return "Payment Required";
|
||||
case 403: return "Forbidden";
|
||||
case 404: return "Not Found";
|
||||
case 405: return "Method Not Allowed";
|
||||
case 406: return "Not Acceptable";
|
||||
case 407: return "Proxy Authentication Required";
|
||||
case 408: return "Request Timeout";
|
||||
case 409: return "Conflict";
|
||||
case 410: return "Gone";
|
||||
case 411: return "Length Required";
|
||||
case 412: return "Precondition Failed";
|
||||
case 413: return "Request Entity Too Large";
|
||||
case 414: return "Request-Uri Too Long";
|
||||
case 415: return "Unsupported Media Type";
|
||||
case 416: return "Requested Range Not Satisfiable";
|
||||
case 417: return "Expectation Failed";
|
||||
case 422: return "Unprocessable Entity";
|
||||
case 423: return "Locked";
|
||||
case 424: return "Failed Dependency";
|
||||
case 500: return "Internal Server Error";
|
||||
case 501: return "Not Implemented";
|
||||
case 502: return "Bad Gateway";
|
||||
case 503: return "Service Unavailable";
|
||||
case 504: return "Gateway Timeout";
|
||||
case 505: return "Http Version Not Supported";
|
||||
case 507: return "Insufficient Storage";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public string StatusDescription
|
||||
{
|
||||
get { return status_description; }
|
||||
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
|
||||
{
|
||||
status_description = value;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
Close(true); //TODO: Abort or Close?
|
||||
}
|
||||
|
||||
public void Abort()
|
||||
{
|
||||
if (disposed)
|
||||
return;
|
||||
|
||||
Close(true);
|
||||
}
|
||||
|
||||
public void AddHeader(string name, string value)
|
||||
{
|
||||
if (name == null)
|
||||
throw new ArgumentNullException("name");
|
||||
Headers.Set(name, value);
|
||||
}
|
||||
|
||||
if (name == "")
|
||||
throw new ArgumentException("'name' cannot be empty", "name");
|
||||
|
||||
//TODO: check for forbidden headers and invalid characters
|
||||
if (value.Length > 65535)
|
||||
throw new ArgumentOutOfRangeException("value");
|
||||
|
||||
headers.Set(name, value);
|
||||
public void AppendHeader(string name, string value)
|
||||
{
|
||||
Headers.Add(name, value);
|
||||
}
|
||||
|
||||
public void AppendCookie(Cookie cookie)
|
||||
{
|
||||
if (cookie == null)
|
||||
throw new ArgumentNullException("cookie");
|
||||
|
||||
{
|
||||
throw new ArgumentNullException(nameof(cookie));
|
||||
}
|
||||
Cookies.Add(cookie);
|
||||
}
|
||||
|
||||
public void AppendHeader(string name, string value)
|
||||
private void ComputeCookies()
|
||||
{
|
||||
if (name == null)
|
||||
throw new ArgumentNullException("name");
|
||||
|
||||
if (name == "")
|
||||
throw new ArgumentException("'name' cannot be empty", "name");
|
||||
|
||||
if (value.Length > 65535)
|
||||
throw new ArgumentOutOfRangeException("value");
|
||||
|
||||
headers.Add(name, value);
|
||||
}
|
||||
|
||||
private void Close(bool force)
|
||||
{
|
||||
if (force)
|
||||
if (_cookies != null)
|
||||
{
|
||||
_logger.Debug("HttpListenerResponse force closing HttpConnection");
|
||||
// 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);
|
||||
// }
|
||||
//}
|
||||
}
|
||||
disposed = true;
|
||||
context.Connection.Close(force);
|
||||
}
|
||||
|
||||
public void Close(byte[] responseEntity, bool willBlock)
|
||||
{
|
||||
//CheckDisposed();
|
||||
|
||||
if (responseEntity == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(responseEntity));
|
||||
}
|
||||
|
||||
//if (_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
|
||||
{
|
||||
thisRef.OutputStream.EndWrite(iar);
|
||||
}
|
||||
finally
|
||||
{
|
||||
thisRef.Close(false);
|
||||
}
|
||||
}, this);
|
||||
}
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
if (disposed)
|
||||
return;
|
||||
|
||||
Close(false);
|
||||
}
|
||||
|
||||
public void Redirect(string url)
|
||||
{
|
||||
StatusCode = 302; // Found
|
||||
location = url;
|
||||
}
|
||||
|
||||
bool FindCookie(Cookie cookie)
|
||||
{
|
||||
string name = cookie.Name;
|
||||
string domain = cookie.Domain;
|
||||
string path = cookie.Path;
|
||||
foreach (Cookie c in cookies)
|
||||
{
|
||||
if (name != c.Name)
|
||||
continue;
|
||||
if (domain != c.Domain)
|
||||
continue;
|
||||
if (path == c.Path)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void DetermineIfChunked()
|
||||
{
|
||||
if (chunked)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Version v = context.Request.ProtocolVersion;
|
||||
if (!cl_set && !chunked && v >= HttpVersion.Version11)
|
||||
chunked = true;
|
||||
if (!chunked && string.Equals(headers["Transfer-Encoding"], "chunked"))
|
||||
{
|
||||
chunked = true;
|
||||
}
|
||||
}
|
||||
|
||||
internal void SendHeaders(bool closing, MemoryStream ms)
|
||||
{
|
||||
Encoding encoding = content_encoding;
|
||||
if (encoding == null)
|
||||
encoding = _textEncoding.GetDefaultEncoding();
|
||||
|
||||
if (content_type != null)
|
||||
{
|
||||
if (content_encoding != null && content_type.IndexOf("charset=", StringComparison.OrdinalIgnoreCase) == -1)
|
||||
{
|
||||
string enc_name = content_encoding.WebName;
|
||||
headers.SetInternal("Content-Type", content_type + "; charset=" + enc_name);
|
||||
}
|
||||
else
|
||||
{
|
||||
headers.SetInternal("Content-Type", content_type);
|
||||
}
|
||||
}
|
||||
|
||||
if (headers["Server"] == null)
|
||||
headers.SetInternal("Server", "Mono-HTTPAPI/1.0");
|
||||
|
||||
CultureInfo inv = CultureInfo.InvariantCulture;
|
||||
if (headers["Date"] == null)
|
||||
headers.SetInternal("Date", DateTime.UtcNow.ToString("r", inv));
|
||||
|
||||
if (!chunked)
|
||||
{
|
||||
if (!cl_set && closing)
|
||||
{
|
||||
cl_set = true;
|
||||
content_length = 0;
|
||||
}
|
||||
|
||||
if (cl_set)
|
||||
headers.SetInternal("Content-Length", content_length.ToString(inv));
|
||||
}
|
||||
|
||||
Version v = context.Request.ProtocolVersion;
|
||||
if (!cl_set && !chunked && v >= HttpVersion.Version11)
|
||||
chunked = true;
|
||||
|
||||
/* Apache forces closing the connection for these status codes:
|
||||
* HttpStatusCode.BadRequest 400
|
||||
* HttpStatusCode.RequestTimeout 408
|
||||
* HttpStatusCode.LengthRequired 411
|
||||
* HttpStatusCode.RequestEntityTooLarge 413
|
||||
* HttpStatusCode.RequestUriTooLong 414
|
||||
* HttpStatusCode.InternalServerError 500
|
||||
* HttpStatusCode.ServiceUnavailable 503
|
||||
*/
|
||||
bool conn_close = status_code == 400 || status_code == 408 || status_code == 411 ||
|
||||
status_code == 413 || status_code == 414 ||
|
||||
status_code == 500 ||
|
||||
status_code == 503;
|
||||
|
||||
if (conn_close == false)
|
||||
conn_close = !context.Request.KeepAlive;
|
||||
|
||||
// They sent both KeepAlive: true and Connection: close!?
|
||||
if (!keep_alive || conn_close)
|
||||
{
|
||||
headers.SetInternal("Connection", "close");
|
||||
conn_close = true;
|
||||
}
|
||||
|
||||
if (chunked)
|
||||
headers.SetInternal("Transfer-Encoding", "chunked");
|
||||
|
||||
//int reuses = context.Connection.Reuses;
|
||||
//if (reuses >= 100)
|
||||
//{
|
||||
// _logger.Debug("HttpListenerResponse - keep alive has exceeded 100 uses and will be closed.");
|
||||
|
||||
// force_close_chunked = true;
|
||||
// if (!conn_close)
|
||||
// {
|
||||
// headers.SetInternal("Connection", "close");
|
||||
// conn_close = true;
|
||||
// }
|
||||
//}
|
||||
|
||||
if (!conn_close)
|
||||
{
|
||||
if (context.Request.ProtocolVersion <= HttpVersion.Version10)
|
||||
headers.SetInternal("Connection", "keep-alive");
|
||||
}
|
||||
|
||||
if (location != null)
|
||||
headers.SetInternal("Location", location);
|
||||
|
||||
if (cookies != null)
|
||||
{
|
||||
foreach (Cookie cookie in cookies)
|
||||
headers.SetInternal("Set-Cookie", cookie.ToString());
|
||||
}
|
||||
|
||||
headers.SetInternal("Status", status_code.ToString(CultureInfo.InvariantCulture));
|
||||
|
||||
using (StreamWriter writer = new StreamWriter(ms, encoding, 256, true))
|
||||
{
|
||||
writer.Write("HTTP/{0} {1} {2}\r\n", version, status_code, status_description);
|
||||
string headers_str = headers.ToStringMultiValue();
|
||||
writer.Write(headers_str);
|
||||
writer.Flush();
|
||||
}
|
||||
|
||||
int preamble = encoding.GetPreamble().Length;
|
||||
if (output_stream == null)
|
||||
output_stream = context.Connection.GetResponseStream();
|
||||
|
||||
/* Assumes that the ms was at position 0 */
|
||||
ms.Position = preamble;
|
||||
HeadersSent = true;
|
||||
Headers["Location"] = url;
|
||||
StatusCode = (int)HttpStatusCode.Redirect;
|
||||
StatusDescription = "Found";
|
||||
}
|
||||
|
||||
public void SetCookie(Cookie cookie)
|
||||
{
|
||||
if (cookie == null)
|
||||
throw new ArgumentNullException("cookie");
|
||||
|
||||
if (cookies != null)
|
||||
{
|
||||
if (FindCookie(cookie))
|
||||
throw new ArgumentException("The cookie already exists.");
|
||||
}
|
||||
else
|
||||
{
|
||||
cookies = new CookieCollection();
|
||||
throw new ArgumentNullException(nameof(cookie));
|
||||
}
|
||||
|
||||
cookies.Add(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");
|
||||
//}
|
||||
}
|
||||
|
||||
public Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken)
|
||||
void IDisposable.Dispose() => Dispose();
|
||||
|
||||
private void CheckDisposed()
|
||||
{
|
||||
return ((HttpResponseStream)OutputStream).TransmitFile(path, offset, count, fileShareMode, cancellationToken);
|
||||
if (Disposed)
|
||||
{
|
||||
throw new ObjectDisposedException(GetType().FullName);
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckSentHeaders()
|
||||
{
|
||||
if (SentHeaders)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -104,9 +104,24 @@ namespace SocketHttpListener.Net
|
|||
return nread;
|
||||
}
|
||||
|
||||
if (_remainingBody > 0)
|
||||
{
|
||||
size = (int)Math.Min(_remainingBody, (long)size);
|
||||
}
|
||||
|
||||
nread = _stream.Read(buffer, offset, size);
|
||||
if (nread > 0 && _remainingBody > 0)
|
||||
|
||||
if (_remainingBody > 0)
|
||||
{
|
||||
if (nread == 0)
|
||||
{
|
||||
throw new Exception("Bad request");
|
||||
}
|
||||
|
||||
//Debug.Assert(nread <= _remainingBody);
|
||||
_remainingBody -= nread;
|
||||
}
|
||||
|
||||
return nread;
|
||||
}
|
||||
|
||||
|
@ -139,7 +154,7 @@ namespace SocketHttpListener.Net
|
|||
// for HTTP pipelining
|
||||
if (_remainingBody >= 0 && size > _remainingBody)
|
||||
{
|
||||
size = (int)Math.Min(int.MaxValue, _remainingBody);
|
||||
size = (int)Math.Min(_remainingBody, (long)size);
|
||||
}
|
||||
|
||||
return _stream.BeginRead(buffer, offset, size, cback, state);
|
||||
|
@ -150,9 +165,7 @@ namespace SocketHttpListener.Net
|
|||
if (asyncResult == null)
|
||||
throw new ArgumentNullException(nameof(asyncResult));
|
||||
|
||||
var r = asyncResult as HttpStreamAsyncResult;
|
||||
|
||||
if (r != null)
|
||||
if (asyncResult is HttpStreamAsyncResult r)
|
||||
{
|
||||
if (!ReferenceEquals(this, r._parent))
|
||||
{
|
||||
|
@ -160,7 +173,7 @@ namespace SocketHttpListener.Net
|
|||
}
|
||||
if (r._endCalled)
|
||||
{
|
||||
throw new InvalidOperationException("Invalid end call");
|
||||
throw new InvalidOperationException("invalid end call");
|
||||
}
|
||||
r._endCalled = true;
|
||||
|
||||
|
@ -185,8 +198,13 @@ namespace SocketHttpListener.Net
|
|||
throw e.InnerException;
|
||||
}
|
||||
|
||||
if (_remainingBody > 0 && nread > 0)
|
||||
if (_remainingBody > 0)
|
||||
{
|
||||
if (nread == 0)
|
||||
{
|
||||
throw new Exception("Bad request");
|
||||
}
|
||||
|
||||
_remainingBody -= nread;
|
||||
}
|
||||
|
||||
|
|
|
@ -132,27 +132,28 @@ namespace SocketHttpListener.Net
|
|||
|
||||
private MemoryStream GetHeaders(bool closing, bool isWebSocketHandshake = false)
|
||||
{
|
||||
// SendHeaders works on shared headers
|
||||
lock (_response.headers_lock)
|
||||
{
|
||||
if (_response.HeadersSent)
|
||||
return null;
|
||||
var ms = _memoryStreamFactory.CreateNew();
|
||||
_response.SendHeaders(closing, ms);
|
||||
return ms;
|
||||
}
|
||||
|
||||
//lock (_response._headersLock)
|
||||
//// SendHeaders works on shared headers
|
||||
//lock (_response.headers_lock)
|
||||
//{
|
||||
// if (_response.SentHeaders)
|
||||
// {
|
||||
// if (_response.HeadersSent)
|
||||
// return null;
|
||||
// }
|
||||
|
||||
// MemoryStream ms = new MemoryStream();
|
||||
// _response.SendHeaders(closing, ms, isWebSocketHandshake);
|
||||
// var ms = _memoryStreamFactory.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 };
|
||||
|
|
75
SocketHttpListener/Net/HttpStatusDescription.cs
Normal file
75
SocketHttpListener/Net/HttpStatusDescription.cs
Normal file
|
@ -0,0 +1,75 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
131
SocketHttpListener/Net/WebHeaderEncoding.cs
Normal file
131
SocketHttpListener/Net/WebHeaderEncoding.cs
Normal file
|
@ -0,0 +1,131 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
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 unsafe byte[] GetBytes(string myString)
|
||||
{
|
||||
byte[] bytes = new byte[myString.Length];
|
||||
if (myString.Length != 0)
|
||||
{
|
||||
GetBytes(myString, 0, myString.Length, bytes, 0);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
// The normal client header parser just casts bytes to chars (see GetString).
|
||||
// Check if those bytes were actually utf-8 instead of ASCII.
|
||||
// If not, just return the input value.
|
||||
internal static string DecodeUtf8FromString(string input)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(input))
|
||||
{
|
||||
return input;
|
||||
}
|
||||
|
||||
bool possibleUtf8 = false;
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
{
|
||||
if (input[i] > (char)255)
|
||||
{
|
||||
return input; // This couldn't have come from the wire, someone assigned it directly.
|
||||
}
|
||||
else if (input[i] > (char)127)
|
||||
{
|
||||
possibleUtf8 = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (possibleUtf8)
|
||||
{
|
||||
byte[] rawBytes = new byte[input.Length];
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
{
|
||||
if (input[i] > (char)255)
|
||||
{
|
||||
return input; // This couldn't have come from the wire, someone assigned it directly.
|
||||
}
|
||||
rawBytes[i] = (byte)input[i];
|
||||
}
|
||||
try
|
||||
{
|
||||
return s_utf8Decoder.GetString(rawBytes);
|
||||
}
|
||||
catch (ArgumentException) { } // Not actually Utf-8
|
||||
}
|
||||
return input;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -21,6 +21,7 @@
|
|||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
|
@ -56,27 +57,32 @@
|
|||
<Compile Include="Mask.cs" />
|
||||
<Compile Include="MessageEventArgs.cs" />
|
||||
<Compile Include="Net\AuthenticationSchemeSelector.cs" />
|
||||
<Compile Include="Net\BoundaryType.cs" />
|
||||
<Compile Include="Net\ChunkedInputStream.cs" />
|
||||
<Compile Include="Net\ChunkStream.cs" />
|
||||
<Compile Include="Net\CookieHelper.cs" />
|
||||
<Compile Include="Net\EndPointListener.cs" />
|
||||
<Compile Include="Net\EndPointManager.cs" />
|
||||
<Compile Include="Net\EntitySendFormat.cs" />
|
||||
<Compile Include="Net\HttpConnection.cs" />
|
||||
<Compile Include="Net\HttpListener.cs" />
|
||||
<Compile Include="Net\HttpListenerBasicIdentity.cs" />
|
||||
<Compile Include="Net\HttpListenerContext.cs" />
|
||||
<Compile Include="Net\HttpListenerPrefixCollection.cs" />
|
||||
<Compile Include="Net\HttpListenerRequest.cs" />
|
||||
<Compile Include="Net\HttpListenerResponse.Managed.cs" />
|
||||
<Compile Include="Net\HttpListenerResponse.cs" />
|
||||
<Compile Include="Net\HttpRequestStream.cs" />
|
||||
<Compile Include="Net\HttpRequestStream.Managed.cs" />
|
||||
<Compile Include="Net\HttpResponseStream.cs" />
|
||||
<Compile Include="Net\HttpResponseStream.Managed.cs" />
|
||||
<Compile Include="Net\HttpStatusCode.cs" />
|
||||
<Compile Include="Net\HttpStatusDescription.cs" />
|
||||
<Compile Include="Net\HttpStreamAsyncResult.cs" />
|
||||
<Compile Include="Net\HttpVersion.cs" />
|
||||
<Compile Include="Net\ListenerPrefix.cs" />
|
||||
<Compile Include="Net\WebHeaderCollection.cs" />
|
||||
<Compile Include="Net\WebHeaderEncoding.cs" />
|
||||
<Compile Include="Net\WebSockets\HttpListenerWebSocketContext.cs" />
|
||||
<Compile Include="Net\WebSockets\WebSocketContext.cs" />
|
||||
<Compile Include="Opcode.cs" />
|
||||
|
|
Loading…
Reference in New Issue
Block a user