Test Failed
Push — master ( d99c6b...fb4ca3 )
by Stiofan
15:44
created

Google_Http_MediaFileUpload   A

Complexity

Total Complexity 33

Size/Duplication

Total Lines 282
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Importance

Changes 0
Metric Value
dl 0
loc 282
rs 9.3999
c 0
b 0
f 0
wmc 33
lcom 1
cbo 7

10 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 25 2
A setFileSize() 0 4 1
A getProgress() 0 4 1
A getHttpResultCode() 0 4 1
B nextChunk() 0 53 6
D process() 0 44 10
A transformToUploadUrl() 0 5 1
A getUploadType() 0 12 4
B getResumeUri() 0 36 6
A setChunkSize() 0 4 1
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
if (!class_exists('Google_Client')) {
19
  require_once dirname(__FILE__) . '/../autoload.php';
20
}
21
22
/**
23
 * Manage large file uploads, which may be media but can be any type
24
 * of sizable data.
25
 */
26
class Google_Http_MediaFileUpload
27
{
28
  const UPLOAD_MEDIA_TYPE = 'media';
29
  const UPLOAD_MULTIPART_TYPE = 'multipart';
30
  const UPLOAD_RESUMABLE_TYPE = 'resumable';
31
32
  /** @var string $mimeType */
33
  private $mimeType;
34
35
  /** @var string $data */
36
  private $data;
37
38
  /** @var bool $resumable */
39
  private $resumable;
40
41
  /** @var int $chunkSize */
42
  private $chunkSize;
43
44
  /** @var int $size */
45
  private $size;
46
47
  /** @var string $resumeUri */
48
  private $resumeUri;
49
50
  /** @var int $progress */
51
  private $progress;
52
53
  /** @var Google_Client */
54
  private $client;
55
56
  /** @var Google_Http_Request */
57
  private $request;
58
59
  /** @var string */
60
  private $boundary;
61
62
  /**
63
   * Result code from last HTTP call
64
   * @var int
65
   */
66
  private $httpResultCode;
67
68
  /**
69
   * @param $mimeType string
70
   * @param $data string The bytes you want to upload.
71
   * @param $resumable bool
72
   * @param bool $chunkSize File will be uploaded in chunks of this many bytes.
73
   * only used if resumable=True
74
   */
75
  public function __construct(
76
      Google_Client $client,
77
      Google_Http_Request $request,
78
      $mimeType,
79
      $data,
80
      $resumable = false,
81
      $chunkSize = false,
82
      $boundary = false
83
  ) {
84
    $this->client = $client;
85
    $this->request = $request;
86
    $this->mimeType = $mimeType;
87
    $this->data = $data;
88
    $this->size = strlen($this->data);
89
    $this->resumable = $resumable;
90
    if (!$chunkSize) {
91
      $chunkSize = 256 * 1024;
92
    }
93
    $this->chunkSize = $chunkSize;
0 ignored issues
show
Documentation Bug introduced by
It seems like $chunkSize can also be of type boolean. However, the property $chunkSize is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
94
    $this->progress = 0;
95
    $this->boundary = $boundary;
0 ignored issues
show
Documentation Bug introduced by
The property $boundary was declared of type string, but $boundary is of type boolean. Maybe add a type cast?

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.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
96
97
    // Process Media Request
98
    $this->process();
99
  }
100
101
  /**
102
   * Set the size of the file that is being uploaded.
103
   * @param $size - int file size in bytes
104
   */
105
  public function setFileSize($size)
106
  {
107
    $this->size = $size;
108
  }
109
110
  /**
111
   * Return the progress on the upload
112
   * @return int progress in bytes uploaded.
113
   */
114
  public function getProgress()
115
  {
116
    return $this->progress;
117
  }
118
119
  /**
120
   * Return the HTTP result code from the last call made.
121
   * @return int code
122
   */
123
  public function getHttpResultCode()
124
  {
125
    return $this->httpResultCode;
126
  }
127
128
  /**
129
   * Send the next part of the file to upload.
130
   * @param [$chunk] the next set of bytes to send. If false will used $data passed
131
   * at construct time.
132
   */
133
  public function nextChunk($chunk = false)
134
  {
135
    if (false == $this->resumeUri) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $this->resumeUri of type string to the boolean false. If you are specifically checking for an empty string, consider using the more explicit === '' instead.
Loading history...
136
      $this->resumeUri = $this->getResumeUri();
0 ignored issues
show
Documentation Bug introduced by
The property $resumeUri was declared of type string, but $this->getResumeUri() is of type boolean. Maybe add a type cast?

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.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
137
    }
138
139
    if (false == $chunk) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
140
      $chunk = substr($this->data, $this->progress, $this->chunkSize);
141
    }
142
143
    $lastBytePos = $this->progress + strlen($chunk) - 1;
144
    $headers = array(
145
      'content-range' => "bytes $this->progress-$lastBytePos/$this->size",
146
      'content-type' => $this->request->getRequestHeader('content-type'),
147
      'content-length' => $this->chunkSize,
148
      'expect' => '',
149
    );
150
151
    $httpRequest = new Google_Http_Request(
152
        $this->resumeUri,
153
        'PUT',
154
        $headers,
155
        $chunk
156
    );
157
158
    if ($this->client->getClassConfig("Google_Http_Request", "enable_gzip_for_uploads")) {
159
      $httpRequest->enableGzip();
160
    } else {
161
      $httpRequest->disableGzip();
162
    }
163
164
    $response = $this->client->getIo()->makeRequest($httpRequest);
165
    $response->setExpectedClass($this->request->getExpectedClass());
166
    $code = $response->getResponseHttpCode();
167
    $this->httpResultCode = $code;
168
169
    if (308 == $code) {
170
      // Track the amount uploaded.
171
      $range = explode('-', $response->getResponseHeader('range'));
172
      $this->progress = $range[1] + 1;
173
174
      // Allow for changing upload URLs.
175
      $location = $response->getResponseHeader('location');
176
      if ($location) {
177
        $this->resumeUri = $location;
0 ignored issues
show
Documentation Bug introduced by
It seems like $location of type array or boolean is incompatible with the declared type string of property $resumeUri.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
178
      }
179
180
      // No problems, but upload not complete.
181
      return false;
182
    } else {
183
      return Google_Http_REST::decodeHttpResponse($response, $this->client);
184
    }
185
  }
186
187
  /**
188
   * @param $meta
189
   * @param $params
190
   * @return array|bool
191
   * @visible for testing
192
   */
193
  private function process()
194
  {
195
    $postBody = false;
196
    $contentType = false;
197
198
    $meta = $this->request->getPostBody();
199
    $meta = is_string($meta) ? json_decode($meta, true) : $meta;
200
201
    $uploadType = $this->getUploadType($meta);
202
    $this->request->setQueryParam('uploadType', $uploadType);
203
    $this->transformToUploadUrl();
204
    $mimeType = $this->mimeType ?
205
        $this->mimeType :
206
        $this->request->getRequestHeader('content-type');
207
208
    if (self::UPLOAD_RESUMABLE_TYPE == $uploadType) {
209
      $contentType = $mimeType;
210
      $postBody = is_string($meta) ? $meta : json_encode($meta);
211
    } else if (self::UPLOAD_MEDIA_TYPE == $uploadType) {
212
      $contentType = $mimeType;
213
      $postBody = $this->data;
214
    } else if (self::UPLOAD_MULTIPART_TYPE == $uploadType) {
215
      // This is a multipart/related upload.
216
      $boundary = $this->boundary ? $this->boundary : mt_rand();
217
      $boundary = str_replace('"', '', $boundary);
218
      $contentType = 'multipart/related; boundary=' . $boundary;
219
      $related = "--$boundary\r\n";
220
      $related .= "Content-Type: application/json; charset=UTF-8\r\n";
221
      $related .= "\r\n" . json_encode($meta) . "\r\n";
222
      $related .= "--$boundary\r\n";
223
      $related .= "Content-Type: $mimeType\r\n";
224
      $related .= "Content-Transfer-Encoding: base64\r\n";
225
      $related .= "\r\n" . base64_encode($this->data) . "\r\n";
226
      $related .= "--$boundary--";
227
      $postBody = $related;
228
    }
229
230
    $this->request->setPostBody($postBody);
0 ignored issues
show
Security Bug introduced by
It seems like $postBody defined by false on line 195 can also be of type false; however, Google_Http_Request::setPostBody() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
231
232
    if (isset($contentType) && $contentType) {
233
      $contentTypeHeader['content-type'] = $contentType;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$contentTypeHeader was never initialized. Although not strictly required by PHP, it is generally a good practice to add $contentTypeHeader = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
234
      $this->request->setRequestHeaders($contentTypeHeader);
235
    }
236
  }
237
238
  private function transformToUploadUrl()
239
  {
240
    $base = $this->request->getBaseComponent();
241
    $this->request->setBaseComponent($base . '/upload');
242
  }
243
244
  /**
245
   * Valid upload types:
246
   * - resumable (UPLOAD_RESUMABLE_TYPE)
247
   * - media (UPLOAD_MEDIA_TYPE)
248
   * - multipart (UPLOAD_MULTIPART_TYPE)
249
   * @param $meta
250
   * @return string
251
   * @visible for testing
252
   */
253
  public function getUploadType($meta)
254
  {
255
    if ($this->resumable) {
256
      return self::UPLOAD_RESUMABLE_TYPE;
257
    }
258
259
    if (false == $meta && $this->data) {
260
      return self::UPLOAD_MEDIA_TYPE;
261
    }
262
263
    return self::UPLOAD_MULTIPART_TYPE;
264
  }
265
266
  private function getResumeUri()
267
  {
268
    $result = null;
0 ignored issues
show
Unused Code introduced by
$result is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
269
    $body = $this->request->getPostBody();
270
    if ($body) {
271
      $headers = array(
272
        'content-type' => 'application/json; charset=UTF-8',
273
        'content-length' => Google_Utils::getStrLen($body),
274
        'x-upload-content-type' => $this->mimeType,
275
        'x-upload-content-length' => $this->size,
276
        'expect' => '',
277
      );
278
      $this->request->setRequestHeaders($headers);
279
    }
280
281
    $response = $this->client->getIo()->makeRequest($this->request);
282
    $location = $response->getResponseHeader('location');
283
    $code = $response->getResponseHttpCode();
284
285
    if (200 == $code && true == $location) {
286
      return $location;
287
    }
288
    $message = $code;
289
    $body = @json_decode($response->getResponseBody());
290
    if (!empty($body->error->errors) ) {
291
      $message .= ': ';
292
      foreach ($body->error->errors as $error) {
293
        $message .= "{$error->domain}, {$error->message};";
294
      }
295
      $message = rtrim($message, ';');
296
    }
297
298
    $error = "Failed to start the resumable upload (HTTP {$message})";
299
    $this->client->getLogger()->error($error);
300
    throw new Google_Exception($error);
301
  }
302
303
  public function setChunkSize($chunkSize)
304
  {
305
    $this->chunkSize = $chunkSize;
306
  }
307
}
308