Skip to content

Commit 234a96e

Browse files
authored
Fix for the Gallery unable to find license file if paths with backslashes are used (#6739)
* Fix for the Gallery unable to find license file if paths with backslashes are used. * Adopted the client way of the file name mangling for license file access. Added logging in case mangling changes the file name.
1 parent 59b5122 commit 234a96e

6 files changed

Lines changed: 94 additions & 15 deletions

File tree

src/NuGetGallery.Core/Services/CoreLicenseFileService.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ public async Task ExtractAndSaveLicenseFileAsync(Package package, Stream package
7272
throw new InvalidOperationException("No license file specified in the nuspec");
7373
}
7474

75-
var filename = packageMetadata.LicenseMetadata.License;
75+
var filename = FileNameHelper.GetZipEntryPath(packageMetadata.LicenseMetadata.License);
7676
var licenseFileEntry = packageArchiveReader.GetEntry(filename); // throws on non-existent file
7777
using (var licenseFileStream = licenseFileEntry.Open())
7878
{

src/NuGetGallery.Core/Services/FIleNameHelper.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
using System;
55
using System.Globalization;
6+
using System.IO;
7+
using NuGet.Common;
68
using NuGet.Services.Entities;
79

810
namespace NuGetGallery
@@ -57,5 +59,27 @@ public static string BuildFileName(string id, string version, string pathTemplat
5759
version.ToLowerInvariant(),
5860
extension);
5961
}
62+
63+
/// <summary>
64+
/// Enforces the correct file separators for passing paths to work with zip file entries.
65+
/// </summary>
66+
/// <remarks>
67+
/// When client packs the nupkg, it enforces all zip file entries to use forward slashes
68+
/// and relative paths.
69+
/// At the same time, paths in nuspec can contain backslashes and start with dot. This
70+
/// method fixes the separators so those paths can be used to retrieve files from zip
71+
/// archive.
72+
/// </remarks>
73+
/// <param name="fileName">File name to fix.</param>
74+
/// <returns>File path with proper path separators.</returns>
75+
public static string GetZipEntryPath(string filePath)
76+
{
77+
if (filePath == null)
78+
{
79+
throw new ArgumentNullException(nameof(filePath));
80+
}
81+
82+
return PathUtility.StripLeadingDirectorySeparators(filePath);
83+
}
6084
}
6185
}

src/NuGetGallery/Services/PackageUploadService.cs

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
using NuGet.Services.Entities;
1616
using NuGet.Versioning;
1717
using NuGetGallery.Configuration;
18+
using NuGetGallery.Diagnostics;
1819
using NuGetGallery.Helpers;
1920
using NuGetGallery.Packaging;
2021

@@ -58,6 +59,7 @@ public class PackageUploadService : IPackageUploadService
5859
private readonly ITyposquattingService _typosquattingService;
5960
private readonly ITelemetryService _telemetryService;
6061
private readonly ICoreLicenseFileService _coreLicenseFileService;
62+
private readonly IDiagnosticsSource _trace;
6163

6264
public PackageUploadService(
6365
IPackageService packageService,
@@ -68,7 +70,8 @@ public PackageUploadService(
6870
IAppConfiguration config,
6971
ITyposquattingService typosquattingService,
7072
ITelemetryService telemetryService,
71-
ICoreLicenseFileService coreLicenseFileService)
73+
ICoreLicenseFileService coreLicenseFileService,
74+
IDiagnosticsService diagnosticsService)
7275
{
7376
_packageService = packageService ?? throw new ArgumentNullException(nameof(packageService));
7477
_packageFileService = packageFileService ?? throw new ArgumentNullException(nameof(packageFileService));
@@ -79,6 +82,11 @@ public PackageUploadService(
7982
_typosquattingService = typosquattingService ?? throw new ArgumentNullException(nameof(typosquattingService));
8083
_telemetryService = telemetryService ?? throw new ArgumentNullException(nameof(telemetryService));
8184
_coreLicenseFileService = coreLicenseFileService ?? throw new ArgumentNullException(nameof(coreLicenseFileService));
85+
if (diagnosticsService == null)
86+
{
87+
throw new ArgumentNullException(nameof(diagnosticsService));
88+
}
89+
_trace = diagnosticsService.GetSource(nameof(PackageUploadService));
8290
}
8391

8492
public async Task<PackageValidationResult> ValidateBeforeGeneratePackageAsync(PackageArchiveReader nuGetPackage, PackageMetadata packageMetadata)
@@ -234,18 +242,26 @@ private async Task<PackageValidationResult> CheckLicenseMetadataAsync(PackageArc
234242

235243
if (licenseMetadata.Type == LicenseType.File)
236244
{
245+
// fix the path separator. Client enforces forward slashes in all file paths when packing
246+
var licenseFilename = FileNameHelper.GetZipEntryPath(licenseMetadata.License);
247+
if (licenseFilename != licenseMetadata.License)
248+
{
249+
var packageIdentity = nuspecReader.GetIdentity();
250+
_trace.Information($"Transformed license file name from `{licenseMetadata.License}` to `{licenseFilename}` for package {packageIdentity.Id} {packageIdentity.Version}");
251+
}
252+
237253
// check if specified file is present in the package
238254
var fileList = new HashSet<string>(nuGetPackage.GetFiles());
239-
if (!fileList.Contains(licenseMetadata.License))
255+
if (!fileList.Contains(licenseFilename))
240256
{
241257
return PackageValidationResult.Invalid(
242258
string.Format(
243259
Strings.UploadPackage_LicenseFileDoesNotExist,
244-
licenseMetadata.License));
260+
licenseFilename));
245261
}
246262

247263
// check if specified file has allowed extension
248-
var licenseFileExtension = Path.GetExtension(licenseMetadata.License);
264+
var licenseFileExtension = Path.GetExtension(licenseFilename);
249265
if (!AllowedLicenseFileExtensions.Contains(licenseFileExtension, StringComparer.OrdinalIgnoreCase))
250266
{
251267
return PackageValidationResult.Invalid(
@@ -255,7 +271,7 @@ private async Task<PackageValidationResult> CheckLicenseMetadataAsync(PackageArc
255271
string.Join(", ", AllowedLicenseFileExtensions.Where(x => x != string.Empty).Select(extension => $"'{extension}'"))));
256272
}
257273

258-
var licenseFileEntry = nuGetPackage.GetEntry(licenseMetadata.License);
274+
var licenseFileEntry = nuGetPackage.GetEntry(licenseFilename);
259275
if (licenseFileEntry.Length > MaxAllowedLicenseLength)
260276
{
261277
return PackageValidationResult.Invalid(
@@ -264,7 +280,7 @@ private async Task<PackageValidationResult> CheckLicenseMetadataAsync(PackageArc
264280
MaxAllowedLicenseLength.ToUserFriendlyBytesLabel()));
265281
}
266282

267-
using (var licenseFileStream = nuGetPackage.GetStream(licenseMetadata.License))
283+
using (var licenseFileStream = nuGetPackage.GetStream(licenseFilename))
268284
{
269285
if (!await IsStreamLengthMatchesReportedAsync(licenseFileStream, licenseFileEntry.Length))
270286
{
@@ -273,7 +289,7 @@ private async Task<PackageValidationResult> CheckLicenseMetadataAsync(PackageArc
273289
}
274290

275291
// zip streams do not support seeking, so we'll have to reopen them
276-
using (var licenseFileStream = nuGetPackage.GetStream(licenseMetadata.License))
292+
using (var licenseFileStream = nuGetPackage.GetStream(licenseFilename))
277293
{
278294
// check if specified file is a text file
279295
if (!await TextHelper.LooksLikeUtf8TextStreamAsync(licenseFileStream))

tests/NuGetGallery.Core.Facts/Services/CoreLicenseFileServiceFacts.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -164,13 +164,15 @@ public async Task ThrowsOnMissingLicenseFile()
164164
Assert.Contains(LicenseFileName, ex.Message); // current implementation of the client does not properly set the FileName property
165165
}
166166

167-
[Fact]
168-
public async Task SavesLicenseFile()
167+
[Theory]
168+
[InlineData("license.txt")]
169+
[InlineData("foo\\bar.txt")]
170+
[InlineData("foo/bar.txt")]
171+
public async Task SavesLicenseFile(string licenseFilenName)
169172
{
170173
var fileStorageSvc = new Mock<ICoreFileStorageService>();
171174
var service = CreateService(fileStorageSvc);
172-
const string LicenseFileName = "license.txt";
173-
var packageStream = GeneratePackageAsync(LicenseFileName);
175+
var packageStream = GeneratePackageAsync(licenseFilenName);
174176
var package = PackageServiceUtility.CreateTestPackage();
175177
package.EmbeddedLicenseType = EmbeddedLicenseFileType.PlainText;
176178
var savedLicenseBytes = new byte[LicenseFileContents.Length];

tests/NuGetGallery.Core.Facts/TestUtils/TestPackage.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.IO.Compression;
88
using System.Linq;
99
using System.Text;
10+
using NuGet.Common;
1011
using NuGet.Packaging;
1112
using NuGet.Packaging.Core;
1213
using ClientPackageType = NuGet.Packaging.Core.PackageType;
@@ -212,6 +213,8 @@ public static MemoryStream CreateTestPackageStream(
212213

213214
if (licenseFileContents != null && licenseFilename != null)
214215
{
216+
// enforce directory separators the same way as the client (see PackageArchiveReader.GetStream)
217+
licenseFilename = PathUtility.StripLeadingDirectorySeparators(licenseFilename);
215218
var licenseEntry = packageArchive.CreateEntry(licenseFilename, CompressionLevel.Fastest);
216219
using (var licenseStream = licenseEntry.Open())
217220
{

tests/NuGetGallery.Facts/Services/PackageUploadServiceFacts.cs

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
using NuGet.Packaging.Core;
1515
using NuGet.Services.Entities;
1616
using NuGetGallery.Configuration;
17+
using NuGetGallery.Diagnostics;
1718
using NuGetGallery.Packaging;
1819
using NuGetGallery.TestUtils;
1920
using Xunit;
@@ -63,6 +64,10 @@ private static PackageUploadService CreateService(
6364

6465
validationService = validationService ?? new Mock<IValidationService>();
6566
config = config ?? new Mock<IAppConfiguration>();
67+
var diagnosticsService = new Mock<IDiagnosticsService>();
68+
diagnosticsService
69+
.Setup(ds => ds.GetSource(It.IsAny<string>()))
70+
.Returns(Mock.Of<IDiagnosticsSource>());
6671

6772
var packageUploadService = new Mock<PackageUploadService>(
6873
packageService.Object,
@@ -73,7 +78,8 @@ private static PackageUploadService CreateService(
7378
config.Object,
7479
new Mock<ITyposquattingService>().Object,
7580
Mock.Of<ITelemetryService>(),
76-
Mock.Of<ICoreLicenseFileService>());
81+
Mock.Of<ICoreLicenseFileService>(),
82+
diagnosticsService.Object);
7783

7884
return packageUploadService.Object;
7985
}
@@ -1046,6 +1052,28 @@ public async Task RejectsNupkgsWithUnknownLicenseTypes(string licenseNode)
10461052
Assert.Empty(result.Warnings);
10471053
}
10481054

1055+
[Theory]
1056+
[InlineData("license/something.txt")]
1057+
[InlineData("license\\anotherthing.txt")]
1058+
[InlineData(".\\license\\morething.txt")]
1059+
[InlineData("./license/lessthing.txt")]
1060+
public async Task AcceptsLicenseFileInSubdirectories(string licensePath)
1061+
{
1062+
_nuGetPackage = GeneratePackageWithLicense(
1063+
licenseFilename: licensePath,
1064+
licenseUrl: new Uri(LicenseDeprecationUrl),
1065+
licenseFileContents: "some license");
1066+
1067+
var result = await _target.ValidateBeforeGeneratePackageAsync(
1068+
_nuGetPackage.Object,
1069+
GetPackageMetadata(_nuGetPackage));
1070+
1071+
Assert.Equal(PackageValidationResultType.Accepted, result.Type);
1072+
Assert.Null(result.Message);
1073+
Assert.Empty(result.Warnings);
1074+
}
1075+
1076+
10491077
/// <summary>
10501078
/// A (quite ineffective) method to search for a sequence in an array
10511079
/// </summary>
@@ -1777,7 +1805,7 @@ public abstract class FactsBase
17771805
protected readonly Mock<ITyposquattingService> _typosquattingService;
17781806
protected readonly Mock<ITelemetryService> _telemetryService;
17791807
protected readonly Mock<ICoreLicenseFileService> _licenseFileService;
1780-
1808+
protected readonly Mock<IDiagnosticsService> _diagnosticsService;
17811809
protected Package _package;
17821810
protected Stream _packageFile;
17831811
protected ArgumentException _unexpectedException;
@@ -1812,6 +1840,11 @@ public FactsBase()
18121840
_conflictException = new FileAlreadyExistsException("Conflict!");
18131841
_token = CancellationToken.None;
18141842
_licenseFileService = new Mock<ICoreLicenseFileService>();
1843+
_diagnosticsService = new Mock<IDiagnosticsService>();
1844+
1845+
_diagnosticsService
1846+
.Setup(ds => ds.GetSource(It.IsAny<string>()))
1847+
.Returns(Mock.Of<IDiagnosticsSource>());
18151848

18161849
_target = new PackageUploadService(
18171850
_packageService.Object,
@@ -1822,7 +1855,8 @@ public FactsBase()
18221855
_config.Object,
18231856
_typosquattingService.Object,
18241857
_telemetryService.Object,
1825-
_licenseFileService.Object);
1858+
_licenseFileService.Object,
1859+
_diagnosticsService.Object);
18261860
}
18271861

18281862
protected static Mock<TestPackageReader> GeneratePackage(

0 commit comments

Comments
 (0)