1 : /*
2 : +----------------------------------------------------------------------+
3 : | PHP Version 6 |
4 : +----------------------------------------------------------------------+
5 : | Copyright (c) 1997-2009 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 : | Authors: Wez Furlong <wez@thebrainroom.com> |
16 : | Borrowed code from: |
17 : | Rasmus Lerdorf <rasmus@lerdorf.on.ca> |
18 : | Jim Winstead <jimw@php.net> |
19 : +----------------------------------------------------------------------+
20 : */
21 :
22 : /* $Id: streams.c 290796 2009-11-15 20:30:57Z felipe $ */
23 :
24 : #define _GNU_SOURCE
25 : #include "php.h"
26 : #include "php_globals.h"
27 : #include "php_network.h"
28 : #include "php_open_temporary_file.h"
29 : #include "ext/standard/file.h"
30 : #include "ext/standard/basic_functions.h" /* for BG(mmap_file) (not strictly required) */
31 : #include "ext/standard/php_string.h" /* for php_memnstr, used by php_stream_get_record() */
32 : #include <stddef.h>
33 : #include <fcntl.h>
34 : #include "php_streams_int.h"
35 :
36 : /* {{{ resource and registration code */
37 : /* Global wrapper hash, copied to FG(stream_wrappers) on registration of volatile wrapper */
38 : static HashTable url_stream_wrappers_hash;
39 : static int le_stream = FAILURE; /* true global */
40 : static int le_pstream = FAILURE; /* true global */
41 : static int le_stream_filter = FAILURE; /* true global */
42 :
43 : PHPAPI int php_file_le_stream(void)
44 10424209 : {
45 10424209 : return le_stream;
46 : }
47 :
48 : PHPAPI int php_file_le_pstream(void)
49 10361922 : {
50 10361922 : return le_pstream;
51 : }
52 :
53 : PHPAPI int php_file_le_stream_filter(void)
54 80 : {
55 80 : return le_stream_filter;
56 : }
57 :
58 : PHPAPI HashTable *_php_stream_get_url_stream_wrappers_hash(TSRMLS_D)
59 32 : {
60 32 : return (FG(stream_wrappers) ? FG(stream_wrappers) : &url_stream_wrappers_hash);
61 : }
62 :
63 : PHPAPI HashTable *php_stream_get_url_stream_wrappers_hash_global(void)
64 1 : {
65 1 : return &url_stream_wrappers_hash;
66 : }
67 :
68 : static int _php_stream_release_context(zend_rsrc_list_entry *le, void *pContext TSRMLS_DC)
69 0 : {
70 0 : if (le->ptr == pContext) {
71 0 : return --le->refcount == 0;
72 : }
73 0 : return 0;
74 : }
75 :
76 : static int forget_persistent_resource_id_numbers(zend_rsrc_list_entry *rsrc TSRMLS_DC)
77 323 : {
78 : php_stream *stream;
79 :
80 323 : if (Z_TYPE_P(rsrc) != le_pstream) {
81 317 : return 0;
82 : }
83 :
84 6 : stream = (php_stream*)rsrc->ptr;
85 :
86 : #if STREAM_DEBUG
87 : fprintf(stderr, "forget_persistent: %s:%p\n", stream->ops->label, stream);
88 : #endif
89 :
90 6 : stream->rsrc_id = FAILURE;
91 :
92 6 : if (stream->context) {
93 0 : zend_hash_apply_with_argument(&EG(regular_list),
94 : (apply_func_arg_t) _php_stream_release_context,
95 : stream->context TSRMLS_CC);
96 0 : stream->context = NULL;
97 : }
98 :
99 6 : return 0;
100 : }
101 :
102 : PHP_RSHUTDOWN_FUNCTION(streams)
103 17025 : {
104 17025 : zend_hash_apply(&EG(persistent_list), (apply_func_t)forget_persistent_resource_id_numbers TSRMLS_CC);
105 17025 : return SUCCESS;
106 : }
107 :
108 : PHPAPI int php_stream_from_persistent_id(const char *persistent_id, php_stream **stream TSRMLS_DC)
109 987 : {
110 : zend_rsrc_list_entry *le;
111 :
112 987 : if (zend_hash_find(&EG(persistent_list), (char*)persistent_id, strlen(persistent_id)+1, (void*) &le) == SUCCESS) {
113 0 : if (Z_TYPE_P(le) == le_pstream) {
114 0 : if (stream) {
115 0 : *stream = (php_stream*)le->ptr;
116 0 : le->refcount++;
117 0 : (*stream)->rsrc_id = ZEND_REGISTER_RESOURCE(NULL, *stream, le_pstream);
118 : }
119 0 : return PHP_STREAM_PERSISTENT_SUCCESS;
120 : }
121 0 : return PHP_STREAM_PERSISTENT_FAILURE;
122 : }
123 987 : return PHP_STREAM_PERSISTENT_NOT_EXIST;
124 : }
125 :
126 : /* }}} */
127 :
128 : /* {{{ wrapper error reporting */
129 : void php_stream_display_wrapper_errors(php_stream_wrapper *wrapper, const char *path, const char *caption TSRMLS_DC)
130 545 : {
131 545 : char *tmp = estrdup(path);
132 : char *msg;
133 545 : int free_msg = 0;
134 :
135 545 : if (wrapper) {
136 526 : if (wrapper->err_count > 0) {
137 : int i;
138 : size_t l;
139 : int brlen;
140 : char *br;
141 :
142 85 : if (PG(html_errors)) {
143 0 : brlen = 7;
144 0 : br = "<br />\n";
145 : } else {
146 85 : brlen = 1;
147 85 : br = "\n";
148 : }
149 :
150 176 : for (i = 0, l = 0; i < wrapper->err_count; i++) {
151 91 : l += strlen(wrapper->err_stack[i]);
152 91 : if (i < wrapper->err_count - 1) {
153 6 : l += brlen;
154 : }
155 : }
156 85 : msg = emalloc(l + 1);
157 85 : msg[0] = '\0';
158 176 : for (i = 0; i < wrapper->err_count; i++) {
159 91 : strcat(msg, wrapper->err_stack[i]);
160 91 : if (i < wrapper->err_count - 1) {
161 6 : strcat(msg, br);
162 : }
163 : }
164 :
165 85 : free_msg = 1;
166 : } else {
167 441 : if (wrapper == &php_plain_files_wrapper) {
168 439 : msg = strerror(errno);
169 : } else {
170 2 : msg = "operation failed";
171 : }
172 : }
173 : } else {
174 19 : msg = "no suitable wrapper could be found";
175 : }
176 :
177 545 : php_strip_url_passwd(tmp);
178 545 : php_error_docref1(NULL TSRMLS_CC, tmp, E_WARNING, "%s: %s", caption, msg);
179 545 : efree(tmp);
180 545 : if (free_msg) {
181 85 : efree(msg);
182 : }
183 545 : }
184 :
185 : void php_stream_tidy_wrapper_error_log(php_stream_wrapper *wrapper TSRMLS_DC)
186 116424 : {
187 116424 : if (wrapper) {
188 : /* tidy up the error stack */
189 : int i;
190 :
191 116496 : for (i = 0; i < wrapper->err_count; i++) {
192 91 : efree(wrapper->err_stack[i]);
193 : }
194 116405 : if (wrapper->err_stack) {
195 85 : efree(wrapper->err_stack);
196 : }
197 116405 : wrapper->err_stack = NULL;
198 116405 : wrapper->err_count = 0;
199 : }
200 116424 : }
201 :
202 : PHPAPI void php_stream_wrapper_log_error(php_stream_wrapper *wrapper, int options TSRMLS_DC, const char *fmt, ...)
203 125 : {
204 : va_list args;
205 125 : char *buffer = NULL;
206 :
207 125 : va_start(args, fmt);
208 125 : vspprintf(&buffer, 0, fmt, args);
209 125 : va_end(args);
210 :
211 159 : if (options & REPORT_ERRORS || wrapper == NULL) {
212 34 : php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", buffer);
213 34 : efree(buffer);
214 : } else {
215 : /* append to stack */
216 91 : wrapper->err_stack = erealloc(wrapper->err_stack, (wrapper->err_count + 1) * sizeof(char *));
217 91 : if (wrapper->err_stack) {
218 91 : wrapper->err_stack[wrapper->err_count++] = buffer;
219 : }
220 : }
221 125 : }
222 :
223 :
224 : /* }}} */
225 :
226 : /* allocate a new stream for a particular ops */
227 : PHPAPI php_stream *_php_stream_alloc(php_stream_ops *ops, void *abstract, const char *persistent_id, const char *mode STREAMS_DC TSRMLS_DC) /* {{{ */
228 191273 : {
229 : php_stream *ret;
230 :
231 191273 : ret = (php_stream*) pemalloc_rel_orig(sizeof(php_stream), persistent_id ? 1 : 0);
232 :
233 191273 : memset(ret, 0, sizeof(php_stream));
234 :
235 191273 : ret->readfilters.stream = ret;
236 191273 : ret->writefilters.stream = ret;
237 :
238 : #if STREAM_DEBUG
239 : fprintf(stderr, "stream_alloc: %s:%p persistent=%s\n", ops->label, ret, persistent_id);
240 : #endif
241 :
242 191273 : ret->ops = ops;
243 191273 : ret->abstract = abstract;
244 191273 : ret->is_persistent = persistent_id ? 1 : 0;
245 191273 : ret->chunk_size = FG(def_chunk_size);
246 191273 : ret->readbuf_type = IS_STRING;
247 :
248 : #if ZEND_DEBUG
249 : ret->open_filename = __zend_orig_filename ? __zend_orig_filename : __zend_filename;
250 : ret->open_lineno = __zend_orig_lineno ? __zend_orig_lineno : __zend_lineno;
251 : #endif
252 :
253 191273 : if (FG(auto_detect_line_endings)) {
254 0 : ret->flags |= PHP_STREAM_FLAG_DETECT_EOL;
255 : }
256 :
257 191273 : if (persistent_id) {
258 : zend_rsrc_list_entry le;
259 :
260 987 : Z_TYPE(le) = le_pstream;
261 987 : le.ptr = ret;
262 987 : le.refcount = 0;
263 :
264 987 : if (FAILURE == zend_hash_update(&EG(persistent_list), (char *)persistent_id,
265 : strlen(persistent_id) + 1,
266 : (void *)&le, sizeof(le), NULL)) {
267 :
268 0 : pefree(ret, 1);
269 0 : return NULL;
270 : }
271 : }
272 :
273 191273 : ret->rsrc_id = ZEND_REGISTER_RESOURCE(NULL, ret, persistent_id ? le_pstream : le_stream);
274 191273 : strlcpy(ret->mode, mode, sizeof(ret->mode));
275 :
276 191273 : return ret;
277 : }
278 : /* }}} */
279 :
280 : static int _php_stream_free_persistent(zend_rsrc_list_entry *le, void *pStream TSRMLS_DC)
281 35 : {
282 35 : return le->ptr == pStream;
283 : }
284 :
285 : PHPAPI int _php_stream_free(php_stream *stream, int close_options TSRMLS_DC) /* {{{ */
286 256748 : {
287 256748 : int ret = 1;
288 256748 : int remove_rsrc = 1;
289 256748 : int preserve_handle = close_options & PHP_STREAM_FREE_PRESERVE_HANDLE ? 1 : 0;
290 256748 : int release_cast = 1;
291 256748 : php_stream_context *context = stream->context;
292 :
293 256748 : if (stream->flags & PHP_STREAM_FLAG_NO_CLOSE) {
294 0 : preserve_handle = 1;
295 : }
296 :
297 : #if STREAM_DEBUG
298 : fprintf(stderr, "stream_free: %s:%p[%s] in_free=%d opts=%08x\n", stream->ops->label, stream, stream->orig_path, stream->in_free, close_options);
299 : #endif
300 :
301 : /* recursion protection */
302 256748 : if (stream->in_free) {
303 65342 : return 1;
304 : }
305 :
306 191406 : stream->in_free++;
307 :
308 : /* if we are releasing the stream only (and preserving the underlying handle),
309 : * we need to do things a little differently.
310 : * We are only ever called like this when the stream is cast to a FILE*
311 : * for include (or other similar) purposes.
312 : * */
313 191406 : if (preserve_handle) {
314 0 : if (stream->fclose_stdiocast == PHP_STREAM_FCLOSE_FOPENCOOKIE) {
315 : /* If the stream was fopencookied, we must NOT touch anything
316 : * here, as the cookied stream relies on it all.
317 : * Instead, mark the stream as OK to auto-clean */
318 : php_stream_auto_cleanup(stream);
319 0 : stream->in_free--;
320 0 : return 0;
321 : }
322 : /* otherwise, make sure that we don't close the FILE* from a cast */
323 0 : release_cast = 0;
324 : }
325 :
326 : #if STREAM_DEBUG
327 : fprintf(stderr, "stream_free: %s:%p[%s] preserve_handle=%d release_cast=%d remove_rsrc=%d\n",
328 : stream->ops->label, stream, stream->orig_path, preserve_handle, release_cast, remove_rsrc);
329 : #endif
330 :
331 : /* make sure everything is saved */
332 191406 : _php_stream_flush(stream, 1 TSRMLS_CC);
333 :
334 : /* If not called from the resource dtor, remove the stream from the resource list. */
335 191406 : if ((close_options & PHP_STREAM_FREE_RSRC_DTOR) == 0 && remove_rsrc) {
336 64429 : zend_list_delete(stream->rsrc_id);
337 : }
338 :
339 : /* Remove stream from any context link list */
340 191406 : if (stream->context && stream->context->links) {
341 0 : php_stream_context_del_link(stream->context, stream);
342 : }
343 :
344 191406 : if (close_options & PHP_STREAM_FREE_CALL_DTOR) {
345 191406 : if (release_cast && stream->fclose_stdiocast == PHP_STREAM_FCLOSE_FOPENCOOKIE) {
346 : /* calling fclose on an fopencookied stream will ultimately
347 : call this very same function. If we were called via fclose,
348 : the cookie_closer unsets the fclose_stdiocast flags, so
349 : we can be sure that we only reach here when PHP code calls
350 : php_stream_free.
351 : Lets let the cookie code clean it all up.
352 : */
353 0 : stream->in_free = 0;
354 0 : return fclose(stream->stdiocast);
355 : }
356 :
357 191406 : ret = stream->ops->close(stream, preserve_handle ? 0 : 1 TSRMLS_CC);
358 191404 : stream->abstract = NULL;
359 :
360 : /* tidy up any FILE* that might have been fdopened */
361 191404 : if (release_cast && stream->fclose_stdiocast == PHP_STREAM_FCLOSE_FDOPEN && stream->stdiocast) {
362 0 : fclose(stream->stdiocast);
363 0 : stream->stdiocast = NULL;
364 0 : stream->fclose_stdiocast = PHP_STREAM_FCLOSE_NONE;
365 : }
366 : }
367 :
368 191404 : if (close_options & PHP_STREAM_FREE_RELEASE_STREAM) {
369 395600 : while (stream->readfilters.head) {
370 12792 : php_stream_filter_remove(stream->readfilters.head, 1 TSRMLS_CC);
371 : }
372 388833 : while (stream->writefilters.head) {
373 6025 : php_stream_filter_remove(stream->writefilters.head, 1 TSRMLS_CC);
374 : }
375 :
376 191404 : if (stream->wrapper && stream->wrapper->wops && stream->wrapper->wops->stream_closer) {
377 0 : stream->wrapper->wops->stream_closer(stream->wrapper, stream TSRMLS_CC);
378 0 : stream->wrapper = NULL;
379 : }
380 :
381 191404 : if (stream->wrapperdata) {
382 65 : zval_ptr_dtor(&stream->wrapperdata);
383 65 : stream->wrapperdata = NULL;
384 : }
385 :
386 191404 : if (stream->readbuf.v) {
387 42508 : pefree(stream->readbuf.v, stream->is_persistent);
388 42508 : stream->readbuf.v = NULL;
389 : }
390 :
391 191404 : if (stream->is_persistent && (close_options & PHP_STREAM_FREE_PERSISTENT)) {
392 : /* we don't work with *stream but need its value for comparison */
393 989 : zend_hash_apply_with_argument(&EG(persistent_list), (apply_func_arg_t) _php_stream_free_persistent, stream TSRMLS_CC);
394 : }
395 : #if ZEND_DEBUG
396 : if ((close_options & PHP_STREAM_FREE_RSRC_DTOR) && (stream->__exposed == 0) && (EG(error_reporting) & E_WARNING)) {
397 : /* it leaked: Lets deliberately NOT pefree it so that the memory manager shows it
398 : * as leaked; it will log a warning, but lets help it out and display what kind
399 : * of stream it was. */
400 : char *leakinfo;
401 : spprintf(&leakinfo, 0, __FILE__ "(%d) : Stream of type '%s' %p (path:%s) was not closed (opened in %s on line %d)\n", __LINE__, stream->ops->label, stream, stream->orig_path, stream->open_filename, stream->open_lineno);
402 :
403 : if (stream->orig_path) {
404 : pefree(stream->orig_path, stream->is_persistent);
405 : stream->orig_path = NULL;
406 : }
407 :
408 : # if defined(PHP_WIN32)
409 : OutputDebugString(leakinfo);
410 : # else
411 : fprintf(stderr, "%s", leakinfo);
412 : # endif
413 : efree(leakinfo);
414 : } else {
415 : if (stream->orig_path) {
416 : pefree(stream->orig_path, stream->is_persistent);
417 : stream->orig_path = NULL;
418 : }
419 :
420 : pefree(stream, stream->is_persistent);
421 : }
422 : #else
423 191404 : if (stream->orig_path) {
424 134571 : pefree(stream->orig_path, stream->is_persistent);
425 134571 : stream->orig_path = NULL;
426 : }
427 :
428 191404 : pefree(stream, stream->is_persistent);
429 : #endif
430 : }
431 :
432 191404 : if (context) {
433 178 : zend_list_delete(context->rsrc_id);
434 : }
435 :
436 191404 : return ret;
437 : }
438 : /* }}} */
439 :
440 : /* {{{ generic stream operations */
441 :
442 : /* size == full characters (char, UChar, or 2x UChar)
443 : TODO: Needs better handling of surrogate pairs */
444 : static void php_stream_fill_read_buffer(php_stream *stream, size_t size TSRMLS_DC)
445 991679 : {
446 991679 : if (stream->readpos == stream->writepos) {
447 991666 : stream->readpos = stream->writepos = 0;
448 : }
449 :
450 : /* allocate/fill the buffer */
451 :
452 991679 : if (stream->readfilters.head) {
453 : char *chunk_buf;
454 12936 : int err_flag = 0;
455 12936 : php_stream_bucket_brigade brig_in = { NULL, NULL }, brig_out = { NULL, NULL };
456 12936 : php_stream_bucket_brigade *brig_inp = &brig_in, *brig_outp = &brig_out, *brig_swap;
457 :
458 : /* Invalidate the existing cache, otherwise reads can fail, see note in
459 : main/streams/filter.c::_php_stream_filter_append */
460 12936 : stream->writepos = stream->readpos = 0;
461 :
462 : /* allocate a buffer for reading chunks */
463 12936 : chunk_buf = emalloc(stream->chunk_size);
464 :
465 38297 : while (!stream->eof && !err_flag && (stream->writepos - stream->readpos < (off_t)size)) {
466 24353 : size_t justread = 0;
467 : int flags;
468 : php_stream_bucket *bucket;
469 24353 : php_stream_filter_status_t status = PSFS_ERR_FATAL;
470 : php_stream_filter *filter;
471 :
472 : /* read a chunk into a bucket */
473 24353 : justread = stream->ops->read(stream, chunk_buf, stream->chunk_size TSRMLS_CC);
474 36778 : if (justread && justread != (size_t)-1) {
475 12425 : bucket = php_stream_bucket_new(stream, chunk_buf, justread, 0, 0 TSRMLS_CC);
476 :
477 : /* after this call, bucket is owned by the brigade */
478 12425 : php_stream_bucket_append(brig_inp, bucket TSRMLS_CC);
479 :
480 12425 : flags = PSFS_FLAG_NORMAL;
481 : } else {
482 11928 : flags = stream->eof ? PSFS_FLAG_FLUSH_CLOSE : PSFS_FLAG_FLUSH_INC;
483 : }
484 :
485 : /* wind the handle... */
486 36668 : for (filter = stream->readfilters.head; filter; filter = filter->next) {
487 24363 : status = filter->fops->filter(stream, filter, brig_inp, brig_outp, NULL, flags TSRMLS_CC);
488 :
489 24363 : if (status != PSFS_PASS_ON) {
490 12048 : break;
491 : }
492 :
493 : /* brig_out becomes brig_in.
494 : * brig_in will always be empty here, as the filter MUST attach any un-consumed buckets
495 : * to its own brigade */
496 12315 : brig_swap = brig_inp;
497 12315 : brig_inp = brig_outp;
498 12315 : brig_outp = brig_swap;
499 12315 : memset(brig_outp, 0, sizeof(*brig_outp));
500 : }
501 :
502 24353 : switch (status) {
503 : case PSFS_PASS_ON:
504 : /* we get here when the last filter in the chain has data to pass on.
505 : * in this situation, we are passing the brig_in brigade into the
506 : * stream read buffer */
507 36903 : while (brig_inp->head) {
508 12293 : bucket = brig_inp->head;
509 12293 : if (bucket->buf_type != stream->readbuf_type) {
510 : /* Stream expects different datatype than bucket has, convert slopily */
511 1 : php_stream_bucket_convert_notranscode(bucket, stream->readbuf_type);
512 : }
513 : /* Bucket type now matches stream type */
514 :
515 : /* grow buffer to hold this bucket
516 : * TODO: this can fail for persistent streams */
517 12293 : if (stream->readbuflen - stream->writepos < (unsigned int)bucket->buflen) {
518 11676 : stream->readbuflen += bucket->buflen;
519 11676 : stream->readbuf.v = perealloc(stream->readbuf.v, PS_ULEN(stream->readbuf_type == IS_UNICODE, stream->readbuflen), stream->is_persistent);
520 : }
521 12293 : memcpy(stream->readbuf.s + PS_ULEN(stream->readbuf_type == IS_UNICODE, stream->writepos), bucket->buf.s, PS_ULEN(stream->readbuf_type == IS_UNICODE, bucket->buflen));
522 12293 : stream->writepos += bucket->buflen;
523 :
524 12293 : php_stream_bucket_unlink(bucket TSRMLS_CC);
525 12293 : php_stream_bucket_delref(bucket TSRMLS_CC);
526 : }
527 12305 : break;
528 :
529 : case PSFS_FEED_ME:
530 : /* when a filter needs feeding, there is no brig_out to deal with.
531 : * we simply continue the loop; if the caller needs more data,
532 : * we will read again, otherwise out job is done here */
533 12031 : if (justread == 0) {
534 : /* there is no data */
535 11916 : err_flag = 1;
536 11916 : break;
537 : }
538 115 : continue;
539 :
540 : case PSFS_ERR_FATAL:
541 : /* some fatal error. Theoretically, the stream is borked, so all
542 : * further reads should fail. */
543 17 : err_flag = 1;
544 : break;
545 : }
546 :
547 24238 : if (justread == 0 || justread == (size_t)-1) {
548 : break;
549 : }
550 : }
551 :
552 12936 : efree(chunk_buf);
553 : } else { /* Unfiltered Binary stream */
554 : /* is there enough data in the buffer ? */
555 978743 : if (stream->writepos - stream->readpos < (off_t)size) {
556 978743 : size_t justread = 0;
557 :
558 : /* reduce buffer memory consumption if possible, to avoid a realloc */
559 978743 : if (stream->readbuf.s && stream->readbuflen - stream->writepos < stream->chunk_size) {
560 1846 : memmove(stream->readbuf.s, stream->readbuf.s + stream->readpos, stream->writepos - stream->readpos);
561 1846 : stream->writepos -= stream->readpos;
562 1846 : stream->readpos = 0;
563 : }
564 :
565 : /* grow the buffer if required
566 : * TODO: this can fail for persistent streams */
567 978743 : if (stream->readbuflen - stream->writepos < stream->chunk_size) {
568 32699 : stream->readbuflen += stream->chunk_size;
569 32699 : stream->readbuf.s = (char*)perealloc(stream->readbuf.s, stream->readbuflen, stream->is_persistent);
570 : }
571 :
572 978743 : justread = stream->ops->read(stream, stream->readbuf.s + stream->writepos, stream->readbuflen - stream->writepos TSRMLS_CC);
573 978743 : if (justread != (size_t)-1 && justread != 0) {
574 944122 : stream->writepos += justread;
575 : }
576 : }
577 : }
578 991679 : }
579 :
580 : /* Reads binary data from stream, if the stream is unicode (text), the raw unicode data will be returned */
581 : PHPAPI size_t _php_stream_read(php_stream *stream, char *buf, size_t size TSRMLS_DC)
582 1754629 : {
583 1754629 : size_t toread = 0, didread = 0;
584 :
585 3604138 : while (size > 0) {
586 :
587 : /* take from the read buffer first.
588 : * It is possible that a buffered stream was switched to non-buffered, so we
589 : * drain the remainder of the buffer before using the "raw" read mode for
590 : * the excess */
591 1758450 : if (stream->writepos - stream->readpos > 0) {
592 787645 : toread = PS_ULEN(stream->readbuf_type == IS_UNICODE, stream->writepos - stream->readpos);
593 :
594 787645 : if (toread > size) {
595 718484 : toread = size;
596 : }
597 :
598 787645 : if (stream->readbuf_type == IS_UNICODE) {
599 : /* Sloppy read, anyone using php_stream_read() on a unicode stream
600 : * had better know what they're doing */
601 :
602 0 : memcpy(buf, stream->readbuf.u + stream->readpos, toread);
603 0 : stream->readpos += ceil(toread / UBYTES(1));
604 : } else {
605 787645 : memcpy(buf, stream->readbuf.s + stream->readpos, toread);
606 787645 : stream->readpos += toread;
607 : }
608 :
609 787645 : size -= toread;
610 787645 : buf += toread;
611 787645 : didread += toread;
612 : }
613 :
614 : /* ignore eof here; the underlying state might have changed */
615 1758450 : if (size == 0) {
616 757366 : break;
617 : }
618 :
619 1070501 : if (!stream->readfilters.head && (stream->flags & PHP_STREAM_FLAG_NO_BUFFER || stream->chunk_size == 1)) {
620 69417 : toread = stream->ops->read(stream, buf, size TSRMLS_CC);
621 : } else {
622 931667 : php_stream_fill_read_buffer(stream, size TSRMLS_CC);
623 :
624 931667 : toread = stream->writepos - stream->readpos;
625 931667 : if (toread > size) {
626 78862 : toread = size;
627 : }
628 :
629 931667 : if (toread > 0) {
630 898482 : if (stream->readbuf_type == IS_UNICODE) {
631 : /* Sloppy read, anyone using php_stream_read() on a unicode stream
632 : * had better know what they're doing */
633 :
634 0 : memcpy(buf, stream->readbuf.u + stream->readpos, toread);
635 0 : stream->readpos += ceil(toread / UBYTES(1));
636 : } else {
637 898482 : memcpy(buf, stream->readbuf.s + stream->readpos, toread);
638 : }
639 898482 : stream->readpos += toread;
640 : }
641 : }
642 1001084 : if (toread > 0) {
643 964336 : didread += toread;
644 964336 : buf += toread;
645 964336 : size -= toread;
646 : } else {
647 : /* EOF, or temporary end of data (for non-blocking mode). */
648 36748 : break;
649 : }
650 : /* just break anyway, to avoid greedy read */
651 964336 : if (stream->wrapper != &php_plain_files_wrapper) {
652 869456 : break;
653 : }
654 : }
655 :
656 1754629 : if (didread > 0) {
657 1724830 : stream->position += didread;
658 : }
659 :
660 1754629 : return didread;
661 : }
662 :
663 : /* Read unicode data from a stream. Returns failure (-1) if not a unicode stream */
664 : PHPAPI size_t _php_stream_read_unicode(php_stream *stream, UChar *buf, int size, int maxchars TSRMLS_DC)
665 282 : {
666 282 : size_t toread = 0, didread = 0, string_length = 0;
667 :
668 282 : if (stream->readbuf_type != IS_UNICODE) {
669 0 : return -1;
670 : }
671 :
672 703 : while (size > 0) {
673 : /* take from the read buffer first.
674 : * It is possible that a buffered stream was switched to non-buffered, so we
675 : * drain the remainder of the buffer before using the "raw" read mode for
676 : * the excess */
677 :
678 981 : while (size > 0 && (toread = (stream->writepos - stream->readpos)) &&
679 : (toread > 1 ||
680 : !U16_IS_SURROGATE(stream->readbuf.u[stream->readpos]) ||
681 : !U16_IS_SURROGATE_LEAD(stream->readbuf.u[stream->readpos]) )) {
682 : int length;
683 :
684 139 : if (toread > size) {
685 0 : toread = size;
686 : }
687 :
688 139 : if (U16_IS_SURROGATE(stream->readbuf.u[stream->readpos + toread - 1]) &&
689 : U16_IS_SURROGATE_LEAD(stream->readbuf.u[stream->readpos + toread - 1])) {
690 : /* Don't split surrogates */
691 0 : toread--;
692 0 : if (!toread) {
693 0 : break;
694 : }
695 : }
696 :
697 139 : if (maxchars > -1) {
698 0 : length = u_countChar32(stream->readbuf.u + stream->readpos, toread);
699 0 : if (string_length + length > maxchars) {
700 : /* Don't read more U32 points than the caller asked for */
701 0 : toread = 0;
702 0 : length = size - string_length;
703 0 : U16_FWD_N(stream->readbuf.u + stream->readpos, toread, stream->writepos - stream->readpos, length);
704 : }
705 0 : string_length += length;
706 : }
707 :
708 139 : memcpy(buf, stream->readbuf.u + stream->readpos, UBYTES(toread));
709 139 : stream->readpos += toread;
710 139 : size -= toread;
711 139 : buf += toread;
712 139 : didread += toread;
713 : }
714 :
715 : /* ignore eof here; the underlying state might have changed */
716 421 : if (size == 0) {
717 0 : break;
718 : }
719 :
720 : /* just break anyway, to avoid greedy read */
721 421 : if (didread > 0 && (stream->wrapper != &php_plain_files_wrapper)) {
722 13 : break;
723 : }
724 :
725 408 : php_stream_fill_read_buffer(stream, size * sizeof(UChar) TSRMLS_CC);
726 408 : if (stream->writepos - stream->readpos <= 0) {
727 : /* EOF, or temporary end of data (for non-blocking mode). */
728 269 : break;
729 : }
730 : }
731 :
732 282 : if (didread > 0) {
733 139 : stream->position += didread;
734 : }
735 :
736 282 : return didread;
737 : }
738 :
739 : PHPAPI UChar *_php_stream_read_unicode_chars(php_stream *stream, int *pchars TSRMLS_DC)
740 131 : {
741 131 : int size = *pchars;
742 : UChar *bufstart, *buf;
743 131 : int buflen = size;
744 131 : size_t toread = 0, didread = 0, string_length = 0;
745 :
746 131 : if (stream->readbuf_type != IS_UNICODE) {
747 0 : return NULL;
748 : }
749 :
750 : /* Allocate for ideal size first, add more later if needed */
751 131 : bufstart = buf = eumalloc(buflen + 1);
752 :
753 334 : while (size > 0) {
754 : /* take from the read buffer first.
755 : * It is possible that a buffered stream was switched to non-buffered, so we
756 : * drain the remainder of the buffer before using the "raw" read mode for
757 : * the excess */
758 :
759 526 : while (size > 0 && (toread = (stream->writepos - stream->readpos)) &&
760 : (toread > 1 ||
761 : !U16_IS_SURROGATE(stream->readbuf.u[stream->readpos]) ||
762 : !U16_IS_SURROGATE_LEAD(stream->readbuf.u[stream->readpos]) )) {
763 : int length;
764 :
765 120 : if (toread > size) {
766 77 : toread = size;
767 : }
768 :
769 120 : if (U16_IS_SURROGATE(stream->readbuf.u[stream->readpos + toread - 1]) &&
770 : U16_IS_SURROGATE_LEAD(stream->readbuf.u[stream->readpos + toread - 1])) {
771 : /* Don't split surrogates */
772 0 : toread--;
773 0 : if (!toread) {
774 0 : break;
775 : }
776 : }
777 :
778 120 : length = u_countChar32(stream->readbuf.u + stream->readpos, toread);
779 120 : if (string_length + length > size) {
780 : /* Don't read more U32 points than the caller asked for */
781 0 : toread = 0;
782 0 : length = size - string_length;
783 0 : U16_FWD_N(stream->readbuf.u + stream->readpos, toread, stream->writepos - stream->readpos, length);
784 : }
785 :
786 120 : if (toread > (buflen - didread)) {
787 : /* We know we're in surrogated territory at this point, allocate aggressively */
788 0 : int ofs = buf - bufstart;
789 0 : buflen += size * 2;
790 0 : bufstart = eurealloc(bufstart, buflen + 1);
791 0 : buf = bufstart + ofs;
792 : }
793 :
794 120 : memcpy(buf, stream->readbuf.u + stream->readpos, UBYTES(toread));
795 120 : stream->readpos += toread;
796 120 : size -= toread;
797 120 : buf += toread;
798 120 : didread += toread;
799 120 : string_length += length;
800 : }
801 :
802 : /* ignore eof here; the underlying state might have changed */
803 203 : if (size == 0) {
804 98 : break;
805 : }
806 :
807 : /* just break anyway, to avoid greedy read */
808 105 : if (didread > 0 && (stream->wrapper != &php_plain_files_wrapper)) {
809 0 : break;
810 : }
811 :
812 105 : php_stream_fill_read_buffer(stream, UBYTES(size) TSRMLS_CC);
813 105 : if (stream->writepos - stream->readpos <= 0) {
814 : /* EOF, or temporary end of data (for non-blocking mode). */
815 33 : break;
816 : }
817 : }
818 :
819 131 : if (didread > 0) {
820 120 : stream->position += didread;
821 : }
822 :
823 131 : *pchars = string_length;
824 131 : buf[0] = 0;
825 131 : return bufstart;
826 : }
827 :
828 : PHPAPI int _php_stream_eof(php_stream *stream TSRMLS_DC)
829 1037170 : {
830 : /* if there is data in the buffer, it's not EOF */
831 1037170 : if (stream->writepos - stream->readpos > 0) {
832 1005885 : return 0;
833 : }
834 :
835 : /* use the configured timeout when checking eof */
836 31285 : if (!stream->eof && PHP_STREAM_OPTION_RETURN_ERR ==
837 : php_stream_set_option(stream, PHP_STREAM_OPTION_CHECK_LIVENESS,
838 : 0, NULL)) {
839 4 : stream->eof = 1;
840 : }
841 :
842 31285 : return stream->eof;
843 : }
844 :
845 : PHPAPI int _php_stream_putc(php_stream *stream, int c TSRMLS_DC)
846 23 : {
847 23 : unsigned char buf = c;
848 :
849 23 : if (php_stream_write(stream, &buf, 1) > 0) {
850 23 : return 1;
851 : }
852 0 : return EOF;
853 : }
854 :
855 : PHPAPI int _php_stream_getc(php_stream *stream TSRMLS_DC)
856 531894 : {
857 : char buf;
858 :
859 531894 : if (php_stream_read(stream, &buf, 1) > 0) {
860 531872 : return buf & 0xff;
861 : }
862 22 : return EOF;
863 : }
864 :
865 : PHPAPI int _php_stream_puts(php_stream *stream, char *buf TSRMLS_DC)
866 0 : {
867 : int len;
868 0 : char newline[2] = "\n"; /* is this OK for Win? */
869 0 : len = strlen(buf);
870 :
871 0 : if (len > 0 && php_stream_write(stream, buf, len) && php_stream_write(stream, newline, 1)) {
872 0 : return 1;
873 : }
874 0 : return 0;
875 : }
876 :
877 : PHPAPI int _php_stream_stat(php_stream *stream, php_stream_statbuf *ssb TSRMLS_DC)
878 33007 : {
879 33007 : memset(ssb, 0, sizeof(*ssb));
880 :
881 : /* if the stream was wrapped, allow the wrapper to stat it */
882 33007 : if (stream->wrapper && stream->wrapper->wops->stream_stat != NULL) {
883 0 : return stream->wrapper->wops->stream_stat(stream->wrapper, stream, ssb TSRMLS_CC);
884 : }
885 :
886 : /* if the stream doesn't directly support stat-ing, return with failure.
887 : * We could try and emulate this by casting to a FD and fstat-ing it,
888 : * but since the fd might not represent the actual underlying content
889 : * this would give bogus results. */
890 33007 : if (stream->ops->stat == NULL) {
891 8 : return -1;
892 : }
893 :
894 32999 : return (stream->ops->stat)(stream, ssb TSRMLS_CC);
895 : }
896 :
897 : PHPAPI void *php_stream_locate_eol(php_stream *stream, zstr zbuf, int buf_len TSRMLS_DC)
898 1376952 : {
899 : size_t avail;
900 1376952 : char *cr, *lf, *eol = NULL;
901 1376952 : char *readptr, *buf = zbuf.s;
902 :
903 1376952 : if (!buf) {
904 1376847 : readptr = stream->readbuf.s + PS_ULEN(stream->readbuf_type == IS_UNICODE, stream->readpos);
905 1376847 : avail = stream->writepos - stream->readpos;
906 : } else {
907 105 : readptr = zbuf.s;
908 105 : avail = buf_len;
909 : }
910 :
911 1376952 : if (stream->flags & PHP_STREAM_FLAG_DETECT_EOL) {
912 0 : if (stream->readbuf_type == IS_UNICODE) {
913 0 : cr = (char*)u_memchr((UChar*)readptr, '\r', avail);
914 0 : lf = (char*)u_memchr((UChar*)readptr, '\n', avail);
915 : } else {
916 0 : cr = memchr(readptr, '\r', avail);
917 0 : lf = memchr(readptr, '\n', avail);
918 : }
919 :
920 0 : if (cr && lf != cr + 1 && !(lf && lf < cr)) {
921 : /* mac */
922 0 : stream->flags ^= PHP_STREAM_FLAG_DETECT_EOL;
923 0 : stream->flags |= PHP_STREAM_FLAG_EOL_MAC;
924 0 : eol = cr;
925 0 : } else if ((cr && lf && cr == lf - 1) || (lf)) {
926 : /* dos or unix endings */
927 0 : stream->flags ^= PHP_STREAM_FLAG_DETECT_EOL;
928 0 : eol = lf;
929 : }
930 1376952 : } else if (stream->flags & PHP_STREAM_FLAG_EOL_MAC) {
931 0 : eol = (stream->readbuf_type == IS_UNICODE) ? u_memchr((UChar*)readptr, '\r', avail) : memchr(readptr, '\r', avail);
932 : } else {
933 : /* unix (and dos) line endings */
934 1376952 : eol = (stream->readbuf_type == IS_UNICODE) ? u_memchr((UChar*)readptr, '\n', avail) : memchr(readptr, '\n', avail);
935 : }
936 :
937 1376952 : return (void*)eol;
938 : }
939 :
940 : /* If buf == NULL, the buffer will be allocated automatically and will be of an
941 : * appropriate length to hold the line, regardless of the line length, memory
942 : * permitting -- returned string will be up to (maxlen-1) units of (maxchars) characters, last byte holding terminating NULL
943 : * Like php_stream_read(), this will (UTODO) treat unicode streams as ugly binary data (use with caution) */
944 : PHPAPI void *_php_stream_get_line(php_stream *stream, int buf_type, zstr buf, size_t maxlen, size_t maxchars, size_t *returned_len TSRMLS_DC)
945 1336979 : {
946 1336979 : size_t avail = 0;
947 1336979 : size_t current_buf_size = 0;
948 1336979 : size_t total_copied = 0;
949 1336979 : int grow_mode = 0;
950 1336979 : int is_unicode = stream->readbuf_type == IS_UNICODE;
951 1336979 : int split_surrogate = 0;
952 1336979 : zstr bufstart = buf;
953 :
954 1336979 : if ((buf_type == IS_STRING && is_unicode) ||
955 : (buf_type == IS_UNICODE && !is_unicode)) {
956 : /* UTODO: Allow sloppy conversion */
957 0 : return NULL;
958 : }
959 :
960 1336979 : if (buf.v == NULL) {
961 1014048 : grow_mode = 1;
962 322931 : } else if (maxlen == 0) {
963 0 : return NULL;
964 : }
965 :
966 : /*
967 : * If the underlying stream operations block when no new data is readable,
968 : * we need to take extra precautions.
969 : *
970 : * If there is buffered data available, we check for a EOL. If it exists,
971 : * we pass the data immediately back to the caller. This saves a call
972 : * to the read implementation and will not block where blocking
973 : * is not necessary at all.
974 : *
975 : * If the stream buffer contains more data than the caller requested,
976 : * we can also avoid that costly step and simply return that data.
977 : */
978 :
979 : for (;;) {
980 1438122 : avail = stream->writepos - stream->readpos;
981 :
982 1481586 : if (!split_surrogate && avail > 0) {
983 1376847 : size_t cpysz = avail;
984 : zstr readptr;
985 1376847 : int done = 0;
986 :
987 1376847 : if (is_unicode) {
988 : UChar *eol;
989 1005261 : readptr.u = stream->readbuf.u + stream->readpos;
990 :
991 1005261 : eol = php_stream_locate_eol(stream, NULL_ZSTR, 0 TSRMLS_CC);
992 1005261 : if (eol) {
993 1003223 : cpysz = eol - readptr.u + 1;
994 1003223 : done = 1;
995 : }
996 :
997 1005261 : if (U16_IS_SURROGATE(readptr.u[cpysz - 1]) &&
998 : U16_IS_SURROGATE_LEAD(readptr.u[cpysz - 1])) {
999 : /* Don't orphan */
1000 0 : cpysz--;
1001 0 : if (!cpysz) {
1002 : /* Force the loop to land on fill_read_buffer */
1003 0 : split_surrogate = 1; /* must specifically be 1 */
1004 0 : continue;
1005 : }
1006 : }
1007 : } else {
1008 : char *eol;
1009 371586 : readptr.s = stream->readbuf.s + stream->readpos;
1010 371586 : eol = php_stream_locate_eol(stream, NULL_ZSTR, 0 TSRMLS_CC);
1011 371586 : if (eol) {
1012 330072 : cpysz = eol - readptr.s + 1;
1013 330072 : done = 1;
1014 : }
1015 : }
1016 :
1017 1376847 : if (grow_mode) {
1018 : /* allow room for a NUL. If this realloc is really a realloc
1019 : * (ie: second time around), we get an extra byte. In most
1020 : * cases, with the default chunk size of 8K, we will only
1021 : * incur that overhead once. When people have lines longer
1022 : * than 8K, we waste 1 byte per additional 8K or so.
1023 : * That seems acceptable to me, to avoid making this code
1024 : * hard to follow */
1025 1013222 : bufstart.s = erealloc(bufstart.s, PS_ULEN(stream->readbuf_type == IS_UNICODE, current_buf_size + cpysz + 1));
1026 1013222 : buf.s = bufstart.s + PS_ULEN(stream->readbuf_type == IS_UNICODE, total_copied);
1027 1013222 : current_buf_size += cpysz + 1;
1028 : } else {
1029 363625 : if (cpysz >= maxlen - 1) {
1030 579 : cpysz = maxlen - 1;
1031 579 : done = 1;
1032 : }
1033 : }
1034 :
1035 1376847 : if (is_unicode) {
1036 1005261 : if (maxchars) {
1037 1005018 : int ulen = u_countChar32(readptr.u, cpysz);
1038 :
1039 1005018 : if (ulen > maxchars) {
1040 0 : int32_t i = 0;
1041 :
1042 0 : ulen = maxchars;
1043 0 : U16_FWD_N(readptr.u, i, cpysz, ulen);
1044 0 : cpysz = i;
1045 : }
1046 1005018 : maxchars -= ulen;
1047 : }
1048 1005261 : memcpy(buf.u, readptr.u, UBYTES(cpysz));
1049 1005261 : buf.u += cpysz;
1050 : } else {
1051 371586 : if (maxchars && cpysz > maxchars) {
1052 0 : cpysz = maxchars;
1053 : }
1054 371586 : memcpy(buf.s, readptr.s, cpysz);
1055 371586 : buf.s += cpysz;
1056 : }
1057 :
1058 1376847 : stream->position += cpysz;
1059 1376847 : stream->readpos += cpysz;
1060 1376847 : maxlen -= cpysz;
1061 1376847 : total_copied += cpysz;
1062 :
1063 1376847 : if (done) {
1064 1333383 : break;
1065 : }
1066 61275 : } else if (stream->eof) {
1067 1808 : break;
1068 : } else {
1069 : /* XXX: Should be fine to always read chunk_size */
1070 : size_t toread;
1071 :
1072 59467 : if (grow_mode) {
1073 13963 : toread = stream->chunk_size;
1074 : } else {
1075 45504 : toread = maxlen - 1;
1076 45504 : if (toread > stream->chunk_size) {
1077 0 : toread = stream->chunk_size;
1078 : }
1079 : }
1080 :
1081 59467 : php_stream_fill_read_buffer(stream, toread TSRMLS_CC);
1082 :
1083 59467 : if (stream->writepos - stream->readpos <= split_surrogate) {
1084 1788 : break;
1085 : }
1086 57679 : split_surrogate = 0;
1087 : }
1088 101143 : }
1089 :
1090 1336979 : if (returned_len) {
1091 1326656 : *returned_len = total_copied;
1092 : }
1093 :
1094 1336979 : if (total_copied == 0) {
1095 : assert(stream->eof || !grow_mode ||
1096 : (grow_mode && bufstart.v == NULL));
1097 1928 : return NULL;
1098 : }
1099 :
1100 1335051 : if (is_unicode) {
1101 1004763 : buf.u[0] = 0;
1102 : } else {
1103 330288 : buf.s[0] = 0;
1104 : }
1105 :
1106 1335051 : return bufstart.s;
1107 : }
1108 :
1109 : PHPAPI char *php_stream_get_record(php_stream *stream, size_t maxlen, size_t *returned_len, char *delim, size_t delim_len TSRMLS_DC)
1110 37 : {
1111 : char *e, *buf;
1112 : size_t toread, len;
1113 37 : int skip = 0;
1114 :
1115 37 : len = stream->writepos - stream->readpos;
1116 :
1117 82 : while (len < maxlen) {
1118 :
1119 : size_t just_read;
1120 32 : toread = MIN(maxlen - len, stream->chunk_size);
1121 :
1122 32 : php_stream_fill_read_buffer(stream, len + toread TSRMLS_CC);
1123 :
1124 32 : just_read = (stream->writepos - stream->readpos) - len;
1125 32 : len += just_read;
1126 :
1127 32 : if (just_read < toread) {
1128 24 : break;
1129 : }
1130 : }
1131 :
1132 37 : if (delim_len == 0 || !delim) {
1133 0 : toread = maxlen;
1134 : } else {
1135 : size_t seek_len;
1136 :
1137 37 : seek_len = stream->writepos - stream->readpos;
1138 37 : if (seek_len > maxlen) {
1139 11 : seek_len = maxlen;
1140 : }
1141 :
1142 37 : if (delim_len == 1) {
1143 12 : e = memchr(stream->readbuf.s + stream->readpos, *delim, seek_len);
1144 : } else {
1145 25 : e = php_memnstr(stream->readbuf.s + stream->readpos, delim, delim_len, (stream->readbuf.s + stream->readpos + seek_len));
1146 : }
1147 :
1148 37 : if (!e) {
1149 14 : if (seek_len < maxlen && !stream->eof) {
1150 6 : return NULL;
1151 : }
1152 8 : toread = maxlen;
1153 : } else {
1154 23 : toread = e - stream->readbuf.s - stream->readpos;
1155 23 : skip = 1;
1156 : }
1157 : }
1158 :
1159 31 : if (toread > maxlen && maxlen > 0) {
1160 0 : toread = maxlen;
1161 : }
1162 :
1163 31 : buf = emalloc(toread + 1);
1164 31 : *returned_len = php_stream_read(stream, buf, toread);
1165 :
1166 : if (*returned_len >= 0) {
1167 31 : if (skip) {
1168 23 : stream->readpos += delim_len;
1169 23 : stream->position += delim_len;
1170 : }
1171 31 : buf[*returned_len] = '\0';
1172 31 : return buf;
1173 : } else {
1174 : efree(buf);
1175 : return NULL;
1176 : }
1177 : }
1178 :
1179 : PHPAPI UChar *php_stream_get_record_unicode(php_stream *stream, size_t maxlen, size_t maxchars, size_t *returned_len, UChar *delim, size_t delim_len TSRMLS_DC)
1180 0 : {
1181 : UChar *e, *buf;
1182 : size_t toread;
1183 0 : int skip = 0;
1184 :
1185 0 : if (stream->readbuf_type != IS_UNICODE) {
1186 0 : return NULL;
1187 : }
1188 :
1189 0 : php_stream_fill_read_buffer(stream, maxlen TSRMLS_CC);
1190 :
1191 0 : if (delim_len == 0 || !delim) {
1192 0 : toread = maxlen;
1193 : } else {
1194 : size_t seek_len;
1195 :
1196 0 : seek_len = stream->writepos - stream->readpos;
1197 0 : if (seek_len > maxlen) {
1198 0 : seek_len = maxlen;
1199 : }
1200 :
1201 0 : if (delim_len == 1) {
1202 0 : e = u_memchr(stream->readbuf.u + stream->readpos, *delim, seek_len);
1203 : } else {
1204 0 : e = u_strFindFirst(stream->readbuf.u + stream->readpos, stream->readbuf.u + stream->readpos + seek_len, delim, delim_len);
1205 : }
1206 :
1207 0 : if (!e) {
1208 0 : toread = maxlen;
1209 : } else {
1210 0 : toread = e - (stream->readbuf.u + stream->readpos);
1211 0 : skip = 1;
1212 : }
1213 : }
1214 :
1215 0 : if (toread > maxlen && maxlen > 0) {
1216 0 : toread = maxlen;
1217 : }
1218 :
1219 0 : if (U16_IS_SURROGATE(stream->readbuf.u[stream->readpos + toread - 1]) &&
1220 : U16_IS_SURROGATE_LEAD(stream->readbuf.u[stream->readpos + toread - 1])) {
1221 : /* Don't orphan */
1222 0 : toread--;
1223 : }
1224 :
1225 0 : if (maxchars > 0) {
1226 0 : size_t ulen = u_countChar32(stream->readbuf.u + stream->readpos, toread);
1227 :
1228 0 : if (maxchars > ulen) {
1229 0 : int i = 0;
1230 0 : UChar *s = stream->readbuf.u + stream->readpos;
1231 :
1232 0 : U16_FWD_N(s, i, toread, maxchars);
1233 0 : toread = i;
1234 : }
1235 : }
1236 :
1237 0 : buf = eumalloc(toread + 1);
1238 0 : *returned_len = php_stream_read_unicode(stream, buf, toread);
1239 :
1240 : if (*returned_len >= 0) {
1241 0 : if (skip) {
1242 0 : stream->readpos += delim_len;
1243 0 : stream->position += delim_len;
1244 : }
1245 0 : buf[*returned_len] = 0;
1246 0 : return buf;
1247 : } else {
1248 : efree(buf);
1249 : return NULL;
1250 : }
1251 : }
1252 :
1253 : /* Writes a buffer directly to a stream, using multiple of the chunk size */
1254 : static size_t _php_stream_write_buffer(php_stream *stream, int buf_type, zstr buf, int buflen TSRMLS_DC)
1255 205301 : {
1256 205301 : size_t didwrite = 0, towrite, justwrote, shouldwrite;
1257 205301 : char *freeme = NULL;
1258 205301 : void *buf_orig = buf.v;
1259 205301 : int buflen_orig = buflen, conv_err = 0;
1260 :
1261 : /* if we have a seekable stream we need to ensure that data is written at the
1262 : * current stream->position. This means invalidating the read buffer and then
1263 : * performing a low-level seek */
1264 205301 : if (stream->ops->seek && (stream->flags & PHP_STREAM_FLAG_NO_SEEK) == 0 && stream->readpos != stream->writepos) {
1265 1 : stream->readpos = stream->writepos = 0;
1266 :
1267 1 : stream->ops->seek(stream, stream->position, SEEK_SET, &stream->position TSRMLS_CC);
1268 : }
1269 :
1270 205301 : if (buf_type == IS_UNICODE) {
1271 8752 : int len, num_conv, ulen = u_countChar32(buf.u, buflen);
1272 : char *str;
1273 8752 : UErrorCode status = U_ZERO_ERROR;
1274 :
1275 : /* Use runtime_encoding to map to binary */
1276 8752 : num_conv = zend_unicode_to_string_ex(ZEND_U_CONVERTER(UG(runtime_encoding_conv)), &str, &len, buf.u, buflen, &status);
1277 8752 : if (U_FAILURE(status)) {
1278 0 : zend_raise_conversion_error_ex("Unable to convert data to be written", ZEND_U_CONVERTER(UG(runtime_encoding_conv)),
1279 : ZEND_FROM_UNICODE, num_conv TSRMLS_CC);
1280 : } else {
1281 8752 : php_error_docref(NULL TSRMLS_CC, E_NOTICE, "%d character unicode buffer downcoded for binary stream runtime_encoding", ulen);
1282 : }
1283 :
1284 8752 : if (num_conv < buflen) {
1285 0 : conv_err = 1;
1286 : }
1287 :
1288 8752 : freeme = buf.s = str;
1289 8752 : buflen = len;
1290 : }
1291 :
1292 205301 : shouldwrite = buflen;
1293 :
1294 617069 : while (buflen > 0) {
1295 206538 : towrite = buflen;
1296 206538 : if (towrite > stream->chunk_size) {
1297 1233 : towrite = stream->chunk_size;
1298 : }
1299 :
1300 206538 : justwrote = stream->ops->write(stream, buf.s, towrite TSRMLS_CC);
1301 :
1302 : /* convert justwrote to an integer, since normally it is unsigned */
1303 206538 : if ((int)justwrote > 0) {
1304 206467 : buf.s += justwrote;
1305 206467 : buflen -= justwrote;
1306 206467 : didwrite += justwrote;
1307 :
1308 : /* Only screw with the buffer if we can seek, otherwise we lose data
1309 : * buffered from fifos and sockets */
1310 206467 : if (stream->ops->seek && (stream->flags & PHP_STREAM_FLAG_NO_SEEK) == 0) {
1311 175016 : stream->position += justwrote;
1312 : }
1313 : } else {
1314 71 : break;
1315 : }
1316 : }
1317 :
1318 205301 : if (buf_type == IS_UNICODE) {
1319 : /* Map bytes written back to UChars written */
1320 :
1321 17499 : if (shouldwrite == didwrite && !conv_err) {
1322 : /* wrote it all */
1323 8747 : didwrite = buflen_orig;
1324 : } else {
1325 : /* Figure out how didwrite corresponds to the input buffer */
1326 5 : char *tmp = emalloc(didwrite + 1), *t = tmp;
1327 5 : const UChar *s = buf_orig;
1328 5 : UErrorCode status = U_ZERO_ERROR;
1329 :
1330 5 : ucnv_resetFromUnicode(ZEND_U_CONVERTER(UG(runtime_encoding_conv)));
1331 5 : ucnv_fromUnicode(ZEND_U_CONVERTER(UG(runtime_encoding_conv)), &t, t + didwrite, &s, s + buflen_orig, NULL, TRUE, &status);
1332 :
1333 5 : didwrite = s - ((UChar*)buf_orig);
1334 5 : efree(tmp);
1335 : }
1336 : }
1337 :
1338 205301 : if (freeme) {
1339 8752 : efree(freeme);
1340 : }
1341 :
1342 205301 : return didwrite;
1343 : }
1344 :
1345 : /* push some data through the write filter chain.
1346 : * buf may be NULL, if flags are set to indicate a flush.
1347 : * This may trigger a real write to the stream.
1348 : * Returns the number of bytes consumed from buf by the first filter in the chain.
1349 : * */
1350 : static size_t _php_stream_write_filtered(php_stream *stream, int buf_type, zstr buf, int count, int flags TSRMLS_DC)
1351 11235 : {
1352 11235 : size_t consumed = 0;
1353 : php_stream_bucket *bucket;
1354 11235 : php_stream_bucket_brigade brig_in = { NULL, NULL }, brig_out = { NULL, NULL };
1355 11235 : php_stream_bucket_brigade *brig_inp = &brig_in, *brig_outp = &brig_out, *brig_swap;
1356 11235 : php_stream_filter_status_t status = PSFS_ERR_FATAL;
1357 : php_stream_filter *filter;
1358 :
1359 11235 : if (buf.v) {
1360 3424 : if (buf_type == IS_UNICODE) {
1361 2659 : bucket = php_stream_bucket_new_unicode(stream, buf.u, count, 0, 0 TSRMLS_CC);
1362 : } else {
1363 765 : bucket = php_stream_bucket_new(stream, buf.s, count, 0, 0 TSRMLS_CC);
1364 : }
1365 3424 : php_stream_bucket_append(brig_inp, bucket TSRMLS_CC);
1366 : }
1367 :
1368 14632 : for (filter = stream->writefilters.head; filter; filter = filter->next) {
1369 : /* for our return value, we are interested in the number of bytes consumed from
1370 : * the first filter in the chain */
1371 11248 : status = filter->fops->filter(stream, filter, brig_inp, brig_outp, (filter == stream->writefilters.head) ? &consumed : NULL, flags TSRMLS_CC);
1372 11248 : if (status != PSFS_PASS_ON) {
1373 7851 : break;
1374 : }
1375 : /* brig_out becomes brig_in.
1376 : * brig_in will always be empty here, as the filter MUST attach any un-consumed buckets
1377 : * to its own brigade */
1378 3397 : brig_swap = brig_inp;
1379 3397 : brig_inp = brig_outp;
1380 3397 : brig_outp = brig_swap;
1381 3397 : memset(brig_outp, 0, sizeof(*brig_outp));
1382 : }
1383 :
1384 11235 : switch (status) {
1385 : case PSFS_PASS_ON:
1386 : /* filter chain generated some output; push it through to the
1387 : * underlying stream */
1388 10302 : while (brig_inp->head) {
1389 3534 : bucket = brig_inp->head;
1390 3534 : _php_stream_write_buffer(stream, bucket->buf_type, bucket->buf, bucket->buflen TSRMLS_CC);
1391 : /* Potential error situation - eg: no space on device. Perhaps we should keep this brigade
1392 : * hanging around and try to write it later.
1393 : * At the moment, we just drop it on the floor
1394 : * */
1395 :
1396 3534 : php_stream_bucket_unlink(bucket TSRMLS_CC);
1397 3534 : php_stream_bucket_delref(bucket TSRMLS_CC);
1398 : }
1399 : break;
1400 : case PSFS_FEED_ME:
1401 : /* need more data before we can push data through to the stream */
1402 : break;
1403 :
1404 : case PSFS_ERR_FATAL:
1405 : /* some fatal error. Theoretically, the stream is borked, so all
1406 : * further writes should fail. */
1407 : break;
1408 : }
1409 :
1410 11235 : return consumed;
1411 : }
1412 :
1413 : PHPAPI int _php_stream_flush(php_stream *stream, int closing TSRMLS_DC)
1414 194101 : {
1415 194101 : int ret = 0;
1416 :
1417 194101 : if (stream->writefilters.head) {
1418 7811 : _php_stream_write_filtered(stream, IS_STRING, NULL_ZSTR, 0, closing ? PSFS_FLAG_FLUSH_CLOSE : PSFS_FLAG_FLUSH_INC TSRMLS_CC);
1419 : }
1420 :
1421 194101 : if (stream->ops->flush) {
1422 190788 : ret = stream->ops->flush(stream TSRMLS_CC);
1423 : }
1424 :
1425 194101 : return ret;
1426 : }
1427 :
1428 : PHPAPI size_t _php_stream_write(php_stream *stream, const char *buf, size_t count TSRMLS_DC)
1429 201717 : {
1430 201717 : if (buf == NULL || count == 0 || stream->ops->write == NULL) {
1431 7936 : return 0;
1432 : }
1433 :
1434 193781 : if (stream->writefilters.head) {
1435 765 : return _php_stream_write_filtered(stream, IS_STRING, ZSTR((void*)buf), count, PSFS_FLAG_NORMAL TSRMLS_CC);
1436 : } else {
1437 193016 : return _php_stream_write_buffer(stream, IS_STRING, ZSTR((void*)buf), count TSRMLS_CC);
1438 : }
1439 : }
1440 :
1441 : PHPAPI size_t _php_stream_write_unicode(php_stream *stream, const UChar *buf, int count TSRMLS_DC)
1442 11464 : {
1443 : int32_t ret;
1444 :
1445 11464 : if (buf == NULL || count == 0 || stream->ops->write == NULL) {
1446 54 : return 0;
1447 : }
1448 :
1449 11410 : if (stream->writefilters.head) {
1450 2659 : ret = _php_stream_write_filtered(stream, IS_UNICODE, ZSTR((void*)buf), count, PSFS_FLAG_NORMAL TSRMLS_CC);
1451 : } else {
1452 8751 : ret = _php_stream_write_buffer(stream, IS_UNICODE, ZSTR((void*)buf), count TSRMLS_CC);
1453 : }
1454 :
1455 11410 : return ret;
1456 : }
1457 :
1458 : PHPAPI size_t _php_stream_printf(php_stream *stream TSRMLS_DC, const char *fmt, ...)
1459 130 : {
1460 : size_t count;
1461 : char *buf;
1462 : va_list ap;
1463 :
1464 130 : va_start(ap, fmt);
1465 130 : count = vspprintf(&buf, 0, fmt, ap);
1466 130 : va_end(ap);
1467 :
1468 130 : if (!buf) {
1469 0 : return 0; /* error condition */
1470 : }
1471 :
1472 130 : count = php_stream_write(stream, buf, count);
1473 130 : efree(buf);
1474 :
1475 130 : return count;
1476 : }
1477 :
1478 : PHPAPI off_t _php_stream_tell(php_stream *stream TSRMLS_DC)
1479 61924 : {
1480 61924 : return stream->position;
1481 : }
1482 :
1483 : PHPAPI int _php_stream_seek(php_stream *stream, off_t offset, int whence TSRMLS_DC)
1484 72719 : {
1485 : /* handle the case where we are in the buffer */
1486 72719 : if ((stream->flags & PHP_STREAM_FLAG_NO_BUFFER) == 0) {
1487 72042 : switch(whence) {
1488 : case SEEK_CUR:
1489 17767 : if (offset > 0 && offset < stream->writepos - stream->readpos) {
1490 436 : stream->readpos += offset;
1491 436 : stream->position += offset;
1492 436 : stream->eof = 0;
1493 436 : return 0;
1494 : }
1495 17331 : break;
1496 : case SEEK_SET:
1497 51948 : if (offset > stream->position &&
1498 : offset < stream->position + stream->writepos - stream->readpos) {
1499 811 : stream->readpos += offset - stream->position;
1500 811 : stream->position = offset;
1501 811 : stream->eof = 0;
1502 811 : return 0;
1503 : }
1504 : break;
1505 : }
1506 : }
1507 :
1508 :
1509 71472 : if (stream->ops->seek && (stream->flags & PHP_STREAM_FLAG_NO_SEEK) == 0) {
1510 : int ret;
1511 :
1512 71470 : if (stream->writefilters.head) {
1513 1652 : _php_stream_flush(stream, 0 TSRMLS_CC);
1514 : }
1515 :
1516 71470 : switch(whence) {
1517 : case SEEK_CUR:
1518 17339 : offset = stream->position + offset;
1519 17339 : whence = SEEK_SET;
1520 : break;
1521 : }
1522 71470 : ret = stream->ops->seek(stream, offset, whence, &stream->position TSRMLS_CC);
1523 :
1524 71470 : if (((stream->flags & PHP_STREAM_FLAG_NO_SEEK) == 0) || ret == 0) {
1525 71470 : if (ret == 0) {
1526 71263 : stream->eof = 0;
1527 : }
1528 :
1529 : /* invalidate the buffer contents */
1530 71470 : stream->readpos = stream->writepos = 0;
1531 :
1532 71470 : return ret;
1533 : }
1534 : /* else the stream has decided that it can't support seeking after all;
1535 : * fall through to attempt emulation */
1536 : }
1537 :
1538 : /* emulate forward moving seeks with reads */
1539 2 : if (whence == SEEK_CUR && offset > 0) {
1540 : char tmp[1024];
1541 0 : while(offset >= sizeof(tmp)) {
1542 0 : if (php_stream_read(stream, tmp, sizeof(tmp)) == 0) {
1543 0 : return -1;
1544 : }
1545 0 : offset -= sizeof(tmp);
1546 : }
1547 0 : if (offset && (php_stream_read(stream, tmp, offset) == 0)) {
1548 0 : return -1;
1549 : }
1550 0 : stream->eof = 0;
1551 0 : return 0;
1552 : }
1553 :
1554 2 : php_error_docref(NULL TSRMLS_CC, E_WARNING, "stream does not support seeking");
1555 :
1556 2 : return -1;
1557 : }
1558 :
1559 : PHPAPI int _php_stream_set_option(php_stream *stream, int option, int value, void *ptrparam TSRMLS_DC)
1560 97537 : {
1561 97537 : int ret = PHP_STREAM_OPTION_RETURN_NOTIMPL;
1562 :
1563 97537 : if (stream->ops->set_option) {
1564 96848 : ret = stream->ops->set_option(stream, option, value, ptrparam TSRMLS_CC);
1565 : }
1566 :
1567 97537 : if (ret == PHP_STREAM_OPTION_RETURN_NOTIMPL) {
1568 18981 : switch(option) {
1569 : case PHP_STREAM_OPTION_SET_CHUNK_SIZE:
1570 0 : ret = stream->chunk_size;
1571 0 : stream->chunk_size = value;
1572 0 : return ret;
1573 :
1574 : case PHP_STREAM_OPTION_READ_BUFFER:
1575 : /* try to match the buffer mode as best we can */
1576 0 : if (value == PHP_STREAM_BUFFER_NONE) {
1577 0 : stream->flags |= PHP_STREAM_FLAG_NO_BUFFER;
1578 : } else {
1579 0 : stream->flags ^= PHP_STREAM_FLAG_NO_BUFFER;
1580 : }
1581 0 : ret = PHP_STREAM_OPTION_RETURN_OK;
1582 : break;
1583 :
1584 : default:
1585 : ;
1586 : }
1587 : }
1588 :
1589 97537 : return ret;
1590 : }
1591 :
1592 : PHPAPI int _php_stream_truncate_set_size(php_stream *stream, size_t newsize TSRMLS_DC)
1593 398 : {
1594 398 : return php_stream_set_option(stream, PHP_STREAM_OPTION_TRUNCATE_API, PHP_STREAM_TRUNCATE_SET_SIZE, &newsize);
1595 : }
1596 :
1597 : PHPAPI size_t _php_stream_passthru(php_stream * stream STREAMS_DC TSRMLS_DC)
1598 528 : {
1599 528 : size_t count = 0;
1600 :
1601 528 : if (php_stream_mmap_possible(stream)) {
1602 : /* mmap_possible == non-filtered stream == binary stream */
1603 : char *p;
1604 : size_t mapped;
1605 :
1606 458 : p = php_stream_mmap_range(stream, php_stream_tell(stream), PHP_STREAM_MMAP_ALL, PHP_STREAM_MAP_MODE_SHARED_READONLY, &mapped);
1607 :
1608 458 : if (p) {
1609 356 : PHPWRITE(p, mapped);
1610 :
1611 356 : php_stream_mmap_unmap_ex(stream, mapped);
1612 :
1613 356 : return mapped;
1614 : }
1615 : }
1616 :
1617 172 : if (stream->readbuf_type == IS_UNICODE) {
1618 : UChar inbuf_start[8192];
1619 14 : UConverter *conv = ZEND_U_CONVERTER(UG(output_encoding_conv));
1620 14 : int outbuflen = UCNV_GET_MAX_BYTES_FOR_STRING(8192, ucnv_getMaxCharSize(conv));
1621 14 : char *outbuf_start = emalloc(outbuflen + 1);
1622 : int b;
1623 :
1624 14 : ucnv_resetFromUnicode(conv);
1625 :
1626 42 : while ((b = php_stream_read_unicode(stream, inbuf_start, 8192)) > 0) {
1627 14 : char *outbuf = outbuf_start;
1628 14 : const UChar *inbuf = inbuf_start;
1629 14 : UErrorCode status = U_ZERO_ERROR;
1630 : int len;
1631 :
1632 14 : ucnv_fromUnicode(conv, &outbuf, outbuf + outbuflen, &inbuf, inbuf + b, NULL, TRUE, &status);
1633 14 : len = u_countChar32(inbuf_start, inbuf - inbuf_start);
1634 14 : if (U_FAILURE(status)) {
1635 : /* Memory overflow isn't a problem becuase MAX_BYTES_FOR_STRING was allocated,
1636 : anything else is a more serious problem */
1637 0 : zend_raise_conversion_error_ex("Unable to convert Unicode character using output_encoding, at least one character was lost",
1638 : conv, ZEND_FROM_UNICODE, len TSRMLS_CC);
1639 : }
1640 14 : if (outbuf > outbuf_start) {
1641 14 : PHPWRITE(outbuf_start, outbuf - outbuf_start);
1642 14 : count += len;
1643 : }
1644 : }
1645 14 : efree(outbuf_start);
1646 : } else {
1647 : char buf[8192];
1648 : int b;
1649 :
1650 389 : while ((b = php_stream_read(stream, buf, sizeof(buf))) > 0) {
1651 73 : PHPWRITE(buf, b);
1652 73 : count += b;
1653 : }
1654 : }
1655 :
1656 172 : return count;
1657 : }
1658 :
1659 :
1660 : PHPAPI size_t _php_stream_copy_to_mem_ex(php_stream *src, zend_uchar rettype, void **buf, size_t maxlen, size_t maxchars, int persistent STREAMS_DC TSRMLS_DC)
1661 2565 : {
1662 2565 : size_t ret = 0;
1663 : zstr ptr;
1664 2565 : size_t len = 0, max_len;
1665 2565 : int step = CHUNK_SIZE;
1666 2565 : int min_room = CHUNK_SIZE / 4;
1667 : php_stream_statbuf ssbuf;
1668 :
1669 2565 : if (rettype != src->readbuf_type) {
1670 : /* UTODO: Introduce sloppy buffer conversion */
1671 0 : return 0;
1672 : }
1673 :
1674 2565 : if (maxlen == 0) {
1675 9 : return 0;
1676 : }
1677 :
1678 2556 : if (maxlen == PHP_STREAM_COPY_ALL) {
1679 2483 : maxlen = 0;
1680 : }
1681 :
1682 2556 : if (maxlen > 0) {
1683 73 : if (rettype == IS_UNICODE) {
1684 0 : ptr.u = *buf = pemalloc_rel_orig(UBYTES(maxlen + 1), persistent);
1685 0 : while ((len < maxlen) && !php_stream_eof(src)) {
1686 : int ulen;
1687 :
1688 0 : ret = php_stream_read_unicode_ex(src, ptr.u, maxlen - len, maxchars);
1689 0 : ulen = u_countChar32(ptr.u, ret);
1690 0 : len += ret;
1691 0 : ptr.u += ret;
1692 0 : maxchars -= ret;
1693 : }
1694 0 : *(ptr.u) = 0;
1695 0 : return len;
1696 : } else {
1697 73 : ptr.s = *buf = pemalloc_rel_orig(maxlen + 1, persistent);
1698 218 : while ((len < maxlen) && !php_stream_eof(src)) {
1699 72 : ret = php_stream_read(src, ptr.s, maxlen - len);
1700 72 : len += ret;
1701 72 : ptr.s += ret;
1702 : }
1703 73 : *(ptr.s) = 0;
1704 73 : return len;
1705 : }
1706 : }
1707 :
1708 : /* avoid many reallocs by allocating a good sized chunk to begin with, if
1709 : * we can. Note that the stream may be filtered, in which case the stat
1710 : * result may be inaccurate, as the filter may inflate or deflate the
1711 : * number of bytes that we can read. In order to avoid an upsize followed
1712 : * by a downsize of the buffer, overestimate by the step size (which is
1713 : * 2K). */
1714 4437 : if (php_stream_stat(src, &ssbuf) == 0 && ssbuf.sb.st_size > 0) {
1715 1954 : max_len = ssbuf.sb.st_size + step;
1716 : } else {
1717 529 : max_len = step;
1718 : }
1719 :
1720 2483 : if (rettype == IS_UNICODE) {
1721 129 : ptr.u = *buf = pemalloc_rel_orig(UBYTES(max_len + 1), persistent);
1722 :
1723 383 : while((ret = php_stream_read_unicode_ex(src, ptr.u, max_len - len, maxchars))) {
1724 125 : int ulen = u_countChar32(ptr.u, ret);
1725 :
1726 125 : len += ret;
1727 125 : if (len + min_room >= max_len) {
1728 0 : *buf = perealloc_rel_orig(*buf, UBYTES(max_len + step), persistent);
1729 0 : max_len += step;
1730 0 : ptr.u = ((UChar*)(*buf)) + len;
1731 : } else {
1732 125 : ptr.u += ret;
1733 : }
1734 125 : maxchars -= ulen;
1735 : }
1736 : } else {
1737 2354 : ptr.s = *buf = pemalloc_rel_orig(max_len + 1, persistent);
1738 :
1739 15525 : while((ret = php_stream_read(src, ptr.s, max_len - len))) {
1740 10817 : len += ret;
1741 10817 : if (len + min_room >= max_len) {
1742 18 : *buf = perealloc_rel_orig(*buf, max_len + step, persistent);
1743 18 : max_len += step;
1744 18 : ptr.s = ((char*)(*buf)) + len;
1745 : } else {
1746 10799 : ptr.s += ret;
1747 : }
1748 : }
1749 : }
1750 :
1751 2483 : if (len) {
1752 2081 : if (rettype == IS_UNICODE) {
1753 125 : if ((max_len - len) > (2 * step)) {
1754 0 : *buf = perealloc_rel_orig(*buf, UBYTES(len + 1), persistent);
1755 : }
1756 125 : ((UChar*)(*buf))[len] = 0;
1757 : } else {
1758 1956 : if ((max_len - len) > (2 * step)) {
1759 1 : *buf = perealloc_rel_orig(*buf, len + 1, persistent);
1760 : }
1761 1956 : ((char*)(*buf))[len] = 0;
1762 : }
1763 : } else {
1764 402 : pefree(*buf, persistent);
1765 402 : *buf = NULL;
1766 : }
1767 2483 : return len;
1768 : }
1769 :
1770 : zend_always_inline
1771 : static size_t _php_stream_copy_to_stream_common(php_stream *src, php_stream *dest, size_t maxlen, size_t maxchars, size_t *len, int utype STREAMS_DC TSRMLS_DC)
1772 26734 : {
1773 26734 : size_t haveread = 0;
1774 : php_stream_statbuf ssbuf;
1775 : size_t dummy;
1776 :
1777 26734 : if (!len) {
1778 7221 : len = &dummy;
1779 : }
1780 :
1781 26734 : if (maxlen == 0 || (utype == IS_UNICODE && maxchars == 0)) {
1782 4125 : *len = 0;
1783 4125 : return SUCCESS;
1784 : }
1785 :
1786 22609 : if (maxlen == PHP_STREAM_COPY_ALL) {
1787 12353 : maxlen = 0;
1788 : }
1789 :
1790 22609 : if (php_stream_stat(src, &ssbuf) == 0) {
1791 22607 : if (ssbuf.sb.st_size == 0
1792 : #ifdef S_ISREG
1793 : && S_ISREG(ssbuf.sb.st_mode)
1794 : #endif
1795 : ) {
1796 4122 : *len = 0;
1797 4122 : return SUCCESS;
1798 : }
1799 : }
1800 :
1801 18487 : if (utype == IS_STRING && php_stream_mmap_possible(src)) {
1802 : char *p;
1803 : size_t mapped;
1804 :
1805 18463 : p = php_stream_mmap_range(src, php_stream_tell(src), maxlen, PHP_STREAM_MAP_MODE_SHARED_READONLY, &mapped);
1806 :
1807 18463 : if (p) {
1808 13920 : mapped = php_stream_write(dest, p, mapped);
1809 :
1810 13920 : php_stream_mmap_unmap_ex(src, mapped);
1811 :
1812 13920 : *len = mapped;
1813 :
1814 : /* we've got at least 1 byte to read.
1815 : * less than 1 is an error */
1816 :
1817 13920 : if (mapped > 0) {
1818 13920 : return SUCCESS;
1819 : }
1820 0 : return FAILURE;
1821 : }
1822 : }
1823 :
1824 : while(1) {
1825 : zstr buf;
1826 : union { char s; UChar u; } _buf[CHUNK_SIZE];
1827 4599 : size_t readchunk = sizeof(_buf) / sizeof(_buf[0]);
1828 : size_t didread;
1829 :
1830 4599 : buf.v = _buf;
1831 :
1832 4599 : if (maxlen && (maxlen - haveread) < readchunk) {
1833 4558 : readchunk = maxlen - haveread;
1834 : }
1835 :
1836 4599 : didread = php_stream_u_read_ex(src, utype, buf, readchunk, maxchars);
1837 :
1838 4599 : if (didread) {
1839 : /* extra paranoid */
1840 : size_t didwrite, towrite;
1841 : zstr writeptr;
1842 :
1843 4589 : if (utype == IS_UNICODE && maxchars > 0) {
1844 : /* Determine number of chars in this buf */
1845 0 : maxchars -= u_countChar32(buf.u, didread);
1846 : }
1847 :
1848 4589 : towrite = didread;
1849 4589 : writeptr = buf;
1850 4589 : haveread += didread;
1851 :
1852 13767 : while(towrite) {
1853 4589 : didwrite = php_stream_u_write(dest, utype, writeptr, towrite);
1854 4589 : if (didwrite == 0) {
1855 0 : *len = haveread - (didread - towrite);
1856 0 : return FAILURE;
1857 : }
1858 :
1859 4589 : towrite -= didwrite;
1860 4589 : if (utype == IS_UNICODE) {
1861 0 : writeptr.u += didwrite;
1862 : } else {
1863 4589 : writeptr.s += didwrite;
1864 : }
1865 : }
1866 : } else {
1867 10 : break;
1868 : }
1869 :
1870 4589 : if (maxlen - haveread == 0 || (utype == IS_UNICODE && maxchars == 0)) {
1871 : break;
1872 : }
1873 32 : }
1874 :
1875 4567 : *len = haveread;
1876 :
1877 : /* we've got at least 1 byte to read.
1878 : * less than 1 is an error */
1879 :
1880 4567 : if (haveread > 0) {
1881 4565 : return SUCCESS;
1882 : }
1883 2 : return FAILURE;
1884 : }
1885 :
1886 : /* Designed for copying UChars (taking into account both maxlen and maxchars) */
1887 : PHPAPI size_t _php_stream_ucopy_to_stream_ex(php_stream *src, php_stream *dest, size_t maxlen, size_t maxchars, size_t *len STREAMS_DC TSRMLS_DC)
1888 0 : {
1889 0 : return _php_stream_copy_to_stream_common(src, dest, maxlen, maxchars, len, IS_UNICODE STREAMS_REL_CC TSRMLS_CC);
1890 : }
1891 :
1892 : /* see _php_stream_copy_to_stream() */
1893 : ZEND_ATTRIBUTE_DEPRECATED
1894 : PHPAPI size_t _php_stream_ucopy_to_stream(php_stream *src, php_stream *dest, size_t maxlen, size_t maxchars STREAMS_DC TSRMLS_DC)
1895 0 : {
1896 : size_t len;
1897 0 : int ret = _php_stream_ucopy_to_stream_ex(src, dest, maxlen, maxchars, &len STREAMS_REL_CC TSRMLS_CC);
1898 0 : if (ret == SUCCESS && maxlen != 0 && maxchars != 0) {
1899 0 : return 1;
1900 : }
1901 0 : return len;
1902 : }
1903 :
1904 : /* Optimized for copying octets from source stream */
1905 : /* Returns SUCCESS/FAILURE and sets *len to the number of bytes moved */
1906 : PHPAPI size_t _php_stream_copy_to_stream_ex(php_stream *src, php_stream *dest, size_t maxlen, size_t *len STREAMS_DC TSRMLS_DC)
1907 26734 : {
1908 26734 : return _php_stream_copy_to_stream_common(src, dest, maxlen, 0, len, IS_STRING STREAMS_REL_CC TSRMLS_CC);
1909 : }
1910 :
1911 : /* Returns the number of bytes moved.
1912 : * Returns 1 when source len is 0.
1913 : * Deprecated in favor of php_stream_copy_to_stream_ex() */
1914 : ZEND_ATTRIBUTE_DEPRECATED
1915 : PHPAPI size_t _php_stream_copy_to_stream(php_stream *src, php_stream *dest, size_t maxlen STREAMS_DC TSRMLS_DC)
1916 24 : {
1917 : size_t len;
1918 24 : int ret = _php_stream_copy_to_stream_ex(src, dest, maxlen, &len STREAMS_REL_CC TSRMLS_CC);
1919 24 : if (ret == SUCCESS && len == 0 && maxlen != 0) {
1920 0 : return 1;
1921 : }
1922 24 : return len;
1923 : }
1924 : /* }}} */
1925 :
1926 : /* {{{ wrapper init and registration */
1927 :
1928 : static void stream_resource_regular_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC)
1929 190358 : {
1930 190358 : php_stream *stream = (php_stream*)rsrc->ptr;
1931 : /* set the return value for pclose */
1932 190358 : FG(pclose_ret) = php_stream_free(stream, PHP_STREAM_FREE_CLOSE | PHP_STREAM_FREE_RSRC_DTOR);
1933 190357 : }
1934 :
1935 : static void stream_resource_persistent_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC)
1936 987 : {
1937 987 : php_stream *stream = (php_stream*)rsrc->ptr;
1938 987 : FG(pclose_ret) = php_stream_free(stream, PHP_STREAM_FREE_CLOSE | PHP_STREAM_FREE_RSRC_DTOR);
1939 987 : }
1940 :
1941 : void php_shutdown_stream_hashes(TSRMLS_D)
1942 17025 : {
1943 17025 : if (FG(stream_wrappers)) {
1944 26 : zend_hash_destroy(FG(stream_wrappers));
1945 26 : efree(FG(stream_wrappers));
1946 26 : FG(stream_wrappers) = NULL;
1947 : }
1948 :
1949 17025 : if (FG(stream_filters)) {
1950 8 : zend_hash_destroy(FG(stream_filters));
1951 8 : efree(FG(stream_filters));
1952 8 : FG(stream_filters) = NULL;
1953 : }
1954 17025 : }
1955 :
1956 : int php_init_stream_wrappers(int module_number TSRMLS_DC)
1957 17007 : {
1958 17007 : le_stream = zend_register_list_destructors_ex(stream_resource_regular_dtor, NULL, "stream", module_number);
1959 17007 : le_pstream = zend_register_list_destructors_ex(NULL, stream_resource_persistent_dtor, "persistent stream", module_number);
1960 :
1961 : /* Filters are cleaned up by the streams they're attached to */
1962 17007 : le_stream_filter = zend_register_list_destructors_ex(NULL, NULL, "stream filter", module_number);
1963 :
1964 17007 : return (
1965 : zend_hash_init(&url_stream_wrappers_hash, 0, NULL, NULL, 1) == SUCCESS
1966 : &&
1967 : zend_hash_init(php_get_stream_filters_hash_global(), 0, NULL, NULL, 1) == SUCCESS
1968 : &&
1969 : zend_hash_init(php_stream_xport_get_hash(), 0, NULL, NULL, 1) == SUCCESS
1970 : &&
1971 : php_stream_xport_register("tcp", php_stream_generic_socket_factory TSRMLS_CC) == SUCCESS
1972 : &&
1973 : php_stream_xport_register("udp", php_stream_generic_socket_factory TSRMLS_CC) == SUCCESS
1974 : #if defined(AF_UNIX) && !(defined(PHP_WIN32) || defined(__riscos__) || defined(NETWARE))
1975 : &&
1976 : php_stream_xport_register("unix", php_stream_generic_socket_factory TSRMLS_CC) == SUCCESS
1977 : &&
1978 : php_stream_xport_register("udg", php_stream_generic_socket_factory TSRMLS_CC) == SUCCESS
1979 : #endif
1980 : ) ? SUCCESS : FAILURE;
1981 : }
1982 :
1983 : int php_shutdown_stream_wrappers(int module_number TSRMLS_DC)
1984 17039 : {
1985 17039 : zend_hash_destroy(&url_stream_wrappers_hash);
1986 17039 : zend_hash_destroy(php_get_stream_filters_hash_global());
1987 17039 : zend_hash_destroy(php_stream_xport_get_hash());
1988 17039 : return SUCCESS;
1989 : }
1990 :
1991 : /* Validate protocol scheme names during registration
1992 : * Must conform to /^[a-zA-Z0-9+.-]+$/
1993 : */
1994 : static inline int php_stream_wrapper_scheme_validate(char *protocol, int protocol_len)
1995 306159 : {
1996 : int i;
1997 :
1998 1870988 : for(i = 0; i < protocol_len; i++) {
1999 1564829 : if (!isalnum((int)protocol[i]) &&
2000 : protocol[i] != '+' &&
2001 : protocol[i] != '-' &&
2002 : protocol[i] != '.') {
2003 0 : return FAILURE;
2004 : }
2005 : }
2006 :
2007 306159 : return SUCCESS;
2008 : }
2009 :
2010 : /* API for registering GLOBAL wrappers */
2011 : PHPAPI int php_register_url_stream_wrapper(char *protocol, php_stream_wrapper *wrapper TSRMLS_DC)
2012 306126 : {
2013 306126 : int protocol_len = strlen(protocol);
2014 :
2015 306126 : if (php_stream_wrapper_scheme_validate(protocol, protocol_len) == FAILURE) {
2016 0 : return FAILURE;
2017 : }
2018 :
2019 306126 : return zend_hash_add(&url_stream_wrappers_hash, protocol, protocol_len + 1, &wrapper, sizeof(wrapper), NULL);
2020 : }
2021 :
2022 : PHPAPI int php_unregister_url_stream_wrapper(char *protocol TSRMLS_DC)
2023 323485 : {
2024 323485 : return zend_hash_del(&url_stream_wrappers_hash, protocol, strlen(protocol) + 1);
2025 : }
2026 :
2027 : static void clone_wrapper_hash(TSRMLS_D)
2028 26 : {
2029 : php_stream_wrapper *tmp;
2030 :
2031 26 : ALLOC_HASHTABLE(FG(stream_wrappers));
2032 26 : zend_hash_init(FG(stream_wrappers), zend_hash_num_elements(&url_stream_wrappers_hash), NULL, NULL, 1);
2033 26 : zend_hash_copy(FG(stream_wrappers), &url_stream_wrappers_hash, NULL, &tmp, sizeof(tmp));
2034 26 : }
2035 :
2036 : /* API for registering VOLATILE wrappers */
2037 : PHPAPI int php_register_url_stream_wrapper_volatile(char *protocol, php_stream_wrapper *wrapper TSRMLS_DC)
2038 33 : {
2039 33 : int protocol_len = strlen(protocol);
2040 :
2041 33 : if (php_stream_wrapper_scheme_validate(protocol, protocol_len) == FAILURE) {
2042 0 : return FAILURE;
2043 : }
2044 :
2045 33 : if (!FG(stream_wrappers)) {
2046 25 : clone_wrapper_hash(TSRMLS_C);
2047 : }
2048 :
2049 33 : return zend_hash_add(FG(stream_wrappers), protocol, protocol_len + 1, &wrapper, sizeof(wrapper), NULL);
2050 : }
2051 :
2052 : PHPAPI int php_unregister_url_stream_wrapper_volatile(char *protocol TSRMLS_DC)
2053 2 : {
2054 2 : if (!FG(stream_wrappers)) {
2055 1 : clone_wrapper_hash(TSRMLS_C);
2056 : }
2057 :
2058 2 : return zend_hash_del(FG(stream_wrappers), protocol, strlen(protocol) + 1);
2059 : }
2060 : /* }}} */
2061 :
2062 : /* {{{ php_stream_locate_url_wrapper */
2063 : PHPAPI php_stream_wrapper *php_stream_locate_url_wrapper(const char *path, char **path_for_open, int options TSRMLS_DC)
2064 469928 : {
2065 469928 : HashTable *wrapper_hash = (FG(stream_wrappers) ? FG(stream_wrappers) : &url_stream_wrappers_hash);
2066 469928 : php_stream_wrapper **wrapperpp = NULL;
2067 469928 : const char *p, *protocol = NULL;
2068 469928 : int n = 0;
2069 :
2070 469928 : if (path_for_open) {
2071 300347 : *path_for_open = (char*)path;
2072 : }
2073 :
2074 469928 : if (options & IGNORE_URL) {
2075 5675 : return (options & STREAM_LOCATE_WRAPPERS_ONLY) ? NULL : &php_plain_files_wrapper;
2076 : }
2077 :
2078 666593 : for (p = path; isalnum((int)*p) || *p == '+' || *p == '-' || *p == '.'; p++) {
2079 202340 : n++;
2080 : }
2081 :
2082 517613 : if ((*p == ':') && (n > 1) && (!strncmp("//", p+1, 2) || (n == 4 && !memcmp("data:", path, 5)))) {
2083 53360 : protocol = path;
2084 410893 : } else if (n == 5 && strncasecmp(path, "zlib:", 5) == 0) {
2085 : /* BC with older php scripts and zlib wrapper */
2086 0 : protocol = "compress.zlib";
2087 0 : n = 13;
2088 0 : php_error_docref(NULL TSRMLS_CC, E_WARNING, "Use of \"zlib:\" wrapper is deprecated; please use \"compress.zlib://\" instead");
2089 : }
2090 :
2091 464253 : if (protocol) {
2092 53360 : char *tmp = estrndup(protocol, n);
2093 53360 : if (FAILURE == zend_hash_find(wrapper_hash, (char*)tmp, n + 1, (void**)&wrapperpp)) {
2094 4 : php_strtolower(tmp, n);
2095 4 : if (FAILURE == zend_hash_find(wrapper_hash, (char*)tmp, n + 1, (void**)&wrapperpp)) {
2096 : char wrapper_name[32];
2097 :
2098 2 : if (n >= sizeof(wrapper_name)) {
2099 0 : n = sizeof(wrapper_name) - 1;
2100 : }
2101 2 : PHP_STRLCPY(wrapper_name, protocol, sizeof(wrapper_name), n);
2102 :
2103 2 : php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to find the wrapper \"%s\" - did you forget to enable it when you configured PHP?", wrapper_name);
2104 :
2105 2 : wrapperpp = NULL;
2106 2 : protocol = NULL;
2107 : }
2108 : }
2109 53360 : efree(tmp);
2110 : }
2111 : /* TODO: curl based streams probably support file:// properly */
2112 464253 : if (!protocol || !strncasecmp(protocol, "file", n)) {
2113 : /* fall back on regular file access */
2114 411052 : php_stream_wrapper *plain_files_wrapper = &php_plain_files_wrapper;
2115 :
2116 411052 : if (protocol) {
2117 157 : int localhost = 0;
2118 :
2119 157 : if (!strncasecmp(path, "file://localhost/", 17)) {
2120 0 : localhost = 1;
2121 : }
2122 :
2123 : #ifdef PHP_WIN32
2124 : if (localhost == 0 && path[n+3] != '\0' && path[n+3] != '/' && path[n+4] != ':') {
2125 : #else
2126 157 : if (localhost == 0 && path[n+3] != '\0' && path[n+3] != '/') {
2127 : #endif
2128 18 : if (options & REPORT_ERRORS) {
2129 12 : php_error_docref(NULL TSRMLS_CC, E_WARNING, "remote host file access not supported, %s", path);
2130 : }
2131 18 : return NULL;
2132 : }
2133 :
2134 139 : if (path_for_open) {
2135 : /* skip past protocol and :/, but handle windows correctly */
2136 24 : *path_for_open = (char*)path + n + 1;
2137 24 : if (localhost == 1) {
2138 0 : (*path_for_open) += 11;
2139 : }
2140 73 : while (*(++*path_for_open)=='/');
2141 : #ifdef PHP_WIN32
2142 : if (*(*path_for_open + 1) != ':')
2143 : #endif
2144 24 : (*path_for_open)--;
2145 : }
2146 : }
2147 :
2148 411034 : if (options & STREAM_LOCATE_WRAPPERS_ONLY) {
2149 584 : return NULL;
2150 : }
2151 :
2152 : /* The file:// wrapper may have been disabled/overridden */
2153 410450 : if (FG(stream_wrappers)) {
2154 12 : if (zend_hash_find(wrapper_hash, "file", sizeof("file"), (void**)&wrapperpp) == FAILURE) {
2155 1 : if (options & REPORT_ERRORS) {
2156 1 : php_error_docref(NULL TSRMLS_CC, E_WARNING, "Plainfiles wrapper disabled");
2157 : }
2158 1 : return NULL;
2159 : } else {
2160 : /* Handles overridden plain files wrapper */
2161 11 : plain_files_wrapper = *wrapperpp;
2162 : }
2163 : }
2164 :
2165 410449 : if (!php_stream_allow_url_fopen("file", sizeof("file") - 1) ||
2166 : ((options & STREAM_OPEN_FOR_INCLUDE) && !php_stream_allow_url_include("file", sizeof("file") - 1)) ) {
2167 0 : if (options & REPORT_ERRORS) {
2168 0 : php_error_docref(NULL TSRMLS_CC, E_WARNING, "file:// wrapper is disabled in the server configuration");
2169 : }
2170 0 : return NULL;
2171 : }
2172 :
2173 410449 : return plain_files_wrapper;
2174 : }
2175 :
2176 53201 : if (((options & STREAM_DISABLE_URL_PROTECTION) == 0) &&
2177 : (!php_stream_allow_url_fopen(protocol, n) ||
2178 : ((options & STREAM_OPEN_FOR_INCLUDE) && !php_stream_allow_url_include(protocol, n)))) {
2179 10 : if (options & REPORT_ERRORS) {
2180 : /* protocol[n] probably isn't '\0' */
2181 7 : char *protocol_dup = estrndup(protocol, n);
2182 7 : if (!php_stream_allow_url_fopen(protocol, n)) {
2183 5 : php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s:// wrapper is disabled in the server configuration by allow_url_fopen=0", protocol_dup);
2184 : } else {
2185 2 : php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s:// wrapper is disabled in the server configuration by allow_url_include=0", protocol_dup);
2186 : }
2187 7 : efree(protocol_dup);
2188 : }
2189 10 : return NULL;
2190 : }
2191 :
2192 53191 : return *wrapperpp;
2193 : }
2194 : /* }}} */
2195 :
2196 : /* {{{ _php_stream_mkdir
2197 : */
2198 : PHPAPI int _php_stream_mkdir(char *path, int mode, int options, php_stream_context *context TSRMLS_DC)
2199 1675 : {
2200 1675 : php_stream_wrapper *wrapper = NULL;
2201 :
2202 1675 : wrapper = php_stream_locate_url_wrapper(path, NULL, 0 TSRMLS_CC);
2203 1675 : if (!wrapper || !wrapper->wops || !wrapper->wops->stream_mkdir) {
2204 1 : return 0;
2205 : }
2206 :
2207 1674 : return wrapper->wops->stream_mkdir(wrapper, path, mode, options, context TSRMLS_CC);
2208 : }
2209 : /* }}} */
2210 :
2211 : /* {{{ _php_stream_rmdir
2212 : */
2213 : PHPAPI int _php_stream_rmdir(char *path, int options, php_stream_context *context TSRMLS_DC)
2214 1703 : {
2215 1703 : php_stream_wrapper *wrapper = NULL;
2216 :
2217 1703 : wrapper = php_stream_locate_url_wrapper(path, NULL, 0 TSRMLS_CC);
2218 1703 : if (!wrapper || !wrapper->wops || !wrapper->wops->stream_rmdir) {
2219 1 : return 0;
2220 : }
2221 :
2222 1702 : return wrapper->wops->stream_rmdir(wrapper, path, options, context TSRMLS_CC);
2223 : }
2224 : /* }}} */
2225 :
2226 : /* {{{ _php_stream_stat_path */
2227 : PHPAPI int _php_stream_stat_path(char *path, int flags, php_stream_statbuf *ssb, php_stream_context *context TSRMLS_DC)
2228 87511 : {
2229 87511 : php_stream_wrapper *wrapper = NULL;
2230 87511 : char *path_to_open = path;
2231 : int ret;
2232 :
2233 : /* Try to hit the cache first */
2234 87511 : if (flags & PHP_STREAM_URL_STAT_LINK) {
2235 3299 : if (BG(CurrentLStatFile) && strcmp(path, BG(CurrentLStatFile)) == 0) {
2236 8 : memcpy(ssb, &BG(lssb), sizeof(php_stream_statbuf));
2237 8 : return 0;
2238 : }
2239 : } else {
2240 84212 : if (BG(CurrentStatFile) && strcmp(path, BG(CurrentStatFile)) == 0) {
2241 4964 : memcpy(ssb, &BG(ssb), sizeof(php_stream_statbuf));
2242 4964 : return 0;
2243 : }
2244 : }
2245 :
2246 82539 : wrapper = php_stream_locate_url_wrapper(path, &path_to_open, 0 TSRMLS_CC);
2247 82539 : if (wrapper && wrapper->wops->url_stat) {
2248 82526 : ret = wrapper->wops->url_stat(wrapper, path_to_open, flags, ssb, context TSRMLS_CC);
2249 82526 : if (ret == 0) {
2250 : /* Drop into cache */
2251 81395 : if (flags & PHP_STREAM_URL_STAT_LINK) {
2252 3266 : if (BG(CurrentLStatFile)) {
2253 3190 : efree(BG(CurrentLStatFile));
2254 : }
2255 3266 : BG(CurrentLStatFile) = estrdup(path);
2256 3266 : memcpy(&BG(lssb), ssb, sizeof(php_stream_statbuf));
2257 : } else {
2258 78129 : if (BG(CurrentStatFile)) {
2259 59538 : efree(BG(CurrentStatFile));
2260 : }
2261 78129 : BG(CurrentStatFile) = estrdup(path);
2262 78129 : memcpy(&BG(ssb), ssb, sizeof(php_stream_statbuf));
2263 : }
2264 : }
2265 82526 : return ret;
2266 : }
2267 13 : return -1;
2268 : }
2269 : /* }}} */
2270 :
2271 : /* {{{ php_stream_opendir */
2272 : PHPAPI php_stream *_php_stream_opendir(char *path, int options,
2273 : php_stream_context *context STREAMS_DC TSRMLS_DC)
2274 3894 : {
2275 3894 : php_stream *stream = NULL;
2276 3894 : php_stream_wrapper *wrapper = NULL;
2277 : char *path_to_open;
2278 :
2279 3894 : if (!path || !*path) {
2280 24 : return NULL;
2281 : }
2282 :
2283 3870 : path_to_open = path;
2284 :
2285 3870 : wrapper = php_stream_locate_url_wrapper(path, &path_to_open, options TSRMLS_CC);
2286 :
2287 7737 : if (wrapper && wrapper->wops->dir_opener) {
2288 3867 : stream = wrapper->wops->dir_opener(wrapper,
2289 : path_to_open, "r", options ^ REPORT_ERRORS, NULL,
2290 : context STREAMS_REL_CC TSRMLS_CC);
2291 :
2292 3867 : if (stream) {
2293 3763 : stream->wrapper = wrapper;
2294 3763 : stream->flags |= PHP_STREAM_FLAG_NO_BUFFER | PHP_STREAM_FLAG_IS_DIR;
2295 : }
2296 3 : } else if (wrapper) {
2297 3 : php_stream_wrapper_log_error(wrapper, options ^ REPORT_ERRORS TSRMLS_CC, "not implemented");
2298 : }
2299 3870 : if (stream == NULL && (options & REPORT_ERRORS)) {
2300 107 : php_stream_display_wrapper_errors(wrapper, path, "failed to open dir" TSRMLS_CC);
2301 : }
2302 3870 : php_stream_tidy_wrapper_error_log(wrapper TSRMLS_CC);
2303 :
2304 3870 : return stream;
2305 : }
2306 : /* }}} */
2307 :
2308 : PHPAPI php_stream *_php_stream_u_opendir(zend_uchar type, zstr path, int path_len, int options, php_stream_context *context STREAMS_DC TSRMLS_DC) /* {{{ */
2309 548 : {
2310 : char *filename;
2311 : int filename_len;
2312 : php_stream *stream;
2313 :
2314 548 : if (type == IS_STRING) {
2315 443 : return php_stream_opendir(path.s, options, context);
2316 : }
2317 :
2318 : /* type == IS_UNICODE */
2319 105 : if (FAILURE == php_stream_path_encode(NULL, &filename, &filename_len, path.u, path_len, options, context)) {
2320 0 : return NULL;
2321 : }
2322 :
2323 105 : stream = php_stream_opendir(filename, options, context);
2324 105 : efree(filename);
2325 105 : return stream;
2326 : }
2327 : /* }}} */
2328 :
2329 : /* {{{ _php_stream_readdir */
2330 : PHPAPI php_stream_dirent *_php_stream_readdir(php_stream *dirstream, php_stream_dirent *ent TSRMLS_DC)
2331 68700 : {
2332 :
2333 68700 : if (sizeof(php_stream_dirent) == php_stream_read(dirstream, (char*)ent, sizeof(php_stream_dirent))) {
2334 65274 : return ent;
2335 : }
2336 :
2337 3426 : return NULL;
2338 : }
2339 : /* }}} */
2340 :
2341 : /* {{{ php_stream_fix_encoding
2342 : * Sets read/write encoding on a stream based on the fopen mode, context options, and INI setting */
2343 : PHPAPI void php_stream_fix_encoding(php_stream *stream, const char *mode, php_stream_context *context TSRMLS_DC)
2344 276076 : {
2345 : /* Output encoding on text mode streams defaults to utf8 unless specified in context parameter */
2346 276076 : if (stream && strchr(mode, 't')) {
2347 : /* Only apply implicit unicode.to. filter if the wrapper didn't do it for us */
2348 34831 : if ((php_stream_filter_product(&stream->writefilters, IS_UNICODE) == IS_UNICODE) &&
2349 : (strchr(mode, 'w') || strchr(mode, 'a') || strchr(mode, '+'))) {
2350 5960 : char *encoding = (context && context->output_encoding) ? context->output_encoding : UG(stream_encoding);
2351 :
2352 : /* UTODO: (Maybe?) Allow overriding the default error handlers on a per-stream basis via context params */
2353 5960 : php_stream_encoding_apply(stream, 1, encoding, UG(from_error_mode), UG(from_subst_char));
2354 : }
2355 :
2356 : /* Only apply implicit unicode.from. filter if the wrapper didn't do it for us */
2357 34831 : if ((stream->readbuf_type == IS_STRING) && (strchr(mode, 'r') || strchr(mode, '+'))) {
2358 12711 : char *encoding = (context && context->input_encoding) ? context->input_encoding : UG(stream_encoding);
2359 :
2360 : /* UTODO: (Maybe?) Allow overriding the default error handlers on a per-stream basis via context params */
2361 12711 : php_stream_encoding_apply(stream, 0, encoding, UG(to_error_mode), NULL);
2362 : }
2363 : }
2364 276076 : }
2365 : /* }}} */
2366 :
2367 : /* {{{ php_stream_open_wrapper_ex */
2368 : PHPAPI php_stream *_php_stream_open_wrapper_ex(char *path, char *mode, int options,
2369 : char **opened_path, php_stream_context *context STREAMS_DC TSRMLS_DC)
2370 121556 : {
2371 121556 : php_stream *stream = NULL;
2372 121556 : php_stream_wrapper *wrapper = NULL;
2373 : char *path_to_open;
2374 121556 : int persistent = options & STREAM_OPEN_PERSISTENT;
2375 121556 : char *resolved_path = NULL;
2376 121556 : char *copy_of_path = NULL;
2377 : char implicit_mode[16];
2378 121556 : int modelen = strlen(mode);
2379 :
2380 121556 : if (opened_path) {
2381 11540 : *opened_path = NULL;
2382 : }
2383 :
2384 121556 : if (!path || !*path) {
2385 67 : php_error_docref(NULL TSRMLS_CC, E_WARNING, "Filename cannot be empty");
2386 67 : return NULL;
2387 : }
2388 :
2389 121489 : if (options & USE_PATH) {
2390 7786 : resolved_path = zend_resolve_path(path, strlen(path) TSRMLS_CC);
2391 7786 : if (resolved_path) {
2392 7424 : path = resolved_path;
2393 : /* we've found this file, don't re-check include_path or run realpath */
2394 7424 : options |= STREAM_ASSUME_REALPATH;
2395 7424 : options &= ~USE_PATH;
2396 : }
2397 : }
2398 :
2399 121489 : path_to_open = path;
2400 :
2401 121489 : wrapper = php_stream_locate_url_wrapper(path, &path_to_open, options TSRMLS_CC);
2402 121489 : if (options & STREAM_USE_URL && (!wrapper || !wrapper->is_url)) {
2403 0 : php_error_docref(NULL TSRMLS_CC, E_WARNING, "This function may only be used against URLs");
2404 0 : if (resolved_path) {
2405 0 : efree(resolved_path);
2406 : }
2407 0 : return NULL;
2408 : }
2409 :
2410 121489 : strlcpy(implicit_mode, mode, sizeof(implicit_mode));
2411 121489 : if (context && context->default_mode && modelen < 15 && !strchr(mode, 't') && !strchr(mode, 'b')) {
2412 0 : if (context->default_mode & PHP_FILE_BINARY) {
2413 0 : implicit_mode[modelen++] = 'b';
2414 0 : } else if (context->default_mode & PHP_FILE_TEXT) {
2415 0 : implicit_mode[modelen++] = 't';
2416 : }
2417 0 : implicit_mode[modelen] = '\0';
2418 : }
2419 :
2420 121489 : if (wrapper) {
2421 121470 : if (!wrapper->wops->stream_opener) {
2422 0 : php_stream_wrapper_log_error(wrapper, options ^ REPORT_ERRORS TSRMLS_CC,
2423 : "wrapper does not support stream open");
2424 : } else {
2425 121470 : stream = wrapper->wops->stream_opener(wrapper,
2426 : path_to_open, implicit_mode, options ^ REPORT_ERRORS,
2427 : opened_path, context STREAMS_REL_CC TSRMLS_CC);
2428 : }
2429 :
2430 : /* if the caller asked for a persistent stream but the wrapper did not
2431 : * return one, force an error here */
2432 121469 : if (stream && (options & STREAM_OPEN_PERSISTENT) && !stream->is_persistent) {
2433 0 : php_stream_wrapper_log_error(wrapper, options ^ REPORT_ERRORS TSRMLS_CC,
2434 : "wrapper does not support persistent streams");
2435 0 : php_stream_close(stream);
2436 0 : stream = NULL;
2437 : }
2438 :
2439 121469 : if (stream) {
2440 120648 : stream->wrapper = wrapper;
2441 120648 : strlcpy(stream->mode, implicit_mode, sizeof(stream->mode));
2442 : }
2443 : }
2444 :
2445 121488 : if (stream) {
2446 120648 : if (opened_path && !*opened_path && resolved_path) {
2447 2 : *opened_path = resolved_path;
2448 2 : resolved_path = NULL;
2449 : }
2450 120648 : if (stream->orig_path) {
2451 1 : pefree(stream->orig_path, persistent);
2452 : }
2453 120648 : copy_of_path = pestrdup(path, persistent);
2454 120648 : stream->orig_path = copy_of_path;
2455 : #if ZEND_DEBUG
2456 : stream->open_filename = __zend_orig_filename ? __zend_orig_filename : __zend_filename;
2457 : stream->open_lineno = __zend_orig_lineno ? __zend_orig_lineno : __zend_lineno;
2458 : #endif
2459 : }
2460 :
2461 121488 : if (stream != NULL && (options & STREAM_MUST_SEEK)) {
2462 : php_stream *newstream;
2463 :
2464 8934 : switch(php_stream_make_seekable_rel(stream, &newstream,
2465 : (options & STREAM_WILL_CAST)
2466 : ? PHP_STREAM_PREFER_STDIO : PHP_STREAM_NO_PREFERENCE)) {
2467 : case PHP_STREAM_UNCHANGED:
2468 8934 : if (resolved_path) {
2469 42 : efree(resolved_path);
2470 : }
2471 8934 : return stream;
2472 : case PHP_STREAM_RELEASED:
2473 0 : if (newstream->orig_path) {
2474 0 : pefree(newstream->orig_path, persistent);
2475 : }
2476 0 : newstream->orig_path = pestrdup(path, persistent);
2477 0 : if (resolved_path) {
2478 0 : efree(resolved_path);
2479 : }
2480 0 : return newstream;
2481 : default:
2482 0 : php_stream_close(stream);
2483 0 : stream = NULL;
2484 0 : if (options & REPORT_ERRORS) {
2485 0 : char *tmp = estrdup(path);
2486 0 : php_strip_url_passwd(tmp);
2487 0 : php_error_docref1(NULL TSRMLS_CC, tmp, E_WARNING, "could not make seekable - %s",
2488 : tmp);
2489 0 : efree(tmp);
2490 :
2491 0 : options ^= REPORT_ERRORS;
2492 : }
2493 : }
2494 : }
2495 :
2496 112554 : if (stream && stream->ops->seek && (stream->flags & PHP_STREAM_FLAG_NO_SEEK) == 0 && strchr(mode, 'a') && stream->position == 0) {
2497 1367 : off_t newpos = 0;
2498 :
2499 : /* if opened for append, we need to revise our idea of the initial file position */
2500 1367 : if (0 == stream->ops->seek(stream, 0, SEEK_CUR, &newpos TSRMLS_CC)) {
2501 1367 : stream->position = newpos;
2502 : }
2503 : }
2504 :
2505 :
2506 112554 : php_stream_fix_encoding(stream, implicit_mode, context TSRMLS_CC);
2507 :
2508 112554 : if (stream == NULL && (options & REPORT_ERRORS)) {
2509 438 : php_stream_display_wrapper_errors(wrapper, path, "failed to open stream" TSRMLS_CC);
2510 438 : if (opened_path && *opened_path) {
2511 0 : efree(*opened_path);
2512 0 : *opened_path = NULL;
2513 : }
2514 : }
2515 112554 : php_stream_tidy_wrapper_error_log(wrapper TSRMLS_CC);
2516 : #if ZEND_DEBUG
2517 : if (stream == NULL && copy_of_path != NULL) {
2518 : pefree(copy_of_path, persistent);
2519 : }
2520 : #endif
2521 :
2522 112554 : if (resolved_path) {
2523 7380 : efree(resolved_path);
2524 : }
2525 112554 : return stream;
2526 : }
2527 : /* }}} */
2528 :
2529 : /* {{{ _php_stream_u_open_wrapper */
2530 : PHPAPI php_stream *_php_stream_u_open_wrapper(zend_uchar type, zstr path, int path_len,
2531 : char *mode, int options, zstr *opened_path, int *opened_path_len,
2532 : php_stream_context *context STREAMS_DC TSRMLS_DC)
2533 47 : {
2534 : php_stream *stream;
2535 47 : char *filename = NULL;
2536 : int filename_len;
2537 :
2538 47 : if (opened_path) {
2539 0 : opened_path->v = NULL;
2540 : }
2541 47 : if (opened_path_len) {
2542 0 : *opened_path_len = 0;
2543 : }
2544 :
2545 47 : if (type == IS_STRING) {
2546 15 : stream = php_stream_open_wrapper_ex(path.s, mode, options, (char**)opened_path, context);
2547 :
2548 15 : if (opened_path_len && opened_path && opened_path->s) {
2549 0 : *opened_path_len = strlen(opened_path->s);
2550 : }
2551 :
2552 15 : return stream;
2553 : }
2554 :
2555 : /* type == IS_UNICODE */
2556 32 : if (FAILURE == php_stream_path_encode(NULL, &filename, &filename_len, path.u, path_len, options, context)) {
2557 0 : return NULL;
2558 : }
2559 :
2560 32 : stream = php_stream_open_wrapper_ex(filename, mode, options, (char**)opened_path, context);
2561 32 : efree(filename);
2562 :
2563 32 : if (opened_path && opened_path->s) {
2564 : UChar *upath;
2565 : int upath_len;
2566 :
2567 0 : if (SUCCESS == php_stream_path_decode(NULL, &upath, &upath_len, opened_path->s, strlen(opened_path->s), options, context)) {
2568 0 : efree(opened_path->s);
2569 0 : opened_path->u = upath;
2570 0 : if (opened_path_len) {
2571 0 : *opened_path_len = upath_len;
2572 : }
2573 : } else {
2574 : /* Shouldn't happen */
2575 0 : efree(opened_path->s);
2576 0 : opened_path->s = NULL;
2577 : }
2578 : }
2579 :
2580 32 : return stream;
2581 : }
2582 : /* }}} */
2583 :
2584 : /* {{{ context API */
2585 : PHPAPI php_stream_context *php_stream_context_set(php_stream *stream, php_stream_context *context TSRMLS_DC)
2586 1775 : {
2587 1775 : php_stream_context *oldcontext = stream->context;
2588 :
2589 1775 : stream->context = context;
2590 :
2591 1775 : if (context) {
2592 109 : zend_list_addref(context->rsrc_id);
2593 : }
2594 1775 : if (oldcontext) {
2595 0 : zend_list_delete(oldcontext->rsrc_id);
2596 : }
2597 :
2598 1775 : return oldcontext;
2599 : }
2600 :
2601 : PHPAPI void php_stream_notification_notify(php_stream_context *context, int notifycode, int severity,
2602 : char *xmsg, int xcode, size_t bytes_sofar, size_t bytes_max, void * ptr TSRMLS_DC)
2603 0 : {
2604 0 : if (context && context->notifier)
2605 0 : context->notifier->func(context, notifycode, severity, xmsg, xcode, bytes_sofar, bytes_max, ptr TSRMLS_CC);
2606 0 : }
2607 :
2608 : PHPAPI void php_stream_context_free(php_stream_context *context)
2609 3095 : {
2610 3095 : if (context->options) {
2611 0 : zval_ptr_dtor(&context->options);
2612 0 : context->options = NULL;
2613 : }
2614 3095 : if (context->notifier) {
2615 1 : php_stream_notification_free(context->notifier);
2616 1 : context->notifier = NULL;
2617 : }
2618 3095 : if (context->input_encoding) {
2619 0 : efree(context->input_encoding);
2620 : }
2621 3095 : if (context->output_encoding) {
2622 0 : efree(context->output_encoding);
2623 : }
2624 3095 : if (context->links) {
2625 0 : zval_ptr_dtor(&context->links);
2626 0 : context->links = NULL;
2627 : }
2628 3095 : efree(context);
2629 3095 : }
2630 :
2631 : PHPAPI php_stream_context *php_stream_context_alloc(void)
2632 3069 : {
2633 : php_stream_context *context;
2634 :
2635 3069 : context = ecalloc(1, sizeof(php_stream_context));
2636 3069 : context->notifier = NULL;
2637 3069 : MAKE_STD_ZVAL(context->options);
2638 3069 : array_init(context->options);
2639 :
2640 3069 : context->rsrc_id = ZEND_REGISTER_RESOURCE(NULL, context, php_le_stream_context());
2641 3069 : return context;
2642 : }
2643 :
2644 : PHPAPI php_stream_notifier *php_stream_notification_alloc(void)
2645 2 : {
2646 2 : return ecalloc(1, sizeof(php_stream_notifier));
2647 : }
2648 :
2649 : PHPAPI void php_stream_notification_free(php_stream_notifier *notifier)
2650 2 : {
2651 2 : if (notifier->dtor) {
2652 2 : notifier->dtor(notifier);
2653 : }
2654 2 : efree(notifier);
2655 2 : }
2656 :
2657 : PHPAPI int php_stream_context_get_option(php_stream_context *context,
2658 : const char *wrappername, const char *optionname, zval ***optionvalue)
2659 643 : {
2660 : zval **wrapperhash;
2661 :
2662 643 : if (FAILURE == zend_rt_hash_find(Z_ARRVAL_P(context->options), (char*)wrappername, strlen(wrappername)+1, (void**)&wrapperhash)) {
2663 463 : return FAILURE;
2664 : }
2665 180 : return zend_rt_hash_find(Z_ARRVAL_PP(wrapperhash), (char*)optionname, strlen(optionname)+1, (void**)optionvalue);
2666 : }
2667 :
2668 : PHPAPI int php_stream_context_set_option(php_stream_context *context,
2669 : const char *wrappername, const char *optionname, zval *optionvalue)
2670 661 : {
2671 : zval **wrapperhash;
2672 : zval *category, *copied_val;
2673 :
2674 661 : ALLOC_INIT_ZVAL(copied_val);
2675 661 : *copied_val = *optionvalue;
2676 661 : zval_copy_ctor(copied_val);
2677 661 : INIT_PZVAL(copied_val);
2678 :
2679 661 : if (FAILURE == zend_rt_hash_find(Z_ARRVAL_P(context->options), (char*)wrappername, strlen(wrappername)+1, (void**)&wrapperhash)) {
2680 334 : MAKE_STD_ZVAL(category);
2681 334 : array_init(category);
2682 334 : if (FAILURE == zend_rt_hash_update(Z_ARRVAL_P(context->options), (char*)wrappername, strlen(wrappername)+1, (void**)&category, sizeof(zval *), NULL)) {
2683 0 : return FAILURE;
2684 : }
2685 :
2686 334 : wrapperhash = &category;
2687 : }
2688 661 : return zend_rt_hash_update(Z_ARRVAL_PP(wrapperhash), (char*)optionname, strlen(optionname)+1, (void**)&copied_val, sizeof(zval *), NULL);
2689 : }
2690 :
2691 : PHPAPI int php_stream_context_get_link(php_stream_context *context,
2692 : const char *hostent, php_stream **stream)
2693 0 : {
2694 : php_stream **pstream;
2695 :
2696 0 : if (!stream || !hostent || !context || !(context->links)) {
2697 0 : return FAILURE;
2698 : }
2699 0 : if (SUCCESS == zend_rt_hash_find(Z_ARRVAL_P(context->links), (char*)hostent, strlen(hostent)+1, (void**)&pstream)) {
2700 0 : *stream = *pstream;
2701 0 : return SUCCESS;
2702 : }
2703 0 : return FAILURE;
2704 : }
2705 :
2706 : PHPAPI int php_stream_context_set_link(php_stream_context *context,
2707 : const char *hostent, php_stream *stream)
2708 0 : {
2709 0 : if (!context) {
2710 0 : return FAILURE;
2711 : }
2712 0 : if (!context->links) {
2713 0 : ALLOC_INIT_ZVAL(context->links);
2714 0 : array_init(context->links);
2715 : }
2716 0 : if (!stream) {
2717 : /* Delete any entry for <hostent> */
2718 0 : return zend_rt_hash_del(Z_ARRVAL_P(context->links), (char*)hostent, strlen(hostent)+1);
2719 : }
2720 0 : return zend_rt_hash_update(Z_ARRVAL_P(context->links), (char*)hostent, strlen(hostent)+1, (void**)&stream, sizeof(php_stream *), NULL);
2721 : }
2722 :
2723 : PHPAPI int php_stream_context_del_link(php_stream_context *context,
2724 : php_stream *stream)
2725 0 : {
2726 : php_stream **pstream;
2727 : zend_uchar type;
2728 : zstr hostent;
2729 : unsigned int hostent_len;
2730 0 : int ret = SUCCESS;
2731 :
2732 0 : if (!context || !context->links || !stream) {
2733 0 : return FAILURE;
2734 : }
2735 :
2736 0 : for(zend_hash_internal_pointer_reset(Z_ARRVAL_P(context->links));
2737 0 : SUCCESS == zend_hash_get_current_data(Z_ARRVAL_P(context->links), (void**)&pstream);
2738 0 : zend_hash_move_forward(Z_ARRVAL_P(context->links))) {
2739 0 : if (*pstream == stream) {
2740 0 : type = zend_hash_get_current_key_ex(Z_ARRVAL_P(context->links), &hostent, &hostent_len, NULL, 0, NULL);
2741 0 : if (type == HASH_KEY_IS_STRING || type == HASH_KEY_IS_UNICODE) {
2742 0 : if (FAILURE == zend_u_hash_del(Z_ARRVAL_P(context->links), type, hostent, hostent_len)) {
2743 0 : ret = FAILURE;
2744 : }
2745 : } else {
2746 0 : ret = FAILURE;
2747 : }
2748 : }
2749 : }
2750 :
2751 0 : return ret;
2752 : }
2753 : /* }}} */
2754 :
2755 : /* {{{ php_stream_dirent_alphasort
2756 : */
2757 : PHPAPI int php_stream_dirent_alphasort(const char **a, const char **b)
2758 124 : {
2759 124 : return strcoll(*a, *b);
2760 : }
2761 : /* }}} */
2762 :
2763 : /* {{{ php_stream_dirent_alphasortr
2764 : */
2765 : PHPAPI int php_stream_dirent_alphasortr(const char **a, const char **b)
2766 24 : {
2767 24 : return strcoll(*b, *a);
2768 : }
2769 : /* }}} */
2770 :
2771 : /* {{{ php_stream_scandir
2772 : */
2773 : PHPAPI int _php_stream_scandir(char *dirname, char **namelist[], int flags, php_stream_context *context,
2774 : int (*compare) (const char **a, const char **b) TSRMLS_DC)
2775 86 : {
2776 : php_stream *stream;
2777 : php_stream_dirent sdp;
2778 86 : char **vector = NULL;
2779 86 : int vector_size = 0;
2780 86 : int nfiles = 0;
2781 :
2782 86 : if (!namelist) {
2783 0 : return FAILURE;
2784 : }
2785 :
2786 86 : stream = php_stream_opendir(dirname, REPORT_ERRORS, context);
2787 86 : if (!stream) {
2788 41 : return FAILURE;
2789 : }
2790 :
2791 229 : while (php_stream_readdir(stream, &sdp)) {
2792 139 : if (nfiles == vector_size) {
2793 46 : if (vector_size == 0) {
2794 45 : vector_size = 10;
2795 : } else {
2796 1 : vector_size *= 2;
2797 : }
2798 46 : vector = (char **) erealloc(vector, vector_size * sizeof(char *));
2799 : }
2800 :
2801 139 : vector[nfiles] = estrdup(sdp.d_name);
2802 :
2803 139 : nfiles++;
2804 : }
2805 45 : php_stream_closedir(stream);
2806 :
2807 45 : *namelist = vector;
2808 :
2809 45 : if (compare) {
2810 45 : qsort(*namelist, nfiles, sizeof(char *), (int(*)(const void *, const void *))compare);
2811 : }
2812 45 : return nfiles;
2813 : }
2814 : /* }}} */
2815 :
2816 : /* {{{ php_stream_path_encode
2817 : Encode a filepath to the appropriate characterset.
2818 : If the wrapper supports its own encoding rules it will be dispatched to wrapper->wops->path_encode()
2819 : Otherwise the INI defined filesystem_encoding converter will be used
2820 : If wrapper == NULL, the path will be explored to locate the correct wrapper
2821 : */
2822 : PHPAPI int _php_stream_path_encode(php_stream_wrapper *wrapper,
2823 : char **pathenc, int *pathenc_len, const UChar *path, int path_len,
2824 : int options, php_stream_context *context TSRMLS_DC)
2825 391534 : {
2826 391534 : UErrorCode status = U_ZERO_ERROR;
2827 : int num_conv;
2828 :
2829 391534 : if (!wrapper) {
2830 : UChar *p;
2831 : U_STRING_DECL(delim, "://", 3);
2832 391534 : int delim_len = 3;
2833 :
2834 391534 : U_STRING_INIT(delim, "://", 3);
2835 :
2836 391534 : p = u_strFindFirst(path, path_len, delim, delim_len);
2837 391534 : if (p) {
2838 973 : char *scheme = NULL;
2839 973 : int scheme_len = 0;
2840 :
2841 : /* Convert just the scheme using utf8 in order to look it up in the registry */
2842 973 : num_conv = zend_unicode_to_string_ex(UG(utf8_conv), &scheme, &scheme_len, path, (p - path) + delim_len, &status);
2843 973 : if (U_FAILURE(status)) {
2844 0 : if (options & REPORT_ERRORS) {
2845 0 : zend_raise_conversion_error_ex("Unable to convert filepath", UG(utf8_conv), ZEND_FROM_UNICODE, num_conv TSRMLS_CC);
2846 : }
2847 0 : *pathenc = NULL;
2848 0 : *pathenc_len = 0;
2849 :
2850 0 : return FAILURE;
2851 : }
2852 973 : wrapper = php_stream_locate_url_wrapper(scheme, NULL, options | STREAM_DISABLE_URL_PROTECTION TSRMLS_CC);
2853 973 : efree(scheme);
2854 973 : if (!wrapper) {
2855 1 : *pathenc = NULL;
2856 1 : *pathenc_len = 0;
2857 :
2858 1 : return FAILURE;
2859 : }
2860 : } else {
2861 390561 : wrapper = &php_plain_files_wrapper;
2862 : }
2863 : }
2864 :
2865 391533 : if (wrapper->wops->path_encode) {
2866 0 : if (wrapper->wops->path_encode(wrapper, pathenc, pathenc_len, path, path_len, options, context TSRMLS_CC) == FAILURE) {
2867 0 : *pathenc = NULL;
2868 0 : *pathenc_len = 0;
2869 :
2870 0 : return FAILURE;
2871 : }
2872 :
2873 0 : return SUCCESS;
2874 : }
2875 :
2876 : /* Otherwise, fallback on filesystem_encoding */
2877 391533 : status = U_ZERO_ERROR;
2878 :
2879 391533 : num_conv = zend_unicode_to_string_ex(ZEND_U_CONVERTER(UG(filesystem_encoding_conv)),
2880 : pathenc, pathenc_len, path, path_len, &status);
2881 391533 : if (U_FAILURE(status)) {
2882 0 : if (options & REPORT_ERRORS) {
2883 0 : zend_raise_conversion_error_ex("Unable to convert filepath", ZEND_U_CONVERTER(UG(filesystem_encoding_conv)),
2884 : ZEND_FROM_UNICODE, num_conv TSRMLS_CC);
2885 : }
2886 :
2887 0 : *pathenc = NULL;
2888 0 : *pathenc_len = 0;
2889 :
2890 0 : return FAILURE;
2891 : }
2892 :
2893 391533 : return SUCCESS;
2894 : }
2895 : /* }}} */
2896 :
2897 :
2898 : /* {{{ php_stream_path_decode
2899 : Decode a filepath from its character set to unicode
2900 : If the wrapper supports its own decoding rules it will be dispatched to wrapper->wops->path_encode()
2901 : Otherwise (or if wrapper == NULL) the INI defined filesystem_encoding converter will be used.
2902 : */
2903 : PHPAPI int _php_stream_path_decode(php_stream_wrapper *wrapper,
2904 : UChar **pathdec, int *pathdec_len, const char *path, int path_len,
2905 : int options, php_stream_context *context TSRMLS_DC)
2906 42270 : {
2907 : int num_conv;
2908 42270 : UErrorCode status = U_ZERO_ERROR;
2909 :
2910 42270 : if (wrapper && wrapper->wops->path_decode) {
2911 0 : if (wrapper->wops->path_decode(wrapper, pathdec, pathdec_len, path, path_len, options, context TSRMLS_CC) == FAILURE) {
2912 0 : *pathdec = NULL;
2913 0 : *pathdec_len = 0;
2914 :
2915 0 : return FAILURE;
2916 : }
2917 0 : return SUCCESS;
2918 : }
2919 :
2920 : /* Otherwise fallback on filesystem_encoding */
2921 42270 : num_conv = zend_string_to_unicode_ex(ZEND_U_CONVERTER(UG(filesystem_encoding_conv)),
2922 : pathdec, pathdec_len, path, path_len, &status);
2923 42270 : if (U_FAILURE(status)) {
2924 0 : if (options & REPORT_ERRORS) {
2925 0 : zend_raise_conversion_error_ex("Unable to convert filepath", ZEND_U_CONVERTER(UG(filesystem_encoding_conv)),
2926 : ZEND_TO_UNICODE, num_conv TSRMLS_CC);
2927 : }
2928 :
2929 0 : *pathdec = NULL;
2930 0 : *pathdec_len = 0;
2931 :
2932 0 : return FAILURE;
2933 : }
2934 :
2935 42270 : return SUCCESS;
2936 :
2937 : }
2938 : /* }}} */
2939 :
2940 : /* {{{ allow_url_fopen / allow_url_include Handlers */
2941 :
2942 : PHPAPI int php_stream_wrapper_is_allowed(const char *wrapper, int wrapper_len, const char *setting TSRMLS_DC)
2943 470715 : {
2944 470715 : HashTable *wrapper_hash = (FG(stream_wrappers) ? FG(stream_wrappers) : &url_stream_wrappers_hash);
2945 : php_stream_wrapper **wrapperpp;
2946 470715 : int setting_len = setting ? strlen(setting) : 0;
2947 470715 : const char *s = setting, *e = s + setting_len;
2948 : char *wrapper_dup;
2949 :
2950 : /* BC: allow_url_* == on */
2951 470715 : if (setting_len == 1 && *setting == '*') {
2952 : /* "*" means everything is allowed */
2953 462791 : return 1;
2954 : }
2955 :
2956 7924 : if (wrapper_len == (sizeof("zlib") - 1) && strncasecmp("zlib", wrapper, sizeof("zlib") - 1) == 0) {
2957 0 : wrapper = "compress.zlib";
2958 0 : wrapper_len = sizeof("compress.zlib") - 1;
2959 : }
2960 :
2961 7924 : wrapper_dup = estrndup(wrapper, wrapper_len);
2962 7924 : php_strtolower(wrapper_dup, wrapper_len);
2963 7924 : if (FAILURE == zend_hash_find(wrapper_hash, wrapper_dup, wrapper_len + 1, (void**)&wrapperpp)) {
2964 : /* Wrapper does not exist, assume disallow */
2965 0 : efree(wrapper_dup);
2966 0 : return 0;
2967 : }
2968 7924 : efree(wrapper_dup);
2969 :
2970 : /* BC: allow_url_* == off */
2971 7924 : if (!setting || !setting_len) {
2972 : /* NULL or empty indicates that only is_url == 0 wrappers are allowed */
2973 :
2974 7924 : if (wrapper_len == (sizeof("file") - 1) && strncasecmp("file", wrapper, sizeof("file") - 1) == 0) {
2975 : /* file:// is non-url */
2976 7296 : return 1;
2977 : }
2978 :
2979 628 : if ((*wrapperpp)->is_url) {
2980 : /* is_url types are disabled, but this is an is_url wrapper, disallow */
2981 15 : return 0;
2982 : }
2983 :
2984 : /* Wrapper is not is_url, allow it */
2985 613 : return 1;
2986 : }
2987 :
2988 : /* Otherwise, scan list */
2989 0 : while (s < e) {
2990 0 : const char *p = php_memnstr((char*)s, ":", 1, (char*)e);
2991 :
2992 0 : if (!p) {
2993 0 : p = e;
2994 : }
2995 :
2996 0 : if (wrapper_len == (p - s) &&
2997 : strncasecmp(s, wrapper, p - s) == 0) {
2998 : /* wrapper found in list */
2999 0 : return 1;
3000 : }
3001 :
3002 0 : if ((*wrapperpp)->wops == php_stream_user_wrapper_ops &&
3003 : (sizeof("user") - 1) == (p - s) &&
3004 : strncasecmp(s, "user", sizeof("user") - 1) == 0) {
3005 : /* Wrapper is userspace wrapper and meta-wrapper "user" is enabled */
3006 0 : return 1;
3007 : }
3008 :
3009 0 : s = p + 1;
3010 : }
3011 :
3012 0 : return 0;
3013 : }
3014 :
3015 : /* allow_url_*_list accepts:
3016 : *
3017 : * 1/on to enable all URL prefixes
3018 : * 0/off to disable all is_url=1 wrappers
3019 : * A colon delimited list of wrappers to allow (wildcards allowed)
3020 : * e.g. file:gzip:compress.*:php
3021 : */
3022 : PHP_INI_MH(OnUpdateAllowUrl)
3023 35893 : {
3024 : #ifndef ZTS
3025 35893 : char *base = (char *) mh_arg2;
3026 : #else
3027 : char *base = (char *) ts_resource(*((int *) mh_arg2));
3028 : #endif
3029 35893 : char **allow = (char **) (base+(size_t) mh_arg1);
3030 :
3031 : /* BC Enable */
3032 35893 : if ((new_value_length == 1 && *new_value == '1') ||
3033 : (new_value_length == (sizeof("on") - 1) && strncasecmp(new_value, "on", sizeof("on") - 1) == 0) ) {
3034 :
3035 17443 : if (*allow && strcmp(*allow, "*") == 0) {
3036 : /* Turning on, but that's no change from current, so leave it alone */
3037 435 : return SUCCESS;
3038 : }
3039 :
3040 17008 : if (stage != PHP_INI_STAGE_STARTUP) {
3041 : /* Not already on, and not in SYSTEM context, fail */
3042 0 : return FAILURE;
3043 : }
3044 :
3045 : /* Otherwise, turn on setting */
3046 17008 : if (*allow) {
3047 0 : free(*allow);
3048 : }
3049 :
3050 17008 : *allow = zend_strndup("*", 1);
3051 :
3052 17008 : return SUCCESS;
3053 : }
3054 :
3055 : /* BC disable */
3056 18450 : if ((new_value_length == 1 && *new_value == '0') ||
3057 : (new_value_length == (sizeof("off") - 1) && strncasecmp(new_value, "off", sizeof("off") - 1) == 0) ) {
3058 :
3059 : /* Always permit shutting off allowurl settings */
3060 17004 : if (*allow) {
3061 0 : free(*allow);
3062 : }
3063 17004 : *allow = NULL;
3064 :
3065 17004 : return SUCCESS;
3066 : }
3067 :
3068 : /* Specify as list */
3069 1446 : if (stage == PHP_INI_STAGE_STARTUP) {
3070 : /* Always allow new settings in startup stage */
3071 2 : if (*allow) {
3072 0 : free(*allow);
3073 : }
3074 2 : *allow = zend_strndup(new_value, new_value_length);
3075 :
3076 2 : return SUCCESS;
3077 : }
3078 :
3079 : /* In PERDIR/RUNTIME context, do more work to ensure we're only tightening the restriction */
3080 :
3081 1444 : if (*allow && strcmp(*allow, "*") == 0) {
3082 : /* Currently allowing everying, so whatever we set it to will be more restrictive */
3083 1444 : free(*allow);
3084 1444 : *allow = zend_strndup(new_value, new_value_length);
3085 :
3086 1444 : return SUCCESS;
3087 : }
3088 :
3089 0 : if (!*allow) {
3090 : /* Currently allowing anything with is_url == 0
3091 : * So long as this list doesn't contain any is_url == 1, allow it
3092 : */
3093 0 : HashTable *wrapper_hash = (FG(stream_wrappers) ? FG(stream_wrappers) : &url_stream_wrappers_hash);
3094 0 : char *s = new_value, *e = new_value + new_value_length;
3095 :
3096 0 : while (s < e) {
3097 : php_stream_wrapper **wrapper;
3098 0 : char *p = php_memnstr(s, ":", 1, e);
3099 : char *scan;
3100 : int scan_len;
3101 :
3102 0 : if (!p) {
3103 0 : p = e;
3104 : }
3105 :
3106 : /* file:// is never a URL */
3107 0 : if ( (p - s) == (sizeof("file") - 1) && strncasecmp(s, "file", sizeof("file") - 1) == 0 ) {
3108 : /* file is not a URL */
3109 0 : s = p + 1;
3110 0 : continue;
3111 : }
3112 :
3113 0 : if ( (p - s) == (sizeof("zlib") - 1) && strncasecmp(s, "zlib", sizeof("zlib") - 1) == 0 ) {
3114 : /* Wastful since we know that compress.zlib is already lower cased, but forgivable */
3115 0 : scan = estrndup("compress.zlib", sizeof("compress.zlib") - 1);
3116 0 : scan_len = sizeof("compress.zlib") - 1;
3117 : } else {
3118 0 : scan = estrndup(s, p - s);;
3119 0 : scan_len = p - s;
3120 0 : php_strtolower(scan, scan_len);
3121 : }
3122 :
3123 0 : if (FAILURE == zend_hash_find(wrapper_hash, scan, scan_len + 1, (void**) &wrapper)) {
3124 : /* Unknown wrapper, not allowed in this context */
3125 0 : efree(scan);
3126 0 : return FAILURE;
3127 : }
3128 0 : efree(scan);
3129 :
3130 0 : if ((*wrapper)->is_url) {
3131 : /* Disallowed is_url wrapper specified when trying to escape is_url == 0 context */
3132 0 : return FAILURE;
3133 : }
3134 :
3135 : /* Seems alright so far... */
3136 0 : s = p+1;
3137 : }
3138 :
3139 : /* All tests passed, allow it */
3140 0 : *allow = zend_strndup(new_value, new_value_length);
3141 :
3142 0 : return SUCCESS;
3143 : }
3144 :
3145 : /* The current allows are restricted to a specific list,
3146 : * Make certain that our new list is a subset of that list
3147 : */
3148 : {
3149 0 : char *s = new_value, *e = new_value + new_value_length;
3150 :
3151 0 : while (s < e) {
3152 0 : char *p = php_memnstr(s, ":", 1, e);
3153 :
3154 0 : if (!p) {
3155 0 : p = e;
3156 : }
3157 :
3158 0 : if (!php_stream_wrapper_is_allowed(s, p - s, *allow TSRMLS_CC)) {
3159 : /* Current settings don't allow this wrapper, deny */
3160 0 : return FAILURE;
3161 : }
3162 :
3163 0 : s = p + 1;
3164 : }
3165 :
3166 0 : free(*allow);
3167 0 : *allow = zend_strndup(new_value, new_value_length);
3168 :
3169 0 : return SUCCESS;
3170 : }
3171 : }
3172 :
3173 : /* }}} */
3174 :
3175 : /*
3176 : * Local variables:
3177 : * tab-width: 4
3178 : * c-basic-offset: 4
3179 : * End:
3180 : * vim600: noet sw=4 ts=4 fdm=marker
3181 : * vim<600: noet sw=4 ts=4
3182 : */
|