Coverage Report

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