aboutsummaryrefslogtreecommitdiff
path: root/Jellyfin.Server
diff options
context:
space:
mode:
authorBond-009 <bond.009@outlook.com>2019-02-16 17:05:44 +0100
committerGitHub <noreply@github.com>2019-02-16 17:05:44 +0100
commit4811e76860c9ad71c14dc8548e70ef001458bf80 (patch)
tree997310af3cace45c32d502ebf7841c41efcef078 /Jellyfin.Server
parent5835c4b21d5ab2666478f29c240d2e56316e1f67 (diff)
parent25c2267a89af5c2e82774c638cdad0defcc894b5 (diff)
Merge branch 'master' into perf
Diffstat (limited to 'Jellyfin.Server')
-rw-r--r--Jellyfin.Server/CoreAppHost.cs11
-rw-r--r--Jellyfin.Server/Jellyfin.Server.csproj7
-rw-r--r--Jellyfin.Server/Program.cs215
-rw-r--r--Jellyfin.Server/SocketSharp/RequestMono.cs152
-rw-r--r--Jellyfin.Server/SocketSharp/SharpWebSocket.cs44
-rw-r--r--Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs61
-rw-r--r--Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs25
-rw-r--r--Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs20
8 files changed, 314 insertions, 221 deletions
diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs
index 315e34a04..126cb467f 100644
--- a/Jellyfin.Server/CoreAppHost.cs
+++ b/Jellyfin.Server/CoreAppHost.cs
@@ -18,15 +18,17 @@ namespace Jellyfin.Server
public override bool CanSelfRestart => StartupOptions.RestartPath != null;
+ protected override bool SupportsDualModeSockets => true;
+
protected override void RestartInternal() => Program.Restart();
protected override IEnumerable<Assembly> GetAssembliesWithPartsInternal()
- => new[] { typeof(CoreAppHost).Assembly };
+ {
+ yield return typeof(CoreAppHost).Assembly;
+ }
protected override void ShutdownInternal() => Program.Shutdown();
- protected override bool SupportsDualModeSockets => true;
-
protected override IHttpListener CreateHttpListener()
=> new WebSocketSharpListener(
Logger,
@@ -37,7 +39,6 @@ namespace Jellyfin.Server
CryptographyProvider,
SupportsDualModeSockets,
FileSystemManager,
- EnvironmentInfo
- );
+ EnvironmentInfo);
}
}
diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj
index b1515df43..bd670df52 100644
--- a/Jellyfin.Server/Jellyfin.Server.csproj
+++ b/Jellyfin.Server/Jellyfin.Server.csproj
@@ -5,11 +5,14 @@
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
+ <GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<PropertyGroup>
<!-- We need C# 7.1 for async main-->
<LangVersion>latest</LangVersion>
+ <!-- Disable documentation warnings (for now) -->
+ <NoWarn>SA1600;CS1591</NoWarn>
</PropertyGroup>
<ItemGroup>
@@ -20,6 +23,10 @@
<EmbeddedResource Include="Resources/Configuration/*" />
</ItemGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
+ <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+ </PropertyGroup>
+
<!-- Code analysers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.6.3" />
diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs
index 7826fde35..ac5aab460 100644
--- a/Jellyfin.Server/Program.cs
+++ b/Jellyfin.Server/Program.cs
@@ -21,6 +21,7 @@ using MediaBrowser.Controller.Drawing;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Serilog;
using Serilog.AspNetCore;
@@ -56,13 +57,28 @@ namespace Jellyfin.Server
errs => Task.FromResult(0)).ConfigureAwait(false);
}
+ public static void Shutdown()
+ {
+ if (!_tokenSource.IsCancellationRequested)
+ {
+ _tokenSource.Cancel();
+ }
+ }
+
+ public static void Restart()
+ {
+ _restartOnShutdown = true;
+
+ Shutdown();
+ }
+
private static async Task StartApp(StartupOptions options)
{
ServerApplicationPaths appPaths = CreateApplicationPaths(options);
// $JELLYFIN_LOG_DIR needs to be set for the logger configuration manager
Environment.SetEnvironmentVariable("JELLYFIN_LOG_DIR", appPaths.LogDirectoryPath);
- await CreateLogger(appPaths);
+ await CreateLogger(appPaths).ConfigureAwait(false);
_logger = _loggerFactory.CreateLogger("Main");
AppDomain.CurrentDomain.UnhandledException += (sender, e)
@@ -75,6 +91,7 @@ namespace Jellyfin.Server
{
return; // Already shutting down
}
+
e.Cancel = true;
_logger.LogInformation("Ctrl+C, shutting down");
Environment.ExitCode = 128 + 2;
@@ -88,6 +105,7 @@ namespace Jellyfin.Server
{
return; // Already shutting down
}
+
_logger.LogInformation("Received a SIGTERM signal, shutting down");
Environment.ExitCode = 128 + 15;
Shutdown();
@@ -101,7 +119,7 @@ namespace Jellyfin.Server
SQLitePCL.Batteries_V2.Init();
// Allow all https requests
- ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(delegate { return true; });
+ ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(delegate { return true; } );
var fileSystem = new ManagedFileSystem(_loggerFactory, environmentInfo, null, appPaths.TempDirectory, true);
@@ -114,18 +132,18 @@ namespace Jellyfin.Server
new NullImageEncoder(),
new NetworkManager(_loggerFactory, environmentInfo)))
{
- await appHost.Init();
+ await appHost.Init(new ServiceCollection()).ConfigureAwait(false);
appHost.ImageProcessor.ImageEncoder = GetImageEncoder(fileSystem, appPaths, appHost.LocalizationManager);
- await appHost.RunStartupTasks();
+ await appHost.RunStartupTasks().ConfigureAwait(false);
// TODO: read input for a stop command
try
{
// Block main thread until shutdown
- await Task.Delay(-1, _tokenSource.Token);
+ await Task.Delay(-1, _tokenSource.Token).ConfigureAwait(false);
}
catch (TaskCanceledException)
{
@@ -139,112 +157,156 @@ namespace Jellyfin.Server
}
}
+ /// <summary>
+ /// Create the data, config and log paths from the variety of inputs(command line args,
+ /// environment variables) or decide on what default to use. For Windows it's %AppPath%
+ /// for everything else the XDG approach is followed:
+ /// https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
+ /// </summary>
+ /// <param name="options">StartupOptions</param>
+ /// <returns>ServerApplicationPaths</returns>
private static ServerApplicationPaths CreateApplicationPaths(StartupOptions options)
{
- string programDataPath = Environment.GetEnvironmentVariable("JELLYFIN_DATA_PATH");
- if (string.IsNullOrEmpty(programDataPath))
+ // dataDir
+ // IF --datadir
+ // ELSE IF $JELLYFIN_DATA_PATH
+ // ELSE IF windows, use <%APPDATA%>/jellyfin
+ // ELSE IF $XDG_DATA_HOME then use $XDG_DATA_HOME/jellyfin
+ // ELSE use $HOME/.local/share/jellyfin
+ var dataDir = options.DataDir;
+
+ if (string.IsNullOrEmpty(dataDir))
{
- if (options.DataDir != null)
- {
- programDataPath = options.DataDir;
- }
- else
+ dataDir = Environment.GetEnvironmentVariable("JELLYFIN_DATA_PATH");
+
+ if (string.IsNullOrEmpty(dataDir))
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
- programDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
+ dataDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
}
else
{
// $XDG_DATA_HOME defines the base directory relative to which user specific data files should be stored.
- programDataPath = Environment.GetEnvironmentVariable("XDG_DATA_HOME");
- // If $XDG_DATA_HOME is either not set or empty, $HOME/.local/share should be used.
- if (string.IsNullOrEmpty(programDataPath))
+ dataDir = Environment.GetEnvironmentVariable("XDG_DATA_HOME");
+
+ // If $XDG_DATA_HOME is either not set or empty, a default equal to $HOME/.local/share should be used.
+ if (string.IsNullOrEmpty(dataDir))
{
- programDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", "share");
+ dataDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", "share");
}
}
- programDataPath = Path.Combine(programDataPath, "jellyfin");
+ dataDir = Path.Combine(dataDir, "jellyfin");
}
}
- if (string.IsNullOrEmpty(programDataPath))
- {
- Console.WriteLine("Cannot continue without path to program data folder (try -programdata)");
- Environment.Exit(1);
- }
- else
- {
- Directory.CreateDirectory(programDataPath);
- }
+ // configDir
+ // IF --configdir
+ // ELSE IF $JELLYFIN_CONFIG_DIR
+ // ELSE IF --datadir, use <datadir>/config (assume portable run)
+ // ELSE IF <datadir>/config exists, use that
+ // ELSE IF windows, use <datadir>/config
+ // ELSE IF $XDG_CONFIG_HOME use $XDG_CONFIG_HOME/jellyfin
+ // ELSE $HOME/.config/jellyfin
+ var configDir = options.ConfigDir;
- string configDir = Environment.GetEnvironmentVariable("JELLYFIN_CONFIG_DIR");
if (string.IsNullOrEmpty(configDir))
{
- if (options.ConfigDir != null)
- {
- configDir = options.ConfigDir;
- }
- else
+ configDir = Environment.GetEnvironmentVariable("JELLYFIN_CONFIG_DIR");
+
+ if (string.IsNullOrEmpty(configDir))
{
- // Let BaseApplicationPaths set up the default value
- configDir = null;
+ if (options.DataDir != null || Directory.Exists(Path.Combine(dataDir, "config")) || RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ // Hang config folder off already set dataDir
+ configDir = Path.Combine(dataDir, "config");
+ }
+ else
+ {
+ // $XDG_CONFIG_HOME defines the base directory relative to which user specific configuration files should be stored.
+ configDir = Environment.GetEnvironmentVariable("XDG_CONFIG_HOME");
+
+ // If $XDG_CONFIG_HOME is either not set or empty, a default equal to $HOME /.config should be used.
+ if (string.IsNullOrEmpty(configDir))
+ {
+ configDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".config");
+ }
+
+ configDir = Path.Combine(configDir, "jellyfin");
+ }
}
}
- if (configDir != null)
- {
- Directory.CreateDirectory(configDir);
- }
+ // cacheDir
+ // IF --cachedir
+ // ELSE IF $JELLYFIN_CACHE_DIR
+ // ELSE IF windows, use <datadir>/cache
+ // ELSE IF XDG_CACHE_HOME, use $XDG_CACHE_HOME/jellyfin
+ // ELSE HOME/.cache/jellyfin
+ var cacheDir = options.CacheDir;
- string cacheDir = Environment.GetEnvironmentVariable("JELLYFIN_CACHE_DIR");
if (string.IsNullOrEmpty(cacheDir))
{
- if (options.CacheDir != null)
- {
- cacheDir = options.CacheDir;
- }
- else if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ cacheDir = Environment.GetEnvironmentVariable("JELLYFIN_CACHE_DIR");
+
+ if (string.IsNullOrEmpty(cacheDir))
{
- // $XDG_CACHE_HOME defines the base directory relative to which user specific non-essential data files should be stored.
- cacheDir = Environment.GetEnvironmentVariable("XDG_CACHE_HOME");
- // If $XDG_CACHE_HOME is either not set or empty, $HOME/.cache should be used.
- if (string.IsNullOrEmpty(cacheDir))
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
- cacheDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".cache");
+ // Hang cache folder off already set dataDir
+ cacheDir = Path.Combine(dataDir, "cache");
+ }
+ else
+ {
+ // $XDG_CACHE_HOME defines the base directory relative to which user specific non-essential data files should be stored.
+ cacheDir = Environment.GetEnvironmentVariable("XDG_CACHE_HOME");
+
+ // If $XDG_CACHE_HOME is either not set or empty, a default equal to $HOME/.cache should be used.
+ if (string.IsNullOrEmpty(cacheDir))
+ {
+ cacheDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".cache");
+ }
+
+ cacheDir = Path.Combine(cacheDir, "jellyfin");
}
- cacheDir = Path.Combine(cacheDir, "jellyfin");
}
}
- if (cacheDir != null)
- {
- Directory.CreateDirectory(cacheDir);
- }
+ // logDir
+ // IF --logdir
+ // ELSE IF $JELLYFIN_LOG_DIR
+ // ELSE IF --datadir, use <datadir>/log (assume portable run)
+ // ELSE <datadir>/log
+ var logDir = options.LogDir;
- string logDir = Environment.GetEnvironmentVariable("JELLYFIN_LOG_DIR");
if (string.IsNullOrEmpty(logDir))
{
- if (options.LogDir != null)
- {
- logDir = options.LogDir;
- }
- else
+ logDir = Environment.GetEnvironmentVariable("JELLYFIN_LOG_DIR");
+
+ if (string.IsNullOrEmpty(logDir))
{
- // Let BaseApplicationPaths set up the default value
- logDir = null;
+ // Hang log folder off already set dataDir
+ logDir = Path.Combine(dataDir, "log");
}
}
- if (logDir != null)
+ // Ensure the main folders exist before we continue
+ try
{
+ Directory.CreateDirectory(dataDir);
Directory.CreateDirectory(logDir);
+ Directory.CreateDirectory(configDir);
+ Directory.CreateDirectory(cacheDir);
+ }
+ catch (IOException ex)
+ {
+ Console.Error.WriteLine("Error whilst attempting to create folder");
+ Console.Error.WriteLine(ex.ToString());
+ Environment.Exit(1);
}
- string appPath = AppContext.BaseDirectory;
-
- return new ServerApplicationPaths(programDataPath, appPath, appPath, logDir, configDir, cacheDir);
+ return new ServerApplicationPaths(dataDir, logDir, configDir, cacheDir);
}
private static async Task CreateLogger(IApplicationPaths appPaths)
@@ -263,6 +325,7 @@ namespace Jellyfin.Server
await rscstr.CopyToAsync(fstr).ConfigureAwait(false);
}
}
+
var configuration = new ConfigurationBuilder()
.SetBasePath(appPaths.ConfigurationDirectoryPath)
.AddJsonFile("logging.json")
@@ -290,7 +353,7 @@ namespace Jellyfin.Server
}
}
- public static IImageEncoder GetImageEncoder(
+ private static IImageEncoder GetImageEncoder(
IFileSystem fileSystem,
IApplicationPaths appPaths,
ILocalizationManager localizationManager)
@@ -331,26 +394,12 @@ namespace Jellyfin.Server
{
return MediaBrowser.Model.System.OperatingSystem.BSD;
}
+
throw new Exception($"Can't resolve OS with description: '{osDescription}'");
}
}
}
- public static void Shutdown()
- {
- if (!_tokenSource.IsCancellationRequested)
- {
- _tokenSource.Cancel();
- }
- }
-
- public static void Restart()
- {
- _restartOnShutdown = true;
-
- Shutdown();
- }
-
private static void StartNewInstance(StartupOptions options)
{
_logger.LogInformation("Starting new instance");
diff --git a/Jellyfin.Server/SocketSharp/RequestMono.cs b/Jellyfin.Server/SocketSharp/RequestMono.cs
index a8ba4cdb5..24cf994ea 100644
--- a/Jellyfin.Server/SocketSharp/RequestMono.cs
+++ b/Jellyfin.Server/SocketSharp/RequestMono.cs
@@ -13,7 +13,7 @@ namespace Jellyfin.Server.SocketSharp
{
internal static string GetParameter(string header, string attr)
{
- int ap = header.IndexOf(attr);
+ int ap = header.IndexOf(attr, StringComparison.Ordinal);
if (ap == -1)
{
return null;
@@ -82,9 +82,7 @@ namespace Jellyfin.Server.SocketSharp
}
else
{
- //
// We use a substream, as in 2.x we will support large uploads streamed to disk,
- //
var sub = new HttpPostedFile(e.Filename, e.ContentType, input, e.Start, e.Length);
files[e.Name] = sub;
}
@@ -127,8 +125,12 @@ namespace Jellyfin.Server.SocketSharp
public string Authorization => string.IsNullOrEmpty(request.Headers["Authorization"]) ? null : request.Headers["Authorization"];
- protected bool validate_cookies, validate_query_string, validate_form;
- protected bool checked_cookies, checked_query_string, checked_form;
+ protected bool validate_cookies { get; set; }
+ protected bool validate_query_string { get; set; }
+ protected bool validate_form { get; set; }
+ protected bool checked_cookies { get; set; }
+ protected bool checked_query_string { get; set; }
+ protected bool checked_form { get; set; }
private static void ThrowValidationException(string name, string key, string value)
{
@@ -138,8 +140,12 @@ namespace Jellyfin.Server.SocketSharp
v = v.Substring(0, 16) + "...\"";
}
- string msg = string.Format("A potentially dangerous Request.{0} value was " +
- "detected from the client ({1}={2}).", name, key, v);
+ string msg = string.Format(
+ CultureInfo.InvariantCulture,
+ "A potentially dangerous Request.{0} value was detected from the client ({1}={2}).",
+ name,
+ key,
+ v);
throw new Exception(msg);
}
@@ -179,6 +185,7 @@ namespace Jellyfin.Server.SocketSharp
for (int idx = 1; idx < len; idx++)
{
char next = val[idx];
+
// See http://secunia.com/advisories/14325
if (current == '<' || current == '\xff1c')
{
@@ -256,6 +263,7 @@ namespace Jellyfin.Server.SocketSharp
value.Append((char)c);
}
}
+
if (c == -1)
{
AddRawKeyValue(form, key, value);
@@ -271,6 +279,7 @@ namespace Jellyfin.Server.SocketSharp
key.Append((char)c);
}
}
+
if (c == -1)
{
AddRawKeyValue(form, key, value);
@@ -308,6 +317,7 @@ namespace Jellyfin.Server.SocketSharp
result.Append(key);
result.Append('=');
}
+
result.Append(pair.Value);
}
@@ -429,13 +439,13 @@ namespace Jellyfin.Server.SocketSharp
real = position + d;
break;
default:
- throw new ArgumentException(nameof(origin));
+ throw new ArgumentException("Unknown SeekOrigin value", nameof(origin));
}
long virt = real - offset;
if (virt < 0 || virt > Length)
{
- throw new ArgumentException();
+ throw new ArgumentException("Invalid position", nameof(d));
}
position = s.Seek(real, SeekOrigin.Begin);
@@ -491,11 +501,6 @@ namespace Jellyfin.Server.SocketSharp
public Stream InputStream => stream;
}
- private class Helpers
- {
- public static readonly CultureInfo InvariantCulture = CultureInfo.InvariantCulture;
- }
-
internal static class StrUtils
{
public static bool StartsWith(string str1, string str2, bool ignore_case)
@@ -533,12 +538,17 @@ namespace Jellyfin.Server.SocketSharp
public class Element
{
- public string ContentType;
- public string Name;
- public string Filename;
- public Encoding Encoding;
- public long Start;
- public long Length;
+ public string ContentType { get; set; }
+
+ public string Name { get; set; }
+
+ public string Filename { get; set; }
+
+ public Encoding Encoding { get; set; }
+
+ public long Start { get; set; }
+
+ public long Length { get; set; }
public override string ToString()
{
@@ -547,15 +557,23 @@ namespace Jellyfin.Server.SocketSharp
}
}
+ private const byte LF = (byte)'\n';
+
+ private const byte CR = (byte)'\r';
+
private Stream data;
+
private string boundary;
- private byte[] boundary_bytes;
+
+ private byte[] boundaryBytes;
+
private byte[] buffer;
- private bool at_eof;
+
+ private bool atEof;
+
private Encoding encoding;
- private StringBuilder sb;
- private const byte LF = (byte)'\n', CR = (byte)'\r';
+ private StringBuilder sb;
// See RFC 2046
// In the case of multipart entities, in which one or more different
@@ -570,18 +588,48 @@ namespace Jellyfin.Server.SocketSharp
public HttpMultipart(Stream data, string b, Encoding encoding)
{
this.data = data;
- //DB: 30/01/11: cannot set or read the Position in HttpListener in Win.NET
- //var ms = new MemoryStream(32 * 1024);
- //data.CopyTo(ms);
- //this.data = ms;
-
boundary = b;
- boundary_bytes = encoding.GetBytes(b);
- buffer = new byte[boundary_bytes.Length + 2]; // CRLF or '--'
+ boundaryBytes = encoding.GetBytes(b);
+ buffer = new byte[boundaryBytes.Length + 2]; // CRLF or '--'
this.encoding = encoding;
sb = new StringBuilder();
}
+ public Element ReadNextElement()
+ {
+ if (atEof || ReadBoundary())
+ {
+ return null;
+ }
+
+ var elem = new Element();
+ string header;
+ while ((header = ReadHeaders()) != null)
+ {
+ if (StrUtils.StartsWith(header, "Content-Disposition:", true))
+ {
+ elem.Name = GetContentDispositionAttribute(header, "name");
+ elem.Filename = StripPath(GetContentDispositionAttributeWithEncoding(header, "filename"));
+ }
+ else if (StrUtils.StartsWith(header, "Content-Type:", true))
+ {
+ elem.ContentType = header.Substring("Content-Type:".Length).Trim();
+ elem.Encoding = GetEncoding(elem.ContentType);
+ }
+ }
+
+ long start = data.Position;
+ elem.Start = start;
+ long pos = MoveToNextBoundary();
+ if (pos == -1)
+ {
+ return null;
+ }
+
+ elem.Length = pos - start;
+ return elem;
+ }
+
private string ReadLine()
{
// CRLF or LF are ok as line endings.
@@ -600,6 +648,7 @@ namespace Jellyfin.Server.SocketSharp
{
break;
}
+
got_cr = b == CR;
sb.Append((char)b);
}
@@ -769,7 +818,7 @@ namespace Jellyfin.Server.SocketSharp
return -1;
}
- if (!CompareBytes(boundary_bytes, buffer))
+ if (!CompareBytes(boundaryBytes, buffer))
{
state = 0;
data.Position = retval + 2;
@@ -785,7 +834,7 @@ namespace Jellyfin.Server.SocketSharp
if (buffer[bl - 2] == '-' && buffer[bl - 1] == '-')
{
- at_eof = true;
+ atEof = true;
}
else if (buffer[bl - 2] != CR || buffer[bl - 1] != LF)
{
@@ -800,6 +849,7 @@ namespace Jellyfin.Server.SocketSharp
c = data.ReadByte();
continue;
}
+
data.Position = retval + 2;
if (got_cr)
{
@@ -818,42 +868,6 @@ namespace Jellyfin.Server.SocketSharp
return retval;
}
- public Element ReadNextElement()
- {
- if (at_eof || ReadBoundary())
- {
- return null;
- }
-
- var elem = new Element();
- string header;
- while ((header = ReadHeaders()) != null)
- {
- if (StrUtils.StartsWith(header, "Content-Disposition:", true))
- {
- elem.Name = GetContentDispositionAttribute(header, "name");
- elem.Filename = StripPath(GetContentDispositionAttributeWithEncoding(header, "filename"));
- }
- else if (StrUtils.StartsWith(header, "Content-Type:", true))
- {
- elem.ContentType = header.Substring("Content-Type:".Length).Trim();
- elem.Encoding = GetEncoding(elem.ContentType);
- }
- }
-
- long start = 0;
- start = data.Position;
- elem.Start = start;
- long pos = MoveToNextBoundary();
- if (pos == -1)
- {
- return null;
- }
-
- elem.Length = pos - start;
- return elem;
- }
-
private static string StripPath(string path)
{
if (path == null || path.Length == 0)
diff --git a/Jellyfin.Server/SocketSharp/SharpWebSocket.cs b/Jellyfin.Server/SocketSharp/SharpWebSocket.cs
index f371cb25a..6eee4cd12 100644
--- a/Jellyfin.Server/SocketSharp/SharpWebSocket.cs
+++ b/Jellyfin.Server/SocketSharp/SharpWebSocket.cs
@@ -24,6 +24,7 @@ namespace Jellyfin.Server.SocketSharp
private TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>();
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
+ private bool _disposed = false;
public SharpWebSocket(SocketHttpListener.WebSocket socket, ILogger logger)
{
@@ -40,9 +41,9 @@ namespace Jellyfin.Server.SocketSharp
_logger = logger;
WebSocket = socket;
- socket.OnMessage += socket_OnMessage;
- socket.OnClose += socket_OnClose;
- socket.OnError += socket_OnError;
+ socket.OnMessage += OnSocketMessage;
+ socket.OnClose += OnSocketClose;
+ socket.OnError += OnSocketError;
WebSocket.ConnectAsServer();
}
@@ -52,29 +53,22 @@ namespace Jellyfin.Server.SocketSharp
return _taskCompletionSource.Task;
}
- void socket_OnError(object sender, SocketHttpListener.ErrorEventArgs e)
+ private void OnSocketError(object sender, SocketHttpListener.ErrorEventArgs e)
{
_logger.LogError("Error in SharpWebSocket: {Message}", e.Message ?? string.Empty);
- //Closed?.Invoke(this, EventArgs.Empty);
+
+ // Closed?.Invoke(this, EventArgs.Empty);
}
- void socket_OnClose(object sender, SocketHttpListener.CloseEventArgs e)
+ private void OnSocketClose(object sender, SocketHttpListener.CloseEventArgs e)
{
_taskCompletionSource.TrySetResult(true);
Closed?.Invoke(this, EventArgs.Empty);
}
- void socket_OnMessage(object sender, SocketHttpListener.MessageEventArgs e)
+ private void OnSocketMessage(object sender, SocketHttpListener.MessageEventArgs e)
{
- //if (!string.IsNullOrEmpty(e.Data))
- //{
- // if (OnReceive != null)
- // {
- // OnReceive(e.Data);
- // }
- // return;
- //}
if (OnReceiveBytes != null)
{
OnReceiveBytes(e.RawData);
@@ -117,6 +111,7 @@ namespace Jellyfin.Server.SocketSharp
public void Dispose()
{
Dispose(true);
+ GC.SuppressFinalize(this);
}
/// <summary>
@@ -125,16 +120,23 @@ namespace Jellyfin.Server.SocketSharp
/// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose(bool dispose)
{
+ if (_disposed)
+ {
+ return;
+ }
+
if (dispose)
{
- WebSocket.OnMessage -= socket_OnMessage;
- WebSocket.OnClose -= socket_OnClose;
- WebSocket.OnError -= socket_OnError;
+ WebSocket.OnMessage -= OnSocketMessage;
+ WebSocket.OnClose -= OnSocketClose;
+ WebSocket.OnError -= OnSocketError;
_cancellationTokenSource.Cancel();
WebSocket.Close();
}
+
+ _disposed = true;
}
/// <summary>
@@ -142,11 +144,5 @@ namespace Jellyfin.Server.SocketSharp
/// </summary>
/// <value>The receive action.</value>
public Action<byte[]> OnReceiveBytes { get; set; }
-
- /// <summary>
- /// Gets or sets the on receive.
- /// </summary>
- /// <value>The on receive.</value>
- public Action<string> OnReceive { get; set; }
}
}
diff --git a/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs b/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs
index a44343ab2..58c4d38a2 100644
--- a/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs
+++ b/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs
@@ -34,9 +34,16 @@ namespace Jellyfin.Server.SocketSharp
private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource();
private CancellationToken _disposeCancellationToken;
- public WebSocketSharpListener(ILogger logger, X509Certificate certificate, IStreamHelper streamHelper,
- INetworkManager networkManager, ISocketFactory socketFactory, ICryptoProvider cryptoProvider,
- bool enableDualMode, IFileSystem fileSystem, IEnvironmentInfo environment)
+ public WebSocketSharpListener(
+ ILogger logger,
+ X509Certificate certificate,
+ IStreamHelper streamHelper,
+ INetworkManager networkManager,
+ ISocketFactory socketFactory,
+ ICryptoProvider cryptoProvider,
+ bool enableDualMode,
+ IFileSystem fileSystem,
+ IEnvironmentInfo environment)
{
_logger = logger;
_certificate = certificate;
@@ -61,7 +68,9 @@ namespace Jellyfin.Server.SocketSharp
public void Start(IEnumerable<string> urlPrefixes)
{
if (_listener == null)
+ {
_listener = new HttpListener(_logger, _cryptoProvider, _socketFactory, _networkManager, _streamHelper, _fileSystem, _environment);
+ }
_listener.EnableDualMode = _enableDualMode;
@@ -83,15 +92,18 @@ namespace Jellyfin.Server.SocketSharp
private void ProcessContext(HttpListenerContext context)
{
- var _ = Task.Run(async () => await InitTask(context, _disposeCancellationToken));
+ _ = Task.Run(async () => await InitTask(context, _disposeCancellationToken).ConfigureAwait(false));
}
private static void LogRequest(ILogger logger, HttpListenerRequest request)
{
var url = request.Url.ToString();
- logger.LogInformation("{0} {1}. UserAgent: {2}",
- request.IsWebSocketRequest ? "WS" : "HTTP " + request.HttpMethod, url, request.UserAgent ?? string.Empty);
+ logger.LogInformation(
+ "{0} {1}. UserAgent: {2}",
+ request.IsWebSocketRequest ? "WS" : "HTTP " + request.HttpMethod,
+ url,
+ request.UserAgent ?? string.Empty);
}
private Task InitTask(HttpListenerContext context, CancellationToken cancellationToken)
@@ -201,7 +213,7 @@ namespace Jellyfin.Server.SocketSharp
}
catch (ObjectDisposedException)
{
- //TODO Investigate and properly fix.
+ // TODO: Investigate and properly fix.
}
catch (Exception ex)
{
@@ -223,38 +235,39 @@ namespace Jellyfin.Server.SocketSharp
public Task Stop()
{
_disposeCancellationTokenSource.Cancel();
-
- if (_listener != null)
- {
- _listener.Close();
- }
+ _listener?.Close();
return Task.CompletedTask;
}
+ /// <summary>
+ /// Releases the unmanaged resources and disposes of the managed resources used.
+ /// </summary>
public void Dispose()
{
Dispose(true);
+ GC.SuppressFinalize(this);
}
private bool _disposed;
- private readonly object _disposeLock = new object();
+
+ /// <summary>
+ /// Releases the unmanaged resources and disposes of the managed resources used.
+ /// </summary>
+ /// <param name="disposing">Whether or not the managed resources should be disposed</param>
protected virtual void Dispose(bool disposing)
{
- if (_disposed) return;
-
- lock (_disposeLock)
+ if (_disposed)
{
- if (_disposed) return;
-
- if (disposing)
- {
- Stop();
- }
+ return;
+ }
- //release unmanaged resources here...
- _disposed = true;
+ if (disposing)
+ {
+ Stop().GetAwaiter().GetResult();
}
+
+ _disposed = true;
}
}
}
diff --git a/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs b/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs
index c2b71e9ef..6458707d9 100644
--- a/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs
+++ b/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Globalization;
using System.IO;
using System.Text;
using Emby.Server.Implementations.HttpServer;
@@ -24,7 +25,7 @@ namespace Jellyfin.Server.SocketSharp
this.request = httpContext.Request;
this.response = new WebSocketSharpResponse(logger, httpContext.Response, this);
- //HandlerFactoryPath = GetHandlerPathIfAny(UrlPrefixes[0]);
+ // HandlerFactoryPath = GetHandlerPathIfAny(UrlPrefixes[0]);
}
public HttpListenerRequest HttpRequest => request;
@@ -45,9 +46,11 @@ namespace Jellyfin.Server.SocketSharp
public string UserHostAddress => request.UserHostAddress;
- public string XForwardedFor => string.IsNullOrEmpty(request.Headers["X-Forwarded-For"]) ? null : request.Headers["X-Forwarded-For"];
+ public string XForwardedFor
+ => string.IsNullOrEmpty(request.Headers["X-Forwarded-For"]) ? null : request.Headers["X-Forwarded-For"];
- public int? XForwardedPort => string.IsNullOrEmpty(request.Headers["X-Forwarded-Port"]) ? (int?)null : int.Parse(request.Headers["X-Forwarded-Port"]);
+ public int? XForwardedPort
+ => string.IsNullOrEmpty(request.Headers["X-Forwarded-Port"]) ? (int?)null : int.Parse(request.Headers["X-Forwarded-Port"], CultureInfo.InvariantCulture);
public string XForwardedProtocol => string.IsNullOrEmpty(request.Headers["X-Forwarded-Proto"]) ? null : request.Headers["X-Forwarded-Proto"];
@@ -83,6 +86,7 @@ namespace Jellyfin.Server.SocketSharp
switch (crlf)
{
case 0:
+ {
if (c == '\r')
{
crlf = 1;
@@ -97,29 +101,39 @@ namespace Jellyfin.Server.SocketSharp
{
throw new ArgumentException("net_WebHeaderInvalidControlChars");
}
+
break;
+ }
case 1:
+ {
if (c == '\n')
{
crlf = 2;
break;
}
+
throw new ArgumentException("net_WebHeaderInvalidCRLFChars");
+ }
case 2:
+ {
if (c == ' ' || c == '\t')
{
crlf = 0;
break;
}
+
throw new ArgumentException("net_WebHeaderInvalidCRLFChars");
+ }
}
}
+
if (crlf != 0)
{
throw new ArgumentException("net_WebHeaderInvalidCRLFChars");
}
+
return name;
}
@@ -132,6 +146,7 @@ namespace Jellyfin.Server.SocketSharp
return true;
}
}
+
return false;
}
@@ -337,6 +352,7 @@ namespace Jellyfin.Server.SocketSharp
this.pathInfo = System.Net.WebUtility.UrlDecode(pathInfo);
this.pathInfo = NormalizePathInfo(pathInfo, mode);
}
+
return this.pathInfo;
}
}
@@ -438,7 +454,7 @@ namespace Jellyfin.Server.SocketSharp
public string ContentType => request.ContentType;
- public Encoding contentEncoding;
+ private Encoding contentEncoding;
public Encoding ContentEncoding
{
get => contentEncoding ?? request.ContentEncoding;
@@ -496,6 +512,7 @@ namespace Jellyfin.Server.SocketSharp
i++;
}
}
+
return httpFiles;
}
}
diff --git a/Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs b/Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs
index cabc96b23..56e5c73d6 100644
--- a/Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs
+++ b/Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs
@@ -13,12 +13,12 @@ using HttpListenerResponse = SocketHttpListener.Net.HttpListenerResponse;
using IHttpResponse = MediaBrowser.Model.Services.IHttpResponse;
using IRequest = MediaBrowser.Model.Services.IRequest;
-
namespace Jellyfin.Server.SocketSharp
{
public class WebSocketSharpResponse : IHttpResponse
{
private readonly ILogger _logger;
+
private readonly HttpListenerResponse _response;
public WebSocketSharpResponse(ILogger logger, HttpListenerResponse response, IRequest request)
@@ -30,7 +30,9 @@ namespace Jellyfin.Server.SocketSharp
}
public IRequest Request { get; private set; }
+
public Dictionary<string, object> Items { get; private set; }
+
public object OriginalResponse => _response;
public int StatusCode
@@ -51,7 +53,7 @@ namespace Jellyfin.Server.SocketSharp
set => _response.ContentType = value;
}
- //public ICookies Cookies { get; set; }
+ public QueryParamCollection Headers => _response.Headers;
public void AddHeader(string name, string value)
{
@@ -64,8 +66,6 @@ namespace Jellyfin.Server.SocketSharp
_response.AddHeader(name, value);
}
- public QueryParamCollection Headers => _response.Headers;
-
public string GetHeader(string name)
{
return _response.Headers[name];
@@ -114,9 +114,9 @@ namespace Jellyfin.Server.SocketSharp
public void SetContentLength(long contentLength)
{
- //you can happily set the Content-Length header in Asp.Net
- //but HttpListener will complain if you do - you have to set ContentLength64 on the response.
- //workaround: HttpListener throws "The parameter is incorrect" exceptions when we try to set the Content-Length header
+ // you can happily set the Content-Length header in Asp.Net
+ // but HttpListener will complain if you do - you have to set ContentLength64 on the response.
+ // workaround: HttpListener throws "The parameter is incorrect" exceptions when we try to set the Content-Length header
_response.ContentLength64 = contentLength;
}
@@ -147,15 +147,12 @@ namespace Jellyfin.Server.SocketSharp
{
sb.Append($";domain={cookie.Domain}");
}
- //else if (restrictAllCookiesToDomain != null)
- //{
- // sb.Append($";domain={restrictAllCookiesToDomain}");
- //}
if (cookie.Secure)
{
sb.Append(";Secure");
}
+
if (cookie.HttpOnly)
{
sb.Append(";HttpOnly");
@@ -164,7 +161,6 @@ namespace Jellyfin.Server.SocketSharp
return sb.ToString();
}
-
public bool SendChunked
{
get => _response.SendChunked;