Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions src/Storage/Device/S3.php
Original file line number Diff line number Diff line change
Expand Up @@ -735,18 +735,25 @@ protected function call(string $method, string $uri, StreamInterface|string $dat
$url = $this->fqdn . $uri . '?' . http_build_query($parameters, '', '&', PHP_QUERY_RFC3986);

if ($data instanceof StreamInterface) {
[$md5, $sha256] = $this->hashBody($data);
[$md5, $sha256, $length] = $this->hashBody($data);
$body = $data;
} else {
$md5 = base64_encode(md5($data, true));
$sha256 = hash('sha256', $data);
$length = \strlen($data);
$body = new Stream($data);
}

$headers = array_filter($headers, fn(string $value): bool => $value !== '');
$headers['host'] = $this->host;
$headers['date'] = gmdate('D, d M Y H:i:s T');
$headers['content-md5'] = $md5;
// Send an explicit Content-Length (signed, alongside content-md5). Without
// it the cURL transport streams the body with Transfer-Encoding: chunked —
// or omits the header on an empty POST — which S3-compatible services such
// as GCS reject with HTTP 411. The value is the full body size, so it also
// matches what the transport sends for a size-known stream.
$headers['content-length'] = (string) $length;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Content-Length behavior lacks coverage

The new request behavior is not covered by regression tests. Existing request tests do not assert the observable Content-Length, and multipart preparation bypasses the real request-building path. Please add request-level tests showing that an unknown-size seekable stream sends its full byte count and that an empty multipart-initiation POST sends Content-Length: 0; otherwise either part of the reported failure could return without CI detecting it.

Knowledge Base Used: S3-compatible storage devices

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/Storage/Device/S3.php
Line: 756

Comment:
**Content-Length behavior lacks coverage**

The new request behavior is not covered by regression tests. Existing request tests do not assert the observable `Content-Length`, and multipart preparation bypasses the real request-building path. Please add request-level tests showing that an unknown-size seekable stream sends its full byte count and that an empty multipart-initiation POST sends `Content-Length: 0`; otherwise either part of the reported failure could return without CI detecting it.

**Knowledge Base Used:** [S3-compatible storage devices](https://app.greptile.com/appwrite/-/custom-context/knowledge-base/utopia-php/storage/-/docs/s3-compatible-storage-devices.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code Fix in Codex


$amzHeaders = array_filter($amzHeaders, fn(string $value): bool => $value !== '');
$amzHeaders['x-amz-date'] = gmdate('Ymd\THis\Z');
Expand Down Expand Up @@ -810,7 +817,7 @@ protected function call(string $method, string $uri, StreamInterface|string $dat
* the cURL adapter rewinds seekable bodies before sending, so the
* signature must cover the full stream.
*
* @return array{string, string} Base64 MD5 and hex SHA-256 of the full stream
* @return array{string, string, int} Base64 MD5, hex SHA-256, and byte length of the full stream
*/
private function hashBody(StreamInterface $body): array
{
Expand All @@ -821,17 +828,19 @@ private function hashBody(StreamInterface $body): array
$body->rewind();
$md5 = hash_init('md5');
$sha256 = hash_init('sha256');
$length = 0;
while (! $body->eof()) {
$chunk = $body->read(self::PIPE_CHUNK_SIZE);
if ($chunk === '') {
break;
}
$length += \strlen($chunk);
hash_update($md5, $chunk);
hash_update($sha256, $chunk);
}
$body->rewind();

return [base64_encode(hash_final($md5, true)), hash_final($sha256)];
return [base64_encode(hash_final($md5, true)), hash_final($sha256), $length];
}

/**
Expand Down