BodyParserMiddlewareTest.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  5. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  6. *
  7. * Licensed under The MIT License
  8. * For full copyright and license information, please see the LICENSE.txt
  9. * Redistributions of files must retain the above copyright notice.
  10. *
  11. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  12. * @link http://cakephp.org CakePHP(tm) Project
  13. * @since 3.5.0
  14. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  15. */
  16. namespace Cake\Test\TestCase\Http\Middleware;
  17. use Cake\Http\Exception\BadRequestException;
  18. use Cake\Http\Middleware\BodyParserMiddleware;
  19. use Cake\Http\Response;
  20. use Cake\Http\ServerRequest;
  21. use Cake\TestSuite\TestCase;
  22. use TestApp\Http\TestRequestHandler;
  23. /**
  24. * Test for BodyParser
  25. */
  26. class BodyParserMiddlewareTest extends TestCase
  27. {
  28. /**
  29. * Data provider for HTTP method tests.
  30. *
  31. * HEAD and GET do not populate $_POST or request->data.
  32. *
  33. * @return array
  34. */
  35. public static function safeHttpMethodProvider()
  36. {
  37. return [
  38. ['GET'],
  39. ['HEAD'],
  40. ];
  41. }
  42. /**
  43. * Data provider for HTTP methods that can contain request bodies.
  44. *
  45. * @return array
  46. */
  47. public static function httpMethodProvider()
  48. {
  49. return [
  50. ['PATCH'], ['PUT'], ['POST'], ['DELETE'],
  51. ];
  52. }
  53. /**
  54. * test constructor options
  55. *
  56. * @return void
  57. */
  58. public function testConstructorMethodsOption()
  59. {
  60. $parser = new BodyParserMiddleware(['methods' => ['PUT']]);
  61. $this->assertEquals(['PUT'], $parser->getMethods());
  62. }
  63. /**
  64. * test constructor options
  65. *
  66. * @return void
  67. */
  68. public function testConstructorXmlOption()
  69. {
  70. $parser = new BodyParserMiddleware(['json' => false]);
  71. $this->assertEquals([], $parser->getParsers(), 'Xml off by default');
  72. $parser = new BodyParserMiddleware(['json' => false, 'xml' => false]);
  73. $this->assertEquals([], $parser->getParsers(), 'No Xml types set.');
  74. $parser = new BodyParserMiddleware(['json' => false, 'xml' => true]);
  75. $this->assertEquals(
  76. ['application/xml', 'text/xml'],
  77. array_keys($parser->getParsers()),
  78. 'Default XML parsers are not set.'
  79. );
  80. }
  81. /**
  82. * test constructor options
  83. *
  84. * @return void
  85. */
  86. public function testConstructorJsonOption()
  87. {
  88. $parser = new BodyParserMiddleware(['json' => false]);
  89. $this->assertEquals([], $parser->getParsers(), 'No JSON types set.');
  90. $parser = new BodyParserMiddleware([]);
  91. $this->assertEquals(
  92. ['application/json', 'text/json'],
  93. array_keys($parser->getParsers()),
  94. 'Default JSON parsers are not set.'
  95. );
  96. }
  97. /**
  98. * test setMethods()
  99. *
  100. * @return void
  101. */
  102. public function testSetMethodsReturn()
  103. {
  104. $parser = new BodyParserMiddleware();
  105. $this->assertSame($parser, $parser->setMethods(['PUT']));
  106. $this->assertEquals(['PUT'], $parser->getMethods());
  107. }
  108. /**
  109. * test addParser()
  110. *
  111. * @return void
  112. */
  113. public function testAddParserReturn()
  114. {
  115. $parser = new BodyParserMiddleware(['json' => false]);
  116. $f1 = function (string $body) {
  117. return json_decode($body, true);
  118. };
  119. $this->assertSame($parser, $parser->addParser(['application/json'], $f1));
  120. }
  121. /**
  122. * test last parser defined wins
  123. *
  124. * @return void
  125. */
  126. public function testAddParserOverwrite()
  127. {
  128. $parser = new BodyParserMiddleware(['json' => false]);
  129. $f1 = function (string $body) {
  130. return json_decode($body, true);
  131. };
  132. $f2 = function (string $body) {
  133. return ['overridden'];
  134. };
  135. $parser->addParser(['application/json'], $f1);
  136. $parser->addParser(['application/json'], $f2);
  137. $this->assertSame(['application/json' => $f2], $parser->getParsers());
  138. }
  139. /**
  140. * test skipping parsing on unknown type
  141. *
  142. * @dataProvider httpMethodProvider
  143. * @return void
  144. */
  145. public function testInvokeMismatchedType($method)
  146. {
  147. $parser = new BodyParserMiddleware();
  148. $request = new ServerRequest([
  149. 'environment' => [
  150. 'REQUEST_METHOD' => $method,
  151. 'CONTENT_TYPE' => 'text/csv',
  152. ],
  153. 'input' => 'a,b,c',
  154. ]);
  155. $handler = new TestRequestHandler(function ($req) {
  156. $this->assertEquals([], $req->getParsedBody());
  157. return new Response();
  158. });
  159. $parser->process($request, $handler);
  160. }
  161. /**
  162. * test parsing on valid http method
  163. *
  164. * @dataProvider httpMethodProvider
  165. * @return void
  166. */
  167. public function testInvokeCaseInsensitiveContentType($method)
  168. {
  169. $parser = new BodyParserMiddleware();
  170. $request = new ServerRequest([
  171. 'environment' => [
  172. 'REQUEST_METHOD' => $method,
  173. 'CONTENT_TYPE' => 'ApPlIcAtIoN/JSoN',
  174. ],
  175. 'input' => '{"title": "yay"}',
  176. ]);
  177. $handler = new TestRequestHandler(function ($req) {
  178. $this->assertEquals(['title' => 'yay'], $req->getParsedBody());
  179. return new Response();
  180. });
  181. $parser->process($request, $handler);
  182. }
  183. /**
  184. * test parsing on valid http method
  185. *
  186. * @dataProvider httpMethodProvider
  187. * @return void
  188. */
  189. public function testInvokeParse($method)
  190. {
  191. $parser = new BodyParserMiddleware();
  192. $request = new ServerRequest([
  193. 'environment' => [
  194. 'REQUEST_METHOD' => $method,
  195. 'CONTENT_TYPE' => 'application/json',
  196. ],
  197. 'input' => '{"title": "yay"}',
  198. ]);
  199. $handler = new TestRequestHandler(function ($req) {
  200. $this->assertEquals(['title' => 'yay'], $req->getParsedBody());
  201. return new Response();
  202. });
  203. $parser->process($request, $handler);
  204. }
  205. /**
  206. * test parsing on valid http method with charset
  207. *
  208. * @return void
  209. */
  210. public function testInvokeParseStripCharset()
  211. {
  212. $parser = new BodyParserMiddleware();
  213. $request = new ServerRequest([
  214. 'environment' => [
  215. 'REQUEST_METHOD' => 'POST',
  216. 'CONTENT_TYPE' => 'application/json; charset=utf-8',
  217. ],
  218. 'input' => '{"title": "yay"}',
  219. ]);
  220. $handler = new TestRequestHandler(function ($req) {
  221. $this->assertEquals(['title' => 'yay'], $req->getParsedBody());
  222. return new Response();
  223. });
  224. $parser->process($request, $handler);
  225. }
  226. /**
  227. * test parsing on ignored http method
  228. *
  229. * @dataProvider safeHttpMethodProvider
  230. * @return void
  231. */
  232. public function testInvokeNoParseOnSafe($method)
  233. {
  234. $parser = new BodyParserMiddleware();
  235. $request = new ServerRequest([
  236. 'environment' => [
  237. 'REQUEST_METHOD' => $method,
  238. 'CONTENT_TYPE' => 'application/json',
  239. ],
  240. 'input' => '{"title": "yay"}',
  241. ]);
  242. $handler = new TestRequestHandler(function ($req) {
  243. $this->assertEquals([], $req->getParsedBody());
  244. return new Response();
  245. });
  246. $parser->process($request, $handler);
  247. }
  248. /**
  249. * test parsing XML bodies.
  250. *
  251. * @return void
  252. */
  253. public function testInvokeXml()
  254. {
  255. $xml = <<<XML
  256. <?xml version="1.0" encoding="utf-8"?>
  257. <article>
  258. <title>yay</title>
  259. </article>
  260. XML;
  261. $request = new ServerRequest([
  262. 'environment' => [
  263. 'REQUEST_METHOD' => 'POST',
  264. 'CONTENT_TYPE' => 'application/xml',
  265. ],
  266. 'input' => $xml,
  267. ]);
  268. $handler = new TestRequestHandler(function ($req) {
  269. $expected = [
  270. 'article' => ['title' => 'yay'],
  271. ];
  272. $this->assertEquals($expected, $req->getParsedBody());
  273. return new Response();
  274. });
  275. $parser = new BodyParserMiddleware(['xml' => true]);
  276. $parser->process($request, $handler);
  277. }
  278. /**
  279. * Test that CDATA is removed in XML data.
  280. *
  281. * @return void
  282. */
  283. public function testInvokeXmlCdata()
  284. {
  285. $xml = <<<XML
  286. <?xml version="1.0" encoding="utf-8"?>
  287. <article>
  288. <id>1</id>
  289. <title><![CDATA[first]]></title>
  290. </article>
  291. XML;
  292. $request = new ServerRequest([
  293. 'environment' => [
  294. 'REQUEST_METHOD' => 'POST',
  295. 'CONTENT_TYPE' => 'application/xml',
  296. ],
  297. 'input' => $xml,
  298. ]);
  299. $handler = new TestRequestHandler(function ($req) {
  300. $expected = [
  301. 'article' => [
  302. 'id' => 1,
  303. 'title' => 'first',
  304. ],
  305. ];
  306. $this->assertEquals($expected, $req->getParsedBody());
  307. return new Response();
  308. });
  309. $parser = new BodyParserMiddleware(['xml' => true]);
  310. $parser->process($request, $handler);
  311. }
  312. /**
  313. * Test that internal entity recursion is ignored.
  314. *
  315. * @return void
  316. */
  317. public function testInvokeXmlInternalEntities()
  318. {
  319. $xml = <<<XML
  320. <?xml version="1.0" encoding="UTF-8"?>
  321. <!DOCTYPE item [
  322. <!ENTITY item "item">
  323. <!ENTITY item1 "&item;&item;&item;&item;&item;&item;">
  324. <!ENTITY item2 "&item1;&item1;&item1;&item1;&item1;&item1;&item1;&item1;&item1;">
  325. <!ENTITY item3 "&item2;&item2;&item2;&item2;&item2;&item2;&item2;&item2;&item2;">
  326. <!ENTITY item4 "&item3;&item3;&item3;&item3;&item3;&item3;&item3;&item3;&item3;">
  327. <!ENTITY item5 "&item4;&item4;&item4;&item4;&item4;&item4;&item4;&item4;&item4;">
  328. <!ENTITY item6 "&item5;&item5;&item5;&item5;&item5;&item5;&item5;&item5;&item5;">
  329. <!ENTITY item7 "&item6;&item6;&item6;&item6;&item6;&item6;&item6;&item6;&item6;">
  330. <!ENTITY item8 "&item7;&item7;&item7;&item7;&item7;&item7;&item7;&item7;&item7;">
  331. ]>
  332. <item>
  333. <description>&item8;</description>
  334. </item>
  335. XML;
  336. $request = new ServerRequest([
  337. 'environment' => [
  338. 'REQUEST_METHOD' => 'POST',
  339. 'CONTENT_TYPE' => 'application/xml',
  340. ],
  341. 'input' => $xml,
  342. ]);
  343. $response = new Response();
  344. $handler = new TestRequestHandler(function ($req) {
  345. $this->assertEquals([], $req->getParsedBody());
  346. return new Response();
  347. });
  348. $parser = new BodyParserMiddleware(['xml' => true]);
  349. $parser->process($request, $handler);
  350. }
  351. /**
  352. * test parsing fails will raise a bad request.
  353. *
  354. * @return void
  355. */
  356. public function testInvokeParseNoArray()
  357. {
  358. $request = new ServerRequest([
  359. 'environment' => [
  360. 'REQUEST_METHOD' => 'POST',
  361. 'CONTENT_TYPE' => 'application/json',
  362. ],
  363. 'input' => 'lol',
  364. ]);
  365. $handler = new TestRequestHandler(function ($req) {
  366. return new Response();
  367. });
  368. $this->expectException(BadRequestException::class);
  369. $parser = new BodyParserMiddleware();
  370. $parser->process($request, $handler);
  371. }
  372. }