xref: /PHP-5.4/ext/curl/streams.c (revision c0d060f5)
1 /*
2    +----------------------------------------------------------------------+
3    | PHP Version 5                                                        |
4    +----------------------------------------------------------------------+
5    | Copyright (c) 1997-2014 The PHP Group                                |
6    +----------------------------------------------------------------------+
7    | This source file is subject to version 3.01 of the PHP license,      |
8    | that is bundled with this package in the file LICENSE, and is        |
9    | available through the world-wide-web at the following url:           |
10    | http://www.php.net/license/3_01.txt                                  |
11    | If you did not receive a copy of the PHP license and are unable to   |
12    | obtain it through the world-wide-web, please send a note to          |
13    | license@php.net so we can mail you a copy immediately.               |
14    +----------------------------------------------------------------------+
15    | Author: Wez Furlong <wez@thebrainroom.com>                           |
16    +----------------------------------------------------------------------+
17 */
18 
19 /* $Id$ */
20 
21 /* This file implements cURL based wrappers.
22  * NOTE: If you are implementing your own streams that are intended to
23  * work independently of wrappers, this is not a good example to follow!
24  **/
25 
26 #ifdef HAVE_CONFIG_H
27 #include "config.h"
28 #endif
29 
30 #include "php.h"
31 #include "php_memory_streams.h"
32 
33 #if HAVE_CURL
34 
35 #include <stdio.h>
36 #include <string.h>
37 
38 #ifdef PHP_WIN32
39 #include <winsock2.h>
40 #include <sys/types.h>
41 #endif
42 
43 #include <curl/curl.h>
44 #include <curl/easy.h>
45 
46 #define SMART_STR_PREALLOC 4096
47 
48 #include "ext/standard/php_smart_str.h"
49 #include "ext/standard/info.h"
50 #include "ext/standard/file.h"
51 #include "ext/standard/php_string.h"
52 #include "php_curl.h"
53 
on_data_available(char * data,size_t size,size_t nmemb,void * ctx)54 static size_t on_data_available(char *data, size_t size, size_t nmemb, void *ctx)
55 {
56 	php_stream *stream = (php_stream *) ctx;
57 	php_curl_stream *curlstream = (php_curl_stream *) stream->abstract;
58 	size_t wrote;
59 	TSRMLS_FETCH();
60 
61 	/* TODO: I'd like to deprecate this.
62 	 * This code is here because until we start getting real data, we don't know
63 	 * if we have had all of the headers
64 	 * */
65 	if (curlstream->readbuffer.writepos == 0) {
66 		zval *sym;
67 
68 		if (!EG(active_symbol_table)) {
69 			zend_rebuild_symbol_table(TSRMLS_C);
70 		}
71 		MAKE_STD_ZVAL(sym);
72 		*sym = *curlstream->headers;
73 		zval_copy_ctor(sym);
74 		ZEND_SET_SYMBOL(EG(active_symbol_table), "http_response_header", sym);
75 	}
76 
77 	php_stream_seek(curlstream->readbuffer.buf, curlstream->readbuffer.writepos, SEEK_SET);
78 	wrote = php_stream_write(curlstream->readbuffer.buf, data, size * nmemb);
79 	curlstream->readbuffer.writepos = php_stream_tell(curlstream->readbuffer.buf);
80 
81 	return wrote;
82 }
83 
84 /* cURL guarantees that headers are written as complete lines, with this function
85  * called once for each header */
on_header_available(char * data,size_t size,size_t nmemb,void * ctx)86 static size_t on_header_available(char *data, size_t size, size_t nmemb, void *ctx)
87 {
88 	size_t length = size * nmemb;
89 	zval *header;
90 	php_stream *stream = (php_stream *) ctx;
91 	php_curl_stream *curlstream = (php_curl_stream *) stream->abstract;
92 	TSRMLS_FETCH();
93 
94 	if (length < 2) {
95 		/* invalid header ? */
96 		return length;
97 	}
98 
99 	if (!(length == 2 && data[0] == '\r' && data[1] == '\n')) {
100 		MAKE_STD_ZVAL(header);
101 		Z_STRLEN_P(header) = length;
102 		Z_STRVAL_P(header) = estrndup(data, length);
103 		if (Z_STRVAL_P(header)[length-1] == '\n') {
104 			Z_STRVAL_P(header)[length-1] = '\0';
105 			Z_STRLEN_P(header)--;
106 
107 			if (Z_STRVAL_P(header)[length-2] == '\r') {
108 				Z_STRVAL_P(header)[length-2] = '\0';
109 				Z_STRLEN_P(header)--;
110 			}
111 		}
112 		Z_TYPE_P(header) = IS_STRING;
113 		zend_hash_next_index_insert(Z_ARRVAL_P(curlstream->headers), &header, sizeof(zval *), NULL);
114 
115 		/* based on the header, we might need to trigger a notification */
116 		if (!strncasecmp(data, "Location: ", 10)) {
117 			php_stream_notify_info(stream->context, PHP_STREAM_NOTIFY_REDIRECTED, data + 10, 0);
118 		} else if (!strncasecmp(data, "Content-Type: ", 14)) {
119 			php_stream_notify_info(stream->context, PHP_STREAM_NOTIFY_MIME_TYPE_IS, data + 14, 0);
120 		} else if (!strncasecmp(data, "Context-Length: ", 16)) {
121 			php_stream_notify_file_size(stream->context, atoi(data + 16), data, 0);
122 			php_stream_notify_progress_init(stream->context, 0, 0);
123 		}
124 	}
125 	return length;
126 
127 }
128 
on_progress_avail(php_stream * stream,double dltotal,double dlnow,double ultotal,double ulnow)129 static int on_progress_avail(php_stream *stream, double dltotal, double dlnow, double ultotal, double ulnow)
130 {
131 	TSRMLS_FETCH();
132 
133 	/* our notification system only works in a single direction; we should detect which
134 	 * direction is important and use the correct values in this call */
135 	php_stream_notify_progress(stream->context, (size_t) dlnow, (size_t) dltotal);
136 	return 0;
137 }
138 
php_curl_stream_write(php_stream * stream,const char * buf,size_t count TSRMLS_DC)139 static size_t php_curl_stream_write(php_stream *stream, const char *buf, size_t count TSRMLS_DC)
140 {
141 	php_curl_stream *curlstream = (php_curl_stream *) stream->abstract;
142 
143 	if (curlstream->writebuffer.buf) {
144 		return php_stream_write(curlstream->writebuffer.buf, buf, count);
145 	}
146 
147 	return 0;
148 }
149 
php_curl_stream_read(php_stream * stream,char * buf,size_t count TSRMLS_DC)150 static size_t php_curl_stream_read(php_stream *stream, char *buf, size_t count TSRMLS_DC)
151 {
152 	php_curl_stream *curlstream = (php_curl_stream *) stream->abstract;
153 	size_t didread = 0;
154 
155 	if (curlstream->readbuffer.readpos >= curlstream->readbuffer.writepos && curlstream->pending) {
156 		/* we need to read some more data */
157 		struct timeval tv;
158 
159 		/* fire up the connection */
160 		if (curlstream->readbuffer.writepos == 0) {
161 			while (CURLM_CALL_MULTI_PERFORM == curl_multi_perform(curlstream->multi, &curlstream->pending));
162 		}
163 
164 		do {
165 			FD_ZERO(&curlstream->readfds);
166 			FD_ZERO(&curlstream->writefds);
167 			FD_ZERO(&curlstream->excfds);
168 
169 			/* get the descriptors from curl */
170 			curl_multi_fdset(curlstream->multi, &curlstream->readfds, &curlstream->writefds, &curlstream->excfds, &curlstream->maxfd);
171 
172 			/* if we are in blocking mode, set a timeout */
173 			tv.tv_usec = 0;
174 			tv.tv_sec = 15; /* TODO: allow this to be configured from the script */
175 
176 			/* wait for data */
177 			switch ((curlstream->maxfd < 0) ? 1 :
178 					select(curlstream->maxfd + 1, &curlstream->readfds, &curlstream->writefds, &curlstream->excfds, &tv)) {
179 				case -1:
180 					/* error */
181 					return 0;
182 				case 0:
183 					/* no data yet: timed-out */
184 					return 0;
185 				default:
186 					/* fetch the data */
187 					do {
188 						curlstream->mcode = curl_multi_perform(curlstream->multi, &curlstream->pending);
189 					} while (curlstream->mcode == CURLM_CALL_MULTI_PERFORM);
190 			}
191 		} while (curlstream->maxfd >= 0 &&
192 				curlstream->readbuffer.readpos >= curlstream->readbuffer.writepos && curlstream->pending > 0);
193 
194 	}
195 
196 	/* if there is data in the buffer, try and read it */
197 	if (curlstream->readbuffer.writepos > 0 && curlstream->readbuffer.readpos < curlstream->readbuffer.writepos) {
198 		php_stream_seek(curlstream->readbuffer.buf, curlstream->readbuffer.readpos, SEEK_SET);
199 		didread = php_stream_read(curlstream->readbuffer.buf, buf, count);
200 		curlstream->readbuffer.readpos = php_stream_tell(curlstream->readbuffer.buf);
201 	}
202 
203 	if (didread == 0) {
204 		stream->eof = 1;
205 	}
206 
207 	return didread;
208 }
209 
php_curl_stream_close(php_stream * stream,int close_handle TSRMLS_DC)210 static int php_curl_stream_close(php_stream *stream, int close_handle TSRMLS_DC)
211 {
212 	php_curl_stream *curlstream = (php_curl_stream *) stream->abstract;
213 
214 	/* TODO: respect the close_handle flag here, so that casting to a FILE* on
215 	 * systems without fopencookie will work properly */
216 
217 	curl_multi_remove_handle(curlstream->multi, curlstream->curl);
218 	curl_easy_cleanup(curlstream->curl);
219 	curl_multi_cleanup(curlstream->multi);
220 
221 	if (curlstream->headers_slist) {
222 		curl_slist_free_all(curlstream->headers_slist);
223 	}
224 
225 	/* we are not closing curlstream->readbuf here, because we export
226 	 * it as a zval with the wrapperdata - the engine will garbage collect it */
227 
228 	efree(curlstream->url);
229 	efree(curlstream);
230 
231 	return 0;
232 }
233 
php_curl_stream_flush(php_stream * stream TSRMLS_DC)234 static int php_curl_stream_flush(php_stream *stream TSRMLS_DC)
235 {
236 #ifdef ilia_0
237 	php_curl_stream *curlstream = (php_curl_stream *) stream->abstract;
238 #endif
239 	return 0;
240 }
241 
php_curl_stream_stat(php_stream * stream,php_stream_statbuf * ssb TSRMLS_DC)242 static int php_curl_stream_stat(php_stream *stream, php_stream_statbuf *ssb TSRMLS_DC)
243 {
244 	/* TODO: fill in details based on Data: and Content-Length: headers, and/or data
245 	 * from curl_easy_getinfo().
246 	 * For now, return -1 to indicate that it doesn't make sense to stat this stream */
247 	return -1;
248 }
249 
php_curl_stream_cast(php_stream * stream,int castas,void ** ret TSRMLS_DC)250 static int php_curl_stream_cast(php_stream *stream, int castas, void **ret TSRMLS_DC)
251 {
252 	php_curl_stream *curlstream = (php_curl_stream *) stream->abstract;
253 	/* delegate to the readbuffer stream */
254 	return php_stream_cast(curlstream->readbuffer.buf, castas, ret, 0);
255 }
256 
257 php_stream_ops php_curl_stream_ops = {
258 	php_curl_stream_write,
259 	php_curl_stream_read,
260 	php_curl_stream_close,
261 	php_curl_stream_flush,
262 	"cURL",
263 	NULL, /* seek */
264 	php_curl_stream_cast, /* cast */
265 	php_curl_stream_stat  /* stat */
266 };
267 
268 
php_curl_stream_opener(php_stream_wrapper * wrapper,char * filename,char * mode,int options,char ** opened_path,php_stream_context * context STREAMS_DC TSRMLS_DC)269 php_stream *php_curl_stream_opener(php_stream_wrapper *wrapper, char *filename, char *mode,
270 		int options, char **opened_path, php_stream_context *context STREAMS_DC TSRMLS_DC)
271 {
272 	php_stream *stream;
273 	php_curl_stream *curlstream;
274 	zval *tmp, **ctx_opt = NULL;
275 
276 	curlstream = emalloc(sizeof(php_curl_stream));
277 	memset(curlstream, 0, sizeof(php_curl_stream));
278 
279 	stream = php_stream_alloc(&php_curl_stream_ops, curlstream, 0, mode);
280 	php_stream_context_set(stream, context);
281 
282 	curlstream->curl = curl_easy_init();
283 	curlstream->multi = curl_multi_init();
284 	curlstream->pending = 1;
285 	curlstream->headers_slist = NULL;
286 
287 	/* if opening for an include statement, ensure that the local storage will
288 	 * have a FILE* associated with it.
289 	 * Otherwise, use the "smart" memory stream that will turn itself into a file
290 	 * when it gets large */
291 #ifndef HAVE_FOPENCOOKIE
292 	if (options & STREAM_WILL_CAST) {
293 		curlstream->readbuffer.buf = php_stream_fopen_tmpfile();
294 	} else
295 #endif
296 	{
297 		curlstream->readbuffer.buf = php_stream_temp_new();
298 	}
299 
300 	/* curl requires the URL to be valid throughout it's operation, so dup it */
301 	curlstream->url = estrdup(filename);
302 	curl_easy_setopt(curlstream->curl, CURLOPT_URL, curlstream->url);
303 
304 	/* feed curl data into our read buffer */
305 	curl_easy_setopt(curlstream->curl, CURLOPT_WRITEFUNCTION, on_data_available);
306 	curl_easy_setopt(curlstream->curl, CURLOPT_FILE, stream);
307 
308 	/* feed headers */
309 	curl_easy_setopt(curlstream->curl, CURLOPT_HEADERFUNCTION, on_header_available);
310 	curl_easy_setopt(curlstream->curl, CURLOPT_WRITEHEADER, stream);
311 
312 	curl_easy_setopt(curlstream->curl, CURLOPT_ERRORBUFFER, curlstream->errstr);
313 	curl_easy_setopt(curlstream->curl, CURLOPT_VERBOSE, 0);
314 
315 	/* enable progress notification */
316 	curl_easy_setopt(curlstream->curl, CURLOPT_PROGRESSFUNCTION, on_progress_avail);
317 	curl_easy_setopt(curlstream->curl, CURLOPT_PROGRESSDATA, stream);
318 	curl_easy_setopt(curlstream->curl, CURLOPT_NOPROGRESS, 0);
319 
320 	curl_easy_setopt(curlstream->curl, CURLOPT_USERAGENT, FG(user_agent) ? FG(user_agent) : "PHP/" PHP_VERSION);
321 
322 	/* TODO: read cookies and options from context */
323 	if (context && !strncasecmp(filename, "http", sizeof("http")-1)) {
324 		/* Protocol version */
325 		if (SUCCESS == php_stream_context_get_option(context, "http", "protocol_version", &ctx_opt) && Z_TYPE_PP(ctx_opt) == IS_DOUBLE) {
326 			if (Z_DVAL_PP(ctx_opt) == 1.1) {
327 				curl_easy_setopt(curlstream->curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
328 			} else {
329 				curl_easy_setopt(curlstream->curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
330 			}
331 		}
332 
333 		if (SUCCESS == php_stream_context_get_option(context, "http", "curl_verify_ssl_host", &ctx_opt) && Z_TYPE_PP(ctx_opt) == IS_BOOL && Z_LVAL_PP(ctx_opt) == 1) {
334 			curl_easy_setopt(curlstream->curl, CURLOPT_SSL_VERIFYHOST, 2);
335 		} else {
336 			curl_easy_setopt(curlstream->curl, CURLOPT_SSL_VERIFYHOST, 0);
337 		}
338 		if (SUCCESS == php_stream_context_get_option(context, "http", "curl_verify_ssl_peer", &ctx_opt) && Z_TYPE_PP(ctx_opt) == IS_BOOL && Z_LVAL_PP(ctx_opt) == 1) {
339 			curl_easy_setopt(curlstream->curl, CURLOPT_SSL_VERIFYPEER, 1);
340 		} else {
341 			curl_easy_setopt(curlstream->curl, CURLOPT_SSL_VERIFYPEER, 0);
342 		}
343 
344 		/* HTTP(S) */
345 		if (SUCCESS == php_stream_context_get_option(context, "http", "user_agent", &ctx_opt) && Z_TYPE_PP(ctx_opt) == IS_STRING) {
346 			curl_easy_setopt(curlstream->curl, CURLOPT_USERAGENT, Z_STRVAL_PP(ctx_opt));
347 		}
348 		if (SUCCESS == php_stream_context_get_option(context, "http", "header", &ctx_opt)) {
349 			if (Z_TYPE_PP(ctx_opt) == IS_ARRAY) {
350 				HashPosition pos;
351 				zval **header = NULL;
352 
353 				for (zend_hash_internal_pointer_reset_ex(Z_ARRVAL_PP(ctx_opt), &pos);
354 					SUCCESS == zend_hash_get_current_data_ex(Z_ARRVAL_PP(ctx_opt), (void *)&header, &pos);
355 					zend_hash_move_forward_ex(Z_ARRVAL_PP(ctx_opt), &pos)
356 				) {
357 					if (Z_TYPE_PP(header) == IS_STRING) {
358 						curlstream->headers_slist = curl_slist_append(curlstream->headers_slist, Z_STRVAL_PP(header));
359 					}
360 				}
361 			} else if (Z_TYPE_PP(ctx_opt) == IS_STRING && Z_STRLEN_PP(ctx_opt)) {
362 				char *p, *token, *trimmed, *copy_ctx_opt;
363 
364 				copy_ctx_opt = php_trim(Z_STRVAL_PP(ctx_opt), Z_STRLEN_PP(ctx_opt), NULL, 0, NULL, 3 TSRMLS_CC);
365 				p = php_strtok_r(copy_ctx_opt, "\r\n", &token);
366 				while (p) {
367 					trimmed = php_trim(p, strlen(p), NULL, 0, NULL, 3 TSRMLS_CC);
368 					curlstream->headers_slist = curl_slist_append(curlstream->headers_slist, trimmed);
369 					efree(trimmed);
370 					p = php_strtok_r(NULL, "\r\n", &token);
371 				}
372 				efree(copy_ctx_opt);
373 			}
374 			if (curlstream->headers_slist) {
375 				curl_easy_setopt(curlstream->curl, CURLOPT_HTTPHEADER, curlstream->headers_slist);
376 			}
377 		}
378 		if (SUCCESS == php_stream_context_get_option(context, "http", "method", &ctx_opt) && Z_TYPE_PP(ctx_opt) == IS_STRING) {
379 			if (strcasecmp(Z_STRVAL_PP(ctx_opt), "get")) {
380 				if (!strcasecmp(Z_STRVAL_PP(ctx_opt), "head")) {
381 					curl_easy_setopt(curlstream->curl, CURLOPT_NOBODY, 1);
382 				} else {
383 					if (!strcasecmp(Z_STRVAL_PP(ctx_opt), "post")) {
384 						curl_easy_setopt(curlstream->curl, CURLOPT_POST, 1);
385 					} else {
386 						curl_easy_setopt(curlstream->curl, CURLOPT_CUSTOMREQUEST, Z_STRVAL_PP(ctx_opt));
387 					}
388 					if (SUCCESS == php_stream_context_get_option(context, "http", "content", &ctx_opt) && Z_TYPE_PP(ctx_opt) == IS_STRING) {
389 						curl_easy_setopt(curlstream->curl, CURLOPT_POSTFIELDS, Z_STRVAL_PP(ctx_opt));
390 						curl_easy_setopt(curlstream->curl, CURLOPT_POSTFIELDSIZE, (long)Z_STRLEN_PP(ctx_opt));
391 					}
392 				}
393 			}
394 		}
395 		if (SUCCESS == php_stream_context_get_option(context, "http", "proxy", &ctx_opt) && Z_TYPE_PP(ctx_opt) == IS_STRING) {
396 			curl_easy_setopt(curlstream->curl, CURLOPT_PROXY, Z_STRVAL_PP(ctx_opt));
397 		}
398 		if (SUCCESS == php_stream_context_get_option(context, "http", "max_redirects", &ctx_opt)) {
399 			long mr = 20;
400 			if (Z_TYPE_PP(ctx_opt) != IS_STRING || !is_numeric_string(Z_STRVAL_PP(ctx_opt), Z_STRLEN_PP(ctx_opt), &mr, NULL, 1)) {
401 				if (Z_TYPE_PP(ctx_opt) == IS_LONG) {
402 					mr = Z_LVAL_PP(ctx_opt);
403 				}
404 			}
405 			if (mr > 1) {
406 				if (PG(open_basedir) && *PG(open_basedir)) {
407 					curl_easy_setopt(curlstream->curl, CURLOPT_FOLLOWLOCATION, 0);
408 				} else {
409 					curl_easy_setopt(curlstream->curl, CURLOPT_FOLLOWLOCATION, 1);
410 				}
411 				curl_easy_setopt(curlstream->curl, CURLOPT_MAXREDIRS, mr);
412 			}
413 		} else {
414 			if (PG(open_basedir) && *PG(open_basedir)) {
415 				curl_easy_setopt(curlstream->curl, CURLOPT_FOLLOWLOCATION, 0);
416 			} else {
417 				curl_easy_setopt(curlstream->curl, CURLOPT_FOLLOWLOCATION, 1);
418 			}
419 			curl_easy_setopt(curlstream->curl, CURLOPT_MAXREDIRS, 20L);
420 		}
421 	} else if (context && !strncasecmp(filename, "ftps", sizeof("ftps")-1)) {
422 		if (SUCCESS == php_stream_context_get_option(context, "ftp", "curl_verify_ssl_host", &ctx_opt) && Z_TYPE_PP(ctx_opt) == IS_BOOL && Z_LVAL_PP(ctx_opt) == 1) {
423 			curl_easy_setopt(curlstream->curl, CURLOPT_SSL_VERIFYHOST, 2);
424 		} else {
425 			curl_easy_setopt(curlstream->curl, CURLOPT_SSL_VERIFYHOST, 0);
426 		}
427 		if (SUCCESS == php_stream_context_get_option(context, "ftp", "curl_verify_ssl_peer", &ctx_opt) && Z_TYPE_PP(ctx_opt) == IS_BOOL && Z_LVAL_PP(ctx_opt) == 1) {
428 			curl_easy_setopt(curlstream->curl, CURLOPT_SSL_VERIFYPEER, 1);
429 		} else {
430 			curl_easy_setopt(curlstream->curl, CURLOPT_SSL_VERIFYPEER, 0);
431 		}
432 	}
433 
434 	/* prepare for "pull" mode */
435 	curl_multi_add_handle(curlstream->multi, curlstream->curl);
436 
437 	/* Prepare stuff for file_get_wrapper_data: the data is an array:
438 	 *
439 	 * data = array(
440 	 *   "headers" => array("Content-Type: text/html", "Xxx: Yyy"),
441 	 *   "readbuf" => resource (equivalent to curlstream->readbuffer)
442 	 * );
443 	 * */
444 	MAKE_STD_ZVAL(stream->wrapperdata);
445 	array_init(stream->wrapperdata);
446 
447 	MAKE_STD_ZVAL(curlstream->headers);
448 	array_init(curlstream->headers);
449 
450 	add_assoc_zval(stream->wrapperdata, "headers", curlstream->headers);
451 
452 	MAKE_STD_ZVAL(tmp);
453 	php_stream_to_zval(curlstream->readbuffer.buf, tmp);
454 	add_assoc_zval(stream->wrapperdata, "readbuf", tmp);
455 
456 #ifndef HAVE_FOPENCOOKIE
457 	if (options & STREAM_WILL_CAST) {
458 		/* we will need to download the whole resource now,
459 		 * since we cannot get the actual FD for the download,
460 		 * so we won't be able to drive curl via stdio. */
461 
462 /* TODO: this needs finishing */
463 
464 		curl_easy_perform(curlstream->curl);
465 	}
466 	else
467 #endif
468 	{
469 		/* fire up the connection; we need to detect a connection error here,
470 		 * otherwise the curlstream we return ends up doing nothing useful. */
471 		CURLMcode m;
472 		CURLMsg *msg;
473 		int msgs_left, msg_found = 0;
474 
475 		while (CURLM_CALL_MULTI_PERFORM == (m = curl_multi_perform(curlstream->multi, &curlstream->pending))) {
476 			; /* spin */
477 		}
478 
479 		if (m != CURLM_OK) {
480 #if HAVE_CURL_MULTI_STRERROR
481 			php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", curl_multi_strerror(m));
482 #else
483 			php_error_docref(NULL TSRMLS_CC, E_WARNING, "There was an error mcode=%d", m);
484 #endif
485 			goto exit_fail;
486 		}
487 
488 		/* we have only one curl handle here, even though we use multi syntax,
489 		 * so it's ok to fail on any error */
490 		while ((msg = curl_multi_info_read(curlstream->multi, &msgs_left))) {
491 			if (msg->data.result == CURLE_OK) {
492 				continue;
493 			} else {
494 #if HAVE_CURL_EASY_STRERROR
495 				php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", curl_easy_strerror(msg->data.result));
496 #else
497 				php_error_docref(NULL TSRMLS_CC, E_WARNING, "There was an error mcode=%d", msg->data.result);
498 #endif
499 				msg_found++;
500 			}
501 		}
502 		if (msg_found) {
503 			goto exit_fail;
504 		}
505 	}
506 
507 	return stream;
508 
509 exit_fail:
510 	php_stream_close(stream);
511 	return NULL;
512 }
513 
514 static php_stream_wrapper_ops php_curl_wrapper_ops = {
515 	php_curl_stream_opener,
516 	NULL, /* stream_close: curl streams know how to clean themselves up */
517 	NULL, /* stream_stat: curl streams know how to stat themselves */
518 	NULL, /* stat url */
519 	NULL, /* opendir */
520 	"cURL", /* label */
521 	NULL, /* unlink */
522 	NULL, /* rename */
523 	NULL, /* mkdir */
524 	NULL  /* rmdir */
525 };
526 
527 php_stream_wrapper php_curl_wrapper = {
528 	&php_curl_wrapper_ops,
529 	NULL,
530 	1 /* is_url */
531 };
532 
533 #endif
534 
535 /*
536  * Local variables:
537  * tab-width: 4
538  * c-basic-offset: 4
539  * End:
540  * vim600: noet sw=4 ts=4 fdm=marker
541  * vim<600: noet sw=4 ts=4
542  */
543