WebRequest   D
last analyzed

Complexity

Total Complexity 80

Size/Duplication

Total Lines 540
Duplicated Lines 2.96 %

Coupling/Cohesion

Components 1
Dependencies 4

Importance

Changes 0
Metric Value
dl 16
loc 540
rs 4.8717
c 0
b 0
f 0
wmc 80
lcom 1
cbo 4

15 Methods

Rating   Name   Duplication   Size   Complexity  
D fixFilesArray() 16 41 10
C startup() 0 25 8
A getProtocol() 0 4 1
A getUrlScheme() 0 4 1
A getUrlHost() 0 4 1
A getUrlPort() 0 4 1
A getUrlAuthority() 0 6 3
A getRequestUri() 0 4 1
A getUrlPath() 0 4 1
A getUrlQuery() 0 4 1
A getUrl() 0 7 1
A isHttps() 0 4 1
A __construct() 0 7 1
B clearMagicQuotes() 0 26 4
F initialize() 0 200 45

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like WebRequest often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use WebRequest, and based on these observations, apply Extract Interface, too.

1
<?php
2
namespace Agavi\Request;
3
4
// +---------------------------------------------------------------------------+
5
// | This file is part of the Agavi package.                                   |
6
// | Copyright (c) 2005-2011 the Agavi Project.                                |
7
// | Based on the Mojavi3 MVC Framework, Copyright (c) 2003-2005 Sean Kerr.    |
8
// |                                                                           |
9
// | For the full copyright and license information, please view the LICENSE   |
10
// | file that was distributed with this source code. You can also view the    |
11
// | LICENSE file online at http://www.agavi.org/LICENSE.txt                   |
12
// |   vi: set noexpandtab:                                                    |
13
// |   Local Variables:                                                        |
14
// |   indent-tabs-mode: t                                                     |
15
// |   End:                                                                    |
16
// +---------------------------------------------------------------------------+
17
use Agavi\Core\Context;
18
use Agavi\Exception\AgaviException;
19
use Agavi\Util\ArrayPathDefinition;
20
use Agavi\Util\Toolkit;
21
22
/**
23
 * AgaviWebRequest provides additional support for web-only client requests
24
 * such as cookie and file manipulation.
25
 *
26
 * @package    agavi
27
 * @subpackage request
28
 *
29
 * @author     Sean Kerr <[email protected]>
30
 * @author     Veikko Mäkinen <[email protected]>
31
 * @author     David Zülke <[email protected]>
32
 * @copyright  Authors
33
 * @copyright  The Agavi Project
34
 *
35
 * @since      0.9.0
36
 *
37
 * @version    $Id$
38
 */
39
class WebRequest extends Request
40
{
41
    /**
42
     * @var        string The protocol information of this request.
43
     */
44
    protected $protocol = null;
45
    
46
    /**
47
     * @var        string The current URL scheme.
48
     */
49
    protected $urlScheme = '';
50
51
    /**
52
     * @var        string The current URL authority.
53
     */
54
    protected $urlHost = '';
55
56
    /**
57
     * @var        string The current URL authority.
58
     */
59
    protected $urlPort = 0;
60
61
    /**
62
     * @var        string The current URL path.
63
     */
64
    protected $urlPath = '';
65
66
    /**
67
     * @var        string The current URL query.
68
     */
69
    protected $urlQuery = '';
70
71
    /**
72
     * @var        string The current request URL (path and query).
73
     */
74
    protected $requestUri = '';
75
76
    /**
77
     * @var        string The current URL.
78
     */
79
    protected $url = '';
80
81
    /**
82
     * Get the request protocol information, e.g. "HTTP/1.1".
83
     *
84
     * @return     string The protocol information.
85
     *
86
     * @author     David Zülke <[email protected]>
87
     * @since      0.11.0
88
     */
89
    public function getProtocol()
90
    {
91
        return $this->protocol;
92
    }
93
94
    /**
95
     * Retrieve the scheme part of a request URL, typically the protocol.
96
     * Example: "http".
97
     *
98
     * @return     string The request URL scheme.
99
     *
100
     * @author     David Zülke <[email protected]>
101
     * @since      0.11.0
102
     */
103
    public function getUrlScheme()
104
    {
105
        return $this->urlScheme;
106
    }
107
108
    /**
109
     * Retrieve the hostname part of a request URL.
110
     *
111
     * @return     string The request URL hostname.
112
     *
113
     * @author     David Zülke <[email protected]>
114
     * @since      0.11.0
115
     */
116
    public function getUrlHost()
117
    {
118
        return $this->urlHost;
119
    }
120
121
    /**
122
     * Retrieve the hostname part of a request URL.
123
     *
124
     * @return     string The request URL hostname.
125
     *
126
     * @author     David Zülke <[email protected]>
127
     * @since      0.11.0
128
     */
129
    public function getUrlPort()
130
    {
131
        return $this->urlPort;
132
    }
133
134
    /**
135
     * Retrieve the request URL authority, typically host and port.
136
     * Example: "foo.example.com:8080".
137
     *
138
     * @param      bool $forcePort Whether or not ports 80 (for HTTP) and 433 (for HTTPS)
139
     *                  should be included in the return string.
140
     *
141
     * @return     string The request URL authority.
142
     *
143
     * @author     David Zülke <[email protected]>
144
     * @since      0.11.0
145
     */
146
    public function getUrlAuthority($forcePort = false)
147
    {
148
        $port = $this->getUrlPort();
149
        $scheme = $this->getUrlScheme();
150
        return $this->getUrlHost() . ($forcePort || Toolkit::isPortNecessary($scheme, $port) ? ':' . $port : '');
151
    }
152
153
    /**
154
     * Retrieve the relative part of the request URL, i.e. path and query.
155
     * Example: "/foo/bar/baz?id=4815162342".
156
     *
157
     * @return     string The relative URL of the current request.
158
     *
159
     * @author     David Zülke <[email protected]>
160
     * @since      0.11.0
161
     */
162
    public function getRequestUri()
163
    {
164
        return $this->requestUri;
165
    }
166
167
    /**
168
     * Retrieve the path part of the URL.
169
     * Example: "/foo/bar/baz".
170
     *
171
     * @return     string The path part of the URL.
172
     *
173
     * @author     David Zülke <[email protected]>
174
     * @since      0.11.0
175
     */
176
    public function getUrlPath()
177
    {
178
        return $this->urlPath;
179
    }
180
181
    /**
182
     * Retrieve the query part of the URL.
183
     * Example: "id=4815162342".
184
     *
185
     * @return     string The query part of the URL, or an empty string.
186
     *
187
     * @author     David Zülke <[email protected]>
188
     * @since      0.11.0
189
     */
190
    public function getUrlQuery()
191
    {
192
        return $this->urlQuery;
193
    }
194
195
    /**
196
     * Retrieve the full request URL, including protocol, server name, port (if
197
     * necessary), and request URI.
198
     * Example: "http://foo.example.com:8080/foo/bar/baz?id=4815162342".
199
     *
200
     * @return     string The URL of the current request.
201
     *
202
     * @author     David Zülke <[email protected]>
203
     * @since      0.11.0
204
     */
205
    public function getUrl()
206
    {
207
        return
208
            $this->getUrlScheme() . '://' .
209
            $this->getUrlAuthority() .
210
            $this->getRequestUri();
211
    }
212
213
    /**
214
     * Whether or not HTTPS was used for this request.
215
     *
216
     * @return     bool True, if it's an HTTPS request, false otherwise.
217
     *
218
     * @author     David Zülke <[email protected]>
219
     * @since      0.11.6
220
     */
221
    public function isHttps()
222
    {
223
        return $this->getUrlScheme() == 'https';
224
    }
225
226
    /**
227
     * Constructor.
228
     *
229
     * @author     David Zülke <[email protected]>
230
     * @since      0.11.0
231
     */
232
    public function __construct()
233
    {
234
        parent::__construct();
235
        $this->setParameters(array(
236
            'request_data_holder_class' => 'Agavi\\Request\\WebRequestDataHolder',
237
        ));
238
    }
239
240
    /**
241
     * Clear magic quotes. Properly. That means keys are cleared, too.
242
     *
243
     * @param      array $input An array of data to be put out of it's misery.
244
     *
245
     * @return     array An array delivered from magic quotes.
246
     *
247
     * @author     David Zülke <[email protected]>
248
     * @since      0.11.0
249
     */
250
    final public static function clearMagicQuotes($input)
251
    {
252
        // this method only works with PHP 5.2.7+
253
        // there used to be special code for versions < 5.2.2
254
        // http://bugs.php.net/bug.php?id=41093
255
        // but we now require 5.2.8 anyway in combination with magic_quotes_gpc, see initialize()
256
        // http://trac.agavi.org/ticket/953
257
        // http://trac.agavi.org/ticket/944
258
        // http://bugs.php.net/bug.php?id=41093
259
        
260
        $retval = array();
261
262
        foreach ($input as $key => $value) {
263
            $key = stripslashes($key);
264
265
            if (is_array($value)) {
266
                $retval[$key] = self::clearMagicQuotes($value);
267
            } elseif (is_string($value)) {
268
                $retval[$key] = stripslashes($value);
269
            } else {
270
                $retval[$key] = $value;
271
            }
272
        }
273
274
        return $retval;
275
    }
276
277
    /**
278
     * Initialize this Request.
279
     *
280
     * @param      Context $context An Context instance.
281
     * @param      array   $parameters An associative array of initialization parameters.
282
     *
283
     * @throws     <b>AgaviInitializationException</b> If an error occurs while
284
     *                                                 initializing this Request.
285
     *
286
     * @author     Veikko Mäkinen <[email protected]>
287
     * @author     David Zülke <[email protected]>
288
     * @since      0.9.0
289
     */
290
    public function initialize(Context $context, array $parameters = array())
0 ignored issues
show
Coding Style introduced by
initialize uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
initialize uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
initialize uses the super-global variable $_COOKIE which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
initialize uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
initialize uses the super-global variable $_FILES which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
initialize uses the super-global variable $GLOBALS which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
initialize uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
291
    {
292
        parent::initialize($context, $parameters);
293
294
        $rla = ini_get('register_long_arrays');
295
296
        // very first thing to do: remove magic quotes
297
        if (get_magic_quotes_gpc()) {
298
            trigger_error('Support for php.ini directive "magic_quotes_gpc" is deprecated and will be dropped in Agavi 1.2. The setting is deprecated in PHP 5.3 and will be removed in PHP 5.4. Please refer to the PHP manual for details.', E_USER_DEPRECATED);
299
            $_GET = self::clearMagicQuotes($_GET);
300
            $_POST = self::clearMagicQuotes($_POST);
301
            $_COOKIE = self::clearMagicQuotes($_COOKIE);
302
            $_REQUEST = self::clearMagicQuotes($_REQUEST);
303
            $_FILES = self::clearMagicQuotes($_FILES);
304
            if ($rla) {
305
                $GLOBALS['HTTP_GET_VARS'] = $_GET;
306
                $GLOBALS['HTTP_POST_VARS'] = $_POST;
307
                $GLOBALS['HTTP_COOKIE_VARS'] = $_COOKIE;
308
                $GLOBALS['HTTP_POST_FILES'] = $_FILES;
309
            }
310
        }
311
312
        $sources = array_merge(array(
313
            'HTTPS' => 'HTTPS',
314
            'REQUEST_METHOD' => 'REQUEST_METHOD',
315
            'SERVER_NAME' => 'SERVER_NAME',
316
            'SERVER_PORT' => 'SERVER_PORT',
317
            'SERVER_PROTOCOL' => 'SERVER_PROTOCOL',
318
            'SERVER_SOFTWARE' => 'SERVER_SOFTWARE',
319
        ), (array)$this->getParameter('sources'));
320
        $this->setParameter('sources', $sources);
321
322
        // this is correct: if a user-supplied parameter was set, then null is used as the default return value, which means getSourceValue() returns the user-supplied parameter as a last resort if a key of the same name could not be found. allows setting of static values for any of those below
323
        $sourceDefaults = array(
324
            'HTTPS' => isset($parameters['sources']['HTTPS']) ? null : 'off',
325
            'REQUEST_METHOD' => isset($parameters['sources']['REQUEST_METHOD']) ? null : 'GET',
326
            'SERVER_NAME' => null,
327
            'SERVER_PORT' => isset($parameters['sources']['SERVER_PORT']) ? null : $this->urlPort,
328
            'SERVER_PROTOCOL' => isset($parameters['sources']['SERVER_PROTOCOL']) ? null : 'HTTP/1.0',
329
            'SERVER_SOFTWARE' => null,
330
        );
331
332
        $methods = array_merge(array(
333
            'GET' => 'read',
334
            'POST' => 'write',
335
            'PUT' => 'create',
336
            'DELETE' => 'remove',
337
        ), (array)$this->getParameter('method_names'));
338
        $this->setParameter('method_names', $methods);
339
340
        $REQUEST_METHOD = self::getSourceValue($sources['REQUEST_METHOD'], $sourceDefaults['REQUEST_METHOD']);
341
342
        // map REQUEST_METHOD value to a method name, or fall back to the default in $sourceDefaults.
343
        // if someone set a static value as default for a source that does not have a mapping, then he's really asking for it, and thus out of luck
344
        $this->setMethod($this->getParameter(sprintf('method_names[%s]', $REQUEST_METHOD), $this->getParameter(sprintf('method_names[%s]', $sourceDefaults['REQUEST_METHOD']))));
345
        
346
        $this->protocol = self::getSourceValue($sources['SERVER_PROTOCOL'], $sourceDefaults['SERVER_PROTOCOL']);
347
        
348
        // "on" (e.g. Apache or IIS) or "https" (e.g. Amazon EC2 Elastic Load Balancer) or "1" or integer 1 or true (e.g. statically set from a config file)
349
        $HTTPS = (bool)preg_match('/^(on|https|1)$/i', self::getSourceValue($sources['HTTPS'], $sourceDefaults['HTTPS']));
350
351
        $this->urlScheme = 'http' . ($HTTPS ? 's' : '');
352
353
        $this->urlPort = (int)self::getSourceValue($sources['SERVER_PORT'], $sourceDefaults['SERVER_PORT']);
0 ignored issues
show
Documentation Bug introduced by
The property $urlPort was declared of type string, but (int) self::getSourceVal...efaults['SERVER_PORT']) is of type integer. 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...
354
355
        $SERVER_NAME = self::getSourceValue($sources['SERVER_NAME'], $sourceDefaults['SERVER_NAME']);
356
        $port = $this->getUrlPort();
357
        if (preg_match_all('/\:/', $SERVER_NAME, $m) > 1) {
358
            $this->urlHost = preg_replace('/\]\:' . preg_quote($port, '/') . '$/', '', $SERVER_NAME);
0 ignored issues
show
Documentation Bug introduced by
It seems like preg_replace('/\\]\\:' ....'$/', '', $SERVER_NAME) can also be of type array<integer,string>. However, the property $urlHost is declared as type string. 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...
359
        } else {
360
            $this->urlHost = preg_replace('/\:' . preg_quote($port, '/') . '$/', '', $SERVER_NAME);
0 ignored issues
show
Documentation Bug introduced by
It seems like preg_replace('/\\:' . pr...'$/', '', $SERVER_NAME) can also be of type array<integer,string>. However, the property $urlHost is declared as type string. 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...
361
        }
362
363
        $_SERVER['SERVER_SOFTWARE'] = self::getSourceValue($sources['SERVER_SOFTWARE'], $sourceDefaults['SERVER_SOFTWARE']);
364
        
365
        if (isset($_SERVER['SERVER_SOFTWARE']) && preg_match('#^Apache(/\d+(\.\d+)?)?\.?$#', $_SERVER['SERVER_SOFTWARE'])) {
366
            throw new AgaviException(
367
                "You are running the Apache HTTP Server with a 'ServerTokens' configuration directive value of 'Minor' or lower.\n" .
368
                "This directive controls the amount of version information Apache exposes about itself.\n" .
369
                "Agavi needs detailed Apache version information to apply URL decoding and parsing workarounds specific to certain versions of Apache that exhibit buggy behavior.\n\n" .
370
                "Please take one of the following measures to fix this problem:\n" .
371
                "- raise your 'ServerTokens' level to 'Min' or higher in httpd.conf\n" .
372
                "- set a static value for the request source 'SERVER_SOFTWARE' in factories.xml (for your environment)\n" .
373
                "- set a value for \$_SERVER['SERVER_SOFTWARE'], e.g. in your pub/index.php\n\n" .
374
                "For detailed instructions and examples on fixing this problem, especially for the factories.xml method which is recommended in case you do not have control over your server's httpd.conf, please refer to:\n" .
375
                "http://trac.agavi.org/ticket/1029\n\n" .
376
                "For more information on the 'ServerTokens' directive, please refer to:\n" .
377
                "http://httpd.apache.org/docs/2.2/en/mod/core.html#servertokens\n\n" .
378
            "For your reference, your SERVER_SOFTWARE string is currently '$_SERVER[SERVER_SOFTWARE]'."
379
            );
380
        }
381
382
        if (isset($_SERVER['UNENCODED_URL']) && isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS') !== false) {
383
            // Microsoft IIS 7 with URL Rewrite Module
384
            $this->requestUri = $_SERVER['UNENCODED_URL'];
385
        } elseif (isset($_SERVER['HTTP_X_REWRITE_URL']) && isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS') !== false) {
386
            // Microsoft IIS with ISAPI_Rewrite
387
            $this->requestUri = $_SERVER['HTTP_X_REWRITE_URL'];
388
        } elseif (!isset($_SERVER['REQUEST_URI']) && isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS') !== false) {
389
            // Microsoft IIS with PHP in CGI mode
390
            $this->requestUri = $_SERVER['ORIG_PATH_INFO'] . (isset($_SERVER['QUERY_STRING']) && $_SERVER['QUERY_STRING'] != '' ? '?' . $_SERVER['QUERY_STRING'] : '');
391
        } elseif (isset($_SERVER['REQUEST_URI'])) {
392
            $this->requestUri = $_SERVER['REQUEST_URI'];
393
        }
394
395
        // Microsoft IIS with PHP in CGI mode
396
        if (!isset($_SERVER['QUERY_STRING'])) {
397
            $_SERVER['QUERY_STRING'] = '';
398
        }
399
        if (!isset($_SERVER['REQUEST_URI'])) {
400
            $_SERVER['REQUEST_URI'] = $this->getRequestUri();
401
        }
402
403
        // okay, this is really bad
404
        // Internet Explorer (many versions, many OSes) seem to be sending improperly urlencoded URLs to the server, in violation of the HTTP RFC
405
        // this can cause a number of problems, most notably html special chars not being escaped and potentially ending up this way in the output
406
        // the result is an XSS attack vector, e.g. on WebRouting::gen(null)
407
        // so we escape those. but not the ampersand, or the query string gets messed up
408
        // we also encode the backtick (Suhosin does this, too), and the space character
409
        // in theory, we shouldn't encode the single quote either, since it's a reserved sub-delimiter as per RFC 3986 - however, that would allow injection again in documents that use single quotes as attribute delimiters, and it's up to implementations to encode sub-delimiters if they deem it necessary
410
        // great, huh?
411
        // more details:
412
        // http://trac.agavi.org/ticket/1019
413
        // http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2009-0417
414
        list($this->requestUri, $_SERVER['REQUEST_URI']) = str_replace(
415
            array(' ',   '"',   '\'',  '<',   '>',   '`',   /*'&'*/),
416
            array('%20', '%22', '%27', '%3C', '%3E', '%60', /*'%26'*/),
417
            array($this->requestUri, $_SERVER['REQUEST_URI'])
418
        );
419
        if ($rla) {
420
            $GLOBALS['HTTP_SERVER_VARS']['REQUEST_URI'] = $this->getRequestUri();
421
        }
422
        
423
        // 'scheme://authority' is necessary so parse_url doesn't stumble over '://' in the request URI
424
        $parts = array_merge(array('path' => '', 'query' => ''), parse_url('scheme://authority' . $this->getRequestUri()));
425
        $this->urlPath = $parts['path'];
426
        $this->urlQuery = $parts['query'];
427
        unset($parts);
428
429
        $files = array();
430
        $ufc = $this->getParameter('uploaded_file_class', 'Agavi\Request\UploadedFile');
431
432
        if ($this->getMethod() == $methods['PUT']) {
433
            if (isset($_SERVER['CONTENT_TYPE']) && $this->getParameter('http_put_decode_urlencoded', true) && preg_match('#^application/x-www-form-urlencoded(;[^;]+)*?$#', $_SERVER['CONTENT_TYPE'])) {
434
                // urlencoded data was sent, we can decode that
435
                parse_str(file_get_contents('php://input'), $_POST);
436
                if (function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc()) {
437
                    $_POST = self::clearMagicQuotes($_POST);
0 ignored issues
show
Bug introduced by
It seems like $_POST can also be of type null; however, Agavi\Request\WebRequest::clearMagicQuotes() 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...
438
                }
439
            } else {
440
                // some other data via PUT. we need to populate $_FILES manually
441
                $httpBody = file_get_contents('php://input');
442
443
                $files = array(
444
                    $this->getParameter('http_put_file_name', 'put_file') => new $ufc(array(
445
                        'name' => $this->getMethod(),
446
                        'type' => isset($_SERVER['CONTENT_TYPE']) ? $_SERVER['CONTENT_TYPE'] : 'application/octet-stream',
447
                        'size' => strlen($httpBody),
448
                        'contents' => $httpBody,
449
                        'error' => UPLOAD_ERR_OK,
450
                        'is_uploaded_file' => false,
451
                    ))
452
                );
453
            }
454
        } elseif ($this->getMethod() == $methods['POST'] && (!isset($_SERVER['CONTENT_TYPE']) || (isset($_SERVER['CONTENT_TYPE']) && !preg_match('#^(application/x-www-form-urlencoded|multipart/form-data)(;[^;]+)*?$#', $_SERVER['CONTENT_TYPE'])))) {
455
            // POST, but no regular urlencoded data or file upload. lets put the request payload into a file
456
            $httpBody = file_get_contents('php://input');
457
458
            $files = array(
459
                $this->getParameter('http_post_file_name', 'post_file') => new $ufc(array(
460
                    'name' => $this->getMethod(),
461
                    'type' => isset($_SERVER['CONTENT_TYPE']) ? $_SERVER['CONTENT_TYPE'] : 'application/octet-stream',
462
                    'size' => strlen($httpBody),
463
                    'contents' => $httpBody,
464
                    'error' => UPLOAD_ERR_OK,
465
                    'is_uploaded_file' => false,
466
                ))
467
            );
468
        } elseif ($this->getMethod() == $methods['POST'] && isset($_SERVER['CONTENT_TYPE']) && preg_match('#^multipart/form-data(;[^;]+)*?$#', $_SERVER['CONTENT_TYPE'])) {
469
            $files = static::fixFilesArray($_FILES, $ufc);
470
        }
471
472
        $headers = array();
473
        foreach ($_SERVER as $key => $value) {
474
            if (substr($key, 0, 5) == 'HTTP_') {
475
                $headers[substr($key, 5)] = $value;
476
            } elseif ($key == 'CONTENT_TYPE' || $key == 'CONTENT_LENGTH') {
477
                // yeah, whatever, PHP...
478
                $headers[$key] = $value;
479
            }
480
        }
481
482
        $rdhc = $this->getParameter('request_data_holder_class');
483
        $this->setRequestData(new $rdhc(array(
484
            constant("$rdhc::SOURCE_PARAMETERS") => array_merge($_GET, $_POST),
485
            constant("$rdhc::SOURCE_COOKIES") => $_COOKIE,
486
            constant("$rdhc::SOURCE_FILES") => $files,
487
            constant("$rdhc::SOURCE_HEADERS") => $headers,
488
        )));
489
    }
490
491
    /**
492
     * Corrects the order of $_FILES for arrays of files.
493
     * The cleaned up array of AgaviUploadedFile objects is returned.
494
     *
495
     * @param      array  $input The array to work on.
496
     * @param      string $uploadedFileClass Name of the wrapper uploaded file class to instantiate.
497
     * @param      array  $input Array of indices used during recursion, initially empty.
498
     * @param      array  $output Output buffer used during recursion, initially empty.
499
     *
500
     * @author     David Zülke <[email protected]>
501
     * @since      1.1.0
502
     */
503
    protected static function fixFilesArray($input, $uploadedFileClass = 'UploadedFile', $index = array(), &$output = array())
504
    {
505
        $fromIndex = $index;
506
        if (count($fromIndex) > 0) {
507
            $first = array_shift($fromIndex);
508
            array_unshift($fromIndex, $first, 'error');
509
        }
510
        $sub = ArrayPathDefinition::getValue($fromIndex, $input);
511
        $theIndices = array();
512
        foreach (array('name', 'type', 'size', 'tmp_name', 'error', 'is_uploaded_file') as $name) {
513
            $theIndex = $fromIndex;
514
            $first = array_shift($theIndex);
515
            array_shift($theIndex);
516
            array_unshift($theIndex, $first, $name);
517
            $theIndices[$name] = $theIndex;
518
        }
519
        if (is_array($sub)) {
520
            foreach ($sub as $key => $value) {
521
                $toIndex = array_merge($index, array($key));
522
                if (is_array($value)) {
523
                    static::fixFilesArray($input, $uploadedFileClass, $toIndex, $output);
524 View Code Duplication
                } else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
525
                    $data = array();
526
                    foreach ($theIndices as $name => $theIndex) {
527
                        $data[$name] = ArrayPathDefinition::getValue(array_merge($theIndex, array($key)), $input, $name == 'is_uploaded_file' ? true : null);
528
                    }
529
                    $data = new $uploadedFileClass($data);
530
                    ArrayPathDefinition::setValue($toIndex, $output, $data);
531
                }
532
            }
533 View Code Duplication
        } else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
534
            $data = array();
535
            foreach ($theIndices as $name => $theIndex) {
536
                $data[$name] = ArrayPathDefinition::getValue($theIndex, $input, $name == 'is_uploaded_file' ? true : null);
537
            }
538
            $data = new $uploadedFileClass($data);
539
            ArrayPathDefinition::setValue($index, $output, $data);
540
        }
541
        
542
        return $output;
543
    }
544
545
    /**
546
     * Do any necessary startup work after initialization.
547
     *
548
     * This method is not called directly after initialize().
549
     *
550
     * @author     David Zülke <[email protected]>
551
     * @since      0.11.0
552
     */
553
    public function startup()
0 ignored issues
show
Coding Style introduced by
startup uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
startup uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
startup uses the super-global variable $_COOKIE which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
startup uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
startup uses the super-global variable $_FILES which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
startup uses the super-global variable $GLOBALS which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
startup uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
startup uses the super-global variable $_ENV which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
554
    {
555
        parent::startup();
556
        
557
        if ($this->getParameter('unset_input', true)) {
558
            $rla = ini_get('register_long_arrays');
559
            
560
            $_GET = $_POST = $_COOKIE = $_REQUEST = $_FILES = array();
561
            if ($rla) {
562
                // clean long arrays, too!
563
                $GLOBALS['HTTP_GET_VARS'] = $GLOBALS['HTTP_POST_VARS'] = $GLOBALS['HTTP_COOKIE_VARS'] = $GLOBALS['HTTP_POST_FILES'] = array();
564
            }
565
            
566
            foreach ($_SERVER as $key => $value) {
567
                if (substr($key, 0, 5) == 'HTTP_' || $key == 'CONTENT_TYPE' || $key == 'CONTENT_LENGTH') {
568
                    unset($_SERVER[$key]);
569
                    unset($_ENV[$key]);
570
                    if ($rla) {
571
                        unset($GLOBALS['HTTP_SERVER_VARS'][$key]);
572
                        unset($GLOBALS['HTTP_ENV_VARS'][$key]);
573
                    }
574
                }
575
            }
576
        }
577
    }
578
}
579