getUri()->getQuery() !== 'delete') { return $request; } if (!$request->hasHeader('Content-MD5')) { $body = $request->getBody(); $contentMd5 = base64_encode(Utils::hash($body, 'md5', true)); return $request->withHeader('Content-MD5', $contentMd5); } return $request; } /** * Test that Content-MD5 header is added to DeleteObjects requests */ public function testContentMd5HeaderAddedToDeleteObjects(): void { $testBody = 'test-key'; $request = new Request('POST', 'http://s3.example.com/bucket?delete', [], $testBody); // Calculate expected MD5 $expectedMd5 = base64_encode(md5($testBody, true)); // Apply middleware logic $resultRequest = $this->applyContentMd5Middleware($request); // Verify header was added $this->assertTrue($resultRequest->hasHeader('Content-MD5')); $this->assertEquals($expectedMd5, $resultRequest->getHeaderLine('Content-MD5')); } /** * Test that Content-MD5 header is NOT added to non-DeleteObjects requests */ public function testContentMd5NotAddedToNonDeleteRequests(): void { $testCases = [ 'GET request' => new Request('GET', 'http://s3.example.com/bucket/key'), 'PUT request' => new Request('PUT', 'http://s3.example.com/bucket/key'), 'HEAD request' => new Request('HEAD', 'http://s3.example.com/bucket/key'), 'POST with different query' => new Request('POST', 'http://s3.example.com/bucket?uploads'), ]; foreach ($testCases as $label => $request) { $resultRequest = $this->applyContentMd5Middleware($request); // Verify header was NOT added for non-delete requests $this->assertFalse($resultRequest->hasHeader('Content-MD5'), "Content-MD5 should not be added for: $label"); } } /** * Test that existing Content-MD5 header is preserved */ public function testExistingContentMd5HeaderPreserved(): void { $testBody = 'test data'; $existingMd5 = 'existing-md5-value'; $request = new Request( 'POST', 'http://s3.example.com/bucket?delete', ['Content-MD5' => $existingMd5], $testBody ); // Apply middleware logic $resultRequest = $this->applyContentMd5Middleware($request); // Verify existing header was preserved $this->assertTrue($resultRequest->hasHeader('Content-MD5')); $this->assertEquals($existingMd5, $resultRequest->getHeaderLine('Content-MD5')); } /** * Test MD5 calculation with various body sizes */ public function testMd5CalculationWithVariousSizes(): void { $testBodies = [ 'small' => 'x', 'medium' => str_repeat('y', 1000), 'large' => str_repeat('z', 10000), 'xml_payload' => 'file1.txtfile2.txt', ]; foreach ($testBodies as $label => $body) { $request = new Request('POST', 'http://s3.example.com/bucket?delete', [], $body); $expectedMd5 = base64_encode(md5($body, true)); $resultRequest = $this->applyContentMd5Middleware($request); $this->assertEquals( $expectedMd5, $resultRequest->getHeaderLine('Content-MD5'), "MD5 mismatch for $label body size" ); } } /** * Test MD5 header format is base64-encoded */ public function testMd5HeaderFormatIsBase64(): void { $testBody = 'test data for base64 validation'; $request = new Request('POST', 'http://s3.example.com/bucket?delete', [], $testBody); $resultRequest = $this->applyContentMd5Middleware($request); $md5Header = $resultRequest->getHeaderLine('Content-MD5'); // Verify it's a valid base64 string $this->assertNotEmpty($md5Header); $this->assertEquals($md5Header, base64_encode(base64_decode($md5Header, true))); // Verify MD5 is typically 24 chars when base64-encoded (16 bytes) $this->assertEquals(24, strlen($md5Header)); } /** * Test edge case: Empty body in DeleteObjects request */ public function testMd5CalculationWithEmptyBody(): void { $request = new Request('POST', 'http://s3.example.com/bucket?delete', [], ''); $resultRequest = $this->applyContentMd5Middleware($request); // MD5 of empty string should still produce a valid header $this->assertTrue($resultRequest->hasHeader('Content-MD5')); $this->assertNotEmpty($resultRequest->getHeaderLine('Content-MD5')); } /** * Test that middleware is idempotent (doesn't double-hash) */ public function testMiddlewareIsIdempotent(): void { $testBody = 'test data'; $request = new Request('POST', 'http://s3.example.com/bucket?delete', [], $testBody); // Apply middleware twice $resultRequest1 = $this->applyContentMd5Middleware($request); $resultRequest2 = $this->applyContentMd5Middleware($resultRequest1); // Headers should be identical $this->assertEquals( $resultRequest1->getHeaderLine('Content-MD5'), $resultRequest2->getHeaderLine('Content-MD5'), 'Middleware should be idempotent' ); } }