1
0

Request.php 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  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.5.1
  11. */
  12. namespace PrivateBin;
  13. use Exception;
  14. /**
  15. * Request
  16. *
  17. * parses request parameters and provides helper functions for routing
  18. */
  19. class Request
  20. {
  21. /**
  22. * MIME type for JSON
  23. *
  24. * @const string
  25. */
  26. const MIME_JSON = 'application/json';
  27. /**
  28. * MIME type for HTML
  29. *
  30. * @const string
  31. */
  32. const MIME_HTML = 'text/html';
  33. /**
  34. * MIME type for XHTML
  35. *
  36. * @const string
  37. */
  38. const MIME_XHTML = 'application/xhtml+xml';
  39. /**
  40. * Input stream to use for PUT parameter parsing
  41. *
  42. * @access private
  43. * @var string
  44. */
  45. private static $_inputStream = 'php://input';
  46. /**
  47. * Operation to perform
  48. *
  49. * @access private
  50. * @var string
  51. */
  52. private $_operation = 'view';
  53. /**
  54. * Request parameters
  55. *
  56. * @access private
  57. * @var array
  58. */
  59. private $_params = array();
  60. /**
  61. * If we are in a JSON API context
  62. *
  63. * @access private
  64. * @var bool
  65. */
  66. private $_isJsonApi = false;
  67. /**
  68. * Return the paste ID of the current paste.
  69. *
  70. * @access private
  71. * @return string
  72. */
  73. private function getPasteId()
  74. {
  75. // RegEx to check for valid paste ID (16 base64 chars)
  76. $pasteIdRegEx = '/^[a-f0-9]{16}$/';
  77. foreach ($_GET as $key => $value) {
  78. // only return if value is empty and key matches RegEx
  79. if (($value === '') and preg_match($pasteIdRegEx, $key, $match)) {
  80. return $match[0];
  81. }
  82. }
  83. return 'invalid id';
  84. }
  85. /**
  86. * Constructor
  87. *
  88. * @access public
  89. */
  90. public function __construct()
  91. {
  92. // decide if we are in JSON API or HTML context
  93. $this->_isJsonApi = $this->_detectJsonRequest();
  94. // parse parameters, depending on request type
  95. switch (array_key_exists('REQUEST_METHOD', $_SERVER) ? $_SERVER['REQUEST_METHOD'] : 'GET') {
  96. case 'DELETE':
  97. case 'PUT':
  98. case 'POST':
  99. // it might be a creation or a deletion, the latter is detected below
  100. $this->_operation = 'create';
  101. try {
  102. $this->_params = Json::decode(
  103. file_get_contents(self::$_inputStream)
  104. );
  105. } catch (Exception $e) {
  106. // ignore error, $this->_params will remain empty
  107. }
  108. break;
  109. default:
  110. $this->_params = $_GET;
  111. }
  112. if (
  113. !array_key_exists('pasteid', $this->_params) &&
  114. !array_key_exists('jsonld', $this->_params) &&
  115. !array_key_exists('link', $this->_params) &&
  116. array_key_exists('QUERY_STRING', $_SERVER) &&
  117. !empty($_SERVER['QUERY_STRING'])
  118. ) {
  119. $this->_params['pasteid'] = $this->getPasteId();
  120. }
  121. // prepare operation, depending on current parameters
  122. if (array_key_exists('pasteid', $this->_params) && !empty($this->_params['pasteid'])) {
  123. if (array_key_exists('deletetoken', $this->_params) && !empty($this->_params['deletetoken'])) {
  124. $this->_operation = 'delete';
  125. } elseif ($this->_operation != 'create') {
  126. $this->_operation = 'read';
  127. }
  128. } elseif (array_key_exists('jsonld', $this->_params) && !empty($this->_params['jsonld'])) {
  129. $this->_operation = 'jsonld';
  130. } elseif (array_key_exists('link', $this->_params) && !empty($this->_params['link'])) {
  131. if (strpos($this->getRequestUri(), '/shortenviayourls') !== false) {
  132. $this->_operation = 'yourlsproxy';
  133. }
  134. }
  135. }
  136. /**
  137. * Get current operation
  138. *
  139. * @access public
  140. * @return string
  141. */
  142. public function getOperation()
  143. {
  144. return $this->_operation;
  145. }
  146. /**
  147. * Get data of paste or comment
  148. *
  149. * @access public
  150. * @return array
  151. */
  152. public function getData()
  153. {
  154. $data = array(
  155. 'adata' => $this->getParam('adata'),
  156. );
  157. $required_keys = array('v', 'ct');
  158. $meta = $this->getParam('meta');
  159. if (empty($meta)) {
  160. $required_keys[] = 'pasteid';
  161. $required_keys[] = 'parentid';
  162. } else {
  163. $data['meta'] = $meta;
  164. }
  165. foreach ($required_keys as $key) {
  166. $data[$key] = $this->getParam($key, $key == 'v' ? 1 : '');
  167. }
  168. // forcing a cast to int or float
  169. $data['v'] = $data['v'] + 0;
  170. return $data;
  171. }
  172. /**
  173. * Get a request parameter
  174. *
  175. * @access public
  176. * @param string $param
  177. * @param string $default
  178. * @return string
  179. */
  180. public function getParam($param, $default = '')
  181. {
  182. return array_key_exists($param, $this->_params) ?
  183. $this->_params[$param] : $default;
  184. }
  185. /**
  186. * Get host as requested by the client
  187. *
  188. * @access public
  189. * @return string
  190. */
  191. public function getHost()
  192. {
  193. return array_key_exists('HTTP_HOST', $_SERVER) ?
  194. htmlspecialchars($_SERVER['HTTP_HOST']) :
  195. 'localhost';
  196. }
  197. /**
  198. * Get request URI
  199. *
  200. * @access public
  201. * @return string
  202. */
  203. public function getRequestUri()
  204. {
  205. return array_key_exists('REQUEST_URI', $_SERVER) ?
  206. htmlspecialchars(
  207. parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH)
  208. ) : '/';
  209. }
  210. /**
  211. * If we are in a JSON API context
  212. *
  213. * @access public
  214. * @return bool
  215. */
  216. public function isJsonApiCall()
  217. {
  218. return $this->_isJsonApi;
  219. }
  220. /**
  221. * Override the default input stream source, used for unit testing
  222. *
  223. * @param string $input
  224. */
  225. public static function setInputStream($input)
  226. {
  227. self::$_inputStream = $input;
  228. }
  229. /**
  230. * Detect the clients supported media type and decide if its a JSON API call or not
  231. *
  232. * Adapted from: https://stackoverflow.com/questions/3770513/detect-browser-language-in-php#3771447
  233. *
  234. * @access private
  235. * @return bool
  236. */
  237. private function _detectJsonRequest()
  238. {
  239. $hasAcceptHeader = array_key_exists('HTTP_ACCEPT', $_SERVER);
  240. $acceptHeader = $hasAcceptHeader ? $_SERVER['HTTP_ACCEPT'] : '';
  241. // simple cases
  242. if (
  243. (array_key_exists('HTTP_X_REQUESTED_WITH', $_SERVER) &&
  244. $_SERVER['HTTP_X_REQUESTED_WITH'] == 'JSONHttpRequest') ||
  245. ($hasAcceptHeader &&
  246. strpos($acceptHeader, self::MIME_JSON) !== false &&
  247. strpos($acceptHeader, self::MIME_HTML) === false &&
  248. strpos($acceptHeader, self::MIME_XHTML) === false)
  249. ) {
  250. return true;
  251. }
  252. // advanced case: media type negotiation
  253. $mediaTypes = array();
  254. if ($hasAcceptHeader) {
  255. $mediaTypeRanges = explode(',', trim($acceptHeader));
  256. foreach ($mediaTypeRanges as $mediaTypeRange) {
  257. if (preg_match(
  258. '#(\*/\*|[a-z\-]+/[a-z\-+*]+(?:\s*;\s*[^q]\S*)*)(?:\s*;\s*q\s*=\s*(0(?:\.\d{0,3})|1(?:\.0{0,3})))?#',
  259. trim($mediaTypeRange), $match
  260. )) {
  261. if (!isset($match[2])) {
  262. $match[2] = '1.0';
  263. } else {
  264. $match[2] = (string) floatval($match[2]);
  265. }
  266. if (!isset($mediaTypes[$match[2]])) {
  267. $mediaTypes[$match[2]] = array();
  268. }
  269. $mediaTypes[$match[2]][] = strtolower($match[1]);
  270. }
  271. }
  272. krsort($mediaTypes);
  273. foreach ($mediaTypes as $acceptedQuality => $acceptedValues) {
  274. if ($acceptedQuality === '0.0') {
  275. continue;
  276. }
  277. foreach ($acceptedValues as $acceptedValue) {
  278. if (
  279. strpos($acceptedValue, self::MIME_HTML) === 0 ||
  280. strpos($acceptedValue, self::MIME_XHTML) === 0
  281. ) {
  282. return false;
  283. } elseif (strpos($acceptedValue, self::MIME_JSON) === 0) {
  284. return true;
  285. }
  286. }
  287. }
  288. }
  289. return false;
  290. }
  291. }