Coverage Report

Created: 2022-02-19 20:31

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