Passed
Pull Request — master (#431)
by El
02:52
created

Request::getData()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 19
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 13
nc 4
nop 0
dl 0
loc 19
ccs 12
cts 12
cp 1
crap 3
rs 9.8333
c 0
b 0
f 0
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 94
    public function __construct()
102
    {
103
        // decide if we are in JSON API or HTML context
104 94
        $this->_isJsonApi = $this->_detectJsonRequest();
105
106
        // parse parameters, depending on request type
107 94
        switch (array_key_exists('REQUEST_METHOD', $_SERVER) ? $_SERVER['REQUEST_METHOD'] : 'GET') {
108 94
            case 'DELETE':
109 93
            case 'PUT':
110 91
            case 'POST':
111 45
                $this->_params = Json::decode(
112 45
                    file_get_contents(self::$_inputStream)
113
                );
114 43
                break;
115
            default:
116 49
                $this->_params = $_GET;
117
        }
118
        if (
119 92
            !array_key_exists('pasteid', $this->_params) &&
120 92
            !array_key_exists('jsonld', $this->_params) &&
121 92
            array_key_exists('QUERY_STRING', $_SERVER) &&
122 92
            !empty($_SERVER['QUERY_STRING'])
123
        ) {
124 27
            $this->_params['pasteid'] = $this->getPasteId();
125
        }
126
127
        // prepare operation, depending on current parameters
128
        if (
129 92
            array_key_exists('ct', $this->_params) &&
130 92
            !empty($this->_params['ct'])
131
        ) {
132 38
            $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 18
                $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 92
    }
143
144
    /**
145
     * Get current operation
146
     *
147
     * @access public
148
     * @return string
149
     */
150 91
    public function getOperation()
151
    {
152 91
        return $this->_operation;
153
    }
154
155
    /**
156
     * Get data of paste or comment
157
     *
158
     * @access public
159
     * @return array
160
     */
161 36
    public function getData()
162
    {
163
        $data = array(
164 36
            'adata' => $this->getParam('adata', array()),
165
        );
166 36
        $required_keys = array('v', 'ct');
167 36
        $meta          = $this->getParam('meta', array());
168 36
        if (empty($meta)) {
169 10
            $required_keys[] = 'pasteid';
170 10
            $required_keys[] = 'parentid';
171
        } else {
172 26
            $data['meta'] = $meta;
173
        }
174 36
        foreach ($required_keys as $key) {
175 36
            $data[$key] = $this->getParam($key);
176
        }
177
        // forcing a cast to int or float
178 36
        $data['v'] = $data['v'] + 0;
179 36
        return $data;
180
    }
181
182
    /**
183
     * Get a request parameter
184
     *
185
     * @access public
186
     * @param  string $param
187
     * @param  string|array $default
188
     * @return string
189
     */
190 83
    public function getParam($param, $default = '')
191
    {
192 83
        return array_key_exists($param, $this->_params) ?
0 ignored issues
show
Bug Best Practice introduced by
The expression return array_key_exists(...rams[$param] : $default also could return the type array which is incompatible with the documented return type string.
Loading history...
193 83
            $this->_params[$param] : $default;
194
    }
195
196
    /**
197
     * Get request URI
198
     *
199
     * @access public
200
     * @return string
201
     */
202 80
    public function getRequestUri()
203
    {
204 80
        return array_key_exists('REQUEST_URI', $_SERVER) ?
205 1
            htmlspecialchars(
206 1
                parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH)
207 80
            ) : '/';
208
    }
209
210
    /**
211
     * If we are in a JSON API context
212
     *
213
     * @access public
214
     * @return bool
215
     */
216 86
    public function isJsonApiCall()
217
    {
218 86
        return $this->_isJsonApi;
219
    }
220
221
    /**
222
     * Override the default input stream source, used for unit testing
223
     *
224
     * @param string $input
225
     */
226 45
    public static function setInputStream($input)
227
    {
228 45
        self::$_inputStream = $input;
229 45
    }
230
231
    /**
232
     * Detect the clients supported media type and decide if its a JSON API call or not
233
     *
234
     * Adapted from: https://stackoverflow.com/questions/3770513/detect-browser-language-in-php#3771447
235
     *
236
     * @access private
237
     * @return bool
238
     */
239 94
    private function _detectJsonRequest()
240
    {
241 94
        $hasAcceptHeader = array_key_exists('HTTP_ACCEPT', $_SERVER);
242 94
        $acceptHeader    = $hasAcceptHeader ? $_SERVER['HTTP_ACCEPT'] : '';
243
244
        // simple cases
245
        if (
246 94
            (array_key_exists('HTTP_X_REQUESTED_WITH', $_SERVER) &&
247 59
                $_SERVER['HTTP_X_REQUESTED_WITH'] == 'JSONHttpRequest') ||
248 35
            ($hasAcceptHeader &&
249 35
                strpos($acceptHeader, self::MIME_JSON) !== false &&
250 35
                strpos($acceptHeader, self::MIME_HTML) === false &&
251 94
                strpos($acceptHeader, self::MIME_XHTML) === false)
252
        ) {
253 61
            return true;
254
        }
255
256
        // advanced case: media type negotiation
257 33
        $mediaTypes = array();
258 33
        if ($hasAcceptHeader) {
259 4
            $mediaTypeRanges = explode(',', trim($acceptHeader));
260 4
            foreach ($mediaTypeRanges as $mediaTypeRange) {
261 4
                if (preg_match(
262 4
                    '#(\*/\*|[a-z\-]+/[a-z\-+*]+(?:\s*;\s*[^q]\S*)*)(?:\s*;\s*q\s*=\s*(0(?:\.\d{0,3})|1(?:\.0{0,3})))?#',
263 4
                    trim($mediaTypeRange), $match
264
                )) {
265 4
                    if (!isset($match[2])) {
266 4
                        $match[2] = '1.0';
267
                    } else {
268 4
                        $match[2] = (string) floatval($match[2]);
269
                    }
270 4
                    if (!isset($mediaTypes[$match[2]])) {
271 4
                        $mediaTypes[$match[2]] = array();
272
                    }
273 4
                    $mediaTypes[$match[2]][] = strtolower($match[1]);
274
                }
275
            }
276 4
            krsort($mediaTypes);
277 4
            foreach ($mediaTypes as $acceptedQuality => $acceptedValues) {
278 4
                if ($acceptedQuality === 0.0) {
279
                    continue;
280
                }
281 4
                foreach ($acceptedValues as $acceptedValue) {
282
                    if (
283 4
                        strpos($acceptedValue, self::MIME_HTML) === 0 ||
284 4
                        strpos($acceptedValue, self::MIME_XHTML) === 0
285
                    ) {
286 2
                        return false;
287 2
                    } elseif (strpos($acceptedValue, self::MIME_JSON) === 0) {
288 2
                        return true;
289
                    }
290
                }
291
            }
292
        }
293 30
        return false;
294
    }
295
}
296