frankenphp.c 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263
  1. #include <SAPI.h>
  2. #include <Zend/zend_alloc.h>
  3. #include <Zend/zend_exceptions.h>
  4. #include <Zend/zend_interfaces.h>
  5. #include <Zend/zend_types.h>
  6. #include <errno.h>
  7. #include <ext/spl/spl_exceptions.h>
  8. #include <ext/standard/head.h>
  9. #include <inttypes.h>
  10. #include <php.h>
  11. #include <php_config.h>
  12. #include <php_ini.h>
  13. #include <php_main.h>
  14. #include <php_output.h>
  15. #include <php_variables.h>
  16. #include <pthread.h>
  17. #include <sapi/embed/php_embed.h>
  18. #include <signal.h>
  19. #include <stdint.h>
  20. #include <stdio.h>
  21. #include <stdlib.h>
  22. #include <unistd.h>
  23. #if defined(__linux__)
  24. #include <sys/prctl.h>
  25. #elif defined(__FreeBSD__) || defined(__OpenBSD__)
  26. #include <pthread_np.h>
  27. #endif
  28. #include "_cgo_export.h"
  29. #include "frankenphp_arginfo.h"
  30. #if defined(PHP_WIN32) && defined(ZTS)
  31. ZEND_TSRMLS_CACHE_DEFINE()
  32. #endif
  33. /* Timeouts are currently fundamentally broken with ZTS except on Linux and
  34. * FreeBSD: https://bugs.php.net/bug.php?id=79464 */
  35. #ifndef ZEND_MAX_EXECUTION_TIMERS
  36. static const char HARDCODED_INI[] = "max_execution_time=0\n"
  37. "max_input_time=-1\n\0";
  38. #endif
  39. static const char *MODULES_TO_RELOAD[] = {"filter", "session", NULL};
  40. frankenphp_version frankenphp_get_version() {
  41. return (frankenphp_version){
  42. PHP_MAJOR_VERSION, PHP_MINOR_VERSION, PHP_RELEASE_VERSION,
  43. PHP_EXTRA_VERSION, PHP_VERSION, PHP_VERSION_ID,
  44. };
  45. }
  46. frankenphp_config frankenphp_get_config() {
  47. return (frankenphp_config){
  48. frankenphp_get_version(),
  49. #ifdef ZTS
  50. true,
  51. #else
  52. false,
  53. #endif
  54. #ifdef ZEND_SIGNALS
  55. true,
  56. #else
  57. false,
  58. #endif
  59. #ifdef ZEND_MAX_EXECUTION_TIMERS
  60. true,
  61. #else
  62. false,
  63. #endif
  64. };
  65. }
  66. typedef struct frankenphp_server_context {
  67. bool has_main_request;
  68. bool has_active_request;
  69. bool worker_ready;
  70. char *cookie_data;
  71. bool finished;
  72. } frankenphp_server_context;
  73. bool should_filter_var = 0;
  74. __thread frankenphp_server_context *local_ctx = NULL;
  75. __thread uintptr_t thread_index;
  76. __thread zval *os_environment = NULL;
  77. static void frankenphp_free_request_context() {
  78. frankenphp_server_context *ctx = SG(server_context);
  79. free(ctx->cookie_data);
  80. ctx->cookie_data = NULL;
  81. /* Is freed via thread.Unpin() */
  82. SG(request_info).auth_password = NULL;
  83. SG(request_info).auth_user = NULL;
  84. SG(request_info).request_method = NULL;
  85. SG(request_info).query_string = NULL;
  86. SG(request_info).content_type = NULL;
  87. SG(request_info).path_translated = NULL;
  88. SG(request_info).request_uri = NULL;
  89. }
  90. static void frankenphp_destroy_super_globals() {
  91. zend_try {
  92. for (int i = 0; i < NUM_TRACK_VARS; i++) {
  93. zval_ptr_dtor_nogc(&PG(http_globals)[i]);
  94. }
  95. }
  96. zend_end_try();
  97. }
  98. /*
  99. * free php_stream resources that are temporary (php_stream_temp_ops)
  100. * streams are globally registered in EG(regular_list), see zend_list.c
  101. * this fixes a leak when reading the body of a request
  102. */
  103. static void frankenphp_release_temporary_streams() {
  104. zend_resource *val;
  105. int stream_type = php_file_le_stream();
  106. ZEND_HASH_FOREACH_PTR(&EG(regular_list), val) {
  107. /* verify the resource is a stream */
  108. if (val->type == stream_type) {
  109. php_stream *stream = (php_stream *)val->ptr;
  110. if (stream != NULL && stream->ops == &php_stream_temp_ops &&
  111. stream->__exposed == 0 && GC_REFCOUNT(val) == 1) {
  112. ZEND_ASSERT(!stream->is_persistent);
  113. zend_list_delete(val);
  114. }
  115. }
  116. }
  117. ZEND_HASH_FOREACH_END();
  118. }
  119. /* Adapted from php_request_shutdown */
  120. static void frankenphp_worker_request_shutdown() {
  121. /* Flush all output buffers */
  122. zend_try { php_output_end_all(); }
  123. zend_end_try();
  124. /* TODO: store the list of modules to reload in a global module variable */
  125. const char **module_name;
  126. zend_module_entry *module;
  127. for (module_name = MODULES_TO_RELOAD; *module_name; module_name++) {
  128. if ((module = zend_hash_str_find_ptr(&module_registry, *module_name,
  129. strlen(*module_name)))) {
  130. module->request_shutdown_func(module->type, module->module_number);
  131. }
  132. }
  133. /* Shutdown output layer (send the set HTTP headers, cleanup output handlers,
  134. * etc.) */
  135. zend_try { php_output_deactivate(); }
  136. zend_end_try();
  137. /* SAPI related shutdown (free stuff) */
  138. frankenphp_free_request_context();
  139. zend_try { sapi_deactivate(); }
  140. zend_end_try();
  141. zend_set_memory_limit(PG(memory_limit));
  142. }
  143. PHPAPI void get_full_env(zval *track_vars_array) {
  144. struct go_getfullenv_return full_env = go_getfullenv(thread_index);
  145. for (int i = 0; i < full_env.r1; i++) {
  146. go_string key = full_env.r0[i * 2];
  147. go_string val = full_env.r0[i * 2 + 1];
  148. // create PHP string for the value
  149. zend_string *val_str = zend_string_init(val.data, val.len, 0);
  150. // add to the associative array
  151. add_assoc_str_ex(track_vars_array, key.data, key.len, val_str);
  152. }
  153. }
  154. /* Adapted from php_request_startup() */
  155. static int frankenphp_worker_request_startup() {
  156. int retval = SUCCESS;
  157. zend_try {
  158. frankenphp_destroy_super_globals();
  159. frankenphp_release_temporary_streams();
  160. php_output_activate();
  161. /* initialize global variables */
  162. PG(header_is_being_sent) = 0;
  163. PG(connection_status) = PHP_CONNECTION_NORMAL;
  164. /* Keep the current execution context */
  165. sapi_activate();
  166. #ifdef ZEND_MAX_EXECUTION_TIMERS
  167. if (PG(max_input_time) == -1) {
  168. zend_set_timeout(EG(timeout_seconds), 1);
  169. } else {
  170. zend_set_timeout(PG(max_input_time), 1);
  171. }
  172. #endif
  173. if (PG(expose_php)) {
  174. sapi_add_header(SAPI_PHP_VERSION_HEADER,
  175. sizeof(SAPI_PHP_VERSION_HEADER) - 1, 1);
  176. }
  177. if (PG(output_handler) && PG(output_handler)[0]) {
  178. zval oh;
  179. ZVAL_STRING(&oh, PG(output_handler));
  180. php_output_start_user(&oh, 0, PHP_OUTPUT_HANDLER_STDFLAGS);
  181. zval_ptr_dtor(&oh);
  182. } else if (PG(output_buffering)) {
  183. php_output_start_user(NULL,
  184. PG(output_buffering) > 1 ? PG(output_buffering) : 0,
  185. PHP_OUTPUT_HANDLER_STDFLAGS);
  186. } else if (PG(implicit_flush)) {
  187. php_output_set_implicit_flush(1);
  188. }
  189. php_hash_environment();
  190. zend_is_auto_global(ZSTR_KNOWN(ZEND_STR_AUTOGLOBAL_SERVER));
  191. /* Unfinish the request */
  192. frankenphp_server_context *ctx = SG(server_context);
  193. ctx->finished = false;
  194. /* TODO: store the list of modules to reload in a global module variable */
  195. const char **module_name;
  196. zend_module_entry *module;
  197. for (module_name = MODULES_TO_RELOAD; *module_name; module_name++) {
  198. if ((module = zend_hash_str_find_ptr(&module_registry, *module_name,
  199. sizeof(*module_name) - 1)) &&
  200. module->request_startup_func) {
  201. module->request_startup_func(module->type, module->module_number);
  202. }
  203. }
  204. }
  205. zend_catch { retval = FAILURE; }
  206. zend_end_try();
  207. SG(sapi_started) = 1;
  208. return retval;
  209. }
  210. PHP_FUNCTION(frankenphp_finish_request) { /* {{{ */
  211. if (zend_parse_parameters_none() == FAILURE) {
  212. RETURN_THROWS();
  213. }
  214. frankenphp_server_context *ctx = SG(server_context);
  215. if (ctx->finished) {
  216. RETURN_FALSE;
  217. }
  218. php_output_end_all();
  219. php_header();
  220. if (ctx->has_active_request) {
  221. go_frankenphp_finish_php_request(thread_index);
  222. }
  223. ctx->finished = true;
  224. RETURN_TRUE;
  225. } /* }}} */
  226. /* {{{ Call go's putenv to prevent race conditions */
  227. PHP_FUNCTION(frankenphp_putenv) {
  228. char *setting;
  229. size_t setting_len;
  230. ZEND_PARSE_PARAMETERS_START(1, 1)
  231. Z_PARAM_STRING(setting, setting_len)
  232. ZEND_PARSE_PARAMETERS_END();
  233. // Cast str_len to int (ensure it fits in an int)
  234. if (setting_len > INT_MAX) {
  235. php_error(E_WARNING, "String length exceeds maximum integer value");
  236. RETURN_FALSE;
  237. }
  238. if (go_putenv(setting, (int)setting_len)) {
  239. RETURN_TRUE;
  240. } else {
  241. RETURN_FALSE;
  242. }
  243. } /* }}} */
  244. /* {{{ Call go's getenv to prevent race conditions */
  245. PHP_FUNCTION(frankenphp_getenv) {
  246. char *name = NULL;
  247. size_t name_len = 0;
  248. bool local_only = 0;
  249. ZEND_PARSE_PARAMETERS_START(0, 2)
  250. Z_PARAM_OPTIONAL
  251. Z_PARAM_STRING_OR_NULL(name, name_len)
  252. Z_PARAM_BOOL(local_only)
  253. ZEND_PARSE_PARAMETERS_END();
  254. if (!name) {
  255. array_init(return_value);
  256. get_full_env(return_value);
  257. return;
  258. }
  259. go_string gname = {name_len, name};
  260. struct go_getenv_return result = go_getenv(thread_index, &gname);
  261. if (result.r0) {
  262. // Return the single environment variable as a string
  263. RETVAL_STRINGL(result.r1->data, result.r1->len);
  264. } else {
  265. // Environment variable does not exist
  266. RETVAL_FALSE;
  267. }
  268. } /* }}} */
  269. /* {{{ Fetch all HTTP request headers */
  270. PHP_FUNCTION(frankenphp_request_headers) {
  271. if (zend_parse_parameters_none() == FAILURE) {
  272. RETURN_THROWS();
  273. }
  274. frankenphp_server_context *ctx = SG(server_context);
  275. struct go_apache_request_headers_return headers =
  276. go_apache_request_headers(thread_index, ctx->has_active_request);
  277. array_init_size(return_value, headers.r1);
  278. for (size_t i = 0; i < headers.r1; i++) {
  279. go_string key = headers.r0[i * 2];
  280. go_string val = headers.r0[i * 2 + 1];
  281. add_assoc_stringl_ex(return_value, key.data, key.len, val.data, val.len);
  282. }
  283. }
  284. /* }}} */
  285. /* add_response_header and apache_response_headers are copied from
  286. * https://github.com/php/php-src/blob/master/sapi/cli/php_cli_server.c
  287. * Copyright (c) The PHP Group
  288. * Licensed under The PHP License
  289. * Original authors: Moriyoshi Koizumi <moriyoshi@php.net> and Xinchen Hui
  290. * <laruence@php.net>
  291. */
  292. static void add_response_header(sapi_header_struct *h,
  293. zval *return_value) /* {{{ */
  294. {
  295. if (h->header_len > 0) {
  296. char *s;
  297. size_t len = 0;
  298. ALLOCA_FLAG(use_heap)
  299. char *p = strchr(h->header, ':');
  300. if (NULL != p) {
  301. len = p - h->header;
  302. }
  303. if (len > 0) {
  304. while (len != 0 &&
  305. (h->header[len - 1] == ' ' || h->header[len - 1] == '\t')) {
  306. len--;
  307. }
  308. if (len) {
  309. s = do_alloca(len + 1, use_heap);
  310. memcpy(s, h->header, len);
  311. s[len] = 0;
  312. do {
  313. p++;
  314. } while (*p == ' ' || *p == '\t');
  315. add_assoc_stringl_ex(return_value, s, len, p,
  316. h->header_len - (p - h->header));
  317. free_alloca(s, use_heap);
  318. }
  319. }
  320. }
  321. }
  322. /* }}} */
  323. PHP_FUNCTION(frankenphp_response_headers) /* {{{ */
  324. {
  325. if (zend_parse_parameters_none() == FAILURE) {
  326. RETURN_THROWS();
  327. }
  328. array_init(return_value);
  329. zend_llist_apply_with_argument(
  330. &SG(sapi_headers).headers,
  331. (llist_apply_with_arg_func_t)add_response_header, return_value);
  332. }
  333. /* }}} */
  334. PHP_FUNCTION(frankenphp_handle_request) {
  335. zend_fcall_info fci;
  336. zend_fcall_info_cache fcc;
  337. ZEND_PARSE_PARAMETERS_START(1, 1)
  338. Z_PARAM_FUNC(fci, fcc)
  339. ZEND_PARSE_PARAMETERS_END();
  340. frankenphp_server_context *ctx = SG(server_context);
  341. if (!ctx->has_main_request) {
  342. /* not a worker, throw an error */
  343. zend_throw_exception(
  344. spl_ce_RuntimeException,
  345. "frankenphp_handle_request() called while not in worker mode", 0);
  346. RETURN_THROWS();
  347. }
  348. if (!ctx->worker_ready) {
  349. /* Clean the first dummy request created to initialize the worker */
  350. frankenphp_worker_request_shutdown();
  351. ctx->worker_ready = true;
  352. }
  353. #ifdef ZEND_MAX_EXECUTION_TIMERS
  354. /* Disable timeouts while waiting for a request to handle */
  355. zend_unset_timeout();
  356. #endif
  357. bool request = go_frankenphp_worker_handle_request_start(thread_index);
  358. if (frankenphp_worker_request_startup() == FAILURE
  359. /* Shutting down */
  360. || !request) {
  361. RETURN_FALSE;
  362. }
  363. #ifdef ZEND_MAX_EXECUTION_TIMERS
  364. /*
  365. * Reset default timeout
  366. */
  367. if (PG(max_input_time) != -1) {
  368. zend_set_timeout(INI_INT("max_execution_time"), 0);
  369. }
  370. #endif
  371. /* Call the PHP func */
  372. zval retval = {0};
  373. fci.size = sizeof fci;
  374. fci.retval = &retval;
  375. if (zend_call_function(&fci, &fcc) == SUCCESS) {
  376. zval_ptr_dtor(&retval);
  377. }
  378. /*
  379. * If an exception occurred, print the message to the client before
  380. * closing the connection
  381. */
  382. if (EG(exception)) {
  383. zend_exception_error(EG(exception), E_ERROR);
  384. }
  385. frankenphp_worker_request_shutdown();
  386. ctx->has_active_request = false;
  387. go_frankenphp_finish_worker_request(thread_index);
  388. RETURN_TRUE;
  389. }
  390. PHP_FUNCTION(headers_send) {
  391. zend_long response_code = 200;
  392. ZEND_PARSE_PARAMETERS_START(0, 1)
  393. Z_PARAM_OPTIONAL
  394. Z_PARAM_LONG(response_code)
  395. ZEND_PARSE_PARAMETERS_END();
  396. int previous_status_code = SG(sapi_headers).http_response_code;
  397. SG(sapi_headers).http_response_code = response_code;
  398. if (response_code >= 100 && response_code < 200) {
  399. int ret = sapi_module.send_headers(&SG(sapi_headers));
  400. SG(sapi_headers).http_response_code = previous_status_code;
  401. RETURN_LONG(ret);
  402. }
  403. RETURN_LONG(sapi_send_headers());
  404. }
  405. PHP_MINIT_FUNCTION(frankenphp) {
  406. zend_function *func;
  407. // Override putenv
  408. func = zend_hash_str_find_ptr(CG(function_table), "putenv",
  409. sizeof("putenv") - 1);
  410. if (func != NULL && func->type == ZEND_INTERNAL_FUNCTION) {
  411. ((zend_internal_function *)func)->handler = ZEND_FN(frankenphp_putenv);
  412. } else {
  413. php_error(E_WARNING, "Failed to find built-in putenv function");
  414. }
  415. // Override getenv
  416. func = zend_hash_str_find_ptr(CG(function_table), "getenv",
  417. sizeof("getenv") - 1);
  418. if (func != NULL && func->type == ZEND_INTERNAL_FUNCTION) {
  419. ((zend_internal_function *)func)->handler = ZEND_FN(frankenphp_getenv);
  420. } else {
  421. php_error(E_WARNING, "Failed to find built-in getenv function");
  422. }
  423. return SUCCESS;
  424. }
  425. static zend_module_entry frankenphp_module = {
  426. STANDARD_MODULE_HEADER,
  427. "frankenphp",
  428. ext_functions, /* function table */
  429. PHP_MINIT(frankenphp), /* initialization */
  430. NULL, /* shutdown */
  431. NULL, /* request initialization */
  432. NULL, /* request shutdown */
  433. NULL, /* information */
  434. TOSTRING(FRANKENPHP_VERSION),
  435. STANDARD_MODULE_PROPERTIES};
  436. static void frankenphp_request_shutdown() {
  437. frankenphp_server_context *ctx = SG(server_context);
  438. if (ctx->has_main_request && ctx->has_active_request) {
  439. frankenphp_destroy_super_globals();
  440. }
  441. php_request_shutdown((void *)0);
  442. frankenphp_free_request_context();
  443. memset(local_ctx, 0, sizeof(frankenphp_server_context));
  444. }
  445. int frankenphp_update_server_context(
  446. bool create, bool has_main_request, bool has_active_request,
  447. const char *request_method, char *query_string, zend_long content_length,
  448. char *path_translated, char *request_uri, const char *content_type,
  449. char *auth_user, char *auth_password, int proto_num) {
  450. frankenphp_server_context *ctx;
  451. if (create) {
  452. ctx = local_ctx;
  453. ctx->worker_ready = false;
  454. ctx->cookie_data = NULL;
  455. ctx->finished = false;
  456. SG(server_context) = ctx;
  457. } else {
  458. ctx = (frankenphp_server_context *)SG(server_context);
  459. }
  460. // It is not reset by zend engine, set it to 200.
  461. SG(sapi_headers).http_response_code = 200;
  462. ctx->has_main_request = has_main_request;
  463. ctx->has_active_request = has_active_request;
  464. SG(request_info).auth_password = auth_password;
  465. SG(request_info).auth_user = auth_user;
  466. SG(request_info).request_method = request_method;
  467. SG(request_info).query_string = query_string;
  468. SG(request_info).content_type = content_type;
  469. SG(request_info).content_length = content_length;
  470. SG(request_info).path_translated = path_translated;
  471. SG(request_info).request_uri = request_uri;
  472. SG(request_info).proto_num = proto_num;
  473. return SUCCESS;
  474. }
  475. static int frankenphp_startup(sapi_module_struct *sapi_module) {
  476. php_import_environment_variables = get_full_env;
  477. return php_module_startup(sapi_module, &frankenphp_module);
  478. }
  479. static int frankenphp_deactivate(void) {
  480. /* TODO: flush everything */
  481. return SUCCESS;
  482. }
  483. static size_t frankenphp_ub_write(const char *str, size_t str_length) {
  484. frankenphp_server_context *ctx = SG(server_context);
  485. if (ctx->finished) {
  486. /* TODO: maybe log a warning that we tried to write to a finished request?
  487. */
  488. return 0;
  489. }
  490. struct go_ub_write_return result =
  491. go_ub_write(thread_index, (char *)str, str_length);
  492. if (result.r1) {
  493. php_handle_aborted_connection();
  494. }
  495. return result.r0;
  496. }
  497. static int frankenphp_send_headers(sapi_headers_struct *sapi_headers) {
  498. if (SG(request_info).no_headers == 1) {
  499. return SAPI_HEADER_SENT_SUCCESSFULLY;
  500. }
  501. int status;
  502. frankenphp_server_context *ctx = SG(server_context);
  503. if (!ctx->has_active_request) {
  504. return SAPI_HEADER_SEND_FAILED;
  505. }
  506. if (SG(sapi_headers).http_status_line) {
  507. status = atoi((SG(sapi_headers).http_status_line) + 9);
  508. } else {
  509. status = SG(sapi_headers).http_response_code;
  510. if (!status) {
  511. status = 200;
  512. }
  513. }
  514. go_write_headers(thread_index, status, &sapi_headers->headers);
  515. return SAPI_HEADER_SENT_SUCCESSFULLY;
  516. }
  517. static void frankenphp_sapi_flush(void *server_context) {
  518. frankenphp_server_context *ctx = (frankenphp_server_context *)server_context;
  519. if (ctx && ctx->has_active_request && go_sapi_flush(thread_index)) {
  520. php_handle_aborted_connection();
  521. }
  522. }
  523. static size_t frankenphp_read_post(char *buffer, size_t count_bytes) {
  524. frankenphp_server_context *ctx = SG(server_context);
  525. return ctx->has_active_request
  526. ? go_read_post(thread_index, buffer, count_bytes)
  527. : 0;
  528. }
  529. static char *frankenphp_read_cookies(void) {
  530. frankenphp_server_context *ctx = SG(server_context);
  531. if (!ctx->has_active_request) {
  532. return "";
  533. }
  534. ctx->cookie_data = go_read_cookies(thread_index);
  535. return ctx->cookie_data;
  536. }
  537. /* all variables with well defined keys can safely be registered like this */
  538. void frankenphp_register_trusted_var(zend_string *z_key, char *value,
  539. size_t val_len, HashTable *ht) {
  540. if (value == NULL) {
  541. zval empty;
  542. ZVAL_EMPTY_STRING(&empty);
  543. zend_hash_update_ind(ht, z_key, &empty);
  544. return;
  545. }
  546. size_t new_val_len = val_len;
  547. if (!should_filter_var ||
  548. sapi_module.input_filter(PARSE_SERVER, ZSTR_VAL(z_key), &value,
  549. new_val_len, &new_val_len)) {
  550. zval z_value;
  551. ZVAL_STRINGL_FAST(&z_value, value, new_val_len);
  552. zend_hash_update_ind(ht, z_key, &z_value);
  553. }
  554. }
  555. void frankenphp_register_single(zend_string *z_key, char *value, size_t val_len,
  556. zval *track_vars_array) {
  557. HashTable *ht = Z_ARRVAL_P(track_vars_array);
  558. frankenphp_register_trusted_var(z_key, value, val_len, ht);
  559. }
  560. /* Register known $_SERVER variables in bulk to avoid cgo overhead */
  561. void frankenphp_register_bulk(
  562. zval *track_vars_array, ht_key_value_pair remote_addr,
  563. ht_key_value_pair remote_host, ht_key_value_pair remote_port,
  564. ht_key_value_pair document_root, ht_key_value_pair path_info,
  565. ht_key_value_pair php_self, ht_key_value_pair document_uri,
  566. ht_key_value_pair script_filename, ht_key_value_pair script_name,
  567. ht_key_value_pair https, ht_key_value_pair ssl_protocol,
  568. ht_key_value_pair request_scheme, ht_key_value_pair server_name,
  569. ht_key_value_pair server_port, ht_key_value_pair content_length,
  570. ht_key_value_pair gateway_interface, ht_key_value_pair server_protocol,
  571. ht_key_value_pair server_software, ht_key_value_pair http_host,
  572. ht_key_value_pair auth_type, ht_key_value_pair remote_ident,
  573. ht_key_value_pair request_uri) {
  574. HashTable *ht = Z_ARRVAL_P(track_vars_array);
  575. frankenphp_register_trusted_var(remote_addr.key, remote_addr.val,
  576. remote_addr.val_len, ht);
  577. frankenphp_register_trusted_var(remote_host.key, remote_host.val,
  578. remote_host.val_len, ht);
  579. frankenphp_register_trusted_var(remote_port.key, remote_port.val,
  580. remote_port.val_len, ht);
  581. frankenphp_register_trusted_var(document_root.key, document_root.val,
  582. document_root.val_len, ht);
  583. frankenphp_register_trusted_var(path_info.key, path_info.val,
  584. path_info.val_len, ht);
  585. frankenphp_register_trusted_var(php_self.key, php_self.val, php_self.val_len,
  586. ht);
  587. frankenphp_register_trusted_var(document_uri.key, document_uri.val,
  588. document_uri.val_len, ht);
  589. frankenphp_register_trusted_var(script_filename.key, script_filename.val,
  590. script_filename.val_len, ht);
  591. frankenphp_register_trusted_var(script_name.key, script_name.val,
  592. script_name.val_len, ht);
  593. frankenphp_register_trusted_var(https.key, https.val, https.val_len, ht);
  594. frankenphp_register_trusted_var(ssl_protocol.key, ssl_protocol.val,
  595. ssl_protocol.val_len, ht);
  596. frankenphp_register_trusted_var(request_scheme.key, request_scheme.val,
  597. request_scheme.val_len, ht);
  598. frankenphp_register_trusted_var(server_name.key, server_name.val,
  599. server_name.val_len, ht);
  600. frankenphp_register_trusted_var(server_port.key, server_port.val,
  601. server_port.val_len, ht);
  602. frankenphp_register_trusted_var(content_length.key, content_length.val,
  603. content_length.val_len, ht);
  604. frankenphp_register_trusted_var(gateway_interface.key, gateway_interface.val,
  605. gateway_interface.val_len, ht);
  606. frankenphp_register_trusted_var(server_protocol.key, server_protocol.val,
  607. server_protocol.val_len, ht);
  608. frankenphp_register_trusted_var(server_software.key, server_software.val,
  609. server_software.val_len, ht);
  610. frankenphp_register_trusted_var(http_host.key, http_host.val,
  611. http_host.val_len, ht);
  612. frankenphp_register_trusted_var(auth_type.key, auth_type.val,
  613. auth_type.val_len, ht);
  614. frankenphp_register_trusted_var(remote_ident.key, remote_ident.val,
  615. remote_ident.val_len, ht);
  616. frankenphp_register_trusted_var(request_uri.key, request_uri.val,
  617. request_uri.val_len, ht);
  618. }
  619. /** Create an immutable zend_string that lasts for the whole process **/
  620. zend_string *frankenphp_init_persistent_string(const char *string, size_t len) {
  621. /* persistent strings will be ignored by the GC at the end of a request */
  622. zend_string *z_string = zend_string_init(string, len, 1);
  623. /* interned strings will not be ref counted by the GC */
  624. GC_ADD_FLAGS(z_string, IS_STR_INTERNED);
  625. return z_string;
  626. }
  627. void frankenphp_release_zend_string(zend_string *z_string) {
  628. zend_string_release(z_string);
  629. }
  630. static void
  631. frankenphp_register_variable_from_request_info(zend_string *zKey, char *value,
  632. bool must_be_present,
  633. zval *track_vars_array) {
  634. if (value != NULL) {
  635. frankenphp_register_trusted_var(zKey, value, strlen(value),
  636. Z_ARRVAL_P(track_vars_array));
  637. } else if (must_be_present) {
  638. frankenphp_register_trusted_var(zKey, NULL, 0,
  639. Z_ARRVAL_P(track_vars_array));
  640. }
  641. }
  642. void frankenphp_register_variables_from_request_info(
  643. zval *track_vars_array, zend_string *content_type,
  644. zend_string *path_translated, zend_string *query_string,
  645. zend_string *auth_user, zend_string *request_method) {
  646. frankenphp_register_variable_from_request_info(
  647. content_type, (char *)SG(request_info).content_type, true,
  648. track_vars_array);
  649. frankenphp_register_variable_from_request_info(
  650. path_translated, (char *)SG(request_info).path_translated, false,
  651. track_vars_array);
  652. frankenphp_register_variable_from_request_info(
  653. query_string, SG(request_info).query_string, true, track_vars_array);
  654. frankenphp_register_variable_from_request_info(
  655. auth_user, (char *)SG(request_info).auth_user, false, track_vars_array);
  656. frankenphp_register_variable_from_request_info(
  657. request_method, (char *)SG(request_info).request_method, false,
  658. track_vars_array);
  659. }
  660. /* variables with user-defined keys must be registered safely
  661. * see: php_variables.c -> php_register_variable_ex (#1106) */
  662. void frankenphp_register_variable_safe(char *key, char *val, size_t val_len,
  663. zval *track_vars_array) {
  664. if (key == NULL) {
  665. return;
  666. }
  667. if (val == NULL) {
  668. val = "";
  669. }
  670. size_t new_val_len = val_len;
  671. if (!should_filter_var ||
  672. sapi_module.input_filter(PARSE_SERVER, key, &val, new_val_len,
  673. &new_val_len)) {
  674. php_register_variable_safe(key, val, new_val_len, track_vars_array);
  675. }
  676. }
  677. static void frankenphp_register_variables(zval *track_vars_array) {
  678. /* https://www.php.net/manual/en/reserved.variables.server.php */
  679. /* In CGI mode, we consider the environment to be a part of the server
  680. * variables.
  681. */
  682. frankenphp_server_context *ctx = SG(server_context);
  683. /* in non-worker mode we import the os environment regularly */
  684. if (!ctx->has_main_request) {
  685. get_full_env(track_vars_array);
  686. // php_import_environment_variables(track_vars_array);
  687. go_register_variables(thread_index, track_vars_array);
  688. return;
  689. }
  690. /* In worker mode we cache the os environment */
  691. if (os_environment == NULL) {
  692. os_environment = malloc(sizeof(zval));
  693. array_init(os_environment);
  694. get_full_env(os_environment);
  695. // php_import_environment_variables(os_environment);
  696. }
  697. zend_hash_copy(Z_ARR_P(track_vars_array), Z_ARR_P(os_environment),
  698. (copy_ctor_func_t)zval_add_ref);
  699. go_register_variables(thread_index, track_vars_array);
  700. }
  701. static void frankenphp_log_message(const char *message, int syslog_type_int) {
  702. go_log((char *)message, syslog_type_int);
  703. }
  704. static char *frankenphp_getenv(const char *name, size_t name_len) {
  705. go_string gname = {name_len, (char *)name};
  706. return go_sapi_getenv(thread_index, &gname);
  707. }
  708. sapi_module_struct frankenphp_sapi_module = {
  709. "frankenphp", /* name */
  710. "FrankenPHP", /* pretty name */
  711. frankenphp_startup, /* startup */
  712. php_module_shutdown_wrapper, /* shutdown */
  713. NULL, /* activate */
  714. frankenphp_deactivate, /* deactivate */
  715. frankenphp_ub_write, /* unbuffered write */
  716. frankenphp_sapi_flush, /* flush */
  717. NULL, /* get uid */
  718. frankenphp_getenv, /* getenv */
  719. php_error, /* error handler */
  720. NULL, /* header handler */
  721. frankenphp_send_headers, /* send headers handler */
  722. NULL, /* send header handler */
  723. frankenphp_read_post, /* read POST data */
  724. frankenphp_read_cookies, /* read Cookies */
  725. frankenphp_register_variables, /* register server variables */
  726. frankenphp_log_message, /* Log message */
  727. NULL, /* Get request time */
  728. NULL, /* Child terminate */
  729. STANDARD_SAPI_MODULE_PROPERTIES};
  730. /* Sets thread name for profiling and debugging.
  731. *
  732. * Adapted from https://github.com/Pithikos/C-Thread-Pool
  733. * Copyright: Johan Hanssen Seferidis
  734. * License: MIT
  735. */
  736. static void set_thread_name(char *thread_name) {
  737. #if defined(__linux__)
  738. /* Use prctl instead to prevent using _GNU_SOURCE flag and implicit
  739. * declaration */
  740. prctl(PR_SET_NAME, thread_name);
  741. #elif defined(__APPLE__) && defined(__MACH__)
  742. pthread_setname_np(thread_name);
  743. #elif defined(__FreeBSD__) || defined(__OpenBSD__)
  744. pthread_set_name_np(pthread_self(), thread_name);
  745. #endif
  746. }
  747. static void *php_thread(void *arg) {
  748. thread_index = (uintptr_t)arg;
  749. char thread_name[16] = {0};
  750. snprintf(thread_name, 16, "php-%" PRIxPTR, thread_index);
  751. set_thread_name(thread_name);
  752. #ifdef ZTS
  753. /* initial resource fetch */
  754. (void)ts_resource(0);
  755. #ifdef PHP_WIN32
  756. ZEND_TSRMLS_CACHE_UPDATE();
  757. #endif
  758. #endif
  759. local_ctx = malloc(sizeof(frankenphp_server_context));
  760. // loop until Go signals to stop
  761. char *scriptName = NULL;
  762. while ((scriptName = go_frankenphp_before_script_execution(thread_index))) {
  763. go_frankenphp_after_script_execution(thread_index,
  764. frankenphp_execute_script(scriptName));
  765. }
  766. #ifdef ZTS
  767. ts_free_thread();
  768. #endif
  769. go_frankenphp_on_thread_shutdown(thread_index);
  770. free(local_ctx);
  771. local_ctx = NULL;
  772. return NULL;
  773. }
  774. static void *php_main(void *arg) {
  775. /*
  776. * SIGPIPE must be masked in non-Go threads:
  777. * https://pkg.go.dev/os/signal#hdr-Go_programs_that_use_cgo_or_SWIG
  778. */
  779. sigset_t set;
  780. sigemptyset(&set);
  781. sigaddset(&set, SIGPIPE);
  782. if (pthread_sigmask(SIG_BLOCK, &set, NULL) != 0) {
  783. perror("failed to block SIGPIPE");
  784. exit(EXIT_FAILURE);
  785. }
  786. set_thread_name("php-main");
  787. #ifdef ZTS
  788. #if (PHP_VERSION_ID >= 80300)
  789. php_tsrm_startup_ex((intptr_t)arg);
  790. #else
  791. php_tsrm_startup();
  792. #endif
  793. /*tsrm_error_set(TSRM_ERROR_LEVEL_INFO, NULL);*/
  794. #ifdef PHP_WIN32
  795. ZEND_TSRMLS_CACHE_UPDATE();
  796. #endif
  797. #endif
  798. sapi_startup(&frankenphp_sapi_module);
  799. #ifndef ZEND_MAX_EXECUTION_TIMERS
  800. #if (PHP_VERSION_ID >= 80300)
  801. frankenphp_sapi_module.ini_entries = HARDCODED_INI;
  802. #else
  803. frankenphp_sapi_module.ini_entries = malloc(sizeof(HARDCODED_INI));
  804. if (frankenphp_sapi_module.ini_entries == NULL) {
  805. perror("malloc failed");
  806. exit(EXIT_FAILURE);
  807. }
  808. memcpy(frankenphp_sapi_module.ini_entries, HARDCODED_INI,
  809. sizeof(HARDCODED_INI));
  810. #endif
  811. #else
  812. /* overwrite php.ini with custom user settings */
  813. char *php_ini_overrides = go_get_custom_php_ini();
  814. if (php_ini_overrides != NULL) {
  815. frankenphp_sapi_module.ini_entries = php_ini_overrides;
  816. }
  817. #endif
  818. frankenphp_sapi_module.startup(&frankenphp_sapi_module);
  819. /* check if a default filter is set in php.ini and only filter if
  820. * it is, this is deprecated and will be removed in PHP 9 */
  821. char *default_filter;
  822. cfg_get_string("filter.default", &default_filter);
  823. should_filter_var = default_filter != NULL;
  824. go_frankenphp_main_thread_is_ready();
  825. /* channel closed, shutdown gracefully */
  826. frankenphp_sapi_module.shutdown(&frankenphp_sapi_module);
  827. sapi_shutdown();
  828. #ifdef ZTS
  829. tsrm_shutdown();
  830. #endif
  831. #if (PHP_VERSION_ID < 80300)
  832. if (frankenphp_sapi_module.ini_entries) {
  833. free(frankenphp_sapi_module.ini_entries);
  834. frankenphp_sapi_module.ini_entries = NULL;
  835. }
  836. #endif
  837. go_frankenphp_shutdown_main_thread();
  838. return NULL;
  839. }
  840. int frankenphp_new_main_thread(int num_threads) {
  841. pthread_t thread;
  842. if (pthread_create(&thread, NULL, &php_main, (void *)(intptr_t)num_threads) !=
  843. 0) {
  844. return -1;
  845. }
  846. return pthread_detach(thread);
  847. }
  848. bool frankenphp_new_php_thread(uintptr_t thread_index) {
  849. pthread_t thread;
  850. if (pthread_create(&thread, NULL, &php_thread, (void *)thread_index) != 0) {
  851. return false;
  852. }
  853. pthread_detach(thread);
  854. return true;
  855. }
  856. int frankenphp_request_startup() {
  857. if (php_request_startup() == SUCCESS) {
  858. return SUCCESS;
  859. }
  860. php_request_shutdown((void *)0);
  861. return FAILURE;
  862. }
  863. int frankenphp_execute_script(char *file_name) {
  864. if (frankenphp_request_startup() == FAILURE) {
  865. return FAILURE;
  866. }
  867. int status = SUCCESS;
  868. zend_file_handle file_handle;
  869. zend_stream_init_filename(&file_handle, file_name);
  870. file_handle.primary_script = 1;
  871. zend_first_try {
  872. EG(exit_status) = 0;
  873. php_execute_script(&file_handle);
  874. status = EG(exit_status);
  875. }
  876. zend_catch { status = EG(exit_status); }
  877. zend_end_try();
  878. // free the cached os environment before shutting down the script
  879. if (os_environment != NULL) {
  880. zval_ptr_dtor(os_environment);
  881. free(os_environment);
  882. os_environment = NULL;
  883. }
  884. zend_destroy_file_handle(&file_handle);
  885. frankenphp_free_request_context();
  886. frankenphp_request_shutdown();
  887. return status;
  888. }
  889. /* Use global variables to store CLI arguments to prevent useless allocations */
  890. static char *cli_script;
  891. static int cli_argc;
  892. static char **cli_argv;
  893. /*
  894. * CLI code is adapted from
  895. * https://github.com/php/php-src/blob/master/sapi/cli/php_cli.c Copyright (c)
  896. * The PHP Group Licensed under The PHP License Original uthors: Edin Kadribasic
  897. * <edink@php.net>, Marcus Boerger <helly@php.net> and Johannes Schlueter
  898. * <johannes@php.net> Parts based on CGI SAPI Module by Rasmus Lerdorf, Stig
  899. * Bakken and Zeev Suraski
  900. */
  901. static void cli_register_file_handles(bool no_close) /* {{{ */
  902. {
  903. php_stream *s_in, *s_out, *s_err;
  904. php_stream_context *sc_in = NULL, *sc_out = NULL, *sc_err = NULL;
  905. zend_constant ic, oc, ec;
  906. s_in = php_stream_open_wrapper_ex("php://stdin", "rb", 0, NULL, sc_in);
  907. s_out = php_stream_open_wrapper_ex("php://stdout", "wb", 0, NULL, sc_out);
  908. s_err = php_stream_open_wrapper_ex("php://stderr", "wb", 0, NULL, sc_err);
  909. if (s_in == NULL || s_out == NULL || s_err == NULL) {
  910. if (s_in)
  911. php_stream_close(s_in);
  912. if (s_out)
  913. php_stream_close(s_out);
  914. if (s_err)
  915. php_stream_close(s_err);
  916. return;
  917. }
  918. if (no_close) {
  919. s_in->flags |= PHP_STREAM_FLAG_NO_CLOSE;
  920. s_out->flags |= PHP_STREAM_FLAG_NO_CLOSE;
  921. s_err->flags |= PHP_STREAM_FLAG_NO_CLOSE;
  922. }
  923. /*s_in_process = s_in;*/
  924. php_stream_to_zval(s_in, &ic.value);
  925. php_stream_to_zval(s_out, &oc.value);
  926. php_stream_to_zval(s_err, &ec.value);
  927. ZEND_CONSTANT_SET_FLAGS(&ic, CONST_CS, 0);
  928. ic.name = zend_string_init_interned("STDIN", sizeof("STDIN") - 1, 0);
  929. zend_register_constant(&ic);
  930. ZEND_CONSTANT_SET_FLAGS(&oc, CONST_CS, 0);
  931. oc.name = zend_string_init_interned("STDOUT", sizeof("STDOUT") - 1, 0);
  932. zend_register_constant(&oc);
  933. ZEND_CONSTANT_SET_FLAGS(&ec, CONST_CS, 0);
  934. ec.name = zend_string_init_interned("STDERR", sizeof("STDERR") - 1, 0);
  935. zend_register_constant(&ec);
  936. }
  937. /* }}} */
  938. static void sapi_cli_register_variables(zval *track_vars_array) /* {{{ */
  939. {
  940. size_t len;
  941. char *docroot = "";
  942. /*
  943. * In CGI mode, we consider the environment to be a part of the server
  944. * variables
  945. */
  946. php_import_environment_variables(track_vars_array);
  947. /* Build the special-case PHP_SELF variable for the CLI version */
  948. len = strlen(cli_script);
  949. if (sapi_module.input_filter(PARSE_SERVER, "PHP_SELF", &cli_script, len,
  950. &len)) {
  951. php_register_variable_safe("PHP_SELF", cli_script, len, track_vars_array);
  952. }
  953. if (sapi_module.input_filter(PARSE_SERVER, "SCRIPT_NAME", &cli_script, len,
  954. &len)) {
  955. php_register_variable_safe("SCRIPT_NAME", cli_script, len,
  956. track_vars_array);
  957. }
  958. /* filenames are empty for stdin */
  959. if (sapi_module.input_filter(PARSE_SERVER, "SCRIPT_FILENAME", &cli_script,
  960. len, &len)) {
  961. php_register_variable_safe("SCRIPT_FILENAME", cli_script, len,
  962. track_vars_array);
  963. }
  964. if (sapi_module.input_filter(PARSE_SERVER, "PATH_TRANSLATED", &cli_script,
  965. len, &len)) {
  966. php_register_variable_safe("PATH_TRANSLATED", cli_script, len,
  967. track_vars_array);
  968. }
  969. /* just make it available */
  970. len = 0U;
  971. if (sapi_module.input_filter(PARSE_SERVER, "DOCUMENT_ROOT", &docroot, len,
  972. &len)) {
  973. php_register_variable_safe("DOCUMENT_ROOT", docroot, len, track_vars_array);
  974. }
  975. }
  976. /* }}} */
  977. static void *execute_script_cli(void *arg) {
  978. void *exit_status;
  979. /*
  980. * The SAPI name "cli" is hardcoded into too many programs... let's usurp it.
  981. */
  982. php_embed_module.name = "cli";
  983. php_embed_module.pretty_name = "PHP CLI embedded in FrankenPHP";
  984. php_embed_module.register_server_variables = sapi_cli_register_variables;
  985. php_embed_init(cli_argc, cli_argv);
  986. cli_register_file_handles(false);
  987. zend_first_try {
  988. zend_file_handle file_handle;
  989. zend_stream_init_filename(&file_handle, cli_script);
  990. CG(skip_shebang) = 1;
  991. php_execute_script(&file_handle);
  992. }
  993. zend_end_try();
  994. exit_status = (void *)(intptr_t)EG(exit_status);
  995. php_embed_shutdown();
  996. return exit_status;
  997. }
  998. int frankenphp_execute_script_cli(char *script, int argc, char **argv) {
  999. pthread_t thread;
  1000. int err;
  1001. void *exit_status;
  1002. cli_script = script;
  1003. cli_argc = argc;
  1004. cli_argv = argv;
  1005. /*
  1006. * Start the script in a dedicated thread to prevent conflicts between Go and
  1007. * PHP signal handlers
  1008. */
  1009. err = pthread_create(&thread, NULL, execute_script_cli, NULL);
  1010. if (err != 0) {
  1011. return err;
  1012. }
  1013. err = pthread_join(thread, &exit_status);
  1014. if (err != 0) {
  1015. return err;
  1016. }
  1017. return (intptr_t)exit_status;
  1018. }
  1019. int frankenphp_execute_php_function(const char *php_function) {
  1020. zval retval = {0};
  1021. zend_fcall_info fci = {0};
  1022. zend_fcall_info_cache fci_cache = {0};
  1023. zend_string *func_name =
  1024. zend_string_init(php_function, strlen(php_function), 0);
  1025. ZVAL_STR(&fci.function_name, func_name);
  1026. fci.size = sizeof fci;
  1027. fci.retval = &retval;
  1028. int success = 0;
  1029. zend_try { success = zend_call_function(&fci, &fci_cache) == SUCCESS; }
  1030. zend_end_try();
  1031. zend_string_release(func_name);
  1032. return success;
  1033. }
  1034. int frankenphp_reset_opcache(void) {
  1035. if (zend_hash_str_exists(CG(function_table), "opcache_reset",
  1036. sizeof("opcache_reset") - 1)) {
  1037. return frankenphp_execute_php_function("opcache_reset");
  1038. }
  1039. return 0;
  1040. }
  1041. int frankenphp_get_current_memory_limit() { return PG(memory_limit); }