Coverage Report

Created: 2025-06-13 06:43

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