Passed
Push — master ( 6bb599...48560e )
by El
01:24 queued 10s
created

Request   B

Complexity

Total Complexity 49

Size/Duplication

Total Lines 245
Duplicated Lines 0 %

Test Coverage

Coverage 98.73%

Importance

Changes 0
Metric Value
eloc 84
dl 0
loc 245
ccs 78
cts 79
cp 0.9873
rs 8.48
c 0
b 0
f 0
wmc 49

8 Methods

Rating   Name   Duplication   Size   Complexity  
A getPasteId() 0 13 4
D __construct() 0 40 19
D _detectJsonRequest() 0 55 19
A getOperation() 0 3 1
A isJsonApiCall() 0 3 1
A getParam() 0 4 2
A getRequestUri() 0 4 2
A setInputStream() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like Request 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.

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 Request, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * PrivateBin
4
 *
5
 * a zero-knowledge paste bin
6
 *
7
 * @link      https://github.com/PrivateBin/PrivateBin
8
 * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
9
 * @license   https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
10
 * @version   1.2.1
11
 */
12
13
namespace PrivateBin;
14
15
/**
16
 * Request
17
 *
18
 * parses request parameters and provides helper functions for routing
19
 */
20
class Request
21
{
22
    /**
23
     * MIME type for JSON
24
     *
25
     * @const string
26
     */
27
    const MIME_JSON = 'application/json';
28
29
    /**
30
     * MIME type for HTML
31
     *
32
     * @const string
33
     */
34
    const MIME_HTML = 'text/html';
35
36
    /**
37
     * MIME type for XHTML
38
     *
39
     * @const string
40
     */
41
    const MIME_XHTML = 'application/xhtml+xml';
42
43
    /**
44
     * Input stream to use for PUT parameter parsing
45
     *
46
     * @access private
47
     * @var string
48
     */
49
    private static $_inputStream = 'php://input';
50
51
    /**
52
     * Operation to perform
53
     *
54
     * @access private
55
     * @var string
56
     */
57
    private $_operation = 'view';
58
59
    /**
60
     * Request parameters
61
     *
62
     * @access private
63
     * @var array
64
     */
65
    private $_params = array();
66
67
    /**
68
     * If we are in a JSON API context
69
     *
70
     * @access private
71
     * @var bool
72
     */
73
    private $_isJsonApi = false;
74
75
    /**
76
     * Return the paste ID of the current paste.
77
     *
78
     * @access private
79
     * @return string
80
     */
81 27
    private function getPasteId()
82
    {
83
        // RegEx to check for valid paste ID (16 base64 chars)
84 27
        $pasteIdRegEx = '/^[a-f0-9]{16}$/';
85
86 27
        foreach ($_GET as $key => $value) {
87
            // only return if value is empty and key matches RegEx
88 27
            if (($value === '') and preg_match($pasteIdRegEx, $key, $match)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
89 27
                return $match[0];
90
            }
91
        }
92
93 2
        return 'invalid id';
94
    }
95
96
    /**
97
     * Constructor
98
     *
99
     * @access public
100
     */
101 100
    public function __construct()
102
    {
103
        // decide if we are in JSON API or HTML context
104 100
        $this->_isJsonApi = $this->_detectJsonRequest();
105
106
        // parse parameters, depending on request type
107 100
        switch (array_key_exists('REQUEST_METHOD', $_SERVER) ? $_SERVER['REQUEST_METHOD'] : 'GET') {
108 100
            case 'DELETE':
109 99
            case 'PUT':
110 3
                parse_str(file_get_contents(self::$_inputStream), $this->_params);
111 3
                break;
112 97
            case 'POST':
113 50
                $this->_params = $_POST;
114 50
                break;
115
            default:
116 47
                $this->_params = $_GET;
117
        }
118
        if (
119 100
            !array_key_exists('pasteid', $this->_params) &&
120 100
            !array_key_exists('jsonld', $this->_params) &&
121 100
            array_key_exists('QUERY_STRING', $_SERVER) &&
122 100
            !empty($_SERVER['QUERY_STRING'])
123
        ) {
124 27
            $this->_params['pasteid'] = $this->getPasteId();
125
        }
126
127
        // prepare operation, depending on current parameters
128
        if (
129 100
            (array_key_exists('data', $this->_params) && !empty($this->_params['data'])) ||
130 100
            (array_key_exists('attachment', $this->_params) && !empty($this->_params['attachment']))
131
        ) {
132 46
            $this->_operation = 'create';
133 54
        } elseif (array_key_exists('pasteid', $this->_params) && !empty($this->_params['pasteid'])) {
134 40
            if (array_key_exists('deletetoken', $this->_params) && !empty($this->_params['deletetoken'])) {
135 20
                $this->_operation = 'delete';
136
            } else {
137 40
                $this->_operation = 'read';
138
            }
139 14
        } elseif (array_key_exists('jsonld', $this->_params) && !empty($this->_params['jsonld'])) {
140 5
            $this->_operation = 'jsonld';
141
        }
142 100
    }
143
144
    /**
145
     * Get current operation
146
     *
147
     * @access public
148
     * @return string
149
     */
150 99
    public function getOperation()
151
    {
152 99
        return $this->_operation;
153
    }
154
155
    /**
156
     * Get a request parameter
157
     *
158
     * @access public
159
     * @param  string $param
160
     * @param  string $default
161
     * @return string
162
     */
163 91
    public function getParam($param, $default = '')
164
    {
165 91
        return array_key_exists($param, $this->_params) ?
166 91
            $this->_params[$param] : $default;
167
    }
168
169
    /**
170
     * Get request URI
171
     *
172
     * @access public
173
     * @return string
174
     */
175 88
    public function getRequestUri()
176
    {
177 88
        return array_key_exists('REQUEST_URI', $_SERVER) ?
178 88
            htmlspecialchars($_SERVER['REQUEST_URI']) : '/';
179
    }
180
181
    /**
182
     * If we are in a JSON API context
183
     *
184
     * @access public
185
     * @return bool
186
     */
187 94
    public function isJsonApiCall()
188
    {
189 94
        return $this->_isJsonApi;
190
    }
191
192
    /**
193
     * Override the default input stream source, used for unit testing
194
     *
195
     * @param string $input
196
     */
197 3
    public static function setInputStream($input)
198
    {
199 3
        self::$_inputStream = $input;
200 3
    }
201
202
    /**
203
     * Detect the clients supported media type and decide if its a JSON API call or not
204
     *
205
     * Adapted from: https://stackoverflow.com/questions/3770513/detect-browser-language-in-php#3771447
206
     *
207
     * @access private
208
     * @return bool
209
     */
210 100
    private function _detectJsonRequest()
211
    {
212 100
        $hasAcceptHeader = array_key_exists('HTTP_ACCEPT', $_SERVER);
213 100
        $acceptHeader    = $hasAcceptHeader ? $_SERVER['HTTP_ACCEPT'] : '';
214
215
        // simple cases
216
        if (
217 100
            (array_key_exists('HTTP_X_REQUESTED_WITH', $_SERVER) &&
218 65
                $_SERVER['HTTP_X_REQUESTED_WITH'] == 'JSONHttpRequest') ||
219 35
            ($hasAcceptHeader &&
220 35
                strpos($acceptHeader, self::MIME_JSON) !== false &&
221 35
                strpos($acceptHeader, self::MIME_HTML) === false &&
222 100
                strpos($acceptHeader, self::MIME_XHTML) === false)
223
        ) {
224 67
            return true;
225
        }
226
227
        // advanced case: media type negotiation
228 33
        $mediaTypes = array();
229 33
        if ($hasAcceptHeader) {
230 4
            $mediaTypeRanges = explode(',', trim($acceptHeader));
231 4
            foreach ($mediaTypeRanges as $mediaTypeRange) {
232 4
                if (preg_match(
233 4
                    '#(\*/\*|[a-z\-]+/[a-z\-+*]+(?:\s*;\s*[^q]\S*)*)(?:\s*;\s*q\s*=\s*(0(?:\.\d{0,3})|1(?:\.0{0,3})))?#',
234 4
                    trim($mediaTypeRange), $match
235
                )) {
236 4
                    if (!isset($match[2])) {
237 4
                        $match[2] = '1.0';
238
                    } else {
239 4
                        $match[2] = (string) floatval($match[2]);
240
                    }
241 4
                    if (!isset($mediaTypes[$match[2]])) {
242 4
                        $mediaTypes[$match[2]] = array();
243
                    }
244 4
                    $mediaTypes[$match[2]][] = strtolower($match[1]);
245
                }
246
            }
247 4
            krsort($mediaTypes);
248 4
            foreach ($mediaTypes as $acceptedQuality => $acceptedValues) {
249 4
                if ($acceptedQuality === 0.0) {
250
                    continue;
251
                }
252 4
                foreach ($acceptedValues as $acceptedValue) {
253
                    if (
254 4
                        strpos($acceptedValue, self::MIME_HTML) === 0 ||
255 4
                        strpos($acceptedValue, self::MIME_XHTML) === 0
256
                    ) {
257 2
                        return false;
258 2
                    } elseif (strpos($acceptedValue, self::MIME_JSON) === 0) {
259 2
                        return true;
260
                    }
261
                }
262
            }
263
        }
264 30
        return false;
265
    }
266
}
267