request_Core::protocol()   A
last analyzed

Complexity

Conditions 4
Paths 3

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 1 Features 0
Metric Value
cc 4
eloc 7
c 2
b 1
f 0
nc 3
nop 0
dl 0
loc 10
rs 9.2
1
<?php defined('SYSPATH') or die('No direct access allowed.');
2
/**
3
 * Request helper class.
4
 *
5
 * $Id: request.php 4355 2009-05-15 17:18:28Z kiall $
6
 *
7
 * @package    Core
8
 * @author     Kohana Team
9
 * @copyright  (c) 2007-2008 Kohana Team
10
 * @license    http://kohanaphp.com/license.html
11
 */
12
class request_Core
13
{
14
15
    // Possible HTTP methods
16
    protected static $http_methods = array('get', 'head', 'options', 'post', 'put', 'delete');
17
18
    // Content types from client's HTTP Accept request header (array)
19
    protected static $accept_types;
20
21
    /**
22
     * Returns the HTTP referrer, or the default if the referrer is not set.
23
     *
24
     * @param   mixed   default to return
25
     * @return  string
26
     */
27
    public static function referrer($default = false)
0 ignored issues
show
Coding Style introduced by
referrer 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...
28
    {
29
        if (! empty($_SERVER['HTTP_REFERER'])) {
30
            // Set referrer
31
            $ref = $_SERVER['HTTP_REFERER'];
32
33
            if (strpos($ref, url::base(false)) === 0) {
34
                // Remove the base URL from the referrer
35
                $ref = substr($ref, strlen(url::base(false)));
36
            }
37
        }
38
39
        return isset($ref) ? $ref : $default;
40
    }
41
42
    /**
43
     * Returns the current request protocol, based on $_SERVER['https']. In CLI
44
     * mode, NULL will be returned.
45
     *
46
     * @return  string
0 ignored issues
show
Documentation introduced by
Should the return type not be null|string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
47
     */
48
    public static function protocol()
0 ignored issues
show
Coding Style introduced by
protocol 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...
49
    {
50
        if (PHP_SAPI === 'cli') {
51
            return null;
52
        } elseif (! empty($_SERVER['HTTPS']) and $_SERVER['HTTPS'] === 'on') {
53
            return 'https';
54
        } else {
55
            return 'http';
56
        }
57
    }
58
59
    /**
60
     * Tests if the current request is an AJAX request by checking the X-Requested-With HTTP
61
     * request header that most popular JS frameworks now set for AJAX calls.
62
     *
63
     * @return  boolean
64
     */
65
    public static function is_ajax()
0 ignored issues
show
Coding Style introduced by
is_ajax 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...
66
    {
67
        return (isset($_SERVER['HTTP_X_REQUESTED_WITH']) and strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest');
68
    }
69
70
    /**
71
     * Returns current request method.
72
     *
73
     * @throws  Kohana_Exception in case of an unknown request method
74
     * @return  string
75
     */
76
    public static function method()
0 ignored issues
show
Coding Style introduced by
method 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...
77
    {
78
        $method = strtolower($_SERVER['REQUEST_METHOD']);
79
80
        if (! in_array($method, request::$http_methods)) {
81
            throw new Kohana_Exception('request.unknown_method', $method);
82
        }
83
84
        return $method;
85
    }
86
87
    /**
88
     * Returns boolean of whether client accepts content type.
89
     *
90
     * @param   string   content type
91
     * @param   boolean  set to TRUE to disable wildcard checking
92
     * @return  boolean
93
     */
94
    public static function accepts($type = null, $explicit_check = false)
95
    {
96
        request::parse_accept_header();
97
98
        if ($type === null) {
99
            return request::$accept_types;
100
        }
101
102
        return (request::accepts_at_quality($type, $explicit_check) > 0);
103
    }
104
105
    /**
106
     * Compare the q values for given array of content types and return the one with the highest value.
107
     * If items are found to have the same q value, the first one encountered in the given array wins.
108
     * If all items in the given array have a q value of 0, FALSE is returned.
109
     *
110
     * @param   array    content types
111
     * @param   boolean  set to TRUE to disable wildcard checking
112
     * @return  mixed    string mime type with highest q value, FALSE if none of the given types are accepted
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use integer|string|false.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
113
     */
114
    public static function preferred_accept($types, $explicit_check = false)
115
    {
116
        // Initialize
117
        $mime_types = array();
118
        $max_q = 0;
119
        $preferred = false;
120
121
        // Load q values for all given content types
122
        foreach (array_unique($types) as $type) {
123
            $mime_types[$type] = request::accepts_at_quality($type, $explicit_check);
124
        }
125
126
        // Look for the highest q value
127
        foreach ($mime_types as $type => $q) {
128
            if ($q > $max_q) {
129
                $max_q = $q;
130
                $preferred = $type;
131
            }
132
        }
133
134
        return $preferred;
135
    }
136
137
    /**
138
     * Returns quality factor at which the client accepts content type.
139
     *
140
     * @param   string   content type (e.g. "image/jpg", "jpg")
141
     * @param   boolean  set to TRUE to disable wildcard checking
142
     * @return  integer|float
143
     */
144
    public static function accepts_at_quality($type = null, $explicit_check = false)
145
    {
146
        request::parse_accept_header();
147
148
        // Normalize type
149
        $type = strtolower((string) $type);
150
151
        // General content type (e.g. "jpg")
152
        if (strpos($type, '/') === false) {
153
            // Don't accept anything by default
154
            $q = 0;
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $q. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
155
156
            // Look up relevant mime types
157
            foreach ((array) Kohana::config('mimes.'.$type) as $type) {
158
                $q2 = request::accepts_at_quality($type, $explicit_check);
159
                $q = ($q2 > $q) ? $q2 : $q;
160
            }
161
162
            return $q;
163
        }
164
165
        // Content type with subtype given (e.g. "image/jpg")
166
        $type = explode('/', $type, 2);
167
168
        // Exact match
169
        if (isset(request::$accept_types[$type[0]][$type[1]])) {
170
            return request::$accept_types[$type[0]][$type[1]];
171
        }
172
173
        // Wildcard match (if not checking explicitly)
174 View Code Duplication
        if ($explicit_check === false and isset(request::$accept_types[$type[0]]['*'])) {
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...
175
            return request::$accept_types[$type[0]]['*'];
176
        }
177
178
        // Catch-all wildcard match (if not checking explicitly)
179 View Code Duplication
        if ($explicit_check === false and isset(request::$accept_types['*']['*'])) {
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...
180
            return request::$accept_types['*']['*'];
181
        }
182
183
        // Content type not accepted
184
        return 0;
185
    }
186
187
    /**
188
     * Parses client's HTTP Accept request header, and builds array structure representing it.
189
     *
190
     * @return  void
191
     */
192
    protected static function parse_accept_header()
0 ignored issues
show
Coding Style introduced by
parse_accept_header 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...
193
    {
194
        // Run this function just once
195
        if (request::$accept_types !== null) {
196
            return;
197
        }
198
199
        // Initialize accept_types array
200
        request::$accept_types = array();
201
202
        // No HTTP Accept header found
203
        if (empty($_SERVER['HTTP_ACCEPT'])) {
204
            // Accept everything
205
            request::$accept_types['*']['*'] = 1;
206
            return;
207
        }
208
209
        // Remove linebreaks and parse the HTTP Accept header
210
        foreach (explode(',', str_replace(array("\r", "\n"), '', $_SERVER['HTTP_ACCEPT'])) as $accept_entry) {
211
            // Explode each entry in content type and possible quality factor
212
            $accept_entry = explode(';', trim($accept_entry), 2);
213
214
            // Explode each content type (e.g. "text/html")
215
            $type = explode('/', $accept_entry[0], 2);
216
217
            // Skip invalid content types
218
            if (! isset($type[1])) {
219
                continue;
220
            }
221
222
            // Assume a default quality factor of 1 if no custom q value found
223
            $q = (isset($accept_entry[1]) and preg_match('~\bq\s*+=\s*+([.0-9]+)~', $accept_entry[1], $match)) ? (float) $match[1] : 1;
0 ignored issues
show
Bug introduced by
The variable $match does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
224
225
            // Populate accept_types array
226
            if (! isset(request::$accept_types[$type[0]][$type[1]]) or $q > request::$accept_types[$type[0]][$type[1]]) {
227
                request::$accept_types[$type[0]][$type[1]] = $q;
228
            }
229
        }
230
    }
231
} // End request
232