Skip to content
Snippets Groups Projects
websrvr.c 196 KiB
Newer Older
deuce's avatar
deuce committed
					free(body);
					break;
				default:
					lprintf(LOG_ERR, "Unhandled FastCGI message type %d", cd->header.type);
					// Read and discard the entire message...
					body = fastcgi_read_body(cd->sock);
					if (body == NULL)
						return ret;
deuce's avatar
deuce committed
					free(body);
					break;
			}
deuce's avatar
deuce committed
	else
		ret |= CGI_PROCESS_TERMINATED;

	return ret;
}

static int fastcgi_read(void *arg, char *buf, size_t sz)
{
	struct fastcgi_data *cd = (struct fastcgi_data *)arg;

deuce's avatar
deuce committed
	if (cd->body == NULL) {
		cd->body = fastcgi_read_body(cd->sock);
		if (cd->body == NULL)
			return -1;
	}

	if (sz > (cd->body->len - cd->used))
		sz = cd->body->len - cd->used;

deuce's avatar
deuce committed
	memcpy(buf, cd->body->data + cd->used, sz);
	cd->used += sz;
	if (cd->used >= cd->body->len) {
		FREE_AND_NULL(cd->body);
		cd->header.type = 0;
		cd->used = 0;
	}
	return sz;
}

/*
 * This one is extra tricky since it may need multiple messages to fill...
 * and those messages may not follow each other in the stream.
 * For now, we just hack and hope.
 */
static int fastcgi_readln_out(void *arg, char *buf, size_t bufsz, char *fbuf, size_t fbufsz)
{
	size_t inpos, outpos;
	struct fastcgi_data *cd = (struct fastcgi_data *)arg;

	outpos = 0;

deuce's avatar
deuce committed
	if (cd->body == NULL) {
		cd->body = fastcgi_read_body(cd->sock);
		if (cd->body == NULL)
			return -1;
	}

deuce's avatar
deuce committed
	for (outpos = 0, inpos = cd->used; inpos < cd->body->len && outpos < bufsz; inpos++) {
		if (cd->body->data[inpos] == '\n') {
			inpos++;
			break;
		buf[outpos++] = cd->body->data[inpos];
	}
deuce's avatar
deuce committed
	if (outpos > 0 && buf[outpos - 1] == '\r')
		outpos--;
deuce's avatar
deuce committed
	// Terminate... even if we need to truncate.
	if (outpos >= bufsz)
		outpos--;
	buf[outpos] = 0;
deuce's avatar
deuce committed

	cd->used = inpos;

	if (cd->used >= cd->body->len) {
		FREE_AND_NULL(cd->body);
		cd->header.type = 0;
		cd->used = 0;
	return outpos;
}
static int fastcgi_write_in(void *arg, char *buf, size_t bufsz)
{
	struct fastcgi_header head;
	struct fastcgi_data *cd = (struct fastcgi_data *)arg;

	fastcgi_init_header(&head, FCGI_STDIN);
	for (pos = 0; pos < bufsz;) {
		chunk_size = bufsz - pos;
		if (chunk_size > UINT16_MAX)
			chunk_size = UINT16_MAX;
		head.len = htons(chunk_size);
		if (sendsocket(cd->sock, (void *)&head, sizeof(head)) != sizeof(head))
			return -1;
		if (sendsocket(cd->sock, buf+pos, chunk_size) != chunk_size)
			return -1;
		pos += chunk_size;
	}
	return bufsz;
}

static int fastcgi_done_wait(void *arg)
{
	struct fastcgi_data *cd = (struct fastcgi_data *)arg;

	return (!socket_check(cd->sock, NULL, NULL, /* timeout: */0));
}

#ifdef __unix__
struct cgi_data {
	int out_pipe;	// out_pipe[0]
	int err_pipe;	// err_pipe[0]
	pid_t child;	// child
};

static int cgi_read_wait_timeout(void *arg)
{
	int ret = 0;
	int status=0;
	int high_fd;
	fd_set read_set;
	fd_set write_set;
	struct cgi_data *cd = (struct cgi_data *)arg;
	struct timeval tv;

	high_fd = cd->err_pipe;
	if (cd->out_pipe > cd->err_pipe)
		high_fd = cd->out_pipe;
	tv.tv_sec=startup->max_cgi_inactivity;
	tv.tv_usec=0;

	FD_ZERO(&read_set);
	FD_SET(cd->out_pipe,&read_set);
	FD_SET(cd->err_pipe,&read_set);
	FD_ZERO(&write_set);

	if(select(high_fd+1,&read_set,&write_set,NULL,&tv)>0)  {
		if (FD_ISSET(cd->out_pipe,&read_set))
			ret |= CGI_OUTPUT_READY;
		if(FD_ISSET(cd->err_pipe,&read_set))
			ret |= CGI_ERROR_READY;
	if (waitpid(cd->child,&status,WNOHANG)==cd->child)
		ret |= CGI_PROCESS_TERMINATED;
	return ret;
}
static int cgi_read_out(void *arg, char *buf, size_t sz)
{
	struct cgi_data *cd = (struct cgi_data *)arg;
	return read(cd->out_pipe,buf,sz);
}
static int cgi_read_err(void *arg, char *buf, size_t sz)
{
	struct cgi_data *cd = (struct cgi_data *)arg;

	return read(cd->err_pipe,buf,sz);
}

static int cgi_readln_out(void *arg, char *buf, size_t bufsz, char *fbuf, size_t fbufsz)
{
	struct cgi_data *cd = (struct cgi_data *)arg;

	return pipereadline(cd->out_pipe, buf, bufsz, fbuf, fbufsz);
}

static int cgi_write_in(void *arg, char *buf, size_t bufsz)
{
	// *nix doesn't have an input pipe
	return 0;
}

static int cgi_done_wait(void *arg)
{
	int		status=0;
	struct cgi_data *cd = (struct cgi_data *)arg;

	return waitpid(cd->child,&status,WNOHANG)==cd->child;
}
#else
struct cgi_data {
	HANDLE rdpipe;
	HANDLE wrpipe;
	HANDLE child;
	http_session_t *session;
};

static int cgi_read_wait_timeout(void *arg)
{
	int ret = 0;
	struct cgi_data *cd = (struct cgi_data *)arg;

	DWORD waiting;
	time_t end = time(NULL) + startup->max_cgi_inactivity;

	while(ret == 0) {
		if(WaitForSingleObject(cd->child,0)==WAIT_OBJECT_0)
			ret |= CGI_PROCESS_TERMINATED;
		waiting = 0;
		PeekNamedPipe(
			cd->rdpipe,         /* handle to pipe to copy from */
			NULL,               /* pointer to data buffer */
			0,					/* size, in bytes, of data buffer */
			NULL,				/* pointer to number of bytes read */
			&waiting,			/* pointer to total number of bytes available */
			NULL				/* pointer to unread bytes in this message */
		);
		if(waiting)
			ret |= CGI_OUTPUT_READY;
		if(!session_check(cd->session, &rd, NULL, /* timeout: */0))
			ret |= CGI_INPUT_READY;
		if (rd)
			ret |= CGI_INPUT_READY;
		if (time(NULL) >= end)
			break;
		if (ret == 0)
			Sleep(1);
	}
	return ret;
}

static int cgi_read_out(void *arg, char *buf, size_t sz)
{
	DWORD msglen = 0;
	struct cgi_data *cd = (struct cgi_data *)arg;

	if(ReadFile(cd->rdpipe,buf,sz,&msglen,NULL)==FALSE) {
		lprintf(LOG_ERR,"%04d !ERROR %d reading from pipe"
			,cd->session->socket,GetLastError());
}

static int cgi_read_err(void *arg, char *buf, size_t sz)
{
	// Win32 doesn't have an error pipe
	return 0;
}

static int cgi_readln_out(void *arg, char *buf, size_t bufsz, char *fbuf, size_t fbufsz)
{
	struct cgi_data *cd = (struct cgi_data *)arg;

	return pipereadline(cd->rdpipe, buf, bufsz, NULL, 0);
}

static int cgi_write_in(void *arg, char *buf, size_t bufsz)
{
	int wr;
	struct cgi_data *cd = (struct cgi_data *)arg;

	WriteFile(cd->wrpipe, buf, bufsz, &wr, /* Overlapped: */NULL);
	return wr;
}

static int cgi_done_wait(void *arg)
{
	struct cgi_data *cd = (struct cgi_data *)arg;

	return (WaitForSingleObject(cd->child,0)==WAIT_OBJECT_0);
}
#endif

struct cgi_api {
	int (*read_wait_timeout)(void *arg);
	int (*read_out)(void *arg, char *buf, size_t sz);
	int (*read_err)(void *arg, char *buf, size_t sz);
	int (*readln_out)(void *arg, char *buf, size_t bufsz, char *fbuf, size_t fbufsz);
	int (*write_in)(void *arg, char *buf, size_t bufsz);
	int (*done_wait)(void *arg);
	void *arg;
};

/*
 * Need to return:
 * Success/fail
 * Timeout out or not
 * Done parsing headers or not
 * Got valid headers or not
 * Process exited or not.
 */

static int do_cgi_stuff(http_session_t *session, struct cgi_api *cgi, BOOL orig_keep)
{
	int ret = 0;
#define CGI_STUFF_FAILED			(1<<0)
#define CGI_STUFF_TIMEDOUT			(1<<1)
#define CGI_STUFF_DONE_PARSING		(1<<2)
#define CGI_STUFF_VALID_HEADERS		(1<<3)
#define CGI_STUFF_PROCESS_EXITED	(1<<4)
	int ready;
	int i;
	char cgi_status[MAX_REQUEST_LINE+1];
	char header[MAX_REQUEST_LINE+1];
	char buf[1024];
	char fbuf[1026];
	char *directive=NULL;
	char *value=NULL;
	char *last;
	BOOL done_reading=FALSE;
	BOOL done_wait=FALSE;
	BOOL no_chunked=FALSE;
	BOOL set_chunked=FALSE;
	time_t start;
	str_list_t	tmpbuf;

	start=time(NULL);
	/* ToDo: Magically set done_parsing_headers for nph-* scripts */
	cgi_status[0]=0;
deuce's avatar
deuce committed
	/* FREE()d following this block */
deuce's avatar
deuce committed
	tmpbuf=strListInit();
	while(!done_reading) {
		ready = cgi->read_wait_timeout(cgi->arg);
		if(ready)  {
			if(ready & CGI_OUTPUT_READY) {
				if((ret & CGI_STUFF_DONE_PARSING) && (ret & CGI_STUFF_VALID_HEADERS))  {
					i=cgi->read_out(cgi->arg,buf,sizeof(buf));
					if(i!=-1 && i!=0)  {
						if(session->req.method!=HTTP_HEAD) {
							if(session->req.ld!=NULL) {
								session->req.ld->size+=snt;
					i=cgi->readln_out(cgi->arg, buf, sizeof(buf), fbuf, sizeof(fbuf));
deuce's avatar
deuce committed
					if(i==-1) {
						ret |= CGI_STUFF_VALID_HEADERS;
deuce's avatar
deuce committed
					if(!(ret & CGI_STUFF_DONE_PARSING) && *buf)  {
deuce's avatar
deuce committed
						if(tmpbuf != NULL)
							strListPush(&tmpbuf, fbuf);
						directive=strtok_r(header,":",&last);
							value=strtok_r(NULL,"",&last);
							i=get_header_type(directive);
							switch (i)  {
								case HEAD_LOCATION:
									ret |= CGI_STUFF_VALID_HEADERS;
									if(*value=='/')  {
										unescape(value);
										SAFECOPY(session->req.virtual_path,value);
										session->req.send_location=MOVED_STAT;
										if(cgi_status[0]==0)
									} else  {
										SAFECOPY(session->req.virtual_path,value);
										session->req.send_location=MOVED_TEMP;
									}
									break;
								case HEAD_STATUS:
									SAFECOPY(cgi_status,value);
									break;
									session->req.keep_alive=orig_keep;
									strListPush(&session->req.dynamic_heads,buf);
									no_chunked=TRUE;
									ret |= CGI_STUFF_VALID_HEADERS;
									strListPush(&session->req.dynamic_heads,buf);
								case HEAD_TRANSFER_ENCODING:
									no_chunked=TRUE;
									break;
									strListPush(&session->req.dynamic_heads,buf);
						if(directive == NULL || value == NULL) {
							/* Invalid header line */
							ret |= CGI_STUFF_DONE_PARSING;
						if(!no_chunked && session->http_ver>=HTTP_1_1) {
							session->req.keep_alive=orig_keep;
							if (session->req.method != HTTP_HEAD)
								set_chunked=TRUE;
						if(ret & CGI_STUFF_VALID_HEADERS)  {
							session->req.dynamic=IS_CGI;
							if(cgi_status[0]==0)
								SAFECOPY(cgi_status,session->req.status);
							send_headers(session,cgi_status,set_chunked);
						}
						else {
							/* Invalid headers... send 'er all as plain-text */
deuce's avatar
deuce committed
							char    content_type[MAX_REQUEST_LINE+1];
deuce's avatar
deuce committed

							lprintf(LOG_DEBUG,"%04d Recieved invalid CGI headers, sending result as plain-text",session->socket);

deuce's avatar
deuce committed
							/* free() the non-headers so they don't get sent, then recreate the list */
							strListFreeStrings(session->req.dynamic_heads);

							/* Copy current status */
							SAFECOPY(cgi_status,session->req.status);

							/* Add the content-type header (REQUIRED) */
							SAFEPRINTF2(content_type,"%s: %s",get_header(HEAD_TYPE),startup->default_cgi_content);
							strListPush(&session->req.dynamic_heads,content_type);
							send_headers(session,cgi_status,FALSE);
deuce's avatar
deuce committed

							/* Now send the tmpbuf */
							for(i=0; tmpbuf != NULL && tmpbuf[i] != NULL; i++) {
								if(strlen(tmpbuf[i])>0) {
									snt=writebuf(session,tmpbuf[i],strlen(tmpbuf[i]));
									if(session->req.ld!=NULL) {
										session->req.ld->size+=snt;
									}
								}
							}
							if(strlen(fbuf)>0) {
								snt=writebuf(session,fbuf,strlen(fbuf));
deuce's avatar
deuce committed
								if(session->req.ld!=NULL && snt>0) {
									session->req.ld->size+=snt;
								}
							ret |= CGI_STUFF_VALID_HEADERS;
deuce's avatar
deuce committed
						}
						ret |= CGI_STUFF_DONE_PARSING;
			if(ready & CGI_ERROR_READY)  {
				i=cgi->read_err(cgi->arg,buf,sizeof(buf)-1);
				if(i>0) {
					buf[i]=0;
					lprintf(LOG_ERR,"%04d CGI Error: %s",session->socket,buf);
			if(ready & CGI_INPUT_READY) {
				/* Send received POST Data to stdin of CGI process */
				if((i=sess_recv(session, buf, sizeof(buf), 0)) > 0)  {
					lprintf(LOG_DEBUG,"%04d CGI Received %d bytes of POST data"
						,session->socket, i);
					cgi->write_in(cgi->arg, buf, i);
				}
			}
deuce's avatar
deuce committed
			if (ready & CGI_PROCESS_TERMINATED) {
				ret |= CGI_STUFF_PROCESS_EXITED;
deuce's avatar
deuce committed
				done_wait = TRUE;
			}
			if(!done_wait)
				done_wait = cgi->done_wait(cgi->arg);
			if((!(ready & (CGI_OUTPUT_READY|CGI_ERROR_READY))) && done_wait)
		}
		else  {
			if((time(NULL)-start) >= startup->max_cgi_inactivity)  {
				lprintf(LOG_ERR,"%04d CGI Process %s Timed out",session->socket,getfname(session->req.physical_path));
				ret |= CGI_STUFF_TIMEDOUT;
deuce's avatar
deuce committed
	if(tmpbuf != NULL)
		strListFree(&tmpbuf);

	return ret;
}

static BOOL exec_fastcgi(http_session_t *session)
{
	int msglen;
	BOOL orig_keep=FALSE;
	SOCKET sock;
	struct fastcgi_message *msg;
	struct fastcgi_begin_request *br;
	struct fastcgi_data cd;
	struct cgi_api cgi = {
		.read_wait_timeout = fastcgi_read_wait_timeout,
		.read_out = fastcgi_read,
		.read_err = fastcgi_read,
		.readln_out = fastcgi_readln_out,
		.write_in = fastcgi_write_in,
		.done_wait = fastcgi_done_wait,
		.arg = &cd
	};

	lprintf(LOG_INFO,"%04d Executing FastCGI: %s",session->socket,session->req.physical_path);
	if (session->req.fastcgi_socket == NULL) {
		lprintf(LOG_ERR, "%04d No FastCGI socket configured!",session->socket);
		return FALSE;
	}

	orig_keep=session->req.keep_alive;
	session->req.keep_alive=FALSE;

	sock = fastcgi_connect(session->req.fastcgi_socket, session->socket);
	if (sock == INVALID_SOCKET)
		return FALSE;

	// Set up request...
	msglen = sizeof(struct fastcgi_header) + sizeof(struct fastcgi_begin_request);
	msg = (struct fastcgi_message *)malloc(msglen);
	if (msg == NULL) {
		closesocket(sock);
		lprintf(LOG_ERR, "%04d Failure to allocate memory for FastCGI message!", session->socket);
		return FALSE;
	}
	fastcgi_init_header(&msg->head, FCGI_BEGIN_REQUEST);
deuce's avatar
deuce committed
	msg->head.len = htons(sizeof(struct fastcgi_begin_request));
	br = (struct fastcgi_begin_request *)&msg->body;
deuce's avatar
deuce committed
	br->role = htons(FCGI_RESPONDER);
	br->flags = 0;
	memset(br->reserved, 0, sizeof(br->reserved));
	if (sendsocket(sock, (void *)msg, msglen) != msglen) {
		free(msg);
		closesocket(sock);
		lprintf(LOG_WARNING, "%04d Failure to send to FastCGI socket!", session->socket);
		return FALSE;
	}
	if (!fastcgi_send_params(sock, session)) {
		free(msg);
		closesocket(sock);
		return FALSE;
	}

	memset(&cd, 0, sizeof(cd));
	cd.sock = sock;
	fastcgi_write_in(&cd, session->req.post_data, session->req.post_len);
	msg->head.len = 0;
deuce's avatar
deuce committed
	msg->head.type = FCGI_STDIN;
	if (sendsocket(sock, (void *)msg, sizeof(struct fastcgi_header)) != sizeof(struct fastcgi_header)) {
		free(msg);
		closesocket(sock);
		lprintf(LOG_WARNING, "%04d Failure to send stdin to FastCGI socket!", session->socket);
		return FALSE;
	}
	free(msg);

	// Now handle stuff coming back from the FastCGI socket...
	int ret = do_cgi_stuff(session, &cgi, orig_keep);
	FREE_AND_NULL(cd.body);
	closesocket(sock);

	if(!(ret & CGI_STUFF_VALID_HEADERS)) {
		lprintf(LOG_ERR,"%04d FastCGI Process did not generate valid headers", session->socket);
		return(FALSE);
	}

	if(!(ret & CGI_STUFF_DONE_PARSING)) {
		lprintf(LOG_ERR,"%04d FastCGI Process did not send data header termination", session->socket);
		return(FALSE);
	}

	return TRUE;
}

static BOOL exec_cgi(http_session_t *session)
{
	struct cgi_data cd;
	struct cgi_api cgi = {
		.read_wait_timeout = cgi_read_wait_timeout,
		.read_out = cgi_read_out,
		.read_err = cgi_read_err,
		.readln_out = cgi_readln_out,
		.write_in = cgi_write_in,
		.done_wait = cgi_done_wait,
		.arg = &cd
	};
#ifdef __unix__
	char	cmdline[MAX_PATH+256];
	int		i=0;
	int		status=0;
	pid_t	child=0;
	int		out_pipe[2];
	int		err_pipe[2];
	struct timeval tv={0,0};
	fd_set	read_set;
	int		high_fd=0;
	char	buf[1024];
	BOOL	done_parsing_headers=FALSE;
	BOOL	done_wait=FALSE;
	BOOL	got_valid_headers=FALSE;
	time_t	start;
	char	cgipath[MAX_PATH+1];
	char	*p;
	BOOL	orig_keep=FALSE;

	SAFECOPY(cmdline,session->req.physical_path);

	lprintf(LOG_INFO,"%04d Executing CGI: %s",session->socket,cmdline);

	orig_keep=session->req.keep_alive;
	session->req.keep_alive=FALSE;

	/* Set up I/O pipes */

	if(pipe(out_pipe)!=0) {
		lprintf(LOG_ERR,"%04d Can't create out_pipe",session->socket);
		return(FALSE);
	}

	if(pipe(err_pipe)!=0) {
		lprintf(LOG_ERR,"%04d Can't create err_pipe",session->socket);
		return(FALSE);
	}

	if((child=fork())==0)  {
		str_list_t  env_list;

		/* Do a full suid thing. */
		if(startup->setuid!=NULL)
			startup->setuid(TRUE);

		env_list=get_cgi_env(session);

		/* Set up STDIO */
		dup2(session->socket,0);		/* redirect stdin */
		close(out_pipe[0]);		/* close read-end of pipe */
		dup2(out_pipe[1],1);	/* stdout */
		close(out_pipe[1]);		/* close excess file descriptor */
		close(err_pipe[0]);		/* close read-end of pipe */
		dup2(err_pipe[1],2);	/* stderr */
		close(err_pipe[1]);		/* close excess file descriptor */

		SAFECOPY(cgipath,cmdline);
		if((p=strrchr(cgipath,'/'))!=NULL)
		{
			*p=0;
			chdir(cgipath);
		}

		/* Execute command */
		if((p=get_cgi_handler(cmdline))!=NULL) {
			char* shell=os_cmdshell();
			lprintf(LOG_INFO,"%04d Using handler %s to execute %s",session->socket,p,cmdline);
			execle(shell,shell,"-c",p,cmdline,NULL,env_list);
		}
		else {
			execle(cmdline,cmdline,NULL,env_list);
		}

		lprintf(LOG_ERR,"%04d !FAILED! execle() (%d)",session->socket,errno);
		exit(EXIT_FAILURE); /* Should never happen */
	}

	if(child==-1)  {
		lprintf(LOG_ERR,"%04d !FAILED! fork() errno=%d",session->socket,errno);
		close(out_pipe[0]);		/* close read-end of pipe */
		close(err_pipe[0]);		/* close read-end of pipe */
	}

	close(out_pipe[1]);		/* close excess file descriptor */
	close(err_pipe[1]);		/* close excess file descriptor */

	if(child==-1)
		return(FALSE);

	start=time(NULL);

	high_fd=out_pipe[0];
	if(err_pipe[0]>high_fd)
		high_fd=err_pipe[0];

	cd.out_pipe = out_pipe[0];
	cd.err_pipe = err_pipe[0];
	cd.child = child;

	int ret = do_cgi_stuff(session, &cgi, orig_keep);
	if (ret & CGI_STUFF_DONE_PARSING)
		done_parsing_headers = TRUE;
	if (ret & CGI_STUFF_PROCESS_EXITED)
		done_wait = TRUE;
	if (ret & CGI_STUFF_TIMEDOUT)
		start = 1;
	if (ret & CGI_STUFF_VALID_HEADERS)
		got_valid_headers = TRUE;

	if(!done_wait)
		done_wait = (waitpid(child,&status,WNOHANG)==child);
	if(!done_wait)  {
		if(start)
			lprintf(LOG_NOTICE,"%04d CGI Process %s still alive on client exit"
				,session->socket,getfname(cmdline));
		kill(child,SIGTERM);
		mswait(1000);
		done_wait = (waitpid(child,&status,WNOHANG)==child);
		if(!done_wait)  {
			kill(child,SIGKILL);
			done_wait = (waitpid(child,&status,0)==child);
		}
	}

	/* Drain STDERR & STDOUT */	
	tv.tv_sec=1;
	tv.tv_usec=0;
	FD_ZERO(&read_set);
	FD_SET(err_pipe[0],&read_set);
	FD_SET(out_pipe[0],&read_set);
	while(select(high_fd+1,&read_set,NULL,NULL,&tv)>0) {
		if(FD_ISSET(err_pipe[0],&read_set)) {
			i=read(err_pipe[0],buf,sizeof(buf)-1);
			if(i!=-1 && i!=0) {
				buf[i]=0;
				lprintf(LOG_ERR,"%04d CGI Error: %s",session->socket,buf);
				start=time(NULL);
			}
		}

		if(FD_ISSET(out_pipe[0],&read_set))  {
			i=read(out_pipe[0],buf,sizeof(buf));
			if(i!=-1 && i!=0)  {
				int snt=0;
				start=time(NULL);
				if(session->req.method!=HTTP_HEAD) {
					if(session->req.ld!=NULL) {
						session->req.ld->size+=snt;
					}
				}
			}
		}

		if(i==0 || i==-1)
			break;

		tv.tv_sec=1;
		tv.tv_usec=0;
		FD_ZERO(&read_set);
		FD_SET(err_pipe[0],&read_set);
		FD_SET(out_pipe[0],&read_set);
	close(out_pipe[0]);		/* close read-end of pipe */
	close(err_pipe[0]);		/* close read-end of pipe */
		lprintf(LOG_ERR,"%04d CGI Process %s did not generate valid headers"
			,session->socket,getfname(cmdline));
		return(FALSE);
	}

	if(!done_parsing_headers) {
		lprintf(LOG_ERR,"%04d CGI Process %s did not send data header termination"
			,session->socket,getfname(cmdline));

	/* These are (more or less) copied from the Unix version */
	char*	p;
	char	cmdline[MAX_PATH+256];
	char	buf[4096];
	BOOL	orig_keep;
	BOOL	done_parsing_headers=FALSE;
	BOOL	got_valid_headers=FALSE;
	char	*directive=NULL;
	char	*value=NULL;
	BOOL	no_chunked=FALSE;
	char*	env_block;
	char	startup_dir[MAX_PATH+1];
	HANDLE	rdpipe=INVALID_HANDLE_VALUE;
	HANDLE	wrpipe=INVALID_HANDLE_VALUE;
	HANDLE	rdoutpipe;
	HANDLE	wrinpipe;
	DWORD	retval;
    PROCESS_INFORMATION process_info;
	SECURITY_ATTRIBUTES sa;
    STARTUPINFO startup_info={0};

    startup_info.cb=sizeof(startup_info);
	startup_info.dwFlags|=STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW;
    startup_info.wShowWindow=SW_HIDE;

	SAFECOPY(startup_dir,session->req.physical_path);
	if((p=strrchr(startup_dir,'/'))!=NULL || (p=strrchr(startup_dir,'\\'))!=NULL)
		*p=0;
	else
		SAFECOPY(startup_dir,session->req.cgi_dir?session->req.cgi_dir:cgi_dir);

	lprintf(LOG_DEBUG,"%04d CGI startup dir: %s", session->socket, startup_dir);

	if((p=get_cgi_handler(session->req.physical_path))!=NULL)
		SAFEPRINTF2(cmdline,"%s %s",p,session->req.physical_path);
	else
		SAFECOPY(cmdline,session->req.physical_path);
	lprintf(LOG_INFO,"%04d Executing CGI: %s",session->socket,cmdline);

	orig_keep=session->req.keep_alive;
	session->req.keep_alive=FALSE;

	memset(&sa,0,sizeof(sa));
	sa.nLength= sizeof(SECURITY_ATTRIBUTES);
	sa.lpSecurityDescriptor = NULL;
	sa.bInheritHandle = TRUE;

	/* Create the child output pipe (override default 4K buffer size) */
	if(!CreatePipe(&rdoutpipe,&startup_info.hStdOutput,&sa,sizeof(buf))) {
		lprintf(LOG_ERR,"%04d !ERROR %d creating stdout pipe",session->socket,GetLastError());
		return(FALSE);
	}
	startup_info.hStdError=startup_info.hStdOutput;

	/* Create the child input pipe. */
	if(!CreatePipe(&startup_info.hStdInput,&wrinpipe,&sa,0 /* default buffer size */)) {
		lprintf(LOG_ERR,"%04d !ERROR %d creating stdin pipe",session->socket,GetLastError());
		return(FALSE);
	}

	DuplicateHandle(
		GetCurrentProcess(), rdoutpipe,
		GetCurrentProcess(), &rdpipe, 0, FALSE, DUPLICATE_SAME_ACCESS);

	DuplicateHandle(
		GetCurrentProcess(), wrinpipe,
		GetCurrentProcess(), &wrpipe, 0, FALSE, DUPLICATE_SAME_ACCESS);

	CloseHandle(rdoutpipe);
	CloseHandle(wrinpipe);

	env_list=get_cgi_env(session);
	env_block = strListCreateBlock(env_list);
	strListFree(&env_list);

    success=CreateProcess(
		NULL,			/* pointer to name of executable module */
		cmdline,  		/* pointer to command line string */
		NULL,  			/* process security attributes */
		NULL,   		/* thread security attributes */
		TRUE,	 		/* handle inheritance flag */
		CREATE_NEW_CONSOLE, /* creation flags */
        env_block,  	/* pointer to new environment block */
		startup_dir,	/* pointer to current directory name */
		&startup_info,  /* pointer to STARTUPINFO */
		&process_info  	/* pointer to PROCESS_INFORMATION */
		);

	strListFreeBlock(env_block);
	
	if(!success) {
		lprintf(LOG_ERR,"%04d !ERROR %d running %s",session->socket,GetLastError(),cmdline);
		return(FALSE);
    }

	cd.wrpipe = wrpipe;
	cd.rdpipe = rdpipe;
	cd.child = process_info.hProcess;
	cd.session = session;
	int ret = do_cgi_stuff(session, &cgi, orig_keep);
	if (ret & CGI_STUFF_DONE_PARSING)
		done_parsing_headers = TRUE;
	if (ret & CGI_STUFF_VALID_HEADERS)
		got_valid_headers = TRUE;

    if(GetExitCodeProcess(process_info.hProcess, &retval)==FALSE)
	    lprintf(LOG_ERR,"%04d !ERROR GetExitCodeProcess(%s) returned %d"
			,session->socket,getfname(cmdline),GetLastError());
		lprintf(LOG_WARNING,"%04d Terminating CGI process: %s"
			,session->socket,getfname(cmdline));
		TerminateProcess(process_info.hProcess, GetLastError());
	}	

	if(rdpipe!=INVALID_HANDLE_VALUE)
		CloseHandle(rdpipe);
	if(wrpipe!=INVALID_HANDLE_VALUE)
		CloseHandle(wrpipe);
	CloseHandle(process_info.hProcess);

		lprintf(LOG_WARNING,"%04d !CGI Process %s did not generate valid headers"
			,session->socket,getfname(cmdline));
		lprintf(LOG_WARNING,"%04d !CGI Process %s did not send data header termination"
			,session->socket,getfname(cmdline));
/********************/
/* JavaScript stuff */
/********************/

JSObject* DLLCALL js_CreateHttpReplyObject(JSContext* cx
										   ,JSObject* parent, http_session_t *session)
{
	JSObject*	reply;
	JSObject*	headers;
	jsval		val;
	JSString*	js_str;
	
	/* Return existing object if it's already been created */
	if(JS_GetProperty(cx,parent,"http_reply",&val) && val!=JSVAL_VOID)  {
		reply = JSVAL_TO_OBJECT(val);
		JS_ClearScope(cx,reply);
	}
	else
		reply = JS_DefineObject(cx, parent, "http_reply", NULL
									, NULL, JSPROP_ENUMERATE|JSPROP_READONLY);
	if((js_str=JS_NewStringCopyZ(cx, session->req.status))==NULL)
		return(FALSE);
	JS_DefineProperty(cx, reply, "status", STRING_TO_JSVAL(js_str)
		,NULL,NULL,JSPROP_ENUMERATE);
	/* Return existing object if it's already been created */
	if(JS_GetProperty(cx,reply,"header",&val) && val!=JSVAL_VOID)  {
		headers = JSVAL_TO_OBJECT(val);
		JS_ClearScope(cx,headers);
	}
	else
		headers = JS_DefineObject(cx, reply, "header", NULL
									, NULL, JSPROP_ENUMERATE|JSPROP_READONLY);

	if((js_str=JS_NewStringCopyZ(cx, "text/html"))==NULL)
		return(FALSE);
	JS_DefineProperty(cx, headers, "Content-Type", STRING_TO_JSVAL(js_str)
		,NULL,NULL,JSPROP_ENUMERATE);

	return(reply);
}

JSObject* DLLCALL js_CreateHttpRequestObject(JSContext* cx
											 ,JSObject* parent, http_session_t *session)
deuce's avatar
deuce committed
{
/*	JSObject*	cookie; */
deuce's avatar
deuce committed
	jsval		val;

	/* Return existing object if it's already been created */
	if(JS_GetProperty(cx,parent,"http_request",&val) && val!=JSVAL_VOID)  {
		session->js_request=JSVAL_TO_OBJECT(val);
deuce's avatar
deuce committed
	else
		session->js_request = JS_DefineObject(cx, parent, "http_request", NULL
									, NULL, JSPROP_ENUMERATE|JSPROP_READONLY);
	js_add_request_prop(session,"path_info",session->req.extra_path_info);
	js_add_request_prop(session,"method",methods[session->req.method]);
	js_add_request_prop(session,"virtual_path",session->req.virtual_path);
deuce's avatar
deuce committed

	/* Return existing object if it's already been created */
	if(JS_GetProperty(cx,session->js_request,"query",&val) && val!=JSVAL_VOID)  {
		session->js_query = JSVAL_TO_OBJECT(val);
		JS_ClearScope(cx,session->js_query);
deuce's avatar
deuce committed
	else
		session->js_query = JS_DefineObject(cx, session->js_request, "query", NULL
									, NULL, JSPROP_ENUMERATE|JSPROP_READONLY);
	/* Return existing object if it's already been created */
	if(JS_GetProperty(cx,session->js_request,"header",&val) && val!=JSVAL_VOID)  {
		session->js_header = JSVAL_TO_OBJECT(val);
		JS_ClearScope(cx,session->js_header);
		session->js_header = JS_DefineObject(cx, session->js_request, "header", NULL
									, NULL, JSPROP_ENUMERATE|JSPROP_READONLY);
	/* Return existing object if it's already been created */
	if(JS_GetProperty(cx,session->js_request,"cookie",&val) && val!=JSVAL_VOID)  {
		session->js_cookie = JSVAL_TO_OBJECT(val);
		JS_ClearScope(cx,session->js_cookie);
	}