RequestTest.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. <?php declare(strict_types=1);
  2. use PHPUnit\Framework\TestCase;
  3. use PrivateBin\Request;
  4. class RequestTest extends TestCase
  5. {
  6. public function reset()
  7. {
  8. $_SERVER = [];
  9. $_GET = [];
  10. $_POST = [];
  11. }
  12. /**
  13. * Returns random query safe characters.
  14. *
  15. * @access public
  16. * @return string
  17. */
  18. public function getRandomQueryChars()
  19. {
  20. $queryChars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ=';
  21. $queryCharCount = strlen($queryChars) - 1;
  22. $resultLength = random_int(1, 10);
  23. $result = '';
  24. for ($i = 0; $i < $resultLength; ++$i) {
  25. $result .= $queryChars[random_int(0, $queryCharCount)];
  26. }
  27. return $result;
  28. }
  29. public function testView()
  30. {
  31. $this->reset();
  32. $_SERVER['REQUEST_METHOD'] = 'GET';
  33. $request = new Request;
  34. $this->assertFalse($request->isJsonApiCall(), 'is HTML call');
  35. $this->assertEquals('view', $request->getOperation());
  36. }
  37. public function testRead()
  38. {
  39. $this->reset();
  40. $id = Helper::getRandomId();
  41. $_SERVER['REQUEST_METHOD'] = 'GET';
  42. $_SERVER['QUERY_STRING'] = $id;
  43. $_GET[$id] = '';
  44. $request = new Request;
  45. $this->assertFalse($request->isJsonApiCall(), 'is HTML call');
  46. $this->assertEquals($id, $request->getParam('pasteid'));
  47. $this->assertEquals('read', $request->getOperation());
  48. }
  49. /**
  50. * paste IDs are 8 bytes hex encoded strings, if unlucky, this turns into
  51. * a numeric string that PHP will cast to an int, for example in array keys
  52. * @see https://www.php.net/manual/en/language.types.array.php
  53. */
  54. public function testReadNumeric()
  55. {
  56. $this->reset();
  57. $id = '1234567812345678';
  58. $_SERVER['REQUEST_METHOD'] = 'GET';
  59. $_SERVER['QUERY_STRING'] = $id;
  60. $_GET[$id] = '';
  61. $request = new Request;
  62. $this->assertFalse($request->isJsonApiCall(), 'is HTML call');
  63. $this->assertEquals($id, $request->getParam('pasteid'));
  64. $this->assertEquals('read', $request->getOperation());
  65. }
  66. public function testDelete()
  67. {
  68. $this->reset();
  69. $id = Helper::getRandomId();
  70. $_SERVER['REQUEST_METHOD'] = 'GET';
  71. $_GET['pasteid'] = $id;
  72. $_GET['deletetoken'] = 'bar';
  73. $request = new Request;
  74. $this->assertFalse($request->isJsonApiCall(), 'is HTML call');
  75. $this->assertEquals('delete', $request->getOperation());
  76. $this->assertEquals($id, $request->getParam('pasteid'));
  77. $this->assertEquals('bar', $request->getParam('deletetoken'));
  78. }
  79. public function testApiCreate()
  80. {
  81. $this->reset();
  82. $_SERVER['REQUEST_METHOD'] = 'PUT';
  83. $_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
  84. $file = Helper::createTempFile();
  85. file_put_contents($file, '{"ct":"foo"}');
  86. Request::setInputStream($file);
  87. $request = new Request;
  88. unlink($file);
  89. $this->assertTrue($request->isJsonApiCall(), 'is JSON API call');
  90. $this->assertEquals('create', $request->getOperation());
  91. $this->assertEquals('foo', $request->getParam('ct'));
  92. }
  93. public function testApiCreateAlternative()
  94. {
  95. $this->reset();
  96. $_SERVER['REQUEST_METHOD'] = 'POST';
  97. $_SERVER['HTTP_ACCEPT'] = 'application/json, text/javascript, */*; q=0.01';
  98. $file = Helper::createTempFile();
  99. file_put_contents($file, '{"ct":"foo"}');
  100. Request::setInputStream($file);
  101. $request = new Request;
  102. $this->assertTrue($request->isJsonApiCall(), 'is JSON API call');
  103. $this->assertEquals('create', $request->getOperation());
  104. $this->assertEquals('foo', $request->getParam('ct'));
  105. }
  106. public function testApiRead()
  107. {
  108. $this->reset();
  109. $id = Helper::getRandomId();
  110. $_SERVER['REQUEST_METHOD'] = 'GET';
  111. $_SERVER['HTTP_ACCEPT'] = 'application/json, text/javascript, */*; q=0.01';
  112. $_SERVER['QUERY_STRING'] = $id;
  113. $_GET[$id] = '';
  114. $request = new Request;
  115. $this->assertTrue($request->isJsonApiCall(), 'is JSON API call');
  116. $this->assertEquals($id, $request->getParam('pasteid'));
  117. $this->assertEquals('read', $request->getOperation());
  118. }
  119. public function testApiDelete()
  120. {
  121. $this->reset();
  122. $id = Helper::getRandomId();
  123. $_SERVER['REQUEST_METHOD'] = 'POST';
  124. $_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
  125. $_SERVER['QUERY_STRING'] = $id;
  126. $_GET = [$id => ''];
  127. $file = Helper::createTempFile();
  128. file_put_contents($file, '{"deletetoken":"bar"}');
  129. Request::setInputStream($file);
  130. $request = new Request;
  131. $this->assertTrue($request->isJsonApiCall(), 'is JSON API call');
  132. $this->assertEquals('delete', $request->getOperation());
  133. $this->assertEquals($id, $request->getParam('pasteid'));
  134. $this->assertEquals('bar', $request->getParam('deletetoken'));
  135. }
  136. public function testPostGarbage()
  137. {
  138. $this->reset();
  139. $_SERVER['REQUEST_METHOD'] = 'POST';
  140. $file = Helper::createTempFile();
  141. file_put_contents($file, random_bytes(256));
  142. Request::setInputStream($file);
  143. $request = new Request;
  144. unlink($file);
  145. $this->assertFalse($request->isJsonApiCall(), 'is HTML call');
  146. $this->assertEquals('create', $request->getOperation());
  147. }
  148. public function testPostScalarJson()
  149. {
  150. // a valid JSON scalar body (number, boolean or quoted string) must not
  151. // be treated as a set of parameters, so that it is rejected cleanly
  152. // instead of triggering a type error further down
  153. foreach (['1', '0.0', 'true', 'false', '"example"'] as $scalar) {
  154. $this->reset();
  155. $_SERVER['REQUEST_METHOD'] = 'POST';
  156. $_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
  157. $file = Helper::createTempFile();
  158. file_put_contents($file, $scalar);
  159. Request::setInputStream($file);
  160. $request = new Request;
  161. unlink($file);
  162. $this->assertTrue($request->isJsonApiCall(), 'is JSON API call');
  163. $this->assertEquals('create', $request->getOperation());
  164. $this->assertEquals('', $request->getParam('ct'), "scalar body {$scalar} yields no parameters");
  165. }
  166. }
  167. public function testReadWithNegotiation()
  168. {
  169. $this->reset();
  170. $id = Helper::getRandomId();
  171. $_SERVER['REQUEST_METHOD'] = 'GET';
  172. $_SERVER['HTTP_ACCEPT'] = 'text/html,text/html; charset=UTF-8,application/xhtml+xml, application/xml;q=0.9,*/*;q=0.8, text/csv,application/json';
  173. $_SERVER['QUERY_STRING'] = $id;
  174. $_GET[$id] = '';
  175. $request = new Request;
  176. $this->assertFalse($request->isJsonApiCall(), 'is HTML call');
  177. $this->assertEquals($id, $request->getParam('pasteid'));
  178. $this->assertEquals('read', $request->getOperation());
  179. }
  180. public function testReadWithXhtmlNegotiation()
  181. {
  182. $this->reset();
  183. $id = Helper::getRandomId();
  184. $_SERVER['REQUEST_METHOD'] = 'GET';
  185. $_SERVER['HTTP_ACCEPT'] = 'application/xhtml+xml,text/html,text/html; charset=UTF-8, application/xml;q=0.9,*/*;q=0.8, text/csv,application/json';
  186. $_SERVER['QUERY_STRING'] = $id;
  187. $_GET[$id] = '';
  188. $request = new Request;
  189. $this->assertFalse($request->isJsonApiCall(), 'is HTML call');
  190. $this->assertEquals($id, $request->getParam('pasteid'));
  191. $this->assertEquals('read', $request->getOperation());
  192. }
  193. public function testApiReadWithNegotiation()
  194. {
  195. $this->reset();
  196. $id = Helper::getRandomId();
  197. $_SERVER['REQUEST_METHOD'] = 'GET';
  198. $_SERVER['HTTP_ACCEPT'] = 'text/plain,text/csv, application/xml;q=0.9, application/json, text/html,text/html; charset=UTF-8,application/xhtml+xml, */*;q=0.8';
  199. $_SERVER['QUERY_STRING'] = $id;
  200. $_GET[$id] = '';
  201. $request = new Request;
  202. $this->assertTrue($request->isJsonApiCall(), 'is JSON Api call');
  203. $this->assertEquals($id, $request->getParam('pasteid'));
  204. $this->assertEquals('read', $request->getOperation());
  205. }
  206. public function testReadWithFailedNegotiation()
  207. {
  208. $this->reset();
  209. $id = Helper::getRandomId();
  210. $_SERVER['REQUEST_METHOD'] = 'GET';
  211. $_SERVER['HTTP_ACCEPT'] = 'text/plain,text/csv, application/xml;q=0.9, */*;q=0.8';
  212. $_SERVER['QUERY_STRING'] = $id;
  213. $_GET[$id] = '';
  214. $request = new Request;
  215. $this->assertFalse($request->isJsonApiCall(), 'is HTML call');
  216. $this->assertEquals($id, $request->getParam('pasteid'));
  217. $this->assertEquals('read', $request->getOperation());
  218. }
  219. public function testPasteIdExtraction()
  220. {
  221. $this->reset();
  222. $id = Helper::getRandomId();
  223. $path = '/' . $this->getRandomQueryChars() . '/';
  224. $queryParams = [$id];
  225. $queryParamCount = random_int(1, 5);
  226. for ($i = 0; $i < $queryParamCount; ++$i) {
  227. array_push($queryParams, $this->getRandomQueryChars());
  228. }
  229. shuffle($queryParams);
  230. $_SERVER['REQUEST_METHOD'] = 'GET';
  231. $_SERVER['QUERY_STRING'] = implode('&', $queryParams);
  232. $_SERVER['REQUEST_URI'] = $path . '?' . $_SERVER['QUERY_STRING'];
  233. $_GET[$id] = '';
  234. $request = new Request;
  235. $this->assertEquals($id, $request->getParam('pasteid'));
  236. $this->assertEquals($path, $request->getRequestUri());
  237. }
  238. }