Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
Total | |
0.00% |
0 / 1 |
|
70.00% |
7 / 10 |
CRAP | |
96.47% |
82 / 85 |
Request | |
0.00% |
0 / 1 |
|
70.00% |
7 / 10 |
52 | |
96.47% |
82 / 85 |
getPasteId | |
100.00% |
1 / 1 |
4 | |
100.00% |
5 / 5 |
|||
__construct | |
100.00% |
1 / 1 |
16 | |
100.00% |
23 / 23 |
|||
getOperation | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
getData | |
100.00% |
1 / 1 |
4 | |
100.00% |
11 / 11 |
|||
getParam | |
100.00% |
1 / 1 |
2 | |
100.00% |
2 / 2 |
|||
getHost | |
0.00% |
0 / 1 |
2.15 | |
66.67% |
2 / 3 |
|||
getRequestUri | |
0.00% |
0 / 1 |
2.06 | |
75.00% |
3 / 4 |
|||
isJsonApiCall | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
setInputStream | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
_detectJsonRequest | |
0.00% |
0 / 1 |
19 | |
96.97% |
32 / 33 |
<?php | |
/** | |
* PrivateBin | |
* | |
* a zero-knowledge paste bin | |
* | |
* @link https://github.com/PrivateBin/PrivateBin | |
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net) | |
* @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License | |
* @version 1.4.0 | |
*/ | |
namespace PrivateBin; | |
/** | |
* Request | |
* | |
* parses request parameters and provides helper functions for routing | |
*/ | |
class Request | |
{ | |
/** | |
* MIME type for JSON | |
* | |
* @const string | |
*/ | |
const MIME_JSON = 'application/json'; | |
/** | |
* MIME type for HTML | |
* | |
* @const string | |
*/ | |
const MIME_HTML = 'text/html'; | |
/** | |
* MIME type for XHTML | |
* | |
* @const string | |
*/ | |
const MIME_XHTML = 'application/xhtml+xml'; | |
/** | |
* Input stream to use for PUT parameter parsing | |
* | |
* @access private | |
* @var string | |
*/ | |
private static $_inputStream = 'php://input'; | |
/** | |
* Operation to perform | |
* | |
* @access private | |
* @var string | |
*/ | |
private $_operation = 'view'; | |
/** | |
* Request parameters | |
* | |
* @access private | |
* @var array | |
*/ | |
private $_params = array(); | |
/** | |
* If we are in a JSON API context | |
* | |
* @access private | |
* @var bool | |
*/ | |
private $_isJsonApi = false; | |
/** | |
* Return the paste ID of the current paste. | |
* | |
* @access private | |
* @return string | |
*/ | |
private function getPasteId() | |
{ | |
// RegEx to check for valid paste ID (16 base64 chars) | |
$pasteIdRegEx = '/^[a-f0-9]{16}$/'; | |
foreach ($_GET as $key => $value) { | |
// only return if value is empty and key matches RegEx | |
if (($value === '') and preg_match($pasteIdRegEx, $key, $match)) { | |
return $match[0]; | |
} | |
} | |
return 'invalid id'; | |
} | |
/** | |
* Constructor | |
* | |
* @access public | |
*/ | |
public function __construct() | |
{ | |
// decide if we are in JSON API or HTML context | |
$this->_isJsonApi = $this->_detectJsonRequest(); | |
// parse parameters, depending on request type | |
switch (array_key_exists('REQUEST_METHOD', $_SERVER) ? $_SERVER['REQUEST_METHOD'] : 'GET') { | |
case 'DELETE': | |
case 'PUT': | |
case 'POST': | |
// it might be a creation or a deletion, the latter is detected below | |
$this->_operation = 'create'; | |
$this->_params = Json::decode( | |
file_get_contents(self::$_inputStream) | |
); | |
break; | |
default: | |
$this->_params = $_GET; | |
} | |
if ( | |
!array_key_exists('pasteid', $this->_params) && | |
!array_key_exists('jsonld', $this->_params) && | |
array_key_exists('QUERY_STRING', $_SERVER) && | |
!empty($_SERVER['QUERY_STRING']) | |
) { | |
$this->_params['pasteid'] = $this->getPasteId(); | |
} | |
// prepare operation, depending on current parameters | |
if (array_key_exists('pasteid', $this->_params) && !empty($this->_params['pasteid'])) { | |
if (array_key_exists('deletetoken', $this->_params) && !empty($this->_params['deletetoken'])) { | |
$this->_operation = 'delete'; | |
} elseif ($this->_operation != 'create') { | |
$this->_operation = 'read'; | |
} | |
} elseif (array_key_exists('jsonld', $this->_params) && !empty($this->_params['jsonld'])) { | |
$this->_operation = 'jsonld'; | |
} | |
} | |
/** | |
* Get current operation | |
* | |
* @access public | |
* @return string | |
*/ | |
public function getOperation() | |
{ | |
return $this->_operation; | |
} | |
/** | |
* Get data of paste or comment | |
* | |
* @access public | |
* @return array | |
*/ | |
public function getData() | |
{ | |
$data = array( | |
'adata' => $this->getParam('adata'), | |
); | |
$required_keys = array('v', 'ct'); | |
$meta = $this->getParam('meta'); | |
if (empty($meta)) { | |
$required_keys[] = 'pasteid'; | |
$required_keys[] = 'parentid'; | |
} else { | |
$data['meta'] = $meta; | |
} | |
foreach ($required_keys as $key) { | |
$data[$key] = $this->getParam($key, $key == 'v' ? 1 : ''); | |
} | |
// forcing a cast to int or float | |
$data['v'] = $data['v'] + 0; | |
return $data; | |
} | |
/** | |
* Get a request parameter | |
* | |
* @access public | |
* @param string $param | |
* @param string $default | |
* @return string | |
*/ | |
public function getParam($param, $default = '') | |
{ | |
return array_key_exists($param, $this->_params) ? | |
$this->_params[$param] : $default; | |
} | |
/** | |
* Get host as requested by the client | |
* | |
* @access public | |
* @return string | |
*/ | |
public function getHost() | |
{ | |
return array_key_exists('HTTP_HOST', $_SERVER) ? | |
htmlspecialchars($_SERVER['HTTP_HOST']) : | |
'localhost'; | |
} | |
/** | |
* Get request URI | |
* | |
* @access public | |
* @return string | |
*/ | |
public function getRequestUri() | |
{ | |
return array_key_exists('REQUEST_URI', $_SERVER) ? | |
htmlspecialchars( | |
parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) | |
) : '/'; | |
} | |
/** | |
* If we are in a JSON API context | |
* | |
* @access public | |
* @return bool | |
*/ | |
public function isJsonApiCall() | |
{ | |
return $this->_isJsonApi; | |
} | |
/** | |
* Override the default input stream source, used for unit testing | |
* | |
* @param string $input | |
*/ | |
public static function setInputStream($input) | |
{ | |
self::$_inputStream = $input; | |
} | |
/** | |
* Detect the clients supported media type and decide if its a JSON API call or not | |
* | |
* Adapted from: https://stackoverflow.com/questions/3770513/detect-browser-language-in-php#3771447 | |
* | |
* @access private | |
* @return bool | |
*/ | |
private function _detectJsonRequest() | |
{ | |
$hasAcceptHeader = array_key_exists('HTTP_ACCEPT', $_SERVER); | |
$acceptHeader = $hasAcceptHeader ? $_SERVER['HTTP_ACCEPT'] : ''; | |
// simple cases | |
if ( | |
(array_key_exists('HTTP_X_REQUESTED_WITH', $_SERVER) && | |
$_SERVER['HTTP_X_REQUESTED_WITH'] == 'JSONHttpRequest') || | |
($hasAcceptHeader && | |
strpos($acceptHeader, self::MIME_JSON) !== false && | |
strpos($acceptHeader, self::MIME_HTML) === false && | |
strpos($acceptHeader, self::MIME_XHTML) === false) | |
) { | |
return true; | |
} | |
// advanced case: media type negotiation | |
$mediaTypes = array(); | |
if ($hasAcceptHeader) { | |
$mediaTypeRanges = explode(',', trim($acceptHeader)); | |
foreach ($mediaTypeRanges as $mediaTypeRange) { | |
if (preg_match( | |
'#(\*/\*|[a-z\-]+/[a-z\-+*]+(?:\s*;\s*[^q]\S*)*)(?:\s*;\s*q\s*=\s*(0(?:\.\d{0,3})|1(?:\.0{0,3})))?#', | |
trim($mediaTypeRange), $match | |
)) { | |
if (!isset($match[2])) { | |
$match[2] = '1.0'; | |
} else { | |
$match[2] = (string) floatval($match[2]); | |
} | |
if (!isset($mediaTypes[$match[2]])) { | |
$mediaTypes[$match[2]] = array(); | |
} | |
$mediaTypes[$match[2]][] = strtolower($match[1]); | |
} | |
} | |
krsort($mediaTypes); | |
foreach ($mediaTypes as $acceptedQuality => $acceptedValues) { | |
if ($acceptedQuality === '0.0') { | |
continue; | |
} | |
foreach ($acceptedValues as $acceptedValue) { | |
if ( | |
strpos($acceptedValue, self::MIME_HTML) === 0 || | |
strpos($acceptedValue, self::MIME_XHTML) === 0 | |
) { | |
return false; | |
} elseif (strpos($acceptedValue, self::MIME_JSON) === 0) { | |
return true; | |
} | |
} | |
} | |
} | |
return false; | |
} | |
} |