diff options
Diffstat (limited to 'Emby.Server.Implementations')
13 files changed, 124 insertions, 162 deletions
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index aa0fdde2d..53bc85b28 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1146,7 +1146,7 @@ namespace Emby.Server.Implementations } catch (Exception ex) { - Logger.LogError(ex, "Error loading plugin {pluginName}", plugin.GetType().FullName); + Logger.LogError(ex, "Error loading plugin {PluginName}", plugin.GetType().FullName); return null; } @@ -1160,10 +1160,32 @@ namespace Emby.Server.Implementations { Logger.LogInformation("Loading assemblies"); - AllConcreteTypes = GetComposablePartAssemblies() - .SelectMany(x => x.ExportedTypes) - .Where(type => type.IsClass && !type.IsAbstract && !type.IsInterface && !type.IsGenericType) - .ToArray(); + AllConcreteTypes = GetTypes(GetComposablePartAssemblies()).ToArray(); + } + + private IEnumerable<Type> GetTypes(IEnumerable<Assembly> assemblies) + { + foreach (var ass in assemblies) + { + Type[] exportedTypes; + try + { + exportedTypes = ass.GetExportedTypes(); + } + catch (TypeLoadException ex) + { + Logger.LogError(ex, "Error getting exported types from {Assembly}", ass.FullName); + continue; + } + + foreach (Type type in exportedTypes) + { + if (type.IsClass && !type.IsAbstract && !type.IsInterface && !type.IsGenericType) + { + yield return type; + } + } + } } private CertificateInfo CertificateInfo { get; set; } @@ -1327,8 +1349,19 @@ namespace Emby.Server.Implementations { foreach (var file in Directory.EnumerateFiles(ApplicationPaths.PluginsPath, "*.dll", SearchOption.AllDirectories)) { - Logger.LogInformation("Loading assembly {Path}", file); - yield return Assembly.LoadFrom(file); + Assembly plugAss; + try + { + plugAss = Assembly.LoadFrom(file); + } + catch (FileLoadException ex) + { + Logger.LogError(ex, "Failed to load assembly {Path}", file); + continue; + } + + Logger.LogInformation("Loaded assembly {Assembly} from {Path}", plugAss.FullName, file); + yield return plugAss; } } diff --git a/Emby.Server.Implementations/Data/SqliteUserRepository.cs b/Emby.Server.Implementations/Data/SqliteUserRepository.cs index 182df0edc..5957b2903 100644 --- a/Emby.Server.Implementations/Data/SqliteUserRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserRepository.cs @@ -81,7 +81,7 @@ namespace Emby.Server.Implementations.Data { // If the user password is the sha1 hash of the empty string, remove it if (!string.Equals(user.Password, "DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", StringComparison.Ordinal) - || !string.Equals(user.Password, "$SHA1$DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", StringComparison.Ordinal)) + && !string.Equals(user.Password, "$SHA1$DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", StringComparison.Ordinal)) { continue; } diff --git a/Emby.Server.Implementations/HttpServer/FileWriter.cs b/Emby.Server.Implementations/HttpServer/FileWriter.cs index c4d2a70e2..c6b7d31a8 100644 --- a/Emby.Server.Implementations/HttpServer/FileWriter.cs +++ b/Emby.Server.Implementations/HttpServer/FileWriter.cs @@ -67,6 +67,7 @@ namespace Emby.Server.Implementations.HttpServer if (string.IsNullOrWhiteSpace(rangeHeader)) { + Headers[HeaderNames.ContentLength] = TotalContentLength.ToString(CultureInfo.InvariantCulture); StatusCode = HttpStatusCode.OK; } else @@ -99,10 +100,13 @@ namespace Emby.Server.Implementations.HttpServer RangeStart = requestedRange.Key; RangeLength = 1 + RangeEnd - RangeStart; + // Content-Length is the length of what we're serving, not the original content + var lengthString = RangeLength.ToString(CultureInfo.InvariantCulture); + Headers[HeaderNames.ContentLength] = lengthString; var rangeString = $"bytes {RangeStart}-{RangeEnd}/{TotalContentLength}"; Headers[HeaderNames.ContentRange] = rangeString; - Logger.LogInformation("Setting range response values for {0}. RangeRequest: {1} Content-Range: {2}", Path, RangeHeader, rangeString); + Logger.LogInformation("Setting range response values for {0}. RangeRequest: {1} Content-Length: {2}, Content-Range: {3}", Path, RangeHeader, lengthString, rangeString); } /// <summary> diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 06d304077..b3d2b9cc2 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -626,6 +626,7 @@ namespace Emby.Server.Implementations.HttpServer private static Task Write(IResponse response, string text) { var bOutput = Encoding.UTF8.GetBytes(text); + response.OriginalResponse.ContentLength = bOutput.Length; return response.OutputStream.WriteAsync(bOutput, 0, bOutput.Length); } diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index 134f3c841..0b2924a3b 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -132,7 +132,7 @@ namespace Emby.Server.Implementations.HttpServer content = Array.Empty<byte>(); } - result = new StreamWriter(content, contentType); + result = new StreamWriter(content, contentType, contentLength); } else { @@ -176,7 +176,7 @@ namespace Emby.Server.Implementations.HttpServer bytes = Array.Empty<byte>(); } - result = new StreamWriter(bytes, contentType); + result = new StreamWriter(bytes, contentType, contentLength); } else { @@ -335,13 +335,13 @@ namespace Emby.Server.Implementations.HttpServer if (isHeadRequest) { - var result = new StreamWriter(Array.Empty<byte>(), contentType); + var result = new StreamWriter(Array.Empty<byte>(), contentType, contentLength); AddResponseHeaders(result, responseHeaders); return result; } else { - var result = new StreamWriter(content, contentType); + var result = new StreamWriter(content, contentType, contentLength); AddResponseHeaders(result, responseHeaders); return result; } @@ -581,6 +581,11 @@ namespace Emby.Server.Implementations.HttpServer } else { + if (totalContentLength.HasValue) + { + responseHeaders["Content-Length"] = totalContentLength.Value.ToString(CultureInfo.InvariantCulture); + } + if (isHeadRequest) { using (stream) @@ -624,7 +629,7 @@ namespace Emby.Server.Implementations.HttpServer if (lastModifiedDate.HasValue) { - responseHeaders[HeaderNames.LastModified] = lastModifiedDate.ToString(); + responseHeaders[HeaderNames.LastModified] = lastModifiedDate.Value.ToString(CultureInfo.InvariantCulture); } } diff --git a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs index 449159834..e27f794ba 100644 --- a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs +++ b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs @@ -96,6 +96,7 @@ namespace Emby.Server.Implementations.HttpServer RangeStart = requestedRange.Key; RangeLength = 1 + RangeEnd - RangeStart; + Headers[HeaderNames.ContentLength] = RangeLength.ToString(CultureInfo.InvariantCulture); Headers[HeaderNames.ContentRange] = $"bytes {RangeStart}-{RangeEnd}/{TotalContentLength}"; if (RangeStart > 0 && SourceStream.CanSeek) diff --git a/Emby.Server.Implementations/HttpServer/ResponseFilter.cs b/Emby.Server.Implementations/HttpServer/ResponseFilter.cs index a53d9bf0b..08f424757 100644 --- a/Emby.Server.Implementations/HttpServer/ResponseFilter.cs +++ b/Emby.Server.Implementations/HttpServer/ResponseFilter.cs @@ -26,7 +26,7 @@ namespace Emby.Server.Implementations.HttpServer public void FilterResponse(IRequest req, IResponse res, object dto) { // Try to prevent compatibility view - res.AddHeader("Access-Control-Allow-Headers", "Accept, Accept-Language, Authorization, Cache-Control, Content-Disposition, Content-Encoding, Content-Language, Content-MD5, Content-Range, Content-Type, Date, Host, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, Origin, OriginToken, Pragma, Range, Slug, Transfer-Encoding, Want-Digest, X-MediaBrowser-Token, X-Emby-Authorization"); + res.AddHeader("Access-Control-Allow-Headers", "Accept, Accept-Language, Authorization, Cache-Control, Content-Disposition, Content-Encoding, Content-Language, Content-Length, Content-MD5, Content-Range, Content-Type, Date, Host, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, Origin, OriginToken, Pragma, Range, Slug, Transfer-Encoding, Want-Digest, X-MediaBrowser-Token, X-Emby-Authorization"); res.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS"); res.AddHeader("Access-Control-Allow-Origin", "*"); @@ -58,6 +58,7 @@ namespace Emby.Server.Implementations.HttpServer if (length > 0) { + res.OriginalResponse.ContentLength = length; res.SendChunked = false; } } diff --git a/Emby.Server.Implementations/HttpServer/StreamWriter.cs b/Emby.Server.Implementations/HttpServer/StreamWriter.cs index 324f9085e..194d04441 100644 --- a/Emby.Server.Implementations/HttpServer/StreamWriter.cs +++ b/Emby.Server.Implementations/HttpServer/StreamWriter.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -49,6 +50,13 @@ namespace Emby.Server.Implementations.HttpServer SourceStream = source; + Headers["Content-Type"] = contentType; + + if (source.CanSeek) + { + Headers[HeaderNames.ContentLength] = source.Length.ToString(CultureInfo.InvariantCulture); + } + Headers[HeaderNames.ContentType] = contentType; } @@ -57,7 +65,7 @@ namespace Emby.Server.Implementations.HttpServer /// </summary> /// <param name="source">The source.</param> /// <param name="contentType">Type of the content.</param> - public StreamWriter(byte[] source, string contentType) + public StreamWriter(byte[] source, string contentType, int contentLength) { if (string.IsNullOrEmpty(contentType)) { @@ -66,6 +74,7 @@ namespace Emby.Server.Implementations.HttpServer SourceBytes = source; + Headers[HeaderNames.ContentLength] = contentLength.ToString(CultureInfo.InvariantCulture); Headers[HeaderNames.ContentType] = contentType; } diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index 952cc6896..c33bb7740 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -596,7 +596,7 @@ namespace Emby.Server.Implementations.Library } bool hasConfiguredPassword = GetAuthenticationProvider(user).HasPassword(user).Result; - bool hasConfiguredEasyPassword = string.IsNullOrEmpty(GetLocalPasswordHash(user)); + bool hasConfiguredEasyPassword = !string.IsNullOrEmpty(GetLocalPasswordHash(user)); bool hasPassword = user.Configuration.EnableLocalPassword && !string.IsNullOrEmpty(remoteEndPoint) && _networkManager.IsInLocalNetwork(remoteEndPoint) ? hasConfiguredEasyPassword : diff --git a/Emby.Server.Implementations/Services/HttpResult.cs b/Emby.Server.Implementations/Services/HttpResult.cs index b6758486c..2b5963a77 100644 --- a/Emby.Server.Implementations/Services/HttpResult.cs +++ b/Emby.Server.Implementations/Services/HttpResult.cs @@ -43,6 +43,11 @@ namespace Emby.Server.Implementations.Services { var contentLength = bytesResponse.Length; + if (response != null) + { + response.OriginalResponse.ContentLength = contentLength; + } + if (contentLength > 0) { await responseStream.WriteAsync(bytesResponse, 0, contentLength, cancellationToken).ConfigureAwait(false); diff --git a/Emby.Server.Implementations/Services/ResponseHelper.cs b/Emby.Server.Implementations/Services/ResponseHelper.cs index 0301ff335..251ba3529 100644 --- a/Emby.Server.Implementations/Services/ResponseHelper.cs +++ b/Emby.Server.Implementations/Services/ResponseHelper.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Net; using System.Text; @@ -20,6 +21,8 @@ namespace Emby.Server.Implementations.Services { response.StatusCode = (int)HttpStatusCode.NoContent; } + + response.OriginalResponse.ContentLength = 0; return Task.CompletedTask; } @@ -39,11 +42,6 @@ namespace Emby.Server.Implementations.Services response.StatusCode = httpResult.Status; response.StatusDescription = httpResult.StatusCode.ToString(); - //if (string.IsNullOrEmpty(httpResult.ContentType)) - //{ - // httpResult.ContentType = defaultContentType; - //} - //response.ContentType = httpResult.ContentType; } var responseOptions = result as IHasHeaders; @@ -53,6 +51,7 @@ namespace Emby.Server.Implementations.Services { if (string.Equals(responseHeaders.Key, "Content-Length", StringComparison.OrdinalIgnoreCase)) { + response.OriginalResponse.ContentLength = long.Parse(responseHeaders.Value, CultureInfo.InvariantCulture); continue; } @@ -72,52 +71,37 @@ namespace Emby.Server.Implementations.Services response.ContentType += "; charset=utf-8"; } - var asyncStreamWriter = result as IAsyncStreamWriter; - if (asyncStreamWriter != null) - { - return asyncStreamWriter.WriteToAsync(response.OutputStream, cancellationToken); - } - - var streamWriter = result as IStreamWriter; - if (streamWriter != null) + switch (result) { - streamWriter.WriteTo(response.OutputStream); - return Task.CompletedTask; - } - - var fileWriter = result as FileWriter; - if (fileWriter != null) - { - return fileWriter.WriteToAsync(response, cancellationToken); - } - - var stream = result as Stream; - if (stream != null) - { - return CopyStream(stream, response.OutputStream); - } + case IAsyncStreamWriter asyncStreamWriter: + return asyncStreamWriter.WriteToAsync(response.OutputStream, cancellationToken); + case IStreamWriter streamWriter: + streamWriter.WriteTo(response.OutputStream); + return Task.CompletedTask; + case FileWriter fileWriter: + return fileWriter.WriteToAsync(response, cancellationToken); + case Stream stream: + return CopyStream(stream, response.OutputStream); + case byte[] bytes: + response.ContentType = "application/octet-stream"; + response.OriginalResponse.ContentLength = bytes.Length; + + if (bytes.Length > 0) + { + return response.OutputStream.WriteAsync(bytes, 0, bytes.Length, cancellationToken); + } - var bytes = result as byte[]; - if (bytes != null) - { - response.ContentType = "application/octet-stream"; + return Task.CompletedTask; + case string responseText: + var responseTextAsBytes = Encoding.UTF8.GetBytes(responseText); + response.OriginalResponse.ContentLength = responseTextAsBytes.Length; - if (bytes.Length > 0) - { - return response.OutputStream.WriteAsync(bytes, 0, bytes.Length, cancellationToken); - } - return Task.CompletedTask; - } + if (responseTextAsBytes.Length > 0) + { + return response.OutputStream.WriteAsync(responseTextAsBytes, 0, responseTextAsBytes.Length, cancellationToken); + } - var responseText = result as string; - if (responseText != null) - { - bytes = Encoding.UTF8.GetBytes(responseText); - if (bytes.Length > 0) - { - return response.OutputStream.WriteAsync(bytes, 0, bytes.Length, cancellationToken); - } - return Task.CompletedTask; + return Task.CompletedTask; } return WriteObject(request, result, response); @@ -143,14 +127,13 @@ namespace Emby.Server.Implementations.Services ms.Position = 0; var contentLength = ms.Length; + response.OriginalResponse.ContentLength = contentLength; if (contentLength > 0) { await ms.CopyToAsync(response.OutputStream).ConfigureAwait(false); } } - - //serializer(result, outputStream); } } } diff --git a/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs b/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs index f835aa1b5..6a522fbef 100644 --- a/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs +++ b/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs @@ -71,7 +71,7 @@ namespace Emby.Server.Implementations.Services string propertyName = pair.Key; string propertyTextValue = pair.Value; - if (string.IsNullOrEmpty(propertyTextValue) + if (propertyTextValue == null || !propertySetterMap.TryGetValue(propertyName, out propertySerializerEntry) || propertySerializerEntry.PropertySetFn == null) { diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs index 792615a0f..00465b63e 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.IO; using System.Net; using System.Text; +using MediaBrowser.Common.Net; using MediaBrowser.Model.Services; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; @@ -38,16 +39,9 @@ namespace Emby.Server.Implementations.SocketSharp public string RawUrl => request.GetEncodedPathAndQuery(); public string AbsoluteUri => request.GetDisplayUrl().TrimEnd('/'); + // Header[name] returns "" when undefined - public string XForwardedFor - => StringValues.IsNullOrEmpty(request.Headers["X-Forwarded-For"]) ? null : request.Headers["X-Forwarded-For"].ToString(); - - public int? XForwardedPort - => StringValues.IsNullOrEmpty(request.Headers["X-Forwarded-Port"]) ? (int?)null : int.Parse(request.Headers["X-Forwarded-Port"], CultureInfo.InvariantCulture); - - public string XForwardedProtocol => StringValues.IsNullOrEmpty(request.Headers["X-Forwarded-Proto"]) ? null : request.Headers["X-Forwarded-Proto"].ToString(); - - public string XRealIp => StringValues.IsNullOrEmpty(request.Headers["X-Real-IP"]) ? null : request.Headers["X-Real-IP"].ToString(); + private string GetHeader(string name) => request.Headers[name].ToString(); private string remoteIp; public string RemoteIp @@ -59,101 +53,27 @@ namespace Emby.Server.Implementations.SocketSharp return remoteIp; } - var temp = CheckBadChars(XForwardedFor.AsSpan()); - if (temp.Length != 0) - { - return remoteIp = temp.ToString(); - } + IPAddress ip; - temp = CheckBadChars(XRealIp.AsSpan()); - if (temp.Length != 0) + // "Real" remote ip might be in X-Forwarded-For of X-Real-Ip + // (if the server is behind a reverse proxy for example) + if (!IPAddress.TryParse(GetHeader(CustomHeaderNames.XForwardedFor), out ip)) { - return remoteIp = NormalizeIp(temp).ToString(); - } - - return remoteIp = NormalizeIp(request.HttpContext.Connection.RemoteIpAddress.ToString().AsSpan()).ToString(); - } - } - - private static readonly char[] HttpTrimCharacters = new char[] { (char)0x09, (char)0xA, (char)0xB, (char)0xC, (char)0xD, (char)0x20 }; - - // CheckBadChars - throws on invalid chars to be not found in header name/value - internal static ReadOnlySpan<char> CheckBadChars(ReadOnlySpan<char> name) - { - if (name.Length == 0) - { - return name; - } - - // VALUE check - // Trim spaces from both ends - name = name.Trim(HttpTrimCharacters); - - // First, check for correctly formed multi-line value - // Second, check for absence of CTL characters - int crlf = 0; - for (int i = 0; i < name.Length; ++i) - { - char c = (char)(0x000000ff & (uint)name[i]); - switch (crlf) - { - case 0: - if (c == '\r') - { - crlf = 1; - } - else if (c == '\n') - { - // Technically this is bad HTTP. But it would be a breaking change to throw here. - // Is there an exploit? - crlf = 2; - } - else if (c == 127 || (c < ' ' && c != '\t')) - { - throw new ArgumentException("net_WebHeaderInvalidControlChars", nameof(name)); - } - - break; - - case 1: - if (c == '\n') - { - crlf = 2; - break; - } - - throw new ArgumentException("net_WebHeaderInvalidCRLFChars", nameof(name)); - - case 2: - if (c == ' ' || c == '\t') - { - crlf = 0; - break; - } - - throw new ArgumentException("net_WebHeaderInvalidCRLFChars", nameof(name)); + if (!IPAddress.TryParse(GetHeader(CustomHeaderNames.XRealIP), out ip)) + { + ip = request.HttpContext.Connection.RemoteIpAddress; + } } - } - if (crlf != 0) - { - throw new ArgumentException("net_WebHeaderInvalidCRLFChars", nameof(name)); + return remoteIp = NormalizeIp(ip).ToString(); } - - return name; } - private ReadOnlySpan<char> NormalizeIp(ReadOnlySpan<char> ip) + private static IPAddress NormalizeIp(IPAddress ip) { - if (ip.Length != 0 && !ip.IsWhiteSpace()) + if (ip.IsIPv4MappedToIPv6) { - // Handle ipv4 mapped to ipv6 - const string srch = "::ffff:"; - var index = ip.IndexOf(srch.AsSpan(), StringComparison.OrdinalIgnoreCase); - if (index == 0) - { - ip = ip.Slice(srch.Length); - } + return ip.MapToIPv4(); } return ip; |
