#include "php.h"
#ifdef HAVE_ROXEN
#include "php_ini.h"
#include "php_globals.h"
#include "SAPI.h"
#include "php_main.h"
#include "ext/standard/info.h"
#include "php_version.h"
#ifndef ZTS
#undef ROXEN_USE_ZTS
#endif
#define NO_PIKE_SHORTHAND
#include <fdlib.h>
#include <program.h>
#include <pike_types.h>
#include <interpret.h>
#include <module_support.h>
#include <error.h>
#include <array.h>
#include <backend.h>
#include <stralloc.h>
#include <mapping.h>
#include <object.h>
#include <threads.h>
#include <builtin_functions.h>
#include <operators.h>
#undef HIDE_GLOBAL_VARIABLES
#undef REVEAL_GLOBAL_VARIABLES
#define HIDE_GLOBAL_VARIABLES()
#define REVEAL_GLOBAL_VARIABLES()
typedef struct
{
struct mapping *request_data;
struct object *my_fd_obj;
int my_fd;
char *filename;
} php_roxen_request;
#ifdef ROXEN_USE_ZTS
static int roxen_globals_id;
# define GET_THIS() php_roxen_request *_request = ts_resource(roxen_globals_id)
# define THIS _request
#else
static php_roxen_request *current_request = NULL;
# define GET_THIS() current_request = ((php_roxen_request *)Pike_fp->current_storage)
# define THIS current_request
#endif
#define MY_FD (THIS->my_fd)
#define MY_FD_OBJ ((struct object *)(THIS->my_fd_obj))
#define REQUEST_DATA ((struct mapping *)(THIS->request_data))
#if defined(_REENTRANT) && !defined(ROXEN_USE_ZTS)
static PIKE_MUTEX_T roxen_php_execution_lock;
# define PHP_INIT_LOCK() mt_init(&roxen_php_execution_lock)
# define PHP_LOCK(X) THREADS_ALLOW();mt_lock(&roxen_php_execution_lock);THREADS_DISALLOW()
# define PHP_UNLOCK(X) mt_unlock(&roxen_php_execution_lock);
# define PHP_DESTROY() mt_destroy(&roxen_php_execution_lock)
#else
# define PHP_INIT_LOCK()
# define PHP_LOCK(X)
# define PHP_UNLOCK(X)
# define PHP_DESTROY()
#endif
extern int fd_from_object(struct object *o);
static unsigned char roxen_php_initialized;
#define THREAD_SAFE_RUN(COMMAND, what) do {\
struct thread_state *state;\
if((state = thread_state_for_id(th_self()))!=NULL) {\
if(!state->swapped) {\
COMMAND;\
} else {\
mt_lock(&interpreter_lock);\
SWAP_IN_THREAD(state);\
COMMAND;\
SWAP_OUT_THREAD(state);\
mt_unlock(&interpreter_lock);\
}\
}\
} while(0)
struct program *php_program;
static int current_thread = -1;
static INLINE struct svalue *lookup_header(char *headername)
{
struct svalue *headers, *value;
struct pike_string *sind;
#ifdef ROXEN_USE_ZTS
GET_THIS();
#endif
sind = make_shared_string("env");
headers = low_mapping_string_lookup(REQUEST_DATA, sind);
free_string(sind);
if(!headers || headers->type != PIKE_T_MAPPING) return NULL;
sind = make_shared_string(headername);
value = low_mapping_string_lookup(headers->u.mapping, sind);
free_string(sind);
if(!value) return NULL;
return value;
}
INLINE static char *lookup_string_header(char *headername, char *default_value)
{
struct svalue *head = NULL;
THREAD_SAFE_RUN(head = lookup_header(headername), "header lookup");
if(!head || head->type != PIKE_T_STRING)
return default_value;
return head->u.string->str;
}
INLINE static int lookup_integer_header(char *headername, int default_value)
{
struct svalue *head = NULL;
THREAD_SAFE_RUN(head = lookup_header(headername), "header lookup");
if(!head || head->type != PIKE_T_INT)
return default_value;
return head->u.integer;
}
static int
php_roxen_low_ub_write(const char *str, uint str_length TSRMLS_DC) {
int sent_bytes = 0;
struct pike_string *to_write = NULL;
#ifdef ROXEN_USE_ZTS
GET_THIS();
#endif
if(!MY_FD_OBJ->prog) {
PG(connection_status) = PHP_CONNECTION_ABORTED;
zend_bailout();
return -1;
}
to_write = make_shared_binary_string(str, str_length);
push_string(to_write);
safe_apply(MY_FD_OBJ, "write", 1);
if(Pike_sp[-1].type == PIKE_T_INT)
sent_bytes = Pike_sp[-1].u.integer;
pop_stack();
if(sent_bytes != str_length) {
php_handle_aborted_connection();
}
return sent_bytes;
}
static int
php_roxen_sapi_ub_write(const char *str, uint str_length TSRMLS_DC)
{
#ifdef ROXEN_USE_ZTS
GET_THIS();
#endif
int sent_bytes = 0, fd = MY_FD;
if(fd)
{
for(sent_bytes=0;sent_bytes < str_length;)
{
int written;
written = fd_write(fd, str + sent_bytes, str_length - sent_bytes);
if(written < 0)
{
switch(errno)
{
default:
PG(connection_status) = PHP_CONNECTION_ABORTED;
zend_bailout();
return sent_bytes;
case EINTR:
case EWOULDBLOCK:
continue;
}
} else {
sent_bytes += written;
}
}
} else {
THREAD_SAFE_RUN(sent_bytes = php_roxen_low_ub_write(str, str_length TSRMLS_CC),
"write");
}
return sent_bytes;
}
static void php_roxen_set_header(char *header_name, char *value, char *p)
{
struct svalue hsval;
struct pike_string *hval, *ind, *hind;
struct mapping *headermap;
struct svalue *s_headermap;
#ifdef ROXEN_USE_ZTS
GET_THIS();
#endif
hval = make_shared_string(value);
ind = make_shared_string(" _headers");
hind = make_shared_binary_string(header_name,
(int)(p - header_name));
s_headermap = low_mapping_string_lookup(REQUEST_DATA, ind);
if(!s_headermap)
{
struct svalue mappie;
mappie.type = PIKE_T_MAPPING;
headermap = allocate_mapping(1);
mappie.u.mapping = headermap;
mapping_string_insert(REQUEST_DATA, ind, &mappie);
free_mapping(headermap);
} else
headermap = s_headermap->u.mapping;
hsval.type = PIKE_T_STRING;
hsval.u.string = hval;
mapping_string_insert(headermap, hind, &hsval);
free_string(hval);
free_string(ind);
free_string(hind);
}
static int
php_roxen_sapi_header_handler(sapi_header_struct *sapi_header,
sapi_headers_struct *sapi_headers TSRMLS_DC)
{
char *header_name, *header_content, *p;
header_name = sapi_header->header;
header_content = p = strchr(header_name, ':');
if(p) {
do {
header_content++;
} while(*header_content == ' ');
THREAD_SAFE_RUN(php_roxen_set_header(header_name, header_content, p), "header handler");
}
sapi_free_header(sapi_header);
return 0;
}
static int
php_roxen_low_send_headers(sapi_headers_struct *sapi_headers TSRMLS_DC)
{
struct pike_string *ind;
struct svalue *s_headermap;
#ifdef ROXEN_USE_ZTS
GET_THIS();
#endif
if(!MY_FD_OBJ->prog) {
PG(connection_status) = PHP_CONNECTION_ABORTED;
zend_bailout();
return SAPI_HEADER_SEND_FAILED;
}
ind = make_shared_string(" _headers");
s_headermap = low_mapping_string_lookup(REQUEST_DATA, ind);
free_string(ind);
push_int(SG(sapi_headers).http_response_code);
if(s_headermap && s_headermap->type == PIKE_T_MAPPING)
ref_push_mapping(s_headermap->u.mapping);
else
push_int(0);
safe_apply(MY_FD_OBJ, "send_headers", 2);
pop_stack();
return SAPI_HEADER_SENT_SUCCESSFULLY;
}
static int
php_roxen_sapi_send_headers(sapi_headers_struct *sapi_headers TSRMLS_DC)
{
int res = 0;
THREAD_SAFE_RUN(res = php_roxen_low_send_headers(sapi_headers TSRMLS_CC), "send headers");
return res;
}
INLINE static int php_roxen_low_read_post(char *buf, uint count_bytes)
{
uint total_read = 0;
#ifdef ROXEN_USE_ZTS
GET_THIS();
#endif
TSRMLS_FETCH();
if(!MY_FD_OBJ->prog)
{
PG(connection_status) = PHP_CONNECTION_ABORTED;
zend_bailout();
return -1;
}
push_int(count_bytes);
safe_apply(MY_FD_OBJ, "read_post", 1);
if(Pike_sp[-1].type == PIKE_T_STRING) {
MEMCPY(buf, Pike_sp[-1].u.string->str,
(total_read = Pike_sp[-1].u.string->len));
buf[total_read] = '\0';
} else
total_read = 0;
pop_stack();
return total_read;
}
static int
php_roxen_sapi_read_post(char *buf, uint count_bytes TSRMLS_DC)
{
uint total_read = 0;
THREAD_SAFE_RUN(total_read = php_roxen_low_read_post(buf, count_bytes), "read post");
return total_read;
}
static char *
php_roxen_sapi_read_cookies(TSRMLS_D)
{
char *cookies;
cookies = lookup_string_header("HTTP_COOKIE", NULL);
return cookies;
}
static void php_info_roxen(ZEND_MODULE_INFO_FUNC_ARGS)
{
php_info_print_table_start();
php_info_print_table_row(2, "SAPI module version", "$Id$");
php_info_print_table_end();
}
static zend_module_entry php_roxen_module = {
STANDARD_MODULE_HEADER,
"Roxen",
NULL,
NULL,
NULL,
NULL,
NULL,
php_info_roxen,
NULL,
STANDARD_MODULE_PROPERTIES
};
static int php_roxen_startup(sapi_module_struct *sapi_module)
{
if(php_module_startup(sapi_module, &php_roxen_module, 1) == FAILURE) {
return FAILURE;
} else {
return SUCCESS;
}
}
static sapi_module_struct roxen_sapi_module = {
"roxen",
"Roxen",
php_roxen_startup,
php_module_shutdown_wrapper,
NULL,
NULL,
php_roxen_sapi_ub_write,
NULL,
NULL,
NULL,
php_error,
php_roxen_sapi_header_handler,
php_roxen_sapi_send_headers,
NULL,
php_roxen_sapi_read_post,
php_roxen_sapi_read_cookies,
NULL,
NULL,
NULL,
NULL,
STANDARD_SAPI_MODULE_PROPERTIES
};
#define ADD_STRING(name) \
MAKE_STD_ZVAL(zvalue); \
zvalue->type = IS_STRING; \
zvalue->value.str.len = strlen(buf); \
zvalue->value.str.val = estrndup(buf, zvalue->value.str.len); \
zend_hash_update(&EG(symbol_table), name, sizeof(name), \
&zvalue, sizeof(zval *), NULL)
static void
php_roxen_hash_environment(TSRMLS_D)
{
int i;
char buf[512];
zval *zvalue;
struct svalue *headers;
struct pike_string *sind;
struct array *indices;
struct svalue *ind, *val;
#ifdef ROXEN_USE_ZTS
GET_THIS();
#endif
sind = make_shared_string("env");
headers = low_mapping_string_lookup(REQUEST_DATA, sind);
free_string(sind);
if(headers && headers->type == PIKE_T_MAPPING) {
indices = mapping_indices(headers->u.mapping);
for(i = 0; i < indices->size; i++) {
ind = &indices->item[i];
val = low_mapping_lookup(headers->u.mapping, ind);
if(ind && ind->type == PIKE_T_STRING &&
val && val->type == PIKE_T_STRING) {
int buf_len;
buf_len = MIN(511, ind->u.string->len);
strncpy(buf, ind->u.string->str, buf_len);
buf[buf_len] = '\0';
MAKE_STD_ZVAL(zvalue);
zvalue->type = IS_STRING;
zvalue->value.str.len = val->u.string->len;
zvalue->value.str.val = estrndup(val->u.string->str, zvalue->value.str.len);
zend_hash_update(&EG(symbol_table), buf, buf_len + 1, &zvalue, sizeof(zval *), NULL);
}
}
free_array(indices);
}
}
static int php_roxen_module_main(TSRMLS_D)
{
int res, len;
char *dir;
zend_file_handle file_handle;
#ifdef ROXEN_USE_ZTS
GET_THIS();
#endif
file_handle.type = ZEND_HANDLE_FILENAME;
file_handle.filename = THIS->filename;
file_handle.free_filename = 0;
file_handle.opened_path = NULL;
THREADS_ALLOW();
res = php_request_startup(TSRMLS_C);
THREADS_DISALLOW();
if(res == FAILURE) {
return 0;
}
php_roxen_hash_environment(TSRMLS_C);
THREADS_ALLOW();
php_execute_script(&file_handle TSRMLS_CC);
php_request_shutdown(NULL);
THREADS_DISALLOW();
return 1;
}
void f_php_roxen_request_handler(INT32 args)
{
struct object *my_fd_obj;
struct mapping *request_data;
struct svalue *done_callback, *raw_fd;
struct pike_string *script, *ind;
int status = 1;
#ifdef ROXEN_USE_ZTS
GET_THIS();
#endif
TSRMLS_FETCH();
if(current_thread == th_self())
php_error(E_WARNING, "PHP5.Interpreter->run: Tried to run a PHP-script from a PHP "
"callback!");
get_all_args("PHP5.Interpreter->run", args, "%S%m%O%*", &script,
&request_data, &my_fd_obj, &done_callback);
if(done_callback->type != PIKE_T_FUNCTION)
php_error(E_WARNING, "PHP5.Interpreter->run: Bad argument 4, expected function.\n");
PHP_LOCK(THIS);
#ifndef ROXEN_USE_ZTS
GET_THIS();
#endif
THIS->request_data = request_data;
THIS->my_fd_obj = my_fd_obj;
THIS->filename = script->str;
current_thread = th_self();
SG(request_info).query_string = lookup_string_header("QUERY_STRING", 0);
SG(server_context) = (void *)1;
SG(request_info).path_translated =
lookup_string_header("SCRIPT_FILENAME", NULL);
SG(request_info).request_uri = lookup_string_header("DOCUMENT_URI", NULL);
if(!SG(request_info).request_uri)
SG(request_info).request_uri = lookup_string_header("SCRIPT_NAME", NULL);
SG(request_info).request_method = lookup_string_header("REQUEST_METHOD", "GET");
SG(request_info).content_length = lookup_integer_header("HTTP_CONTENT_LENGTH", 0);
SG(request_info).content_type = lookup_string_header("HTTP_CONTENT_TYPE", NULL);
SG(sapi_headers).http_response_code = 200;
SG(request_info).auth_user = NULL;
SG(request_info).auth_password = NULL;
ind = make_shared_binary_string("my_fd", 5);
raw_fd = low_mapping_string_lookup(THIS->request_data, ind);
if(raw_fd && raw_fd->type == PIKE_T_OBJECT)
{
int fd = fd_from_object(raw_fd->u.object);
if(fd == -1)
php_error(E_WARNING, "PHP5.Interpreter->run: my_fd object not open or not an FD.\n");
THIS->my_fd = fd;
} else
THIS->my_fd = 0;
status = php_roxen_module_main(TSRMLS_C);
current_thread = -1;
apply_svalue(done_callback, 0);
pop_stack();
pop_n_elems(args);
push_int(status);
PHP_UNLOCK(THIS);
}
static void clear_struct(struct object *o)
{
MEMSET(Pike_fp->current_storage, 0, sizeof(php_roxen_request));
}
void pike_module_init( void )
{
if (!roxen_php_initialized) {
#ifdef ZTS
tsrm_startup(1, 1, 0, NULL);
#ifdef ROXEN_USE_ZTS
ts_allocate_id(&roxen_globals_id, sizeof(php_roxen_request), NULL, NULL);
#endif
#endif
sapi_startup(&roxen_sapi_module);
roxen_php_initialized = 1;
PHP_INIT_LOCK();
}
start_new_program();
ADD_STORAGE(php_roxen_request);
set_init_callback(clear_struct);
pike_add_function("run", f_php_roxen_request_handler,
"function(string, mapping, object, function:int)", 0);
add_program_constant("Interpreter", (php_program = end_program()), 0);
}
void pike_module_exit(void)
{
roxen_php_initialized = 0;
roxen_sapi_module.shutdown(&roxen_sapi_module);
if(php_program) free_program(php_program);
#ifdef ZTS
tsrm_shutdown();
#endif
PHP_DESTROY();
}
#endif