requestcore.class.php 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840
  1. <?php
  2. namespace Think\Upload\Driver\Bcs;
  3. use Think\Upload\Driver\Bcs\BCS_RequestCore;
  4. use Think\Upload\Driver\Bcs\BCS_ResponseCore;
  5. use THink\Upload\Driver\Bcs\BCS_RequestCore_Exception;
  6. /**
  7. * Handles all HTTP requests using cURL and manages the responses.
  8. *
  9. * @version 2011.03.01
  10. * @copyright 2006-2011 Ryan Parman
  11. * @copyright 2006-2010 Foleeo Inc.
  12. * @copyright 2010-2011 Amazon.com, Inc. or its affiliates.
  13. * @copyright 2008-2011 Contributors
  14. * @license http://opensource.org/licenses/bsd-license.php Simplified BSD License
  15. */
  16. class BCS_RequestCore {
  17. /**
  18. * The URL being requested.
  19. */
  20. public $request_url;
  21. /**
  22. * The headers being sent in the request.
  23. */
  24. public $request_headers;
  25. /**
  26. * The body being sent in the request.
  27. */
  28. public $request_body;
  29. /**
  30. * The response returned by the request.
  31. */
  32. public $response;
  33. /**
  34. * The headers returned by the request.
  35. */
  36. public $response_headers;
  37. /**
  38. * The body returned by the request.
  39. */
  40. public $response_body;
  41. /**
  42. * The HTTP status code returned by the request.
  43. */
  44. public $response_code;
  45. /**
  46. * Additional response data.
  47. */
  48. public $response_info;
  49. /**
  50. * The handle for the cURL object.
  51. */
  52. public $curl_handle;
  53. /**
  54. * The method by which the request is being made.
  55. */
  56. public $method;
  57. /**
  58. * Stores the proxy settings to use for the request.
  59. */
  60. public $proxy = null;
  61. /**
  62. * The username to use for the request.
  63. */
  64. public $username = null;
  65. /**
  66. * The password to use for the request.
  67. */
  68. public $password = null;
  69. /**
  70. * Custom CURLOPT settings.
  71. */
  72. public $curlopts = null;
  73. /**
  74. * The state of debug mode.
  75. */
  76. public $debug_mode = false;
  77. /**
  78. * The default class to use for HTTP Requests (defaults to <BCS_RequestCore>).
  79. */
  80. public $request_class = 'BCS_RequestCore';
  81. /**
  82. * The default class to use for HTTP Responses (defaults to <BCS_ResponseCore>).
  83. */
  84. public $response_class = 'BCS_ResponseCore';
  85. /**
  86. * Default useragent string to use.
  87. */
  88. public $useragent = 'BCS_RequestCore/1.4.2';
  89. /**
  90. * File to read from while streaming up.
  91. */
  92. public $read_file = null;
  93. /**
  94. * The resource to read from while streaming up.
  95. */
  96. public $read_stream = null;
  97. /**
  98. * The size of the stream to read from.
  99. */
  100. public $read_stream_size = null;
  101. /**
  102. * The length already read from the stream.
  103. */
  104. public $read_stream_read = 0;
  105. /**
  106. * File to write to while streaming down.
  107. */
  108. public $write_file = null;
  109. /**
  110. * The resource to write to while streaming down.
  111. */
  112. public $write_stream = null;
  113. /**
  114. * Stores the intended starting seek position.
  115. */
  116. public $seek_position = null;
  117. /**
  118. * The user-defined callback function to call when a stream is read from.
  119. */
  120. public $registered_streaming_read_callback = null;
  121. /**
  122. * The user-defined callback function to call when a stream is written to.
  123. */
  124. public $registered_streaming_write_callback = null;
  125. /*%******************************************************************************************%*/
  126. // CONSTANTS
  127. /**
  128. * GET HTTP Method
  129. */
  130. const HTTP_GET = 'GET';
  131. /**
  132. * POST HTTP Method
  133. */
  134. const HTTP_POST = 'POST';
  135. /**
  136. * PUT HTTP Method
  137. */
  138. const HTTP_PUT = 'PUT';
  139. /**
  140. * DELETE HTTP Method
  141. */
  142. const HTTP_DELETE = 'DELETE';
  143. /**
  144. * HEAD HTTP Method
  145. */
  146. const HTTP_HEAD = 'HEAD';
  147. /*%******************************************************************************************%*/
  148. // CONSTRUCTOR/DESTRUCTOR
  149. /**
  150. * Constructs a new instance of this class.
  151. *
  152. * @param string $url (Optional) The URL to request or service endpoint to query.
  153. * @param string $proxy (Optional) The faux-url to use for proxy settings. Takes the following format: `proxy://user:pass@hostname:port`
  154. * @param array $helpers (Optional) An associative array of classnames to use for request, and response functionality. Gets passed in automatically by the calling class.
  155. * @return $this A reference to the current instance.
  156. */
  157. public function __construct($url = null, $proxy = null, $helpers = null) {
  158. // Set some default values.
  159. $this->request_url = $url;
  160. $this->method = self::HTTP_GET;
  161. $this->request_headers = array ();
  162. $this->request_body = '';
  163. // Set a new Request class if one was set.
  164. if (isset ( $helpers ['request'] ) && ! empty ( $helpers ['request'] )) {
  165. $this->request_class = $helpers ['request'];
  166. }
  167. // Set a new Request class if one was set.
  168. if (isset ( $helpers ['response'] ) && ! empty ( $helpers ['response'] )) {
  169. $this->response_class = $helpers ['response'];
  170. }
  171. if ($proxy) {
  172. $this->set_proxy ( $proxy );
  173. }
  174. return $this;
  175. }
  176. /**
  177. * Destructs the instance. Closes opened file handles.
  178. *
  179. * @return $this A reference to the current instance.
  180. */
  181. public function __destruct() {
  182. if (isset ( $this->read_file ) && isset ( $this->read_stream )) {
  183. fclose ( $this->read_stream );
  184. }
  185. if (isset ( $this->write_file ) && isset ( $this->write_stream )) {
  186. fclose ( $this->write_stream );
  187. }
  188. return $this;
  189. }
  190. /*%******************************************************************************************%*/
  191. // REQUEST METHODS
  192. /**
  193. * Sets the credentials to use for authentication.
  194. *
  195. * @param string $user (Required) The username to authenticate with.
  196. * @param string $pass (Required) The password to authenticate with.
  197. * @return $this A reference to the current instance.
  198. */
  199. public function set_credentials($user, $pass) {
  200. $this->username = $user;
  201. $this->password = $pass;
  202. return $this;
  203. }
  204. /**
  205. * Adds a custom HTTP header to the cURL request.
  206. *
  207. * @param string $key (Required) The custom HTTP header to set.
  208. * @param mixed $value (Required) The value to assign to the custom HTTP header.
  209. * @return $this A reference to the current instance.
  210. */
  211. public function add_header($key, $value) {
  212. $this->request_headers [$key] = $value;
  213. return $this;
  214. }
  215. /**
  216. * Removes an HTTP header from the cURL request.
  217. *
  218. * @param string $key (Required) The custom HTTP header to set.
  219. * @return $this A reference to the current instance.
  220. */
  221. public function remove_header($key) {
  222. if (isset ( $this->request_headers [$key] )) {
  223. unset ( $this->request_headers [$key] );
  224. }
  225. return $this;
  226. }
  227. /**
  228. * Set the method type for the request.
  229. *
  230. * @param string $method (Required) One of the following constants: <HTTP_GET>, <HTTP_POST>, <HTTP_PUT>, <HTTP_HEAD>, <HTTP_DELETE>.
  231. * @return $this A reference to the current instance.
  232. */
  233. public function set_method($method) {
  234. $this->method = strtoupper ( $method );
  235. return $this;
  236. }
  237. /**
  238. * Sets a custom useragent string for the class.
  239. *
  240. * @param string $ua (Required) The useragent string to use.
  241. * @return $this A reference to the current instance.
  242. */
  243. public function set_useragent($ua) {
  244. $this->useragent = $ua;
  245. return $this;
  246. }
  247. /**
  248. * Set the body to send in the request.
  249. *
  250. * @param string $body (Required) The textual content to send along in the body of the request.
  251. * @return $this A reference to the current instance.
  252. */
  253. public function set_body($body) {
  254. $this->request_body = $body;
  255. return $this;
  256. }
  257. /**
  258. * Set the URL to make the request to.
  259. *
  260. * @param string $url (Required) The URL to make the request to.
  261. * @return $this A reference to the current instance.
  262. */
  263. public function set_request_url($url) {
  264. $this->request_url = $url;
  265. return $this;
  266. }
  267. /**
  268. * Set additional CURLOPT settings. These will merge with the default settings, and override if
  269. * there is a duplicate.
  270. *
  271. * @param array $curlopts (Optional) A set of key-value pairs that set `CURLOPT` options. These will merge with the existing CURLOPTs, and ones passed here will override the defaults. Keys should be the `CURLOPT_*` constants, not strings.
  272. * @return $this A reference to the current instance.
  273. */
  274. public function set_curlopts($curlopts) {
  275. $this->curlopts = $curlopts;
  276. return $this;
  277. }
  278. /**
  279. * Sets the length in bytes to read from the stream while streaming up.
  280. *
  281. * @param integer $size (Required) The length in bytes to read from the stream.
  282. * @return $this A reference to the current instance.
  283. */
  284. public function set_read_stream_size($size) {
  285. $this->read_stream_size = $size;
  286. return $this;
  287. }
  288. /**
  289. * Sets the resource to read from while streaming up. Reads the stream from its current position until
  290. * EOF or `$size` bytes have been read. If `$size` is not given it will be determined by <php:fstat()> and
  291. * <php:ftell()>.
  292. *
  293. * @param resource $resource (Required) The readable resource to read from.
  294. * @param integer $size (Optional) The size of the stream to read.
  295. * @return $this A reference to the current instance.
  296. */
  297. public function set_read_stream($resource, $size = null) {
  298. if (! isset ( $size ) || $size < 0) {
  299. $stats = fstat ( $resource );
  300. if ($stats && $stats ['size'] >= 0) {
  301. $position = ftell ( $resource );
  302. if ($position !== false && $position >= 0) {
  303. $size = $stats ['size'] - $position;
  304. }
  305. }
  306. }
  307. $this->read_stream = $resource;
  308. return $this->set_read_stream_size ( $size );
  309. }
  310. /**
  311. * Sets the file to read from while streaming up.
  312. *
  313. * @param string $location (Required) The readable location to read from.
  314. * @return $this A reference to the current instance.
  315. */
  316. public function set_read_file($location) {
  317. $this->read_file = $location;
  318. $read_file_handle = fopen ( $location, 'r' );
  319. return $this->set_read_stream ( $read_file_handle );
  320. }
  321. /**
  322. * Sets the resource to write to while streaming down.
  323. *
  324. * @param resource $resource (Required) The writeable resource to write to.
  325. * @return $this A reference to the current instance.
  326. */
  327. public function set_write_stream($resource) {
  328. $this->write_stream = $resource;
  329. return $this;
  330. }
  331. /**
  332. * Sets the file to write to while streaming down.
  333. *
  334. * @param string $location (Required) The writeable location to write to.
  335. * @return $this A reference to the current instance.
  336. */
  337. public function set_write_file($location) {
  338. $this->write_file = $location;
  339. $write_file_handle = fopen ( $location, 'w' );
  340. return $this->set_write_stream ( $write_file_handle );
  341. }
  342. /**
  343. * Set the proxy to use for making requests.
  344. *
  345. * @param string $proxy (Required) The faux-url to use for proxy settings. Takes the following format: `proxy://user:pass@hostname:port`
  346. * @return $this A reference to the current instance.
  347. */
  348. public function set_proxy($proxy) {
  349. $proxy = parse_url ( $proxy );
  350. $proxy ['user'] = isset ( $proxy ['user'] ) ? $proxy ['user'] : null;
  351. $proxy ['pass'] = isset ( $proxy ['pass'] ) ? $proxy ['pass'] : null;
  352. $proxy ['port'] = isset ( $proxy ['port'] ) ? $proxy ['port'] : null;
  353. $this->proxy = $proxy;
  354. return $this;
  355. }
  356. /**
  357. * Set the intended starting seek position.
  358. *
  359. * @param integer $position (Required) The byte-position of the stream to begin reading from.
  360. * @return $this A reference to the current instance.
  361. */
  362. public function set_seek_position($position) {
  363. $this->seek_position = isset ( $position ) ? ( integer ) $position : null;
  364. return $this;
  365. }
  366. /**
  367. * Register a callback function to execute whenever a data stream is read from using
  368. * <CFRequest::streaming_read_callback()>.
  369. *
  370. * The user-defined callback function should accept three arguments:
  371. *
  372. * <ul>
  373. * <li><code>$curl_handle</code> - <code>resource</code> - Required - The cURL handle resource that represents the in-progress transfer.</li>
  374. * <li><code>$file_handle</code> - <code>resource</code> - Required - The file handle resource that represents the file on the local file system.</li>
  375. * <li><code>$length</code> - <code>integer</code> - Required - The length in kilobytes of the data chunk that was transferred.</li>
  376. * </ul>
  377. *
  378. * @param string|array|function $callback (Required) The callback function is called by <php:call_user_func()>, so you can pass the following values: <ul>
  379. * <li>The name of a global function to execute, passed as a string.</li>
  380. * <li>A method to execute, passed as <code>array('ClassName', 'MethodName')</code>.</li>
  381. * <li>An anonymous function (PHP 5.3+).</li></ul>
  382. * @return $this A reference to the current instance.
  383. */
  384. public function register_streaming_read_callback($callback) {
  385. $this->registered_streaming_read_callback = $callback;
  386. return $this;
  387. }
  388. /**
  389. * Register a callback function to execute whenever a data stream is written to using
  390. * <CFRequest::streaming_write_callback()>.
  391. *
  392. * The user-defined callback function should accept two arguments:
  393. *
  394. * <ul>
  395. * <li><code>$curl_handle</code> - <code>resource</code> - Required - The cURL handle resource that represents the in-progress transfer.</li>
  396. * <li><code>$length</code> - <code>integer</code> - Required - The length in kilobytes of the data chunk that was transferred.</li>
  397. * </ul>
  398. *
  399. * @param string|array|function $callback (Required) The callback function is called by <php:call_user_func()>, so you can pass the following values: <ul>
  400. * <li>The name of a global function to execute, passed as a string.</li>
  401. * <li>A method to execute, passed as <code>array('ClassName', 'MethodName')</code>.</li>
  402. * <li>An anonymous function (PHP 5.3+).</li></ul>
  403. * @return $this A reference to the current instance.
  404. */
  405. public function register_streaming_write_callback($callback) {
  406. $this->registered_streaming_write_callback = $callback;
  407. return $this;
  408. }
  409. /*%******************************************************************************************%*/
  410. // PREPARE, SEND, AND PROCESS REQUEST
  411. /**
  412. * A callback function that is invoked by cURL for streaming up.
  413. *
  414. * @param resource $curl_handle (Required) The cURL handle for the request.
  415. * @param resource $file_handle (Required) The open file handle resource.
  416. * @param integer $length (Required) The maximum number of bytes to read.
  417. * @return binary Binary data from a stream.
  418. */
  419. public function streaming_read_callback($curl_handle, $file_handle, $length) {
  420. // Once we've sent as much as we're supposed to send...
  421. if ($this->read_stream_read >= $this->read_stream_size) {
  422. // Send EOF
  423. return '';
  424. }
  425. // If we're at the beginning of an upload and need to seek...
  426. if ($this->read_stream_read == 0 && isset ( $this->seek_position ) && $this->seek_position !== ftell ( $this->read_stream )) {
  427. if (fseek ( $this->read_stream, $this->seek_position ) !== 0) {
  428. throw new BCS_RequestCore_Exception ( 'The stream does not support seeking and is either not at the requested position or the position is unknown.' );
  429. }
  430. }
  431. $read = fread ( $this->read_stream, min ( $this->read_stream_size - $this->read_stream_read, $length ) ); // Remaining upload data or cURL's requested chunk size
  432. $this->read_stream_read += strlen ( $read );
  433. $out = $read === false ? '' : $read;
  434. // Execute callback function
  435. if ($this->registered_streaming_read_callback) {
  436. call_user_func ( $this->registered_streaming_read_callback, $curl_handle, $file_handle, $out );
  437. }
  438. return $out;
  439. }
  440. /**
  441. * A callback function that is invoked by cURL for streaming down.
  442. *
  443. * @param resource $curl_handle (Required) The cURL handle for the request.
  444. * @param binary $data (Required) The data to write.
  445. * @return integer The number of bytes written.
  446. */
  447. public function streaming_write_callback($curl_handle, $data) {
  448. $length = strlen ( $data );
  449. $written_total = 0;
  450. $written_last = 0;
  451. while ( $written_total < $length ) {
  452. $written_last = fwrite ( $this->write_stream, substr ( $data, $written_total ) );
  453. if ($written_last === false) {
  454. return $written_total;
  455. }
  456. $written_total += $written_last;
  457. }
  458. // Execute callback function
  459. if ($this->registered_streaming_write_callback) {
  460. call_user_func ( $this->registered_streaming_write_callback, $curl_handle, $written_total );
  461. }
  462. return $written_total;
  463. }
  464. /**
  465. * Prepares and adds the details of the cURL request. This can be passed along to a <php:curl_multi_exec()>
  466. * function.
  467. *
  468. * @return resource The handle for the cURL object.
  469. */
  470. public function prep_request() {
  471. $curl_handle = curl_init ();
  472. // Set default options.
  473. curl_setopt ( $curl_handle, CURLOPT_URL, $this->request_url );
  474. curl_setopt ( $curl_handle, CURLOPT_FILETIME, true );
  475. curl_setopt ( $curl_handle, CURLOPT_FRESH_CONNECT, false );
  476. curl_setopt ( $curl_handle, CURLOPT_SSL_VERIFYPEER, false );
  477. curl_setopt ( $curl_handle, CURLOPT_SSL_VERIFYHOST, true );
  478. curl_setopt ( $curl_handle, CURLOPT_CLOSEPOLICY, CURLCLOSEPOLICY_LEAST_RECENTLY_USED );
  479. curl_setopt ( $curl_handle, CURLOPT_MAXREDIRS, 5 );
  480. curl_setopt ( $curl_handle, CURLOPT_HEADER, true );
  481. curl_setopt ( $curl_handle, CURLOPT_RETURNTRANSFER, true );
  482. curl_setopt ( $curl_handle, CURLOPT_TIMEOUT, 5184000 );
  483. curl_setopt ( $curl_handle, CURLOPT_CONNECTTIMEOUT, 120 );
  484. curl_setopt ( $curl_handle, CURLOPT_NOSIGNAL, true );
  485. curl_setopt ( $curl_handle, CURLOPT_REFERER, $this->request_url );
  486. curl_setopt ( $curl_handle, CURLOPT_USERAGENT, $this->useragent );
  487. curl_setopt ( $curl_handle, CURLOPT_READFUNCTION, array (
  488. $this,
  489. 'streaming_read_callback' ) );
  490. if ($this->debug_mode) {
  491. curl_setopt ( $curl_handle, CURLOPT_VERBOSE, true );
  492. }
  493. //if (! ini_get ( 'safe_mode' )) {
  494. //modify by zhengkan
  495. //curl_setopt($curl_handle, CURLOPT_FOLLOWLOCATION, true);
  496. //}
  497. // Enable a proxy connection if requested.
  498. if ($this->proxy) {
  499. curl_setopt ( $curl_handle, CURLOPT_HTTPPROXYTUNNEL, true );
  500. $host = $this->proxy ['host'];
  501. $host .= ($this->proxy ['port']) ? ':' . $this->proxy ['port'] : '';
  502. curl_setopt ( $curl_handle, CURLOPT_PROXY, $host );
  503. if (isset ( $this->proxy ['user'] ) && isset ( $this->proxy ['pass'] )) {
  504. curl_setopt ( $curl_handle, CURLOPT_PROXYUSERPWD, $this->proxy ['user'] . ':' . $this->proxy ['pass'] );
  505. }
  506. }
  507. // Set credentials for HTTP Basic/Digest Authentication.
  508. if ($this->username && $this->password) {
  509. curl_setopt ( $curl_handle, CURLOPT_HTTPAUTH, CURLAUTH_ANY );
  510. curl_setopt ( $curl_handle, CURLOPT_USERPWD, $this->username . ':' . $this->password );
  511. }
  512. // Handle the encoding if we can.
  513. if (extension_loaded ( 'zlib' )) {
  514. curl_setopt ( $curl_handle, CURLOPT_ENCODING, '' );
  515. }
  516. // Process custom headers
  517. if (isset ( $this->request_headers ) && count ( $this->request_headers )) {
  518. $temp_headers = array ();
  519. foreach ( $this->request_headers as $k => $v ) {
  520. $temp_headers [] = $k . ': ' . $v;
  521. }
  522. curl_setopt ( $curl_handle, CURLOPT_HTTPHEADER, $temp_headers );
  523. }
  524. switch ($this->method) {
  525. case self::HTTP_PUT :
  526. curl_setopt ( $curl_handle, CURLOPT_CUSTOMREQUEST, 'PUT' );
  527. if (isset ( $this->read_stream )) {
  528. if (! isset ( $this->read_stream_size ) || $this->read_stream_size < 0) {
  529. throw new BCS_RequestCore_Exception ( 'The stream size for the streaming upload cannot be determined.' );
  530. }
  531. curl_setopt ( $curl_handle, CURLOPT_INFILESIZE, $this->read_stream_size );
  532. curl_setopt ( $curl_handle, CURLOPT_UPLOAD, true );
  533. } else {
  534. curl_setopt ( $curl_handle, CURLOPT_POSTFIELDS, $this->request_body );
  535. }
  536. break;
  537. case self::HTTP_POST :
  538. curl_setopt ( $curl_handle, CURLOPT_POST, true );
  539. curl_setopt ( $curl_handle, CURLOPT_POSTFIELDS, $this->request_body );
  540. break;
  541. case self::HTTP_HEAD :
  542. curl_setopt ( $curl_handle, CURLOPT_CUSTOMREQUEST, self::HTTP_HEAD );
  543. curl_setopt ( $curl_handle, CURLOPT_NOBODY, 1 );
  544. break;
  545. default : // Assumed GET
  546. curl_setopt ( $curl_handle, CURLOPT_CUSTOMREQUEST, $this->method );
  547. if (isset ( $this->write_stream )) {
  548. curl_setopt ( $curl_handle, CURLOPT_WRITEFUNCTION, array (
  549. $this,
  550. 'streaming_write_callback' ) );
  551. curl_setopt ( $curl_handle, CURLOPT_HEADER, false );
  552. } else {
  553. curl_setopt ( $curl_handle, CURLOPT_POSTFIELDS, $this->request_body );
  554. }
  555. break;
  556. }
  557. // Merge in the CURLOPTs
  558. if (isset ( $this->curlopts ) && sizeof ( $this->curlopts ) > 0) {
  559. foreach ( $this->curlopts as $k => $v ) {
  560. curl_setopt ( $curl_handle, $k, $v );
  561. }
  562. }
  563. return $curl_handle;
  564. }
  565. /**
  566. * is the environment BAE?
  567. * @return boolean the result of the answer
  568. */
  569. private function isBaeEnv() {
  570. if (isset ( $_SERVER ['HTTP_HOST'] )) {
  571. $host = $_SERVER ['HTTP_HOST'];
  572. $pos = strpos ( $host, '.' );
  573. if ($pos !== false) {
  574. $substr = substr ( $host, $pos + 1 );
  575. if ($substr == 'duapp.com') {
  576. return true;
  577. }
  578. }
  579. }
  580. if (isset ( $_SERVER ["HTTP_BAE_LOGID"] )) {
  581. return true;
  582. }
  583. return false;
  584. }
  585. /**
  586. * Take the post-processed cURL data and break it down into useful header/body/info chunks. Uses the
  587. * data stored in the `curl_handle` and `response` properties unless replacement data is passed in via
  588. * parameters.
  589. *
  590. * @param resource $curl_handle (Optional) The reference to the already executed cURL request.
  591. * @param string $response (Optional) The actual response content itself that needs to be parsed.
  592. * @return BCS_ResponseCore A <BCS_ResponseCore> object containing a parsed HTTP response.
  593. */
  594. public function process_response($curl_handle = null, $response = null) {
  595. // Accept a custom one if it's passed.
  596. if ($curl_handle && $response) {
  597. $this->curl_handle = $curl_handle;
  598. $this->response = $response;
  599. }
  600. // As long as this came back as a valid resource...
  601. if (is_resource ( $this->curl_handle )) {
  602. // Determine what's what.
  603. $header_size = curl_getinfo ( $this->curl_handle, CURLINFO_HEADER_SIZE );
  604. $this->response_headers = substr ( $this->response, 0, $header_size );
  605. $this->response_body = substr ( $this->response, $header_size );
  606. $this->response_code = curl_getinfo ( $this->curl_handle, CURLINFO_HTTP_CODE );
  607. $this->response_info = curl_getinfo ( $this->curl_handle );
  608. // Parse out the headers
  609. $this->response_headers = explode ( "\r\n\r\n", trim ( $this->response_headers ) );
  610. $this->response_headers = array_pop ( $this->response_headers );
  611. $this->response_headers = explode ( "\r\n", $this->response_headers );
  612. array_shift ( $this->response_headers );
  613. // Loop through and split up the headers.
  614. $header_assoc = array ();
  615. foreach ( $this->response_headers as $header ) {
  616. $kv = explode ( ': ', $header );
  617. //$header_assoc [strtolower ( $kv [0] )] = $kv [1];
  618. $header_assoc [$kv [0]] = $kv [1];
  619. }
  620. // Reset the headers to the appropriate property.
  621. $this->response_headers = $header_assoc;
  622. $this->response_headers ['_info'] = $this->response_info;
  623. $this->response_headers ['_info'] ['method'] = $this->method;
  624. if ($curl_handle && $response) {
  625. $class='\Think\Upload\Driver\Bcs\\'. $this->response_class;
  626. return new $class ( $this->response_headers, $this->response_body, $this->response_code, $this->curl_handle );
  627. }
  628. }
  629. // Return false
  630. return false;
  631. }
  632. /**
  633. * Sends the request, calling necessary utility functions to update built-in properties.
  634. *
  635. * @param boolean $parse (Optional) Whether to parse the response with BCS_ResponseCore or not.
  636. * @return string The resulting unparsed data from the request.
  637. */
  638. public function send_request($parse = false) {
  639. if (false === $this->isBaeEnv ()) {
  640. set_time_limit ( 0 );
  641. }
  642. $curl_handle = $this->prep_request ();
  643. $this->response = curl_exec ( $curl_handle );
  644. if ($this->response === false ||
  645. ($this->method === self::HTTP_GET &&
  646. curl_errno($curl_handle) === CURLE_PARTIAL_FILE)) {
  647. throw new BCS_RequestCore_Exception ( 'cURL resource: ' . ( string ) $curl_handle . '; cURL error: ' . curl_error ( $curl_handle ) . ' (' . curl_errno ( $curl_handle ) . ')' );
  648. }
  649. $parsed_response = $this->process_response ( $curl_handle, $this->response );
  650. curl_close ( $curl_handle );
  651. if ($parse) {
  652. return $parsed_response;
  653. }
  654. return $this->response;
  655. }
  656. /**
  657. * Sends the request using <php:curl_multi_exec()>, enabling parallel requests. Uses the "rolling" method.
  658. *
  659. * @param array $handles (Required) An indexed array of cURL handles to process simultaneously.
  660. * @param array $opt (Optional) An associative array of parameters that can have the following keys: <ul>
  661. * <li><code>callback</code> - <code>string|array</code> - Optional - The string name of a function to pass the response data to. If this is a method, pass an array where the <code>[0]</code> index is the class and the <code>[1]</code> index is the method name.</li>
  662. * <li><code>limit</code> - <code>integer</code> - Optional - The number of simultaneous requests to make. This can be useful for scaling around slow server responses. Defaults to trusting cURLs judgement as to how many to use.</li></ul>
  663. * @return array Post-processed cURL responses.
  664. */
  665. public function send_multi_request($handles, $opt = null) {
  666. if (false === $this->isBaeEnv ()) {
  667. set_time_limit ( 0 );
  668. }
  669. // Skip everything if there are no handles to process.
  670. if (count ( $handles ) === 0)
  671. return array ();
  672. if (! $opt)
  673. $opt = array ();
  674. // Initialize any missing options
  675. $limit = isset ( $opt ['limit'] ) ? $opt ['limit'] : - 1;
  676. // Initialize
  677. $handle_list = $handles;
  678. $http = new $this->request_class ();
  679. $multi_handle = curl_multi_init ();
  680. $handles_post = array ();
  681. $added = count ( $handles );
  682. $last_handle = null;
  683. $count = 0;
  684. $i = 0;
  685. // Loop through the cURL handles and add as many as it set by the limit parameter.
  686. while ( $i < $added ) {
  687. if ($limit > 0 && $i >= $limit)
  688. break;
  689. curl_multi_add_handle ( $multi_handle, array_shift ( $handles ) );
  690. $i ++;
  691. }
  692. do {
  693. $active = false;
  694. // Start executing and wait for a response.
  695. while ( ($status = curl_multi_exec ( $multi_handle, $active )) === CURLM_CALL_MULTI_PERFORM ) {
  696. // Start looking for possible responses immediately when we have to add more handles
  697. if (count ( $handles ) > 0)
  698. break;
  699. }
  700. // Figure out which requests finished.
  701. $to_process = array ();
  702. while ( $done = curl_multi_info_read ( $multi_handle ) ) {
  703. // Since curl_errno() isn't reliable for handles that were in multirequests, we check the 'result' of the info read, which contains the curl error number, (listed here http://curl.haxx.se/libcurl/c/libcurl-errors.html )
  704. if ($done ['result'] > 0) {
  705. throw new BCS_RequestCore_Exception ( 'cURL resource: ' . ( string ) $done ['handle'] . '; cURL error: ' . curl_error ( $done ['handle'] ) . ' (' . $done ['result'] . ')' );
  706. } // Because curl_multi_info_read() might return more than one message about a request, we check to see if this request is already in our array of completed requests
  707. elseif (! isset ( $to_process [( int ) $done ['handle']] )) {
  708. $to_process [( int ) $done ['handle']] = $done;
  709. }
  710. }
  711. // Actually deal with the request
  712. foreach ( $to_process as $pkey => $done ) {
  713. $response = $http->process_response ( $done ['handle'], curl_multi_getcontent ( $done ['handle'] ) );
  714. $key = array_search ( $done ['handle'], $handle_list, true );
  715. $handles_post [$key] = $response;
  716. if (count ( $handles ) > 0) {
  717. curl_multi_add_handle ( $multi_handle, array_shift ( $handles ) );
  718. }
  719. curl_multi_remove_handle ( $multi_handle, $done ['handle'] );
  720. curl_close ( $done ['handle'] );
  721. }
  722. } while ( $active || count ( $handles_post ) < $added );
  723. curl_multi_close ( $multi_handle );
  724. ksort ( $handles_post, SORT_NUMERIC );
  725. return $handles_post;
  726. }
  727. /*%******************************************************************************************%*/
  728. // RESPONSE METHODS
  729. /**
  730. * Get the HTTP response headers from the request.
  731. *
  732. * @param string $header (Optional) A specific header value to return. Defaults to all headers.
  733. * @return string|array All or selected header values.
  734. */
  735. public function get_response_header($header = null) {
  736. if ($header) {
  737. // return $this->response_headers [strtolower ( $header )];
  738. return $this->response_headers [$header];
  739. }
  740. return $this->response_headers;
  741. }
  742. /**
  743. * Get the HTTP response body from the request.
  744. *
  745. * @return string The response body.
  746. */
  747. public function get_response_body() {
  748. return $this->response_body;
  749. }
  750. /**
  751. * Get the HTTP response code from the request.
  752. *
  753. * @return string The HTTP response code.
  754. */
  755. public function get_response_code() {
  756. return $this->response_code;
  757. }
  758. }
  759. /**
  760. * Container for all response-related methods.
  761. */
  762. class BCS_ResponseCore {
  763. /**
  764. * Stores the HTTP header information.
  765. */
  766. public $header;
  767. /**
  768. * Stores the SimpleXML response.
  769. */
  770. public $body;
  771. /**
  772. * Stores the HTTP response code.
  773. */
  774. public $status;
  775. /**
  776. * Constructs a new instance of this class.
  777. *
  778. * @param array $header (Required) Associative array of HTTP headers (typically returned by <BCS_RequestCore::get_response_header()>).
  779. * @param string $body (Required) XML-formatted response from AWS.
  780. * @param integer $status (Optional) HTTP response status code from the request.
  781. * @return object Contains an <php:array> `header` property (HTTP headers as an associative array), a <php:SimpleXMLElement> or <php:string> `body` property, and an <php:integer> `status` code.
  782. */
  783. public function __construct($header, $body, $status = null) {
  784. $this->header = $header;
  785. $this->body = $body;
  786. $this->status = $status;
  787. return $this;
  788. }
  789. /**
  790. * Did we receive the status code we expected?
  791. *
  792. * @param integer|array $codes (Optional) The status code(s) to expect. Pass an <php:integer> for a single acceptable value, or an <php:array> of integers for multiple acceptable values.
  793. * @return boolean Whether we received the expected status code or not.
  794. */
  795. public function isOK($codes = array(200, 201, 204, 206)) {
  796. if (is_array ( $codes )) {
  797. return in_array ( $this->status, $codes );
  798. }
  799. return $this->status === $codes;
  800. }
  801. }
  802. /**
  803. * Default BCS_RequestCore Exception.
  804. */
  805. class BCS_RequestCore_Exception extends \Exception {
  806. }