1 /*
2 +----------------------------------------------------------------------+
3 | PHP Version 7 |
4 +----------------------------------------------------------------------+
5 | Copyright (c) The PHP Group |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
15 | Author: Rasmus Lerdorf <rasmus@lerdorf.on.ca> |
16 +----------------------------------------------------------------------+
17 */
18
19 #include <stdio.h>
20 #include "php.h"
21 #include "ext/standard/php_standard.h"
22 #include "ext/date/php_date.h"
23 #include "SAPI.h"
24 #include "php_main.h"
25 #include "head.h"
26 #include <time.h>
27
28 #include "php_globals.h"
29 #include "zend_smart_str.h"
30
31
32 /* Implementation of the language Header() function */
33 /* {{{ proto void header(string header [, bool replace, [int http_response_code]])
34 Sends a raw HTTP header */
PHP_FUNCTION(header)35 PHP_FUNCTION(header)
36 {
37 zend_bool rep = 1;
38 sapi_header_line ctr = {0};
39 size_t len;
40
41 ZEND_PARSE_PARAMETERS_START(1, 3)
42 Z_PARAM_STRING(ctr.line, len)
43 Z_PARAM_OPTIONAL
44 Z_PARAM_BOOL(rep)
45 Z_PARAM_LONG(ctr.response_code)
46 ZEND_PARSE_PARAMETERS_END();
47
48 ctr.line_len = (uint32_t)len;
49 sapi_header_op(rep ? SAPI_HEADER_REPLACE:SAPI_HEADER_ADD, &ctr);
50 }
51 /* }}} */
52
53 /* {{{ proto void header_remove([string name])
54 Removes an HTTP header previously set using header() */
PHP_FUNCTION(header_remove)55 PHP_FUNCTION(header_remove)
56 {
57 sapi_header_line ctr = {0};
58 size_t len = 0;
59
60 ZEND_PARSE_PARAMETERS_START(0, 1)
61 Z_PARAM_OPTIONAL
62 Z_PARAM_STRING(ctr.line, len)
63 ZEND_PARSE_PARAMETERS_END();
64
65 ctr.line_len = (uint32_t)len;
66 sapi_header_op(ZEND_NUM_ARGS() == 0 ? SAPI_HEADER_DELETE_ALL : SAPI_HEADER_DELETE, &ctr);
67 }
68 /* }}} */
69
php_header(void)70 PHPAPI int php_header(void)
71 {
72 if (sapi_send_headers()==FAILURE || SG(request_info).headers_only) {
73 return 0; /* don't allow output */
74 } else {
75 return 1; /* allow output */
76 }
77 }
78
php_setcookie(zend_string * name,zend_string * value,time_t expires,zend_string * path,zend_string * domain,int secure,int httponly,zend_string * samesite,int url_encode)79 PHPAPI int php_setcookie(zend_string *name, zend_string *value, time_t expires, zend_string *path, zend_string *domain, int secure, int httponly, zend_string *samesite, int url_encode)
80 {
81 zend_string *dt;
82 sapi_header_line ctr = {0};
83 int result;
84 smart_str buf = {0};
85
86 if (!ZSTR_LEN(name)) {
87 zend_error( E_WARNING, "Cookie names must not be empty" );
88 return FAILURE;
89 } else if (strpbrk(ZSTR_VAL(name), "=,; \t\r\n\013\014") != NULL) { /* man isspace for \013 and \014 */
90 zend_error(E_WARNING, "Cookie names cannot contain any of the following '=,; \\t\\r\\n\\013\\014'" );
91 return FAILURE;
92 }
93
94 if (!url_encode && value &&
95 strpbrk(ZSTR_VAL(value), ",; \t\r\n\013\014") != NULL) { /* man isspace for \013 and \014 */
96 zend_error(E_WARNING, "Cookie values cannot contain any of the following ',; \\t\\r\\n\\013\\014'" );
97 return FAILURE;
98 }
99
100 if (path && strpbrk(ZSTR_VAL(path), ",; \t\r\n\013\014") != NULL) { /* man isspace for \013 and \014 */
101 zend_error(E_WARNING, "Cookie paths cannot contain any of the following ',; \\t\\r\\n\\013\\014'" );
102 return FAILURE;
103 }
104
105 if (domain && strpbrk(ZSTR_VAL(domain), ",; \t\r\n\013\014") != NULL) { /* man isspace for \013 and \014 */
106 zend_error(E_WARNING, "Cookie domains cannot contain any of the following ',; \\t\\r\\n\\013\\014'" );
107 return FAILURE;
108 }
109
110 if (value == NULL || ZSTR_LEN(value) == 0) {
111 /*
112 * MSIE doesn't delete a cookie when you set it to a null value
113 * so in order to force cookies to be deleted, even on MSIE, we
114 * pick an expiry date in the past
115 */
116 dt = php_format_date("D, d-M-Y H:i:s T", sizeof("D, d-M-Y H:i:s T")-1, 1, 0);
117 smart_str_appends(&buf, "Set-Cookie: ");
118 smart_str_append(&buf, name);
119 smart_str_appends(&buf, "=deleted; expires=");
120 smart_str_append(&buf, dt);
121 smart_str_appends(&buf, "; Max-Age=0");
122 zend_string_free(dt);
123 } else {
124 smart_str_appends(&buf, "Set-Cookie: ");
125 smart_str_append(&buf, name);
126 smart_str_appendc(&buf, '=');
127 if (url_encode) {
128 zend_string *encoded_value = php_raw_url_encode(ZSTR_VAL(value), ZSTR_LEN(value));
129 smart_str_append(&buf, encoded_value);
130 zend_string_release_ex(encoded_value, 0);
131 } else {
132 smart_str_append(&buf, value);
133 }
134 if (expires > 0) {
135 const char *p;
136 double diff;
137
138 smart_str_appends(&buf, COOKIE_EXPIRES);
139 dt = php_format_date("D, d-M-Y H:i:s T", sizeof("D, d-M-Y H:i:s T")-1, expires, 0);
140 /* check to make sure that the year does not exceed 4 digits in length */
141 p = zend_memrchr(ZSTR_VAL(dt), '-', ZSTR_LEN(dt));
142 if (!p || *(p + 5) != ' ') {
143 zend_string_free(dt);
144 smart_str_free(&buf);
145 zend_error(E_WARNING, "Expiry date cannot have a year greater than 9999");
146 return FAILURE;
147 }
148
149 smart_str_append(&buf, dt);
150 zend_string_free(dt);
151
152 diff = difftime(expires, php_time());
153 if (diff < 0) {
154 diff = 0;
155 }
156
157 smart_str_appends(&buf, COOKIE_MAX_AGE);
158 smart_str_append_long(&buf, (zend_long) diff);
159 }
160 }
161
162 if (path && ZSTR_LEN(path)) {
163 smart_str_appends(&buf, COOKIE_PATH);
164 smart_str_append(&buf, path);
165 }
166 if (domain && ZSTR_LEN(domain)) {
167 smart_str_appends(&buf, COOKIE_DOMAIN);
168 smart_str_append(&buf, domain);
169 }
170 if (secure) {
171 smart_str_appends(&buf, COOKIE_SECURE);
172 }
173 if (httponly) {
174 smart_str_appends(&buf, COOKIE_HTTPONLY);
175 }
176 if (samesite && ZSTR_LEN(samesite)) {
177 smart_str_appends(&buf, COOKIE_SAMESITE);
178 smart_str_append(&buf, samesite);
179 }
180
181 ctr.line = ZSTR_VAL(buf.s);
182 ctr.line_len = (uint32_t) ZSTR_LEN(buf.s);
183
184 result = sapi_header_op(SAPI_HEADER_ADD, &ctr);
185 zend_string_release(buf.s);
186 return result;
187 }
188
php_head_parse_cookie_options_array(zval * options,zend_long * expires,zend_string ** path,zend_string ** domain,zend_bool * secure,zend_bool * httponly,zend_string ** samesite)189 static void php_head_parse_cookie_options_array(zval *options, zend_long *expires, zend_string **path, zend_string **domain, zend_bool *secure, zend_bool *httponly, zend_string **samesite) {
190 int found = 0;
191 zend_string *key;
192 zval *value;
193
194 ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(options), key, value) {
195 if (key) {
196 if (zend_string_equals_literal_ci(key, "expires")) {
197 *expires = zval_get_long(value);
198 found++;
199 } else if (zend_string_equals_literal_ci(key, "path")) {
200 *path = zval_get_string(value);
201 found++;
202 } else if (zend_string_equals_literal_ci(key, "domain")) {
203 *domain = zval_get_string(value);
204 found++;
205 } else if (zend_string_equals_literal_ci(key, "secure")) {
206 *secure = zval_is_true(value);
207 found++;
208 } else if (zend_string_equals_literal_ci(key, "httponly")) {
209 *httponly = zval_is_true(value);
210 found++;
211 } else if (zend_string_equals_literal_ci(key, "samesite")) {
212 *samesite = zval_get_string(value);
213 found++;
214 } else {
215 php_error_docref(NULL, E_WARNING, "Unrecognized key '%s' found in the options array", ZSTR_VAL(key));
216 }
217 } else {
218 php_error_docref(NULL, E_WARNING, "Numeric key found in the options array");
219 }
220 } ZEND_HASH_FOREACH_END();
221
222 /* Array is not empty but no valid keys were found */
223 if (found == 0 && zend_hash_num_elements(Z_ARRVAL_P(options)) > 0) {
224 php_error_docref(NULL, E_WARNING, "No valid options were found in the given array");
225 }
226 }
227
228 /* {{{ proto bool setcookie(string name [, string value [, int expires [, string path [, string domain [, bool secure[, bool httponly]]]]]])
229 setcookie(string name [, string value [, array options]])
230 Send a cookie */
PHP_FUNCTION(setcookie)231 PHP_FUNCTION(setcookie)
232 {
233 zval *expires_or_options = NULL;
234 zend_string *name, *value = NULL, *path = NULL, *domain = NULL, *samesite = NULL;
235 zend_long expires = 0;
236 zend_bool secure = 0, httponly = 0;
237
238 ZEND_PARSE_PARAMETERS_START(1, 7)
239 Z_PARAM_STR(name)
240 Z_PARAM_OPTIONAL
241 Z_PARAM_STR(value)
242 Z_PARAM_ZVAL(expires_or_options)
243 Z_PARAM_STR(path)
244 Z_PARAM_STR(domain)
245 Z_PARAM_BOOL(secure)
246 Z_PARAM_BOOL(httponly)
247 ZEND_PARSE_PARAMETERS_END();
248
249 if (expires_or_options) {
250 if (Z_TYPE_P(expires_or_options) == IS_ARRAY) {
251 if (UNEXPECTED(ZEND_NUM_ARGS() > 3)) {
252 php_error_docref(NULL, E_WARNING, "Cannot pass arguments after the options array");
253 RETURN_FALSE;
254 }
255 php_head_parse_cookie_options_array(expires_or_options, &expires, &path, &domain, &secure, &httponly, &samesite);
256 } else {
257 expires = zval_get_long(expires_or_options);
258 }
259 }
260
261 if (!EG(exception)) {
262 if (php_setcookie(name, value, expires, path, domain, secure, httponly, samesite, 1) == SUCCESS) {
263 RETVAL_TRUE;
264 } else {
265 RETVAL_FALSE;
266 }
267 }
268
269 if (expires_or_options && Z_TYPE_P(expires_or_options) == IS_ARRAY) {
270 if (path) {
271 zend_string_release(path);
272 }
273 if (domain) {
274 zend_string_release(domain);
275 }
276 if (samesite) {
277 zend_string_release(samesite);
278 }
279 }
280 }
281 /* }}} */
282
283 /* {{{ proto bool setrawcookie(string name [, string value [, int expires [, string path [, string domain [, bool secure[, bool httponly]]]]]])
284 setrawcookie(string name [, string value [, array options]])
285 Send a cookie with no url encoding of the value */
PHP_FUNCTION(setrawcookie)286 PHP_FUNCTION(setrawcookie)
287 {
288 zval *expires_or_options = NULL;
289 zend_string *name, *value = NULL, *path = NULL, *domain = NULL, *samesite = NULL;
290 zend_long expires = 0;
291 zend_bool secure = 0, httponly = 0;
292
293 ZEND_PARSE_PARAMETERS_START(1, 7)
294 Z_PARAM_STR(name)
295 Z_PARAM_OPTIONAL
296 Z_PARAM_STR(value)
297 Z_PARAM_ZVAL(expires_or_options)
298 Z_PARAM_STR(path)
299 Z_PARAM_STR(domain)
300 Z_PARAM_BOOL(secure)
301 Z_PARAM_BOOL(httponly)
302 ZEND_PARSE_PARAMETERS_END();
303
304 if (expires_or_options) {
305 if (Z_TYPE_P(expires_or_options) == IS_ARRAY) {
306 if (UNEXPECTED(ZEND_NUM_ARGS() > 3)) {
307 php_error_docref(NULL, E_WARNING, "Cannot pass arguments after the options array");
308 RETURN_FALSE;
309 }
310 php_head_parse_cookie_options_array(expires_or_options, &expires, &path, &domain, &secure, &httponly, &samesite);
311 } else {
312 expires = zval_get_long(expires_or_options);
313 }
314 }
315
316 if (!EG(exception)) {
317 if (php_setcookie(name, value, expires, path, domain, secure, httponly, samesite, 0) == SUCCESS) {
318 RETVAL_TRUE;
319 } else {
320 RETVAL_FALSE;
321 }
322 }
323
324 if (expires_or_options && Z_TYPE_P(expires_or_options) == IS_ARRAY) {
325 if (path) {
326 zend_string_release(path);
327 }
328 if (domain) {
329 zend_string_release(domain);
330 }
331 if (samesite) {
332 zend_string_release(samesite);
333 }
334 }
335 }
336 /* }}} */
337
338
339 /* {{{ proto bool headers_sent([string &$file [, int &$line]])
340 Returns true if headers have already been sent, false otherwise */
PHP_FUNCTION(headers_sent)341 PHP_FUNCTION(headers_sent)
342 {
343 zval *arg1 = NULL, *arg2 = NULL;
344 const char *file="";
345 int line=0;
346
347 ZEND_PARSE_PARAMETERS_START(0, 2)
348 Z_PARAM_OPTIONAL
349 Z_PARAM_ZVAL(arg1)
350 Z_PARAM_ZVAL(arg2)
351 ZEND_PARSE_PARAMETERS_END();
352
353 if (SG(headers_sent)) {
354 line = php_output_get_start_lineno();
355 file = php_output_get_start_filename();
356 }
357
358 switch(ZEND_NUM_ARGS()) {
359 case 2:
360 ZEND_TRY_ASSIGN_REF_LONG(arg2, line);
361 case 1:
362 if (file) {
363 ZEND_TRY_ASSIGN_REF_STRING(arg1, file);
364 } else {
365 ZEND_TRY_ASSIGN_REF_EMPTY_STRING(arg1);
366 }
367 break;
368 }
369
370 if (SG(headers_sent)) {
371 RETURN_TRUE;
372 } else {
373 RETURN_FALSE;
374 }
375 }
376 /* }}} */
377
378 /* {{{ php_head_apply_header_list_to_hash
379 Turn an llist of sapi_header_struct headers into a numerically indexed zval hash */
php_head_apply_header_list_to_hash(void * data,void * arg)380 static void php_head_apply_header_list_to_hash(void *data, void *arg)
381 {
382 sapi_header_struct *sapi_header = (sapi_header_struct *)data;
383
384 if (arg && sapi_header) {
385 add_next_index_string((zval *)arg, (char *)(sapi_header->header));
386 }
387 }
388
389 /* {{{ proto array headers_list(void)
390 Return list of headers to be sent / already sent */
PHP_FUNCTION(headers_list)391 PHP_FUNCTION(headers_list)
392 {
393 if (zend_parse_parameters_none() == FAILURE) {
394 return;
395 }
396
397 array_init(return_value);
398 zend_llist_apply_with_argument(&SG(sapi_headers).headers, php_head_apply_header_list_to_hash, return_value);
399 }
400 /* }}} */
401
402 /* {{{ proto int http_response_code([int response_code])
403 Sets a response code, or returns the current HTTP response code */
PHP_FUNCTION(http_response_code)404 PHP_FUNCTION(http_response_code)
405 {
406 zend_long response_code = 0;
407
408 ZEND_PARSE_PARAMETERS_START(0, 1)
409 Z_PARAM_OPTIONAL
410 Z_PARAM_LONG(response_code)
411 ZEND_PARSE_PARAMETERS_END();
412
413 if (response_code)
414 {
415 zend_long old_response_code;
416
417 old_response_code = SG(sapi_headers).http_response_code;
418 SG(sapi_headers).http_response_code = (int)response_code;
419
420 if (old_response_code) {
421 RETURN_LONG(old_response_code);
422 }
423
424 RETURN_TRUE;
425 }
426
427 if (!SG(sapi_headers).http_response_code) {
428 RETURN_FALSE;
429 }
430
431 RETURN_LONG(SG(sapi_headers).http_response_code);
432 }
433 /* }}} */
434