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: