Issues (61)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

lib/Mongo/MongoGridFS.php (6 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 */
15
16
if (class_exists('MongoGridFS', false)) {
17
    return;
18
}
19
20
class MongoGridFS extends MongoCollection
21
{
22
    const ASCENDING = 1;
23
    const DESCENDING = -1;
24
25
    /**
26
     * @link http://php.net/manual/en/class.mongogridfs.php#mongogridfs.props.chunks
27
     * @var $chunks MongoCollection
28
     */
29
    public $chunks;
30
31
    /**
32
     * @link http://php.net/manual/en/class.mongogridfs.php#mongogridfs.props.filesname
33
     * @var $filesName string
34
     */
35
    protected $filesName;
36
37
    /**
38
     * @link http://php.net/manual/en/class.mongogridfs.php#mongogridfs.props.chunksname
39
     * @var $chunksName string
40
     */
41
    protected $chunksName;
42
43
    /**
44
     * @var MongoDB
45
     */
46
    private $database;
47
48
    private $prefix;
49
50
    private $defaultChunkSize = 261120;
51
52
    /**
53
     * Files as stored across two collections, the first containing file meta
54
     * information, the second containing chunks of the actual file. By default,
55
     * fs.files and fs.chunks are the collection names used.
56
     *
57
     * @link http://php.net/manual/en/mongogridfs.construct.php
58
     * @param MongoDB $db Database
59
     * @param string $prefix [optional] <p>Optional collection name prefix.</p>
60
     * @param mixed $chunks  [optional]
61
     * @throws \Exception
62
     */
63
    public function __construct(MongoDB $db, $prefix = "fs", $chunks = null)
64
    {
65
        if ($chunks) {
66
            trigger_error("The 'chunks' argument is deprecated and ignored", E_USER_DEPRECATED);
67
        }
68
        if (empty($prefix)) {
69
            throw new \Exception('MongoGridFS::__construct(): invalid prefix');
70
        }
71
72
        $this->database = $db;
73
        $this->prefix = (string) $prefix;
74
        $this->filesName = $prefix . '.files';
75
        $this->chunksName = $prefix . '.chunks';
76
77
        $this->chunks = $db->selectCollection($this->chunksName);
78
79
        parent::__construct($db, $this->filesName);
80
    }
81
82
    /**
83
     * Delete a file from the database
84
     *
85
     * @link http://php.net/manual/en/mongogridfs.delete.php
86
     * @param mixed $id _id of the file to remove
87
     * @return boolean Returns true if the remove was successfully sent to the database.
88
     */
89
    public function delete($id)
90
    {
91
        $this->createChunksIndex();
92
93
        $this->chunks->remove(['files_id' => $id], ['justOne' => false]);
94
        return parent::remove(['_id' => $id]);
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (remove() instead of delete()). Are you sure this is correct? If so, you might want to change this to $this->remove().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
95
    }
96
97
    /**
98
     * Drops the files and chunks collections
99
     * @link http://php.net/manual/en/mongogridfs.drop.php
100
     * @return array The database response
101
     */
102
    public function drop()
103
    {
104
        $this->chunks->drop();
105
        return parent::drop();
106
    }
107
108
    /**
109
     * @link http://php.net/manual/en/mongogridfs.find.php
110
     * @param array $query The query
111
     * @param array $fields Fields to return
112
     * @param array $options Options for the find command
0 ignored issues
show
There is no parameter named $options. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
113
     * @return MongoGridFSCursor A MongoGridFSCursor
114
     */
115 View Code Duplication
    public function find(array $query = [], array $fields = [])
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
116
    {
117
        $cursor = new MongoGridFSCursor($this, $this->db->getConnection(), (string) $this, $query, $fields);
118
        $cursor->setReadPreference($this->getReadPreference());
0 ignored issues
show
$this->getReadPreference() is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
119
120
        return $cursor;
121
    }
122
123
    /**
124
     * Returns a single file matching the criteria
125
     *
126
     * @link http://www.php.net/manual/en/mongogridfs.findone.php
127
     * @param mixed $query The fields for which to search or a filename to search for.
128
     * @param array $fields Fields of the results to return.
129
     * @param array $options Options for the find command
130
     * @return MongoGridFSFile|null
131
     */
132
    public function findOne($query = [], array $fields = [], array $options = [])
133
    {
134
        if (! is_array($query)) {
135
            $query = ['filename' => (string) $query];
136
        }
137
138
        $items = iterator_to_array($this->find($query, $fields)->limit(1));
139
        return count($items) ? current($items) : null;
140
    }
141
142
    /**
143
     * Retrieve a file from the database
144
     *
145
     * @link http://www.php.net/manual/en/mongogridfs.get.php
146
     * @param mixed $id _id of the file to find.
147
     * @return MongoGridFSFile|null
148
     */
149
    public function get($id)
150
    {
151
        return $this->findOne(['_id' => $id]);
152
    }
153
154
    /**
155
     * Stores a file in the database
156
     *
157
     * @link http://php.net/manual/en/mongogridfs.put.php
158
     * @param string $filename The name of the file
159
     * @param array $extra Other metadata to add to the file saved
160
     * @param array $options An array of options for the insert operations executed against the chunks and files collections.
161
     * @return mixed Returns the _id of the saved object
162
     */
163
    public function put($filename, array $extra = [], array $options = [])
164
    {
165
        return $this->storeFile($filename, $extra, $options);
166
    }
167
168
    /**
169
     * Removes files from the collections
170
     *
171
     * @link http://www.php.net/manual/en/mongogridfs.remove.php
172
     * @param array $criteria Description of records to remove.
173
     * @param array $options Options for remove.
174
     * @throws MongoCursorException
175
     * @return boolean
176
     */
177
    public function remove(array $criteria = [], array $options = [])
178
    {
179
        $this->createChunksIndex();
180
181
        $matchingFiles = parent::find($criteria, ['_id' => 1]);
182
        $ids = [];
183
        foreach ($matchingFiles as $file) {
184
            $ids[] = $file['_id'];
185
        }
186
        $this->chunks->remove(['files_id' => ['$in' => $ids]], ['justOne' => false] + $options);
187
        return parent::remove(['_id' => ['$in' => $ids]], ['justOne' => false] + $options);
188
    }
189
190
    /**
191
     * Chunkifies and stores bytes in the database
192
     * @link http://php.net/manual/en/mongogridfs.storebytes.php
193
     * @param string $bytes A string of bytes to store
194
     * @param array $extra Other metadata to add to the file saved
195
     * @param array $options Options for the store. "safe": Check that this store succeeded
196
     * @return mixed The _id of the object saved
197
     */
198
    public function storeBytes($bytes, array $extra = [], array $options = [])
199
    {
200
        $this->createChunksIndex();
201
202
        $record = $extra + [
203
            'length' => mb_strlen($bytes, '8bit'),
204
            'md5' => md5($bytes),
205
        ];
206
207
        try {
208
            $file = $this->insertFile($record, $options);
209
        } catch (MongoException $e) {
210
            throw new MongoGridFSException('Could not store file: ' . $e->getMessage(), $e->getCode(), $e);
211
        }
212
213
        try {
214
            $this->insertChunksFromBytes($bytes, $file);
0 ignored issues
show
It seems like $file defined by $this->insertFile($record, $options) on line 208 can also be of type object; however, MongoGridFS::insertChunksFromBytes() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
215
        } catch (MongoException $e) {
216
            $this->delete($file['_id']);
217
            throw new MongoGridFSException('Could not store file: ' . $e->getMessage(), $e->getCode(), $e);
218
        }
219
220
        return $file['_id'];
221
    }
222
223
    /**
224
     * Stores a file in the database
225
     *
226
     * @link http://php.net/manual/en/mongogridfs.storefile.php
227
     * @param string $filename The name of the file
228
     * @param array $extra Other metadata to add to the file saved
229
     * @param array $options Options for the store. "safe": Check that this store succeeded
230
     * @return mixed Returns the _id of the saved object
231
     * @throws MongoGridFSException
232
     * @throws Exception
233
     */
234
    public function storeFile($filename, array $extra = [], array $options = [])
235
    {
236
        $this->createChunksIndex();
237
238
        $record = $extra;
239
        if (is_string($filename)) {
240
            $record += [
241
                'md5' => md5_file($filename),
242
                'length' => filesize($filename),
243
                'filename' => $filename,
244
            ];
245
246
            $handle = fopen($filename, 'r');
247
            if (! $handle) {
248
                throw new MongoGridFSException('could not open file: ' . $filename);
249
            }
250
        } elseif (! is_resource($filename)) {
251
            throw new \Exception('first argument must be a string or stream resource');
252
        } else {
253
            $handle = $filename;
254
        }
255
256
        $md5 = null;
257
        try {
258
            $file = $this->insertFile($record, $options);
259
        } catch (MongoException $e) {
260
            throw new MongoGridFSException('Could not store file: ' . $e->getMessage(), $e->getCode(), $e);
261
        }
262
263
        try {
264
            $length = $this->insertChunksFromFile($handle, $file, $md5);
0 ignored issues
show
It seems like $file defined by $this->insertFile($record, $options) on line 258 can also be of type object; however, MongoGridFS::insertChunksFromFile() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
265
        } catch (MongoException $e) {
266
            $this->delete($file['_id']);
267
            throw new MongoGridFSException('Could not store file: ' . $e->getMessage(), $e->getCode(), $e);
268
        }
269
270
271
        // Add length and MD5 if they were not present before
272
        $update = [];
273
        if (! isset($record['length'])) {
274
            $update['length'] = $length;
275
        }
276
        if (! isset($record['md5'])) {
277
            try {
278
                $update['md5'] = $md5;
279
            } catch (MongoException $e) {
280
                throw new MongoGridFSException('Could not store file: ' . $e->getMessage(), $e->getCode(), $e);
281
            }
282
        }
283
284
        if (count($update)) {
285
            try {
286
                $result = $this->update(['_id' => $file['_id']], ['$set' => $update]);
287
                if (! $this->isOKResult($result)) {
288
                    throw new MongoGridFSException('Could not store file');
289
                }
290
            } catch (MongoException $e) {
291
                $this->delete($file['_id']);
292
                throw new MongoGridFSException('Could not store file: ' . $e->getMessage(), $e->getCode(), $e);
293
            }
294
        }
295
296
        return $file['_id'];
297
    }
298
299
    /**
300
     * Saves an uploaded file directly from a POST to the database
301
     *
302
     * @link http://www.php.net/manual/en/mongogridfs.storeupload.php
303
     * @param string $name The name attribute of the uploaded file, from <input type="file" name="something"/>.
304
     * @param array $metadata An array of extra fields for the uploaded file.
305
     * @return mixed Returns the _id of the uploaded file.
306
     * @throws MongoGridFSException
307
     */
308
    public function storeUpload($name, array $metadata = [])
309
    {
310
        if (! isset($_FILES[$name]) || $_FILES[$name]['error'] !== UPLOAD_ERR_OK) {
311
            throw new MongoGridFSException("Could not find uploaded file $name");
312
        }
313
        if (! isset($_FILES[$name]['tmp_name'])) {
314
            throw new MongoGridFSException("Couldn't find tmp_name in the \$_FILES array. Are you sure the upload worked?");
315
        }
316
317
        $uploadedFile = $_FILES[$name];
318
        $uploadedFile['tmp_name'] = (array) $uploadedFile['tmp_name'];
319
        $uploadedFile['name'] = (array) $uploadedFile['name'];
320
321
        if (count($uploadedFile['tmp_name']) > 1) {
322
            foreach ($uploadedFile['tmp_name'] as $key => $file) {
323
                $metadata['filename'] = $uploadedFile['name'][$key];
324
                $this->storeFile($file, $metadata);
325
            }
326
327
            return null;
328
        } else {
329
            $metadata += ['filename' => array_pop($uploadedFile['name'])];
330
            return $this->storeFile(array_pop($uploadedFile['tmp_name']), $metadata);
331
        }
332
    }
333
334
    /**
335
     * Creates the index on the chunks collection
336
     */
337
    private function createChunksIndex()
338
    {
339
        try {
340
            $this->chunks->createIndex(['files_id' => 1, 'n' => 1], ['unique' => true]);
341
        } catch (MongoDuplicateKeyException $e) {
342
        }
343
    }
344
345
    /**
346
     * Inserts a single chunk into the database
347
     *
348
     * @param mixed $fileId
349
     * @param string $data
350
     * @param int $chunkNumber
351
     * @return array|bool
352
     */
353
    private function insertChunk($fileId, $data, $chunkNumber)
354
    {
355
        $chunk = [
356
            'files_id' => $fileId,
357
            'n' => $chunkNumber,
358
            'data' => new MongoBinData($data),
359
        ];
360
361
        $result = $this->chunks->insert($chunk);
362
363
        if (! $this->isOKResult($result)) {
364
            throw new \MongoException('error inserting chunk');
365
        }
366
367
        return $result;
368
    }
369
370
    /**
371
     * Splits a string into chunks and writes them to the database
372
     *
373
     * @param string $bytes
374
     * @param array $record
375
     */
376
    private function insertChunksFromBytes($bytes, $record)
377
    {
378
        $chunkSize = $record['chunkSize'];
379
        $fileId = $record['_id'];
380
        $i = 0;
381
382
        $chunks = str_split($bytes, $chunkSize);
383
        foreach ($chunks as $chunk) {
384
            $this->insertChunk($fileId, $chunk, $i++);
385
        }
386
    }
387
388
    /**
389
     * Reads chunks from a file and writes them to the database
390
     *
391
     * @param resource $handle
392
     * @param array $record
393
     * @param string $md5
394
     * @return int Returns the number of bytes written to the database
395
     */
396
    private function insertChunksFromFile($handle, $record, &$md5)
397
    {
398
        $written = 0;
399
        $offset = 0;
400
        $i = 0;
401
402
        $fileId = $record['_id'];
403
        $chunkSize = $record['chunkSize'];
404
405
        $hash = hash_init('md5');
406
407
        rewind($handle);
408
        while (! feof($handle)) {
409
            $data = stream_get_contents($handle, $chunkSize);
410
            hash_update($hash, $data);
411
            $this->insertChunk($fileId, $data, $i++);
412
            $written += strlen($data);
413
            $offset += $chunkSize;
414
        }
415
416
        $md5 = hash_final($hash);
417
418
        return $written;
419
    }
420
421
    /**
422
     * Writes a file record to the database
423
     *
424
     * @param $record
425
     * @param array $options
426
     * @return array
427
     */
428
    private function insertFile($record, array $options = [])
429
    {
430
        $record += [
431
            '_id' => new MongoId(),
432
            'uploadDate' => new MongoDate(),
433
            'chunkSize' => $this->defaultChunkSize,
434
        ];
435
436
        $result = $this->insert($record, $options);
437
438
        if (! $this->isOKResult($result)) {
439
            throw new \MongoException('error inserting file');
440
        }
441
442
        return $record;
443
    }
444
445
    private function isOKResult($result)
446
    {
447
        return (is_array($result) && $result['ok'] == 1.0) ||
448
               (is_bool($result) && $result);
449
    }
450
451
    /**
452
     * @return array
453
     */
454
    public function __sleep()
455
    {
456
        return ['chunks', 'chunksName', 'database', 'defaultChunkSize', 'filesName', 'prefix'] + parent::__sleep();
457
    }
458
}
459