From 5eaf5465a55df0359f85077b7922ca4a45681831 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Mon, 29 Jul 2019 23:47:25 +0200 Subject: Check checksum for plugin downloads * Compare the MD5 checksum when downloading plugins * Reduced log spam due to http requests * Removed 'GetTempFileResponse' function from HttpClientManager * Fixed caching for HttpClientManager --- .../Updates/InstallationManager.cs | 140 ++++++++------------- 1 file changed, 49 insertions(+), 91 deletions(-) (limited to 'Emby.Server.Implementations/Updates') diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index 9bc85633d..c60319964 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -4,13 +4,14 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Net.Http; +using System.Security.Cryptography; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common; using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Common.Plugins; -using MediaBrowser.Common.Progress; using MediaBrowser.Common.Updates; using MediaBrowser.Controller.Configuration; using MediaBrowser.Model.Events; @@ -126,13 +127,16 @@ namespace Emby.Server.Implementations.Updates /// Task{List{PackageInfo}}. public async Task> GetAvailablePackagesWithoutRegistrationInfo(CancellationToken cancellationToken) { - using (var response = await _httpClient.SendAsync(new HttpRequestOptions - { - Url = "https://repo.jellyfin.org/releases/plugin/manifest.json", - CancellationToken = cancellationToken, - CacheLength = GetCacheLength() - }, HttpMethod.Get).ConfigureAwait(false)) - using (var stream = response.Content) + using (var response = await _httpClient.SendAsync( + new HttpRequestOptions + { + Url = "https://repo.jellyfin.org/releases/plugin/manifest.json", + CancellationToken = cancellationToken, + CacheMode = CacheMode.Unconditional, + CacheLength = GetCacheLength() + }, + HttpMethod.Get).ConfigureAwait(false)) + using (Stream stream = response.Content) { return FilterPackages(await _jsonSerializer.DeserializeFromStreamAsync(stream).ConfigureAwait(false)); } @@ -309,27 +313,14 @@ namespace Emby.Server.Implementations.Updates .Where(p => !string.IsNullOrEmpty(p.sourceUrl) && !CompletedInstallations.Any(i => string.Equals(i.AssemblyGuid, p.guid, StringComparison.OrdinalIgnoreCase))); } - /// - /// Installs the package. - /// - /// The package. - /// if set to true [is plugin]. - /// The progress. - /// The cancellation token. - /// Task. - /// package - public async Task InstallPackage(PackageVersionInfo package, IProgress progress, CancellationToken cancellationToken) + /// + public async Task InstallPackage(PackageVersionInfo package, CancellationToken cancellationToken) { if (package == null) { throw new ArgumentNullException(nameof(package)); } - if (progress == null) - { - throw new ArgumentNullException(nameof(progress)); - } - var installationInfo = new InstallationInfo { Id = Guid.NewGuid(), @@ -349,16 +340,6 @@ namespace Emby.Server.Implementations.Updates _currentInstallations.Add(tuple); } - var innerProgress = new ActionableProgress(); - - // Whenever the progress updates, update the outer progress object and InstallationInfo - innerProgress.RegisterAction(percent => - { - progress.Report(percent); - - installationInfo.PercentComplete = percent; - }); - var linkedToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, innerCancellationTokenSource.Token).Token; var installationEventArgs = new InstallationEventArgs @@ -371,7 +352,7 @@ namespace Emby.Server.Implementations.Updates try { - await InstallPackageInternal(package, innerProgress, linkedToken).ConfigureAwait(false); + await InstallPackageInternal(package, linkedToken).ConfigureAwait(false); lock (_currentInstallations) { @@ -423,20 +404,16 @@ namespace Emby.Server.Implementations.Updates /// Installs the package internal. /// /// The package. - /// if set to true [is plugin]. - /// The progress. /// The cancellation token. - /// Task. - private async Task InstallPackageInternal(PackageVersionInfo package, IProgress progress, CancellationToken cancellationToken) + /// . + private async Task InstallPackageInternal(PackageVersionInfo package, CancellationToken cancellationToken) { // Set last update time if we were installed before IPlugin plugin = _applicationHost.Plugins.FirstOrDefault(p => string.Equals(p.Id.ToString(), package.guid, StringComparison.OrdinalIgnoreCase)) ?? _applicationHost.Plugins.FirstOrDefault(p => p.Name.Equals(package.name, StringComparison.OrdinalIgnoreCase)); - string targetPath = plugin == null ? null : plugin.AssemblyFilePath; - // Do the install - await PerformPackageInstallation(progress, targetPath, package, cancellationToken).ConfigureAwait(false); + await PerformPackageInstallation(package, cancellationToken).ConfigureAwait(false); // Do plugin-specific processing if (plugin == null) @@ -455,76 +432,57 @@ namespace Emby.Server.Implementations.Updates _applicationHost.NotifyPendingRestart(); } - private async Task PerformPackageInstallation(IProgress progress, string target, PackageVersionInfo package, CancellationToken cancellationToken) + private async Task PerformPackageInstallation(PackageVersionInfo package, CancellationToken cancellationToken) { - // TODO: Remove the `string target` argument as it is not used any longer - var extension = Path.GetExtension(package.targetFilename); - var isArchive = string.Equals(extension, ".zip", StringComparison.OrdinalIgnoreCase); - - if (!isArchive) + if (!string.Equals(extension, ".zip", StringComparison.OrdinalIgnoreCase)) { _logger.LogError("Only zip packages are supported. {Filename} is not a zip archive.", package.targetFilename); return; } // Always override the passed-in target (which is a file) and figure it out again - target = Path.Combine(_appPaths.PluginsPath, package.name); - _logger.LogDebug("Installing plugin to {Filename}.", target); - - // Download to temporary file so that, if interrupted, it won't destroy the existing installation - _logger.LogDebug("Downloading ZIP."); - var tempFile = await _httpClient.GetTempFile(new HttpRequestOptions - { - Url = package.sourceUrl, - CancellationToken = cancellationToken, - Progress = progress - - }).ConfigureAwait(false); + string targetDir = Path.Combine(_appPaths.PluginsPath, package.name); - cancellationToken.ThrowIfCancellationRequested(); - - // TODO: Validate with a checksum, *properly* - - // Check if the target directory already exists, and remove it if so - if (Directory.Exists(target)) - { - _logger.LogDebug("Deleting existing plugin at {Filename}.", target); - Directory.Delete(target, true); - } +// CA5351: Do Not Use Broken Cryptographic Algorithms +#pragma warning disable CA5351 + using (var res = await _httpClient.SendAsync( + new HttpRequestOptions + { + Url = package.sourceUrl, + CancellationToken = cancellationToken, + // We need it to be buffered for setting the position + BufferContent = true + }, + HttpMethod.Get).ConfigureAwait(false)) + using (var stream = res.Content) + using (var md5 = MD5.Create()) + { + cancellationToken.ThrowIfCancellationRequested(); + + var hash = HexHelper.ToHexString(md5.ComputeHash(stream)); + if (!string.Equals(package.checksum, hash, StringComparison.OrdinalIgnoreCase)) + { + _logger.LogDebug("{0}, {1}", package.checksum, hash); + throw new InvalidDataException($"The checksums didn't match while installing {package.name}."); + } - // Success - move it to the real target - try - { - _logger.LogDebug("Extracting ZIP {TempFile} to {Filename}.", tempFile, target); - using (var stream = File.OpenRead(tempFile)) + if (Directory.Exists(targetDir)) { - _zipClient.ExtractAllFromZip(stream, target, true); + Directory.Delete(targetDir); } - } - catch (IOException ex) - { - _logger.LogError(ex, "Error attempting to extract {TempFile} to {TargetFile}", tempFile, target); - throw; - } - try - { - _logger.LogDebug("Deleting temporary file {Filename}.", tempFile); - _fileSystem.DeleteFile(tempFile); - } - catch (IOException ex) - { - // Don't fail because of this - _logger.LogError(ex, "Error deleting temp file {TempFile}", tempFile); + stream.Position = 0; + _zipClient.ExtractAllFromZip(stream, targetDir, true); } + +#pragma warning restore CA5351 } /// /// Uninstalls a plugin /// /// The plugin. - /// public void UninstallPlugin(IPlugin plugin) { plugin.OnUninstalling(); -- cgit v1.2.3 From 72436892154892c53c75ee5fdcbcb3bf843ac85c Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sun, 11 Aug 2019 15:57:36 +0200 Subject: Minor improvements --- Emby.Server.Implementations/Updates/InstallationManager.cs | 8 +------- MediaBrowser.Api/PackageService.cs | 2 +- MediaBrowser.Common/Updates/IInstallationManager.cs | 2 +- 3 files changed, 3 insertions(+), 9 deletions(-) (limited to 'Emby.Server.Implementations/Updates') diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index c60319964..2f84b91ec 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -279,12 +279,7 @@ namespace Emby.Server.Implementations.Updates var package = availablePackages.FirstOrDefault(p => string.Equals(p.guid, guid ?? "none", StringComparison.OrdinalIgnoreCase)) ?? availablePackages.FirstOrDefault(p => p.name.Equals(name, StringComparison.OrdinalIgnoreCase)); - if (package == null) - { - return null; - } - - return package.versions + return package?.versions .OrderByDescending(x => x.Version) .FirstOrDefault(v => v.classification <= classification && IsPackageVersionUpToDate(v, currentServerVersion)); } @@ -308,7 +303,6 @@ namespace Emby.Server.Implementations.Updates var latestPluginInfo = GetLatestCompatibleVersion(catalog, p.Name, p.Id.ToString(), applicationVersion, systemUpdateLevel); return latestPluginInfo != null && latestPluginInfo.Version > p.Version ? latestPluginInfo : null; - }).Where(i => i != null) .Where(p => !string.IsNullOrEmpty(p.sourceUrl) && !CompletedInstallations.Any(i => string.Equals(i.AssemblyGuid, p.guid, StringComparison.OrdinalIgnoreCase))); } diff --git a/MediaBrowser.Api/PackageService.cs b/MediaBrowser.Api/PackageService.cs index 915c9784e..baa6f7bb9 100644 --- a/MediaBrowser.Api/PackageService.cs +++ b/MediaBrowser.Api/PackageService.cs @@ -197,7 +197,7 @@ namespace MediaBrowser.Api throw new ResourceNotFoundException(string.Format("Package not found: {0}", request.Name)); } - await _installationManager.InstallPackage(package, CancellationToken.None); + await _installationManager.InstallPackage(package); } /// diff --git a/MediaBrowser.Common/Updates/IInstallationManager.cs b/MediaBrowser.Common/Updates/IInstallationManager.cs index d43024ac8..88ac7e473 100644 --- a/MediaBrowser.Common/Updates/IInstallationManager.cs +++ b/MediaBrowser.Common/Updates/IInstallationManager.cs @@ -99,7 +99,7 @@ namespace MediaBrowser.Common.Updates /// The package. /// The cancellation token. /// . - Task InstallPackage(PackageVersionInfo package, CancellationToken cancellationToken); + Task InstallPackage(PackageVersionInfo package, CancellationToken cancellationToken = default); /// /// Uninstalls a plugin -- cgit v1.2.3