Issues (42)

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.

src/Psr/Messages/Stream.php (2 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
namespace Almendra\Http\Psr\Messages;
4
5
use Psr\Http\Message\StreamInterface;
6
use Exception;
7
8
9
/**
10
 * Represent a message stream
11
 *
12
 * @package Almendra\Http
13
 */
14
class Stream implements StreamInterface
15
{
16
17
    /**
18
     * @var stream body / the resource
19
     */
20
    protected $body;
21
22
    /**
23
     * @var The stream uri, if any
24
     */
25
    protected $uri;
26
27
    /**
28
     * @var boolean Is the stream seekable?
29
     */
30
    protected $seekable;
31
32
    /**
33
     * @var boolean Is the stream readable?
34
     */
35
    protected $readable;
36
37
    /**
38
     * @var boolean Is the stream writable?
39
     */
40
    protected $writable;
41
42
    /**
43
     * @var array The stream metadata, if any
44
     */
45
    protected $metaData = [];
46
47
    /**
48
     * @var array The overriding options (size, uri, etc.)
49
     */
50
    protected $options = [];
51
52
    /**
53
     * @var string Default output format
54
     */
55
    protected $defaultFormat; // 'JSON'
56
57
    /**
58
     * @var array The accepted overriding options
59
     */
60
    protected $overridingOptions = [
61
        'size',
62
        'uri',
63
        ];
64
65
    /**
66
     * @var array Hash of readable and writable stream types
67
     */
68
    private static $readWriteHash = [
69
        'read' => [
70
            'r' => true, 'w+' => true, 'r+' => true, 'x+' => true, 'c+' => true,
71
            'rb' => true, 'w+b' => true, 'r+b' => true, 'x+b' => true,
72
            'c+b' => true, 'rt' => true, 'w+t' => true, 'r+t' => true,
73
            'x+t' => true, 'c+t' => true, 'a+' => true
74
        ],
75
        'write' => [
76
            'w' => true, 'w+' => true, 'rw' => true, 'r+' => true, 'x+' => true,
77
            'c+' => true, 'wb' => true, 'w+b' => true, 'r+b' => true,
78
            'x+b' => true, 'c+b' => true, 'w+t' => true, 'r+t' => true,
79
            'x+t' => true, 'c+t' => true, 'a' => true, 'a+' => true
80
        ]
81
    ];
82
83
    /**
84
     * Sets up the resource.
85
     *
86
     * @param resource $stream
87
     * @param array $options
88
     */
89
    public function __construct($stream, $metaData = [], $options = [])
90
    {
91
        if (!is_resource($stream)) {
92
            throw new \InvalidArgumentException("Stream must be a resource.");
93
        }
94
95
        $this -> setBody($stream);
96
        $this -> setMetadata($stream, $metaData);
97
        $this -> setOptions($options);
98
        $this -> setOperations();
99
    }
100
101
    /**
102
     * Sets the stream metadata.
103
     *
104
     * @param resource $stream         The stream
105
     * @param array $userData         The overriding user metadata
106
     * @return void
107
     */
108
    protected function setMetadata($stream, array $userData = [])
109
    {
110
        $meta = stream_get_meta_data($stream);
111
        $this -> metaData = array_merge($meta, $userData);
112
    }
113
114
    /**
115
     * Sets the options such as the size, effectively overriding those from the stream.
116
     *
117
     * @param array $options         The overriding options
118
     * @return void
119
     */
120
    protected function setOptions(array $options)
121
    {
122
        foreach ($this -> overridingOptions as $name) {
123
            if (isset($options[$name])) {
124
                $this -> options[$name] = $options[$name];
125
            }
126
        }
127
    }
128
129
    /**
130
     * Reads all data from the stream into a string, from the beginning to end.
131
     *
132
     * This method MUST attempt to seek to the beginning of the stream before
133
     * reading data and read the stream until the end is reached.
134
     *
135
     * Warning: This could attempt to load a large amount of data into memory.
136
     *
137
     * This method MUST NOT raise an exception in order to conform with PHP's
138
     * string casting operations.
139
     *
140
     * @see http://php.net/manual/en/language.oop5.magic.php#object.tostring
141
     * @return string
142
     */
143
    public function __toString()
144
    {
145
        // protect against exception
146
        try {
147
            $this -> seek(0);
148
149
            $result = (string) stream_get_contents($this -> body);
150
            if ($this -> isJsonable() && ($this -> defaultFormat === 'JSON')) {
151
                $result = json_encode($result, JSON_PRETTY_PRINT);
152
            }
153
        } catch (Exception $e) {
154
            $result = '';
155
        }
156
157
        return $result;
158
    }
159
160
    /**
161
     * Closes the stream and any underlying resources.
162
     *
163
     * @return void
164
     */
165
    public function close()
166
    {
167
        if (!isset($this -> body)) {
168
            return;
169
        }
170
171
        fclose($this -> body);
172
        $this -> detach();
173
    }
174
175
    /**
176
     * Separates any underlying resources from the stream.
177
     *
178
     * After the stream has been detached, the stream is in an unusable state.
179
     *
180
     * @return resource|null Underlying PHP stream, if any
181
     */
182
    public function detach()
183
    {
184
        if (!isset($this -> body)) {
185
            return null;
186
        }
187
188
        $this -> options = [];
189
        $this -> metaData = [];
190
        $this -> unsetOperations();
191
192
        $result = $this -> body;
193
        unset($this -> body);
194
195
        return $result;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $result; (Almendra\Http\Psr\Messages\Stream) is incompatible with the return type declared by the interface Psr\Http\Message\StreamInterface::detach of type resource|null.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

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

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
196
    }
197
198
    /**
199
     * Get the size of the stream if known.
200
     *
201
     * @return int|null Returns the size in bytes if known, or null if unknown.
202
     */
203
    public function getSize()
204
    {
205
        if (!isset($this -> options['size'])) {
206
            try {
207
                $fstat = fstat($this -> body);
208
                if (!isset($fstat['size'])) {
209
                    return null;
210
                }
211
212
                $this -> options['size'] = $fstat['size'];
213
            } catch (Exception $e) {
214
                return null; // fails to determine the size
215
            }
216
        }
217
218
        return $this -> options['size'];
219
    }
220
221
    /**
222
     * Returns the current position of the file read/write pointer
223
     *
224
     * @return int Position of the file pointer
225
     * @throws \RuntimeException on error.
226
     */
227
    public function tell()
228
    {
229
        if (!$result = ftell($this -> body)) {
230
            throw new \RuntimeException("Cannot determine the file pointer position.");
231
        }
232
233
        return $result;
234
    }
235
236
    /**
237
     * Returns true if the stream is at the end of the stream.
238
     *
239
     * @return bool
240
     */
241
    public function eof()
242
    {
243
        // should use feof
244
        return (!$this->body || feof($this->body));
245
    }
246
247
    /**
248
     * Returns whether or not the stream is seekable.
249
     *
250
     * @return bool
251
     */
252
    public function isSeekable()
253
    {
254
        return $this -> seekable;
255
    }
256
257
    /**
258
     * Seek to a position in the stream.
259
     *
260
     * @link http://www.php.net/manual/en/function.fseek.php
261
     * @param int $offset Stream offset
262
     * @param int $whence Specifies how the cursor position will be calculated
263
     *     based on the seek offset. Valid values are identical to the built-in
264
     *     PHP $whence values for `fseek()`.  SEEK_SET: Set position equal to
265
     *     offset bytes SEEK_CUR: Set position to current location plus offset
266
     *     SEEK_END: Set position to end-of-stream plus offset.
267
     * @throws \RuntimeException on failure.
268
     */
269
    public function seek($offset, $whence = SEEK_SET)
270
    {
271
        if (!$this -> isSeekable()) {
272
            throw new \RuntimeException("The stream is not seekable.");
273
        }
274
275
        if (fseek($this -> body, $offset, $whence) === -1) {
276
            throw new \RuntimeException("Could not seek stream position.");
277
        }
278
    }
279
280
    /**
281
     * Seek to the beginning of the stream.
282
     *
283
     * If the stream is not seekable, this method will raise an exception;
284
     * otherwise, it will perform a seek(0).
285
     *
286
     * @see seek()
287
     * @link http://www.php.net/manual/en/function.fseek.php
288
     * @throws \RuntimeException on failure.
289
     */
290
    public function rewind()
291
    {
292
        $this -> seek(0);
293
    }
294
295
    /**
296
     * Returns whether or not the stream is writable.
297
     *
298
     * @return bool
299
     */
300
    public function isWritable()
301
    {
302
        return $this -> writable;
303
    }
304
305
    /**
306
     * Write data to the stream.
307
     *
308
     * @param string $string The string that is to be written.
309
     * @return int Returns the number of bytes written to the stream.
310
     * @throws \RuntimeException on failure.
311
     */
312
    public function write($string)
313
    {
314
        if (!$this -> isWritable()) {
315
            throw new \RuntimeException("Stream is not writable.");
316
        }
317
318
        $this -> options['size'] = null;
319
        $result = fwrite($this -> body, $string);
320
        if (false === $result) {
321
            throw new \RuntimeException("Error while writing to stream.");
322
        }
323
324
        return $result;
325
    }
326
327
    /**
328
     * Returns whether or not the stream is readable.
329
     *
330
     * @return bool
331
     */
332
    public function isReadable()
333
    {
334
        return $this -> readable;
335
    }
336
337
    /**
338
     * Read data from the stream.
339
     *
340
     * @param int $length Read up to $length bytes from the object and return
341
     *     them. Fewer than $length bytes may be returned if underlying stream
342
     *     call returns fewer bytes.
343
     * @return string Returns the data read from the stream, or an empty string
344
     *     if no bytes are available.
345
     * @throws \RuntimeException if an error occurs.
346
     */
347
    public function read($length)
348
    {
349
        if (!$this -> isReadable()) {
350
            throw new \RuntimeException("The stream is not readable.");
351
        }
352
353
        return fread($this -> body, $length);
354
    }
355
356
    /**
357
     * Returns the remaining contents in a string
358
     *
359
     * @return string
360
     * @throws \RuntimeException if unable to read or an error occurs while
361
     *     reading.
362
     */
363
    public function getContents()
364
    {
365
        if (!$result = fread($this -> body, $this -> getSize())) {
366
            throw new \RuntimeException("Unable to read or error while reading the stream.");
367
        }
368
369
        return $result;
370
    }
371
372
    /**
373
     * Get stream metadata as an associative array or retrieve a specific key.
374
     *
375
     * The keys returned are identical to the keys returned from PHP's
376
     * stream_get_meta_data() function.
377
     *
378
     * @link http://php.net/manual/en/function.stream-get-meta-data.php
379
     * @param string $key Specific metadata to retrieve.
380
     * @return array|mixed|null Returns an associative array if no key is
381
     *     provided. Returns a specific key value if a key is provided and the
382
     *     value is found, or null if the key is not found.
383
     */
384
    public function getMetadata($key = null)
385
    {
386
        if (!$key) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $key of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
387
            return [];
388
        }
389
390
        if (isset($this -> metaData)) {
391
            $meta = $this -> metaData;
392
        } else {
393
            $meta = stream_get_meta_data($this -> getBody());
394
        }
395
396
        return isset($meta[$key]) ? $meta[$key] : null;
397
    }
398
399
    /**
400
     * Gets the value of body.
401
     *
402
     * @return mixed
403
     */
404
    public function getBody()
405
    {
406
        if (!isset($this -> body)) {
407
            return null;
408
        }
409
410
        return $this->body;
411
    }
412
413
    /**
414
     * Sets the value of body.
415
     *
416
     * @param mixed $body the body
417
     *
418
     * @return self
419
     */
420
    protected function setBody($body)
421
    {
422
        $this->body = $body;
423
424
        return $this;
425
    }
426
427
    /**
428
     * Is the stream JSONable?
429
     *
430
     * @return boolean
431
     */
432
    protected function isJsonable()
433
    {
434
        $contents = $this -> getBody();
435
        try {
436
            $contents = json_encode($contents, JSON_PRETTY_PRINT);
437
            if ($contents === null ||
438
                $contents === '') {
439
                return false;
440
            }
441
        } catch (Exception $e) { // Invalid operation
442
            return false;
443
        }
444
        
445
        return true;
446
    }
447
448
    /**
449
     * Returns an overriding option.
450
     *
451
     * @param string $name         The option name
452
     * @return mixed                 
453
     */
454
    protected function getOption($name)
455
    {
456
        if (!isset($this -> options[$name])) {
457
            try {
458
                $fstat = fstat($this -> body);
459
                if (!isset($fstat[$name])) {
460
                    return null;
461
                }
462
463
                $this -> options[$name] = $fstat[$name];
464
            } catch (Exception $e) {
465
                return null; // fails to determine the size
466
            }
467
        }
468
469
        return $this -> options[$name];
470
    }
471
472
    /**
473
     * Set all available operations.
474
     *
475
     * @return void                 
476
     */
477
    protected function setOperations()
478
    {
479
        $this -> seekable = $this -> getMetadata('seekable');
480
        $this -> readable = isset(self::$readWriteHash['read'][$this -> getMetadata('mode')]);
481
        $this -> writable = isset(self::$readWriteHash['write'][$this -> getMetadata('mode')]);
482
    }
483
484
    /**
485
     * Unset all available operations.
486
     *
487
     * @return void                 
488
     */
489
    protected function unsetOperations()
490
    {
491
        $this -> writable = false;
492
        $this -> readable = false;
493
        $this -> seekable = false;
494
    }
495
496
    /**
497
     * Free resources
498
     *
499
     * @return void
500
     */
501
    public function __destruct()
502
    {
503
        $this -> close();
504
    }
505
}
506