PageRenderTime 44ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Http/ResponseTransformer.php

http://github.com/cakephp/cakephp
PHP | 237 lines | 151 code | 22 blank | 64 comment | 21 complexity | 434fe779babcc0998922c6909b8688df MD5 | raw file
Possible License(s): JSON
  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.3.0
  13. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Http;
  16. use Cake\Network\Response as CakeResponse;
  17. use Psr\Http\Message\ResponseInterface as PsrResponse;
  18. use Zend\Diactoros\CallbackStream;
  19. use Zend\Diactoros\Response as DiactorosResponse;
  20. use Zend\Diactoros\Stream;
  21. /**
  22. * This class converts PSR7 responses into CakePHP ones and back again.
  23. *
  24. * By bridging the CakePHP and PSR7 responses together, applications
  25. * can be embedded as PSR7 middleware in a fully compatible way.
  26. *
  27. * @internal
  28. */
  29. class ResponseTransformer
  30. {
  31. /**
  32. * Convert a PSR7 Response into a CakePHP one.
  33. *
  34. * @param PsrResponse $response The response to convert.
  35. * @return CakeResponse The equivalent CakePHP response
  36. */
  37. public static function toCake(PsrResponse $response)
  38. {
  39. $body = static::getBody($response);
  40. $data = [
  41. 'status' => $response->getStatusCode(),
  42. 'body' => $body['body'],
  43. ];
  44. $cake = new CakeResponse($data);
  45. if ($body['file']) {
  46. $cake->file($body['file']);
  47. }
  48. $cookies = static::parseCookies($response->getHeader('Set-Cookie'));
  49. foreach ($cookies as $cookie) {
  50. $cake->cookie($cookie);
  51. }
  52. $headers = static::collapseHeaders($response);
  53. $cake->header($headers);
  54. if (!empty($headers['Content-Type'])) {
  55. $cake->type($headers['Content-Type']);
  56. }
  57. return $cake;
  58. }
  59. /**
  60. * Get the response body from a PSR7 Response.
  61. *
  62. * @param PsrResponse $response The response to convert.
  63. * @return array A hash of 'body' and 'file'
  64. */
  65. protected static function getBody(PsrResponse $response)
  66. {
  67. $stream = $response->getBody();
  68. if ($stream->getMetadata('wrapper_type') === 'plainfile') {
  69. return ['body' => '', 'file' => $stream->getMetadata('uri')];
  70. }
  71. if ($stream->getSize() === 0) {
  72. return ['body' => '', 'file' => false];
  73. }
  74. $stream->rewind();
  75. return ['body' => $stream->getContents(), 'file' => false];
  76. }
  77. /**
  78. * Parse the Set-Cookie headers in a PSR7 response
  79. * into the format CakePHP expects.
  80. *
  81. * @param array $cookieHeader A list of Set-Cookie headers.
  82. * @return array Parsed cookie data.
  83. */
  84. protected static function parseCookies(array $cookieHeader)
  85. {
  86. $cookies = [];
  87. foreach ($cookieHeader as $cookie) {
  88. if (strpos($cookie, '";"') !== false) {
  89. $cookie = str_replace('";"', "{__cookie_replace__}", $cookie);
  90. $parts = preg_split('/\;[ \t]*/', $cookie);
  91. $parts = str_replace("{__cookie_replace__}", '";"', $parts);
  92. } else {
  93. $parts = preg_split('/\;[ \t]*/', $cookie);
  94. }
  95. list($name, $value) = explode('=', array_shift($parts), 2);
  96. $parsed = ['name' => $name, 'value' => urldecode($value)];
  97. foreach ($parts as $part) {
  98. if (strpos($part, '=') !== false) {
  99. list($key, $value) = explode('=', $part);
  100. } else {
  101. $key = $part;
  102. $value = true;
  103. }
  104. $key = strtolower($key);
  105. if ($key === 'httponly') {
  106. $key = 'httpOnly';
  107. }
  108. if ($key === 'expires') {
  109. $key = 'expire';
  110. $value = strtotime($value);
  111. }
  112. if (!isset($parsed[$key])) {
  113. $parsed[$key] = $value;
  114. }
  115. }
  116. $cookies[] = $parsed;
  117. }
  118. return $cookies;
  119. }
  120. /**
  121. * Convert a PSR7 Response headers into a flat array
  122. *
  123. * @param PsrResponse $response The response to convert.
  124. * @return CakeResponse The equivalent CakePHP response
  125. */
  126. protected static function collapseHeaders(PsrResponse $response)
  127. {
  128. $out = [];
  129. foreach ($response->getHeaders() as $name => $value) {
  130. if (count($value) === 1) {
  131. $out[$name] = $value[0];
  132. } else {
  133. $out[$name] = $value;
  134. }
  135. }
  136. return $out;
  137. }
  138. /**
  139. * Convert a CakePHP response into a PSR7 one.
  140. *
  141. * @param CakeResponse $response The CakePHP response to convert
  142. * @return PsrResponse $response The equivalent PSR7 response.
  143. */
  144. public static function toPsr(CakeResponse $response)
  145. {
  146. $status = $response->statusCode();
  147. $headers = $response->header();
  148. if (!isset($headers['Content-Type'])) {
  149. $headers['Content-Type'] = $response->type();
  150. }
  151. if ($response->cookie()) {
  152. $headers['Set-Cookie'] = static::buildCookieHeader($response->cookie());
  153. }
  154. $stream = static::getStream($response);
  155. return new DiactorosResponse($stream, $status, $headers);
  156. }
  157. /**
  158. * Convert an array of cookies into header lines.
  159. *
  160. * @param array $cookies The cookies to serialize.
  161. * @return array A list of cookie header values.
  162. */
  163. protected static function buildCookieHeader($cookies)
  164. {
  165. $headers = [];
  166. foreach ($cookies as $cookie) {
  167. $parts = [
  168. sprintf('%s=%s', urlencode($cookie['name']), urlencode($cookie['value']))
  169. ];
  170. if ($cookie['expire']) {
  171. $cookie['expire'] = gmdate('D, d M Y H:i:s T', $cookie['expire']);
  172. }
  173. $attributes = [
  174. 'expire' => 'Expires=%s',
  175. 'path' => 'Path=%s',
  176. 'domain' => 'Domain=%s',
  177. 'httpOnly' => 'HttpOnly',
  178. 'secure' => 'Secure',
  179. ];
  180. foreach ($attributes as $key => $attr) {
  181. if ($cookie[$key]) {
  182. $parts[] = sprintf($attr, $cookie[$key]);
  183. }
  184. }
  185. $headers[] = implode('; ', $parts);
  186. }
  187. return $headers;
  188. }
  189. /**
  190. * Get the stream for the new response.
  191. *
  192. * @param \Cake\Network\Response $response The cake response to extract the body from.
  193. * @return Psr\Http\Message\StreamInterface The stream.
  194. */
  195. protected static function getStream($response)
  196. {
  197. $stream = 'php://memory';
  198. $body = $response->body();
  199. if (is_string($body) && strlen($body)) {
  200. $stream = new Stream('php://memory', 'wb');
  201. $stream->write($body);
  202. return $stream;
  203. }
  204. if (is_callable($body)) {
  205. $stream = new CallbackStream($body);
  206. return $stream;
  207. }
  208. $file = $response->getFile();
  209. if ($file) {
  210. $stream = new Stream($file->path, 'rb');
  211. return $stream;
  212. }
  213. return $stream;
  214. }
  215. }