Skip to content

Commit a193e8e

Browse files
author
Sean O'Brien
committed
unset tagging when directive is unspecified
1 parent 17848f4 commit a193e8e

3 files changed

Lines changed: 33 additions & 29 deletions

File tree

features/multipart/s3.feature

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ Feature: S3 Multipart Uploads
6666
Scenario: Caller-supplied Tagging does not trigger REPLACE
6767
Given I have an s3 client and an uploaded file named "tags-no-auto" with tags
6868
When I call multipartCopy on "tags-no-auto" with caller-supplied Tagging "k=v&Project=X" only
69-
Then the copied file "tags-no-auto-copy" should have tags "k=v&Project=X"
69+
Then the copied file "tags-no-auto-copy" should have no tags
7070

7171
@s3annotations
7272
Scenario: tags_directive=COPY copies tags to the destination

src/S3/MultipartCopy.php

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -115,11 +115,11 @@ class MultipartCopy extends AbstractUploadManager
115115
* and any matching caller-supplied fields are dropped. When 'REPLACE',
116116
* no source metadata is read and caller-supplied params are used as-is.
117117
* - tags_directive: (string, default='UNSPECIFIED') 'UNSPECIFIED', 'COPY',
118-
* or 'REPLACE'. UNSPECIFIED means no tag work. COPY reads source tags
119-
* via GetObjectTagging and writes to the destination via
120-
* PutObjectTagging after CompleteMultipartUpload. REPLACE skips the
121-
* read and writes caller-supplied `params['Tagging']` to the
122-
* destination.
118+
* or 'REPLACE'. UNSPECIFIED means no tag work and any caller-supplied
119+
* `params['Tagging']` is dropped. COPY reads source tags via GetObjectTagging
120+
* and writes them to the destination via PutObjectTagging after
121+
* CompleteMultipartUpload. REPLACE skips the read and writes
122+
* caller-supplied `params['Tagging']` to the destination.
123123
* - annotations_directive: (string, default='UNSPECIFIED') 'UNSPECIFIED',
124124
* 'COPY', or 'EXCLUDE'. UNSPECIFIED and EXCLUDE both skip annotation
125125
* work. COPY reads source annotations via ListObjectAnnotations and
@@ -417,13 +417,11 @@ protected function getInitiateParams()
417417
}
418418
}
419419

420-
// Tags are written separately in Phase 3 to avoid x-amz-tagging header bloat.
421-
$tagsDir = $this->resolveTagsDirective();
422-
if ($tagsDir === self::TAGS_DIRECTIVE_COPY
423-
|| $tagsDir === self::TAGS_DIRECTIVE_REPLACE
424-
) {
425-
unset($params['Tagging']);
426-
}
420+
// When tags_directive is UNSPECIFIED, no tag work
421+
// happens at all and any caller-supplied
422+
// params['Tagging'] is dropped here too — callers who need their
423+
// tags applied must opt in via tags_directive='REPLACE'.
424+
unset($params['Tagging']);
427425

428426
return $params;
429427
}

tests/S3/MultipartCopyTest.php

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1040,10 +1040,8 @@ function (CommandInterface $cmd) use (&$captured) {
10401040
public function testCallerSuppliedTaggingDoesNotTriggerReplace()
10411041
{
10421042
// Caller-supplied params['Tagging'] does NOT auto-flip tags_directive.
1043-
// With no explicit tags_directive (the default), the resolver stays
1044-
// at UNSPECIFIED. No Phase 1 read,
1045-
// no Phase 3 PUT. Caller's Tagging rides on CreateMultipartUpload as
1046-
// before, preserving legacy initiate-time tagging behavior.
1043+
// Callers who need their Tagging applied must opt in via
1044+
// tags_directive='REPLACE'.
10471045
$client = $this->getTestClient('s3');
10481046
$this->addMockResults($client, [
10491047
new Result(['UploadId' => 'baz']),
@@ -1075,7 +1073,11 @@ function (CommandInterface $cmd) use (&$observed, &$initiate) {
10751073
$this->assertNotContains('GetObjectTagging', $observed);
10761074
$this->assertNotContains('PutObjectTagging', $observed);
10771075
$this->assertNotNull($initiate);
1078-
$this->assertSame('Project=Override&Env=prod', $initiate['Tagging']);
1076+
$this->assertNull(
1077+
$initiate['Tagging'] ?? null,
1078+
'UNSPECIFIED must drop caller-supplied Tagging from '
1079+
. 'CreateMultipartUpload — MPU never carries Tagging on initiate.'
1080+
);
10791081
}
10801082

10811083
public function testAnnotationPutTransientFailureIsRetried()
@@ -1739,14 +1741,13 @@ function (CommandInterface $cmd) use (&$initiate, &$putTagging) {
17391741
);
17401742
}
17411743

1742-
public function testTagsDirectiveUnspecifiedLeavesCallerTaggingOnInitiate()
1744+
public function testTagsDirectiveUnspecifiedDropsCallerTaggingFromInitiate()
17431745
{
1744-
// When tags_directive resolves to UNSPECIFIED (legacy default with
1745-
// no caller --tagging), there is
1746-
// no Phase 3 tag write. Caller-supplied params['Tagging'], if any, is
1747-
// left on CreateMultipartUpload exactly as before, preserving
1748-
// backwards compatibility for callers that relied on the original
1749-
// initiate-time tagging behavior.
1746+
// When tags_directive resolves to UNSPECIFIED
1747+
// (the default), there is no Phase 3 tag write AND any caller-supplied
1748+
// params['Tagging'] is dropped from the initiate. Callers who need
1749+
// their Tagging applied to the destination must opt in via
1750+
// tags_directive='REPLACE'.
17501751
$client = $this->getTestClient('s3');
17511752
$this->addMockResults($client, [
17521753
new Result(['UploadId' => 'baz']),
@@ -1772,13 +1773,18 @@ function (CommandInterface $cmd) use (&$initiate, &$observed) {
17721773
'key' => 'bar',
17731774
'source_metadata' => $this->srcMeta(),
17741775
'tags_directive' => 'UNSPECIFIED',
1775-
'params' => ['Tagging' => 'preserved=yes'],
1776+
'params' => ['Tagging' => 'dropped=yes'],
17761777
]);
17771778
$uploader->upload();
17781779

17791780
$this->assertNotNull($initiate);
1780-
$this->assertSame('preserved=yes', $initiate['Tagging']);
1781-
// No Phase 3 tag writes.
1781+
$this->assertNull(
1782+
$initiate['Tagging'] ?? null,
1783+
'UNSPECIFIED must drop caller-supplied Tagging from '
1784+
. 'CreateMultipartUpload — MPU never carries Tagging on initiate.'
1785+
);
1786+
// No Phase 1 read, no Phase 3 write.
1787+
$this->assertNotContains('GetObjectTagging', $observed);
17821788
$this->assertNotContains('PutObjectTagging', $observed);
17831789
}
17841790

@@ -2108,7 +2114,7 @@ function (CommandInterface $cmd) use (&$putAnnotCmds) {
21082114
$this->assertCount(3, $putAnnotCmds);
21092115

21102116
// First attempt: no @http.delay (cold path).
2111-
$this->assertNull($putAnnotCmds[0]['@http']['delay'] ?? null);
2117+
$this->assertEmpty($putAnnotCmds[0]['@http']['delay'] ?? null);
21122118

21132119
// Subsequent retries: @http.delay set to a non-negative integer
21142120
// within the 5000ms ceiling. (Full-jitter formula allows 0 as a

0 commit comments

Comments
 (0)