Client.php 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
  5. * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  6. *
  7. * Licensed under The MIT License
  8. * Redistributions of files must retain the above copyright notice.
  9. *
  10. * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  11. * @link https://cakephp.org CakePHP(tm) Project
  12. * @since 3.0.0
  13. * @license https://opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Http;
  16. use Cake\Core\App;
  17. use Cake\Core\Exception\CakeException;
  18. use Cake\Core\InstanceConfigTrait;
  19. use Cake\Http\Client\Adapter\Curl;
  20. use Cake\Http\Client\Adapter\Mock as MockAdapter;
  21. use Cake\Http\Client\Adapter\Stream;
  22. use Cake\Http\Client\AdapterInterface;
  23. use Cake\Http\Client\Request;
  24. use Cake\Http\Client\Response;
  25. use Cake\Http\Cookie\CookieCollection;
  26. use Cake\Http\Cookie\CookieInterface;
  27. use Cake\Utility\Hash;
  28. use InvalidArgumentException;
  29. use Laminas\Diactoros\Uri;
  30. use Psr\Http\Client\ClientInterface;
  31. use Psr\Http\Message\RequestInterface;
  32. use Psr\Http\Message\ResponseInterface;
  33. /**
  34. * The end user interface for doing HTTP requests.
  35. *
  36. * ### Scoped clients
  37. *
  38. * If you're doing multiple requests to the same hostname it's often convenient
  39. * to use the constructor arguments to create a scoped client. This allows you
  40. * to keep your code DRY and not repeat hostnames, authentication, and other options.
  41. *
  42. * ### Doing requests
  43. *
  44. * Once you've created an instance of Client you can do requests
  45. * using several methods. Each corresponds to a different HTTP method.
  46. *
  47. * - get()
  48. * - post()
  49. * - put()
  50. * - delete()
  51. * - patch()
  52. *
  53. * ### Cookie management
  54. *
  55. * Client will maintain cookies from the responses done with
  56. * a client instance. These cookies will be automatically added
  57. * to future requests to matching hosts. Cookies will respect the
  58. * `Expires`, `Path` and `Domain` attributes. You can get the client's
  59. * CookieCollection using cookies()
  60. *
  61. * You can use the 'cookieJar' constructor option to provide a custom
  62. * cookie jar instance you've restored from cache/disk. By default
  63. * an empty instance of Cake\Http\Client\CookieCollection will be created.
  64. *
  65. * ### Sending request bodies
  66. *
  67. * By default any POST/PUT/PATCH/DELETE request with $data will
  68. * send their data as `application/x-www-form-urlencoded` unless
  69. * there are attached files. In that case `multipart/form-data`
  70. * will be used.
  71. *
  72. * When sending request bodies you can use the `type` option to
  73. * set the Content-Type for the request:
  74. *
  75. * ```
  76. * $http->get('/users', [], ['type' => 'json']);
  77. * ```
  78. *
  79. * The `type` option sets both the `Content-Type` and `Accept` header, to
  80. * the same mime type. When using `type` you can use either a full mime
  81. * type or an alias. If you need different types in the Accept and Content-Type
  82. * headers you should set them manually and not use `type`
  83. *
  84. * ### Using authentication
  85. *
  86. * By using the `auth` key you can use authentication. The type sub option
  87. * can be used to specify which authentication strategy you want to use.
  88. * CakePHP comes with a few built-in strategies:
  89. *
  90. * - Basic
  91. * - Digest
  92. * - Oauth
  93. *
  94. * ### Using proxies
  95. *
  96. * By using the `proxy` key you can set authentication credentials for
  97. * a proxy if you need to use one. The type sub option can be used to
  98. * specify which authentication strategy you want to use.
  99. * CakePHP comes with built-in support for basic authentication.
  100. */
  101. class Client implements ClientInterface
  102. {
  103. use InstanceConfigTrait;
  104. /**
  105. * Default configuration for the client.
  106. *
  107. * @var array<string, mixed>
  108. */
  109. protected array $_defaultConfig = [
  110. 'adapter' => null,
  111. 'host' => null,
  112. 'port' => null,
  113. 'scheme' => 'http',
  114. 'basePath' => '',
  115. 'timeout' => 30,
  116. 'ssl_verify_peer' => true,
  117. 'ssl_verify_peer_name' => true,
  118. 'ssl_verify_depth' => 5,
  119. 'ssl_verify_host' => true,
  120. 'redirect' => false,
  121. 'protocolVersion' => '1.1',
  122. ];
  123. /**
  124. * List of cookies from responses made with this client.
  125. *
  126. * Cookies are indexed by the cookie's domain or
  127. * request host name.
  128. *
  129. * @var \Cake\Http\Cookie\CookieCollection
  130. */
  131. protected CookieCollection $_cookies;
  132. /**
  133. * Mock adapter for stubbing requests in tests.
  134. *
  135. * @var \Cake\Http\Client\Adapter\Mock|null
  136. */
  137. protected static ?MockAdapter $_mockAdapter = null;
  138. /**
  139. * Adapter for sending requests.
  140. *
  141. * @var \Cake\Http\Client\AdapterInterface
  142. */
  143. protected AdapterInterface $_adapter;
  144. /**
  145. * Create a new HTTP Client.
  146. *
  147. * ### Config options
  148. *
  149. * You can set the following options when creating a client:
  150. *
  151. * - host - The hostname to do requests on.
  152. * - port - The port to use.
  153. * - scheme - The default scheme/protocol to use. Defaults to http.
  154. * - basePath - A path to append to the domain to use. (/api/v1/)
  155. * - timeout - The timeout in seconds. Defaults to 30
  156. * - ssl_verify_peer - Whether SSL certificates should be validated.
  157. * Defaults to true.
  158. * - ssl_verify_peer_name - Whether peer names should be validated.
  159. * Defaults to true.
  160. * - ssl_verify_depth - The maximum certificate chain depth to traverse.
  161. * Defaults to 5.
  162. * - ssl_verify_host - Verify that the certificate and hostname match.
  163. * Defaults to true.
  164. * - redirect - Number of redirects to follow. Defaults to false.
  165. * - adapter - The adapter class name or instance. Defaults to
  166. * \Cake\Http\Client\Adapter\Curl if `curl` extension is loaded else
  167. * \Cake\Http\Client\Adapter\Stream.
  168. * - protocolVersion - The HTTP protocol version to use. Defaults to 1.1
  169. *
  170. * @param array<string, mixed> $config Config options for scoped clients.
  171. * @throws \InvalidArgumentException
  172. */
  173. public function __construct(array $config = [])
  174. {
  175. $this->setConfig($config);
  176. $adapter = $this->_config['adapter'];
  177. if ($adapter === null) {
  178. $adapter = Curl::class;
  179. if (!extension_loaded('curl')) {
  180. $adapter = Stream::class;
  181. }
  182. } else {
  183. $this->setConfig('adapter', null);
  184. }
  185. if (is_string($adapter)) {
  186. $adapter = new $adapter();
  187. }
  188. if (!$adapter instanceof AdapterInterface) {
  189. throw new InvalidArgumentException('Adapter must be an instance of Cake\Http\Client\AdapterInterface');
  190. }
  191. $this->_adapter = $adapter;
  192. if (!empty($this->_config['cookieJar'])) {
  193. $this->_cookies = $this->_config['cookieJar'];
  194. $this->setConfig('cookieJar', null);
  195. } else {
  196. $this->_cookies = new CookieCollection();
  197. }
  198. }
  199. /**
  200. * Client instance returned is scoped to the domain, port, and scheme parsed from the passed URL string. The passed
  201. * string must have a scheme and a domain. Optionally, if a port is included in the string, the port will be scoped
  202. * too. If a path is included in the URL, the client instance will build urls with it prepended.
  203. * Other parts of the url string are ignored.
  204. *
  205. * @param string $url A string URL e.g. https://example.com
  206. * @return static
  207. * @throws \InvalidArgumentException
  208. */
  209. public static function createFromUrl(string $url): static
  210. {
  211. $parts = parse_url($url);
  212. if ($parts === false) {
  213. throw new InvalidArgumentException('String ' . $url . ' did not parse');
  214. }
  215. $config = array_intersect_key($parts, ['scheme' => '', 'port' => '', 'host' => '', 'path' => '']);
  216. if (empty($config['scheme']) || empty($config['host'])) {
  217. throw new InvalidArgumentException('The URL was parsed but did not contain a scheme or host');
  218. }
  219. if (isset($config['path'])) {
  220. $config['basePath'] = $config['path'];
  221. unset($config['path']);
  222. }
  223. return new static($config);
  224. }
  225. /**
  226. * Get the cookies stored in the Client.
  227. *
  228. * @return \Cake\Http\Cookie\CookieCollection
  229. */
  230. public function cookies(): CookieCollection
  231. {
  232. return $this->_cookies;
  233. }
  234. /**
  235. * Adds a cookie to the Client collection.
  236. *
  237. * @param \Cake\Http\Cookie\CookieInterface $cookie Cookie object.
  238. * @return $this
  239. * @throws \InvalidArgumentException
  240. */
  241. public function addCookie(CookieInterface $cookie)
  242. {
  243. if (!$cookie->getDomain() || !$cookie->getPath()) {
  244. throw new InvalidArgumentException('Cookie must have a domain and a path set.');
  245. }
  246. $this->_cookies = $this->_cookies->add($cookie);
  247. return $this;
  248. }
  249. /**
  250. * Do a GET request.
  251. *
  252. * The $data argument supports a special `_content` key
  253. * for providing a request body in a GET request. This is
  254. * generally not used, but services like ElasticSearch use
  255. * this feature.
  256. *
  257. * @param string $url The url or path you want to request.
  258. * @param array|string $data The query data you want to send.
  259. * @param array<string, mixed> $options Additional options for the request.
  260. * @return \Cake\Http\Client\Response
  261. */
  262. public function get(string $url, array|string $data = [], array $options = []): Response
  263. {
  264. $options = $this->_mergeOptions($options);
  265. $body = null;
  266. if (is_array($data) && isset($data['_content'])) {
  267. $body = $data['_content'];
  268. unset($data['_content']);
  269. }
  270. $url = $this->buildUrl($url, $data, $options);
  271. return $this->_doRequest(
  272. Request::METHOD_GET,
  273. $url,
  274. $body,
  275. $options
  276. );
  277. }
  278. /**
  279. * Do a POST request.
  280. *
  281. * @param string $url The url or path you want to request.
  282. * @param mixed $data The post data you want to send.
  283. * @param array<string, mixed> $options Additional options for the request.
  284. * @return \Cake\Http\Client\Response
  285. */
  286. public function post(string $url, mixed $data = [], array $options = []): Response
  287. {
  288. $options = $this->_mergeOptions($options);
  289. $url = $this->buildUrl($url, [], $options);
  290. return $this->_doRequest(Request::METHOD_POST, $url, $data, $options);
  291. }
  292. /**
  293. * Do a PUT request.
  294. *
  295. * @param string $url The url or path you want to request.
  296. * @param mixed $data The request data you want to send.
  297. * @param array<string, mixed> $options Additional options for the request.
  298. * @return \Cake\Http\Client\Response
  299. */
  300. public function put(string $url, mixed $data = [], array $options = []): Response
  301. {
  302. $options = $this->_mergeOptions($options);
  303. $url = $this->buildUrl($url, [], $options);
  304. return $this->_doRequest(Request::METHOD_PUT, $url, $data, $options);
  305. }
  306. /**
  307. * Do a PATCH request.
  308. *
  309. * @param string $url The url or path you want to request.
  310. * @param mixed $data The request data you want to send.
  311. * @param array<string, mixed> $options Additional options for the request.
  312. * @return \Cake\Http\Client\Response
  313. */
  314. public function patch(string $url, mixed $data = [], array $options = []): Response
  315. {
  316. $options = $this->_mergeOptions($options);
  317. $url = $this->buildUrl($url, [], $options);
  318. return $this->_doRequest(Request::METHOD_PATCH, $url, $data, $options);
  319. }
  320. /**
  321. * Do an OPTIONS request.
  322. *
  323. * @param string $url The url or path you want to request.
  324. * @param mixed $data The request data you want to send.
  325. * @param array<string, mixed> $options Additional options for the request.
  326. * @return \Cake\Http\Client\Response
  327. */
  328. public function options(string $url, mixed $data = [], array $options = []): Response
  329. {
  330. $options = $this->_mergeOptions($options);
  331. $url = $this->buildUrl($url, [], $options);
  332. return $this->_doRequest(Request::METHOD_OPTIONS, $url, $data, $options);
  333. }
  334. /**
  335. * Do a TRACE request.
  336. *
  337. * @param string $url The url or path you want to request.
  338. * @param mixed $data The request data you want to send.
  339. * @param array<string, mixed> $options Additional options for the request.
  340. * @return \Cake\Http\Client\Response
  341. */
  342. public function trace(string $url, mixed $data = [], array $options = []): Response
  343. {
  344. $options = $this->_mergeOptions($options);
  345. $url = $this->buildUrl($url, [], $options);
  346. return $this->_doRequest(Request::METHOD_TRACE, $url, $data, $options);
  347. }
  348. /**
  349. * Do a DELETE request.
  350. *
  351. * @param string $url The url or path you want to request.
  352. * @param mixed $data The request data you want to send.
  353. * @param array<string, mixed> $options Additional options for the request.
  354. * @return \Cake\Http\Client\Response
  355. */
  356. public function delete(string $url, mixed $data = [], array $options = []): Response
  357. {
  358. $options = $this->_mergeOptions($options);
  359. $url = $this->buildUrl($url, [], $options);
  360. return $this->_doRequest(Request::METHOD_DELETE, $url, $data, $options);
  361. }
  362. /**
  363. * Do a HEAD request.
  364. *
  365. * @param string $url The url or path you want to request.
  366. * @param array $data The query string data you want to send.
  367. * @param array<string, mixed> $options Additional options for the request.
  368. * @return \Cake\Http\Client\Response
  369. */
  370. public function head(string $url, array $data = [], array $options = []): Response
  371. {
  372. $options = $this->_mergeOptions($options);
  373. $url = $this->buildUrl($url, $data, $options);
  374. return $this->_doRequest(Request::METHOD_HEAD, $url, '', $options);
  375. }
  376. /**
  377. * Helper method for doing non-GET requests.
  378. *
  379. * @param string $method HTTP method.
  380. * @param string $url URL to request.
  381. * @param mixed $data The request body.
  382. * @param array<string, mixed> $options The options to use. Contains auth, proxy, etc.
  383. * @return \Cake\Http\Client\Response
  384. */
  385. protected function _doRequest(string $method, string $url, mixed $data, array $options): Response
  386. {
  387. $request = $this->_createRequest(
  388. $method,
  389. $url,
  390. $data,
  391. $options
  392. );
  393. return $this->send($request, $options);
  394. }
  395. /**
  396. * Does a recursive merge of the parameter with the scope config.
  397. *
  398. * @param array<string, mixed> $options Options to merge.
  399. * @return array Options merged with set config.
  400. */
  401. protected function _mergeOptions(array $options): array
  402. {
  403. return Hash::merge($this->_config, $options);
  404. }
  405. /**
  406. * Sends a PSR-7 request and returns a PSR-7 response.
  407. *
  408. * @param \Psr\Http\Message\RequestInterface $request Request instance.
  409. * @return \Psr\Http\Message\ResponseInterface Response instance.
  410. * @throws \Psr\Http\Client\ClientExceptionInterface If an error happens while processing the request.
  411. */
  412. public function sendRequest(RequestInterface $request): ResponseInterface
  413. {
  414. return $this->send($request, $this->_config);
  415. }
  416. /**
  417. * Send a request.
  418. *
  419. * Used internally by other methods, but can also be used to send
  420. * handcrafted Request objects.
  421. *
  422. * @param \Psr\Http\Message\RequestInterface $request The request to send.
  423. * @param array<string, mixed> $options Additional options to use.
  424. * @return \Cake\Http\Client\Response
  425. */
  426. public function send(RequestInterface $request, array $options = []): Response
  427. {
  428. $redirects = 0;
  429. if (isset($options['redirect'])) {
  430. $redirects = (int)$options['redirect'];
  431. unset($options['redirect']);
  432. }
  433. do {
  434. $response = $this->_sendRequest($request, $options);
  435. $handleRedirect = $response->isRedirect() && $redirects-- > 0;
  436. if ($handleRedirect) {
  437. $url = $request->getUri();
  438. $location = $response->getHeaderLine('Location');
  439. $locationUrl = $this->buildUrl($location, [], [
  440. 'host' => $url->getHost(),
  441. 'port' => $url->getPort(),
  442. 'scheme' => $url->getScheme(),
  443. 'protocolRelative' => true,
  444. ]);
  445. $request = $request->withUri(new Uri($locationUrl));
  446. $request = $this->_cookies->addToRequest($request, []);
  447. }
  448. } while ($handleRedirect);
  449. return $response;
  450. }
  451. /**
  452. * Clear all mocked responses
  453. *
  454. * @return void
  455. */
  456. public static function clearMockResponses(): void
  457. {
  458. static::$_mockAdapter = null;
  459. }
  460. /**
  461. * Add a mocked response.
  462. *
  463. * Mocked responses are stored in an adapter that is called
  464. * _before_ the network adapter is called.
  465. *
  466. * ### Matching Requests
  467. *
  468. * TODO finish this.
  469. *
  470. * ### Options
  471. *
  472. * - `match` An additional closure to match requests with.
  473. *
  474. * @param string $method The HTTP method being mocked.
  475. * @param string $url The URL being matched. See above for examples.
  476. * @param \Cake\Http\Client\Response $response The response that matches the request.
  477. * @param array<string, mixed> $options See above.
  478. * @return void
  479. */
  480. public static function addMockResponse(string $method, string $url, Response $response, array $options = []): void
  481. {
  482. if (!static::$_mockAdapter) {
  483. static::$_mockAdapter = new MockAdapter();
  484. }
  485. $request = new Request($url, $method);
  486. static::$_mockAdapter->addResponse($request, $response, $options);
  487. }
  488. /**
  489. * Send a request without redirection.
  490. *
  491. * @param \Psr\Http\Message\RequestInterface $request The request to send.
  492. * @param array<string, mixed> $options Additional options to use.
  493. * @return \Cake\Http\Client\Response
  494. */
  495. protected function _sendRequest(RequestInterface $request, array $options): Response
  496. {
  497. if (static::$_mockAdapter) {
  498. $responses = static::$_mockAdapter->send($request, $options);
  499. }
  500. if (empty($responses)) {
  501. $responses = $this->_adapter->send($request, $options);
  502. }
  503. foreach ($responses as $response) {
  504. $this->_cookies = $this->_cookies->addFromResponse($response, $request);
  505. }
  506. return array_pop($responses);
  507. }
  508. /**
  509. * Generate a URL based on the scoped client options.
  510. *
  511. * @param string $url Either a full URL or just the path.
  512. * @param array|string $query The query data for the URL.
  513. * @param array<string, mixed> $options The config options stored with Client::config()
  514. * @return string A complete url with scheme, port, host, and path.
  515. */
  516. public function buildUrl(string $url, array|string $query = [], array $options = []): string
  517. {
  518. if (empty($options) && empty($query)) {
  519. return $url;
  520. }
  521. $defaults = [
  522. 'host' => null,
  523. 'port' => null,
  524. 'scheme' => 'http',
  525. 'basePath' => '',
  526. 'protocolRelative' => false,
  527. ];
  528. $options += $defaults;
  529. if ($query) {
  530. $q = str_contains($url, '?') ? '&' : '?';
  531. $url .= $q;
  532. $url .= is_string($query) ? $query : http_build_query($query, '', '&', PHP_QUERY_RFC3986);
  533. }
  534. if ($options['protocolRelative'] && preg_match('#^//#', $url)) {
  535. $url = $options['scheme'] . ':' . $url;
  536. }
  537. if (preg_match('#^https?://#', $url)) {
  538. return $url;
  539. }
  540. $defaultPorts = [
  541. 'http' => 80,
  542. 'https' => 443,
  543. ];
  544. $out = $options['scheme'] . '://' . $options['host'];
  545. if ($options['port'] && (int)$options['port'] !== $defaultPorts[$options['scheme']]) {
  546. $out .= ':' . $options['port'];
  547. }
  548. if (!empty($options['basePath'])) {
  549. $out .= '/' . trim($options['basePath'], '/');
  550. }
  551. $out .= '/' . ltrim($url, '/');
  552. return $out;
  553. }
  554. /**
  555. * Creates a new request object based on the parameters.
  556. *
  557. * @param string $method HTTP method name.
  558. * @param string $url The url including query string.
  559. * @param mixed $data The request body.
  560. * @param array<string, mixed> $options The options to use. Contains auth, proxy, etc.
  561. * @return \Cake\Http\Client\Request
  562. */
  563. protected function _createRequest(string $method, string $url, mixed $data, array $options): Request
  564. {
  565. /** @var array<non-empty-string, non-empty-string> $headers */
  566. $headers = (array)($options['headers'] ?? []);
  567. if (isset($options['type'])) {
  568. $headers = array_merge($headers, $this->_typeHeaders($options['type']));
  569. }
  570. if (is_string($data) && !isset($headers['Content-Type']) && !isset($headers['content-type'])) {
  571. $headers['Content-Type'] = 'application/x-www-form-urlencoded';
  572. }
  573. $request = new Request($url, $method, $headers, $data);
  574. /** @var \Cake\Http\Client\Request $request */
  575. $request = $request->withProtocolVersion($this->getConfig('protocolVersion'));
  576. $cookies = $options['cookies'] ?? [];
  577. /** @var \Cake\Http\Client\Request $request */
  578. $request = $this->_cookies->addToRequest($request, $cookies);
  579. if (isset($options['auth'])) {
  580. $request = $this->_addAuthentication($request, $options);
  581. }
  582. if (isset($options['proxy'])) {
  583. $request = $this->_addProxy($request, $options);
  584. }
  585. return $request;
  586. }
  587. /**
  588. * Returns headers for Accept/Content-Type based on a short type
  589. * or full mime-type.
  590. *
  591. * @phpstan-param non-empty-string $type
  592. * @param string $type short type alias or full mimetype.
  593. * @return array<string, string> Headers to set on the request.
  594. * @throws \Cake\Core\Exception\CakeException When an unknown type alias is used.
  595. * @psalm-return array<non-empty-string, non-empty-string>
  596. */
  597. protected function _typeHeaders(string $type): array
  598. {
  599. if (str_contains($type, '/')) {
  600. return [
  601. 'Accept' => $type,
  602. 'Content-Type' => $type,
  603. ];
  604. }
  605. $typeMap = [
  606. 'json' => 'application/json',
  607. 'xml' => 'application/xml',
  608. ];
  609. if (!isset($typeMap[$type])) {
  610. throw new CakeException("Unknown type alias '$type'.");
  611. }
  612. return [
  613. 'Accept' => $typeMap[$type],
  614. 'Content-Type' => $typeMap[$type],
  615. ];
  616. }
  617. /**
  618. * Add authentication headers to the request.
  619. *
  620. * Uses the authentication type to choose the correct strategy
  621. * and use its methods to add headers.
  622. *
  623. * @param \Cake\Http\Client\Request $request The request to modify.
  624. * @param array<string, mixed> $options Array of options containing the 'auth' key.
  625. * @return \Cake\Http\Client\Request The updated request object.
  626. */
  627. protected function _addAuthentication(Request $request, array $options): Request
  628. {
  629. $auth = $options['auth'];
  630. /** @var \Cake\Http\Client\Auth\Basic $adapter */
  631. $adapter = $this->_createAuth($auth, $options);
  632. return $adapter->authentication($request, $options['auth']);
  633. }
  634. /**
  635. * Add proxy authentication headers.
  636. *
  637. * Uses the authentication type to choose the correct strategy
  638. * and use its methods to add headers.
  639. *
  640. * @param \Cake\Http\Client\Request $request The request to modify.
  641. * @param array<string, mixed> $options Array of options containing the 'proxy' key.
  642. * @return \Cake\Http\Client\Request The updated request object.
  643. */
  644. protected function _addProxy(Request $request, array $options): Request
  645. {
  646. $auth = $options['proxy'];
  647. /** @var \Cake\Http\Client\Auth\Basic $adapter */
  648. $adapter = $this->_createAuth($auth, $options);
  649. return $adapter->proxyAuthentication($request, $options['proxy']);
  650. }
  651. /**
  652. * Create the authentication strategy.
  653. *
  654. * Use the configuration options to create the correct
  655. * authentication strategy handler.
  656. *
  657. * @param array $auth The authentication options to use.
  658. * @param array<string, mixed> $options The overall request options to use.
  659. * @return object Authentication strategy instance.
  660. * @throws \Cake\Core\Exception\CakeException when an invalid strategy is chosen.
  661. */
  662. protected function _createAuth(array $auth, array $options): object
  663. {
  664. if (empty($auth['type'])) {
  665. $auth['type'] = 'basic';
  666. }
  667. $name = ucfirst($auth['type']);
  668. $class = App::className($name, 'Http/Client/Auth');
  669. if (!$class) {
  670. throw new CakeException(
  671. sprintf('Invalid authentication type %s', $name)
  672. );
  673. }
  674. return new $class($this, $options);
  675. }
  676. }