mirror of
https://github.com/jellyfin/jellyfin.git
synced 2024-11-17 10:58:58 -07:00
commit
c84747d88a
@ -511,8 +511,6 @@ namespace Emby.Server.Core
|
|||||||
{
|
{
|
||||||
var migrations = new List<IVersionMigration>
|
var migrations = new List<IVersionMigration>
|
||||||
{
|
{
|
||||||
new LibraryScanMigration(ServerConfigurationManager, TaskManager),
|
|
||||||
new GuideMigration(ServerConfigurationManager, TaskManager)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
foreach (var task in migrations)
|
foreach (var task in migrations)
|
||||||
|
@ -182,8 +182,6 @@
|
|||||||
<Compile Include="Logging\UnhandledExceptionWriter.cs" />
|
<Compile Include="Logging\UnhandledExceptionWriter.cs" />
|
||||||
<Compile Include="MediaEncoder\EncodingManager.cs" />
|
<Compile Include="MediaEncoder\EncodingManager.cs" />
|
||||||
<Compile Include="Migrations\IVersionMigration.cs" />
|
<Compile Include="Migrations\IVersionMigration.cs" />
|
||||||
<Compile Include="Migrations\LibraryScanMigration.cs" />
|
|
||||||
<Compile Include="Migrations\GuideMigration.cs" />
|
|
||||||
<Compile Include="News\NewsEntryPoint.cs" />
|
<Compile Include="News\NewsEntryPoint.cs" />
|
||||||
<Compile Include="News\NewsService.cs" />
|
<Compile Include="News\NewsService.cs" />
|
||||||
<Compile Include="Notifications\CoreNotificationTypes.cs" />
|
<Compile Include="Notifications\CoreNotificationTypes.cs" />
|
||||||
|
@ -598,9 +598,10 @@ namespace Emby.Server.Implementations.HttpServer
|
|||||||
{
|
{
|
||||||
ErrorHandler(ex, httpReq, false);
|
ErrorHandler(ex, httpReq, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
ErrorHandler(ex, httpReq);
|
ErrorHandler(ex, httpReq, !string.Equals(ex.GetType().Name, "SocketException", StringComparison.OrdinalIgnoreCase));
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
@ -53,7 +53,7 @@ namespace Emby.Server.Implementations.HttpServer
|
|||||||
{
|
{
|
||||||
if (!hasHeaders.Headers.ContainsKey("Server"))
|
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";
|
//hasHeaders.Headers["Server"] = "Mono-HTTPAPI/1.1";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2505,9 +2505,32 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
public NamingOptions GetNamingOptions()
|
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());
|
return GetNamingOptions(new LibraryOptions());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private NamingOptions _namingOptionsWithoutOptimisticEpisodeDetection;
|
||||||
private NamingOptions _namingOptions;
|
private NamingOptions _namingOptions;
|
||||||
private string[] _videoFileExtensions;
|
private string[] _videoFileExtensions;
|
||||||
public NamingOptions GetNamingOptions(LibraryOptions libraryOptions)
|
public NamingOptions GetNamingOptions(LibraryOptions libraryOptions)
|
||||||
@ -2516,23 +2539,8 @@ namespace Emby.Server.Implementations.Library
|
|||||||
{
|
{
|
||||||
var options = new ExtendedNamingOptions();
|
var options = new ExtendedNamingOptions();
|
||||||
|
|
||||||
// These cause apps to have problems
|
InitNamingOptions(options);
|
||||||
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");
|
|
||||||
_namingOptions = options;
|
_namingOptions = options;
|
||||||
_videoFileExtensions = _namingOptions.VideoFileExtensions.ToArray();
|
_videoFileExtensions = _namingOptions.VideoFileExtensions.ToArray();
|
||||||
}
|
}
|
||||||
@ -2540,6 +2548,27 @@ namespace Emby.Server.Implementations.Library
|
|||||||
return _namingOptions;
|
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)
|
public ItemLookupInfo ParseName(string name)
|
||||||
{
|
{
|
||||||
var resolver = new VideoResolver(GetNamingOptions(), new NullLogger());
|
var resolver = new VideoResolver(GetNamingOptions(), new NullLogger());
|
||||||
|
@ -3,6 +3,7 @@ using MediaBrowser.Controller.Resolvers;
|
|||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using System;
|
using System;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
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 (_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 collectionType = args.GetCollectionType();
|
||||||
|
|
||||||
var isMixed = string.IsNullOrWhiteSpace(collectionType);
|
var isMixed = string.IsNullOrWhiteSpace(collectionType);
|
||||||
|
@ -160,15 +160,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var namingOptions = ((LibraryManager)libraryManager).GetNamingOptions();
|
var allowOptimisticEpisodeDetection = isTvContentType;
|
||||||
|
var namingOptions = ((LibraryManager)libraryManager).GetNamingOptions(allowOptimisticEpisodeDetection);
|
||||||
// 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 episodeResolver = new MediaBrowser.Naming.TV.EpisodeResolver(namingOptions, new NullLogger());
|
var episodeResolver = new MediaBrowser.Naming.TV.EpisodeResolver(namingOptions, new NullLogger());
|
||||||
var episodeInfo = episodeResolver.Resolve(fullName, false, false);
|
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 bool DisplayCollectionsView { get; set; }
|
||||||
public string[] LocalNetworkAddresses { get; set; }
|
public string[] LocalNetworkAddresses { get; set; }
|
||||||
public string[] CodecsUsed { get; set; }
|
public string[] CodecsUsed { get; set; }
|
||||||
public string[] Migrations { get; set; }
|
|
||||||
public bool EnableChannelView { get; set; }
|
public bool EnableChannelView { get; set; }
|
||||||
public bool EnableExternalContentInSuggestions { get; set; }
|
public bool EnableExternalContentInSuggestions { get; set; }
|
||||||
|
|
||||||
@ -203,7 +202,6 @@ namespace MediaBrowser.Model.Configuration
|
|||||||
{
|
{
|
||||||
LocalNetworkAddresses = new string[] { };
|
LocalNetworkAddresses = new string[] { };
|
||||||
CodecsUsed = new string[] { };
|
CodecsUsed = new string[] { };
|
||||||
Migrations = new string[] { };
|
|
||||||
ImageExtractionTimeoutMs = 0;
|
ImageExtractionTimeoutMs = 0;
|
||||||
EnableLocalizedGuids = true;
|
EnableLocalizedGuids = true;
|
||||||
PathSubstitutions = new PathSubstitution[] { };
|
PathSubstitutions = new PathSubstitution[] { };
|
||||||
|
@ -248,6 +248,22 @@ namespace MediaBrowser.Model.Net
|
|||||||
{
|
{
|
||||||
return "audio/ac3";
|
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
|
// Playlists
|
||||||
if (StringHelper.EqualsIgnoreCase(ext, ".m3u8"))
|
if (StringHelper.EqualsIgnoreCase(ext, ".m3u8"))
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
using System.Reflection;
|
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;
|
StringBuilder _currentLine;
|
||||||
ListenerPrefix _prefix;
|
ListenerPrefix _prefix;
|
||||||
HttpRequestStream _requestStream;
|
HttpRequestStream _requestStream;
|
||||||
Stream _responseStream;
|
HttpResponseStream _responseStream;
|
||||||
bool _chunked;
|
bool _chunked;
|
||||||
int _reuses;
|
int _reuses;
|
||||||
bool _contextBound;
|
bool _contextBound;
|
||||||
@ -202,7 +202,7 @@ namespace SocketHttpListener.Net
|
|||||||
return _requestStream;
|
return _requestStream;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Stream GetResponseStream(bool isExpect100Continue = false)
|
public HttpResponseStream GetResponseStream(bool isExpect100Continue = false)
|
||||||
{
|
{
|
||||||
// TODO: can we get this _stream before reading the input?
|
// TODO: can we get this _stream before reading the input?
|
||||||
if (_responseStream == null)
|
if (_responseStream == null)
|
||||||
@ -423,14 +423,14 @@ namespace SocketHttpListener.Net
|
|||||||
HttpListenerResponse response = _context.Response;
|
HttpListenerResponse response = _context.Response;
|
||||||
response.StatusCode = status;
|
response.StatusCode = status;
|
||||||
response.ContentType = "text/html";
|
response.ContentType = "text/html";
|
||||||
string description = HttpListenerResponse.GetStatusDescription(status);
|
string description = HttpStatusDescription.Get(status);
|
||||||
string str;
|
string str;
|
||||||
if (msg != null)
|
if (msg != null)
|
||||||
str = string.Format("<h1>{0} ({1})</h1>", description, msg);
|
str = string.Format("<h1>{0} ({1})</h1>", description, msg);
|
||||||
else
|
else
|
||||||
str = string.Format("<h1>{0}</h1>", description);
|
str = string.Format("<h1>{0}</h1>", description);
|
||||||
|
|
||||||
byte[] error = Encoding.Default.GetBytes(str);
|
byte[] error = _textEncoding.GetDefaultEncoding().GetBytes(str);
|
||||||
response.Close(error, false);
|
response.Close(error, false);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
|
@ -29,7 +29,7 @@ namespace SocketHttpListener.Net
|
|||||||
_memoryStreamFactory = memoryStreamFactory;
|
_memoryStreamFactory = memoryStreamFactory;
|
||||||
_textEncoding = textEncoding;
|
_textEncoding = textEncoding;
|
||||||
request = new HttpListenerRequest(this, _textEncoding);
|
request = new HttpListenerRequest(this, _textEncoding);
|
||||||
response = new HttpListenerResponse(this, logger, _textEncoding, fileSystem);
|
response = new HttpListenerResponse(this, _textEncoding);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal int ErrorStatus
|
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;
|
||||||
using System.Globalization;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Model.IO;
|
using System.Globalization;
|
||||||
using MediaBrowser.Model.Logging;
|
using System.Runtime.InteropServices;
|
||||||
using MediaBrowser.Model.Text;
|
using System.ComponentModel;
|
||||||
using SocketHttpListener.Primitives;
|
using System.Diagnostics;
|
||||||
|
using Microsoft.Win32.SafeHandles;
|
||||||
|
|
||||||
namespace SocketHttpListener.Net
|
namespace SocketHttpListener.Net
|
||||||
{
|
{
|
||||||
public sealed class HttpListenerResponse : IDisposable
|
public sealed unsafe partial class HttpListenerResponse : IDisposable
|
||||||
{
|
{
|
||||||
bool disposed;
|
private BoundaryType _boundaryType = BoundaryType.None;
|
||||||
Encoding content_encoding;
|
private CookieCollection _cookies;
|
||||||
long content_length;
|
private HttpListenerContext _httpContext;
|
||||||
bool cl_set;
|
private bool _keepAlive = true;
|
||||||
string content_type;
|
private HttpResponseStream _responseStream;
|
||||||
CookieCollection cookies;
|
private string _statusDescription;
|
||||||
WebHeaderCollection headers = new WebHeaderCollection();
|
private WebHeaderCollection _webHeaders = new WebHeaderCollection();
|
||||||
bool keep_alive = true;
|
|
||||||
Stream output_stream;
|
|
||||||
Version version = HttpVersion.Version11;
|
|
||||||
string location;
|
|
||||||
int status_code = 200;
|
|
||||||
string status_description = "OK";
|
|
||||||
bool chunked;
|
|
||||||
HttpListenerContext context;
|
|
||||||
|
|
||||||
internal bool HeadersSent;
|
public WebHeaderCollection Headers
|
||||||
internal object headers_lock = new object();
|
|
||||||
|
|
||||||
private readonly ILogger _logger;
|
|
||||||
private readonly ITextEncoding _textEncoding;
|
|
||||||
private readonly IFileSystem _fileSystem;
|
|
||||||
|
|
||||||
internal HttpListenerResponse(HttpListenerContext context, ILogger logger, ITextEncoding textEncoding, IFileSystem fileSystem)
|
|
||||||
{
|
{
|
||||||
this.context = context;
|
get => _webHeaders;
|
||||||
_logger = logger;
|
|
||||||
_textEncoding = textEncoding;
|
|
||||||
_fileSystem = fileSystem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal bool CloseConnection
|
public Encoding ContentEncoding { get; set; }
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return headers["Connection"] == "close";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool ForceCloseChunked
|
public string ContentType
|
||||||
{
|
{
|
||||||
get { return false; }
|
get => Headers["Content-Type"];
|
||||||
}
|
|
||||||
|
|
||||||
public Encoding ContentEncoding
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (content_encoding == null)
|
|
||||||
content_encoding = _textEncoding.GetDefaultEncoding();
|
|
||||||
return content_encoding;
|
|
||||||
}
|
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (disposed)
|
CheckDisposed();
|
||||||
throw new ObjectDisposedException(GetType().ToString());
|
if (string.IsNullOrEmpty(value))
|
||||||
|
{
|
||||||
content_encoding = 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
|
public long ContentLength64
|
||||||
{
|
{
|
||||||
get { return content_length; }
|
get => _contentLength;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (disposed)
|
CheckDisposed();
|
||||||
throw new ObjectDisposedException(GetType().ToString());
|
CheckSentHeaders();
|
||||||
|
if (value >= 0)
|
||||||
if (HeadersSent)
|
{
|
||||||
throw new InvalidOperationException("Cannot be changed after headers are sent.");
|
_contentLength = value;
|
||||||
|
_boundaryType = BoundaryType.ContentLength;
|
||||||
if (value < 0)
|
}
|
||||||
throw new ArgumentOutOfRangeException("Must be >= 0", "value");
|
else
|
||||||
|
{
|
||||||
cl_set = true;
|
throw new ArgumentOutOfRangeException("net_clsmall");
|
||||||
content_length = value;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string ContentType
|
|
||||||
{
|
|
||||||
get { return content_type; }
|
|
||||||
set
|
|
||||||
{
|
|
||||||
// TODO: is null ok?
|
|
||||||
if (disposed)
|
|
||||||
throw new ObjectDisposedException(GetType().ToString());
|
|
||||||
|
|
||||||
content_type = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// RFC 2109, 2965 + the netscape specification at http://wp.netscape.com/newsref/std/cookie_spec.html
|
|
||||||
public CookieCollection Cookies
|
public CookieCollection Cookies
|
||||||
{
|
{
|
||||||
get
|
get => _cookies ?? (_cookies = new CookieCollection());
|
||||||
{
|
set => _cookies = value;
|
||||||
if (cookies == null)
|
|
||||||
cookies = new CookieCollection();
|
|
||||||
return cookies;
|
|
||||||
}
|
|
||||||
set { cookies = value; } // null allowed?
|
|
||||||
}
|
|
||||||
|
|
||||||
public WebHeaderCollection Headers
|
|
||||||
{
|
|
||||||
get { return headers; }
|
|
||||||
set
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* "If you attempt to set a Content-Length, Keep-Alive, Transfer-Encoding, or
|
|
||||||
* WWW-Authenticate header using the Headers property, an exception will be
|
|
||||||
* thrown. Use the KeepAlive or ContentLength64 properties to set these headers.
|
|
||||||
* You cannot set the Transfer-Encoding or WWW-Authenticate headers manually."
|
|
||||||
*/
|
|
||||||
// TODO: check if this is marked readonly after headers are sent.
|
|
||||||
headers = value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool KeepAlive
|
public bool KeepAlive
|
||||||
{
|
{
|
||||||
get { return keep_alive; }
|
get => _keepAlive;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (disposed)
|
CheckDisposed();
|
||||||
throw new ObjectDisposedException(GetType().ToString());
|
_keepAlive = value;
|
||||||
|
|
||||||
keep_alive = value;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,422 +130,173 @@ namespace SocketHttpListener.Net
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (output_stream == null)
|
CheckDisposed();
|
||||||
output_stream = context.Connection.GetResponseStream();
|
EnsureResponseStream();
|
||||||
return output_stream;
|
return _responseStream;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Version ProtocolVersion
|
|
||||||
{
|
|
||||||
get { return version; }
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (disposed)
|
|
||||||
throw new ObjectDisposedException(GetType().ToString());
|
|
||||||
|
|
||||||
if (value == null)
|
|
||||||
throw new ArgumentNullException("value");
|
|
||||||
|
|
||||||
if (value.Major != 1 || (value.Minor != 0 && value.Minor != 1))
|
|
||||||
throw new ArgumentException("Must be 1.0 or 1.1", "value");
|
|
||||||
|
|
||||||
if (disposed)
|
|
||||||
throw new ObjectDisposedException(GetType().ToString());
|
|
||||||
|
|
||||||
version = value;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string RedirectLocation
|
public string RedirectLocation
|
||||||
{
|
{
|
||||||
get { return location; }
|
get => Headers["Location"];
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (disposed)
|
// note that this doesn't set the status code to a redirect one
|
||||||
throw new ObjectDisposedException(GetType().ToString());
|
CheckDisposed();
|
||||||
|
if (string.IsNullOrEmpty(value))
|
||||||
location = 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
|
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
|
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)
|
public void AddHeader(string name, string value)
|
||||||
{
|
{
|
||||||
if (name == null)
|
Headers.Set(name, value);
|
||||||
throw new ArgumentNullException("name");
|
}
|
||||||
|
|
||||||
if (name == "")
|
public void AppendHeader(string name, string value)
|
||||||
throw new ArgumentException("'name' cannot be empty", "name");
|
{
|
||||||
|
Headers.Add(name, value);
|
||||||
//TODO: check for forbidden headers and invalid characters
|
|
||||||
if (value.Length > 65535)
|
|
||||||
throw new ArgumentOutOfRangeException("value");
|
|
||||||
|
|
||||||
headers.Set(name, value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AppendCookie(Cookie cookie)
|
public void AppendCookie(Cookie cookie)
|
||||||
{
|
{
|
||||||
if (cookie == null)
|
if (cookie == null)
|
||||||
throw new ArgumentNullException("cookie");
|
{
|
||||||
|
throw new ArgumentNullException(nameof(cookie));
|
||||||
|
}
|
||||||
Cookies.Add(cookie);
|
Cookies.Add(cookie);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AppendHeader(string name, string value)
|
private void ComputeCookies()
|
||||||
{
|
{
|
||||||
if (name == null)
|
if (_cookies != null)
|
||||||
throw new ArgumentNullException("name");
|
|
||||||
|
|
||||||
if (name == "")
|
|
||||||
throw new ArgumentException("'name' cannot be empty", "name");
|
|
||||||
|
|
||||||
if (value.Length > 65535)
|
|
||||||
throw new ArgumentOutOfRangeException("value");
|
|
||||||
|
|
||||||
headers.Add(name, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Close(bool force)
|
|
||||||
{
|
|
||||||
if (force)
|
|
||||||
{
|
{
|
||||||
_logger.Debug("HttpListenerResponse force closing HttpConnection");
|
// 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)
|
public void Redirect(string url)
|
||||||
{
|
{
|
||||||
StatusCode = 302; // Found
|
Headers["Location"] = url;
|
||||||
location = url;
|
StatusCode = (int)HttpStatusCode.Redirect;
|
||||||
}
|
StatusDescription = "Found";
|
||||||
|
|
||||||
bool FindCookie(Cookie cookie)
|
|
||||||
{
|
|
||||||
string name = cookie.Name;
|
|
||||||
string domain = cookie.Domain;
|
|
||||||
string path = cookie.Path;
|
|
||||||
foreach (Cookie c in cookies)
|
|
||||||
{
|
|
||||||
if (name != c.Name)
|
|
||||||
continue;
|
|
||||||
if (domain != c.Domain)
|
|
||||||
continue;
|
|
||||||
if (path == c.Path)
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void DetermineIfChunked()
|
|
||||||
{
|
|
||||||
if (chunked)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Version v = context.Request.ProtocolVersion;
|
|
||||||
if (!cl_set && !chunked && v >= HttpVersion.Version11)
|
|
||||||
chunked = true;
|
|
||||||
if (!chunked && string.Equals(headers["Transfer-Encoding"], "chunked"))
|
|
||||||
{
|
|
||||||
chunked = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void SendHeaders(bool closing, MemoryStream ms)
|
|
||||||
{
|
|
||||||
Encoding encoding = content_encoding;
|
|
||||||
if (encoding == null)
|
|
||||||
encoding = _textEncoding.GetDefaultEncoding();
|
|
||||||
|
|
||||||
if (content_type != null)
|
|
||||||
{
|
|
||||||
if (content_encoding != null && content_type.IndexOf("charset=", StringComparison.OrdinalIgnoreCase) == -1)
|
|
||||||
{
|
|
||||||
string enc_name = content_encoding.WebName;
|
|
||||||
headers.SetInternal("Content-Type", content_type + "; charset=" + enc_name);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
headers.SetInternal("Content-Type", content_type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (headers["Server"] == null)
|
|
||||||
headers.SetInternal("Server", "Mono-HTTPAPI/1.0");
|
|
||||||
|
|
||||||
CultureInfo inv = CultureInfo.InvariantCulture;
|
|
||||||
if (headers["Date"] == null)
|
|
||||||
headers.SetInternal("Date", DateTime.UtcNow.ToString("r", inv));
|
|
||||||
|
|
||||||
if (!chunked)
|
|
||||||
{
|
|
||||||
if (!cl_set && closing)
|
|
||||||
{
|
|
||||||
cl_set = true;
|
|
||||||
content_length = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cl_set)
|
|
||||||
headers.SetInternal("Content-Length", content_length.ToString(inv));
|
|
||||||
}
|
|
||||||
|
|
||||||
Version v = context.Request.ProtocolVersion;
|
|
||||||
if (!cl_set && !chunked && v >= HttpVersion.Version11)
|
|
||||||
chunked = true;
|
|
||||||
|
|
||||||
/* Apache forces closing the connection for these status codes:
|
|
||||||
* HttpStatusCode.BadRequest 400
|
|
||||||
* HttpStatusCode.RequestTimeout 408
|
|
||||||
* HttpStatusCode.LengthRequired 411
|
|
||||||
* HttpStatusCode.RequestEntityTooLarge 413
|
|
||||||
* HttpStatusCode.RequestUriTooLong 414
|
|
||||||
* HttpStatusCode.InternalServerError 500
|
|
||||||
* HttpStatusCode.ServiceUnavailable 503
|
|
||||||
*/
|
|
||||||
bool conn_close = status_code == 400 || status_code == 408 || status_code == 411 ||
|
|
||||||
status_code == 413 || status_code == 414 ||
|
|
||||||
status_code == 500 ||
|
|
||||||
status_code == 503;
|
|
||||||
|
|
||||||
if (conn_close == false)
|
|
||||||
conn_close = !context.Request.KeepAlive;
|
|
||||||
|
|
||||||
// They sent both KeepAlive: true and Connection: close!?
|
|
||||||
if (!keep_alive || conn_close)
|
|
||||||
{
|
|
||||||
headers.SetInternal("Connection", "close");
|
|
||||||
conn_close = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (chunked)
|
|
||||||
headers.SetInternal("Transfer-Encoding", "chunked");
|
|
||||||
|
|
||||||
//int reuses = context.Connection.Reuses;
|
|
||||||
//if (reuses >= 100)
|
|
||||||
//{
|
|
||||||
// _logger.Debug("HttpListenerResponse - keep alive has exceeded 100 uses and will be closed.");
|
|
||||||
|
|
||||||
// force_close_chunked = true;
|
|
||||||
// if (!conn_close)
|
|
||||||
// {
|
|
||||||
// headers.SetInternal("Connection", "close");
|
|
||||||
// conn_close = true;
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
||||||
if (!conn_close)
|
|
||||||
{
|
|
||||||
if (context.Request.ProtocolVersion <= HttpVersion.Version10)
|
|
||||||
headers.SetInternal("Connection", "keep-alive");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (location != null)
|
|
||||||
headers.SetInternal("Location", location);
|
|
||||||
|
|
||||||
if (cookies != null)
|
|
||||||
{
|
|
||||||
foreach (Cookie cookie in cookies)
|
|
||||||
headers.SetInternal("Set-Cookie", cookie.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
headers.SetInternal("Status", status_code.ToString(CultureInfo.InvariantCulture));
|
|
||||||
|
|
||||||
using (StreamWriter writer = new StreamWriter(ms, encoding, 256, true))
|
|
||||||
{
|
|
||||||
writer.Write("HTTP/{0} {1} {2}\r\n", version, status_code, status_description);
|
|
||||||
string headers_str = headers.ToStringMultiValue();
|
|
||||||
writer.Write(headers_str);
|
|
||||||
writer.Flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
int preamble = encoding.GetPreamble().Length;
|
|
||||||
if (output_stream == null)
|
|
||||||
output_stream = context.Connection.GetResponseStream();
|
|
||||||
|
|
||||||
/* Assumes that the ms was at position 0 */
|
|
||||||
ms.Position = preamble;
|
|
||||||
HeadersSent = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetCookie(Cookie cookie)
|
public void SetCookie(Cookie cookie)
|
||||||
{
|
{
|
||||||
if (cookie == null)
|
if (cookie == null)
|
||||||
throw new ArgumentNullException("cookie");
|
|
||||||
|
|
||||||
if (cookies != null)
|
|
||||||
{
|
{
|
||||||
if (FindCookie(cookie))
|
throw new ArgumentNullException(nameof(cookie));
|
||||||
throw new ArgumentException("The cookie already exists.");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
cookies = new CookieCollection();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
return nread;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_remainingBody > 0)
|
||||||
|
{
|
||||||
|
size = (int)Math.Min(_remainingBody, (long)size);
|
||||||
|
}
|
||||||
|
|
||||||
nread = _stream.Read(buffer, offset, 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;
|
_remainingBody -= nread;
|
||||||
|
}
|
||||||
|
|
||||||
return nread;
|
return nread;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,7 +154,7 @@ namespace SocketHttpListener.Net
|
|||||||
// for HTTP pipelining
|
// for HTTP pipelining
|
||||||
if (_remainingBody >= 0 && size > _remainingBody)
|
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);
|
return _stream.BeginRead(buffer, offset, size, cback, state);
|
||||||
@ -150,9 +165,7 @@ namespace SocketHttpListener.Net
|
|||||||
if (asyncResult == null)
|
if (asyncResult == null)
|
||||||
throw new ArgumentNullException(nameof(asyncResult));
|
throw new ArgumentNullException(nameof(asyncResult));
|
||||||
|
|
||||||
var r = asyncResult as HttpStreamAsyncResult;
|
if (asyncResult is HttpStreamAsyncResult r)
|
||||||
|
|
||||||
if (r != null)
|
|
||||||
{
|
{
|
||||||
if (!ReferenceEquals(this, r._parent))
|
if (!ReferenceEquals(this, r._parent))
|
||||||
{
|
{
|
||||||
@ -160,7 +173,7 @@ namespace SocketHttpListener.Net
|
|||||||
}
|
}
|
||||||
if (r._endCalled)
|
if (r._endCalled)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Invalid end call");
|
throw new InvalidOperationException("invalid end call");
|
||||||
}
|
}
|
||||||
r._endCalled = true;
|
r._endCalled = true;
|
||||||
|
|
||||||
@ -185,8 +198,13 @@ namespace SocketHttpListener.Net
|
|||||||
throw e.InnerException;
|
throw e.InnerException;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_remainingBody > 0 && nread > 0)
|
if (_remainingBody > 0)
|
||||||
{
|
{
|
||||||
|
if (nread == 0)
|
||||||
|
{
|
||||||
|
throw new Exception("Bad request");
|
||||||
|
}
|
||||||
|
|
||||||
_remainingBody -= nread;
|
_remainingBody -= nread;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,27 +132,28 @@ namespace SocketHttpListener.Net
|
|||||||
|
|
||||||
private MemoryStream GetHeaders(bool closing, bool isWebSocketHandshake = false)
|
private MemoryStream GetHeaders(bool closing, bool isWebSocketHandshake = false)
|
||||||
{
|
{
|
||||||
// SendHeaders works on shared headers
|
//// SendHeaders works on shared headers
|
||||||
lock (_response.headers_lock)
|
//lock (_response.headers_lock)
|
||||||
{
|
|
||||||
if (_response.HeadersSent)
|
|
||||||
return null;
|
|
||||||
var ms = _memoryStreamFactory.CreateNew();
|
|
||||||
_response.SendHeaders(closing, ms);
|
|
||||||
return ms;
|
|
||||||
}
|
|
||||||
|
|
||||||
//lock (_response._headersLock)
|
|
||||||
//{
|
//{
|
||||||
// if (_response.SentHeaders)
|
// if (_response.HeadersSent)
|
||||||
// {
|
|
||||||
// return null;
|
// return null;
|
||||||
// }
|
// var ms = _memoryStreamFactory.CreateNew();
|
||||||
|
// _response.SendHeaders(closing, ms);
|
||||||
// MemoryStream ms = new MemoryStream();
|
|
||||||
// _response.SendHeaders(closing, ms, isWebSocketHandshake);
|
|
||||||
// return 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[] 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>
|
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||||
<ErrorReport>prompt</ErrorReport>
|
<ErrorReport>prompt</ErrorReport>
|
||||||
<WarningLevel>4</WarningLevel>
|
<WarningLevel>4</WarningLevel>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||||
<DebugType>pdbonly</DebugType>
|
<DebugType>pdbonly</DebugType>
|
||||||
@ -56,27 +57,32 @@
|
|||||||
<Compile Include="Mask.cs" />
|
<Compile Include="Mask.cs" />
|
||||||
<Compile Include="MessageEventArgs.cs" />
|
<Compile Include="MessageEventArgs.cs" />
|
||||||
<Compile Include="Net\AuthenticationSchemeSelector.cs" />
|
<Compile Include="Net\AuthenticationSchemeSelector.cs" />
|
||||||
|
<Compile Include="Net\BoundaryType.cs" />
|
||||||
<Compile Include="Net\ChunkedInputStream.cs" />
|
<Compile Include="Net\ChunkedInputStream.cs" />
|
||||||
<Compile Include="Net\ChunkStream.cs" />
|
<Compile Include="Net\ChunkStream.cs" />
|
||||||
<Compile Include="Net\CookieHelper.cs" />
|
<Compile Include="Net\CookieHelper.cs" />
|
||||||
<Compile Include="Net\EndPointListener.cs" />
|
<Compile Include="Net\EndPointListener.cs" />
|
||||||
<Compile Include="Net\EndPointManager.cs" />
|
<Compile Include="Net\EndPointManager.cs" />
|
||||||
|
<Compile Include="Net\EntitySendFormat.cs" />
|
||||||
<Compile Include="Net\HttpConnection.cs" />
|
<Compile Include="Net\HttpConnection.cs" />
|
||||||
<Compile Include="Net\HttpListener.cs" />
|
<Compile Include="Net\HttpListener.cs" />
|
||||||
<Compile Include="Net\HttpListenerBasicIdentity.cs" />
|
<Compile Include="Net\HttpListenerBasicIdentity.cs" />
|
||||||
<Compile Include="Net\HttpListenerContext.cs" />
|
<Compile Include="Net\HttpListenerContext.cs" />
|
||||||
<Compile Include="Net\HttpListenerPrefixCollection.cs" />
|
<Compile Include="Net\HttpListenerPrefixCollection.cs" />
|
||||||
<Compile Include="Net\HttpListenerRequest.cs" />
|
<Compile Include="Net\HttpListenerRequest.cs" />
|
||||||
|
<Compile Include="Net\HttpListenerResponse.Managed.cs" />
|
||||||
<Compile Include="Net\HttpListenerResponse.cs" />
|
<Compile Include="Net\HttpListenerResponse.cs" />
|
||||||
<Compile Include="Net\HttpRequestStream.cs" />
|
<Compile Include="Net\HttpRequestStream.cs" />
|
||||||
<Compile Include="Net\HttpRequestStream.Managed.cs" />
|
<Compile Include="Net\HttpRequestStream.Managed.cs" />
|
||||||
<Compile Include="Net\HttpResponseStream.cs" />
|
<Compile Include="Net\HttpResponseStream.cs" />
|
||||||
<Compile Include="Net\HttpResponseStream.Managed.cs" />
|
<Compile Include="Net\HttpResponseStream.Managed.cs" />
|
||||||
<Compile Include="Net\HttpStatusCode.cs" />
|
<Compile Include="Net\HttpStatusCode.cs" />
|
||||||
|
<Compile Include="Net\HttpStatusDescription.cs" />
|
||||||
<Compile Include="Net\HttpStreamAsyncResult.cs" />
|
<Compile Include="Net\HttpStreamAsyncResult.cs" />
|
||||||
<Compile Include="Net\HttpVersion.cs" />
|
<Compile Include="Net\HttpVersion.cs" />
|
||||||
<Compile Include="Net\ListenerPrefix.cs" />
|
<Compile Include="Net\ListenerPrefix.cs" />
|
||||||
<Compile Include="Net\WebHeaderCollection.cs" />
|
<Compile Include="Net\WebHeaderCollection.cs" />
|
||||||
|
<Compile Include="Net\WebHeaderEncoding.cs" />
|
||||||
<Compile Include="Net\WebSockets\HttpListenerWebSocketContext.cs" />
|
<Compile Include="Net\WebSockets\HttpListenerWebSocketContext.cs" />
|
||||||
<Compile Include="Net\WebSockets\WebSocketContext.cs" />
|
<Compile Include="Net\WebSockets\WebSocketContext.cs" />
|
||||||
<Compile Include="Opcode.cs" />
|
<Compile Include="Opcode.cs" />
|
||||||
|
Loading…
Reference in New Issue
Block a user