diff --git a/.sdkharness/tests/conformance/LargeResume.java b/.sdkharness/tests/conformance/LargeResume.java index 53be57139..bd14fa83d 100644 --- a/.sdkharness/tests/conformance/LargeResume.java +++ b/.sdkharness/tests/conformance/LargeResume.java @@ -38,11 +38,11 @@ *

Two legs, because the SDK treats the two content shapes differently. Leg "no sha1": the content source does * not know its sha1, so the large file carries no large_file_sha1 and the rerun is the same request as the first * run. Leg "sha1": the content source knows its sha1 (the SDK records it as the large_file_sha1 file info); the - * rerun is again the same request as the first run. If the SDK refuses it with a "mismatch" error, the scenario - * FAILS with a FINDING as the reason: the check detects, it does not work around the refusal. The finding is about - * the SDK, not about B2: B2LargeFileUploader removes the key - * {@code X-Bz-Info-large_file_sha1} (B2Headers.LARGE_FILE_SHA1) from the file info before comparing it with the - * request's, but the info map is keyed {@code large_file_sha1} (B2Headers.LARGE_FILE_SHA1_INFO_NAME). + * rerun is again the same request as the first run, and the SDK must accept it. An earlier SDK version refused it + * with a "mismatch" error, because B2LargeFileUploader removed the key {@code X-Bz-Info-large_file_sha1} + * (B2Headers.LARGE_FILE_SHA1) from the stored file info before comparing it with the request's, while the info map + * is keyed {@code large_file_sha1} (B2Headers.LARGE_FILE_SHA1_INFO_NAME). This leg fails with a FINDING as the + * reason if that refusal comes back; the check detects, it does not work around the refusal. */ public class LargeResume { private static final int PARTS = 4; diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c43cf915..a831222eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ A missing or empty signature header throws `B2SignatureVerificationException`; a null or empty signing secret throws `IllegalArgumentException`. * Signature comparison is now constant-time. +### Fixed +* `finishUploadingLargeFile` no longer throws a local `mismatch` error when resuming a large file whose content source knows its sha1. + ### Added * Added `B2EventNotification.parseUnverified`, which parses without checking a signature. The one-argument `parse` overloads are deprecated in its favor. diff --git a/core/src/main/java/com/backblaze/b2/client/B2LargeFileUploader.java b/core/src/main/java/com/backblaze/b2/client/B2LargeFileUploader.java index ffeaa4590..f3c1c8785 100644 --- a/core/src/main/java/com/backblaze/b2/client/B2LargeFileUploader.java +++ b/core/src/main/java/com/backblaze/b2/client/B2LargeFileUploader.java @@ -138,7 +138,8 @@ B2FileVersion finishUploadingLargeFile(B2FileVersion largeFileVersion, final Map infos = new TreeMap<>(); //noinspection CollectionAddAllCanBeReplacedWithConstructor infos.putAll(largeFileVersion.getFileInfo()); - infos.remove(B2Headers.LARGE_FILE_SHA1); + // the stored file info is keyed by the bare name, "large_file_sha1", not by the "X-Bz-Info-" header name. + infos.remove(B2Headers.LARGE_FILE_SHA1_INFO_NAME); throwIfMismatch("fileInfo", toString(request.getFileInfo()), toString(infos)); } diff --git a/core/src/test/java/com/backblaze/b2/client/B2LargeFileUploaderTest.java b/core/src/test/java/com/backblaze/b2/client/B2LargeFileUploaderTest.java index feaabf482..481ba17ad 100644 --- a/core/src/test/java/com/backblaze/b2/client/B2LargeFileUploaderTest.java +++ b/core/src/test/java/com/backblaze/b2/client/B2LargeFileUploaderTest.java @@ -370,6 +370,59 @@ public void testThrowIfLargeFileVersionDoesntSeemToMatchRequest_mismatchingSha1( B2LargeFileUploader.throwIfLargeFileVersionDoesntSeemToMatchRequest(largeFileVersion, contentSource.getContentLength(), request); } + @Test + public void testThrowIfLargeFileVersionDoesntSeemToMatchRequest_matchingSha1IsNotAFileInfoMismatch() throws IOException, B2Exception { + // the SDK stores the sha1 of a large file as the file info "large_file_sha1" when it starts the file. + // that entry is checked as the sha1, so it must not also count as an unexpected file info entry. + when(contentSource.getSha1OrNull()).thenReturn(makeSha1(1)); + final B2UploadFileRequest request = B2UploadFileRequest + .builder(bucketId(1), fileName(2), B2ContentTypes.APPLICATION_OCTET, contentSource) + .build(); + final B2FileVersion largeFileVersion = new B2FileVersion( + fileId(1), + fileName(2), + contentSource.getContentLength(), + B2ContentTypes.APPLICATION_OCTET, + null, + null, + B2Collections.mapOf(B2Headers.LARGE_FILE_SHA1_INFO_NAME, makeSha1(1)), + "action", + 1234, + null, + null, + null, + null); + + // does not throw. + B2LargeFileUploader.throwIfLargeFileVersionDoesntSeemToMatchRequest(largeFileVersion, contentSource.getContentLength(), request); + } + + @Test + public void testThrowIfLargeFileVersionDoesntSeemToMatchRequest_otherFileInfoStillMismatches() throws IOException, B2Exception { + when(contentSource.getSha1OrNull()).thenReturn(makeSha1(1)); + final B2UploadFileRequest request = B2UploadFileRequest + .builder(bucketId(1), fileName(2), B2ContentTypes.APPLICATION_OCTET, contentSource) + .build(); + final B2FileVersion largeFileVersion = new B2FileVersion( + fileId(1), + fileName(2), + contentSource.getContentLength(), + B2ContentTypes.APPLICATION_OCTET, + null, + null, + B2Collections.mapOf(B2Headers.LARGE_FILE_SHA1_INFO_NAME, makeSha1(1), "color", "blue"), + "action", + 1234, + null, + null, + null, + null); + + thrown.expect(B2LocalException.class); + thrown.expectMessage("contentSource has fileInfo"); + B2LargeFileUploader.throwIfLargeFileVersionDoesntSeemToMatchRequest(largeFileVersion, contentSource.getContentLength(), request); + } + @Test public void testThrowIfLargeFileVersionDoesntSeemToMatchRequest_mismatchingContentType() throws IOException, B2Exception { final B2UploadFileRequest request = B2UploadFileRequest