#include "main/php.h"
#include "main/SAPI.h"
#include "main/php_main.h"
#include "main/php_variables.h"
#include "Zend/zend.h"
#include "Zend/zend_exceptions.h"
#include "Zend/zend_execute.h"
#include "Zend/zend_API.h"
#include "Zend/zend_call_stack.h"
#include <setjmp.h>
#include <signal.h>
#include <string.h>
#include <pthread.h>
#include <sys/resource.h>
#ifdef ZTS
#include "TSRM/TSRM.h"
#endif
static __thread sigjmp_buf folk_sigsegv_jmpbuf;
static __thread volatile int folk_sigsegv_active = 0;
#ifdef ZTS
#include "Zend/zend_ini.h"
static pthread_mutex_t folk_tsrm_init_mutex = PTHREAD_MUTEX_INITIALIZER;
#endif
void *folk_thread_init(void) {
#ifdef ZTS
{
void *before = tsrm_get_ls_cache();
fprintf(stderr, "[folk-zts] pre-ts_resource: tsrm_get_ls_cache=%p\n", before);
fflush(stderr);
}
fprintf(stderr, "[folk-zts] calling ts_resource(0)...\n"); fflush(stderr);
void *ctx = ts_resource(0);
fprintf(stderr, "[folk-zts] ts_resource done ctx=%p ls_cache=%p\n", ctx, tsrm_get_ls_cache());
fflush(stderr);
zend_call_stack_init();
return ctx;
#else
zend_call_stack_init();
return NULL;
#endif
}
void folk_thread_set_ctx(void *ctx) {
(void)ctx;
}
void folk_thread_shutdown(void *ctx) {
#ifdef ZTS
(void)ctx;
ts_free_thread();
#else
(void)ctx;
#endif
}
int folk_is_zts(void) {
#ifdef ZTS
return 1;
#else
return 0;
#endif
}
typedef struct {
const char *request_method;
const char *request_uri;
const char *query_string;
const char *content_type;
size_t content_length;
const char *path_translated;
const char *post_data;
size_t post_data_len;
size_t post_data_read;
const char *cookie_data;
const char **header_names;
const char **header_values;
size_t header_count;
const char *server_name;
int server_port;
const char *protocol;
} folk_request_context_t;
static __thread folk_request_context_t *folk_current_request = NULL;
typedef struct {
int status_code;
char **headers;
size_t header_count;
size_t header_cap;
} folk_response_context_t;
static __thread folk_response_context_t folk_response = {0};
static __thread char *folk_output_buf = NULL;
static __thread size_t folk_output_len = 0;
static __thread size_t folk_output_cap = 0;
static size_t folk_sapi_ub_write(const char *str, size_t str_length) {
if (folk_output_buf == NULL) {
folk_output_cap = (str_length > 4096) ? str_length * 2 : 4096;
folk_output_buf = malloc(folk_output_cap);
folk_output_len = 0;
}
if (folk_output_len + str_length > folk_output_cap) {
folk_output_cap = (folk_output_len + str_length) * 2;
folk_output_buf = realloc(folk_output_buf, folk_output_cap);
}
memcpy(folk_output_buf + folk_output_len, str, str_length);
folk_output_len += str_length;
return str_length;
}
static void folk_sapi_flush(void *server_context) {
(void)server_context;
}
static int folk_sapi_send_headers(sapi_headers_struct *sapi_headers) {
(void)sapi_headers;
folk_response.status_code = SG(sapi_headers).http_response_code;
return SAPI_HEADER_SENT_SUCCESSFULLY;
}
static void folk_sapi_send_header(sapi_header_struct *sapi_header, void *server_context) {
(void)server_context;
if (sapi_header == NULL || sapi_header->header == NULL || sapi_header->header_len == 0) {
return;
}
if (folk_response.header_count >= folk_response.header_cap) {
folk_response.header_cap = folk_response.header_cap == 0 ? 16 : folk_response.header_cap * 2;
folk_response.headers = realloc(folk_response.headers, folk_response.header_cap * sizeof(char *));
}
char *copy = malloc(sapi_header->header_len + 1);
memcpy(copy, sapi_header->header, sapi_header->header_len);
copy[sapi_header->header_len] = '\0';
folk_response.headers[folk_response.header_count++] = copy;
}
static size_t folk_sapi_read_post(char *buffer, size_t count_bytes) {
if (folk_current_request == NULL || folk_current_request->post_data == NULL) {
return 0;
}
size_t remaining = folk_current_request->post_data_len - folk_current_request->post_data_read;
if (remaining == 0) {
return 0;
}
size_t to_read = (count_bytes < remaining) ? count_bytes : remaining;
memcpy(buffer, folk_current_request->post_data + folk_current_request->post_data_read, to_read);
folk_current_request->post_data_read += to_read;
return to_read;
}
static char *folk_sapi_read_cookies(void) {
if (folk_current_request == NULL || folk_current_request->cookie_data == NULL) {
return "";
}
return (char *)folk_current_request->cookie_data;
}
static void folk_sapi_register_variables(zval *track_vars_array) {
php_import_environment_variables(track_vars_array);
if (folk_current_request == NULL) {
return;
}
if (folk_current_request->request_method) {
php_register_variable_safe("REQUEST_METHOD",
(char *)folk_current_request->request_method,
strlen(folk_current_request->request_method),
track_vars_array);
}
if (folk_current_request->request_uri) {
php_register_variable_safe("REQUEST_URI",
(char *)folk_current_request->request_uri,
strlen(folk_current_request->request_uri),
track_vars_array);
}
if (folk_current_request->query_string) {
php_register_variable_safe("QUERY_STRING",
(char *)folk_current_request->query_string,
strlen(folk_current_request->query_string),
track_vars_array);
}
if (folk_current_request->content_type) {
php_register_variable_safe("CONTENT_TYPE",
(char *)folk_current_request->content_type,
strlen(folk_current_request->content_type),
track_vars_array);
}
if (folk_current_request->content_length > 0) {
char cl_buf[32];
snprintf(cl_buf, sizeof(cl_buf), "%zu", folk_current_request->content_length);
php_register_variable_safe("CONTENT_LENGTH", cl_buf, strlen(cl_buf), track_vars_array);
}
if (folk_current_request->path_translated) {
php_register_variable_safe("SCRIPT_FILENAME",
(char *)folk_current_request->path_translated,
strlen(folk_current_request->path_translated),
track_vars_array);
}
if (folk_current_request->server_name) {
php_register_variable_safe("SERVER_NAME",
(char *)folk_current_request->server_name,
strlen(folk_current_request->server_name),
track_vars_array);
}
if (folk_current_request->server_port > 0) {
char port_buf[8];
snprintf(port_buf, sizeof(port_buf), "%d", folk_current_request->server_port);
php_register_variable_safe("SERVER_PORT", port_buf, strlen(port_buf), track_vars_array);
}
if (folk_current_request->protocol) {
php_register_variable_safe("SERVER_PROTOCOL",
(char *)folk_current_request->protocol,
strlen(folk_current_request->protocol),
track_vars_array);
}
php_register_variable_safe("SERVER_SOFTWARE", "folk-embed", 10, track_vars_array);
for (size_t i = 0; i < folk_current_request->header_count; i++) {
const char *name = folk_current_request->header_names[i];
const char *value = folk_current_request->header_values[i];
if (name == NULL || value == NULL) continue;
size_t name_len = strlen(name);
size_t var_len = 5 + name_len + 1;
char *var_name = alloca(var_len);
memcpy(var_name, "HTTP_", 5);
for (size_t j = 0; j < name_len; j++) {
char c = name[j];
if (c == '-') c = '_';
else if (c >= 'a' && c <= 'z') c = c - 32;
var_name[5 + j] = c;
}
var_name[5 + name_len] = '\0';
php_register_variable_safe(var_name, (char *)value, strlen(value), track_vars_array);
}
}
static void folk_sapi_log_message(const char *message, int syslog_type_int) {
(void)syslog_type_int;
fprintf(stderr, "[folk-sapi] %s\n", message);
}
static int folk_sapi_startup(sapi_module_struct *sapi_module) {
return php_module_startup(sapi_module, NULL);
}
static sapi_module_struct folk_sapi_module = {
"folk-embed",
"Folk Embedded PHP",
folk_sapi_startup,
php_module_shutdown_wrapper,
NULL,
NULL,
folk_sapi_ub_write,
folk_sapi_flush,
NULL,
NULL,
php_error,
NULL,
folk_sapi_send_headers,
folk_sapi_send_header,
folk_sapi_read_post,
folk_sapi_read_cookies,
folk_sapi_register_variables,
folk_sapi_log_message,
NULL,
NULL,
STANDARD_SAPI_MODULE_PROPERTIES
};
static const char folk_ini_entries[] =
"opcache.enable=0\0"
"opcache.enable_cli=0\0"
"zend.max_allowed_stack_size=134217728\0"
"\0";
int folk_sapi_init(void) {
{
struct rlimit rl;
if (getrlimit(RLIMIT_STACK, &rl) == 0) {
rl.rlim_cur = rl.rlim_max;
setrlimit(RLIMIT_STACK, &rl);
}
}
#ifdef ZTS
php_tsrm_startup();
#endif
folk_sapi_module.ini_entries = folk_ini_entries;
folk_sapi_module.additional_functions = NULL;
sapi_startup(&folk_sapi_module);
if (folk_sapi_module.startup(&folk_sapi_module) == FAILURE) {
return -1;
}
return 0;
}
int folk_sapi_init_with_ini(const char *ini_overrides) {
sapi_startup(&folk_sapi_module);
if (ini_overrides != NULL) {
folk_sapi_module.ini_entries = ini_overrides;
} else {
folk_sapi_module.ini_entries = folk_ini_entries;
}
folk_sapi_module.additional_functions = NULL;
if (folk_sapi_module.startup(&folk_sapi_module) == FAILURE) {
return -1;
}
return 0;
}
void folk_sapi_shutdown(void) {
php_module_shutdown();
sapi_shutdown();
#ifdef ZTS
tsrm_shutdown();
#endif
}
void folk_request_context_set(folk_request_context_t *ctx) {
folk_current_request = ctx;
}
void folk_request_context_clear(void) {
folk_current_request = NULL;
}
void folk_request_info_populate(void) {
if (folk_current_request == NULL) return;
SG(request_info).request_method = folk_current_request->request_method;
SG(request_info).request_uri = (char *)folk_current_request->request_uri;
SG(request_info).query_string = (char *)folk_current_request->query_string;
SG(request_info).content_type = folk_current_request->content_type;
SG(request_info).content_length = folk_current_request->content_length;
SG(request_info).path_translated = (char *)folk_current_request->path_translated;
}
int folk_response_status_code(void) {
return folk_response.status_code;
}
size_t folk_response_header_count(void) {
return folk_response.header_count;
}
const char *folk_response_header_get(size_t index, size_t *out_len) {
if (index >= folk_response.header_count) {
*out_len = 0;
return NULL;
}
*out_len = strlen(folk_response.headers[index]);
return folk_response.headers[index];
}
void folk_response_clear(void) {
for (size_t i = 0; i < folk_response.header_count; i++) {
free(folk_response.headers[i]);
}
folk_response.header_count = 0;
folk_response.status_code = 200;
}
void folk_response_free(void) {
for (size_t i = 0; i < folk_response.header_count; i++) {
free(folk_response.headers[i]);
}
free(folk_response.headers);
folk_response.headers = NULL;
folk_response.header_count = 0;
folk_response.header_cap = 0;
folk_response.status_code = 0;
}
int folk_request_startup_safe(void) {
int result = 0;
zend_try {
folk_request_info_populate();
if (php_request_startup() != SUCCESS) {
result = -2;
}
} zend_catch {
result = -1;
} zend_end_try();
return result;
}
int folk_request_shutdown_safe(void) {
int result = 0;
zend_try {
php_request_shutdown(NULL);
} zend_catch {
result = -1;
} zend_end_try();
return result;
}
const char *folk_get_output(size_t *out_len) {
*out_len = folk_output_len;
return folk_output_buf;
}
void folk_clear_output(void) {
folk_output_len = 0;
}
void folk_free_output(void) {
free(folk_output_buf);
folk_output_buf = NULL;
folk_output_len = 0;
folk_output_cap = 0;
}
void folk_install_output_handler(void) {
sapi_module.ub_write = folk_sapi_ub_write;
}
int folk_execute_script_safe(const char *filename) {
int result = 0;
zend_try {
zend_file_handle file_handle;
zend_stream_init_filename(&file_handle, filename);
if (!php_execute_script(&file_handle)) {
result = -2;
}
zend_destroy_file_handle(&file_handle);
} zend_catch {
result = -1;
} zend_end_try();
return result;
}
int folk_eval_string_safe(const char *code, zval *retval) {
int result = 0;
zend_try {
zend_eval_string(code, retval, "folk-embed");
} zend_catch {
result = -1;
} zend_end_try();
return result;
}
int folk_call_function_safe(
const char *func_name,
zval *retval,
uint32_t param_count,
zval *params
) {
int result = 0;
zend_try {
zval func_zval;
ZVAL_STRING(&func_zval, func_name);
int call_result = call_user_function(
EG(function_table),
NULL,
&func_zval,
retval,
param_count,
params
);
zval_ptr_dtor(&func_zval);
if (call_result != SUCCESS) {
result = -2;
}
} zend_catch {
result = -1;
} zend_end_try();
return result;
}
int folk_call_with_binary(
const char *func_name,
const char *method_name, size_t method_name_len,
const char *params, size_t params_len,
char **response_buf, size_t *response_len
) {
*response_buf = NULL;
*response_len = 0;
folk_sigsegv_active = 1;
if (sigsetjmp(folk_sigsegv_jmpbuf, 1) != 0) {
folk_sigsegv_active = 0;
return -3;
}
int result = 0;
zend_try {
zval args[2];
ZVAL_STRINGL(&args[0], method_name, method_name_len);
ZVAL_STRINGL(&args[1], params, params_len);
zval func_zval;
ZVAL_STRING(&func_zval, func_name);
zval retval;
ZVAL_UNDEF(&retval);
int call_result = call_user_function(
EG(function_table), NULL, &func_zval, &retval, 2, args
);
zval_ptr_dtor(&func_zval);
zval_ptr_dtor(&args[0]);
zval_ptr_dtor(&args[1]);
if (call_result != SUCCESS) {
zval_ptr_dtor(&retval);
result = -2;
} else if (Z_TYPE(retval) == IS_STRING) {
*response_len = Z_STRLEN(retval);
*response_buf = malloc(*response_len);
if (*response_buf != NULL) {
memcpy(*response_buf, Z_STRVAL(retval), *response_len);
}
zval_ptr_dtor(&retval);
} else {
zval_ptr_dtor(&retval);
}
} zend_catch {
result = -1;
} zend_end_try();
folk_sigsegv_active = 0;
return result;
}
void folk_free_buffer(char *buf) {
free(buf);
}
void folk_array_init(zval *arr) {
array_init(arr);
}
void folk_array_add_string(
zval *arr, const char *key, size_t key_len,
const char *val, size_t val_len
) {
zval zv;
ZVAL_STRINGL(&zv, val, val_len);
zend_hash_str_add(Z_ARR_P(arr), key, key_len, &zv);
}
void folk_array_add_long(
zval *arr, const char *key, size_t key_len,
zend_long val
) {
zval zv;
ZVAL_LONG(&zv, val);
zend_hash_str_add(Z_ARR_P(arr), key, key_len, &zv);
}
void folk_array_add_array(
zval *arr, const char *key, size_t key_len,
zval *sub_arr
) {
zend_hash_str_add(Z_ARR_P(arr), key, key_len, sub_arr);
}
void folk_array_append_string(zval *arr, const char *val, size_t val_len) {
zval zv;
ZVAL_STRINGL(&zv, val, val_len);
zend_hash_next_index_insert(Z_ARR_P(arr), &zv);
}
int folk_call_with_array(
const char *func_name,
zval *request_arr,
zval *retval
) {
folk_sigsegv_active = 1;
if (sigsetjmp(folk_sigsegv_jmpbuf, 1) != 0) {
folk_sigsegv_active = 0;
return -3;
}
int result = 0;
zend_try {
zval func_zval;
ZVAL_STRING(&func_zval, func_name);
int call_result = call_user_function(
EG(function_table), NULL, &func_zval, retval, 1, request_arr
);
zval_ptr_dtor(&func_zval);
if (call_result != SUCCESS) {
result = -2;
}
} zend_catch {
result = -1;
} zend_end_try();
folk_sigsegv_active = 0;
return result;
}
const char *folk_array_get_string(zval *arr, const char *key, size_t key_len, size_t *out_len) {
if (arr == NULL || Z_TYPE_P(arr) != IS_ARRAY) {
*out_len = 0;
return NULL;
}
zval *entry = zend_hash_str_find(Z_ARR_P(arr), key, key_len);
if (entry == NULL || Z_TYPE_P(entry) != IS_STRING) {
*out_len = 0;
return NULL;
}
*out_len = Z_STRLEN_P(entry);
return Z_STRVAL_P(entry);
}
zend_long folk_array_get_long(zval *arr, const char *key, size_t key_len) {
if (arr == NULL || Z_TYPE_P(arr) != IS_ARRAY) {
return 0;
}
zval *entry = zend_hash_str_find(Z_ARR_P(arr), key, key_len);
if (entry == NULL || Z_TYPE_P(entry) != IS_LONG) {
return 0;
}
return Z_LVAL_P(entry);
}
size_t folk_array_count(zval *arr) {
if (arr == NULL || Z_TYPE_P(arr) != IS_ARRAY) return 0;
return zend_hash_num_elements(Z_ARR_P(arr));
}
const char *folk_array_index_string(zval *arr, size_t index, size_t *out_len) {
if (arr == NULL || Z_TYPE_P(arr) != IS_ARRAY) {
*out_len = 0;
return NULL;
}
zval *entry = zend_hash_index_find(Z_ARR_P(arr), index);
if (entry == NULL || Z_TYPE_P(entry) != IS_STRING) {
*out_len = 0;
return NULL;
}
*out_len = Z_STRLEN_P(entry);
return Z_STRVAL_P(entry);
}
const char *folk_zval_get_string(zval *val, size_t *out_len) {
if (val == NULL || Z_TYPE_P(val) != IS_STRING) {
*out_len = 0;
return NULL;
}
*out_len = Z_STRLEN_P(val);
return Z_STRVAL_P(val);
}
long folk_zval_get_long(zval *val) {
if (val == NULL || Z_TYPE_P(val) != IS_LONG) {
return 0;
}
return Z_LVAL_P(val);
}
int folk_zval_type(zval *val) {
if (val == NULL) return 0;
return Z_TYPE_P(val);
}
void folk_zval_dtor(zval *val) {
if (val != NULL) {
zval_ptr_dtor(val);
}
}
void folk_zval_undef(zval *val) {
ZVAL_UNDEF(val);
}
void folk_zval_set_string(zval *val, const char *str, size_t len) {
ZVAL_STRINGL(val, str, len);
}
#define FOLK_SAVED_SIGNALS_COUNT 5
static int folk_saved_signal_nums[FOLK_SAVED_SIGNALS_COUNT] = {
SIGTERM, SIGINT, SIGQUIT, SIGPIPE, SIGSEGV
};
static struct sigaction folk_saved_handlers[FOLK_SAVED_SIGNALS_COUNT];
void folk_signals_save(void) {
for (int i = 0; i < FOLK_SAVED_SIGNALS_COUNT; i++) {
sigaction(folk_saved_signal_nums[i], NULL, &folk_saved_handlers[i]);
}
}
void folk_signals_restore(void) {
for (int i = 0; i < FOLK_SAVED_SIGNALS_COUNT; i++) {
sigaction(folk_saved_signal_nums[i], &folk_saved_handlers[i], NULL);
}
}
static void folk_sigsegv_handler(int sig, siginfo_t *info, void *ucontext) {
(void)info;
(void)ucontext;
(void)sig;
if (folk_sigsegv_active) {
siglongjmp(folk_sigsegv_jmpbuf, 1);
}
signal(SIGSEGV, SIG_DFL);
raise(SIGSEGV);
}
void folk_sigsegv_handler_install(void) {
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_sigaction = folk_sigsegv_handler;
sa.sa_flags = SA_SIGINFO | SA_NODEFER;
sigemptyset(&sa.sa_mask);
sigaction(SIGSEGV, &sa, NULL);
}
int folk_eval_string_protected(const char *code, zval *retval) {
folk_sigsegv_active = 1;
if (sigsetjmp(folk_sigsegv_jmpbuf, 1) != 0) {
folk_sigsegv_active = 0;
return -3;
}
int result = folk_eval_string_safe(code, retval);
folk_sigsegv_active = 0;
return result;
}
int folk_call_function_protected(
const char *func_name,
zval *retval,
uint32_t param_count,
zval *params
) {
folk_sigsegv_active = 1;
if (sigsetjmp(folk_sigsegv_jmpbuf, 1) != 0) {
folk_sigsegv_active = 0;
return -3;
}
int result = folk_call_function_safe(func_name, retval, param_count, params);
folk_sigsegv_active = 0;
return result;
}