]> granicus.if.org Git - php/commitdiff
Promote warnings to errors for set(raw)cookie()
authorGeorge Peter Banyard <girgias@php.net>
Tue, 7 Jul 2020 16:59:05 +0000 (18:59 +0200)
committerGeorge Peter Banyard <girgias@php.net>
Tue, 8 Sep 2020 12:28:49 +0000 (14:28 +0200)
Closes GH-5819

ext/standard/head.c
ext/standard/head.h
ext/standard/tests/network/bug69523.phpt
ext/standard/tests/network/bug69948.phpt
ext/standard/tests/network/setcookie_array_option_error.phpt [new file with mode: 0644]
ext/standard/tests/network/setcookie_error.phpt
ext/standard/tests/network/setrawcookie_error.phpt [new file with mode: 0644]

index cbc7e24a45b3329a402e9c829bfabf765c17bb2d..18cb0c4f62c97fa260b53f57005b6d9dda4b99f1 100644 (file)
@@ -76,36 +76,41 @@ PHPAPI int php_header(void)
        }
 }
 
-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)
+#define ILLEGAL_COOKIE_CHARACTER "\",\", \";\", \" \", \"\\t\", \"\\r\", \"\\n\", \"\\013\", or \"\\014\""
+PHPAPI zend_result php_setcookie(zend_string *name, zend_string *value, time_t expires,
+       zend_string *path, zend_string *domain, bool secure, bool httponly,
+       zend_string *samesite, bool url_encode)
 {
        zend_string *dt;
        sapi_header_line ctr = {0};
-       int result;
+       zend_result result;
        smart_str buf = {0};
 
        if (!ZSTR_LEN(name)) {
-               zend_error( E_WARNING, "Cookie names must not be empty" );
+               zend_argument_value_error(1, "cannot be empty");
                return FAILURE;
-       } else if (strpbrk(ZSTR_VAL(name), "=,; \t\r\n\013\014") != NULL) {   /* man isspace for \013 and \014 */
-               zend_error(E_WARNING, "Cookie names cannot contain any of the following '=,; \\t\\r\\n\\013\\014'" );
+       }
+       if (strpbrk(ZSTR_VAL(name), "=,; \t\r\n\013\014") != NULL) {   /* man isspace for \013 and \014 */
+               zend_argument_value_error(1, "cannot contain \"=\", " ILLEGAL_COOKIE_CHARACTER);
                return FAILURE;
        }
-
        if (!url_encode && value &&
                        strpbrk(ZSTR_VAL(value), ",; \t\r\n\013\014") != NULL) { /* man isspace for \013 and \014 */
-               zend_error(E_WARNING, "Cookie values cannot contain any of the following ',; \\t\\r\\n\\013\\014'" );
+               zend_argument_value_error(2, "cannot contain " ILLEGAL_COOKIE_CHARACTER);
                return FAILURE;
        }
 
        if (path && strpbrk(ZSTR_VAL(path), ",; \t\r\n\013\014") != NULL) { /* man isspace for \013 and \014 */
-               zend_error(E_WARNING, "Cookie paths cannot contain any of the following ',; \\t\\r\\n\\013\\014'" );
+               zend_value_error("%s(): \"path\" option cannot contain " ILLEGAL_COOKIE_CHARACTER,
+                       get_active_function_name());
                return FAILURE;
        }
-
        if (domain && strpbrk(ZSTR_VAL(domain), ",; \t\r\n\013\014") != NULL) { /* man isspace for \013 and \014 */
-               zend_error(E_WARNING, "Cookie domains cannot contain any of the following ',; \\t\\r\\n\\013\\014'" );
+               zend_value_error("%s(): \"domain\" option cannot contain " ILLEGAL_COOKIE_CHARACTER,
+                       get_active_function_name());
                return FAILURE;
        }
+       /* Should check value of SameSite? */
 
        if (value == NULL || ZSTR_LEN(value) == 0) {
                /*
@@ -142,7 +147,8 @@ PHPAPI int php_setcookie(zend_string *name, zend_string *value, time_t expires,
                        if (!p || *(p + 5) != ' ') {
                                zend_string_free(dt);
                                smart_str_free(&buf);
-                               zend_error(E_WARNING, "Expiry date cannot have a year greater than 9999");
+                               zend_value_error("%s(): \"expires\" option cannot have a year greater than 9999",
+                                       get_active_function_name());
                                return FAILURE;
                        }
 
@@ -186,49 +192,40 @@ PHPAPI int php_setcookie(zend_string *name, zend_string *value, time_t expires,
        return result;
 }
 
-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) {
-       int found = 0;
+static zend_result 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)
+{
        zend_string *key;
        zval *value;
 
        ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(options), key, value) {
-               if (key) {
-                       if (zend_string_equals_literal_ci(key, "expires")) {
-                               *expires = zval_get_long(value);
-                               found++;
-                       } else if (zend_string_equals_literal_ci(key, "path")) {
-                               *path = zval_get_string(value);
-                               found++;
-                       } else if (zend_string_equals_literal_ci(key, "domain")) {
-                               *domain = zval_get_string(value);
-                               found++;
-                       } else if (zend_string_equals_literal_ci(key, "secure")) {
-                               *secure = zval_is_true(value);
-                               found++;
-                       } else if (zend_string_equals_literal_ci(key, "httponly")) {
-                               *httponly = zval_is_true(value);
-                               found++;
-                       } else if (zend_string_equals_literal_ci(key, "samesite")) {
-                               *samesite = zval_get_string(value);
-                               found++;
-                       } else {
-                               php_error_docref(NULL, E_WARNING, "Unrecognized key '%s' found in the options array", ZSTR_VAL(key));
-                       }
+               if (!key) {
+                       zend_value_error("%s(): option array cannot have numeric keys", get_active_function_name());
+                       return FAILURE;
+               }
+               if (zend_string_equals_literal_ci(key, "expires")) {
+                       *expires = zval_get_long(value);
+               } else if (zend_string_equals_literal_ci(key, "path")) {
+                       *path = zval_get_string(value);
+               } else if (zend_string_equals_literal_ci(key, "domain")) {
+                       *domain = zval_get_string(value);
+               } else if (zend_string_equals_literal_ci(key, "secure")) {
+                       *secure = zval_is_true(value);
+               } else if (zend_string_equals_literal_ci(key, "httponly")) {
+                       *httponly = zval_is_true(value);
+               } else if (zend_string_equals_literal_ci(key, "samesite")) {
+                       *samesite = zval_get_string(value);
                } else {
-                       php_error_docref(NULL, E_WARNING, "Numeric key found in the options array");
+                       zend_value_error("%s(): option \"%s\" is invalid", get_active_function_name(), ZSTR_VAL(key));
+                       return FAILURE;
                }
        } ZEND_HASH_FOREACH_END();
-
-       /* Array is not empty but no valid keys were found */
-       if (found == 0 && zend_hash_num_elements(Z_ARRVAL_P(options)) > 0) {
-               php_error_docref(NULL, E_WARNING, "No valid options were found in the given array");
-       }
+       return SUCCESS;
 }
 
-/* {{{ setcookie(string name [, string value [, array options]])
-   Send a cookie */
-PHP_FUNCTION(setcookie)
+static void php_setcookie_common(INTERNAL_FUNCTION_PARAMETERS, bool is_raw)
 {
+       /* to handle overloaded function array|int */
        zval *expires_or_options = NULL;
        zend_string *name, *value = NULL, *path = NULL, *domain = NULL, *samesite = NULL;
        zend_long expires = 0;
@@ -248,24 +245,27 @@ PHP_FUNCTION(setcookie)
        if (expires_or_options) {
                if (Z_TYPE_P(expires_or_options) == IS_ARRAY) {
                        if (UNEXPECTED(ZEND_NUM_ARGS() > 3)) {
-                               php_error_docref(NULL, E_WARNING, "Cannot pass arguments after the options array");
-                               RETURN_FALSE;
+                               zend_argument_count_error("%s(): Expects exactly 3 arguments when argument #3 "
+                                       "($expires_or_options) is an array", get_active_function_name());
+                               RETURN_THROWS();
+                       }
+                       if (FAILURE == php_head_parse_cookie_options_array(expires_or_options, &expires, &path,
+                                       &domain, &secure, &httponly, &samesite)) {
+                               goto cleanup;
                        }
-                       php_head_parse_cookie_options_array(expires_or_options, &expires, &path, &domain, &secure, &httponly, &samesite);
                } else {
                        expires = zval_get_long(expires_or_options);
                }
        }
 
-       if (!EG(exception)) {
-               if (php_setcookie(name, value, expires, path, domain, secure, httponly, samesite, 1) == SUCCESS) {
-                       RETVAL_TRUE;
-               } else {
-                       RETVAL_FALSE;
-               }
+       if (php_setcookie(name, value, expires, path, domain, secure, httponly, samesite, !is_raw) == SUCCESS) {
+               RETVAL_TRUE;
+       } else {
+               RETVAL_FALSE;
        }
 
        if (expires_or_options && Z_TYPE_P(expires_or_options) == IS_ARRAY) {
+cleanup:
                if (path) {
                        zend_string_release(path);
                }
@@ -277,59 +277,20 @@ PHP_FUNCTION(setcookie)
                }
        }
 }
+
+/* {{{ setcookie(string name [, string value [, array options]])
+   Send a cookie */
+PHP_FUNCTION(setcookie)
+{
+       php_setcookie_common(INTERNAL_FUNCTION_PARAM_PASSTHRU, false);
+}
 /* }}} */
 
 /* {{{ setrawcookie(string name [, string value [, array options]])
    Send a cookie with no url encoding of the value */
 PHP_FUNCTION(setrawcookie)
 {
-       zval *expires_or_options = NULL;
-       zend_string *name, *value = NULL, *path = NULL, *domain = NULL, *samesite = NULL;
-       zend_long expires = 0;
-       zend_bool secure = 0, httponly = 0;
-
-       ZEND_PARSE_PARAMETERS_START(1, 7)
-               Z_PARAM_STR(name)
-               Z_PARAM_OPTIONAL
-               Z_PARAM_STR(value)
-               Z_PARAM_ZVAL(expires_or_options)
-               Z_PARAM_STR(path)
-               Z_PARAM_STR(domain)
-               Z_PARAM_BOOL(secure)
-               Z_PARAM_BOOL(httponly)
-       ZEND_PARSE_PARAMETERS_END();
-
-       if (expires_or_options) {
-               if (Z_TYPE_P(expires_or_options) == IS_ARRAY) {
-                       if (UNEXPECTED(ZEND_NUM_ARGS() > 3)) {
-                               php_error_docref(NULL, E_WARNING, "Cannot pass arguments after the options array");
-                               RETURN_FALSE;
-                       }
-                       php_head_parse_cookie_options_array(expires_or_options, &expires, &path, &domain, &secure, &httponly, &samesite);
-               } else {
-                       expires = zval_get_long(expires_or_options);
-               }
-       }
-
-       if (!EG(exception)) {
-               if (php_setcookie(name, value, expires, path, domain, secure, httponly, samesite, 0) == SUCCESS) {
-                       RETVAL_TRUE;
-               } else {
-                       RETVAL_FALSE;
-               }
-       }
-
-       if (expires_or_options && Z_TYPE_P(expires_or_options) == IS_ARRAY) {
-               if (path) {
-                       zend_string_release(path);
-               }
-               if (domain) {
-                       zend_string_release(domain);
-               }
-               if (samesite) {
-                       zend_string_release(samesite);
-               }
-       }
+       php_setcookie_common(INTERNAL_FUNCTION_PARAM_PASSTHRU, true);
 }
 /* }}} */
 
index a972ebcf3d5df2da785cdced477a231889c926f7..6f44dbfe9f7b53979082fb092e95f2bbe6215f53 100644 (file)
@@ -28,6 +28,8 @@
 extern PHP_RINIT_FUNCTION(head);
 
 PHPAPI int php_header(void);
-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);
+PHPAPI zend_result php_setcookie(zend_string *name, zend_string *value, time_t expires,
+       zend_string *path, zend_string *domain, bool secure, bool httponly,
+       zend_string *samesite, bool url_encode);
 
 #endif
index 979ae00d179a76c466a571e3e9953d89b7c4b78f..60f3643044c600875f268bb6bf0681617866af5d 100644 (file)
@@ -2,7 +2,11 @@
 setcookie() allows empty cookie name
 --FILE--
 <?php
-setcookie('', 'foo');
+try {
+    setcookie('', 'foo');
+} catch (\ValueError $e) {
+    echo $e->getMessage() . \PHP_EOL;
+}
 ?>
---EXPECTF--
-Warning: Cookie names must not be empty in %s on line %d
+--EXPECT--
+setcookie(): Argument #1 ($name) cannot be empty
index 957d72f99d23ae3a0dcf946226eeb333a302e367..c2a72aac8720860d537de56fa98552df296dba31 100644 (file)
@@ -2,17 +2,21 @@
 Bug #69948 (path/domain are not sanitized for special characters in setcookie)
 --FILE--
 <?php
-var_dump(
-    setcookie('foo', 'bar', 0, 'asdf;asdf'),
-    setcookie('foo', 'bar', 0, '/', 'foobar; secure')
-);
+try {
+    var_dump(setcookie('foo', 'bar', 0, 'asdf;asdf'));
+} catch (\ValueError $e) {
+    echo $e->getMessage() . \PHP_EOL;
+}
+try {
+    var_dump(setcookie('foo', 'bar', 0, '/', 'foobar; secure'));
+} catch (\ValueError $e) {
+    echo $e->getMessage() . \PHP_EOL;
+}
+
 ?>
 ===DONE===
 --EXPECTHEADERS--
---EXPECTF--
-Warning: Cookie paths cannot contain any of the following ',; \t\r\n\013\014' in %s on line %d
-
-Warning: Cookie domains cannot contain any of the following ',; \t\r\n\013\014' in %s on line %d
-bool(false)
-bool(false)
+--EXPECT--
+setcookie(): "path" option cannot contain ",", ";", " ", "\t", "\r", "\n", "\013", or "\014"
+setcookie(): "domain" option cannot contain ",", ";", " ", "\t", "\r", "\n", "\013", or "\014"
 ===DONE===
diff --git a/ext/standard/tests/network/setcookie_array_option_error.phpt b/ext/standard/tests/network/setcookie_array_option_error.phpt
new file mode 100644 (file)
index 0000000..545e93e
--- /dev/null
@@ -0,0 +1,69 @@
+--TEST--
+setcookie() array variant error tests
+--INI--
+date.timezone=UTC
+--FILE--
+<?php
+
+ob_start();
+
+// Unrecognized key and no valid keys
+try {
+    setcookie('name', 'value', ['unknown_key' => 'only']);
+} catch (\ValueError $e) {
+    echo $e->getMessage() . "\n";
+}
+// Numeric key and no valid keys
+try {
+    setcookie('name2', 'value2', [0 => 'numeric_key']);
+} catch (\ValueError $e) {
+    echo $e->getMessage() . "\n";
+}
+// Unrecognized key
+try {
+    setcookie('name3', 'value3', ['path' => '/path/', 'foo' => 'bar']);
+} catch (\ValueError $e) {
+    echo $e->getMessage() . "\n";
+}
+// Invalid expiration date
+// To go above year 9999: 60 * 60 * 24 * 365 * 9999
+try {
+    setcookie('name', 'value', ['expires' => 315328464000]);
+} catch (\ValueError $e) {
+    echo $e->getMessage() . "\n";
+}
+// Invalid path key content
+try {
+    setcookie('name', 'value', ['path' => '/;/']);
+} catch (\ValueError $e) {
+    echo $e->getMessage() . "\n";
+}
+// Invalid domain key content
+try {
+    setcookie('name', 'value', ['path' => '/path/', 'domain' => 'ba;r']);
+} catch (\ValueError $e) {
+    echo $e->getMessage() . "\n";
+}
+
+// Arguments after options array (will not be set)
+try {
+    setcookie('name4', 'value4', [], "path", "domain.tld", true, true);
+} catch (\ArgumentCountError $e) {
+    echo $e->getMessage() . "\n";
+}
+
+var_dump(headers_list());
+--EXPECTHEADERS--
+
+--EXPECTF--
+setcookie(): option "unknown_key" is invalid
+setcookie(): option array cannot have numeric keys
+setcookie(): option "foo" is invalid
+setcookie(): "expires" option cannot have a year greater than 9999
+setcookie(): "path" option cannot contain ",", ";", " ", "\t", "\r", "\n", "\013", or "\014"
+setcookie(): "domain" option cannot contain ",", ";", " ", "\t", "\r", "\n", "\013", or "\014"
+setcookie(): Expects exactly 3 arguments when argument #3 ($expires_or_options) is an array
+array(1) {
+  [0]=>
+  string(%s) "X-Powered-By: PHP/%s"
+}
index 98fb64b8512f7b504a34cde286a024d2fa76433a..12b0f25ceb09e15ef99188bc2b36fb1fea8ca3b0 100644 (file)
@@ -1,5 +1,5 @@
 --TEST--
-setcookie() array variant error tests
+setcookie() error tests
 --INI--
 date.timezone=UTC
 --FILE--
@@ -7,37 +7,50 @@ date.timezone=UTC
 
 ob_start();
 
-// Unrecognized key and no valid keys
-setcookie('name', 'value', ['unknown_key' => 'only']);
-// Numeric key and no valid keys
-setcookie('name2', 'value2', [0 => 'numeric_key']);
-// Unrecognized key
-setcookie('name3', 'value3', ['path' => '/path/', 'foo' => 'bar']);
-// Arguments after options array (will not be set)
-setcookie('name4', 'value4', [], "path", "domain.tld", true, true);
+try {
+    setcookie('');
+} catch (\ValueError $e) {
+    echo $e->getMessage() . "\n";
+}
+try {
+    setcookie('invalid=');
+} catch (\ValueError $e) {
+    echo $e->getMessage() . "\n";
+}
+try {
+    setcookie('name', 'invalid;');
+} catch (\ValueError $e) {
+    echo $e->getMessage() . "\n";
+}
+// To go above year 9999: 60 * 60 * 24 * 365 * 9999
+try {
+    setcookie('name', 'value', 315328464000);
+} catch (\ValueError $e) {
+    echo $e->getMessage() . "\n";
+}
+try {
+    setcookie('name', 'value', 100, 'invalid;');
+} catch (\ValueError $e) {
+    echo $e->getMessage() . "\n";
+}
+try {
+    setcookie('name', 'value', 100, 'path', 'invalid;');
+} catch (\ValueError $e) {
+    echo $e->getMessage() . "\n";
+}
 
 var_dump(headers_list());
 --EXPECTHEADERS--
 
 --EXPECTF--
-Warning: setcookie(): Unrecognized key 'unknown_key' found in the options array in %s
-
-Warning: setcookie(): No valid options were found in the given array in %s
-
-Warning: setcookie(): Numeric key found in the options array in %s
-
-Warning: setcookie(): No valid options were found in the given array in %s
-
-Warning: setcookie(): Unrecognized key 'foo' found in the options array in %s
-
-Warning: setcookie(): Cannot pass arguments after the options array in %s
-array(4) {
+setcookie(): Argument #1 ($name) cannot be empty
+setcookie(): Argument #1 ($name) cannot contain "=", ",", ";", " ", "\t", "\r", "\n", "\013", or "\014"
+setcookie(): "expires" option cannot have a year greater than 9999
+setcookie(): "path" option cannot contain ",", ";", " ", "\t", "\r", "\n", "\013", or "\014"
+setcookie(): "domain" option cannot contain ",", ";", " ", "\t", "\r", "\n", "\013", or "\014"
+array(2) {
   [0]=>
   string(%d) "X-Powered-By: PHP/%s"
   [1]=>
-  string(22) "Set-Cookie: name=value"
-  [2]=>
-  string(24) "Set-Cookie: name2=value2"
-  [3]=>
-  string(37) "Set-Cookie: name3=value3; path=/path/"
+  string(27) "Set-Cookie: name=invalid%3B"
 }
diff --git a/ext/standard/tests/network/setrawcookie_error.phpt b/ext/standard/tests/network/setrawcookie_error.phpt
new file mode 100644 (file)
index 0000000..3950430
--- /dev/null
@@ -0,0 +1,55 @@
+--TEST--
+setrawcookie() error tests
+--INI--
+date.timezone=UTC
+--FILE--
+<?php
+
+ob_start();
+
+try {
+    setrawcookie('');
+} catch (\ValueError $e) {
+    echo $e->getMessage() . "\n";
+}
+try {
+    setrawcookie('invalid=');
+} catch (\ValueError $e) {
+    echo $e->getMessage() . "\n";
+}
+try {
+    setrawcookie('name', 'invalid;');
+} catch (\ValueError $e) {
+    echo $e->getMessage() . "\n";
+}
+// To go above year 9999: 60 * 60 * 24 * 365 * 9999
+try {
+    setrawcookie('name', 'value', 315328464000);
+} catch (\ValueError $e) {
+    echo $e->getMessage() . "\n";
+}
+try {
+    setrawcookie('name', 'value', 100, 'invalid;');
+} catch (\ValueError $e) {
+    echo $e->getMessage() . "\n";
+}
+try {
+    setrawcookie('name', 'value', 100, 'path', 'invalid;');
+} catch (\ValueError $e) {
+    echo $e->getMessage() . "\n";
+}
+
+var_dump(headers_list());
+--EXPECTHEADERS--
+
+--EXPECTF--
+setrawcookie(): Argument #1 ($name) cannot be empty
+setrawcookie(): Argument #1 ($name) cannot contain "=", ",", ";", " ", "\t", "\r", "\n", "\013", or "\014"
+setrawcookie(): Argument #2 ($value) cannot contain ",", ";", " ", "\t", "\r", "\n", "\013", or "\014"
+setrawcookie(): "expires" option cannot have a year greater than 9999
+setrawcookie(): "path" option cannot contain ",", ";", " ", "\t", "\r", "\n", "\013", or "\014"
+setrawcookie(): "domain" option cannot contain ",", ";", " ", "\t", "\r", "\n", "\013", or "\014"
+array(1) {
+  [0]=>
+  string(%d) "X-Powered-By: PHP/%s"
+}