#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "bext_php_sapi.h"
#undef ZEND_ENABLE_STATIC_TSRMLS_CACHE
#include <sapi/embed/php_embed.h>
#include <main/php.h>
#include <main/SAPI.h>
#include <main/php_main.h>
#include <main/php_variables.h>
#include <main/php_ini.h>
#include <main/php_output.h>
#include <ext/standard/info.h>
#include <Zend/zend_exceptions.h>
#ifdef ZTS
#include <TSRM/TSRM.h>
#endif
#include <string.h>
#include <stdlib.h>
#ifdef __linux__
#include <sys/resource.h>
#endif
static __thread bext_request_ctx *tls_request_ctx = NULL;
static __thread int tls_is_worker = 0;
static __thread int tls_worker_had_request = 0;
static size_t bext_ub_write(const char *str, size_t str_length)
{
if (!tls_request_ctx) return 0;
return bext_sapi_ub_write(tls_request_ctx, str, str_length);
}
static size_t bext_read_post(char *buffer, size_t count_bytes)
{
if (!tls_request_ctx) return 0;
return bext_sapi_read_post(tls_request_ctx, buffer, count_bytes);
}
static char *bext_read_cookies(void)
{
if (!tls_request_ctx) return NULL;
return bext_sapi_read_cookies(tls_request_ctx);
}
static int bext_header_handler(sapi_header_struct *sapi_header,
sapi_header_op_enum op,
sapi_headers_struct *sapi_headers)
{
(void)sapi_headers;
if (!tls_request_ctx || !sapi_header || !sapi_header->header) return 0;
if (op == SAPI_HEADER_ADD || op == SAPI_HEADER_REPLACE) {
bext_sapi_on_header(tls_request_ctx, sapi_header->header, sapi_header->header_len);
}
return 0;
}
static int bext_send_headers(sapi_headers_struct *sapi_headers)
{
(void)sapi_headers;
return SAPI_HEADER_SENT_SUCCESSFULLY;
}
static void bext_register_variables(zval *track_vars_array)
{
php_import_environment_variables(track_vars_array);
if (tls_request_ctx) {
bext_sapi_register_server_variables(tls_request_ctx, track_vars_array);
}
}
static void bext_log_message(const char *message, int syslog_type_int)
{
bext_sapi_log_message(message, syslog_type_int);
}
static void bext_reset_superglobals(void)
{
zend_hash_str_del(&EG(symbol_table), "_SERVER", sizeof("_SERVER") - 1);
zend_hash_str_del(&EG(symbol_table), "_GET", sizeof("_GET") - 1);
zend_hash_str_del(&EG(symbol_table), "_POST", sizeof("_POST") - 1);
zend_hash_str_del(&EG(symbol_table), "_COOKIE", sizeof("_COOKIE") - 1);
zend_hash_str_del(&EG(symbol_table), "_FILES", sizeof("_FILES") - 1);
zend_hash_str_del(&EG(symbol_table), "_REQUEST", sizeof("_REQUEST") - 1);
zend_hash_str_del(&EG(symbol_table), "_SESSION", sizeof("_SESSION") - 1);
{
zval server_arr;
array_init(&server_arr);
php_import_environment_variables(&server_arr);
if (tls_request_ctx) {
bext_sapi_register_server_variables(tls_request_ctx, &server_arr);
}
if (SG(request_info).request_method) {
php_register_variable_safe("REQUEST_METHOD",
(char*)SG(request_info).request_method,
strlen(SG(request_info).request_method), &server_arr);
}
if (SG(request_info).request_uri) {
php_register_variable_safe("REQUEST_URI",
(char*)SG(request_info).request_uri,
strlen(SG(request_info).request_uri), &server_arr);
}
if (SG(request_info).query_string && *SG(request_info).query_string) {
php_register_variable_safe("QUERY_STRING",
(char*)SG(request_info).query_string,
strlen(SG(request_info).query_string), &server_arr);
}
if (SG(request_info).content_type) {
php_register_variable_safe("CONTENT_TYPE",
(char*)SG(request_info).content_type,
strlen(SG(request_info).content_type), &server_arr);
}
if (SG(request_info).content_length > 0) {
char cl[32];
snprintf(cl, sizeof(cl), "%ld", (long)SG(request_info).content_length);
php_register_variable_safe("CONTENT_LENGTH", cl, strlen(cl), &server_arr);
}
zend_hash_str_update(&EG(symbol_table), "_SERVER", sizeof("_SERVER") - 1, &server_arr);
}
{
zval get_arr;
array_init(&get_arr);
if (SG(request_info).query_string && *SG(request_info).query_string) {
char *qs = estrdup(SG(request_info).query_string);
sapi_module.treat_data(PARSE_STRING, qs, &get_arr);
}
zend_hash_str_update(&EG(symbol_table), "_GET", sizeof("_GET") - 1, &get_arr);
}
{
static const char *empty_globals[] = {
"_POST", "_COOKIE", "_FILES", "_REQUEST"
};
for (int i = 0; i < 4; i++) {
zval arr;
array_init(&arr);
zend_hash_str_update(&EG(symbol_table),
empty_globals[i], strlen(empty_globals[i]), &arr);
}
}
}
PHP_FUNCTION(bext_handle_request)
{
zend_fcall_info fci;
zend_fcall_info_cache fcc;
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_FUNC(fci, fcc)
ZEND_PARSE_PARAMETERS_END();
if (!tls_is_worker) {
zend_throw_exception_ex(NULL, 0,
"bext_handle_request() can only be called from a worker script");
RETURN_FALSE;
}
int has_request = bext_sapi_worker_wait_request(&tls_request_ctx);
if (!has_request) {
RETURN_FALSE;
}
SG(request_info).request_method = bext_sapi_get_method(tls_request_ctx);
SG(request_info).request_uri = (char *)bext_sapi_get_uri(tls_request_ctx);
SG(request_info).query_string = (char *)bext_sapi_get_query_string(tls_request_ctx);
SG(request_info).content_type = bext_sapi_get_content_type(tls_request_ctx);
SG(request_info).content_length = bext_sapi_get_content_length(tls_request_ctx);
SG(headers_sent) = 0;
SG(sapi_headers).http_response_code = 200;
bext_reset_superglobals();
#if defined(ZEND_CHECK_STACK_LIMIT) && !defined(ZTS)
EG(stack_limit) = (void *)0;
#endif
zval retval;
fci.retval = &retval;
fci.param_count = 0;
fci.params = NULL;
if (zend_call_function(&fci, &fcc) == FAILURE) {
bext_sapi_log_message("bext_handle_request: callback execution failed", 3);
}
if (EG(exception)) {
if (zend_is_unwind_exit(EG(exception)) || zend_is_graceful_exit(EG(exception))) {
zval_ptr_dtor(&retval);
zend_bailout();
}
zend_exception_error(EG(exception), E_ERROR);
}
zval_ptr_dtor(&retval);
int status = SG(sapi_headers).http_response_code
? SG(sapi_headers).http_response_code : 200;
bext_sapi_worker_finish_request(tls_request_ctx, status);
RETURN_TRUE;
}
PHP_FUNCTION(bext_render)
{
char *component = NULL;
size_t component_len = 0;
char *props_json = NULL;
size_t props_len = 0;
ZEND_PARSE_PARAMETERS_START(1, 2)
Z_PARAM_STRING(component, component_len)
Z_PARAM_OPTIONAL
Z_PARAM_STRING(props_json, props_len)
ZEND_PARSE_PARAMETERS_END();
if (!props_json || props_len == 0) {
props_json = "{}";
}
char *html = bext_sapi_jsc_render(component, props_json);
if (!html) {
RETURN_STRING("<div>bext_render: null result</div>");
}
size_t html_len = strlen(html);
RETVAL_STRINGL(html, html_len);
bext_sapi_free_string(html);
}
static const zend_function_entry bext_functions[] = {
ZEND_FE(bext_handle_request, NULL)
ZEND_FE(bext_render, NULL)
PHP_FE_END
};
void bext_php_register_variable(const char *key, const char *val, void *track_vars_array)
{
if (key && val && track_vars_array) {
php_register_variable_safe(
(char *)key, (char *)val, strlen(val), (zval *)track_vars_array);
}
}
int bext_php_module_init(const char *ini_entries)
{
const char *prefix = "zend.max_allowed_stack_size=-1\nzend.reserved_stack_size=0\n";
size_t prefix_len = strlen(prefix);
size_t user_len = (ini_entries && *ini_entries) ? strlen(ini_entries) : 0;
char *combined = malloc(prefix_len + user_len + 1);
if (combined) {
memcpy(combined, prefix, prefix_len);
if (user_len > 0) {
memcpy(combined + prefix_len, ini_entries, user_len);
}
combined[prefix_len + user_len] = '\0';
php_embed_module.ini_entries = combined;
}
char *argv[] = {"bext-php", NULL};
if (php_embed_init(1, argv) == FAILURE) {
if (combined) free(combined);
return -1;
}
php_request_shutdown(NULL);
sapi_module.ub_write = bext_ub_write;
sapi_module.read_post = bext_read_post;
sapi_module.read_cookies = bext_read_cookies;
sapi_module.header_handler = bext_header_handler;
sapi_module.send_headers = bext_send_headers;
sapi_module.register_server_variables = bext_register_variables;
sapi_module.log_message = bext_log_message;
return 0;
}
void bext_php_register_functions(void)
{
zend_register_functions(NULL, bext_functions, NULL, MODULE_PERSISTENT);
}
void bext_php_module_shutdown(void)
{
php_embed_shutdown();
}
int bext_php_execute_script(bext_request_ctx *ctx,
const char *script_path,
const char *method,
const char *uri,
const char *query_string,
const char *content_type,
int64_t content_length)
{
volatile int status = 200;
tls_request_ctx = ctx;
tls_is_worker = 0;
#ifdef ZTS
(void)ts_resource(0);
#endif
if (php_request_startup() == FAILURE) {
tls_request_ctx = NULL;
return 500;
}
SG(request_info).request_method = method;
SG(request_info).request_uri = (char *)uri;
SG(request_info).query_string = query_string ? (char *)query_string : "";
SG(request_info).content_type = content_type;
SG(request_info).content_length = (content_length >= 0) ? content_length : 0;
SG(request_info).proto_num = 1001;
SG(request_info).path_translated = (char *)script_path;
#if defined(ZEND_CHECK_STACK_LIMIT) && !defined(ZTS)
EG(stack_limit) = (void *)0;
#endif
zend_file_handle file_handle;
zend_stream_init_filename(&file_handle, script_path);
zend_first_try {
php_execute_script(&file_handle);
} zend_catch {
status = 500;
} zend_end_try();
if (SG(sapi_headers).http_response_code) {
status = SG(sapi_headers).http_response_code;
}
php_request_shutdown(NULL);
tls_request_ctx = NULL;
return status;
}
int bext_php_execute_worker(bext_request_ctx *initial_ctx,
const char *worker_script_path)
{
tls_request_ctx = initial_ctx;
tls_is_worker = 1;
tls_worker_had_request = 0;
#ifdef ZTS
ts_resource(0);
#endif
if (php_request_startup() == FAILURE) {
tls_request_ctx = NULL;
tls_is_worker = 0;
return -1;
}
#ifdef ZTS
ZEND_TSRMLS_CACHE_UPDATE();
#endif
bext_php_register_functions();
SG(request_info).request_method = "GET";
SG(request_info).request_uri = "/";
SG(request_info).query_string = "";
SG(request_info).proto_num = 1001;
SG(request_info).path_translated = (char *)worker_script_path;
PG(ignore_user_abort) = 1;
#if defined(ZEND_CHECK_STACK_LIMIT) && !defined(ZTS)
EG(stack_limit) = (void *)0;
#endif
int exit_status = 0;
zend_file_handle file_handle;
zend_stream_init_filename(&file_handle, worker_script_path);
zend_first_try {
php_execute_script(&file_handle);
exit_status = EG(exit_status);
} zend_catch {
exit_status = 1;
} zend_end_try();
php_request_shutdown(NULL);
tls_request_ctx = NULL;
tls_is_worker = 0;
return exit_status;
}