1 #include "php_snuffleupagus.h"
2
sp_lookup_cookie_config(const zend_string * key)3 static inline const sp_cookie *sp_lookup_cookie_config(const zend_string *key) {
4 const sp_list_node *it = SNUFFLEUPAGUS_G(config).config_cookie->cookies;
5
6 while (it) {
7 const sp_cookie *config = it->data;
8 if (config && sp_match_value(key, config->name, config->name_r)) {
9 return config;
10 }
11 it = it->next;
12 }
13 return NULL;
14 }
15
16 /* called at RINIT time with each cookie, eventually decrypt said cookie */
decrypt_cookie(zval * pDest,int num_args,va_list args,zend_hash_key * hash_key)17 int decrypt_cookie(zval *pDest, int num_args, va_list args,
18 zend_hash_key *hash_key) {
19 const sp_cookie *cookie = sp_lookup_cookie_config(hash_key->key);
20
21 /* If the cookie isn't in the conf, it shouldn't be encrypted. */
22 if (!cookie || !cookie->encrypt) {
23 return ZEND_HASH_APPLY_KEEP;
24 }
25
26 /* If the cookie has no value, it shouldn't be encrypted. */
27 if (0 == Z_STRLEN_P(pDest)) {
28 return ZEND_HASH_APPLY_KEEP;
29 }
30
31 return decrypt_zval(pDest, cookie->simulation, hash_key);
32 }
33
encrypt_data(zend_string * data)34 static zend_string *encrypt_data(zend_string *data) {
35 zend_string *z = encrypt_zval(data);
36 sp_log_debug("cookie_encryption", "Cookie value:%s:", ZSTR_VAL(z));
37 return z;
38 }
39
40 #if PHP_VERSION_ID >= 70300
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)41 static void php_head_parse_cookie_options_array(
42 zval *options, zend_long *expires, zend_string **path, zend_string **domain,
43 zend_bool *secure, zend_bool *httponly, zend_string **samesite) {
44 int found = 0;
45 zend_string *key;
46 zval *value;
47
48 ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(options), key, value) {
49 if (key) {
50 if (zend_string_equals_literal_ci(key, "expires")) {
51 *expires = zval_get_long(value);
52 found++;
53 } else if (zend_string_equals_literal_ci(key, "path")) {
54 *path = zval_get_string(value);
55 found++;
56 } else if (zend_string_equals_literal_ci(key, "domain")) {
57 *domain = zval_get_string(value);
58 found++;
59 } else if (zend_string_equals_literal_ci(key, "secure")) {
60 *secure = zval_is_true(value);
61 found++;
62 } else if (zend_string_equals_literal_ci(key, "httponly")) {
63 *httponly = zval_is_true(value);
64 found++;
65 } else if (zend_string_equals_literal_ci(key, "samesite")) {
66 *samesite = zval_get_string(value);
67 found++;
68 } else {
69 php_error_docref(NULL, E_WARNING,
70 "Unrecognized key '%s' found in the options array",
71 ZSTR_VAL(key));
72 }
73 } else {
74 php_error_docref(NULL, E_WARNING,
75 "Numeric key found in the options array");
76 }
77 }
78 ZEND_HASH_FOREACH_END();
79
80 /* Array is not empty but no valid keys were found */
81 if (found == 0 && zend_hash_num_elements(Z_ARRVAL_P(options)) > 0) {
82 php_error_docref(NULL, E_WARNING,
83 "No valid options were found in the given array");
84 }
85 }
86 #endif
87
PHP_FUNCTION(sp_setcookie)88 PHP_FUNCTION(sp_setcookie) {
89 zend_string *name = NULL, *value = NULL, *path = NULL, *domain = NULL,
90 *value_enc = NULL,
91 #if PHP_VERSION_ID < 70300
92 *path_samesite = NULL;
93 #else
94 *samesite = NULL;
95 #endif
96
97 zend_long expires = 0;
98 zval *expires_or_options = NULL;
99 zend_bool secure = 0, httponly = 0;
100 const sp_cookie *cookie_node = NULL;
101 char *cookie_samesite;
102
103 // LCOV_EXCL_BR_START
104 ZEND_PARSE_PARAMETERS_START(1, 7)
105 Z_PARAM_STR(name)
106 Z_PARAM_OPTIONAL
107 Z_PARAM_STR(value)
108 Z_PARAM_ZVAL(expires_or_options)
109 Z_PARAM_STR(path)
110 Z_PARAM_STR(domain)
111 Z_PARAM_BOOL(secure)
112 Z_PARAM_BOOL(httponly)
113 ZEND_PARSE_PARAMETERS_END();
114 // LCOV_EXCL_BR_END
115
116 if (expires_or_options) {
117 #if PHP_VERSION_ID < 70300
118 expires = zval_get_long(expires_or_options);
119 #else
120 if (Z_TYPE_P(expires_or_options) == IS_ARRAY) {
121 if (UNEXPECTED(ZEND_NUM_ARGS() > 3)) {
122 php_error_docref(NULL, E_WARNING,
123 "Cannot pass arguments after the options array");
124 RETURN_FALSE;
125 }
126 php_head_parse_cookie_options_array(expires_or_options, &expires, &path,
127 &domain, &secure, &httponly,
128 &samesite);
129 } else {
130 expires = zval_get_long(expires_or_options);
131 }
132 #endif
133 }
134
135 /* If the request was issued over HTTPS, the cookie should be "secure" */
136 if (SNUFFLEUPAGUS_G(config).config_auto_cookie_secure) {
137 const zval server_vars = PG(http_globals)[TRACK_VARS_SERVER];
138 if (Z_TYPE(server_vars) == IS_ARRAY) {
139 const zval *is_https =
140 zend_hash_str_find(Z_ARRVAL(server_vars), "HTTPS", strlen("HTTPS"));
141 if (NULL != is_https) {
142 secure = 1;
143 }
144 }
145 }
146
147 /* lookup existing configuration for said cookie */
148 cookie_node = sp_lookup_cookie_config(name);
149
150 /* If the cookie's value is encrypted, it won't be usable by
151 * javascript anyway.
152 */
153 if (cookie_node && cookie_node->encrypt) {
154 httponly = 1;
155 }
156
157 /* Shall we encrypt the cookie's value? */
158 if (cookie_node && cookie_node->encrypt && value) {
159 value_enc = encrypt_data(value);
160 }
161
162 if (cookie_node && cookie_node->samesite) {
163 if (!path) {
164 path = zend_string_init("", 0, 0);
165 }
166 #if PHP_VERSION_ID < 70300
167 cookie_samesite = (cookie_node->samesite == lax)
168 ? SAMESITE_COOKIE_FORMAT SP_TOKEN_SAMESITE_LAX
169 : SAMESITE_COOKIE_FORMAT SP_TOKEN_SAMESITE_STRICT;
170
171 /* Concatenating everything, as is in PHP internals */
172 path_samesite = zend_string_init(ZSTR_VAL(path), ZSTR_LEN(path), 0);
173 path_samesite = zend_string_extend(
174 path_samesite, ZSTR_LEN(path) + strlen(cookie_samesite) + 1, 0);
175 memcpy(ZSTR_VAL(path_samesite) + ZSTR_LEN(path), cookie_samesite,
176 strlen(cookie_samesite) + 1);
177 #else
178 cookie_samesite = (cookie_node->samesite == lax) ? SP_TOKEN_SAMESITE_LAX
179 : SP_TOKEN_SAMESITE_STRICT;
180
181 samesite = zend_string_init(cookie_samesite, strlen(cookie_samesite), 0);
182 #endif
183 }
184
185 #if PHP_VERSION_ID < 70300
186 if (php_setcookie(name, (value_enc ? value_enc : value), expires,
187 (path_samesite ? path_samesite : path), domain, secure, 1,
188 httponly) == SUCCESS) {
189 #else
190 if (php_setcookie(name, (value_enc ? value_enc : value), expires, path,
191 domain, secure, httponly, samesite, 1) == SUCCESS) {
192 #endif
193 RETVAL_TRUE;
194 } else {
195 RETVAL_FALSE;
196 }
197
198 if (value_enc) {
199 zend_string_release(value_enc);
200 }
201 #if PHP_VERSION_ID < 70300
202 if (path_samesite) {
203 zend_string_release(path_samesite);
204 }
205 #endif
206 }
207
208 int hook_cookies() {
209 HOOK_FUNCTION("setcookie", sp_internal_functions_hook, PHP_FN(sp_setcookie));
210
211 return SUCCESS;
212 }
213