/// <summary> /// Save the readme file from package stream. This method should throw if the package /// does not have an embedded readme file /// </summary> /// <param name="package">Package information.</param> /// <param name="packageStream">Package stream with .nupkg contents.</param> public async Task ExtractAndSaveReadmeFileAsync(Package package, Stream packageStream) { if (package == null) { throw new ArgumentNullException(nameof(package)); } if (packageStream == null) { throw new ArgumentNullException(nameof(packageStream)); } packageStream.Seek(0, SeekOrigin.Begin); using (var packageArchiveReader = new PackageArchiveReader(packageStream, leaveStreamOpen: true)) { var packageMetadata = PackageMetadata.FromNuspecReader(packageArchiveReader.GetNuspecReader(), strict: true); if (string.IsNullOrWhiteSpace(packageMetadata.ReadmeFile)) { throw new InvalidOperationException("No readme file specified in the nuspec"); } var filename = FileNameHelper.GetZipEntryPath(packageMetadata.ReadmeFile); var ReadmeFileEntry = packageArchiveReader.GetEntry(filename); // throws on non-existent file using (var readmeFileStream = ReadmeFileEntry.Open()) { await SaveReadmeFileAsync(package, readmeFileStream); } } }
private async Task <PackageValidationResult> CheckIconMetadataAsync(PackageArchiveReader nuGetPackage, List <IValidationMessage> warnings, User user) { var nuspecReader = GetNuspecReader(nuGetPackage); var iconElement = nuspecReader.IconElement; var embeddedIconsEnabled = _featureFlagService.AreEmbeddedIconsEnabled(user); if (iconElement == null) { if (embeddedIconsEnabled && !string.IsNullOrWhiteSpace(nuspecReader.GetIconUrl())) { warnings.Add(new IconUrlDeprecationValidationMessage()); } return(null); } if (!embeddedIconsEnabled) { return(PackageValidationResult.Invalid(Strings.UploadPackage_EmbeddedIconNotAccepted)); } if (HasChildElements(iconElement)) { return(PackageValidationResult.Invalid(string.Format(Strings.UploadPackage_NodeContainsChildren, IconNodeName))); } var iconFilePath = FileNameHelper.GetZipEntryPath(iconElement.Value); if (!FileExists(nuGetPackage, iconFilePath)) { return(PackageValidationResult.Invalid( string.Format( Strings.UploadPackage_FileDoesNotExist, Strings.UploadPackage_IconFileType, iconFilePath))); } var iconFileExtension = Path.GetExtension(iconFilePath); if (!AllowedIconFileExtensions.Contains(iconFileExtension, StringComparer.OrdinalIgnoreCase)) { return(PackageValidationResult.Invalid( string.Format( Strings.UploadPackage_InvalidIconFileExtension, iconFileExtension, string.Join(", ", AllowedIconFileExtensions.Where(x => x != string.Empty).Select(extension => $"'{extension}'"))))); } var iconFileEntry = nuGetPackage.GetEntry(iconFilePath); if (iconFileEntry.Length > MaxAllowedIconLengthForUploading) { return(PackageValidationResult.Invalid( string.Format( Strings.UploadPackage_FileTooLong, Strings.UploadPackage_IconFileType, MaxAllowedLicenseLengthForUploading.ToUserFriendlyBytesLabel()))); } if (!await IsStreamLengthMatchesReportedAsync(nuGetPackage, iconFilePath, iconFileEntry.Length)) { return(PackageValidationResult.Invalid(Strings.UploadPackage_CorruptNupkg)); } bool isJpg = await IsJpegAsync(nuGetPackage, iconFilePath); bool isPng = await IsPngAsync(nuGetPackage, iconFilePath); if (!isPng && !isJpg) { return(PackageValidationResult.Invalid(Strings.UploadPackage_UnsupportedIconImageFormat)); } return(null); }
private async Task <PackageValidationResult> CheckLicenseMetadataAsync(PackageArchiveReader nuGetPackage, List <IValidationMessage> warnings, User user) { var nuspecReader = GetNuspecReader(nuGetPackage); var licenseElement = nuspecReader.LicenseElement; if (licenseElement != null) { if (licenseElement.Value.Length > MaxAllowedLicenseNodeValueLength) { return(PackageValidationResult.Invalid(Strings.UploadPackage_LicenseNodeValueTooLong)); } if (HasChildElements(licenseElement)) { return(PackageValidationResult.Invalid(string.Format(Strings.UploadPackage_NodeContainsChildren, LicenseNodeName))); } var typeText = GetLicenseType(licenseElement); if (!AllowedLicenseTypes.Contains(typeText, StringComparer.OrdinalIgnoreCase)) { return(PackageValidationResult.Invalid(string.Format(Strings.UploadPackage_UnsupportedLicenseType, typeText))); } var versionText = GetLicenseVersion(licenseElement); if (versionText != null && AllowedLicenseVersion != versionText) { return(PackageValidationResult.Invalid( string.Format( Strings.UploadPackage_UnsupportedLicenseVersion, versionText))); } } var licenseUrl = nuspecReader.GetLicenseUrl(); var licenseMetadata = nuspecReader.GetLicenseMetadata(); var licenseDeprecationUrl = GetExpectedLicenseUrl(licenseMetadata); if (licenseMetadata == null) { if (string.IsNullOrWhiteSpace(licenseUrl)) { if (!_config.AllowLicenselessPackages) { return(PackageValidationResult.Invalid(new LicenseUrlDeprecationValidationMessage(Strings.UploadPackage_MissingLicenseInformation))); } else { warnings.Add(new LicenseUrlDeprecationValidationMessage(Strings.UploadPackage_LicenseShouldBeSpecified)); } } if (licenseDeprecationUrl == licenseUrl) { return(PackageValidationResult.Invalid(Strings.UploadPackage_DeprecationUrlUsage)); } if (!string.IsNullOrWhiteSpace(licenseUrl)) { if (_config.BlockLegacyLicenseUrl) { return(PackageValidationResult.Invalid(new LicenseUrlDeprecationValidationMessage(Strings.UploadPackage_LegacyLicenseUrlNotAllowed))); } else { warnings.Add(new LicenseUrlDeprecationValidationMessage(Strings.UploadPackage_DeprecatingLicenseUrl)); } } // we will return here, so the code below would not need to check for licenseMetadata to be non-null over and over. return(null); } if (licenseMetadata.WarningsAndErrors != null && licenseMetadata.WarningsAndErrors.Any()) { _telemetryService.TrackInvalidLicenseMetadata(licenseMetadata.License); return(PackageValidationResult.Invalid( string.Format( Strings.UploadPackage_InvalidLicenseMetadata, string.Join(" ", licenseMetadata.WarningsAndErrors)))); } if (licenseDeprecationUrl != licenseUrl) { if (IsMalformedDeprecationUrl(licenseUrl)) { return(PackageValidationResult.Invalid(new InvalidUrlEncodingForLicenseUrlValidationMessage())); } if (licenseMetadata.Type == LicenseType.File) { return(PackageValidationResult.Invalid( new InvalidLicenseUrlValidationMessage( string.Format(Strings.UploadPackage_DeprecationUrlRequiredForLicenseFiles, licenseDeprecationUrl)))); } else if (licenseMetadata.Type == LicenseType.Expression) { return(PackageValidationResult.Invalid( new InvalidLicenseUrlValidationMessage( string.Format(Strings.UploadPackage_DeprecationUrlRequiredForLicenseExpressions, licenseDeprecationUrl)))); } } if (licenseMetadata.Type == LicenseType.File) { // fix the path separator. Client enforces forward slashes in all file paths when packing var licenseFilename = FileNameHelper.GetZipEntryPath(licenseMetadata.License); if (licenseFilename != licenseMetadata.License) { var packageIdentity = nuspecReader.GetIdentity(); _trace.Information($"Transformed license file name from `{licenseMetadata.License}` to `{licenseFilename}` for package {packageIdentity.Id} {packageIdentity.Version}"); } // check if specified file is present in the package if (!FileExists(nuGetPackage, licenseFilename)) { return(PackageValidationResult.Invalid( string.Format( Strings.UploadPackage_FileDoesNotExist, Strings.UploadPackage_LicenseFileType, licenseFilename))); } // check if specified file has allowed extension var licenseFileExtension = Path.GetExtension(licenseFilename); if (!AllowedLicenseFileExtensions.Contains(licenseFileExtension, StringComparer.OrdinalIgnoreCase)) { return(PackageValidationResult.Invalid( string.Format( Strings.UploadPackage_InvalidLicenseFileExtension, licenseFileExtension, string.Join(", ", AllowedLicenseFileExtensions.Where(x => x != string.Empty).Select(extension => $"'{extension}'"))))); } var licenseFileEntry = nuGetPackage.GetEntry(licenseFilename); if (licenseFileEntry.Length > MaxAllowedLicenseLengthForUploading) { return(PackageValidationResult.Invalid( string.Format( Strings.UploadPackage_FileTooLong, Strings.UploadPackage_LicenseFileType, MaxAllowedLicenseLengthForUploading.ToUserFriendlyBytesLabel()))); } if (!await IsStreamLengthMatchesReportedAsync(nuGetPackage, licenseFilename, licenseFileEntry.Length)) { return(PackageValidationResult.Invalid(Strings.UploadPackage_CorruptNupkg)); } // zip streams do not support seeking, so we'll have to reopen them using (var licenseFileStream = nuGetPackage.GetStream(licenseFilename)) { // check if specified file is a text file if (!await TextHelper.LooksLikeUtf8TextStreamAsync(licenseFileStream)) { return(PackageValidationResult.Invalid(Strings.UploadPackage_LicenseMustBePlainText)); } } } if (licenseMetadata.Type == LicenseType.Expression) { if (licenseMetadata.LicenseExpression == null) { throw new InvalidOperationException($"Unexpected value of {nameof(licenseMetadata.LicenseExpression)} property"); } var licenseList = GetLicenseList(licenseMetadata.LicenseExpression); var unapprovedLicenses = licenseList.Where(license => !license.IsOsiApproved && !license.IsFsfLibre).ToList(); if (unapprovedLicenses.Any()) { _telemetryService.TrackNonFsfOsiLicenseUse(licenseMetadata.License); return(PackageValidationResult.Invalid( string.Format( Strings.UploadPackage_NonFsfOrOsiLicense, string.Join(", ", unapprovedLicenses.Select(l => l.LicenseID))))); } } return(null); }
private async Task <PackageValidationResult> CheckReadmeMetadataAsync(PackageArchiveReader nuGetPackage, User user) { var nuspecReader = GetNuspecReader(nuGetPackage); var readmeElement = nuspecReader.ReadmeElement; if (readmeElement == null) { return(null); } var embeddedReadmeEnabled = _featureFlagService.AreEmbeddedReadmesEnabled(user); if (!embeddedReadmeEnabled) { return(PackageValidationResult.Invalid(Strings.UploadPackage_EmbeddedReadmeNotAccepted)); } if (HasChildElements(readmeElement)) { return(PackageValidationResult.Invalid(string.Format(Strings.UploadPackage_NodeContainsChildren, ReadmeNodeName))); } var readmeFilePath = FileNameHelper.GetZipEntryPath(readmeElement.Value); if (!FileExists(nuGetPackage, readmeFilePath)) { return(PackageValidationResult.Invalid( string.Format( Strings.UploadPackage_FileDoesNotExist, Strings.UploadPackage_ReadmeFileType, readmeFilePath))); } var readmeFileExtension = Path.GetExtension(readmeFilePath); if (!AllowedReadmeFileExtensions.Contains(readmeFileExtension, StringComparer.OrdinalIgnoreCase)) { var result = PackageValidationResult.Invalid( string.Format( Strings.UploadPackage_InvalidReadmeFileExtension, readmeFileExtension, string.Join(", ", AllowedReadmeFileExtensions.Where(x => x != string.Empty).Select(extension => $"'{extension}'")))); return(result); } var readmeFileEntry = nuGetPackage.GetEntry(readmeFilePath); if (readmeFileEntry.Length > MaxAllowedReadmeLengthForUploading) { return(PackageValidationResult.Invalid( string.Format( Strings.UploadPackage_FileTooLong, Strings.UploadPackage_ReadmeFileType, MaxAllowedReadmeLengthForUploading.ToUserFriendlyBytesLabel()))); } if (!await IsStreamLengthMatchesReportedAsync(nuGetPackage, readmeFilePath, readmeFileEntry.Length)) { return(PackageValidationResult.Invalid(Strings.UploadPackage_CorruptNupkg)); } // zip streams do not support seeking, so we'll have to reopen them using (var readmeFileStream = nuGetPackage.GetStream(readmeFilePath)) { // check if specified file is a text file if (!await TextHelper.LooksLikeUtf8TextStreamAsync(readmeFileStream)) { return(PackageValidationResult.Invalid(Strings.UploadPackage_ReadmeMustBePlainText)); } } return(null); }