File:  [ELWIX - Embedded LightWeight unIX -] / embedaddon / lighttpd / src / mod_ssi.c
Revision 1.1.1.2 (vendor branch): download - view: text, annotated - select for diffs - revision graph
Sun Jun 15 20:20:06 2014 UTC (10 years ago) by misho
Branches: lighttpd, MAIN
CVS tags: v1_4_35p0, v1_4_35, HEAD
lighttpd 1.4.35

    1: #include "base.h"
    2: #include "log.h"
    3: #include "buffer.h"
    4: #include "stat_cache.h"
    5: 
    6: #include "plugin.h"
    7: #include "stream.h"
    8: 
    9: #include "response.h"
   10: 
   11: #include "mod_ssi.h"
   12: 
   13: #include "inet_ntop_cache.h"
   14: 
   15: #include "sys-socket.h"
   16: 
   17: #include <sys/types.h>
   18: 
   19: #include <ctype.h>
   20: #include <stdlib.h>
   21: #include <stdio.h>
   22: #include <string.h>
   23: #include <errno.h>
   24: #include <time.h>
   25: #include <unistd.h>
   26: 
   27: #ifdef HAVE_PWD_H
   28: # include <pwd.h>
   29: #endif
   30: 
   31: #ifdef HAVE_FORK
   32: # include <sys/wait.h>
   33: #endif
   34: 
   35: #ifdef HAVE_SYS_FILIO_H
   36: # include <sys/filio.h>
   37: #endif
   38: 
   39: #include "etag.h"
   40: #include "version.h"
   41: 
   42: /* The newest modified time of included files for include statement */
   43: static volatile time_t include_file_last_mtime = 0;
   44: 
   45: /* init the plugin data */
   46: INIT_FUNC(mod_ssi_init) {
   47: 	plugin_data *p;
   48: 
   49: 	p = calloc(1, sizeof(*p));
   50: 
   51: 	p->timefmt = buffer_init();
   52: 	p->stat_fn = buffer_init();
   53: 
   54: 	p->ssi_vars = array_init();
   55: 	p->ssi_cgi_env = array_init();
   56: 
   57: 	return p;
   58: }
   59: 
   60: /* detroy the plugin data */
   61: FREE_FUNC(mod_ssi_free) {
   62: 	plugin_data *p = p_d;
   63: 	UNUSED(srv);
   64: 
   65: 	if (!p) return HANDLER_GO_ON;
   66: 
   67: 	if (p->config_storage) {
   68: 		size_t i;
   69: 		for (i = 0; i < srv->config_context->used; i++) {
   70: 			plugin_config *s = p->config_storage[i];
   71: 
   72: 			array_free(s->ssi_extension);
   73: 			buffer_free(s->content_type);
   74: 
   75: 			free(s);
   76: 		}
   77: 		free(p->config_storage);
   78: 	}
   79: 
   80: 	array_free(p->ssi_vars);
   81: 	array_free(p->ssi_cgi_env);
   82: #ifdef HAVE_PCRE_H
   83: 	pcre_free(p->ssi_regex);
   84: #endif
   85: 	buffer_free(p->timefmt);
   86: 	buffer_free(p->stat_fn);
   87: 
   88: 	free(p);
   89: 
   90: 	return HANDLER_GO_ON;
   91: }
   92: 
   93: /* handle plugin config and check values */
   94: 
   95: SETDEFAULTS_FUNC(mod_ssi_set_defaults) {
   96: 	plugin_data *p = p_d;
   97: 	size_t i = 0;
   98: #ifdef HAVE_PCRE_H
   99: 	const char *errptr;
  100: 	int erroff;
  101: #endif
  102: 
  103: 	config_values_t cv[] = {
  104: 		{ "ssi.extension",              NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION },       /* 0 */
  105: 		{ "ssi.content-type",           NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },      /* 1 */
  106: 		{ NULL,                         NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
  107: 	};
  108: 
  109: 	if (!p) return HANDLER_ERROR;
  110: 
  111: 	p->config_storage = calloc(1, srv->config_context->used * sizeof(plugin_config *));
  112: 
  113: 	for (i = 0; i < srv->config_context->used; i++) {
  114: 		plugin_config *s;
  115: 
  116: 		s = calloc(1, sizeof(plugin_config));
  117: 		s->ssi_extension  = array_init();
  118: 		s->content_type = buffer_init();
  119: 
  120: 		cv[0].destination = s->ssi_extension;
  121: 		cv[1].destination = s->content_type;
  122: 
  123: 		p->config_storage[i] = s;
  124: 
  125: 		if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
  126: 			return HANDLER_ERROR;
  127: 		}
  128: 	}
  129: 
  130: #ifdef HAVE_PCRE_H
  131: 	/* allow 2 params */
  132: 	if (NULL == (p->ssi_regex = pcre_compile("<!--#([a-z]+)\\s+(?:([a-z]+)=\"(.*?)(?<!\\\\)\"\\s*)?(?:([a-z]+)=\"(.*?)(?<!\\\\)\"\\s*)?-->", 0, &errptr, &erroff, NULL))) {
  133: 		log_error_write(srv, __FILE__, __LINE__, "sds",
  134: 				"ssi: pcre ",
  135: 				erroff, errptr);
  136: 		return HANDLER_ERROR;
  137: 	}
  138: #else
  139: 	log_error_write(srv, __FILE__, __LINE__, "s",
  140: 			"mod_ssi: pcre support is missing, please recompile with pcre support or remove mod_ssi from the list of modules");
  141: 	return HANDLER_ERROR;
  142: #endif
  143: 
  144: 	return HANDLER_GO_ON;
  145: }
  146: 
  147: static int ssi_env_add(array *env, const char *key, const char *val) {
  148: 	data_string *ds;
  149: 
  150: 	if (NULL == (ds = (data_string *)array_get_unused_element(env, TYPE_STRING))) {
  151: 		ds = data_string_init();
  152: 	}
  153: 	buffer_copy_string(ds->key,   key);
  154: 	buffer_copy_string(ds->value, val);
  155: 
  156: 	array_insert_unique(env, (data_unset *)ds);
  157: 
  158: 	return 0;
  159: }
  160: 
  161: /**
  162:  *
  163:  *  the next two functions are take from fcgi.c
  164:  *
  165:  */
  166: 
  167: static int ssi_env_add_request_headers(server *srv, connection *con, plugin_data *p) {
  168: 	size_t i;
  169: 
  170: 	for (i = 0; i < con->request.headers->used; i++) {
  171: 		data_string *ds;
  172: 
  173: 		ds = (data_string *)con->request.headers->data[i];
  174: 
  175: 		if (ds->value->used && ds->key->used) {
  176: 			size_t j;
  177: 			buffer_reset(srv->tmp_buf);
  178: 
  179: 			/* don't forward the Authorization: Header */
  180: 			if (0 == strcasecmp(ds->key->ptr, "AUTHORIZATION")) {
  181: 				continue;
  182: 			}
  183: 
  184: 			if (0 != strcasecmp(ds->key->ptr, "CONTENT-TYPE")) {
  185: 				buffer_copy_string_len(srv->tmp_buf, CONST_STR_LEN("HTTP_"));
  186: 				srv->tmp_buf->used--;
  187: 			}
  188: 
  189: 			buffer_prepare_append(srv->tmp_buf, ds->key->used + 2);
  190: 			for (j = 0; j < ds->key->used - 1; j++) {
  191: 				char c = '_';
  192: 				if (light_isalpha(ds->key->ptr[j])) {
  193: 					/* upper-case */
  194: 					c = ds->key->ptr[j] & ~32;
  195: 				} else if (light_isdigit(ds->key->ptr[j])) {
  196: 					/* copy */
  197: 					c = ds->key->ptr[j];
  198: 				}
  199: 				srv->tmp_buf->ptr[srv->tmp_buf->used++] = c;
  200: 			}
  201: 			srv->tmp_buf->ptr[srv->tmp_buf->used] = '\0';
  202: 
  203: 			ssi_env_add(p->ssi_cgi_env, srv->tmp_buf->ptr, ds->value->ptr);
  204: 		}
  205: 	}
  206: 
  207: 	for (i = 0; i < con->environment->used; i++) {
  208: 		data_string *ds;
  209: 
  210: 		ds = (data_string *)con->environment->data[i];
  211: 
  212: 		if (ds->value->used && ds->key->used) {
  213: 			size_t j;
  214: 
  215: 			buffer_reset(srv->tmp_buf);
  216: 			buffer_prepare_append(srv->tmp_buf, ds->key->used + 2);
  217: 
  218: 			for (j = 0; j < ds->key->used - 1; j++) {
  219: 				char c = '_';
  220: 				if (light_isalpha(ds->key->ptr[j])) {
  221: 					/* upper-case */
  222: 					c = ds->key->ptr[j] & ~32;
  223: 				} else if (light_isdigit(ds->key->ptr[j])) {
  224: 					/* copy */
  225: 					c = ds->key->ptr[j];
  226: 				}
  227: 				srv->tmp_buf->ptr[srv->tmp_buf->used++] = c;
  228: 			}
  229: 			srv->tmp_buf->ptr[srv->tmp_buf->used] = '\0';
  230: 
  231: 			ssi_env_add(p->ssi_cgi_env, srv->tmp_buf->ptr, ds->value->ptr);
  232: 		}
  233: 	}
  234: 
  235: 	return 0;
  236: }
  237: 
  238: static int build_ssi_cgi_vars(server *srv, connection *con, plugin_data *p) {
  239: 	char buf[32];
  240: 
  241: 	server_socket *srv_sock = con->srv_socket;
  242: 
  243: #ifdef HAVE_IPV6
  244: 	char b2[INET6_ADDRSTRLEN + 1];
  245: #endif
  246: 
  247: #define CONST_STRING(x) \
  248: 		x
  249: 
  250: 	array_reset(p->ssi_cgi_env);
  251: 
  252: 	ssi_env_add(p->ssi_cgi_env, CONST_STRING("SERVER_SOFTWARE"), PACKAGE_DESC);
  253: 	ssi_env_add(p->ssi_cgi_env, CONST_STRING("SERVER_NAME"),
  254: #ifdef HAVE_IPV6
  255: 		     inet_ntop(srv_sock->addr.plain.sa_family,
  256: 			       srv_sock->addr.plain.sa_family == AF_INET6 ?
  257: 			       (const void *) &(srv_sock->addr.ipv6.sin6_addr) :
  258: 			       (const void *) &(srv_sock->addr.ipv4.sin_addr),
  259: 			       b2, sizeof(b2)-1)
  260: #else
  261: 		     inet_ntoa(srv_sock->addr.ipv4.sin_addr)
  262: #endif
  263: 		     );
  264: 	ssi_env_add(p->ssi_cgi_env, CONST_STRING("GATEWAY_INTERFACE"), "CGI/1.1");
  265: 
  266: 	LI_ltostr(buf,
  267: #ifdef HAVE_IPV6
  268: 	       ntohs(srv_sock->addr.plain.sa_family ? srv_sock->addr.ipv6.sin6_port : srv_sock->addr.ipv4.sin_port)
  269: #else
  270: 	       ntohs(srv_sock->addr.ipv4.sin_port)
  271: #endif
  272: 	       );
  273: 
  274: 	ssi_env_add(p->ssi_cgi_env, CONST_STRING("SERVER_PORT"), buf);
  275: 
  276: 	ssi_env_add(p->ssi_cgi_env, CONST_STRING("REMOTE_ADDR"),
  277: 		    inet_ntop_cache_get_ip(srv, &(con->dst_addr)));
  278: 
  279: 	if (con->request.content_length > 0) {
  280: 		/* CGI-SPEC 6.1.2 and FastCGI spec 6.3 */
  281: 
  282: 		/* request.content_length < SSIZE_MAX, see request.c */
  283: 		LI_ltostr(buf, con->request.content_length);
  284: 		ssi_env_add(p->ssi_cgi_env, CONST_STRING("CONTENT_LENGTH"), buf);
  285: 	}
  286: 
  287: 	/*
  288: 	 * SCRIPT_NAME, PATH_INFO and PATH_TRANSLATED according to
  289: 	 * http://cgi-spec.golux.com/draft-coar-cgi-v11-03-clean.html
  290: 	 * (6.1.14, 6.1.6, 6.1.7)
  291: 	 */
  292: 
  293: 	ssi_env_add(p->ssi_cgi_env, CONST_STRING("SCRIPT_NAME"), con->uri.path->ptr);
  294: 	ssi_env_add(p->ssi_cgi_env, CONST_STRING("PATH_INFO"), "");
  295: 
  296: 	/*
  297: 	 * SCRIPT_FILENAME and DOCUMENT_ROOT for php. The PHP manual
  298: 	 * http://www.php.net/manual/en/reserved.variables.php
  299: 	 * treatment of PATH_TRANSLATED is different from the one of CGI specs.
  300: 	 * TODO: this code should be checked against cgi.fix_pathinfo php
  301: 	 * parameter.
  302: 	 */
  303: 
  304: 	if (con->request.pathinfo->used) {
  305: 		ssi_env_add(p->ssi_cgi_env, CONST_STRING("PATH_INFO"), con->request.pathinfo->ptr);
  306: 	}
  307: 
  308: 	ssi_env_add(p->ssi_cgi_env, CONST_STRING("SCRIPT_FILENAME"), con->physical.path->ptr);
  309: 	ssi_env_add(p->ssi_cgi_env, CONST_STRING("DOCUMENT_ROOT"), con->physical.doc_root->ptr);
  310: 
  311: 	ssi_env_add(p->ssi_cgi_env, CONST_STRING("REQUEST_URI"), con->request.uri->ptr);
  312: 	ssi_env_add(p->ssi_cgi_env, CONST_STRING("QUERY_STRING"), con->uri.query->used ? con->uri.query->ptr : "");
  313: 	ssi_env_add(p->ssi_cgi_env, CONST_STRING("REQUEST_METHOD"), get_http_method_name(con->request.http_method));
  314: 	ssi_env_add(p->ssi_cgi_env, CONST_STRING("REDIRECT_STATUS"), "200");
  315: 	ssi_env_add(p->ssi_cgi_env, CONST_STRING("SERVER_PROTOCOL"), get_http_version_name(con->request.http_version));
  316: 
  317: 	ssi_env_add_request_headers(srv, con, p);
  318: 
  319: 	return 0;
  320: }
  321: 
  322: static int process_ssi_stmt(server *srv, connection *con, plugin_data *p, const char **l, size_t n, stat_cache_entry *sce) {
  323: 	size_t i, ssicmd = 0;
  324: 	char buf[255];
  325: 	buffer *b = NULL;
  326: 
  327: 	struct {
  328: 		const char *var;
  329: 		enum { SSI_UNSET, SSI_ECHO, SSI_FSIZE, SSI_INCLUDE, SSI_FLASTMOD,
  330: 				SSI_CONFIG, SSI_PRINTENV, SSI_SET, SSI_IF, SSI_ELIF,
  331: 				SSI_ELSE, SSI_ENDIF, SSI_EXEC } type;
  332: 	} ssicmds[] = {
  333: 		{ "echo",     SSI_ECHO },
  334: 		{ "include",  SSI_INCLUDE },
  335: 		{ "flastmod", SSI_FLASTMOD },
  336: 		{ "fsize",    SSI_FSIZE },
  337: 		{ "config",   SSI_CONFIG },
  338: 		{ "printenv", SSI_PRINTENV },
  339: 		{ "set",      SSI_SET },
  340: 		{ "if",       SSI_IF },
  341: 		{ "elif",     SSI_ELIF },
  342: 		{ "endif",    SSI_ENDIF },
  343: 		{ "else",     SSI_ELSE },
  344: 		{ "exec",     SSI_EXEC },
  345: 
  346: 		{ NULL, SSI_UNSET }
  347: 	};
  348: 
  349: 	for (i = 0; ssicmds[i].var; i++) {
  350: 		if (0 == strcmp(l[1], ssicmds[i].var)) {
  351: 			ssicmd = ssicmds[i].type;
  352: 			break;
  353: 		}
  354: 	}
  355: 
  356: 	switch(ssicmd) {
  357: 	case SSI_ECHO: {
  358: 		/* echo */
  359: 		int var = 0;
  360: 		/* int enc = 0; */
  361: 		const char *var_val = NULL;
  362: 
  363: 		struct {
  364: 			const char *var;
  365: 			enum { SSI_ECHO_UNSET, SSI_ECHO_DATE_GMT, SSI_ECHO_DATE_LOCAL, SSI_ECHO_DOCUMENT_NAME, SSI_ECHO_DOCUMENT_URI,
  366: 					SSI_ECHO_LAST_MODIFIED, SSI_ECHO_USER_NAME } type;
  367: 		} echovars[] = {
  368: 			{ "DATE_GMT",      SSI_ECHO_DATE_GMT },
  369: 			{ "DATE_LOCAL",    SSI_ECHO_DATE_LOCAL },
  370: 			{ "DOCUMENT_NAME", SSI_ECHO_DOCUMENT_NAME },
  371: 			{ "DOCUMENT_URI",  SSI_ECHO_DOCUMENT_URI },
  372: 			{ "LAST_MODIFIED", SSI_ECHO_LAST_MODIFIED },
  373: 			{ "USER_NAME",     SSI_ECHO_USER_NAME },
  374: 
  375: 			{ NULL, SSI_ECHO_UNSET }
  376: 		};
  377: 
  378: /*
  379: 		struct {
  380: 			const char *var;
  381: 			enum { SSI_ENC_UNSET, SSI_ENC_URL, SSI_ENC_NONE, SSI_ENC_ENTITY } type;
  382: 		} encvars[] = {
  383: 			{ "url",          SSI_ENC_URL },
  384: 			{ "none",         SSI_ENC_NONE },
  385: 			{ "entity",       SSI_ENC_ENTITY },
  386: 
  387: 			{ NULL, SSI_ENC_UNSET }
  388: 		};
  389: */
  390: 
  391: 		for (i = 2; i < n; i += 2) {
  392: 			if (0 == strcmp(l[i], "var")) {
  393: 				int j;
  394: 
  395: 				var_val = l[i+1];
  396: 
  397: 				for (j = 0; echovars[j].var; j++) {
  398: 					if (0 == strcmp(l[i+1], echovars[j].var)) {
  399: 						var = echovars[j].type;
  400: 						break;
  401: 					}
  402: 				}
  403: 			} else if (0 == strcmp(l[i], "encoding")) {
  404: /*
  405: 				int j;
  406: 
  407: 				for (j = 0; encvars[j].var; j++) {
  408: 					if (0 == strcmp(l[i+1], encvars[j].var)) {
  409: 						enc = encvars[j].type;
  410: 						break;
  411: 					}
  412: 				}
  413: */
  414: 			} else {
  415: 				log_error_write(srv, __FILE__, __LINE__, "sss",
  416: 						"ssi: unknow attribute for ",
  417: 						l[1], l[i]);
  418: 			}
  419: 		}
  420: 
  421: 		if (p->if_is_false) break;
  422: 
  423: 		if (!var_val) {
  424: 			log_error_write(srv, __FILE__, __LINE__, "sss",
  425: 					"ssi: ",
  426: 					l[1], "var is missing");
  427: 			break;
  428: 		}
  429: 
  430: 		switch(var) {
  431: 		case SSI_ECHO_USER_NAME: {
  432: 			struct passwd *pw;
  433: 
  434: 			b = chunkqueue_get_append_buffer(con->write_queue);
  435: #ifdef HAVE_PWD_H
  436: 			if (NULL == (pw = getpwuid(sce->st.st_uid))) {
  437: 				buffer_copy_long(b, sce->st.st_uid);
  438: 			} else {
  439: 				buffer_copy_string(b, pw->pw_name);
  440: 			}
  441: #else
  442: 			buffer_copy_long(b, sce->st.st_uid);
  443: #endif
  444: 			break;
  445: 		}
  446: 		case SSI_ECHO_LAST_MODIFIED:	{
  447: 			time_t t = sce->st.st_mtime;
  448: 
  449: 			b = chunkqueue_get_append_buffer(con->write_queue);
  450: 			if (0 == strftime(buf, sizeof(buf), p->timefmt->ptr, localtime(&t))) {
  451: 				buffer_copy_string_len(b, CONST_STR_LEN("(none)"));
  452: 			} else {
  453: 				buffer_copy_string(b, buf);
  454: 			}
  455: 			break;
  456: 		}
  457: 		case SSI_ECHO_DATE_LOCAL: {
  458: 			time_t t = time(NULL);
  459: 
  460: 			b = chunkqueue_get_append_buffer(con->write_queue);
  461: 			if (0 == strftime(buf, sizeof(buf), p->timefmt->ptr, localtime(&t))) {
  462: 				buffer_copy_string_len(b, CONST_STR_LEN("(none)"));
  463: 			} else {
  464: 				buffer_copy_string(b, buf);
  465: 			}
  466: 			break;
  467: 		}
  468: 		case SSI_ECHO_DATE_GMT: {
  469: 			time_t t = time(NULL);
  470: 
  471: 			b = chunkqueue_get_append_buffer(con->write_queue);
  472: 			if (0 == strftime(buf, sizeof(buf), p->timefmt->ptr, gmtime(&t))) {
  473: 				buffer_copy_string_len(b, CONST_STR_LEN("(none)"));
  474: 			} else {
  475: 				buffer_copy_string(b, buf);
  476: 			}
  477: 			break;
  478: 		}
  479: 		case SSI_ECHO_DOCUMENT_NAME: {
  480: 			char *sl;
  481: 
  482: 			b = chunkqueue_get_append_buffer(con->write_queue);
  483: 			if (NULL == (sl = strrchr(con->physical.path->ptr, '/'))) {
  484: 				buffer_copy_string_buffer(b, con->physical.path);
  485: 			} else {
  486: 				buffer_copy_string(b, sl + 1);
  487: 			}
  488: 			break;
  489: 		}
  490: 		case SSI_ECHO_DOCUMENT_URI: {
  491: 			b = chunkqueue_get_append_buffer(con->write_queue);
  492: 			buffer_copy_string_buffer(b, con->uri.path);
  493: 			break;
  494: 		}
  495: 		default: {
  496: 			data_string *ds;
  497: 			/* check if it is a cgi-var */
  498: 
  499: 			b = chunkqueue_get_append_buffer(con->write_queue);
  500: 
  501: 			if (NULL != (ds = (data_string *)array_get_element(p->ssi_cgi_env, var_val))) {
  502: 				buffer_copy_string_buffer(b, ds->value);
  503: 			} else {
  504: 				buffer_copy_string_len(b, CONST_STR_LEN("(none)"));
  505: 			}
  506: 
  507: 			break;
  508: 		}
  509: 		}
  510: 		break;
  511: 	}
  512: 	case SSI_INCLUDE:
  513: 	case SSI_FLASTMOD:
  514: 	case SSI_FSIZE: {
  515: 		const char * file_path = NULL, *virt_path = NULL;
  516: 		struct stat st;
  517: 		char *sl;
  518: 
  519: 		for (i = 2; i < n; i += 2) {
  520: 			if (0 == strcmp(l[i], "file")) {
  521: 				file_path = l[i+1];
  522: 			} else if (0 == strcmp(l[i], "virtual")) {
  523: 				virt_path = l[i+1];
  524: 			} else {
  525: 				log_error_write(srv, __FILE__, __LINE__, "sss",
  526: 						"ssi: unknow attribute for ",
  527: 						l[1], l[i]);
  528: 			}
  529: 		}
  530: 
  531: 		if (!file_path && !virt_path) {
  532: 			log_error_write(srv, __FILE__, __LINE__, "sss",
  533: 					"ssi: ",
  534: 					l[1], "file or virtual are missing");
  535: 			break;
  536: 		}
  537: 
  538: 		if (file_path && virt_path) {
  539: 			log_error_write(srv, __FILE__, __LINE__, "sss",
  540: 					"ssi: ",
  541: 					l[1], "only one of file and virtual is allowed here");
  542: 			break;
  543: 		}
  544: 
  545: 
  546: 		if (p->if_is_false) break;
  547: 
  548: 		if (file_path) {
  549: 			/* current doc-root */
  550: 			if (NULL == (sl = strrchr(con->physical.path->ptr, '/'))) {
  551: 				buffer_copy_string_len(p->stat_fn, CONST_STR_LEN("/"));
  552: 			} else {
  553: 				buffer_copy_string_len(p->stat_fn, con->physical.path->ptr, sl - con->physical.path->ptr + 1);
  554: 			}
  555: 
  556: 			buffer_copy_string(srv->tmp_buf, file_path);
  557: 			buffer_urldecode_path(srv->tmp_buf);
  558: 			buffer_path_simplify(srv->tmp_buf, srv->tmp_buf);
  559: 			buffer_append_string_buffer(p->stat_fn, srv->tmp_buf);
  560: 		} else {
  561: 			/* virtual */
  562: 
  563: 			if (virt_path[0] == '/') {
  564: 				buffer_copy_string(p->stat_fn, virt_path);
  565: 			} else {
  566: 				/* there is always a / */
  567: 				sl = strrchr(con->uri.path->ptr, '/');
  568: 
  569: 				buffer_copy_string_len(p->stat_fn, con->uri.path->ptr, sl - con->uri.path->ptr + 1);
  570: 				buffer_append_string(p->stat_fn, virt_path);
  571: 			}
  572: 
  573: 			buffer_urldecode_path(p->stat_fn);
  574: 			buffer_path_simplify(srv->tmp_buf, p->stat_fn);
  575: 
  576: 			/* we have an uri */
  577: 
  578: 			buffer_copy_string_buffer(p->stat_fn, con->physical.doc_root);
  579: 			buffer_append_string_buffer(p->stat_fn, srv->tmp_buf);
  580: 		}
  581: 
  582: 		if (0 == stat(p->stat_fn->ptr, &st)) {
  583: 			time_t t = st.st_mtime;
  584: 
  585: 			switch (ssicmd) {
  586: 			case SSI_FSIZE:
  587: 				b = chunkqueue_get_append_buffer(con->write_queue);
  588: 				if (p->sizefmt) {
  589: 					int j = 0;
  590: 					const char *abr[] = { " B", " kB", " MB", " GB", " TB", NULL };
  591: 
  592: 					off_t s = st.st_size;
  593: 
  594: 					for (j = 0; s > 1024 && abr[j+1]; s /= 1024, j++);
  595: 
  596: 					buffer_copy_off_t(b, s);
  597: 					buffer_append_string(b, abr[j]);
  598: 				} else {
  599: 					buffer_copy_off_t(b, st.st_size);
  600: 				}
  601: 				break;
  602: 			case SSI_FLASTMOD:
  603: 				b = chunkqueue_get_append_buffer(con->write_queue);
  604: 				if (0 == strftime(buf, sizeof(buf), p->timefmt->ptr, localtime(&t))) {
  605: 					buffer_copy_string_len(b, CONST_STR_LEN("(none)"));
  606: 				} else {
  607: 					buffer_copy_string(b, buf);
  608: 				}
  609: 				break;
  610: 			case SSI_INCLUDE:
  611: 				chunkqueue_append_file(con->write_queue, p->stat_fn, 0, st.st_size);
  612: 
  613: 				/* Keep the newest mtime of included files */
  614: 				if (st.st_mtime > include_file_last_mtime)
  615: 				  include_file_last_mtime = st.st_mtime;
  616: 
  617: 				break;
  618: 			}
  619: 		} else {
  620: 			log_error_write(srv, __FILE__, __LINE__, "sbs",
  621: 					"ssi: stating failed ",
  622: 					p->stat_fn, strerror(errno));
  623: 		}
  624: 		break;
  625: 	}
  626: 	case SSI_SET: {
  627: 		const char *key = NULL, *val = NULL;
  628: 		for (i = 2; i < n; i += 2) {
  629: 			if (0 == strcmp(l[i], "var")) {
  630: 				key = l[i+1];
  631: 			} else if (0 == strcmp(l[i], "value")) {
  632: 				val = l[i+1];
  633: 			} else {
  634: 				log_error_write(srv, __FILE__, __LINE__, "sss",
  635: 						"ssi: unknow attribute for ",
  636: 						l[1], l[i]);
  637: 			}
  638: 		}
  639: 
  640: 		if (p->if_is_false) break;
  641: 
  642: 		if (key && val) {
  643: 			data_string *ds;
  644: 
  645: 			if (NULL == (ds = (data_string *)array_get_unused_element(p->ssi_vars, TYPE_STRING))) {
  646: 				ds = data_string_init();
  647: 			}
  648: 			buffer_copy_string(ds->key,   key);
  649: 			buffer_copy_string(ds->value, val);
  650: 
  651: 			array_insert_unique(p->ssi_vars, (data_unset *)ds);
  652: 		} else {
  653: 			log_error_write(srv, __FILE__, __LINE__, "sss",
  654: 					"ssi: var and value have to be set in",
  655: 					l[0], l[1]);
  656: 		}
  657: 		break;
  658: 	}
  659: 	case SSI_CONFIG:
  660: 		if (p->if_is_false) break;
  661: 
  662: 		for (i = 2; i < n; i += 2) {
  663: 			if (0 == strcmp(l[i], "timefmt")) {
  664: 				buffer_copy_string(p->timefmt, l[i+1]);
  665: 			} else if (0 == strcmp(l[i], "sizefmt")) {
  666: 				if (0 == strcmp(l[i+1], "abbrev")) {
  667: 					p->sizefmt = 1;
  668: 				} else if (0 == strcmp(l[i+1], "abbrev")) {
  669: 					p->sizefmt = 0;
  670: 				} else {
  671: 					log_error_write(srv, __FILE__, __LINE__, "sssss",
  672: 							"ssi: unknow value for attribute '",
  673: 							l[i],
  674: 							"' for ",
  675: 							l[1], l[i+1]);
  676: 				}
  677: 			} else {
  678: 				log_error_write(srv, __FILE__, __LINE__, "sss",
  679: 						"ssi: unknow attribute for ",
  680: 						l[1], l[i]);
  681: 			}
  682: 		}
  683: 		break;
  684: 	case SSI_PRINTENV:
  685: 		if (p->if_is_false) break;
  686: 
  687: 		b = chunkqueue_get_append_buffer(con->write_queue);
  688: 		for (i = 0; i < p->ssi_vars->used; i++) {
  689: 			data_string *ds = (data_string *)p->ssi_vars->data[p->ssi_vars->sorted[i]];
  690: 
  691: 			buffer_append_string_buffer(b, ds->key);
  692: 			buffer_append_string_len(b, CONST_STR_LEN("="));
  693: 			buffer_append_string_encoded(b, CONST_BUF_LEN(ds->value), ENCODING_MINIMAL_XML);
  694: 			buffer_append_string_len(b, CONST_STR_LEN("\n"));
  695: 		}
  696: 		for (i = 0; i < p->ssi_cgi_env->used; i++) {
  697: 			data_string *ds = (data_string *)p->ssi_cgi_env->data[p->ssi_cgi_env->sorted[i]];
  698: 
  699: 			buffer_append_string_buffer(b, ds->key);
  700: 			buffer_append_string_len(b, CONST_STR_LEN("="));
  701: 			buffer_append_string_encoded(b, CONST_BUF_LEN(ds->value), ENCODING_MINIMAL_XML);
  702: 			buffer_append_string_len(b, CONST_STR_LEN("\n"));
  703: 		}
  704: 
  705: 		break;
  706: 	case SSI_EXEC: {
  707: 		const char *cmd = NULL;
  708: 		pid_t pid;
  709: 		int from_exec_fds[2];
  710: 
  711: 		for (i = 2; i < n; i += 2) {
  712: 			if (0 == strcmp(l[i], "cmd")) {
  713: 				cmd = l[i+1];
  714: 			} else {
  715: 				log_error_write(srv, __FILE__, __LINE__, "sss",
  716: 						"ssi: unknow attribute for ",
  717: 						l[1], l[i]);
  718: 			}
  719: 		}
  720: 
  721: 		if (p->if_is_false) break;
  722: 
  723: 		/* create a return pipe and send output to the html-page
  724: 		 *
  725: 		 * as exec is assumed evil it is implemented synchronously
  726: 		 */
  727: 
  728: 		if (!cmd) break;
  729: #ifdef HAVE_FORK
  730: 		if (pipe(from_exec_fds)) {
  731: 			log_error_write(srv, __FILE__, __LINE__, "ss",
  732: 					"pipe failed: ", strerror(errno));
  733: 			return -1;
  734: 		}
  735: 
  736: 		/* fork, execve */
  737: 		switch (pid = fork()) {
  738: 		case 0: {
  739: 			/* move stdout to from_rrdtool_fd[1] */
  740: 			close(STDOUT_FILENO);
  741: 			dup2(from_exec_fds[1], STDOUT_FILENO);
  742: 			close(from_exec_fds[1]);
  743: 			/* not needed */
  744: 			close(from_exec_fds[0]);
  745: 
  746: 			/* close stdin */
  747: 			close(STDIN_FILENO);
  748: 
  749: 			execl("/bin/sh", "sh", "-c", cmd, (char *)NULL);
  750: 
  751: 			log_error_write(srv, __FILE__, __LINE__, "sss", "spawing exec failed:", strerror(errno), cmd);
  752: 
  753: 			/* */
  754: 			SEGFAULT();
  755: 			break;
  756: 		}
  757: 		case -1:
  758: 			/* error */
  759: 			log_error_write(srv, __FILE__, __LINE__, "ss", "fork failed:", strerror(errno));
  760: 			break;
  761: 		default: {
  762: 			/* father */
  763: 			int status;
  764: 			ssize_t r;
  765: 			int was_interrupted = 0;
  766: 
  767: 			close(from_exec_fds[1]);
  768: 
  769: 			/* wait for the client to end */
  770: 
  771: 			/*
  772: 			 * OpenBSD and Solaris send a EINTR on SIGCHILD even if we ignore it
  773: 			 */
  774: 			do {
  775: 				if (-1 == waitpid(pid, &status, 0)) {
  776: 					if (errno == EINTR) {
  777: 						was_interrupted++;
  778: 					} else {
  779: 						was_interrupted = 0;
  780: 						log_error_write(srv, __FILE__, __LINE__, "ss", "waitpid failed:", strerror(errno));
  781: 					}
  782: 				} else if (WIFEXITED(status)) {
  783: 					int toread;
  784: 					/* read everything from client and paste it into the output */
  785: 					was_interrupted = 0;
  786: 	
  787: 					while(1) {
  788: 						if (ioctl(from_exec_fds[0], FIONREAD, &toread)) {
  789: 							log_error_write(srv, __FILE__, __LINE__, "s",
  790: 								"unexpected end-of-file (perhaps the ssi-exec process died)");
  791: 							return -1;
  792: 						}
  793: 	
  794: 						if (toread > 0) {
  795: 							b = chunkqueue_get_append_buffer(con->write_queue);
  796: 	
  797: 							buffer_prepare_copy(b, toread + 1);
  798: 	
  799: 							if ((r = read(from_exec_fds[0], b->ptr, b->size - 1)) < 0) {
  800: 								/* read failed */
  801: 								break;
  802: 							} else {
  803: 								b->used = r;
  804: 								b->ptr[b->used++] = '\0';
  805: 							}
  806: 						} else {
  807: 							break;
  808: 						}
  809: 					}
  810: 				} else {
  811: 					was_interrupted = 0;
  812: 					log_error_write(srv, __FILE__, __LINE__, "s", "process exited abnormally");
  813: 				}
  814: 			} while (was_interrupted > 0 && was_interrupted < 4); /* if waitpid() gets interrupted, retry, but max 4 times */
  815: 
  816: 			close(from_exec_fds[0]);
  817: 
  818: 			break;
  819: 		}
  820: 		}
  821: #else
  822: 
  823: 		return -1;
  824: #endif
  825: 
  826: 		break;
  827: 	}
  828: 	case SSI_IF: {
  829: 		const char *expr = NULL;
  830: 
  831: 		for (i = 2; i < n; i += 2) {
  832: 			if (0 == strcmp(l[i], "expr")) {
  833: 				expr = l[i+1];
  834: 			} else {
  835: 				log_error_write(srv, __FILE__, __LINE__, "sss",
  836: 						"ssi: unknow attribute for ",
  837: 						l[1], l[i]);
  838: 			}
  839: 		}
  840: 
  841: 		if (!expr) {
  842: 			log_error_write(srv, __FILE__, __LINE__, "sss",
  843: 					"ssi: ",
  844: 					l[1], "expr missing");
  845: 			break;
  846: 		}
  847: 
  848: 		if ((!p->if_is_false) &&
  849: 		    ((p->if_is_false_level == 0) ||
  850: 		     (p->if_level < p->if_is_false_level))) {
  851: 			switch (ssi_eval_expr(srv, con, p, expr)) {
  852: 			case -1:
  853: 			case 0:
  854: 				p->if_is_false = 1;
  855: 				p->if_is_false_level = p->if_level;
  856: 				break;
  857: 			case 1:
  858: 				p->if_is_false = 0;
  859: 				break;
  860: 			}
  861: 		}
  862: 
  863: 		p->if_level++;
  864: 
  865: 		break;
  866: 	}
  867: 	case SSI_ELSE:
  868: 		p->if_level--;
  869: 
  870: 		if (p->if_is_false) {
  871: 			if ((p->if_level == p->if_is_false_level) &&
  872: 			    (p->if_is_false_endif == 0)) {
  873: 				p->if_is_false = 0;
  874: 			}
  875: 		} else {
  876: 			p->if_is_false = 1;
  877: 
  878: 			p->if_is_false_level = p->if_level;
  879: 		}
  880: 		p->if_level++;
  881: 
  882: 		break;
  883: 	case SSI_ELIF: {
  884: 		const char *expr = NULL;
  885: 		for (i = 2; i < n; i += 2) {
  886: 			if (0 == strcmp(l[i], "expr")) {
  887: 				expr = l[i+1];
  888: 			} else {
  889: 				log_error_write(srv, __FILE__, __LINE__, "sss",
  890: 						"ssi: unknow attribute for ",
  891: 						l[1], l[i]);
  892: 			}
  893: 		}
  894: 
  895: 		if (!expr) {
  896: 			log_error_write(srv, __FILE__, __LINE__, "sss",
  897: 					"ssi: ",
  898: 					l[1], "expr missing");
  899: 			break;
  900: 		}
  901: 
  902: 		p->if_level--;
  903: 
  904: 		if (p->if_level == p->if_is_false_level) {
  905: 			if ((p->if_is_false) &&
  906: 			    (p->if_is_false_endif == 0)) {
  907: 				switch (ssi_eval_expr(srv, con, p, expr)) {
  908: 				case -1:
  909: 				case 0:
  910: 					p->if_is_false = 1;
  911: 					p->if_is_false_level = p->if_level;
  912: 					break;
  913: 				case 1:
  914: 					p->if_is_false = 0;
  915: 					break;
  916: 				}
  917: 			} else {
  918: 				p->if_is_false = 1;
  919: 				p->if_is_false_level = p->if_level;
  920: 				p->if_is_false_endif = 1;
  921: 			}
  922: 		}
  923: 
  924: 		p->if_level++;
  925: 
  926: 		break;
  927: 	}
  928: 	case SSI_ENDIF:
  929: 		p->if_level--;
  930: 
  931: 		if (p->if_level == p->if_is_false_level) {
  932: 			p->if_is_false = 0;
  933: 			p->if_is_false_endif = 0;
  934: 		}
  935: 
  936: 		break;
  937: 	default:
  938: 		log_error_write(srv, __FILE__, __LINE__, "ss",
  939: 				"ssi: unknow ssi-command:",
  940: 				l[1]);
  941: 		break;
  942: 	}
  943: 
  944: 	return 0;
  945: 
  946: }
  947: 
  948: static int mod_ssi_handle_request(server *srv, connection *con, plugin_data *p) {
  949: 	stream s;
  950: #ifdef  HAVE_PCRE_H
  951: 	int i, n;
  952: 
  953: #define N 10
  954: 	int ovec[N * 3];
  955: #endif
  956: 
  957: 	stat_cache_entry *sce = NULL;
  958: 
  959: 
  960: 	/* get a stream to the file */
  961: 
  962: 	array_reset(p->ssi_vars);
  963: 	array_reset(p->ssi_cgi_env);
  964: 	buffer_copy_string_len(p->timefmt, CONST_STR_LEN("%a, %d %b %Y %H:%M:%S %Z"));
  965: 	p->sizefmt = 0;
  966: 	build_ssi_cgi_vars(srv, con, p);
  967: 	p->if_is_false = 0;
  968: 
  969: 	/* Reset the modified time of included files */
  970: 	include_file_last_mtime = 0;
  971: 
  972: 	if (HANDLER_ERROR == stat_cache_get_entry(srv, con, con->physical.path, &sce)) {
  973: 		log_error_write(srv, __FILE__, __LINE__,  "SB", "stat_cache_get_entry failed: ", con->physical.path);
  974: 		return -1;
  975: 	}
  976: 
  977: 	if (-1 == stream_open(&s, con->physical.path)) {
  978: 		log_error_write(srv, __FILE__, __LINE__, "sb",
  979: 				"stream-open: ", con->physical.path);
  980: 		return -1;
  981: 	}
  982: 
  983: 
  984: 	/**
  985: 	 * <!--#element attribute=value attribute=value ... -->
  986: 	 *
  987: 	 * config       DONE
  988: 	 *   errmsg     -- missing
  989: 	 *   sizefmt    DONE
  990: 	 *   timefmt    DONE
  991: 	 * echo         DONE
  992: 	 *   var        DONE
  993: 	 *   encoding   -- missing
  994: 	 * exec         DONE
  995: 	 *   cgi        -- never
  996: 	 *   cmd        DONE
  997: 	 * fsize        DONE
  998: 	 *   file       DONE
  999: 	 *   virtual    DONE
 1000: 	 * flastmod     DONE
 1001: 	 *   file       DONE
 1002: 	 *   virtual    DONE
 1003: 	 * include      DONE
 1004: 	 *   file       DONE
 1005: 	 *   virtual    DONE
 1006: 	 * printenv     DONE
 1007: 	 * set          DONE
 1008: 	 *   var        DONE
 1009: 	 *   value      DONE
 1010: 	 *
 1011: 	 * if           DONE
 1012: 	 * elif         DONE
 1013: 	 * else         DONE
 1014: 	 * endif        DONE
 1015: 	 *
 1016: 	 *
 1017: 	 * expressions
 1018: 	 * AND, OR      DONE
 1019: 	 * comp         DONE
 1020: 	 * ${...}       -- missing
 1021: 	 * $...         DONE
 1022: 	 * '...'        DONE
 1023: 	 * ( ... )      DONE
 1024: 	 *
 1025: 	 *
 1026: 	 *
 1027: 	 * ** all DONE **
 1028: 	 * DATE_GMT
 1029: 	 *   The current date in Greenwich Mean Time.
 1030: 	 * DATE_LOCAL
 1031: 	 *   The current date in the local time zone.
 1032: 	 * DOCUMENT_NAME
 1033: 	 *   The filename (excluding directories) of the document requested by the user.
 1034: 	 * DOCUMENT_URI
 1035: 	 *   The (%-decoded) URL path of the document requested by the user. Note that in the case of nested include files, this is not then URL for the current document.
 1036: 	 * LAST_MODIFIED
 1037: 	 *   The last modification date of the document requested by the user.
 1038: 	 * USER_NAME
 1039: 	 *   Contains the owner of the file which included it.
 1040: 	 *
 1041: 	 */
 1042: #ifdef HAVE_PCRE_H
 1043: 	for (i = 0; (n = pcre_exec(p->ssi_regex, NULL, s.start, s.size, i, 0, ovec, N * 3)) > 0; i = ovec[1]) {
 1044: 		const char **l;
 1045: 		/* take everything from last offset to current match pos */
 1046: 
 1047: 		if (!p->if_is_false) chunkqueue_append_file(con->write_queue, con->physical.path, i, ovec[0] - i);
 1048: 
 1049: 		pcre_get_substring_list(s.start, ovec, n, &l);
 1050: 		process_ssi_stmt(srv, con, p, l, n, sce);
 1051: 		pcre_free_substring_list(l);
 1052: 	}
 1053: 
 1054: 	switch(n) {
 1055: 	case PCRE_ERROR_NOMATCH:
 1056: 		/* copy everything/the rest */
 1057: 		chunkqueue_append_file(con->write_queue, con->physical.path, i, s.size - i);
 1058: 
 1059: 		break;
 1060: 	default:
 1061: 		log_error_write(srv, __FILE__, __LINE__, "sd",
 1062: 				"execution error while matching: ", n);
 1063: 		break;
 1064: 	}
 1065: #endif
 1066: 
 1067: 
 1068: 	stream_close(&s);
 1069: 
 1070: 	con->file_started  = 1;
 1071: 	con->file_finished = 1;
 1072: 	con->mode = p->id;
 1073: 
 1074: 	if (p->conf.content_type->used <= 1) {
 1075: 		response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html"));
 1076: 	} else {
 1077: 		response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(p->conf.content_type));
 1078: 	}
 1079: 
 1080: 	{
 1081: 		/* Generate "ETag" & "Last-Modified" headers */
 1082: 		time_t lm_time = 0;
 1083: 		buffer *mtime = NULL;
 1084: 
 1085: 		etag_mutate(con->physical.etag, sce->etag);
 1086: 		response_header_overwrite(srv, con, CONST_STR_LEN("ETag"), CONST_BUF_LEN(con->physical.etag));
 1087: 
 1088: 		if (sce->st.st_mtime > include_file_last_mtime)
 1089: 			lm_time = sce->st.st_mtime;
 1090: 		else
 1091: 			lm_time = include_file_last_mtime;
 1092: 
 1093: 		mtime = strftime_cache_get(srv, lm_time);
 1094: 		response_header_overwrite(srv, con, CONST_STR_LEN("Last-Modified"), CONST_BUF_LEN(mtime));
 1095: 	}
 1096: 
 1097: 	/* Reset the modified time of included files */
 1098: 	include_file_last_mtime = 0;
 1099: 
 1100: 	/* reset physical.path */
 1101: 	buffer_reset(con->physical.path);
 1102: 
 1103: 	return 0;
 1104: }
 1105: 
 1106: #define PATCH(x) \
 1107: 	p->conf.x = s->x;
 1108: static int mod_ssi_patch_connection(server *srv, connection *con, plugin_data *p) {
 1109: 	size_t i, j;
 1110: 	plugin_config *s = p->config_storage[0];
 1111: 
 1112: 	PATCH(ssi_extension);
 1113: 	PATCH(content_type);
 1114: 
 1115: 	/* skip the first, the global context */
 1116: 	for (i = 1; i < srv->config_context->used; i++) {
 1117: 		data_config *dc = (data_config *)srv->config_context->data[i];
 1118: 		s = p->config_storage[i];
 1119: 
 1120: 		/* condition didn't match */
 1121: 		if (!config_check_cond(srv, con, dc)) continue;
 1122: 
 1123: 		/* merge config */
 1124: 		for (j = 0; j < dc->value->used; j++) {
 1125: 			data_unset *du = dc->value->data[j];
 1126: 
 1127: 			if (buffer_is_equal_string(du->key, CONST_STR_LEN("ssi.extension"))) {
 1128: 				PATCH(ssi_extension);
 1129: 			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN("ssi.content-type"))) {
 1130: 				PATCH(content_type);
 1131: 			}
 1132: 		}
 1133: 	}
 1134: 
 1135: 	return 0;
 1136: }
 1137: #undef PATCH
 1138: 
 1139: URIHANDLER_FUNC(mod_ssi_physical_path) {
 1140: 	plugin_data *p = p_d;
 1141: 	size_t k;
 1142: 
 1143: 	if (con->mode != DIRECT) return HANDLER_GO_ON;
 1144: 
 1145: 	if (con->physical.path->used == 0) return HANDLER_GO_ON;
 1146: 
 1147: 	mod_ssi_patch_connection(srv, con, p);
 1148: 
 1149: 	for (k = 0; k < p->conf.ssi_extension->used; k++) {
 1150: 		data_string *ds = (data_string *)p->conf.ssi_extension->data[k];
 1151: 
 1152: 		if (ds->value->used == 0) continue;
 1153: 
 1154: 		if (buffer_is_equal_right_len(con->physical.path, ds->value, ds->value->used - 1)) {
 1155: 			/* handle ssi-request */
 1156: 
 1157: 			if (mod_ssi_handle_request(srv, con, p)) {
 1158: 				/* on error */
 1159: 				con->http_status = 500;
 1160: 				con->mode = DIRECT;
 1161: 			}
 1162: 
 1163: 			return HANDLER_FINISHED;
 1164: 		}
 1165: 	}
 1166: 
 1167: 	/* not found */
 1168: 	return HANDLER_GO_ON;
 1169: }
 1170: 
 1171: /* this function is called at dlopen() time and inits the callbacks */
 1172: 
 1173: int mod_ssi_plugin_init(plugin *p);
 1174: int mod_ssi_plugin_init(plugin *p) {
 1175: 	p->version     = LIGHTTPD_VERSION_ID;
 1176: 	p->name        = buffer_init_string("ssi");
 1177: 
 1178: 	p->init        = mod_ssi_init;
 1179: 	p->handle_subrequest_start = mod_ssi_physical_path;
 1180: 	p->set_defaults  = mod_ssi_set_defaults;
 1181: 	p->cleanup     = mod_ssi_free;
 1182: 
 1183: 	p->data        = NULL;
 1184: 
 1185: 	return 0;
 1186: }

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>