1: <?php
  2: 
  3: namespace Guzzle\Http\Curl;
  4: 
  5: use Guzzle\Common\Exception\InvalidArgumentException;
  6: use Guzzle\Common\Exception\RuntimeException;
  7: use Guzzle\Common\Collection;
  8: use Guzzle\Http\Message\EntityEnclosingRequest;
  9: use Guzzle\Http\Message\RequestInterface;
 10: use Guzzle\Parser\ParserRegistry;
 11: use Guzzle\Http\Url;
 12: 
 13:  14:  15: 
 16: class CurlHandle
 17: {
 18:     const BODY_AS_STRING = 'body_as_string';
 19:     const PROGRESS = 'progress';
 20:     const DEBUG = 'debug';
 21: 
 22:      23:  24: 
 25:     protected $options;
 26: 
 27:      28:  29: 
 30:     protected $handle;
 31: 
 32:      33:  34: 
 35:     protected $errorNo = CURLE_OK;
 36: 
 37:      38:  39:  40:  41:  42:  43:  44:  45:  46:  47:  48: 
 49:     public static function factory(RequestInterface $request)
 50:     {
 51:         $requestCurlOptions = $request->getCurlOptions();
 52:         $mediator = new RequestMediator($request, $requestCurlOptions->get('emit_io'));
 53:         $tempContentLength = null;
 54:         $method = $request->getMethod();
 55:         $bodyAsString = $requestCurlOptions->get(self::BODY_AS_STRING);
 56: 
 57:         
 58:         $curlOptions = array(
 59:             CURLOPT_URL            => $request->getUrl(),
 60:             CURLOPT_CONNECTTIMEOUT => 150,
 61:             CURLOPT_RETURNTRANSFER => false,
 62:             CURLOPT_HEADER         => false,
 63:             CURLOPT_PORT           => $request->getPort(),
 64:             CURLOPT_HTTPHEADER     => array(),
 65:             CURLOPT_WRITEFUNCTION  => array($mediator, 'writeResponseBody'),
 66:             CURLOPT_HEADERFUNCTION => array($mediator, 'receiveResponseHeader'),
 67:             CURLOPT_HTTP_VERSION   => $request->getProtocolVersion() === '1.0'
 68:                 ? CURL_HTTP_VERSION_1_0 : CURL_HTTP_VERSION_1_1,
 69:             
 70:             CURLOPT_SSL_VERIFYPEER => 1,
 71:             
 72:             CURLOPT_SSL_VERIFYHOST => 2
 73:         );
 74: 
 75:         if (defined('CURLOPT_PROTOCOLS')) {
 76:             
 77:             $curlOptions[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS;
 78:         }
 79: 
 80:         
 81:         if ($acceptEncodingHeader = $request->getHeader('Accept-Encoding')) {
 82:             $curlOptions[CURLOPT_ENCODING] = (string) $acceptEncodingHeader;
 83:             
 84:             $request->removeHeader('Accept-Encoding');
 85:         }
 86: 
 87:         
 88:         if ($requestCurlOptions->get('progress')) {
 89:             $curlOptions[CURLOPT_PROGRESSFUNCTION] = array($mediator, 'progress');
 90:             $curlOptions[CURLOPT_NOPROGRESS] = false;
 91:         }
 92: 
 93:         
 94:         if ($requestCurlOptions->get('debug')) {
 95:             $curlOptions[CURLOPT_STDERR] = fopen('php://temp', 'r+');
 96:             
 97:             if (false === $curlOptions[CURLOPT_STDERR]) {
 98:                 throw new RuntimeException('Unable to create a stream for CURLOPT_STDERR');
 99:             }
100:             
101:             $curlOptions[CURLOPT_VERBOSE] = true;
102:         }
103: 
104:         
105:         if ($method == 'GET') {
106:             $curlOptions[CURLOPT_HTTPGET] = true;
107:         } elseif ($method == 'HEAD') {
108:             $curlOptions[CURLOPT_NOBODY] = true;
109:             
110:             unset($curlOptions[CURLOPT_WRITEFUNCTION]);
111:         } elseif (!($request instanceof EntityEnclosingRequest)) {
112:             $curlOptions[CURLOPT_CUSTOMREQUEST] = $method;
113:         } else {
114: 
115:             $curlOptions[CURLOPT_CUSTOMREQUEST] = $method;
116: 
117:             
118:             if ($request->getBody()) {
119:                 
120:                 if ($bodyAsString) {
121:                     $curlOptions[CURLOPT_POSTFIELDS] = (string) $request->getBody();
122:                     
123:                     
124:                     if ($tempContentLength = $request->getHeader('Content-Length')) {
125:                         $tempContentLength = (int) (string) $tempContentLength;
126:                     }
127:                     
128:                     if (!$request->hasHeader('Content-Type')) {
129:                         $curlOptions[CURLOPT_HTTPHEADER][] = 'Content-Type:';
130:                     }
131:                 } else {
132:                     $curlOptions[CURLOPT_UPLOAD] = true;
133:                     
134:                     if ($tempContentLength = $request->getHeader('Content-Length')) {
135:                         $tempContentLength = (int) (string) $tempContentLength;
136:                         $curlOptions[CURLOPT_INFILESIZE] = $tempContentLength;
137:                     }
138:                     
139:                     $curlOptions[CURLOPT_READFUNCTION] = array($mediator, 'readRequestBody');
140:                     
141:                     $request->getBody()->seek(0);
142:                 }
143: 
144:             } else {
145: 
146:                 
147:                 $postFields = false;
148:                 if (count($request->getPostFiles())) {
149:                     $postFields = $request->getPostFields()->useUrlEncoding(false)->urlEncode();
150:                     foreach ($request->getPostFiles() as $key => $data) {
151:                         $prefixKeys = count($data) > 1;
152:                         foreach ($data as $index => $file) {
153:                             
154:                             $fieldKey = $prefixKeys ? "{$key}[{$index}]" : $key;
155:                             $postFields[$fieldKey] = $file->getCurlValue();
156:                         }
157:                     }
158:                 } elseif (count($request->getPostFields())) {
159:                     $postFields = (string) $request->getPostFields()->useUrlEncoding(true);
160:                 }
161: 
162:                 if ($postFields !== false) {
163:                     if ($method == 'POST') {
164:                         unset($curlOptions[CURLOPT_CUSTOMREQUEST]);
165:                         $curlOptions[CURLOPT_POST] = true;
166:                     }
167:                     $curlOptions[CURLOPT_POSTFIELDS] = $postFields;
168:                     $request->removeHeader('Content-Length');
169:                 }
170:             }
171: 
172:             
173:             if (!$request->hasHeader('Expect')) {
174:                 $curlOptions[CURLOPT_HTTPHEADER][] = 'Expect:';
175:             }
176:         }
177: 
178:         
179:         if (null !== $tempContentLength) {
180:             $request->removeHeader('Content-Length');
181:         }
182: 
183:         
184:         foreach ($requestCurlOptions->getAll() as $key => $value) {
185:             if (is_numeric($key)) {
186:                 $curlOptions[$key] = $value;
187:             }
188:         }
189: 
190:         
191:         if (!isset($curlOptions[CURLOPT_ENCODING])) {
192:             $curlOptions[CURLOPT_HTTPHEADER][] = 'Accept:';
193:         }
194: 
195:         
196:         if ($blacklist = $requestCurlOptions->get('blacklist')) {
197:             foreach ($blacklist as $value) {
198:                 if (strpos($value, 'header.') !== 0) {
199:                     unset($curlOptions[$value]);
200:                 } else {
201:                     
202:                     $key = substr($value, 7);
203:                     $request->removeHeader($key);
204:                     $curlOptions[CURLOPT_HTTPHEADER][] = $key . ':';
205:                 }
206:             }
207:         }
208: 
209:         
210:         foreach ($request->getHeaderLines() as $line) {
211:             $curlOptions[CURLOPT_HTTPHEADER][] = $line;
212:         }
213: 
214:         
215:         $handle = curl_init();
216:         curl_setopt_array($handle, $curlOptions);
217: 
218:         if ($tempContentLength) {
219:             $request->setHeader('Content-Length', $tempContentLength);
220:         }
221: 
222:         $handle = new static($handle, $curlOptions);
223:         $mediator->setCurlHandle($handle);
224: 
225:         return $handle;
226:     }
227: 
228:     229: 230: 231: 232: 233: 234: 235: 
236:     public function __construct($handle, $options)
237:     {
238:         if (!is_resource($handle)) {
239:             throw new InvalidArgumentException('Invalid handle provided');
240:         }
241:         if (is_array($options)) {
242:             $this->options = new Collection($options);
243:         } elseif ($options instanceof Collection) {
244:             $this->options = $options;
245:         } else {
246:             throw new InvalidArgumentException('Expected array or Collection');
247:         }
248:         $this->handle = $handle;
249:     }
250: 
251:     252: 253: 
254:     public function __destruct()
255:     {
256:         $this->close();
257:     }
258: 
259:     260: 261: 
262:     public function close()
263:     {
264:         if (is_resource($this->handle)) {
265:             curl_close($this->handle);
266:         }
267:         $this->handle = null;
268:     }
269: 
270:     271: 272: 273: 274: 
275:     public function isAvailable()
276:     {
277:         return is_resource($this->handle);
278:     }
279: 
280:     281: 282: 283: 284: 
285:     public function getError()
286:     {
287:         return $this->isAvailable() ? curl_error($this->handle) : '';
288:     }
289: 
290:     291: 292: 293: 294: 
295:     public function getErrorNo()
296:     {
297:         if ($this->errorNo) {
298:             return $this->errorNo;
299:         }
300: 
301:         return $this->isAvailable() ? curl_errno($this->handle) : CURLE_OK;
302:     }
303: 
304:     305: 306: 307: 308: 309: 310: 
311:     public function setErrorNo($error)
312:     {
313:         $this->errorNo = $error;
314: 
315:         return $this;
316:     }
317: 
318:     319: 320: 321: 322: 323: 324: 
325:     public function getInfo($option = null)
326:     {
327:         if (!is_resource($this->handle)) {
328:             return null;
329:         }
330: 
331:         if (null !== $option) {
332:             return curl_getinfo($this->handle, $option) ?: null;
333:         }
334: 
335:         return curl_getinfo($this->handle) ?: array();
336:     }
337: 
338:     339: 340: 341: 342: 343: 344: 
345:     public function getStderr($asResource = false)
346:     {
347:         $stderr = $this->getOptions()->get(CURLOPT_STDERR);
348:         if (!$stderr) {
349:             return null;
350:         }
351: 
352:         if ($asResource) {
353:             return $stderr;
354:         }
355: 
356:         fseek($stderr, 0);
357:         $e = stream_get_contents($stderr);
358:         fseek($stderr, 0, SEEK_END);
359: 
360:         return $e;
361:     }
362: 
363:     364: 365: 366: 367: 
368:     public function getUrl()
369:     {
370:         return Url::factory($this->options->get(CURLOPT_URL));
371:     }
372: 
373:     374: 375: 376: 377: 
378:     public function getHandle()
379:     {
380:         return $this->isAvailable() ? $this->handle : null;
381:     }
382: 
383:     384: 385: 386: 387: 388: 
389:     public function getOptions()
390:     {
391:         return $this->options;
392:     }
393: 
394:     395: 396: 397: 398: 
399:     public function updateRequestFromTransfer(RequestInterface $request)
400:     {
401:         if (!$request->getResponse()) {
402:             return;
403:         }
404: 
405:         
406:         $request->getResponse()->setInfo($this->getInfo());
407: 
408:         if (!$log = $this->getStderr(true)) {
409:             return;
410:         }
411: 
412:         
413:         $headers = '';
414:         fseek($log, 0);
415:         while (($line = fgets($log)) !== false) {
416:             if ($line && $line[0] == '>') {
417:                 $headers = substr(trim($line), 2) . "\r\n";
418:                 while (($line = fgets($log)) !== false) {
419:                     if ($line[0] == '*' || $line[0] == '<') {
420:                         break;
421:                     } else {
422:                         $headers .= trim($line) . "\r\n";
423:                     }
424:                 }
425:             }
426:         }
427: 
428:         
429:         if ($headers) {
430:             $parsed = ParserRegistry::getInstance()->getParser('message')->parseRequest($headers);
431:             if (!empty($parsed['headers'])) {
432:                 $request->setHeaders(array());
433:                 foreach ($parsed['headers'] as $name => $value) {
434:                     $request->setHeader($name, $value);
435:                 }
436:             }
437:             if (!empty($parsed['version'])) {
438:                 $request->setProtocolVersion($parsed['version']);
439:             }
440:         }
441:     }
442: 
443:     444: 445: 446: 447: 448: 449: 
450:     public static function parseCurlConfig($config)
451:     {
452:         $curlOptions = array();
453:         foreach ($config as $key => $value) {
454:             if (!is_numeric($key) && defined($key)) {
455:                 
456:                 $key = constant($key);
457:             }
458:             if (is_string($value) && defined($value)) {
459:                 $value = constant($value);
460:             }
461:             $curlOptions[$key] = $value;
462:         }
463: 
464:         return $curlOptions;
465:     }
466: }
467: