Coverage Report

Created: 2026-10-28 06:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/php-src/main/SAPI.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
   | Original design:  Shane Caraveo <shane@caraveo.com>                  |
12
   | Authors: Andi Gutmans <andi@php.net>                                 |
13
   |          Zeev Suraski <zeev@php.net>                                 |
14
   +----------------------------------------------------------------------+
15
*/
16
17
#include <ctype.h>
18
#include <sys/stat.h>
19
#include <locale.h>
20
21
#include "php.h"
22
#include "SAPI.h"
23
#include "php_variables.h"
24
#include "php_ini.h"
25
#ifdef ZTS
26
#include "TSRM.h"
27
#endif
28
#ifdef HAVE_SYS_TIME_H
29
#include <sys/time.h>
30
#elif defined(PHP_WIN32)
31
#include "win32/time.h"
32
#endif
33
34
#include "rfc1867.h"
35
36
#include "php_content_types.h"
37
38
#ifdef ZTS
39
SAPI_API int sapi_globals_id;
40
SAPI_API size_t sapi_globals_offset;
41
#else
42
sapi_globals_struct sapi_globals;
43
#endif
44
45
static void _type_dtor(zval *zv)
46
0
{
47
0
  free(Z_PTR_P(zv));
48
0
}
49
50
static void sapi_globals_ctor(sapi_globals_struct *sapi_globals)
51
16
{
52
16
  memset(sapi_globals, 0, sizeof(*sapi_globals));
53
16
  zend_hash_init(&sapi_globals->known_post_content_types, 8, NULL, _type_dtor, 1);
54
16
  php_setup_sapi_content_types();
55
16
}
56
57
static void sapi_globals_dtor(sapi_globals_struct *sapi_globals)
58
0
{
59
0
  zend_hash_destroy(&sapi_globals->known_post_content_types);
60
0
}
61
62
/* True globals (no need for thread safety) */
63
SAPI_API sapi_module_struct sapi_module;
64
65
66
SAPI_API void sapi_startup(sapi_module_struct *sf)
67
16
{
68
16
  sf->ini_entries = NULL;
69
16
  sapi_module = *sf;
70
71
#ifdef ZTS
72
  ts_allocate_fast_id(&sapi_globals_id, &sapi_globals_offset, sizeof(sapi_globals_struct), (ts_allocate_ctor) sapi_globals_ctor, (ts_allocate_dtor) sapi_globals_dtor);
73
# ifdef PHP_WIN32
74
  _configthreadlocale(_ENABLE_PER_THREAD_LOCALE);
75
# endif
76
#else
77
16
  sapi_globals_ctor(&sapi_globals);
78
16
#endif
79
80
#ifdef PHP_WIN32
81
  tsrm_win32_startup();
82
#endif
83
84
16
  reentrancy_startup();
85
16
}
86
87
SAPI_API void sapi_shutdown(void)
88
0
{
89
#ifdef ZTS
90
  ts_free_id(sapi_globals_id);
91
#else
92
0
  sapi_globals_dtor(&sapi_globals);
93
0
#endif
94
95
0
  reentrancy_shutdown();
96
97
#ifdef PHP_WIN32
98
  tsrm_win32_shutdown();
99
#endif
100
0
}
101
102
103
SAPI_API void sapi_free_header(sapi_header_struct *sapi_header)
104
613k
{
105
613k
  efree(sapi_header->header);
106
613k
}
107
108
/* {{{ call a header function */
109
PHP_FUNCTION(header_register_callback)
110
0
{
111
0
  zend_fcall_info fci;
112
0
  zend_fcall_info_cache fcc;
113
114
0
  if (zend_parse_parameters(ZEND_NUM_ARGS(), "F", &fci, &fcc) == FAILURE) {
115
0
    RETURN_THROWS();
116
0
  }
117
118
0
  if (ZEND_FCC_INITIALIZED(SG(send_header_fcc))) {
119
0
    zend_fcc_dtor(&SG(send_header_fcc));
120
0
  }
121
122
  /* Don't store callback if headers have already been sent:
123
   * It won't get used and we won't have a chance to release it. */
124
0
  if (UNEXPECTED(SG(headers_sent))) {
125
0
    zend_release_fcall_info_cache(&fcc);
126
0
  } else {
127
0
    zend_fcc_dup(&SG(send_header_fcc), &fcc);
128
0
  }
129
0
  RETURN_TRUE;
130
0
}
131
/* }}} */
132
133
SAPI_API void sapi_handle_post(void *arg)
134
0
{
135
0
  if (SG(request_info).post_entry && SG(request_info).content_type_dup) {
136
0
    SG(request_info).post_entry->post_handler(SG(request_info).content_type_dup, arg);
137
0
    efree(SG(request_info).content_type_dup);
138
0
    SG(request_info).content_type_dup = NULL;
139
140
0
    if (ZEND_HAS_OOM()) {
141
0
      zend_handle_delayed_oom();
142
0
    }
143
0
  }
144
0
}
145
146
SAPI_API void sapi_read_post_data(void)
147
0
{
148
0
  sapi_post_entry *post_entry;
149
0
  uint32_t content_type_length = (uint32_t)strlen(SG(request_info).content_type);
150
0
  char *content_type = estrndup(SG(request_info).content_type, content_type_length);
151
0
  char *p;
152
0
  char oldchar=0;
153
0
  void (*post_reader_func)(void) = NULL;
154
155
156
  /* dedicated implementation for increased performance:
157
   * - Make the content type lowercase
158
   * - Trim descriptive data, stay with the content-type only
159
   */
160
0
  for (p = content_type; p < content_type + content_type_length; p++) {
161
0
    switch (*p) {
162
0
      case ';':
163
0
      case ',':
164
0
      case ' ':
165
0
        content_type_length = p-content_type;
166
0
        oldchar = *p;
167
0
        *p = 0;
168
0
        break;
169
0
      default:
170
0
        *p = tolower((unsigned char)*p);
171
0
        break;
172
0
    }
173
0
  }
174
175
  /* now try to find an appropriate POST content handler */
176
0
  if ((post_entry = zend_hash_str_find_ptr(&SG(known_post_content_types), content_type,
177
0
      content_type_length)) != NULL) {
178
    /* found one, register it for use */
179
0
    SG(request_info).post_entry = post_entry;
180
0
    post_reader_func = post_entry->post_reader;
181
0
  } else {
182
    /* fallback */
183
0
    SG(request_info).post_entry = NULL;
184
0
    if (UNEXPECTED(!sapi_module.default_post_reader)) {
185
      /* this should not happen as there should always be a default_post_reader */
186
0
      SG(request_info).content_type_dup = NULL;
187
0
      sapi_module.sapi_error(E_WARNING, "Unsupported content type:  '%s'", content_type);
188
0
      efree(content_type);
189
0
      return;
190
0
    }
191
0
  }
192
0
  if (oldchar) {
193
0
    *(p-1) = oldchar;
194
0
  }
195
196
  /* the content_type_dup is not set at this stage so no need to try to free it first */
197
0
  SG(request_info).content_type_dup = content_type;
198
199
0
  if(post_reader_func) {
200
0
    post_reader_func();
201
0
  }
202
203
0
  if(sapi_module.default_post_reader) {
204
0
    sapi_module.default_post_reader();
205
0
  }
206
0
}
207
208
SAPI_API size_t sapi_read_post_block(char *buffer, size_t buflen)
209
0
{
210
0
  size_t read_bytes;
211
212
0
  if (!sapi_module.read_post) {
213
0
    return 0;
214
0
  }
215
216
0
  read_bytes = sapi_module.read_post(buffer, buflen);
217
218
0
  if (read_bytes > 0) {
219
    /* gogo */
220
0
    SG(read_post_bytes) += read_bytes;
221
0
  }
222
0
  if (read_bytes < buflen) {
223
    /* done */
224
0
    SG(post_read) = true;
225
0
  }
226
227
0
  return read_bytes;
228
0
}
229
230
SAPI_API SAPI_POST_READER_FUNC(sapi_read_standard_form_data)
231
0
{
232
0
  zend_long post_max_size = REQUEST_PARSE_BODY_OPTION_GET(post_max_size, SG(post_max_size));
233
234
0
  if (post_max_size > 0 && SG(request_info).content_length > post_max_size) {
235
0
    php_error_docref(NULL, E_WARNING, "POST Content-Length of " ZEND_LONG_FMT " bytes exceeds the limit of " ZEND_LONG_FMT " bytes",
236
0
          SG(request_info).content_length, post_max_size);
237
0
    return;
238
0
  }
239
240
241
0
  SG(request_info).request_body = php_stream_temp_create_ex(TEMP_STREAM_DEFAULT, SAPI_POST_BLOCK_SIZE, PG(upload_tmp_dir));
242
243
0
  if (sapi_module.read_post) {
244
0
    size_t read_bytes;
245
246
0
    for (;;) {
247
0
      char buffer[SAPI_POST_BLOCK_SIZE];
248
249
0
      read_bytes = sapi_read_post_block(buffer, SAPI_POST_BLOCK_SIZE);
250
251
0
      if (read_bytes > 0) {
252
0
        if (php_stream_write(SG(request_info).request_body, buffer, read_bytes) != read_bytes) {
253
          /* if parts of the stream can't be written, purge it completely */
254
0
          php_stream_truncate_set_size(SG(request_info).request_body, 0);
255
0
          php_error_docref(NULL, E_WARNING, "POST data can't be buffered; all data discarded");
256
0
          break;
257
0
        }
258
0
      }
259
260
0
      if (post_max_size > 0 && SG(read_post_bytes) > post_max_size) {
261
0
        php_error_docref(NULL, E_WARNING, "Actual POST length does not match Content-Length, and exceeds " ZEND_LONG_FMT " bytes", post_max_size);
262
0
        break;
263
0
      }
264
265
0
      if (read_bytes < SAPI_POST_BLOCK_SIZE) {
266
        /* done */
267
0
        break;
268
0
      }
269
0
    }
270
0
    php_stream_rewind(SG(request_info).request_body);
271
0
  }
272
0
}
273
274
275
static inline char *get_default_content_type(uint32_t prefix_len, uint32_t *len)
276
306k
{
277
306k
  char *mimetype, *charset, *content_type;
278
306k
  uint32_t mimetype_len, charset_len;
279
280
306k
  if (SG(default_mimetype)) {
281
306k
    mimetype = SG(default_mimetype);
282
306k
    mimetype_len = (uint32_t)strlen(SG(default_mimetype));
283
306k
  } else {
284
0
    mimetype = SAPI_DEFAULT_MIMETYPE;
285
0
    mimetype_len = sizeof(SAPI_DEFAULT_MIMETYPE) - 1;
286
0
  }
287
306k
  if (SG(default_charset)) {
288
306k
    charset = SG(default_charset);
289
306k
    charset_len = (uint32_t)strlen(SG(default_charset));
290
306k
  } else {
291
0
    charset = SAPI_DEFAULT_CHARSET;
292
0
    charset_len = sizeof(SAPI_DEFAULT_CHARSET) - 1;
293
0
  }
294
295
306k
  if (*charset && strncasecmp(mimetype, "text/", 5) == 0) {
296
306k
    char *p;
297
298
306k
    *len = prefix_len + mimetype_len + sizeof("; charset=") - 1 + charset_len;
299
306k
    content_type = (char*)emalloc(*len + 1);
300
306k
    p = content_type + prefix_len;
301
306k
    p = zend_mempcpy(p, mimetype, mimetype_len);
302
306k
    p = zend_mempcpy(p, "; charset=", sizeof("; charset=") - 1);
303
306k
    memcpy(p, charset, charset_len + 1);
304
306k
  } else {
305
0
    *len = prefix_len + mimetype_len;
306
0
    content_type = (char*)emalloc(*len + 1);
307
0
    memcpy(content_type + prefix_len, mimetype, mimetype_len + 1);
308
0
  }
309
306k
  return content_type;
310
306k
}
311
312
313
SAPI_API char *sapi_get_default_content_type(void)
314
0
{
315
0
  uint32_t len;
316
317
0
  return get_default_content_type(0, &len);
318
0
}
319
320
321
SAPI_API void sapi_get_default_content_type_header(sapi_header_struct *default_header)
322
306k
{
323
306k
  uint32_t len;
324
325
306k
  default_header->header = get_default_content_type(sizeof("Content-type: ")-1, &len);
326
306k
  default_header->header_len = len;
327
306k
  memcpy(default_header->header, "Content-type: ", sizeof("Content-type: ") - 1);
328
306k
}
329
330
/*
331
 * Add charset on content-type header if the MIME type starts with
332
 * "text/", the default_charset directive is not empty and
333
 * there is not already a charset option in there.
334
 *
335
 * If "mimetype" is non-NULL, it should point to a pointer allocated
336
 * with emalloc(). If a charset is added, the string will be
337
 * re-allocated and the new length is returned.  If mimetype is
338
 * unchanged, 0 is returned.
339
 *
340
 */
341
SAPI_API size_t sapi_apply_default_charset(char **mimetype, size_t len)
342
0
{
343
0
  char *charset, *newtype;
344
0
  size_t newlen;
345
0
  charset = SG(default_charset) ? SG(default_charset) : SAPI_DEFAULT_CHARSET;
346
347
0
  if (*mimetype != NULL) {
348
0
    if (*charset && strncmp(*mimetype, "text/", 5) == 0 && strstr(*mimetype, "charset=") == NULL) {
349
0
      newlen = len + (sizeof(";charset=")-1) + strlen(charset);
350
0
      newtype = zend_cstr_concat3(
351
0
        *mimetype, len,
352
0
        ";charset=", sizeof(";charset=")-1,
353
0
        charset, strlen(charset));
354
0
      efree(*mimetype);
355
0
      *mimetype = newtype;
356
0
      return newlen;
357
0
    }
358
0
  }
359
0
  return 0;
360
0
}
361
362
SAPI_API void sapi_activate_headers_only(void)
363
0
{
364
0
  if (SG(request_info).headers_read == 1)
365
0
    return;
366
0
  SG(request_info).headers_read = 1;
367
0
  zend_llist_init(&SG(sapi_headers).headers, sizeof(sapi_header_struct),
368
0
      (void (*)(void *)) sapi_free_header, 0);
369
0
  SG(sapi_headers).send_default_content_type = 1;
370
371
  /* SG(sapi_headers).http_response_code = 200; */
372
0
  SG(sapi_headers).http_status_line = NULL;
373
0
  SG(sapi_headers).mimetype = NULL;
374
0
  SG(read_post_bytes) = 0;
375
0
  SG(request_info).request_body = NULL;
376
0
  SG(request_info).current_user = NULL;
377
0
  SG(request_info).no_headers = 0;
378
0
  SG(request_info).post_entry = NULL;
379
0
  SG(global_request_time) = 0;
380
381
  /*
382
   * It's possible to override this general case in the activate() callback,
383
   * if necessary.
384
   */
385
0
  if (SG(request_info).request_method && !strcmp(SG(request_info).request_method, "HEAD")) {
386
0
    SG(request_info).headers_only = 1;
387
0
  } else {
388
0
    SG(request_info).headers_only = 0;
389
0
  }
390
0
  if (SG(server_context)) {
391
0
    SG(request_info).cookie_data = sapi_module.read_cookies();
392
0
    if (sapi_module.activate) {
393
0
      sapi_module.activate();
394
0
    }
395
0
  }
396
0
  if (sapi_module.input_filter_init ) {
397
0
    sapi_module.input_filter_init();
398
0
  }
399
0
}
400
401
/*
402
 * Called from php_request_startup() for every request.
403
 */
404
405
SAPI_API void sapi_activate(void)
406
306k
{
407
306k
  zend_llist_init(&SG(sapi_headers).headers, sizeof(sapi_header_struct), (void (*)(void *)) sapi_free_header, 0);
408
306k
  SG(sapi_headers).send_default_content_type = 1;
409
410
  /*
411
  SG(sapi_headers).http_response_code = 200;
412
  */
413
306k
  SG(sapi_headers).http_status_line = NULL;
414
306k
  SG(sapi_headers).mimetype = NULL;
415
306k
  SG(headers_sent) = false;
416
306k
  SG(read_post_bytes) = 0;
417
306k
  SG(request_info).request_body = NULL;
418
306k
  SG(request_info).current_user = NULL;
419
306k
  SG(request_info).no_headers = 0;
420
306k
  SG(request_info).post_entry = NULL;
421
306k
  SG(request_info).proto_num = 1000; /* Default to HTTP 1.0 */
422
306k
  SG(global_request_time) = 0;
423
306k
  SG(post_read) = false;
424
306k
  SG(send_header_fcc) = empty_fcall_info_cache;
425
  /* It's possible to override this general case in the activate() callback, if necessary. */
426
306k
  if (SG(request_info).request_method && !strcmp(SG(request_info).request_method, "HEAD")) {
427
0
    SG(request_info).headers_only = 1;
428
306k
  } else {
429
306k
    SG(request_info).headers_only = 0;
430
306k
  }
431
306k
  SG(rfc1867_uploaded_files) = NULL;
432
306k
  SG(request_parse_body_context).throw_exceptions = false;
433
306k
  memset(&SG(request_parse_body_context).options_cache, 0, sizeof(SG(request_parse_body_context).options_cache));
434
435
306k
  if (sapi_module.pre_request_init) {
436
0
    sapi_module.pre_request_init();
437
0
  }
438
439
  /* Handle request method */
440
306k
  if (SG(server_context)) {
441
0
    if (PG(enable_post_data_reading)
442
0
    &&  SG(request_info).content_type
443
0
    &&  SG(request_info).request_method
444
0
    && !strcmp(SG(request_info).request_method, "POST")) {
445
      /* HTTP POST may contain form data to be processed into variables
446
       * depending on given content type */
447
0
      sapi_read_post_data();
448
0
    } else {
449
0
      SG(request_info).content_type_dup = NULL;
450
0
    }
451
452
    /* Cookies */
453
0
    SG(request_info).cookie_data = sapi_module.read_cookies();
454
0
  }
455
306k
  if (sapi_module.activate) {
456
0
    sapi_module.activate();
457
0
  }
458
306k
  if (sapi_module.input_filter_init) {
459
0
    sapi_module.input_filter_init();
460
0
  }
461
306k
}
462
463
464
static void sapi_send_headers_free(void)
465
613k
{
466
613k
  if (SG(sapi_headers).http_status_line) {
467
0
    efree(SG(sapi_headers).http_status_line);
468
0
    SG(sapi_headers).http_status_line = NULL;
469
0
  }
470
613k
}
471
472
SAPI_API void sapi_deactivate_module(void)
473
306k
{
474
306k
  zend_llist_destroy(&SG(sapi_headers).headers);
475
306k
  if (SG(request_info).request_body) {
476
0
    SG(request_info).request_body = NULL;
477
306k
  } else if (SG(server_context)) {
478
0
    if (!SG(post_read)) {
479
      /* make sure we've consumed all request input data */
480
0
      char dummy[SAPI_POST_BLOCK_SIZE];
481
0
      size_t read_bytes;
482
483
0
      do {
484
0
        read_bytes = sapi_read_post_block(dummy, SAPI_POST_BLOCK_SIZE);
485
0
      } while (SAPI_POST_BLOCK_SIZE == read_bytes);
486
0
    }
487
0
  }
488
306k
  if (SG(request_info).auth_user) {
489
0
    efree(SG(request_info).auth_user);
490
0
    SG(request_info).auth_user = NULL;
491
0
  }
492
306k
  if (SG(request_info).auth_password) {
493
0
    efree(SG(request_info).auth_password);
494
0
    SG(request_info).auth_password = NULL;
495
0
  }
496
306k
  if (SG(request_info).auth_digest) {
497
0
    efree(SG(request_info).auth_digest);
498
0
    SG(request_info).auth_digest = NULL;
499
0
  }
500
306k
  if (SG(request_info).content_type_dup) {
501
0
    efree(SG(request_info).content_type_dup);
502
0
    SG(request_info).content_type_dup = NULL;
503
0
  }
504
306k
  if (SG(request_info).current_user) {
505
0
    zend_string_release_ex(SG(request_info).current_user, false);
506
0
    SG(request_info).current_user = NULL;
507
0
  }
508
306k
  if (sapi_module.deactivate) {
509
0
    sapi_module.deactivate();
510
0
  }
511
306k
}
512
513
SAPI_API void sapi_deactivate_destroy(void)
514
306k
{
515
306k
  if (SG(rfc1867_uploaded_files)) {
516
0
    destroy_uploaded_files_hash();
517
0
  }
518
306k
  if (SG(sapi_headers).mimetype) {
519
0
    efree(SG(sapi_headers).mimetype);
520
0
    SG(sapi_headers).mimetype = NULL;
521
0
  }
522
306k
  sapi_send_headers_free();
523
306k
  SG(sapi_started) = 0;
524
306k
  SG(headers_sent) = false;
525
306k
  SG(request_info).headers_read = 0;
526
306k
  SG(global_request_time) = 0;
527
306k
}
528
529
SAPI_API void sapi_deactivate(void)
530
16
{
531
16
  sapi_deactivate_module();
532
16
  sapi_deactivate_destroy();
533
16
}
534
535
536
SAPI_API void sapi_initialize_empty_request(void)
537
16
{
538
16
  SG(server_context) = NULL;
539
16
  SG(request_info).request_method = NULL;
540
16
  SG(request_info).auth_digest = SG(request_info).auth_user = SG(request_info).auth_password = NULL;
541
16
  SG(request_info).content_type_dup = NULL;
542
16
}
543
544
545
static int sapi_extract_response_code(const char *header_line)
546
0
{
547
0
  int code = 200;
548
0
  const char *ptr;
549
550
0
  for (ptr = header_line; *ptr; ptr++) {
551
0
    if (*ptr == ' ' && *(ptr + 1) != ' ') {
552
0
      code = atoi(ptr + 1);
553
0
      break;
554
0
    }
555
0
  }
556
557
0
  return code;
558
0
}
559
560
561
static void sapi_update_response_code(int ncode)
562
0
{
563
  /* if the status code did not change, we do not want
564
     to change the status line, and no need to change the code */
565
0
  if (SG(sapi_headers).http_response_code == ncode) {
566
0
    return;
567
0
  }
568
569
0
  if (SG(sapi_headers).http_status_line) {
570
0
    efree(SG(sapi_headers).http_status_line);
571
0
    SG(sapi_headers).http_status_line = NULL;
572
0
  }
573
0
  SG(sapi_headers).http_response_code = ncode;
574
0
}
575
576
/*
577
 * since zend_llist_del_element only removes one matched item once,
578
 * we should remove them manually
579
 */
580
static void sapi_remove_header(zend_llist *l, char *name, size_t len, size_t prefix_len)
581
306k
{
582
306k
  sapi_header_struct *header;
583
306k
  zend_llist_element *next;
584
306k
  zend_llist_element *current=l->head;
585
586
306k
  while (current) {
587
0
    header = (sapi_header_struct *)(current->data);
588
0
    next = current->next;
589
    /*
590
     * prefix_len is set for DELETE_PREFIX (used for deleting i.e.
591
     * "Set-Cookie: PHPSESSID=", where we need more than just key)
592
     * look for the : otherwise
593
     */
594
0
    if (header->header_len > len
595
0
        && (header->header[len] == ':' || (prefix_len && len > prefix_len))
596
0
        && !strncasecmp(header->header, name, len)) {
597
0
      if (current->prev) {
598
0
        current->prev->next = next;
599
0
      } else {
600
0
        l->head = next;
601
0
      }
602
0
      if (next) {
603
0
        next->prev = current->prev;
604
0
      } else {
605
0
        l->tail = current->prev;
606
0
      }
607
0
      sapi_free_header(header);
608
0
      efree(current);
609
0
      --l->count;
610
0
    }
611
0
    current = next;
612
0
  }
613
306k
}
614
615
SAPI_API int sapi_add_header_ex(const char *header_line, size_t header_line_len, bool duplicate, bool replace)
616
306k
{
617
306k
  sapi_header_line ctr = {0};
618
306k
  int r;
619
620
306k
  ctr.line = header_line;
621
306k
  ctr.line_len = header_line_len;
622
623
306k
  r = sapi_header_op(replace ? SAPI_HEADER_REPLACE : SAPI_HEADER_ADD,
624
306k
      &ctr);
625
626
306k
  if (!duplicate)
627
0
    efree((void *) header_line);
628
629
306k
  return r;
630
306k
}
631
632
static void sapi_header_add_op(sapi_header_op_enum op, sapi_header_struct *sapi_header)
633
306k
{
634
306k
  if (!sapi_module.header_handler ||
635
306k
    (SAPI_HEADER_ADD & sapi_module.header_handler(sapi_header, op, &SG(sapi_headers)))) {
636
306k
    if (op == SAPI_HEADER_REPLACE) {
637
306k
      char *colon_offset = strchr(sapi_header->header, ':');
638
639
306k
      if (colon_offset) {
640
306k
        char saved_char = *colon_offset;
641
642
306k
        *colon_offset = 0;
643
306k
        sapi_remove_header(&SG(sapi_headers).headers, sapi_header->header, strlen(sapi_header->header), 0);
644
306k
        *colon_offset = saved_char;
645
306k
      }
646
306k
    }
647
306k
    zend_llist_add_element(&SG(sapi_headers).headers, (void *) sapi_header);
648
306k
  } else {
649
0
    sapi_free_header(sapi_header);
650
0
  }
651
306k
}
652
653
SAPI_API zend_result sapi_header_op(sapi_header_op_enum op, void *arg)
654
306k
{
655
306k
  sapi_header_struct sapi_header;
656
306k
  char *colon_offset;
657
306k
  char *header_line;
658
306k
  size_t header_line_len, header_len;
659
306k
  int http_response_code;
660
661
306k
  if (SG(headers_sent) && !SG(request_info).no_headers) {
662
0
    const char *output_start_filename = php_output_get_start_filename();
663
0
    int output_start_lineno = php_output_get_start_lineno();
664
665
0
    if (output_start_filename) {
666
0
      sapi_module.sapi_error(E_WARNING, "Cannot modify header information - headers already sent by (output started at %s:%d)",
667
0
        output_start_filename, output_start_lineno);
668
0
    } else {
669
0
      sapi_module.sapi_error(E_WARNING, "Cannot modify header information - headers already sent");
670
0
    }
671
0
    return FAILURE;
672
0
  }
673
674
306k
  switch (op) {
675
0
    case SAPI_HEADER_SET_STATUS:
676
0
      sapi_update_response_code((int)(intptr_t) arg);
677
0
      return SUCCESS;
678
679
0
    case SAPI_HEADER_ADD:
680
306k
    case SAPI_HEADER_REPLACE:
681
306k
    case SAPI_HEADER_DELETE_PREFIX:
682
306k
    case SAPI_HEADER_DELETE: {
683
306k
        sapi_header_line *p = arg;
684
685
306k
        if (!p->line || !p->line_len) {
686
0
          return FAILURE;
687
0
        }
688
306k
        header_line = estrndup(p->line, p->line_len);
689
306k
        header_line_len = p->line_len;
690
306k
        if (op == SAPI_HEADER_DELETE_PREFIX) {
691
0
          header_len = p->header_len;
692
0
          http_response_code = 0;
693
306k
        } else {
694
306k
          header_len = 0;
695
306k
          http_response_code = p->response_code;
696
306k
        }
697
306k
        break;
698
306k
      }
699
700
0
    case SAPI_HEADER_DELETE_ALL:
701
0
      if (sapi_module.header_handler) {
702
0
        sapi_module.header_handler(&sapi_header, op, &SG(sapi_headers));
703
0
      }
704
0
      zend_llist_clean(&SG(sapi_headers).headers);
705
0
      return SUCCESS;
706
707
0
    default:
708
0
      return FAILURE;
709
306k
  }
710
711
  /* cut off trailing spaces, linefeeds and carriage-returns */
712
306k
  if (header_line_len && isspace((unsigned char)header_line[header_line_len - 1])) {
713
1
    do {
714
1
      header_line_len--;
715
1
    } while(header_line_len && isspace((unsigned char)header_line[header_line_len - 1]));
716
1
    header_line[header_line_len]='\0';
717
1
  }
718
719
306k
  if (op == SAPI_HEADER_DELETE || op == SAPI_HEADER_DELETE_PREFIX) {
720
0
    if (op == SAPI_HEADER_DELETE && strchr(header_line, ':')) {
721
0
      efree(header_line);
722
0
      sapi_module.sapi_error(E_WARNING, "Header to delete may not contain colon.");
723
0
      return FAILURE;
724
0
    }
725
0
    if (sapi_module.header_handler) {
726
0
      sapi_header.header = header_line;
727
0
      sapi_header.header_len = header_line_len;
728
0
      sapi_module.header_handler(&sapi_header, op, &SG(sapi_headers));
729
0
    }
730
0
    sapi_remove_header(&SG(sapi_headers).headers, header_line, header_line_len, header_len);
731
0
    efree(header_line);
732
0
    return SUCCESS;
733
306k
  } else {
734
    /* new line/NUL character safety check */
735
306k
    size_t i;
736
8.59M
    for (i = 0; i < header_line_len; i++) {
737
      /* RFC 7230 ch. 3.2.4 deprecates folding support */
738
8.28M
      if (header_line[i] == '\n' || header_line[i] == '\r') {
739
1
        efree(header_line);
740
1
        sapi_module.sapi_error(E_WARNING, "Header may not contain "
741
1
            "more than a single header, new line detected");
742
1
        return FAILURE;
743
1
      }
744
8.28M
      if (header_line[i] == '\0') {
745
1
        efree(header_line);
746
1
        sapi_module.sapi_error(E_WARNING, "Header may not contain NUL bytes");
747
1
        return FAILURE;
748
1
      }
749
8.28M
    }
750
306k
  }
751
752
306k
  sapi_header.header = header_line;
753
306k
  sapi_header.header_len = header_line_len;
754
755
  /* Check the header for a few cases that we have special support for in SAPI */
756
306k
  if (header_line_len>=5
757
306k
    && !strncasecmp(header_line, "HTTP/", 5)) {
758
    /* filter out the response code */
759
0
    sapi_update_response_code(sapi_extract_response_code(header_line));
760
    /* sapi_update_response_code doesn't free the status line if the code didn't change */
761
0
    if (SG(sapi_headers).http_status_line) {
762
0
      efree(SG(sapi_headers).http_status_line);
763
0
    }
764
0
    SG(sapi_headers).http_status_line = header_line;
765
0
    return SUCCESS;
766
306k
  } else {
767
306k
    colon_offset = strchr(header_line, ':');
768
306k
    if (colon_offset) {
769
306k
      *colon_offset = 0;
770
306k
      if (!strcasecmp(header_line, "Content-Type")) {
771
0
        char *ptr = colon_offset+1, *mimetype = NULL, *newheader;
772
0
        size_t len = header_line_len - (ptr - header_line), newlen;
773
0
        while (*ptr == ' ') {
774
0
          ptr++;
775
0
          len--;
776
0
        }
777
778
0
        mimetype = estrdup(ptr);
779
0
        newlen = sapi_apply_default_charset(&mimetype, len);
780
0
        if (!SG(sapi_headers).mimetype){
781
0
          SG(sapi_headers).mimetype = estrdup(mimetype);
782
0
        }
783
784
0
        if (newlen != 0) {
785
0
          newlen += sizeof("Content-type: ");
786
0
          newheader = emalloc(newlen);
787
0
          PHP_STRLCPY(newheader, "Content-type: ", newlen, sizeof("Content-type: ")-1);
788
0
          strlcat(newheader, mimetype, newlen);
789
0
          sapi_header.header = newheader;
790
0
          sapi_header.header_len = (uint32_t)(newlen - 1);
791
0
          efree(header_line);
792
0
        }
793
0
        efree(mimetype);
794
0
        SG(sapi_headers).send_default_content_type = 0;
795
306k
      } else if (!strcasecmp(header_line, "Content-Length")) {
796
        /* Script is setting Content-length. The script cannot reasonably
797
         * know the size of the message body after compression, so it's best
798
         * to disable compression altogether. This contributes to making scripts
799
         * portable between setups that have and don't have zlib compression
800
         * enabled globally. See req #44164 */
801
0
        zend_string *key = ZSTR_INIT_LITERAL("zlib.output_compression", 0);
802
0
        zend_alter_ini_entry_chars(key,
803
0
          "0", sizeof("0") - 1, PHP_INI_USER, PHP_INI_STAGE_RUNTIME);
804
0
        zend_string_release_ex(key, 0);
805
306k
      } else if (!strcasecmp(header_line, "Location")) {
806
0
        if ((SG(sapi_headers).http_response_code < 300 ||
807
0
          SG(sapi_headers).http_response_code > 399) &&
808
0
          SG(sapi_headers).http_response_code != 201) {
809
          /* Return a Found Redirect if one is not already specified */
810
0
          if (http_response_code) { /* user specified redirect code */
811
0
            sapi_update_response_code(http_response_code);
812
0
          } else if (SG(request_info).proto_num > 1000 &&
813
0
             SG(request_info).request_method &&
814
0
             strcmp(SG(request_info).request_method, "HEAD") &&
815
0
             strcmp(SG(request_info).request_method, "GET")) {
816
0
            sapi_update_response_code(303);
817
0
          } else {
818
0
            sapi_update_response_code(302);
819
0
          }
820
0
        }
821
306k
      } else if (!strcasecmp(header_line, "WWW-Authenticate")) { /* HTTP Authentication */
822
0
        sapi_update_response_code(401); /* authentication-required */
823
0
      }
824
306k
      if (sapi_header.header==header_line) {
825
306k
        *colon_offset = ':';
826
306k
      }
827
306k
    }
828
306k
  }
829
306k
  if (http_response_code) {
830
0
    sapi_update_response_code(http_response_code);
831
0
  }
832
306k
  sapi_header_add_op(op, &sapi_header);
833
306k
  return SUCCESS;
834
306k
}
835
836
837
SAPI_API zend_result sapi_send_headers(void)
838
306k
{
839
306k
  int retval;
840
306k
  zend_result ret = FAILURE;
841
842
306k
  if (SG(headers_sent) || SG(request_info).no_headers) {
843
0
    return SUCCESS;
844
0
  }
845
846
  /* Success-oriented. We set headers_sent to 1 here to avoid an infinite loop
847
   * in case of an error situation.
848
   */
849
306k
  if (SG(sapi_headers).send_default_content_type && sapi_module.send_headers) {
850
0
      uint32_t len = 0;
851
0
    char *default_mimetype = get_default_content_type(0, &len);
852
853
0
    if (default_mimetype && len) {
854
0
      sapi_header_struct default_header;
855
856
0
      SG(sapi_headers).mimetype = default_mimetype;
857
858
0
      default_header.header_len = sizeof("Content-type: ") - 1 + len;
859
0
      default_header.header = zend_cstr_concat(
860
0
        "Content-type: ", sizeof("Content-type: ") - 1,
861
0
        SG(sapi_headers).mimetype, len);
862
863
0
      sapi_header_add_op(SAPI_HEADER_ADD, &default_header);
864
0
    } else {
865
0
      efree(default_mimetype);
866
0
    }
867
0
    SG(sapi_headers).send_default_content_type = 0;
868
0
  }
869
870
306k
  if (ZEND_FCC_INITIALIZED(SG(send_header_fcc))) {
871
0
    zend_fcall_info_cache fcc = SG(send_header_fcc);
872
    /* Prevent triggering the callback multiple times */
873
0
    SG(send_header_fcc) = empty_fcall_info_cache;
874
0
    zend_call_known_fcc(&fcc, NULL, 0, NULL, NULL);
875
0
    zend_fcc_dtor(&fcc);
876
0
  }
877
878
306k
  SG(headers_sent) = true;
879
880
306k
  if (sapi_module.send_headers) {
881
0
    retval = sapi_module.send_headers(&SG(sapi_headers));
882
306k
  } else {
883
306k
    retval = SAPI_HEADER_DO_SEND;
884
306k
  }
885
886
306k
  switch (retval) {
887
0
    case SAPI_HEADER_SENT_SUCCESSFULLY:
888
0
      ret = SUCCESS;
889
0
      break;
890
306k
    case SAPI_HEADER_DO_SEND: {
891
306k
        sapi_header_struct http_status_line;
892
306k
        char buf[255];
893
894
306k
        if (SG(sapi_headers).http_status_line) {
895
0
          http_status_line.header = SG(sapi_headers).http_status_line;
896
0
          http_status_line.header_len = (uint32_t)strlen(SG(sapi_headers).http_status_line);
897
306k
        } else {
898
306k
          http_status_line.header = buf;
899
306k
          http_status_line.header_len = slprintf(buf, sizeof(buf), "HTTP/1.0 %d X", SG(sapi_headers).http_response_code);
900
306k
        }
901
306k
        sapi_module.send_header(&http_status_line, SG(server_context));
902
306k
      }
903
306k
      zend_llist_apply_with_argument(&SG(sapi_headers).headers, (llist_apply_with_arg_func_t) sapi_module.send_header, SG(server_context));
904
306k
      if(SG(sapi_headers).send_default_content_type) {
905
306k
        sapi_header_struct default_header;
906
907
306k
        sapi_get_default_content_type_header(&default_header);
908
306k
        sapi_module.send_header(&default_header, SG(server_context));
909
306k
        sapi_free_header(&default_header);
910
306k
      }
911
306k
      sapi_module.send_header(NULL, SG(server_context));
912
306k
      ret = SUCCESS;
913
306k
      break;
914
0
    case SAPI_HEADER_SEND_FAILED:
915
0
      SG(headers_sent) = false;
916
0
      ret = FAILURE;
917
0
      break;
918
306k
  }
919
920
306k
  sapi_send_headers_free();
921
922
306k
  return ret;
923
306k
}
924
925
926
SAPI_API zend_result sapi_register_post_entries(const sapi_post_entry *post_entries)
927
16
{
928
16
  const sapi_post_entry *p=post_entries;
929
930
48
  while (p->content_type) {
931
32
    if (sapi_register_post_entry(p) == FAILURE) {
932
0
      return FAILURE;
933
0
    }
934
32
    p++;
935
32
  }
936
16
  return SUCCESS;
937
16
}
938
939
940
SAPI_API zend_result sapi_register_post_entry(const sapi_post_entry *post_entry)
941
32
{
942
32
  zend_string *key;
943
32
  if (SG(sapi_started) && EG(current_execute_data)) {
944
0
    return FAILURE;
945
0
  }
946
32
  key = zend_string_init(post_entry->content_type, post_entry->content_type_len, 1);
947
32
  GC_MAKE_PERSISTENT_LOCAL(key);
948
32
  zend_result ret = zend_hash_add_mem(&SG(known_post_content_types), key,
949
32
      (void *) post_entry, sizeof(sapi_post_entry)) ? SUCCESS : FAILURE;
950
32
  zend_string_release_ex(key, 1);
951
32
  return ret;
952
32
}
953
954
SAPI_API void sapi_unregister_post_entry(const sapi_post_entry *post_entry)
955
0
{
956
0
  if (SG(sapi_started) && EG(current_execute_data)) {
957
0
    return;
958
0
  }
959
0
  zend_hash_str_del(&SG(known_post_content_types), post_entry->content_type,
960
0
      post_entry->content_type_len);
961
0
}
962
963
964
SAPI_API zend_result sapi_register_default_post_reader(void (*default_post_reader)(void))
965
16
{
966
16
  if (SG(sapi_started) && EG(current_execute_data)) {
967
0
    return FAILURE;
968
0
  }
969
16
  sapi_module.default_post_reader = default_post_reader;
970
16
  return SUCCESS;
971
16
}
972
973
974
SAPI_API zend_result sapi_register_treat_data(void (*treat_data)(int arg, char *str, zval *destArray))
975
16
{
976
16
  if (SG(sapi_started) && EG(current_execute_data)) {
977
0
    return FAILURE;
978
0
  }
979
16
  sapi_module.treat_data = treat_data;
980
16
  return SUCCESS;
981
16
}
982
983
SAPI_API zend_result sapi_register_input_filter(unsigned int (*input_filter)(int arg, const char *var, char **val, size_t val_len, size_t *new_val_len), unsigned int (*input_filter_init)(void))
984
16
{
985
16
  if (SG(sapi_started) && EG(current_execute_data)) {
986
0
    return FAILURE;
987
0
  }
988
16
  sapi_module.input_filter = input_filter;
989
16
  sapi_module.input_filter_init = input_filter_init;
990
16
  return SUCCESS;
991
16
}
992
993
SAPI_API zend_result sapi_flush(void)
994
9.83M
{
995
9.83M
  if (sapi_module.flush) {
996
9.83M
    sapi_module.flush(SG(server_context));
997
9.83M
    return SUCCESS;
998
9.83M
  } else {
999
0
    return FAILURE;
1000
0
  }
1001
9.83M
}
1002
1003
SAPI_API zend_stat_t *sapi_get_stat(void)
1004
0
{
1005
0
  if (sapi_module.get_stat) {
1006
0
    return sapi_module.get_stat();
1007
0
  } else {
1008
0
    if (!SG(request_info).path_translated || (VCWD_STAT(SG(request_info).path_translated, &SG(global_stat)) == -1)) {
1009
0
      return NULL;
1010
0
    }
1011
0
    return &SG(global_stat);
1012
0
  }
1013
0
}
1014
1015
SAPI_API char *sapi_getenv(const char *name, size_t name_len)
1016
2
{
1017
2
  char *value, *tmp;
1018
1019
2
  if (!sapi_module.getenv) {
1020
2
    return NULL;
1021
2
  }
1022
0
  if (!strncasecmp(name, "HTTP_PROXY", name_len)) {
1023
    /* Ugly fix for HTTP_PROXY issue, see bug #72573 */
1024
0
    return NULL;
1025
0
  }
1026
0
  tmp = sapi_module.getenv(name, name_len);
1027
0
  if (!tmp) {
1028
0
    return NULL;
1029
0
  }
1030
0
  value = estrdup(tmp);
1031
#ifdef PHP_WIN32
1032
  if (strlen(sapi_module.name) == sizeof("cgi-fcgi") - 1 && !strcmp(sapi_module.name, "cgi-fcgi")) {
1033
    /* XXX more modules to go, if needed. */
1034
    free(tmp);
1035
  }
1036
#endif
1037
0
  if (sapi_module.input_filter) {
1038
0
    sapi_module.input_filter(PARSE_STRING, name, &value, strlen(value), NULL);
1039
0
  }
1040
0
  return value;
1041
0
}
1042
1043
SAPI_API double sapi_get_request_time(void)
1044
306k
{
1045
306k
  if(SG(global_request_time)) return SG(global_request_time);
1046
1047
306k
  if (!sapi_module.get_request_time
1048
306k
      || sapi_module.get_request_time(&SG(global_request_time)) == FAILURE) {
1049
306k
    struct timeval tp = {0};
1050
306k
    if (!gettimeofday(&tp, NULL)) {
1051
306k
      SG(global_request_time) = (double)(tp.tv_sec + tp.tv_usec / 1000000.00);
1052
306k
    } else {
1053
0
      SG(global_request_time) = (double)time(0);
1054
0
    }
1055
306k
  }
1056
306k
  return SG(global_request_time);
1057
306k
}
1058
1059
SAPI_API void sapi_add_request_header(const char *var, unsigned int var_len, char *val, unsigned int val_len, void *arg) /* {{{ */
1060
0
{
1061
0
  zval *return_value = (zval*)arg;
1062
0
  char *buf = NULL;
1063
1064
0
  ALLOCA_FLAG(use_heap)
1065
1066
0
  if (var_len > 5 &&
1067
0
      var[0] == 'H' &&
1068
0
      var[1] == 'T' &&
1069
0
      var[2] == 'T' &&
1070
0
      var[3] == 'P' &&
1071
0
      var[4] == '_') {
1072
1073
0
    const char *p;
1074
0
    char *str;
1075
1076
0
    var_len -= 5;
1077
0
    p = var + 5;
1078
0
    var = str = buf = do_alloca(var_len + 1, use_heap);
1079
0
    *str++ = *p++;
1080
0
    while (*p) {
1081
0
      if (*p == '_') {
1082
0
        *str++ = '-';
1083
0
        p++;
1084
0
        if (*p) {
1085
0
          *str++ = *p++;
1086
0
        }
1087
0
      } else if (*p >= 'A' && *p <= 'Z') {
1088
0
        *str++ = (*p++ - 'A' + 'a');
1089
0
      } else {
1090
0
        *str++ = *p++;
1091
0
      }
1092
0
    }
1093
0
    *str = 0;
1094
0
  } else if (var_len == sizeof("CONTENT_TYPE")-1 &&
1095
0
             memcmp(var, "CONTENT_TYPE", sizeof("CONTENT_TYPE")-1) == 0) {
1096
0
    var = "Content-Type";
1097
0
  } else if (var_len == sizeof("CONTENT_LENGTH")-1 &&
1098
0
             memcmp(var, "CONTENT_LENGTH", sizeof("CONTENT_LENGTH")-1) == 0) {
1099
0
    var = "Content-Length";
1100
0
  } else {
1101
0
    return;
1102
0
  }
1103
0
  add_assoc_stringl_ex(return_value, var, var_len, val, val_len);
1104
0
  if (buf) {
1105
    free_alloca(buf, use_heap);
1106
0
  }
1107
0
}
1108
/* }}} */