PageRenderTime 56ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/monica/vendor/zendframework/zendframework/library/Zend/Http/Client.php

https://bitbucket.org/alexandretaz/maniac_divers
PHP | 1365 lines | 729 code | 164 blank | 472 comment | 160 complexity | b73d17e0fd57bee078c6e8f6b5be6ab8 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. <?php
  2. /**
  3. * Zend Framework (http://framework.zend.com/)
  4. *
  5. * @link http://github.com/zendframework/zf2 for the canonical source repository
  6. * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
  7. * @license http://framework.zend.com/license/new-bsd New BSD License
  8. */
  9. namespace Zend\Http;
  10. use ArrayIterator;
  11. use Traversable;
  12. use Zend\Stdlib;
  13. use Zend\Stdlib\ArrayUtils;
  14. use Zend\Stdlib\ErrorHandler;
  15. use Zend\Uri\Http;
  16. /**
  17. * Http client
  18. */
  19. class Client implements Stdlib\DispatchableInterface
  20. {
  21. /**
  22. * @const string Supported HTTP Authentication methods
  23. */
  24. const AUTH_BASIC = 'basic';
  25. const AUTH_DIGEST = 'digest'; // not implemented yet
  26. /**
  27. * @const string POST data encoding methods
  28. */
  29. const ENC_URLENCODED = 'application/x-www-form-urlencoded';
  30. const ENC_FORMDATA = 'multipart/form-data';
  31. /**
  32. * @const string DIGEST Authentication
  33. */
  34. const DIGEST_REALM = 'realm';
  35. const DIGEST_QOP = 'qop';
  36. const DIGEST_NONCE = 'nonce';
  37. const DIGEST_OPAQUE = 'opaque';
  38. const DIGEST_NC = 'nc';
  39. const DIGEST_CNONCE = 'cnonce';
  40. /**
  41. * @var Response
  42. */
  43. protected $response;
  44. /**
  45. * @var Request
  46. */
  47. protected $request;
  48. /**
  49. * @var Client/Adapter
  50. */
  51. protected $adapter;
  52. /**
  53. * @var array
  54. */
  55. protected $auth = array();
  56. /**
  57. * @var string
  58. */
  59. protected $streamName = null;
  60. /**
  61. * @var array of Header\SetCookie
  62. */
  63. protected $cookies = array();
  64. /**
  65. * @var string
  66. */
  67. protected $encType = '';
  68. /**
  69. * @var Request
  70. */
  71. protected $lastRawRequest = null;
  72. /**
  73. * @var Response
  74. */
  75. protected $lastRawResponse = null;
  76. /**
  77. * @var int
  78. */
  79. protected $redirectCounter = 0;
  80. /**
  81. * Configuration array, set using the constructor or using ::setOptions()
  82. *
  83. * @var array
  84. */
  85. protected $config = array(
  86. 'maxredirects' => 5,
  87. 'strictredirects' => false,
  88. 'useragent' => 'Zend\Http\Client',
  89. 'timeout' => 10,
  90. 'adapter' => 'Zend\Http\Client\Adapter\Socket',
  91. 'httpversion' => Request::VERSION_11,
  92. 'storeresponse' => true,
  93. 'keepalive' => false,
  94. 'outputstream' => false,
  95. 'encodecookies' => true,
  96. 'argseparator' => null,
  97. 'rfc3986strict' => false
  98. );
  99. /**
  100. * Fileinfo magic database resource
  101. *
  102. * This variable is populated the first time _detectFileMimeType is called
  103. * and is then reused on every call to this method
  104. *
  105. * @var resource
  106. */
  107. protected static $fileInfoDb = null;
  108. /**
  109. * Constructor
  110. *
  111. * @param string $uri
  112. * @param array|Traversable $options
  113. */
  114. public function __construct($uri = null, $options = null)
  115. {
  116. if ($uri !== null) {
  117. $this->setUri($uri);
  118. }
  119. if ($options !== null) {
  120. $this->setOptions($options);
  121. }
  122. }
  123. /**
  124. * Set configuration parameters for this HTTP client
  125. *
  126. * @param array|Traversable $options
  127. * @return Client
  128. * @throws Client\Exception\InvalidArgumentException
  129. */
  130. public function setOptions($options = array())
  131. {
  132. if ($options instanceof Traversable) {
  133. $options = ArrayUtils::iteratorToArray($options);
  134. }
  135. if (!is_array($options)) {
  136. throw new Client\Exception\InvalidArgumentException('Config parameter is not valid');
  137. }
  138. /** Config Key Normalization */
  139. foreach ($options as $k => $v) {
  140. $this->config[str_replace(array('-', '_', ' ', '.'), '', strtolower($k))] = $v; // replace w/ normalized
  141. }
  142. // Pass configuration options to the adapter if it exists
  143. if ($this->adapter instanceof Client\Adapter\AdapterInterface) {
  144. $this->adapter->setOptions($options);
  145. }
  146. return $this;
  147. }
  148. /**
  149. * Load the connection adapter
  150. *
  151. * While this method is not called more than one for a client, it is
  152. * separated from ->request() to preserve logic and readability
  153. *
  154. * @param Client\Adapter\AdapterInterface|string $adapter
  155. * @return Client
  156. * @throws Client\Exception\InvalidArgumentException
  157. */
  158. public function setAdapter($adapter)
  159. {
  160. if (is_string($adapter)) {
  161. if (!class_exists($adapter)) {
  162. throw new Client\Exception\InvalidArgumentException('Unable to locate adapter class "' . $adapter . '"');
  163. }
  164. $adapter = new $adapter;
  165. }
  166. if (! $adapter instanceof Client\Adapter\AdapterInterface) {
  167. throw new Client\Exception\InvalidArgumentException('Passed adapter is not a HTTP connection adapter');
  168. }
  169. $this->adapter = $adapter;
  170. $config = $this->config;
  171. unset($config['adapter']);
  172. $this->adapter->setOptions($config);
  173. return $this;
  174. }
  175. /**
  176. * Load the connection adapter
  177. *
  178. * @return Client\Adapter\AdapterInterface $adapter
  179. */
  180. public function getAdapter()
  181. {
  182. return $this->adapter;
  183. }
  184. /**
  185. * Set request
  186. *
  187. * @param Request $request
  188. * @return Client
  189. */
  190. public function setRequest(Request $request)
  191. {
  192. $this->request = $request;
  193. return $this;
  194. }
  195. /**
  196. * Get Request
  197. *
  198. * @return Request
  199. */
  200. public function getRequest()
  201. {
  202. if (empty($this->request)) {
  203. $this->request = new Request();
  204. }
  205. return $this->request;
  206. }
  207. /**
  208. * Set response
  209. *
  210. * @param Response $response
  211. * @return Client
  212. */
  213. public function setResponse(Response $response)
  214. {
  215. $this->response = $response;
  216. return $this;
  217. }
  218. /**
  219. * Get Response
  220. *
  221. * @return Response
  222. */
  223. public function getResponse()
  224. {
  225. if (empty($this->response)) {
  226. $this->response = new Response();
  227. }
  228. return $this->response;
  229. }
  230. /**
  231. * Get the last request (as a string)
  232. *
  233. * @return string
  234. */
  235. public function getLastRawRequest()
  236. {
  237. return $this->lastRawRequest;
  238. }
  239. /**
  240. * Get the last response (as a string)
  241. *
  242. * @return string
  243. */
  244. public function getLastRawResponse()
  245. {
  246. return $this->lastRawResponse;
  247. }
  248. /**
  249. * Get the redirections count
  250. *
  251. * @return integer
  252. */
  253. public function getRedirectionsCount()
  254. {
  255. return $this->redirectCounter;
  256. }
  257. /**
  258. * Set Uri (to the request)
  259. *
  260. * @param string|Http $uri
  261. * @return Client
  262. */
  263. public function setUri($uri)
  264. {
  265. if (!empty($uri)) {
  266. $this->getRequest()->setUri($uri);
  267. // Set auth if username and password has been specified in the uri
  268. if ($this->getUri()->getUser() && $this->getUri()->getPassword()) {
  269. $this->setAuth($this->getUri()->getUser(), $this->getUri()->getPassword());
  270. }
  271. // We have no ports, set the defaults
  272. if (! $this->getUri()->getPort()) {
  273. $this->getUri()->setPort(($this->getUri()->getScheme() == 'https' ? 443 : 80));
  274. }
  275. }
  276. return $this;
  277. }
  278. /**
  279. * Get uri (from the request)
  280. *
  281. * @return Http
  282. */
  283. public function getUri()
  284. {
  285. return $this->getRequest()->getUri();
  286. }
  287. /**
  288. * Set the HTTP method (to the request)
  289. *
  290. * @param string $method
  291. * @return Client
  292. */
  293. public function setMethod($method)
  294. {
  295. $method = $this->getRequest()->setMethod($method)->getMethod();
  296. if (($method == Request::METHOD_POST || $method == Request::METHOD_PUT ||
  297. $method == Request::METHOD_DELETE || $method == Request::METHOD_PATCH)
  298. && empty($this->encType)) {
  299. $this->setEncType(self::ENC_URLENCODED);
  300. }
  301. return $this;
  302. }
  303. /**
  304. * Get the HTTP method
  305. *
  306. * @return string
  307. */
  308. public function getMethod()
  309. {
  310. return $this->getRequest()->getMethod();
  311. }
  312. /**
  313. * Set the query string argument separator
  314. *
  315. * @param string $argSeparator
  316. * @return Client
  317. */
  318. public function setArgSeparator($argSeparator)
  319. {
  320. $this->setOptions(array("argseparator" => $argSeparator));
  321. return $this;
  322. }
  323. /**
  324. * Get the query string argument separator
  325. *
  326. * @return string
  327. */
  328. public function getArgSeparator()
  329. {
  330. $argSeparator = $this->config['argseparator'];
  331. if (empty($argSeparator)) {
  332. $argSeparator = ini_get('arg_separator.output');
  333. $this->setArgSeparator($argSeparator);
  334. }
  335. return $argSeparator;
  336. }
  337. /**
  338. * Set the encoding type and the boundary (if any)
  339. *
  340. * @param string $encType
  341. * @param string $boundary
  342. * @return Client
  343. */
  344. public function setEncType($encType, $boundary = null)
  345. {
  346. if (!empty($encType)) {
  347. if (!empty($boundary)) {
  348. $this->encType = $encType . "; boundary={$boundary}";
  349. } else {
  350. $this->encType = $encType;
  351. }
  352. }
  353. return $this;
  354. }
  355. /**
  356. * Get the encoding type
  357. *
  358. * @return string
  359. */
  360. public function getEncType()
  361. {
  362. return $this->encType;
  363. }
  364. /**
  365. * Set raw body (for advanced use cases)
  366. *
  367. * @param string $body
  368. * @return Client
  369. */
  370. public function setRawBody($body)
  371. {
  372. $this->getRequest()->setContent($body);
  373. return $this;
  374. }
  375. /**
  376. * Set the POST parameters
  377. *
  378. * @param array $post
  379. * @return Client
  380. */
  381. public function setParameterPost(array $post)
  382. {
  383. $this->getRequest()->getPost()->fromArray($post);
  384. return $this;
  385. }
  386. /**
  387. * Set the GET parameters
  388. *
  389. * @param array $query
  390. * @return Client
  391. */
  392. public function setParameterGet(array $query)
  393. {
  394. $this->getRequest()->getQuery()->fromArray($query);
  395. return $this;
  396. }
  397. /**
  398. * Return the current cookies
  399. *
  400. * @return array
  401. */
  402. public function getCookies()
  403. {
  404. return $this->cookies;
  405. }
  406. /**
  407. * Get the cookie Id (name+domain+path)
  408. *
  409. * @param Header\SetCookie|Header\Cookie $cookie
  410. * @return string|bool
  411. */
  412. protected function getCookieId($cookie)
  413. {
  414. if (($cookie instanceof Header\SetCookie) || ($cookie instanceof Header\Cookie)) {
  415. return $cookie->getName() . $cookie->getDomain() . $cookie->getPath();
  416. }
  417. return false;
  418. }
  419. /**
  420. * Add a cookie
  421. *
  422. * @param array|ArrayIterator|Header\SetCookie|string $cookie
  423. * @param string $value
  424. * @param string $expire
  425. * @param string $path
  426. * @param string $domain
  427. * @param bool $secure
  428. * @param bool $httponly
  429. * @param string $maxAge
  430. * @param string $version
  431. * @throws Exception\InvalidArgumentException
  432. * @return Client
  433. */
  434. public function addCookie($cookie, $value = null, $expire = null, $path = null, $domain = null, $secure = false, $httponly = true, $maxAge = null, $version = null)
  435. {
  436. if (is_array($cookie) || $cookie instanceof ArrayIterator) {
  437. foreach ($cookie as $setCookie) {
  438. if ($setCookie instanceof Header\SetCookie) {
  439. $this->cookies[$this->getCookieId($setCookie)] = $setCookie;
  440. } else {
  441. throw new Exception\InvalidArgumentException('The cookie parameter is not a valid Set-Cookie type');
  442. }
  443. }
  444. } elseif (is_string($cookie) && $value !== null) {
  445. $setCookie = new Header\SetCookie($cookie, $value, $expire, $path, $domain, $secure, $httponly, $maxAge, $version);
  446. $this->cookies[$this->getCookieId($setCookie)] = $setCookie;
  447. } elseif ($cookie instanceof Header\SetCookie) {
  448. $this->cookies[$this->getCookieId($cookie)] = $cookie;
  449. } else {
  450. throw new Exception\InvalidArgumentException('Invalid parameter type passed as Cookie');
  451. }
  452. return $this;
  453. }
  454. /**
  455. * Set an array of cookies
  456. *
  457. * @param array $cookies
  458. * @throws Exception\InvalidArgumentException
  459. * @return Client
  460. */
  461. public function setCookies($cookies)
  462. {
  463. if (is_array($cookies)) {
  464. $this->clearCookies();
  465. foreach ($cookies as $name => $value) {
  466. $this->addCookie($name, $value);
  467. }
  468. } else {
  469. throw new Exception\InvalidArgumentException('Invalid cookies passed as parameter, it must be an array');
  470. }
  471. return $this;
  472. }
  473. /**
  474. * Clear all the cookies
  475. */
  476. public function clearCookies()
  477. {
  478. $this->cookies = array();
  479. }
  480. /**
  481. * Set the headers (for the request)
  482. *
  483. * @param Headers|array $headers
  484. * @throws Exception\InvalidArgumentException
  485. * @return Client
  486. */
  487. public function setHeaders($headers)
  488. {
  489. if (is_array($headers)) {
  490. $newHeaders = new Headers();
  491. $newHeaders->addHeaders($headers);
  492. $this->getRequest()->setHeaders($newHeaders);
  493. } elseif ($headers instanceof Headers) {
  494. $this->getRequest()->setHeaders($headers);
  495. } else {
  496. throw new Exception\InvalidArgumentException('Invalid parameter headers passed');
  497. }
  498. return $this;
  499. }
  500. /**
  501. * Check if exists the header type specified
  502. *
  503. * @param string $name
  504. * @return bool
  505. */
  506. public function hasHeader($name)
  507. {
  508. $headers = $this->getRequest()->getHeaders();
  509. if ($headers instanceof Headers) {
  510. return $headers->has($name);
  511. }
  512. return false;
  513. }
  514. /**
  515. * Get the header value of the request
  516. *
  517. * @param string $name
  518. * @return string|bool
  519. */
  520. public function getHeader($name)
  521. {
  522. $headers = $this->getRequest()->getHeaders();
  523. if ($headers instanceof Headers) {
  524. if ($headers->get($name)) {
  525. return $headers->get($name)->getFieldValue();
  526. }
  527. }
  528. return false;
  529. }
  530. /**
  531. * Set streaming for received data
  532. *
  533. * @param string|bool $streamfile Stream file, true for temp file, false/null for no streaming
  534. * @return \Zend\Http\Client
  535. */
  536. public function setStream($streamfile = true)
  537. {
  538. $this->setOptions(array("outputstream" => $streamfile));
  539. return $this;
  540. }
  541. /**
  542. * Get status of streaming for received data
  543. * @return bool|string
  544. */
  545. public function getStream()
  546. {
  547. if (null !== $this->streamName) {
  548. return $this->streamName;
  549. }
  550. return $this->config['outputstream'];
  551. }
  552. /**
  553. * Create temporary stream
  554. *
  555. * @throws Exception\RuntimeException
  556. * @return resource
  557. */
  558. protected function openTempStream()
  559. {
  560. $this->streamName = $this->config['outputstream'];
  561. if (!is_string($this->streamName)) {
  562. // If name is not given, create temp name
  563. $this->streamName = tempnam(
  564. isset($this->config['streamtmpdir']) ? $this->config['streamtmpdir'] : sys_get_temp_dir(),
  565. 'Zend\Http\Client'
  566. );
  567. }
  568. ErrorHandler::start();
  569. $fp = fopen($this->streamName, "w+b");
  570. $error = ErrorHandler::stop();
  571. if (false === $fp) {
  572. if ($this->adapter instanceof Client\Adapter\AdapterInterface) {
  573. $this->adapter->close();
  574. }
  575. throw new Exception\RuntimeException("Could not open temp file {$this->streamName}", 0, $error);
  576. }
  577. return $fp;
  578. }
  579. /**
  580. * Create a HTTP authentication "Authorization:" header according to the
  581. * specified user, password and authentication method.
  582. *
  583. * @param string $user
  584. * @param string $password
  585. * @param string $type
  586. * @throws Exception\InvalidArgumentException
  587. * @return Client
  588. */
  589. public function setAuth($user, $password, $type = self::AUTH_BASIC)
  590. {
  591. if (!defined('self::AUTH_' . strtoupper($type))) {
  592. throw new Exception\InvalidArgumentException("Invalid or not supported authentication type: '$type'");
  593. }
  594. if (empty($user)) {
  595. throw new Exception\InvalidArgumentException("The username cannot be empty");
  596. }
  597. $this->auth = array (
  598. 'user' => $user,
  599. 'password' => $password,
  600. 'type' => $type
  601. );
  602. return $this;
  603. }
  604. /**
  605. * Calculate the response value according to the HTTP authentication type
  606. *
  607. * @see http://www.faqs.org/rfcs/rfc2617.html
  608. * @param string $user
  609. * @param string $password
  610. * @param string $type
  611. * @param array $digest
  612. * @param null|string $entityBody
  613. * @throws Exception\InvalidArgumentException
  614. * @return string|bool
  615. */
  616. protected function calcAuthDigest($user, $password, $type = self::AUTH_BASIC, $digest = array(), $entityBody = null)
  617. {
  618. if (!defined('self::AUTH_' . strtoupper($type))) {
  619. throw new Exception\InvalidArgumentException("Invalid or not supported authentication type: '$type'");
  620. }
  621. $response = false;
  622. switch (strtolower($type)) {
  623. case self::AUTH_BASIC :
  624. // In basic authentication, the user name cannot contain ":"
  625. if (strpos($user, ':') !== false) {
  626. throw new Exception\InvalidArgumentException("The user name cannot contain ':' in Basic HTTP authentication");
  627. }
  628. $response = base64_encode($user . ':' . $password);
  629. break;
  630. case self::AUTH_DIGEST :
  631. if (empty($digest)) {
  632. throw new Exception\InvalidArgumentException("The digest cannot be empty");
  633. }
  634. foreach ($digest as $key => $value) {
  635. if (!defined('self::DIGEST_' . strtoupper($key))) {
  636. throw new Exception\InvalidArgumentException("Invalid or not supported digest authentication parameter: '$key'");
  637. }
  638. }
  639. $ha1 = md5($user . ':' . $digest['realm'] . ':' . $password);
  640. if (empty($digest['qop']) || strtolower($digest['qop']) == 'auth') {
  641. $ha2 = md5($this->getMethod() . ':' . $this->getUri()->getPath());
  642. } elseif (strtolower($digest['qop']) == 'auth-int') {
  643. if (empty($entityBody)) {
  644. throw new Exception\InvalidArgumentException("I cannot use the auth-int digest authentication without the entity body");
  645. }
  646. $ha2 = md5($this->getMethod() . ':' . $this->getUri()->getPath() . ':' . md5($entityBody));
  647. }
  648. if (empty($digest['qop'])) {
  649. $response = md5($ha1 . ':' . $digest['nonce'] . ':' . $ha2);
  650. } else {
  651. $response = md5($ha1 . ':' . $digest['nonce'] . ':' . $digest['nc']
  652. . ':' . $digest['cnonce'] . ':' . $digest['qoc'] . ':' . $ha2);
  653. }
  654. break;
  655. }
  656. return $response;
  657. }
  658. /**
  659. * Reset all the HTTP parameters (auth,cookies,request, response, etc)
  660. *
  661. * @param bool $clearCookies Also clear all valid cookies? (defaults to false)
  662. * @return Client
  663. */
  664. public function resetParameters($clearCookies = false)
  665. {
  666. $uri = $this->getUri();
  667. $this->auth = null;
  668. $this->streamName = null;
  669. $this->encType = null;
  670. $this->request = null;
  671. $this->response = null;
  672. $this->setUri($uri);
  673. if ($clearCookies) {
  674. $this->clearCookies();
  675. }
  676. return $this;
  677. }
  678. /**
  679. * Dispatch
  680. *
  681. * @param Stdlib\RequestInterface $request
  682. * @param Stdlib\ResponseInterface $response
  683. * @return Stdlib\ResponseInterface
  684. */
  685. public function dispatch(Stdlib\RequestInterface $request, Stdlib\ResponseInterface $response = null)
  686. {
  687. $response = $this->send($request);
  688. return $response;
  689. }
  690. /**
  691. * Send HTTP request
  692. *
  693. * @param Request $request
  694. * @return Response
  695. * @throws Exception\RuntimeException
  696. * @throws Client\Exception\RuntimeException
  697. */
  698. public function send(Request $request = null)
  699. {
  700. if ($request !== null) {
  701. $this->setRequest($request);
  702. }
  703. $this->redirectCounter = 0;
  704. $response = null;
  705. // Make sure the adapter is loaded
  706. if ($this->adapter == null) {
  707. $this->setAdapter($this->config['adapter']);
  708. }
  709. // Send the first request. If redirected, continue.
  710. do {
  711. // uri
  712. $uri = $this->getUri();
  713. // query
  714. $query = $this->getRequest()->getQuery();
  715. if (!empty($query)) {
  716. $queryArray = $query->toArray();
  717. if (!empty($queryArray)) {
  718. $newUri = $uri->toString();
  719. $queryString = http_build_query($query, null, $this->getArgSeparator());
  720. if ($this->config['rfc3986strict']) {
  721. $queryString = str_replace('+', '%20', $queryString);
  722. }
  723. if (strpos($newUri, '?') !== false) {
  724. $newUri .= $this->getArgSeparator() . $queryString;
  725. } else {
  726. $newUri .= '?' . $queryString;
  727. }
  728. $uri = new Http($newUri);
  729. }
  730. }
  731. // If we have no ports, set the defaults
  732. if (!$uri->getPort()) {
  733. $uri->setPort($uri->getScheme() == 'https' ? 443 : 80);
  734. }
  735. // method
  736. $method = $this->getRequest()->getMethod();
  737. // body
  738. $body = $this->prepareBody();
  739. // headers
  740. $headers = $this->prepareHeaders($body, $uri);
  741. $secure = $uri->getScheme() == 'https';
  742. // cookies
  743. $cookie = $this->prepareCookies($uri->getHost(), $uri->getPath(), $secure);
  744. if ($cookie->getFieldValue()) {
  745. $headers['Cookie'] = $cookie->getFieldValue();
  746. }
  747. // check that adapter supports streaming before using it
  748. if (is_resource($body) && !($this->adapter instanceof Client\Adapter\StreamInterface)) {
  749. throw new Client\Exception\RuntimeException('Adapter does not support streaming');
  750. }
  751. // calling protected method to allow extending classes
  752. // to wrap the interaction with the adapter
  753. $response = $this->doRequest($uri, $method, $secure, $headers, $body);
  754. if (! $response) {
  755. throw new Exception\RuntimeException('Unable to read response, or response is empty');
  756. }
  757. if ($this->config['storeresponse']) {
  758. $this->lastRawResponse = $response;
  759. } else {
  760. $this->lastRawResponse = null;
  761. }
  762. if ($this->config['outputstream']) {
  763. $stream = $this->getStream();
  764. if (!is_resource($stream) && is_string($stream)) {
  765. $stream = fopen($stream, 'r');
  766. }
  767. $streamMetaData = stream_get_meta_data($stream);
  768. if ($streamMetaData['seekable']) {
  769. rewind($stream);
  770. }
  771. // cleanup the adapter
  772. $this->adapter->setOutputStream(null);
  773. $response = Response\Stream::fromStream($response, $stream);
  774. $response->setStreamName($this->streamName);
  775. if (!is_string($this->config['outputstream'])) {
  776. // we used temp name, will need to clean up
  777. $response->setCleanup(true);
  778. }
  779. } else {
  780. $response = Response::fromString($response);
  781. }
  782. // Get the cookies from response (if any)
  783. $setCookies = $response->getCookie();
  784. if (!empty($setCookies)) {
  785. $this->addCookie($setCookies);
  786. }
  787. // If we got redirected, look for the Location header
  788. if ($response->isRedirect() && ($response->getHeaders()->has('Location'))) {
  789. // Avoid problems with buggy servers that add whitespace at the
  790. // end of some headers
  791. $location = trim($response->getHeaders()->get('Location')->getFieldValue());
  792. // Check whether we send the exact same request again, or drop the parameters
  793. // and send a GET request
  794. if ($response->getStatusCode() == 303 ||
  795. ((! $this->config['strictredirects']) && ($response->getStatusCode() == 302 ||
  796. $response->getStatusCode() == 301))) {
  797. $this->resetParameters();
  798. $this->setMethod(Request::METHOD_GET);
  799. }
  800. // If we got a well formed absolute URI
  801. if (($scheme = substr($location, 0, 6)) &&
  802. ($scheme == 'http:/' || $scheme == 'https:')) {
  803. $this->setUri($location);
  804. } else {
  805. // Split into path and query and set the query
  806. if (strpos($location, '?') !== false) {
  807. list($location, $query) = explode('?', $location, 2);
  808. } else {
  809. $query = '';
  810. }
  811. $this->getUri()->setQuery($query);
  812. // Else, if we got just an absolute path, set it
  813. if (strpos($location, '/') === 0) {
  814. $this->getUri()->setPath($location);
  815. // Else, assume we have a relative path
  816. } else {
  817. // Get the current path directory, removing any trailing slashes
  818. $path = $this->getUri()->getPath();
  819. $path = rtrim(substr($path, 0, strrpos($path, '/')), "/");
  820. $this->getUri()->setPath($path . '/' . $location);
  821. }
  822. }
  823. ++$this->redirectCounter;
  824. } else {
  825. // If we didn't get any location, stop redirecting
  826. break;
  827. }
  828. } while ($this->redirectCounter < $this->config['maxredirects']);
  829. $this->response = $response;
  830. return $response;
  831. }
  832. /**
  833. * Set a file to upload (using a POST request)
  834. *
  835. * Can be used in two ways:
  836. *
  837. * 1. $data is null (default): $filename is treated as the name if a local file which
  838. * will be read and sent. Will try to guess the content type using mime_content_type().
  839. * 2. $data is set - $filename is sent as the file name, but $data is sent as the file
  840. * contents and no file is read from the file system. In this case, you need to
  841. * manually set the Content-Type ($ctype) or it will default to
  842. * application/octet-stream.
  843. *
  844. * @param string $filename Name of file to upload, or name to save as
  845. * @param string $formname Name of form element to send as
  846. * @param string $data Data to send (if null, $filename is read and sent)
  847. * @param string $ctype Content type to use (if $data is set and $ctype is
  848. * null, will be application/octet-stream)
  849. * @return Client
  850. * @throws Exception\RuntimeException
  851. */
  852. public function setFileUpload($filename, $formname, $data = null, $ctype = null)
  853. {
  854. if ($data === null) {
  855. ErrorHandler::start();
  856. $data = file_get_contents($filename);
  857. $error = ErrorHandler::stop();
  858. if ($data === false) {
  859. throw new Exception\RuntimeException("Unable to read file '{$filename}' for upload", 0, $error);
  860. }
  861. if (!$ctype) {
  862. $ctype = $this->detectFileMimeType($filename);
  863. }
  864. }
  865. $this->getRequest()->getFiles()->set($filename, array(
  866. 'formname' => $formname,
  867. 'filename' => basename($filename),
  868. 'ctype' => $ctype,
  869. 'data' => $data
  870. ));
  871. return $this;
  872. }
  873. /**
  874. * Remove a file to upload
  875. *
  876. * @param string $filename
  877. * @return bool
  878. */
  879. public function removeFileUpload($filename)
  880. {
  881. $file = $this->getRequest()->getFiles()->get($filename);
  882. if (!empty($file)) {
  883. $this->getRequest()->getFiles()->set($filename, null);
  884. return true;
  885. }
  886. return false;
  887. }
  888. /**
  889. * Prepare Cookies
  890. *
  891. * @param string $domain
  892. * @param string $path
  893. * @param boolean $secure
  894. * @return Header\Cookie|bool
  895. */
  896. protected function prepareCookies($domain, $path, $secure)
  897. {
  898. $validCookies = array();
  899. if (!empty($this->cookies)) {
  900. foreach ($this->cookies as $id => $cookie) {
  901. if ($cookie->isExpired()) {
  902. unset($this->cookies[$id]);
  903. continue;
  904. }
  905. if ($cookie->isValidForRequest($domain, $path, $secure)) {
  906. // OAM hack some domains try to set the cookie multiple times
  907. $validCookies[$cookie->getName()] = $cookie;
  908. }
  909. }
  910. }
  911. $cookies = Header\Cookie::fromSetCookieArray($validCookies);
  912. $cookies->setEncodeValue($this->config['encodecookies']);
  913. return $cookies;
  914. }
  915. /**
  916. * Prepare the request headers
  917. *
  918. * @param resource|string $body
  919. * @param Http $uri
  920. * @throws Exception\RuntimeException
  921. * @return array
  922. */
  923. protected function prepareHeaders($body, $uri)
  924. {
  925. $headers = array();
  926. // Set the host header
  927. if ($this->config['httpversion'] == Request::VERSION_11) {
  928. $host = $uri->getHost();
  929. // If the port is not default, add it
  930. if (!(($uri->getScheme() == 'http' && $uri->getPort() == 80) ||
  931. ($uri->getScheme() == 'https' && $uri->getPort() == 443))) {
  932. $host .= ':' . $uri->getPort();
  933. }
  934. $headers['Host'] = $host;
  935. }
  936. // Set the connection header
  937. if (!$this->getRequest()->getHeaders()->has('Connection')) {
  938. if (!$this->config['keepalive']) {
  939. $headers['Connection'] = 'close';
  940. }
  941. }
  942. // Set the Accept-encoding header if not set - depending on whether
  943. // zlib is available or not.
  944. if (!$this->getRequest()->getHeaders()->has('Accept-Encoding')) {
  945. if (function_exists('gzinflate')) {
  946. $headers['Accept-Encoding'] = 'gzip, deflate';
  947. } else {
  948. $headers['Accept-Encoding'] = 'identity';
  949. }
  950. }
  951. // Set the user agent header
  952. if (!$this->getRequest()->getHeaders()->has('User-Agent') && isset($this->config['useragent'])) {
  953. $headers['User-Agent'] = $this->config['useragent'];
  954. }
  955. // Set HTTP authentication if needed
  956. if (!empty($this->auth)) {
  957. switch ($this->auth['type']) {
  958. case self::AUTH_BASIC :
  959. $auth = $this->calcAuthDigest($this->auth['user'], $this->auth['password'], $this->auth['type']);
  960. if ($auth !== false) {
  961. $headers['Authorization'] = 'Basic ' . $auth;
  962. }
  963. break;
  964. case self::AUTH_DIGEST :
  965. throw new Exception\RuntimeException("The digest authentication is not implemented yet");
  966. }
  967. }
  968. // Content-type
  969. $encType = $this->getEncType();
  970. if (!empty($encType)) {
  971. $headers['Content-Type'] = $encType;
  972. }
  973. if (!empty($body)) {
  974. if (is_resource($body)) {
  975. $fstat = fstat($body);
  976. $headers['Content-Length'] = $fstat['size'];
  977. } else {
  978. $headers['Content-Length'] = strlen($body);
  979. }
  980. }
  981. // Merge the headers of the request (if any)
  982. $requestHeaders = $this->getRequest()->getHeaders()->toArray();
  983. foreach ($requestHeaders as $key => $value) {
  984. $headers[$key] = $value;
  985. }
  986. return $headers;
  987. }
  988. /**
  989. * Prepare the request body (for PATCH, POST and PUT requests)
  990. *
  991. * @return string
  992. * @throws \Zend\Http\Client\Exception\RuntimeException
  993. */
  994. protected function prepareBody()
  995. {
  996. // According to RFC2616, a TRACE request should not have a body.
  997. if ($this->getRequest()->isTrace()) {
  998. return '';
  999. }
  1000. $rawBody = $this->getRequest()->getContent();
  1001. if (!empty($rawBody)) {
  1002. return $rawBody;
  1003. }
  1004. $body = '';
  1005. $totalFiles = 0;
  1006. if (!$this->getRequest()->getHeaders()->has('Content-Type')) {
  1007. $totalFiles = count($this->getRequest()->getFiles()->toArray());
  1008. // If we have files to upload, force encType to multipart/form-data
  1009. if ($totalFiles > 0) {
  1010. $this->setEncType(self::ENC_FORMDATA);
  1011. }
  1012. } else {
  1013. $this->setEncType($this->getHeader('Content-Type'));
  1014. }
  1015. // If we have POST parameters or files, encode and add them to the body
  1016. if (count($this->getRequest()->getPost()->toArray()) > 0 || $totalFiles > 0) {
  1017. if (stripos($this->getEncType(), self::ENC_FORMDATA) === 0) {
  1018. $boundary = '---ZENDHTTPCLIENT-' . md5(microtime());
  1019. $this->setEncType(self::ENC_FORMDATA, $boundary);
  1020. // Get POST parameters and encode them
  1021. $params = self::flattenParametersArray($this->getRequest()->getPost()->toArray());
  1022. foreach ($params as $pp) {
  1023. $body .= $this->encodeFormData($boundary, $pp[0], $pp[1]);
  1024. }
  1025. // Encode files
  1026. foreach ($this->getRequest()->getFiles()->toArray() as $file) {
  1027. $fhead = array('Content-Type' => $file['ctype']);
  1028. $body .= $this->encodeFormData($boundary, $file['formname'], $file['data'], $file['filename'], $fhead);
  1029. }
  1030. $body .= "--{$boundary}--\r\n";
  1031. } elseif (stripos($this->getEncType(), self::ENC_URLENCODED) === 0) {
  1032. // Encode body as application/x-www-form-urlencoded
  1033. $body = http_build_query($this->getRequest()->getPost()->toArray());
  1034. } else {
  1035. throw new Client\Exception\RuntimeException("Cannot handle content type '{$this->encType}' automatically");
  1036. }
  1037. }
  1038. return $body;
  1039. }
  1040. /**
  1041. * Attempt to detect the MIME type of a file using available extensions
  1042. *
  1043. * This method will try to detect the MIME type of a file. If the fileinfo
  1044. * extension is available, it will be used. If not, the mime_magic
  1045. * extension which is deprecated but is still available in many PHP setups
  1046. * will be tried.
  1047. *
  1048. * If neither extension is available, the default application/octet-stream
  1049. * MIME type will be returned
  1050. *
  1051. * @param string $file File path
  1052. * @return string MIME type
  1053. */
  1054. protected function detectFileMimeType($file)
  1055. {
  1056. $type = null;
  1057. // First try with fileinfo functions
  1058. if (function_exists('finfo_open')) {
  1059. if (static::$fileInfoDb === null) {
  1060. ErrorHandler::start();
  1061. static::$fileInfoDb = finfo_open(FILEINFO_MIME);
  1062. ErrorHandler::stop();
  1063. }
  1064. if (static::$fileInfoDb) {
  1065. $type = finfo_file(static::$fileInfoDb, $file);
  1066. }
  1067. } elseif (function_exists('mime_content_type')) {
  1068. $type = mime_content_type($file);
  1069. }
  1070. // Fallback to the default application/octet-stream
  1071. if (! $type) {
  1072. $type = 'application/octet-stream';
  1073. }
  1074. return $type;
  1075. }
  1076. /**
  1077. * Encode data to a multipart/form-data part suitable for a POST request.
  1078. *
  1079. * @param string $boundary
  1080. * @param string $name
  1081. * @param mixed $value
  1082. * @param string $filename
  1083. * @param array $headers Associative array of optional headers @example ("Content-Transfer-Encoding" => "binary")
  1084. * @return string
  1085. */
  1086. public function encodeFormData($boundary, $name, $value, $filename = null, $headers = array())
  1087. {
  1088. $ret = "--{$boundary}\r\n" .
  1089. 'Content-Disposition: form-data; name="' . $name . '"';
  1090. if ($filename) {
  1091. $ret .= '; filename="' . $filename . '"';
  1092. }
  1093. $ret .= "\r\n";
  1094. foreach ($headers as $hname => $hvalue) {
  1095. $ret .= "{$hname}: {$hvalue}\r\n";
  1096. }
  1097. $ret .= "\r\n";
  1098. $ret .= "{$value}\r\n";
  1099. return $ret;
  1100. }
  1101. /**
  1102. * Convert an array of parameters into a flat array of (key, value) pairs
  1103. *
  1104. * Will flatten a potentially multi-dimentional array of parameters (such
  1105. * as POST parameters) into a flat array of (key, value) paris. In case
  1106. * of multi-dimentional arrays, square brackets ([]) will be added to the
  1107. * key to indicate an array.
  1108. *
  1109. * @since 1.9
  1110. *
  1111. * @param array $parray
  1112. * @param string $prefix
  1113. * @return array
  1114. */
  1115. protected function flattenParametersArray($parray, $prefix = null)
  1116. {
  1117. if (!is_array($parray)) {
  1118. return $parray;
  1119. }
  1120. $parameters = array();
  1121. foreach ($parray as $name => $value) {
  1122. // Calculate array key
  1123. if ($prefix) {
  1124. if (is_int($name)) {
  1125. $key = $prefix . '[]';
  1126. } else {
  1127. $key = $prefix . "[$name]";
  1128. }
  1129. } else {
  1130. $key = $name;
  1131. }
  1132. if (is_array($value)) {
  1133. $parameters = array_merge($parameters, $this->flattenParametersArray($value, $key));
  1134. } else {
  1135. $parameters[] = array($key, $value);
  1136. }
  1137. }
  1138. return $parameters;
  1139. }
  1140. /**
  1141. * Separating this from send method allows subclasses to wrap
  1142. * the interaction with the adapter
  1143. *
  1144. * @param Http $uri
  1145. * @param string $method
  1146. * @param bool $secure
  1147. * @param array $headers
  1148. * @param string $body
  1149. * @return string the raw response
  1150. * @throws Exception\RuntimeException
  1151. */
  1152. protected function doRequest(Http $uri, $method, $secure = false, $headers = array(), $body = '')
  1153. {
  1154. // Open the connection, send the request and read the response
  1155. $this->adapter->connect($uri->getHost(), $uri->getPort(), $secure);
  1156. if ($this->config['outputstream']) {
  1157. if ($this->adapter instanceof Client\Adapter\StreamInterface) {
  1158. $stream = $this->openTempStream();
  1159. $this->adapter->setOutputStream($stream);
  1160. } else {
  1161. throw new Exception\RuntimeException('Adapter does not support streaming');
  1162. }
  1163. }
  1164. // HTTP connection
  1165. $this->lastRawRequest = $this->adapter->write($method,
  1166. $uri, $this->config['httpversion'], $headers, $body);
  1167. return $this->adapter->read();
  1168. }
  1169. /**
  1170. * Create a HTTP authentication "Authorization:" header according to the
  1171. * specified user, password and authentication method.
  1172. *
  1173. * @see http://www.faqs.org/rfcs/rfc2617.html
  1174. * @param string $user
  1175. * @param string $password
  1176. * @param string $type
  1177. * @return string
  1178. * @throws Zend\Http\Client\Exception\InvalidArgumentException
  1179. */
  1180. public static function encodeAuthHeader($user, $password, $type = self::AUTH_BASIC)
  1181. {
  1182. $authHeader = null;
  1183. switch ($type) {
  1184. case self::AUTH_BASIC:
  1185. // In basic authentication, the user name cannot contain ":"
  1186. if (strpos($user, ':') !== false) {
  1187. throw new Client\Exception\InvalidArgumentException("The user name cannot contain ':' in 'Basic' HTTP authentication");
  1188. }
  1189. $authHeader = 'Basic ' . base64_encode($user . ':' . $password);
  1190. break;
  1191. //case self::AUTH_DIGEST:
  1192. /**
  1193. * @todo Implement digest authentication
  1194. */
  1195. // break;
  1196. default:
  1197. throw new Client\Exception\InvalidArgumentException("Not a supported HTTP authentication type: '$type'");
  1198. }
  1199. return $authHeader;
  1200. }
  1201. }