Skip to content
Snippets Groups Projects
websrvr.c 161 KiB
Newer Older
		if((str=JS_ValueToString(cx, argv[i]))==NULL)
			continue;
deuce's avatar
deuce committed
		len=JS_GetStringLength(str);
		JSSTRING_TO_STRING(cx, str, cstr, NULL);
deuce's avatar
deuce committed
		js_writebuf(session, cstr, len);
		if(writeln)
			js_writebuf(session, newline, 2);
deuce's avatar
deuce committed
		JS_SET_RVAL(cx,arglist,JSVAL_VOID);
deuce's avatar
deuce committed
		JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(str));
js_write(JSContext *cx, uintN argc, jsval *arglist)
deuce's avatar
deuce committed
	js_writefunc(cx, argc, arglist, FALSE);
js_writeln(JSContext *cx, uintN argc, jsval *arglist)
deuce's avatar
deuce committed
	js_writefunc(cx, argc, arglist,TRUE);
deuce's avatar
deuce committed
static JSBool
js_set_cookie(JSContext *cx, uintN argc, jsval *arglist)
	jsval *argv=JS_ARGV(cx, arglist);
deuce's avatar
deuce committed
	char	header_buf[8192];
	char	*header;
	char	*p;
	int32	i;
rswindell's avatar
rswindell committed
	JSBool	b;
deuce's avatar
deuce committed
	struct tm tm;
	http_session_t* session;
deuce's avatar
deuce committed
	time_t	tt;
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

deuce's avatar
deuce committed
	if((session=(http_session_t*)JS_GetContextPrivate(cx))==NULL)
		return(JS_FALSE);

	if(argc<2)
		return(JS_FALSE);

	header=header_buf;
	JSVALUE_TO_STRING(cx, argv[0], p, NULL);
deuce's avatar
deuce committed
	if(!p)
		return(JS_FALSE);
	header+=sprintf(header,"Set-Cookie: %s=",p);
	JSVALUE_TO_STRING(cx, argv[1], p, NULL);
deuce's avatar
deuce committed
	if(!p)
		return(JS_FALSE);
	header+=sprintf(header,"%s",p);
	if(argc>2) {
		if(!JS_ValueToInt32(cx,argv[2],&i))
			return JS_FALSE;
deuce's avatar
deuce committed
		tt=i;
		if(i && gmtime_r(&tt,&tm)!=NULL)
deuce's avatar
deuce committed
			header += strftime(header,50,"; expires=%a, %d-%b-%Y %H:%M:%S GMT",&tm);
	}
	if(argc>3) {
		JSVALUE_TO_STRING(cx, argv[3], p, NULL);
		if(p!=NULL && *p)
deuce's avatar
deuce committed
			header += sprintf(header,"; domain=%s",p);
	}
	if(argc>4) {
		JSVALUE_TO_STRING(cx, argv[4], p, NULL);
		if(p!=NULL && *p)
deuce's avatar
deuce committed
			header += sprintf(header,"; path=%s",p);
	}
	if(argc>5) {
rswindell's avatar
rswindell committed
		JS_ValueToBoolean(cx, argv[5], &b);
		if(b)
deuce's avatar
deuce committed
			header += sprintf(header,"; secure");
	}
	strListPush(&session->req.dynamic_heads,header_buf);

	return(JS_TRUE);
}

js_log(JSContext *cx, uintN argc, jsval *arglist)
	jsval *argv=JS_ARGV(cx, arglist);
	char		str[512];
    uintN		i=0;
	int32		level=LOG_INFO;
	http_session_t* session;
deuce's avatar
deuce committed
	jsrefcount	rc;
deuce's avatar
deuce committed
	char		*val;
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

	if((session=(http_session_t*)JS_GetContextPrivate(cx))==NULL)
		return(JS_FALSE);

    if(startup==NULL || startup->lputs==NULL)
        return(JS_FALSE);

	if(argc > 1 && JSVAL_IS_NUMBER(argv[i])) {
		if(!JS_ValueToInt32(cx,argv[i++],&level))
			return JS_FALSE;
	}

	str[0]=0;
    for(;i<argc && strlen(str)<(sizeof(str)/2);i++) {
		JSVALUE_TO_STRING(cx, argv[i], val, NULL);
deuce's avatar
deuce committed
		if(val==NULL)
deuce's avatar
deuce committed
		strncat(str,val,sizeof(str)/2);
	lprintf(level,"%04d %s",session->socket,str);
	JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(JS_NewStringCopyZ(cx, str)));
js_login(JSContext *cx, uintN argc, jsval *arglist)
	jsval *argv=JS_ARGV(cx, arglist);
	char*		p;
	JSBool		inc_logons=JS_FALSE;
	user_t		user;
	http_session_t*	session;
deuce's avatar
deuce committed
	jsrefcount	rc;
	JS_SET_RVAL(cx, arglist, BOOLEAN_TO_JSVAL(JS_FALSE));

	if((session=(http_session_t*)JS_GetContextPrivate(cx))==NULL)
		return(JS_FALSE);

	/* User name */
	JSVALUE_TO_STRING(cx, argv[0], p, NULL);
deuce's avatar
deuce committed
	if(p==NULL) 
	memset(&user,0,sizeof(user));

	if(isdigit(*p))
		user.number=atoi(p);
	else if(*p)
		user.number=matchuser(&scfg,p,FALSE);

	if(getuserdat(&scfg,&user)!=0) {
		lprintf(LOG_NOTICE,"%04d !USER NOT FOUND: '%s'"
			,session->socket,p);
		return(JS_TRUE);
	}

	if(user.misc&(DELETED|INACTIVE)) {
		lprintf(LOG_WARNING,"%04d !DELETED OR INACTIVE USER #%d: %s"
			,session->socket,user.number,p);
		JSVALUE_TO_STRING(cx, argv[1], p, NULL);
deuce's avatar
deuce committed
		if(p==NULL) 
			return(JS_FALSE);

		if(stricmp(user.pass,p)) { /* Wrong password */
			lprintf(LOG_WARNING,"%04d !INVALID PASSWORD ATTEMPT FOR USER: %s"
				,session->socket,user.alias);
			return(JS_TRUE);
		}
	}

	if(argc>2)
		JS_ValueToBoolean(cx,argv[2],&inc_logons);

	if(inc_logons) {
		user.logons++;
		user.ltoday++;
	}

	http_logon(session, &user);

rswindell's avatar
rswindell committed
	if(!js_CreateUserObjects(session->js_cx, session->js_glob, &scfg, &session->user, &session->client
		,NULL /* ftp index file */, session->subscan /* subscan */)) {
		lprintf(LOG_ERR,"%04d !JavaScript ERROR creating user objects",session->socket);
		send_error(session,"500 Error initializing JavaScript User Objects");
		return(FALSE);
	}

	JS_SET_RVAL(cx, arglist,BOOLEAN_TO_JSVAL(JS_TRUE));
#if 0
static char *find_next_pair(char *buffer, size_t buflen, char find)
{
	char	*p;
	char	*search;
	char	*end;
	size_t	buflen2;
	char	chars[5]="@%^<";

	end=buffer+buflen;
	search=buffer;
	buflen2=buflen;

	for(;search<end;) {
		p=memchr(search, chars[i], buflen2);
		/* Can't even find one... there's definatly no pair */
		if(p==NULL)
			return(NULL);

		if(*(p+1)==find)
			return(p);

		/* Next search pos is at the char after the match */
		search=p+1;
		buflen2=end-search;
	}
}

static void js_write_escaped(JSContext *cx, JSObject *obj, char *pos, size_t len, char *name_end, char *repeat_section)
{
	char	*name=pos+2;

}

enum {
	 T_AT
	,T_PERCENT
	,T_CARET
	,T_LT
};

static int js_write_template_part(JSContext *cx, JSObject *obj, char *template, size_t len, char *repeat_section)
{
	size_t		len2;
	char		*pos;
	char		*end;
	char		*p;
	char		*p2;
	char		*send_end;
	int			no_more[4];
	char		*next[4];
	int			i,j;
	char		chars[5]="@%^<";

	end=template+len;
	pos=template;
	memset(&next,0,sizeof(next));
	memset(&no_more,0,sizeof(no_more));

	while(pos<end) {
		send_end=NULL;

		/* Find next seperator */
		for(i=0; i<4; i++) {
			if(!no_more[i]) {
				if(next[i] < pos)
					next[i]=NULL;
				if(next[i] == NULL) {
					if((next[i]=find_next_pair(pos, len, chars[i]))==NULL) {
						no_more[i]=TRUE;
						continue;
					}
				}
				if(!send_end || next[i] < send_end)
					send_end=next[i];
			}
		}
		if(send_end==NULL) {
			/* Nothing else matched... we're good now! */
			js_writebuf(session, pos, len);
			pos=end;
			len=0;
			continue;
		}
		if(send_end > pos) {
			i=send_end-pos;
			js_writebuf(session, pos, i);
			pos+=i;
			len-=i;
		}

		/*
		 * At this point, pos points to a matched introducer.
		 * If it's not a repeat section, we can just output it here.
		 */
		if(*pos != '<') {
			/*
			 * If there is no corresponding terminator to this introdcer,
			 * force it to be output unchanged.
			 */
			if((p=find_next_pair(pos, len, *pos))==NULL) {
				no_more[strchr(chars,*pos)-char]=TRUE;
				continue;
			}
			js_write_escaped(cx, obj, pos, len, p, repeat_section);
			continue;
		}

		/*
		 * Pos is the start of a repeat section now... this is where things
		 * start to get tricky.  Set up RepeatObj object, then call self
		 * once for each repeat.
		 */
	}
}

static JSBool
js_write_template(JSContext *cx, uintN argc, jsval *arglist)
	JSObject *obj=JS_THIS_OBJECT(cx, arglist);
	jsval *argv=JS_ARGV(cx, arglist);
	JSString*	js_str;
	char		*filename;
	char		*template;
	FILE		*tfile;
	size_t		len;
	http_session_t* session;

	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

	if((session=(http_session_t*)JS_GetContextPrivate(cx))==NULL)
		return(JS_FALSE);

	if(session->req.fp==NULL)
		return(JS_FALSE);

	JSVALUE_TO_STRING(cx, argv[0], filename, NULL);
	if(filename==NULL)
		return(JS_FALSE);

	if(!fexist(filename)) {
		JS_ReportError(cx, "Template file %s does not exist.", filename);
		return(JS_FALSE);
	}
	len=flength(filename);

	if((tfile=fopen(filename,"r"))==NULL) {
		JS_ReportError(cx, "Unable to open template %s for read.", filename);
		return(JS_FALSE);
	}

	if((template=(char *)alloca(len))==NULL) {
		JS_ReportError(cx, "Unable to allocate %u bytes for template.", len);
		return(JS_FALSE);
	}

	if(fread(template, 1, len, tfile) != len) {
		fclose(tfile);
		JS_ReportError(cx, "Unable to read %u bytes from template %s.", len, filename);
		return(JS_FALSE);
	}
	fclose(tfile);

	if((!session->req.prev_write) && (!session->req.sent_headers)) {
		if(session->http_ver>=HTTP_1_1 && session->req.keep_alive) {
			if(!ssjs_send_headers(session,TRUE))
				return(JS_FALSE);
		}
		else {
			/* "Fast Mode" requested? */
			jsval		val;
			JSObject*	reply;
			JS_GetProperty(cx, session->js_glob, "http_reply", &val);
			reply=JSVAL_TO_OBJECT(val);
			JS_GetProperty(cx, reply, "fast", &val);
			if(JSVAL_IS_BOOLEAN(val) && JSVAL_TO_BOOLEAN(val)) {
				session->req.keep_alive=FALSE;
				if(!ssjs_send_headers(session,FALSE))
					return(JS_FALSE);
			}
		}
	}

	session->req.prev_write=TRUE;
	js_write_template_part(cx, obj, template, len, NULL);

	return(JS_TRUE);
}
#endif

static JSFunctionSpec js_global_functions[] = {
	{"write",           js_write,           1},		/* write to HTML file */
	{"writeln",         js_writeln,         1},		/* write line to HTML file */
	{"print",			js_writeln,			1},		/* write line to HTML file (alias) */
	{"log",				js_log,				0},		/* Log a string */
	{"login",           js_login,           2},		/* log in as a different user */
deuce's avatar
deuce committed
	{"set_cookie",		js_set_cookie,		2},		/* Set a cookie */
js_OperationCallback(JSContext *cx)
	JS_SetOperationCallback(cx, NULL);
	if((session=(http_session_t*)JS_GetContextPrivate(cx))==NULL) {
		JS_SetOperationCallback(cx, js_OperationCallback);
    ret=js_CommonOperationCallback(cx,&session->js_callback);
	JS_SetOperationCallback(cx, js_OperationCallback);
js_initcx(http_session_t *session)
	lprintf(LOG_DEBUG,"%04d JavaScript: Initializing context (stack: %lu bytes)"
		,session->socket,startup->js.cx_stack);
    if((js_cx = JS_NewContext(session->js_runtime, startup->js.cx_stack))==NULL)
	lprintf(LOG_DEBUG,"%04d JavaScript: Context created",session->socket);

    JS_SetErrorReporter(js_cx, js_ErrorReporter);

	JS_SetOperationCallback(js_cx, js_OperationCallback);
	lprintf(LOG_DEBUG,"%04d JavaScript: Creating Global Objects and Classes",session->socket);
	if(!js_CreateCommonObjects(js_cx, &scfg, NULL
									,NULL						/* global */
									,uptime						/* system */
									,startup->host_name			/* system */
									,SOCKLIB_DESC				/* system */
									,&startup->js				/* js */
									,&session->client			/* client */
									,session->socket			/* client */
		|| !JS_DefineFunctions(js_cx, session->js_glob, js_global_functions)) {
		JS_RemoveObjectRoot(js_cx, &session->js_glob);
static BOOL js_setup(http_session_t* session)
	if(session->js_runtime == NULL) {
		lprintf(LOG_DEBUG,"%04d JavaScript: Creating runtime: %lu bytes"
			,session->socket,startup->js.max_bytes);
		if((session->js_runtime=jsrt_GetNew(startup->js.max_bytes, 5000, __FILE__, __LINE__))==NULL) {
			lprintf(LOG_ERR,"%04d !ERROR creating JavaScript runtime",session->socket);

	if(session->js_cx==NULL) {	/* Context not yet created, create it now */
		/* js_initcx() begins a context */
		if(((session->js_cx=js_initcx(session))==NULL)) {
			lprintf(LOG_ERR,"%04d !ERROR initializing JavaScript context",session->socket);
		argv=JS_NewArrayObject(session->js_cx, 0, NULL);

		JS_DefineProperty(session->js_cx, session->js_glob, "argv", OBJECT_TO_JSVAL(argv)
			,NULL,NULL,JSPROP_READONLY|JSPROP_ENUMERATE);
		JS_DefineProperty(session->js_cx, session->js_glob, "argc", INT_TO_JSVAL(0)
			,NULL,NULL,JSPROP_READONLY|JSPROP_ENUMERATE);

		JS_DefineProperty(session->js_cx, session->js_glob, "web_root_dir",
			STRING_TO_JSVAL(JS_NewStringCopyZ(session->js_cx, root_dir))
			,NULL,NULL,JSPROP_READONLY|JSPROP_ENUMERATE);
		JS_DefineProperty(session->js_cx, session->js_glob, "web_error_dir",
			STRING_TO_JSVAL(JS_NewStringCopyZ(session->js_cx, session->req.error_dir?session->req.error_dir:error_dir))
			,NULL,NULL,JSPROP_READONLY|JSPROP_ENUMERATE);

		JS_BEGINREQUEST(session->js_cx);
	lprintf(LOG_DEBUG,"%04d JavaScript: Initializing HttpRequest object",session->socket);
	if(js_CreateHttpRequestObject(session->js_cx, session->js_glob, session)==NULL) {
		lprintf(LOG_ERR,"%04d !ERROR initializing JavaScript HttpRequest object",session->socket);
		JS_ENDREQUEST(session->js_cx);
	lprintf(LOG_DEBUG,"%04d JavaScript: Initializing HttpReply object",session->socket);
	if(js_CreateHttpReplyObject(session->js_cx, session->js_glob, session)==NULL) {
		lprintf(LOG_ERR,"%04d !ERROR initializing JavaScript HttpReply object",session->socket);
		JS_ENDREQUEST(session->js_cx);
	JS_SetContextPrivate(session->js_cx, session);
	JS_ENDREQUEST(session->js_cx);
static BOOL ssjs_send_headers(http_session_t* session,int chunked)
	jsval		val;
	JSObject*	reply;
	JSIdArray*	heads;
	JSObject*	headers;
	int			i;
deuce's avatar
deuce committed
	char		*p,*p2;
	JS_BEGINREQUEST(session->js_cx);
	JS_GetProperty(session->js_cx,session->js_glob,"http_reply",&val);
	reply = JSVAL_TO_OBJECT(val);
	JS_GetProperty(session->js_cx,reply,"status",&val);
	JSVALUE_TO_STRING(session->js_cx, val, p, NULL);
deuce's avatar
deuce committed
	SAFECOPY(session->req.status,p);
	JS_GetProperty(session->js_cx,reply,"header",&val);
	headers = JSVAL_TO_OBJECT(val);
	heads=JS_Enumerate(session->js_cx,headers);
deuce's avatar
deuce committed
	if(heads != NULL) {
		for(i=0;i<heads->length;i++)  {
			JS_IdToValue(session->js_cx,heads->vector[i],&val);
			JSVALUE_TO_STRING(session->js_cx, val, p, NULL);
deuce's avatar
deuce committed
			JS_GetProperty(session->js_cx,headers,p,&val);
			JSVALUE_TO_STRING(session->js_cx, val, p2, NULL);
deuce's avatar
deuce committed
			safe_snprintf(str,sizeof(str),"%s: %s",p,p2);
deuce's avatar
deuce committed
			strListPush(&session->req.dynamic_heads,str);
		}
		JS_ClearScope(session->js_cx, headers);
	JS_ENDREQUEST(session->js_cx);
	return(send_headers(session,session->req.status,chunked));
static BOOL exec_ssjs(http_session_t* session, char* script)  {
	jsval		rval;
	char		path[MAX_PATH+1];
	BOOL		retval=TRUE;
	/* External JavaScript handler? */
	if(script == session->req.physical_path && session->req.xjs_handler[0])
		script = session->req.xjs_handler;

	sprintf(path,"%sSBBS_SSJS.%u.%u.html",temp_dir,getpid(),session->socket);
	if((session->req.fp=fopen(path,"wb"))==NULL) {
		lprintf(LOG_ERR,"%04d !ERROR %d opening/creating %s", session->socket, errno, path);
		return(FALSE);
	}
deuce's avatar
deuce committed
	if(session->req.cleanup_file[CLEANUP_SSJS_TMP_FILE]) {
		if(!(startup->options&WEB_OPT_DEBUG_SSJS))
			remove(session->req.cleanup_file[CLEANUP_SSJS_TMP_FILE]);
		free(session->req.cleanup_file[CLEANUP_SSJS_TMP_FILE]);
	}
	/* FREE()d in close_request() */
	session->req.cleanup_file[CLEANUP_SSJS_TMP_FILE]=strdup(path);
	JS_BEGINREQUEST(session->js_cx);
	js_add_request_prop(session,"real_path",session->req.physical_path);
	js_add_request_prop(session,"virtual_path",session->req.virtual_path);
	js_add_request_prop(session,"ars",session->req.ars);
	js_add_request_prop(session,"request_string",session->req.request_line);
	js_add_request_prop(session,"host",session->req.host);
	js_add_request_prop(session,"vhost",session->req.vhost);
	js_add_request_prop(session,"http_ver",http_vers[session->http_ver]);
	js_add_request_prop(session,"remote_ip",session->host_ip);
	js_add_request_prop(session,"remote_host",session->host_name);
	if(session->req.query_str && session->req.query_str[0])  {
		js_add_request_prop(session,"query_string",session->req.query_str);
		js_parse_query(session,session->req.query_str);
	}
	if(session->req.post_data && session->req.post_data[0]) {
		js_add_request_prop(session,"post_data",session->req.post_data);
		js_parse_query(session,session->req.post_data);
	}
	parse_js_headers(session);

	do {
		/* RUN SCRIPT */
		JS_ClearPendingException(session->js_cx);

		lprintf(LOG_DEBUG,"%04d JavaScript: Compiling script: %s",session->socket,script);
		if((js_script=JS_CompileFile(session->js_cx, session->js_glob
			lprintf(LOG_ERR,"%04d !JavaScript FAILED to compile script (%s)"
			JS_RemoveObjectRoot(session->js_cx, &session->js_glob);
			JS_ENDREQUEST(session->js_cx);
		lprintf(LOG_DEBUG,"%04d JavaScript: Executing script: %s",session->socket,script);
		js_PrepareToExecute(session->js_cx, session->js_glob, script, /* startup_dir */NULL);
		JS_ExecuteScript(session->js_cx, session->js_glob, js_script, &rval);
		js_EvalOnExit(session->js_cx, session->js_glob, &session->js_callback);
		JS_RemoveObjectRoot(session->js_cx, &session->js_glob);
		lprintf(LOG_DEBUG,"%04d JavaScript: Done executing script: %s (%.2Lf seconds)"
			,session->socket,script,xp_timer()-start);
	SAFECOPY(session->req.physical_path, path);
	if(session->req.fp!=NULL) {
		fclose(session->req.fp);
		session->req.fp=NULL;
	}


	/* Read http_reply object */
	if(!session->req.sent_headers) {
		retval=ssjs_send_headers(session,FALSE);
	}

	/* Free up temporary resources here */

	session->req.dynamic=IS_SSJS;
	JS_ENDREQUEST(session->js_cx);
static void respond(http_session_t * session)
{
	if(session->req.method==HTTP_OPTIONS) {
		send_headers(session,session->req.status,FALSE);
	}
	else {
		if(session->req.dynamic==IS_CGI)  {
			if(!exec_cgi(session))  {
				send_error(session,error_500);
				return;
			}
			session->req.finished=TRUE;
		if(session->req.dynamic==IS_SSJS) {	/* Server-Side JavaScript */
			if(!exec_ssjs(session,session->req.physical_path))  {
				send_error(session,error_500);
				return;
			}
			sprintf(session->req.physical_path
				,"%sSBBS_SSJS.%u.%u.html",temp_dir,getpid(),session->socket);
		}
		else {
			session->req.mime_type=get_mime_type(strrchr(session->req.physical_path,'.'));
			send_file=send_headers(session,session->req.status,FALSE);
	if(session->req.method==HTTP_HEAD || session->req.method==HTTP_OPTIONS)
		send_file=FALSE;
		lprintf(LOG_INFO,"%04d Sending file: %s (%"PRIuOFF" bytes)"
			,session->socket, session->req.physical_path, flength(session->req.physical_path));
		snt=sock_sendfile(session,session->req.physical_path,session->req.range_start,session->req.range_end);
		if(session->req.ld!=NULL) {
			if(snt<0)
				snt=0;
			session->req.ld->size=snt;
		}
			lprintf(LOG_INFO,"%04d Sent file: %s (%d bytes)"
				,session->socket, session->req.physical_path, snt);
	session->req.finished=TRUE;
int read_post_data(http_session_t * session)
{
	if(session->req.dynamic!=IS_CGI && (session->req.post_len || session->req.read_chunked))  {
		if(session->req.read_chunked) {
			char *p;
			size_t	ch_len=0;
			int	bytes_read=0;
			char	ch_lstr[12];
			session->req.post_len=0;

			while(1) {
				/* Read chunk length */
				if(sockreadline(session,ch_lstr,sizeof(ch_lstr)-1)>0) {
					ch_len=strtol(ch_lstr,NULL,16);
				}
				else {
					send_error(session,error_500);
					return(FALSE);
				}
				if(ch_len==0)
					break;
				/* Check size */
				i += ch_len;
				if(i > MAX_POST_LEN) {
					send_error(session,"413 Request entity too large");
					return(FALSE);
				}
				/* realloc() to new size */
				p=realloc(session->req.post_data, i);
				if(p==NULL) {
					lprintf(LOG_CRIT,"%04d !ERROR Allocating %d bytes of memory",session->socket,session->req.post_len);
					send_error(session,"413 Request entity too large");
					return(FALSE);
				}
				session->req.post_data=p;
				/* read new data */
				bytes_read=recvbufsocket(&session->socket,session->req.post_data+session->req.post_len,ch_len);
				if(!bytes_read) {
					send_error(session,error_500);
					return(FALSE);
				}
				session->req.post_len+=bytes_read;
				/* Read chunk terminator */
				if(sockreadline(session,ch_lstr,sizeof(ch_lstr)-1)>0)
					send_error(session,error_500);
			/* Read more headers! */
			if(!get_request_headers(session))
				return(FALSE);
			if(!parse_headers(session))
				return(FALSE);
deuce's avatar
deuce committed
			FREE_AND_NULL(session->req.post_data);
			/* FREE()d in close_request()  */
			if(i < (MAX_POST_LEN+1) && (session->req.post_data=malloc(i+1)) != NULL)
				session->req.post_len=recvbufsocket(&session->socket,session->req.post_data,i);
			else  {
				lprintf(LOG_CRIT,"%04d !ERROR Allocating %d bytes of memory",session->socket,i);
				send_error(session,"413 Request entity too large");
				return(FALSE);
			}
		}
		if(session->req.post_len != i)
				lprintf(LOG_DEBUG,"%04d !ERROR Browser said they sent %d bytes, but I got %d",session->socket,i,session->req.post_len);
		if(session->req.post_len > i)
			session->req.post_len = i;
		session->req.post_data[session->req.post_len]=0;
void http_output_thread(void *arg)
{
	http_session_t	*session=(http_session_t *)arg;
	RingBuf	*obuf;
	char	buf[OUTBUF_LEN+12];						/* *MUST* be large enough to hold the buffer,
														the size of the buffer in hex, and four extra bytes. */
	char	*bufdata;
	int		failed=0;
	int		len;
	SetThreadName("HTTP Output");
deuce's avatar
deuce committed
	/* Destroyed at end of function */
	if((i=pthread_mutex_init(&session->outbuf_write,NULL))!=0) {
		lprintf(LOG_DEBUG,"Error %d initializing outbuf mutex",i);
		close_socket(&session->socket);
		return;
	}
	session->outbuf_write_initialized=1;
#ifdef TCP_MAXSEG
	/*
	 * Auto-tune the highwater mark to be the negotiated MSS for the
	 * socket (when possible)
	 */
	if(!obuf->highwater_mark) {
		socklen_t   sl;
		sl=sizeof(i);
		if(!getsockopt(session->socket, IPPROTO_TCP, TCP_MAXSEG, &i, &sl)) {
			/* Check for sanity... */
			if(i>100) {
				obuf->highwater_mark=i-12;
				lprintf(LOG_DEBUG,"%04d Autotuning outbuf highwater mark to %d based on MSS"
					,session->socket,i);
				mss=obuf->highwater_mark;
				if(mss>OUTBUF_LEN) {
					mss=OUTBUF_LEN;
					lprintf(LOG_DEBUG,"%04d MSS (%d) is higher than OUTBUF_LEN (%d)"
						,session->socket,i,OUTBUF_LEN);
	thread_up(TRUE /* setuid */);
	/*
	 * Do *not* exit on terminate_server... wait for session thread
	 * to close the socket and set it to INVALID_SOCKET
	 */
    while(session->socket!=INVALID_SOCKET) {
deuce's avatar
deuce committed
		/* Wait for something to output in the RingBuffer */
		if((avail=RingBufFull(obuf))==0) {	/* empty */
			if(sem_trywait_block(&obuf->sem,1000))
deuce's avatar
deuce committed
				continue;
			/* Check for spurious sem post... */
			if((avail=RingBufFull(obuf))==0)
				continue;
		}
		else
			sem_trywait(&obuf->sem);
deuce's avatar
deuce committed
		/* Wait for full buffer or drain timeout */
		if(obuf->highwater_mark) {
			if(avail<obuf->highwater_mark) {
				sem_trywait_block(&obuf->highwater_sem,startup->outbuf_drain_timeout);
				/* We (potentially) blocked, so get fill level again */
		    	avail=RingBufFull(obuf);
			} else
				sem_trywait(&obuf->highwater_sem);

        /*
         * At this point, there's something to send and,
         * if the highwater mark is set, the timeout has
         * passed or we've hit highwater.  Read ring buffer
         * into linear buffer.
         */
deuce's avatar
deuce committed
        len=avail;
			len=(avail=mss);

		/* 
		 * Read the current value of write_chunked... since we wait until the
		 * ring buffer is empty before fiddling with it.
		 */
		chunked=session->req.write_chunked;

		bufdata=buf;
		if(chunked) {
			i=sprintf(buf, "%X\r\n", avail);
			bufdata+=i;
			len+=i;
		}

		pthread_mutex_lock(&session->outbuf_write);
        RingBufRead(obuf, bufdata, avail);
		if(chunked) {
			bufdata+=avail;
			*(bufdata++)='\r';
			*(bufdata++)='\n';
			len+=2;
		}

			sock_sendbuf(&session->socket, buf, len, &failed);
		pthread_mutex_unlock(&session->outbuf_write);
deuce's avatar
deuce committed
	/* Ensure outbuf isn't currently being drained */
	pthread_mutex_lock(&session->outbuf_write);
	session->outbuf_write_initialized=0;
	pthread_mutex_unlock(&session->outbuf_write);
	pthread_mutex_destroy(&session->outbuf_write);
	sem_post(&session->output_thread_terminated);
void http_session_thread(void* arg)
{
	char			redir_req[MAX_REQUEST_LINE+1];
	char			*redirp;
	http_session_t	session;
	SetThreadName("HTTP Session");
	pthread_mutex_lock(&((http_session_t*)arg)->struct_filled);
	pthread_mutex_unlock(&((http_session_t*)arg)->struct_filled);
	pthread_mutex_destroy(&((http_session_t*)arg)->struct_filled);

	session=*(http_session_t*)arg;	/* copies arg BEFORE it's freed */
deuce's avatar
deuce committed
	FREE_AND_NULL(arg);
	if(socket==INVALID_SOCKET) {
		session_threads--;
		return;
	}
	lprintf(LOG_DEBUG,"%04d Session thread started", session.socket);
	if(startup->index_file_name==NULL || startup->cgi_ext==NULL)
		lprintf(LOG_DEBUG,"%04d !!! DANGER WILL ROBINSON, DANGER !!!", session.socket);

#ifdef _WIN32
	if(startup->answer_sound[0] && !(startup->options&BBS_OPT_MUTE)) 
		PlaySound(startup->answer_sound, NULL, SND_ASYNC|SND_FILENAME);
#endif

	thread_up(TRUE /* setuid */);
	/* Start up the output buffer */
	/* FREE()d in this block (RingBufDispose before all returns) */
	if(RingBufInit(&(session.outbuf), OUTBUF_LEN)) {
		lprintf(LOG_ERR,"%04d Canot create output ringbuffer!", session.socket);
		close_socket(&session.socket);
deuce's avatar
deuce committed
	/* Destroyed in this block (before all returns) */
	sem_init(&session.output_thread_terminated,0,0);
	_beginthread(http_output_thread, 0, &session);

	sbbs_srand();	/* Seed random number generator */
	if(startup->options&BBS_OPT_NO_HOST_LOOKUP)
		host=NULL;
	else
		host=gethostbyaddr ((char *)&session.addr.sin_addr
deuce's avatar
deuce committed
			,sizeof(session.addr.sin_addr),AF_INET);
	if(host!=NULL && host->h_name!=NULL)
		host_name=host->h_name;
	else
		host_name=session.host_ip;

	SAFECOPY(session.host_name,host_name);
	if(!(startup->options&BBS_OPT_NO_HOST_LOOKUP))  {
		lprintf(LOG_INFO,"%04d Hostname: %s", session.socket, session.host_name);
#if	0 /* gethostbyaddr() is apparently not (always) thread-safe
	     and getnameinfo() doesn't return alias information */
		for(i=0;host!=NULL && host->h_aliases!=NULL 
			&& host->h_aliases[i]!=NULL;i++)
			lprintf(LOG_INFO,"%04d HostAlias: %s", session.socket, host->h_aliases[i]);
		if(trashcan(&scfg,session.host_name,"host")) {
			lprintf(LOG_NOTICE,"%04d !CLIENT BLOCKED in host.can: %s", session.socket, session.host_name);
			close_socket(&session.socket);
			sem_wait(&session.output_thread_terminated);
deuce's avatar
deuce committed
			sem_destroy(&session.output_thread_terminated);
			RingBufDispose(&session.outbuf);
			thread_down();