Coverage Report

Created: 2025-11-16 06:23

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