1/*
2   +----------------------------------------------------------------------+
3   | PHP Version 7                                                        |
4   +----------------------------------------------------------------------+
5   | Copyright (c) 1997-2014 The PHP Group                                |
6   +----------------------------------------------------------------------+
7   | This source file is subject to version 3.01 of the PHP license,      |
8   | that is bundled with this package in the file LICENSE, and is        |
9   | available through the world-wide-web at the following url:           |
10   | http://www.php.net/license/3_01.txt                                  |
11   | If you did not receive a copy of the PHP license and are unable to   |
12   | obtain it through the world-wide-web, please send a note to          |
13   | license@php.net so we can mail you a copy immediately.               |
14   +----------------------------------------------------------------------+
15   | 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$ */
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 */
38static HashTable url_stream_wrappers_hash;
39static int le_stream = FAILURE; /* true global */
40static int le_pstream = FAILURE; /* true global */
41static int le_stream_filter = FAILURE; /* true global */
42
43PHPAPI int php_file_le_stream(void)
44{
45    return le_stream;
46}
47
48PHPAPI int php_file_le_pstream(void)
49{
50    return le_pstream;
51}
52
53PHPAPI int php_file_le_stream_filter(void)
54{
55    return le_stream_filter;
56}
57
58PHPAPI HashTable *_php_stream_get_url_stream_wrappers_hash(TSRMLS_D)
59{
60    return (FG(stream_wrappers) ? FG(stream_wrappers) : &url_stream_wrappers_hash);
61}
62
63PHPAPI HashTable *php_stream_get_url_stream_wrappers_hash_global(void)
64{
65    return &url_stream_wrappers_hash;
66}
67
68static int _php_stream_release_context(zval *zv, void *pContext TSRMLS_DC)
69{
70    zend_resource *le = Z_RES_P(zv);
71    if (le->ptr == pContext) {
72        return --GC_REFCOUNT(le) == 0;
73    }
74    return 0;
75}
76
77static int forget_persistent_resource_id_numbers(zval *el TSRMLS_DC)
78{
79    php_stream *stream;
80    zend_resource *rsrc = Z_RES_P(el);
81
82    if (rsrc->type != le_pstream) {
83        return 0;
84    }
85
86    stream = (php_stream*)rsrc->ptr;
87
88#if STREAM_DEBUG
89fprintf(stderr, "forget_persistent: %s:%p\n", stream->ops->label, stream);
90#endif
91
92    stream->res = NULL;
93
94    if (PHP_STREAM_CONTEXT(stream)) {
95        zend_hash_apply_with_argument(&EG(regular_list),
96                _php_stream_release_context,
97                PHP_STREAM_CONTEXT(stream) TSRMLS_CC);
98        stream->ctx = NULL;
99    }
100
101    return 0;
102}
103
104PHP_RSHUTDOWN_FUNCTION(streams)
105{
106    zend_hash_apply(&EG(persistent_list), forget_persistent_resource_id_numbers TSRMLS_CC);
107    return SUCCESS;
108}
109
110PHPAPI php_stream *php_stream_encloses(php_stream *enclosing, php_stream *enclosed)
111{
112    php_stream *orig = enclosed->enclosing_stream;
113
114    php_stream_auto_cleanup(enclosed);
115    enclosed->enclosing_stream = enclosing;
116    return orig;
117}
118
119PHPAPI int php_stream_from_persistent_id(const char *persistent_id, php_stream **stream TSRMLS_DC)
120{
121    zend_resource *le;
122
123    if ((le = zend_hash_str_find_ptr(&EG(persistent_list), persistent_id, strlen(persistent_id))) != NULL) {
124        if (le->type == le_pstream) {
125            if (stream) {
126                HashPosition pos;
127                zend_resource *regentry;
128
129                /* see if this persistent resource already has been loaded to the
130                 * regular list; allowing the same resource in several entries in the
131                 * regular list causes trouble (see bug #54623) */
132                zend_hash_internal_pointer_reset_ex(&EG(regular_list), &pos);
133                while ((regentry = zend_hash_get_current_data_ptr_ex(&EG(regular_list), &pos)) != NULL) {
134                    if (regentry->ptr == le->ptr) {
135                        break;
136                    }
137                    zend_hash_move_forward_ex(&EG(regular_list), &pos);
138                }
139
140                *stream = (php_stream*)le->ptr;
141                if (!regentry) { /* not found in regular list */
142                    GC_REFCOUNT(le)++;
143                    (*stream)->res = ZEND_REGISTER_RESOURCE(NULL, *stream, le_pstream);
144                } else {
145                    GC_REFCOUNT(regentry)++;
146                    (*stream)->res = regentry;
147                }
148            }
149            return PHP_STREAM_PERSISTENT_SUCCESS;
150        }
151        return PHP_STREAM_PERSISTENT_FAILURE;
152    }
153    return PHP_STREAM_PERSISTENT_NOT_EXIST;
154}
155
156/* }}} */
157
158static zend_llist *php_get_wrapper_errors_list(php_stream_wrapper *wrapper TSRMLS_DC)
159{
160    if (!FG(wrapper_errors)) {
161        return NULL;
162    } else {
163        return (zend_llist*) zend_hash_str_find_ptr(FG(wrapper_errors), (const char*)&wrapper, sizeof(wrapper));
164    }
165}
166
167/* {{{ wrapper error reporting */
168void php_stream_display_wrapper_errors(php_stream_wrapper *wrapper, const char *path, const char *caption TSRMLS_DC)
169{
170    char *tmp = estrdup(path);
171    char *msg;
172    int free_msg = 0;
173
174    if (wrapper) {
175        zend_llist *err_list = php_get_wrapper_errors_list(wrapper TSRMLS_CC);
176        if (err_list) {
177            size_t l = 0;
178            int brlen;
179            int i;
180            int count = zend_llist_count(err_list);
181            const char *br;
182            const char **err_buf_p;
183            zend_llist_position pos;
184
185            if (PG(html_errors)) {
186                brlen = 7;
187                br = "<br />\n";
188            } else {
189                brlen = 1;
190                br = "\n";
191            }
192
193            for (err_buf_p = zend_llist_get_first_ex(err_list, &pos), i = 0;
194                    err_buf_p;
195                    err_buf_p = zend_llist_get_next_ex(err_list, &pos), i++) {
196                l += strlen(*err_buf_p);
197                if (i < count - 1) {
198                    l += brlen;
199                }
200            }
201            msg = emalloc(l + 1);
202            msg[0] = '\0';
203            for (err_buf_p = zend_llist_get_first_ex(err_list, &pos), i = 0;
204                    err_buf_p;
205                    err_buf_p = zend_llist_get_next_ex(err_list, &pos), i++) {
206                strcat(msg, *err_buf_p);
207                if (i < count - 1) {
208                    strcat(msg, br);
209                }
210            }
211
212            free_msg = 1;
213        } else {
214            if (wrapper == &php_plain_files_wrapper) {
215                msg = strerror(errno); /* TODO: not ts on linux */
216            } else {
217                msg = "operation failed";
218            }
219        }
220    } else {
221        msg = "no suitable wrapper could be found";
222    }
223
224    php_strip_url_passwd(tmp);
225    php_error_docref1(NULL TSRMLS_CC, tmp, E_WARNING, "%s: %s", caption, msg);
226    efree(tmp);
227    if (free_msg) {
228        efree(msg);
229    }
230}
231
232void php_stream_tidy_wrapper_error_log(php_stream_wrapper *wrapper TSRMLS_DC)
233{
234    if (wrapper && FG(wrapper_errors)) {
235        zend_hash_str_del(FG(wrapper_errors), (const char*)&wrapper, sizeof(wrapper));
236    }
237}
238
239static void wrapper_error_dtor(void *error)
240{
241    efree(*(char**)error);
242}
243
244static void wrapper_list_dtor(zval *item) {
245    zend_llist *list = (zend_llist*)Z_PTR_P(item);
246    zend_llist_destroy(list);
247    efree(list);
248}
249
250PHPAPI void php_stream_wrapper_log_error(php_stream_wrapper *wrapper, int options TSRMLS_DC, const char *fmt, ...)
251{
252    va_list args;
253    char *buffer = NULL;
254
255    va_start(args, fmt);
256    vspprintf(&buffer, 0, fmt, args);
257    va_end(args);
258
259    if (options & REPORT_ERRORS || wrapper == NULL) {
260        php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", buffer);
261        efree(buffer);
262    } else {
263        zend_llist *list = NULL;
264        if (!FG(wrapper_errors)) {
265            ALLOC_HASHTABLE(FG(wrapper_errors));
266            zend_hash_init(FG(wrapper_errors), 8, NULL, wrapper_list_dtor, 0);
267        } else {
268            list = zend_hash_str_find_ptr(FG(wrapper_errors), (const char*)&wrapper, sizeof(wrapper));
269        }
270
271        if (!list) {
272            zend_llist new_list;
273            zend_llist_init(&new_list, sizeof(buffer), wrapper_error_dtor, 0);
274            list = zend_hash_str_update_mem(FG(wrapper_errors), (const char*)&wrapper,
275                    sizeof(wrapper), &new_list, sizeof(new_list));
276        }
277
278        /* append to linked list */
279        zend_llist_add_element(list, &buffer);
280    }
281}
282
283
284/* }}} */
285
286/* allocate a new stream for a particular ops */
287PHPAPI php_stream *_php_stream_alloc(php_stream_ops *ops, void *abstract, const char *persistent_id, const char *mode STREAMS_DC TSRMLS_DC) /* {{{ */
288{
289    php_stream *ret;
290
291    ret = (php_stream*) pemalloc_rel_orig(sizeof(php_stream), persistent_id ? 1 : 0);
292
293    memset(ret, 0, sizeof(php_stream));
294
295    ret->readfilters.stream = ret;
296    ret->writefilters.stream = ret;
297
298#if STREAM_DEBUG
299fprintf(stderr, "stream_alloc: %s:%p persistent=%s\n", ops->label, ret, persistent_id);
300#endif
301
302    ret->ops = ops;
303    ret->abstract = abstract;
304    ret->is_persistent = persistent_id ? 1 : 0;
305    ret->chunk_size = FG(def_chunk_size);
306
307#if ZEND_DEBUG
308    ret->open_filename = __zend_orig_filename ? __zend_orig_filename : __zend_filename;
309    ret->open_lineno = __zend_orig_lineno ? __zend_orig_lineno : __zend_lineno;
310#endif
311
312    if (FG(auto_detect_line_endings)) {
313        ret->flags |= PHP_STREAM_FLAG_DETECT_EOL;
314    }
315
316    if (persistent_id) {
317        zval tmp;
318
319        ZVAL_NEW_PERSISTENT_RES(&tmp, -1, ret, le_pstream);
320
321        if (NULL == zend_hash_str_update(&EG(persistent_list), persistent_id,
322                    strlen(persistent_id), &tmp)) {
323            pefree(ret, 1);
324            return NULL;
325        }
326    }
327
328    ret->res = ZEND_REGISTER_RESOURCE(NULL, ret, persistent_id ? le_pstream : le_stream);
329    strlcpy(ret->mode, mode, sizeof(ret->mode));
330
331    ret->wrapper          = NULL;
332    ret->wrapperthis      = NULL;
333    ZVAL_UNDEF(&ret->wrapperdata);
334    ret->stdiocast        = NULL;
335    ret->orig_path        = NULL;
336    ret->ctx              = NULL;
337    ret->readbuf          = NULL;
338    ret->enclosing_stream = NULL;
339
340    return ret;
341}
342/* }}} */
343
344PHPAPI int _php_stream_free_enclosed(php_stream *stream_enclosed, int close_options TSRMLS_DC) /* {{{ */
345{
346    return _php_stream_free(stream_enclosed,
347        close_options | PHP_STREAM_FREE_IGNORE_ENCLOSING TSRMLS_CC);
348}
349/* }}} */
350
351#if STREAM_DEBUG
352static const char *_php_stream_pretty_free_options(int close_options, char *out)
353{
354    if (close_options & PHP_STREAM_FREE_CALL_DTOR)
355        strcat(out, "CALL_DTOR, ");
356    if (close_options & PHP_STREAM_FREE_RELEASE_STREAM)
357        strcat(out, "RELEASE_STREAM, ");
358    if (close_options & PHP_STREAM_FREE_PRESERVE_HANDLE)
359        strcat(out, "PREVERSE_HANDLE, ");
360    if (close_options & PHP_STREAM_FREE_RSRC_DTOR)
361        strcat(out, "RSRC_DTOR, ");
362    if (close_options & PHP_STREAM_FREE_PERSISTENT)
363        strcat(out, "PERSISTENT, ");
364    if (close_options & PHP_STREAM_FREE_IGNORE_ENCLOSING)
365        strcat(out, "IGNORE_ENCLOSING, ");
366    if (out[0] != '\0')
367        out[strlen(out) - 2] = '\0';
368    return out;
369}
370#endif
371
372static int _php_stream_free_persistent(zval *zv, void *pStream TSRMLS_DC)
373{
374    zend_resource *le = Z_RES_P(zv);
375    return le->ptr == pStream;
376}
377
378
379PHPAPI int _php_stream_free(php_stream *stream, int close_options TSRMLS_DC) /* {{{ */
380{
381    int ret = 1;
382    int preserve_handle = close_options & PHP_STREAM_FREE_PRESERVE_HANDLE ? 1 : 0;
383    int release_cast = 1;
384    php_stream_context *context = NULL;
385
386    /* on an resource list destruction, the context, another resource, may have
387     * already been freed (if it was created after the stream resource), so
388     * don't reference it */
389    if (EG(active)) {
390        context = PHP_STREAM_CONTEXT(stream);
391    }
392
393    if (stream->flags & PHP_STREAM_FLAG_NO_CLOSE) {
394        preserve_handle = 1;
395    }
396
397#if STREAM_DEBUG
398    {
399        char out[200] = "";
400        fprintf(stderr, "stream_free: %s:%p[%s] in_free=%d opts=%s\n",
401            stream->ops->label, stream, stream->orig_path, stream->in_free, _php_stream_pretty_free_options(close_options, out));
402    }
403
404#endif
405
406    if (stream->in_free) {
407        /* hopefully called recursively from the enclosing stream; the pointer was NULLed below */
408        if ((stream->in_free == 1) && (close_options & PHP_STREAM_FREE_IGNORE_ENCLOSING) && (stream->enclosing_stream == NULL)) {
409            close_options |= PHP_STREAM_FREE_RSRC_DTOR; /* restore flag */
410        } else {
411            return 1; /* recursion protection */
412        }
413    }
414
415    stream->in_free++;
416
417    /* force correct order on enclosing/enclosed stream destruction (only from resource
418     * destructor as in when reverse destroying the resource list) */
419    if ((close_options & PHP_STREAM_FREE_RSRC_DTOR) &&
420            !(close_options & PHP_STREAM_FREE_IGNORE_ENCLOSING) &&
421            (close_options & (PHP_STREAM_FREE_CALL_DTOR | PHP_STREAM_FREE_RELEASE_STREAM)) && /* always? */
422            (stream->enclosing_stream != NULL)) {
423        php_stream *enclosing_stream = stream->enclosing_stream;
424        stream->enclosing_stream = NULL;
425        /* we force PHP_STREAM_CALL_DTOR because that's from where the
426         * enclosing stream can free this stream. We remove rsrc_dtor because
427         * we want the enclosing stream to be deleted from the resource list */
428        return _php_stream_free(enclosing_stream,
429            (close_options | PHP_STREAM_FREE_CALL_DTOR) & ~PHP_STREAM_FREE_RSRC_DTOR TSRMLS_CC);
430    }
431
432    /* if we are releasing the stream only (and preserving the underlying handle),
433     * we need to do things a little differently.
434     * We are only ever called like this when the stream is cast to a FILE*
435     * for include (or other similar) purposes.
436     * */
437    if (preserve_handle) {
438        if (stream->fclose_stdiocast == PHP_STREAM_FCLOSE_FOPENCOOKIE) {
439            /* If the stream was fopencookied, we must NOT touch anything
440             * here, as the cookied stream relies on it all.
441             * Instead, mark the stream as OK to auto-clean */
442            php_stream_auto_cleanup(stream);
443            stream->in_free--;
444            return 0;
445        }
446        /* otherwise, make sure that we don't close the FILE* from a cast */
447        release_cast = 0;
448    }
449
450#if STREAM_DEBUG
451fprintf(stderr, "stream_free: %s:%p[%s] preserve_handle=%d release_cast=%d remove_rsrc=%d\n",
452        stream->ops->label, stream, stream->orig_path, preserve_handle, release_cast,
453        (close_options & PHP_STREAM_FREE_RSRC_DTOR) == 0);
454#endif
455
456    /* make sure everything is saved */
457    _php_stream_flush(stream, 1 TSRMLS_CC);
458
459    /* If not called from the resource dtor, remove the stream from the resource list. */
460    if ((close_options & PHP_STREAM_FREE_RSRC_DTOR) == 0 && stream->res) {
461        /* zend_list_delete actually only decreases the refcount; if we're
462         * releasing the stream, we want to actually delete the resource from
463         * the resource list, otherwise the resource will point to invalid memory.
464         * In any case, let's always completely delete it from the resource list,
465         * not only when PHP_STREAM_FREE_RELEASE_STREAM is set */
466//???       while (zend_list_delete(stream->res) == SUCCESS) {}
467//???       stream->res->gc.refcount = 0;
468        zend_list_close(stream->res);
469        if (!stream->__exposed) {
470            zend_list_delete(stream->res);
471            stream->res = NULL;
472        }
473    }
474
475    if (close_options & PHP_STREAM_FREE_CALL_DTOR) {
476        if (release_cast && stream->fclose_stdiocast == PHP_STREAM_FCLOSE_FOPENCOOKIE) {
477            /* calling fclose on an fopencookied stream will ultimately
478                call this very same function.  If we were called via fclose,
479                the cookie_closer unsets the fclose_stdiocast flags, so
480                we can be sure that we only reach here when PHP code calls
481                php_stream_free.
482                Lets let the cookie code clean it all up.
483             */
484            stream->in_free = 0;
485            return fclose(stream->stdiocast);
486        }
487
488        ret = stream->ops->close(stream, preserve_handle ? 0 : 1 TSRMLS_CC);
489        stream->abstract = NULL;
490
491        /* tidy up any FILE* that might have been fdopened */
492        if (release_cast && stream->fclose_stdiocast == PHP_STREAM_FCLOSE_FDOPEN && stream->stdiocast) {
493            fclose(stream->stdiocast);
494            stream->stdiocast = NULL;
495            stream->fclose_stdiocast = PHP_STREAM_FCLOSE_NONE;
496        }
497    }
498
499    if (close_options & PHP_STREAM_FREE_RELEASE_STREAM) {
500        while (stream->readfilters.head) {
501            php_stream_filter_remove(stream->readfilters.head, 1 TSRMLS_CC);
502        }
503        while (stream->writefilters.head) {
504            php_stream_filter_remove(stream->writefilters.head, 1 TSRMLS_CC);
505        }
506
507        if (stream->wrapper && stream->wrapper->wops && stream->wrapper->wops->stream_closer) {
508            stream->wrapper->wops->stream_closer(stream->wrapper, stream TSRMLS_CC);
509            stream->wrapper = NULL;
510        }
511
512        if (Z_TYPE(stream->wrapperdata) != IS_UNDEF) {
513            zval_ptr_dtor(&stream->wrapperdata);
514            ZVAL_UNDEF(&stream->wrapperdata);
515        }
516
517        if (stream->readbuf) {
518            pefree(stream->readbuf, stream->is_persistent);
519            stream->readbuf = NULL;
520        }
521
522        if (stream->is_persistent && (close_options & PHP_STREAM_FREE_PERSISTENT)) {
523            /* we don't work with *stream but need its value for comparison */
524            zend_hash_apply_with_argument(&EG(persistent_list), _php_stream_free_persistent, stream TSRMLS_CC);
525        }
526#if ZEND_DEBUG
527        if ((close_options & PHP_STREAM_FREE_RSRC_DTOR) && (stream->__exposed == 0) && (EG(error_reporting) & E_WARNING)) {
528            /* it leaked: Lets deliberately NOT pefree it so that the memory manager shows it
529             * as leaked; it will log a warning, but lets help it out and display what kind
530             * of stream it was. */
531            if (!CG(unclean_shutdown)) {
532                char *leakinfo;
533                spprintf(&leakinfo, 0, __FILE__ "(%d) : Stream of type '%s' %p (path:%s) was not closed\n", __LINE__, stream->ops->label, stream, stream->orig_path);
534
535                if (stream->orig_path) {
536                    pefree(stream->orig_path, stream->is_persistent);
537                    stream->orig_path = NULL;
538                }
539
540# if defined(PHP_WIN32)
541                OutputDebugString(leakinfo);
542# else
543                fprintf(stderr, "%s", leakinfo);
544# endif
545                efree(leakinfo);
546            }
547        } else {
548            if (stream->orig_path) {
549                pefree(stream->orig_path, stream->is_persistent);
550                stream->orig_path = NULL;
551            }
552
553            pefree(stream, stream->is_persistent);
554        }
555#else
556        if (stream->orig_path) {
557            pefree(stream->orig_path, stream->is_persistent);
558            stream->orig_path = NULL;
559        }
560
561        pefree(stream, stream->is_persistent);
562#endif
563    }
564
565    if (context) {
566        zend_list_delete(context->res);
567    }
568
569    return ret;
570}
571/* }}} */
572
573/* {{{ generic stream operations */
574
575PHPAPI void _php_stream_fill_read_buffer(php_stream *stream, size_t size TSRMLS_DC)
576{
577    /* allocate/fill the buffer */
578
579    if (stream->readfilters.head) {
580        char *chunk_buf;
581        int err_flag = 0;
582        php_stream_bucket_brigade brig_in = { NULL, NULL }, brig_out = { NULL, NULL };
583        php_stream_bucket_brigade *brig_inp = &brig_in, *brig_outp = &brig_out, *brig_swap;
584
585        /* Invalidate the existing cache, otherwise reads can fail, see note in
586           main/streams/filter.c::_php_stream_filter_append */
587        stream->writepos = stream->readpos = 0;
588
589        /* allocate a buffer for reading chunks */
590        chunk_buf = emalloc(stream->chunk_size);
591
592        while (!stream->eof && !err_flag && (stream->writepos - stream->readpos < (off_t)size)) {
593            size_t justread = 0;
594            int flags;
595            php_stream_bucket *bucket;
596            php_stream_filter_status_t status = PSFS_ERR_FATAL;
597            php_stream_filter *filter;
598
599            /* read a chunk into a bucket */
600            justread = stream->ops->read(stream, chunk_buf, stream->chunk_size TSRMLS_CC);
601            if (justread && justread != (size_t)-1) {
602                bucket = php_stream_bucket_new(stream, chunk_buf, justread, 0, 0 TSRMLS_CC);
603
604                /* after this call, bucket is owned by the brigade */
605                php_stream_bucket_append(brig_inp, bucket TSRMLS_CC);
606
607                flags = PSFS_FLAG_NORMAL;
608            } else {
609                flags = stream->eof ? PSFS_FLAG_FLUSH_CLOSE : PSFS_FLAG_FLUSH_INC;
610            }
611
612            /* wind the handle... */
613            for (filter = stream->readfilters.head; filter; filter = filter->next) {
614                status = filter->fops->filter(stream, filter, brig_inp, brig_outp, NULL, flags TSRMLS_CC);
615
616                if (status != PSFS_PASS_ON) {
617                    break;
618                }
619
620                /* brig_out becomes brig_in.
621                 * brig_in will always be empty here, as the filter MUST attach any un-consumed buckets
622                 * to its own brigade */
623                brig_swap = brig_inp;
624                brig_inp = brig_outp;
625                brig_outp = brig_swap;
626                memset(brig_outp, 0, sizeof(*brig_outp));
627            }
628
629            switch (status) {
630                case PSFS_PASS_ON:
631                    /* we get here when the last filter in the chain has data to pass on.
632                     * in this situation, we are passing the brig_in brigade into the
633                     * stream read buffer */
634                    while (brig_inp->head) {
635                        bucket = brig_inp->head;
636                        /* grow buffer to hold this bucket
637                         * TODO: this can fail for persistent streams */
638                        if (stream->readbuflen - stream->writepos < bucket->buflen) {
639                            stream->readbuflen += bucket->buflen;
640                            stream->readbuf = perealloc(stream->readbuf, stream->readbuflen,
641                                    stream->is_persistent);
642                        }
643                        memcpy(stream->readbuf + stream->writepos, bucket->buf, bucket->buflen);
644                        stream->writepos += bucket->buflen;
645
646                        php_stream_bucket_unlink(bucket TSRMLS_CC);
647                        php_stream_bucket_delref(bucket TSRMLS_CC);
648                    }
649                    break;
650
651                case PSFS_FEED_ME:
652                    /* when a filter needs feeding, there is no brig_out to deal with.
653                     * we simply continue the loop; if the caller needs more data,
654                     * we will read again, otherwise out job is done here */
655                    if (justread == 0) {
656                        /* there is no data */
657                        err_flag = 1;
658                        break;
659                    }
660                    continue;
661
662                case PSFS_ERR_FATAL:
663                    /* some fatal error. Theoretically, the stream is borked, so all
664                     * further reads should fail. */
665                    err_flag = 1;
666                    break;
667            }
668
669            if (justread == 0 || justread == (size_t)-1) {
670                break;
671            }
672        }
673
674        efree(chunk_buf);
675
676    } else {
677        /* is there enough data in the buffer ? */
678        if (stream->writepos - stream->readpos < (zend_off_t)size) {
679            size_t justread = 0;
680
681            /* reduce buffer memory consumption if possible, to avoid a realloc */
682            if (stream->readbuf && stream->readbuflen - stream->writepos < stream->chunk_size) {
683                memmove(stream->readbuf, stream->readbuf + stream->readpos, stream->readbuflen - stream->readpos);
684                stream->writepos -= stream->readpos;
685                stream->readpos = 0;
686            }
687
688            /* grow the buffer if required
689             * TODO: this can fail for persistent streams */
690            if (stream->readbuflen - stream->writepos < stream->chunk_size) {
691                stream->readbuflen += stream->chunk_size;
692                stream->readbuf = perealloc(stream->readbuf, stream->readbuflen,
693                        stream->is_persistent);
694            }
695
696            justread = stream->ops->read(stream, (char*)stream->readbuf + stream->writepos,
697                    stream->readbuflen - stream->writepos
698                    TSRMLS_CC);
699
700            if (justread != (size_t)-1) {
701                stream->writepos += justread;
702            }
703        }
704    }
705}
706
707PHPAPI size_t _php_stream_read(php_stream *stream, char *buf, size_t size TSRMLS_DC)
708{
709    size_t toread = 0, didread = 0;
710
711    while (size > 0) {
712
713        /* take from the read buffer first.
714         * It is possible that a buffered stream was switched to non-buffered, so we
715         * drain the remainder of the buffer before using the "raw" read mode for
716         * the excess */
717        if (stream->writepos > stream->readpos) {
718
719            toread = stream->writepos - stream->readpos;
720            if (toread > size) {
721                toread = size;
722            }
723
724            memcpy(buf, stream->readbuf + stream->readpos, toread);
725            stream->readpos += toread;
726            size -= toread;
727            buf += toread;
728            didread += toread;
729        }
730
731        /* ignore eof here; the underlying state might have changed */
732        if (size == 0) {
733            break;
734        }
735
736        if (!stream->readfilters.head && (stream->flags & PHP_STREAM_FLAG_NO_BUFFER || stream->chunk_size == 1)) {
737            toread = stream->ops->read(stream, buf, size TSRMLS_CC);
738            if (toread == (size_t) -1) {
739                /* e.g. underlying read(2) returned -1 */
740                break;
741            }
742        } else {
743            php_stream_fill_read_buffer(stream, size);
744
745            toread = stream->writepos - stream->readpos;
746            if (toread > size) {
747                toread = size;
748            }
749
750            if (toread > 0) {
751                memcpy(buf, stream->readbuf + stream->readpos, toread);
752                stream->readpos += toread;
753            }
754        }
755        if (toread > 0) {
756            didread += toread;
757            buf += toread;
758            size -= toread;
759        } else {
760            /* EOF, or temporary end of data (for non-blocking mode). */
761            break;
762        }
763
764        /* just break anyway, to avoid greedy read */
765        if (stream->wrapper != &php_plain_files_wrapper) {
766            break;
767        }
768    }
769
770    if (didread > 0) {
771        stream->position += didread;
772    }
773
774    return didread;
775}
776
777PHPAPI int _php_stream_eof(php_stream *stream TSRMLS_DC)
778{
779    /* if there is data in the buffer, it's not EOF */
780    if (stream->writepos - stream->readpos > 0) {
781        return 0;
782    }
783
784    /* use the configured timeout when checking eof */
785    if (!stream->eof && PHP_STREAM_OPTION_RETURN_ERR ==
786            php_stream_set_option(stream, PHP_STREAM_OPTION_CHECK_LIVENESS,
787            0, NULL)) {
788        stream->eof = 1;
789    }
790
791    return stream->eof;
792}
793
794PHPAPI int _php_stream_putc(php_stream *stream, int c TSRMLS_DC)
795{
796    unsigned char buf = c;
797
798    if (php_stream_write(stream, (char*)&buf, 1) > 0) {
799        return 1;
800    }
801    return EOF;
802}
803
804PHPAPI int _php_stream_getc(php_stream *stream TSRMLS_DC)
805{
806    char buf;
807
808    if (php_stream_read(stream, &buf, 1) > 0) {
809        return buf & 0xff;
810    }
811    return EOF;
812}
813
814PHPAPI int _php_stream_puts(php_stream *stream, const char *buf TSRMLS_DC)
815{
816    int len;
817    char newline[2] = "\n"; /* is this OK for Win? */
818    len = strlen(buf);
819
820    if (len > 0 && php_stream_write(stream, buf, len) && php_stream_write(stream, newline, 1)) {
821        return 1;
822    }
823    return 0;
824}
825
826PHPAPI int _php_stream_stat(php_stream *stream, php_stream_statbuf *ssb TSRMLS_DC)
827{
828    memset(ssb, 0, sizeof(*ssb));
829
830    /* if the stream was wrapped, allow the wrapper to stat it */
831    if (stream->wrapper && stream->wrapper->wops->stream_stat != NULL) {
832        return stream->wrapper->wops->stream_stat(stream->wrapper, stream, ssb TSRMLS_CC);
833    }
834
835    /* if the stream doesn't directly support stat-ing, return with failure.
836     * We could try and emulate this by casting to a FD and fstat-ing it,
837     * but since the fd might not represent the actual underlying content
838     * this would give bogus results. */
839    if (stream->ops->stat == NULL) {
840        return -1;
841    }
842
843    return (stream->ops->stat)(stream, ssb TSRMLS_CC);
844}
845
846PHPAPI const char *php_stream_locate_eol(php_stream *stream, zend_string *buf TSRMLS_DC)
847{
848    size_t avail;
849    const char *cr, *lf, *eol = NULL;
850    const char *readptr;
851
852    if (!buf) {
853        readptr = (char*)stream->readbuf + stream->readpos;
854        avail = stream->writepos - stream->readpos;
855    } else {
856        readptr = buf->val;
857        avail = buf->len;
858    }
859
860    /* Look for EOL */
861    if (stream->flags & PHP_STREAM_FLAG_DETECT_EOL) {
862        cr = memchr(readptr, '\r', avail);
863        lf = memchr(readptr, '\n', avail);
864
865        if (cr && lf != cr + 1 && !(lf && lf < cr)) {
866            /* mac */
867            stream->flags ^= PHP_STREAM_FLAG_DETECT_EOL;
868            stream->flags |= PHP_STREAM_FLAG_EOL_MAC;
869            eol = cr;
870        } else if ((cr && lf && cr == lf - 1) || (lf)) {
871            /* dos or unix endings */
872            stream->flags ^= PHP_STREAM_FLAG_DETECT_EOL;
873            eol = lf;
874        }
875    } else if (stream->flags & PHP_STREAM_FLAG_EOL_MAC) {
876        eol = memchr(readptr, '\r', avail);
877    } else {
878        /* unix (and dos) line endings */
879        eol = memchr(readptr, '\n', avail);
880    }
881
882    return eol;
883}
884
885/* If buf == NULL, the buffer will be allocated automatically and will be of an
886 * appropriate length to hold the line, regardless of the line length, memory
887 * permitting */
888PHPAPI char *_php_stream_get_line(php_stream *stream, char *buf, size_t maxlen,
889        size_t *returned_len TSRMLS_DC)
890{
891    size_t avail = 0;
892    size_t current_buf_size = 0;
893    size_t total_copied = 0;
894    int grow_mode = 0;
895    char *bufstart = buf;
896
897    if (buf == NULL) {
898        grow_mode = 1;
899    } else if (maxlen == 0) {
900        return NULL;
901    }
902
903    /*
904     * If the underlying stream operations block when no new data is readable,
905     * we need to take extra precautions.
906     *
907     * If there is buffered data available, we check for a EOL. If it exists,
908     * we pass the data immediately back to the caller. This saves a call
909     * to the read implementation and will not block where blocking
910     * is not necessary at all.
911     *
912     * If the stream buffer contains more data than the caller requested,
913     * we can also avoid that costly step and simply return that data.
914     */
915
916    for (;;) {
917        avail = stream->writepos - stream->readpos;
918
919        if (avail > 0) {
920            size_t cpysz = 0;
921            char *readptr;
922            const char *eol;
923            int done = 0;
924
925            readptr = (char*)stream->readbuf + stream->readpos;
926            eol = php_stream_locate_eol(stream, NULL TSRMLS_CC);
927
928            if (eol) {
929                cpysz = eol - readptr + 1;
930                done = 1;
931            } else {
932                cpysz = avail;
933            }
934
935            if (grow_mode) {
936                /* allow room for a NUL. If this realloc is really a realloc
937                 * (ie: second time around), we get an extra byte. In most
938                 * cases, with the default chunk size of 8K, we will only
939                 * incur that overhead once.  When people have lines longer
940                 * than 8K, we waste 1 byte per additional 8K or so.
941                 * That seems acceptable to me, to avoid making this code
942                 * hard to follow */
943                bufstart = erealloc(bufstart, current_buf_size + cpysz + 1);
944                current_buf_size += cpysz + 1;
945                buf = bufstart + total_copied;
946            } else {
947                if (cpysz >= maxlen - 1) {
948                    cpysz = maxlen - 1;
949                    done = 1;
950                }
951            }
952
953            memcpy(buf, readptr, cpysz);
954
955            stream->position += cpysz;
956            stream->readpos += cpysz;
957            buf += cpysz;
958            maxlen -= cpysz;
959            total_copied += cpysz;
960
961            if (done) {
962                break;
963            }
964        } else if (stream->eof) {
965            break;
966        } else {
967            /* XXX: Should be fine to always read chunk_size */
968            size_t toread;
969
970            if (grow_mode) {
971                toread = stream->chunk_size;
972            } else {
973                toread = maxlen - 1;
974                if (toread > stream->chunk_size) {
975                    toread = stream->chunk_size;
976                }
977            }
978
979            php_stream_fill_read_buffer(stream, toread);
980
981            if (stream->writepos - stream->readpos == 0) {
982                break;
983            }
984        }
985    }
986
987    if (total_copied == 0) {
988        if (grow_mode) {
989            assert(bufstart == NULL);
990        }
991        return NULL;
992    }
993
994    buf[0] = '\0';
995    if (returned_len) {
996        *returned_len = total_copied;
997    }
998
999    return bufstart;
1000}
1001
1002#define STREAM_BUFFERED_AMOUNT(stream) \
1003    ((size_t)(((stream)->writepos) - (stream)->readpos))
1004
1005static const char *_php_stream_search_delim(php_stream *stream,
1006                                            size_t maxlen,
1007                                            size_t skiplen,
1008                                            const char *delim, /* non-empty! */
1009                                            size_t delim_len TSRMLS_DC)
1010{
1011    size_t  seek_len;
1012
1013    /* set the maximum number of bytes we're allowed to read from buffer */
1014    seek_len = MIN(STREAM_BUFFERED_AMOUNT(stream), maxlen);
1015    if (seek_len <= skiplen) {
1016        return NULL;
1017    }
1018
1019    if (delim_len == 1) {
1020        return memchr(&stream->readbuf[stream->readpos + skiplen],
1021            delim[0], seek_len - skiplen);
1022    } else {
1023        return php_memnstr((char*)&stream->readbuf[stream->readpos + skiplen],
1024                delim, delim_len,
1025                (char*)&stream->readbuf[stream->readpos + seek_len]);
1026    }
1027}
1028
1029PHPAPI zend_string *php_stream_get_record(php_stream *stream, size_t maxlen, const char *delim, size_t delim_len TSRMLS_DC)
1030{
1031    zend_string *ret_buf;               /* returned buffer */
1032    const char *found_delim = NULL;
1033    size_t  buffered_len,
1034            tent_ret_len;           /* tentative returned length */
1035    int has_delim = delim_len > 0;
1036
1037    if (maxlen == 0) {
1038        return NULL;
1039    }
1040
1041    if (has_delim) {
1042        found_delim = _php_stream_search_delim(
1043            stream, maxlen, 0, delim, delim_len TSRMLS_CC);
1044    }
1045
1046    buffered_len = STREAM_BUFFERED_AMOUNT(stream);
1047    /* try to read up to maxlen length bytes while we don't find the delim */
1048    while (!found_delim && buffered_len < maxlen) {
1049        size_t  just_read,
1050                to_read_now;
1051
1052        to_read_now = MIN(maxlen - buffered_len, stream->chunk_size);
1053
1054        php_stream_fill_read_buffer(stream, buffered_len + to_read_now);
1055
1056        just_read = STREAM_BUFFERED_AMOUNT(stream) - buffered_len;
1057
1058        /* Assume the stream is temporarily or permanently out of data */
1059        if (just_read == 0) {
1060            break;
1061        }
1062
1063        if (has_delim) {
1064            /* search for delimiter, but skip buffered_len (the number of bytes
1065             * buffered before this loop iteration), as they have already been
1066             * searched for the delimiter.
1067             * The left part of the delimiter may still remain in the buffer,
1068             * so subtract up to <delim_len - 1> from buffered_len, which is
1069             * the ammount of data we skip on this search  as an optimization
1070             */
1071            found_delim = _php_stream_search_delim(
1072                stream, maxlen,
1073                buffered_len >= (delim_len - 1)
1074                        ? buffered_len - (delim_len - 1)
1075                        : 0,
1076                delim, delim_len TSRMLS_CC);
1077            if (found_delim) {
1078                break;
1079            }
1080        }
1081        buffered_len += just_read;
1082    }
1083
1084    if (has_delim && found_delim) {
1085        tent_ret_len = found_delim - (char*)&stream->readbuf[stream->readpos];
1086    } else if (!has_delim && STREAM_BUFFERED_AMOUNT(stream) >= maxlen) {
1087        tent_ret_len = maxlen;
1088    } else {
1089        /* return with error if the delimiter string (if any) was not found, we
1090         * could not completely fill the read buffer with maxlen bytes and we
1091         * don't know we've reached end of file. Added with non-blocking streams
1092         * in mind, where this situation is frequent */
1093        if (STREAM_BUFFERED_AMOUNT(stream) < maxlen && !stream->eof) {
1094            return NULL;
1095        } else if (STREAM_BUFFERED_AMOUNT(stream) == 0 && stream->eof) {
1096            /* refuse to return an empty string just because by accident
1097             * we knew of EOF in a read that returned no data */
1098            return NULL;
1099        } else {
1100            tent_ret_len = MIN(STREAM_BUFFERED_AMOUNT(stream), maxlen);
1101        }
1102    }
1103
1104    ret_buf = zend_string_alloc(tent_ret_len, 0);
1105    /* php_stream_read will not call ops->read here because the necessary
1106     * data is guaranteedly buffered */
1107    ret_buf->len = php_stream_read(stream, ret_buf->val, tent_ret_len);
1108
1109    if (found_delim) {
1110        stream->readpos += delim_len;
1111        stream->position += delim_len;
1112    }
1113    ret_buf->val[ret_buf->len] = '\0';
1114    return ret_buf;
1115}
1116
1117/* Writes a buffer directly to a stream, using multiple of the chunk size */
1118static size_t _php_stream_write_buffer(php_stream *stream, const char *buf, size_t count TSRMLS_DC)
1119{
1120    size_t didwrite = 0, towrite, justwrote;
1121
1122    /* if we have a seekable stream we need to ensure that data is written at the
1123     * current stream->position. This means invalidating the read buffer and then
1124     * performing a low-level seek */
1125    if (stream->ops->seek && (stream->flags & PHP_STREAM_FLAG_NO_SEEK) == 0 && stream->readpos != stream->writepos) {
1126        stream->readpos = stream->writepos = 0;
1127
1128        stream->ops->seek(stream, stream->position, SEEK_SET, &stream->position TSRMLS_CC);
1129    }
1130
1131
1132    while (count > 0) {
1133        towrite = count;
1134        if (towrite > stream->chunk_size)
1135            towrite = stream->chunk_size;
1136
1137        justwrote = stream->ops->write(stream, buf, towrite TSRMLS_CC);
1138
1139        /* convert justwrote to an integer, since normally it is unsigned */
1140        if ((int)justwrote > 0) {
1141            buf += justwrote;
1142            count -= justwrote;
1143            didwrite += justwrote;
1144
1145            /* Only screw with the buffer if we can seek, otherwise we lose data
1146             * buffered from fifos and sockets */
1147            if (stream->ops->seek && (stream->flags & PHP_STREAM_FLAG_NO_SEEK) == 0) {
1148                stream->position += justwrote;
1149            }
1150        } else {
1151            break;
1152        }
1153    }
1154    return didwrite;
1155
1156}
1157
1158/* push some data through the write filter chain.
1159 * buf may be NULL, if flags are set to indicate a flush.
1160 * This may trigger a real write to the stream.
1161 * Returns the number of bytes consumed from buf by the first filter in the chain.
1162 * */
1163static size_t _php_stream_write_filtered(php_stream *stream, const char *buf, size_t count, int flags TSRMLS_DC)
1164{
1165    size_t consumed = 0;
1166    php_stream_bucket *bucket;
1167    php_stream_bucket_brigade brig_in = { NULL, NULL }, brig_out = { NULL, NULL };
1168    php_stream_bucket_brigade *brig_inp = &brig_in, *brig_outp = &brig_out, *brig_swap;
1169    php_stream_filter_status_t status = PSFS_ERR_FATAL;
1170    php_stream_filter *filter;
1171
1172    if (buf) {
1173        bucket = php_stream_bucket_new(stream, (char *)buf, count, 0, 0 TSRMLS_CC);
1174        php_stream_bucket_append(&brig_in, bucket TSRMLS_CC);
1175    }
1176
1177    for (filter = stream->writefilters.head; filter; filter = filter->next) {
1178        /* for our return value, we are interested in the number of bytes consumed from
1179         * the first filter in the chain */
1180        status = filter->fops->filter(stream, filter, brig_inp, brig_outp,
1181                filter == stream->writefilters.head ? &consumed : NULL, flags TSRMLS_CC);
1182
1183        if (status != PSFS_PASS_ON) {
1184            break;
1185        }
1186        /* brig_out becomes brig_in.
1187         * brig_in will always be empty here, as the filter MUST attach any un-consumed buckets
1188         * to its own brigade */
1189        brig_swap = brig_inp;
1190        brig_inp = brig_outp;
1191        brig_outp = brig_swap;
1192        memset(brig_outp, 0, sizeof(*brig_outp));
1193    }
1194
1195    switch (status) {
1196        case PSFS_PASS_ON:
1197            /* filter chain generated some output; push it through to the
1198             * underlying stream */
1199            while (brig_inp->head) {
1200                bucket = brig_inp->head;
1201                _php_stream_write_buffer(stream, bucket->buf, bucket->buflen TSRMLS_CC);
1202                /* Potential error situation - eg: no space on device. Perhaps we should keep this brigade
1203                 * hanging around and try to write it later.
1204                 * At the moment, we just drop it on the floor
1205                 * */
1206
1207                php_stream_bucket_unlink(bucket TSRMLS_CC);
1208                php_stream_bucket_delref(bucket TSRMLS_CC);
1209            }
1210            break;
1211        case PSFS_FEED_ME:
1212            /* need more data before we can push data through to the stream */
1213            break;
1214
1215        case PSFS_ERR_FATAL:
1216            /* some fatal error.  Theoretically, the stream is borked, so all
1217             * further writes should fail. */
1218            break;
1219    }
1220
1221    return consumed;
1222}
1223
1224PHPAPI int _php_stream_flush(php_stream *stream, int closing TSRMLS_DC)
1225{
1226    int ret = 0;
1227
1228    if (stream->writefilters.head) {
1229        _php_stream_write_filtered(stream, NULL, 0, closing ? PSFS_FLAG_FLUSH_CLOSE : PSFS_FLAG_FLUSH_INC  TSRMLS_CC);
1230    }
1231
1232    if (stream->ops->flush) {
1233        ret = stream->ops->flush(stream TSRMLS_CC);
1234    }
1235
1236    return ret;
1237}
1238
1239PHPAPI size_t _php_stream_write(php_stream *stream, const char *buf, size_t count TSRMLS_DC)
1240{
1241    if (buf == NULL || count == 0 || stream->ops->write == NULL) {
1242        return 0;
1243    }
1244
1245    if (stream->writefilters.head) {
1246        return _php_stream_write_filtered(stream, buf, count, PSFS_FLAG_NORMAL TSRMLS_CC);
1247    } else {
1248        return _php_stream_write_buffer(stream, buf, count TSRMLS_CC);
1249    }
1250}
1251
1252PHPAPI size_t _php_stream_printf(php_stream *stream TSRMLS_DC, const char *fmt, ...)
1253{
1254    size_t count;
1255    char *buf;
1256    va_list ap;
1257
1258    va_start(ap, fmt);
1259    count = vspprintf(&buf, 0, fmt, ap);
1260    va_end(ap);
1261
1262    if (!buf) {
1263        return 0; /* error condition */
1264    }
1265
1266    count = php_stream_write(stream, buf, count);
1267    efree(buf);
1268
1269    return count;
1270}
1271
1272PHPAPI zend_off_t _php_stream_tell(php_stream *stream TSRMLS_DC)
1273{
1274    return stream->position;
1275}
1276
1277PHPAPI int _php_stream_seek(php_stream *stream, zend_off_t offset, int whence TSRMLS_DC)
1278{
1279    if (stream->fclose_stdiocast == PHP_STREAM_FCLOSE_FOPENCOOKIE) {
1280        /* flush to commit data written to the fopencookie FILE* */
1281        fflush(stream->stdiocast);
1282    }
1283
1284    /* handle the case where we are in the buffer */
1285    if ((stream->flags & PHP_STREAM_FLAG_NO_BUFFER) == 0) {
1286        switch(whence) {
1287            case SEEK_CUR:
1288                if (offset > 0 && offset <= stream->writepos - stream->readpos) {
1289                    stream->readpos += offset; /* if offset = ..., then readpos = writepos */
1290                    stream->position += offset;
1291                    stream->eof = 0;
1292                    return 0;
1293                }
1294                break;
1295            case SEEK_SET:
1296                if (offset > stream->position &&
1297                        offset <= stream->position + stream->writepos - stream->readpos) {
1298                    stream->readpos += offset - stream->position;
1299                    stream->position = offset;
1300                    stream->eof = 0;
1301                    return 0;
1302                }
1303                break;
1304        }
1305    }
1306
1307
1308    if (stream->ops->seek && (stream->flags & PHP_STREAM_FLAG_NO_SEEK) == 0) {
1309        int ret;
1310
1311        if (stream->writefilters.head) {
1312            _php_stream_flush(stream, 0 TSRMLS_CC);
1313        }
1314
1315        switch(whence) {
1316            case SEEK_CUR:
1317                offset = stream->position + offset;
1318                whence = SEEK_SET;
1319                break;
1320        }
1321        ret = stream->ops->seek(stream, offset, whence, &stream->position TSRMLS_CC);
1322
1323        if (((stream->flags & PHP_STREAM_FLAG_NO_SEEK) == 0) || ret == 0) {
1324            if (ret == 0) {
1325                stream->eof = 0;
1326            }
1327
1328            /* invalidate the buffer contents */
1329            stream->readpos = stream->writepos = 0;
1330
1331            return ret;
1332        }
1333        /* else the stream has decided that it can't support seeking after all;
1334         * fall through to attempt emulation */
1335    }
1336
1337    /* emulate forward moving seeks with reads */
1338    if (whence == SEEK_CUR && offset >= 0) {
1339        char tmp[1024];
1340        size_t didread;
1341        while(offset > 0) {
1342            if ((didread = php_stream_read(stream, tmp, MIN(offset, sizeof(tmp)))) == 0) {
1343                return -1;
1344            }
1345            offset -= didread;
1346        }
1347        stream->eof = 0;
1348        return 0;
1349    }
1350
1351    php_error_docref(NULL TSRMLS_CC, E_WARNING, "stream does not support seeking");
1352
1353    return -1;
1354}
1355
1356PHPAPI int _php_stream_set_option(php_stream *stream, int option, int value, void *ptrparam TSRMLS_DC)
1357{
1358    int ret = PHP_STREAM_OPTION_RETURN_NOTIMPL;
1359
1360    if (stream->ops->set_option) {
1361        ret = stream->ops->set_option(stream, option, value, ptrparam TSRMLS_CC);
1362    }
1363
1364    if (ret == PHP_STREAM_OPTION_RETURN_NOTIMPL) {
1365        switch(option) {
1366            case PHP_STREAM_OPTION_SET_CHUNK_SIZE:
1367                ret = stream->chunk_size;
1368                stream->chunk_size = value;
1369                return ret;
1370
1371            case PHP_STREAM_OPTION_READ_BUFFER:
1372                /* try to match the buffer mode as best we can */
1373                if (value == PHP_STREAM_BUFFER_NONE) {
1374                    stream->flags |= PHP_STREAM_FLAG_NO_BUFFER;
1375                } else if (stream->flags & PHP_STREAM_FLAG_NO_BUFFER) {
1376                    stream->flags ^= PHP_STREAM_FLAG_NO_BUFFER;
1377                }
1378                ret = PHP_STREAM_OPTION_RETURN_OK;
1379                break;
1380
1381            default:
1382                ;
1383        }
1384    }
1385
1386    return ret;
1387}
1388
1389PHPAPI int _php_stream_truncate_set_size(php_stream *stream, size_t newsize TSRMLS_DC)
1390{
1391    return php_stream_set_option(stream, PHP_STREAM_OPTION_TRUNCATE_API, PHP_STREAM_TRUNCATE_SET_SIZE, &newsize);
1392}
1393
1394PHPAPI size_t _php_stream_passthru(php_stream * stream STREAMS_DC TSRMLS_DC)
1395{
1396    size_t bcount = 0;
1397    char buf[8192];
1398    int b;
1399
1400    if (php_stream_mmap_possible(stream)) {
1401        char *p;
1402        size_t mapped;
1403
1404        p = php_stream_mmap_range(stream, php_stream_tell(stream), PHP_STREAM_MMAP_ALL, PHP_STREAM_MAP_MODE_SHARED_READONLY, &mapped);
1405
1406        if (p) {
1407            do {
1408                /* output functions return int, so pass in int max */
1409                if (0 < (b = PHPWRITE(p, MIN(mapped - bcount, INT_MAX)))) {
1410                    bcount += b;
1411                }
1412            } while (b > 0 && mapped > bcount);
1413
1414            php_stream_mmap_unmap_ex(stream, mapped);
1415
1416            return bcount;
1417        }
1418    }
1419
1420    while ((b = php_stream_read(stream, buf, sizeof(buf))) > 0) {
1421        PHPWRITE(buf, b);
1422        bcount += b;
1423    }
1424
1425    return bcount;
1426}
1427
1428
1429PHPAPI zend_string *_php_stream_copy_to_mem(php_stream *src, size_t maxlen, int persistent STREAMS_DC TSRMLS_DC)
1430{
1431    size_t ret = 0;
1432    char *ptr;
1433    size_t len = 0, max_len;
1434    int step = CHUNK_SIZE;
1435    int min_room = CHUNK_SIZE / 4;
1436    php_stream_statbuf ssbuf;
1437    zend_string *result;
1438
1439    if (maxlen == 0) {
1440        return STR_EMPTY_ALLOC();
1441    }
1442
1443    if (maxlen == PHP_STREAM_COPY_ALL) {
1444        maxlen = 0;
1445    }
1446
1447    if (maxlen > 0) {
1448        result = zend_string_alloc(maxlen, persistent);
1449        ptr = result->val;
1450        while ((len < maxlen) && !php_stream_eof(src)) {
1451            ret = php_stream_read(src, ptr, maxlen - len);
1452            if (!ret) {
1453                break;
1454            }
1455            len += ret;
1456            ptr += ret;
1457        }
1458        if (len) {
1459            *ptr = '\0';
1460            result->len = len;
1461        } else {
1462            zend_string_free(result);
1463            result = NULL;
1464        }
1465        return result;
1466    }
1467
1468    /* avoid many reallocs by allocating a good sized chunk to begin with, if
1469     * we can.  Note that the stream may be filtered, in which case the stat
1470     * result may be inaccurate, as the filter may inflate or deflate the
1471     * number of bytes that we can read.  In order to avoid an upsize followed
1472     * by a downsize of the buffer, overestimate by the step size (which is
1473     * 2K).  */
1474    if (php_stream_stat(src, &ssbuf) == 0 && ssbuf.sb.st_size > 0) {
1475        max_len = ssbuf.sb.st_size + step;
1476    } else {
1477        max_len = step;
1478    }
1479
1480    result = zend_string_alloc(max_len, persistent);
1481    ptr = result->val;
1482
1483    while ((ret = php_stream_read(src, ptr, max_len - len)))    {
1484        len += ret;
1485        if (len + min_room >= max_len) {
1486            result = zend_string_realloc(result, max_len + step, persistent);
1487            max_len += step;
1488            ptr = result->val + len;
1489        } else {
1490            ptr += ret;
1491        }
1492    }
1493    if (len) {
1494        result = zend_string_realloc(result, len, persistent);
1495        result->val[len] = '\0';
1496    } else {
1497        zend_string_free(result);
1498        result = NULL;
1499    }
1500
1501    return result;
1502}
1503
1504/* Returns SUCCESS/FAILURE and sets *len to the number of bytes moved */
1505PHPAPI int _php_stream_copy_to_stream_ex(php_stream *src, php_stream *dest, size_t maxlen, size_t *len STREAMS_DC TSRMLS_DC)
1506{
1507    char buf[CHUNK_SIZE];
1508    size_t readchunk;
1509    size_t haveread = 0;
1510    size_t didread, didwrite, towrite;
1511    size_t dummy;
1512    php_stream_statbuf ssbuf;
1513
1514    if (!len) {
1515        len = &dummy;
1516    }
1517
1518    if (maxlen == 0) {
1519        *len = 0;
1520        return SUCCESS;
1521    }
1522
1523    if (maxlen == PHP_STREAM_COPY_ALL) {
1524        maxlen = 0;
1525    }
1526
1527    if (php_stream_stat(src, &ssbuf) == 0) {
1528        if (ssbuf.sb.st_size == 0
1529#ifdef S_ISREG
1530            && S_ISREG(ssbuf.sb.st_mode)
1531#endif
1532        ) {
1533            *len = 0;
1534            return SUCCESS;
1535        }
1536    }
1537
1538    if (php_stream_mmap_possible(src)) {
1539        char *p;
1540        size_t mapped;
1541
1542        p = php_stream_mmap_range(src, php_stream_tell(src), maxlen, PHP_STREAM_MAP_MODE_SHARED_READONLY, &mapped);
1543
1544        if (p) {
1545            didwrite = php_stream_write(dest, p, mapped);
1546
1547            php_stream_mmap_unmap_ex(src, mapped);
1548
1549            *len = didwrite;
1550
1551            /* we've got at least 1 byte to read
1552             * less than 1 is an error
1553             * AND read bytes match written */
1554            if (mapped > 0 && mapped == didwrite) {
1555                return SUCCESS;
1556            }
1557            return FAILURE;
1558        }
1559    }
1560
1561    while(1) {
1562        readchunk = sizeof(buf);
1563
1564        if (maxlen && (maxlen - haveread) < readchunk) {
1565            readchunk = maxlen - haveread;
1566        }
1567
1568        didread = php_stream_read(src, buf, readchunk);
1569
1570        if (didread) {
1571            /* extra paranoid */
1572            char *writeptr;
1573
1574            towrite = didread;
1575            writeptr = buf;
1576            haveread += didread;
1577
1578            while(towrite) {
1579                didwrite = php_stream_write(dest, writeptr, towrite);
1580                if (didwrite == 0) {
1581                    *len = haveread - (didread - towrite);
1582                    return FAILURE;
1583                }
1584
1585                towrite -= didwrite;
1586                writeptr += didwrite;
1587            }
1588        } else {
1589            break;
1590        }
1591
1592        if (maxlen - haveread == 0) {
1593            break;
1594        }
1595    }
1596
1597    *len = haveread;
1598
1599    /* we've got at least 1 byte to read.
1600     * less than 1 is an error */
1601
1602    if (haveread > 0 || src->eof) {
1603        return SUCCESS;
1604    }
1605    return FAILURE;
1606}
1607
1608/* Returns the number of bytes moved.
1609 * Returns 1 when source len is 0.
1610 * Deprecated in favor of php_stream_copy_to_stream_ex() */
1611ZEND_ATTRIBUTE_DEPRECATED
1612PHPAPI size_t _php_stream_copy_to_stream(php_stream *src, php_stream *dest, size_t maxlen STREAMS_DC TSRMLS_DC)
1613{
1614    size_t len;
1615    int ret = _php_stream_copy_to_stream_ex(src, dest, maxlen, &len STREAMS_REL_CC TSRMLS_CC);
1616    if (ret == SUCCESS && len == 0 && maxlen != 0) {
1617        return 1;
1618    }
1619    return len;
1620}
1621/* }}} */
1622
1623/* {{{ wrapper init and registration */
1624
1625static void stream_resource_regular_dtor(zend_resource *rsrc TSRMLS_DC)
1626{
1627    php_stream *stream = (php_stream*)rsrc->ptr;
1628    /* set the return value for pclose */
1629    FG(pclose_ret) = php_stream_free(stream, PHP_STREAM_FREE_CLOSE | PHP_STREAM_FREE_RSRC_DTOR);
1630}
1631
1632static void stream_resource_persistent_dtor(zend_resource *rsrc TSRMLS_DC)
1633{
1634    php_stream *stream = (php_stream*)rsrc->ptr;
1635    FG(pclose_ret) = php_stream_free(stream, PHP_STREAM_FREE_CLOSE | PHP_STREAM_FREE_RSRC_DTOR);
1636}
1637
1638void php_shutdown_stream_hashes(TSRMLS_D)
1639{
1640    if (FG(stream_wrappers)) {
1641        zend_hash_destroy(FG(stream_wrappers));
1642        efree(FG(stream_wrappers));
1643        FG(stream_wrappers) = NULL;
1644    }
1645
1646    if (FG(stream_filters)) {
1647        zend_hash_destroy(FG(stream_filters));
1648        efree(FG(stream_filters));
1649        FG(stream_filters) = NULL;
1650    }
1651
1652    if (FG(wrapper_errors)) {
1653        zend_hash_destroy(FG(wrapper_errors));
1654        efree(FG(wrapper_errors));
1655        FG(wrapper_errors) = NULL;
1656    }
1657}
1658
1659int php_init_stream_wrappers(int module_number TSRMLS_DC)
1660{
1661    le_stream = zend_register_list_destructors_ex(stream_resource_regular_dtor, NULL, "stream", module_number);
1662    le_pstream = zend_register_list_destructors_ex(NULL, stream_resource_persistent_dtor, "persistent stream", module_number);
1663
1664    /* Filters are cleaned up by the streams they're attached to */
1665    le_stream_filter = zend_register_list_destructors_ex(NULL, NULL, "stream filter", module_number);
1666
1667    zend_hash_init(&url_stream_wrappers_hash, 8, NULL, NULL, 1);
1668    zend_hash_init(php_get_stream_filters_hash_global(), 8, NULL, NULL, 1);
1669    zend_hash_init(php_stream_xport_get_hash(), 8, NULL, NULL, 1);
1670
1671    return (php_stream_xport_register("tcp", php_stream_generic_socket_factory TSRMLS_CC) == SUCCESS
1672            &&
1673            php_stream_xport_register("udp", php_stream_generic_socket_factory TSRMLS_CC) == SUCCESS
1674#if defined(AF_UNIX) && !(defined(PHP_WIN32) || defined(__riscos__) || defined(NETWARE))
1675            &&
1676            php_stream_xport_register("unix", php_stream_generic_socket_factory TSRMLS_CC) == SUCCESS
1677            &&
1678            php_stream_xport_register("udg", php_stream_generic_socket_factory TSRMLS_CC) == SUCCESS
1679#endif
1680        ) ? SUCCESS : FAILURE;
1681}
1682
1683int php_shutdown_stream_wrappers(int module_number TSRMLS_DC)
1684{
1685    zend_hash_destroy(&url_stream_wrappers_hash);
1686    zend_hash_destroy(php_get_stream_filters_hash_global());
1687    zend_hash_destroy(php_stream_xport_get_hash());
1688    return SUCCESS;
1689}
1690
1691/* Validate protocol scheme names during registration
1692 * Must conform to /^[a-zA-Z0-9+.-]+$/
1693 */
1694static inline int php_stream_wrapper_scheme_validate(const char *protocol, unsigned int protocol_len)
1695{
1696    unsigned int i;
1697
1698    for(i = 0; i < protocol_len; i++) {
1699        if (!isalnum((int)protocol[i]) &&
1700            protocol[i] != '+' &&
1701            protocol[i] != '-' &&
1702            protocol[i] != '.') {
1703            return FAILURE;
1704        }
1705    }
1706
1707    return SUCCESS;
1708}
1709
1710/* API for registering GLOBAL wrappers */
1711PHPAPI int php_register_url_stream_wrapper(const char *protocol, php_stream_wrapper *wrapper TSRMLS_DC)
1712{
1713    unsigned int protocol_len = strlen(protocol);
1714
1715    if (php_stream_wrapper_scheme_validate(protocol, protocol_len) == FAILURE) {
1716        return FAILURE;
1717    }
1718
1719    return zend_hash_str_add_ptr(&url_stream_wrappers_hash, protocol, protocol_len, wrapper) ? SUCCESS : FAILURE;
1720}
1721
1722PHPAPI int php_unregister_url_stream_wrapper(const char *protocol TSRMLS_DC)
1723{
1724    return zend_hash_str_del(&url_stream_wrappers_hash, protocol, strlen(protocol));
1725}
1726
1727static void clone_wrapper_hash(TSRMLS_D)
1728{
1729    ALLOC_HASHTABLE(FG(stream_wrappers));
1730    zend_hash_init(FG(stream_wrappers), zend_hash_num_elements(&url_stream_wrappers_hash), NULL, NULL, 1);
1731    zend_hash_copy(FG(stream_wrappers), &url_stream_wrappers_hash, NULL);
1732}
1733
1734/* API for registering VOLATILE wrappers */
1735PHPAPI int php_register_url_stream_wrapper_volatile(const char *protocol, php_stream_wrapper *wrapper TSRMLS_DC)
1736{
1737    unsigned int protocol_len = strlen(protocol);
1738
1739    if (php_stream_wrapper_scheme_validate(protocol, protocol_len) == FAILURE) {
1740        return FAILURE;
1741    }
1742
1743    if (!FG(stream_wrappers)) {
1744        clone_wrapper_hash(TSRMLS_C);
1745    }
1746
1747    return zend_hash_str_add_ptr(FG(stream_wrappers), protocol, protocol_len, wrapper) ? SUCCESS : FAILURE;
1748}
1749
1750PHPAPI int php_unregister_url_stream_wrapper_volatile(const char *protocol TSRMLS_DC)
1751{
1752    if (!FG(stream_wrappers)) {
1753        clone_wrapper_hash(TSRMLS_C);
1754    }
1755
1756    return zend_hash_str_del(FG(stream_wrappers), protocol, strlen(protocol));
1757}
1758/* }}} */
1759
1760/* {{{ php_stream_locate_url_wrapper */
1761PHPAPI php_stream_wrapper *php_stream_locate_url_wrapper(const char *path, const char **path_for_open, int options TSRMLS_DC)
1762{
1763    HashTable *wrapper_hash = (FG(stream_wrappers) ? FG(stream_wrappers) : &url_stream_wrappers_hash);
1764    php_stream_wrapper *wrapper = NULL;
1765    const char *p, *protocol = NULL;
1766    int n = 0;
1767
1768    if (path_for_open) {
1769        *path_for_open = (char*)path;
1770    }
1771
1772    if (options & IGNORE_URL) {
1773        return (options & STREAM_LOCATE_WRAPPERS_ONLY) ? NULL : &php_plain_files_wrapper;
1774    }
1775
1776    for (p = path; isalnum((int)*p) || *p == '+' || *p == '-' || *p == '.'; p++) {
1777        n++;
1778    }
1779
1780    if ((*p == ':') && (n > 1) && (!strncmp("//", p+1, 2) || (n == 4 && !memcmp("data:", path, 5)))) {
1781        protocol = path;
1782    } else if (n == 5 && strncasecmp(path, "zlib:", 5) == 0) {
1783        /* BC with older php scripts and zlib wrapper */
1784        protocol = "compress.zlib";
1785        n = 13;
1786        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Use of \"zlib:\" wrapper is deprecated; please use \"compress.zlib://\" instead");
1787    }
1788
1789    if (protocol) {
1790        char *tmp = estrndup(protocol, n);
1791        if (NULL == (wrapper = zend_hash_str_find_ptr(wrapper_hash, (char*)tmp, n))) {
1792            php_strtolower(tmp, n);
1793            if (NULL == (wrapper = zend_hash_str_find_ptr(wrapper_hash, (char*)tmp, n))) {
1794                char wrapper_name[32];
1795
1796                if (n >= sizeof(wrapper_name)) {
1797                    n = sizeof(wrapper_name) - 1;
1798                }
1799                PHP_STRLCPY(wrapper_name, protocol, sizeof(wrapper_name), n);
1800
1801                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);
1802
1803                wrapper = NULL;
1804                protocol = NULL;
1805            }
1806        }
1807        efree(tmp);
1808    }
1809    /* TODO: curl based streams probably support file:// properly */
1810    if (!protocol || !strncasecmp(protocol, "file", n)) {
1811        /* fall back on regular file access */
1812        php_stream_wrapper *plain_files_wrapper = &php_plain_files_wrapper;
1813
1814        if (protocol) {
1815            int localhost = 0;
1816
1817            if (!strncasecmp(path, "file://localhost/", 17)) {
1818                localhost = 1;
1819            }
1820
1821#ifdef PHP_WIN32
1822            if (localhost == 0 && path[n+3] != '\0' && path[n+3] != '/' && path[n+4] != ':')    {
1823#else
1824            if (localhost == 0 && path[n+3] != '\0' && path[n+3] != '/') {
1825#endif
1826                if (options & REPORT_ERRORS) {
1827                    php_error_docref(NULL TSRMLS_CC, E_WARNING, "remote host file access not supported, %s", path);
1828                }
1829                return NULL;
1830            }
1831
1832            if (path_for_open) {
1833                /* skip past protocol and :/, but handle windows correctly */
1834                *path_for_open = (char*)path + n + 1;
1835                if (localhost == 1) {
1836                    (*path_for_open) += 11;
1837                }
1838                while (*(++*path_for_open)=='/');
1839#ifdef PHP_WIN32
1840                if (*(*path_for_open + 1) != ':')
1841#endif
1842                    (*path_for_open)--;
1843            }
1844        }
1845
1846        if (options & STREAM_LOCATE_WRAPPERS_ONLY) {
1847            return NULL;
1848        }
1849
1850        if (FG(stream_wrappers)) {
1851        /* The file:// wrapper may have been disabled/overridden */
1852
1853            if (wrapper) {
1854                /* It was found so go ahead and provide it */
1855                return wrapper;
1856            }
1857
1858            /* Check again, the original check might have not known the protocol name */
1859            if ((wrapper = zend_hash_str_find_ptr(wrapper_hash, "file", sizeof("file")-1)) != NULL) {
1860                return wrapper;
1861            }
1862
1863            if (options & REPORT_ERRORS) {
1864                php_error_docref(NULL TSRMLS_CC, E_WARNING, "file:// wrapper is disabled in the server configuration");
1865            }
1866            return NULL;
1867        }
1868
1869        return plain_files_wrapper;
1870    }
1871
1872    if (wrapper && wrapper->is_url &&
1873        (options & STREAM_DISABLE_URL_PROTECTION) == 0 &&
1874        (!PG(allow_url_fopen) ||
1875         (((options & STREAM_OPEN_FOR_INCLUDE) ||
1876           PG(in_user_include)) && !PG(allow_url_include)))) {
1877        if (options & REPORT_ERRORS) {
1878            /* protocol[n] probably isn't '\0' */
1879            char *protocol_dup = estrndup(protocol, n);
1880            if (!PG(allow_url_fopen)) {
1881                php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s:// wrapper is disabled in the server configuration by allow_url_fopen=0", protocol_dup);
1882            } else {
1883                php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s:// wrapper is disabled in the server configuration by allow_url_include=0", protocol_dup);
1884            }
1885            efree(protocol_dup);
1886        }
1887        return NULL;
1888    }
1889
1890    return wrapper;
1891}
1892/* }}} */
1893
1894/* {{{ _php_stream_mkdir
1895 */
1896PHPAPI int _php_stream_mkdir(const char *path, int mode, int options, php_stream_context *context TSRMLS_DC)
1897{
1898    php_stream_wrapper *wrapper = NULL;
1899
1900    wrapper = php_stream_locate_url_wrapper(path, NULL, 0 TSRMLS_CC);
1901    if (!wrapper || !wrapper->wops || !wrapper->wops->stream_mkdir) {
1902        return 0;
1903    }
1904
1905    return wrapper->wops->stream_mkdir(wrapper, path, mode, options, context TSRMLS_CC);
1906}
1907/* }}} */
1908
1909/* {{{ _php_stream_rmdir
1910 */
1911PHPAPI int _php_stream_rmdir(const char *path, int options, php_stream_context *context TSRMLS_DC)
1912{
1913    php_stream_wrapper *wrapper = NULL;
1914
1915    wrapper = php_stream_locate_url_wrapper(path, NULL, 0 TSRMLS_CC);
1916    if (!wrapper || !wrapper->wops || !wrapper->wops->stream_rmdir) {
1917        return 0;
1918    }
1919
1920    return wrapper->wops->stream_rmdir(wrapper, path, options, context TSRMLS_CC);
1921}
1922/* }}} */
1923
1924/* {{{ _php_stream_stat_path */
1925PHPAPI int _php_stream_stat_path(const char *path, int flags, php_stream_statbuf *ssb, php_stream_context *context TSRMLS_DC)
1926{
1927    php_stream_wrapper *wrapper = NULL;
1928    const char *path_to_open = path;
1929    int ret;
1930
1931    if (!(flags & PHP_STREAM_URL_STAT_NOCACHE)) {
1932        /* Try to hit the cache first */
1933        if (flags & PHP_STREAM_URL_STAT_LINK) {
1934            if (BG(CurrentLStatFile) && strcmp(path, BG(CurrentLStatFile)) == 0) {
1935                memcpy(ssb, &BG(lssb), sizeof(php_stream_statbuf));
1936                return 0;
1937            }
1938        } else {
1939            if (BG(CurrentStatFile) && strcmp(path, BG(CurrentStatFile)) == 0) {
1940                memcpy(ssb, &BG(ssb), sizeof(php_stream_statbuf));
1941                return 0;
1942            }
1943        }
1944    }
1945
1946    wrapper = php_stream_locate_url_wrapper(path, &path_to_open, 0 TSRMLS_CC);
1947    if (wrapper && wrapper->wops->url_stat) {
1948        ret = wrapper->wops->url_stat(wrapper, path_to_open, flags, ssb, context TSRMLS_CC);
1949        if (ret == 0) {
1950                if (!(flags & PHP_STREAM_URL_STAT_NOCACHE)) {
1951                /* Drop into cache */
1952                if (flags & PHP_STREAM_URL_STAT_LINK) {
1953                    if (BG(CurrentLStatFile)) {
1954                        efree(BG(CurrentLStatFile));
1955                    }
1956                    BG(CurrentLStatFile) = estrdup(path);
1957                    memcpy(&BG(lssb), ssb, sizeof(php_stream_statbuf));
1958                } else {
1959                    if (BG(CurrentStatFile)) {
1960                        efree(BG(CurrentStatFile));
1961                    }
1962                    BG(CurrentStatFile) = estrdup(path);
1963                    memcpy(&BG(ssb), ssb, sizeof(php_stream_statbuf));
1964                }
1965            }
1966        }
1967        return ret;
1968    }
1969    return -1;
1970}
1971/* }}} */
1972
1973/* {{{ php_stream_opendir */
1974PHPAPI php_stream *_php_stream_opendir(const char *path, int options,
1975        php_stream_context *context STREAMS_DC TSRMLS_DC)
1976{
1977    php_stream *stream = NULL;
1978    php_stream_wrapper *wrapper = NULL;
1979    const char *path_to_open;
1980
1981    if (!path || !*path) {
1982        return NULL;
1983    }
1984
1985    path_to_open = path;
1986
1987    wrapper = php_stream_locate_url_wrapper(path, &path_to_open, options TSRMLS_CC);
1988
1989    if (wrapper && wrapper->wops->dir_opener) {
1990        stream = wrapper->wops->dir_opener(wrapper,
1991                path_to_open, "r", options ^ REPORT_ERRORS, NULL,
1992                context STREAMS_REL_CC TSRMLS_CC);
1993
1994        if (stream) {
1995            stream->wrapper = wrapper;
1996            stream->flags |= PHP_STREAM_FLAG_NO_BUFFER | PHP_STREAM_FLAG_IS_DIR;
1997        }
1998    } else if (wrapper) {
1999        php_stream_wrapper_log_error(wrapper, options ^ REPORT_ERRORS TSRMLS_CC, "not implemented");
2000    }
2001    if (stream == NULL && (options & REPORT_ERRORS)) {
2002        php_stream_display_wrapper_errors(wrapper, path, "failed to open dir" TSRMLS_CC);
2003    }
2004    php_stream_tidy_wrapper_error_log(wrapper TSRMLS_CC);
2005
2006    return stream;
2007}
2008/* }}} */
2009
2010/* {{{ _php_stream_readdir */
2011PHPAPI php_stream_dirent *_php_stream_readdir(php_stream *dirstream, php_stream_dirent *ent TSRMLS_DC)
2012{
2013
2014    if (sizeof(php_stream_dirent) == php_stream_read(dirstream, (char*)ent, sizeof(php_stream_dirent))) {
2015        return ent;
2016    }
2017
2018    return NULL;
2019}
2020/* }}} */
2021
2022/* {{{ php_stream_open_wrapper_ex */
2023PHPAPI php_stream *_php_stream_open_wrapper_ex(const char *path, const char *mode, int options,
2024        char **opened_path, php_stream_context *context STREAMS_DC TSRMLS_DC)
2025{
2026    php_stream *stream = NULL;
2027    php_stream_wrapper *wrapper = NULL;
2028    const char *path_to_open;
2029    int persistent = options & STREAM_OPEN_PERSISTENT;
2030    char *resolved_path = NULL;
2031    char *copy_of_path = NULL;
2032
2033    if (opened_path) {
2034        *opened_path = NULL;
2035    }
2036
2037    if (!path || !*path) {
2038        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Filename cannot be empty");
2039        return NULL;
2040    }
2041
2042    if (options & USE_PATH) {
2043        resolved_path = zend_resolve_path(path, strlen(path) TSRMLS_CC);
2044        if (resolved_path) {
2045            path = resolved_path;
2046            /* we've found this file, don't re-check include_path or run realpath */
2047            options |= STREAM_ASSUME_REALPATH;
2048            options &= ~USE_PATH;
2049        }
2050    }
2051
2052    path_to_open = path;
2053
2054    wrapper = php_stream_locate_url_wrapper(path, &path_to_open, options TSRMLS_CC);
2055    if (options & STREAM_USE_URL && (!wrapper || !wrapper->is_url)) {
2056        php_error_docref(NULL TSRMLS_CC, E_WARNING, "This function may only be used against URLs");
2057        if (resolved_path) {
2058            efree(resolved_path);
2059        }
2060        return NULL;
2061    }
2062
2063    if (wrapper) {
2064        if (!wrapper->wops->stream_opener) {
2065            php_stream_wrapper_log_error(wrapper, options ^ REPORT_ERRORS TSRMLS_CC,
2066                    "wrapper does not support stream open");
2067        } else {
2068            stream = wrapper->wops->stream_opener(wrapper,
2069                path_to_open, mode, options ^ REPORT_ERRORS,
2070                opened_path, context STREAMS_REL_CC TSRMLS_CC);
2071        }
2072
2073        /* if the caller asked for a persistent stream but the wrapper did not
2074         * return one, force an error here */
2075        if (stream && (options & STREAM_OPEN_PERSISTENT) && !stream->is_persistent) {
2076            php_stream_wrapper_log_error(wrapper, options ^ REPORT_ERRORS TSRMLS_CC,
2077                    "wrapper does not support persistent streams");
2078            php_stream_close(stream);
2079            stream = NULL;
2080        }
2081
2082        if (stream) {
2083            stream->wrapper = wrapper;
2084        }
2085    }
2086
2087    if (stream) {
2088        if (opened_path && !*opened_path && resolved_path) {
2089            *opened_path = resolved_path;
2090            resolved_path = NULL;
2091        }
2092        if (stream->orig_path) {
2093            pefree(stream->orig_path, persistent);
2094        }
2095        copy_of_path = pestrdup(path, persistent);
2096        stream->orig_path = copy_of_path;
2097#if ZEND_DEBUG
2098        stream->open_filename = __zend_orig_filename ? __zend_orig_filename : __zend_filename;
2099        stream->open_lineno = __zend_orig_lineno ? __zend_orig_lineno : __zend_lineno;
2100#endif
2101    }
2102
2103    if (stream != NULL && (options & STREAM_MUST_SEEK)) {
2104        php_stream *newstream;
2105
2106        switch(php_stream_make_seekable_rel(stream, &newstream,
2107                    (options & STREAM_WILL_CAST)
2108                        ? PHP_STREAM_PREFER_STDIO : PHP_STREAM_NO_PREFERENCE)) {
2109            case PHP_STREAM_UNCHANGED:
2110                if (resolved_path) {
2111                    efree(resolved_path);
2112                }
2113                return stream;
2114            case PHP_STREAM_RELEASED:
2115                if (newstream->orig_path) {
2116                    pefree(newstream->orig_path, persistent);
2117                }
2118                newstream->orig_path = pestrdup(path, persistent);
2119                if (resolved_path) {
2120                    efree(resolved_path);
2121                }
2122                return newstream;
2123            default:
2124                php_stream_close(stream);
2125                stream = NULL;
2126                if (options & REPORT_ERRORS) {
2127                    char *tmp = estrdup(path);
2128                    php_strip_url_passwd(tmp);
2129                    php_error_docref1(NULL TSRMLS_CC, tmp, E_WARNING, "could not make seekable - %s",
2130                            tmp);
2131                    efree(tmp);
2132
2133                    options ^= REPORT_ERRORS;
2134                }
2135        }
2136    }
2137
2138    if (stream && stream->ops->seek && (stream->flags & PHP_STREAM_FLAG_NO_SEEK) == 0 && strchr(mode, 'a') && stream->position == 0) {
2139        zend_off_t newpos = 0;
2140
2141        /* if opened for append, we need to revise our idea of the initial file position */
2142        if (0 == stream->ops->seek(stream, 0, SEEK_CUR, &newpos TSRMLS_CC)) {
2143            stream->position = newpos;
2144        }
2145    }
2146
2147    if (stream == NULL && (options & REPORT_ERRORS)) {
2148        php_stream_display_wrapper_errors(wrapper, path, "failed to open stream" TSRMLS_CC);
2149        if (opened_path && *opened_path) {
2150            efree(*opened_path);
2151            *opened_path = NULL;
2152        }
2153    }
2154    php_stream_tidy_wrapper_error_log(wrapper TSRMLS_CC);
2155#if ZEND_DEBUG
2156    if (stream == NULL && copy_of_path != NULL) {
2157        pefree(copy_of_path, persistent);
2158    }
2159#endif
2160    if (resolved_path) {
2161        efree(resolved_path);
2162    }
2163    return stream;
2164}
2165/* }}} */
2166
2167/* {{{ context API */
2168PHPAPI php_stream_context *php_stream_context_set(php_stream *stream, php_stream_context *context TSRMLS_DC)
2169{
2170    php_stream_context *oldcontext = PHP_STREAM_CONTEXT(stream);
2171
2172    if (context) {
2173        stream->ctx = context->res;
2174        GC_REFCOUNT(context->res)++;
2175    } else {
2176        stream->ctx = NULL;
2177    }
2178    if (oldcontext) {
2179        zend_list_delete(oldcontext->res);
2180    }
2181
2182    return oldcontext;
2183}
2184
2185PHPAPI void php_stream_notification_notify(php_stream_context *context, int notifycode, int severity,
2186        char *xmsg, int xcode, size_t bytes_sofar, size_t bytes_max, void * ptr TSRMLS_DC)
2187{
2188    if (context && context->notifier)
2189        context->notifier->func(context, notifycode, severity, xmsg, xcode, bytes_sofar, bytes_max, ptr TSRMLS_CC);
2190}
2191
2192PHPAPI void php_stream_context_free(php_stream_context *context)
2193{
2194    if (Z_TYPE(context->options) != IS_UNDEF) {
2195        zval_ptr_dtor(&context->options);
2196        ZVAL_UNDEF(&context->options);
2197    }
2198    if (context->notifier) {
2199        php_stream_notification_free(context->notifier);
2200        context->notifier = NULL;
2201    }
2202    efree(context);
2203}
2204
2205PHPAPI php_stream_context *php_stream_context_alloc(TSRMLS_D)
2206{
2207    php_stream_context *context;
2208
2209    context = ecalloc(1, sizeof(php_stream_context));
2210    context->notifier = NULL;
2211    array_init(&context->options);
2212
2213    context->res = ZEND_REGISTER_RESOURCE(NULL, context, php_le_stream_context(TSRMLS_C));
2214    return context;
2215}
2216
2217PHPAPI php_stream_notifier *php_stream_notification_alloc(void)
2218{
2219    return ecalloc(1, sizeof(php_stream_notifier));
2220}
2221
2222PHPAPI void php_stream_notification_free(php_stream_notifier *notifier)
2223{
2224    if (notifier->dtor) {
2225        notifier->dtor(notifier);
2226    }
2227    efree(notifier);
2228}
2229
2230PHPAPI zval *php_stream_context_get_option(php_stream_context *context,
2231        const char *wrappername, const char *optionname)
2232{
2233    zval *wrapperhash;
2234
2235    if (NULL == (wrapperhash = zend_hash_str_find(Z_ARRVAL(context->options), wrappername, strlen(wrappername)))) {
2236        return NULL;
2237    }
2238    return zend_hash_str_find(Z_ARRVAL_P(wrapperhash), optionname, strlen(optionname));
2239}
2240
2241PHPAPI int php_stream_context_set_option(php_stream_context *context,
2242        const char *wrappername, const char *optionname, zval *optionvalue)
2243{
2244    zval *wrapperhash;
2245    zval category, copied_val;
2246
2247    ZVAL_DUP(&copied_val, optionvalue);
2248
2249    if (NULL == (wrapperhash = zend_hash_str_find(Z_ARRVAL(context->options), wrappername, strlen(wrappername)))) {
2250        array_init(&category);
2251        if (NULL == zend_hash_str_update(Z_ARRVAL(context->options), (char*)wrappername, strlen(wrappername), &category)) {
2252            return FAILURE;
2253        }
2254
2255        wrapperhash = &category;
2256    }
2257    return zend_hash_str_update(Z_ARRVAL_P(wrapperhash), optionname, strlen(optionname), &copied_val) ? SUCCESS : FAILURE;
2258}
2259/* }}} */
2260
2261/* {{{ php_stream_dirent_alphasort
2262 */
2263PHPAPI int php_stream_dirent_alphasort(const zend_string **a, const zend_string **b)
2264{
2265    return strcoll((*a)->val, (*b)->val);
2266}
2267/* }}} */
2268
2269/* {{{ php_stream_dirent_alphasortr
2270 */
2271PHPAPI int php_stream_dirent_alphasortr(const zend_string **a, const zend_string **b)
2272{
2273    return strcoll((*b)->val, (*a)->val);
2274}
2275/* }}} */
2276
2277/* {{{ php_stream_scandir
2278 */
2279PHPAPI int _php_stream_scandir(const char *dirname, zend_string **namelist[], int flags, php_stream_context *context,
2280              int (*compare) (const zend_string **a, const zend_string **b) TSRMLS_DC)
2281{
2282    php_stream *stream;
2283    php_stream_dirent sdp;
2284    zend_string **vector = NULL;
2285    unsigned int vector_size = 0;
2286    unsigned int nfiles = 0;
2287
2288    if (!namelist) {
2289        return FAILURE;
2290    }
2291
2292    stream = php_stream_opendir(dirname, REPORT_ERRORS, context);
2293    if (!stream) {
2294        return FAILURE;
2295    }
2296
2297    while (php_stream_readdir(stream, &sdp)) {
2298        if (nfiles == vector_size) {
2299            if (vector_size == 0) {
2300                vector_size = 10;
2301            } else {
2302                if(vector_size*2 < vector_size) {
2303                    /* overflow */
2304                    php_stream_closedir(stream);
2305                    efree(vector);
2306                    return FAILURE;
2307                }
2308                vector_size *= 2;
2309            }
2310            vector = (zend_string **) safe_erealloc(vector, vector_size, sizeof(char *), 0);
2311        }
2312
2313        vector[nfiles] = zend_string_init(sdp.d_name, strlen(sdp.d_name), 0);
2314
2315        nfiles++;
2316        if(vector_size < 10 || nfiles == 0) {
2317            /* overflow */
2318            php_stream_closedir(stream);
2319            efree(vector);
2320            return FAILURE;
2321        }
2322    }
2323    php_stream_closedir(stream);
2324
2325    *namelist = vector;
2326
2327    if (nfiles > 0 && compare) {
2328        qsort(*namelist, nfiles, sizeof(zend_string *), (int(*)(const void *, const void *))compare);
2329    }
2330    return nfiles;
2331}
2332/* }}} */
2333
2334/*
2335 * Local variables:
2336 * tab-width: 4
2337 * c-basic-offset: 4
2338 * End:
2339 * vim600: noet sw=4 ts=4 fdm=marker
2340 * vim<600: noet sw=4 ts=4
2341 */
2342