Coverage Report

Created: 2025-09-27 06:26

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