Skip to content

Commit c945f16

Browse files
committed
Create a working copy of the uploaded blob before processing it.
1 parent 76a2116 commit c945f16

File tree

1 file changed

+36
-13
lines changed

1 file changed

+36
-13
lines changed

app/lib/package/backend.dart

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ PackageBackend get packageBackend =>
7979
class PackageBackend {
8080
final DatastoreDB db;
8181

82+
final Storage _storage;
83+
8284
/// The Cloud Storage bucket to use for incoming package archives.
8385
/// The following files are present:
8486
/// - `tmp/$guid` (incoming package archive that was uploaded, but not yet processed)
@@ -92,12 +94,12 @@ class PackageBackend {
9294

9395
PackageBackend(
9496
this.db,
95-
Storage storage,
97+
this._storage,
9698
this._incomingBucket,
9799
Bucket canonicalBucket,
98100
Bucket publicBucket,
99101
) : tarballStorage =
100-
TarballStorage(db, storage, canonicalBucket, publicBucket);
102+
TarballStorage(db, _storage, canonicalBucket, publicBucket);
101103

102104
/// Whether the package exists and is not blocked or deleted.
103105
Future<bool> isPackageVisible(String package) async {
@@ -891,28 +893,48 @@ class PackageBackend {
891893

892894
/// Finishes the upload of a package and returns the list of messages
893895
/// related to the publishing.
894-
Future<List<String>> publishUploadedBlob(String guid) async {
896+
Future<List<String>> publishUploadedBlob(String uploadGuid) async {
895897
final restriction = await getUploadRestrictionStatus();
896898
if (restriction == UploadRestrictionStatus.noUploads) {
897899
throw PackageRejectedException.uploadRestricted();
898900
}
899901
final agent = await requireAuthenticatedClient();
900-
_logger.info('Finishing async upload (uuid: $guid)');
902+
_logger.info('Finishing async upload (uuid: $uploadGuid)');
901903
_logger.info('Reading tarball from cloud storage.');
902904

903905
return await withTempDirectory((Directory dir) async {
904-
final filename = '${dir.absolute.path}/tarball.tar.gz';
905-
final info = await _incomingBucket.tryInfo(tmpObjectName(guid));
906+
// Check the existence of the uploaded file
907+
final uploadObjectName = tmpObjectName(uploadGuid);
908+
final info = await _incomingBucket.tryInfo(uploadObjectName);
906909
if (info?.length == null) {
907910
throw PackageRejectedException.archiveEmpty();
908911
}
912+
913+
// Create a temporary copy that we will continue working with.
914+
// This will protect us against the unlikely scenario where the
915+
// uploaded blob changes during this processing.
916+
final workGuid = createUuid();
917+
final workObjectName = '${tmpObjectName(workGuid)}-$uploadGuid';
918+
try {
919+
await _storage.copyObjectWithRetry(
920+
_incomingBucket.absoluteObjectName(uploadObjectName),
921+
_incomingBucket.absoluteObjectName(workObjectName),
922+
);
923+
} catch (e) {
924+
throw InvalidInputException(
925+
'Failed to copy uploaded file (uuid:$uploadGuid).');
926+
}
927+
928+
// Check the file size is within limits.
909929
if (info!.length > UploadSignerService.maxUploadSize) {
910930
throw PackageRejectedException.archiveTooLarge(
911931
UploadSignerService.maxUploadSize);
912932
}
933+
934+
final filename = '${dir.absolute.path}/tarball.tar.gz';
913935
await _incomingBucket.readWithRetry(
914-
tmpObjectName(guid), (input) => _saveTarballToFS(input, filename));
915-
_logger.info('Examining tarball content ($guid).');
936+
workObjectName, (input) => _saveTarballToFS(input, filename));
937+
_logger.info('Examining tarball content ($uploadGuid).');
916938
final sw = Stopwatch()..start();
917939
final file = File(filename);
918940
final sha256Hash = (await file.openRead().transform(sha256).single).bytes;
@@ -996,15 +1018,16 @@ class PackageBackend {
9961018
entities: entities,
9971019
agent: agent,
9981020
archive: archive,
999-
guid: guid,
1021+
objectName: workObjectName,
10001022
hasCanonicalArchiveObject:
10011023
canonicalContentMatch == ContentMatchStatus.same,
10021024
);
10031025
_logger.info('Tarball uploaded in ${sw.elapsed}.');
1004-
_logger.info('Removing temporary object $guid.');
1026+
_logger.info('Removing temporary object $uploadGuid.');
10051027

10061028
sw.reset();
1007-
await _incomingBucket.deleteWithRetry(tmpObjectName(guid));
1029+
await _incomingBucket.deleteWithRetry(uploadObjectName);
1030+
await _incomingBucket.deleteWithRetry(workObjectName);
10081031
_logger.info('Temporary object removed in ${sw.elapsed}.');
10091032
return [
10101033
'Successfully uploaded '
@@ -1074,7 +1097,7 @@ class PackageBackend {
10741097
required _UploadEntities entities,
10751098
required AuthenticatedAgent agent,
10761099
required PackageSummary archive,
1077-
required String guid,
1100+
required String objectName,
10781101
required bool hasCanonicalArchiveObject,
10791102
}) async {
10801103
final sw = Stopwatch()..start();
@@ -1205,7 +1228,7 @@ class PackageBackend {
12051228
// Copy archive to canonical bucket.
12061229
await tarballStorage.copyFromTempToCanonicalBucket(
12071230
sourceAbsoluteObjectName:
1208-
_incomingBucket.absoluteObjectName(tmpObjectName(guid)),
1231+
_incomingBucket.absoluteObjectName(objectName),
12091232
package: newVersion.package,
12101233
version: newVersion.version!,
12111234
);

0 commit comments

Comments
 (0)