Newer
Older
/* websrvr.c */
/* Synchronet Web Server */
/* $Id$ */
/****************************************************************************
* @format.tab-size 4 (Plain Text/Source Code File Header) *
* @format.use-tabs true (see http://www.synchro.net/ptsc_hdr.html) *
* *
* Copyright 2004 Rob Swindell - http://www.synchro.net/copyright.html *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License *
* as published by the Free Software Foundation; either version 2 *
* of the License, or (at your option) any later version. *
* See the GNU General Public License for more details: gpl.txt or *
* http://www.fsf.org/copyleft/gpl.html *
* *
* Anonymous FTP access to the most recent released source is available at *
* ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net *
* *
* Anonymous CVS access to the development source and modification history *
* is available at cvs.synchro.net:/cvsroot/sbbs, example: *
* cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login *
* (just hit return, no password is necessary) *
* cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src *
* *
* For Synchronet coding style and modification guidelines, see *
* http://www.synchro.net/source.html *
* *
* You are encouraged to submit any modifications (preferably in Unix diff *
* format) via e-mail to mods@synchro.net *
* *
* Note: If this box doesn't appear square, then you need to fix your tabs. *
****************************************************************************/
/*
* General notes: (ToDo stuff)
* strtok() is used a LOT in here... notice that there is a strtok_r() for reentrant...
* this may imply that strtok() is NOT thread-safe... if in fact it isn't this HAS
* to be fixed before any real production-level quality is achieved with this web server
* however, strtok_r() may not be a standard function.
*
* RE: not sending the headers if an nph scrpit is detected. (The headers buffer could
* just be free()ed and NULLed)
*
* Currently, all SSJS requests for a session are ran in the same context without clearing the context in
* any way. This behaviour should not be relied on as it may disappear in the future... this will require
* some thought as it COULD be handy in some circumstances and COULD cause weird bugs in others.
*
* Dynamic content is always resent on an If-Modified-Since request... this may not be optimal behaviour
* for GET requests...
*
* Should support RFC2617 Digest auth.
*
* Fix up all the logging stuff.
*
* SSJS stuff could work using three different methods:
* 1) Temporary file as happens currently
* Advantages:
* Allows to keep current connection (keep-alive works)
* write() doesn't need to be "special"
* Disadvantages:
* Depends on the temp dir being writable and capable of holding
* the full reply
* Everything goes throug the disk, so probobly some performance
* penalty is involved
* No way of sending directly to the remote system
* 2) nph- style
* Advantages:
* No file I/O involved
* Can do magic tricks (ala my perl web wrapper)
* Disadvantages:
* Pretty much everything needs to be handled by the script.
* 3) Return body in http_reply object
* All the advantages of 1)
* Could use a special write() to make everything just great.
* Still doesn't allow page to be sent until fully composed (ie: long
* delays)
* 4) Type three with a callback that sends the header and current body, then
* converts write() to send directly to remote.
*
* Add in support to pass connections through to a different webserver...
* probobly in access.ars... with like a simplified mod_rewrite.
* This would allow people to run apache and Synchronet as the same site.
#if defined(__unix__)
#include <sys/wait.h> /* waitpid() */
#include <sys/types.h>
#include <signal.h> /* kill() */
#define JAVASCRIPT
#undef SBBS /* this shouldn't be defined unless building sbbs.dll/libsbbs.so */
#include "sbbs.h"
#include "sockwrap.h" /* sendfilesocket() */
#include "threadwrap.h" /* pthread_mutex_t */
#include "semwrap.h"
#include "websrvr.h"
static const char* server_name="Synchronet Web Server";
static const char* newline="\r\n";
static const char* http_scheme="http://";
static const size_t http_scheme_len=7;
static const char* error_404="404 Not Found";
static const char* error_500="500 Internal Server Error";
static const char* unknown="<unknown>";
/* Is this not in a header somewhere? */
extern const uchar* nular;
#define MAX_REQUEST_LINE 1024 /* NOT including terminator */
#define MAX_HEADERS_SIZE 16384 /* Maximum total size of all headers
(Including terminator )*/
#define MAX_REDIR_LOOPS 20 /* Max. times to follow internal redirects for a single request */
static scfg_t scfg;
static BOOL scfg_reloaded=TRUE;
static BOOL http_logging_thread_running=FALSE;
static ulong active_clients=0;
static ulong sockets=0;
static BOOL terminate_server=FALSE;
static BOOL terminate_http_logging_thread=FALSE;
static uint thread_count=0;
static SOCKET server_socket=INVALID_SOCKET;
static char revision[16];
static char root_dir[MAX_PATH+1];
static char error_dir[MAX_PATH+1];
static time_t uptime=0;
static DWORD served=0;
static web_startup_t* startup=NULL;
static js_server_props_t js_server_props;
static link_list_t recycle_semfiles;
static link_list_t shutdown_semfiles;
static named_string_t** mime_types;
/* Logging stuff */
sem_t log_sem;
pthread_mutex_t log_mutex;
link_list_t log_list;
struct log_data {
char *hostname;
char *ident;
char *user;
char *request;
char *referrer;
char *agent;
int status;
unsigned int size;
struct tm completed;
};
typedef struct {
char virtual_path[MAX_PATH+1];
char physical_path[MAX_PATH+1];
BOOL parsed_headers;
BOOL expect_go_ahead;
time_t if_modified_since;
BOOL keep_alive;
char ars[256];
char auth[128]; /* UserID:Password */
char host[128]; /* The requested host. (virtual hosts) */
int send_location;
const char* mime_type;
char status[MAX_REQUEST_LINE+1];
char * post_data;
size_t post_len;
int dynamic;
struct log_data *ld;
/* CGI parameters */
char query_str[MAX_REQUEST_LINE+1];
char extra_path_info[MAX_REQUEST_LINE+1];
link_list_t cgi_env;
link_list_t dynamic_heads;
/* Dynamically (sever-side JS) generated HTML parameters */
FILE* fp;
} http_request_t;
typedef struct {
SOCKET socket;
SOCKADDR_IN addr;
http_request_t req;
char host_ip[64];
char host_name[128]; /* Resolved remote host */
int http_ver; /* HTTP version. 0 = HTTP/0.9, 1=HTTP/1.0, 2=HTTP/1.1 */
BOOL finished; /* Do not accept any more imput from client */
user_t user;
int last_user_num;
time_t logon_time;
int last_js_user_num;
/* JavaScript parameters */
JSRuntime* js_runtime;
JSContext* js_cx;
JSObject* js_glob;
JSObject* js_query;
JSObject* js_header;
JSObject* js_request;
/* Client info */
client_t client;
} http_session_t;
enum {
HTTP_0_9
,HTTP_1_0
,HTTP_1_1
};
static char* http_vers[] = {
""
,"HTTP/1.0"
,"HTTP/1.1"
};
enum {
HTTP_HEAD
,HTTP_GET
};
IS_STATIC
,IS_CGI
,IS_JS
,IS_SSJS
};
HEAD_DATE
,HEAD_HOST
,HEAD_IFMODIFIED
,HEAD_LENGTH
,HEAD_TYPE
,HEAD_AUTH
,HEAD_CONNECTION
,HEAD_WWWAUTH
,HEAD_STATUS
,HEAD_ALLOW
,HEAD_EXPIRES
,HEAD_LASTMODIFIED
,HEAD_LOCATION
,HEAD_PRAGMA
,HEAD_SERVER
,HEAD_REFERER
,HEAD_AGENT
};
static struct {
int id;
char* text;
} headers[] = {
{ HEAD_DATE, "Date" },
{ HEAD_HOST, "Host" },
{ HEAD_IFMODIFIED, "If-Modified-Since" },
{ HEAD_LENGTH, "Content-Length" },
{ HEAD_TYPE, "Content-Type" },
{ HEAD_AUTH, "Authorization" },
{ HEAD_CONNECTION, "Connection" },
{ HEAD_WWWAUTH, "WWW-Authenticate" },
{ HEAD_STATUS, "Status" },
{ HEAD_ALLOW, "Allow" },
{ HEAD_EXPIRES, "Expires" },
{ HEAD_LASTMODIFIED, "Last-Modified" },
{ HEAD_LOCATION, "Location" },
{ HEAD_PRAGMA, "Pragma" },
{ HEAD_SERVER, "Server" },
{ HEAD_REFERER, "Referer" },
{ HEAD_AGENT, "User-Agent" },
/* Everything MOVED_TEMP and everything after is a magical internal redirect */
,MOVED_STAT
static char *days[]={"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
static char *months[]={"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"};
static void respond(http_session_t * session);
static BOOL js_setup(http_session_t* session);
static char *find_last_slash(char *str);
static BOOL check_extra_path(http_session_t * session);
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
static time_t
sub_mkgmt(struct tm *tm)
{
int y, nleapdays;
time_t t;
/* days before the month */
static const unsigned short moff[12] = {
0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
};
/*
* XXX: This code assumes the given time to be normalized.
* Normalizing here is impossible in case the given time is a leap
* second but the local time library is ignorant of leap seconds.
*/
/* minimal sanity checking not to access outside of the array */
if ((unsigned) tm->tm_mon >= 12)
return (time_t) -1;
if (tm->tm_year < 1970 - 1900)
return (time_t) -1;
y = tm->tm_year + 1900 - (tm->tm_mon < 2);
nleapdays = y / 4 - y / 100 + y / 400 -
((1970-1) / 4 - (1970-1) / 100 + (1970-1) / 400);
t = ((((time_t) (tm->tm_year - (1970 - 1900)) * 365 +
moff[tm->tm_mon] + tm->tm_mday - 1 + nleapdays) * 24 +
tm->tm_hour) * 60 + tm->tm_min) * 60 + tm->tm_sec;
return (t < 0 ? (time_t) -1 : t);
}
time_t
time_gm(struct tm *tm)
{
time_t t, t2;
struct tm *tm2;
int sec;
/* Do the first guess. */
if ((t = sub_mkgmt(tm)) == (time_t) -1)
return (time_t) -1;
/* save value in case *tm is overwritten by gmtime() */
sec = tm->tm_sec;
tm2 = gmtime(&t);
if ((t2 = sub_mkgmt(tm2)) == (time_t) -1)
return (time_t) -1;
if (t2 < t || tm2->tm_sec != sec) {
/*
* Adjust for leap seconds.
*
* real time_t time
* |
* tm
* / ... (a) first sub_mkgmt() conversion
* t
* |
* tm2
* / ... (b) second sub_mkgmt() conversion
* t2
* --->time
*/
/*
* Do the second guess, assuming (a) and (b) are almost equal.
*/
t += t - t2;
tm2 = gmtime(&t);
/*
* Either (a) or (b), may include one or two extra
* leap seconds. Try t, t + 2, t - 2, t + 1, and t - 1.
*/
if (tm2->tm_sec == sec
|| (t += 2, tm2 = gmtime(&t), tm2->tm_sec == sec)
|| (t -= 4, tm2 = gmtime(&t), tm2->tm_sec == sec)
|| (t += 3, tm2 = gmtime(&t), tm2->tm_sec == sec)
|| (t -= 2, tm2 = gmtime(&t), tm2->tm_sec == sec))
; /* found */
else {
/*
* Not found.
*/
if (sec >= 60)
/*
* The given time is a leap second
* (sec 60 or 61), but the time library
* is ignorant of the leap second.
*/
; /* treat sec 60 as 59,
sec 61 as 0 of the next minute */
else
/* The given time may not be normalized. */
t++; /* restore t */
}
}
return (t < 0 ? (time_t) -1 : t);
}
static int lprintf(int level, char *fmt, ...)
{
va_list argptr;
char sbuf[1024];
if(startup==NULL || startup->lputs==NULL)
return(0);
va_start(argptr,fmt);
vsnprintf(sbuf,sizeof(sbuf),fmt,argptr);
sbuf[sizeof(sbuf)-1]=0;
va_end(argptr);
return(startup->lputs(startup->cbdata,level,sbuf));
}
#ifdef _WINSOCKAPI_
static WSADATA WSAData;
#define SOCKLIB_DESC WSAData.szDescription
static BOOL WSAInitialized=FALSE;
static BOOL winsock_startup(void)
{
int status; /* Status Code */
if((status = WSAStartup(MAKEWORD(1,1), &WSAData))==0) {
lprintf(LOG_INFO,"%s %s",WSAData.szDescription, WSAData.szSystemStatus);
WSAInitialized=TRUE;
return (TRUE);
}
lprintf(LOG_ERR,"!WinSock startup ERROR %d", status);
return (FALSE);
}
#else /* No WINSOCK */
#define winsock_startup() (TRUE)
#define SOCKLIB_DESC NULL
#endif
static void status(char* str)
{
if(startup!=NULL && startup->status!=NULL)
startup->status(startup->cbdata,str);
}
static void update_clients(void)
{
if(startup!=NULL && startup->clients!=NULL)
startup->clients(startup->cbdata,active_clients);
}
static void client_on(SOCKET sock, client_t* client, BOOL update)
{
if(startup!=NULL && startup->client_on!=NULL)
startup->client_on(startup->cbdata,TRUE,sock,client,update);
}
static void client_off(SOCKET sock)
{
if(startup!=NULL && startup->client_on!=NULL)
startup->client_on(startup->cbdata,FALSE,sock,NULL,FALSE);
}
static void thread_up(BOOL setuid)
{
thread_count++;
if(startup!=NULL && startup->thread_up!=NULL)
startup->thread_up(startup->cbdata,TRUE, setuid);
}
static void thread_down(void)
{
if(thread_count>0)
thread_count--;
if(startup!=NULL && startup->thread_up!=NULL)
startup->thread_up(startup->cbdata,FALSE, FALSE);
/*********************************************************************/
/* Adds an environment variable to the sessions cgi_env linked list */
/*********************************************************************/
static void add_env(http_session_t *session, const char *name,const char *value) {
char newname[129];
lprintf(LOG_WARNING,"%04d Attempt to set NULL env variable", session->socket);
return;
}
SAFECOPY(newname,name);
for(p=newname;*p;p++) {
*p=toupper(*p);
if(*p=='-')
*p='_';
}
p=(char *)malloc(strlen(name)+strlen(value)+2);
if(p==NULL) {
lprintf(LOG_WARNING,"%04d Cannot allocate memory for string", session->socket);
return;
}
sprintf(p,"%s=%s",newname,value);
listPushNodeString(&session->req.cgi_env,p);
free(p);
/***************************************/
/* Initializes default CGI envirnoment */
/***************************************/
static void init_enviro(http_session_t *session) {
char str[128];
add_env(session,"SERVER_SOFTWARE",VERSION_NOTICE);
sprintf(str,"%d",startup->port);
add_env(session,"SERVER_PORT",str);
add_env(session,"GATEWAY_INTERFACE","CGI/1.1");
if(!strcmp(session->host_name,session->host_ip))
add_env(session,"REMOTE_HOST",session->host_name);
add_env(session,"REMOTE_ADDR",session->host_ip);
}
* Sends string str to socket sock... returns number of bytes written, or 0 on an error
* (Should it be -1 on an error?)
* Can not close the socket since it can not set it to INVALID_SOCKET
* ToDo - Decide error behaviour, should a SOCKET * be passed around rather than a socket?
*/
static int sockprint(SOCKET sock, const char *str)
{
int len;
int result;
int written=0;
if(sock==INVALID_SOCKET)
return(0);
if(startup->options&WEB_OPT_DEBUG_TX)
lprintf(LOG_DEBUG,"%04d TX: %s", sock, str);
while(socket_check(sock,NULL,&wr,startup->max_inactivity*1000) && wr && written<len) {
result=sendsocket(sock,str+written,len-written);
if(result==SOCKET_ERROR) {
if(ERROR_VALUE==ECONNRESET)
lprintf(LOG_NOTICE,"%04d Connection reset by peer on send",sock);
else if(ERROR_VALUE==ECONNABORTED)
lprintf(LOG_NOTICE,"%04d Connection aborted by peer on send",sock);
lprintf(LOG_WARNING,"%04d !ERROR %d sending on socket",sock,ERROR_VALUE);
return(0);
}
written+=result;
}
if(written != len) {
lprintf(LOG_WARNING,"%04d !ERROR %d sending on socket",sock,ERROR_VALUE);
return(0);
}
return(len);
}
/**********************************************************/
/* Converts a month name/abbr to the 0-based month number */
/* ToDo: This probobly exists somewhere else already */
/**********************************************************/
static int getmonth(char *mon)
{
int i;
for(i=0;i<12;i++)
if(!stricmp(mon,months[i]))
return(i);
return 0;
}
/*******************************************************************/
/* Converts a date string in any of the common formats to a time_t */
/*******************************************************************/
static time_t decode_date(char *date)
{
struct tm ti;
ti.tm_sec=0; /* seconds (0 - 60) */
ti.tm_min=0; /* minutes (0 - 59) */
ti.tm_hour=0; /* hours (0 - 23) */
ti.tm_mday=1; /* day of month (1 - 31) */
ti.tm_mon=0; /* month of year (0 - 11) */
ti.tm_year=0; /* year - 1900 */
ti.tm_isdst=0; /* is summer time in effect? */
if(token==NULL)
return(0);
/* This probobly only needs to be 9, but the extra one is for luck. */
if(strlen(date)>15) {
/* Toss away week day */
token=strtok(date," ");
if(token==NULL)
return(0);
token=strtok(NULL," ");
if(token==NULL)
return(0);
ti.tm_mon=getmonth(token);
token=strtok(NULL," ");
if(token==NULL)
return(0);
ti.tm_mday=atoi(token);
token=strtok(NULL,":");
if(token==NULL)
return(0);
ti.tm_hour=atoi(token);
token=strtok(NULL,":");
if(token==NULL)
return(0);
ti.tm_min=atoi(token);
token=strtok(NULL," ");
if(token==NULL)
return(0);
ti.tm_sec=atoi(token);
token=strtok(NULL,"");
if(token==NULL)
return(0);
ti.tm_year=atoi(token)-1900;
}
else {
/* RFC 1123 or RFC 850 */
token=strtok(NULL," -");
if(token==NULL)
return(0);
ti.tm_mday=atoi(token);
token=strtok(NULL," -");
if(token==NULL)
return(0);
ti.tm_mon=getmonth(token);
token=strtok(NULL," ");
if(token==NULL)
return(0);
ti.tm_year=atoi(token);
token=strtok(NULL,":");
if(token==NULL)
return(0);
ti.tm_hour=atoi(token);
token=strtok(NULL,":");
if(token==NULL)
return(0);
ti.tm_min=atoi(token);
token=strtok(NULL," ");
if(token==NULL)
return(0);
ti.tm_sec=atoi(token);
if(ti.tm_year>1900)
ti.tm_year -= 1900;
}
t=time_gm(&ti);
}
static SOCKET open_socket(int type)
{
char error[256];
SOCKET sock;
sock=socket(AF_INET, type, IPPROTO_IP);
if(sock!=INVALID_SOCKET && startup!=NULL && startup->socket_open!=NULL)
startup->socket_open(startup->cbdata,TRUE);
if(sock!=INVALID_SOCKET) {
if(set_socket_options(&scfg, sock,error))
lprintf(LOG_ERR,"%04d !ERROR %s",sock,error);
sockets++;
}
return(sock);
}
static int close_socket(SOCKET sock)
{
int result;
if(sock==INVALID_SOCKET)
return(-1);
shutdown(sock,SHUT_RDWR); /* required on Unix */
result=closesocket(sock);
if(startup!=NULL && startup->socket_open!=NULL) {
startup->socket_open(startup->cbdata,FALSE);
}
sockets--;
if(result!=0) {
if(ERROR_VALUE!=ENOTSOCK)
lprintf(LOG_WARNING,"%04d !ERROR %d closing socket",sock, ERROR_VALUE);
}
return(result);
}
/**************************************************/
/* End of a single request... */
/* This is called at the end of EVERY request */
/* Log the request */
/* Free request-specific data ie: dynamic stuff */
/* Close socket unless it's being kept alive */
/* If the socket is closed, the session is done */
/**************************************************/
static void close_request(http_session_t * session)
{
if(session->req.ld!=NULL) {
now=time(NULL);
localtime_r(&now,&session->req.ld->completed);
pthread_mutex_lock(&log_mutex);
listPushNode(&log_list,session->req.ld);
pthread_mutex_unlock(&log_mutex);
sem_post(&log_sem);
session->req.ld=NULL;
}
listFree(&session->req.headers);
listFree(&session->req.dynamic_heads);
listFree(&session->req.cgi_env);
FREE_AND_NULL(session->req.post_data);
close_socket(session->socket);
session->socket=INVALID_SOCKET;
if(session->socket==INVALID_SOCKET)
session->finished=TRUE;
if(session->js_cx!=NULL && (session->req.dynamic==IS_SSJS || session->req.dynamic==IS_JS)) {
lprintf(LOG_INFO,"%04d JavaScript: Garbage Collection",session->socket);
JS_GC(session->js_cx);
}
memset(&session->req,0,sizeof(session->req));
}
static int get_header_type(char *header)
{
for(i=0; headers[i].text!=NULL; i++) {
if(!stricmp(header,headers[i].text)) {
return(headers[i].id);
}
}
return(-1);
}
static char *get_header(int id)
{
if(headers[id].id==id)
return(headers[id].text);
for(i=0;headers[i].text!=NULL;i++) {
if(headers[i].id==id) {
return(headers[i].text);
}
}
return(NULL);
}
static const char* unknown_mime_type="application/octet-stream";
static const char* get_mime_type(char *ext)
{
uint i;
if(ext==NULL)
return(unknown_mime_type);
for(i=0;mime_types[i]!=NULL;i++)
if(!stricmp(ext+1,mime_types[i]->name))
return(mime_types[i]->value);
return(unknown_mime_type);
/* This function appends append plus a newline IF the final dst string would have a length less than maxlen */
static void safecat(char *dst, const char *append, size_t maxlen) {
size_t dstlen,appendlen;
dstlen=strlen(dst);
appendlen=strlen(append);
if(dstlen+appendlen+2 < maxlen) {
strcat(dst,append);
strcat(dst,newline);
}
}
/*************************************************/
/* Sends headers for the reply. */
/* HTTP/0.9 doesn't use headers, so just returns */
/*************************************************/
static BOOL send_headers(http_session_t *session, const char *status)
int ret;
const char *status_line;
struct stat stats;
struct tm tm;
char *headers;
lprintf(LOG_DEBUG,"%04d Request resolved to: %s"
,session->socket,session->req.physical_path);
if(session->http_ver <= HTTP_0_9) {
if(session->req.ld != NULL)
session->req.ld->status=atoi(status);
status_line=status;
ret=stat(session->req.physical_path,&stats);
if(!ret && session->req.if_modified_since && (stats.st_mtime <= session->req.if_modified_since) && !session->req.dynamic) {
status_line="304 Not Modified";
if(session->req.send_location==MOVED_PERM) {
status_line="301 Moved Permanently";
ret=-1;
send_file=FALSE;
}
if(session->req.send_location==MOVED_TEMP) {
status_line="302 Moved Temporarily";
ret=-1;
send_file=FALSE;
}
if(session->req.ld!=NULL)
session->req.ld->status=atoi(status_line);
headers=malloc(MAX_HEADERS_SIZE);
if(headers==NULL) {
lprintf(LOG_CRIT,"Could not allocate memory for response headers.");
return(FALSE);
}
*headers=0;
safe_snprintf(header,sizeof(header),"%s %s",http_vers[session->http_ver],status_line);
lprintf(LOG_DEBUG,"%04d Result: %s",session->socket,header);
safecat(headers,header,MAX_HEADERS_SIZE);
/* General Headers */
ti=time(NULL);
if(gmtime_r(&ti,&tm)==NULL)
memset(&tm,0,sizeof(tm));
safe_snprintf(header,sizeof(header),"%s: %s, %02d %s %04d %02d:%02d:%02d GMT"
,get_header(HEAD_DATE)
,days[tm.tm_wday],tm.tm_mday,months[tm.tm_mon]
,tm.tm_year+1900,tm.tm_hour,tm.tm_min,tm.tm_sec);
safecat(headers,header,MAX_HEADERS_SIZE);
if(session->req.keep_alive) {
safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_CONNECTION),"Keep-Alive");
safecat(headers,header,MAX_HEADERS_SIZE);
}
else {
safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_CONNECTION),"Close");
safecat(headers,header,MAX_HEADERS_SIZE);
}
/* Response Headers */
safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_SERVER),VERSION_NOTICE);
safecat(headers,header,MAX_HEADERS_SIZE);
/* Entity Headers */
if(session->req.dynamic) {
safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_ALLOW),"GET, HEAD, POST");
safecat(headers,header,MAX_HEADERS_SIZE);
}
else {
safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_ALLOW),"GET, HEAD");
safecat(headers,header,MAX_HEADERS_SIZE);
}
if(session->req.send_location) {
safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_LOCATION),(session->req.virtual_path));
safecat(headers,header,MAX_HEADERS_SIZE);
if(session->req.keep_alive) {
safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_LENGTH),"0");
safecat(headers,header,MAX_HEADERS_SIZE);
safe_snprintf(header,sizeof(header),"%s: %d",get_header(HEAD_LENGTH),(int)stats.st_size);
safecat(headers,header,MAX_HEADERS_SIZE);
if(!ret && !session->req.dynamic) {
safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_TYPE),session->req.mime_type);
safecat(headers,header,MAX_HEADERS_SIZE);
gmtime_r(&stats.st_mtime,&tm);
safe_snprintf(header,sizeof(header),"%s: %s, %02d %s %04d %02d:%02d:%02d GMT"
,days[tm.tm_wday],tm.tm_mday,months[tm.tm_mon]
,tm.tm_year+1900,tm.tm_hour,tm.tm_min,tm.tm_sec);
safecat(headers,header,MAX_HEADERS_SIZE);
}
if(session->req.dynamic) {
/* Dynamic headers */
/* Set up environment */
for(node=listFirstNode(&session->req.dynamic_heads);node!=NULL;node=listNextNode(node))
safecat(headers,listNodeData(node),MAX_HEADERS_SIZE);
safecat(headers,"",MAX_HEADERS_SIZE);
send_file = (sockprint(session->socket,headers) && send_file);
static int sock_sendfile(SOCKET socket,char *path)
long offset=0;
int ret=0;
if(startup->options&WEB_OPT_DEBUG_TX)
lprintf(LOG_DEBUG,"%04d Sending %s",socket,path);
if((file=open(path,O_RDONLY|O_BINARY))==-1)
lprintf(LOG_WARNING,"%04d !ERROR %d opening %s",socket,errno,path);
if((ret=sendfilesocket(socket, file, &offset, 0)) < 1) {
lprintf(LOG_DEBUG,"%04d !ERROR %d sending %s"
ret=0;
}
close(file);
}
return(ret);
/********************************************************/
/* Sends a specified error message, closes the request, */
/* and marks the session to be closed */
/********************************************************/
static void send_error(http_session_t * session, const char* message)
{
char error_code[4];
char sbuf[1024];
session->req.if_modified_since=0;
lprintf(LOG_INFO,"%04d !ERROR: %s",session->socket,message);
session->req.keep_alive=FALSE;
session->req.send_location=NO_LOCATION;
SAFECOPY(error_code,message);
sprintf(session->req.physical_path,"%s%s.html",error_dir,error_code);
session->req.mime_type=get_mime_type(strrchr(session->req.physical_path,'.'));
send_headers(session,message);
if(!stat(session->req.physical_path,&sb)) {
int snt=0;
snt=sock_sendfile(session->socket,session->req.physical_path);
if(snt<0)
snt=0;
if(session->req.ld!=NULL)
session->req.ld->size=snt;
}
lprintf(LOG_NOTICE,"%04d Error message file %s doesn't exist"
,session->socket,session->req.physical_path);
,"<HTML><HEAD><TITLE>%s Error</TITLE></HEAD>"
"<BODY><H1>%s Error</H1><BR><H3>In addition, "
"I can't seem to find the %s error file</H3><br>"
"please notify <a href=\"mailto:sysop@%s\">"
"%s</a></BODY></HTML>"
,error_code,error_code,error_code,scfg.sys_inetaddr,scfg.sys_op);
sockprint(session->socket,sbuf);
if(session->req.ld!=NULL)
session->req.ld->size=strlen(sbuf);
}
close_request(session);
}