/******************************************************************************* * FILENAME: WebServer.c * * PROJECT: * Bitty HTTP 1.4 * * FILE DESCRIPTION: * This file has the main web server in it. You need to copy this file * to your project. * * COPYRIGHT: * Copyright (c) 2019 Paul Hutchinson * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * ******************************************************************************/ /*** HEADER FILES TO INCLUDE ***/ #include "WebServer.h" #include "SocketsCon.h" #include #include #include #include /*** DEFINES ***/ /*** MACROS ***/ /*** TYPE DEFINITIONS ***/ /*** FUNCTION PROTOTYPES ***/ static int WS_GetNextLine(struct WebServer *Web,char *ReadBuff,int Bytes); static void WS_RunServer(struct WebServer *Web,char *ReadBuff,int Bytes); static bool WS_ProcessURI(struct WebServer *Web); static void WS_ProcessGetVars(struct WebServer *Web); static void WS_ProcessCookieVars(struct WebServer *Web); static void WS_ProcessHeader(struct WebServer *Web); static void WS_SendResponse(struct WebServer *Web); static void WS_ProcessETag(struct WebServer *Web,bool Weak,const char *ETag); static void WS_ResetWebServer(struct WebServer *Web); static void WS_EndReply(struct WebServer *Web); static char *WS_SkipStorageArgs(char *StartingPos,const char **ArgsList); static const char *WS_FindArgInStorage(char *Pos,const char *Arg, const char **ArgsList); static void WS_StartReply(struct WebServer *Web); static void WS_StartProcessingPOSTVar(struct WebServer *Web); static bool WS_CopyLineBuffer2POSTVar(struct WebServer *Web); static void WS_InsertCopy(char *Dest,char *DestEnd,const char *Src,int CopyLen); //static void DEBUG_PrintStoredArgs(struct WebServer *Web); /*** VARIABLE DEFINITIONS ***/ struct SocketCon m_ListeningSocket; struct WebServer m_WebServers[WS_OPT_MAX_CONNECTIONS]; /******************************************************************************* * NAME: * WS_Init * * SYNOPSIS: * void WS_Init(void); * * PARAMETERS: * NONE * * FUNCTION: * This function init's the web server. * * RETURNS: * NONE * * SEE ALSO: * WS_Shutdown() ******************************************************************************/ void WS_Init(void) { int r; SocketsCon_InitSockCon(&m_ListeningSocket); for(r=0;rLineBuffPos=0; Web->State=e_WebServerState_Request; Web->ReplyStatus=e_ReplyStatusMAX; Web->UserSetReplyStatus=false; Web->WriteStarted=false; Web->WriteChunked=false; Web->PageProp.DynamicFile=false; Web->PageProp.Cookies=NULL; Web->PageProp.Gets=NULL; Web->PageProp.Posts=NULL; Web->ReplyStarted=false; Web->LastReadTime=ReadElapsedClock(); Web->BodySize=0; Web->PostState=e_WSPostState_GettingKey; Web->PostWritePos=NULL; Web->PostEndOfStorage=NULL; } /******************************************************************************* * NAME: * WS_Tick * * SYNOPSIS: * void WS_Tick(void); * * PARAMETERS: * NONE * * FUNCTION: * This function runs the web server. It accepts new connections, reads * from existing connections, and sends data out of the open connections * (it also handles timeouts). * * RETURNS: * NONE * * NOTES: * This must be called regularly. * * SEE ALSO: * ******************************************************************************/ void WS_Tick(void) { int con; int Bytes; char ReadBuff[100]; SocketsCon_Tick(&m_ListeningSocket); /* Do all the connections */ for(con=0;con= WS_SECONDS_UNTIL_CONNECTION_RELEASE) { /* Ok, connection timed out, hang up so others can use it */ SocketsCon_Close(&m_WebServers[con].Con); m_WebServers[con].State=e_WebServerState_Closed; } continue; } if(Bytes<0) { /* Error, hang up */ SocketsCon_Close(&m_WebServers[con].Con); m_WebServers[con].State=e_WebServerState_Closed; continue; } WS_RunServer(&m_WebServers[con],ReadBuff,Bytes); m_WebServers[con].LastReadTime=ReadElapsedClock(); } } } /******************************************************************************* * NAME: * WS_RunServer * * SYNOPSIS: * static void WS_RunServer(struct WebServer *Web,char *ReadBuff,int Bytes); * * PARAMETERS: * Web [I] -- The web server context to work on * ReadBuff [I] -- The new bytes that have been read from this connection. * Bytes [I] -- The number of bytes read. * * FUNCTION: * This function handles the http headers and other data coming from the * web browser. * * RETURNS: * NONE * * SEE ALSO: * ******************************************************************************/ static void WS_RunServer(struct WebServer *Web,char *ReadBuff,int Bytes) { int BytesUsed; char *ReadPoint; int BytesLeft; ReadPoint=ReadBuff; BytesLeft=Bytes; for(;;) { switch(Web->State) { case e_WebServerState_Closed: SocketsCon_Close(&Web->Con); return; break; case e_WebServerState_Request: BytesUsed=WS_GetNextLine(Web,ReadPoint,BytesLeft); if(BytesUsed==0) return; if(BytesUsed<0) { Web->ReplyStatus=e_ReplyStatus_URITooLong; WS_StartReply(Web); WS_EndReply(Web); SocketsCon_Close(&Web->Con); Web->State=e_WebServerState_Closed; return; } ReadPoint+=BytesUsed; BytesLeft-=BytesUsed; /* We found the end */ if(strncmp(Web->LineBuff,"GET ",4)==0) { Web->Req=e_ReqType_Get; WS_ProcessURI(Web); if(FS_GetFileProperties(&Web->LineBuff[4],&Web->PageProp)) { WS_ProcessGetVars(Web); } else { Web->ReplyStatus=e_ReplyStatus_NotFound; } } else if(strncmp(Web->LineBuff,"POST ",5)==0) { Web->Req=e_ReqType_Post; WS_ProcessURI(Web); if(FS_GetFileProperties(&Web->LineBuff[5],&Web->PageProp)) { WS_ProcessGetVars(Web); } else { Web->ReplyStatus=e_ReplyStatus_NotFound; } } else { Web->ReplyStatus=e_ReplyStatus_NotImplemented; } Web->State++; break; case e_WebServerState_Headers: BytesUsed=WS_GetNextLine(Web,ReadPoint,BytesLeft); if(BytesUsed==0) return; if(BytesUsed<0) { Web->ReplyStatus=e_ReplyStatus_RequestHeaderFieldsTooLarge; WS_StartReply(Web); WS_EndReply(Web); SocketsCon_Close(&Web->Con); Web->State=e_WebServerState_Closed; continue; } ReadPoint+=BytesUsed; BytesLeft-=BytesUsed; if(Web->LineBuff[0]==0) Web->State++; else WS_ProcessHeader(Web); break; case e_WebServerState_Body: /* We need to read in the whole body before moving on */ if(Web->Req==e_ReqType_Post) { while(BytesLeft>0) { switch(Web->PostState) { case e_WSPostState_GettingKey: if(*ReadPoint=='=') { /* We are at the end of the name of the POST var. See if we can find it in the list of POST vars we are expecting. */ Web->LineBuff[Web->LineBuffPos++]=0; /* Decode the key */ WS_URLDecodeInPlace(Web->LineBuff); /* Setup for starting to store this var */ WS_StartProcessingPOSTVar(Web); Web->PostState=e_WSPostState_GettingValue; Web->LineBuffPos=0; break; } Web->LineBuff[Web->LineBuffPos++]=*ReadPoint; if(Web->LineBuffPos>=sizeof(Web->LineBuff)-1) { /* Out of space */ Web->ReplyStatus= e_ReplyStatus_InsufficientStorage; Web->PostState=e_WSPostState_Error; break; } break; case e_WSPostState_GettingValue: if(*ReadPoint=='&') { /* Ok, this is the end of the var, finish copying */ if(!WS_CopyLineBuffer2POSTVar(Web)) { Web->PostState=e_WSPostState_Error; break; } Web->PostState=e_WSPostState_GettingKey; } else { /* Store as much as we can in the Line Buffer. When it fills we copy it over to the arg storage. */ Web->LineBuff[Web->LineBuffPos++]= *ReadPoint; if(Web->LineBuffPos>=sizeof(Web->LineBuff)-1) { /* Line buffer filled. Empty it */ if(!WS_CopyLineBuffer2POSTVar(Web)) { Web->PostState=e_WSPostState_Error; break; } } } break; case e_WSPostState_Error: /* Skip until we get to the next var */ if(*ReadPoint=='&') { Web->PostState=e_WSPostState_GettingKey; Web->LineBuffPos=0; // Setup for next var } break; case e_WSPostStateMAX: break; } /* Consume this char */ ReadPoint++; BytesLeft--; Web->BodySize--; if(Web->BodySize==0) { /* Ran out of body */ if(Web->PostState==e_WSPostState_GettingValue) { /* Finish processing the POST var */ if(!WS_CopyLineBuffer2POSTVar(Web)) { Web->PostState=e_WSPostState_Error; break; } Web->PostState=e_WSPostState_GettingKey; Web->LineBuffPos=0; // Setup for next var } } } } else { if(Web->BodySizeBodySize; Web->BodySize=0; } else { Web->BodySize-=BytesLeft; BytesUsed=BytesLeft; } /* Use up the bytes */ ReadPoint+=BytesUsed; BytesLeft-=BytesUsed; } if(Web->BodySize==0) { /* Ok, we have read all of the body */ Web->State++; } else { /* We need more bytes to finish the body */ return; } break; case e_WebServerState_Response: //DEBUG_PrintStoredArgs(Web); WS_SendResponse(Web); WS_ResetWebServer(Web); return; break; case e_WebServerStateMAX: break; } } } /******************************************************************************* * NAME: * WS_GetNextLine * * SYNOPSIS: * static int WS_GetNextLine(struct WebServer *Web,char *ReadBuff,int Bytes); * * PARAMETERS: * Web [I] -- The web server context to work on * ReadBuff [I] -- The new bytes read * Bytes [I] -- The number of bytes read * * FUNCTION: * This function collects bytes from the connection until a header line * from the http connection is fully read. * * RETURNS: * The number of bytes used out of the 'ReadBuff' (how many bytes to advance * the read point) * -1 -- Out of space * * SEE ALSO: * ******************************************************************************/ static int WS_GetNextLine(struct WebServer *Web,char *ReadBuff,int Bytes) { int r; for(r=0;rLineBuff[Web->LineBuffPos++]=0; // String it Web->LineBuffPos=0; // Setup for next line break; } Web->LineBuff[Web->LineBuffPos++]=ReadBuff[r]; if(Web->LineBuffPos>=sizeof(Web->LineBuff)) { /* Out of space */ Web->LineBuffPos=0; // Setup for next line return -1; } } if(rLineBuff); /* Find the version */ while(EndOfLine>0) { if(Web->LineBuff[EndOfLine]=='H') break; EndOfLine--; } if(EndOfLine==0) { /* Bad request */ Web->ReplyStatus=e_ReplyStatus_BadRequest; return false; } if(strcmp(&Web->LineBuff[EndOfLine],"HTTP/1.1")!=0) { /* We only support 1.1 */ Web->ReplyStatus=e_ReplyStatus_HTTPVersionNotSupported; return false; } EndOfLine--; Web->LineBuff[EndOfLine]=0; /* Find the get args (if any) */ ArgsStart=Web->LineBuff; while(*ArgsStart!='?' && *ArgsStart!=0) ArgsStart++; if(*ArgsStart!=0) { *ArgsStart=0; // Blank the '?' } else { /* Add a second '\0' to the end (to end the args, we will be overwriting the 'H' in HTTP) */ ArgsStart++; *ArgsStart=0; } return true; } /******************************************************************************* * NAME: * WS_ProcessHeader * * SYNOPSIS: * static void WS_ProcessHeader(struct WebServer *Web); * * PARAMETERS: * Web [I] -- The web server context to work on * * FUNCTION: * This function processes a http header line. It will take the line from * 'Web->LineBuff'. * * Currently supported headers: * Cookie -- Used for sending a cookie back to the server * If-None-Match -- Used for ETag caching. * * RETURNS: * NONE * * SEE ALSO: * ******************************************************************************/ static void WS_ProcessHeader(struct WebServer *Web) { char *ETag; char *End; char *Pos; bool Weak; if(strncmp(Web->LineBuff,"If-None-Match:",14)==0) { /* Ok, check the ETag */ ETag=&Web->LineBuff[14]; while(*ETag==' ') ETag++; while(*ETag!=0) { if(*ETag=='*') { /* Match everything? */ return; } Weak=false; if(strncmp(ETag,"W/",2)==0) Weak=true; /* Find the start of the next tag */ while(*ETag!='\"' && *ETag!=0) ETag++; if(*ETag==0) { Web->ReplyStatus=e_ReplyStatus_BadRequest; return; } /* Ok, skip the " and find the end */ ETag++; End=ETag; while(*End!='\"' && *End!=0) End++; if(*End==0) { /* The end? */ Web->ReplyStatus=e_ReplyStatus_BadRequest; return; } *End=0; WS_ProcessETag(Web,Weak,ETag); /* Ok, Move to just after the " */ ETag=End+1; /* Skip until we find a , or the end */ while(*ETag!=',' && *ETag!=0) ETag++; } } if(strncmp(Web->LineBuff,"Cookie:",7)==0) { /* We have a cookie */ WS_ProcessCookieVars(Web); } if(strncmp(Web->LineBuff,"Content-Length:",15)==0) { Pos=&Web->LineBuff[15]; if(*Pos==' ') Pos++; Web->BodySize=strtol(Pos,NULL,10); } } /******************************************************************************* * NAME: * WS_ProcessETag * * SYNOPSIS: * static void WS_ProcessETag(struct WebServer *Web,bool Weak, * const char *ETag); * * PARAMETERS: * Web [I] -- The web server context to work on * Weak [I] -- Is this ETag a weak tag (ignored) * ETag [I] -- The ETag string. * * FUNCTION: * This function checks to see if the ETag for a request matches the * doc version (and that the page is not marked as dynamic). * If the doc version matches it send back that the page has not * been changed (browser should use local version). * * RETURNS: * NONE * * SEE ALSO: * ******************************************************************************/ static void WS_ProcessETag(struct WebServer *Web,bool Weak,const char *ETag) { if(!Web->PageProp.DynamicFile && strcmp(ETag,DOCVER)==0) { /* They match, we are going to reply with HTTP code */ Web->ReplyStatus=e_ReplyStatus_NotModified; } } /******************************************************************************* * NAME: * WS_StartReply * * SYNOPSIS: * static void WS_StartReply(struct WebServer *Web); * * PARAMETERS: * Web [I] -- The web server context to work on * * FUNCTION: * This function is called to start a reply. This means you can not * change the reply status after this function has been called. * * RETURNS: * NONE * * SEE ALSO: * ******************************************************************************/ static void WS_StartReply(struct WebServer *Web) { const char *Msg; char buff[100]; SocketsCon_Write(&Web->Con,"HTTP/1.1 ",9); switch(Web->ReplyStatus) { case e_ReplyStatus_Ok: Msg="200 OK"; break; case e_ReplyStatus_MovedPerm: Msg="301 Moved Permanently"; break; case e_ReplyStatus_NotModified: Msg="304 Not Modified"; break; case e_ReplyStatus_TmpRedirect: Msg="307 Temporary Redirect"; break; case e_ReplyStatus_PermRedirect: Msg="308 Permanent Redirect"; break; case e_ReplyStatus_BadRequest: Msg="400 Bad Request"; break; case e_ReplyStatus_Forbidden: Msg="403 Forbidden"; break; case e_ReplyStatus_NotFound: Msg="404 Not Found"; break; case e_ReplyStatus_MethodNotAllowed: Msg="405 Method Not Allowed"; break; case e_ReplyStatus_URITooLong: Msg="414 URI Too Long"; break; case e_ReplyStatus_RequestHeaderFieldsTooLarge: Msg="431 Request Header Fields Too Large"; break; case e_ReplyStatusMAX: case e_ReplyStatus_InternalServerError: Msg="500 Internal Server Error"; break; case e_ReplyStatus_NotImplemented: Msg="501 Not Implemented"; break; case e_ReplyStatus_HTTPVersionNotSupported: Msg="505 HTTP Version Not Supported"; break; case e_ReplyStatus_InsufficientStorage: Msg="507 Insufficient Storage"; break; } SocketsCon_Write(&Web->Con,Msg,strlen(Msg)); SocketsCon_Write(&Web->Con,"\r\n",2); SocketsCon_Write(&Web->Con,"Server: BittyHTTP\r\n",19); if(Web->ReplyStatus!=e_ReplyStatus_Ok && !Web->UserSetReplyStatus) { sprintf(buff,"Content-Length: %ld\r\n",strlen(Msg)); SocketsCon_Write(&Web->Con,buff,strlen(buff)); SocketsCon_Write(&Web->Con,"\r\n",2); SocketsCon_Write(&Web->Con,Msg,strlen(Msg)); } else { if(!Web->PageProp.DynamicFile) { /* It's dynamic, so we need to add the ETag */ sprintf(buff,"ETag: \"%s\"\r\n",DOCVER); SocketsCon_Write(&Web->Con,buff,strlen(buff)); } } Web->ReplyStarted=true; } /******************************************************************************* * NAME: * WS_EndReply * * SYNOPSIS: * static void WS_EndReply(struct WebServer *Web); * * PARAMETERS: * Web [I] -- The web server context to work on * * FUNCTION: * This function ends the reply headers and starts the content for the * current request. * * RETURNS: * NONE * * SEE ALSO: * ******************************************************************************/ static void WS_EndReply(struct WebServer *Web) { if(Web->WriteChunked) { SocketsCon_Write(&Web->Con,"0\r\n",3); SocketsCon_Write(&Web->Con,"\r\n",2); // No extra header fields } } /******************************************************************************* * NAME: * WS_SendResponse * * SYNOPSIS: * static void WS_SendResponse(struct WebServer *Web); * * PARAMETERS: * Web [I] -- The web server context to work on * * FUNCTION: * This function does the callback into the file server when the web server * is ready to start sending the contents. * * It may not call the file server if there was an error so there is no * content to send. * * RETURNS: * NONE * * SEE ALSO: * ******************************************************************************/ static void WS_SendResponse(struct WebServer *Web) { /* DEBUG PAUL: Let the code handle e_ReplyStatus_InsufficientStorage and override it if it wants */ if(Web->ReplyStatus==e_ReplyStatusMAX) { /* Ok, process file */ Web->ReplyStatus=e_ReplyStatus_Ok; FS_SendFile(Web,Web->PageProp.FileID); } else { WS_StartReply(Web); } WS_EndReply(Web); } /******************************************************************************* * NAME: * WS_WriteWhole * * SYNOPSIS: * void WS_WriteWhole(struct WebServer *Web,const char *Buffer,int Len); * * PARAMETERS: * Web [I] -- The web server context to work on * Buffer [I] -- The buffer with the whole contents to send in it. * Len [I] -- The number of bytes in 'Buffer' * * FUNCTION: * This function sends the content to the client using 'Content-Length'. * After you call this function you can not send any more content or headers. * * You use this function if you are sending a static resource like a graphic * or static web page. You can also use this you have built all the content * is a buffer and will not need to send any more. * * RETURNS: * NONE * * SEE ALSO: * WS_Start(), WS_WriteWholeStr(), WS_WriteChunk() ******************************************************************************/ void WS_WriteWhole(struct WebServer *Web,const char *Buffer,int Len) { char buff[100]; if(Web->WriteStarted) { Web->ReplyStatus=e_ReplyStatus_InternalServerError; return; } Web->WriteStarted=true; Web->ReplyStatus=e_ReplyStatus_Ok; if(!Web->ReplyStarted) WS_StartReply(Web); sprintf(buff,"Content-Length: %d\r\n",Len); SocketsCon_Write(&Web->Con,buff,strlen(buff)); SocketsCon_Write(&Web->Con,"\r\n",2); SocketsCon_Write(&Web->Con,Buffer,Len); } /******************************************************************************* * NAME: * WS_WriteChunk * * SYNOPSIS: * void WS_WriteChunk(struct WebServer *Web,const char *Buffer,int Len); * * PARAMETERS: * Web [I] -- The web server context to work on * Buffer [I] -- The buffer with the contents to send in it. * Len [I] -- The number of bytes in 'Buffer' * * FUNCTION: * This function sends content using the chunked method. This means you * do not need to know the length of the content before sending (you can * build it as you go). * * You would normally use this function for dynamic content. That way * you do not have to build the whole web page in a buffer before * repling. * * RETURNS: * NONE * * SEE ALSO: * WS_WriteChunkStr(), WS_WriteWhole() ******************************************************************************/ void WS_WriteChunk(struct WebServer *Web,const char *Buffer,int Len) { char buff[100]; if(Len==0) return; if(!Web->WriteStarted) { WS_StartReply(Web); SocketsCon_Write(&Web->Con,"Transfer-Encoding: chunked\r\n",28); /* End the headers */ SocketsCon_Write(&Web->Con,"\r\n",2); } Web->WriteChunked=true; Web->WriteStarted=true; sprintf(buff,"%X\r\n",Len); SocketsCon_Write(&Web->Con,buff,strlen(buff)); SocketsCon_Write(&Web->Con,Buffer,Len); SocketsCon_Write(&Web->Con,"\r\n",2); // End of chunk } /******************************************************************************* * NAME: * WS_WriteWholeStr * * SYNOPSIS: * void WS_WriteWholeStr(struct WebServer *Web,const char *Buffer); * * PARAMETERS: * Web [I] -- The web server context to work on * Buffer [I] -- The buffer with the whole contents to send in it. This * is a C string (ending in \0). * * FUNCTION: * This function sends the content to the client using 'Content-Length'. * After you call this function you can not send any more content or headers. * * This is the same as WS_WriteWhole() except it works on a C string * instead of a buffer. This function just calls WS_WriteWhole() with * a strlen() for the length. * * RETURNS: * NONE * * SEE ALSO: * WS_Start(), WS_WriteWhole() ******************************************************************************/ void WS_WriteWholeStr(struct WebServer *Web,const char *Buffer) { WS_WriteWhole(Web,Buffer,strlen(Buffer)); } /******************************************************************************* * NAME: * WS_WriteChunkStr * * SYNOPSIS: * void WS_WriteChunkStr(struct WebServer *Web,const char *Buffer); * * PARAMETERS: * Web [I] -- The web server context to work on * Buffer [I] -- The buffer with the contents to send in it. This is * a C string (ending in \0). * * FUNCTION: * This function sends content using the chunked method. This means you * do not need to know the length of the content before sending (you can * build it as you go). * * This is the same as WS_WriteChunk() except it works on a C string * instead of a buffer. This function just calls WS_WriteChunk() with * a strlen() for the length. * * RETURNS: * NONE * * SEE ALSO: * WS_Start(), WS_WriteChunk() ******************************************************************************/ void WS_WriteChunkStr(struct WebServer *Web,const char *Buffer) { WS_WriteChunk(Web,Buffer,strlen(Buffer)); } /******************************************************************************* * NAME: * WS_URLDecode * * SYNOPSIS: * bool WS_URLDecode(const char *Value,char *Decoded,int MaxLen); * * PARAMETERS: * Value [I] -- The string to decode. * Decoded [O] -- The buffer to store the decoded string in. * MaxLen [I] -- The size of 'Decoded'. * * FUNCTION: * This function encodes a string into URL encoding (%20 for space) * * RETURNS: * true -- Things worked out * false -- If we had to clip the string to keep it in 'MaxLen'. * * SEE ALSO: * ******************************************************************************/ bool WS_URLDecode(const char *Value,char *Decoded,int MaxLen) { char *Write; const char *Read; char buff[100]; Write=Decoded; Read=Value; while(*Read!=0) { if(*Read=='%') { /* Encoded */ Read++; // Move past the % /* Copy the next 2 bytes */ buff[0]=*Read++; buff[1]=*Read++; buff[2]=0; *Write++=strtol(buff,NULL,16); if(Write-Decoded>=MaxLen) return false; } else { *Write++=*Read; if(Write-Decoded>=MaxLen) return false; Read++; } } *Write=0; return true; } /******************************************************************************* * NAME: * WS_URLDecodeInPlace * * SYNOPSIS: * char *WS_URLDecodeInPlace(char *Value); * * PARAMETERS: * Value [I] -- The URL encoded string to decode * * FUNCTION: * This function decodes a URL encoded string directly over top of the * string. * * RETURNS: * A pointer to the char directly after the new string ends (after the \0) * * SEE ALSO: * ******************************************************************************/ char *WS_URLDecodeInPlace(char *Value) { char *Write; char *Read; char buff[100]; Write=Value; Read=Value; while(*Read!=0) { if(*Read=='%') { /* Encoded */ buff[0]=0; buff[1]=0; buff[2]=0; Read++; // Move past the % /* Copy the next 2 bytes */ if(*Read!=0) buff[0]=*Read++; if(*Read!=0) buff[1]=*Read++; *Write++=strtol(buff,NULL,16); } else { *Write++=*Read; Read++; } } *Write++=0; return Write; } /******************************************************************************* * NAME: * WS_URLEncode * * SYNOPSIS: * bool WS_URLEncode(const char *Value,char *OutputBuffer,int MaxLen); * * PARAMETERS: * Value [I] -- The string to encode. * OutputBuffer [O] -- The buffer to store the encoded string in. This * should be 3 times the size of Value for worst case. * MaxLen [I] -- The size of 'OutputBuffer'. * * FUNCTION: * This function encodes a string into URL encoding (%20 for space) * * RETURNS: * true -- Things worked out * false -- If we had to clip the string to keep it in 'MaxLen'. * * SEE ALSO: * ******************************************************************************/ bool WS_URLEncode(const char *Value,char *OutputBuffer,int MaxLen) { const char *Pos; char *Write; char *End; char c; Pos=Value; Write=OutputBuffer; End=OutputBuffer+MaxLen-1; while(*Pos!=0) { c=*Pos++; if((c>='A' && c<='Z') || (c>='a' && c<='z') || (c>='0' && c<='9') || c=='-' || c=='_' || c=='.' || c=='~') { /* Normal copy */ *Write++=c; if(Write==End) { *Write++=0; return false; } } else { /* % encode */ if(Write+3>=End) { *Write++=0; return false; } sprintf(Write,"%%%02X",c); Write+=3; } } *Write=0; return true; } /******************************************************************************* * NAME: * WS_Header * * SYNOPSIS: * bool WS_Header(struct WebServer *Web,const char *Header); * * PARAMETERS: * Web [I] -- The web server context to work on * Header [I] -- The header to write. * * FUNCTION: * This function writes out a header as part of the http reply. * * RETURNS: * true -- Things worked out * false -- There was an error (have you already started sending content?) * * NOTES: * You do not need to end the string with a \r\n. This function adds it * for you. * * LIMITATIONS: * You can not use this function if you have started sending content. * * SEE ALSO: * WS_Start(), WS_WriteChunk() ******************************************************************************/ bool WS_Header(struct WebServer *Web,const char *Header) { if(Header[0]==0) return false; if(Web->WriteStarted) return false; if(!Web->ReplyStarted) WS_StartReply(Web); SocketsCon_Write(&Web->Con,Header,strlen(Header)); SocketsCon_Write(&Web->Con,"\r\n",2); return true; } /******************************************************************************* * NAME: * WS_Location * * SYNOPSIS: * bool WS_Location(struct WebServer *Web,const char *NewURL); * * PARAMETERS: * Web [I] -- The web server context to work on * NewURL [I] -- The new URL to redirect the browser to. * * FUNCTION: * This function does a 301 Moved Permanently redirect. * * RETURNS: * true -- Things worked out * false -- There was an error * * NOTES: * This must be done before anything else is sent (content, header, etc) * * SEE ALSO: * ******************************************************************************/ bool WS_Location(struct WebServer *Web,const char *NewURL) { if(NewURL[0]==0) return false; if(!WS_SetHTTPStatusCode(Web,e_ReplyStatus_MovedPerm)) return false; SocketsCon_Write(&Web->Con,"Location: ",10); SocketsCon_Write(&Web->Con,NewURL,strlen(NewURL)); SocketsCon_Write(&Web->Con,"\r\n",2); return true; } /******************************************************************************* * NAME: * WS_SetHTTPStatusCode * * SYNOPSIS: * bool WS_SetHTTPStatusCode(struct WebServer *Web,e_ReplyStatusType Code); * * PARAMETERS: * Web [I] -- The web server context to work on * Code [I] -- The status code to set. Only a limited set are supported. * * FUNCTION: * This function sets the http reply status code that is sent out. * * RETURNS: * true -- Things worked out * false -- There was an error * * NOTES: * This must be done before anything else is sent (content, header, etc) * * SEE ALSO: * WS_Location(), WS_Header() ******************************************************************************/ bool WS_SetHTTPStatusCode(struct WebServer *Web,e_ReplyStatusType Code) { if(Code>=e_ReplyStatusMAX) return false; if(Web->WriteStarted) return false; if(Web->ReplyStarted) return false; Web->UserSetReplyStatus=true; Web->ReplyStatus=Code; WS_StartReply(Web); return true; } /******************************************************************************* * NAME: * WS_GET * * SYNOPSIS: * const char *WS_GET(struct WebServer *Web,const char *Arg); * * PARAMETERS: * Web [I] -- The web server context to work on * Arg [I] -- The GET argument to search for an return * * FUNCTION: * This function gets a GET arg from the request and returns it. * * You must place all the GET args you are going to be using (or could want * to use) in the Web->PageProp->Gets[] array for the system to be able * read a GET arg. * * RETURNS: * A pointer to the value or NULL if was not set. * * SEE ALSO: * WS_Start(), WS_COOKIE(), WS_POST() ******************************************************************************/ const char *WS_GET(struct WebServer *Web,const char *Arg) { /* GETS are first so no need to skip anything */ return WS_FindArgInStorage(Web->ArgsStorage,Arg,Web->PageProp.Gets); } /******************************************************************************* * NAME: * WS_COOKIE * * SYNOPSIS: * const char *WS_COOKIE(struct WebServer *Web,const char *Arg); * * PARAMETERS: * Web [I] -- The web server context to work on * Arg [I] -- The GET argument to search for an return * * FUNCTION: * This function gets a COOKIE from the request and returns it. * * You must place all the COOKIE names you are going to be using (or could * want to use) in the Web->PageProp->Cookies[] array for the system to be * able read a cookie. * * RETURNS: * A pointer to the value or NULL if was not set. * * SEE ALSO: * WS_Start(), WS_GET(), WS_POST() ******************************************************************************/ const char *WS_COOKIE(struct WebServer *Web,const char *Arg) { char *Pos; Pos=Web->ArgsStorage; /* Skip all the GETS args */ Pos=WS_SkipStorageArgs(Pos,Web->PageProp.Gets); return WS_FindArgInStorage(Pos,Arg,Web->PageProp.Cookies); } /******************************************************************************* * NAME: * WS_POST * * SYNOPSIS: * const char *WS_POST(struct WebServer *Web,const char *Arg); * * PARAMETERS: * Web [I] -- The web server context to work on * Arg [I] -- The GET argument to search for an return * * FUNCTION: * This function gets a POST arg from the request and returns it. * * You must place all the POST args you are going to be using (or could * want to use) in the Web->PageProp->Posts[] array for the system to be * able read a POST arg. * * RETURNS: * A pointer to the value or NULL if was not set. * * SEE ALSO: * WS_Start(), WS_GET(), WS_COOKIE() ******************************************************************************/ const char *WS_POST(struct WebServer *Web,const char *Arg) { char *Pos; Pos=Web->ArgsStorage; /* Skip all the GETS args */ Pos=WS_SkipStorageArgs(Pos,Web->PageProp.Gets); /* Skip all the cookies */ Pos=WS_SkipStorageArgs(Pos,Web->PageProp.Cookies); return WS_FindArgInStorage(Pos,Arg,Web->PageProp.Posts); } /******************************************************************************* * NAME: * WS_FindArgInStorage * * SYNOPSIS: * static const char *WS_FindArgInStorage(char *Pos,const char *Arg, * const char **ArgsList) * * PARAMETERS: * Pos [I] -- The starting pos in 'Web->ArgsStorage' to start searching * Arg [I] -- The arg to search for * ArgsList [I] -- The Web->PageProp->Gets[], Web->PageProp->Posts[], or * Web->PageProp->Cookies[] array to search. * * FUNCTION: * This function searchs a section of the 'Web->ArgsStorage' looking for * an arg. * * You must set 'Pos' the start of the section you want to search. * * RETURNS: * A pointer to the arg's value, or NULL if it was not found. * * SEE ALSO: * WS_SkipStorageArgs() ******************************************************************************/ static const char *WS_FindArgInStorage(char *Pos,const char *Arg, const char **ArgsList) { int g; if(ArgsList==NULL) return NULL; /* Find this arg */ for(g=0;ArgsList[g]!=0;g++) { if(strcmp(ArgsList[g],Arg)==0) { /* Ok, this is the one */ if(*Pos=='N') return NULL; return Pos+1; } /* Move to the next arg value */ if(*Pos=='Y') { /* Skip the string (because the 'Y' is also a string we just skip to the end of the string instead of having to Pos++ before skipping) */ while(*Pos++!=0) ; } else { /* Nothing after the Y/N so just skip the 'N' */ Pos++; } } return NULL; } /******************************************************************************* * NAME: * WS_SkipStorageArgs * * SYNOPSIS: * static char *WS_SkipStorageArgs(char *StartingPos,const char **ArgsList); * * PARAMETERS: * StartingPos [I] -- A pointer to the section to skip in the * 'Web->ArgsStorage' array. * ArgsList [I] -- The Web->PageProp->Gets[], Web->PageProp->Posts[], or * Web->PageProp->Cookies[] array to search. * * FUNCTION: * This function skips a section in the 'Web->ArgsStorage' storage area. * * Because the GETS, COOKIES, and POST are stored one after each other * in the 'Web->ArgsStorage' array if you want to access the COOKIES you * must skip the GETS. This function provides this. * * RETURNS: * A pointer to the next section. * * SEE ALSO: * WS_FindArgInStorage() ******************************************************************************/ static char *WS_SkipStorageArgs(char *StartingPos,const char **ArgsList) { char *Pos; int g; if(ArgsList==NULL) return StartingPos; /* Go over all the args */ Pos=StartingPos; for(g=0;ArgsList[g]!=0;g++) { /* Move to the next arg value */ if(*Pos=='Y') { /* Skip the string (because the 'Y' is also a string we just skip to the end of the string instead of having to Pos++ before skipping) */ while(*Pos++!=0) ; } else { /* Nothing after the Y/N so just skip the 'N' */ Pos++; } } return Pos; } /******************************************************************************* * NAME: * WS_InsertCopy * * SYNOPSIS: * static void WS_InsertCopy(char *Dest,char *DestEnd,const char *Src, * int CopyLen); * * PARAMETERS: * Dest [I] -- The point to insert the 'Src' string at * DestEnd [I] -- The end of the data in the 'Web->ArgsStorage' * Src [I] -- The string to insert. * CopyLen [I] -- How many bytes to insert/copy * * FUNCTION: * This function inserts a string into 'Web->ArgsStorage' by copying * the content above the insert pos to past the end of the current data. * * For example: * 012345678901234 * Starting String: Cat In The Hat * DestEnd=14; * Dest=6; * Src="/Out" * CopyLen=4; * * This will first copy as follows: * "Cat In____ The Hat" * and then copy the new string in: * "Can In/Out The Hat" * * RETURNS: * NONE * * SEE ALSO: * ******************************************************************************/ static void WS_InsertCopy(char *Dest,char *DestEnd,const char *Src,int CopyLen) { char *p1; char *p2; /* Make room */ p2=DestEnd+CopyLen-1; p1=p2-CopyLen; while(p1>=Dest) *p2--=*p1--; /* Insert the memory */ memcpy(Dest,Src,CopyLen); } /******************************************************************************* * NAME: * WS_SetCookie * * SYNOPSIS: * bool WS_SetCookie(struct WebServer *Web,const char *Name, * const char *Value,time_t Expire,const char *Path,const char *Domain, * bool Secure,bool HttpOnly); * * PARAMETERS: * Web [I] -- The web server context to work on * Name [I] -- Name of the cookie. This can not have ' ', ',', or ';'. * Value [I] -- The value of the cookie. This value is stored on the clients * computer; do not store sensitive information. This is also * not escaped (or URI encoded). You can not have the chars * ';', '=', '\r', '\n', ' ', or ','. Short answer, URI encode * your string (WS_URLEncode). * Expire [I] -- The time the cookie expires. This is a time_t * (Unix timestamp) so is in number of seconds since the epoch. * This is sent into gmtime() to make a string. Pass 0 to * ignore. * Path [I] -- The path on the server in which the cookie will be available * on. If set to '/', the cookie will be available within the * entire domain. If set to '/foo/', the cookie will only be * available within the /foo/ directory and all sub-directories * such as /foo/bar/ of domain. If you pass NULL the current * directory that the cookie is being set in. * Domain [I] -- The (sub)domain that the cookie is available to. Setting * this to a subdomain (such as 'www.example.com') will make * the cookie available to that subdomain and all other * sub-domains of it (i.e. w2.www.example.com). To make * the cookie available to the whole domain (including * all subdomains of it), simply set the value to the * domain name ('example.com', in this case). * * Older browsers still implementing the deprecated RFC 2109 * may require a leading . to match all subdomains. * Secure [I] -- Indicates that the cookie should only be transmitted over * a secure HTTPS connection from the client. When set to * true, the cookie will only be set if a secure connection * exists. On the server-side, it's on the programmer to * send this kind of cookie only on secure connection. * HttpOnly [I] -- When true the cookie will be made accessible only * through the HTTP protocol. This means that the cookie * won't be accessible by scripting languages, such as * JavaScript. It has been suggested that this setting * can effectively help to reduce identity theft through * XSS attacks (although it is not supported by all * browsers), but that claim is often disputed. * * FUNCTION: * This function sets a cookie to be sent to the client. * * RETURNS: * true -- Things worked out * false -- There was an error (have you already started sending content?) * * NOTES: * Most of this header was taken from the PHP doc's. * * SEE ALSO: * ******************************************************************************/ bool WS_SetCookie(struct WebServer *Web,const char *Name,const char *Value, time_t Expire,const char *Path,const char *Domain,bool Secure, bool HttpOnly) { struct tm *TheTm; char buff[100]; static char mon_name[12][3] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun","Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; static char wday_name[7][3] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; const char *Pos; if(Name[0]==0) return false; /* Check for bad char's */ for(Pos=Name;*Pos!=0;Pos++) if(*Pos==' ' || *Pos==',' || *Pos==';' || *Pos=='\r' || *Pos=='\n') return false; for(Pos=Value;*Pos!=0;Pos++) if(*Pos==' ' || *Pos==',' || *Pos==';' || *Pos=='\r' || *Pos=='\n') return false; if(Web->WriteStarted) return false; if(!Web->ReplyStarted) WS_StartReply(Web); SocketsCon_Write(&Web->Con,"Set-Cookie: ",12); SocketsCon_Write(&Web->Con,Name,strlen(Name)); SocketsCon_Write(&Web->Con,"=",1); SocketsCon_Write(&Web->Con,Value,strlen(Value)); if(Expire!=0) { TheTm=gmtime(&Expire); sprintf(buff,"; Expires=%s, %02d %s %04d %02d:%02d:%02d GMT", wday_name[TheTm->tm_wday], TheTm->tm_mday, mon_name[TheTm->tm_mon], TheTm->tm_year+1900, TheTm->tm_hour, TheTm->tm_min, TheTm->tm_sec); SocketsCon_Write(&Web->Con,buff,strlen(buff)); } if(Path!=NULL && Path[0]!=0) { SocketsCon_Write(&Web->Con,"; Path=",7); SocketsCon_Write(&Web->Con,Path,strlen(Path)); } if(Domain!=NULL && Domain[0]!=0) { SocketsCon_Write(&Web->Con,"; Domain=",7); SocketsCon_Write(&Web->Con,Domain,strlen(Domain)); } if(Secure) { SocketsCon_Write(&Web->Con,"; Secure",8); } if(HttpOnly) { SocketsCon_Write(&Web->Con,"; HttpOnly",10); } SocketsCon_Write(&Web->Con,"\r\n",2); return true; } /******************************************************************************* * NAME: * WS_GetOSSocketHandles * * SYNOPSIS: * int WS_GetOSSocketHandles(t_ConSocketHandle *Handles); * * PARAMETERS: * Handles [O] -- An array to fill in with the handles being used by the * web server. This must be at least * 'WS_OPT_MAX_CONNECTIONS+1' in size. * * FUNCTION: * This function gets all the socket handles being used by the web server. * You can use these handles to wait on. * * The returned handles depend on what 't_ConSocketHandle' is defined as. * * RETURNS: * The number of handles being returned * * SEE ALSO: * ******************************************************************************/ int WS_GetOSSocketHandles(t_ConSocketHandle *Handles) { t_ConSocketHandle SocketHandle; int r; int InsertPos; /* Fill in the first entry with the listening socket */ InsertPos=0; SocketsCon_GetSocketHandle(&m_ListeningSocket,&Handles[InsertPos++]); for(r=0;rLineBuff') and pulls * out the GET args and stores them in 'Web->ArgsStorage'. * * RETURNS: * NONE * * NOTES: * The format of 'ArgsStorage' is that we store each variable * (GET/POST/COOKIE) in the order they are in listed in the array of * names. We store a Y or an N depending on if we found the var. If * it's an N then the next var is right after it in the next byte (0 bytes * between). If it is Y then we store the string followed by a \0. * * We store the GETs first, then the COOKIEs, then POSTs. * * For example: * struct WSPageProp Props= * { * .Gets={"one","two","three",NULL}, * .Cookies={"four","five",NULL}, * .Posts={"six","seven",NULL}, * } * * Where nothing is passed in then 'ArgsStorage' will look like: * NNNNNNN * * Where one is passed in (?one=value1): * Yvalue1\0NNNNNN * * Where two is passed in (?two=value2): * NYvalue2\0NNNNN * * Where one and two are passed in (?two=value2&one=value1): * Yvalue1\0Yvalue2\0NNNNN * * Where the 'five' cookie is set: * NNNNYcookie\0NN * * SEE ALSO: * ******************************************************************************/ static void WS_ProcessGetVars(struct WebServer *Web) { char *ArgsStart; char *Start; char *Pos; char *Write; char *StorageStart; char *StartOfLastWrite; int g; int ArgCount; int arg; int Len; bool Found; /* Find the end of the string (it will be the start of the args or the real end of the string) */ ArgsStart=Web->LineBuff; while(*ArgsStart!=0) ArgsStart++; ArgsStart++; // Move to the start of args ArgCount=0; Pos=ArgsStart; /* We need to break up the args first */ while(*Pos!=0) { if(*Pos=='&') { *Pos=0; ArgCount++; } Pos++; } if(Pos!=ArgsStart) ArgCount++; StorageStart=Web->ArgsStorage; Write=StorageStart; StartOfLastWrite=Write; /* See if this arg is in the list of get args */ if(Web->PageProp.Gets!=NULL) { for(g=0;Web->PageProp.Gets[g]!=0;g++) { /* See if this arg is in the args we had sent in */ arg=0; Pos=ArgsStart; Start=Pos; while(argPageProp.Gets[g]); if(Len==Pos-Start && strncmp(Start,Web->PageProp.Gets[g],Len)==0) { /* Found this arg */ Pos++; // Move past the '=' break; } } if(*Pos==0) { /* End of the arg, check it */ arg++; Start=Pos+1; } Pos++; } Found=false; if(arg=WS_OPT_ARG_MEMORY_SIZE) { Web->ReplyStatus=e_ReplyStatus_InsufficientStorage; return; } *Write++=Found?'Y':'N'; if(Found) { /* See if we have space for the value */ while(*Pos!=0) { if((Write-StorageStart)>=WS_OPT_ARG_MEMORY_SIZE) { Web->ReplyStatus=e_ReplyStatus_InsufficientStorage; return; } *Write++=*Pos++; } if((Write-StorageStart)>=WS_OPT_ARG_MEMORY_SIZE) { Web->ReplyStatus=e_ReplyStatus_InsufficientStorage; return; } *Write++=0; /* Decode in place, and then use the end of the string as where to continue writing new args (if the string shrinks use the new end) */ Write=WS_URLDecodeInPlace(StartOfLastWrite); } StartOfLastWrite=Write; } } /* Fill in the cookies and post vars with all not found (so the cookie/post code doesn't have to handle the unknown state) */ if(Web->PageProp.Cookies!=NULL) { for(g=0;Web->PageProp.Cookies[g]!=0;g++) { if((Write-StorageStart)>=WS_OPT_ARG_MEMORY_SIZE) { Web->ReplyStatus=e_ReplyStatus_InsufficientStorage; return; } *Write++='N'; } } if(Web->PageProp.Posts!=NULL) { for(g=0;Web->PageProp.Posts[g]!=0;g++) { if((Write-StorageStart)>=WS_OPT_ARG_MEMORY_SIZE) { Web->ReplyStatus=e_ReplyStatus_InsufficientStorage; return; } *Write++='N'; } } //DEBUG_PrintStoredArgs(Web); } /******************************************************************************* * NAME: * WS_ProcessCookieVars * * SYNOPSIS: * static void WS_ProcessCookieVars(struct WebServer *Web); * * PARAMETERS: * Web [I] -- The web server context to work on * * FUNCTION: * This function processes a "Cookie: " header. It will set the cookies * value in 'Web->ArgsStorage' if any of the sent in cookies match the * list of supported cookies. * * See WS_ProcessGetVars() for a description of how 'ArgsStorage' works. * * RETURNS: * NONE * * SEE ALSO: * ******************************************************************************/ static void WS_ProcessCookieVars(struct WebServer *Web) { int g; char *ArgsStart; int ArgCount; char *Pos; char *StorageStart; char *EndOfStorage; char *Write; int arg; char *Start; int Len; /* Break the header line up into each cookie */ /* Find the end of the Cookie: string */ ArgsStart=Web->LineBuff; while(*ArgsStart!=':' && *ArgsStart!=0) ArgsStart++; /* Skip the : */ if(*ArgsStart==':') ArgsStart++; /* Skip the space (There should be a space, but we handle if there isn't) */ if(*ArgsStart==' ') ArgsStart++; /* See if there are any cookies */ if(*ArgsStart==0) return; ArgCount=0; Pos=ArgsStart; /* We need to break up the args first */ while(*Pos!=0) { if(*Pos==';') { *Pos=0; ArgCount++; } Pos++; } if(Pos!=ArgsStart) ArgCount++; /* Find the end of the GET vars (where the cookies start) */ StorageStart=Web->ArgsStorage; Write=StorageStart; if(Web->PageProp.Gets!=NULL) { /* Skip all the GETS args */ Write=WS_SkipStorageArgs(Write,Web->PageProp.Gets); } /* Find the end of the 'ArgsStorage' storage */ EndOfStorage=Write; // Already skipped the GETS if(Web->PageProp.Cookies!=NULL) { /* Skip all the COOKIES args */ EndOfStorage=WS_SkipStorageArgs(EndOfStorage,Web->PageProp.Cookies); } if(Web->PageProp.Posts!=NULL) { /* Skip all the POSTS args */ EndOfStorage=WS_SkipStorageArgs(EndOfStorage,Web->PageProp.Posts); } /* See if this arg is in the list of cookie args */ if(Web->PageProp.Cookies!=NULL) { for(g=0;Web->PageProp.Cookies[g]!=0;g++) { /* See if this arg is in the args we had sent in */ arg=0; Pos=ArgsStart; Start=Pos; /* Skip any spaces at the start */ while(*Start==' ') Start++; while(argPageProp.Cookies[g]); /* Skip any spaces at the start of the name */ while(*Start==' ') Start++; if(Len==Pos-Start && strncmp(Start,Web->PageProp.Cookies[g],Len)==0) { /* Found this arg */ Pos++; // Move past the '=' break; } } if(*Pos==0) { /* End of the arg */ arg++; Start=Pos+1; } Pos++; } /* Did we find it? */ if(arg=WS_OPT_ARG_MEMORY_SIZE) { Web->ReplyStatus=e_ReplyStatus_InsufficientStorage; return; } WS_InsertCopy(Write+1,EndOfStorage,Pos,Len+1); // +1 for the end of the string *Write++='Y'; Write+=Len+1; // +1 for the end of the string EndOfStorage+=Len+1; // +1 for the end of the string } } else { /* Skip the 'N' we already have */ Write++; } } } //DEBUG_PrintStoredArgs(Web); } /******************************************************************************* * NAME: * WS_StartProcessingPOSTVar * * SYNOPSIS: * static void WS_StartProcessingPOSTVar(struct WebServer *Web); * * PARAMETERS: * Web [I] -- The web server context to work on * * FUNCTION: * This function sets up for processing of a POST var. It will find the * var in the list of POST vars and setup Web->PostWritePos to the * pos where the POST value should be written, and Web->PostEndOfStorage * to the end of arg storage. * * RETURNS: * NONE * * SEE ALSO: * ******************************************************************************/ static void WS_StartProcessingPOSTVar(struct WebServer *Web) { char *Write; char *StorageStart; int p; unsigned int Len; StorageStart=Web->ArgsStorage; Write=StorageStart; Web->PostWritePos=NULL; if(Web->PageProp.Gets!=NULL) { /* Skip all the GETS args */ Write=WS_SkipStorageArgs(Write,Web->PageProp.Gets); } if(Web->PageProp.Cookies!=NULL) { /* Skip all the COOKIE args */ Write=WS_SkipStorageArgs(Write,Web->PageProp.Cookies); } /* See if this arg is in the list of get args */ if(Web->PageProp.Posts!=NULL) { /* Skip all the POST args (this will be the end of storage) */ Web->PostEndOfStorage=WS_SkipStorageArgs(Write,Web->PageProp.Posts); for(p=0;Web->PageProp.Posts[p]!=0;p++) { /* See if this arg is in the args we had sent in */ Len=strlen(Web->LineBuff); if(strncmp(Web->LineBuff,Web->PageProp.Posts[p],Len)==0 && Web->PageProp.Posts[p][Len]==0) { /* Found this arg */ *Write++='Y'; Web->PostWritePos=Write; break; } else { /* Skip this arg */ if(*Write=='Y') { while(*Write!=0) Write++; } Write++; // Skip the N or the \0 } } } } /******************************************************************************* * NAME: * WS_CopyLineBuffer2POSTVar * * SYNOPSIS: * static bool WS_CopyLineBuffer2POSTVar(struct WebServer *Web); * * PARAMETERS: * Web [I] -- The web server context to work on * * FUNCTION: * This function copies what is in the line buffer to the POST var storage. * * You have to have started this with WS_StartProcessingPOSTVar() to setup * the needed vars. * * RETURNS: * true -- All ok * false -- There wasn't enough room to store this var. * * SEE ALSO: * WS_StartProcessingPOSTVar() ******************************************************************************/ static bool WS_CopyLineBuffer2POSTVar(struct WebServer *Web) { char EscBuff[3]; char *Pos; char *EndOfLineBuff; int Len; char Zero; /* Make the line buffer in to a string */ Web->LineBuff[Web->LineBuffPos]=0; //{ //printf("\33[1;32m"); //Pos=Web->LineBuff; //while(*Pos!=0) // printf("%c",*Pos++); //printf("\33[0m\r\n"); //} if(Web->PostWritePos!=NULL) { /* First see we where in the middle of a % esc seq */ EscBuff[0]=0; EscBuff[1]=0; EscBuff[2]=0; if(Web->LineBuffPos>=2) { /* Ok, move the esc seq to 'EscBuff' and kill it out of the main buffer */ Pos=&Web->LineBuff[Web->LineBuffPos-1]; if(*Pos=='%') { EscBuff[0]='%'; *Pos=0; Web->LineBuffPos--; } else { Pos--; if(*Pos=='%') { EscBuff[0]='%'; EscBuff[1]=*(Pos+1); *Pos=0; Web->LineBuffPos-=2; } } } /* Decode the line buffer (we have to handle the + thing before we decode it) */ Pos=Web->LineBuff; while(*Pos!=0) { if(*Pos=='+') *Pos=' '; Pos++; } EndOfLineBuff=WS_URLDecodeInPlace(Web->LineBuff); Len=EndOfLineBuff-Web->LineBuff; /* Make sure we can fit this */ if(Web->PostEndOfStorage+Len>Web->ArgsStorage+WS_OPT_ARG_MEMORY_SIZE) { /* It's not going to fit, clip it and return insufficient storage */ Zero=0; WS_InsertCopy(Web->PostWritePos,Web->PostEndOfStorage,&Zero,1); Web->ReplyStatus=e_ReplyStatus_InsufficientStorage; return false; } WS_InsertCopy(Web->PostWritePos,Web->PostEndOfStorage,Web->LineBuff, Len); Web->PostEndOfStorage+=Len; /* Move to the end of what we just inserted */ Web->PostWritePos+=Len-1; /* Ok, if we where in the middle of an esc seq, but it in the buffer */ strcpy(Web->LineBuff,EscBuff); Web->LineBuffPos=strlen(EscBuff); } else { Web->LineBuffPos=0; } //DEBUG_PrintStoredArgs(Web); return true; } //static void DEBUG_PrintStoredArgs(struct WebServer *Web) //{ // char *StorageStart; // int r; // int rr; // char c; // char *Write; // int g; // // StorageStart=Web->ArgsStorage; // // printf("\r\nARGS DUMP:\r\n"); // for(r=0;r<16*4;r++) // { // printf("%02X ",(unsigned char)StorageStart[r]); // if((r&15)==15) // { // for(rr=0;rr<16;rr++) // { // c=StorageStart[r-15+rr]; // printf("%c",c<32||c>127?'.':c); // } // printf("\r\n"); // } // } // // Write=StorageStart; // if(Web->PageProp.Gets!=NULL) // { // for(g=0;Web->PageProp.Gets[g]!=0;g++) // { // if(*Write=='Y') // { // printf("GET %s=\"%s\"\r\n",Web->PageProp.Gets[g],Write+1); // while(*Write!=0) // Write++; // } // else // { // printf("GET %s not found\r\n",Web->PageProp.Gets[g]); // } // Write++; // } // } // // if(Web->PageProp.Cookies!=NULL) // { // for(g=0;Web->PageProp.Cookies[g]!=0;g++) // { // if(*Write=='Y') // { // printf("COOKIE %s=\"%s\"\r\n",Web->PageProp.Cookies[g],Write+1); // while(*Write!=0) // Write++; // } // else // { // printf("COOKIE %s not found\r\n",Web->PageProp.Cookies[g]); // } // Write++; // } // } // // if(Web->PageProp.Posts!=NULL) // { // for(g=0;Web->PageProp.Posts[g]!=0;g++) // { // if(*Write=='Y') // { // printf("POST %s=\"%s\"\r\n",Web->PageProp.Posts[g],Write+1); // while(*Write!=0) // Write++; // } // else // { // printf("POST %s not found\r\n",Web->PageProp.Posts[g]); // } // Write++; // } // } //}