Coverage Report

Created: 2026-10-28 06:20

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