Curl.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Framework\HTTP\Client;
  7. /**
  8. * Class to work with HTTP protocol using curl library
  9. *
  10. * @author Magento Core Team <core@magentocommerce.com>
  11. * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
  12. */
  13. class Curl implements \Magento\Framework\HTTP\ClientInterface
  14. {
  15. /**
  16. * Max supported protocol by curl CURL_SSLVERSION_TLSv1_2
  17. * @var int
  18. */
  19. private $sslVersion;
  20. /**
  21. * Hostname
  22. * @var string
  23. */
  24. protected $_host = 'localhost';
  25. /**
  26. * Port
  27. * @var int
  28. */
  29. protected $_port = 80;
  30. /**
  31. * Stream resource
  32. * @var object
  33. */
  34. protected $_sock = null;
  35. /**
  36. * Request headers
  37. * @var array
  38. */
  39. protected $_headers = [];
  40. /**
  41. * Fields for POST method - hash
  42. * @var array
  43. */
  44. protected $_postFields = [];
  45. /**
  46. * Request cookies
  47. * @var array
  48. */
  49. protected $_cookies = [];
  50. /**
  51. * Response headers
  52. * @var array
  53. */
  54. protected $_responseHeaders = [];
  55. /**
  56. * Response body
  57. * @var string
  58. */
  59. protected $_responseBody = '';
  60. /**
  61. * Response status
  62. * @var int
  63. */
  64. protected $_responseStatus = 0;
  65. /**
  66. * Request timeout
  67. * @var int type
  68. */
  69. protected $_timeout = 300;
  70. /**
  71. * TODO
  72. * @var int
  73. */
  74. protected $_redirectCount = 0;
  75. /**
  76. * Curl
  77. * @var resource
  78. */
  79. protected $_ch;
  80. /**
  81. * User overrides options hash
  82. * Are applied before curl_exec
  83. *
  84. * @var array
  85. */
  86. protected $_curlUserOptions = [];
  87. /**
  88. * Header count, used while parsing headers
  89. * in CURL callback function
  90. * @var int
  91. */
  92. protected $_headerCount = 0;
  93. /**
  94. * Set request timeout, msec
  95. *
  96. * @param int $value
  97. * @return void
  98. */
  99. public function setTimeout($value)
  100. {
  101. $this->_timeout = (int)$value;
  102. }
  103. /**
  104. * @param int|null $sslVersion
  105. */
  106. public function __construct($sslVersion = null)
  107. {
  108. $this->sslVersion = $sslVersion;
  109. }
  110. /**
  111. * Set headers from hash
  112. *
  113. * @param array $headers
  114. * @return void
  115. */
  116. public function setHeaders($headers)
  117. {
  118. $this->_headers = $headers;
  119. }
  120. /**
  121. * Add header
  122. *
  123. * @param string $name name, ex. "Location"
  124. * @param string $value value ex. "http://google.com"
  125. * @return void
  126. */
  127. public function addHeader($name, $value)
  128. {
  129. $this->_headers[$name] = $value;
  130. }
  131. /**
  132. * Remove specified header
  133. *
  134. * @param string $name
  135. * @return void
  136. */
  137. public function removeHeader($name)
  138. {
  139. unset($this->_headers[$name]);
  140. }
  141. /**
  142. * Authorization: Basic header
  143. *
  144. * Login credentials support
  145. *
  146. * @param string $login username
  147. * @param string $pass password
  148. * @return void
  149. */
  150. public function setCredentials($login, $pass)
  151. {
  152. $val = base64_encode("{$login}:{$pass}");
  153. $this->addHeader("Authorization", "Basic {$val}");
  154. }
  155. /**
  156. * Add cookie
  157. *
  158. * @param string $name
  159. * @param string $value
  160. * @return void
  161. */
  162. public function addCookie($name, $value)
  163. {
  164. $this->_cookies[$name] = $value;
  165. }
  166. /**
  167. * Remove cookie
  168. *
  169. * @param string $name
  170. * @return void
  171. */
  172. public function removeCookie($name)
  173. {
  174. unset($this->_cookies[$name]);
  175. }
  176. /**
  177. * Set cookies array
  178. *
  179. * @param array $cookies
  180. * @return void
  181. */
  182. public function setCookies($cookies)
  183. {
  184. $this->_cookies = $cookies;
  185. }
  186. /**
  187. * Clear cookies
  188. *
  189. * @return void
  190. */
  191. public function removeCookies()
  192. {
  193. $this->setCookies([]);
  194. }
  195. /**
  196. * Make GET request
  197. *
  198. * @param string $uri uri relative to host, ex. "/index.php"
  199. * @return void
  200. */
  201. public function get($uri)
  202. {
  203. $this->makeRequest("GET", $uri);
  204. }
  205. /**
  206. * Make POST request
  207. *
  208. * String type was added to parameter $param in order to support sending JSON or XML requests.
  209. * This feature was added base on Community Pull Request https://github.com/magento/magento2/pull/8373
  210. *
  211. * @param string $uri
  212. * @param array|string $params
  213. * @return void
  214. *
  215. * @see \Magento\Framework\HTTP\Client#post($uri, $params)
  216. */
  217. public function post($uri, $params)
  218. {
  219. $this->makeRequest("POST", $uri, $params);
  220. }
  221. /**
  222. * Get response headers
  223. *
  224. * @return array
  225. */
  226. public function getHeaders()
  227. {
  228. return $this->_responseHeaders;
  229. }
  230. /**
  231. * Get response body
  232. *
  233. * @return string
  234. */
  235. public function getBody()
  236. {
  237. return $this->_responseBody;
  238. }
  239. /**
  240. * Get cookies response hash
  241. *
  242. * @return array
  243. */
  244. public function getCookies()
  245. {
  246. if (empty($this->_responseHeaders['Set-Cookie'])) {
  247. return [];
  248. }
  249. $out = [];
  250. foreach ($this->_responseHeaders['Set-Cookie'] as $row) {
  251. $values = explode("; ", $row);
  252. $c = count($values);
  253. if (!$c) {
  254. continue;
  255. }
  256. list($key, $val) = explode("=", $values[0]);
  257. if ($val === null) {
  258. continue;
  259. }
  260. $out[trim($key)] = trim($val);
  261. }
  262. return $out;
  263. }
  264. /**
  265. * Get cookies array with details
  266. * (domain, expire time etc)
  267. *
  268. * @return array
  269. */
  270. public function getCookiesFull()
  271. {
  272. if (empty($this->_responseHeaders['Set-Cookie'])) {
  273. return [];
  274. }
  275. $out = [];
  276. foreach ($this->_responseHeaders['Set-Cookie'] as $row) {
  277. $values = explode("; ", $row);
  278. $c = count($values);
  279. if (!$c) {
  280. continue;
  281. }
  282. list($key, $val) = explode("=", $values[0]);
  283. if ($val === null) {
  284. continue;
  285. }
  286. $out[trim($key)] = ['value' => trim($val)];
  287. array_shift($values);
  288. $c--;
  289. if (!$c) {
  290. continue;
  291. }
  292. for ($i = 0; $i < $c; $i++) {
  293. list($subkey, $val) = explode("=", $values[$i]);
  294. $out[trim($key)][trim($subkey)] = trim($val);
  295. }
  296. }
  297. return $out;
  298. }
  299. /**
  300. * Get response status code
  301. *
  302. * @see lib\Magento\Framework\HTTP\Client#getStatus()
  303. *
  304. * @return int
  305. */
  306. public function getStatus()
  307. {
  308. return $this->_responseStatus;
  309. }
  310. /**
  311. * Make request
  312. *
  313. * String type was added to parameter $param in order to support sending JSON or XML requests.
  314. * This feature was added base on Community Pull Request https://github.com/magento/magento2/pull/8373
  315. *
  316. * @param string $method
  317. * @param string $uri
  318. * @param array|string $params - use $params as a string in case of JSON or XML POST request.
  319. *
  320. * @return void
  321. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  322. * @SuppressWarnings(PHPMD.NPathComplexity)
  323. */
  324. protected function makeRequest($method, $uri, $params = [])
  325. {
  326. $this->_ch = curl_init();
  327. $this->curlOption(CURLOPT_URL, $uri);
  328. if ($method == 'POST') {
  329. $this->curlOption(CURLOPT_POST, 1);
  330. $this->curlOption(CURLOPT_POSTFIELDS, is_array($params) ? http_build_query($params) : $params);
  331. } elseif ($method == "GET") {
  332. $this->curlOption(CURLOPT_HTTPGET, 1);
  333. } else {
  334. $this->curlOption(CURLOPT_CUSTOMREQUEST, $method);
  335. }
  336. if (count($this->_headers)) {
  337. $heads = [];
  338. foreach ($this->_headers as $k => $v) {
  339. $heads[] = $k . ': ' . $v;
  340. }
  341. $this->curlOption(CURLOPT_HTTPHEADER, $heads);
  342. }
  343. if (count($this->_cookies)) {
  344. $cookies = [];
  345. foreach ($this->_cookies as $k => $v) {
  346. $cookies[] = "{$k}={$v}";
  347. }
  348. $this->curlOption(CURLOPT_COOKIE, implode(";", $cookies));
  349. }
  350. if ($this->_timeout) {
  351. $this->curlOption(CURLOPT_TIMEOUT, $this->_timeout);
  352. }
  353. if ($this->_port != 80) {
  354. $this->curlOption(CURLOPT_PORT, $this->_port);
  355. }
  356. $this->curlOption(CURLOPT_RETURNTRANSFER, 1);
  357. $this->curlOption(CURLOPT_HEADERFUNCTION, [$this, 'parseHeaders']);
  358. if ($this->sslVersion !== null) {
  359. $this->curlOption(CURLOPT_SSLVERSION, $this->sslVersion);
  360. }
  361. if (count($this->_curlUserOptions)) {
  362. foreach ($this->_curlUserOptions as $k => $v) {
  363. $this->curlOption($k, $v);
  364. }
  365. }
  366. $this->_headerCount = 0;
  367. $this->_responseHeaders = [];
  368. $this->_responseBody = curl_exec($this->_ch);
  369. $err = curl_errno($this->_ch);
  370. if ($err) {
  371. $this->doError(curl_error($this->_ch));
  372. }
  373. curl_close($this->_ch);
  374. }
  375. /**
  376. * Throw error exception
  377. *
  378. * @param string $string
  379. * @return void
  380. * @throws \Exception
  381. */
  382. public function doError($string)
  383. {
  384. throw new \Exception($string);
  385. }
  386. /**
  387. * Parse headers - CURL callback function
  388. *
  389. * @param resource $ch curl handle, not needed
  390. * @param string $data
  391. * @return int
  392. * @throws \Exception
  393. * @SuppressWarnings(PHPMD.UnusedFormalParameter)
  394. */
  395. protected function parseHeaders($ch, $data)
  396. {
  397. if ($this->_headerCount == 0) {
  398. $line = explode(" ", trim($data), 3);
  399. if (count($line) < 2) {
  400. $this->doError("Invalid response line returned from server: " . $data);
  401. }
  402. $this->_responseStatus = (int)$line[1];
  403. } else {
  404. $name = $value = '';
  405. $out = explode(": ", trim($data), 2);
  406. if (count($out) == 2) {
  407. $name = $out[0];
  408. $value = $out[1];
  409. }
  410. if (strlen($name)) {
  411. if ("Set-Cookie" == $name) {
  412. if (!isset($this->_responseHeaders[$name])) {
  413. $this->_responseHeaders[$name] = [];
  414. }
  415. $this->_responseHeaders[$name][] = $value;
  416. } else {
  417. $this->_responseHeaders[$name] = $value;
  418. }
  419. }
  420. }
  421. $this->_headerCount++;
  422. return strlen($data);
  423. }
  424. /**
  425. * Set curl option directly
  426. *
  427. * @param string $name
  428. * @param string $value
  429. * @return void
  430. */
  431. protected function curlOption($name, $value)
  432. {
  433. curl_setopt($this->_ch, $name, $value);
  434. }
  435. /**
  436. * Set curl options array directly
  437. *
  438. * @param array $arr
  439. * @return void
  440. */
  441. protected function curlOptions($arr)
  442. {
  443. curl_setopt_array($this->_ch, $arr);
  444. }
  445. /**
  446. * Set CURL options overrides array
  447. *
  448. * @param array $arr
  449. * @return void
  450. */
  451. public function setOptions($arr)
  452. {
  453. $this->_curlUserOptions = $arr;
  454. }
  455. /**
  456. * Set curl option
  457. *
  458. * @param string $name
  459. * @param string $value
  460. * @return void
  461. */
  462. public function setOption($name, $value)
  463. {
  464. $this->_curlUserOptions[$name] = $value;
  465. }
  466. }