ControllerTest.php 38 KB

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