Coverage Report

Created: 2026-07-25 06:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/php-src/main/streams/userspace.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: Wez Furlong <wez@thebrainroom.com>                          |
12
   |          Sara Golemon <pollita@php.net>                              |
13
   +----------------------------------------------------------------------+
14
*/
15
16
#include "php.h"
17
#include "php_globals.h"
18
#include "ext/standard/file.h"
19
#include "ext/standard/flock_compat.h"
20
#ifdef HAVE_SYS_FILE_H
21
#include <sys/file.h>
22
#endif
23
#include <stddef.h>
24
25
#ifdef HAVE_UTIME
26
# ifdef PHP_WIN32
27
#  include <sys/utime.h>
28
# else
29
#  include <utime.h>
30
# endif
31
#endif
32
#include "userspace_arginfo.h"
33
34
static int le_protocols;
35
36
struct php_user_stream_wrapper {
37
  php_stream_wrapper wrapper;
38
  zend_class_entry *ce;
39
  zend_resource *resource;
40
};
41
42
static php_stream *user_wrapper_opener(php_stream_wrapper *wrapper, const char *filename, const char *mode, int options, zend_string **opened_path, php_stream_context *context STREAMS_DC);
43
static int user_wrapper_close(php_stream_wrapper *wrapper, php_stream *stream);
44
static int user_wrapper_stat_url(php_stream_wrapper *wrapper, const char *url, int flags, php_stream_statbuf *ssb, php_stream_context *context);
45
static int user_wrapper_unlink(php_stream_wrapper *wrapper, const char *url, int options, php_stream_context *context);
46
static int user_wrapper_rename(php_stream_wrapper *wrapper, const char *url_from, const char *url_to, int options, php_stream_context *context);
47
static int user_wrapper_mkdir(php_stream_wrapper *wrapper, const char *url, int mode, int options, php_stream_context *context);
48
static int user_wrapper_rmdir(php_stream_wrapper *wrapper, const char *url, int options, php_stream_context *context);
49
static int user_wrapper_metadata(php_stream_wrapper *wrapper, const char *url, int option, void *value, php_stream_context *context);
50
static php_stream *user_wrapper_opendir(php_stream_wrapper *wrapper, const char *filename, const char *mode,
51
    int options, zend_string **opened_path, php_stream_context *context STREAMS_DC);
52
53
static const php_stream_wrapper_ops user_stream_wops = {
54
  user_wrapper_opener,
55
  user_wrapper_close,
56
  NULL, /* stat - the streams themselves know how */
57
  user_wrapper_stat_url,
58
  user_wrapper_opendir,
59
  "user-space",
60
  user_wrapper_unlink,
61
  user_wrapper_rename,
62
  user_wrapper_mkdir,
63
  user_wrapper_rmdir,
64
  user_wrapper_metadata
65
};
66
67
68
static void stream_wrapper_dtor(zend_resource *rsrc)
69
499
{
70
499
  struct php_user_stream_wrapper * uwrap = (struct php_user_stream_wrapper*)rsrc->ptr;
71
72
499
  efree(uwrap);
73
499
}
74
75
76
PHP_MINIT_FUNCTION(user_streams)
77
16
{
78
16
  le_protocols = zend_register_list_destructors_ex(stream_wrapper_dtor, NULL, "stream factory", 0);
79
16
  if (le_protocols == FAILURE)
80
0
    return FAILURE;
81
82
16
  register_userspace_symbols(module_number);
83
84
16
  return SUCCESS;
85
16
}
86
87
struct _php_userstream_data {
88
  struct php_user_stream_wrapper * wrapper;
89
  zval object;
90
};
91
typedef struct _php_userstream_data php_userstream_data_t;
92
93
/* names of methods */
94
#define USERSTREAM_OPEN   "stream_open"
95
#define USERSTREAM_CLOSE  "stream_close"
96
#define USERSTREAM_READ   "stream_read"
97
#define USERSTREAM_WRITE  "stream_write"
98
#define USERSTREAM_FLUSH  "stream_flush"
99
#define USERSTREAM_SEEK   "stream_seek"
100
#define USERSTREAM_TELL   "stream_tell"
101
#define USERSTREAM_EOF    "stream_eof"
102
#define USERSTREAM_STAT   "stream_stat"
103
#define USERSTREAM_STATURL  "url_stat"
104
#define USERSTREAM_UNLINK "unlink"
105
#define USERSTREAM_RENAME "rename"
106
#define USERSTREAM_MKDIR  "mkdir"
107
#define USERSTREAM_RMDIR  "rmdir"
108
#define USERSTREAM_DIR_OPEN   "dir_opendir"
109
#define USERSTREAM_DIR_READ   "dir_readdir"
110
#define USERSTREAM_DIR_REWIND "dir_rewinddir"
111
#define USERSTREAM_DIR_CLOSE  "dir_closedir"
112
#define USERSTREAM_LOCK     "stream_lock"
113
#define USERSTREAM_CAST   "stream_cast"
114
#define USERSTREAM_SET_OPTION "stream_set_option"
115
#define USERSTREAM_TRUNCATE "stream_truncate"
116
#define USERSTREAM_METADATA "stream_metadata"
117
118
/* {{{ class should have methods like these:
119
120
  function stream_open($path, $mode, $options, &$opened_path)
121
  {
122
      return true/false;
123
  }
124
125
  function stream_read($count)
126
  {
127
      return false on error;
128
    else return string;
129
  }
130
131
  function stream_write($data)
132
  {
133
      return false on error;
134
    else return count written;
135
  }
136
137
  function stream_close()
138
  {
139
  }
140
141
  function stream_flush()
142
  {
143
    return true/false;
144
  }
145
146
  function stream_seek($offset, $whence)
147
  {
148
    return true/false;
149
  }
150
151
  function stream_tell()
152
  {
153
    return (int)$position;
154
  }
155
156
  function stream_eof()
157
  {
158
    return true/false;
159
  }
160
161
  function stream_stat()
162
  {
163
    return array( just like that returned by fstat() );
164
  }
165
166
  function stream_cast($castas)
167
  {
168
    if ($castas == STREAM_CAST_FOR_SELECT) {
169
      return $this->underlying_stream;
170
    }
171
    return false;
172
  }
173
174
  function stream_set_option($option, $arg1, $arg2)
175
  {
176
    switch($option) {
177
    case STREAM_OPTION_BLOCKING:
178
      $blocking = $arg1;
179
      ...
180
    case STREAM_OPTION_READ_TIMEOUT:
181
      $sec = $arg1;
182
      $usec = $arg2;
183
      ...
184
    case STREAM_OPTION_WRITE_BUFFER:
185
      $mode = $arg1;
186
      $size = $arg2;
187
      ...
188
    default:
189
      return false;
190
    }
191
  }
192
193
  function url_stat(string $url, int $flags)
194
  {
195
    return array( just like that returned by stat() );
196
  }
197
198
  function unlink(string $url)
199
  {
200
    return true / false;
201
  }
202
203
  function rename(string $from, string $to)
204
  {
205
    return true / false;
206
  }
207
208
  function mkdir($dir, $mode, $options)
209
  {
210
    return true / false;
211
  }
212
213
  function rmdir($dir, $options)
214
  {
215
    return true / false;
216
  }
217
218
  function dir_opendir(string $url, int $options)
219
  {
220
    return true / false;
221
  }
222
223
  function dir_readdir()
224
  {
225
    return string next filename in dir ;
226
  }
227
228
  function dir_closedir()
229
  {
230
    release dir related resources;
231
  }
232
233
  function dir_rewinddir()
234
  {
235
    reset to start of dir list;
236
  }
237
238
  function stream_lock($operation)
239
  {
240
    return true / false;
241
  }
242
243
  function stream_truncate($new_size)
244
  {
245
    return true / false;
246
  }
247
248
  }}} **/
249
250
static void user_stream_create_object(struct php_user_stream_wrapper *uwrap, php_stream_context *context, zval *object)
251
5.53k
{
252
5.53k
  ZEND_ASSERT((uwrap->ce->ce_flags & ZEND_ACC_UNINSTANTIABLE) == 0);
253
254
  /* create an instance of our class */
255
5.53k
  if (object_init_ex(object, uwrap->ce) == FAILURE) {
256
2
    ZVAL_UNDEF(object);
257
2
    return;
258
2
  }
259
260
5.53k
  if (context) {
261
241
    GC_ADDREF(context->res);
262
241
    add_property_resource(object, "context", context->res);
263
5.29k
  } else {
264
5.29k
    add_property_null(object, "context");
265
5.29k
  }
266
267
5.53k
  if (EG(exception) != NULL) {
268
5
    zval_ptr_dtor(object);
269
5
    ZVAL_UNDEF(object);
270
5
    return;
271
5
  }
272
273
5.52k
  if (uwrap->ce->constructor) {
274
0
    zend_call_known_instance_method_with_0_params(
275
0
      uwrap->ce->constructor, Z_OBJ_P(object), NULL);
276
0
  }
277
5.52k
}
278
279
static php_stream *user_wrapper_opener(php_stream_wrapper *wrapper, const char *filename, const char *mode,
280
                     int options, zend_string **opened_path, php_stream_context *context STREAMS_DC)
281
1.46k
{
282
1.46k
  struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract;
283
1.46k
  php_userstream_data_t *us;
284
1.46k
  zval zretval;
285
1.46k
  zval args[4];
286
1.46k
  php_stream *stream = NULL;
287
1.46k
  bool old_in_user_include;
288
289
  /* Try to catch bad usage without preventing flexibility */
290
1.46k
  if (FG(user_stream_current_filename) != NULL && strcmp(filename, FG(user_stream_current_filename)) == 0) {
291
663
    php_stream_wrapper_log_warn(wrapper, context, options,
292
663
        RecursionDetected, "infinite recursion prevented");
293
663
    return NULL;
294
663
  }
295
806
  FG(user_stream_current_filename) = filename;
296
297
  /* if the user stream was registered as local and we are in include context,
298
    we add allow_url_include restrictions to allow_url_fopen ones */
299
  /* we need only is_url == 0 here since if is_url == 1 and remote wrappers
300
    were restricted we wouldn't get here */
301
806
  old_in_user_include = PG(in_user_include);
302
806
  if(uwrap->wrapper.is_url == 0 &&
303
806
    (options & STREAM_OPEN_FOR_INCLUDE) &&
304
806
    !PG(allow_url_include)) {
305
806
    PG(in_user_include) = 1;
306
806
  }
307
308
806
  us = emalloc(sizeof(*us));
309
806
  us->wrapper = uwrap;
310
  /* zend_call_method_if_exists() may unregister the stream wrapper. Hold on to it. */
311
806
  GC_ADDREF(us->wrapper->resource);
312
313
806
  user_stream_create_object(uwrap, context, &us->object);
314
806
  if (Z_ISUNDEF(us->object)) {
315
5
    goto end;
316
5
  }
317
318
  /* call its stream_open method - set up params first */
319
801
  ZVAL_STRING(&args[0], filename);
320
801
  ZVAL_STRING(&args[1], mode);
321
801
  ZVAL_LONG(&args[2], options);
322
801
  ZVAL_NEW_REF(&args[3], &EG(uninitialized_zval));
323
324
801
  zend_string *func_name = ZSTR_INIT_LITERAL(USERSTREAM_OPEN, false);
325
801
  zend_result call_result = zend_call_method_if_exists(Z_OBJ(us->object), func_name, &zretval, 4, args);
326
801
  zend_string_release_ex(func_name, false);
327
328
  /* Keep arg3 alive if it has assigned the reference */
329
801
  zval_ptr_dtor(&args[1]);
330
801
  zval_ptr_dtor(&args[0]);
331
332
801
  if (UNEXPECTED(call_result == FAILURE)) {
333
5
    php_stream_wrapper_log_warn(wrapper, context, options,NotImplemented,
334
5
        "\"%s::" USERSTREAM_OPEN "\" is not implemented", ZSTR_VAL(us->wrapper->ce->name));
335
5
    zval_ptr_dtor(&args[3]);
336
5
    goto end;
337
5
  }
338
  /* Exception occurred */
339
796
  if (UNEXPECTED(Z_ISUNDEF(zretval))) {
340
15
    zval_ptr_dtor(&args[3]);
341
15
    goto end;
342
15
  }
343
781
  if (zend_is_true(&zretval)) {
344
    /* the stream is now open! */
345
774
    stream = php_stream_alloc_rel(&php_stream_userspace_ops, us, 0, mode);
346
347
    /* if the opened path is set, copy it out */
348
774
    if (Z_ISREF(args[3]) && Z_TYPE_P(Z_REFVAL(args[3])) == IS_STRING && opened_path) {
349
0
      *opened_path = zend_string_copy(Z_STR_P(Z_REFVAL(args[3])));
350
0
    }
351
    // TODO Warn when assigning a non string value to the reference?
352
353
    /* set wrapper data to be a reference to our object */
354
774
    ZVAL_COPY(&stream->wrapperdata, &us->object);
355
774
  } else {
356
7
    php_stream_wrapper_log_warn(wrapper, context, options,
357
7
        UserspaceCallFailed,
358
7
        "\"%s::" USERSTREAM_OPEN "\" call failed", ZSTR_VAL(us->wrapper->ce->name));
359
7
  }
360
361
781
  zval_ptr_dtor(&zretval);
362
781
  zval_ptr_dtor(&args[3]);
363
364
804
end:
365
804
  FG(user_stream_current_filename) = NULL;
366
804
  PG(in_user_include) = old_in_user_include;
367
804
  if (stream == NULL) {
368
30
    zval_ptr_dtor(&us->object);
369
30
    zend_list_delete(us->wrapper->resource);
370
30
    efree(us);
371
30
  }
372
804
  return stream;
373
781
}
374
375
static int user_wrapper_close(php_stream_wrapper *wrapper, php_stream *stream)
376
313
{
377
313
  struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract;
378
313
  zend_list_delete(uwrap->resource);
379
  // FIXME: Unused?
380
313
  return 0;
381
313
}
382
383
static php_stream *user_wrapper_opendir(php_stream_wrapper *wrapper, const char *filename, const char *mode,
384
    int options, zend_string **opened_path, php_stream_context *context STREAMS_DC)
385
241
{
386
241
  struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract;
387
241
  php_userstream_data_t *us;
388
241
  zval zretval;
389
241
  zval args[2];
390
241
  php_stream *stream = NULL;
391
392
  /* Try to catch bad usage without preventing flexibility */
393
241
  if (FG(user_stream_current_filename) != NULL && strcmp(filename, FG(user_stream_current_filename)) == 0) {
394
0
    php_stream_wrapper_log_warn(wrapper, context, options,
395
0
        RecursionDetected, "infinite recursion prevented");
396
0
    return NULL;
397
0
  }
398
241
  FG(user_stream_current_filename) = filename;
399
400
241
  us = emalloc(sizeof(*us));
401
241
  us->wrapper = uwrap;
402
  /* zend_call_method_if_exists() may unregister the stream wrapper. Hold on to it. */
403
241
  GC_ADDREF(us->wrapper->resource);
404
405
241
  user_stream_create_object(uwrap, context, &us->object);
406
241
  if (Z_TYPE(us->object) == IS_UNDEF) {
407
2
    goto end;
408
2
  }
409
410
  /* call its dir_open method - set up params first */
411
239
  ZVAL_STRING(&args[0], filename);
412
239
  ZVAL_LONG(&args[1], options);
413
414
239
  zend_string *func_name = ZSTR_INIT_LITERAL(USERSTREAM_DIR_OPEN, false);
415
239
  zend_result call_result = zend_call_method_if_exists(Z_OBJ(us->object), func_name, &zretval, 2, args);
416
239
  zend_string_release_ex(func_name, false);
417
239
  zval_ptr_dtor(&args[0]);
418
419
239
  if (UNEXPECTED(call_result == FAILURE)) {
420
3
    php_stream_wrapper_log_warn(wrapper, context, options, NotImplemented,
421
3
        "\"%s::" USERSTREAM_DIR_OPEN "\" is not implemented",
422
3
        ZSTR_VAL(us->wrapper->ce->name));
423
3
    goto end;
424
3
  }
425
  /* Exception occurred in call */
426
236
  if (UNEXPECTED(Z_ISUNDEF(zretval))) {
427
5
    goto end;
428
5
  }
429
430
231
  if (zend_is_true(&zretval)) {
431
    /* the stream is now open! */
432
228
    stream = php_stream_alloc_rel(&php_stream_userspace_dir_ops, us, 0, mode);
433
434
    /* set wrapper data to be a reference to our object */
435
228
    ZVAL_COPY(&stream->wrapperdata, &us->object);
436
228
  } else {
437
3
    php_stream_wrapper_log_warn(wrapper, context, options,
438
3
        UserspaceCallFailed,
439
3
        "\"%s::" USERSTREAM_DIR_OPEN "\" call failed", ZSTR_VAL(us->wrapper->ce->name));
440
3
  }
441
231
  zval_ptr_dtor(&zretval);
442
443
241
end:
444
241
  FG(user_stream_current_filename) = NULL;
445
241
  if (stream == NULL) {
446
13
    zval_ptr_dtor(&us->object);
447
13
    zend_list_delete(us->wrapper->resource);
448
13
    efree(us);
449
13
  }
450
241
  return stream;
451
231
}
452
453
454
/* {{{ Registers a custom URL protocol handler class */
455
PHP_FUNCTION(stream_wrapper_register)
456
509
{
457
509
  zend_string *protocol;
458
509
  struct php_user_stream_wrapper *uwrap;
459
509
  zend_class_entry *ce = NULL;
460
509
  zend_resource *rsrc;
461
509
  zend_long flags = 0;
462
463
509
  if (zend_parse_parameters(ZEND_NUM_ARGS(), "SC|l", &protocol, &ce, &flags) == FAILURE) {
464
10
    RETURN_THROWS();
465
10
  }
466
467
499
  if (UNEXPECTED(ce->ce_flags & ZEND_ACC_UNINSTANTIABLE)) {
468
0
    zend_argument_value_error(2, "must be a concrete class");
469
0
    RETURN_THROWS();
470
0
  }
471
472
499
  uwrap = (struct php_user_stream_wrapper *)ecalloc(1, sizeof(*uwrap));
473
499
  uwrap->ce = ce;
474
499
  uwrap->wrapper.wops = &user_stream_wops;
475
499
  uwrap->wrapper.abstract = uwrap;
476
499
  uwrap->wrapper.is_url = ((flags & PHP_STREAM_IS_URL) != 0);
477
478
499
  rsrc = zend_register_resource(uwrap, le_protocols);
479
480
499
  if (php_register_url_stream_wrapper_volatile(protocol, &uwrap->wrapper) == SUCCESS) {
481
490
    uwrap->resource = rsrc;
482
490
    RETURN_TRUE;
483
490
  }
484
485
  /* We failed.  But why? */
486
9
  if (zend_hash_exists(php_stream_get_url_stream_wrappers_hash(), protocol)) {
487
0
    php_stream_wrapper_warn(&uwrap->wrapper, NULL, REPORT_ERRORS,
488
0
        WrapperRegistrationFailed,
489
0
        "Protocol %s:// is already defined.", ZSTR_VAL(protocol));
490
9
  } else {
491
    /* Hash doesn't exist so it must have been an invalid protocol scheme */
492
9
    php_stream_wrapper_warn(&uwrap->wrapper, NULL, REPORT_ERRORS,
493
9
        WrapperRegistrationFailed,
494
9
        "Invalid protocol scheme specified. Unable to register wrapper class %s to %s://",
495
9
        ZSTR_VAL(uwrap->ce->name), ZSTR_VAL(protocol));
496
9
  }
497
498
9
  zend_list_delete(rsrc);
499
9
  RETURN_FALSE;
500
9
}
501
/* }}} */
502
503
/* {{{ Unregister a wrapper for the life of the current request. */
504
PHP_FUNCTION(stream_wrapper_unregister)
505
237
{
506
237
  zend_string *protocol;
507
508
237
  if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &protocol) == FAILURE) {
509
0
    RETURN_THROWS();
510
0
  }
511
512
237
  php_stream_wrapper *wrapper = zend_hash_find_ptr(php_stream_get_url_stream_wrappers_hash(), protocol);
513
237
  if (php_unregister_url_stream_wrapper_volatile(protocol) == FAILURE) {
514
    /* We failed */
515
3
    php_stream_wrapper_warn(wrapper, NULL, REPORT_ERRORS,
516
3
        WrapperUnregistrationFailed,
517
3
        "Unable to unregister protocol %s://", ZSTR_VAL(protocol));
518
3
    RETURN_FALSE;
519
3
  }
520
521
234
  ZEND_ASSERT(wrapper != NULL);
522
234
  if (wrapper->wops == &user_stream_wops) {
523
234
    struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper *)wrapper;
524
    // uwrap will be released by resource destructor
525
234
    zend_list_delete(uwrap->resource);
526
234
  }
527
528
234
  RETURN_TRUE;
529
234
}
530
/* }}} */
531
532
/* {{{ Restore the original protocol handler, overriding if necessary */
533
PHP_FUNCTION(stream_wrapper_restore)
534
0
{
535
0
  zend_string *protocol;
536
0
  php_stream_wrapper *wrapper;
537
0
  HashTable *global_wrapper_hash, *wrapper_hash;
538
539
0
  if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &protocol) == FAILURE) {
540
0
    RETURN_THROWS();
541
0
  }
542
543
0
  global_wrapper_hash = php_stream_get_url_stream_wrappers_hash_global();
544
0
  if ((wrapper = zend_hash_find_ptr(global_wrapper_hash, protocol)) == NULL) {
545
0
    php_stream_wrapper_warn_name(user_stream_wops.label, NULL, REPORT_ERRORS,
546
0
        WrapperNotFound,
547
0
        "%s:// never existed, nothing to restore", ZSTR_VAL(protocol));
548
0
    RETURN_FALSE;
549
0
  }
550
551
0
  wrapper_hash = php_stream_get_url_stream_wrappers_hash();
552
0
  if (wrapper_hash == global_wrapper_hash || zend_hash_find_ptr(wrapper_hash, protocol) == wrapper) {
553
0
    php_stream_wrapper_notice(wrapper, NULL, REPORT_ERRORS,
554
0
        WrapperRestorationFailed,
555
0
        "%s:// was never changed, nothing to restore", ZSTR_VAL(protocol));
556
0
    RETURN_TRUE;
557
0
  }
558
559
  /* A failure here could be okay given that the protocol might have been merely unregistered */
560
0
  php_unregister_url_stream_wrapper_volatile(protocol);
561
562
0
  if (php_register_url_stream_wrapper_volatile(protocol, wrapper) == FAILURE) {
563
0
    php_stream_wrapper_warn(wrapper, NULL, REPORT_ERRORS,
564
0
      WrapperRestorationFailed,
565
0
      "Unable to restore original %s:// wrapper", ZSTR_VAL(protocol));
566
0
    RETURN_FALSE;
567
0
  }
568
569
0
  RETURN_TRUE;
570
0
}
571
/* }}} */
572
573
static ssize_t php_userstreamop_write(php_stream *stream, const char *buf, size_t count)
574
0
{
575
0
  zval retval;
576
0
  php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
577
0
  zval args[1];
578
0
  ssize_t didwrite;
579
580
0
  assert(us != NULL);
581
582
0
  ZVAL_STRINGL(&args[0], (char*)buf, count);
583
584
0
  uint32_t orig_no_fclose = stream->flags & PHP_STREAM_FLAG_NO_FCLOSE;
585
0
  stream->flags |= PHP_STREAM_FLAG_NO_FCLOSE;
586
587
0
  zend_string *func_name = ZSTR_INIT_LITERAL(USERSTREAM_WRITE, false);
588
0
  zend_result call_result = zend_call_method_if_exists(Z_OBJ(us->object), func_name, &retval, 1, args);
589
0
  zend_string_release_ex(func_name, false);
590
0
  zval_ptr_dtor(&args[0]);
591
592
0
  if (UNEXPECTED(call_result == FAILURE)) {
593
0
    php_stream_warn(stream, NotImplemented,
594
0
        "%s::" USERSTREAM_WRITE " is not implemented!", ZSTR_VAL(us->wrapper->ce->name));
595
0
  }
596
597
0
  stream->flags &= ~PHP_STREAM_FLAG_NO_FCLOSE;
598
0
  stream->flags |= orig_no_fclose;
599
600
  /* Exception occurred */
601
0
  if (Z_ISUNDEF(retval)) {
602
0
    return -1;
603
0
  }
604
605
0
  if (Z_TYPE(retval) == IS_FALSE) {
606
0
    didwrite = -1;
607
0
  } else {
608
0
    convert_to_long(&retval);
609
0
    didwrite = Z_LVAL(retval);
610
0
  }
611
612
  /* don't allow strange buffer overruns due to bogus return */
613
0
  if (didwrite > 0 && didwrite > count) {
614
0
    php_stream_warn_nt(stream, UserspaceInvalidReturn,
615
0
        "%s::" USERSTREAM_WRITE " wrote " ZEND_LONG_FMT " bytes more data than requested ("
616
0
            ZEND_LONG_FMT " written, " ZEND_LONG_FMT " max)",
617
0
        ZSTR_VAL(us->wrapper->ce->name),
618
0
        (zend_long)(didwrite - count), (zend_long)didwrite, (zend_long)count);
619
0
    didwrite = count;
620
0
  }
621
622
0
  return didwrite;
623
0
}
624
625
static ssize_t php_userstreamop_read(php_stream *stream, char *buf, size_t count)
626
286
{
627
286
  zval retval;
628
286
  zval args[1];
629
286
  size_t didread = 0;
630
286
  php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
631
632
286
  assert(us != NULL);
633
634
286
  uint32_t orig_no_fclose = stream->flags & PHP_STREAM_FLAG_NO_FCLOSE;
635
286
  stream->flags |= PHP_STREAM_FLAG_NO_FCLOSE;
636
637
286
  ZVAL_LONG(&args[0], count);
638
286
  zend_string *func_name = ZSTR_INIT_LITERAL(USERSTREAM_READ, false);
639
286
  zend_result call_result = zend_call_method_if_exists(Z_OBJ(us->object), func_name, &retval, 1, args);
640
286
  zend_string_release_ex(func_name, false);
641
642
286
  if (UNEXPECTED(Z_ISUNDEF(retval))) {
643
32
    goto err;
644
32
  }
645
646
254
  if (UNEXPECTED(call_result == FAILURE)) {
647
0
    php_stream_warn(stream, NotImplemented,
648
0
        "%s::" USERSTREAM_READ " is not implemented!", ZSTR_VAL(us->wrapper->ce->name));
649
0
    goto err;
650
0
  }
651
652
254
  if (Z_TYPE(retval) == IS_FALSE) {
653
0
    goto err;
654
0
  }
655
656
254
  if (!try_convert_to_string(&retval)) {
657
0
    zval_ptr_dtor(&retval);
658
0
    goto err;
659
0
  }
660
661
254
  didread = Z_STRLEN(retval);
662
254
  if (didread > 0) {
663
216
    if (didread > count) {
664
0
      php_stream_warn_nt(stream, UserspaceInvalidReturn,
665
0
          "%s::" USERSTREAM_READ " - read " ZEND_LONG_FMT
666
0
              " bytes more data than requested (" ZEND_LONG_FMT " read, "
667
0
              ZEND_LONG_FMT " max) - excess data will be lost",
668
0
          ZSTR_VAL(us->wrapper->ce->name), (zend_long)(didread - count),
669
0
          (zend_long)didread, (zend_long)count);
670
0
      didread = count;
671
0
    }
672
216
    memcpy(buf, Z_STRVAL(retval), didread);
673
216
  }
674
675
254
  zval_ptr_dtor(&retval);
676
254
  ZVAL_UNDEF(&retval);
677
678
  /* since the user stream has no way of setting the eof flag directly, we need to ask it if we hit eof */
679
680
254
  func_name = ZSTR_INIT_LITERAL(USERSTREAM_EOF, false);
681
254
  call_result = zend_call_method_if_exists(Z_OBJ(us->object), func_name, &retval, 0, NULL);
682
254
  zend_string_release_ex(func_name, false);
683
684
254
  if (UNEXPECTED(call_result == FAILURE)) {
685
8
    php_stream_warn(stream, NotImplemented,
686
8
        "%s::" USERSTREAM_EOF " is not implemented! Assuming EOF",
687
8
        ZSTR_VAL(us->wrapper->ce->name));
688
8
    stream->eof = 1;
689
8
    goto err;
690
8
  }
691
246
  if (UNEXPECTED(Z_ISUNDEF(retval))) {
692
7
    stream->eof = 1;
693
7
    goto err;
694
7
  }
695
696
239
  if (zend_is_true(&retval)) {
697
19
    stream->eof = 1;
698
19
  }
699
239
  zval_ptr_dtor(&retval);
700
701
239
  stream->flags &= ~PHP_STREAM_FLAG_NO_FCLOSE;
702
239
  stream->flags |= orig_no_fclose;
703
704
239
  return didread;
705
706
47
err:
707
47
  stream->flags &= ~PHP_STREAM_FLAG_NO_FCLOSE;
708
47
  stream->flags |= orig_no_fclose;
709
47
  return -1;
710
246
}
711
712
static int php_userstreamop_close(php_stream *stream, int close_handle)
713
774
{
714
774
  zval retval;
715
774
  php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
716
717
774
  assert(us != NULL);
718
719
774
  zend_string *func_name = ZSTR_INIT_LITERAL(USERSTREAM_CLOSE, false);
720
774
  zend_call_method_if_exists(Z_OBJ(us->object), func_name, &retval, 0, NULL);
721
774
  zend_string_release_ex(func_name, false);
722
723
774
  zval_ptr_dtor(&retval);
724
725
774
  zval_ptr_dtor(&us->object);
726
774
  ZVAL_UNDEF(&us->object);
727
728
774
  efree(us);
729
730
774
  return 0;
731
774
}
732
733
static int php_userstreamop_flush(php_stream *stream)
734
0
{
735
0
  zval retval;
736
0
  php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
737
738
0
  assert(us != NULL);
739
740
0
  zend_string *func_name = ZSTR_INIT_LITERAL(USERSTREAM_FLUSH, false);
741
0
  zend_result call_result = zend_call_method_if_exists(Z_OBJ(us->object), func_name, &retval, 0, NULL);
742
0
  zend_string_release_ex(func_name, false);
743
744
0
  int ret = call_result == SUCCESS && Z_TYPE(retval) != IS_UNDEF && zend_is_true(&retval) ? 0 : -1;
745
746
0
  zval_ptr_dtor(&retval);
747
748
0
  return ret;
749
0
}
750
751
static int php_userstreamop_seek(php_stream *stream, zend_off_t offset, int whence, zend_off_t *newoffs)
752
0
{
753
0
  zval retval;
754
0
  int ret;
755
0
  php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
756
0
  zval args[2];
757
758
0
  assert(us != NULL);
759
760
0
  ZVAL_LONG(&args[0], offset);
761
0
  ZVAL_LONG(&args[1], whence);
762
763
0
  uint32_t orig_no_fclose = stream->flags & PHP_STREAM_FLAG_NO_FCLOSE;
764
0
  stream->flags |= PHP_STREAM_FLAG_NO_FCLOSE;
765
766
0
  zend_string *func_name = ZSTR_INIT_LITERAL(USERSTREAM_SEEK, false);
767
0
  zend_result call_result = zend_call_method_if_exists(Z_OBJ(us->object), func_name, &retval, 2, args);
768
0
  zend_string_release_ex(func_name, false);
769
770
0
  if (call_result == FAILURE) {
771
    /* stream_seek is not implemented, so disable seeks for this stream */
772
0
    stream->flags |= PHP_STREAM_FLAG_NO_SEEK;
773
    /* there should be no retval to clean up */
774
775
0
    zval_ptr_dtor(&retval);
776
777
0
    ret = -1;
778
0
    goto out;
779
0
  } else if (call_result == SUCCESS && Z_TYPE(retval) != IS_UNDEF && zend_is_true(&retval)) {
780
0
    ret = 0;
781
0
  } else {
782
0
    ret = -1;
783
0
  }
784
785
0
  zval_ptr_dtor(&retval);
786
0
  ZVAL_UNDEF(&retval);
787
788
0
  if (ret) {
789
0
    goto out;
790
0
  }
791
792
  /* now determine where we are */
793
0
  func_name = ZSTR_INIT_LITERAL(USERSTREAM_TELL, false);
794
0
  call_result = zend_call_method_if_exists(Z_OBJ(us->object), func_name, &retval, 0, NULL);
795
0
  zend_string_release_ex(func_name, false);
796
797
0
  if (call_result == SUCCESS && Z_TYPE(retval) == IS_LONG) {
798
0
    *newoffs = Z_LVAL(retval);
799
0
    ret = 0;
800
0
  } else if (UNEXPECTED(call_result == FAILURE)) {
801
0
    php_stream_warn(stream, NotImplemented,
802
0
        "%s::" USERSTREAM_TELL " is not implemented!", ZSTR_VAL(us->wrapper->ce->name));
803
0
    ret = -1;
804
0
  } else {
805
0
    ret = -1;
806
0
  }
807
808
0
  zval_ptr_dtor(&retval);
809
810
0
out:
811
0
  stream->flags &= ~PHP_STREAM_FLAG_NO_FCLOSE;
812
0
  stream->flags |= orig_no_fclose;
813
814
0
  return ret;
815
0
}
816
817
/* parse the return value from one of the stat functions and store the
818
 * relevant fields into the statbuf provided */
819
static void statbuf_from_array(const HashTable *array, php_stream_statbuf *ssb)
820
61
{
821
61
  zval *elem;
822
823
61
#define STAT_PROP_ENTRY_EX(name, name2)                        \
824
793
  if (NULL != (elem = zend_hash_str_find(array, #name, sizeof(#name)-1))) {     \
825
106
    ssb->sb.st_##name2 = zval_get_long(elem);                                                      \
826
106
  }
827
828
793
#define STAT_PROP_ENTRY(name) STAT_PROP_ENTRY_EX(name,name)
829
830
61
  memset(ssb, 0, sizeof(php_stream_statbuf));
831
61
  STAT_PROP_ENTRY(dev);
832
61
  STAT_PROP_ENTRY(ino);
833
61
  STAT_PROP_ENTRY(mode);
834
61
  STAT_PROP_ENTRY(nlink);
835
61
  STAT_PROP_ENTRY(uid);
836
61
  STAT_PROP_ENTRY(gid);
837
61
#ifdef HAVE_STRUCT_STAT_ST_RDEV
838
61
  STAT_PROP_ENTRY(rdev);
839
61
#endif
840
61
  STAT_PROP_ENTRY(size);
841
61
  STAT_PROP_ENTRY(atime);
842
61
  STAT_PROP_ENTRY(mtime);
843
61
  STAT_PROP_ENTRY(ctime);
844
61
#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
845
61
  STAT_PROP_ENTRY(blksize);
846
61
#endif
847
61
#ifdef HAVE_STRUCT_STAT_ST_BLOCKS
848
61
  STAT_PROP_ENTRY(blocks);
849
61
#endif
850
851
61
#undef STAT_PROP_ENTRY
852
61
#undef STAT_PROP_ENTRY_EX
853
61
}
854
855
static int php_userstreamop_stat(php_stream *stream, php_stream_statbuf *ssb)
856
112
{
857
112
  zval retval;
858
112
  php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
859
112
  int ret = -1;
860
861
112
  zend_string *func_name = ZSTR_INIT_LITERAL(USERSTREAM_STAT, false);
862
112
  zend_result call_result = zend_call_method_if_exists(Z_OBJ(us->object), func_name, &retval, 0, NULL);
863
112
  zend_string_release_ex(func_name, false);
864
865
112
  if (UNEXPECTED(call_result == FAILURE)) {
866
16
    php_stream_warn(stream, NotImplemented,
867
16
        "%s::" USERSTREAM_STAT " is not implemented!", ZSTR_VAL(us->wrapper->ce->name));
868
16
    return -1;
869
16
  }
870
96
  if (UNEXPECTED(Z_ISUNDEF(retval))) {
871
6
    return -1;
872
6
  }
873
874
90
  if (EXPECTED(Z_TYPE(retval) == IS_ARRAY)) {
875
61
    statbuf_from_array(Z_ARR(retval), ssb);
876
61
    ret = 0;
877
61
  }
878
  // TODO: Warning on incorrect return type?
879
880
90
  zval_ptr_dtor(&retval);
881
882
90
  return ret;
883
96
}
884
885
static int user_stream_set_check_liveliness(php_stream *stream, const php_userstream_data_t *us)
886
0
{
887
0
  zval retval;
888
889
0
  zend_string *func_name = ZSTR_INIT_LITERAL(USERSTREAM_EOF, false);
890
0
  zend_result call_result = zend_call_method_if_exists(Z_OBJ(us->object), func_name, &retval, 0, NULL);
891
0
  zend_string_release_ex(func_name, false);
892
893
0
  if (UNEXPECTED(call_result == FAILURE)) {
894
0
    php_stream_warn(stream, NotImplemented,
895
0
        "%s::" USERSTREAM_EOF " is not implemented! Assuming EOF",
896
0
        ZSTR_VAL(us->wrapper->ce->name));
897
0
    return PHP_STREAM_OPTION_RETURN_ERR;
898
0
  }
899
0
  if (UNEXPECTED(Z_ISUNDEF(retval))) {
900
0
    return PHP_STREAM_OPTION_RETURN_ERR;
901
0
  }
902
0
  if (EXPECTED(Z_TYPE(retval) == IS_FALSE || Z_TYPE(retval) == IS_TRUE)) {
903
0
    return Z_TYPE(retval) == IS_TRUE ? PHP_STREAM_OPTION_RETURN_ERR : PHP_STREAM_OPTION_RETURN_OK;
904
0
  } else {
905
0
    php_stream_warn(stream, UserspaceInvalidReturn,
906
0
        "%s::" USERSTREAM_EOF " value must be of type bool, %s given",
907
0
        ZSTR_VAL(us->wrapper->ce->name), zend_zval_value_name(&retval));
908
0
    zval_ptr_dtor(&retval);
909
0
    return PHP_STREAM_OPTION_RETURN_ERR;
910
0
  }
911
0
}
912
913
static int user_stream_set_locking(php_stream *stream, const php_userstream_data_t *us, int value)
914
0
{
915
0
  zval retval;
916
0
  zval zlock;
917
0
  zend_long lock = 0;
918
919
0
  if (value & LOCK_NB) {
920
0
    lock |= PHP_LOCK_NB;
921
0
  }
922
0
  switch (value & ~LOCK_NB) {
923
0
    case LOCK_SH:
924
0
      lock |= PHP_LOCK_SH;
925
0
      break;
926
0
    case LOCK_EX:
927
0
      lock |= PHP_LOCK_EX;
928
0
      break;
929
0
    case LOCK_UN:
930
0
      lock |= PHP_LOCK_UN;
931
0
      break;
932
0
    default:
933
      // TODO: Warn on invalid option value?
934
0
      ;
935
0
  }
936
0
  ZVAL_LONG(&zlock, lock);
937
938
  /* TODO wouldblock */
939
0
  zend_string *func_name = ZSTR_INIT_LITERAL(USERSTREAM_LOCK, false);
940
0
  zend_result call_result = zend_call_method_if_exists(Z_OBJ(us->object), func_name, &retval, 1, &zlock);
941
0
  zend_string_release_ex(func_name, false);
942
943
0
  if (UNEXPECTED(call_result == FAILURE)) {
944
0
    if (value == 0) {
945
      /* lock support test (TODO: more check) */
946
0
      return PHP_STREAM_OPTION_RETURN_OK;
947
0
    }
948
0
    php_stream_warn(stream, NotImplemented,
949
0
        "%s::" USERSTREAM_LOCK " is not implemented!", ZSTR_VAL(us->wrapper->ce->name));
950
0
    return PHP_STREAM_OPTION_RETURN_ERR;
951
0
  }
952
0
  if (UNEXPECTED(Z_ISUNDEF(retval))) {
953
0
    return PHP_STREAM_OPTION_RETURN_ERR;
954
0
  }
955
0
  if (EXPECTED(Z_TYPE(retval) == IS_FALSE || Z_TYPE(retval) == IS_TRUE)) {
956
    // This is somewhat confusing and relies on magic numbers.
957
0
    return Z_TYPE(retval) == IS_FALSE;
958
0
  }
959
  // TODO: ext/standard/tests/file/userstreams_004.phpt returns null implicitly for function
960
  // Should this warn or not? And should this be considered an error?
961
  //php_stream_warn(stream, UserspaceInvalidReturn,
962
  //    "%s::" USERSTREAM_LOCK " value must be of type bool, %s given",
963
  //    ZSTR_VAL(us->wrapper->ce->name), zend_zval_value_name(&retval));
964
0
  zval_ptr_dtor(&retval);
965
0
  return PHP_STREAM_OPTION_RETURN_NOTIMPL;
966
0
}
967
968
static int user_stream_set_truncation(php_stream *stream, const php_userstream_data_t *us,
969
0
    int value, void *ptrparam) {
970
0
  zend_string *func_name = ZSTR_INIT_LITERAL(USERSTREAM_TRUNCATE, false);
971
972
0
  if (value == PHP_STREAM_TRUNCATE_SUPPORTED) {
973
0
    zval zstr;
974
0
    ZVAL_STR(&zstr, func_name);
975
0
    bool is_callable = zend_is_callable_ex(&zstr, Z_OBJ(us->object), IS_CALLABLE_SUPPRESS_DEPRECATIONS, NULL, NULL, NULL);
976
    // Frees func_name
977
0
    zval_ptr_dtor(&zstr);
978
0
    return is_callable ? PHP_STREAM_OPTION_RETURN_OK : PHP_STREAM_OPTION_RETURN_ERR;
979
0
  }
980
0
  ZEND_ASSERT(value == PHP_STREAM_TRUNCATE_SET_SIZE);
981
0
  ptrdiff_t new_size = *(ptrdiff_t*) ptrparam;
982
983
0
  if (UNEXPECTED(new_size < 0 || new_size > (ptrdiff_t)LONG_MAX)) {
984
    /* bad new size */
985
0
    zend_string_release_ex(func_name, false);
986
0
    return PHP_STREAM_OPTION_RETURN_ERR;
987
0
  }
988
989
0
  zval retval;
990
0
  zval size;
991
992
0
  ZVAL_LONG(&size, (zend_long)new_size);
993
0
  zend_result call_result = zend_call_method_if_exists(Z_OBJ(us->object), func_name, &retval, 1, &size);
994
0
  zend_string_release_ex(func_name, false);
995
996
0
  if (UNEXPECTED(call_result == FAILURE)) {
997
0
    php_stream_warn(stream, NotImplemented,
998
0
        "%s::" USERSTREAM_TRUNCATE " is not implemented!", ZSTR_VAL(us->wrapper->ce->name));
999
0
    return PHP_STREAM_OPTION_RETURN_ERR;
1000
0
  }
1001
0
  if (UNEXPECTED(Z_ISUNDEF(retval))) {
1002
0
    return PHP_STREAM_OPTION_RETURN_ERR;
1003
0
  }
1004
0
  if (EXPECTED(Z_TYPE(retval) == IS_FALSE || Z_TYPE(retval) == IS_TRUE)) {
1005
0
    return Z_TYPE(retval) == IS_TRUE ? PHP_STREAM_OPTION_RETURN_OK : PHP_STREAM_OPTION_RETURN_ERR;
1006
0
  } else {
1007
0
    php_stream_warn(stream, UserspaceInvalidReturn,
1008
0
        "%s::" USERSTREAM_TRUNCATE " value must be of type bool, %s given",
1009
0
        ZSTR_VAL(us->wrapper->ce->name), zend_zval_value_name(&retval));
1010
0
    zval_ptr_dtor(&retval);
1011
0
    return PHP_STREAM_OPTION_RETURN_ERR;
1012
0
  }
1013
0
}
1014
1015
static int user_stream_set_option(php_stream *stream, const php_userstream_data_t *us, int option,
1016
    int value, void *ptrparam)
1017
774
{
1018
774
  zval args[3];
1019
774
  ZVAL_LONG(&args[0], option);
1020
774
  ZVAL_LONG(&args[1], value);
1021
774
  ZVAL_NULL(&args[2]);
1022
1023
774
  if (option == PHP_STREAM_OPTION_READ_TIMEOUT) {
1024
0
    struct timeval tv = *(struct timeval*)ptrparam;
1025
0
    ZVAL_LONG(&args[1], tv.tv_sec);
1026
0
    ZVAL_LONG(&args[2], tv.tv_usec);
1027
774
  } else if (option == PHP_STREAM_OPTION_READ_BUFFER || option == PHP_STREAM_OPTION_WRITE_BUFFER) {
1028
774
    if (ptrparam) {
1029
0
      ZVAL_LONG(&args[2], *(long *)ptrparam);
1030
774
    } else {
1031
774
      ZVAL_LONG(&args[2], BUFSIZ);
1032
774
    }
1033
774
  }
1034
1035
774
  zval retval;
1036
774
  zend_string *func_name = ZSTR_INIT_LITERAL(USERSTREAM_SET_OPTION, false);
1037
774
  zend_result call_result = zend_call_method_if_exists(Z_OBJ(us->object), func_name, &retval, 3, args);
1038
774
  zend_string_release_ex(func_name, false);
1039
1040
774
  if (UNEXPECTED(call_result == FAILURE)) {
1041
19
    php_stream_warn(stream, NotImplemented,
1042
19
        "%s::" USERSTREAM_SET_OPTION " is not implemented!",
1043
19
        ZSTR_VAL(us->wrapper->ce->name));
1044
19
    return PHP_STREAM_OPTION_RETURN_ERR;
1045
19
  }
1046
755
  if (UNEXPECTED(Z_ISUNDEF(retval))) {
1047
6
    return PHP_STREAM_OPTION_RETURN_ERR;
1048
6
  }
1049
1050
749
  int ret;
1051
749
  if (zend_is_true(&retval)) {
1052
0
    ret = PHP_STREAM_OPTION_RETURN_OK;
1053
749
  } else {
1054
749
    ret = PHP_STREAM_OPTION_RETURN_ERR;
1055
749
  }
1056
1057
749
  zval_ptr_dtor(&retval);
1058
749
  return ret;
1059
755
}
1060
1061
774
static int php_userstreamop_set_option(php_stream *stream, int option, int value, void *ptrparam) {
1062
774
  php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
1063
1064
774
  switch (option) {
1065
0
    case PHP_STREAM_OPTION_CHECK_LIVENESS:
1066
0
      return user_stream_set_check_liveliness(stream, us);
1067
1068
0
    case PHP_STREAM_OPTION_LOCKING:
1069
0
      return user_stream_set_locking(stream, us, value);
1070
1071
0
    case PHP_STREAM_OPTION_TRUNCATE_API:
1072
0
      return user_stream_set_truncation(stream, us, value, ptrparam);
1073
1074
774
    case PHP_STREAM_OPTION_READ_BUFFER:
1075
774
    case PHP_STREAM_OPTION_WRITE_BUFFER:
1076
774
    case PHP_STREAM_OPTION_READ_TIMEOUT:
1077
774
    case PHP_STREAM_OPTION_BLOCKING:
1078
774
      return user_stream_set_option(stream, us, option, value, ptrparam);
1079
1080
0
    default:
1081
0
      return PHP_STREAM_OPTION_RETURN_NOTIMPL;
1082
774
  }
1083
774
}
1084
1085
1086
static int user_wrapper_unlink(php_stream_wrapper *wrapper, const char *url, int options, php_stream_context *context)
1087
0
{
1088
0
  struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract;
1089
0
  zval zretval;
1090
0
  zval args[1];
1091
0
  zval object;
1092
0
  int ret = 0;
1093
1094
  /* create an instance of our class */
1095
0
  user_stream_create_object(uwrap, context, &object);
1096
0
  if (Z_TYPE(object) == IS_UNDEF) {
1097
0
    return ret;
1098
0
  }
1099
1100
  /* call the unlink method */
1101
0
  ZVAL_STRING(&args[0], url);
1102
1103
1104
0
  zend_string *func_name = ZSTR_INIT_LITERAL(USERSTREAM_UNLINK, false);
1105
0
  zend_result call_result = zend_call_method_if_exists(Z_OBJ(object), func_name, &zretval, 1, args);
1106
0
  zend_string_release_ex(func_name, false);
1107
0
  zval_ptr_dtor(&args[0]);
1108
0
  zval_ptr_dtor(&object);
1109
1110
0
  if (UNEXPECTED(call_result == FAILURE)) {
1111
0
    php_stream_wrapper_warn(wrapper, context, REPORT_ERRORS, NotImplemented,
1112
0
        "%s::" USERSTREAM_UNLINK " is not implemented!", ZSTR_VAL(uwrap->ce->name));
1113
0
  } else if (Z_TYPE(zretval) == IS_FALSE || Z_TYPE(zretval) == IS_TRUE) {
1114
0
    ret = Z_TYPE(zretval) == IS_TRUE;
1115
0
  }
1116
  // TODO: Warn on invalid return type, or use zend_is_true()?
1117
1118
0
  zval_ptr_dtor(&zretval);
1119
1120
0
  return ret;
1121
0
}
1122
1123
static int user_wrapper_rename(php_stream_wrapper *wrapper, const char *url_from, const char *url_to,
1124
                 int options, php_stream_context *context)
1125
0
{
1126
0
  struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract;
1127
0
  zval zretval;
1128
0
  zval args[2];
1129
0
  zval object;
1130
0
  int ret = 0;
1131
1132
  /* create an instance of our class */
1133
0
  user_stream_create_object(uwrap, context, &object);
1134
0
  if (Z_TYPE(object) == IS_UNDEF) {
1135
0
    return ret;
1136
0
  }
1137
1138
  /* call the rename method */
1139
0
  ZVAL_STRING(&args[0], url_from);
1140
0
  ZVAL_STRING(&args[1], url_to);
1141
1142
0
  zend_string *func_name = ZSTR_INIT_LITERAL(USERSTREAM_RENAME, false);
1143
0
  zend_result call_result = zend_call_method_if_exists(Z_OBJ(object), func_name, &zretval, 2, args);
1144
0
  zend_string_release_ex(func_name, false);
1145
0
  zval_ptr_dtor(&args[1]);
1146
0
  zval_ptr_dtor(&args[0]);
1147
0
  zval_ptr_dtor(&object);
1148
1149
0
  if (UNEXPECTED(call_result == FAILURE)) {
1150
0
    php_stream_wrapper_warn(wrapper, context, REPORT_ERRORS, NotImplemented,
1151
0
        "%s::" USERSTREAM_RENAME " is not implemented!", ZSTR_VAL(uwrap->ce->name));
1152
0
  } else if (Z_TYPE(zretval) == IS_FALSE || Z_TYPE(zretval) == IS_TRUE) {
1153
0
    ret = Z_TYPE(zretval) == IS_TRUE;
1154
0
  }
1155
  // TODO: Warn on invalid return type, or use zend_is_true()?
1156
1157
0
  zval_ptr_dtor(&zretval);
1158
1159
0
  return ret;
1160
0
}
1161
1162
static int user_wrapper_mkdir(php_stream_wrapper *wrapper, const char *url, int mode,
1163
                int options, php_stream_context *context)
1164
0
{
1165
0
  struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract;
1166
0
  zval zretval;
1167
0
  zval args[3];
1168
0
  zval object;
1169
0
  int ret = 0;
1170
1171
  /* create an instance of our class */
1172
0
  user_stream_create_object(uwrap, context, &object);
1173
0
  if (Z_TYPE(object) == IS_UNDEF) {
1174
0
    return ret;
1175
0
  }
1176
1177
  /* call the mkdir method */
1178
0
  ZVAL_STRING(&args[0], url);
1179
0
  ZVAL_LONG(&args[1], mode);
1180
0
  ZVAL_LONG(&args[2], options);
1181
1182
0
  zend_string *func_name = ZSTR_INIT_LITERAL(USERSTREAM_MKDIR, false);
1183
0
  zend_result call_result = zend_call_method_if_exists(Z_OBJ(object), func_name, &zretval, 3, args);
1184
0
  zend_string_release_ex(func_name, false);
1185
0
  zval_ptr_dtor(&args[0]);
1186
0
  zval_ptr_dtor(&object);
1187
1188
0
  if (UNEXPECTED(call_result == FAILURE)) {
1189
0
    php_stream_wrapper_warn(wrapper, context, REPORT_ERRORS, NotImplemented,
1190
0
        "%s::" USERSTREAM_MKDIR " is not implemented!", ZSTR_VAL(uwrap->ce->name));
1191
0
  } else if (Z_TYPE(zretval) == IS_FALSE || Z_TYPE(zretval) == IS_TRUE) {
1192
0
    ret = Z_TYPE(zretval) == IS_TRUE;
1193
0
  }
1194
  // TODO: Warn on invalid return type, or use zend_is_true()?
1195
1196
0
  zval_ptr_dtor(&zretval);
1197
1198
0
  return ret;
1199
0
}
1200
1201
static int user_wrapper_rmdir(php_stream_wrapper *wrapper, const char *url,
1202
                int options, php_stream_context *context)
1203
0
{
1204
0
  struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract;
1205
0
  zval zretval;
1206
0
  zval args[2];
1207
0
  zval object;
1208
0
  int ret = 0;
1209
1210
  /* create an instance of our class */
1211
0
  user_stream_create_object(uwrap, context, &object);
1212
0
  if (Z_TYPE(object) == IS_UNDEF) {
1213
0
    return ret;
1214
0
  }
1215
1216
  /* call the rmdir method */
1217
0
  ZVAL_STRING(&args[0], url);
1218
0
  ZVAL_LONG(&args[1], options);
1219
1220
0
  zend_string *func_name = ZSTR_INIT_LITERAL(USERSTREAM_RMDIR, false);
1221
0
  zend_result call_result = zend_call_method_if_exists(Z_OBJ(object), func_name, &zretval, 2, args);
1222
0
  zend_string_release_ex(func_name, false);
1223
0
  zval_ptr_dtor(&args[0]);
1224
0
  zval_ptr_dtor(&object);
1225
1226
0
  if (UNEXPECTED(call_result == FAILURE)) {
1227
0
    php_stream_wrapper_warn(wrapper, context, REPORT_ERRORS, NotImplemented,
1228
0
        "%s::" USERSTREAM_RMDIR " is not implemented!", ZSTR_VAL(uwrap->ce->name));
1229
0
  } else if (Z_TYPE(zretval) == IS_FALSE || Z_TYPE(zretval) == IS_TRUE) {
1230
0
    ret = Z_TYPE(zretval) == IS_TRUE;
1231
0
  }
1232
  // TODO: Warn on invalid return type, or use zend_is_true()?
1233
1234
0
  zval_ptr_dtor(&zretval);
1235
1236
0
  return ret;
1237
0
}
1238
1239
static int user_wrapper_metadata(php_stream_wrapper *wrapper, const char *url, int option,
1240
                 void *value, php_stream_context *context)
1241
0
{
1242
0
  struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract;
1243
0
  zval zretval;
1244
0
  zval args[3];
1245
0
  zval object;
1246
0
  int ret = 0;
1247
1248
0
  switch(option) {
1249
0
    case PHP_STREAM_META_TOUCH:
1250
0
      array_init(&args[2]);
1251
0
      if(value) {
1252
0
        struct utimbuf *newtime = (struct utimbuf *)value;
1253
0
        add_index_long(&args[2], 0, newtime->modtime);
1254
0
        add_index_long(&args[2], 1, newtime->actime);
1255
0
      }
1256
0
      break;
1257
0
    case PHP_STREAM_META_GROUP:
1258
0
    case PHP_STREAM_META_OWNER:
1259
0
    case PHP_STREAM_META_ACCESS:
1260
0
      ZVAL_LONG(&args[2], *(long *)value);
1261
0
      break;
1262
0
    case PHP_STREAM_META_GROUP_NAME:
1263
0
    case PHP_STREAM_META_OWNER_NAME:
1264
0
      ZVAL_STRING(&args[2], value);
1265
0
      break;
1266
0
    default:
1267
0
      php_stream_wrapper_warn(wrapper, context, REPORT_ERRORS,
1268
0
          InvalidMeta,
1269
0
          "Unknown option %d for " USERSTREAM_METADATA, option);
1270
0
      return ret;
1271
0
  }
1272
1273
  /* create an instance of our class */
1274
0
  user_stream_create_object(uwrap, context, &object);
1275
0
  if (Z_TYPE(object) == IS_UNDEF) {
1276
0
    zval_ptr_dtor(&args[2]);
1277
0
    return ret;
1278
0
  }
1279
1280
  /* call the mkdir method */
1281
0
  ZVAL_STRING(&args[0], url);
1282
0
  ZVAL_LONG(&args[1], option);
1283
1284
0
  zend_string *func_name = ZSTR_INIT_LITERAL(USERSTREAM_METADATA, false);
1285
0
  zend_result call_result = zend_call_method_if_exists(Z_OBJ(object), func_name, &zretval, 3, args);
1286
0
  zend_string_release_ex(func_name, false);
1287
0
  zval_ptr_dtor(&args[2]);
1288
0
  zval_ptr_dtor(&args[0]);
1289
0
  zval_ptr_dtor(&object);
1290
1291
0
  if (UNEXPECTED(call_result == FAILURE)) {
1292
0
    php_stream_wrapper_warn(wrapper, context, REPORT_ERRORS, NotImplemented,
1293
0
        "%s::" USERSTREAM_METADATA " is not implemented!", ZSTR_VAL(uwrap->ce->name));
1294
0
  } else if (Z_TYPE(zretval) == IS_FALSE || Z_TYPE(zretval) == IS_TRUE) {
1295
0
    ret = Z_TYPE(zretval) == IS_TRUE;
1296
0
  }
1297
  // TODO: Warn on invalid return type, or use zend_is_true()?
1298
1299
0
  zval_ptr_dtor(&zretval);
1300
1301
0
  return ret;
1302
0
}
1303
1304
1305
static int user_wrapper_stat_url(php_stream_wrapper *wrapper, const char *url, int flags,
1306
                 php_stream_statbuf *ssb, php_stream_context *context)
1307
4.48k
{
1308
4.48k
  struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract;
1309
4.48k
  zval zretval;
1310
4.48k
  zval args[2];
1311
4.48k
  zval object;
1312
4.48k
  int ret = -1;
1313
1314
  /* create an instance of our class */
1315
4.48k
  user_stream_create_object(uwrap, context, &object);
1316
4.48k
  if (Z_TYPE(object) == IS_UNDEF) {
1317
0
    return -1;
1318
0
  }
1319
1320
  /* call it's stat_url method - set up params first */
1321
4.48k
  ZVAL_STRING(&args[0], url);
1322
4.48k
  ZVAL_LONG(&args[1], flags);
1323
1324
4.48k
  zend_string *func_name = ZSTR_INIT_LITERAL(USERSTREAM_STATURL, false);
1325
4.48k
  zend_result call_result = zend_call_method_if_exists(Z_OBJ(object), func_name, &zretval, 2, args);
1326
4.48k
  zend_string_release_ex(func_name, false);
1327
4.48k
  zval_ptr_dtor(&args[0]);
1328
4.48k
  zval_ptr_dtor(&object);
1329
1330
4.48k
  if (UNEXPECTED(call_result == FAILURE)) {
1331
31
    php_stream_wrapper_warn(wrapper, context, REPORT_ERRORS, NotImplemented,
1332
31
      "%s::" USERSTREAM_STATURL " is not implemented!", ZSTR_VAL(uwrap->ce->name));
1333
31
    return -1;
1334
31
  }
1335
4.45k
  if (UNEXPECTED(Z_ISUNDEF(zretval))) {
1336
37
    return -1;
1337
37
  }
1338
4.42k
  if (EXPECTED(Z_TYPE(zretval) == IS_ARRAY)) {
1339
0
    statbuf_from_array(Z_ARR(zretval), ssb);
1340
0
    ret = 0;
1341
0
  }
1342
  // TODO: Warning on incorrect return type?
1343
1344
4.42k
  zval_ptr_dtor(&zretval);
1345
1346
4.42k
  return ret;
1347
1348
4.45k
}
1349
1350
static ssize_t php_userstreamop_readdir(php_stream *stream, char *buf, size_t count)
1351
0
{
1352
0
  zval retval;
1353
0
  size_t didread = 0;
1354
0
  php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
1355
0
  php_stream_dirent *ent = (php_stream_dirent*)buf;
1356
1357
  /* avoid problems if someone mis-uses the stream */
1358
0
  if (count != sizeof(php_stream_dirent)) {
1359
0
    return -1;
1360
0
  }
1361
1362
0
  zend_string *func_name = ZSTR_INIT_LITERAL(USERSTREAM_DIR_READ, false);
1363
0
  zend_result call_result = zend_call_method_if_exists(Z_OBJ(us->object), func_name, &retval, 0, NULL);
1364
0
  zend_string_release_ex(func_name, false);
1365
1366
0
  if (UNEXPECTED(call_result == FAILURE)) {
1367
0
    php_stream_warn(stream, NotImplemented,
1368
0
      "%s::" USERSTREAM_DIR_READ " is not implemented!",
1369
0
      ZSTR_VAL(us->wrapper->ce->name));
1370
0
    return -1;
1371
0
  }
1372
0
  if (UNEXPECTED(Z_ISUNDEF(retval))) {
1373
0
    return -1;
1374
0
  }
1375
  // TODO: Warn/TypeError for invalid returns?
1376
0
  if (Z_TYPE(retval) != IS_FALSE && Z_TYPE(retval) != IS_TRUE) {
1377
0
    if (UNEXPECTED(!try_convert_to_string(&retval))) {
1378
0
      zval_ptr_dtor(&retval);
1379
0
      return -1;
1380
0
    }
1381
0
    PHP_STRLCPY(ent->d_name, Z_STRVAL(retval), sizeof(ent->d_name), Z_STRLEN(retval));
1382
0
    ent->d_type = DT_UNKNOWN;
1383
1384
0
    didread = sizeof(php_stream_dirent);
1385
0
  }
1386
1387
0
  zval_ptr_dtor(&retval);
1388
1389
0
  return didread;
1390
0
}
1391
1392
static int php_userstreamop_closedir(php_stream *stream, int close_handle)
1393
228
{
1394
228
  zval retval;
1395
228
  php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
1396
1397
228
  assert(us != NULL);
1398
1399
228
  zend_string *func_name = ZSTR_INIT_LITERAL(USERSTREAM_DIR_CLOSE, false);
1400
228
  zend_call_method_if_exists(Z_OBJ(us->object), func_name, &retval, 0, NULL);
1401
228
  zend_string_release_ex(func_name, false);
1402
1403
228
  zval_ptr_dtor(&retval);
1404
228
  zval_ptr_dtor(&us->object);
1405
228
  ZVAL_UNDEF(&us->object);
1406
228
  efree(us);
1407
1408
228
  return 0;
1409
228
}
1410
1411
static int php_userstreamop_rewinddir(php_stream *stream, zend_off_t offset, int whence, zend_off_t *newoffs)
1412
0
{
1413
0
  zval retval;
1414
0
  php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
1415
1416
0
  zend_string *func_name = ZSTR_INIT_LITERAL(USERSTREAM_DIR_REWIND, false);
1417
0
  zend_call_method_if_exists(Z_OBJ(us->object), func_name, &retval, 0, NULL);
1418
0
  zend_string_release_ex(func_name, false);
1419
1420
0
  zval_ptr_dtor(&retval);
1421
1422
0
  return 0;
1423
1424
0
}
1425
1426
static int php_userstreamop_cast(php_stream *stream, int castas, void **retptr)
1427
0
{
1428
0
  php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
1429
0
  zval retval;
1430
0
  zval args[1];
1431
0
  php_stream * intstream = NULL;
1432
0
  int ret = FAILURE;
1433
  /* If we are checking if the stream can cast, no return pointer is provided, so do not emit errors */
1434
0
  bool report_errors = retptr;
1435
1436
0
  switch(castas) {
1437
0
  case PHP_STREAM_AS_FD_FOR_SELECT:
1438
0
    ZVAL_LONG(&args[0], PHP_STREAM_AS_FD_FOR_SELECT);
1439
0
    break;
1440
0
  default:
1441
0
    ZVAL_LONG(&args[0], PHP_STREAM_AS_STDIO);
1442
0
    break;
1443
0
  }
1444
1445
0
  uint32_t orig_no_fclose = stream->flags & PHP_STREAM_FLAG_NO_FCLOSE;
1446
0
  stream->flags |= PHP_STREAM_FLAG_NO_FCLOSE;
1447
1448
0
  zend_string *func_name = ZSTR_INIT_LITERAL(USERSTREAM_CAST, false);
1449
0
  zend_result call_result = zend_call_method_if_exists(Z_OBJ(us->object), func_name, &retval, 1, args);
1450
0
  zend_string_release_ex(func_name, false);
1451
1452
0
  if (UNEXPECTED(call_result == FAILURE)) {
1453
0
    if (report_errors) {
1454
0
      php_stream_warn(stream, NotImplemented,
1455
0
          "%s::" USERSTREAM_CAST " is not implemented!",
1456
0
          ZSTR_VAL(us->wrapper->ce->name));
1457
0
    }
1458
0
    goto out;
1459
0
  }
1460
1461
0
  do {
1462
0
    if (!zend_is_true(&retval)) {
1463
0
      break;
1464
0
    }
1465
    // TODO: Can this emit an exception even with no error reporting?
1466
0
    php_stream_from_zval_no_verify(intstream, &retval);
1467
0
    if (!intstream) {
1468
0
      if (report_errors) {
1469
0
        php_stream_warn(stream, UserspaceInvalidReturn,
1470
0
            "%s::" USERSTREAM_CAST " must return a stream resource",
1471
0
            ZSTR_VAL(us->wrapper->ce->name));
1472
0
      }
1473
0
      break;
1474
0
    }
1475
0
    if (intstream == stream) {
1476
0
      if (report_errors) {
1477
0
        php_stream_warn(stream, UserspaceInvalidReturn,
1478
0
            "%s::" USERSTREAM_CAST " must not return itself",
1479
0
            ZSTR_VAL(us->wrapper->ce->name));
1480
0
      }
1481
0
      intstream = NULL;
1482
0
      break;
1483
0
    }
1484
0
    ret = php_stream_cast(intstream, castas, retptr, 1);
1485
0
  } while (0);
1486
1487
0
  zval_ptr_dtor(&retval);
1488
1489
0
out:
1490
0
  stream->flags &= ~PHP_STREAM_FLAG_NO_FCLOSE;
1491
0
  stream->flags |= orig_no_fclose;
1492
1493
0
  return ret;
1494
0
}
1495
1496
const php_stream_ops php_stream_userspace_ops = {
1497
  php_userstreamop_write, php_userstreamop_read,
1498
  php_userstreamop_close, php_userstreamop_flush,
1499
  "user-space",
1500
  php_userstreamop_seek,
1501
  php_userstreamop_cast,
1502
  php_userstreamop_stat,
1503
  php_userstreamop_set_option,
1504
};
1505
1506
const php_stream_ops php_stream_userspace_dir_ops = {
1507
  NULL, /* write */
1508
  php_userstreamop_readdir,
1509
  php_userstreamop_closedir,
1510
  NULL, /* flush */
1511
  "user-space-dir",
1512
  php_userstreamop_rewinddir,
1513
  NULL, /* cast */
1514
  NULL, /* stat */
1515
  NULL  /* set_option */
1516
};