Open Journal Systems  3.3.0
CurlHandle.php
1 <?php
2 
3 namespace Guzzle\Http\Curl;
4 
11 use Guzzle\Http\Url;
12 
17 {
18  const BODY_AS_STRING = 'body_as_string';
19  const PROGRESS = 'progress';
20  const DEBUG = 'debug';
21 
23  protected $options;
24 
26  protected $handle;
27 
29  protected $errorNo = CURLE_OK;
30 
43  public static function factory(RequestInterface $request)
44  {
45  $requestCurlOptions = $request->getCurlOptions();
46  $mediator = new RequestMediator($request, $requestCurlOptions->get('emit_io'));
47  $tempContentLength = null;
48  $method = $request->getMethod();
49  $bodyAsString = $requestCurlOptions->get(self::BODY_AS_STRING);
50 
51  // Prepare url
52  $url = (string)$request->getUrl();
53  if(($pos = strpos($url, '#')) !== false ){
54  // strip fragment from url
55  $url = substr($url, 0, $pos);
56  }
57 
58  // Array of default cURL options.
59  $curlOptions = array(
60  CURLOPT_URL => $url,
61  CURLOPT_CONNECTTIMEOUT => 150,
62  CURLOPT_RETURNTRANSFER => false,
63  CURLOPT_HEADER => false,
64  CURLOPT_PORT => $request->getPort(),
65  CURLOPT_HTTPHEADER => array(),
66  CURLOPT_WRITEFUNCTION => array($mediator, 'writeResponseBody'),
67  CURLOPT_HEADERFUNCTION => array($mediator, 'receiveResponseHeader'),
68  CURLOPT_HTTP_VERSION => $request->getProtocolVersion() === '1.0'
69  ? CURL_HTTP_VERSION_1_0 : CURL_HTTP_VERSION_1_1,
70  // Verifies the authenticity of the peer's certificate
71  CURLOPT_SSL_VERIFYPEER => 1,
72  // Certificate must indicate that the server is the server to which you meant to connect
73  CURLOPT_SSL_VERIFYHOST => 2
74  );
75 
76  if (defined('CURLOPT_PROTOCOLS')) {
77  // Allow only HTTP and HTTPS protocols
78  $curlOptions[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS;
79  }
80 
81  // Add CURLOPT_ENCODING if Accept-Encoding header is provided
82  if ($acceptEncodingHeader = $request->getHeader('Accept-Encoding')) {
83  $curlOptions[CURLOPT_ENCODING] = (string) $acceptEncodingHeader;
84  // Let cURL set the Accept-Encoding header, prevents duplicate values
85  $request->removeHeader('Accept-Encoding');
86  }
87 
88  // Enable curl debug information if the 'debug' param was set
89  if ($requestCurlOptions->get('debug')) {
90  $curlOptions[CURLOPT_STDERR] = fopen('php://temp', 'r+');
91  // @codeCoverageIgnoreStart
92  if (false === $curlOptions[CURLOPT_STDERR]) {
93  throw new RuntimeException('Unable to create a stream for CURLOPT_STDERR');
94  }
95  // @codeCoverageIgnoreEnd
96  $curlOptions[CURLOPT_VERBOSE] = true;
97  }
98 
99  // Specify settings according to the HTTP method
100  if ($method == 'GET') {
101  $curlOptions[CURLOPT_HTTPGET] = true;
102  } elseif ($method == 'HEAD') {
103  $curlOptions[CURLOPT_NOBODY] = true;
104  // HEAD requests do not use a write function
105  unset($curlOptions[CURLOPT_WRITEFUNCTION]);
106  } elseif (!($request instanceof EntityEnclosingRequest)) {
107  $curlOptions[CURLOPT_CUSTOMREQUEST] = $method;
108  } else {
109 
110  $curlOptions[CURLOPT_CUSTOMREQUEST] = $method;
111 
112  // Handle sending raw bodies in a request
113  if ($request->getBody()) {
114  // You can send the body as a string using curl's CURLOPT_POSTFIELDS
115  if ($bodyAsString) {
116  $curlOptions[CURLOPT_POSTFIELDS] = (string) $request->getBody();
117  // Allow curl to add the Content-Length for us to account for the times when
118  // POST redirects are followed by GET requests
119  if ($tempContentLength = $request->getHeader('Content-Length')) {
120  $tempContentLength = (int) (string) $tempContentLength;
121  }
122  // Remove the curl generated Content-Type header if none was set manually
123  if (!$request->hasHeader('Content-Type')) {
124  $curlOptions[CURLOPT_HTTPHEADER][] = 'Content-Type:';
125  }
126  } else {
127  $curlOptions[CURLOPT_UPLOAD] = true;
128  // Let cURL handle setting the Content-Length header
129  if ($tempContentLength = $request->getHeader('Content-Length')) {
130  $tempContentLength = (int) (string) $tempContentLength;
131  $curlOptions[CURLOPT_INFILESIZE] = $tempContentLength;
132  }
133  // Add a callback for curl to read data to send with the request only if a body was specified
134  $curlOptions[CURLOPT_READFUNCTION] = array($mediator, 'readRequestBody');
135  // Attempt to seek to the start of the stream
136  $request->getBody()->seek(0);
137  }
138 
139  } else {
140 
141  // Special handling for POST specific fields and files
142  $postFields = false;
143  if (count($request->getPostFiles())) {
144  $postFields = $request->getPostFields()->useUrlEncoding(false)->urlEncode();
145  foreach ($request->getPostFiles() as $key => $data) {
146  $prefixKeys = count($data) > 1;
147  foreach ($data as $index => $file) {
148  // Allow multiple files in the same key
149  $fieldKey = $prefixKeys ? "{$key}[{$index}]" : $key;
150  $postFields[$fieldKey] = $file->getCurlValue();
151  }
152  }
153  } elseif (count($request->getPostFields())) {
154  $postFields = (string) $request->getPostFields()->useUrlEncoding(true);
155  }
156 
157  if ($postFields !== false) {
158  if ($method == 'POST') {
159  unset($curlOptions[CURLOPT_CUSTOMREQUEST]);
160  $curlOptions[CURLOPT_POST] = true;
161  }
162  $curlOptions[CURLOPT_POSTFIELDS] = $postFields;
163  $request->removeHeader('Content-Length');
164  }
165  }
166 
167  // If the Expect header is not present, prevent curl from adding it
168  if (!$request->hasHeader('Expect')) {
169  $curlOptions[CURLOPT_HTTPHEADER][] = 'Expect:';
170  }
171  }
172 
173  // If a Content-Length header was specified but we want to allow curl to set one for us
174  if (null !== $tempContentLength) {
175  $request->removeHeader('Content-Length');
176  }
177 
178  // Set custom cURL options
179  foreach ($requestCurlOptions->toArray() as $key => $value) {
180  if (is_numeric($key)) {
181  $curlOptions[$key] = $value;
182  }
183  }
184 
185  // Do not set an Accept header by default
186  if (!isset($curlOptions[CURLOPT_ENCODING])) {
187  $curlOptions[CURLOPT_HTTPHEADER][] = 'Accept:';
188  }
189 
190  // Add any custom headers to the request. Empty headers will cause curl to not send the header at all.
191  foreach ($request->getHeaderLines() as $line) {
192  $curlOptions[CURLOPT_HTTPHEADER][] = $line;
193  }
194 
195  // Add the content-length header back if it was temporarily removed
196  if (null !== $tempContentLength) {
197  $request->setHeader('Content-Length', $tempContentLength);
198  }
199 
200  // Apply the options to a new cURL handle.
201  $handle = curl_init();
202 
203  // Enable the progress function if the 'progress' param was set
204  if ($requestCurlOptions->get('progress')) {
205  // Wrap the function in a function that provides the curl handle to the mediator's progress function
206  // Using this rather than injecting the handle into the mediator prevents a circular reference
207  $curlOptions[CURLOPT_PROGRESSFUNCTION] = function () use ($mediator, $handle) {
208  $args = func_get_args();
209  $args[] = $handle;
210 
211  // PHP 5.5 pushed the handle onto the start of the args
212  if (is_resource($args[0])) {
213  array_shift($args);
214  }
215 
216  call_user_func_array(array($mediator, 'progress'), $args);
217  };
218  $curlOptions[CURLOPT_NOPROGRESS] = false;
219  }
220 
221  curl_setopt_array($handle, $curlOptions);
222 
223  return new static($handle, $curlOptions);
224  }
225 
234  public function __construct($handle, $options)
235  {
236  if (!is_resource($handle)) {
237  throw new InvalidArgumentException('Invalid handle provided');
238  }
239  if (is_array($options)) {
240  $this->options = new Collection($options);
241  } elseif ($options instanceof Collection) {
242  $this->options = $options;
243  } else {
244  throw new InvalidArgumentException('Expected array or Collection');
245  }
246  $this->handle = $handle;
247  }
248 
252  public function __destruct()
253  {
254  $this->close();
255  }
256 
260  public function close()
261  {
262  if (is_resource($this->handle)) {
263  curl_close($this->handle);
264  }
265  $this->handle = null;
266  }
267 
273  public function isAvailable()
274  {
275  return is_resource($this->handle);
276  }
277 
283  public function getError()
284  {
285  return $this->isAvailable() ? curl_error($this->handle) : '';
286  }
287 
293  public function getErrorNo()
294  {
295  if ($this->errorNo) {
296  return $this->errorNo;
297  }
298 
299  return $this->isAvailable() ? curl_errno($this->handle) : CURLE_OK;
300  }
301 
309  public function setErrorNo($error)
310  {
311  $this->errorNo = $error;
312 
313  return $this;
314  }
315 
323  public function getInfo($option = null)
324  {
325  if (!is_resource($this->handle)) {
326  return null;
327  }
328 
329  if (null !== $option) {
330  return curl_getinfo($this->handle, $option) ?: null;
331  }
332 
333  return curl_getinfo($this->handle) ?: array();
334  }
335 
343  public function getStderr($asResource = false)
344  {
345  $stderr = $this->getOptions()->get(CURLOPT_STDERR);
346  if (!$stderr) {
347  return null;
348  }
349 
350  if ($asResource) {
351  return $stderr;
352  }
353 
354  fseek($stderr, 0);
355  $e = stream_get_contents($stderr);
356  fseek($stderr, 0, SEEK_END);
357 
358  return $e;
359  }
360 
366  public function getUrl()
367  {
368  return Url::factory($this->options->get(CURLOPT_URL));
369  }
370 
376  public function getHandle()
377  {
378  return $this->isAvailable() ? $this->handle : null;
379  }
380 
387  public function getOptions()
388  {
389  return $this->options;
390  }
391 
397  public function updateRequestFromTransfer(RequestInterface $request)
398  {
399  if (!$request->getResponse()) {
400  return;
401  }
402 
403  // Update the transfer stats of the response
404  $request->getResponse()->setInfo($this->getInfo());
405 
406  if (!$log = $this->getStderr(true)) {
407  return;
408  }
409 
410  // Parse the cURL stderr output for outgoing requests
411  $headers = '';
412  fseek($log, 0);
413  while (($line = fgets($log)) !== false) {
414  if ($line && $line[0] == '>') {
415  $headers = substr(trim($line), 2) . "\r\n";
416  while (($line = fgets($log)) !== false) {
417  if ($line[0] == '*' || $line[0] == '<') {
418  break;
419  } else {
420  $headers .= trim($line) . "\r\n";
421  }
422  }
423  }
424  }
425 
426  // Add request headers to the request exactly as they were sent
427  if ($headers) {
428  $parsed = ParserRegistry::getInstance()->getParser('message')->parseRequest($headers);
429  if (!empty($parsed['headers'])) {
430  $request->setHeaders(array());
431  foreach ($parsed['headers'] as $name => $value) {
432  $request->setHeader($name, $value);
433  }
434  }
435  if (!empty($parsed['version'])) {
436  $request->setProtocolVersion($parsed['version']);
437  }
438  }
439  }
440 
448  public static function parseCurlConfig($config)
449  {
450  $curlOptions = array();
451  foreach ($config as $key => $value) {
452  if (is_string($key) && defined($key)) {
453  // Convert constants represented as string to constant int values
454  $key = constant($key);
455  }
456  if (is_string($value) && defined($value)) {
457  $value = constant($value);
458  }
459  $curlOptions[$key] = $value;
460  }
461 
462  return $curlOptions;
463  }
464 }
Guzzle\Http\Message\RequestInterface\getResponse
getResponse()
Guzzle\Http\Message\RequestInterface\getProtocolVersion
getProtocolVersion()
Guzzle\Parser\ParserRegistry
Definition: ParserRegistry.php:8
Guzzle\Http\Message\RequestInterface
Definition: lib/vendor/guzzle/guzzle/src/Guzzle/Http/Message/RequestInterface.php:16
Guzzle\Http\Message\MessageInterface\setHeader
setHeader($header, $value)
Guzzle\Http\Curl\CurlHandle\getHandle
getHandle()
Definition: CurlHandle.php:385
Guzzle\Http\Message\RequestInterface\getUrl
getUrl($asObject=false)
Guzzle\Http\Curl\CurlHandle\getErrorNo
getErrorNo()
Definition: CurlHandle.php:302
Guzzle\Http\Curl\CurlHandle\getError
getError()
Definition: CurlHandle.php:292
Guzzle\Http\Curl
Definition: CurlHandle.php:3
Guzzle\Http\Message\RequestInterface\getCurlOptions
getCurlOptions()
Guzzle\Http\Curl\CurlHandle\$errorNo
$errorNo
Definition: CurlHandle.php:38
Guzzle\Http\Curl\CurlHandle\BODY_AS_STRING
const BODY_AS_STRING
Definition: CurlHandle.php:18
Guzzle\Http\Curl\CurlHandle\getStderr
getStderr($asResource=false)
Definition: CurlHandle.php:352
Guzzle\Http\Curl\CurlHandle\parseCurlConfig
static parseCurlConfig($config)
Definition: CurlHandle.php:457
Guzzle\Http\Curl\CurlHandle\DEBUG
const DEBUG
Definition: CurlHandle.php:20
Guzzle\Parser\ParserRegistry\getInstance
static getInstance()
Definition: ParserRegistry.php:34
Guzzle\Http\Curl\CurlHandle\getInfo
getInfo($option=null)
Definition: CurlHandle.php:332
Guzzle\Http\Curl\CurlHandle\$options
$options
Definition: CurlHandle.php:26
Guzzle\Http\Message\MessageInterface\removeHeader
removeHeader($header)
Guzzle\Http\Url
Definition: Url.php:10
Guzzle\Http\Curl\CurlHandle\__construct
__construct($handle, $options)
Definition: CurlHandle.php:243
Guzzle\Http\Curl\CurlHandle\isAvailable
isAvailable()
Definition: CurlHandle.php:282
Guzzle\Common\Exception\InvalidArgumentException
Definition: lib/vendor/guzzle/guzzle/src/Guzzle/Common/Exception/InvalidArgumentException.php:5
Guzzle\Http\Curl\CurlHandle\getUrl
getUrl()
Definition: CurlHandle.php:375
Seboettg\Collection\count
count()
Definition: ArrayListTrait.php:253
Guzzle\Http\Message\EntityEnclosingRequest
Definition: EntityEnclosingRequest.php:14
Guzzle\Http\Message\RequestInterface\getPort
getPort()
Guzzle\Http\Message\RequestInterface\getMethod
getMethod()
Guzzle\Http\Curl\CurlHandle\__destruct
__destruct()
Definition: CurlHandle.php:261
Guzzle\Http\Curl\CurlHandle\close
close()
Definition: CurlHandle.php:269
Guzzle\Http\Message\MessageInterface\getHeader
getHeader($header)
Collection
Definition: collection.php:5
Guzzle\Http\Curl\RequestMediator
Definition: RequestMediator.php:12
Guzzle\Http\Message\MessageInterface\setHeaders
setHeaders(array $headers)
Guzzle\Http\Curl\CurlHandle\updateRequestFromTransfer
updateRequestFromTransfer(RequestInterface $request)
Definition: CurlHandle.php:406
Guzzle\Http\Message\MessageInterface\hasHeader
hasHeader($header)
Guzzle\Http\Curl\CurlHandle\getOptions
getOptions()
Definition: CurlHandle.php:396
Guzzle\Http\Curl\CurlHandle
Definition: CurlHandle.php:16
Guzzle\Http\Curl\CurlHandle\PROGRESS
const PROGRESS
Definition: CurlHandle.php:19
Guzzle\Common\Exception\RuntimeException
Definition: lib/vendor/guzzle/guzzle/src/Guzzle/Common/Exception/RuntimeException.php:5
Guzzle\Http\Curl\CurlHandle\setErrorNo
setErrorNo($error)
Definition: CurlHandle.php:318
Guzzle\Http\Message\RequestInterface\setProtocolVersion
setProtocolVersion($protocol)
Guzzle\Http\Message\MessageInterface\getHeaderLines
getHeaderLines()
Guzzle\Common\Collection
Definition: paymethod/paypal/lib/vendor/guzzle/guzzle/src/Guzzle/Common/Collection.php:11
Guzzle\Http\Curl\CurlHandle\$handle
$handle
Definition: CurlHandle.php:32
Guzzle\Http\Curl\CurlHandle\factory
static factory(RequestInterface $request)
Definition: CurlHandle.php:52
Guzzle\Http\Url\factory
static factory($url)
Definition: Url.php:34