PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.6.6
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.6.6
1.6.6 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 All 49 releases
← All changes | app/Services/FileSystem/Drivers/S3/S3Driver.php +79 -9 1.3.20 → 1.6.6 View file →
@@ -24,9 +24,9 @@
24 24 $getSettings = (new S3Settings())->get();
25 25
26 26 $this->secretKey = Arr::get($getSettings, 'secret_key', '');
27 27 $this->accessKey = Arr::get($getSettings, 'access_key', '');
28 - $this->bucket = Arr::get($getSettings, 'bucket', '');
28 + $this->bucket = S3Settings::resolveEffectiveBucket($getSettings);
29 29 $this->region = Arr::get($getSettings, 'region', '');
30 30 $this->storageDriver = new S3StorageDriver();
31 31 }
32 32
@@ -45,9 +45,9 @@
45 45
46 46 $response = S3FileUploader::upload(
47 47 $this->secretKey,
48 48 $this->accessKey,
49 - Arr::get($params, 'bucket'),
49 + $this->bucket,
50 50 $this->region,
51 51 $localFilePath,
52 52 $uploadToFilePath
53 53 );
@@ -60,13 +60,12 @@
60 60 'message' => __('File Uploaded Successfully', 'fluent-cart'),
61 61 'path' => $response['path'],
62 62 'file' => [
63 63 'driver' => 's3',
64 - 'size' =>$fileSize,
65 - 'bucket' => Arr::get($params, 'bucket'),
64 + 'size' => $fileSize,
65 + 'bucket' => $this->bucket,
66 66 'name' => $response['path'],
67 67 ],
68 -
69 68 ];
70 69 }
71 70
72 71
@@ -74,9 +73,9 @@
74 73 {
75 74 return S3FileList::get(
76 75 $this->secretKey,
77 76 $this->accessKey,
78 - Arr::get($params, 'activeBucket'),
77 + $this->bucket,
79 78 $this->region,
80 79 Arr::get($params, 'search', ''),
81 80 );
82 81 }
@@ -144,12 +143,14 @@
144 143 $credentialScope = "{$dateStamp}/{$this->region}/s3/aws4_request";
145 144
146 145 $hasDot = strpos($this->bucket, '.') !== false;
147 146
148 - // Canonical request components
147 + // Canonical request components. Deliberately does not ltrim() leading
148 + // slashes: "foo", "/foo", and "//foo" are distinct S3 keys, and
149 + // stripping the slash would redirect the customer to the wrong object.
149 150 $encodedFilePath = '/' . implode('/', array_map(
150 151 'rawurlencode',
151 - explode('/', ltrim($filePath, '/'))
152 + explode('/', $filePath)
152 153 ));
153 154
154 155 // For dotted buckets, use path-style: include bucket in canonical URI
155 156 if ($hasDot) {
@@ -168,9 +169,9 @@
168 169 'X-Amz-SignedHeaders' => 'host',
169 170 ];
170 171
171 172 if (!empty($fileName)) {
172 - $canonicalQueryString['response-content-disposition'] = 'attachment; filename="' . $fileName . '"';
173 + $canonicalQueryString['response-content-disposition'] = $this->buildContentDispositionValue($fileName);
173 174 }
174 175
175 176 // Sort query parameters
176 177 ksort($canonicalQueryString);
@@ -202,8 +203,77 @@
202 203 $url = "https://{$host}{$canonicalUri}?{$canonicalQueryStringStr}&X-Amz-Signature={$signature}";
203 204
204 205 return $url;
205 206 }
207 +
208 + /**
209 + * Builds an RFC 6266/RFC 5987-compliant Content-Disposition value.
210 + * HTTP header values must be ISO-8859-1, so a raw Unicode filename (e.g.
211 + * Bengali) in a plain filename="..." parameter makes S3 reject the
212 + * request with "Header value cannot be represented using ISO-8859-1."
213 + * This supplies both an ASCII-safe filename="..." fallback for clients
214 + * that don't understand filename*, and a filename*=UTF-8''... parameter
215 + * carrying the exact intended Unicode name that modern browsers use.
216 + *
217 + * The returned raw value is assigned into $canonicalQueryString as-is;
218 + * the existing per-parameter rawurlencode() loop further down encodes
219 + * it exactly once for the query string, so this method must not
220 + * pre-encode the value itself (only the filename* attr-value, which is
221 + * a distinct RFC 5987 encoding layer, not query-string encoding).
222 + */
223 + private function buildContentDispositionValue(string $fileName): string
224 + {
225 + // Strip control/CR-LF characters so neither the raw header value nor
226 + // the filename* payload can inject extra header content.
227 + $sanitized = preg_replace('/[\x00-\x1F\x7F]/', '', $fileName);
228 + if ($sanitized === null) {
229 + $sanitized = $fileName;
230 + }
231 +
232 + $asciiFileName = $this->buildAsciiFallbackFileName($sanitized);
233 + // RFC 6266 quoted-string: backslash-escape backslashes and quotes.
234 + $asciiFileName = addcslashes($asciiFileName, '\\"');
235 +
236 + $encodedFileName = rawurlencode($sanitized);
237 +
238 + return 'attachment; filename="' . $asciiFileName . '"; filename*=UTF-8\'\'' . $encodedFileName;
239 + }
240 +
241 + private function buildAsciiFallbackFileName(string $fileName): string
242 + {
243 + $extension = preg_replace('/[^\x20-\x7E]/', '', pathinfo($fileName, PATHINFO_EXTENSION));
244 +
245 + // Empty or dot-only names ("", ".", "..", "...") carry no meaningful
246 + // identity to preserve; keep the generic fallback for them.
247 + if (preg_match('/^\.*$/', $fileName)) {
248 + return $extension !== '' ? "download.{$extension}" : 'download';
249 + }
250 +
251 + // Already fully ASCII: preserve it exactly, including leading dots,
252 + // underscores, hyphens, and extension punctuation (e.g. ".env",
253 + // "_report_.txt", "report.a-b") - nothing here needs to change for
254 + // a client that ignores filename*.
255 + if (preg_replace('/[^\x20-\x7E]/', '', $fileName) === $fileName) {
256 + return $fileName;
257 + }
258 +
259 + // Otherwise the basename has non-ASCII content to strip. Anything
260 + // dropped here (Bengali, Japanese, Arabic, emoji…) is fully
261 + // preserved via filename*; legacy clients ignoring filename* never
262 + // see this fallback. Trim the separator characters (dots, dashes,
263 + // underscores, spaces) that content leaves dangling at the edges
264 + // once it's gone.
265 + $basename = pathinfo($fileName, PATHINFO_FILENAME);
266 + $basename = preg_replace('/[^\x20-\x7E]/', '', $basename);
267 + $basename = trim($basename, " \t\n\r\0\x0B-_.");
268 +
269 + if ($basename === '') {
270 + return $extension !== '' ? "download.{$extension}" : 'download';
271 + }
272 +
273 + return $extension !== '' ? "{$basename}.{$extension}" : $basename;
274 + }
275 +
206 276 protected function retrieveFileForDownload(string $downloadableFilePath, $bucket = null)
207 277 {
208 278 $this->bucket = $bucket;
209 279 $this->region = S3::getBucketRegion($this->bucket);