Coverage Report

Created: 2026-06-02 06:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/php-src/main/streams/transports.c
Line
Count
Source
1
/*
2
  +----------------------------------------------------------------------+
3
  | Copyright © The PHP Group and Contributors.                          |
4
  +----------------------------------------------------------------------+
5
  | This source file is subject to the Modified BSD License that is      |
6
  | bundled with this package in the file LICENSE, and is available      |
7
  | through the World Wide Web at <https://www.php.net/license/>.        |
8
  |                                                                      |
9
  | SPDX-License-Identifier: BSD-3-Clause                                |
10
  +----------------------------------------------------------------------+
11
  | Author: Wez Furlong <wez@thebrainroom.com>                           |
12
  +----------------------------------------------------------------------+
13
*/
14
15
#include "php.h"
16
#include "php_streams_int.h"
17
#include "ext/standard/file.h"
18
19
static HashTable xport_hash;
20
21
PHPAPI HashTable *php_stream_xport_get_hash(void)
22
4
{
23
4
  return &xport_hash;
24
4
}
25
26
PHPAPI int php_stream_xport_register(const char *protocol, php_stream_transport_factory factory)
27
8
{
28
8
  zend_string *str = zend_string_init_interned(protocol, strlen(protocol), 1);
29
30
8
  zend_hash_update_ptr(&xport_hash, str, factory);
31
8
  zend_string_release_ex(str, 1);
32
8
  return SUCCESS;
33
8
}
34
35
PHPAPI int php_stream_xport_unregister(const char *protocol)
36
0
{
37
0
  return zend_hash_str_del(&xport_hash, protocol, strlen(protocol));
38
0
}
39
40
#define ERR_REPORT(code, out_err, fmt, arg) \
41
0
  if (out_err) { *out_err = strpprintf(0, fmt, arg); } \
42
0
  else { php_stream_wrapper_warn(NULL, NULL, REPORT_ERRORS, code, fmt, arg); }
43
44
#define ERR_RETURN(code, out_err, local_err, fmt) \
45
0
  if (out_err) { *out_err = local_err; } \
46
0
  else { php_stream_wrapper_warn(NULL, NULL, REPORT_ERRORS, code, fmt, local_err ? ZSTR_VAL(local_err) : "Unspecified error"); \
47
0
    if (local_err) { zend_string_release_ex(local_err, 0); local_err = NULL; } \
48
0
  }
49
50
PHPAPI php_stream *_php_stream_xport_create(const char *name, size_t namelen, int options,
51
    int flags, const char *persistent_id,
52
    struct timeval *timeout,
53
    php_stream_context *context,
54
    zend_string **error_string,
55
    int *error_code
56
    STREAMS_DC)
57
0
{
58
0
  php_stream *stream = NULL;
59
0
  php_stream_transport_factory factory = NULL;
60
0
  const char *p, *protocol, *orig_path = NULL;
61
0
  size_t n = 0;
62
0
  bool failed = false;
63
0
  bool bailout = false;
64
0
  zend_string *error_text = NULL;
65
0
  struct timeval default_timeout = { 0, 0 };
66
67
0
  default_timeout.tv_sec = FG(default_socket_timeout);
68
69
0
  if (timeout == NULL) {
70
0
    timeout = &default_timeout;
71
0
  }
72
73
  /* check for a cached persistent socket */
74
0
  if (persistent_id) {
75
0
    switch(php_stream_from_persistent_id(persistent_id, &stream)) {
76
0
      case PHP_STREAM_PERSISTENT_SUCCESS:
77
        /* use a 0-second timeout when checking if the socket
78
         * has already died */
79
0
        if (PHP_STREAM_OPTION_RETURN_OK == php_stream_set_option(stream, PHP_STREAM_OPTION_CHECK_LIVENESS, 0, NULL)) {
80
0
          return stream;
81
0
        }
82
        /* dead - kill it */
83
0
        php_stream_pclose(stream);
84
0
        stream = NULL;
85
86
        /* fall through */
87
88
0
      case PHP_STREAM_PERSISTENT_FAILURE:
89
0
      default:
90
        /* failed; get a new one */
91
0
        ;
92
0
    }
93
0
  }
94
95
0
  orig_path = name;
96
0
  for (p = name; isalnum((unsigned char)*p) || *p == '+' || *p == '-' || *p == '.'; p++) {
97
0
    n++;
98
0
  }
99
100
0
  if ((*p == ':') && (n > 1) && !strncmp("://", p, 3)) {
101
0
    protocol = name;
102
0
    name = p + 3;
103
0
    namelen -= n + 3;
104
0
  } else {
105
0
    protocol = "tcp";
106
0
    n = 3;
107
0
  }
108
109
0
  if (protocol) {
110
0
    if (NULL == (factory = zend_hash_str_find_ptr(&xport_hash, protocol, n))) {
111
0
      char wrapper_name[32];
112
113
0
      if (n >= sizeof(wrapper_name))
114
0
        n = sizeof(wrapper_name) - 1;
115
0
      PHP_STRLCPY(wrapper_name, protocol, sizeof(wrapper_name), n);
116
117
0
      ERR_REPORT(WrapperNotFound, error_string,
118
0
          "Unable to find the socket transport \"%s\" - did you forget to enable it when you configured PHP?",
119
0
          wrapper_name);
120
121
0
      return NULL;
122
0
    }
123
0
  }
124
125
0
  if (factory == NULL) {
126
    /* should never happen */
127
0
    php_stream_wrapper_warn(NULL, context, REPORT_ERRORS,
128
0
      WrapperNotFound, "Could not find a factory !?");
129
0
    return NULL;
130
0
  }
131
132
0
  stream = (factory)(protocol, n,
133
0
      name, namelen, persistent_id, options, flags, timeout,
134
0
      context STREAMS_REL_CC);
135
136
0
  if (stream) {
137
0
    zend_try {
138
0
      php_stream_context_set(stream, context);
139
0
      stream->orig_path = pestrdup(orig_path, persistent_id ? 1 : 0);
140
141
0
      if ((flags & STREAM_XPORT_SERVER) == 0) {
142
        /* client */
143
144
0
        if (flags & (STREAM_XPORT_CONNECT|STREAM_XPORT_CONNECT_ASYNC)) {
145
0
          if (-1 == php_stream_xport_connect(stream, name, namelen,
146
0
                flags & STREAM_XPORT_CONNECT_ASYNC ? 1 : 0,
147
0
                timeout, &error_text, error_code)) {
148
149
0
            ERR_RETURN(ConnectFailed, error_string, error_text, "connect() failed: %s");
150
151
0
            failed = true;
152
0
          }
153
0
        }
154
155
0
      } else {
156
        /* server */
157
0
        if (flags & STREAM_XPORT_BIND) {
158
0
          if (0 != php_stream_xport_bind(stream, name, namelen, &error_text)) {
159
0
            ERR_RETURN(BindFailed, error_string, error_text, "bind() failed: %s");
160
0
            failed = true;
161
0
          } else if (flags & STREAM_XPORT_LISTEN) {
162
0
            zval *zbacklog = NULL;
163
0
            int backlog = 32;
164
165
0
            if (PHP_STREAM_CONTEXT(stream) && (zbacklog = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "backlog")) != NULL) {
166
0
              backlog = zval_get_long(zbacklog);
167
0
            }
168
169
0
            if (0 != php_stream_xport_listen(stream, backlog, &error_text)) {
170
0
              ERR_RETURN(ListenFailed, error_string, error_text, "listen() failed: %s");
171
0
              failed = true;
172
0
            }
173
0
          }
174
0
          if (!failed) {
175
0
            stream->flags |= PHP_STREAM_FLAG_NO_IO;
176
0
          }
177
0
        }
178
0
      }
179
0
    } zend_catch {
180
0
      bailout = true;
181
0
    } zend_end_try();
182
0
  }
183
184
0
  if (failed || bailout) {
185
    /* failure means that they don't get a stream to play with */
186
0
    if (persistent_id) {
187
0
      php_stream_pclose(stream);
188
0
    } else {
189
0
      php_stream_close(stream);
190
0
    }
191
0
    stream = NULL;
192
0
    if (bailout) {
193
0
      zend_bailout();
194
0
    }
195
0
  }
196
197
0
  return stream;
198
0
}
199
200
/* Bind the stream to a local address */
201
PHPAPI int php_stream_xport_bind(php_stream *stream,
202
    const char *name, size_t namelen,
203
    zend_string **error_text
204
    )
205
0
{
206
0
  php_stream_xport_param param;
207
0
  int ret;
208
209
0
  memset(&param, 0, sizeof(param));
210
0
  param.op = STREAM_XPORT_OP_BIND;
211
0
  param.inputs.name = (char*)name;
212
0
  param.inputs.namelen = namelen;
213
0
  param.want_errortext = error_text ? 1 : 0;
214
215
0
  ret = php_stream_set_option(stream, PHP_STREAM_OPTION_XPORT_API, 0, &param);
216
217
0
  if (ret == PHP_STREAM_OPTION_RETURN_OK) {
218
0
    if (error_text) {
219
0
      *error_text = param.outputs.error_text;
220
0
    }
221
222
0
    return param.outputs.returncode;
223
0
  }
224
225
0
  return ret;
226
0
}
227
228
/* Connect to a remote address */
229
PHPAPI int php_stream_xport_connect(php_stream *stream,
230
    const char *name, size_t namelen,
231
    int asynchronous,
232
    struct timeval *timeout,
233
    zend_string **error_text,
234
    int *error_code
235
    )
236
0
{
237
0
  php_stream_xport_param param;
238
0
  int ret;
239
240
0
  memset(&param, 0, sizeof(param));
241
0
  param.op = asynchronous ? STREAM_XPORT_OP_CONNECT_ASYNC: STREAM_XPORT_OP_CONNECT;
242
0
  param.inputs.name = (char*)name;
243
0
  param.inputs.namelen = namelen;
244
0
  param.inputs.timeout = timeout;
245
246
0
  param.want_errortext = error_text ? 1 : 0;
247
248
0
  ret = php_stream_set_option(stream, PHP_STREAM_OPTION_XPORT_API, 0, &param);
249
250
0
  if (ret == PHP_STREAM_OPTION_RETURN_OK) {
251
0
    if (error_text) {
252
0
      *error_text = param.outputs.error_text;
253
0
    }
254
0
    if (error_code) {
255
0
      *error_code = param.outputs.error_code;
256
0
    }
257
0
    return param.outputs.returncode;
258
0
  }
259
260
0
  return ret;
261
262
0
}
263
264
/* Prepare to listen */
265
PHPAPI int php_stream_xport_listen(php_stream *stream, int backlog, zend_string **error_text)
266
0
{
267
0
  php_stream_xport_param param;
268
0
  int ret;
269
270
0
  memset(&param, 0, sizeof(param));
271
0
  param.op = STREAM_XPORT_OP_LISTEN;
272
0
  param.inputs.backlog = backlog;
273
0
  param.want_errortext = error_text ? 1 : 0;
274
275
0
  ret = php_stream_set_option(stream, PHP_STREAM_OPTION_XPORT_API, 0, &param);
276
277
0
  if (ret == PHP_STREAM_OPTION_RETURN_OK) {
278
0
    if (error_text) {
279
0
      *error_text = param.outputs.error_text;
280
0
    }
281
282
0
    return param.outputs.returncode;
283
0
  }
284
285
0
  return ret;
286
0
}
287
288
/* Get the next client and their address (as a string) */
289
PHPAPI int php_stream_xport_accept(php_stream *stream, php_stream **client,
290
    zend_string **textaddr,
291
    void **addr, socklen_t *addrlen,
292
    struct timeval *timeout,
293
    zend_string **error_text
294
    )
295
0
{
296
0
  php_stream_xport_param param;
297
0
  int ret;
298
299
0
  memset(&param, 0, sizeof(param));
300
301
0
  param.op = STREAM_XPORT_OP_ACCEPT;
302
0
  param.inputs.timeout = timeout;
303
0
  param.want_addr = addr ? 1 : 0;
304
0
  param.want_textaddr = textaddr ? 1 : 0;
305
0
  param.want_errortext = error_text ? 1 : 0;
306
307
0
  ret = php_stream_set_option(stream, PHP_STREAM_OPTION_XPORT_API, 0, &param);
308
309
0
  if (ret == PHP_STREAM_OPTION_RETURN_OK) {
310
0
    *client = param.outputs.client;
311
0
    if (addr) {
312
0
      *addr = param.outputs.addr;
313
0
      *addrlen = param.outputs.addrlen;
314
0
    }
315
0
    if (textaddr) {
316
0
      *textaddr = param.outputs.textaddr;
317
0
    }
318
0
    if (error_text) {
319
0
      *error_text = param.outputs.error_text;
320
0
    }
321
322
0
    return param.outputs.returncode;
323
0
  }
324
0
  return ret;
325
0
}
326
327
PHPAPI int php_stream_xport_get_name(php_stream *stream, int want_peer,
328
    zend_string **textaddr,
329
    void **addr, socklen_t *addrlen
330
    )
331
0
{
332
0
  php_stream_xport_param param;
333
0
  int ret;
334
335
0
  memset(&param, 0, sizeof(param));
336
337
0
  param.op = want_peer ? STREAM_XPORT_OP_GET_PEER_NAME : STREAM_XPORT_OP_GET_NAME;
338
0
  param.want_addr = addr ? 1 : 0;
339
0
  param.want_textaddr = textaddr ? 1 : 0;
340
341
0
  ret = php_stream_set_option(stream, PHP_STREAM_OPTION_XPORT_API, 0, &param);
342
343
0
  if (ret == PHP_STREAM_OPTION_RETURN_OK) {
344
0
    if (addr) {
345
0
      *addr = param.outputs.addr;
346
0
      *addrlen = param.outputs.addrlen;
347
0
    }
348
0
    if (textaddr) {
349
0
      *textaddr = param.outputs.textaddr;
350
0
    }
351
352
0
    return param.outputs.returncode;
353
0
  }
354
0
  return ret;
355
0
}
356
357
PHPAPI int php_stream_xport_crypto_setup(php_stream *stream, php_stream_xport_crypt_method_t crypto_method, php_stream *session_stream)
358
0
{
359
0
  php_stream_xport_crypto_param param;
360
0
  int ret;
361
362
0
  memset(&param, 0, sizeof(param));
363
0
  param.op = STREAM_XPORT_CRYPTO_OP_SETUP;
364
0
  param.inputs.method = crypto_method;
365
0
  param.inputs.session = session_stream;
366
367
0
  ret = php_stream_set_option(stream, PHP_STREAM_OPTION_CRYPTO_API, 0, &param);
368
369
0
  if (ret == PHP_STREAM_OPTION_RETURN_OK) {
370
0
    return param.outputs.returncode;
371
0
  }
372
373
0
  php_stream_warn_docref(stream, "streams.crypto", SslNotSupported,
374
0
    "This stream does not support SSL/crypto");
375
376
0
  return ret;
377
0
}
378
379
PHPAPI int php_stream_xport_crypto_enable(php_stream *stream, int activate)
380
0
{
381
0
  php_stream_xport_crypto_param param;
382
0
  int ret;
383
384
0
  memset(&param, 0, sizeof(param));
385
0
  param.op = STREAM_XPORT_CRYPTO_OP_ENABLE;
386
0
  param.inputs.activate = activate;
387
388
0
  ret = php_stream_set_option(stream, PHP_STREAM_OPTION_CRYPTO_API, 0, &param);
389
390
0
  if (ret == PHP_STREAM_OPTION_RETURN_OK) {
391
0
    return param.outputs.returncode;
392
0
  }
393
394
0
  php_stream_warn_docref(stream, "streams.crypto", SslNotSupported,
395
0
    "This stream does not support SSL/crypto");
396
397
0
  return ret;
398
0
}
399
400
/* Similar to recv() system call; read data from the stream, optionally
401
 * peeking, optionally retrieving OOB data */
402
PHPAPI int php_stream_xport_recvfrom(php_stream *stream, char *buf, size_t buflen,
403
    int flags, void **addr, socklen_t *addrlen, zend_string **textaddr
404
    )
405
0
{
406
0
  php_stream_xport_param param;
407
0
  int ret = 0;
408
0
  int recvd_len = 0;
409
#if 0
410
  int oob;
411
412
  if (flags == 0 && addr == NULL) {
413
    return php_stream_read(stream, buf, buflen);
414
  }
415
416
  if (stream->readfilters.head) {
417
    php_stream_warn(stream, FilterFailed,
418
      "Cannot peek or fetch OOB data from a filtered stream");
419
    return -1;
420
  }
421
422
  oob = (flags & STREAM_OOB) == STREAM_OOB;
423
424
  if (!oob && addr == NULL) {
425
    /* must be peeking at regular data; copy content from the buffer
426
     * first, then adjust the pointer/len before handing off to the
427
     * stream */
428
    recvd_len = stream->writepos - stream->readpos;
429
    if (recvd_len > buflen) {
430
      recvd_len = buflen;
431
    }
432
    if (recvd_len) {
433
      memcpy(buf, stream->readbuf, recvd_len);
434
      buf += recvd_len;
435
      buflen -= recvd_len;
436
    }
437
    /* if we filled their buffer, return */
438
    if (buflen == 0) {
439
      return recvd_len;
440
    }
441
  }
442
#endif
443
444
  /* otherwise, we are going to bypass the buffer */
445
446
0
  memset(&param, 0, sizeof(param));
447
448
0
  param.op = STREAM_XPORT_OP_RECV;
449
0
  param.want_addr = addr ? 1 : 0;
450
0
  param.want_textaddr = textaddr ? 1 : 0;
451
0
  param.inputs.buf = buf;
452
0
  param.inputs.buflen = buflen;
453
0
  param.inputs.flags = flags;
454
455
0
  ret = php_stream_set_option(stream, PHP_STREAM_OPTION_XPORT_API, 0, &param);
456
457
0
  if (ret == PHP_STREAM_OPTION_RETURN_OK) {
458
0
    if (addr) {
459
0
      *addr = param.outputs.addr;
460
0
      *addrlen = param.outputs.addrlen;
461
0
    }
462
0
    if (textaddr) {
463
0
      *textaddr = param.outputs.textaddr;
464
0
    }
465
0
    return recvd_len + param.outputs.returncode;
466
0
  }
467
0
  return recvd_len ? recvd_len : -1;
468
0
}
469
470
/* Similar to send() system call; send data to the stream, optionally
471
 * sending it as OOB data */
472
PHPAPI int php_stream_xport_sendto(php_stream *stream, const char *buf, size_t buflen,
473
    int flags, void *addr, socklen_t addrlen)
474
0
{
475
0
  php_stream_xport_param param;
476
0
  int ret = 0;
477
0
  int oob;
478
479
#if 0
480
  if (flags == 0 && addr == NULL) {
481
    return php_stream_write(stream, buf, buflen);
482
  }
483
#endif
484
485
0
  oob = (flags & STREAM_OOB) == STREAM_OOB;
486
487
0
  if ((oob || addr) && stream->writefilters.head) {
488
0
    php_stream_warn(stream, FilterFailed,
489
0
      "Cannot write OOB data, or data to a targeted address on a filtered stream");
490
0
    return -1;
491
0
  }
492
493
0
  memset(&param, 0, sizeof(param));
494
495
0
  param.op = STREAM_XPORT_OP_SEND;
496
0
  param.want_addr = addr ? 1 : 0;
497
0
  param.inputs.buf = (char*)buf;
498
0
  param.inputs.buflen = buflen;
499
0
  param.inputs.flags = flags;
500
0
  param.inputs.addr = addr;
501
0
  param.inputs.addrlen = addrlen;
502
503
0
  ret = php_stream_set_option(stream, PHP_STREAM_OPTION_XPORT_API, 0, &param);
504
505
0
  if (ret == PHP_STREAM_OPTION_RETURN_OK) {
506
0
    return param.outputs.returncode;
507
0
  }
508
0
  return -1;
509
0
}
510
511
/* Similar to shutdown() system call; shut down part of a full-duplex
512
 * connection */
513
PHPAPI int php_stream_xport_shutdown(php_stream *stream, stream_shutdown_t how)
514
0
{
515
0
  php_stream_xport_param param;
516
0
  int ret = 0;
517
518
0
  memset(&param, 0, sizeof(param));
519
520
0
  param.op = STREAM_XPORT_OP_SHUTDOWN;
521
0
  param.how = how;
522
523
0
  ret = php_stream_set_option(stream, PHP_STREAM_OPTION_XPORT_API, 0, &param);
524
525
0
  if (ret == PHP_STREAM_OPTION_RETURN_OK) {
526
0
    return param.outputs.returncode;
527
0
  }
528
0
  return -1;
529
0
}