1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* @package Redcore |
4
|
|
|
* @subpackage Api |
5
|
|
|
* |
6
|
|
|
* @copyright Copyright (C) 2008 - 2021 redWEB.dk. All rights reserved. |
7
|
|
|
* @license GNU General Public License version 2 or later, see LICENSE. |
8
|
|
|
*/ |
9
|
|
|
|
10
|
|
|
defined('JPATH_BASE') or die; |
11
|
|
|
|
12
|
|
|
use Joomla\Utilities\ArrayHelper; |
13
|
|
|
|
14
|
|
|
/** |
15
|
|
|
* Interface to handle api calls |
16
|
|
|
* |
17
|
|
|
* @package Redcore |
18
|
|
|
* @subpackage Api |
19
|
|
|
* @since 1.2 |
20
|
|
|
*/ |
21
|
|
|
class RApi extends RApiBase |
22
|
|
|
{ |
23
|
|
|
/** |
24
|
|
|
* @var array RApi instances container. |
25
|
|
|
* @since 1.2 |
26
|
|
|
*/ |
27
|
|
|
public static $instances = array(); |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* @var string Name of the Api |
31
|
|
|
* @since 1.2 |
32
|
|
|
*/ |
33
|
|
|
public $apiName = ''; |
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* @var string Operation that will be preformed with this Api call. supported: CREATE, READ, UPDATE, DELETE |
37
|
|
|
* @since 1.2 |
38
|
|
|
*/ |
39
|
|
|
public $operation = 'read'; |
40
|
|
|
|
41
|
|
|
/** |
42
|
|
|
* The start time for measuring the execution time. |
43
|
|
|
* |
44
|
|
|
* @var float |
45
|
|
|
* @since 1.2 |
46
|
|
|
*/ |
47
|
|
|
public $startTime; |
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* Method to return a RApi instance based on the given options. There is one global option and then |
51
|
|
|
* the rest are specific to the Api. The 'api' option defines which RApi class is |
52
|
|
|
* used for, default is 'hal'. |
53
|
|
|
* |
54
|
|
|
* Instances are unique to the given options and new objects are only created when a unique options array is |
55
|
|
|
* passed into the method. This ensures that we don't end up with unnecessary api resources. |
56
|
|
|
* |
57
|
|
|
* @param array $options Parameters to be passed to the creating api. |
58
|
|
|
* |
59
|
|
|
* @return RApi Api object. |
60
|
|
|
* |
61
|
|
|
* @since 1.2 |
62
|
|
|
* @throws RuntimeException |
63
|
|
|
*/ |
64
|
|
|
public static function getInstance($options = array()) |
65
|
|
|
{ |
66
|
|
|
// Sanitize the api options. |
67
|
|
|
$options['api'] = (isset($options['api'])) ? preg_replace('/[^A-Z0-9_\.-]/i', '', $options['api']) : 'hal'; |
68
|
|
|
|
69
|
|
|
// Get the options signature for the api connector. |
70
|
|
|
$signature = md5(serialize($options)); |
71
|
|
|
|
72
|
|
|
// If we already have a api connector instance for these options then just use that. |
73
|
|
|
if (!empty(self::$instances[$signature])) |
74
|
|
|
{ |
75
|
|
|
return self::$instances[$signature]; |
76
|
|
|
} |
77
|
|
|
|
78
|
|
|
// Derive the class name from the driver. |
79
|
|
|
$class = 'RApi' . ucfirst(strtolower($options['api'])) . ucfirst(strtolower($options['api'])); |
80
|
|
|
|
81
|
|
|
// If the class still doesn't exist we have nothing left to do but throw an exception. |
82
|
|
|
if (!class_exists($class)) |
83
|
|
|
{ |
84
|
|
|
throw new RuntimeException(JText::sprintf('LIB_REDCORE_API_UNABLE_TO_LOAD_API', $options['api'])); |
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
// Create our new RApi connector based on the options given. |
88
|
|
|
try |
89
|
|
|
{ |
90
|
|
|
$instance = new $class($options); |
91
|
|
|
} |
92
|
|
|
catch (RuntimeException $e) |
93
|
|
|
{ |
94
|
|
|
throw new RuntimeException(JText::sprintf('LIB_REDCORE_API_UNABLE_TO_CONNECT_TO_API', $e->getMessage())); |
95
|
|
|
} |
96
|
|
|
|
97
|
|
|
// Set the new connector to the global instances based on signature. |
98
|
|
|
self::$instances[$signature] = $instance; |
99
|
|
|
|
100
|
|
|
return self::$instances[$signature]; |
101
|
|
|
} |
102
|
|
|
|
103
|
|
|
/** |
104
|
|
|
* Method to instantiate the file-based api call. |
105
|
|
|
* |
106
|
|
|
* @param mixed $options Optional custom options to load. JRegistry or array format |
107
|
|
|
* |
108
|
|
|
* @since 1.2 |
109
|
|
|
*/ |
110
|
|
|
public function __construct($options = null) |
111
|
|
|
{ |
112
|
|
|
$this->startTime = microtime(true); |
|
|
|
|
113
|
|
|
|
114
|
|
|
// Initialise / Load options |
115
|
|
|
$this->setOptions($options); |
116
|
|
|
|
117
|
|
|
// Main properties |
118
|
|
|
$this->setOptionsFromHeader(); |
119
|
|
|
|
120
|
|
|
// Main properties |
121
|
|
|
$this->setApi($this->options->get('api', 'hal')); |
122
|
|
|
|
123
|
|
|
// Load Library language |
124
|
|
|
$this->loadExtensionLanguage('lib_joomla', JPATH_ADMINISTRATOR); |
125
|
|
|
} |
126
|
|
|
|
127
|
|
|
/** |
128
|
|
|
* Set options received from Headers of the request |
129
|
|
|
* |
130
|
|
|
* @return RApi |
131
|
|
|
* |
132
|
|
|
* @since 1.7 |
133
|
|
|
*/ |
134
|
|
|
public function setOptionsFromHeader() |
135
|
|
|
{ |
136
|
|
|
$app = JFactory::getApplication(); |
137
|
|
|
$headers = self::getHeaderVariablesFromGlobals(); |
138
|
|
|
|
139
|
|
|
// Setting the language from the header options information |
140
|
|
|
if (isset($headers['ACCEPT_LANGUAGE'])) |
141
|
|
|
{ |
142
|
|
|
// We are only using header options if the URI does not contain lang parameter as it have higher priority |
143
|
|
|
if ($app->input->get('lang', '') == '') |
144
|
|
|
{ |
145
|
|
|
$acceptLanguages = explode(',', $headers['ACCEPT_LANGUAGE']); |
146
|
|
|
|
147
|
|
|
// We go through all proposed languages. First language that is found installed on the website is used |
148
|
|
|
foreach ($acceptLanguages as $acceptLanguage) |
149
|
|
|
{ |
150
|
|
|
$acceptLanguage = explode(';', $acceptLanguage); |
151
|
|
|
|
152
|
|
|
if (RTranslationHelper::setLanguage($acceptLanguage[0])) |
153
|
|
|
{ |
154
|
|
|
$this->options->set('lang', $acceptLanguage[0]); |
155
|
|
|
$app->input->set('lang', $acceptLanguage[0]); |
156
|
|
|
|
157
|
|
|
break; |
158
|
|
|
} |
159
|
|
|
} |
160
|
|
|
} |
161
|
|
|
} |
162
|
|
|
|
163
|
|
|
// Setting option for compressed output |
164
|
|
|
if (isset($headers['ACCEPT_ENCODING'])) |
165
|
|
|
{ |
166
|
|
|
$acceptCompression = strpos(strtolower($headers['ACCEPT_ENCODING']), 'gzip') !== false ? 1 : 0; |
167
|
|
|
$this->options->set('enable_gzip_compression', $acceptCompression); |
168
|
|
|
} |
169
|
|
|
|
170
|
|
|
// Setting option for compressed output |
171
|
|
|
if (isset($headers['CONTENT_ENCODING'])) |
172
|
|
|
{ |
173
|
|
|
$acceptCompression = strpos(strtolower($headers['CONTENT_ENCODING']), 'gzip') !== false ? 1 : 0; |
174
|
|
|
$this->options->set('enable_gzip_input_compression', $acceptCompression); |
175
|
|
|
} |
176
|
|
|
|
177
|
|
|
return $this; |
178
|
|
|
} |
179
|
|
|
|
180
|
|
|
/** |
181
|
|
|
* Change the Api |
182
|
|
|
* |
183
|
|
|
* @param string $apiName Api instance to render |
184
|
|
|
* |
185
|
|
|
* @return void |
186
|
|
|
* |
187
|
|
|
* @since 1.2 |
188
|
|
|
*/ |
189
|
|
|
public function setApi($apiName) |
190
|
|
|
{ |
191
|
|
|
$this->apiName = $apiName; |
192
|
|
|
} |
193
|
|
|
|
194
|
|
|
/** |
195
|
|
|
* Change the debug mode |
196
|
|
|
* |
197
|
|
|
* @param boolean $debug Enable / Disable debug |
198
|
|
|
* |
199
|
|
|
* @return void |
200
|
|
|
* |
201
|
|
|
* @since 1.2 |
202
|
|
|
*/ |
203
|
|
|
public function setDebug($debug) |
204
|
|
|
{ |
205
|
|
|
$this->options->set('debug', (boolean) $debug); |
206
|
|
|
} |
207
|
|
|
|
208
|
|
|
/** |
209
|
|
|
* Load extension language file. |
210
|
|
|
* |
211
|
|
|
* @param string $option Option name |
212
|
|
|
* @param string $path Path to language file |
213
|
|
|
* |
214
|
|
|
* @return object |
215
|
|
|
*/ |
216
|
|
|
public function loadExtensionLanguage($option, $path = JPATH_SITE) |
217
|
|
|
{ |
218
|
|
|
// Load common and local language files. |
219
|
|
|
$lang = JFactory::getLanguage(); |
220
|
|
|
|
221
|
|
|
// Load language file |
222
|
|
|
$lang->load($option, $path, null, false, false) |
223
|
|
|
|| $lang->load($option, $path . "/components/$option", null, false, false) |
224
|
|
|
|| $lang->load($option, $path, $lang->getDefault(), false, false) |
225
|
|
|
|| $lang->load($option, $path . "/components/$option", $lang->getDefault(), false, false); |
226
|
|
|
|
227
|
|
|
return $this; |
228
|
|
|
} |
229
|
|
|
|
230
|
|
|
/** |
231
|
|
|
* @return array |
232
|
|
|
* @since __DEPLOY_VERSION__ |
233
|
|
|
*/ |
234
|
|
|
private static function parsePut(): array |
235
|
|
|
{ |
236
|
|
|
$rawData = file_get_contents("php://input"); |
237
|
|
|
|
238
|
|
|
// Fetch content and determine boundary |
239
|
|
|
$boundary = substr($rawData, 0, strpos($rawData, "\r\n")); |
240
|
|
|
|
241
|
|
|
if (empty($boundary)) |
242
|
|
|
{ |
243
|
|
|
parse_str($rawData, $data); |
244
|
|
|
|
245
|
|
|
return $data; |
246
|
|
|
} |
247
|
|
|
|
248
|
|
|
// Fetch each part |
249
|
|
|
$parts = array_slice(explode($boundary, $rawData), 1); |
250
|
|
|
$str = []; |
251
|
|
|
$files = []; |
252
|
|
|
|
253
|
|
|
foreach ($parts as $part) |
254
|
|
|
{ |
255
|
|
|
// If this is the last part, break |
256
|
|
|
if ($part == "--\r\n") |
257
|
|
|
{ |
258
|
|
|
break; |
259
|
|
|
} |
260
|
|
|
|
261
|
|
|
// Separate content from headers |
262
|
|
|
$part = ltrim($part, "\r\n"); |
263
|
|
|
list($rawHeaders, $body) = explode("\r\n\r\n", $part, 2); |
264
|
|
|
|
265
|
|
|
// Parse the headers list |
266
|
|
|
$rawHeaders = explode("\r\n", $rawHeaders); |
267
|
|
|
$headers = []; |
268
|
|
|
|
269
|
|
|
foreach ($rawHeaders as $header) |
270
|
|
|
{ |
271
|
|
|
list($name, $value) = explode(':', $header); |
272
|
|
|
$headers[strtolower($name)] = ltrim($value, ' '); |
273
|
|
|
} |
274
|
|
|
|
275
|
|
|
// Parse the Content-Disposition to get the field name, etc. |
276
|
|
|
if (isset($headers['content-disposition'])) |
277
|
|
|
{ |
278
|
|
|
$filename = null; |
279
|
|
|
preg_match( |
280
|
|
|
'/^(.+); *name="([^"]+)"(; *filename="([^"]+)")?/', |
281
|
|
|
$headers['content-disposition'], |
282
|
|
|
$matches |
283
|
|
|
); |
284
|
|
|
list(, , $name) = $matches; |
285
|
|
|
|
286
|
|
|
// Parse File |
287
|
|
|
if (isset($matches[4])) |
288
|
|
|
{ |
289
|
|
|
// Get filename |
290
|
|
|
$filename = $matches[4]; |
291
|
|
|
|
292
|
|
|
// Get tmp name |
293
|
|
|
$tmpName = tempnam(ini_get('upload_tmp_dir'), rand()); |
294
|
|
|
$values = [ |
295
|
|
|
'error' => 0, |
296
|
|
|
'name' => $filename, |
297
|
|
|
'tmp_name' => $tmpName, |
298
|
|
|
'size' => strlen($body), |
299
|
|
|
'type' => trim($value), |
|
|
|
|
300
|
|
|
]; |
301
|
|
|
|
302
|
|
|
$exploded = explode('[', $name); |
303
|
|
|
|
304
|
|
|
$first = array_shift($exploded); |
305
|
|
|
|
306
|
|
|
if (!empty($exploded)) |
307
|
|
|
{ |
308
|
|
|
$last = '[' . implode('[', $exploded); |
309
|
|
|
} |
310
|
|
|
else |
311
|
|
|
{ |
312
|
|
|
$last = ''; |
313
|
|
|
} |
314
|
|
|
|
315
|
|
|
foreach ($values as $key => $val) |
316
|
|
|
{ |
317
|
|
|
$files[] = $first . '[' . $key . ']' . $last . '=' . $val; |
318
|
|
|
} |
319
|
|
|
|
320
|
|
|
// Place in temporary directory |
321
|
|
|
file_put_contents($tmpName, $body); |
322
|
|
|
|
323
|
|
|
// Register a shutdown function to cleanup the temporary file |
324
|
|
|
register_shutdown_function( |
325
|
|
|
function () use ($tmpName) |
326
|
|
|
{ |
327
|
|
|
unlink($tmpName); |
328
|
|
|
} |
329
|
|
|
); |
330
|
|
|
} |
331
|
|
|
|
332
|
|
|
// Parse Field |
333
|
|
|
else |
334
|
|
|
{ |
335
|
|
|
$str[] = $name . '=' . substr($body, 0, strlen($body) - 2); |
336
|
|
|
} |
337
|
|
|
} |
338
|
|
|
} |
339
|
|
|
|
340
|
|
|
parse_str(implode('&', $str), $data); |
341
|
|
|
parse_str(implode('&', $files), $list); |
342
|
|
|
$_FILES = array_replace($_FILES, $list); |
343
|
|
|
|
344
|
|
|
return $data; |
345
|
|
|
} |
346
|
|
|
|
347
|
|
|
/** |
348
|
|
|
* @param string $group Group |
349
|
|
|
* @param array|string $values Values |
350
|
|
|
* |
351
|
|
|
* @return array |
352
|
|
|
* @since __DEPLOY_VERSION__ |
353
|
|
|
*/ |
354
|
|
|
private static function rearrangeFiles(string $group, $values): array |
355
|
|
|
{ |
356
|
|
|
if (is_array($values)) |
357
|
|
|
{ |
358
|
|
|
$return = []; |
359
|
|
|
|
360
|
|
|
foreach ($values as $k => $v) |
361
|
|
|
{ |
362
|
|
|
$return[$k] = static::rearrangeFiles($group, $v); |
363
|
|
|
} |
364
|
|
|
|
365
|
|
|
return $return; |
366
|
|
|
} |
367
|
|
|
else |
368
|
|
|
{ |
369
|
|
|
return [ |
370
|
|
|
$group => $values, |
371
|
|
|
]; |
372
|
|
|
} |
373
|
|
|
} |
374
|
|
|
|
375
|
|
|
/** |
376
|
|
|
* Returns posted data in array format |
377
|
|
|
* |
378
|
|
|
* @return array |
379
|
|
|
* |
380
|
|
|
* @since 1.2 |
381
|
|
|
*/ |
382
|
|
|
public static function getPostedData() |
383
|
|
|
{ |
384
|
|
|
$headers = self::getHeaderVariablesFromGlobals(); |
385
|
|
|
$input = JFactory::getApplication()->input; |
386
|
|
|
$inputData = file_get_contents("php://input"); |
387
|
|
|
|
388
|
|
|
// Is data is compressed we will fetch it through separate function |
389
|
|
|
if (isset($headers['CONTENT_ENCODING'])) |
390
|
|
|
{ |
391
|
|
|
if (strpos(strtolower($headers['CONTENT_ENCODING']), 'gzip') !== false) |
392
|
|
|
{ |
393
|
|
|
$decompressed = gzdecode($inputData); |
394
|
|
|
|
395
|
|
|
if ($decompressed) |
396
|
|
|
{ |
397
|
|
|
$inputData = $decompressed; |
398
|
|
|
} |
399
|
|
|
} |
400
|
|
|
} |
401
|
|
|
|
402
|
|
|
if (is_object($inputData)) |
|
|
|
|
403
|
|
|
{ |
404
|
|
|
$inputData = ArrayHelper::fromObject($inputData); |
405
|
|
|
} |
406
|
|
|
elseif (is_string($inputData) && !empty($inputData)) |
407
|
|
|
{ |
408
|
|
|
$inputData = trim($inputData); |
409
|
|
|
$parsedData = null; |
410
|
|
|
|
411
|
|
|
// We try to transform it into JSON |
412
|
|
|
if ($dataJson = @json_decode($inputData, true)) |
413
|
|
|
{ |
414
|
|
|
if (json_last_error() == JSON_ERROR_NONE) |
415
|
|
|
{ |
416
|
|
|
$parsedData = (array) $dataJson; |
417
|
|
|
} |
418
|
|
|
} |
419
|
|
|
|
420
|
|
|
// We try to transform it into XML |
421
|
|
|
if (is_null($parsedData) && $xml = @simplexml_load_string($inputData)) |
422
|
|
|
{ |
423
|
|
|
$json = json_encode((array) $xml); |
424
|
|
|
$parsedData = json_decode($json, true); |
425
|
|
|
} |
426
|
|
|
|
427
|
|
|
// We try to transform it into Array |
428
|
|
|
if (is_null($parsedData)) |
429
|
|
|
{ |
430
|
|
|
$parsedData = static::parsePut(); |
431
|
|
|
} |
432
|
|
|
|
433
|
|
|
$inputData = $parsedData; |
434
|
|
|
} |
435
|
|
|
else |
436
|
|
|
{ |
437
|
|
|
$inputData = $input->post->getArray(); |
438
|
|
|
} |
439
|
|
|
|
440
|
|
|
$files = []; |
441
|
|
|
|
442
|
|
|
foreach ($_FILES as $fieldName => $keys) |
443
|
|
|
{ |
444
|
|
|
$files[$fieldName] = []; |
445
|
|
|
|
446
|
|
|
foreach ($keys as $key => $list) |
447
|
|
|
{ |
448
|
|
|
$files[$fieldName] = array_replace_recursive($files[$fieldName], static::rearrangeFiles($key, $list)); |
449
|
|
|
} |
450
|
|
|
} |
451
|
|
|
|
452
|
|
|
$inputData = array_replace_recursive($inputData, $files); |
453
|
|
|
|
454
|
|
|
$filter = JFilterInput::getInstance(array(), array(), 1, 1); |
455
|
|
|
|
456
|
|
|
// Filter data with JInput default filter in blacklist mode |
457
|
|
|
$postedData = new JInput($inputData, array('filter' => $filter)); |
458
|
|
|
|
459
|
|
|
if (version_compare(JVERSION, '3') >= 0) |
460
|
|
|
{ |
461
|
|
|
return $postedData->getArray(array(), null, 'HTML'); |
462
|
|
|
} |
463
|
|
|
elseif ($inputData) |
|
|
|
|
464
|
|
|
{ |
465
|
|
|
return $postedData->getArray(array(), $inputData, 'HTML'); |
466
|
|
|
} |
467
|
|
|
|
468
|
|
|
return array(); |
469
|
|
|
} |
470
|
|
|
|
471
|
|
|
/** |
472
|
|
|
* Execute the Api operation. |
473
|
|
|
* |
474
|
|
|
* @return mixed RApi object with information on success, boolean false on failure. |
475
|
|
|
* |
476
|
|
|
* @since 1.2 |
477
|
|
|
* @throws RuntimeException |
478
|
|
|
*/ |
479
|
|
|
public function execute() |
480
|
|
|
{ |
481
|
|
|
return null; |
482
|
|
|
} |
483
|
|
|
|
484
|
|
|
/** |
485
|
|
|
* Method to render the api call output. |
486
|
|
|
* |
487
|
|
|
* @return string Api call output |
488
|
|
|
* |
489
|
|
|
* @since 1.2 |
490
|
|
|
*/ |
491
|
|
|
public function render() |
492
|
|
|
{ |
493
|
|
|
return ''; |
494
|
|
|
} |
495
|
|
|
|
496
|
|
|
/** |
497
|
|
|
* Returns header variables from globals |
498
|
|
|
* |
499
|
|
|
* @return array |
500
|
|
|
*/ |
501
|
|
|
public static function getHeaderVariablesFromGlobals() |
502
|
|
|
{ |
503
|
|
|
$headers = array(); |
504
|
|
|
|
505
|
|
|
foreach ($_SERVER as $key => $value) |
506
|
|
|
{ |
507
|
|
|
if (strpos($key, 'HTTP_') === 0) |
508
|
|
|
{ |
509
|
|
|
$headers[substr($key, 5)] = $value; |
510
|
|
|
} |
511
|
|
|
// CONTENT_* are not prefixed with HTTP_ |
512
|
|
|
elseif (in_array($key, array('CONTENT_LENGTH', 'CONTENT_MD5', 'CONTENT_TYPE'))) |
513
|
|
|
{ |
514
|
|
|
$headers[$key] = $value; |
515
|
|
|
} |
516
|
|
|
} |
517
|
|
|
|
518
|
|
|
if (isset($_SERVER['PHP_AUTH_USER'])) |
519
|
|
|
{ |
520
|
|
|
$headers['PHP_AUTH_USER'] = $_SERVER['PHP_AUTH_USER']; |
521
|
|
|
$headers['PHP_AUTH_PW'] = isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : ''; |
522
|
|
|
} |
523
|
|
|
else |
524
|
|
|
{ |
525
|
|
|
/* |
526
|
|
|
* php-cgi under Apache does not pass HTTP Basic user/pass to PHP by default |
527
|
|
|
* For this workaround to work, add this line to your .htaccess file: |
528
|
|
|
* RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] |
529
|
|
|
* |
530
|
|
|
* A sample .htaccess file: |
531
|
|
|
* RewriteEngine On |
532
|
|
|
* RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] |
533
|
|
|
* RewriteCond %{REQUEST_FILENAME} !-f |
534
|
|
|
* RewriteRule ^(.*)$ app.php [QSA,L] |
535
|
|
|
*/ |
536
|
|
|
|
537
|
|
|
$authorizationHeader = null; |
538
|
|
|
|
539
|
|
|
if (isset($_SERVER['HTTP_AUTHORIZATION'])) |
540
|
|
|
{ |
541
|
|
|
$authorizationHeader = $_SERVER['HTTP_AUTHORIZATION']; |
542
|
|
|
} |
543
|
|
|
elseif (isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) |
544
|
|
|
{ |
545
|
|
|
$authorizationHeader = $_SERVER['REDIRECT_HTTP_AUTHORIZATION']; |
546
|
|
|
} |
547
|
|
|
elseif (function_exists('apache_request_headers')) |
548
|
|
|
{ |
549
|
|
|
$requestHeaders = (array) apache_request_headers(); |
550
|
|
|
|
551
|
|
|
// Server-side fix for bug in old Android versions (a nice side-effect of this fix means we don't care about capitalization for Authorization) |
552
|
|
|
$requestHeaders = array_combine(array_map('ucwords', array_keys($requestHeaders)), array_values($requestHeaders)); |
553
|
|
|
|
554
|
|
|
if (isset($requestHeaders['Authorization'])) |
555
|
|
|
{ |
556
|
|
|
$authorizationHeader = trim($requestHeaders['Authorization']); |
557
|
|
|
} |
558
|
|
|
} |
559
|
|
|
|
560
|
|
|
if (null !== $authorizationHeader) |
561
|
|
|
{ |
562
|
|
|
$headers['AUTHORIZATION'] = $authorizationHeader; |
563
|
|
|
|
564
|
|
|
// Decode AUTHORIZATION header into PHP_AUTH_USER and PHP_AUTH_PW when authorization header is basic |
565
|
|
|
if (0 === stripos($authorizationHeader, 'basic')) |
566
|
|
|
{ |
567
|
|
|
$exploded = explode(':', base64_decode(substr($authorizationHeader, 6))); |
568
|
|
|
|
569
|
|
|
if (count($exploded) == 2) |
570
|
|
|
{ |
571
|
|
|
list($headers['PHP_AUTH_USER'], $headers['PHP_AUTH_PW']) = $exploded; |
572
|
|
|
} |
573
|
|
|
} |
574
|
|
|
} |
575
|
|
|
} |
576
|
|
|
|
577
|
|
|
// PHP_AUTH_USER/PHP_AUTH_PW |
578
|
|
|
if (isset($headers['PHP_AUTH_USER'])) |
579
|
|
|
{ |
580
|
|
|
$headers['AUTHORIZATION'] = 'Basic ' . base64_encode($headers['PHP_AUTH_USER'] . ':' . $headers['PHP_AUTH_PW']); |
581
|
|
|
} |
582
|
|
|
|
583
|
|
|
return $headers; |
584
|
|
|
} |
585
|
|
|
|
586
|
|
|
/** |
587
|
|
|
* Method to clear any set response headers. |
588
|
|
|
* |
589
|
|
|
* @return void |
590
|
|
|
*/ |
591
|
|
|
public static function clearHeaders() |
592
|
|
|
{ |
593
|
|
|
if (version_compare(JVERSION, '3') >= 0) |
594
|
|
|
{ |
595
|
|
|
JFactory::getApplication()->clearHeaders(); |
596
|
|
|
} |
597
|
|
|
else |
598
|
|
|
{ |
599
|
|
|
JResponse::clearHeaders(); |
|
|
|
|
600
|
|
|
} |
601
|
|
|
} |
602
|
|
|
|
603
|
|
|
/** |
604
|
|
|
* Send the response headers. |
605
|
|
|
* |
606
|
|
|
* @return void |
607
|
|
|
*/ |
608
|
|
|
public static function sendHeaders() |
609
|
|
|
{ |
610
|
|
|
if (version_compare(JVERSION, '3') >= 0) |
611
|
|
|
{ |
612
|
|
|
JFactory::getApplication()->sendHeaders(); |
613
|
|
|
} |
614
|
|
|
else |
615
|
|
|
{ |
616
|
|
|
JResponse::sendHeaders(); |
|
|
|
|
617
|
|
|
} |
618
|
|
|
} |
619
|
|
|
|
620
|
|
|
/** |
621
|
|
|
* Method to set a response header. If the replace flag is set then all headers |
622
|
|
|
* with the given name will be replaced by the new one. The headers are stored |
623
|
|
|
* in an internal array to be sent when the site is sent to the browser. |
624
|
|
|
* |
625
|
|
|
* @param string $name The name of the header to set. |
626
|
|
|
* @param string $value The value of the header to set. |
627
|
|
|
* @param boolean $replace True to replace any headers with the same name. |
628
|
|
|
* |
629
|
|
|
* @return void |
630
|
|
|
*/ |
631
|
|
|
public static function setHeader($name, $value, $replace = false) |
632
|
|
|
{ |
633
|
|
|
if (version_compare(JVERSION, '3') >= 0) |
634
|
|
|
{ |
635
|
|
|
JFactory::getApplication()->setHeader($name, $value, $replace); |
636
|
|
|
} |
637
|
|
|
else |
638
|
|
|
{ |
639
|
|
|
JResponse::setHeader($name, $value, $replace); |
|
|
|
|
640
|
|
|
} |
641
|
|
|
} |
642
|
|
|
} |
643
|
|
|
|
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 theid
property of an instance of theAccount
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.