ControllerTest.php 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973
  1. <?php declare(strict_types=1);
  2. use PHPUnit\Framework\TestCase;
  3. use PrivateBin\Configuration;
  4. use PrivateBin\Controller;
  5. use PrivateBin\Data\Filesystem;
  6. use PrivateBin\Persistence\ServerSalt;
  7. use PrivateBin\Persistence\TrafficLimiter;
  8. use PrivateBin\Request;
  9. class ControllerTest extends TestCase
  10. {
  11. protected $_data;
  12. protected $_path;
  13. public function setUp(): void
  14. {
  15. /* Setup Routine */
  16. $this->_path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'privatebin_data';
  17. $this->_data = new Filesystem(['dir' => $this->_path]);
  18. ServerSalt::setStore($this->_data);
  19. TrafficLimiter::setStore($this->_data);
  20. $this->reset();
  21. }
  22. public function tearDown(): void
  23. {
  24. /* Tear Down Routine */
  25. unlink(CONF);
  26. Helper::confRestore();
  27. Helper::rmDir($this->_path);
  28. }
  29. public function reset()
  30. {
  31. $_POST = [];
  32. $_GET = [];
  33. $_SERVER = [];
  34. if ($this->_data->exists(Helper::getPasteId())) {
  35. $this->_data->delete(Helper::getPasteId());
  36. }
  37. $options = parse_ini_file(CONF_SAMPLE, true);
  38. $options['model_options']['dir'] = $this->_path;
  39. Helper::createIniFile(CONF, $options);
  40. }
  41. /**
  42. * @runInSeparateProcess
  43. */
  44. public function testView()
  45. {
  46. $_SERVER['HTTP_HOST'] = 'example.com';
  47. $_SERVER['QUERY_STRING'] = Helper::getPasteId();
  48. $_GET[Helper::getPasteId()] = '';
  49. ob_start();
  50. new Controller;
  51. $content = ob_get_contents();
  52. ob_end_clean();
  53. $this->assertStringContainsString(
  54. '<title>PrivateBin</title>',
  55. $content,
  56. 'outputs title correctly'
  57. );
  58. $this->assertStringNotContainsString(
  59. 'id="shortenbutton"',
  60. $content,
  61. 'doesn\'t output shortener button'
  62. );
  63. $this->assertMatchesRegularExpression(
  64. '# href="https://' . preg_quote($_SERVER['HTTP_HOST']) . '/">switching to HTTPS#',
  65. $content,
  66. 'outputs configured https URL correctly'
  67. );
  68. }
  69. /**
  70. * @runInSeparateProcess
  71. */
  72. public function testViewLanguageSelection()
  73. {
  74. $options = parse_ini_file(CONF, true);
  75. $options['main']['languageselection'] = true;
  76. Helper::createIniFile(CONF, $options);
  77. $_COOKIE['lang'] = 'de';
  78. ob_start();
  79. new Controller;
  80. $content = ob_get_contents();
  81. ob_end_clean();
  82. $this->assertStringContainsString(
  83. '<title>PrivateBin</title>',
  84. $content,
  85. 'outputs title correctly'
  86. );
  87. }
  88. /**
  89. * @runInSeparateProcess
  90. */
  91. public function testViewForceLanguageDefault()
  92. {
  93. $options = parse_ini_file(CONF, true);
  94. $options['main']['languageselection'] = false;
  95. $options['main']['languagedefault'] = 'fr';
  96. Helper::createIniFile(CONF, $options);
  97. $_COOKIE['lang'] = 'de';
  98. ob_start();
  99. new Controller;
  100. $content = ob_get_contents();
  101. ob_end_clean();
  102. $this->assertStringContainsString(
  103. '<title>PrivateBin</title>',
  104. $content,
  105. 'outputs title correctly'
  106. );
  107. }
  108. /**
  109. * @runInSeparateProcess
  110. */
  111. public function testViewForceRegionalLanguageDefault()
  112. {
  113. $options = parse_ini_file(CONF, true);
  114. $options['main']['languageselection'] = false;
  115. $options['main']['languagedefault'] = 'zh-tw';
  116. Helper::createIniFile(CONF, $options);
  117. $_COOKIE['lang'] = 'de';
  118. ob_start();
  119. new Controller;
  120. $content = ob_get_contents();
  121. ob_end_clean();
  122. $this->assertStringContainsString(
  123. '<title>PrivateBin</title>',
  124. $content,
  125. 'outputs title correctly'
  126. );
  127. $this->assertSame('zh-tw', $_COOKIE['lang'], 'forces configured default language');
  128. }
  129. /**
  130. * @runInSeparateProcess
  131. */
  132. public function testViewUrlShortener()
  133. {
  134. $shortener = 'https://shortener.example.com/api?link=';
  135. $options = parse_ini_file(CONF, true);
  136. $options['main']['urlshortener'] = $shortener;
  137. Helper::createIniFile(CONF, $options);
  138. $_COOKIE['lang'] = 'de';
  139. ob_start();
  140. new Controller;
  141. $content = ob_get_contents();
  142. ob_end_clean();
  143. $this->assertMatchesRegularExpression(
  144. '#id="shortenbutton"[^>]*data-shortener="' . preg_quote($shortener) . '"#',
  145. $content,
  146. 'outputs configured shortener URL correctly'
  147. );
  148. }
  149. public function testConf()
  150. {
  151. file_put_contents(CONF, '');
  152. $this->expectException(Exception::class);
  153. $this->expectExceptionCode(2);
  154. new Controller;
  155. }
  156. /**
  157. * @runInSeparateProcess
  158. */
  159. public function testNonDefaultConf()
  160. {
  161. $newConfig = new class extends Configuration {};
  162. $configValue = (new ReflectionClass(Controller::class))->getProperty('_conf');
  163. if (version_compare(PHP_VERSION, '8.1') < 0) {
  164. // > This function has been DEPRECATED as of PHP 8.5.0. [...]
  165. // > As of PHP 8.1.0, calling this method has no effect; all properties are accessible by default.
  166. // @see: https://www.php.net/manual/en/reflectionproperty.setaccessible.php
  167. $configValue->setAccessible(true);
  168. }
  169. ob_start();
  170. $controller = new Controller($newConfig);
  171. ob_end_clean();
  172. $this->assertSame($newConfig, $configValue->getValue($controller));
  173. }
  174. /**
  175. * @runInSeparateProcess
  176. */
  177. public function testCreate()
  178. {
  179. $options = parse_ini_file(CONF, true);
  180. $options['traffic']['limit'] = 0;
  181. Helper::createIniFile(CONF, $options);
  182. $paste = Helper::getPasteJson();
  183. $file = Helper::createTempFile();
  184. file_put_contents($file, $paste);
  185. Request::setInputStream($file);
  186. $_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
  187. $_SERVER['REQUEST_METHOD'] = 'POST';
  188. $_SERVER['REMOTE_ADDR'] = '::1';
  189. ob_start();
  190. new Controller;
  191. $content = ob_get_contents();
  192. ob_end_clean();
  193. $response = json_decode($content, true);
  194. $this->assertEquals(0, $response['status'], 'outputs status');
  195. $this->assertTrue($this->_data->exists($response['id']), 'paste exists after posting data');
  196. $paste = $this->_data->read($response['id']);
  197. $this->assertFalse(array_key_exists('created', $paste['meta']), 'does not output created');
  198. $this->assertEquals(
  199. hash_hmac('sha256', $response['id'], $paste['meta']['salt']),
  200. $response['deletetoken'],
  201. 'outputs valid delete token'
  202. );
  203. }
  204. /**
  205. * @runInSeparateProcess
  206. */
  207. public function testCreateInvalidTimelimit()
  208. {
  209. $options = parse_ini_file(CONF, true);
  210. $options['traffic']['limit'] = 0;
  211. Helper::createIniFile(CONF, $options);
  212. $paste = Helper::getPasteJson(['expire' => 25]);
  213. $file = Helper::createTempFile();
  214. file_put_contents($file, $paste);
  215. Request::setInputStream($file);
  216. $_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
  217. $_SERVER['REQUEST_METHOD'] = 'POST';
  218. $_SERVER['REMOTE_ADDR'] = '::1';
  219. // Explicitly invoke TrafficLimiter to ensure limit applies to this test
  220. TrafficLimiter::canPass();
  221. ob_start();
  222. new Controller;
  223. $content = ob_get_contents();
  224. ob_end_clean();
  225. $response = json_decode($content, true);
  226. $this->assertEquals(0, $response['status'], 'outputs status');
  227. $this->assertTrue($this->_data->exists($response['id']), 'paste exists after posting data');
  228. $paste = $this->_data->read($response['id']);
  229. $this->assertEquals(
  230. hash_hmac('sha256', $response['id'], $paste['meta']['salt']),
  231. $response['deletetoken'],
  232. 'outputs valid delete token'
  233. );
  234. }
  235. /**
  236. * @runInSeparateProcess
  237. */
  238. public function testCreateInvalidSize()
  239. {
  240. $options = parse_ini_file(CONF, true);
  241. $options['main']['sizelimit'] = 10;
  242. $options['traffic']['limit'] = 0;
  243. Helper::createIniFile(CONF, $options);
  244. $paste = Helper::getPasteJson();
  245. $file = Helper::createTempFile();
  246. file_put_contents($file, $paste);
  247. Request::setInputStream($file);
  248. $_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
  249. $_SERVER['REQUEST_METHOD'] = 'POST';
  250. $_SERVER['REMOTE_ADDR'] = '::1';
  251. ob_start();
  252. new Controller;
  253. $content = ob_get_contents();
  254. ob_end_clean();
  255. $response = json_decode($content, true);
  256. $this->assertEquals(1, $response['status'], 'outputs error status');
  257. $this->assertFalse($this->_data->exists(Helper::getPasteId()), 'paste exists after posting data');
  258. }
  259. /**
  260. * @runInSeparateProcess
  261. */
  262. public function testCreateOverSize()
  263. {
  264. $options = parse_ini_file(CONF, true);
  265. $options['main']['sizelimit'] = 1000;
  266. $options['traffic']['limit'] = 0;
  267. Helper::createIniFile(CONF, $options);
  268. $paste = Helper::getPastePost();
  269. // add some extra authenticated data, could be a hypothetical future cryptographic hash, exceeding the sizelimit
  270. $paste['adata'][] = str_repeat('0', $options['main']['sizelimit']);
  271. $file = Helper::createTempFile();
  272. file_put_contents($file, json_encode($paste));
  273. Request::setInputStream($file);
  274. $_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
  275. $_SERVER['REQUEST_METHOD'] = 'POST';
  276. $_SERVER['REMOTE_ADDR'] = '::1';
  277. ob_start();
  278. new Controller;
  279. $content = ob_get_contents();
  280. ob_end_clean();
  281. $response = json_decode($content, true);
  282. $this->assertEquals(1, $response['status'], 'outputs error status');
  283. $this->assertFalse($this->_data->exists(Helper::getPasteId()), 'paste exists after posting data');
  284. }
  285. /**
  286. * @runInSeparateProcess
  287. */
  288. public function testCreateProxyHeader()
  289. {
  290. $options = parse_ini_file(CONF, true);
  291. $options['traffic']['header'] = 'X_FORWARDED_FOR';
  292. Helper::createIniFile(CONF, $options);
  293. $paste = Helper::getPasteJson();
  294. $file = Helper::createTempFile();
  295. file_put_contents($file, $paste);
  296. Request::setInputStream($file);
  297. $_SERVER['HTTP_X_FORWARDED_FOR'] = '::2';
  298. $_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
  299. $_SERVER['REQUEST_METHOD'] = 'POST';
  300. $_SERVER['REMOTE_ADDR'] = '::1';
  301. ob_start();
  302. new Controller;
  303. $content = ob_get_contents();
  304. ob_end_clean();
  305. $response = json_decode($content, true);
  306. $this->assertEquals(0, $response['status'], 'outputs status');
  307. $this->assertTrue($this->_data->exists($response['id']), 'paste exists after posting data');
  308. $paste = $this->_data->read($response['id']);
  309. $this->assertEquals(
  310. hash_hmac('sha256', $response['id'], $paste['meta']['salt']),
  311. $response['deletetoken'],
  312. 'outputs valid delete token'
  313. );
  314. }
  315. /**
  316. * @runInSeparateProcess
  317. */
  318. public function testCreateDuplicateId()
  319. {
  320. $options = parse_ini_file(CONF, true);
  321. $options['traffic']['limit'] = 0;
  322. Helper::createIniFile(CONF, $options);
  323. $paste = Helper::getPaste();
  324. $this->_data->create(Helper::getPasteId(), $paste);
  325. $paste = Helper::getPasteJson();
  326. $file = Helper::createTempFile();
  327. file_put_contents($file, $paste);
  328. Request::setInputStream($file);
  329. $_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
  330. $_SERVER['REQUEST_METHOD'] = 'POST';
  331. $_SERVER['REMOTE_ADDR'] = '::1';
  332. ob_start();
  333. new Controller;
  334. $content = ob_get_contents();
  335. ob_end_clean();
  336. $response = json_decode($content, true);
  337. $this->assertEquals(1, $response['status'], 'outputs error status');
  338. $this->assertTrue($this->_data->exists(Helper::getPasteId()), 'paste exists after posting data');
  339. }
  340. /**
  341. * @runInSeparateProcess
  342. */
  343. public function testCreateValidExpire()
  344. {
  345. $options = parse_ini_file(CONF, true);
  346. $options['traffic']['limit'] = 0;
  347. Helper::createIniFile(CONF, $options);
  348. $paste = Helper::getPasteJson();
  349. $file = Helper::createTempFile();
  350. file_put_contents($file, $paste);
  351. Request::setInputStream($file);
  352. $_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
  353. $_SERVER['REQUEST_METHOD'] = 'POST';
  354. $_SERVER['REMOTE_ADDR'] = '::1';
  355. $time = time();
  356. ob_start();
  357. new Controller;
  358. $content = ob_get_contents();
  359. ob_end_clean();
  360. $response = json_decode($content, true);
  361. $this->assertEquals(0, $response['status'], 'outputs status');
  362. $this->assertTrue($this->_data->exists($response['id']), 'paste exists after posting data');
  363. $paste = $this->_data->read($response['id']);
  364. $this->assertEquals(
  365. hash_hmac('sha256', $response['id'], $paste['meta']['salt']),
  366. $response['deletetoken'],
  367. 'outputs valid delete token'
  368. );
  369. $this->assertGreaterThanOrEqual($time + 300, $paste['meta']['expire_date'], 'time is set correctly');
  370. }
  371. /**
  372. * @runInSeparateProcess
  373. */
  374. public function testCreateValidExpireWithDiscussion()
  375. {
  376. $options = parse_ini_file(CONF, true);
  377. $options['traffic']['limit'] = 0;
  378. Helper::createIniFile(CONF, $options);
  379. $paste = Helper::getPasteJson();
  380. $file = Helper::createTempFile();
  381. file_put_contents($file, $paste);
  382. Request::setInputStream($file);
  383. $_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
  384. $_SERVER['REQUEST_METHOD'] = 'POST';
  385. $_SERVER['REMOTE_ADDR'] = '::1';
  386. $time = time();
  387. ob_start();
  388. new Controller;
  389. $content = ob_get_contents();
  390. ob_end_clean();
  391. $response = json_decode($content, true);
  392. $this->assertEquals(0, $response['status'], 'outputs status');
  393. $this->assertTrue($this->_data->exists($response['id']), 'paste exists after posting data');
  394. $paste = $this->_data->read($response['id']);
  395. $this->assertEquals(
  396. hash_hmac('sha256', $response['id'], $paste['meta']['salt']),
  397. $response['deletetoken'],
  398. 'outputs valid delete token'
  399. );
  400. $this->assertGreaterThanOrEqual($time + 300, $paste['meta']['expire_date'], 'time is set correctly');
  401. $this->assertEquals(1, $paste['adata'][2], 'discussion is enabled');
  402. }
  403. /**
  404. * @runInSeparateProcess
  405. */
  406. public function testCreateInvalidExpire()
  407. {
  408. $options = parse_ini_file(CONF, true);
  409. $options['traffic']['limit'] = 0;
  410. Helper::createIniFile(CONF, $options);
  411. $paste = Helper::getPasteJson(['expire' => 'foo']);
  412. $file = Helper::createTempFile();
  413. file_put_contents($file, $paste);
  414. Request::setInputStream($file);
  415. $_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
  416. $_SERVER['REQUEST_METHOD'] = 'POST';
  417. $_SERVER['REMOTE_ADDR'] = '::1';
  418. ob_start();
  419. new Controller;
  420. $content = ob_get_contents();
  421. ob_end_clean();
  422. $response = json_decode($content, true);
  423. $this->assertEquals(0, $response['status'], 'outputs status');
  424. $this->assertTrue($this->_data->exists($response['id']), 'paste exists after posting data');
  425. $paste = $this->_data->read($response['id']);
  426. $this->assertEquals(
  427. hash_hmac('sha256', $response['id'], $paste['meta']['salt']),
  428. $response['deletetoken'],
  429. 'outputs valid delete token'
  430. );
  431. }
  432. /**
  433. * @runInSeparateProcess
  434. */
  435. public function testCreateInvalidBurn()
  436. {
  437. $options = parse_ini_file(CONF, true);
  438. $options['traffic']['limit'] = 0;
  439. Helper::createIniFile(CONF, $options);
  440. $paste = Helper::getPastePost();
  441. $paste['adata'][3] = 'neither 1 nor 0';
  442. $file = Helper::createTempFile();
  443. file_put_contents($file, json_encode($paste));
  444. Request::setInputStream($file);
  445. $_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
  446. $_SERVER['REQUEST_METHOD'] = 'POST';
  447. $_SERVER['REMOTE_ADDR'] = '::1';
  448. ob_start();
  449. new Controller;
  450. $content = ob_get_contents();
  451. ob_end_clean();
  452. $response = json_decode($content, true);
  453. $this->assertEquals(1, $response['status'], 'outputs error status');
  454. $this->assertFalse($this->_data->exists(Helper::getPasteId()), 'paste exists after posting data');
  455. }
  456. /**
  457. * @runInSeparateProcess
  458. */
  459. public function testCreateInvalidOpenDiscussion()
  460. {
  461. $options = parse_ini_file(CONF, true);
  462. $options['traffic']['limit'] = 0;
  463. Helper::createIniFile(CONF, $options);
  464. $paste = Helper::getPastePost();
  465. $paste['adata'][2] = 'neither 1 nor 0';
  466. $file = Helper::createTempFile();
  467. file_put_contents($file, json_encode($paste));
  468. Request::setInputStream($file);
  469. $_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
  470. $_SERVER['REQUEST_METHOD'] = 'POST';
  471. $_SERVER['REMOTE_ADDR'] = '::1';
  472. ob_start();
  473. new Controller;
  474. $content = ob_get_contents();
  475. ob_end_clean();
  476. $response = json_decode($content, true);
  477. $this->assertEquals(1, $response['status'], 'outputs error status');
  478. $this->assertFalse($this->_data->exists(Helper::getPasteId()), 'paste exists after posting data');
  479. }
  480. /**
  481. * In some webserver setups (found with Suhosin) overly long POST params are
  482. * silently removed, check that this case is handled
  483. *
  484. * @runInSeparateProcess
  485. */
  486. public function testCreateBrokenUpload()
  487. {
  488. $paste = substr(Helper::getPasteJson(), 0, -10);
  489. $file = Helper::createTempFile();
  490. file_put_contents($file, $paste);
  491. Request::setInputStream($file);
  492. $_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
  493. $_SERVER['REQUEST_METHOD'] = 'POST';
  494. $_SERVER['REMOTE_ADDR'] = '::1';
  495. $this->assertFalse($this->_data->exists(Helper::getPasteId()), 'paste does not exists before posting data');
  496. ob_start();
  497. new Controller;
  498. $content = ob_get_contents();
  499. ob_end_clean();
  500. $response = json_decode($content, true);
  501. $this->assertEquals(1, $response['status'], 'outputs error status');
  502. $this->assertFalse($this->_data->exists(Helper::getPasteId()), 'paste exists after posting data');
  503. }
  504. /**
  505. * @runInSeparateProcess
  506. */
  507. public function testCreateTooSoon()
  508. {
  509. $paste = Helper::getPasteJson();
  510. $file = Helper::createTempFile();
  511. file_put_contents($file, $paste);
  512. Request::setInputStream($file);
  513. $_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
  514. $_SERVER['REQUEST_METHOD'] = 'POST';
  515. $_SERVER['REMOTE_ADDR'] = '::1';
  516. ob_start();
  517. new Controller;
  518. ob_end_clean();
  519. $this->_data->delete(Helper::getPasteId());
  520. ob_start();
  521. new Controller;
  522. $content = ob_get_contents();
  523. ob_end_clean();
  524. $response = json_decode($content, true);
  525. $this->assertEquals(1, $response['status'], 'outputs error status');
  526. $this->assertFalse($this->_data->exists(Helper::getPasteId()), 'paste exists after posting data');
  527. }
  528. /**
  529. * @runInSeparateProcess
  530. */
  531. public function testCreateInvalidFormat()
  532. {
  533. $options = parse_ini_file(CONF, true);
  534. $options['traffic']['limit'] = 0;
  535. Helper::createIniFile(CONF, $options);
  536. $file = Helper::createTempFile();
  537. file_put_contents($file, '{"data":"","meta":{}}');
  538. Request::setInputStream($file);
  539. $_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
  540. $_SERVER['REQUEST_METHOD'] = 'POST';
  541. $_SERVER['REMOTE_ADDR'] = '::1';
  542. ob_start();
  543. new Controller;
  544. $content = ob_get_contents();
  545. ob_end_clean();
  546. $response = json_decode($content, true);
  547. $this->assertEquals(1, $response['status'], 'outputs error status');
  548. $this->assertFalse($this->_data->exists(Helper::getPasteId()), 'paste exists after posting data');
  549. }
  550. /**
  551. * @runInSeparateProcess
  552. */
  553. public function testCreateComment()
  554. {
  555. $options = parse_ini_file(CONF, true);
  556. $options['traffic']['limit'] = 0;
  557. Helper::createIniFile(CONF, $options);
  558. $comment = Helper::getCommentJson();
  559. $file = Helper::createTempFile();
  560. file_put_contents($file, $comment);
  561. Request::setInputStream($file);
  562. $_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
  563. $_SERVER['REQUEST_METHOD'] = 'POST';
  564. $_SERVER['REMOTE_ADDR'] = '::1';
  565. $paste = Helper::getPaste();
  566. $this->_data->create(Helper::getPasteId(), $paste);
  567. ob_start();
  568. new Controller;
  569. $content = ob_get_contents();
  570. ob_end_clean();
  571. $response = json_decode($content, true);
  572. $this->assertEquals(0, $response['status'], 'outputs status');
  573. $this->assertTrue($this->_data->existsComment(Helper::getPasteId(), Helper::getPasteId(), $response['id']), 'paste exists after posting data');
  574. }
  575. /**
  576. * @runInSeparateProcess
  577. */
  578. public function testCreateInvalidComment()
  579. {
  580. $options = parse_ini_file(CONF, true);
  581. $options['traffic']['limit'] = 0;
  582. Helper::createIniFile(CONF, $options);
  583. $comment = Helper::getCommentPost();
  584. $comment['parentid'] = 'foo';
  585. $file = Helper::createTempFile();
  586. file_put_contents($file, json_encode($comment));
  587. Request::setInputStream($file);
  588. $_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
  589. $_SERVER['REQUEST_METHOD'] = 'POST';
  590. $_SERVER['REMOTE_ADDR'] = '::1';
  591. $paste = Helper::getPaste();
  592. $this->_data->create(Helper::getPasteId(), $paste);
  593. ob_start();
  594. new Controller;
  595. $content = ob_get_contents();
  596. ob_end_clean();
  597. $response = json_decode($content, true);
  598. $this->assertEquals(1, $response['status'], 'outputs error status');
  599. $this->assertFalse($this->_data->existsComment(Helper::getPasteId(), Helper::getPasteId(), Helper::getCommentId()), 'comment exists after posting data');
  600. }
  601. /**
  602. * @runInSeparateProcess
  603. */
  604. public function testCreateCommentDiscussionDisabled()
  605. {
  606. $options = parse_ini_file(CONF, true);
  607. $options['traffic']['limit'] = 0;
  608. Helper::createIniFile(CONF, $options);
  609. $comment = Helper::getCommentJson();
  610. $file = Helper::createTempFile();
  611. file_put_contents($file, $comment);
  612. Request::setInputStream($file);
  613. $_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
  614. $_SERVER['REQUEST_METHOD'] = 'POST';
  615. $_SERVER['REMOTE_ADDR'] = '::1';
  616. $paste = Helper::getPaste();
  617. $paste['adata'][2] = 0;
  618. $this->_data->create(Helper::getPasteId(), $paste);
  619. ob_start();
  620. new Controller;
  621. $content = ob_get_contents();
  622. ob_end_clean();
  623. $response = json_decode($content, true);
  624. $this->assertEquals(1, $response['status'], 'outputs error status');
  625. $this->assertFalse($this->_data->existsComment(Helper::getPasteId(), Helper::getPasteId(), Helper::getCommentId()), 'paste exists after posting data');
  626. }
  627. /**
  628. * @runInSeparateProcess
  629. */
  630. public function testCreateCommentInvalidPaste()
  631. {
  632. $options = parse_ini_file(CONF, true);
  633. $options['traffic']['limit'] = 0;
  634. Helper::createIniFile(CONF, $options);
  635. $comment = Helper::getCommentJson();
  636. $file = Helper::createTempFile();
  637. file_put_contents($file, $comment);
  638. Request::setInputStream($file);
  639. $_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
  640. $_SERVER['REQUEST_METHOD'] = 'POST';
  641. $_SERVER['REMOTE_ADDR'] = '::1';
  642. ob_start();
  643. new Controller;
  644. $content = ob_get_contents();
  645. ob_end_clean();
  646. $response = json_decode($content, true);
  647. $this->assertEquals(1, $response['status'], 'outputs error status');
  648. $this->assertFalse($this->_data->existsComment(Helper::getPasteId(), Helper::getPasteId(), Helper::getCommentId()), 'paste exists after posting data');
  649. }
  650. /**
  651. * @runInSeparateProcess
  652. */
  653. public function testCreateDuplicateComment()
  654. {
  655. $options = parse_ini_file(CONF, true);
  656. $options['traffic']['limit'] = 0;
  657. Helper::createIniFile(CONF, $options);
  658. $paste = Helper::getPaste();
  659. $this->_data->create(Helper::getPasteId(), $paste);
  660. $comment = Helper::getComment();
  661. $this->_data->createComment(Helper::getPasteId(), Helper::getPasteId(), Helper::getPasteId(), $comment);
  662. $this->assertTrue($this->_data->existsComment(Helper::getPasteId(), Helper::getPasteId(), Helper::getPasteId()), 'comment exists before posting data');
  663. $comment = Helper::getCommentJson();
  664. $file = Helper::createTempFile();
  665. file_put_contents($file, $comment);
  666. Request::setInputStream($file);
  667. $_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
  668. $_SERVER['REQUEST_METHOD'] = 'POST';
  669. $_SERVER['REMOTE_ADDR'] = '::1';
  670. ob_start();
  671. new Controller;
  672. $content = ob_get_contents();
  673. ob_end_clean();
  674. $response = json_decode($content, true);
  675. $this->assertEquals(1, $response['status'], 'outputs error status');
  676. $this->assertTrue($this->_data->existsComment(Helper::getPasteId(), Helper::getPasteId(), Helper::getPasteId()), 'paste exists after posting data');
  677. }
  678. /**
  679. * @runInSeparateProcess
  680. */
  681. public function testReadInvalidId()
  682. {
  683. $_SERVER['QUERY_STRING'] = 'foo';
  684. $_GET['foo'] = '';
  685. $_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
  686. ob_start();
  687. new Controller;
  688. $content = ob_get_contents();
  689. ob_end_clean();
  690. $response = json_decode($content, true);
  691. $this->assertEquals(1, $response['status'], 'outputs error status');
  692. $this->assertEquals('Invalid document ID.', $response['message'], 'outputs error message');
  693. }
  694. /**
  695. * @runInSeparateProcess
  696. */
  697. public function testReadNonexisting()
  698. {
  699. $_SERVER['QUERY_STRING'] = Helper::getPasteId();
  700. $_GET[Helper::getPasteId()] = '';
  701. $_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
  702. ob_start();
  703. new Controller;
  704. $content = ob_get_contents();
  705. ob_end_clean();
  706. $response = json_decode($content, true);
  707. $this->assertEquals(1, $response['status'], 'outputs error status');
  708. $this->assertEquals('Document does not exist, has expired or has been deleted.', $response['message'], 'outputs error message');
  709. }
  710. /**
  711. * @runInSeparateProcess
  712. */
  713. public function testReadExpired()
  714. {
  715. $expiredPaste = Helper::getPaste(['expire_date' => 1344803344]);
  716. $this->_data->create(Helper::getPasteId(), $expiredPaste);
  717. $_SERVER['QUERY_STRING'] = Helper::getPasteId();
  718. $_GET[Helper::getPasteId()] = '';
  719. $_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
  720. ob_start();
  721. new Controller;
  722. $content = ob_get_contents();
  723. ob_end_clean();
  724. $response = json_decode($content, true);
  725. $this->assertEquals(1, $response['status'], 'outputs error status');
  726. $this->assertEquals('Document does not exist, has expired or has been deleted.', $response['message'], 'outputs error message');
  727. }
  728. /**
  729. * @runInSeparateProcess
  730. */
  731. public function testReadBurn()
  732. {
  733. $paste = Helper::getPaste();
  734. $paste['adata'][3] = 1;
  735. $this->_data->create(Helper::getPasteId(), $paste);
  736. $_SERVER['QUERY_STRING'] = Helper::getPasteId();
  737. $_GET[Helper::getPasteId()] = '';
  738. $_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
  739. ob_start();
  740. new Controller;
  741. $content = ob_get_contents();
  742. ob_end_clean();
  743. $response = json_decode($content, true);
  744. $this->assertEquals(0, $response['status'], 'outputs success status');
  745. $this->assertEquals(Helper::getPasteId(), $response['id'], 'outputs data correctly');
  746. $this->assertStringEndsWith('?' . $response['id'], $response['url'], 'returned URL points to new paste');
  747. $this->assertEquals($paste['ct'], $response['ct'], 'outputs ct correctly');
  748. $this->assertEquals($paste['adata'][1], $response['adata'][1], 'outputs formatter correctly');
  749. $this->assertEquals($paste['adata'][2], $response['adata'][2], 'outputs opendiscussion correctly');
  750. $this->assertEquals($paste['adata'][3], $response['adata'][3], 'outputs burnafterreading correctly');
  751. $this->assertFalse(array_key_exists('created', $response['meta']), 'does not output created');
  752. $this->assertEquals(0, $response['comment_count'], 'outputs comment_count correctly');
  753. $this->assertEquals(0, $response['comment_offset'], 'outputs comment_offset correctly');
  754. // by default it will be deleted instantly after it is read
  755. $this->assertFalse($this->_data->exists(Helper::getPasteId()), 'paste exists after reading');
  756. }
  757. /**
  758. * @runInSeparateProcess
  759. */
  760. public function testReadJson()
  761. {
  762. $paste = Helper::getPaste();
  763. $this->_data->create(Helper::getPasteId(), $paste);
  764. $_SERVER['QUERY_STRING'] = Helper::getPasteId();
  765. $_GET[Helper::getPasteId()] = '';
  766. $_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
  767. ob_start();
  768. new Controller;
  769. $content = ob_get_contents();
  770. ob_end_clean();
  771. $response = json_decode($content, true);
  772. $this->assertEquals(0, $response['status'], 'outputs success status');
  773. $this->assertEquals(Helper::getPasteId(), $response['id'], 'outputs data correctly');
  774. $this->assertStringEndsWith('?' . $response['id'], $response['url'], 'returned URL points to new paste');
  775. $this->assertEquals($paste['ct'], $response['ct'], 'outputs ct correctly');
  776. $this->assertEquals($paste['adata'][1], $response['adata'][1], 'outputs formatter correctly');
  777. $this->assertEquals($paste['adata'][2], $response['adata'][2], 'outputs opendiscussion correctly');
  778. $this->assertEquals($paste['adata'][3], $response['adata'][3], 'outputs burnafterreading correctly');
  779. $this->assertFalse(array_key_exists('created', $response['meta']), 'does not output created');
  780. $this->assertEquals(0, $response['comment_count'], 'outputs comment_count correctly');
  781. $this->assertEquals(0, $response['comment_offset'], 'outputs comment_offset correctly');
  782. }
  783. /**
  784. * @runInSeparateProcess
  785. */
  786. public function testReadBurnAfterReading()
  787. {
  788. $burnPaste = Helper::getPaste();
  789. $burnPaste['adata'][3] = 1;
  790. $this->_data->create(Helper::getPasteId(), $burnPaste);
  791. $this->assertTrue($this->_data->exists(Helper::getPasteId()), 'paste exists before deleting data');
  792. $_SERVER['QUERY_STRING'] = Helper::getPasteId();
  793. $_GET[Helper::getPasteId()] = '';
  794. $_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
  795. ob_start();
  796. new Controller;
  797. $content = ob_get_contents();
  798. ob_end_clean();
  799. $response = json_decode($content, true);
  800. $this->assertEquals(0, $response['status'], 'outputs status');
  801. $this->assertFalse($this->_data->exists(Helper::getPasteId()), 'paste successfully deleted');
  802. }
  803. /**
  804. * @runInSeparateProcess
  805. */
  806. public function testDelete()
  807. {
  808. $paste = Helper::getPaste();
  809. $this->_data->create(Helper::getPasteId(), $paste);
  810. $this->assertTrue($this->_data->exists(Helper::getPasteId()), 'paste exists before deleting data');
  811. $paste = $this->_data->read(Helper::getPasteId());
  812. $_GET['pasteid'] = Helper::getPasteId();
  813. $_GET['deletetoken'] = hash_hmac('sha256', Helper::getPasteId(), $paste['meta']['salt']);
  814. ob_start();
  815. new Controller;
  816. $content = ob_get_contents();
  817. ob_end_clean();
  818. $this->assertMatchesRegularExpression(
  819. '#<div[^>]*id="status"[^>]*>.*Document was properly deleted\.#s',
  820. $content,
  821. 'outputs deleted status correctly'
  822. );
  823. $this->assertFalse($this->_data->exists(Helper::getPasteId()), 'paste successfully deleted');
  824. }
  825. /**
  826. * @runInSeparateProcess
  827. */
  828. public function testDeleteInvalidId()
  829. {
  830. $paste = Helper::getPaste();
  831. $this->_data->create(Helper::getPasteId(), $paste);
  832. $_GET['pasteid'] = 'foo';
  833. $_GET['deletetoken'] = 'bar';
  834. ob_start();
  835. new Controller;
  836. $content = ob_get_contents();
  837. ob_end_clean();
  838. $this->assertMatchesRegularExpression(
  839. '#<div[^>]*id="errormessage"[^>]*>.*Invalid document ID\.#s',
  840. $content,
  841. 'outputs delete error correctly'
  842. );
  843. $this->assertTrue($this->_data->exists(Helper::getPasteId()), 'paste exists after failing to delete data');
  844. }
  845. /**
  846. * @runInSeparateProcess
  847. */
  848. public function testDeleteInexistantId()
  849. {
  850. $_GET['pasteid'] = Helper::getPasteId();
  851. $_GET['deletetoken'] = 'bar';
  852. ob_start();
  853. new Controller;
  854. $content = ob_get_contents();
  855. ob_end_clean();
  856. $this->assertMatchesRegularExpression(
  857. '#<div[^>]*id="errormessage"[^>]*>.*Document does not exist, has expired or has been deleted\.#s',
  858. $content,
  859. 'outputs delete error correctly'
  860. );
  861. }
  862. /**
  863. * @runInSeparateProcess
  864. */
  865. public function testDeleteInvalidToken()
  866. {
  867. $paste = Helper::getPaste();
  868. $this->_data->create(Helper::getPasteId(), $paste);
  869. $_GET['pasteid'] = Helper::getPasteId();
  870. $_GET['deletetoken'] = 'bar';
  871. ob_start();
  872. new Controller;
  873. $content = ob_get_contents();
  874. ob_end_clean();
  875. $this->assertMatchesRegularExpression(
  876. '#<div[^>]*id="errormessage"[^>]*>.*Wrong deletion token\. Document was not deleted\.#s',
  877. $content,
  878. 'outputs delete error correctly'
  879. );
  880. $this->assertTrue($this->_data->exists(Helper::getPasteId()), 'paste exists after failing to delete data');
  881. }
  882. /**
  883. * @runInSeparateProcess
  884. */
  885. public function testDeleteInvalidBurnAfterReading()
  886. {
  887. $paste = Helper::getPaste();
  888. $this->_data->create(Helper::getPasteId(), $paste);
  889. $this->assertTrue($this->_data->exists(Helper::getPasteId()), 'paste exists before deleting data');
  890. $file = Helper::createTempFile();
  891. file_put_contents($file, json_encode([
  892. 'deletetoken' => 'burnafterreading',
  893. ]));
  894. Request::setInputStream($file);
  895. $_SERVER['QUERY_STRING'] = Helper::getPasteId();
  896. $_GET[Helper::getPasteId()] = '';
  897. $_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
  898. $_SERVER['REQUEST_METHOD'] = 'POST';
  899. ob_start();
  900. new Controller;
  901. $content = ob_get_contents();
  902. ob_end_clean();
  903. $response = json_decode($content, true);
  904. $this->assertEquals(1, $response['status'], 'outputs status');
  905. $this->assertTrue($this->_data->exists(Helper::getPasteId()), 'paste exists after failing to delete data');
  906. }
  907. /**
  908. * @runInSeparateProcess
  909. */
  910. public function testDeleteExpired()
  911. {
  912. $expiredPaste = Helper::getPaste(['expire_date' => 1000]);
  913. $this->assertFalse($this->_data->exists(Helper::getPasteId()), 'paste does not exist before being created');
  914. $this->_data->create(Helper::getPasteId(), $expiredPaste);
  915. $this->assertTrue($this->_data->exists(Helper::getPasteId()), 'paste exists before deleting data');
  916. $_GET['pasteid'] = Helper::getPasteId();
  917. $_GET['deletetoken'] = 'does not matter in this context, but has to be set';
  918. ob_start();
  919. new Controller;
  920. $content = ob_get_contents();
  921. ob_end_clean();
  922. $this->assertMatchesRegularExpression(
  923. '#<div[^>]*id="errormessage"[^>]*>.*Document does not exist, has expired or has been deleted\.#s',
  924. $content,
  925. 'outputs error correctly'
  926. );
  927. $this->assertFalse($this->_data->exists(Helper::getPasteId()), 'paste successfully deleted');
  928. }
  929. }