Skip to content
Snippets Groups Projects
websrvr.c 170 KiB
Newer Older
deuce's avatar
deuce committed
				case 1:
					if (session->is_tls)
						session->tls_pending=TRUE;
					break;
				case -1:
					close_session_socket(session);
					lprintf(LOG_DEBUG,"%04d !ERROR %d selecting socket for read",session->socket,ERROR_VALUE);
					return(-1);
				default:
					/* Timeout */
					lprintf(LOG_NOTICE,"%04d Session timeout due to inactivity (%d seconds)",session->socket,startup->max_inactivity);
					return(-1);
			}
deuce's avatar
deuce committed
		switch(sess_recv(session, &ch, 1, 0)) {
deuce's avatar
deuce committed
				if(session->is_tls || ERROR_VALUE!=EAGAIN) {
					if(startup->options&WEB_OPT_DEBUG_RX)
						lprintf(LOG_DEBUG,"%04d !ERROR %d receiving on socket",session->socket,ERROR_VALUE);
deuce's avatar
deuce committed
					close_session_socket(session);
				/* Socket has been closed */
deuce's avatar
deuce committed
				close_session_socket(session);
		if(ch=='\n')
			break;

		if(i<length)
			buf[i++]=ch;

	/* Terminate at length if longer */
	if(i>length)
		i=length;
	while(i>0 && buf[i-1]=='\r')
		i--;

	buf[i]=0;
	if(startup->options&WEB_OPT_DEBUG_RX) {
		lprintf(LOG_DEBUG,"%04d RX: %s",session->socket,buf);
			lprintf(LOG_DEBUG,"%04d Long header, chucked %d bytes",session->socket,chucked);
	return(i);
deuce's avatar
deuce committed
static int pipereadline(HANDLE pipe, char *buf, size_t length, char *fullbuf, size_t fullbuf_len)
deuce's avatar
deuce committed
static int pipereadline(int pipe, char *buf, size_t length, char *fullbuf, size_t fullbuf_len)
#ifndef _WIN32
	struct timeval tv={0,0};
	fd_set  read_set;
#endif
deuce's avatar
deuce committed
	/* Terminate buffers */
	if(buf != NULL)
		buf[0]=0;
	if(fullbuf != NULL)
		fullbuf[0]=0;
		ReadFile(pipe, &ch, 1, (DWORD*)&ret, NULL);
		tv.tv_sec=startup->max_cgi_inactivity;
		tv.tv_usec=0;
		FD_ZERO(&read_set);
		FD_SET(pipe, &read_set);
		if(select(pipe+1, &read_set, NULL, NULL, &tv)<1)
			return(-1);
		ret=read(pipe, &ch, 1);
deuce's avatar
deuce committed
			if(fullbuf != NULL && i < (fullbuf_len-1)) {
				fullbuf[i]=ch;
				fullbuf[i+1]=0;
			}

deuce's avatar
deuce committed
			if(buf != NULL && i<length)
				buf[i]=ch;

			i++;
	}

	/* Terminate at length if longer */
	if(i>length)
		i=length;
deuce's avatar
deuce committed
	if(i>0 && buf != NULL && buf[i-1]=='\r')
		buf[--i]=0;
deuce's avatar
deuce committed
	else {
		if(buf != NULL)
			buf[i]=0;
	}
	return(i);
deuce's avatar
deuce committed
static int recvbufsocket(http_session_t *session, char *buf, long count)
	int		i;
	time_t	start;
deuce's avatar
deuce committed
	while(rd<count && session_check(session,NULL,NULL,startup->max_inactivity*1000))  {
		i=sess_recv(session,buf+rd,count-rd,0);
deuce's avatar
deuce committed
				if(session->is_tls || ERROR_VALUE!=EAGAIN)
					close_session_socket(session);
deuce's avatar
deuce committed
				close_session_socket(session);
		}

		rd+=i;
		start=time(NULL);
	return(0);
static void unescape(char *p)
{
	char *	dst;
	char	code[3];
	
	dst=p;
	for(;*p;p++) {
		if(*p=='%' && isxdigit((uchar)*(p+1)) && isxdigit((uchar)*(p+2))) {
			sprintf(code,"%.2s",p+1);
			*(dst++)=(char)strtol(code,NULL,16);
			p+=2;
		}
		else  {
			if(*p=='+')  {
				*(dst++)=' ';
			}
			else  {
				*(dst++)=*p;
			}
		}
	}
	*(dst)=0;
}

static void js_add_queryval(http_session_t * session, char *key, char *value)
	jsuint		len;
	int			alen;

	/* Return existing object if it's already been created */
	if(JS_GetProperty(session->js_cx,session->js_query,key,&val) && val!=JSVAL_VOID)  {
		keyarray = JSVAL_TO_OBJECT(val);
		alen=-1;
	}
	else {
		keyarray = JS_NewArrayObject(session->js_cx, 0, NULL);
		if(!JS_DefineProperty(session->js_cx, session->js_query, key, OBJECT_TO_JSVAL(keyarray)
			, NULL, NULL, JSPROP_ENUMERATE))
			return;
		alen=0;
	}

	if(alen==-1) {
		if(JS_GetArrayLength(session->js_cx, keyarray, &len)==JS_FALSE)
			return;
deuce's avatar
deuce committed
		alen=len;
deuce's avatar
deuce committed
	lprintf(LOG_DEBUG,"%04d Adding query value %s=%s at pos %d",session->socket,key,value,alen);
	val=STRING_TO_JSVAL(JS_NewStringCopyZ(session->js_cx,value));
	JS_SetElement(session->js_cx, keyarray, alen, &val);
}

static void js_add_cookieval(http_session_t * session, char *key, char *value)
{
	JSObject*	keyarray;
	jsval		val;
	jsuint		len;
	int			alen;

	/* Return existing object if it's already been created */
	if(JS_GetProperty(session->js_cx,session->js_cookie,key,&val) && val!=JSVAL_VOID)  {
		keyarray = JSVAL_TO_OBJECT(val);
		alen=-1;
	}
	else {
		keyarray = JS_NewArrayObject(session->js_cx, 0, NULL);
		if(!JS_DefineProperty(session->js_cx, session->js_cookie, key, OBJECT_TO_JSVAL(keyarray)
			, NULL, NULL, JSPROP_ENUMERATE))
			return;
		alen=0;
	}

	if(alen==-1) {
		if(JS_GetArrayLength(session->js_cx, keyarray, &len)==JS_FALSE)
			return;
		alen=len;
	}

	lprintf(LOG_DEBUG,"%04d Adding cookie value %s=%s at pos %d",session->socket,key,value,alen);
	val=STRING_TO_JSVAL(JS_NewStringCopyZ(session->js_cx,value));
	JS_SetElement(session->js_cx, keyarray, alen, &val);
}

static void js_add_request_prop(http_session_t * session, char *key, char *value)
	if(session->js_cx==NULL || session->js_request==NULL)
		return;
	if(key==NULL || value==NULL)
		return;
	if((js_str=JS_NewStringCopyZ(session->js_cx, value))==NULL)
		return;
	JS_DefineProperty(session->js_cx, session->js_request, key, STRING_TO_JSVAL(js_str)
		,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY);
}

static void js_add_header(http_session_t * session, char *key, char *value)  
{
	JSString*	js_str;
deuce's avatar
deuce committed
	if((lckey=strdup(key))==NULL)
	strlwr(lckey);
	if((js_str=JS_NewStringCopyZ(session->js_cx, value))==NULL) {
		return;
	}
	JS_DefineProperty(session->js_cx, session->js_header, lckey, STRING_TO_JSVAL(js_str)
		,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY);
deuce's avatar
deuce committed
	free(lckey);
#if 0
static void js_parse_multipart(http_session_t * session, char *p)  {
	size_t		key_len;
	size_t		value_len;
	char		*lp;
	char		*key;
	char		*value;

	if(p == NULL)
		return;

	lp=p;

	while((key_len=strcspn(lp,"="))!=0)  {
		key=lp;
		lp+=key_len;
		if(*lp) {
			*lp=0;
			lp++;
		}
		value_len=strcspn(lp,"&");
		value=lp;
		lp+=value_len;
		if(*lp) {
			*lp=0;
			lp++;
		}
		unescape(value);
		unescape(key);
		js_add_queryval(session, key, value);
	}
}
#endif

static void js_parse_query(http_session_t * session, char *p)  {
	size_t		key_len;
	size_t		value_len;
	char		*lp;
	while((key_len=strcspn(lp,"="))!=0)  {
		if(*lp) {
			*lp=0;
			lp++;
		}
		value_len=strcspn(lp,"&");
		value=lp;
deuce's avatar
deuce committed
		lp+=value_len;
		if(*lp) {
			*lp=0;
			lp++;
		}
		js_add_queryval(session, key, value);
static char *get_token_value(char **p)
{
	char	*pos=*p;
	char	*start;
	char	*out;
	BOOL	escaped=FALSE;

	start=pos;
	out=start;
	if(*pos=='"') {
		for(pos++; *pos; pos++) {
			if(escaped && *pos)
				*(out++)=*pos;
			else if(*pos=='"') {
				pos++;
				break;
			}
			else if(*pos=='\\')
				escaped=TRUE;
			else
				*(out++)=*pos;
		}
	}
	else {
		for(; *pos; pos++) {
			if(iscntrl(*pos))
			switch(*pos) {
				case 0:
				case '(':
				case ')':
				case '<':
				case '>':
				case '@':
				case ',':
				case ';':
				case ':':
				case '\\':
				case '"':
				case '/':
				case '[':
				case ']':
				case '?':
				case '=':
				case '{':
				case '}':
				case ' ':
				case '\t':
					goto end_of_text;
			}
			*(out++)=*pos;
		}
	}
end_of_text:
	while(*pos==',' || isspace(*pos))
		pos++;
	*out=0;
static int hexval(unsigned char ch)
{
	ch-='0';
	if(ch<10)
		return(ch);
	ch-=7;
	if(ch<16 && ch>9)
		return(ch);
	if(ch>41) {
		ch-=32;
		if(ch<16 && ch>9)
			return(ch);
	}
	return(0);
}

static BOOL parse_headers(http_session_t * session)
{
	char	*tvalue;
	char	env_name[128];
	for(idx=0;session->req.headers[idx]!=NULL;idx++) {
		/* TODO: strdup() is possibly too slow here... */
		head_line=strdup(session->req.headers[idx]);
		if((strtok_r(head_line,":",&last))!=NULL && (value=strtok_r(NULL,"",&last))!=NULL) {
			i=get_header_type(head_line);
			while(*value && *value<=' ') value++;
			switch(i) {
				case HEAD_AUTH:
deuce's avatar
deuce committed
					/* If you're authenticated via TLS-PSK, you can't use basic or digest */
					if (session->req.auth.type != AUTHENTICATION_TLS_PSK) {
						if((p=strtok_r(value," ",&last))!=NULL) {
							if(stricmp(p, "Basic")==0) {
								p=strtok_r(NULL," ",&last);
								if(p==NULL)
deuce's avatar
deuce committed
								while(*p && *p<' ') p++;
								b64_decode(p,strlen(p),p,strlen(p));
								p=strtok_r(p,":",&last);
deuce's avatar
deuce committed
									if(strlen(p) >= sizeof(session->req.auth.username))
deuce's avatar
deuce committed
									SAFECOPY(session->req.auth.username, p);
									p=strtok_r(NULL,":",&last);
									if(p) {
										if(strlen(p) >= sizeof(session->req.auth.password))
											break;
										SAFECOPY(session->req.auth.password, p);
										session->req.auth.type=AUTHENTICATION_BASIC;
									}
deuce's avatar
deuce committed
							else if(stricmp(p, "Digest")==0) {
								p=strtok_r(NULL, "", &last);
								/* Defaults */
								session->req.auth.algorithm=ALGORITHM_MD5;
								session->req.auth.type=AUTHENTICATION_DIGEST;
								/* Parse out values one at a time and store */
								while(*p) {
									while(isspace(*p))
										p++;
									if(strnicmp(p,"username=",9)==0) {
										p+=9;
										tvalue=get_token_value(&p);
										if(strlen(tvalue) >= sizeof(session->req.auth.username))
											break;
										SAFECOPY(session->req.auth.username, tvalue);
									}
									else if(strnicmp(p,"realm=",6)==0) {
										p+=6;
										session->req.auth.realm=strdup(get_token_value(&p));
									}
									else if(strnicmp(p,"nonce=",6)==0) {
										p+=6;
										session->req.auth.nonce=strdup(get_token_value(&p));
									}
									else if(strnicmp(p,"uri=",4)==0) {
										p+=4;
										session->req.auth.digest_uri=strdup(get_token_value(&p));
									}
									else if(strnicmp(p,"response=",9)==0) {
										p+=9;
										tvalue=get_token_value(&p);
										if(strlen(tvalue)==32) {
											for(i=0; i<16; i++) {
												session->req.auth.digest[i]=hexval(tvalue[i*2])<<4 | hexval(tvalue[i*2+1]);
											}
deuce's avatar
deuce committed
									else if(strnicmp(p,"algorithm=",10)==0) {
										p+=10;
										tvalue=get_token_value(&p);
										if(stricmp(tvalue,"MD5")==0) {
											session->req.auth.algorithm=ALGORITHM_MD5;
										}
										else {
											session->req.auth.algorithm=ALGORITHM_UNKNOWN;
										}
deuce's avatar
deuce committed
									else if(strnicmp(p,"cnonce=",7)==0) {
										p+=7;
										session->req.auth.cnonce=strdup(get_token_value(&p));
deuce's avatar
deuce committed
									else if(strnicmp(p,"qop=",4)==0) {
										p+=4;
										tvalue=get_token_value(&p);
										if(stricmp(tvalue,"auth")==0) {
											session->req.auth.qop_value=QOP_AUTH;
										}
										else if (stricmp(tvalue,"auth-int")==0) {
											session->req.auth.qop_value=QOP_AUTH_INT;
										}
										else {
											session->req.auth.qop_value=QOP_UNKNOWN;
										}
deuce's avatar
deuce committed
									else if(strnicmp(p,"nc=",3)==0) {
										p+=3;
										session->req.auth.nonce_count=strdup(get_token_value(&p));
deuce's avatar
deuce committed
										while(*p && *p != '=')
											p++;
										if(*p == '=')
											get_token_value(&p);
deuce's avatar
deuce committed
								if(session->req.auth.digest_uri==NULL)
									session->req.auth.digest_uri=strdup(session->req.request_line);
								/* Validate that we have the required values... */
								switch(session->req.auth.qop_value) {
									case QOP_NONE:
										if(session->req.auth.realm==NULL
												|| session->req.auth.nonce==NULL
												|| session->req.auth.digest_uri==NULL)
											send_error(session,"400 Bad Request");
										break;
									case QOP_AUTH:
									case QOP_AUTH_INT:
										if(session->req.auth.realm==NULL
												|| session->req.auth.nonce==NULL
												|| session->req.auth.nonce_count==NULL
												|| session->req.auth.cnonce==NULL
												|| session->req.auth.digest_uri==NULL)
											send_error(session,"400 Bad Request");
										break;
									default:
										send_error(session,"400 Bad Request");
deuce's avatar
deuce committed
										break;
								}
					add_env(session,"CONTENT_LENGTH",value);
deuce's avatar
deuce committed
					content_len=strtol(value,NULL,10);
					break;
				case HEAD_IFMODIFIED:
					session->req.if_modified_since=decode_date(value);
					break;
				case HEAD_CONNECTION:
					if(!stricmp(value,"Keep-Alive")) {
						session->req.keep_alive=TRUE;
					}
					if(!stricmp(value,"Close")) {
						session->req.keep_alive=FALSE;
					}
					if(session->req.ld!=NULL) {
						FREE_AND_NULL(session->req.ld->referrer);
						/* FREE()d in http_logging_thread() */
						session->req.ld->referrer=strdup(value);
					if(session->req.ld!=NULL) {
						FREE_AND_NULL(session->req.ld->agent);
						/* FREE()d in http_logging_thread() */
						session->req.ld->agent=strdup(value);
				case HEAD_TRANSFER_ENCODING:
					if(!stricmp(value,"chunked"))
						session->req.read_chunked=TRUE;
					else
						send_error(session,"501 Not Implemented");
					break;
				case HEAD_RANGE:
					if(!stricmp(value,"bytes=")) {
						send_error(session,error_416);
						break;
					}
					value+=6;
					if(strchr(value,',')!=NULL) {	/* We don't do multiple ranges yet - TODO */
						send_error(session,error_416);
						break;
					}
					/* Check for offset from end. */
					if(*value=='-') {
						session->req.range_start=strtol(value,NULL,10);
						session->req.range_end=-1;
						break;
					}
					if((p=strtok_r(value,"-",&last))!=NULL) {
						session->req.range_start=strtol(p,NULL,10);
						if((p=strtok_r(NULL,"-",&last))!=NULL)
							session->req.range_end=strtol(p,NULL,10);
						else
							session->req.range_end=-1;
					}
					else {
						send_error(session,error_416);
deuce's avatar
deuce committed
				case HEAD_IFRANGE:
					session->req.if_range=decode_date(value);
					break;
				case HEAD_TYPE:
					add_env(session,"CONTENT_TYPE",value);
					break;
				default:
					break;
			}
			sprintf(env_name,"HTTP_%s",head_line);
			add_env(session,env_name,value);
		}
		free(head_line);
	}
	if(content_len)
		session->req.post_len = content_len;
	add_env(session,"SERVER_NAME",session->req.host[0] ? session->req.host : startup->host_name );
	return TRUE;
}

static BOOL parse_js_headers(http_session_t * session)
{
	char	*head_line;
	char	*value;
	char	*last;
	char	*p;
	int		i;
	size_t	idx;

	for(idx=0;session->req.headers[idx]!=NULL;idx++) {
		head_line=session->req.headers[idx];
		if((strtok_r(head_line,":",&last))!=NULL && (value=strtok_r(NULL,"",&last))!=NULL) {
			i=get_header_type(head_line);
			while(*value && *value<=' ') value++;
			js_add_header(session,head_line,value);
			switch(i) {
				case HEAD_TYPE:
					if(session->req.dynamic==IS_SSJS || session->req.dynamic==IS_JS) {
						/*
						 * We need to parse out the files based on RFC1867
						 *
						 * And example reponse looks like this:
						 * Content-type: multipart/form-data, boundary=AaB03x
						 * 
						 * --AaB03x
						 * content-disposition: form-data; name="field1"
						 * 
						 * Joe Blow
						 * --AaB03x
						 * content-disposition: form-data; name="pics"
						 * Content-type: multipart/mixed, boundary=BbC04y
						 * 
						 * --BbC04y
						 * Content-disposition: attachment; filename="file1.txt"
						 * 
						 * Content-Type: text/plain
						 * 
						 * ... contents of file1.txt ...
						 * --BbC04y
						 * Content-disposition: attachment; filename="file2.gif"
						 * Content-type: image/gif
						 * Content-Transfer-Encoding: binary
						 * 
						 * ...contents of file2.gif...
						 * --BbC04y--
						 * --AaB03x--						 
						 */
					}
					break;
					if(session->req.dynamic==IS_SSJS || session->req.dynamic==IS_JS) {
						while((key=strtok_r(p,"=",&last))!=NULL) {
							while(isspace(*key))
								key++;
							if((val=strtok_r(p,";\t\n\v\f\r ",&last))!=NULL) {	/* Whitespace */
				default:
					break;
			}
		}
	}
	return TRUE;
}

static int get_version(char *p)
{
	int		i;
	if(p==NULL)
		return(0);
	while(*p && *p<' ') p++;
	if(*p==0)
		return(0);
	for(i=1;http_vers[i]!=NULL;i++) {
		if(!stricmp(p,http_vers[i])) {
			return(i);
		}
	}
	return(i-1);
}

static int is_dynamic_req(http_session_t* session)
{
	char	fname[MAX_PATH+1];
	char	ext[MAX_PATH+1];
	check_extra_path(session);
	_splitpath(session->req.physical_path, drive, dir, fname, ext);
	if(stricmp(ext,startup->ssjs_ext)==0)
		i=IS_SSJS;
	else if(get_xjs_handler(ext,session))
		i=IS_SSJS;
	else if(stricmp(ext,startup->js_ext)==0)
		i=IS_JS;
	if(!(startup->options&BBS_OPT_NO_JAVASCRIPT) && i)  {
rswindell's avatar
rswindell committed
		lprintf(LOG_DEBUG,"%04d Setting up JavaScript support", session->socket);
			lprintf(LOG_ERR,"%04d !ERROR setting up JavaScript support", session->socket);
			send_error(session,error_500);
	if(!(startup->options&WEB_OPT_NO_CGI)) {
		for(i=0; startup->cgi_ext!=NULL && startup->cgi_ext[i]!=NULL; i++)  {
			if(stricmp(ext,startup->cgi_ext[i])==0)  {
				init_enviro(session);
		_splitpath(session->req.cgi_dir?session->req.cgi_dir:cgi_dir, cgidrive, cgidir, fname, ext);
		if(stricmp(dir,cgidir)==0 && stricmp(drive,cgidrive)==0)  {
			init_enviro(session);
			return(IS_CGI);
static char *get_request(http_session_t * session, char *req_line)
	char*	query;
	SAFECOPY(session->req.virtual_path,req_line);
	if(strtok_r(session->req.virtual_path," \t",&last))
		retval=strtok_r(NULL," \t",&last);
	else
		retval=NULL;
	SAFECOPY(session->req.request_line,session->req.virtual_path);
	if(strtok_r(session->req.virtual_path,"?",&last))
		query=strtok_r(NULL,"",&last);
	else
		query=NULL;

	/* Must initialize physical_path before calling is_dynamic_req() */
	SAFECOPY(session->req.physical_path,session->req.virtual_path);
	unescape(session->req.physical_path);
	if(!strnicmp(session->req.physical_path,http_scheme,http_scheme_len)) {
deuce's avatar
deuce committed
		/* Remove http:// from start of physical_path */
		memmove(session->req.physical_path, session->req.physical_path+http_scheme_len, strlen(session->req.physical_path+http_scheme_len)+1);

		/* Set HOST value... ignore HOST header */
deuce's avatar
deuce committed
		SAFECOPY(session->req.host,session->req.physical_path);

		/* Remove path if present (everything after the first /) */
		strtok_r(session->req.host,"/",&last);

deuce's avatar
deuce committed
		/* Set vhost value to host value */
		SAFECOPY(session->req.vhost,session->req.host);
deuce's avatar
deuce committed

		/* Remove port specification from vhost (if present) */
		strtok_r(session->req.vhost,":",&last);
		/* Sets p to point to the first character after the first slash */
		p=strchr(session->req.physical_path, '/');
		 * If we have a slash, make it the first char in the string.
		 * otherwise, set path to "/"
			strcpy(session->req.physical_path, "/");
deuce's avatar
deuce committed
		}
		else {
			offset=p-session->req.physical_path;
			memmove(session->req.physical_path
				,session->req.physical_path+offset
				,strlen(session->req.physical_path+offset)+1	/* move '\0' terminator too */
				);
		SAFECOPY(session->req.query_str,query);
static char *get_method(http_session_t * session, char *req_line)
{
	int i;

	for(i=0;methods[i]!=NULL;i++) {
		if(!strnicmp(req_line,methods[i],strlen(methods[i]))) {
			session->req.method=i;
			if(strlen(req_line)<strlen(methods[i])+2) {
				send_error(session,"400 Bad Request");
				return(NULL);
			}
			return(req_line+strlen(methods[i])+1);
		}
	}
rswindell's avatar
rswindell committed
	if(req_line!=NULL && *req_line>=' ')
		send_error(session,"501 Not Implemented");
static BOOL get_request_headers(http_session_t * session)
{
	char	head_line[MAX_REQUEST_LINE+1];
	char	next_char;
	char	*value;
	int		i;

	while(sockreadline(session,head_line,sizeof(head_line)-1)>0) {
		/* Multi-line headers */
deuce's avatar
deuce committed
		while((i=sess_recv(session,&next_char,1,MSG_PEEK))>0
			&& (next_char=='\t' || next_char==' ')) {
deuce's avatar
deuce committed
			if(i==-1 && (session->is_tls || ERROR_VALUE != EAGAIN))
				close_session_socket(session);
			i=strlen(head_line);
			if(i>sizeof(head_line)-1) {
				lprintf(LOG_ERR,"%04d !ERROR long multi-line header. The web server is broken!", session->socket);
				i=sizeof(head_line)/2;
				break;
			}
			sockreadline(session,head_line+i,sizeof(head_line)-i-1);
		}
		strListPush(&session->req.headers,head_line);
		if((strtok_r(head_line,":",&last))!=NULL && (value=strtok_r(NULL,"",&last))!=NULL) {
			i=get_header_type(head_line);
			while(*value && *value<=' ') value++;
			switch(i) {
				case HEAD_HOST:
					if(session->req.host[0]==0) {
						SAFECOPY(session->req.host,value);
						SAFECOPY(session->req.vhost,value);
						/* Remove port part of host (Win32 doesn't allow : in dir names) */
						/* Either an existing : will be replaced with a null, or nothing */
						/* Will happen... the return value is not relevent here */
						strtok_r(session->req.vhost,":",&last);
		SAFECOPY(session->req.vhost, startup->host_name);
	if(!(session->req.host[0]))
		SAFECOPY(session->req.host, startup->host_name);
	return TRUE;
}

static BOOL get_fullpath(http_session_t * session)
{
	char	str[MAX_PATH+1];

	if(session->req.vhost[0] && startup->options&WEB_OPT_VIRTUAL_HOSTS) {
		safe_snprintf(str,sizeof(str),"%s/%s",root_dir,session->req.vhost);
			safe_snprintf(str,sizeof(str),"%s/%s%s",root_dir,session->req.vhost,session->req.physical_path);
		else
			safe_snprintf(str,sizeof(str),"%s%s",root_dir,session->req.physical_path);
	} else
		safe_snprintf(str,sizeof(str),"%s%s",root_dir,session->req.physical_path);
	if(FULLPATH(session->req.physical_path,str,sizeof(session->req.physical_path))==NULL)
	return(isabspath(session->req.physical_path));
static BOOL get_req(http_session_t * session, char *request_line)
	char	req_line[MAX_REQUEST_LINE+1];

	req_line[0]=0;
	if(request_line == NULL) {
		/* Eat leaing blank lines... as apache does...
		 * "This is a legacy issue. The CERN webserver required POST data to have an extra
		 * CRLF following it. Thus many clients send an extra CRLF that is not included in the
		 * Content-Length of the request. Apache works around this problem by eating any empty
		 * lines which appear before a request."
		 * http://httpd.apache.org/docs/misc/known_client_problems.html
		 */
		while((len=sockreadline(session,req_line,sizeof(req_line)-1))==0);
		if(len<0)
		if(req_line[0])
			lprintf(LOG_INFO,"%04d Request: %s",session->socket,req_line);
		if(session->req.ld!=NULL && session->req.ld->request==NULL)
			/* FREE()d in http_logging_thread() */
			session->req.ld->request=strdup(req_line);
		lprintf(LOG_DEBUG,"%04d Handling Internal Redirect to: %s",session->socket,request_line);
		SAFECOPY(req_line,request_line);
		p=NULL;
		p=get_method(session,req_line);
		if(p!=NULL) {
			p=get_request(session,p);
			session->http_ver=get_version(p);
			if(session->http_ver>=HTTP_1_1)
				session->req.keep_alive=TRUE;
			if(!is_redir)
				get_request_headers(session);
			if(!get_fullpath(session)) {
				send_error(session,error_500);
			if(session->req.ld!=NULL && session->req.ld->vhost==NULL)
				/* FREE()d in http_logging_thread() */
				session->req.ld->vhost=strdup(session->req.vhost);
			session->req.dynamic=is_dynamic_req(session);
				add_env(session,"QUERY_STRING",session->req.query_str);
			add_env(session,"REQUEST_METHOD",methods[session->req.method]);
			add_env(session,"SERVER_PROTOCOL",session->http_ver ? 
				http_vers[session->http_ver] : "HTTP/0.9");
			return(TRUE);
	session->req.keep_alive=FALSE;
	send_error(session,"400 Bad Request");
/* This may exist somewhere else - ToDo */
static char *find_last_slash(char *str)
{
#ifdef _WIN32
	char * LastFSlash;
	char * LastBSlash;
	LastFSlash=strrchr(str,'/');