diff options
25 files changed, 266 insertions, 267 deletions
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index c3fcea1e2..9168dccc8 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -23,8 +23,10 @@ - [fruhnow](https://github.com/fruhnow) - [Lynxy](https://github.com/Lynxy) - [fasheng](https://github.com/fasheng) - - [ploughpuff](https://github.com/ploughpuff) + - [ploughpuff](https://github.com/ploughpuff) - [pjeanjean](https://github.com/pjeanjean) + - [DrPandemic](https://github.com/drpandemic) + - [joern-h](https://github.com/joern-h) # Emby Contributors diff --git a/Dockerfile b/Dockerfile index c971f1cc1..864cfa445 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ RUN apt-get update \ COPY --from=ffmpeg / / COPY --from=builder /jellyfin /jellyfin -ARG JELLYFIN_WEB_VERSION=10.3.5 +ARG JELLYFIN_WEB_VERSION=10.3.6 RUN curl -L https://github.com/jellyfin/jellyfin-web/archive/v${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \ && rm -rf /jellyfin/jellyfin-web \ && mv jellyfin-web-${JELLYFIN_WEB_VERSION} /jellyfin/jellyfin-web diff --git a/Dockerfile.arm b/Dockerfile.arm index 4847c726b..816739063 100644 --- a/Dockerfile.arm +++ b/Dockerfile.arm @@ -26,7 +26,7 @@ RUN apt-get update \ && chmod 777 /cache /config /media COPY --from=builder /jellyfin /jellyfin -ARG JELLYFIN_WEB_VERSION=10.3.5 +ARG JELLYFIN_WEB_VERSION=10.3.6 RUN curl -L https://github.com/jellyfin/jellyfin-web/archive/v${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \ && rm -rf /jellyfin/jellyfin-web \ && mv jellyfin-web-${JELLYFIN_WEB_VERSION} /jellyfin/jellyfin-web diff --git a/Dockerfile.arm64 b/Dockerfile.arm64 index a26cfc7b3..1843f7224 100644 --- a/Dockerfile.arm64 +++ b/Dockerfile.arm64 @@ -26,7 +26,7 @@ RUN apt-get update \ && chmod 777 /cache /config /media COPY --from=builder /jellyfin /jellyfin -ARG JELLYFIN_WEB_VERSION=10.3.5 +ARG JELLYFIN_WEB_VERSION=10.3.6 RUN curl -L https://github.com/jellyfin/jellyfin-web/archive/v${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \ && rm -rf /jellyfin/jellyfin-web \ && mv jellyfin-web-${JELLYFIN_WEB_VERSION} /jellyfin/jellyfin-web diff --git a/Emby.Dlna/PlayTo/SsdpHttpClient.cs b/Emby.Dlna/PlayTo/SsdpHttpClient.cs index 1ad99fac5..780b0a889 100644 --- a/Emby.Dlna/PlayTo/SsdpHttpClient.cs +++ b/Emby.Dlna/PlayTo/SsdpHttpClient.cs @@ -34,16 +34,13 @@ namespace Emby.Dlna.PlayTo { var cancellationToken = CancellationToken.None; - using (var response = await PostSoapDataAsync(NormalizeServiceUrl(baseUrl, service.ControlUrl), "\"" + service.ServiceType + "#" + command + "\"", postData, header, logRequest, cancellationToken) + var url = NormalizeServiceUrl(baseUrl, service.ControlUrl); + using (var response = await PostSoapDataAsync(url, '\"' + service.ServiceType + '#' + command + '\"', postData, header, logRequest, cancellationToken) .ConfigureAwait(false)) + using (var stream = response.Content) + using (var reader = new StreamReader(stream, Encoding.UTF8)) { - using (var stream = response.Content) - { - using (var reader = new StreamReader(stream, Encoding.UTF8)) - { - return XDocument.Parse(reader.ReadToEnd(), LoadOptions.PreserveWhitespace); - } - } + return XDocument.Parse(reader.ReadToEnd(), LoadOptions.PreserveWhitespace); } } @@ -121,15 +118,18 @@ namespace Emby.Dlna.PlayTo } } - private Task<HttpResponseInfo> PostSoapDataAsync(string url, + private Task<HttpResponseInfo> PostSoapDataAsync( + string url, string soapAction, string postData, string header, bool logRequest, CancellationToken cancellationToken) { - if (!soapAction.StartsWith("\"")) - soapAction = "\"" + soapAction + "\""; + if (soapAction[0] != '\"') + { + soapAction = '\"' + soapAction + '\"'; + } var options = new HttpRequestOptions { diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 3aa2dbf9a..120aade39 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -420,7 +420,7 @@ namespace Emby.Server.Implementations /// Gets the current application user agent /// </summary> /// <value>The application user agent.</value> - public string ApplicationUserAgent => Name.Replace(' ','-') + "/" + ApplicationVersion; + public string ApplicationUserAgent => Name.Replace(' ','-') + '/' + ApplicationVersion; /// <summary> /// Gets the email address for use within a comment section of a user agent field. @@ -678,11 +678,6 @@ namespace Emby.Server.Implementations await HttpServer.RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, CancellationToken.None).ConfigureAwait(false); } - protected virtual IHttpClient CreateHttpClient() - { - return new HttpClientManager.HttpClientManager(ApplicationPaths, LoggerFactory, FileSystemManager, () => ApplicationUserAgent); - } - public static IStreamHelper StreamHelper { get; set; } /// <summary> @@ -708,7 +703,11 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(FileSystemManager); serviceCollection.AddSingleton<TvDbClientManager>(); - HttpClient = CreateHttpClient(); + HttpClient = new HttpClientManager.HttpClientManager( + ApplicationPaths, + LoggerFactory.CreateLogger<HttpClientManager.HttpClientManager>(), + FileSystemManager, + () => ApplicationUserAgent); serviceCollection.AddSingleton(HttpClient); serviceCollection.AddSingleton(NetworkManager); @@ -1690,9 +1689,7 @@ namespace Emby.Server.Implementations LogErrors = LogPing, LogRequest = LogPing, BufferContent = false, - CancellationToken = cancellationToken - }, HttpMethod.Post).ConfigureAwait(false)) { using (var reader = new StreamReader(response.Content)) diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs index 9bc0bb945..919453d2a 100644 --- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs +++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs @@ -170,6 +170,8 @@ namespace Emby.Server.Implementations.Data columnNames.Add(name); } } + // Configuration and pragmas can affect VACUUM so it needs to be last. + queries.Add("VACUUM"); return columnNames; } diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs index b82d55d0e..c9dc6c815 100644 --- a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs +++ b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs @@ -23,30 +23,24 @@ namespace Emby.Server.Implementations.HttpClientManager /// </summary> public class HttpClientManager : IHttpClient { - /// <summary> - /// When one request to a host times out, we'll ban all other requests for this period of time, to prevent scans from stalling - /// </summary> - private const int TimeoutSeconds = 30; - - /// <summary> - /// The _logger - /// </summary> private readonly ILogger _logger; - - /// <summary> - /// The _app paths - /// </summary> private readonly IApplicationPaths _appPaths; - private readonly IFileSystem _fileSystem; private readonly Func<string> _defaultUserAgentFn; /// <summary> + /// Holds a dictionary of http clients by host. Use GetHttpClient(host) to retrieve or create a client for web requests. + /// DON'T dispose it after use. + /// </summary> + /// <value>The HTTP clients.</value> + private readonly ConcurrentDictionary<string, HttpClient> _httpClients = new ConcurrentDictionary<string, HttpClient>(); + + /// <summary> /// Initializes a new instance of the <see cref="HttpClientManager" /> class. /// </summary> public HttpClientManager( IApplicationPaths appPaths, - ILoggerFactory loggerFactory, + ILogger<HttpClientManager> logger, IFileSystem fileSystem, Func<string> defaultUserAgentFn) { @@ -55,41 +49,28 @@ namespace Emby.Server.Implementations.HttpClientManager throw new ArgumentNullException(nameof(appPaths)); } - if (loggerFactory == null) + if (logger == null) { - throw new ArgumentNullException(nameof(loggerFactory)); + throw new ArgumentNullException(nameof(logger)); } - _logger = loggerFactory.CreateLogger(nameof(HttpClientManager)); + _logger = logger; _fileSystem = fileSystem; _appPaths = appPaths; _defaultUserAgentFn = defaultUserAgentFn; - - // http://stackoverflow.com/questions/566437/http-post-returns-the-error-417-expectation-failed-c - ServicePointManager.Expect100Continue = false; } /// <summary> - /// Holds a dictionary of http clients by host. Use GetHttpClient(host) to retrieve or create a client for web requests. - /// DON'T dispose it after use. - /// </summary> - /// <value>The HTTP clients.</value> - private readonly ConcurrentDictionary<string, HttpClient> _httpClients = new ConcurrentDictionary<string, HttpClient>(); - - /// <summary> - /// Gets + /// Gets the correct http client for the given url. /// </summary> - /// <param name="url">The host.</param> - /// <param name="enableHttpCompression">if set to <c>true</c> [enable HTTP compression].</param> + /// <param name="url">The url.</param> /// <returns>HttpClient.</returns> - /// <exception cref="ArgumentNullException">host</exception> - private HttpClient GetHttpClient(string url, bool enableHttpCompression) + private HttpClient GetHttpClient(string url) { - var key = GetHostFromUrl(url) + enableHttpCompression; + var key = GetHostFromUrl(url); if (!_httpClients.TryGetValue(key, out var client)) { - client = new HttpClient() { BaseAddress = new Uri(url) @@ -109,24 +90,26 @@ namespace Emby.Server.Implementations.HttpClientManager if (!string.IsNullOrWhiteSpace(userInfo)) { _logger.LogWarning("Found userInfo in url: {0} ... url: {1}", userInfo, url); - url = url.Replace(userInfo + "@", string.Empty); + url = url.Replace(userInfo + '@', string.Empty); } var request = new HttpRequestMessage(method, url); AddRequestHeaders(request, options); - if (options.EnableHttpCompression) + switch (options.DecompressionMethod) { - if (options.DecompressionMethod.HasValue - && options.DecompressionMethod.Value == CompressionMethod.Gzip) - { + case CompressionMethod.Deflate | CompressionMethod.Gzip: request.Headers.Add(HeaderNames.AcceptEncoding, new[] { "gzip", "deflate" }); - } - else - { + break; + case CompressionMethod.Deflate: request.Headers.Add(HeaderNames.AcceptEncoding, "deflate"); - } + break; + case CompressionMethod.Gzip: + request.Headers.Add(HeaderNames.AcceptEncoding, "gzip"); + break; + default: + break; } if (options.EnableKeepAlive) @@ -134,20 +117,8 @@ namespace Emby.Server.Implementations.HttpClientManager request.Headers.Add(HeaderNames.Connection, "Keep-Alive"); } - if (!string.IsNullOrEmpty(options.Host)) - { - request.Headers.Add(HeaderNames.Host, options.Host); - } - - if (!string.IsNullOrEmpty(options.Referer)) - { - request.Headers.Add(HeaderNames.Referer, options.Referer); - } - //request.Headers.Add(HeaderNames.CacheControl, "no-cache"); - //request.Headers.Add(HeaderNames., options.TimeoutMs; - /* if (!string.IsNullOrWhiteSpace(userInfo)) { @@ -188,9 +159,7 @@ namespace Emby.Server.Implementations.HttpClientManager /// <param name="options">The options.</param> /// <returns>Task{HttpResponseInfo}.</returns> public Task<HttpResponseInfo> GetResponse(HttpRequestOptions options) - { - return SendAsync(options, HttpMethod.Get); - } + => SendAsync(options, HttpMethod.Get); /// <summary> /// Performs a GET request and returns the resulting stream @@ -209,8 +178,6 @@ namespace Emby.Server.Implementations.HttpClientManager /// <param name="options">The options.</param> /// <param name="httpMethod">The HTTP method.</param> /// <returns>Task{HttpResponseInfo}.</returns> - /// <exception cref="HttpException"> - /// </exception> public Task<HttpResponseInfo> SendAsync(HttpRequestOptions options, string httpMethod) { var httpMethod2 = GetHttpMethod(httpMethod); @@ -223,8 +190,6 @@ namespace Emby.Server.Implementations.HttpClientManager /// <param name="options">The options.</param> /// <param name="httpMethod">The HTTP method.</param> /// <returns>Task{HttpResponseInfo}.</returns> - /// <exception cref="HttpException"> - /// </exception> public async Task<HttpResponseInfo> SendAsync(HttpRequestOptions options, HttpMethod httpMethod) { if (options.CacheMode == CacheMode.None) @@ -324,32 +289,37 @@ namespace Emby.Server.Implementations.HttpClientManager options.CancellationToken.ThrowIfCancellationRequested(); - var client = GetHttpClient(options.Url, options.EnableHttpCompression); + var client = GetHttpClient(options.Url); var httpWebRequest = GetRequestMessage(options, httpMethod); - if (options.RequestContentBytes != null || - !string.IsNullOrEmpty(options.RequestContent) || - httpMethod == HttpMethod.Post) + if (options.RequestContentBytes != null + || !string.IsNullOrEmpty(options.RequestContent) + || httpMethod == HttpMethod.Post) { - try + if (options.RequestContentBytes != null) { - httpWebRequest.Content = new StringContent(Encoding.UTF8.GetString(options.RequestContentBytes) ?? options.RequestContent ?? string.Empty); - - var contentType = options.RequestContentType ?? "application/x-www-form-urlencoded"; + httpWebRequest.Content = new ByteArrayContent(options.RequestContentBytes); + } + else if (options.RequestContent != null) + { + httpWebRequest.Content = new StringContent(options.RequestContent); + } + else + { + httpWebRequest.Content = new ByteArrayContent(Array.Empty<byte>()); + } - if (options.AppendCharsetToMimeType) - { - contentType = contentType.TrimEnd(';') + "; charset=\"utf-8\""; - } + // TODO: add correct content type + /* + var contentType = options.RequestContentType ?? "application/x-www-form-urlencoded"; - httpWebRequest.Headers.Add(HeaderNames.ContentType, contentType); - await client.SendAsync(httpWebRequest).ConfigureAwait(false); - } - catch (Exception ex) + if (options.AppendCharsetToMimeType) { - throw new HttpException(ex.Message) { IsTimedOut = true }; + contentType = contentType.TrimEnd(';') + "; charset=\"utf-8\""; } + + httpWebRequest.Headers.Add(HeaderNames.ContentType, contentType);*/ } if (options.LogRequest) @@ -357,92 +327,53 @@ namespace Emby.Server.Implementations.HttpClientManager _logger.LogDebug("HttpClientManager {0}: {1}", httpMethod.ToString(), options.Url); } - try - { - options.CancellationToken.ThrowIfCancellationRequested(); + options.CancellationToken.ThrowIfCancellationRequested(); - /*if (!options.BufferContent) - { - var response = await client.HttpClient.SendAsync(httpWebRequest).ConfigureAwait(false); + if (!options.BufferContent) + { + var response = await client.SendAsync(httpWebRequest, options.CancellationToken).ConfigureAwait(false); - await EnsureSuccessStatusCode(client, response, options).ConfigureAwait(false); + await EnsureSuccessStatusCode(response, options).ConfigureAwait(false); - options.CancellationToken.ThrowIfCancellationRequested(); - - return GetResponseInfo(response, await response.Content.ReadAsStreamAsync().ConfigureAwait(false), response.Content.Headers.ContentLength, response); - }*/ + options.CancellationToken.ThrowIfCancellationRequested(); - using (var response = await client.SendAsync(httpWebRequest).ConfigureAwait(false)) + var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + return new HttpResponseInfo(response.Headers) { - await EnsureSuccessStatusCode(response, options).ConfigureAwait(false); - - options.CancellationToken.ThrowIfCancellationRequested(); - - using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false)) - { - var memoryStream = new MemoryStream(); - await stream.CopyToAsync(memoryStream).ConfigureAwait(false); - memoryStream.Position = 0; - - return GetResponseInfo(response, memoryStream, memoryStream.Length, null); - } - } - } - catch (OperationCanceledException ex) - { - throw GetCancellationException(options, options.CancellationToken, ex); - } - } - - private HttpResponseInfo GetResponseInfo(HttpResponseMessage httpResponse, Stream content, long? contentLength, IDisposable disposable) - { - var responseInfo = new HttpResponseInfo(disposable) - { - Content = content, - StatusCode = httpResponse.StatusCode, - ContentType = httpResponse.Content.Headers.ContentType?.MediaType, - ContentLength = contentLength, - ResponseUrl = httpResponse.Content.Headers.ContentLocation?.ToString() - }; - - if (httpResponse.Headers != null) - { - SetHeaders(httpResponse.Content.Headers, responseInfo); + Content = stream, + StatusCode = response.StatusCode, + ContentType = response.Content.Headers.ContentType?.MediaType, + ContentLength = stream.Length, + ResponseUrl = response.Content.Headers.ContentLocation?.ToString() + }; } - return responseInfo; - } - - private HttpResponseInfo GetResponseInfo(HttpResponseMessage httpResponse, string tempFile, long? contentLength) - { - var responseInfo = new HttpResponseInfo + using (var response = await client.SendAsync(httpWebRequest, options.CancellationToken).ConfigureAwait(false)) { - TempFilePath = tempFile, - StatusCode = httpResponse.StatusCode, - ContentType = httpResponse.Content.Headers.ContentType?.MediaType, - ContentLength = contentLength - }; + await EnsureSuccessStatusCode(response, options).ConfigureAwait(false); - if (httpResponse.Headers != null) - { - SetHeaders(httpResponse.Content.Headers, responseInfo); - } + options.CancellationToken.ThrowIfCancellationRequested(); - return responseInfo; - } + using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false)) + { + var memoryStream = new MemoryStream(); + await stream.CopyToAsync(memoryStream, StreamDefaults.DefaultCopyToBufferSize, options.CancellationToken).ConfigureAwait(false); + memoryStream.Position = 0; - private static void SetHeaders(HttpContentHeaders headers, HttpResponseInfo responseInfo) - { - foreach (var key in headers) - { - responseInfo.Headers[key.Key] = string.Join(", ", key.Value); + return new HttpResponseInfo(response.Headers) + { + Content = memoryStream, + StatusCode = response.StatusCode, + ContentType = response.Content.Headers.ContentType?.MediaType, + ContentLength = memoryStream.Length, + ResponseUrl = response.Content.Headers.ContentLocation?.ToString() + }; + } } } public Task<HttpResponseInfo> Post(HttpRequestOptions options) - { - return SendAsync(options, HttpMethod.Post); - } + => SendAsync(options, HttpMethod.Post); /// <summary> /// Downloads the contents of a given url into a temporary location @@ -451,10 +382,8 @@ namespace Emby.Server.Implementations.HttpClientManager /// <returns>Task{System.String}.</returns> public async Task<string> GetTempFile(HttpRequestOptions options) { - using (var response = await GetTempFileResponse(options).ConfigureAwait(false)) - { - return response.TempFilePath; - } + var response = await GetTempFileResponse(options).ConfigureAwait(false); + return response.TempFilePath; } public async Task<HttpResponseInfo> GetTempFileResponse(HttpRequestOptions options) @@ -481,13 +410,13 @@ namespace Emby.Server.Implementations.HttpClientManager _logger.LogDebug("HttpClientManager.GetTempFileResponse url: {0}", options.Url); } - var client = GetHttpClient(options.Url, options.EnableHttpCompression); + var client = GetHttpClient(options.Url); try { options.CancellationToken.ThrowIfCancellationRequested(); - using (var response = (await client.SendAsync(httpWebRequest).ConfigureAwait(false))) + using (var response = (await client.SendAsync(httpWebRequest, options.CancellationToken).ConfigureAwait(false))) { await EnsureSuccessStatusCode(response, options).ConfigureAwait(false); @@ -501,8 +430,15 @@ namespace Emby.Server.Implementations.HttpClientManager options.Progress.Report(100); - var contentLength = response.Content.Headers.ContentLength; - return GetResponseInfo(response, tempFile, contentLength); + var responseInfo = new HttpResponseInfo(response.Headers) + { + TempFilePath = tempFile, + StatusCode = response.StatusCode, + ContentType = response.Content.Headers.ContentType?.MediaType, + ContentLength = response.Content.Headers.ContentLength + }; + + return responseInfo; } } catch (Exception ex) @@ -530,7 +466,7 @@ namespace Emby.Server.Implementations.HttpClientManager { if (options.LogErrors) { - _logger.LogError(webException, "Error {status} getting response from {url}", webException.Status, options.Url); + _logger.LogError(webException, "Error {Status} getting response from {Url}", webException.Status, options.Url); } var exception = new HttpException(webException.Message, webException); @@ -565,7 +501,7 @@ namespace Emby.Server.Implementations.HttpClientManager if (options.LogErrors) { - _logger.LogError(ex, "Error getting response from {url}", options.Url); + _logger.LogError(ex, "Error getting response from {Url}", options.Url); } return ex; @@ -639,7 +575,7 @@ namespace Emby.Server.Implementations.HttpClientManager } var msg = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - _logger.LogError(msg); + _logger.LogError("HTTP request failed with message: {Message}", msg); throw new HttpException(response.ReasonPhrase) { diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs index dd636e6cd..8dee7046e 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs @@ -71,7 +71,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV UserAgent = "Emby/3.0", // Shouldn't matter but may cause issues - EnableHttpCompression = false + DecompressionMethod = CompressionMethod.None }; using (var response = await _httpClient.SendAsync(httpRequestOptions, "GET").ConfigureAwait(false)) diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index f3f747718..f5dffc22a 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -627,15 +627,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings ListingsProviderInfo providerInfo) { // Schedules direct requires that the client support compression and will return a 400 response without it - options.EnableHttpCompression = true; - - // On windows 7 under .net core, this header is not getting added -#if NETSTANDARD2_0 - if (Environment.OSVersion.Platform == PlatformID.Win32NT) - { - options.RequestHeaders[HeaderNames.AcceptEncoding] = "deflate"; - } -#endif + options.DecompressionMethod = CompressionMethod.Deflate; try { @@ -665,15 +657,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings ListingsProviderInfo providerInfo) { // Schedules direct requires that the client support compression and will return a 400 response without it - options.EnableHttpCompression = true; - - // On windows 7 under .net core, this header is not getting added -#if NETSTANDARD2_0 - if (Environment.OSVersion.Platform == PlatformID.Win32NT) - { - options.RequestHeaders[HeaderNames.AcceptEncoding] = "deflate"; - } -#endif + options.DecompressionMethod = CompressionMethod.Deflate; try { diff --git a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs index 69b10e6da..d39c91783 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs @@ -73,11 +73,9 @@ namespace Emby.Server.Implementations.LiveTv.Listings CancellationToken = cancellationToken, Url = path, Progress = new SimpleProgress<double>(), - DecompressionMethod = CompressionMethod.Gzip, - // It's going to come back gzipped regardless of this value // So we need to make sure the decompression method is set to gzip - EnableHttpCompression = true, + DecompressionMethod = CompressionMethod.Gzip, UserAgent = "Emby/3.0" diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs index e8b34da0c..7de9931c7 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs @@ -46,8 +46,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts Url = url, CancellationToken = CancellationToken.None, BufferContent = false, - - EnableHttpCompression = false, + DecompressionMethod = CompressionMethod.None }; foreach (var header in mediaSource.RequiredHttpHeaders) diff --git a/Jellyfin.Drawing.Skia/SkiaEncoder.cs b/Jellyfin.Drawing.Skia/SkiaEncoder.cs index f6c120801..80b9974fa 100644 --- a/Jellyfin.Drawing.Skia/SkiaEncoder.cs +++ b/Jellyfin.Drawing.Skia/SkiaEncoder.cs @@ -186,6 +186,11 @@ namespace Jellyfin.Drawing.Skia public ImageDimensions GetImageSize(string path) { + if (!File.Exists(path)) + { + throw new FileNotFoundException("File not found", path); + } + using (var s = new SKFileStream(path)) using (var codec = SKCodec.Create(s)) { diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 11c09db98..75b820b8e 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -123,6 +123,17 @@ namespace Jellyfin.Server // CA5359: Do Not Disable Certificate Validation #pragma warning disable CA5359 + // Increase the max http request limit + // The default connection limit is 10 for ASP.NET hosted applications and 2 for all others. + ServicePointManager.DefaultConnectionLimit = Math.Max(96, ServicePointManager.DefaultConnectionLimit); + + // Disable the "Expect: 100-Continue" header by default + // http://stackoverflow.com/questions/566437/http-post-returns-the-error-417-expectation-failed-c + ServicePointManager.Expect100Continue = false; + +// CA5359: Do Not Disable Certificate Validation +#pragma warning disable CA5359 + // Allow all https requests ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(delegate { return true; }); #pragma warning restore CA5359 diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs index f1ae48492..f842230ee 100644 --- a/MediaBrowser.Api/UserLibrary/ItemsService.cs +++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs @@ -224,7 +224,17 @@ namespace MediaBrowser.Api.UserLibrary request.IncludeItemTypes = "Playlist"; } - if (!(item is UserRootFolder) && !user.Policy.EnableAllFolders && !user.Policy.EnabledFolders.Any(i => new Guid(i) == item.Id)) + bool isInEnabledFolder = user.Policy.EnabledFolders.Any(i => new Guid(i) == item.Id); + var collectionFolders = _libraryManager.GetCollectionFolders(item); + foreach (var collectionFolder in collectionFolders) + { + if (user.Policy.EnabledFolders.Contains(collectionFolder.Id.ToString("N"), StringComparer.OrdinalIgnoreCase)) + { + isInEnabledFolder = true; + } + } + + if (!(item is UserRootFolder) && !user.Policy.EnableAllFolders && !isInEnabledFolder) { Logger.LogWarning("{UserName} is not permitted to access Library {ItemName}.", user.Name, item.Name); return new QueryResult<BaseItem> diff --git a/MediaBrowser.Common/Net/HttpRequestOptions.cs b/MediaBrowser.Common/Net/HttpRequestOptions.cs index 38e0ff0f5..0576a1a5d 100644 --- a/MediaBrowser.Common/Net/HttpRequestOptions.cs +++ b/MediaBrowser.Common/Net/HttpRequestOptions.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Threading; using Microsoft.Net.Http.Headers; @@ -17,7 +16,7 @@ namespace MediaBrowser.Common.Net /// <value>The URL.</value> public string Url { get; set; } - public CompressionMethod? DecompressionMethod { get; set; } + public CompressionMethod DecompressionMethod { get; set; } /// <summary> /// Gets or sets the accept header. @@ -49,13 +48,21 @@ namespace MediaBrowser.Common.Net /// Gets or sets the referrer. /// </summary> /// <value>The referrer.</value> - public string Referer { get; set; } + public string Referer + { + get => GetHeaderValue(HeaderNames.Referer); + set => RequestHeaders[HeaderNames.Referer] = value; + } /// <summary> /// Gets or sets the host. /// </summary> /// <value>The host.</value> - public string Host { get; set; } + public string Host + { + get => GetHeaderValue(HeaderNames.Host); + set => RequestHeaders[HeaderNames.Host] = value; + } /// <summary> /// Gets or sets the progress. @@ -63,12 +70,6 @@ namespace MediaBrowser.Common.Net /// <value>The progress.</value> public IProgress<double> Progress { get; set; } - /// <summary> - /// Gets or sets a value indicating whether [enable HTTP compression]. - /// </summary> - /// <value><c>true</c> if [enable HTTP compression]; otherwise, <c>false</c>.</value> - public bool EnableHttpCompression { get; set; } - public Dictionary<string, string> RequestHeaders { get; private set; } public string RequestContentType { get; set; } @@ -104,13 +105,12 @@ namespace MediaBrowser.Common.Net /// </summary> public HttpRequestOptions() { - EnableHttpCompression = true; - RequestHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); LogRequest = true; LogErrors = true; CacheMode = CacheMode.None; + DecompressionMethod = CompressionMethod.Deflate; } } @@ -120,9 +120,11 @@ namespace MediaBrowser.Common.Net Unconditional = 1 } + [Flags] public enum CompressionMethod { - Deflate, - Gzip + None = 0b00000001, + Deflate = 0b00000010, + Gzip = 0b00000100 } } diff --git a/MediaBrowser.Common/Net/HttpResponseInfo.cs b/MediaBrowser.Common/Net/HttpResponseInfo.cs index 186674167..cd9feabfe 100644 --- a/MediaBrowser.Common/Net/HttpResponseInfo.cs +++ b/MediaBrowser.Common/Net/HttpResponseInfo.cs @@ -1,7 +1,7 @@ using System; -using System.Collections.Generic; using System.IO; using System.Net; +using System.Net.Http.Headers; namespace MediaBrowser.Common.Net { @@ -50,26 +50,21 @@ namespace MediaBrowser.Common.Net /// Gets or sets the headers. /// </summary> /// <value>The headers.</value> - public Dictionary<string, string> Headers { get; set; } + public HttpResponseHeaders Headers { get; set; } - private readonly IDisposable _disposable; - - public HttpResponseInfo(IDisposable disposable) + public HttpResponseInfo() { - _disposable = disposable; - Headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); + } - public HttpResponseInfo() + + public HttpResponseInfo(HttpResponseHeaders headers) { - Headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); + Headers = headers; } public void Dispose() { - if (_disposable != null) - { - _disposable.Dispose(); - } + // Only IDisposable for backwards compatibility } } } diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvDbClientManager.cs b/MediaBrowser.Providers/TV/TheTVDB/TvDbClientManager.cs index 1d1fbd00f..85833223e 100644 --- a/MediaBrowser.Providers/TV/TheTVDB/TvDbClientManager.cs +++ b/MediaBrowser.Providers/TV/TheTVDB/TvDbClientManager.cs @@ -24,24 +24,28 @@ namespace MediaBrowser.Providers.TV.TheTVDB { _cache = memoryCache; _tvDbClient = new TvDbClient(); - _tvDbClient.Authentication.AuthenticateAsync(TvdbUtils.TvdbApiKey); - _tokenCreatedAt = DateTime.Now; } - public TvDbClient TvDbClient + private TvDbClient TvDbClient { get { + if (string.IsNullOrEmpty(_tvDbClient.Authentication.Token)) + { + _tvDbClient.Authentication.AuthenticateAsync(TvdbUtils.TvdbApiKey).GetAwaiter().GetResult(); + _tokenCreatedAt = DateTime.Now; + } + // Refresh if necessary if (_tokenCreatedAt < DateTime.Now.Subtract(TimeSpan.FromHours(20))) { try { - _tvDbClient.Authentication.RefreshTokenAsync(); + _tvDbClient.Authentication.RefreshTokenAsync().GetAwaiter().GetResult(); } catch { - _tvDbClient.Authentication.AuthenticateAsync(TvdbUtils.TvdbApiKey); + _tvDbClient.Authentication.AuthenticateAsync(TvdbUtils.TvdbApiKey).GetAwaiter().GetResult(); } _tokenCreatedAt = DateTime.Now; diff --git a/MediaBrowser.WebDashboard/jellyfin-web b/MediaBrowser.WebDashboard/jellyfin-web -Subproject 37636dae5c6c0b0711dfc7612f843b864dd5946 +Subproject c9e70d95643e84437189dd500b0380ec0fbbf65 diff --git a/SharedVersion.cs b/SharedVersion.cs index 27ba1cf2c..1e74d8f7d 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,4 +1,4 @@ using System.Reflection; -[assembly: AssemblyVersion("10.3.5")] -[assembly: AssemblyFileVersion("10.3.5")] +[assembly: AssemblyVersion("10.3.6")] +[assembly: AssemblyFileVersion("10.3.6")] diff --git a/build.yaml b/build.yaml index cb010ed59..7a5e66b11 100644 --- a/build.yaml +++ b/build.yaml @@ -1,7 +1,7 @@ --- # We just wrap `build` so this is really it name: "jellyfin" -version: "10.3.5" +version: "10.3.6" packages: - debian-package-x64 - debian-package-armhf diff --git a/deployment/debian-package-x64/pkg-src/bin/restart.sh b/deployment/debian-package-x64/pkg-src/bin/restart.sh index 738f86727..9b64b6d72 100755 --- a/deployment/debian-package-x64/pkg-src/bin/restart.sh +++ b/deployment/debian-package-x64/pkg-src/bin/restart.sh @@ -1,20 +1,36 @@ #!/bin/bash -NAME=jellyfin +# restart.sh - Jellyfin server restart script +# Part of the Jellyfin project (https://github.com/jellyfin) +# +# This script restarts the Jellyfin daemon on Linux when using +# the Restart button on the admin dashboard. It supports the +# systemctl, service, and traditional /etc/init.d (sysv) restart +# methods, chosen automatically by which one is found first (in +# that order). +# +# This script is used by the Debian/Ubuntu/Fedora/CentOS packages. -restart_cmds=( - "systemctl restart ${NAME}" - "service ${NAME} restart" - "/etc/init.d/${NAME} restart" - "s6-svc -t /var/run/s6/services/${NAME}" -) +get_service_command() { + for command in systemctl service; do + if which $command &>/dev/null; then + echo $command && return + fi + done + echo "sysv" +} -for restart_cmd in "${restart_cmds[@]}"; do - cmd=$(echo "$restart_cmd" | awk '{print $1}') - cmd_loc=$(command -v ${cmd}) - if [[ -n "$cmd_loc" ]]; then - restart_cmd=$(echo "$restart_cmd" | sed -e "s%${cmd}%${cmd_loc}%") - echo "sleep 2; sudo $restart_cmd > /dev/null 2>&1" | at now > /dev/null 2>&1 - exit 0 - fi -done +cmd="$( get_service_command )" +echo "Detected service control platform '$cmd'; using it to restart Jellyfin..." +case $cmd in + 'systemctl') + echo "sleep 2; /usr/bin/sudo $( which systemctl ) restart jellyfin" | at now + ;; + 'service') + echo "sleep 2; /usr/bin/sudo $( which service ) jellyfin restart" | at now + ;; + 'sysv') + echo "sleep 2; /usr/bin/sudo /etc/init.d/jellyfin restart" | at now + ;; +esac +exit 0 diff --git a/deployment/debian-package-x64/pkg-src/changelog b/deployment/debian-package-x64/pkg-src/changelog index 94d0c87df..64912a11d 100644 --- a/deployment/debian-package-x64/pkg-src/changelog +++ b/deployment/debian-package-x64/pkg-src/changelog @@ -1,3 +1,9 @@ +jellyfin (10.3.6-1) unstable; urgency=medium + + * New upstream version 10.3.6; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.6 + + -- Jellyfin Packaging Team <packaging@jellyfin.org> Sat, 06 Jul 2019 13:34:19 -0400 + jellyfin (10.3.5-1) unstable; urgency=medium * New upstream version 10.3.5; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.5 diff --git a/deployment/fedora-package-x64/pkg-src/jellyfin.spec b/deployment/fedora-package-x64/pkg-src/jellyfin.spec index aeea7ecd0..809bde39d 100644 --- a/deployment/fedora-package-x64/pkg-src/jellyfin.spec +++ b/deployment/fedora-package-x64/pkg-src/jellyfin.spec @@ -7,7 +7,7 @@ %endif Name: jellyfin -Version: 10.3.5 +Version: 10.3.6 Release: 1%{?dist} Summary: The Free Software Media Browser License: GPLv2 @@ -140,6 +140,8 @@ fi %systemd_postun_with_restart jellyfin.service %changelog +* Sat Jul 06 2019 Jellyfin Packaging Team <packaging@jellyfin.org> +- New upstream version 10.3.6; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.6 * Sun Jun 09 2019 Jellyfin Packaging Team <packaging@jellyfin.org> - New upstream version 10.3.5; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.5 * Thu Jun 06 2019 Jellyfin Packaging Team <packaging@jellyfin.org> diff --git a/deployment/fedora-package-x64/pkg-src/restart.sh b/deployment/fedora-package-x64/pkg-src/restart.sh index e84dca587..9b64b6d72 100755 --- a/deployment/fedora-package-x64/pkg-src/restart.sh +++ b/deployment/fedora-package-x64/pkg-src/restart.sh @@ -1,6 +1,36 @@ -#!/bin/sh +#!/bin/bash -NAME=jellyfin -restart_cmd="/usr/bin/systemctl restart ${NAME}" -echo "sleep 2; sudo $restart_cmd > /dev/null 2>&1" | at now > /dev/null 2>&1 -exit 0
\ No newline at end of file +# restart.sh - Jellyfin server restart script +# Part of the Jellyfin project (https://github.com/jellyfin) +# +# This script restarts the Jellyfin daemon on Linux when using +# the Restart button on the admin dashboard. It supports the +# systemctl, service, and traditional /etc/init.d (sysv) restart +# methods, chosen automatically by which one is found first (in +# that order). +# +# This script is used by the Debian/Ubuntu/Fedora/CentOS packages. + +get_service_command() { + for command in systemctl service; do + if which $command &>/dev/null; then + echo $command && return + fi + done + echo "sysv" +} + +cmd="$( get_service_command )" +echo "Detected service control platform '$cmd'; using it to restart Jellyfin..." +case $cmd in + 'systemctl') + echo "sleep 2; /usr/bin/sudo $( which systemctl ) restart jellyfin" | at now + ;; + 'service') + echo "sleep 2; /usr/bin/sudo $( which service ) jellyfin restart" | at now + ;; + 'sysv') + echo "sleep 2; /usr/bin/sudo /etc/init.d/jellyfin restart" | at now + ;; +esac +exit 0 |
