22// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33
44using System ;
5+ using System . Globalization ;
56using System . IO ;
7+ using System . IO . Compression ;
68using System . Linq ;
79using System . Threading . Tasks ;
10+ using NuGet . Frameworks ;
11+ using NuGet . Packaging ;
812using NuGetGallery . Packaging ;
913
1014namespace NuGetGallery
@@ -15,17 +19,109 @@ public class SymbolPackageUploadService : ISymbolPackageUploadService
1519 private readonly IValidationService _validationService ;
1620 private readonly ISymbolPackageService _symbolPackageService ;
1721 private readonly ISymbolPackageFileService _symbolPackageFileService ;
22+ private readonly IPackageService _packageService ;
23+ private readonly ITelemetryService _telemetryService ;
24+ private readonly IContentObjectService _contentObjectService ;
1825
1926 public SymbolPackageUploadService (
2027 ISymbolPackageService symbolPackageService ,
2128 ISymbolPackageFileService symbolPackageFileService ,
2229 IEntitiesContext entitiesContext ,
23- IValidationService validationService )
30+ IValidationService validationService ,
31+ IPackageService packageService ,
32+ ITelemetryService telemetryService ,
33+ IContentObjectService contentObjectService )
2434 {
2535 _symbolPackageService = symbolPackageService ?? throw new ArgumentNullException ( nameof ( symbolPackageService ) ) ;
2636 _symbolPackageFileService = symbolPackageFileService ?? throw new ArgumentNullException ( nameof ( symbolPackageFileService ) ) ;
2737 _entitiesContext = entitiesContext ?? throw new ArgumentNullException ( nameof ( entitiesContext ) ) ;
2838 _validationService = validationService ?? throw new ArgumentNullException ( nameof ( validationService ) ) ;
39+ _packageService = packageService ?? throw new ArgumentNullException ( nameof ( packageService ) ) ;
40+ _telemetryService = telemetryService ?? throw new ArgumentNullException ( nameof ( telemetryService ) ) ;
41+ _contentObjectService = contentObjectService ?? throw new ArgumentNullException ( nameof ( contentObjectService ) ) ;
42+ }
43+
44+ /// <summary>
45+ /// This method does not perform the ownership validations for the symbols package. It is the responsibility
46+ /// of the caller to do it. Also, this method does not dispose the <see cref="Stream"/> object, the caller
47+ /// should take care of it.
48+ /// </summary>
49+ /// <param name="symbolPackageStream"><see cref="Stream"/> object for the symbols package.</param>
50+ /// <param name="currentUser">The user performing the uploads.</param>
51+ /// <returns>Awaitable task for <see cref="SymbolPackageValidationResult"/></returns>
52+ public async Task < SymbolPackageValidationResult > ValidateUploadedSymbolsPackage ( Stream symbolPackageStream , User currentUser )
53+ {
54+ Package package = null ;
55+
56+ // Check if symbol package upload is allowed for this user.
57+ if ( ! _contentObjectService . SymbolsConfiguration . IsSymbolsUploadEnabledForUser ( currentUser ) )
58+ {
59+ return SymbolPackageValidationResult . UserNotAllowedToUpload ( Strings . SymbolsPackage_UploadNotAllowed ) ;
60+ }
61+
62+ try
63+ {
64+ if ( ZipArchiveHelpers . FoundEntryInFuture ( symbolPackageStream , out ZipArchiveEntry entryInTheFuture ) )
65+ {
66+ return SymbolPackageValidationResult . Invalid ( string . Format (
67+ CultureInfo . CurrentCulture ,
68+ Strings . PackageEntryFromTheFuture ,
69+ entryInTheFuture . Name ) ) ;
70+ }
71+
72+ using ( var packageToPush = new PackageArchiveReader ( symbolPackageStream , leaveStreamOpen : true ) )
73+ {
74+ var nuspec = packageToPush . GetNuspecReader ( ) ;
75+ var id = nuspec . GetId ( ) ;
76+ var version = nuspec . GetVersion ( ) ;
77+ var normalizedVersion = version . ToNormalizedStringSafe ( ) ;
78+
79+ // Ensure the corresponding package exists before pushing a snupkg.
80+ package = _packageService . FindPackageByIdAndVersionStrict ( id , version . ToStringSafe ( ) ) ;
81+ if ( package == null || package . PackageStatusKey == PackageStatus . Deleted )
82+ {
83+ return SymbolPackageValidationResult . MissingPackage ( string . Format (
84+ CultureInfo . CurrentCulture ,
85+ Strings . SymbolsPackage_PackageIdAndVersionNotFound ,
86+ id ,
87+ normalizedVersion ) ) ;
88+ }
89+
90+ // Do not allow to upload a snupkg to a package which has symbols package pending validations.
91+ if ( package . SymbolPackages . Any ( sp => sp . StatusKey == PackageStatus . Validating ) )
92+ {
93+ return SymbolPackageValidationResult . SymbolsPackageExists ( Strings . SymbolsPackage_ConflictValidating ) ;
94+ }
95+
96+ try
97+ {
98+ await _symbolPackageService . EnsureValidAsync ( packageToPush ) ;
99+ }
100+ catch ( Exception ex )
101+ {
102+ ex . Log ( ) ;
103+
104+ var message = Strings . SymbolsPackage_FailedToReadPackage ;
105+ if ( ex is InvalidPackageException || ex is InvalidDataException || ex is EntityException )
106+ {
107+ message = ex . Message ;
108+ }
109+
110+ _telemetryService . TrackSymbolPackageFailedGalleryValidationEvent ( id , normalizedVersion ) ;
111+ return SymbolPackageValidationResult . Invalid ( message ) ;
112+ }
113+ }
114+
115+ return SymbolPackageValidationResult . AcceptedForPackage ( package ) ;
116+ }
117+ catch ( Exception ex ) when ( ex is InvalidPackageException
118+ || ex is InvalidDataException
119+ || ex is EntityException
120+ || ex is FrameworkException )
121+ {
122+ return SymbolPackageValidationResult . Invalid (
123+ string . Format ( CultureInfo . CurrentCulture , Strings . UploadPackage_InvalidPackage , ex . Message ) ) ;
124+ }
29125 }
30126
31127 /// <summary>
@@ -34,11 +130,21 @@ public SymbolPackageUploadService(
34130 /// based on the result. It will then update the references in the database for persistence with appropriate status.
35131 /// </summary>
36132 /// <param name="package">The package for which symbols package is to be uplloaded</param>
37- /// <param name="packageStreamMetadata">The package stream metadata for the uploaded symbols package file.</param>
38- /// <param name="symbolPackageFile">The symbol package file stream.</param>
39- /// <returns>The <see cref="PackageCommitResult"/> for the symbol package upload flow.</returns>
40- public async Task < PackageCommitResult > CreateAndUploadSymbolsPackage ( Package package , PackageStreamMetadata packageStreamMetadata , Stream symbolPackageFile )
133+ /// <param name="symbolPackageStream">The symbols package stream metadata for the uploaded symbols package file.</param>
134+ /// <returns>The <see cref="PackageCommitResult"/> for the create and upload symbol package flow.</returns>
135+ public async Task < PackageCommitResult > CreateAndUploadSymbolsPackage ( Package package , Stream symbolPackageStream )
41136 {
137+ var packageStreamMetadata = new PackageStreamMetadata
138+ {
139+ HashAlgorithm = CoreConstants . Sha512HashAlgorithmId ,
140+ Hash = CryptographyService . GenerateHash (
141+ symbolPackageStream . AsSeekableStream ( ) ,
142+ CoreConstants . Sha512HashAlgorithmId ) ,
143+ Size = symbolPackageStream . Length
144+ } ;
145+
146+ Stream symbolPackageFile = symbolPackageStream . AsSeekableStream ( ) ;
147+
42148 var symbolPackage = _symbolPackageService . CreateSymbolPackage ( package , packageStreamMetadata ) ;
43149
44150 await _validationService . StartValidationAsync ( symbolPackage ) ;
@@ -66,7 +172,7 @@ public async Task<PackageCommitResult> CreateAndUploadSymbolsPackage(Package pac
66172 // Mark any other associated available symbol packages for deletion.
67173 var availableSymbolPackages = package
68174 . SymbolPackages
69- . Where ( sp => sp . StatusKey == PackageStatus . Available
175+ . Where ( sp => sp . StatusKey == PackageStatus . Available
70176 && sp != symbolPackage ) ;
71177
72178 var overwrite = false ;
@@ -120,6 +226,8 @@ await _symbolPackageFileService.DeletePackageFileAsync(
120226 return PackageCommitResult . Conflict ;
121227 }
122228
229+ _telemetryService . TrackSymbolPackagePushEvent ( package . Id , package . NormalizedVersion ) ;
230+
123231 return PackageCommitResult . Success ;
124232 }
125233 }
0 commit comments