Coverage Report

Created: 2026-06-02 06:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/php-src/ext/standard/php_fopen_wrapper.c
Line
Count
Source
1
/*
2
   +----------------------------------------------------------------------+
3
   | Copyright © The PHP Group and Contributors.                          |
4
   +----------------------------------------------------------------------+
5
   | This source file is subject to the Modified BSD License that is      |
6
   | bundled with this package in the file LICENSE, and is available      |
7
   | through the World Wide Web at <https://www.php.net/license/>.        |
8
   |                                                                      |
9
   | SPDX-License-Identifier: BSD-3-Clause                                |
10
   +----------------------------------------------------------------------+
11
   | Authors: Rasmus Lerdorf <rasmus@php.net>                             |
12
   |          Jim Winstead <jimw@php.net>                                 |
13
   |          Hartmut Holzgraefe <hholzgra@php.net>                       |
14
   +----------------------------------------------------------------------+
15
 */
16
17
#include <stdio.h>
18
#include <stdlib.h>
19
#ifdef HAVE_UNISTD_H
20
# include <unistd.h>
21
#endif
22
23
#include "php.h"
24
#include "php_globals.h"
25
#include "php_standard.h"
26
#include "php_memory_streams.h"
27
#include "php_fopen_wrappers.h"
28
#include "SAPI.h"
29
30
static ssize_t php_stream_output_write(php_stream *stream, const char *buf, size_t count) /* {{{ */
31
0
{
32
0
  PHPWRITE(buf, count);
33
0
  return count;
34
0
}
35
/* }}} */
36
37
static ssize_t php_stream_output_read(php_stream *stream, char *buf, size_t count) /* {{{ */
38
0
{
39
0
  stream->eof = 1;
40
0
  return -1;
41
0
}
42
/* }}} */
43
44
static int php_stream_output_close(php_stream *stream, int close_handle) /* {{{ */
45
0
{
46
0
  return 0;
47
0
}
48
/* }}} */
49
50
static const php_stream_ops php_stream_output_ops = {
51
  php_stream_output_write,
52
  php_stream_output_read,
53
  php_stream_output_close,
54
  NULL, /* flush */
55
  "Output",
56
  NULL, /* seek */
57
  NULL, /* cast */
58
  NULL, /* stat */
59
  NULL  /* set_option */
60
};
61
62
typedef struct php_stream_input { /* {{{ */
63
  php_stream *body;
64
  zend_off_t position;
65
} php_stream_input_t;
66
/* }}} */
67
68
static ssize_t php_stream_input_write(php_stream *stream, const char *buf, size_t count) /* {{{ */
69
0
{
70
0
  return -1;
71
0
}
72
/* }}} */
73
74
static ssize_t php_stream_input_read(php_stream *stream, char *buf, size_t count) /* {{{ */
75
0
{
76
0
  php_stream_input_t *input = stream->abstract;
77
0
  ssize_t read;
78
79
0
  if (!SG(post_read) && SG(read_post_bytes) < (int64_t)(input->position + count)) {
80
    /* read requested data from SAPI */
81
0
    size_t read_bytes = sapi_read_post_block(buf, count);
82
83
0
    if (read_bytes > 0) {
84
0
      php_stream_seek(input->body, 0, SEEK_END);
85
0
      php_stream_write(input->body, buf, read_bytes);
86
0
    }
87
0
  }
88
89
0
  if (!input->body->readfilters.head) {
90
    /* If the input stream contains filters, it's not really seekable. The
91
      input->position is likely to be wrong for unfiltered data. */
92
0
    php_stream_seek(input->body, input->position, SEEK_SET);
93
0
  }
94
0
  read = php_stream_read(input->body, buf, count);
95
96
0
  if (!read || read == (size_t) -1) {
97
0
    stream->eof = 1;
98
0
  } else {
99
0
    input->position += read;
100
0
  }
101
102
0
  return read;
103
0
}
104
/* }}} */
105
106
static int php_stream_input_close(php_stream *stream, int close_handle) /* {{{ */
107
0
{
108
0
  efree(stream->abstract);
109
0
  stream->abstract = NULL;
110
111
0
  return 0;
112
0
}
113
/* }}} */
114
115
static int php_stream_input_flush(php_stream *stream) /* {{{ */
116
0
{
117
0
  return -1;
118
0
}
119
/* }}} */
120
121
static int php_stream_input_seek(php_stream *stream, zend_off_t offset, int whence, zend_off_t *newoffset) /* {{{ */
122
0
{
123
0
  php_stream_input_t *input = stream->abstract;
124
125
0
  if (input->body) {
126
0
    int sought = php_stream_seek(input->body, offset, whence);
127
0
    *newoffset = input->position = (input->body)->position;
128
0
    return sought;
129
0
  }
130
131
0
  return -1;
132
0
}
133
/* }}} */
134
135
static const php_stream_ops php_stream_input_ops = {
136
  php_stream_input_write,
137
  php_stream_input_read,
138
  php_stream_input_close,
139
  php_stream_input_flush,
140
  "Input",
141
  php_stream_input_seek,
142
  NULL, /* cast */
143
  NULL, /* stat */
144
  NULL  /* set_option */
145
};
146
147
static void php_stream_apply_filter_list(php_stream *stream, char *filterlist, int read_chain, int write_chain) /* {{{ */
148
123
{
149
123
  char *p, *token = NULL;
150
123
  php_stream_filter *temp_filter;
151
152
123
  p = php_strtok_r(filterlist, "|", &token);
153
249
  while (p) {
154
126
    php_url_decode(p, strlen(p));
155
126
    if (read_chain) {
156
126
      if ((temp_filter = php_stream_filter_create(p, NULL, php_stream_is_persistent(stream)))) {
157
48
        php_stream_filter_append(&stream->readfilters, temp_filter);
158
78
      } else {
159
78
        php_stream_wrapper_warn_nt(NULL, PHP_STREAM_CONTEXT(stream), REPORT_ERRORS,
160
78
          CreateFailed,
161
78
          "Unable to create filter (%s)", p);
162
78
      }
163
126
    }
164
126
    if (write_chain) {
165
0
      if ((temp_filter = php_stream_filter_create(p, NULL, php_stream_is_persistent(stream)))) {
166
0
        php_stream_filter_append(&stream->writefilters, temp_filter);
167
0
      } else {
168
0
        php_stream_wrapper_warn_nt(NULL, PHP_STREAM_CONTEXT(stream), REPORT_ERRORS,
169
0
          CreateFailed,
170
0
          "Unable to create filter (%s)", p);
171
0
      }
172
0
    }
173
126
    p = php_strtok_r(NULL, "|", &token);
174
126
  }
175
123
}
176
/* }}} */
177
178
static php_stream * php_stream_url_wrap_php(php_stream_wrapper *wrapper, const char *path, const char *mode, int options,
179
                   zend_string **opened_path, php_stream_context *context STREAMS_DC) /* {{{ */
180
39
{
181
39
  int fd = -1;
182
39
  int mode_rw = 0;
183
39
  php_stream * stream = NULL;
184
39
  char *p, *token = NULL, *pathdup;
185
39
  zend_long max_memory;
186
39
  FILE *file = NULL;
187
#ifdef PHP_WIN32
188
  int pipe_requested = 0;
189
#endif
190
191
39
  if (!strncasecmp(path, "php://", 6)) {
192
39
    path += 6;
193
39
  }
194
195
39
  if (!strncasecmp(path, "temp", 4)) {
196
0
    path += 4;
197
0
    max_memory = PHP_STREAM_MAX_MEM;
198
0
    if (!strncasecmp(path, "/maxmemory:", 11)) {
199
0
      path += 11;
200
0
      max_memory = ZEND_STRTOL(path, NULL, 10);
201
0
      if (max_memory < 0) {
202
0
        zend_argument_value_error(2, "must be greater than or equal to 0");
203
0
        return NULL;
204
0
      }
205
0
    }
206
0
    mode_rw = php_stream_mode_from_str(mode);
207
0
    return php_stream_temp_create(mode_rw, max_memory);
208
0
  }
209
210
39
  if (!strcasecmp(path, "memory")) {
211
0
    mode_rw = php_stream_mode_from_str(mode);
212
0
    return php_stream_memory_create(mode_rw);
213
0
  }
214
215
39
  if (!strcasecmp(path, "output")) {
216
0
    return php_stream_alloc(&php_stream_output_ops, NULL, 0, "wb");
217
0
  }
218
219
39
  if (!strcasecmp(path, "input")) {
220
0
    php_stream_input_t *input;
221
222
0
    if ((options & STREAM_OPEN_FOR_INCLUDE) && !PG(allow_url_include) ) {
223
0
      if (options & REPORT_ERRORS) {
224
0
        php_stream_wrapper_warn(wrapper, context, options,
225
0
          Disabled,
226
0
          "URL file-access is disabled in the server configuration");
227
0
      }
228
0
      return NULL;
229
0
    }
230
231
0
    input = ecalloc(1, sizeof(*input));
232
0
    if ((input->body = SG(request_info).request_body)) {
233
0
      php_stream_rewind(input->body);
234
0
    } else {
235
0
      input->body = php_stream_temp_create_ex(TEMP_STREAM_DEFAULT, SAPI_POST_BLOCK_SIZE, PG(upload_tmp_dir));
236
0
      SG(request_info).request_body = input->body;
237
0
    }
238
239
0
    return php_stream_alloc(&php_stream_input_ops, input, 0, "rb");
240
0
  }
241
242
39
  if (!strcasecmp(path, "stdin")) {
243
0
    if ((options & STREAM_OPEN_FOR_INCLUDE) && !PG(allow_url_include) ) {
244
0
      if (options & REPORT_ERRORS) {
245
0
        php_stream_wrapper_warn(wrapper, context, options,
246
0
          Disabled,
247
0
          "URL file-access is disabled in the server configuration");
248
0
      }
249
0
      return NULL;
250
0
    }
251
0
    if (!strcmp(sapi_module.name, "cli")) {
252
0
      static int cli_in = 0;
253
0
      fd = STDIN_FILENO;
254
0
      if (cli_in) {
255
0
        fd = dup(fd);
256
0
      } else {
257
0
        cli_in = 1;
258
0
        file = stdin;
259
0
      }
260
0
    } else {
261
0
      fd = dup(STDIN_FILENO);
262
0
    }
263
#ifdef PHP_WIN32
264
    pipe_requested = 1;
265
#endif
266
39
  } else if (!strcasecmp(path, "stdout")) {
267
0
    if (!strcmp(sapi_module.name, "cli")) {
268
0
      static int cli_out = 0;
269
0
      fd = STDOUT_FILENO;
270
0
      if (cli_out++) {
271
0
        fd = dup(fd);
272
0
      } else {
273
0
        cli_out = 1;
274
0
        file = stdout;
275
0
      }
276
0
    } else {
277
0
      fd = dup(STDOUT_FILENO);
278
0
    }
279
#ifdef PHP_WIN32
280
    pipe_requested = 1;
281
#endif
282
39
  } else if (!strcasecmp(path, "stderr")) {
283
0
    if (!strcmp(sapi_module.name, "cli")) {
284
0
      static int cli_err = 0;
285
0
      fd = STDERR_FILENO;
286
0
      if (cli_err++) {
287
0
        fd = dup(fd);
288
0
      } else {
289
0
        cli_err = 1;
290
0
        file = stderr;
291
0
      }
292
0
    } else {
293
0
      fd = dup(STDERR_FILENO);
294
0
    }
295
#ifdef PHP_WIN32
296
    pipe_requested = 1;
297
#endif
298
39
  } else if (!strncasecmp(path, "fd/", 3)) {
299
0
    const char *start;
300
0
    char       *end;
301
0
    zend_long  fildes_ori;
302
0
    int      dtablesize;
303
304
0
    if (strcmp(sapi_module.name, "cli")) {
305
0
      if (options & REPORT_ERRORS) {
306
0
        php_stream_wrapper_warn(wrapper, context, options,
307
0
          Disabled,
308
0
          "Direct access to file descriptors is only available from command-line PHP");
309
0
      }
310
0
      return NULL;
311
0
    }
312
313
0
    if ((options & STREAM_OPEN_FOR_INCLUDE) && !PG(allow_url_include) ) {
314
0
      if (options & REPORT_ERRORS) {
315
0
        php_stream_wrapper_warn(wrapper, context, options,
316
0
          Disabled,
317
0
          "URL file-access is disabled in the server configuration");
318
0
      }
319
0
      return NULL;
320
0
    }
321
322
0
    start = &path[3];
323
0
    fildes_ori = ZEND_STRTOL(start, &end, 10);
324
0
    if (end == start || *end != '\0') {
325
0
      php_stream_wrapper_log_warn(wrapper, context, options,
326
0
        InvalidUrl,
327
0
        "php://fd/ stream must be specified in the form php://fd/<orig fd>");
328
0
      return NULL;
329
0
    }
330
331
0
#ifdef HAVE_UNISTD_H
332
0
    dtablesize = getdtablesize();
333
#else
334
    dtablesize = INT_MAX;
335
#endif
336
337
0
    if (fildes_ori < 0 || fildes_ori >= dtablesize) {
338
0
      php_stream_wrapper_log_warn(wrapper, context, options,
339
0
        InvalidParam,
340
0
        "The file descriptors must be non-negative numbers smaller than %d", dtablesize);
341
0
      return NULL;
342
0
    }
343
344
0
    fd = dup((int)fildes_ori);
345
0
    if (fd == -1) {
346
0
      php_stream_wrapper_log_warn(wrapper, context, options,
347
0
        DupFailed,
348
0
        "Error duping file descriptor " ZEND_LONG_FMT "; possibly it doesn't exist: "
349
0
        "[%d]: %s", fildes_ori, errno, strerror(errno));
350
0
      return NULL;
351
0
    }
352
39
  } else if (!strncasecmp(path, "filter/", 7)) {
353
    /* Save time/memory when chain isn't specified */
354
36
    if (strchr(mode, 'r') || strchr(mode, '+')) {
355
36
      mode_rw |= PHP_STREAM_FILTER_READ;
356
36
    }
357
36
    if (strchr(mode, 'w') || strchr(mode, '+') || strchr(mode, 'a')) {
358
0
      mode_rw |= PHP_STREAM_FILTER_WRITE;
359
0
    }
360
36
    pathdup = estrndup(path + 6, strlen(path + 6));
361
36
    p = strstr(pathdup, "/resource=");
362
36
    if (!p) {
363
0
      zend_throw_error(NULL, "No URL resource specified");
364
0
      efree(pathdup);
365
0
      return NULL;
366
0
    }
367
368
36
    if (!(stream = php_stream_open_wrapper_ex(p + 10, mode, options, opened_path, context))) {
369
0
      efree(pathdup);
370
0
      return NULL;
371
0
    }
372
373
36
    *p = '\0';
374
375
36
    p = php_strtok_r(pathdup + 1, "/", &token);
376
159
    while (p) {
377
123
      if (!strncasecmp(p, "read=", 5)) {
378
60
        php_stream_apply_filter_list(stream, p + 5, 1, 0);
379
63
      } else if (!strncasecmp(p, "write=", 6)) {
380
0
        php_stream_apply_filter_list(stream, p + 6, 0, 1);
381
63
      } else {
382
63
        php_stream_apply_filter_list(stream, p, mode_rw & PHP_STREAM_FILTER_READ, mode_rw & PHP_STREAM_FILTER_WRITE);
383
63
      }
384
123
      p = php_strtok_r(NULL, "/", &token);
385
123
    }
386
36
    efree(pathdup);
387
388
36
    if (EG(exception)) {
389
0
      php_stream_close(stream);
390
0
      return NULL;
391
0
    }
392
393
36
    return stream;
394
36
  } else {
395
    /* invalid php://thingy */
396
3
    php_stream_wrapper_warn(wrapper, context, options,
397
3
        InvalidUrl, "Invalid php:// URL specified");
398
3
    return NULL;
399
3
  }
400
401
  /* must be stdin, stderr or stdout */
402
0
  if (fd == -1) {
403
    /* failed to dup */
404
0
    return NULL;
405
0
  }
406
407
0
#if defined(S_IFSOCK) && !defined(PHP_WIN32)
408
0
  do {
409
0
    zend_stat_t st = {0};
410
0
    memset(&st, 0, sizeof(st));
411
0
    if (zend_fstat(fd, &st) == 0 && (st.st_mode & S_IFMT) == S_IFSOCK) {
412
0
      stream = php_stream_sock_open_from_socket(fd, NULL);
413
0
      if (stream) {
414
0
        stream->ops = &php_stream_socket_ops;
415
0
        return stream;
416
0
      }
417
0
    }
418
0
  } while (0);
419
0
#endif
420
421
0
  if (file) {
422
0
    stream = php_stream_fopen_from_file(file, mode);
423
0
  } else {
424
0
    stream = php_stream_fopen_from_fd(fd, mode, NULL);
425
0
    if (stream == NULL) {
426
0
      close(fd);
427
0
    }
428
0
  }
429
430
#ifdef PHP_WIN32
431
  if (pipe_requested && stream && context) {
432
    zval *blocking_pipes = php_stream_context_get_option(context, "pipe", "blocking");
433
    if (blocking_pipes) {
434
      php_stream_set_option(stream, PHP_STREAM_OPTION_PIPE_BLOCKING, zval_get_long(blocking_pipes), NULL);
435
    }
436
  }
437
#endif
438
0
  return stream;
439
0
}
440
/* }}} */
441
442
static const php_stream_wrapper_ops php_stdio_wops = {
443
  php_stream_url_wrap_php,
444
  NULL, /* close */
445
  NULL, /* fstat */
446
  NULL, /* stat */
447
  NULL, /* opendir */
448
  "PHP",
449
  NULL, /* unlink */
450
  NULL, /* rename */
451
  NULL, /* mkdir */
452
  NULL, /* rmdir */
453
  NULL
454
};
455
456
PHPAPI const php_stream_wrapper php_stream_php_wrapper =  {
457
  &php_stdio_wops,
458
  NULL,
459
  0, /* is_url */
460
};