head.c 13 KB

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