/* HTTPServ.c ** HTTP SERVER MODULE ** ** (c) COPYRIGHT MIT 1995. ** Please first read the full copyright statement in the file COPYRIGH. ** @(#) $Id: HTTPServ.c,v 2.22 1996/10/07 02:05:30 frystyk Exp $ ** ** This module implments the HTTP protocol as a state machine ** ** History: ** Dec 95 HFN Written with Christmas in my eyes ** */ /* Library include files */ #include "sysdep.h" #include "WWWUtil.h" #include "WWWCore.h" #include "WWWMIME.h" #include "WWWStream.h" #include "WWWTrans.h" #include "HTNetMan.h" #include "HTTPUtil.h" #include "HTTPRes.h" #include "HTTPServ.h" /* Implements */ /* Macros and other defines */ #define PUTC(c) (*me->target->isa->put_character)(me->target, c) #define PUTS(s) (*me->target->isa->put_string)(me->target, s) #define PUTBLOCK(b, l) (*me->target->isa->put_block)(me->target, b, l) /* Final states have negative value */ typedef enum _HTTPState { HTTPS_ERROR = -2, HTTPS_OK = -1, HTTPS_BEGIN = 0, HTTPS_NEED_REQUEST, HTTPS_LOAD_CLIENT } HTTPState; /* This is the context object for the this module */ typedef struct _https_info { HTRequest * server; /* The server request */ HTList * clients; /* List of client requests */ HTTPState state; /* Current State of the connection */ } https_info; /* The HTTP Receive Stream */ struct _HTStream { const HTStreamClass * isa; HTStream * target; HTRequest * request; https_info * http; HTEOLState state; HTChunk * buffer; BOOL transparent; }; struct _HTInputStream { const HTInputStreamClass * isa; }; /* ------------------------------------------------------------------------- */ /* ServerCleanup ** ------------- ** This function cleans up after the request ** Returns YES on OK, else NO */ PRIVATE int ServerCleanup (HTRequest * req, HTNet * net, int status) { https_info * http = (https_info *) HTNet_context(net); HTStream * input = HTRequest_inputStream(req); HTChannel * channel = HTNet_channel(net); /* Free stream with data TO network */ if (input) { if (status == HT_INTERRUPTED) (*input->isa->abort)(input, NULL); else (*input->isa->_free)(input); HTRequest_setInputStream(req, NULL); } /* Kill all remaining requests */ if (http->clients) { HTList * cur = http->clients; HTRequest * pres; while ((pres = HTList_nextObject(cur)) != NULL) HTRequest_kill(pres); HTList_delete(http->clients); } /* ** Remove the net object and our own context structure for http. ** Also unregister all pending requests and close the connection */ HTChannel_setSemaphore(channel, 0); HTNet_delete(net, HT_IGNORE); HT_FREE(http); return YES; } /* ------------------------------------------------------------------------- */ /* REPLY STREAM */ /* ------------------------------------------------------------------------- */ /* ** This is our handle to the server reply stream when data is coming ** back from our "client" request. It is responsible for setting up the ** remaining streams in order to produce a complete HTTP output. ** If we have a HTTP 1.x response then forward untouched. */ PRIVATE int MakeReplyPipe (HTStream * me, HTRequest * client) { char * response_line = NULL; /* Generate the Response line */ { HTAlertCallback *cbf = HTAlert_find(HT_A_MESSAGE); if (cbf) { HTAlertPar * reply = HTAlert_newReply(); if ((*cbf)(client, HT_A_MESSAGE, HT_MSG_NULL, NULL, HTRequest_error(client), reply)) response_line = HTAlert_replyMessage(reply); HTAlert_deleteReply(reply); } if (response_line) { PUTS(response_line); HT_FREE(response_line); } else { PUTS(HTTP_VERSION); PUTS(" 500 Internal"); PUTC(CR); PUTC(LF); } } /* ** We now have to create the rest of the response stream. We see whether ** there is a data object or not by looking at the Content Type of the ** client anchor. */ { HTParentAnchor * anchor = HTRequest_anchor(client); HTFormat format = HTAnchor_format(anchor); me->target = (format == WWW_UNKNOWN) ? HTTPResponse_new(client, me->target, YES, HTTP_11) : HTMIMERequest_new(client, HTTPResponse_new(client,me->target, NO, HTTP_11), YES); } return HT_OK; } PRIVATE int HTTPReply_put_block (HTStream * me, const char * b, int l) { if (me->transparent) return b ? PUTBLOCK(b, l) : HT_OK; else { MakeReplyPipe(me, me->request); me->transparent = YES; return b ? PUTBLOCK(b, l) : HT_OK; } } PRIVATE int HTTPReply_put_string (HTStream * me, const char * s) { return HTTPReply_put_block(me, s, strlen(s)); } PRIVATE int HTTPReply_put_character (HTStream * me, char c) { return HTTPReply_put_block(me, &c, 1); } PRIVATE int HTTPReply_flush (HTStream * me) { int status = HTTPReply_put_block(me, NULL, 0); return status==HT_OK ? (*me->target->isa->flush)(me->target) : status; } PRIVATE int HTTPReply_free (HTStream * me) { int status = HTTPReply_put_block(me, NULL, 0); if (STREAM_TRACE) HTTrace("HTTPReply... Freeing server stream\n"); return status==HT_OK ? (*me->target->isa->_free)(me->target) : status; } PRIVATE int HTTPReply_abort (HTStream * me, HTList * e) { if (STREAM_TRACE) HTTrace("HTTPReply... ABORTING\n"); if (me->target) (*me->target->isa->abort)(me->target, e); HT_FREE(me); return HT_ERROR; } /* HTTPReply Stream ** ----------------- */ PRIVATE const HTStreamClass HTTPReplyClass = { "HTTPReply", HTTPReply_flush, HTTPReply_free, HTTPReply_abort, HTTPReply_put_character, HTTPReply_put_string, HTTPReply_put_block }; PRIVATE HTStream * HTTPReply_new (HTRequest * request, https_info * http, HTStream * target) { HTStream * me; if ((me = (HTStream *) HT_CALLOC(1, sizeof(HTStream))) == NULL) HT_OUTOFMEM("HTTPReply_new"); me->isa = &HTTPReplyClass; me->request = request; me->http = http; me->target = target; if (STREAM_TRACE) HTTrace("HTTP Reply.. Stream %p created\n", me); return me; } /* ------------------------------------------------------------------------- */ /* RECEIVE STREAM */ /* ------------------------------------------------------------------------- */ /* ** Scan the request line for METHOD, URI and VERSION ** Returns: HT_OK if 1.x request and OK ** HT_LOADED if 0.9 request and OK ** HT_ERROR if invalid request line */ PRIVATE int ParseRequest (HTStream * me) { HTRequest * client = HTList_firstObject(me->http->clients); char * line = HTChunk_data(me->buffer); char * method_str = HTNextField(&line); char * request_uri = HTNextField(&line); char * version_str = HTNextField(&line); HTMethod method; /* Check if method is allowed */ if (!method_str || (method = HTMethod_enum(method_str))==METHOD_INVALID) { HTRequest_addError(client, ERR_FATAL, NO, HTERR_NOT_ALLOWED, NULL, 0, "ParseRequest"); return HT_ERROR; } HTRequest_setMethod(client, method); /* Find an anchor for the request URI */ if (request_uri) { char * uri = HTParse(request_uri, "file:", PARSE_ALL); HTRequest_setAnchor(client, HTAnchor_findAddress(uri)); HT_FREE(uri); } else { HTRequest_addError(client, ERR_FATAL, NO, HTERR_BAD_REQUEST, NULL, 0, "ParseRequest"); return HT_ERROR; } /* Get ready to get the rest of the request */ if (version_str) { me->target = HTStreamStack(WWW_MIME_HEAD, HTRequest_debugFormat(client), HTRequest_debugStream(client), client, NO); return HT_OK; } else { HTRequest_addError(client, ERR_FATAL, NO, HTERR_BAD_VERSION, NULL, 0, "ParseRequest"); return HT_ERROR; } } /* ** Searches for HTTP Request Line before going into transparent mode */ PRIVATE int HTTPReceive_put_block (HTStream * me, const char * b, int l) { if (!me->transparent) { const char *p=b; while (l>0 && *p!=CR && *p!=LF) l--, p++; HTChunk_putb(me->buffer, b, p-b); if (*p==CR || *p==LF) { int status = ParseRequest(me); HTChunk_clear(me->buffer); if (status != HT_OK) return status; me->transparent = YES; b=p; } } if (l > 0) { int status = PUTBLOCK(b, l); if (status == HT_LOADED) me->transparent = NO; return status; } return HT_OK; } PRIVATE int HTTPReceive_put_string (HTStream * me, const char * s) { return HTTPReceive_put_block(me, s, (int) strlen(s)); } PRIVATE int HTTPReceive_put_character (HTStream * me, char c) { return HTTPReceive_put_block(me, &c, 1); } PRIVATE int HTTPReceive_flush (HTStream * me) { return (*me->target->isa->flush)(me->target); } PRIVATE int HTTPReceive_free (HTStream * me) { int status = HT_OK; if (me->target) { if ((status = (*me->target->isa->_free)(me->target)) == HT_WOULD_BLOCK) return HT_WOULD_BLOCK; } HTChunk_delete(me->buffer); HT_FREE(me); return status; } PRIVATE int HTTPReceive_abort (HTStream * me, HTList * e) { if (me->target) (*me->target->isa->abort)(me->target, e); HTChunk_delete(me->buffer); HT_FREE(me); if (PROT_TRACE) HTTrace("HTTPReceive. ABORTING...\n"); return HT_ERROR; } /* HTTPReceive Stream ** ----------------- */ PRIVATE const HTStreamClass HTTPReceiveClass = { "HTTPReceive", HTTPReceive_flush, HTTPReceive_free, HTTPReceive_abort, HTTPReceive_put_character, HTTPReceive_put_string, HTTPReceive_put_block }; PRIVATE HTStream * HTTPReceive_new (HTRequest * request, https_info * http) { HTStream * me; if ((me = (HTStream *) HT_CALLOC(1, sizeof(HTStream))) == NULL) HT_OUTOFMEM("HTTPReceive_new"); me->isa = &HTTPReceiveClass; me->request = request; me->http = http; me->state = EOL_BEGIN; me->buffer = HTChunk_new(128); /* Sufficiant for most URLs */ if (STREAM_TRACE) HTTrace("HTTP Request Stream %p created\n", me); return me; } /* ------------------------------------------------------------------------- */ /* HTServHTTP ** ---------- ** Serv Document using HTTP. ** returns HT_ERROR or HT_OK */ PUBLIC int HTServHTTP (SOCKET soc, HTRequest * request, SockOps ops) { HTNet * net = HTRequest_net(request); int status = HT_ERROR; https_info * http; /* Specific protocol information */ if (!net || !request) { if (PROT_TRACE) HTTrace("Serv HTTP... Invalid argument\n"); return HT_ERROR; } /* ** Initiate a new https object and bind to request object ** This is actually state HTTPS_BEGIN, but it can't be in the state ** machine as we need the object first (chicken and egg problem). */ if (ops == FD_NONE) { if (PROT_TRACE) HTTrace("Serv HTTP... on socket %d\n", soc); if ((http = (https_info *) HT_CALLOC(1, sizeof(https_info))) == NULL) HT_OUTOFMEM("HTServHTTP"); http->server = request; http->state = HTTPS_BEGIN; http->clients = HTList_new(); HTNet_setContext(net, http); /* ** Create the stream pipe FROM the channel to the server request. */ HTNet_getInput(net, HTTPReceive_new(request, http), NULL, 0); HTRequest_setOutputConnected(request, YES); http->state = HTTPS_BEGIN; } else if (ops == FD_CLOSE) { /* Interrupted */ ServerCleanup(request, net, HT_INTERRUPTED); return HT_OK; } else http = (https_info *) HTNet_context(net); /* Get existing copy */ /* Now jump into the machine. We know the state from the previous run */ while (1) { switch (http->state) { case HTTPS_BEGIN: { /* ** Create the request to handle the request and inherit the old ** context */ HTRequest * client = HTRequest_new(); void * context = HTRequest_context(request); if (context) HTRequest_setContext(client, context); HTRequest_setOutputConnected(client, NO); HTRequest_setGnHd(client, HTRequest_gnHd(request)); HTRequest_setRsHd(client, HTRequest_rsHd(request)); HTRequest_setEnHd(client, HTRequest_enHd(request)); HTList_addObject(http->clients, client); /* ** Create the HTTP output stream for generating the reply ** FROM the client request to the channel */ { HTOutputStream * output = HTNet_getOutput(net, NULL, 0); HTStream * app = HTTPReply_new(client, http,(HTStream*)output); HTRequest_setOutputStream(client, app); HTRequest_setOutputFormat(client, WWW_SOURCE); } http->state = HTTPS_NEED_REQUEST; } break; case HTTPS_NEED_REQUEST: if (ops == FD_READ || ops == FD_NONE) { status = (*net->input->isa->read)(net->input); if (status == HT_WOULD_BLOCK) return HT_OK; else if (status == HT_CLOSED) http->state = HTTPS_OK; else if (status==HT_LOADED || status==HT_PAUSE) { http->state = HTTPS_LOAD_CLIENT; } else http->state = HTTPS_ERROR; } else http->state = HTTPS_ERROR; break; case HTTPS_LOAD_CLIENT: { HTRequest * client = HTList_removeFirstObject(http->clients); HTLoad(client, NO); http->state = HTTPS_BEGIN; break; } case HTTPS_OK: ServerCleanup(request, net, HT_IGNORE); return HT_OK; case HTTPS_ERROR: ServerCleanup(request, net, HT_ERROR); return HT_OK; } } }