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 = (int)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
575static 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 TSRMLS_CC);
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    size_t 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 TSRMLS_CC);
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 TSRMLS_CC);
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 amount 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                /* XXX chunk size itself is of size_t, that might be ok or not for a particular case*/
1368                ret = stream->chunk_size > INT_MAX ? INT_MAX : (int)stream->chunk_size;
1369                stream->chunk_size = value;
1370                return ret;
1371
1372            case PHP_STREAM_OPTION_READ_BUFFER:
1373                /* try to match the buffer mode as best we can */
1374                if (value == PHP_STREAM_BUFFER_NONE) {
1375                    stream->flags |= PHP_STREAM_FLAG_NO_BUFFER;
1376                } else if (stream->flags & PHP_STREAM_FLAG_NO_BUFFER) {
1377                    stream->flags ^= PHP_STREAM_FLAG_NO_BUFFER;
1378                }
1379                ret = PHP_STREAM_OPTION_RETURN_OK;
1380                break;
1381
1382            default:
1383                ;
1384        }
1385    }
1386
1387    return ret;
1388}
1389
1390PHPAPI int _php_stream_truncate_set_size(php_stream *stream, size_t newsize TSRMLS_DC)
1391{
1392    return php_stream_set_option(stream, PHP_STREAM_OPTION_TRUNCATE_API, PHP_STREAM_TRUNCATE_SET_SIZE, &newsize);
1393}
1394
1395PHPAPI size_t _php_stream_passthru(php_stream * stream STREAMS_DC TSRMLS_DC)
1396{
1397    size_t bcount = 0;
1398    char buf[8192];
1399    size_t b;
1400
1401    if (php_stream_mmap_possible(stream)) {
1402        char *p;
1403        size_t mapped;
1404
1405        p = php_stream_mmap_range(stream, php_stream_tell(stream), PHP_STREAM_MMAP_ALL, PHP_STREAM_MAP_MODE_SHARED_READONLY, &mapped);
1406
1407        if (p) {
1408            do {
1409                /* output functions return int, so pass in int max */
1410                if (0 < (b = PHPWRITE(p, MIN(mapped - bcount, INT_MAX)))) {
1411                    bcount += b;
1412                }
1413            } while (b > 0 && mapped > bcount);
1414
1415            php_stream_mmap_unmap_ex(stream, mapped);
1416
1417            return bcount;
1418        }
1419    }
1420
1421    while ((b = php_stream_read(stream, buf, sizeof(buf))) > 0) {
1422        PHPWRITE(buf, b);
1423        bcount += b;
1424    }
1425
1426    return bcount;
1427}
1428
1429
1430PHPAPI zend_string *_php_stream_copy_to_mem(php_stream *src, size_t maxlen, int persistent STREAMS_DC TSRMLS_DC)
1431{
1432    size_t ret = 0;
1433    char *ptr;
1434    size_t len = 0, max_len;
1435    int step = CHUNK_SIZE;
1436    int min_room = CHUNK_SIZE / 4;
1437    php_stream_statbuf ssbuf;
1438    zend_string *result;
1439
1440    if (maxlen == 0) {
1441        return STR_EMPTY_ALLOC();
1442    }
1443
1444    if (maxlen == PHP_STREAM_COPY_ALL) {
1445        maxlen = 0;
1446    }
1447
1448    if (maxlen > 0) {
1449        result = zend_string_alloc(maxlen, persistent);
1450        ptr = result->val;
1451        while ((len < maxlen) && !php_stream_eof(src)) {
1452            ret = php_stream_read(src, ptr, maxlen - len);
1453            if (!ret) {
1454                break;
1455            }
1456            len += ret;
1457            ptr += ret;
1458        }
1459        if (len) {
1460            *ptr = '\0';
1461            result->len = len;
1462        } else {
1463            zend_string_free(result);
1464            result = NULL;
1465        }
1466        return result;
1467    }
1468
1469    /* avoid many reallocs by allocating a good sized chunk to begin with, if
1470     * we can.  Note that the stream may be filtered, in which case the stat
1471     * result may be inaccurate, as the filter may inflate or deflate the
1472     * number of bytes that we can read.  In order to avoid an upsize followed
1473     * by a downsize of the buffer, overestimate by the step size (which is
1474     * 2K).  */
1475    if (php_stream_stat(src, &ssbuf) == 0 && ssbuf.sb.st_size > 0) {
1476        max_len = ssbuf.sb.st_size + step;
1477    } else {
1478        max_len = step;
1479    }
1480
1481    result = zend_string_alloc(max_len, persistent);
1482    ptr = result->val;
1483
1484    while ((ret = php_stream_read(src, ptr, max_len - len)))    {
1485        len += ret;
1486        if (len + min_room >= max_len) {
1487            result = zend_string_realloc(result, max_len + step, persistent);
1488            max_len += step;
1489            ptr = result->val + len;
1490        } else {
1491            ptr += ret;
1492        }
1493    }
1494    if (len) {
1495        result = zend_string_realloc(result, len, persistent);
1496        result->val[len] = '\0';
1497    } else {
1498        zend_string_free(result);
1499        result = NULL;
1500    }
1501
1502    return result;
1503}
1504
1505/* Returns SUCCESS/FAILURE and sets *len to the number of bytes moved */
1506PHPAPI int _php_stream_copy_to_stream_ex(php_stream *src, php_stream *dest, size_t maxlen, size_t *len STREAMS_DC TSRMLS_DC)
1507{
1508    char buf[CHUNK_SIZE];
1509    size_t readchunk;
1510    size_t haveread = 0;
1511    size_t didread, didwrite, towrite;
1512    size_t dummy;
1513    php_stream_statbuf ssbuf;
1514
1515    if (!len) {
1516        len = &dummy;
1517    }
1518
1519    if (maxlen == 0) {
1520        *len = 0;
1521        return SUCCESS;
1522    }
1523
1524    if (maxlen == PHP_STREAM_COPY_ALL) {
1525        maxlen = 0;
1526    }
1527
1528    if (php_stream_stat(src, &ssbuf) == 0) {
1529        if (ssbuf.sb.st_size == 0
1530#ifdef S_ISREG
1531            && S_ISREG(ssbuf.sb.st_mode)
1532#endif
1533        ) {
1534            *len = 0;
1535            return SUCCESS;
1536        }
1537    }
1538
1539    if (php_stream_mmap_possible(src)) {
1540        char *p;
1541        size_t mapped;
1542
1543        p = php_stream_mmap_range(src, php_stream_tell(src), maxlen, PHP_STREAM_MAP_MODE_SHARED_READONLY, &mapped);
1544
1545        if (p) {
1546            didwrite = php_stream_write(dest, p, mapped);
1547
1548            php_stream_mmap_unmap_ex(src, mapped);
1549
1550            *len = didwrite;
1551
1552            /* we've got at least 1 byte to read
1553             * less than 1 is an error
1554             * AND read bytes match written */
1555            if (mapped > 0 && mapped == didwrite) {
1556                return SUCCESS;
1557            }
1558            return FAILURE;
1559        }
1560    }
1561
1562    while(1) {
1563        readchunk = sizeof(buf);
1564
1565        if (maxlen && (maxlen - haveread) < readchunk) {
1566            readchunk = maxlen - haveread;
1567        }
1568
1569        didread = php_stream_read(src, buf, readchunk);
1570
1571        if (didread) {
1572            /* extra paranoid */
1573            char *writeptr;
1574
1575            towrite = didread;
1576            writeptr = buf;
1577            haveread += didread;
1578
1579            while(towrite) {
1580                didwrite = php_stream_write(dest, writeptr, towrite);
1581                if (didwrite == 0) {
1582                    *len = haveread - (didread - towrite);
1583                    return FAILURE;
1584                }
1585
1586                towrite -= didwrite;
1587                writeptr += didwrite;
1588            }
1589        } else {
1590            break;
1591        }
1592
1593        if (maxlen - haveread == 0) {
1594            break;
1595        }
1596    }
1597
1598    *len = haveread;
1599
1600    /* we've got at least 1 byte to read.
1601     * less than 1 is an error */
1602
1603    if (haveread > 0 || src->eof) {
1604        return SUCCESS;
1605    }
1606    return FAILURE;
1607}
1608
1609/* Returns the number of bytes moved.
1610 * Returns 1 when source len is 0.
1611 * Deprecated in favor of php_stream_copy_to_stream_ex() */
1612ZEND_ATTRIBUTE_DEPRECATED
1613PHPAPI size_t _php_stream_copy_to_stream(php_stream *src, php_stream *dest, size_t maxlen STREAMS_DC TSRMLS_DC)
1614{
1615    size_t len;
1616    int ret = _php_stream_copy_to_stream_ex(src, dest, maxlen, &len STREAMS_REL_CC TSRMLS_CC);
1617    if (ret == SUCCESS && len == 0 && maxlen != 0) {
1618        return 1;
1619    }
1620    return len;
1621}
1622/* }}} */
1623
1624/* {{{ wrapper init and registration */
1625
1626static void stream_resource_regular_dtor(zend_resource *rsrc TSRMLS_DC)
1627{
1628    php_stream *stream = (php_stream*)rsrc->ptr;
1629    /* set the return value for pclose */
1630    FG(pclose_ret) = php_stream_free(stream, PHP_STREAM_FREE_CLOSE | PHP_STREAM_FREE_RSRC_DTOR);
1631}
1632
1633static void stream_resource_persistent_dtor(zend_resource *rsrc TSRMLS_DC)
1634{
1635    php_stream *stream = (php_stream*)rsrc->ptr;
1636    FG(pclose_ret) = php_stream_free(stream, PHP_STREAM_FREE_CLOSE | PHP_STREAM_FREE_RSRC_DTOR);
1637}
1638
1639void php_shutdown_stream_hashes(TSRMLS_D)
1640{
1641    if (FG(stream_wrappers)) {
1642        zend_hash_destroy(FG(stream_wrappers));
1643        efree(FG(stream_wrappers));
1644        FG(stream_wrappers) = NULL;
1645    }
1646
1647    if (FG(stream_filters)) {
1648        zend_hash_destroy(FG(stream_filters));
1649        efree(FG(stream_filters));
1650        FG(stream_filters) = NULL;
1651    }
1652
1653    if (FG(wrapper_errors)) {
1654        zend_hash_destroy(FG(wrapper_errors));
1655        efree(FG(wrapper_errors));
1656        FG(wrapper_errors) = NULL;
1657    }
1658}
1659
1660int php_init_stream_wrappers(int module_number TSRMLS_DC)
1661{
1662    le_stream = zend_register_list_destructors_ex(stream_resource_regular_dtor, NULL, "stream", module_number);
1663    le_pstream = zend_register_list_destructors_ex(NULL, stream_resource_persistent_dtor, "persistent stream", module_number);
1664
1665    /* Filters are cleaned up by the streams they're attached to */
1666    le_stream_filter = zend_register_list_destructors_ex(NULL, NULL, "stream filter", module_number);
1667
1668    zend_hash_init(&url_stream_wrappers_hash, 8, NULL, NULL, 1);
1669    zend_hash_init(php_get_stream_filters_hash_global(), 8, NULL, NULL, 1);
1670    zend_hash_init(php_stream_xport_get_hash(), 8, NULL, NULL, 1);
1671
1672    return (php_stream_xport_register("tcp", php_stream_generic_socket_factory TSRMLS_CC) == SUCCESS
1673            &&
1674            php_stream_xport_register("udp", php_stream_generic_socket_factory TSRMLS_CC) == SUCCESS
1675#if defined(AF_UNIX) && !(defined(PHP_WIN32) || defined(__riscos__) || defined(NETWARE))
1676            &&
1677            php_stream_xport_register("unix", php_stream_generic_socket_factory TSRMLS_CC) == SUCCESS
1678            &&
1679            php_stream_xport_register("udg", php_stream_generic_socket_factory TSRMLS_CC) == SUCCESS
1680#endif
1681        ) ? SUCCESS : FAILURE;
1682}
1683
1684int php_shutdown_stream_wrappers(int module_number TSRMLS_DC)
1685{
1686    zend_hash_destroy(&url_stream_wrappers_hash);
1687    zend_hash_destroy(php_get_stream_filters_hash_global());
1688    zend_hash_destroy(php_stream_xport_get_hash());
1689    return SUCCESS;
1690}
1691
1692/* Validate protocol scheme names during registration
1693 * Must conform to /^[a-zA-Z0-9+.-]+$/
1694 */
1695static inline int php_stream_wrapper_scheme_validate(const char *protocol, unsigned int protocol_len)
1696{
1697    unsigned int i;
1698
1699    for(i = 0; i < protocol_len; i++) {
1700        if (!isalnum((int)protocol[i]) &&
1701            protocol[i] != '+' &&
1702            protocol[i] != '-' &&
1703            protocol[i] != '.') {
1704            return FAILURE;
1705        }
1706    }
1707
1708    return SUCCESS;
1709}
1710
1711/* API for registering GLOBAL wrappers */
1712PHPAPI int php_register_url_stream_wrapper(const char *protocol, php_stream_wrapper *wrapper TSRMLS_DC)
1713{
1714    unsigned int protocol_len = (unsigned int)strlen(protocol);
1715
1716    if (php_stream_wrapper_scheme_validate(protocol, protocol_len) == FAILURE) {
1717        return FAILURE;
1718    }
1719
1720    return zend_hash_str_add_ptr(&url_stream_wrappers_hash, protocol, protocol_len, wrapper) ? SUCCESS : FAILURE;
1721}
1722
1723PHPAPI int php_unregister_url_stream_wrapper(const char *protocol TSRMLS_DC)
1724{
1725    return zend_hash_str_del(&url_stream_wrappers_hash, protocol, strlen(protocol));
1726}
1727
1728static void clone_wrapper_hash(TSRMLS_D)
1729{
1730    ALLOC_HASHTABLE(FG(stream_wrappers));
1731    zend_hash_init(FG(stream_wrappers), zend_hash_num_elements(&url_stream_wrappers_hash), NULL, NULL, 1);
1732    zend_hash_copy(FG(stream_wrappers), &url_stream_wrappers_hash, NULL);
1733}
1734
1735/* API for registering VOLATILE wrappers */
1736PHPAPI int php_register_url_stream_wrapper_volatile(const char *protocol, php_stream_wrapper *wrapper TSRMLS_DC)
1737{
1738    unsigned int protocol_len = (unsigned int)strlen(protocol);
1739
1740    if (php_stream_wrapper_scheme_validate(protocol, protocol_len) == FAILURE) {
1741        return FAILURE;
1742    }
1743
1744    if (!FG(stream_wrappers)) {
1745        clone_wrapper_hash(TSRMLS_C);
1746    }
1747
1748    return zend_hash_str_add_ptr(FG(stream_wrappers), protocol, protocol_len, wrapper) ? SUCCESS : FAILURE;
1749}
1750
1751PHPAPI int php_unregister_url_stream_wrapper_volatile(const char *protocol TSRMLS_DC)
1752{
1753    if (!FG(stream_wrappers)) {
1754        clone_wrapper_hash(TSRMLS_C);
1755    }
1756
1757    return zend_hash_str_del(FG(stream_wrappers), protocol, strlen(protocol));
1758}
1759/* }}} */
1760
1761/* {{{ php_stream_locate_url_wrapper */
1762PHPAPI php_stream_wrapper *php_stream_locate_url_wrapper(const char *path, const char **path_for_open, int options TSRMLS_DC)
1763{
1764    HashTable *wrapper_hash = (FG(stream_wrappers) ? FG(stream_wrappers) : &url_stream_wrappers_hash);
1765    php_stream_wrapper *wrapper = NULL;
1766    const char *p, *protocol = NULL;
1767    int n = 0;
1768
1769    if (path_for_open) {
1770        *path_for_open = (char*)path;
1771    }
1772
1773    if (options & IGNORE_URL) {
1774        return (options & STREAM_LOCATE_WRAPPERS_ONLY) ? NULL : &php_plain_files_wrapper;
1775    }
1776
1777    for (p = path; isalnum((int)*p) || *p == '+' || *p == '-' || *p == '.'; p++) {
1778        n++;
1779    }
1780
1781    if ((*p == ':') && (n > 1) && (!strncmp("//", p+1, 2) || (n == 4 && !memcmp("data:", path, 5)))) {
1782        protocol = path;
1783    } else if (n == 5 && strncasecmp(path, "zlib:", 5) == 0) {
1784        /* BC with older php scripts and zlib wrapper */
1785        protocol = "compress.zlib";
1786        n = 13;
1787        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Use of \"zlib:\" wrapper is deprecated; please use \"compress.zlib://\" instead");
1788    }
1789
1790    if (protocol) {
1791        char *tmp = estrndup(protocol, n);
1792        if (NULL == (wrapper = zend_hash_str_find_ptr(wrapper_hash, (char*)tmp, n))) {
1793            php_strtolower(tmp, n);
1794            if (NULL == (wrapper = zend_hash_str_find_ptr(wrapper_hash, (char*)tmp, n))) {
1795                char wrapper_name[32];
1796
1797                if (n >= sizeof(wrapper_name)) {
1798                    n = sizeof(wrapper_name) - 1;
1799                }
1800                PHP_STRLCPY(wrapper_name, protocol, sizeof(wrapper_name), n);
1801
1802                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);
1803
1804                wrapper = NULL;
1805                protocol = NULL;
1806            }
1807        }
1808        efree(tmp);
1809    }
1810    /* TODO: curl based streams probably support file:// properly */
1811    if (!protocol || !strncasecmp(protocol, "file", n)) {
1812        /* fall back on regular file access */
1813        php_stream_wrapper *plain_files_wrapper = &php_plain_files_wrapper;
1814
1815        if (protocol) {
1816            int localhost = 0;
1817
1818            if (!strncasecmp(path, "file://localhost/", 17)) {
1819                localhost = 1;
1820            }
1821
1822#ifdef PHP_WIN32
1823            if (localhost == 0 && path[n+3] != '\0' && path[n+3] != '/' && path[n+4] != ':')    {
1824#else
1825            if (localhost == 0 && path[n+3] != '\0' && path[n+3] != '/') {
1826#endif
1827                if (options & REPORT_ERRORS) {
1828                    php_error_docref(NULL TSRMLS_CC, E_WARNING, "remote host file access not supported, %s", path);
1829                }
1830                return NULL;
1831            }
1832
1833            if (path_for_open) {
1834                /* skip past protocol and :/, but handle windows correctly */
1835                *path_for_open = (char*)path + n + 1;
1836                if (localhost == 1) {
1837                    (*path_for_open) += 11;
1838                }
1839                while (*(++*path_for_open)=='/');
1840#ifdef PHP_WIN32
1841                if (*(*path_for_open + 1) != ':')
1842#endif
1843                    (*path_for_open)--;
1844            }
1845        }
1846
1847        if (options & STREAM_LOCATE_WRAPPERS_ONLY) {
1848            return NULL;
1849        }
1850
1851        if (FG(stream_wrappers)) {
1852        /* The file:// wrapper may have been disabled/overridden */
1853
1854            if (wrapper) {
1855                /* It was found so go ahead and provide it */
1856                return wrapper;
1857            }
1858
1859            /* Check again, the original check might have not known the protocol name */
1860            if ((wrapper = zend_hash_str_find_ptr(wrapper_hash, "file", sizeof("file")-1)) != NULL) {
1861                return wrapper;
1862            }
1863
1864            if (options & REPORT_ERRORS) {
1865                php_error_docref(NULL TSRMLS_CC, E_WARNING, "file:// wrapper is disabled in the server configuration");
1866            }
1867            return NULL;
1868        }
1869
1870        return plain_files_wrapper;
1871    }
1872
1873    if (wrapper && wrapper->is_url &&
1874        (options & STREAM_DISABLE_URL_PROTECTION) == 0 &&
1875        (!PG(allow_url_fopen) ||
1876         (((options & STREAM_OPEN_FOR_INCLUDE) ||
1877           PG(in_user_include)) && !PG(allow_url_include)))) {
1878        if (options & REPORT_ERRORS) {
1879            /* protocol[n] probably isn't '\0' */
1880            char *protocol_dup = estrndup(protocol, n);
1881            if (!PG(allow_url_fopen)) {
1882                php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s:// wrapper is disabled in the server configuration by allow_url_fopen=0", protocol_dup);
1883            } else {
1884                php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s:// wrapper is disabled in the server configuration by allow_url_include=0", protocol_dup);
1885            }
1886            efree(protocol_dup);
1887        }
1888        return NULL;
1889    }
1890
1891    return wrapper;
1892}
1893/* }}} */
1894
1895/* {{{ _php_stream_mkdir
1896 */
1897PHPAPI int _php_stream_mkdir(const char *path, int mode, int options, php_stream_context *context TSRMLS_DC)
1898{
1899    php_stream_wrapper *wrapper = NULL;
1900
1901    wrapper = php_stream_locate_url_wrapper(path, NULL, 0 TSRMLS_CC);
1902    if (!wrapper || !wrapper->wops || !wrapper->wops->stream_mkdir) {
1903        return 0;
1904    }
1905
1906    return wrapper->wops->stream_mkdir(wrapper, path, mode, options, context TSRMLS_CC);
1907}
1908/* }}} */
1909
1910/* {{{ _php_stream_rmdir
1911 */
1912PHPAPI int _php_stream_rmdir(const char *path, int options, php_stream_context *context TSRMLS_DC)
1913{
1914    php_stream_wrapper *wrapper = NULL;
1915
1916    wrapper = php_stream_locate_url_wrapper(path, NULL, 0 TSRMLS_CC);
1917    if (!wrapper || !wrapper->wops || !wrapper->wops->stream_rmdir) {
1918        return 0;
1919    }
1920
1921    return wrapper->wops->stream_rmdir(wrapper, path, options, context TSRMLS_CC);
1922}
1923/* }}} */
1924
1925/* {{{ _php_stream_stat_path */
1926PHPAPI int _php_stream_stat_path(const char *path, int flags, php_stream_statbuf *ssb, php_stream_context *context TSRMLS_DC)
1927{
1928    php_stream_wrapper *wrapper = NULL;
1929    const char *path_to_open = path;
1930    int ret;
1931
1932    if (!(flags & PHP_STREAM_URL_STAT_NOCACHE)) {
1933        /* Try to hit the cache first */
1934        if (flags & PHP_STREAM_URL_STAT_LINK) {
1935            if (BG(CurrentLStatFile) && strcmp(path, BG(CurrentLStatFile)) == 0) {
1936                memcpy(ssb, &BG(lssb), sizeof(php_stream_statbuf));
1937                return 0;
1938            }
1939        } else {
1940            if (BG(CurrentStatFile) && strcmp(path, BG(CurrentStatFile)) == 0) {
1941                memcpy(ssb, &BG(ssb), sizeof(php_stream_statbuf));
1942                return 0;
1943            }
1944        }
1945    }
1946
1947    wrapper = php_stream_locate_url_wrapper(path, &path_to_open, 0 TSRMLS_CC);
1948    if (wrapper && wrapper->wops->url_stat) {
1949        ret = wrapper->wops->url_stat(wrapper, path_to_open, flags, ssb, context TSRMLS_CC);
1950        if (ret == 0) {
1951                if (!(flags & PHP_STREAM_URL_STAT_NOCACHE)) {
1952                /* Drop into cache */
1953                if (flags & PHP_STREAM_URL_STAT_LINK) {
1954                    if (BG(CurrentLStatFile)) {
1955                        efree(BG(CurrentLStatFile));
1956                    }
1957                    BG(CurrentLStatFile) = estrdup(path);
1958                    memcpy(&BG(lssb), ssb, sizeof(php_stream_statbuf));
1959                } else {
1960                    if (BG(CurrentStatFile)) {
1961                        efree(BG(CurrentStatFile));
1962                    }
1963                    BG(CurrentStatFile) = estrdup(path);
1964                    memcpy(&BG(ssb), ssb, sizeof(php_stream_statbuf));
1965                }
1966            }
1967        }
1968        return ret;
1969    }
1970    return -1;
1971}
1972/* }}} */
1973
1974/* {{{ php_stream_opendir */
1975PHPAPI php_stream *_php_stream_opendir(const char *path, int options,
1976        php_stream_context *context STREAMS_DC TSRMLS_DC)
1977{
1978    php_stream *stream = NULL;
1979    php_stream_wrapper *wrapper = NULL;
1980    const char *path_to_open;
1981
1982    if (!path || !*path) {
1983        return NULL;
1984    }
1985
1986    path_to_open = path;
1987
1988    wrapper = php_stream_locate_url_wrapper(path, &path_to_open, options TSRMLS_CC);
1989
1990    if (wrapper && wrapper->wops->dir_opener) {
1991        stream = wrapper->wops->dir_opener(wrapper,
1992                path_to_open, "r", options ^ REPORT_ERRORS, NULL,
1993                context STREAMS_REL_CC TSRMLS_CC);
1994
1995        if (stream) {
1996            stream->wrapper = wrapper;
1997            stream->flags |= PHP_STREAM_FLAG_NO_BUFFER | PHP_STREAM_FLAG_IS_DIR;
1998        }
1999    } else if (wrapper) {
2000        php_stream_wrapper_log_error(wrapper, options ^ REPORT_ERRORS TSRMLS_CC, "not implemented");
2001    }
2002    if (stream == NULL && (options & REPORT_ERRORS)) {
2003        php_stream_display_wrapper_errors(wrapper, path, "failed to open dir" TSRMLS_CC);
2004    }
2005    php_stream_tidy_wrapper_error_log(wrapper TSRMLS_CC);
2006
2007    return stream;
2008}
2009/* }}} */
2010
2011/* {{{ _php_stream_readdir */
2012PHPAPI php_stream_dirent *_php_stream_readdir(php_stream *dirstream, php_stream_dirent *ent TSRMLS_DC)
2013{
2014
2015    if (sizeof(php_stream_dirent) == php_stream_read(dirstream, (char*)ent, sizeof(php_stream_dirent))) {
2016        return ent;
2017    }
2018
2019    return NULL;
2020}
2021/* }}} */
2022
2023/* {{{ php_stream_open_wrapper_ex */
2024PHPAPI php_stream *_php_stream_open_wrapper_ex(const char *path, const char *mode, int options,
2025        char **opened_path, php_stream_context *context STREAMS_DC TSRMLS_DC)
2026{
2027    php_stream *stream = NULL;
2028    php_stream_wrapper *wrapper = NULL;
2029    const char *path_to_open;
2030    int persistent = options & STREAM_OPEN_PERSISTENT;
2031    char *resolved_path = NULL;
2032    char *copy_of_path = NULL;
2033
2034    if (opened_path) {
2035        *opened_path = NULL;
2036    }
2037
2038    if (!path || !*path) {
2039        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Filename cannot be empty");
2040        return NULL;
2041    }
2042
2043    if (options & USE_PATH) {
2044        resolved_path = zend_resolve_path(path, (int)strlen(path) TSRMLS_CC);
2045        if (resolved_path) {
2046            path = resolved_path;
2047            /* we've found this file, don't re-check include_path or run realpath */
2048            options |= STREAM_ASSUME_REALPATH;
2049            options &= ~USE_PATH;
2050        }
2051    }
2052
2053    path_to_open = path;
2054
2055    wrapper = php_stream_locate_url_wrapper(path, &path_to_open, options TSRMLS_CC);
2056    if (options & STREAM_USE_URL && (!wrapper || !wrapper->is_url)) {
2057        php_error_docref(NULL TSRMLS_CC, E_WARNING, "This function may only be used against URLs");
2058        if (resolved_path) {
2059            efree(resolved_path);
2060        }
2061        return NULL;
2062    }
2063
2064    if (wrapper) {
2065        if (!wrapper->wops->stream_opener) {
2066            php_stream_wrapper_log_error(wrapper, options ^ REPORT_ERRORS TSRMLS_CC,
2067                    "wrapper does not support stream open");
2068        } else {
2069            stream = wrapper->wops->stream_opener(wrapper,
2070                path_to_open, mode, options ^ REPORT_ERRORS,
2071                opened_path, context STREAMS_REL_CC TSRMLS_CC);
2072        }
2073
2074        /* if the caller asked for a persistent stream but the wrapper did not
2075         * return one, force an error here */
2076        if (stream && (options & STREAM_OPEN_PERSISTENT) && !stream->is_persistent) {
2077            php_stream_wrapper_log_error(wrapper, options ^ REPORT_ERRORS TSRMLS_CC,
2078                    "wrapper does not support persistent streams");
2079            php_stream_close(stream);
2080            stream = NULL;
2081        }
2082
2083        if (stream) {
2084            stream->wrapper = wrapper;
2085        }
2086    }
2087
2088    if (stream) {
2089        if (opened_path && !*opened_path && resolved_path) {
2090            *opened_path = resolved_path;
2091            resolved_path = NULL;
2092        }
2093        if (stream->orig_path) {
2094            pefree(stream->orig_path, persistent);
2095        }
2096        copy_of_path = pestrdup(path, persistent);
2097        stream->orig_path = copy_of_path;
2098#if ZEND_DEBUG
2099        stream->open_filename = __zend_orig_filename ? __zend_orig_filename : __zend_filename;
2100        stream->open_lineno = __zend_orig_lineno ? __zend_orig_lineno : __zend_lineno;
2101#endif
2102    }
2103
2104    if (stream != NULL && (options & STREAM_MUST_SEEK)) {
2105        php_stream *newstream;
2106
2107        switch(php_stream_make_seekable_rel(stream, &newstream,
2108                    (options & STREAM_WILL_CAST)
2109                        ? PHP_STREAM_PREFER_STDIO : PHP_STREAM_NO_PREFERENCE)) {
2110            case PHP_STREAM_UNCHANGED:
2111                if (resolved_path) {
2112                    efree(resolved_path);
2113                }
2114                return stream;
2115            case PHP_STREAM_RELEASED:
2116                if (newstream->orig_path) {
2117                    pefree(newstream->orig_path, persistent);
2118                }
2119                newstream->orig_path = pestrdup(path, persistent);
2120                if (resolved_path) {
2121                    efree(resolved_path);
2122                }
2123                return newstream;
2124            default:
2125                php_stream_close(stream);
2126                stream = NULL;
2127                if (options & REPORT_ERRORS) {
2128                    char *tmp = estrdup(path);
2129                    php_strip_url_passwd(tmp);
2130                    php_error_docref1(NULL TSRMLS_CC, tmp, E_WARNING, "could not make seekable - %s",
2131                            tmp);
2132                    efree(tmp);
2133
2134                    options ^= REPORT_ERRORS;
2135                }
2136        }
2137    }
2138
2139    if (stream && stream->ops->seek && (stream->flags & PHP_STREAM_FLAG_NO_SEEK) == 0 && strchr(mode, 'a') && stream->position == 0) {
2140        zend_off_t newpos = 0;
2141
2142        /* if opened for append, we need to revise our idea of the initial file position */
2143        if (0 == stream->ops->seek(stream, 0, SEEK_CUR, &newpos TSRMLS_CC)) {
2144            stream->position = newpos;
2145        }
2146    }
2147
2148    if (stream == NULL && (options & REPORT_ERRORS)) {
2149        php_stream_display_wrapper_errors(wrapper, path, "failed to open stream" TSRMLS_CC);
2150        if (opened_path && *opened_path) {
2151            efree(*opened_path);
2152            *opened_path = NULL;
2153        }
2154    }
2155    php_stream_tidy_wrapper_error_log(wrapper TSRMLS_CC);
2156#if ZEND_DEBUG
2157    if (stream == NULL && copy_of_path != NULL) {
2158        pefree(copy_of_path, persistent);
2159    }
2160#endif
2161    if (resolved_path) {
2162        efree(resolved_path);
2163    }
2164    return stream;
2165}
2166/* }}} */
2167
2168/* {{{ context API */
2169PHPAPI php_stream_context *php_stream_context_set(php_stream *stream, php_stream_context *context TSRMLS_DC)
2170{
2171    php_stream_context *oldcontext = PHP_STREAM_CONTEXT(stream);
2172
2173    if (context) {
2174        stream->ctx = context->res;
2175        GC_REFCOUNT(context->res)++;
2176    } else {
2177        stream->ctx = NULL;
2178    }
2179    if (oldcontext) {
2180        zend_list_delete(oldcontext->res);
2181    }
2182
2183    return oldcontext;
2184}
2185
2186PHPAPI void php_stream_notification_notify(php_stream_context *context, int notifycode, int severity,
2187        char *xmsg, int xcode, size_t bytes_sofar, size_t bytes_max, void * ptr TSRMLS_DC)
2188{
2189    if (context && context->notifier)
2190        context->notifier->func(context, notifycode, severity, xmsg, xcode, bytes_sofar, bytes_max, ptr TSRMLS_CC);
2191}
2192
2193PHPAPI void php_stream_context_free(php_stream_context *context)
2194{
2195    if (Z_TYPE(context->options) != IS_UNDEF) {
2196        zval_ptr_dtor(&context->options);
2197        ZVAL_UNDEF(&context->options);
2198    }
2199    if (context->notifier) {
2200        php_stream_notification_free(context->notifier);
2201        context->notifier = NULL;
2202    }
2203    efree(context);
2204}
2205
2206PHPAPI php_stream_context *php_stream_context_alloc(TSRMLS_D)
2207{
2208    php_stream_context *context;
2209
2210    context = ecalloc(1, sizeof(php_stream_context));
2211    context->notifier = NULL;
2212    array_init(&context->options);
2213
2214    context->res = ZEND_REGISTER_RESOURCE(NULL, context, php_le_stream_context(TSRMLS_C));
2215    return context;
2216}
2217
2218PHPAPI php_stream_notifier *php_stream_notification_alloc(void)
2219{
2220    return ecalloc(1, sizeof(php_stream_notifier));
2221}
2222
2223PHPAPI void php_stream_notification_free(php_stream_notifier *notifier)
2224{
2225    if (notifier->dtor) {
2226        notifier->dtor(notifier);
2227    }
2228    efree(notifier);
2229}
2230
2231PHPAPI zval *php_stream_context_get_option(php_stream_context *context,
2232        const char *wrappername, const char *optionname)
2233{
2234    zval *wrapperhash;
2235
2236    if (NULL == (wrapperhash = zend_hash_str_find(Z_ARRVAL(context->options), wrappername, strlen(wrappername)))) {
2237        return NULL;
2238    }
2239    return zend_hash_str_find(Z_ARRVAL_P(wrapperhash), optionname, strlen(optionname));
2240}
2241
2242PHPAPI int php_stream_context_set_option(php_stream_context *context,
2243        const char *wrappername, const char *optionname, zval *optionvalue)
2244{
2245    zval *wrapperhash;
2246    zval category, copied_val;
2247
2248    ZVAL_DUP(&copied_val, optionvalue);
2249
2250    if (NULL == (wrapperhash = zend_hash_str_find(Z_ARRVAL(context->options), wrappername, strlen(wrappername)))) {
2251        array_init(&category);
2252        if (NULL == zend_hash_str_update(Z_ARRVAL(context->options), (char*)wrappername, strlen(wrappername), &category)) {
2253            return FAILURE;
2254        }
2255
2256        wrapperhash = &category;
2257    }
2258    return zend_hash_str_update(Z_ARRVAL_P(wrapperhash), optionname, strlen(optionname), &copied_val) ? SUCCESS : FAILURE;
2259}
2260/* }}} */
2261
2262/* {{{ php_stream_dirent_alphasort
2263 */
2264PHPAPI int php_stream_dirent_alphasort(const zend_string **a, const zend_string **b)
2265{
2266    return strcoll((*a)->val, (*b)->val);
2267}
2268/* }}} */
2269
2270/* {{{ php_stream_dirent_alphasortr
2271 */
2272PHPAPI int php_stream_dirent_alphasortr(const zend_string **a, const zend_string **b)
2273{
2274    return strcoll((*b)->val, (*a)->val);
2275}
2276/* }}} */
2277
2278/* {{{ php_stream_scandir
2279 */
2280PHPAPI int _php_stream_scandir(const char *dirname, zend_string **namelist[], int flags, php_stream_context *context,
2281              int (*compare) (const zend_string **a, const zend_string **b) TSRMLS_DC)
2282{
2283    php_stream *stream;
2284    php_stream_dirent sdp;
2285    zend_string **vector = NULL;
2286    unsigned int vector_size = 0;
2287    unsigned int nfiles = 0;
2288
2289    if (!namelist) {
2290        return FAILURE;
2291    }
2292
2293    stream = php_stream_opendir(dirname, REPORT_ERRORS, context);
2294    if (!stream) {
2295        return FAILURE;
2296    }
2297
2298    while (php_stream_readdir(stream, &sdp)) {
2299        if (nfiles == vector_size) {
2300            if (vector_size == 0) {
2301                vector_size = 10;
2302            } else {
2303                if(vector_size*2 < vector_size) {
2304                    /* overflow */
2305                    php_stream_closedir(stream);
2306                    efree(vector);
2307                    return FAILURE;
2308                }
2309                vector_size *= 2;
2310            }
2311            vector = (zend_string **) safe_erealloc(vector, vector_size, sizeof(char *), 0);
2312        }
2313
2314        vector[nfiles] = zend_string_init(sdp.d_name, strlen(sdp.d_name), 0);
2315
2316        nfiles++;
2317        if(vector_size < 10 || nfiles == 0) {
2318            /* overflow */
2319            php_stream_closedir(stream);
2320            efree(vector);
2321            return FAILURE;
2322        }
2323    }
2324    php_stream_closedir(stream);
2325
2326    *namelist = vector;
2327
2328    if (nfiles > 0 && compare) {
2329        qsort(*namelist, nfiles, sizeof(zend_string *), (int(*)(const void *, const void *))compare);
2330    }
2331    return nfiles;
2332}
2333/* }}} */
2334
2335/*
2336 * Local variables:
2337 * tab-width: 4
2338 * c-basic-offset: 4
2339 * End:
2340 * vim600: noet sw=4 ts=4 fdm=marker
2341 * vim<600: noet sw=4 ts=4
2342 */
2343