/* Copyright (C) 1996 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. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. The author can be contacted via Email at bmorin@wpi.edu */ #include "text_t.h" #include #include #include #include #include #pragma hdrstop #include "parse.h" #include "netio.h" #include "settings.h" #include "httpreq.h" //Had to put myself here because of the global types... #include "httpsrv.h" #include "httpsend.h" #include "cgiwrapper.h" /* Implementation Notes: HTTP field names, method and version strings are converted to upper case right after being read from the client in order to allow case insensitive string comparisons to be done on them. Since these fields are worked with a lot, this should help performance. */ //Private Data and declarations #define IO_BUFFER_SIZE 16384 //16K IO Buffer #define MAX_HTTP_LINE_LEN 1024 //Max length of line in a header of 1024 #define MAX_HTTP_FIELD_NAME_LEN 128 //Max length of name field in line #define MAX_HTTP_FIELD_LEN 1024 //Max length of data in line //Private Function Declarations with Return Contstants /* Function Name: DispatchRequest Purpose: Manages having the request parsed, then sent to the right function to send a response or handle an error. Parameters: ClientSocket - Socket the client is on ClientSockAddr - Address of client AddrLen - Length of address of client IOBuffer - Pointer to buffer allocated for IO operations ThreadNum - Number of thread that called this function for debugging purposes Notes: I'm still playing with the keep alive support. I commented out the stuff for giving a client a timeout because I was unable to detect disconnects. More Notes: Not sure if this organization will allow me to easily add support for ISAPI filter DLLs. */ void DispatchRequest(SOCKET ClientSocket, SOCKADDR_IN ClientSockAddr, int AddrLen, BYTE *IOBuffer); /* Function Name: Get HTTP Headers Purpose: Manages having the request parsed, then sent to the right function to send a response or handle an error. Parameters: RequestInfo - Request information structure (see httpreq.h) RequestFields - HTTP request fields structure (see httpreq.h) Returns: GH_ERROR on error (diconnect, bad data, Windows in a bad mood, etc.) GH_UNKNOWN_VERSION if the version number is not HTTP/0.9 or HTTP/1.x GH_SIMPLE_REQUEST on a properly formated HTTP/0.9 request GH_10_REQUEST on a properly formated HTTP/1.x request */ int GetHTTPHeaders(RequestInfoT &RequestInfo, RequestFieldsT &RequestFields); #define GH_ERROR -1 #define GH_UNKNOWN_VERSION 0 #define GH_SIMPLE_REQUEST 1 #define GH_10_REQUEST 2 /* Function Name: Clean Up HTTP Headers Purpose: Cleans up memory dynamicly allocated for headers Parameters: RequestInfo - Request information structure (see httpreq.h) RequestFields - HTTP request fields structure (see httpreq.h) Returns: Nothing */ void CleanUpHTTPHeaders(RequestInfoT &RequestInfo, RequestFieldsT &RequestFields); /* Function Name: Split Query Purpose: Splits the file and query part of a URI. In other words, it puts the parts before and after the "?" in differnet strings. Parameters: URIStr - The requested URI FileStr - String to contain the name of the path + file part of the URI QueryStr - String to contain the query part of the URI Returns: TRUE if there is a query, else FALSE */ BOOL SplitQuery(char *URIStr, char *FileStr, char *QueryStr, int ThreadNum); /* Function Name: Get File Purpose: Attempts to find a given file, including looking for index.html. Updates the given URI string so it points to the true document location Parameters: FilePath - Path of file, may be modified to best reflect the retrived file or directory URIStr - URI string, minus the query Returns: GF_ERROR on error GF_FILE_FOUND on success GF_INDEX_FOUND if file is a directory with an index.html file in it GF_DIRECTORY if file is a directory GF_FILE_NOT_FOUND if file was found */ /* Function Name: Process Simple Request Purpose: Sends a reply to a HTTP 0.9 "simple" request Parameters: ClientSocket - Socket the client is on RequestInfo - Structure storing the parsed headers IOBuffer - Pointer to buffer allocated for IO operations TheadNum - Number of calling thread for debugging Notes: I should really test this and see if it works... */ void ProcessSimpleRequest(RequestInfoT &RequestInfo, RequestFieldsT &RequestFields); //Public Functions /******************************************************************************/ void RequestThread(RequestThreadMessageT *Parameters) { SOCKADDR_IN ClientSockAddr; SOCKET ClientSocket; int AddrLen; //Allocate an IO buffer for this thread BYTE *IOBuffer = new BYTE[IO_BUFFER_SIZE]; //Get the parameters for the request ClientSocket = Parameters->ClientSocket; ClientSockAddr = Parameters->ClientSockAddr; AddrLen = Parameters->AddrLen; DispatchRequest(ClientSocket, ClientSockAddr, AddrLen, IOBuffer); } /******************************************************************************/ //Private Functions /******************************************************************************/ void DispatchRequest(SOCKET ClientSocket, SOCKADDR_IN ClientSockAddr, int AddrLen, BYTE *IOBuffer) { RequestInfoT RequestInfo; RequestFieldsT RequestFields; // TrayAddConnection(); //Setup the RequestInfo structure memset(&RequestInfo, 0, sizeof(RequestInfoT)); RequestInfo.ThreadNum = 0; RequestInfo.IOBuffer = IOBuffer; RequestInfo.IOBufferSize = IO_BUFFER_SIZE; RequestInfo.ClientSocket = ClientSocket; RequestInfo.ClientSockAddr = ClientSockAddr; RequestInfo.AddrLen = AddrLen; RequestInfo.KeepAlive = FALSE; int GetHeadersResult; do { //Get Headers GetHeadersResult = GetHTTPHeaders(RequestInfo, RequestFields); //Figure out what version we're dealing with and deal with it switch (GetHeadersResult) { case GH_SIMPLE_REQUEST : SendHTTPError(400, "HTTP Request not supported", "Only 1.x requests supported", RequestInfo, RequestFields); // TrayIncNumServed(); break; case GH_10_REQUEST : ExamineURIStr(RequestFields.URIStr,&RequestInfo,&RequestFields); // TrayIncNumServed(); break; case GH_UNKNOWN_VERSION : SendHTTPError(400, "HTTP Version not supported", "Only 1.x requests supported", RequestInfo, RequestFields); // TrayIncNumServed(); break; case GH_ERROR: //Disconnect RequestInfo.KeepAlive = FALSE; break; } CleanUpHTTPHeaders(RequestInfo, RequestFields); } while (0/*RequestInfo.KeepAlive == TRUE*/); //Close connection CloseSocket(RequestInfo.ClientSocket); // TrayRemoveConnection(); } /******************************************************************************/ int GetHTTPHeaders(RequestInfoT &RequestInfo, RequestFieldsT &RequestFields) { //Parsing and IO buffers char CurLine[NETIO_MAX_LINE]; char NextLine[NETIO_MAX_LINE]; char FieldNameStr[MAX_HTTP_FIELD_NAME_LEN]; char FieldValStr[MAX_HTTP_FIELD_LEN]; //Parsing and IO working vars int ReadBufferIndex; int DataInBuffer; int Start; int End; int Len; //Clear all the fields memset(&RequestFields, 0, sizeof(RequestFieldsT)); ReadBufferIndex = 0; DataInBuffer = 0; //Get First Line if (GetLine(CurLine, RequestInfo.ClientSocket, RequestInfo.IOBuffer, RequestInfo.IOBufferSize, ReadBufferIndex, DataInBuffer, RequestInfo.ThreadNum) != 0) return GH_ERROR; do {//Get Next Line, append it if the first charactor is space if(GetLine(NextLine, RequestInfo.ClientSocket, RequestInfo.IOBuffer, RequestInfo.IOBufferSize, ReadBufferIndex, DataInBuffer, RequestInfo.ThreadNum) != 0) return GH_ERROR; if ((NextLine[0] == ' ') || (NextLine[0] == '\t')) strcat(CurLine, NextLine); } while ((NextLine[0] == ' ') || (NextLine[0] == '\t')); //Method String (first word) Start = 0; GetWord(RequestFields.MethodStr, CurLine, Start, End); CharUpper(RequestFields.MethodStr); //Version String (last word) GetLastWord(RequestFields.VersionStr, CurLine, Start); CharUpper(RequestFields.VersionStr); if (strncmp(RequestFields.VersionStr, "HTTP/", 5) != 0) { //No version, assume simple request //part after method is URI memcpy(RequestFields.URIStr, CurLine + End, strlen(CurLine) + 1 - End); return GH_SIMPLE_REQUEST; } //URI String (in between End of first and Start of last) // // End^ Start^ int URIStrLen = Start - End;; if (URIStrLen > ReqURIStrLen-1) URIStrLen = ReqURIStrLen - 1; memcpy(RequestFields.URIStr, CurLine + End, URIStrLen); RequestFields.URIStr[URIStrLen] = 0; TrimRight(RequestFields.URIStr); //Remove trailing space //Only accept requests from HTTP/0.9 or HTTP/1.X clients, we'll //assume that anything else will require an upgrade or patch if (strncmp(RequestFields.VersionStr, "HTTP/1.", 7) != 0) return GH_UNKNOWN_VERSION; //Get the rest of the lines strcpy(CurLine, NextLine); while (CurLine[0] != 0) {//Blank Line, we're done do {//Get Next Line, append it if the first charactor is space if (GetLine(NextLine, RequestInfo.ClientSocket, RequestInfo.IOBuffer, RequestInfo.IOBufferSize, ReadBufferIndex, DataInBuffer, RequestInfo.ThreadNum) != 0) return GH_ERROR; if ((NextLine[0] == ' ') || (NextLine[0] == '\t')) strcat(CurLine, NextLine); } while ((NextLine[0] == ' ') || (NextLine[0] == '\t')); Start = 0; GetWord(FieldNameStr, CurLine, Start, End); CharUpper(FieldNameStr); Len = strlen(CurLine) - End; memcpy(FieldValStr, CurLine + End, Len); FieldValStr[Len] = 0; //Process it //In order of expected commonality //All constants are in canonized, thus in upper case and case sensitive //comparisons are used //--Just About Always-- if (strcmp("ACCEPT:", FieldNameStr) == 0) { if (RequestFields.AcceptStr[0] == '\0') { strncpy(RequestFields.AcceptStr, FieldValStr, ReqAcceptStrLen - 1); // if (Len >= ReqAcceptStrLen) // if (log_debug) log_message("Accept field truncated"); } else { //Append it with a comma int AcceptStrLen = strlen(RequestFields.AcceptStr); if ((ReqAcceptStrLen - AcceptStrLen) >= 10) { strncat(RequestFields.AcceptStr, ", ", ReqAcceptStrLen - AcceptStrLen - 1); strncat(RequestFields.AcceptStr, FieldValStr, ReqAcceptStrLen - AcceptStrLen - 3); } } } else if (strcmp("DATE:", FieldNameStr) == 0) { strncpy(RequestFields.DateStr, FieldValStr, ReqDateStrLen - 1); // if (Len >= ReqDateStrLen) LogError("Date field truncated"); } else if (strcmp("USER-AGENT:", FieldNameStr) == 0) { strncpy(RequestFields.UserAgentStr, FieldValStr, ReqUserAgentStrLen - 1); // if (Len >= ReqUserAgentStrLen) { // LogError("User Agent field truncated, value follows"); // LogError(RequestFields.UserAgentStr); // } } else if (strcmp("CONNECTION:", FieldNameStr) == 0) { strncpy(RequestFields.ConnectionStr, FieldValStr, ReqConnectionStrLen - 1); // if (Len >= ReqConnectionStrLen) LogError("Connection field truncated"); } //--Sometimes-- else if (strcmp("ACCEPT-LANGUAGE:", FieldNameStr) == 0) { strncpy(RequestFields.AcceptLangStr, FieldValStr, ReqAcceptLangStrLen - 1); // if (Len >= ReqAcceptLangStrLen) LogError("Accept-Language field truncated"); } else if (strcmp("REFERER:", FieldNameStr) == 0) { strncpy(RequestFields.RefererStr, FieldValStr, ReqRefererStrLen - 1); // if (Len >= ReqRefererStrLen) LogError("Referer field truncated"); } else if (strcmp("IF-MODIFIED-SINCE:", FieldNameStr) == 0) { strncpy(RequestFields.IfModSinceStr, FieldValStr, ReqIfModSinceStrLen - 1); // if (Len >= ReqIfModSinceStrLen) LogError("If Modified Since field truncated"); } //--Uncommon-- else if (strcmp("FROM:", FieldNameStr) == 0) { strncpy(RequestFields.FromStr, FieldValStr, ReqFromStrLen - 1); // if (Len >= ReqFromStrLen) LogError("From field truncated"); } else if (strcmp("MIME-VERSION:", FieldNameStr) == 0) { strncpy(RequestFields.MIMEVerStr, FieldValStr, ReqMIMEVerStrLen - 1); // if (Len >= ReqMIMEVerStrLen) LogError("MIME Version field truncated"); } else if (strcmp("PRAGMA:", FieldNameStr) == 0) { strncpy(RequestFields.PragmaStr, FieldValStr, ReqPragmaStrLen - 1); // if (Len >= ReqPragmaStrLen) LogError("Pragma field truncated"); } //--Special case-- else if (strcmp("AUTHORIZATION:", FieldNameStr) == 0) { strncpy(RequestFields.AuthorizationStr, FieldValStr, ReqAuthorizationStrLen - 1); // if (Len >= ReqAuthorizationStrLen) LogError("Authorization field truncated"); } else if (strcmp("CONTENT-LENGTH:", FieldNameStr) == 0) { strncpy(RequestFields.ContentLengthStr, FieldValStr, ReqContentLengthStrLen - 1); // if (Len >= ReqContentLengthStrLen) LogError("Content Length field truncated"); } else if (strcmp("CONTENT-TYPE:", FieldNameStr) == 0) { strncpy(RequestFields.ContentTypeStr, FieldValStr, ReqContentTypeStrLen - 1); // if (Len >= ReqContentTypeStrLen) LogError("Content Type field truncated"); } else if (strcmp("CONTENT-ENCODING:", FieldNameStr) == 0) { strncpy(RequestFields.ContentEncodingStr, FieldValStr, ReqContentEncodingStrLen - 1); // if (Len >= ReqContentEncodingStrLen) LogError("Content Encoding field truncated"); } else { //Add it to the other headers int VarLen = strlen(FieldNameStr); if (FieldNameStr[VarLen - 1] == ':') { //Remove the colon FieldNameStr[VarLen - 1] = '\0'; VarLen--; } RequestFields.OtherHeaders[RequestFields.NumOtherHeaders].Var = new char[VarLen + 1]; RequestFields.OtherHeaders[RequestFields.NumOtherHeaders].Val = new char[Len + 1]; strcpy(RequestFields.OtherHeaders[RequestFields.NumOtherHeaders].Var, FieldNameStr); strcpy(RequestFields.OtherHeaders[RequestFields.NumOtherHeaders].Val, FieldValStr); RequestFields.NumOtherHeaders++; } strcpy(CurLine, NextLine); } if (RequestFields.ContentLengthStr[0] != 0) { //Do we have attached data? unsigned int NumRecv; RequestFields.ContentLength = atol(RequestFields.ContentLengthStr); if (RequestFields.ContentLength > 0) { // ThreadDebugMessage(RequestInfo.ThreadNum, "Getting content"); //Allocate memory RequestFields.Content = new BYTE[RequestFields.ContentLength]; //Get rest of data from get lines NumRecv = DataInBuffer - ReadBufferIndex; if (NumRecv >RequestFields.ContentLength) { //Overflow, only read what they said they'd send NumRecv = RequestFields.ContentLength; } memcpy(RequestFields.Content, RequestInfo.IOBuffer + ReadBufferIndex, NumRecv); while (NumRecv < RequestFields.ContentLength) { NumRecv = GetData(RequestInfo.ClientSocket, RequestFields.Content + NumRecv, RequestFields.ContentLength - NumRecv, RequestInfo.ThreadNum); if (NumRecv < 0) return GH_ERROR; } } else { RequestFields.Content = NULL; RequestFields.ContentLength = 0; } } else { RequestFields.Content = NULL; RequestFields.ContentLength = 0; } return GH_10_REQUEST; } /******************************************************************************/ void CleanUpHTTPHeaders(RequestInfoT &RequestInfo, RequestFieldsT &RequestFields) { //Clean up memory allocated for the Content if (RequestFields.Content != NULL) delete[] RequestFields.Content; while (RequestFields.NumOtherHeaders > 0) { RequestFields.NumOtherHeaders--; delete[] RequestFields.OtherHeaders[RequestFields.NumOtherHeaders].Var; delete[] RequestFields.OtherHeaders[RequestFields.NumOtherHeaders].Val; } // clean up memory allocated for the IOBuffer if (RequestInfo.IOBuffer != NULL) { delete[] RequestInfo.IOBuffer; RequestInfo.IOBuffer = NULL; } }