BodyParserMiddleware.php 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  5. *
  6. * Licensed under The MIT License
  7. * For full copyright and license information, please see the LICENSE.txt
  8. * Redistributions of files must retain the above copyright notice.
  9. *
  10. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  11. * @link http://cakephp.org CakePHP(tm) Project
  12. * @since 3.6.0
  13. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Http\Middleware;
  16. use Cake\Http\Exception\BadRequestException;
  17. use Cake\Utility\Exception\XmlException;
  18. use Cake\Utility\Xml;
  19. use Psr\Http\Message\ResponseInterface;
  20. use Psr\Http\Message\ServerRequestInterface;
  21. /**
  22. * Parse encoded request body data.
  23. *
  24. * Enables JSON and XML request payloads to be parsed into the request's
  25. * Provides CSRF protection & validation.
  26. *
  27. * You can also add your own request body parsers using the `addParser()` method.
  28. */
  29. class BodyParserMiddleware
  30. {
  31. /**
  32. * Registered Parsers
  33. *
  34. * @var array
  35. */
  36. protected $parsers = [];
  37. /**
  38. * The HTTP methods to parse data on.
  39. *
  40. * @var array
  41. */
  42. protected $methods = ['PUT', 'POST', 'PATCH', 'DELETE'];
  43. /**
  44. * Constructor
  45. *
  46. * ### Options
  47. *
  48. * - `json` Set to false to disable json body parsing.
  49. * - `xml` Set to true to enable XML parsing. Defaults to false, as XML
  50. * handling requires more care than JSON does.
  51. * - `methods` The HTTP methods to parse on. Defaults to PUT, POST, PATCH DELETE.
  52. *
  53. * @param array $options The options to use. See above.
  54. */
  55. public function __construct(array $options = [])
  56. {
  57. $options += ['json' => true, 'xml' => false, 'methods' => null];
  58. if ($options['json']) {
  59. $this->addParser(
  60. ['application/json', 'text/json'],
  61. [$this, 'decodeJson']
  62. );
  63. }
  64. if ($options['xml']) {
  65. $this->addParser(
  66. ['application/xml', 'text/xml'],
  67. [$this, 'decodeXml']
  68. );
  69. }
  70. if ($options['methods']) {
  71. $this->setMethods($options['methods']);
  72. }
  73. }
  74. /**
  75. * Set the HTTP methods to parse request bodies on.
  76. *
  77. * @param array $methods The methods to parse data on.
  78. * @return $this
  79. */
  80. public function setMethods(array $methods)
  81. {
  82. $this->methods = $methods;
  83. return $this;
  84. }
  85. /**
  86. * Add a parser.
  87. *
  88. * Map a set of content-type header values to be parsed by the $parser.
  89. *
  90. * ### Example
  91. *
  92. * An naive CSV request body parser could be built like so:
  93. *
  94. * ```
  95. * $parser->addParser(['text/csv'], function ($body) {
  96. * return str_getcsv($body);
  97. * });
  98. * ```
  99. *
  100. * @param array $types An array of content-type header values to match. eg. application/json
  101. * @param callable $parser The parser function. Must return an array of data to be inserted
  102. * into the request.
  103. * @return $this
  104. */
  105. public function addParser(array $types, callable $parser)
  106. {
  107. foreach ($types as $type) {
  108. $type = strtolower($type);
  109. $this->parsers[$type] = $parser;
  110. }
  111. return $this;
  112. }
  113. /**
  114. * Apply the middleware.
  115. *
  116. * Will modify the request adding a parsed body if the content-type is known.
  117. *
  118. * @param \Psr\Http\Message\ServerRequestInterface $request The request.
  119. * @param \Psr\Http\Message\ResponseInterface $response The response.
  120. * @param callable $next Callback to invoke the next middleware.
  121. * @return \Cake\Http\Response A response
  122. */
  123. public function __invoke(ServerRequestInterface $request, ResponseInterface $response, $next)
  124. {
  125. if (!in_array($request->getMethod(), $this->methods)) {
  126. return $next($request, $response);
  127. }
  128. list($type) = explode(';', $request->getHeaderLine('Content-Type'));
  129. $type = strtolower($type);
  130. if (!isset($this->parsers[$type])) {
  131. return $next($request, $response);
  132. }
  133. $parser = $this->parsers[$type];
  134. $result = $parser($request->getBody()->getContents());
  135. if (!is_array($result)) {
  136. throw new BadRequestException();
  137. }
  138. $request = $request->withParsedBody($result);
  139. return $next($request, $response);
  140. }
  141. /**
  142. * Decode JSON into an array.
  143. *
  144. * @param string $body The request body to decode
  145. * @return array
  146. */
  147. protected function decodeJson($body)
  148. {
  149. return json_decode($body, true);
  150. }
  151. /**
  152. * Decode XML into an array.
  153. *
  154. * @param string $body The request body to decode
  155. * @return array
  156. */
  157. protected function decodeXml($body)
  158. {
  159. try {
  160. $xml = Xml::build($body, ['return' => 'domdocument', 'readFile' => false]);
  161. // We might not get child nodes if there are nested inline entities.
  162. if ($xml->childNodes->length > 0) {
  163. return Xml::toArray($xml);
  164. }
  165. return [];
  166. } catch (XmlException $e) {
  167. return [];
  168. }
  169. }
  170. }