PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.6.5
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.6.5
1.6.5 1.6.4 1.6.3 1.6.2 1.6.1 1.6.0 1.5.4 1.5.5 1.5.3 1.5.2 1.5.1 1.5.0 1.4.2 1.4.1 1.4.0 1.3.28 1.3.27 1.3.26 1.3.25 1.3.23 1.3.22 1.3.21 1.3.20 1.3.19 trunk All 48 releases
← All changes | app/Services/FileSystem/Drivers/S3/S3FileUploader.php +21 -6 1.6.2 → 1.6.5 View file →
@@ -3,9 +3,8 @@
3 3 namespace FluentCart\App\Services\FileSystem\Drivers\S3;
4 4
5 5 use Exception;
6 6 use FluentCart\App\Modules\StorageDrivers\S3\S3;
7 -use FluentCart\Framework\Support\Str;
8 7 use WP_Error;
9 8
10 9 class S3FileUploader
11 10 {
@@ -48,15 +47,16 @@
48 47 // ✅ Correct Regional Endpoint
49 48 // $this->requestUrl = "https://{$this->bucket}.s3.{$this->region}.amazonaws.com/{$this->s3FilePath}";
50 49
51 50 $hasDot = strpos($this->bucket, '.') !== false;
51 + $encodedFilePath = $this->encodeS3ObjectKey($this->s3FilePath);
52 52
53 53 if ($hasDot) {
54 54 // Path-style URL
55 - $this->requestUrl = "https://s3.{$this->region}.amazonaws.com/{$this->bucket}/{$this->s3FilePath}";
55 + $this->requestUrl = "https://s3.{$this->region}.amazonaws.com/{$this->bucket}/{$encodedFilePath}";
56 56 } else {
57 57 // Virtual-hosted style
58 - $this->requestUrl = "https://{$this->bucket}.s3.{$this->region}.amazonaws.com/{$this->s3FilePath}";
58 + $this->requestUrl = "https://{$this->bucket}.s3.{$this->region}.amazonaws.com/{$encodedFilePath}";
59 59 }
60 60
61 61 // Opt-in: when enabled the upload is refused instead of replacing an
62 62 // existing object. Resolved before signing because the header is part
@@ -151,16 +151,31 @@
151 151 return hash($this->hashAlgorithm, file_get_contents($this->localFilePath));
152 152 }
153 153
154 154 /**
155 + * Percent-encodes each "/"-separated segment of an S3 object key using
156 + * AWS's UriEncode rules (rawurlencode leaves "/" alone). This keeps the
157 + * canonical signing path and the actual request URL identical to what
158 + * S3 receives on the wire, so multi-byte UTF-8 characters, spaces, and
159 + * reserved characters ("+", "#", "?", literal "%") all round-trip to
160 + * the exact key that was requested instead of a different object.
161 + *
162 + * Deliberately does not ltrim() leading slashes: "foo", "/foo", and
163 + * "//foo" are three distinct S3 keys, and stripping the slash made a
164 + * delete/upload targeting "/foo" silently operate on "foo" instead.
165 + */
166 + private function encodeS3ObjectKey(string $path): string
167 + {
168 + return implode('/', array_map('rawurlencode', explode('/', $path)));
169 + }
170 +
171 + /**
155 172 * @throws Exception
156 173 */
157 174 private function createCanonicalUrl(): string
158 175 {
159 176 // Ensure file path begins with /
160 - $s3FilePath = Str::startsWith($this->s3FilePath, '/')
161 - ? $this->s3FilePath
162 - : "/{$this->s3FilePath}";
177 + $s3FilePath = '/' . $this->encodeS3ObjectKey($this->s3FilePath);
163 178
164 179 $contentHash = $this->getContentHash();
165 180
166 181 // If bucket has dot, use path-style URL in canonical request