PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.4.1
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.4.1
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
fluent-cart / app / Services / FileSystem / Drivers / S3 / S3FileUploader.php

S3FileUploader.php in FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler 1.4.1, at app/Services/FileSystem/Drivers/S3/S3FileUploader.php

203 lines 6.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentCart\App\Services\FileSystem\Drivers\S3;
4
5 use Exception;
6 use FluentCart\App\Modules\StorageDrivers\S3\S3;
7 use FluentCart\Framework\Support\Str;
8 use WP_Error;
9
10 class S3FileUploader
11 {
12 private string $accessKey;
13 private string $secretKey;
14 private string $bucket;
15 private string $region;
16 private string $hashAlgorithm = 'sha256';
17 private string $httpMethod;
18 private string $localFilePath;
19 private string $s3FilePath;
20 private string $signature;
21 private string $requestUrl;
22 private string $timeStamp;
23 private string $date;
24
25 /**
26 * @throws Exception
27 */
28 public function __construct(string $secret, string $accessKey, string $bucket, string $region, string $localFilePath, string $s3FilePath)
29 {
30 $this->accessKey = $accessKey;
31 $this->secretKey = $secret;
32 $this->bucket = $bucket;
33 $this->region = S3::getBucketRegion($bucket);
34 $this->localFilePath = $localFilePath;
35 $this->s3FilePath = $s3FilePath;
36 $this->httpMethod = "PUT";
37
38 $this->timeStamp = gmdate('Ymd\THis\Z');
39 $this->date = substr($this->timeStamp, 0, 8);
40
41 // �
42 Correct Regional Endpoint
43 // $this->requestUrl = "https://{$this->bucket}.s3.{$this->region}.amazonaws.com/{$this->s3FilePath}";
44
45 $hasDot = strpos($this->bucket, '.') !== false;
46
47 if ($hasDot) {
48 // Path-style URL
49 $this->requestUrl = "https://s3.{$this->region}.amazonaws.com/{$this->bucket}/{$this->s3FilePath}";
50 } else {
51 // Virtual-hosted style
52 $this->requestUrl = "https://{$this->bucket}.s3.{$this->region}.amazonaws.com/{$this->s3FilePath}";
53 }
54 $this->signature = $this->generateSignature();
55 }
56
57 /**
58 * @throws Exception
59 */
60 public static function upload(string $secret, string $accessKey, string $bucket, string $region, string $localFilePath, string $s3FilePath)
61 {
62 return (new static($secret, $accessKey, $bucket, $region, $localFilePath, $s3FilePath))->uploadFile();
63 }
64
65 /**
66 * @throws Exception
67 */
68 public function uploadFile()
69 {
70 add_filter('http_request_timeout', fn() => 30);
71
72 $args = [
73 'method' => 'PUT',
74 'headers' => $this->getHeaders(),
75 'body' => file_get_contents($this->localFilePath),
76 ];
77
78 $response = wp_remote_request($this->requestUrl, $args);
79 $responseCode = wp_remote_retrieve_response_code($response);
80
81 if ($responseCode === 200) {
82 return [
83 'message' => __('File Uploaded Successfully', 'fluent-cart'),
84 'driver' => 's3',
85 'path' => $this->s3FilePath
86 ];
87 }
88
89 return new WP_Error($responseCode, __('Failed To Upload File', 'fluent-cart'));
90 }
91
92 public function getSignature(): string
93 {
94 return $this->signature;
95 }
96
97 /**
98 * @throws Exception
99 */
100 public function generateSignature()
101 {
102 return hash_hmac(
103 $this->hashAlgorithm,
104 $this->createStringToSign(),
105 $this->getSigningKey()
106 );
107 }
108
109 private function createScope(): string
110 {
111 return "{$this->date}/{$this->region}/s3/aws4_request";
112 }
113
114 /**
115 * @throws Exception
116 */
117 private function getContentHash(): string
118 {
119 if (!file_exists($this->localFilePath)) {
120 throw new \Exception(esc_html__('File not found', 'fluent-cart'));
121 }
122 return hash($this->hashAlgorithm, file_get_contents($this->localFilePath));
123 }
124
125 /**
126 * @throws Exception
127 */
128 private function createCanonicalUrl(): string
129 {
130 // Ensure file path begins with /
131 $s3FilePath = Str::startsWith($this->s3FilePath, '/')
132 ? $this->s3FilePath
133 : "/{$this->s3FilePath}";
134
135 $contentHash = $this->getContentHash();
136
137 // If bucket has dot, use path-style URL in canonical request
138 if (strpos($this->bucket, '.') !== false) {
139 $canonicalUri = "/{$this->bucket}{$s3FilePath}";
140 } else {
141 $canonicalUri = $s3FilePath;
142 }
143
144 return "{$this->httpMethod}\n"
145 . "{$canonicalUri}\n\n"
146 . "host:{$this->getUploadHost()}\n"
147 . "x-amz-content-sha256:{$contentHash}\n"
148 . "x-amz-date:{$this->timeStamp}\n\n"
149 . "host;x-amz-content-sha256;x-amz-date\n"
150 . "{$contentHash}";
151 }
152 private function getUploadHostOld(): string
153 {
154 // �
155 Regional host
156 return "{$this->bucket}.s3.{$this->region}.amazonaws.com";
157 }
158
159 private function getUploadHost(): string
160 {
161 if ($this->bucket === '') {
162 return "s3.{$this->region}.amazonaws.com";
163 }
164
165 // If bucket contains dot, use path-style host
166 if (strpos($this->bucket, '.') !== false) {
167 return "s3.{$this->region}.amazonaws.com";
168 }
169
170 return "{$this->bucket}.s3.{$this->region}.amazonaws.com";
171 }
172
173 /**
174 * @throws Exception
175 */
176 private function createStringToSign(): string
177 {
178 $hash = hash($this->hashAlgorithm, $this->createCanonicalUrl());
179 return "AWS4-HMAC-SHA256\n{$this->timeStamp}\n{$this->createScope()}\n{$hash}";
180 }
181
182 private function getSigningKey()
183 {
184 $dateKey = hash_hmac($this->hashAlgorithm, $this->date, "AWS4{$this->secretKey}", true);
185 $regionKey = hash_hmac($this->hashAlgorithm, $this->region, $dateKey, true);
186 $serviceKey = hash_hmac($this->hashAlgorithm, 's3', $regionKey, true);
187
188 return hash_hmac($this->hashAlgorithm, 'aws4_request', $serviceKey, true);
189 }
190
191 /**
192 * @throws Exception
193 */
194 public function getHeaders(): array
195 {
196 return [
197 'x-amz-content-sha256' => $this->getContentHash(),
198 'x-amz-date' => $this->timeStamp,
199 'Authorization' => "AWS4-HMAC-SHA256 Credential={$this->accessKey}/{$this->date}/{$this->region}/s3/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature={$this->getSignature()}"
200 ];
201 }
202 }
203