Issues (69)

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/Http/Response.php (4 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
declare(strict_types = 1);
3
4
/**
5
 * Micro
6
 *
7
 * @author    Raffael Sahli <[email protected]>
8
 * @copyright Copyright (c) 2017 gyselroth GmbH (https://gyselroth.com)
9
 * @license   MIT https://opensource.org/licenses/MIT
10
 */
11
12
namespace Micro\Http;
13
14
use \Micro\Http;
15
use \Closure;
16
17
class Response
18
{
19
    /**
20
     * Output format
21
     *
22
     * @var string
23
     */
24
    protected $output_format = 'json';
25
26
27
    /**
28
     * Possible output formats
29
     */
30
    const OUTPUT_FORMATS = [
31
        'json' => 'application/json; charset=utf-8',
32
        'xml'  => 'application/xml; charset=utf-8',
33
        'text' => 'text/html; charset=utf-8'
34
    ];
35
36
37
    /**
38
     * Human readable output
39
     *
40
     * @var bool
41
     */
42
    protected $pretty_format = false;
43
44
45
    /**
46
     * Headers
47
     *
48
     * @var array
49
     */
50
    protected $headers = [];
51
52
53
    /**
54
     * Code
55
     *
56
     * @var int
57
     */
58
    protected $code = 200;
59
60
61
    /**
62
     * Body
63
     *
64
     * @var string
65
     */
66
    protected $body;
67
68
69
    /**
70
     * body only
71
     *
72
     * @var bool
73
     */
74
    protected $body_only = false;
75
76
77
    /**
78
     * Init response
79
     *
80
     * @return void
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
81
     */
82
    public function __construct()
83
    {
84
        $this->setupFormats();
85
    }
86
87
88
    /**
89
     * Set header
90
     *
91
     * @param   string $header
92
     * @param   string $value
93
     * @return  Response
94
     */
95
    public function setHeader(string $header, string $value): Response
96
    {
97
        $this->headers[$header] = $value;
98
        return $this;
99
    }
100
101
102
    /**
103
     * Delete header
104
     *
105
     * @param   string $header
106
     * @return  Response
107
     */
108
    public function removeHeader(string $header): Response
109
    {
110
        if(isset($this->headers[$header])) {
111
            unset($this->headers[$header]);
112
        }
113
114
        return $this;
115
    }
116
117
118
    /**
119
     * Get headers
120
     *
121
     * @return array
122
     */
123
    public function getHeaders(): array
124
    {
125
        return $this->headers;
126
    }
127
128
129
    /**
130
     * Send headers
131
     *
132
     * @return  Response
133
     */
134
    public function sendHeaders(): Response
135
    {
136
        foreach ($this->headers as $header => $value) {
137
            header($header.': '.$value);
138
        }
139
140
        return $this;
141
    }
142
143
144
    /**
145
     * Set response code
146
     *
147
     * @param   int $code
148
     * @return  Response
149
     */
150
    public function setCode(int $code): Response
151
    {
152
        if (!array_key_exists($code, Http::STATUS_CODES)) {
153
            throw new Exception('invalid http code set');
154
        }
155
156
        $this->code = $code;
157
        return $this;
158
    }
159
160
161
    /**
162
     * Get response code
163
     *
164
     * @return int
165
     */
166
    public function getCode(): int
167
    {
168
        return $this->code;
169
    }
170
171
172
    /**
173
     * Set body
174
     *
175
     * @param  mixed $body
176
     * @param  bool $body_only
177
     * @return Response
178
     */
179
    public function setBody($body, bool $body_only = false): Response
180
    {
181
        $this->body = $body;
182
        $this->body_only = $body_only;
183
        $this->setOutputFormat($this->output_format);
184
185
        return $this;
186
    }
187
188
189
    /**
190
     * Get body
191
     *
192
     * @return string
193
     */
194
    public function getBody()
195
    {
196
        return $this->body;
197
    }
198
199
200
    /**
201
     * Sends the actual response.
202
     *
203
     * @return  void
204
     */
205
    public function send(): void
206
    {
207
        $status = Http::STATUS_CODES[$this->code];
208
        $this->sendHeaders();
209
        header('HTTP/1.0 '.$this->code.' '.$status, true, $this->code);
210
211
        if ($this->body === null && $this->code == 204) {
212
            $this->terminate();
213
        }
214
215
        if($this->body instanceof Closure) {
216
            $body = $this->body->call($this);
217
        } else {
218
            $body = $this->body;
219
        }
220
221
        if ($this->body_only === false && $this->output_format !== 'text') {
222
            $body = ['data' => $body];
223
            $body['status'] = intval($this->code);
224
            $body = array_reverse($body, true);
225
        }
226
227
        switch ($this->output_format) {
228
            case null:
229
            break;
230
231
            default:
232
            case 'json':
0 ignored issues
show
case 'json': echo $t...JSON($body); break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
233
                echo $this->asJSON($body);
234
            break;
235
236
            case 'xml':
237
                echo $this->asXML($body);
238
            break;
239
240
            case 'text':
241
                echo $body;
242
            break;
243
        }
244
245
        //$this->terminate();
0 ignored issues
show
Unused Code Comprehensibility introduced by
84% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
246
    }
247
248
249
    /**
250
     * Get output format
251
     *
252
     * @return string
253
     */
254
    public function getOutputFormat(): string
255
    {
256
        return $this->output_format;
257
    }
258
259
260
    /**
261
     * Convert response to human readable output
262
     *
263
     * @param   bool $format
264
     * @return  Response
265
     */
266
    public function setPrettyFormat(bool $format): Response
267
    {
268
        $this->pretty_format = (bool)$format;
269
        return $this;
270
    }
271
272
273
    /**
274
     * Set header Content-Length $body.
275
     *
276
     * @param  string $body
277
     * @return Response
278
     */
279
    public function setContentLength(string $body): Response
280
    {
281
        header('Content-Length: '.strlen($body));
282
        return $this;
283
    }
284
285
286
    /**
287
     * Converts $body to pretty json.
288
     *
289
     * @param  mixed $body
290
     * @return string
291
     */
292
    public function asJSON($body): string
293
    {
294
        if ($this->pretty_format) {
295
            $result = json_encode($body, JSON_PRETTY_PRINT);
296
        } else {
297
            $result = json_encode($body);
298
        }
299
300
        $this->setContentLength($result);
301
302
        return $result;
303
    }
304
305
306
    /**
307
     * Converts mixed data to XML
308
     *
309
     * @param    mixed $data
310
     * @param    SimpleXMLElement $xml
311
     * @param    string $child_name
312
     * @return   string
313
     */
314
    public function toXML($data, Config $xml, string $child_name): string
315
    {
316
        if (is_array($data)) {
317
            foreach ($data as $k => $v) {
318
                if (is_array($v)) {
319
                    (is_int($k)) ? $this->toXML($v, $xml->addChild($child_name), $v) : $this->toXML($v, $xml->addChild(strtolower($k)), $child_name);
0 ignored issues
show
$v 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...
320
                } else {
321
                    (is_int($k)) ? $xml->addChild($child_name, $v) : $xml->addChild(strtolower($k), $v);
322
                }
323
            }
324
        } else {
325
            $xml->addChild($child_name, $data);
326
        }
327
328
        return $xml->asXML();
329
    }
330
331
332
    /**
333
     * Converts response to xml.
334
     *
335
     * @param   mixed $body
336
     * @return  string
337
     */
338
    public function asXML($body): string
339
    {
340
        $root = new Config('<response></response>');
341
        $raw = $this->toXML($body, $root, 'node');
342
343
        if ($this->pretty_format) {
344
            $raw = $this->prettyXml($raw);
345
        }
346
347
        $this->setContentLength($raw);
348
        return $raw;
349
    }
350
351
352
    /**
353
     * Pretty formatted xml
354
     *
355
     * @param   string $xml
356
     * @return  string
357
     */
358
    public function prettyXml(string $xml): string
359
    {
360
        $domxml = new \DOMDocument('1.0');
361
        $domxml->preserveWhiteSpace = false;
362
        $domxml->formatOutput = true;
363
        $domxml->loadXML($xml);
364
365
        return $domxml->saveXML();
366
    }
367
368
369
    /**
370
     * Set the current output format.
371
     *
372
     * @param  string $format
373
     * @return Response
374
     */
375
    public function setOutputFormat(?string $format=null): Response
376
    {
377
        if($format === null) {
378
            $this->output_format = null;
379
            return $this->removeHeader('Content-Type');
380
        }
381
382
        if(!array_key_exists($format, self::OUTPUT_FORMATS)) {
383
            throw new Exception('invalid output format given');
384
        }
385
386
        $this->setHeader('Content-Type', self::OUTPUT_FORMATS[$format]);
387
        $this->output_format = $format;
388
        return $this;
389
    }
390
391
392
    /**
393
     * Abort after response
394
     *
395
     * @return void
396
     */
397
    public function terminate(): void
398
    {
399
        exit();
400
    }
401
402
403
    /**
404
     * Setup formats.
405
     *
406
     * @return Response
407
     */
408
    public function setupFormats(): Response
409
    {
410
        $pretty = array_key_exists('pretty', $_GET) && ($_GET['pretty'] != 'false' && $_GET['pretty'] != '0');
411
        $this->setPrettyFormat($pretty);
412
413
        //through HTTP_ACCEPT
414
        if (isset($_SERVER['HTTP_ACCEPT']) && strpos($_SERVER['HTTP_ACCEPT'], '*/*') === false) {
415
            foreach (self::OUTPUT_FORMATS as $format) {
416
                if (strpos($_SERVER['HTTP_ACCEPT'], $format) !== false) {
417
                    $this->output_format = $format;
418
                    break;
419
                }
420
            }
421
        }
422
423
        return $this;
424
    }
425
}
426