Coverage Report

Created: 2026-07-25 06:39

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