Client.php 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
  5. * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
  6. * SPDX-License-Identifier: AGPL-3.0-only
  7. */
  8. namespace OC\Http\Client;
  9. use GuzzleHttp\Client as GuzzleClient;
  10. use GuzzleHttp\Promise\PromiseInterface;
  11. use GuzzleHttp\RequestOptions;
  12. use OCP\Http\Client\IClient;
  13. use OCP\Http\Client\IPromise;
  14. use OCP\Http\Client\IResponse;
  15. use OCP\Http\Client\LocalServerException;
  16. use OCP\ICertificateManager;
  17. use OCP\IConfig;
  18. use OCP\Security\IRemoteHostValidator;
  19. use Psr\Log\LoggerInterface;
  20. use function parse_url;
  21. /**
  22. * Class Client
  23. *
  24. * @package OC\Http
  25. */
  26. class Client implements IClient {
  27. /** @var GuzzleClient */
  28. private $client;
  29. /** @var IConfig */
  30. private $config;
  31. /** @var ICertificateManager */
  32. private $certificateManager;
  33. private IRemoteHostValidator $remoteHostValidator;
  34. public function __construct(
  35. IConfig $config,
  36. ICertificateManager $certificateManager,
  37. GuzzleClient $client,
  38. IRemoteHostValidator $remoteHostValidator,
  39. protected LoggerInterface $logger,
  40. ) {
  41. $this->config = $config;
  42. $this->client = $client;
  43. $this->certificateManager = $certificateManager;
  44. $this->remoteHostValidator = $remoteHostValidator;
  45. }
  46. private function buildRequestOptions(array $options): array {
  47. $proxy = $this->getProxyUri();
  48. $defaults = [
  49. RequestOptions::VERIFY => $this->getCertBundle(),
  50. RequestOptions::TIMEOUT => 30,
  51. ];
  52. $options['nextcloud']['allow_local_address'] = $this->isLocalAddressAllowed($options);
  53. if ($options['nextcloud']['allow_local_address'] === false) {
  54. $onRedirectFunction = function (
  55. \Psr\Http\Message\RequestInterface $request,
  56. \Psr\Http\Message\ResponseInterface $response,
  57. \Psr\Http\Message\UriInterface $uri
  58. ) use ($options) {
  59. $this->preventLocalAddress($uri->__toString(), $options);
  60. };
  61. $defaults[RequestOptions::ALLOW_REDIRECTS] = [
  62. 'on_redirect' => $onRedirectFunction
  63. ];
  64. }
  65. // Only add RequestOptions::PROXY if Nextcloud is explicitly
  66. // configured to use a proxy. This is needed in order not to override
  67. // Guzzle default values.
  68. if ($proxy !== null) {
  69. $defaults[RequestOptions::PROXY] = $proxy;
  70. }
  71. $options = array_merge($defaults, $options);
  72. if (!isset($options[RequestOptions::HEADERS]['User-Agent'])) {
  73. $options[RequestOptions::HEADERS]['User-Agent'] = 'Nextcloud Server Crawler';
  74. }
  75. if (!isset($options[RequestOptions::HEADERS]['Accept-Encoding'])) {
  76. $options[RequestOptions::HEADERS]['Accept-Encoding'] = 'gzip';
  77. }
  78. // Fallback for save_to
  79. if (isset($options['save_to'])) {
  80. $options['sink'] = $options['save_to'];
  81. unset($options['save_to']);
  82. }
  83. return $options;
  84. }
  85. private function getCertBundle(): string {
  86. // If the instance is not yet setup we need to use the static path as
  87. // $this->certificateManager->getAbsoluteBundlePath() tries to instantiate
  88. // a view
  89. if (!$this->config->getSystemValueBool('installed', false)) {
  90. return \OC::$SERVERROOT . '/resources/config/ca-bundle.crt';
  91. }
  92. return $this->certificateManager->getAbsoluteBundlePath();
  93. }
  94. /**
  95. * Returns a null or an associative array specifying the proxy URI for
  96. * 'http' and 'https' schemes, in addition to a 'no' key value pair
  97. * providing a list of host names that should not be proxied to.
  98. *
  99. * @return array|null
  100. *
  101. * The return array looks like:
  102. * [
  103. * 'http' => 'username:password@proxy.example.com',
  104. * 'https' => 'username:password@proxy.example.com',
  105. * 'no' => ['foo.com', 'bar.com']
  106. * ]
  107. *
  108. */
  109. private function getProxyUri(): ?array {
  110. $proxyHost = $this->config->getSystemValueString('proxy', '');
  111. if ($proxyHost === '') {
  112. return null;
  113. }
  114. $proxyUserPwd = $this->config->getSystemValueString('proxyuserpwd', '');
  115. if ($proxyUserPwd !== '') {
  116. $proxyHost = $proxyUserPwd . '@' . $proxyHost;
  117. }
  118. $proxy = [
  119. 'http' => $proxyHost,
  120. 'https' => $proxyHost,
  121. ];
  122. $proxyExclude = $this->config->getSystemValue('proxyexclude', []);
  123. if ($proxyExclude !== [] && $proxyExclude !== null) {
  124. $proxy['no'] = $proxyExclude;
  125. }
  126. return $proxy;
  127. }
  128. private function isLocalAddressAllowed(array $options) : bool {
  129. if (($options['nextcloud']['allow_local_address'] ?? false) ||
  130. $this->config->getSystemValueBool('allow_local_remote_servers', false)) {
  131. return true;
  132. }
  133. return false;
  134. }
  135. protected function preventLocalAddress(string $uri, array $options): void {
  136. if ($this->isLocalAddressAllowed($options)) {
  137. return;
  138. }
  139. $host = parse_url($uri, PHP_URL_HOST);
  140. if ($host === false || $host === null) {
  141. throw new LocalServerException('Could not detect any host');
  142. }
  143. if (!$this->remoteHostValidator->isValid($host)) {
  144. throw new LocalServerException('Host "'.$host.'" violates local access rules');
  145. }
  146. }
  147. /**
  148. * Sends a GET request
  149. *
  150. * @param string $uri
  151. * @param array $options Array such as
  152. * 'query' => [
  153. * 'field' => 'abc',
  154. * 'other_field' => '123',
  155. * 'file_name' => fopen('/path/to/file', 'r'),
  156. * ],
  157. * 'headers' => [
  158. * 'foo' => 'bar',
  159. * ],
  160. * 'cookies' => [
  161. * 'foo' => 'bar',
  162. * ],
  163. * 'allow_redirects' => [
  164. * 'max' => 10, // allow at most 10 redirects.
  165. * 'strict' => true, // use "strict" RFC compliant redirects.
  166. * 'referer' => true, // add a Referer header
  167. * 'protocols' => ['https'] // only allow https URLs
  168. * ],
  169. * 'sink' => '/path/to/file', // save to a file or a stream
  170. * 'verify' => true, // bool or string to CA file
  171. * 'debug' => true,
  172. * 'timeout' => 5,
  173. * @return IResponse
  174. * @throws \Exception If the request could not get completed
  175. */
  176. public function get(string $uri, array $options = []): IResponse {
  177. $this->preventLocalAddress($uri, $options);
  178. $response = $this->client->request('get', $uri, $this->buildRequestOptions($options));
  179. $isStream = isset($options['stream']) && $options['stream'];
  180. return new Response($response, $isStream);
  181. }
  182. /**
  183. * Sends a HEAD request
  184. *
  185. * @param string $uri
  186. * @param array $options Array such as
  187. * 'headers' => [
  188. * 'foo' => 'bar',
  189. * ],
  190. * 'cookies' => [
  191. * 'foo' => 'bar',
  192. * ],
  193. * 'allow_redirects' => [
  194. * 'max' => 10, // allow at most 10 redirects.
  195. * 'strict' => true, // use "strict" RFC compliant redirects.
  196. * 'referer' => true, // add a Referer header
  197. * 'protocols' => ['https'] // only allow https URLs
  198. * ],
  199. * 'sink' => '/path/to/file', // save to a file or a stream
  200. * 'verify' => true, // bool or string to CA file
  201. * 'debug' => true,
  202. * 'timeout' => 5,
  203. * @return IResponse
  204. * @throws \Exception If the request could not get completed
  205. */
  206. public function head(string $uri, array $options = []): IResponse {
  207. $this->preventLocalAddress($uri, $options);
  208. $response = $this->client->request('head', $uri, $this->buildRequestOptions($options));
  209. return new Response($response);
  210. }
  211. /**
  212. * Sends a POST request
  213. *
  214. * @param string $uri
  215. * @param array $options Array such as
  216. * 'body' => [
  217. * 'field' => 'abc',
  218. * 'other_field' => '123',
  219. * 'file_name' => fopen('/path/to/file', 'r'),
  220. * ],
  221. * 'headers' => [
  222. * 'foo' => 'bar',
  223. * ],
  224. * 'cookies' => [
  225. * 'foo' => 'bar',
  226. * ],
  227. * 'allow_redirects' => [
  228. * 'max' => 10, // allow at most 10 redirects.
  229. * 'strict' => true, // use "strict" RFC compliant redirects.
  230. * 'referer' => true, // add a Referer header
  231. * 'protocols' => ['https'] // only allow https URLs
  232. * ],
  233. * 'sink' => '/path/to/file', // save to a file or a stream
  234. * 'verify' => true, // bool or string to CA file
  235. * 'debug' => true,
  236. * 'timeout' => 5,
  237. * @return IResponse
  238. * @throws \Exception If the request could not get completed
  239. */
  240. public function post(string $uri, array $options = []): IResponse {
  241. $this->preventLocalAddress($uri, $options);
  242. if (isset($options['body']) && is_array($options['body'])) {
  243. $options['form_params'] = $options['body'];
  244. unset($options['body']);
  245. }
  246. $response = $this->client->request('post', $uri, $this->buildRequestOptions($options));
  247. $isStream = isset($options['stream']) && $options['stream'];
  248. return new Response($response, $isStream);
  249. }
  250. /**
  251. * Sends a PUT request
  252. *
  253. * @param string $uri
  254. * @param array $options Array such as
  255. * 'body' => [
  256. * 'field' => 'abc',
  257. * 'other_field' => '123',
  258. * 'file_name' => fopen('/path/to/file', 'r'),
  259. * ],
  260. * 'headers' => [
  261. * 'foo' => 'bar',
  262. * ],
  263. * 'cookies' => [
  264. * 'foo' => 'bar',
  265. * ],
  266. * 'allow_redirects' => [
  267. * 'max' => 10, // allow at most 10 redirects.
  268. * 'strict' => true, // use "strict" RFC compliant redirects.
  269. * 'referer' => true, // add a Referer header
  270. * 'protocols' => ['https'] // only allow https URLs
  271. * ],
  272. * 'sink' => '/path/to/file', // save to a file or a stream
  273. * 'verify' => true, // bool or string to CA file
  274. * 'debug' => true,
  275. * 'timeout' => 5,
  276. * @return IResponse
  277. * @throws \Exception If the request could not get completed
  278. */
  279. public function put(string $uri, array $options = []): IResponse {
  280. $this->preventLocalAddress($uri, $options);
  281. $response = $this->client->request('put', $uri, $this->buildRequestOptions($options));
  282. return new Response($response);
  283. }
  284. /**
  285. * Sends a PATCH request
  286. *
  287. * @param string $uri
  288. * @param array $options Array such as
  289. * 'body' => [
  290. * 'field' => 'abc',
  291. * 'other_field' => '123',
  292. * 'file_name' => fopen('/path/to/file', 'r'),
  293. * ],
  294. * 'headers' => [
  295. * 'foo' => 'bar',
  296. * ],
  297. * 'cookies' => [
  298. * 'foo' => 'bar',
  299. * ],
  300. * 'allow_redirects' => [
  301. * 'max' => 10, // allow at most 10 redirects.
  302. * 'strict' => true, // use "strict" RFC compliant redirects.
  303. * 'referer' => true, // add a Referer header
  304. * 'protocols' => ['https'] // only allow https URLs
  305. * ],
  306. * 'sink' => '/path/to/file', // save to a file or a stream
  307. * 'verify' => true, // bool or string to CA file
  308. * 'debug' => true,
  309. * 'timeout' => 5,
  310. * @return IResponse
  311. * @throws \Exception If the request could not get completed
  312. */
  313. public function patch(string $uri, array $options = []): IResponse {
  314. $this->preventLocalAddress($uri, $options);
  315. $response = $this->client->request('patch', $uri, $this->buildRequestOptions($options));
  316. return new Response($response);
  317. }
  318. /**
  319. * Sends a DELETE request
  320. *
  321. * @param string $uri
  322. * @param array $options Array such as
  323. * 'body' => [
  324. * 'field' => 'abc',
  325. * 'other_field' => '123',
  326. * 'file_name' => fopen('/path/to/file', 'r'),
  327. * ],
  328. * 'headers' => [
  329. * 'foo' => 'bar',
  330. * ],
  331. * 'cookies' => [
  332. * 'foo' => 'bar',
  333. * ],
  334. * 'allow_redirects' => [
  335. * 'max' => 10, // allow at most 10 redirects.
  336. * 'strict' => true, // use "strict" RFC compliant redirects.
  337. * 'referer' => true, // add a Referer header
  338. * 'protocols' => ['https'] // only allow https URLs
  339. * ],
  340. * 'sink' => '/path/to/file', // save to a file or a stream
  341. * 'verify' => true, // bool or string to CA file
  342. * 'debug' => true,
  343. * 'timeout' => 5,
  344. * @return IResponse
  345. * @throws \Exception If the request could not get completed
  346. */
  347. public function delete(string $uri, array $options = []): IResponse {
  348. $this->preventLocalAddress($uri, $options);
  349. $response = $this->client->request('delete', $uri, $this->buildRequestOptions($options));
  350. return new Response($response);
  351. }
  352. /**
  353. * Sends an OPTIONS request
  354. *
  355. * @param string $uri
  356. * @param array $options Array such as
  357. * 'body' => [
  358. * 'field' => 'abc',
  359. * 'other_field' => '123',
  360. * 'file_name' => fopen('/path/to/file', 'r'),
  361. * ],
  362. * 'headers' => [
  363. * 'foo' => 'bar',
  364. * ],
  365. * 'cookies' => [
  366. * 'foo' => 'bar',
  367. * ],
  368. * 'allow_redirects' => [
  369. * 'max' => 10, // allow at most 10 redirects.
  370. * 'strict' => true, // use "strict" RFC compliant redirects.
  371. * 'referer' => true, // add a Referer header
  372. * 'protocols' => ['https'] // only allow https URLs
  373. * ],
  374. * 'sink' => '/path/to/file', // save to a file or a stream
  375. * 'verify' => true, // bool or string to CA file
  376. * 'debug' => true,
  377. * 'timeout' => 5,
  378. * @return IResponse
  379. * @throws \Exception If the request could not get completed
  380. */
  381. public function options(string $uri, array $options = []): IResponse {
  382. $this->preventLocalAddress($uri, $options);
  383. $response = $this->client->request('options', $uri, $this->buildRequestOptions($options));
  384. return new Response($response);
  385. }
  386. /**
  387. * Get the response of a Throwable thrown by the request methods when possible
  388. *
  389. * @param \Throwable $e
  390. * @return IResponse
  391. * @throws \Throwable When $e did not have a response
  392. * @since 29.0.0
  393. */
  394. public function getResponseFromThrowable(\Throwable $e): IResponse {
  395. if (method_exists($e, 'hasResponse') && method_exists($e, 'getResponse') && $e->hasResponse()) {
  396. return new Response($e->getResponse());
  397. }
  398. throw $e;
  399. }
  400. /**
  401. * Sends a HTTP request
  402. *
  403. * @param string $method The HTTP method to use
  404. * @param string $uri
  405. * @param array $options Array such as
  406. * 'query' => [
  407. * 'field' => 'abc',
  408. * 'other_field' => '123',
  409. * 'file_name' => fopen('/path/to/file', 'r'),
  410. * ],
  411. * 'headers' => [
  412. * 'foo' => 'bar',
  413. * ],
  414. * 'cookies' => [
  415. * 'foo' => 'bar',
  416. * ],
  417. * 'allow_redirects' => [
  418. * 'max' => 10, // allow at most 10 redirects.
  419. * 'strict' => true, // use "strict" RFC compliant redirects.
  420. * 'referer' => true, // add a Referer header
  421. * 'protocols' => ['https'] // only allow https URLs
  422. * ],
  423. * 'sink' => '/path/to/file', // save to a file or a stream
  424. * 'verify' => true, // bool or string to CA file
  425. * 'debug' => true,
  426. * 'timeout' => 5,
  427. * @return IResponse
  428. * @throws \Exception If the request could not get completed
  429. */
  430. public function request(string $method, string $uri, array $options = []): IResponse {
  431. $this->preventLocalAddress($uri, $options);
  432. $response = $this->client->request($method, $uri, $this->buildRequestOptions($options));
  433. $isStream = isset($options['stream']) && $options['stream'];
  434. return new Response($response, $isStream);
  435. }
  436. protected function wrapGuzzlePromise(PromiseInterface $promise): IPromise {
  437. return new GuzzlePromiseAdapter(
  438. $promise,
  439. $this->logger
  440. );
  441. }
  442. /**
  443. * Sends an asynchronous GET request
  444. *
  445. * @param string $uri
  446. * @param array $options Array such as
  447. * 'query' => [
  448. * 'field' => 'abc',
  449. * 'other_field' => '123',
  450. * 'file_name' => fopen('/path/to/file', 'r'),
  451. * ],
  452. * 'headers' => [
  453. * 'foo' => 'bar',
  454. * ],
  455. * 'cookies' => [
  456. * 'foo' => 'bar',
  457. * ],
  458. * 'allow_redirects' => [
  459. * 'max' => 10, // allow at most 10 redirects.
  460. * 'strict' => true, // use "strict" RFC compliant redirects.
  461. * 'referer' => true, // add a Referer header
  462. * 'protocols' => ['https'] // only allow https URLs
  463. * ],
  464. * 'sink' => '/path/to/file', // save to a file or a stream
  465. * 'verify' => true, // bool or string to CA file
  466. * 'debug' => true,
  467. * 'timeout' => 5,
  468. * @return IPromise
  469. */
  470. public function getAsync(string $uri, array $options = []): IPromise {
  471. $this->preventLocalAddress($uri, $options);
  472. $response = $this->client->requestAsync('get', $uri, $this->buildRequestOptions($options));
  473. return $this->wrapGuzzlePromise($response);
  474. }
  475. /**
  476. * Sends an asynchronous HEAD request
  477. *
  478. * @param string $uri
  479. * @param array $options Array such as
  480. * 'headers' => [
  481. * 'foo' => 'bar',
  482. * ],
  483. * 'cookies' => [
  484. * 'foo' => 'bar',
  485. * ],
  486. * 'allow_redirects' => [
  487. * 'max' => 10, // allow at most 10 redirects.
  488. * 'strict' => true, // use "strict" RFC compliant redirects.
  489. * 'referer' => true, // add a Referer header
  490. * 'protocols' => ['https'] // only allow https URLs
  491. * ],
  492. * 'sink' => '/path/to/file', // save to a file or a stream
  493. * 'verify' => true, // bool or string to CA file
  494. * 'debug' => true,
  495. * 'timeout' => 5,
  496. * @return IPromise
  497. */
  498. public function headAsync(string $uri, array $options = []): IPromise {
  499. $this->preventLocalAddress($uri, $options);
  500. $response = $this->client->requestAsync('head', $uri, $this->buildRequestOptions($options));
  501. return $this->wrapGuzzlePromise($response);
  502. }
  503. /**
  504. * Sends an asynchronous POST request
  505. *
  506. * @param string $uri
  507. * @param array $options Array such as
  508. * 'body' => [
  509. * 'field' => 'abc',
  510. * 'other_field' => '123',
  511. * 'file_name' => fopen('/path/to/file', 'r'),
  512. * ],
  513. * 'headers' => [
  514. * 'foo' => 'bar',
  515. * ],
  516. * 'cookies' => [
  517. * 'foo' => 'bar',
  518. * ],
  519. * 'allow_redirects' => [
  520. * 'max' => 10, // allow at most 10 redirects.
  521. * 'strict' => true, // use "strict" RFC compliant redirects.
  522. * 'referer' => true, // add a Referer header
  523. * 'protocols' => ['https'] // only allow https URLs
  524. * ],
  525. * 'sink' => '/path/to/file', // save to a file or a stream
  526. * 'verify' => true, // bool or string to CA file
  527. * 'debug' => true,
  528. * 'timeout' => 5,
  529. * @return IPromise
  530. */
  531. public function postAsync(string $uri, array $options = []): IPromise {
  532. $this->preventLocalAddress($uri, $options);
  533. if (isset($options['body']) && is_array($options['body'])) {
  534. $options['form_params'] = $options['body'];
  535. unset($options['body']);
  536. }
  537. return $this->wrapGuzzlePromise($this->client->requestAsync('post', $uri, $this->buildRequestOptions($options)));
  538. }
  539. /**
  540. * Sends an asynchronous PUT request
  541. *
  542. * @param string $uri
  543. * @param array $options Array such as
  544. * 'body' => [
  545. * 'field' => 'abc',
  546. * 'other_field' => '123',
  547. * 'file_name' => fopen('/path/to/file', 'r'),
  548. * ],
  549. * 'headers' => [
  550. * 'foo' => 'bar',
  551. * ],
  552. * 'cookies' => [
  553. * 'foo' => 'bar',
  554. * ],
  555. * 'allow_redirects' => [
  556. * 'max' => 10, // allow at most 10 redirects.
  557. * 'strict' => true, // use "strict" RFC compliant redirects.
  558. * 'referer' => true, // add a Referer header
  559. * 'protocols' => ['https'] // only allow https URLs
  560. * ],
  561. * 'sink' => '/path/to/file', // save to a file or a stream
  562. * 'verify' => true, // bool or string to CA file
  563. * 'debug' => true,
  564. * 'timeout' => 5,
  565. * @return IPromise
  566. */
  567. public function putAsync(string $uri, array $options = []): IPromise {
  568. $this->preventLocalAddress($uri, $options);
  569. $response = $this->client->requestAsync('put', $uri, $this->buildRequestOptions($options));
  570. return $this->wrapGuzzlePromise($response);
  571. }
  572. /**
  573. * Sends an asynchronous DELETE request
  574. *
  575. * @param string $uri
  576. * @param array $options Array such as
  577. * 'body' => [
  578. * 'field' => 'abc',
  579. * 'other_field' => '123',
  580. * 'file_name' => fopen('/path/to/file', 'r'),
  581. * ],
  582. * 'headers' => [
  583. * 'foo' => 'bar',
  584. * ],
  585. * 'cookies' => [
  586. * 'foo' => 'bar',
  587. * ],
  588. * 'allow_redirects' => [
  589. * 'max' => 10, // allow at most 10 redirects.
  590. * 'strict' => true, // use "strict" RFC compliant redirects.
  591. * 'referer' => true, // add a Referer header
  592. * 'protocols' => ['https'] // only allow https URLs
  593. * ],
  594. * 'sink' => '/path/to/file', // save to a file or a stream
  595. * 'verify' => true, // bool or string to CA file
  596. * 'debug' => true,
  597. * 'timeout' => 5,
  598. * @return IPromise
  599. */
  600. public function deleteAsync(string $uri, array $options = []): IPromise {
  601. $this->preventLocalAddress($uri, $options);
  602. $response = $this->client->requestAsync('delete', $uri, $this->buildRequestOptions($options));
  603. return $this->wrapGuzzlePromise($response);
  604. }
  605. /**
  606. * Sends an asynchronous OPTIONS request
  607. *
  608. * @param string $uri
  609. * @param array $options Array such as
  610. * 'body' => [
  611. * 'field' => 'abc',
  612. * 'other_field' => '123',
  613. * 'file_name' => fopen('/path/to/file', 'r'),
  614. * ],
  615. * 'headers' => [
  616. * 'foo' => 'bar',
  617. * ],
  618. * 'cookies' => [
  619. * 'foo' => 'bar',
  620. * ],
  621. * 'allow_redirects' => [
  622. * 'max' => 10, // allow at most 10 redirects.
  623. * 'strict' => true, // use "strict" RFC compliant redirects.
  624. * 'referer' => true, // add a Referer header
  625. * 'protocols' => ['https'] // only allow https URLs
  626. * ],
  627. * 'sink' => '/path/to/file', // save to a file or a stream
  628. * 'verify' => true, // bool or string to CA file
  629. * 'debug' => true,
  630. * 'timeout' => 5,
  631. * @return IPromise
  632. */
  633. public function optionsAsync(string $uri, array $options = []): IPromise {
  634. $this->preventLocalAddress($uri, $options);
  635. $response = $this->client->requestAsync('options', $uri, $this->buildRequestOptions($options));
  636. return $this->wrapGuzzlePromise($response);
  637. }
  638. }