CurlService.php 35 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282
  1. <?php
  2. namespace App\Services\Common;
  3. class CurlService
  4. {
  5. const VERSION = '5.1.0';
  6. const DEFAULT_TIMEOUT = 30;
  7. public static $RFC2616 = array(
  8. // RFC2616: "any CHAR except CTLs or separators".
  9. // CHAR = <any US-ASCII character (octets 0 - 127)>
  10. // CTL = <any US-ASCII control character
  11. // (octets 0 - 31) and DEL (127)>
  12. // separators = "(" | ")" | "<" | ">" | "@"
  13. // | "," | ";" | ":" | "\" | <">
  14. // | "/" | "[" | "]" | "?" | "="
  15. // | "{" | "}" | SP | HT
  16. // SP = <US-ASCII SP, space (32)>
  17. // HT = <US-ASCII HT, horizontal-tab (9)>
  18. // <"> = <US-ASCII double-quote mark (34)>
  19. '!', '#', '$', '%', '&', "'", '*', '+', '-', '.', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B',
  20. 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
  21. 'Y', 'Z', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
  22. 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '|', '~',
  23. );
  24. public static $RFC6265 = array(
  25. // RFC6265: "US-ASCII characters excluding CTLs, whitespace DQUOTE, comma, semicolon, and backslash".
  26. // %x21
  27. '!',
  28. // %x23-2B
  29. '#', '$', '%', '&', "'", '(', ')', '*', '+',
  30. // %x2D-3A
  31. '-', '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':',
  32. // %x3C-5B
  33. '<', '=', '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q',
  34. 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[',
  35. // %x5D-7E
  36. ']', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
  37. 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~',
  38. );
  39. public $curl;
  40. public $id = null;
  41. public $error = false;
  42. public $errorCode = 0;
  43. public $errorMessage = null;
  44. public $curlError = false;
  45. public $curlErrorCode = 0;
  46. public $curlErrorMessage = null;
  47. public $httpError = false;
  48. public $httpStatusCode = 0;
  49. public $httpErrorMessage = null;
  50. public $baseUrl = null;
  51. public $url = null;
  52. public $requestHeaders = null;
  53. public $responseHeaders = null;
  54. public $rawResponseHeaders = '';
  55. public $response = null;
  56. public $rawResponse = null;
  57. public $beforeSendFunction = null;
  58. public $downloadCompleteFunction = null;
  59. public $successFunction = null;
  60. public $errorFunction = null;
  61. public $completeFunction = null;
  62. public $fileHandle = null;
  63. private $cookies = array();
  64. private $responseCookies = array();
  65. private $headers = array();
  66. private $options = array();
  67. private $jsonDecoder = null;
  68. private $jsonPattern = '/^(?:application|text)\/(?:[a-z]+(?:[\.-][0-9a-z]+){0,}[\+\.]|x-)?json(?:-[a-z]+)?/i';
  69. private $xmlDecoder = null;
  70. private $xmlPattern = '~^(?:text/|application/(?:atom\+|rss\+)?)xml~i';
  71. private $defaultDecoder = null;
  72. private static $deferredProperties = array(
  73. 'effectiveUrl',
  74. 'totalTime',
  75. );
  76. /**
  77. * Construct
  78. *
  79. * @access public
  80. * @param $base_url
  81. * @throws \ErrorException
  82. */
  83. public function __construct($base_url = null)
  84. {
  85. if (!extension_loaded('curl')) {
  86. throw new \ErrorException('cURL library is not loaded');
  87. }
  88. $this->curl = curl_init();
  89. $this->id = 1;
  90. $this->setDefaultUserAgent();
  91. $this->setDefaultJsonDecoder();
  92. $this->setDefaultXmlDecoder();
  93. $this->setDefaultTimeout();
  94. $this->setOpt(CURLINFO_HEADER_OUT, true);
  95. $this->setOpt(CURLOPT_HEADERFUNCTION, array($this, 'headerCallback'));
  96. $this->setOpt(CURLOPT_RETURNTRANSFER, true);
  97. $this->headers = new CaseInsensitiveArrayService();
  98. $this->setURL($base_url);
  99. $this->rfc2616 = array_fill_keys(self::$RFC2616, true);
  100. $this->rfc6265 = array_fill_keys(self::$RFC6265, true);
  101. }
  102. /**
  103. * Before Send
  104. *
  105. * @access public
  106. * @param $callback
  107. */
  108. public function beforeSend($callback)
  109. {
  110. $this->beforeSendFunction = $callback;
  111. }
  112. /**
  113. * Build Post Data
  114. *
  115. * @access public
  116. * @param $data
  117. *
  118. * @return array|string
  119. */
  120. public function buildPostData($data)
  121. {
  122. $binary_data = false;
  123. if (is_array($data)) {
  124. // Return JSON-encoded string when the request's content-type is JSON.
  125. if (isset($this->headers['Content-Type']) &&
  126. preg_match($this->jsonPattern, $this->headers['Content-Type'])) {
  127. $json_str = json_encode($data);
  128. if (!($json_str === false)) {
  129. $data = $json_str;
  130. }
  131. } else {
  132. // Manually build a single-dimensional array from a multi-dimensional array as using curl_setopt($ch,
  133. // CURLOPT_POSTFIELDS, $data) doesn't correctly handle multi-dimensional arrays when files are
  134. // referenced.
  135. if (self::is_array_multidim($data)) {
  136. $data = self::array_flatten_multidim($data);
  137. }
  138. // Modify array values to ensure any referenced files are properly handled depending on the support of
  139. // the @filename API or CURLFile usage. This also fixes the warning "curl_setopt(): The usage of the
  140. // @filename API for file uploading is deprecated. Please use the CURLFile class instead". Ignore
  141. // non-file values prefixed with the @ character.
  142. foreach ($data as $key => $value) {
  143. if (is_string($value) && strpos($value, '@') === 0 && is_file(substr($value, 1))) {
  144. $binary_data = true;
  145. if (class_exists('CURLFile')) {
  146. $data[$key] = new \CURLFile(substr($value, 1));
  147. }
  148. } elseif ($value instanceof \CURLFile) {
  149. $binary_data = true;
  150. }
  151. }
  152. }
  153. }
  154. if (!$binary_data && (is_array($data) || is_object($data))) {
  155. $data = http_build_query($data, '', '&');
  156. }
  157. return $data;
  158. }
  159. /**
  160. * Call
  161. *
  162. * @access public
  163. */
  164. public function call()
  165. {
  166. $args = func_get_args();
  167. $function = array_shift($args);
  168. if (is_callable($function)) {
  169. array_unshift($args, $this);
  170. call_user_func_array($function, $args);
  171. }
  172. }
  173. /**
  174. * Close
  175. *
  176. * @access public
  177. */
  178. public function close()
  179. {
  180. if (is_resource($this->curl)) {
  181. curl_close($this->curl);
  182. }
  183. $this->options = null;
  184. $this->jsonDecoder = null;
  185. $this->xmlDecoder = null;
  186. }
  187. /**
  188. * Complete
  189. *
  190. * @access public
  191. * @param $callback
  192. */
  193. public function complete($callback)
  194. {
  195. $this->completeFunction = $callback;
  196. }
  197. /**
  198. * Progress
  199. *
  200. * @access public
  201. * @param $callback
  202. */
  203. public function progress($callback)
  204. {
  205. $this->setOpt(CURLOPT_PROGRESSFUNCTION, $callback);
  206. $this->setOpt(CURLOPT_NOPROGRESS, false);
  207. }
  208. /**
  209. * Delete
  210. *
  211. * @access public
  212. * @param $url
  213. * @param $query_parameters
  214. * @param $data
  215. *
  216. * @return string
  217. */
  218. public function delete($url, $query_parameters = array(), $data = array())
  219. {
  220. if (is_array($url)) {
  221. $data = $query_parameters;
  222. $query_parameters = $url;
  223. $url = $this->baseUrl;
  224. }
  225. $this->setURL($url, $query_parameters);
  226. $this->setOpt(CURLOPT_CUSTOMREQUEST, 'DELETE');
  227. $this->setOpt(CURLOPT_POSTFIELDS, $this->buildPostData($data));
  228. return $this->exec();
  229. }
  230. /**
  231. * Download Complete
  232. *
  233. * @access public
  234. * @param $fh
  235. */
  236. public function downloadComplete($fh)
  237. {
  238. if (!$this->error && $this->downloadCompleteFunction) {
  239. rewind($fh);
  240. $this->call($this->downloadCompleteFunction, $fh);
  241. $this->downloadCompleteFunction = null;
  242. }
  243. if (is_resource($fh)) {
  244. fclose($fh);
  245. }
  246. // Fix "PHP Notice: Use of undefined constant STDOUT" when reading the
  247. // PHP script from stdin. Using null causes "Warning: curl_setopt():
  248. // supplied argument is not a valid File-Handle resource".
  249. if (!defined('STDOUT')) {
  250. define('STDOUT', fopen('php://stdout', 'w'));
  251. }
  252. // Reset CURLOPT_FILE with STDOUT to avoid: "curl_exec(): CURLOPT_FILE
  253. // resource has gone away, resetting to default".
  254. $this->setOpt(CURLOPT_FILE, STDOUT);
  255. // Reset CURLOPT_RETURNTRANSFER to tell cURL to return subsequent
  256. // responses as the return value of curl_exec(). Without this,
  257. // curl_exec() will revert to returning boolean values.
  258. $this->setOpt(CURLOPT_RETURNTRANSFER, true);
  259. }
  260. /**
  261. * Download
  262. *
  263. * @access public
  264. * @param $url
  265. * @param $mixed_filename
  266. *
  267. * @return boolean
  268. */
  269. public function download($url, $mixed_filename)
  270. {
  271. if (is_callable($mixed_filename)) {
  272. $this->downloadCompleteFunction = $mixed_filename;
  273. $fh = tmpfile();
  274. } else {
  275. $filename = $mixed_filename;
  276. $fh = fopen($filename, 'wb');
  277. }
  278. $this->setOpt(CURLOPT_FILE, $fh);
  279. $this->get($url);
  280. $this->downloadComplete($fh);
  281. return ! $this->error;
  282. }
  283. /**
  284. * Error
  285. *
  286. * @access public
  287. * @param $callback
  288. */
  289. public function error($callback)
  290. {
  291. $this->errorFunction = $callback;
  292. }
  293. /**
  294. * Exec
  295. *
  296. * @access public
  297. * @param $ch
  298. *
  299. * @return mixed Returns the value provided by parseResponse.
  300. */
  301. public function exec($ch = null)
  302. {
  303. $this->responseCookies = array();
  304. if ($ch === null) {
  305. $this->call($this->beforeSendFunction);
  306. $this->rawResponse = curl_exec($this->curl);
  307. $this->curlErrorCode = curl_errno($this->curl);
  308. } else {
  309. $this->rawResponse = curl_multi_getcontent($ch);
  310. }
  311. $this->curlErrorMessage = curl_error($this->curl);
  312. $this->curlError = !($this->curlErrorCode === 0);
  313. $this->httpStatusCode = $this->getInfo(CURLINFO_HTTP_CODE);
  314. $this->httpError = in_array(floor($this->httpStatusCode / 100), array(4, 5));
  315. $this->error = $this->curlError || $this->httpError;
  316. $this->errorCode = $this->error ? ($this->curlError ? $this->curlErrorCode : $this->httpStatusCode) : 0;
  317. // NOTE: CURLINFO_HEADER_OUT set to true is required for requestHeaders
  318. // to not be empty (e.g. $curl->setOpt(CURLINFO_HEADER_OUT, true);).
  319. if ($this->getOpt(CURLINFO_HEADER_OUT) === true) {
  320. $this->requestHeaders = $this->parseRequestHeaders($this->getInfo(CURLINFO_HEADER_OUT));
  321. }
  322. $this->responseHeaders = $this->parseResponseHeaders($this->rawResponseHeaders);
  323. $this->response = $this->parseResponse($this->responseHeaders, $this->rawResponse);
  324. $this->httpErrorMessage = '';
  325. if ($this->error) {
  326. if (isset($this->responseHeaders['Status-Line'])) {
  327. $this->httpErrorMessage = $this->responseHeaders['Status-Line'];
  328. }
  329. }
  330. $this->errorMessage = $this->curlError ? $this->curlErrorMessage : $this->httpErrorMessage;
  331. if (!$this->error) {
  332. $this->call($this->successFunction);
  333. } else {
  334. $this->call($this->errorFunction);
  335. }
  336. $this->call($this->completeFunction);
  337. return $this->response;
  338. }
  339. /**
  340. * Get
  341. *
  342. * @access public
  343. * @param $url
  344. * @param $data
  345. *
  346. * @return mixed Returns the value provided by exec.
  347. */
  348. public function get($url, $data = array())
  349. {
  350. if (is_array($url)) {
  351. $data = $url;
  352. $url = $this->baseUrl;
  353. }
  354. $this->setURL($url, $data);
  355. $this->setOpt(CURLOPT_CUSTOMREQUEST, 'GET');
  356. $this->setOpt(CURLOPT_HTTPGET, true);
  357. return $this->exec();
  358. }
  359. /**
  360. * Get Info
  361. *
  362. * @access public
  363. * @param $opt
  364. */
  365. public function getInfo($opt)
  366. {
  367. return curl_getinfo($this->curl, $opt);
  368. }
  369. /**
  370. * Get Opt
  371. *
  372. * @access public
  373. * @param $option
  374. *
  375. * @return mixed
  376. */
  377. public function getOpt($option)
  378. {
  379. return $this->options[$option];
  380. }
  381. /**
  382. * Head
  383. *
  384. * @access public
  385. * @param $url
  386. * @param $data
  387. *
  388. * @return string
  389. */
  390. public function head($url, $data = array())
  391. {
  392. if (is_array($url)) {
  393. $data = $url;
  394. $url = $this->baseUrl;
  395. }
  396. $this->setURL($url, $data);
  397. $this->setOpt(CURLOPT_CUSTOMREQUEST, 'HEAD');
  398. $this->setOpt(CURLOPT_NOBODY, true);
  399. return $this->exec();
  400. }
  401. /**
  402. * Header Callback
  403. *
  404. * @access public
  405. * @param $ch
  406. * @param $header
  407. *
  408. * @return integer
  409. */
  410. public function headerCallback($ch, $header)
  411. {
  412. if (preg_match('/^Set-Cookie:\s*([^=]+)=([^;]+)/mi', $header, $cookie) === 1) {
  413. $this->responseCookies[$cookie[1]] = trim($cookie[2], " \n\r\t\0\x0B");
  414. }
  415. $this->rawResponseHeaders .= $header;
  416. return strlen($header);
  417. }
  418. /**
  419. * Options
  420. *
  421. * @access public
  422. * @param $url
  423. * @param $data
  424. *
  425. * @return string
  426. */
  427. public function options($url, $data = array())
  428. {
  429. if (is_array($url)) {
  430. $data = $url;
  431. $url = $this->baseUrl;
  432. }
  433. $this->setURL($url, $data);
  434. $this->unsetHeader('Content-Length');
  435. $this->setOpt(CURLOPT_CUSTOMREQUEST, 'OPTIONS');
  436. return $this->exec();
  437. }
  438. /**
  439. * Patch
  440. *
  441. * @access public
  442. * @param $url
  443. * @param $data
  444. *
  445. * @return string
  446. */
  447. public function patch($url, $data = array())
  448. {
  449. if (is_array($url)) {
  450. $data = $url;
  451. $url = $this->baseUrl;
  452. }
  453. if (is_array($data) && empty($data)) {
  454. $this->unsetHeader('Content-Length');
  455. }
  456. $this->setURL($url);
  457. $this->setOpt(CURLOPT_CUSTOMREQUEST, 'PATCH');
  458. $this->setOpt(CURLOPT_POSTFIELDS, $this->buildPostData($data));
  459. return $this->exec();
  460. }
  461. /**
  462. * Post
  463. *
  464. * @access public
  465. * @param $url
  466. * @param $data
  467. * @param $follow_303_with_post If true, will cause 303 redirections to be followed using
  468. * a POST request (default: false).
  469. * Notes:
  470. * - Redirections are only followed if the CURLOPT_FOLLOWLOCATION option is set to true.
  471. * - According to the HTTP specs (see [1]), a 303 redirection should be followed using
  472. * the GET method. 301 and 302 must not.
  473. * - In order to force a 303 redirection to be performed using the same method, the
  474. * underlying cURL object must be set in a special state (the CURLOPT_CURSTOMREQUEST
  475. * option must be set to the method to use after the redirection). Due to a limitation
  476. * of the cURL extension of PHP < 5.5.11 ([2], [3]) and of HHVM, it is not possible
  477. * to reset this option. Using these PHP engines, it is therefore impossible to
  478. * restore this behavior on an existing php-curl-class Curl object.
  479. *
  480. * @return string
  481. *
  482. * [1] https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.2
  483. * [2] https://github.com/php/php-src/pull/531
  484. * [3] http://php.net/ChangeLog-5.php#5.5.11
  485. */
  486. public function post($url, $data = array(), $follow_303_with_post = false)
  487. {
  488. if (is_array($url)) {
  489. $follow_303_with_post = (bool)$data;
  490. $data = $url;
  491. $url = $this->baseUrl;
  492. }
  493. $this->setURL($url);
  494. if ($follow_303_with_post) {
  495. $this->setOpt(CURLOPT_CUSTOMREQUEST, 'POST');
  496. } else {
  497. if (isset($this->options[CURLOPT_CUSTOMREQUEST])) {
  498. if ((version_compare(PHP_VERSION, '5.5.11') < 0) || defined('HHVM_VERSION')) {
  499. trigger_error('Due to technical limitations of PHP <= 5.5.11 and HHVM, it is not possible to '
  500. . 'perform a post-redirect-get request using a php-curl-class Curl object that '
  501. . 'has already been used to perform other types of requests. Either use a new '
  502. . 'php-curl-class Curl object or upgrade your PHP engine.',
  503. E_USER_ERROR);
  504. } else {
  505. $this->setOpt(CURLOPT_CUSTOMREQUEST, null);
  506. }
  507. }
  508. }
  509. $this->setOpt(CURLOPT_POST, true);
  510. $this->setOpt(CURLOPT_POSTFIELDS, $this->buildPostData($data));
  511. return $this->exec();
  512. }
  513. /**
  514. * Put
  515. *
  516. * @access public
  517. * @param $url
  518. * @param $data
  519. *
  520. * @return string
  521. */
  522. public function put($url, $data = array())
  523. {
  524. if (is_array($url)) {
  525. $data = $url;
  526. $url = $this->baseUrl;
  527. }
  528. $this->setURL($url);
  529. $this->setOpt(CURLOPT_CUSTOMREQUEST, 'PUT');
  530. $put_data = $this->buildPostData($data);
  531. if (empty($this->options[CURLOPT_INFILE]) && empty($this->options[CURLOPT_INFILESIZE])) {
  532. if (is_string($put_data)) {
  533. $this->setHeader('Content-Length', strlen($put_data));
  534. }
  535. }
  536. if (!empty($put_data)) {
  537. $this->setOpt(CURLOPT_POSTFIELDS, $put_data);
  538. }
  539. return $this->exec();
  540. }
  541. /**
  542. * Search
  543. *
  544. * @access public
  545. * @param $url
  546. * @param $data
  547. *
  548. * @return string
  549. */
  550. public function search($url, $data = array())
  551. {
  552. if (is_array($url)) {
  553. $data = $url;
  554. $url = $this->baseUrl;
  555. }
  556. $this->setURL($url);
  557. $this->setOpt(CURLOPT_CUSTOMREQUEST, 'SEARCH');
  558. $put_data = $this->buildPostData($data);
  559. if (empty($this->options[CURLOPT_INFILE]) && empty($this->options[CURLOPT_INFILESIZE])) {
  560. if (is_string($put_data)) {
  561. $this->setHeader('Content-Length', strlen($put_data));
  562. }
  563. }
  564. if (!empty($put_data)) {
  565. $this->setOpt(CURLOPT_POSTFIELDS, $put_data);
  566. }
  567. return $this->exec();
  568. }
  569. /**
  570. * Set Basic Authentication
  571. *
  572. * @access public
  573. * @param $username
  574. * @param $password
  575. */
  576. public function setBasicAuthentication($username, $password = '')
  577. {
  578. $this->setOpt(CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
  579. $this->setOpt(CURLOPT_USERPWD, $username . ':' . $password);
  580. }
  581. /**
  582. * Set Digest Authentication
  583. *
  584. * @access public
  585. * @param $username
  586. * @param $password
  587. */
  588. public function setDigestAuthentication($username, $password = '')
  589. {
  590. $this->setOpt(CURLOPT_HTTPAUTH, CURLAUTH_DIGEST);
  591. $this->setOpt(CURLOPT_USERPWD, $username . ':' . $password);
  592. }
  593. /**
  594. * Set Cookie
  595. *
  596. * @access public
  597. * @param $key
  598. * @param $value
  599. */
  600. public function setCookie($key, $value)
  601. {
  602. $name_chars = array();
  603. foreach (str_split($key) as $name_char) {
  604. if (!isset($this->rfc2616[$name_char])) {
  605. $name_chars[] = rawurlencode($name_char);
  606. } else {
  607. $name_chars[] = $name_char;
  608. }
  609. }
  610. $value_chars = array();
  611. foreach (str_split($value) as $value_char) {
  612. if (!isset($this->rfc6265[$value_char])) {
  613. $value_chars[] = rawurlencode($value_char);
  614. } else {
  615. $value_chars[] = $value_char;
  616. }
  617. }
  618. $this->cookies[implode('', $name_chars)] = implode('', $value_chars);
  619. $this->setOpt(CURLOPT_COOKIE, implode('; ', array_map(function($k, $v) {
  620. return $k . '=' . $v;
  621. }, array_keys($this->cookies), array_values($this->cookies))));
  622. }
  623. /**
  624. * Get cookie.
  625. *
  626. * @access public
  627. * @param $key
  628. * @return mixed
  629. */
  630. public function getCookie($key)
  631. {
  632. return $this->getResponseCookie($key);
  633. }
  634. /**
  635. * Get response cookie.
  636. *
  637. * @access public
  638. * @param $key
  639. * @return mixed
  640. */
  641. public function getResponseCookie($key)
  642. {
  643. return isset($this->responseCookies[$key]) ? $this->responseCookies[$key] : null;
  644. }
  645. /**
  646. * Get response cookies.
  647. *
  648. * @access public
  649. * @return array
  650. */
  651. public function getResponseCookies()
  652. {
  653. return $this->responseCookies;
  654. }
  655. /**
  656. * Set Port
  657. *
  658. * @access public
  659. * @param $port
  660. */
  661. public function setPort($port)
  662. {
  663. $this->setOpt(CURLOPT_PORT, intval($port));
  664. }
  665. /**
  666. * Set Connect Timeout
  667. *
  668. * @access public
  669. * @param $seconds
  670. */
  671. public function setConnectTimeout($seconds)
  672. {
  673. $this->setOpt(CURLOPT_CONNECTTIMEOUT, $seconds);
  674. }
  675. /**
  676. * Set Cookie String
  677. *
  678. * @access public
  679. * @param $string
  680. */
  681. public function setCookieString($string)
  682. {
  683. return $this->setOpt(CURLOPT_COOKIE, $string);
  684. }
  685. /**
  686. * Set Cookie File
  687. *
  688. * @access public
  689. * @param $cookie_file
  690. */
  691. public function setCookieFile($cookie_file)
  692. {
  693. $this->setOpt(CURLOPT_COOKIEFILE, $cookie_file);
  694. }
  695. /**
  696. * Set Cookie Jar
  697. *
  698. * @access public
  699. * @param $cookie_jar
  700. */
  701. public function setCookieJar($cookie_jar)
  702. {
  703. $this->setOpt(CURLOPT_COOKIEJAR, $cookie_jar);
  704. }
  705. /**
  706. * Set Default JSON Decoder
  707. *
  708. * @access public
  709. * @param $assoc
  710. * @param $depth
  711. * @param $options
  712. */
  713. public function setDefaultJsonDecoder()
  714. {
  715. $args = func_get_args();
  716. $this->jsonDecoder = function($response) use ($args) {
  717. array_unshift($args, $response);
  718. // Call json_decode() without the $options parameter in PHP
  719. // versions less than 5.4.0 as the $options parameter was added in
  720. // PHP version 5.4.0.
  721. if (version_compare(PHP_VERSION, '5.4.0', '<')) {
  722. $args = array_slice($args, 0, 3);
  723. }
  724. $json_obj = call_user_func_array('json_decode', $args);
  725. if (!($json_obj === null)) {
  726. $response = $json_obj;
  727. }
  728. return $response;
  729. };
  730. }
  731. /**
  732. * Set Default XML Decoder
  733. *
  734. * @access public
  735. */
  736. public function setDefaultXmlDecoder()
  737. {
  738. $this->xmlDecoder = function($response) {
  739. $xml_obj = @simplexml_load_string($response);
  740. if (!($xml_obj === false)) {
  741. $response = $xml_obj;
  742. }
  743. return $response;
  744. };
  745. }
  746. /**
  747. * Set Default Decoder
  748. *
  749. * @access public
  750. * @param $decoder string|callable
  751. */
  752. public function setDefaultDecoder($decoder = 'json')
  753. {
  754. if (is_callable($decoder)) {
  755. $this->defaultDecoder = $decoder;
  756. } else {
  757. if ($decoder === 'json') {
  758. $this->defaultDecoder = $this->jsonDecoder;
  759. } elseif ($decoder === 'xml') {
  760. $this->defaultDecoder = $this->xmlDecoder;
  761. }
  762. }
  763. }
  764. /**
  765. * Set Default Timeout
  766. *
  767. * @access public
  768. */
  769. public function setDefaultTimeout()
  770. {
  771. $this->setTimeout(self::DEFAULT_TIMEOUT);
  772. }
  773. /**
  774. * Set Default User Agent
  775. *
  776. * @access public
  777. */
  778. public function setDefaultUserAgent()
  779. {
  780. $user_agent = 'PHP-Curl-Class/' . self::VERSION . ' (+https://github.com/php-curl-class/php-curl-class)';
  781. $user_agent .= ' PHP/' . PHP_VERSION;
  782. $curl_version = curl_version();
  783. $user_agent .= ' curl/' . $curl_version['version'];
  784. $this->setUserAgent($user_agent);
  785. }
  786. /**
  787. * Set Header
  788. *
  789. * @access public
  790. * @param $key
  791. * @param $value
  792. */
  793. public function setHeader($key, $value)
  794. {
  795. $this->headers[$key] = $value;
  796. $headers = array();
  797. foreach ($this->headers as $key => $value) {
  798. $headers[] = $key . ': ' . $value;
  799. }
  800. $this->setOpt(CURLOPT_HTTPHEADER, $headers);
  801. }
  802. /**
  803. * Set JSON Decoder
  804. *
  805. * @access public
  806. * @param $function
  807. */
  808. public function setJsonDecoder($function)
  809. {
  810. if (is_callable($function)) {
  811. $this->jsonDecoder = $function;
  812. }
  813. }
  814. /**
  815. * Set XML Decoder
  816. *
  817. * @access public
  818. * @param $function
  819. */
  820. public function setXmlDecoder($function)
  821. {
  822. if (is_callable($function)) {
  823. $this->xmlDecoder = $function;
  824. }
  825. }
  826. /**
  827. * Set Opt
  828. *
  829. * @access public
  830. * @param $option
  831. * @param $value
  832. *
  833. * @return boolean
  834. */
  835. public function setOpt($option, $value)
  836. {
  837. $required_options = array(
  838. CURLOPT_RETURNTRANSFER => 'CURLOPT_RETURNTRANSFER',
  839. );
  840. if (in_array($option, array_keys($required_options), true) && !($value === true)) {
  841. trigger_error($required_options[$option] . ' is a required option', E_USER_WARNING);
  842. }
  843. $success = curl_setopt($this->curl, $option, $value);
  844. if ($success) {
  845. $this->options[$option] = $value;
  846. }
  847. return $success;
  848. }
  849. /**
  850. * Set Opts
  851. *
  852. * @access public
  853. * @param $options
  854. *
  855. * @return boolean
  856. * Returns true if all options were successfully set. If an option could not be successfully set, false is
  857. * immediately returned, ignoring any future options in the options array. Similar to curl_setopt_array().
  858. */
  859. public function setOpts($options)
  860. {
  861. foreach ($options as $option => $value) {
  862. if (!$this->setOpt($option, $value)) {
  863. return false;
  864. }
  865. }
  866. }
  867. /**
  868. * Set Referer
  869. *
  870. * @access public
  871. * @param $referer
  872. */
  873. public function setReferer($referer)
  874. {
  875. $this->setReferrer($referer);
  876. }
  877. /**
  878. * Set Referrer
  879. *
  880. * @access public
  881. * @param $referrer
  882. */
  883. public function setReferrer($referrer)
  884. {
  885. $this->setOpt(CURLOPT_REFERER, $referrer);
  886. }
  887. /**
  888. * Set Timeout
  889. *
  890. * @access public
  891. * @param $seconds
  892. */
  893. public function setTimeout($seconds)
  894. {
  895. $this->setOpt(CURLOPT_TIMEOUT, $seconds);
  896. }
  897. /**
  898. * Set Url
  899. *
  900. * @access public
  901. * @param $url
  902. * @param $data
  903. */
  904. public function setURL($url, $data = array())
  905. {
  906. $this->baseUrl = $url;
  907. $this->url = $this->buildURL($url, $data);
  908. $this->setOpt(CURLOPT_URL, $this->url);
  909. }
  910. /**
  911. * Set User Agent
  912. *
  913. * @access public
  914. * @param $user_agent
  915. */
  916. public function setUserAgent($user_agent)
  917. {
  918. $this->setOpt(CURLOPT_USERAGENT, $user_agent);
  919. }
  920. /**
  921. * Success
  922. *
  923. * @access public
  924. * @param $callback
  925. */
  926. public function success($callback)
  927. {
  928. $this->successFunction = $callback;
  929. }
  930. /**
  931. * Unset Header
  932. *
  933. * @access public
  934. * @param $key
  935. */
  936. public function unsetHeader($key)
  937. {
  938. $this->setHeader($key, '');
  939. unset($this->headers[$key]);
  940. }
  941. /**
  942. * Verbose
  943. *
  944. * @access public
  945. * @param bool $on
  946. * @param resource $output
  947. */
  948. public function verbose($on = true, $output = STDERR)
  949. {
  950. // Turn off CURLINFO_HEADER_OUT for verbose to work. This has the side
  951. // effect of causing Curl::requestHeaders to be empty.
  952. if ($on) {
  953. $this->setOpt(CURLINFO_HEADER_OUT, false);
  954. }
  955. $this->setOpt(CURLOPT_VERBOSE, $on);
  956. $this->setOpt(CURLOPT_STDERR, $output);
  957. }
  958. /**
  959. * Destruct
  960. *
  961. * @access public
  962. */
  963. public function __destruct()
  964. {
  965. $this->close();
  966. }
  967. public function __get($name)
  968. {
  969. $return = null;
  970. if (in_array($name, self::$deferredProperties) && is_callable(array($this, $getter = '__get_' . $name))) {
  971. $return = $this->$name = $this->$getter();
  972. }
  973. return $return;
  974. }
  975. /**
  976. * Get Effective Url
  977. *
  978. * @access private
  979. */
  980. private function __get_effectiveUrl() {
  981. return $this->getInfo(CURLINFO_EFFECTIVE_URL);
  982. }
  983. /**
  984. * Get Total Time
  985. *
  986. * @access private
  987. */
  988. private function __get_totalTime() {
  989. return $this->getInfo(CURLINFO_TOTAL_TIME);
  990. }
  991. /**
  992. * Build Url
  993. *
  994. * @access private
  995. * @param $url
  996. * @param $data
  997. *
  998. * @return string
  999. */
  1000. private function buildURL($url, $data = array())
  1001. {
  1002. return $url . (empty($data) ? '' : '?' . http_build_query($data, '', '&'));
  1003. }
  1004. /**
  1005. * Parse Headers
  1006. *
  1007. * @access private
  1008. * @param $raw_headers
  1009. *
  1010. * @return array
  1011. */
  1012. private function parseHeaders($raw_headers)
  1013. {
  1014. $raw_headers = preg_split('/\r\n/', $raw_headers, null, PREG_SPLIT_NO_EMPTY);
  1015. $http_headers = new CaseInsensitiveArrayService();
  1016. $raw_headers_count = count($raw_headers);
  1017. for ($i = 1; $i < $raw_headers_count; $i++) {
  1018. list($key, $value) = explode(':', $raw_headers[$i], 2);
  1019. $key = trim($key);
  1020. $value = trim($value);
  1021. // Use isset() as array_key_exists() and ArrayAccess are not compatible.
  1022. if (isset($http_headers[$key])) {
  1023. $http_headers[$key] .= ',' . $value;
  1024. } else {
  1025. $http_headers[$key] = $value;
  1026. }
  1027. }
  1028. return array(isset($raw_headers['0']) ? $raw_headers['0'] : '', $http_headers);
  1029. }
  1030. /**
  1031. * Parse Request Headers
  1032. *
  1033. * @access private
  1034. * @param $raw_headers
  1035. *
  1036. * @return array
  1037. */
  1038. private function parseRequestHeaders($raw_headers)
  1039. {
  1040. $request_headers = new CaseInsensitiveArrayService();
  1041. list($first_line, $headers) = $this->parseHeaders($raw_headers);
  1042. $request_headers['Request-Line'] = $first_line;
  1043. foreach ($headers as $key => $value) {
  1044. $request_headers[$key] = $value;
  1045. }
  1046. return $request_headers;
  1047. }
  1048. /**
  1049. * Parse Response
  1050. *
  1051. * @access private
  1052. * @param $response_headers
  1053. * @param $raw_response
  1054. *
  1055. * @return mixed
  1056. * Provided the content-type is determined to be json or xml:
  1057. * Returns stdClass object when the default json decoder is used and the content-type is json.
  1058. * Returns SimpleXMLElement object when the default xml decoder is used and the content-type is xml.
  1059. */
  1060. private function parseResponse($response_headers, $raw_response)
  1061. {
  1062. $response = $raw_response;
  1063. if (isset($response_headers['Content-Type'])) {
  1064. if (preg_match($this->jsonPattern, $response_headers['Content-Type'])) {
  1065. $json_decoder = $this->jsonDecoder;
  1066. if (is_callable($json_decoder)) {
  1067. $response = $json_decoder($response);
  1068. }
  1069. } elseif (preg_match($this->xmlPattern, $response_headers['Content-Type'])) {
  1070. $xml_decoder = $this->xmlDecoder;
  1071. if (is_callable($xml_decoder)) {
  1072. $response = $xml_decoder($response);
  1073. }
  1074. } else {
  1075. $decoder = $this->defaultDecoder;
  1076. if (is_callable($decoder)) {
  1077. $response = $decoder($response);
  1078. }
  1079. }
  1080. }
  1081. return $response;
  1082. }
  1083. /**
  1084. * Parse Response Headers
  1085. *
  1086. * @access private
  1087. * @param $raw_response_headers
  1088. *
  1089. * @return array
  1090. */
  1091. private function parseResponseHeaders($raw_response_headers)
  1092. {
  1093. $response_header_array = explode("\r\n\r\n", $raw_response_headers);
  1094. $response_header = '';
  1095. for ($i = count($response_header_array) - 1; $i >= 0; $i--) {
  1096. if (stripos($response_header_array[$i], 'HTTP/') === 0) {
  1097. $response_header = $response_header_array[$i];
  1098. break;
  1099. }
  1100. }
  1101. $response_headers = new CaseInsensitiveArrayService();
  1102. list($first_line, $headers) = $this->parseHeaders($response_header);
  1103. $response_headers['Status-Line'] = $first_line;
  1104. foreach ($headers as $key => $value) {
  1105. $response_headers[$key] = $value;
  1106. }
  1107. return $response_headers;
  1108. }
  1109. /**
  1110. * Is Array Assoc
  1111. *
  1112. * @access public
  1113. * @param $array
  1114. *
  1115. * @return boolean
  1116. */
  1117. public static function is_array_assoc($array)
  1118. {
  1119. return (bool)count(array_filter(array_keys($array), 'is_string'));
  1120. }
  1121. /**
  1122. * Is Array Multidim
  1123. *
  1124. * @access public
  1125. * @param $array
  1126. *
  1127. * @return boolean
  1128. */
  1129. public static function is_array_multidim($array)
  1130. {
  1131. if (!is_array($array)) {
  1132. return false;
  1133. }
  1134. return (bool)count(array_filter($array, 'is_array'));
  1135. }
  1136. /**
  1137. * Array Flatten Multidim
  1138. *
  1139. * @access public
  1140. * @param $array
  1141. * @param $prefix
  1142. *
  1143. * @return array
  1144. */
  1145. public static function array_flatten_multidim($array, $prefix = false)
  1146. {
  1147. $return = array();
  1148. if (is_array($array) || is_object($array)) {
  1149. if (empty($array)) {
  1150. $return[$prefix] = '';
  1151. } else {
  1152. foreach ($array as $key => $value) {
  1153. if (is_scalar($value)) {
  1154. if ($prefix) {
  1155. $return[$prefix . '[' . $key . ']'] = $value;
  1156. } else {
  1157. $return[$key] = $value;
  1158. }
  1159. } else {
  1160. if ($value instanceof \CURLFile) {
  1161. $return[$key] = $value;
  1162. } else {
  1163. $return = array_merge(
  1164. $return, self::array_flatten_multidim(
  1165. $value, $prefix ? $prefix . '[' . $key . ']' : $key));
  1166. }
  1167. }
  1168. }
  1169. }
  1170. } elseif ($array === null) {
  1171. $return[$prefix] = $array;
  1172. }
  1173. return $return;
  1174. }
  1175. }