/*
   +----------------------------------------------------------------------+
   | PHP Version 5                                                        |
   +----------------------------------------------------------------------+
   | Copyright (c) 1997-2006 The PHP Group                                |
   +----------------------------------------------------------------------+
   | This source file is subject to version 3.01 of the PHP license,      |
   | that is bundled with this package in the file LICENSE, and is        |
   | available through the world-wide-web at the following url:           |
   | http://www.php.net/license/3_01.txt                                  |
   | If you did not receive a copy of the PHP license and are unable to   |
   | obtain it through the world-wide-web, please send a note to          |
   | license@php.net so we can mail you a copy immediately.               |
   +----------------------------------------------------------------------+
   | Author: Jayakumar Muthukumarasamy <jk@kasenna.com>                   |
   |         Uwe Schindler <uwe@thetaphi.de>                              |
   +----------------------------------------------------------------------+
*/

/* $Id: nsapi.c,v 1.69.2.3 2006/01/01 12:50:19 sniper Exp $ */

/*
 * PHP includes
 */
#define NSAPI 1

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "php.h"
#include "php_variables.h"
#include "ext/standard/info.h"
#include "php_ini.h"
#include "php_globals.h"
#include "SAPI.h"
#include "php_main.h"
#include "php_version.h"
#include "TSRM.h"
#include "ext/standard/php_standard.h"
#include <sys/types.h>
#include <sys/stat.h>

#ifndef RTLD_DEFAULT
#define RTLD_DEFAULT NULL
#endif

/*
 * If neither XP_UNIX not XP_WIN32 is defined use PHP_WIN32
 */
#if !defined(XP_UNIX) && !defined(XP_WIN32)
#ifdef PHP_WIN32
#define XP_WIN32
#else
#define XP_UNIX
#endif
#endif

/*
 * NSAPI includes
 */
#include "nsapi.h"
#include "base/pblock.h"
#include "base/session.h"
#include "frame/req.h"
#include "frame/protocol.h"  /* protocol_start_response */
#include "base/util.h"       /* is_mozilla, getline */
#include "frame/log.h"       /* log_error */

#define NSLS_D		struct nsapi_request_context *request_context
#define NSLS_DC		, NSLS_D
#define NSLS_C		request_context
#define NSLS_CC		, NSLS_C
#define NSG(v)		(request_context->v)

#define NS_BUF_SIZE 2048

/*
 * ZTS needs to be defined for NSAPI to work
 */
#if !defined(ZTS)
#error "NSAPI module needs ZTS to be defined"
#endif

/*
 * Structure to encapsulate the NSAPI request in SAPI
 */
typedef struct nsapi_request_context {
	pblock	*pb;
	Session	*sn;
	Request	*rq;
	int	read_post_bytes;
	char *path_info;
	int fixed_script; /* 0 if script is from URI, 1 if script is from "script" parameter */
	short http_error; /* 0 in normal mode; for errors the HTTP error code */
} nsapi_request_context;

/*
 * Mappings between NSAPI names and environment variables. This
 * mapping was obtained from the sample programs at the iplanet
 * website.
 */
typedef struct nsapi_equiv {
	const char *env_var;
	const char *nsapi_eq;
} nsapi_equiv;

static nsapi_equiv nsapi_reqpb[] = {
	{ "QUERY_STRING",		"query" },
	{ "REQUEST_LINE",		"clf-request" },
	{ "REQUEST_METHOD",		"method" },
	{ "PHP_SELF",			"uri" },
	{ "SERVER_PROTOCOL",	"protocol" }
};
static size_t nsapi_reqpb_size = sizeof(nsapi_reqpb)/sizeof(nsapi_reqpb[0]);

static nsapi_equiv nsapi_vars[] = {
	{ "AUTH_TYPE",			"auth-type" },
	{ "CLIENT_CERT",		"auth-cert" },
	{ "REMOTE_USER",		"auth-user" }
};
static size_t nsapi_vars_size = sizeof(nsapi_vars)/sizeof(nsapi_vars[0]);

static nsapi_equiv nsapi_client[] = {
	{ "HTTPS_KEYSIZE",		"keysize" },
	{ "HTTPS_SECRETSIZE",	"secret-keysize" },
	{ "REMOTE_ADDR",		"ip" },
	{ "REMOTE_HOST",		"ip" }
};
static size_t nsapi_client_size = sizeof(nsapi_client)/sizeof(nsapi_client[0]);

/* this parameters to "Service"/"Error" are NSAPI ones which should not be php.ini keys and are excluded */
static char *nsapi_exclude_from_ini_entries[] = { "fn", "type", "method", "directive", "code", "reason", "script", "bucket", NULL };

static char *nsapi_strdup(char *str)
{
	if (str != NULL) {
		return STRDUP(str);
	}
	return NULL;
}

static void nsapi_free(void *addr)
{
	if (addr != NULL) {
		FREE(addr);
	}
}


/*******************/
/* PHP module part */
/*******************/

PHP_MINIT_FUNCTION(nsapi);
PHP_MSHUTDOWN_FUNCTION(nsapi);
PHP_RINIT_FUNCTION(nsapi);
PHP_RSHUTDOWN_FUNCTION(nsapi);
PHP_MINFO_FUNCTION(nsapi);

PHP_FUNCTION(nsapi_virtual);
PHP_FUNCTION(nsapi_request_headers);
PHP_FUNCTION(nsapi_response_headers);

ZEND_BEGIN_MODULE_GLOBALS(nsapi)
	long read_timeout;
ZEND_END_MODULE_GLOBALS(nsapi)

ZEND_DECLARE_MODULE_GLOBALS(nsapi)

#define NSAPI_G(v) TSRMG(nsapi_globals_id, zend_nsapi_globals *, v)

/* {{{ nsapi_functions[]
 *
 * Every user visible function must have an entry in nsapi_functions[].
 */
zend_function_entry nsapi_functions[] = {
	PHP_FE(nsapi_virtual,	NULL)										/* Make subrequest */
	PHP_FALIAS(virtual, nsapi_virtual, NULL)							/* compatibility */
	PHP_FE(nsapi_request_headers, NULL)									/* get request headers */
	PHP_FALIAS(getallheaders, nsapi_request_headers, NULL)				/* compatibility */
	PHP_FALIAS(apache_request_headers, nsapi_request_headers, NULL)		/* compatibility */
	PHP_FE(nsapi_response_headers, NULL)								/* get response headers */
	PHP_FALIAS(apache_response_headers, nsapi_response_headers, NULL)	/* compatibility */
	{NULL, NULL, NULL}
};
/* }}} */

/* {{{ nsapi_module_entry
 */
zend_module_entry nsapi_module_entry = {
	STANDARD_MODULE_HEADER,
	"nsapi",
	nsapi_functions,
	PHP_MINIT(nsapi),
	PHP_MSHUTDOWN(nsapi),
	NULL,
	NULL,
	PHP_MINFO(nsapi),
	NO_VERSION_YET,
	STANDARD_MODULE_PROPERTIES
};
/* }}} */

/* {{{ PHP_INI
 */
PHP_INI_BEGIN()
    STD_PHP_INI_ENTRY("nsapi.read_timeout", "60", PHP_INI_ALL, OnUpdateLong, read_timeout, zend_nsapi_globals, nsapi_globals)
PHP_INI_END()
/* }}} */

/* newer servers hide this functions from the programmer so redefine the functions dynamically
   thanks to Chris Elving from Sun for the function declarations */
typedef int (*nsapi_servact_prototype)(Session *sn, Request *rq);
nsapi_servact_prototype nsapi_servact_uri2path = NULL;
nsapi_servact_prototype nsapi_servact_pathchecks = NULL;
nsapi_servact_prototype nsapi_servact_fileinfo = NULL;
nsapi_servact_prototype nsapi_servact_service = NULL;

#ifdef PHP_WIN32
/* The following dll-names for nsapi are in use at this time. The undocumented
 * servact_* functions are always in the newest one, older ones are supported by
 * the server only by wrapping the function table nothing else. So choose
 * the newest one found in process space for dynamic linking */
static char *nsapi_dlls[] = { "ns-httpd40.dll", "ns-httpd36.dll", "ns-httpd35.dll", "ns-httpd30.dll", NULL };
/* if user specifies an other dll name by server_lib parameter 
 * it is placed in the following variable and only this DLL is
 * checked for the servact_* functions */
char *nsapi_dll = NULL;
#endif

/* {{{ php_nsapi_init_dynamic_symbols
 */
static void php_nsapi_init_dynamic_symbols(void)
{
#if defined(servact_uri2path) && defined(servact_pathchecks) && defined(servact_fileinfo) && defined(servact_service)
	/* use functions from nsapi.h if available */
	nsapi_servact_uri2path = &servact_uri2path;
	nsapi_servact_pathchecks = &servact_pathchecks;
	nsapi_servact_fileinfo = &servact_fileinfo;
	nsapi_servact_service = &servact_service;
#else
	/* find address of internal NSAPI functions */
#ifdef PHP_WIN32
	register int i;
	DL_HANDLE module = NULL;
	if (nsapi_dll) {
		/* try user specified server_lib */
		module = GetModuleHandle(nsapi_dll);
		if (!module) {
			log_error(LOG_WARN, "php5_init", NULL, NULL, "Cannot find DLL specified by server_lib parameter: %s", nsapi_dll);
		}
	} else {
		/* find a LOADED dll module from nsapi_dlls */
		for (i=0; nsapi_dlls[i]; i++) {
			if (module = GetModuleHandle(nsapi_dlls[i])) {
				break;
			}
		}
	}
	if (!module) return;
#else
	DL_HANDLE module = RTLD_DEFAULT;
#endif
	nsapi_servact_uri2path = (nsapi_servact_prototype)DL_FETCH_SYMBOL(module, "INTservact_uri2path");
	nsapi_servact_pathchecks = (nsapi_servact_prototype)DL_FETCH_SYMBOL(module, "INTservact_pathchecks");
	nsapi_servact_fileinfo = (nsapi_servact_prototype)DL_FETCH_SYMBOL(module, "INTservact_fileinfo");
	nsapi_servact_service = (nsapi_servact_prototype)DL_FETCH_SYMBOL(module, "INTservact_service");
	if (!(nsapi_servact_uri2path && nsapi_servact_pathchecks && nsapi_servact_fileinfo && nsapi_servact_service)) {
		/* not found - could be cause they are undocumented */
		nsapi_servact_uri2path = NULL;
		nsapi_servact_pathchecks = NULL;
		nsapi_servact_fileinfo = NULL;
		nsapi_servact_service = NULL;
	}
#endif
}
/* }}} */

/* {{{ php_nsapi_init_globals
 */
static void php_nsapi_init_globals(zend_nsapi_globals *nsapi_globals)
{
	nsapi_globals->read_timeout = 60;
}
/* }}} */

/* {{{ PHP_MINIT_FUNCTION
 */
PHP_MINIT_FUNCTION(nsapi)
{
	php_nsapi_init_dynamic_symbols();
	ZEND_INIT_MODULE_GLOBALS(nsapi, php_nsapi_init_globals, NULL);
	REGISTER_INI_ENTRIES();
	return SUCCESS;
}
/* }}} */

/* {{{ PHP_MSHUTDOWN_FUNCTION
 */
PHP_MSHUTDOWN_FUNCTION(nsapi)
{
	UNREGISTER_INI_ENTRIES();
	return SUCCESS;
}
/* }}} */

/* {{{ PHP_MINFO_FUNCTION
 */
PHP_MINFO_FUNCTION(nsapi)
{
	php_info_print_table_start();
	php_info_print_table_row(2, "NSAPI Module Revision", "$Revision: 1.69.2.3 $");
	php_info_print_table_row(2, "Server Software", system_version());
	php_info_print_table_row(2, "Sub-requests with nsapi_virtual()",
	 (nsapi_servact_service)?((zend_ini_long("zlib.output_compression", sizeof("zlib.output_compression"), 0))?"not supported with zlib.output_compression":"enabled"):"not supported on this platform" );
	php_info_print_table_end();

	DISPLAY_INI_ENTRIES();
}
/* }}} */

/* {{{ proto bool nsapi_virtual(string uri)
   Perform an NSAPI sub-request */
/* This function is equivalent to <!--#include virtual...-->
 * in SSI. It does an NSAPI sub-request. It is useful
 * for including CGI scripts or .shtml files, or anything else
 * that you'd parse through webserver.
 */
PHP_FUNCTION(nsapi_virtual)
{
	zval **uri;
	int rv;
	char *value;
	Request *rq;
	nsapi_request_context *rc = (nsapi_request_context *)SG(server_context);

	if (ZEND_NUM_ARGS() != 1 || zend_get_parameters_ex(1, &uri) == FAILURE) {
		WRONG_PARAM_COUNT;
	}
	convert_to_string_ex(uri);

	if (!nsapi_servact_service) {
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to include uri '%s' - Sub-requests not supported on this platform", (*uri)->value.str.val);
		RETURN_FALSE;
	} else if (zend_ini_long("zlib.output_compression", sizeof("zlib.output_compression"), 0)) {
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to include uri '%s' - Sub-requests do not work with zlib.output_compression", (*uri)->value.str.val);
		RETURN_FALSE;
	} else {
		php_end_ob_buffers(1 TSRMLS_CC);
		php_header(TSRMLS_C);

		/* do the sub-request */
		/* thanks to Chris Elving from Sun for this code sniplet */
		if ((rq = request_restart_internal((*uri)->value.str.val, NULL)) == NULL) {
			php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to include uri '%s' - Internal request creation failed", (*uri)->value.str.val);
			RETURN_FALSE;
		}

		/* insert host of current request to get page from same vhost */
		param_free(pblock_remove("host", rq->headers));
		if (value = pblock_findval("host", rc->rq->headers)) {
			pblock_nvinsert("host", value, rq->headers);
		}

		/* go through the normal request stages as given in obj.conf,
		   but leave out the logging/error section */
		do {
			rv = (*nsapi_servact_uri2path)(rc->sn, rq);
			if (rv != REQ_PROCEED) {
				continue;
			}

			rv = (*nsapi_servact_pathchecks)(rc->sn, rq);
			if (rv != REQ_PROCEED) {
				continue;
			}

			rv = (*nsapi_servact_fileinfo)(rc->sn, rq);
			if (rv != REQ_PROCEED) {
				continue;
			}

			rv = (*nsapi_servact_service)(rc->sn, rq);
		} while (rv == REQ_RESTART);

		if (rq->status_num != 200) {
			php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to include uri '%s' - HTTP status code %d during subrequest", (*uri)->value.str.val, rq->status_num);
			request_free(rq);
			RETURN_FALSE;
		}

		request_free(rq);

		RETURN_TRUE;
	}
}
/* }}} */

/* {{{ proto array nsapi_request_headers(void)
   Get all headers from the request */
PHP_FUNCTION(nsapi_request_headers)
{
	register int i;
	struct pb_entry *entry;
	nsapi_request_context *rc = (nsapi_request_context *)SG(server_context);

	array_init(return_value);

	for (i=0; i < rc->rq->headers->hsize; i++) {
		entry=rc->rq->headers->ht[i];
		while (entry) {
			if (!PG(safe_mode) || strncasecmp(entry->param->name, "authorization", 13)) {
				add_assoc_string(return_value, entry->param->name, entry->param->value, 1);
			}
			entry=entry->next;
		}
  	}
}
/* }}} */

/* {{{ proto array nsapi_response_headers(void)
   Get all headers from the response */
PHP_FUNCTION(nsapi_response_headers)
{
	register int i;
	struct pb_entry *entry;
	nsapi_request_context *rc = (nsapi_request_context *)SG(server_context);

	array_init(return_value);

	php_header(TSRMLS_C);

	for (i=0; i < rc->rq->srvhdrs->hsize; i++) {
		entry=rc->rq->srvhdrs->ht[i];
		while (entry) {
			add_assoc_string(return_value, entry->param->name, entry->param->value, 1);
			entry=entry->next;
		}
  	}
}
/* }}} */


/*************/
/* SAPI part */
/*************/

static int sapi_nsapi_ub_write(const char *str, unsigned int str_length TSRMLS_DC)
{
	int retval;
	nsapi_request_context *rc;

	rc = (nsapi_request_context *)SG(server_context);
	retval = net_write(rc->sn->csd, (char *)str, str_length);
	if (retval == IO_ERROR /* -1 */ || retval == IO_EOF /* 0 */) {
		php_handle_aborted_connection();
	}
	return retval;
}

static int sapi_nsapi_header_handler(sapi_header_struct *sapi_header, sapi_headers_struct *sapi_headers TSRMLS_DC)
{
	char *header_name, *header_content, *p;
	nsapi_request_context *rc = (nsapi_request_context *)SG(server_context);

	header_name = sapi_header->header;
	header_content = p = strchr(header_name, ':');
	if (p == NULL) {
		efree(sapi_header->header);
		return 0;
	}

	*p = 0;
	do {
		header_content++;
	} while (*header_content == ' ');

	if (!strcasecmp(header_name, "Content-Type")) {
		param_free(pblock_remove("content-type", rc->rq->srvhdrs));
		pblock_nvinsert("content-type", header_content, rc->rq->srvhdrs);
	} else {
		/* to lower case because NSAPI reformats the headers and wants lowercase */
		for (p=header_name; *p; p++) {
			*p=tolower(*p);
		}
		if (sapi_header->replace) param_free(pblock_remove(header_name, rc->rq->srvhdrs));
		pblock_nvinsert(header_name, header_content, rc->rq->srvhdrs);
	}

	sapi_free_header(sapi_header);

	return 0;	/* don't use the default SAPI mechanism, NSAPI duplicates this functionality */
}

static int sapi_nsapi_send_headers(sapi_headers_struct *sapi_headers TSRMLS_DC)
{
	int retval;
	nsapi_request_context *rc = (nsapi_request_context *)SG(server_context);

	if (SG(sapi_headers).send_default_content_type) {
		char *hd;
		param_free(pblock_remove("content-type", rc->rq->srvhdrs));
		hd = sapi_get_default_content_type(TSRMLS_C);
		pblock_nvinsert("content-type", hd, rc->rq->srvhdrs);
		efree(hd);
	}

	protocol_status(rc->sn, rc->rq, SG(sapi_headers).http_response_code, NULL);
	retval = protocol_start_response(rc->sn, rc->rq);

	if (retval == REQ_PROCEED || retval == REQ_NOACTION) {
		return SAPI_HEADER_SENT_SUCCESSFULLY;
	} else {
		return SAPI_HEADER_SEND_FAILED;
	}
}

static int sapi_nsapi_read_post(char *buffer, uint count_bytes TSRMLS_DC)
{
	nsapi_request_context *rc = (nsapi_request_context *)SG(server_context);
	char *read_ptr = buffer, *content_length_str = NULL;
	uint bytes_read = 0;
	int length, content_length = 0;
	netbuf *nbuf = rc->sn->inbuf;

	/*
	 *	Yesss!
	 */
	count_bytes = MIN(count_bytes, SG(request_info).content_length-rc->read_post_bytes);
	content_length = SG(request_info).content_length;

	if (content_length <= 0) {
		return 0;
	}

	/*
	 * Gobble any pending data in the netbuf.
	 */
	length = nbuf->cursize - nbuf->pos;
	length = MIN(count_bytes, length);
	if (length > 0) {
		memcpy(read_ptr, nbuf->inbuf + nbuf->pos, length);
		bytes_read += length;
		read_ptr += length;
		content_length -= length;
		nbuf->pos += length;
	}

	/*
	 * Read the remaining from the socket.
	 */
	while (content_length > 0 && bytes_read < count_bytes) {
		int bytes_to_read = count_bytes - bytes_read;

		if (content_length < bytes_to_read) {
			bytes_to_read = content_length;
		}

		length = net_read(rc->sn->csd, read_ptr, bytes_to_read, NSAPI_G(read_timeout));

		if (length == IO_ERROR || length == IO_EOF) {
			break;
		}

		bytes_read += length;
		read_ptr += length;
		content_length -= length;
	}

	if ( bytes_read > 0 ) {
		rc->read_post_bytes += bytes_read;
	}
	return bytes_read;
}

static char *sapi_nsapi_read_cookies(TSRMLS_D)
{
	char *cookie_string;
	nsapi_request_context *rc = (nsapi_request_context *)SG(server_context);

	cookie_string = pblock_findval("cookie", rc->rq->headers);
	return cookie_string;
}

static void sapi_nsapi_register_server_variables(zval *track_vars_array TSRMLS_DC)
{
	nsapi_request_context *rc = (nsapi_request_context *)SG(server_context);
	register size_t i;
	int pos;
	char *value,*p;
	char buf[NS_BUF_SIZE + 1];
	struct pb_entry *entry;

	for (i = 0; i < nsapi_reqpb_size; i++) {
		value = pblock_findval(nsapi_reqpb[i].nsapi_eq, rc->rq->reqpb);
		if (value) {
			php_register_variable((char *)nsapi_reqpb[i].env_var, value, track_vars_array TSRMLS_CC);
		}
	}

	for (i=0; i < rc->rq->headers->hsize; i++) {
		entry=rc->rq->headers->ht[i];
		while (entry) {
			if (!PG(safe_mode) || strncasecmp(entry->param->name, "authorization", 13)) {
				if (strcasecmp(entry->param->name, "content-length")==0 || strcasecmp(entry->param->name, "content-type")==0) {
					strlcpy(buf, entry->param->name, NS_BUF_SIZE);
					pos = 0;
				} else {
					snprintf(buf, NS_BUF_SIZE, "HTTP_%s", entry->param->name);
					pos = 5;
				}
				buf[NS_BUF_SIZE]='\0';
				for(p = buf + pos; *p; p++) {
					*p = toupper(*p);
					if (*p < 'A' || *p > 'Z') {
						*p = '_';
					}
				}
				php_register_variable(buf, entry->param->value, track_vars_array TSRMLS_CC);
			}
			entry=entry->next;
		}
  	}

	for (i = 0; i < nsapi_vars_size; i++) {
		value = pblock_findval(nsapi_vars[i].nsapi_eq, rc->rq->vars);
		if (value) {
			php_register_variable((char *)nsapi_vars[i].env_var, value, track_vars_array TSRMLS_CC);
		}
	}

	for (i = 0; i < nsapi_client_size; i++) {
		value = pblock_findval(nsapi_client[i].nsapi_eq, rc->sn->client);
		if (value) {
			php_register_variable((char *)nsapi_client[i].env_var, value, track_vars_array TSRMLS_CC);
		}
	}

	if (value = session_dns(rc->sn)) {
		php_register_variable("REMOTE_HOST", value, track_vars_array TSRMLS_CC);
		nsapi_free(value);
	}

	sprintf(buf, "%d", conf_getglobals()->Vport);
	php_register_variable("SERVER_PORT", buf, track_vars_array TSRMLS_CC);
	php_register_variable("SERVER_NAME", conf_getglobals()->Vserver_hostname, track_vars_array TSRMLS_CC);

	value = http_uri2url_dynamic("", "", rc->sn, rc->rq);
	php_register_variable("SERVER_URL", value, track_vars_array TSRMLS_CC);
	nsapi_free(value);

	php_register_variable("SERVER_SOFTWARE", system_version(), track_vars_array TSRMLS_CC);
	php_register_variable("HTTPS", (security_active ? "ON" : "OFF"), track_vars_array TSRMLS_CC);
	php_register_variable("GATEWAY_INTERFACE", "CGI/1.1", track_vars_array TSRMLS_CC);

	/* DOCUMENT_ROOT */
	if (value = request_translate_uri("/", rc->sn)) {
	  	value[strlen(value) - 1] = '\0';
		php_register_variable("DOCUMENT_ROOT", value, track_vars_array TSRMLS_CC);
		nsapi_free(value);
	}

	/* PATH_INFO / PATH_TRANSLATED */
	if (rc->path_info) {
		if (value = request_translate_uri(rc->path_info, rc->sn)) {
			php_register_variable("PATH_TRANSLATED", value, track_vars_array TSRMLS_CC);
			nsapi_free(value);
		}
		php_register_variable("PATH_INFO", rc->path_info, track_vars_array TSRMLS_CC);
	}

	/* Create full Request-URI & Script-Name */
	if (SG(request_info).request_uri) {
		strlcpy(buf, SG(request_info).request_uri, NS_BUF_SIZE);
		if (SG(request_info).query_string) {
		  	p = strchr(buf, 0);
			snprintf(p, NS_BUF_SIZE-(p-buf), "?%s", SG(request_info).query_string);
			buf[NS_BUF_SIZE]='\0';
		}
		php_register_variable("REQUEST_URI", buf, track_vars_array TSRMLS_CC);

		strlcpy(buf, SG(request_info).request_uri, NS_BUF_SIZE);
		if (rc->path_info) {
			pos = strlen(SG(request_info).request_uri) - strlen(rc->path_info);
			if (pos>=0 && pos<=NS_BUF_SIZE && rc->path_info) {
				buf[pos] = '\0';
			} else {
				buf[0]='\0';
			}
		}
		php_register_variable("SCRIPT_NAME", buf, track_vars_array TSRMLS_CC);
	}
	php_register_variable("SCRIPT_FILENAME", SG(request_info).path_translated, track_vars_array TSRMLS_CC);

	/* special variables in error mode */
	if (rc->http_error) {
		sprintf(buf, "%d", rc->http_error);
		php_register_variable("ERROR_TYPE", buf, track_vars_array TSRMLS_CC);
	}
}

static void nsapi_log_message(char *message)
{
	TSRMLS_FETCH();
	nsapi_request_context *rc = (nsapi_request_context *)SG(server_context);

	log_error(LOG_INFORM, pblock_findval("fn", rc->pb), rc->sn, rc->rq, "%s", message);
}

static time_t sapi_nsapi_get_request_time(TSRMLS_D)
{
	return REQ_TIME( ((nsapi_request_context *)SG(server_context))->rq );
}

static int php_nsapi_startup(sapi_module_struct *sapi_module)
{
	if (php_module_startup(sapi_module, &nsapi_module_entry, 1)==FAILURE) {
		return FAILURE;
	}
	return SUCCESS;
}


static sapi_module_struct nsapi_sapi_module = {
	"nsapi",                                /* name */
	"NSAPI",                                /* pretty name */

	php_nsapi_startup,                      /* startup */
	php_module_shutdown_wrapper,            /* shutdown */

	NULL,                                   /* activate */
	NULL,                                   /* deactivate */

	sapi_nsapi_ub_write,                    /* unbuffered write */
	NULL,                                   /* flush */
	NULL,                                   /* get uid */
	NULL,                                   /* getenv */

	php_error,                              /* error handler */

	sapi_nsapi_header_handler,              /* header handler */
	sapi_nsapi_send_headers,                /* send headers handler */
	NULL,                                   /* send header handler */

	sapi_nsapi_read_post,                   /* read POST data */
	sapi_nsapi_read_cookies,                /* read Cookies */

	sapi_nsapi_register_server_variables,   /* register server variables */
	nsapi_log_message,                      /* Log message */
	sapi_nsapi_get_request_time,			/* Get request time */

	NULL,                                   /* Block interruptions */
	NULL,                                   /* Unblock interruptions */

	STANDARD_SAPI_MODULE_PROPERTIES
};

static void nsapi_php_ini_entries(NSLS_D TSRMLS_DC)
{
	struct pb_entry *entry;
	register int i,j,ok;

	for (i=0; i < NSG(pb)->hsize; i++) {
		entry=NSG(pb)->ht[i];
		while (entry) {
			/* exclude standard entries given to "Service" which should not go into ini entries */
			ok=1;
			for (j=0; nsapi_exclude_from_ini_entries[j]; j++) {
				ok&=(strcasecmp(entry->param->name, nsapi_exclude_from_ini_entries[j])!=0);
			}

			if (ok) {
				/* change the ini entry */
				if (zend_alter_ini_entry(entry->param->name, strlen(entry->param->name)+1,
				 entry->param->value, strlen(entry->param->value),
				 PHP_INI_SYSTEM, PHP_INI_STAGE_ACTIVATE)==FAILURE) {
					log_error(LOG_WARN, pblock_findval("fn", NSG(pb)), NSG(sn), NSG(rq), "Cannot change php.ini key \"%s\" to \"%s\"", entry->param->name, entry->param->value);
				}
			}
			entry=entry->next;
		}
  	}
}

void NSAPI_PUBLIC php5_close(void *vparam)
{
	if (nsapi_sapi_module.shutdown) {
		nsapi_sapi_module.shutdown(&nsapi_sapi_module);
	}

	if (nsapi_sapi_module.php_ini_path_override) {
		free(nsapi_sapi_module.php_ini_path_override);
	}
	
#ifdef PHP_WIN32
	if (nsapi_dll) {
		free(nsapi_dll);
		nsapi_dll = NULL;
	}
#endif	

	tsrm_shutdown();

	log_error(LOG_INFORM, "php5_close", NULL, NULL, "Shutdown PHP Module");
}

/*********************************************************
/ init SAF
/
/ Init fn="php5_init" [php_ini="/path/to/php.ini"] [server_lib="ns-httpdXX.dll"]
/   Initialize the NSAPI module in magnus.conf
/
/ php_ini: gives path to php.ini file
/ server_lib: (only Win32) gives name of DLL (without path) to look for
/  servact_* functions
/
/*********************************************************/
int NSAPI_PUBLIC php5_init(pblock *pb, Session *sn, Request *rq)
{
	php_core_globals *core_globals;
	char *strval;
	int threads=128; /* default for server */

	/* fetch max threads from NSAPI and initialize TSRM with it */
#if defined(pool_maxthreads)
	threads=pool_maxthreads;
	if (threads<1) {
		threads=128; /* default for server */
	}
#endif
	tsrm_startup(threads, 1, 0, NULL);

	core_globals = ts_resource(core_globals_id);

	/* look if php_ini parameter is given to php5_init */
	if (strval = pblock_findval("php_ini", pb)) {
		nsapi_sapi_module.php_ini_path_override = strdup(strval);
	}
	
#ifdef PHP_WIN32
	/* look if server_lib parameter is given to php5_init
	 * (this disables the automatic search for the newest ns-httpdXX.dll) */
	if (strval = pblock_findval("server_lib", pb)) {
		nsapi_dll = strdup(strval);
	}
#endif	

	/* start SAPI */
	sapi_startup(&nsapi_sapi_module);
	nsapi_sapi_module.startup(&nsapi_sapi_module);

	daemon_atrestart(&php5_close, NULL);

	log_error(LOG_INFORM, pblock_findval("fn", pb), sn, rq, "Initialized PHP Module (%d threads exspected)", threads);
	return REQ_PROCEED;
}

/*********************************************************
/ normal use in Service directive:
/
/ Service fn="php5_execute" type=... method=... [inikey=inivalue inikey=inivalue...]
/
/ use in Service for a directory to supply a php-made directory listing instead of server default:
/
/ Service fn="php5_execute" type="magnus-internal/directory" script="/path/to/script.php" [inikey=inivalue inikey=inivalue...]
/
/ use in Error SAF to display php script as error page:
/
/ Error fn="php5_execute" code=XXX script="/path/to/script.php" [inikey=inivalue inikey=inivalue...]
/ Error fn="php5_execute" reason="Reason" script="/path/to/script.php" [inikey=inivalue inikey=inivalue...]
/
/*********************************************************/
int NSAPI_PUBLIC php5_execute(pblock *pb, Session *sn, Request *rq)
{
	int retval;
	nsapi_request_context *request_context;
	zend_file_handle file_handle = {0};
	struct stat fst;

	char *path_info;
	char *query_string    = pblock_findval("query", rq->reqpb);
	char *uri             = pblock_findval("uri", rq->reqpb);
	char *request_method  = pblock_findval("method", rq->reqpb);
	char *content_type    = pblock_findval("content-type", rq->headers);
	char *content_length  = pblock_findval("content-length", rq->headers);
	char *directive       = pblock_findval("Directive", pb);
	int error_directive   = (directive && !strcasecmp(directive, "error"));
	int fixed_script      = 1;

	/* try to use script parameter -> Error or Service for directory listing */
	char *path_translated = pblock_findval("script", pb);

	TSRMLS_FETCH();

	/* if script parameter is missing: normal use as Service SAF  */
	if (!path_translated) {
		path_translated = pblock_findval("path", rq->vars);
		path_info       = pblock_findval("path-info", rq->vars);
		fixed_script = 0;
		if (error_directive) {
			/* go to next error directive if script parameter is missing */
			log_error(LOG_WARN, pblock_findval("fn", pb), sn, rq, "Missing 'script' parameter");
			return REQ_NOACTION;
		}
	} else {
		/* in error the path_info is the uri to the requested page */
		path_info = pblock_findval("uri", rq->reqpb);
	}

	/* check if this uri was included in an other PHP script with nsapi_virtual()
	   by looking for a request context in the current thread */
	if (SG(server_context)) {
		/* send 500 internal server error */
		log_error(LOG_WARN, pblock_findval("fn", pb), sn, rq, "Cannot make nesting PHP requests with nsapi_virtual()");
		if (error_directive) {
			return REQ_NOACTION;
		} else {
			protocol_status(sn, rq, 500, NULL);
			return REQ_ABORTED;
		}
	}

	request_context = (nsapi_request_context *)MALLOC(sizeof(nsapi_request_context));
	request_context->pb = pb;
	request_context->sn = sn;
	request_context->rq = rq;
	request_context->read_post_bytes = 0;
	request_context->fixed_script = fixed_script;
	request_context->http_error = (error_directive) ? rq->status_num : 0;
	request_context->path_info = nsapi_strdup(path_info);

	SG(server_context) = request_context;
	SG(request_info).query_string = nsapi_strdup(query_string);
	SG(request_info).request_uri = nsapi_strdup(uri);
	SG(request_info).request_method = nsapi_strdup(request_method);
	SG(request_info).path_translated = nsapi_strdup(path_translated);
	SG(request_info).content_type = nsapi_strdup(content_type);
	SG(request_info).content_length = (content_length == NULL) ? 0 : strtoul(content_length, 0, 0);
	SG(sapi_headers).http_response_code = (error_directive) ? rq->status_num : 200;
	
	nsapi_php_ini_entries(NSLS_C TSRMLS_CC);

	if (!PG(safe_mode)) php_handle_auth_data(pblock_findval("authorization", rq->headers) TSRMLS_CC);

	file_handle.type = ZEND_HANDLE_FILENAME;
	file_handle.filename = SG(request_info).path_translated;
	file_handle.free_filename = 0;
	file_handle.opened_path = NULL;

	if (stat(SG(request_info).path_translated, &fst)==0 && S_ISREG(fst.st_mode)) {
		if (php_request_startup(TSRMLS_C) == SUCCESS) {
			php_execute_script(&file_handle TSRMLS_CC);
			php_request_shutdown(NULL);
			retval=REQ_PROCEED;
		} else {
			/* send 500 internal server error */
			log_error(LOG_WARN, pblock_findval("fn", pb), sn, rq, "Cannot prepare PHP engine!");
			if (error_directive) {
				retval=REQ_NOACTION;
			} else {
				protocol_status(sn, rq, 500, NULL);
				retval=REQ_ABORTED;
			}
		}
	} else {
		/* send 404 because file not found */
		log_error(LOG_WARN, pblock_findval("fn", pb), sn, rq, "Cannot execute PHP script: %s (File not found)", SG(request_info).path_translated);
		if (error_directive) {
			retval=REQ_NOACTION;
		} else {
			protocol_status(sn, rq, 404, NULL);
			retval=REQ_ABORTED;
		}
	}

	nsapi_free(request_context->path_info);
	nsapi_free(SG(request_info).query_string);
	nsapi_free(SG(request_info).request_uri);
	nsapi_free((void*)(SG(request_info).request_method));
	nsapi_free(SG(request_info).path_translated);
	nsapi_free((void*)(SG(request_info).content_type));

	FREE(request_context);
	SG(server_context) = NULL;

	return retval;
}

/*********************************************************
/ authentication
/
/ we have to make a 'fake' authenticator for netscape so it
/ will pass authentication through to php, and allow us to
/ check authentication with our scripts.
/
/ php5_auth_trans
/   main function called from netscape server to authenticate
/   a line in obj.conf:
/		funcs=php5_auth_trans shlib="path/to/this/phpnsapi.dll"
/	and:
/		<Object ppath="path/to/be/authenticated/by/php/*">
/		AuthTrans fn="php5_auth_trans"
/*********************************************************/
int NSAPI_PUBLIC php5_auth_trans(pblock * pb, Session * sn, Request * rq)
{
	/* This is a DO NOTHING function that allows authentication
	 * information
	 * to be passed through to PHP scripts.
	 */
	return REQ_PROCEED;
}

/*
 * Local variables:
 * tab-width: 4
 * c-basic-offset: 4
 * End:
 * vim600: sw=4 ts=4 fdm=marker
 * vim<600: sw=4 ts=4
 */
