1 | <?php |
||||||
2 | /** |
||||||
3 | * Copyright 2012 Google Inc. |
||||||
4 | * |
||||||
5 | * Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
6 | * you may not use this file except in compliance with the License. |
||||||
7 | * You may obtain a copy of the License at |
||||||
8 | * |
||||||
9 | * http://www.apache.org/licenses/LICENSE-2.0 |
||||||
10 | * |
||||||
11 | * Unless required by applicable law or agreed to in writing, software |
||||||
12 | * distributed under the License is distributed on an "AS IS" BASIS, |
||||||
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
14 | * See the License for the specific language governing permissions and |
||||||
15 | * limitations under the License. |
||||||
16 | */ |
||||||
17 | |||||||
18 | use GuzzleHttp\Psr7; |
||||||
19 | use GuzzleHttp\Psr7\Request; |
||||||
20 | use GuzzleHttp\Psr7\Uri; |
||||||
21 | use Psr\Http\Message\RequestInterface; |
||||||
22 | |||||||
23 | /** |
||||||
24 | * Manage large file uploads, which may be media but can be any type |
||||||
25 | * of sizable data. |
||||||
26 | */ |
||||||
27 | class Google_Http_MediaFileUpload |
||||||
28 | { |
||||||
29 | const UPLOAD_MEDIA_TYPE = 'media'; |
||||||
30 | const UPLOAD_MULTIPART_TYPE = 'multipart'; |
||||||
31 | const UPLOAD_RESUMABLE_TYPE = 'resumable'; |
||||||
32 | |||||||
33 | /** @var string $mimeType */ |
||||||
34 | private $mimeType; |
||||||
35 | |||||||
36 | /** @var string $data */ |
||||||
37 | private $data; |
||||||
38 | |||||||
39 | /** @var bool $resumable */ |
||||||
40 | private $resumable; |
||||||
41 | |||||||
42 | /** @var int $chunkSize */ |
||||||
43 | private $chunkSize; |
||||||
44 | |||||||
45 | /** @var int $size */ |
||||||
46 | private $size; |
||||||
47 | |||||||
48 | /** @var string $resumeUri */ |
||||||
49 | private $resumeUri; |
||||||
50 | |||||||
51 | /** @var int $progress */ |
||||||
52 | private $progress; |
||||||
53 | |||||||
54 | /** @var Google_Client */ |
||||||
55 | private $client; |
||||||
56 | |||||||
57 | /** @var Psr\Http\Message\RequestInterface */ |
||||||
58 | private $request; |
||||||
59 | |||||||
60 | /** @var string */ |
||||||
61 | private $boundary; |
||||||
62 | |||||||
63 | /** |
||||||
64 | * Result code from last HTTP call |
||||||
65 | * @var int |
||||||
66 | */ |
||||||
67 | private $httpResultCode; |
||||||
68 | |||||||
69 | /** |
||||||
70 | * @param $mimeType string |
||||||
71 | * @param $data string The bytes you want to upload. |
||||||
72 | * @param $resumable bool |
||||||
73 | * @param bool $chunkSize File will be uploaded in chunks of this many bytes. |
||||||
74 | * only used if resumable=True |
||||||
75 | */ |
||||||
76 | public function __construct( |
||||||
77 | Google_Client $client, |
||||||
78 | RequestInterface $request, |
||||||
79 | $mimeType, |
||||||
80 | $data, |
||||||
81 | $resumable = false, |
||||||
82 | $chunkSize = false |
||||||
83 | ) { |
||||||
84 | $this->client = $client; |
||||||
85 | $this->request = $request; |
||||||
86 | $this->mimeType = $mimeType; |
||||||
87 | $this->data = $data; |
||||||
88 | $this->resumable = $resumable; |
||||||
89 | $this->chunkSize = $chunkSize; |
||||||
0 ignored issues
–
show
|
|||||||
90 | $this->progress = 0; |
||||||
91 | |||||||
92 | $this->process(); |
||||||
93 | } |
||||||
94 | |||||||
95 | /** |
||||||
96 | * Set the size of the file that is being uploaded. |
||||||
97 | * @param $size - int file size in bytes |
||||||
0 ignored issues
–
show
|
|||||||
98 | */ |
||||||
99 | public function setFileSize($size) |
||||||
100 | { |
||||||
101 | $this->size = $size; |
||||||
102 | } |
||||||
103 | |||||||
104 | /** |
||||||
105 | * Return the progress on the upload |
||||||
106 | * @return int progress in bytes uploaded. |
||||||
107 | */ |
||||||
108 | public function getProgress() |
||||||
109 | { |
||||||
110 | return $this->progress; |
||||||
111 | } |
||||||
112 | |||||||
113 | /** |
||||||
114 | * Send the next part of the file to upload. |
||||||
115 | * @param [$chunk] the next set of bytes to send. If false will used $data passed |
||||||
0 ignored issues
–
show
The type
the was not found. Maybe you did not declare it correctly or list all dependencies?
The issue could also be caused by a filter entry in the build configuration.
If the path has been excluded in your configuration, e.g. filter:
dependency_paths: ["lib/*"]
For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths ![]() |
|||||||
116 | * at construct time. |
||||||
117 | */ |
||||||
118 | public function nextChunk($chunk = false) |
||||||
119 | { |
||||||
120 | $resumeUri = $this->getResumeUri(); |
||||||
121 | |||||||
122 | if (false == $chunk) { |
||||||
123 | $chunk = substr($this->data, $this->progress, $this->chunkSize); |
||||||
124 | } |
||||||
125 | |||||||
126 | $lastBytePos = $this->progress + strlen($chunk) - 1; |
||||||
127 | $headers = array( |
||||||
128 | 'content-range' => "bytes $this->progress-$lastBytePos/$this->size", |
||||||
129 | 'content-length' => strlen($chunk), |
||||||
130 | 'expect' => '', |
||||||
131 | ); |
||||||
132 | |||||||
133 | $request = new Request( |
||||||
134 | 'PUT', |
||||||
135 | $resumeUri, |
||||||
136 | $headers, |
||||||
137 | Psr7\stream_for($chunk) |
||||||
0 ignored issues
–
show
The function
stream_for was not found. Maybe you did not declare it correctly or list all dependencies?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
138 | ); |
||||||
139 | |||||||
140 | return $this->makePutRequest($request); |
||||||
141 | } |
||||||
142 | |||||||
143 | /** |
||||||
144 | * Return the HTTP result code from the last call made. |
||||||
145 | * @return int code |
||||||
146 | */ |
||||||
147 | public function getHttpResultCode() |
||||||
148 | { |
||||||
149 | return $this->httpResultCode; |
||||||
150 | } |
||||||
151 | |||||||
152 | /** |
||||||
153 | * Sends a PUT-Request to google drive and parses the response, |
||||||
154 | * setting the appropiate variables from the response() |
||||||
155 | * |
||||||
156 | * @param Google_Http_Request $httpRequest the Reuqest which will be send |
||||||
0 ignored issues
–
show
The type
Google_Http_Request was not found. Maybe you did not declare it correctly or list all dependencies?
The issue could also be caused by a filter entry in the build configuration.
If the path has been excluded in your configuration, e.g. filter:
dependency_paths: ["lib/*"]
For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths ![]() |
|||||||
157 | * |
||||||
158 | * @return false|mixed false when the upload is unfinished or the decoded http response |
||||||
159 | * |
||||||
160 | */ |
||||||
161 | private function makePutRequest(RequestInterface $request) |
||||||
162 | { |
||||||
163 | $response = $this->client->execute($request); |
||||||
164 | $this->httpResultCode = $response->getStatusCode(); |
||||||
165 | |||||||
166 | if (308 == $this->httpResultCode) { |
||||||
167 | // Track the amount uploaded. |
||||||
168 | $range = $response->getHeaderLine('range'); |
||||||
169 | if ($range) { |
||||||
170 | $range_array = explode('-', $range); |
||||||
171 | $this->progress = $range_array[1] + 1; |
||||||
172 | } |
||||||
173 | |||||||
174 | // Allow for changing upload URLs. |
||||||
175 | $location = $response->getHeaderLine('location'); |
||||||
176 | if ($location) { |
||||||
177 | $this->resumeUri = $location; |
||||||
178 | } |
||||||
179 | |||||||
180 | // No problems, but upload not complete. |
||||||
181 | return false; |
||||||
182 | } |
||||||
183 | |||||||
184 | return Google_Http_REST::decodeHttpResponse($response, $this->request); |
||||||
185 | } |
||||||
186 | |||||||
187 | /** |
||||||
188 | * Resume a previously unfinished upload |
||||||
189 | * @param $resumeUri the resume-URI of the unfinished, resumable upload. |
||||||
190 | */ |
||||||
191 | public function resume($resumeUri) |
||||||
192 | { |
||||||
193 | $this->resumeUri = $resumeUri; |
||||||
194 | $headers = array( |
||||||
195 | 'content-range' => "bytes */$this->size", |
||||||
196 | 'content-length' => 0, |
||||||
197 | ); |
||||||
198 | $httpRequest = new Request( |
||||||
199 | 'PUT', |
||||||
200 | $this->resumeUri, |
||||||
201 | $headers |
||||||
202 | ); |
||||||
203 | |||||||
204 | return $this->makePutRequest($httpRequest); |
||||||
205 | } |
||||||
206 | |||||||
207 | /** |
||||||
208 | * @return Psr\Http\Message\RequestInterface $request |
||||||
209 | * @visible for testing |
||||||
210 | */ |
||||||
211 | private function process() |
||||||
212 | { |
||||||
213 | $this->transformToUploadUrl(); |
||||||
214 | $request = $this->request; |
||||||
215 | |||||||
216 | $postBody = ''; |
||||||
217 | $contentType = false; |
||||||
218 | |||||||
219 | $meta = (string) $request->getBody(); |
||||||
220 | $meta = is_string($meta) ? json_decode($meta, true) : $meta; |
||||||
0 ignored issues
–
show
|
|||||||
221 | |||||||
222 | $uploadType = $this->getUploadType($meta); |
||||||
223 | $request = $request->withUri( |
||||||
224 | Uri::withQueryValue($request->getUri(), 'uploadType', $uploadType) |
||||||
225 | ); |
||||||
226 | |||||||
227 | $mimeType = $this->mimeType ?: $request->getHeaderLine('content-type'); |
||||||
228 | |||||||
229 | if (self::UPLOAD_RESUMABLE_TYPE == $uploadType) { |
||||||
230 | $contentType = $mimeType; |
||||||
231 | $postBody = is_string($meta) ? $meta : json_encode($meta); |
||||||
232 | } else if (self::UPLOAD_MEDIA_TYPE == $uploadType) { |
||||||
233 | $contentType = $mimeType; |
||||||
234 | $postBody = $this->data; |
||||||
235 | } else if (self::UPLOAD_MULTIPART_TYPE == $uploadType) { |
||||||
236 | // This is a multipart/related upload. |
||||||
237 | $boundary = $this->boundary ?: mt_rand(); |
||||||
238 | $boundary = str_replace('"', '', $boundary); |
||||||
239 | $contentType = 'multipart/related; boundary=' . $boundary; |
||||||
240 | $related = "--$boundary\r\n"; |
||||||
241 | $related .= "Content-Type: application/json; charset=UTF-8\r\n"; |
||||||
242 | $related .= "\r\n" . json_encode($meta) . "\r\n"; |
||||||
243 | $related .= "--$boundary\r\n"; |
||||||
244 | $related .= "Content-Type: $mimeType\r\n"; |
||||||
245 | $related .= "Content-Transfer-Encoding: base64\r\n"; |
||||||
246 | $related .= "\r\n" . base64_encode($this->data) . "\r\n"; |
||||||
247 | $related .= "--$boundary--"; |
||||||
248 | $postBody = $related; |
||||||
249 | } |
||||||
250 | |||||||
251 | $request = $request->withBody(Psr7\stream_for($postBody)); |
||||||
0 ignored issues
–
show
The function
stream_for was not found. Maybe you did not declare it correctly or list all dependencies?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
252 | |||||||
253 | if (isset($contentType) && $contentType) { |
||||||
254 | $request = $request->withHeader('content-type', $contentType); |
||||||
255 | } |
||||||
256 | |||||||
257 | return $this->request = $request; |
||||||
258 | } |
||||||
259 | |||||||
260 | /** |
||||||
261 | * Valid upload types: |
||||||
262 | * - resumable (UPLOAD_RESUMABLE_TYPE) |
||||||
263 | * - media (UPLOAD_MEDIA_TYPE) |
||||||
264 | * - multipart (UPLOAD_MULTIPART_TYPE) |
||||||
265 | * @param $meta |
||||||
266 | * @return string |
||||||
267 | * @visible for testing |
||||||
268 | */ |
||||||
269 | public function getUploadType($meta) |
||||||
270 | { |
||||||
271 | if ($this->resumable) { |
||||||
272 | return self::UPLOAD_RESUMABLE_TYPE; |
||||||
273 | } |
||||||
274 | |||||||
275 | if (false == $meta && $this->data) { |
||||||
276 | return self::UPLOAD_MEDIA_TYPE; |
||||||
277 | } |
||||||
278 | |||||||
279 | return self::UPLOAD_MULTIPART_TYPE; |
||||||
280 | } |
||||||
281 | |||||||
282 | public function getResumeUri() |
||||||
283 | { |
||||||
284 | if (null === $this->resumeUri) { |
||||||
285 | $this->resumeUri = $this->fetchResumeUri(); |
||||||
286 | } |
||||||
287 | |||||||
288 | return $this->resumeUri; |
||||||
289 | } |
||||||
290 | |||||||
291 | private function fetchResumeUri() |
||||||
292 | { |
||||||
293 | $body = $this->request->getBody(); |
||||||
294 | if ($body) { |
||||||
0 ignored issues
–
show
|
|||||||
295 | $headers = array( |
||||||
296 | 'content-type' => 'application/json; charset=UTF-8', |
||||||
297 | 'content-length' => $body->getSize(), |
||||||
298 | 'x-upload-content-type' => $this->mimeType, |
||||||
299 | 'x-upload-content-length' => $this->size, |
||||||
300 | 'expect' => '', |
||||||
301 | ); |
||||||
302 | foreach ($headers as $key => $value) { |
||||||
303 | $this->request = $this->request->withHeader($key, $value); |
||||||
304 | } |
||||||
305 | } |
||||||
306 | |||||||
307 | $response = $this->client->execute($this->request, false); |
||||||
308 | $location = $response->getHeaderLine('location'); |
||||||
309 | $code = $response->getStatusCode(); |
||||||
310 | |||||||
311 | if (200 == $code && true == $location) { |
||||||
312 | return $location; |
||||||
313 | } |
||||||
314 | |||||||
315 | $message = $code; |
||||||
316 | $body = json_decode((string) $this->request->getBody(), true); |
||||||
317 | if (isset($body['error']['errors'])) { |
||||||
318 | $message .= ': '; |
||||||
319 | foreach ($body['error']['errors'] as $error) { |
||||||
320 | $message .= "{$error['domain']}, {$error['message']};"; |
||||||
321 | } |
||||||
322 | $message = rtrim($message, ';'); |
||||||
323 | } |
||||||
324 | |||||||
325 | $error = "Failed to start the resumable upload (HTTP {$message})"; |
||||||
326 | $this->client->getLogger()->error($error); |
||||||
327 | |||||||
328 | throw new Google_Exception($error); |
||||||
329 | } |
||||||
330 | |||||||
331 | private function transformToUploadUrl() |
||||||
332 | { |
||||||
333 | $parts = parse_url((string) $this->request->getUri()); |
||||||
334 | if (!isset($parts['path'])) { |
||||||
335 | $parts['path'] = ''; |
||||||
336 | } |
||||||
337 | $parts['path'] = '/upload' . $parts['path']; |
||||||
338 | $uri = Uri::fromParts($parts); |
||||||
339 | $this->request = $this->request->withUri($uri); |
||||||
340 | } |
||||||
341 | |||||||
342 | public function setChunkSize($chunkSize) |
||||||
343 | { |
||||||
344 | $this->chunkSize = $chunkSize; |
||||||
345 | } |
||||||
346 | |||||||
347 | public function getRequest() |
||||||
348 | { |
||||||
349 | return $this->request; |
||||||
350 | } |
||||||
351 | } |
||||||
352 |
This check looks for assignments to scalar types that may be of the wrong type.
To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.