Coverage Report

Created: 2025-06-13 06:43

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