source: trunk/gsdl/src/w32server/httpreq.cpp@ 2286

Last change on this file since 2286 was 2286, checked in by sjboddie, 23 years ago

Had a bit of a tidy up in the fnord webserver code. The main change of note
was the removal of our reliance on the MAX_URL_SIZE constant. URLs and post
data of any length should now work.

  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
File size: 15.7 KB
Line 
1/**********************************************************************
2 *
3 * httpreq.cpp
4 * Copyright (C) 1996
5 *
6 * A component of the fnord webserver written by [email protected].
7 *
8 * Altered for use with the Greenstone digital library software by the
9 * New Zealand Digital Library Project at the University of Waikato,
10 * New Zealand.
11 *
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
16 *
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
25 *
26 *********************************************************************/
27
28#include "text_t.h"
29#include <windows.h>
30#include <stdlib.h>
31#include <stdio.h>
32#include <string.h>
33#include <memory.h>
34#pragma hdrstop
35#include "parse.h"
36#include "netio.h"
37#include "settings.h"
38#include "httpreq.h" //Had to put myself here because of the global types...
39#include "httpsrv.h"
40#include "httpsend.h"
41#include "cgiwrapper.h"
42
43/*
44Implementation Notes:
45
46HTTP field names, method and version strings are converted to upper case
47right after being read from the client in order to allow case insensitive
48string comparisons to be done on them. Since these fields are worked with a
49lot, this should help performance.
50*/
51
52//Private Data and declarations
53#define IO_BUFFER_SIZE 16384 //16K IO Buffer
54#define MAX_HTTP_LINE_LEN 1024 //Max length of line in a header of 1024
55#define MAX_HTTP_FIELD_NAME_LEN 128 //Max length of name field in line
56#define MAX_HTTP_FIELD_LEN 1024 //Max length of data in line
57
58//Private Function Declarations with Return Contstants
59
60/*
61Function Name: DispatchRequest
62Purpose: Manages having the request parsed, then sent to the right function
63 to send a response or handle an error.
64Parameters:
65 ClientSocket - Socket the client is on
66 ClientSockAddr - Address of client
67 AddrLen - Length of address of client
68 IOBuffer - Pointer to buffer allocated for IO operations
69 ThreadNum - Number of thread that called this function for debugging purposes
70Notes: I'm still playing with the keep alive support. I commented out
71 the stuff for giving a client a timeout because I was unable to detect
72 disconnects.
73More Notes: Not sure if this organization will allow me to easily add support
74 for ISAPI filter DLLs.
75*/
76void DispatchRequest(SOCKET ClientSocket, SOCKADDR_IN ClientSockAddr, int AddrLen, BYTE *IOBuffer);
77
78/*
79Function Name: Get HTTP Headers
80Purpose: Manages having the request parsed, then sent to the right function
81 to send a response or handle an error.
82Parameters:
83 RequestInfo - Request information structure (see httpreq.h)
84 RequestFields - HTTP request fields structure (see httpreq.h)
85Returns: GH_ERROR on error (diconnect, bad data, Windows in a bad mood, etc.)
86 GH_UNKNOWN_VERSION if the version number is not HTTP/0.9 or HTTP/1.x
87 GH_SIMPLE_REQUEST on a properly formated HTTP/0.9 request
88 GH_10_REQUEST on a properly formated HTTP/1.x request
89*/
90int GetHTTPHeaders(RequestInfoT &RequestInfo, RequestFieldsT &RequestFields);
91#define GH_ERROR -1
92#define GH_UNKNOWN_VERSION 0
93#define GH_SIMPLE_REQUEST 1
94#define GH_10_REQUEST 2
95
96/*
97Function Name: Clean Up HTTP Headers
98Purpose: Cleans up memory dynamicly allocated for headers
99Parameters:
100 RequestInfo - Request information structure (see httpreq.h)
101 RequestFields - HTTP request fields structure (see httpreq.h)
102Returns: Nothing
103*/
104void CleanUpHTTPHeaders(RequestInfoT &RequestInfo, RequestFieldsT &RequestFields);
105
106/*
107Function Name: Split Query
108Purpose: Splits the file and query part of a URI. In other words, it
109 puts the parts before and after the "?" in differnet strings.
110Parameters:
111 URIStr - The requested URI
112 FileStr - String to contain the name of the path + file part of the URI
113 QueryStr - String to contain the query part of the URI
114Returns: TRUE if there is a query, else FALSE
115*/
116BOOL SplitQuery(char *URIStr, char *FileStr, char *QueryStr, int ThreadNum);
117
118/*
119Function Name: Get File
120Purpose: Attempts to find a given file, including looking for index.html.
121 Updates the given URI string so it points to the true document location
122Parameters:
123 FilePath - Path of file, may be modified to best reflect the retrived file
124 or directory
125 URIStr - URI string, minus the query
126Returns: GF_ERROR on error
127 GF_FILE_FOUND on success
128 GF_INDEX_FOUND if file is a directory with an index.html file in it
129 GF_DIRECTORY if file is a directory
130 GF_FILE_NOT_FOUND if file was found
131*/
132
133/*
134Function Name: Process Simple Request
135Purpose: Sends a reply to a HTTP 0.9 "simple" request
136Parameters:
137 ClientSocket - Socket the client is on
138 RequestInfo - Structure storing the parsed headers
139 IOBuffer - Pointer to buffer allocated for IO operations
140 TheadNum - Number of calling thread for debugging
141Notes: I should really test this and see if it works...
142*/
143void ProcessSimpleRequest(RequestInfoT &RequestInfo, RequestFieldsT &RequestFields);
144
145//Public Functions
146/******************************************************************************/
147void RequestThread(RequestThreadMessageT *Parameters) {
148 SOCKADDR_IN ClientSockAddr;
149 SOCKET ClientSocket;
150 int AddrLen;
151 //Allocate an IO buffer for this thread
152 BYTE *IOBuffer = new BYTE[IO_BUFFER_SIZE];
153
154 //Get the parameters for the request
155 ClientSocket = Parameters->ClientSocket;
156 ClientSockAddr = Parameters->ClientSockAddr;
157 AddrLen = Parameters->AddrLen;
158 DispatchRequest(ClientSocket, ClientSockAddr, AddrLen, IOBuffer);
159}
160/******************************************************************************/
161
162//Private Functions
163
164/******************************************************************************/
165void DispatchRequest(SOCKET ClientSocket, SOCKADDR_IN ClientSockAddr, int AddrLen, BYTE *IOBuffer) {
166 RequestInfoT RequestInfo;
167 RequestFieldsT RequestFields;
168
169 // TrayAddConnection();
170
171 //Setup the RequestInfo structure
172 memset(&RequestInfo, 0, sizeof(RequestInfoT));
173 RequestInfo.ThreadNum = 0;
174 RequestInfo.IOBuffer = IOBuffer;
175 RequestInfo.IOBufferSize = IO_BUFFER_SIZE;
176 RequestInfo.ClientSocket = ClientSocket;
177 RequestInfo.ClientSockAddr = ClientSockAddr;
178 RequestInfo.AddrLen = AddrLen;
179 RequestInfo.KeepAlive = FALSE;
180
181 int GetHeadersResult;
182 do {
183 //Get Headers
184 GetHeadersResult = GetHTTPHeaders(RequestInfo, RequestFields);
185 //Figure out what version we're dealing with and deal with it
186 switch (GetHeadersResult) {
187 case GH_SIMPLE_REQUEST :
188 SendHTTPError(400, "HTTP Request not supported", "Only 1.x requests supported", RequestInfo, RequestFields);
189 // TrayIncNumServed();
190 break;
191 case GH_10_REQUEST :
192 ExamineURIStr(RequestFields.URIStr,&RequestInfo,&RequestFields);
193 // TrayIncNumServed();
194 break;
195 case GH_UNKNOWN_VERSION :
196 SendHTTPError(400, "HTTP Version not supported", "Only 1.x requests supported", RequestInfo, RequestFields);
197 // TrayIncNumServed();
198 break;
199 case GH_ERROR:
200 //Disconnect
201 RequestInfo.KeepAlive = FALSE;
202 break;
203 }
204 CleanUpHTTPHeaders(RequestInfo, RequestFields);
205 } while (0/*RequestInfo.KeepAlive == TRUE*/);
206 //Close connection
207 CloseSocket(RequestInfo.ClientSocket);
208 // TrayRemoveConnection();
209}
210
211/******************************************************************************/
212int GetHTTPHeaders(RequestInfoT &RequestInfo, RequestFieldsT &RequestFields) {
213 //Parsing and IO buffers
214 char CurLine[NETIO_MAX_LINE];
215 char NextLine[NETIO_MAX_LINE];
216 char FieldNameStr[MAX_HTTP_FIELD_NAME_LEN];
217 char FieldValStr[MAX_HTTP_FIELD_LEN];
218
219 //Parsing and IO working vars
220 int ReadBufferIndex;
221 int DataInBuffer;
222 int Start;
223 int End;
224 int Len;
225
226 //Clear all the fields
227 memset(&RequestFields, 0, sizeof(RequestFieldsT));
228
229 ReadBufferIndex = 0;
230 DataInBuffer = 0;
231
232 //Get First Line
233 if (GetLine(CurLine, RequestInfo.ClientSocket, RequestInfo.IOBuffer,
234 RequestInfo.IOBufferSize, ReadBufferIndex, DataInBuffer,
235 RequestInfo.ThreadNum) != 0) return GH_ERROR;
236 do {//Get Next Line, append it if the first charactor is space
237 if(GetLine(NextLine, RequestInfo.ClientSocket, RequestInfo.IOBuffer,
238 RequestInfo.IOBufferSize, ReadBufferIndex, DataInBuffer,
239 RequestInfo.ThreadNum) != 0) return GH_ERROR;
240 if ((NextLine[0] == ' ') || (NextLine[0] == '\t'))
241 strcat(CurLine, NextLine);
242 } while ((NextLine[0] == ' ') || (NextLine[0] == '\t'));
243 //Method String (first word)
244 Start = 0;
245 GetWord(RequestFields.MethodStr, CurLine, Start, End);
246 CharUpper(RequestFields.MethodStr);
247
248 //Version String (last word)
249 GetLastWord(RequestFields.VersionStr, CurLine, Start);
250 CharUpper(RequestFields.VersionStr);
251
252 if (strncmp(RequestFields.VersionStr, "HTTP/", 5) != 0) {
253 //No version, assume simple request
254 //part after method is URI
255 for (int i = 0; i < strlen(CurLine); i++) {
256 RequestFields.URIStr.push_back(CurLine[i]);
257 }
258 return GH_SIMPLE_REQUEST;
259 }
260
261 //URI String (in between End of first and Start of last)
262 //<Method> <WhiteSpace> <URI> <WhiteSpace> <Version> <CRLF>
263 // End^ Start^
264 text_t spacebuffer;
265 for (int i = End; i < Start; i++) {
266 // do this to remove trailing space
267 if (CurLine[i] == ' ') {
268 spacebuffer.push_back(' ');
269 } else {
270 if (!spacebuffer.empty()) {
271 RequestFields.URIStr += spacebuffer;
272 spacebuffer.clear();
273 }
274 RequestFields.URIStr.push_back(CurLine[i]);
275 }
276 }
277
278 //Only accept requests from HTTP/0.9 or HTTP/1.X clients, we'll
279 //assume that anything else will require an upgrade or patch
280 if (strncmp(RequestFields.VersionStr, "HTTP/1.", 7) != 0)
281 return GH_UNKNOWN_VERSION;
282
283 //Get the rest of the lines
284
285 strcpy(CurLine, NextLine);
286
287 while (CurLine[0] != 0) {//Blank Line, we're done
288 do {//Get Next Line, append it if the first charactor is space
289 if (GetLine(NextLine, RequestInfo.ClientSocket, RequestInfo.IOBuffer,
290 RequestInfo.IOBufferSize, ReadBufferIndex, DataInBuffer,
291 RequestInfo.ThreadNum) != 0)
292 return GH_ERROR;
293 if ((NextLine[0] == ' ') || (NextLine[0] == '\t'))
294 strcat(CurLine, NextLine);
295 } while ((NextLine[0] == ' ') || (NextLine[0] == '\t'));
296
297 Start = 0;
298 GetWord(FieldNameStr, CurLine, Start, End);
299 CharUpper(FieldNameStr);
300
301 Len = strlen(CurLine) - End;
302 memcpy(FieldValStr, CurLine + End, Len);
303 FieldValStr[Len] = 0;
304
305 //Process it
306 //In order of expected commonality
307 //All constants are in canonized, thus in upper case and case sensitive
308 //comparisons are used
309 //--Just About Always--
310 if (strcmp("ACCEPT:", FieldNameStr) == 0) {
311 if (RequestFields.AcceptStr[0] == '\0') {
312 strncpy(RequestFields.AcceptStr, FieldValStr, ReqAcceptStrLen - 1);
313 }
314 else {
315 //Append it with a comma
316 int AcceptStrLen = strlen(RequestFields.AcceptStr);
317 if ((ReqAcceptStrLen - AcceptStrLen) >= 10) {
318 strncat(RequestFields.AcceptStr, ", ", ReqAcceptStrLen - AcceptStrLen - 1);
319 strncat(RequestFields.AcceptStr, FieldValStr, ReqAcceptStrLen - AcceptStrLen - 3);
320 }
321 }
322 }
323 else if (strcmp("DATE:", FieldNameStr) == 0) {
324 strncpy(RequestFields.DateStr, FieldValStr, ReqDateStrLen - 1);
325 }
326 else if (strcmp("USER-AGENT:", FieldNameStr) == 0) {
327 strncpy(RequestFields.UserAgentStr, FieldValStr, ReqUserAgentStrLen - 1);
328 }
329 else if (strcmp("CONNECTION:", FieldNameStr) == 0) {
330 strncpy(RequestFields.ConnectionStr, FieldValStr, ReqConnectionStrLen - 1);
331 }
332 //--Sometimes--
333 else if (strcmp("ACCEPT-LANGUAGE:", FieldNameStr) == 0) {
334 strncpy(RequestFields.AcceptLangStr, FieldValStr, ReqAcceptLangStrLen - 1);
335 }
336 else if (strcmp("REFERER:", FieldNameStr) == 0) {
337 strncpy(RequestFields.RefererStr, FieldValStr, ReqRefererStrLen - 1);
338 }
339 else if (strcmp("IF-MODIFIED-SINCE:", FieldNameStr) == 0) {
340 strncpy(RequestFields.IfModSinceStr, FieldValStr, ReqIfModSinceStrLen - 1);
341 }
342 //--Uncommon--
343 else if (strcmp("FROM:", FieldNameStr) == 0) {
344 strncpy(RequestFields.FromStr, FieldValStr, ReqFromStrLen - 1);
345 }
346 else if (strcmp("MIME-VERSION:", FieldNameStr) == 0) {
347 strncpy(RequestFields.MIMEVerStr, FieldValStr, ReqMIMEVerStrLen - 1);
348 }
349 else if (strcmp("PRAGMA:", FieldNameStr) == 0) {
350 strncpy(RequestFields.PragmaStr, FieldValStr, ReqPragmaStrLen - 1);
351 }
352 //--Special case--
353 else if (strcmp("AUTHORIZATION:", FieldNameStr) == 0) {
354 strncpy(RequestFields.AuthorizationStr, FieldValStr, ReqAuthorizationStrLen - 1);
355 }
356 else if (strcmp("CONTENT-LENGTH:", FieldNameStr) == 0) {
357 strncpy(RequestFields.ContentLengthStr, FieldValStr, ReqContentLengthStrLen - 1);
358 }
359 else if (strcmp("CONTENT-TYPE:", FieldNameStr) == 0) {
360 strncpy(RequestFields.ContentTypeStr, FieldValStr, ReqContentTypeStrLen - 1);
361 }
362 else if (strcmp("CONTENT-ENCODING:", FieldNameStr) == 0) {
363 strncpy(RequestFields.ContentEncodingStr, FieldValStr, ReqContentEncodingStrLen - 1);
364 }
365 else {
366 //Add it to the other headers
367 int VarLen = strlen(FieldNameStr);
368 if (FieldNameStr[VarLen - 1] == ':') {
369 //Remove the colon
370 FieldNameStr[VarLen - 1] = '\0';
371 VarLen--;
372 }
373 RequestFields.OtherHeaders[RequestFields.NumOtherHeaders].Var = new char[VarLen + 1];
374 RequestFields.OtherHeaders[RequestFields.NumOtherHeaders].Val = new char[Len + 1];
375 strcpy(RequestFields.OtherHeaders[RequestFields.NumOtherHeaders].Var, FieldNameStr);
376 strcpy(RequestFields.OtherHeaders[RequestFields.NumOtherHeaders].Val, FieldValStr);
377 RequestFields.NumOtherHeaders++;
378 }
379 strcpy(CurLine, NextLine);
380 }
381
382 if (RequestFields.ContentLengthStr[0] != 0) { //Do we have attached data?
383 unsigned int NumRecv;
384
385 RequestFields.ContentLength = atol(RequestFields.ContentLengthStr);
386 if (RequestFields.ContentLength > 0) {
387
388 //Allocate memory
389 RequestFields.Content = new BYTE[RequestFields.ContentLength];
390
391 //Get rest of data from get lines
392 NumRecv = DataInBuffer - ReadBufferIndex;
393
394 if (NumRecv >RequestFields.ContentLength) {
395 //Overflow, only read what they said they'd send
396 NumRecv = RequestFields.ContentLength;
397 }
398 memcpy(RequestFields.Content, RequestInfo.IOBuffer + ReadBufferIndex,
399 NumRecv);
400
401 while (NumRecv < RequestFields.ContentLength) {
402 NumRecv = GetData(RequestInfo.ClientSocket,
403 RequestFields.Content + NumRecv,
404 RequestFields.ContentLength - NumRecv,
405 RequestInfo.ThreadNum);
406 if (NumRecv < 0) return GH_ERROR;
407 }
408 }
409 else {
410 RequestFields.Content = NULL;
411 RequestFields.ContentLength = 0;
412 }
413 }
414 else {
415 RequestFields.Content = NULL;
416 RequestFields.ContentLength = 0;
417 }
418
419 return GH_10_REQUEST;
420}
421
422/******************************************************************************/
423void CleanUpHTTPHeaders(RequestInfoT &RequestInfo, RequestFieldsT &RequestFields) {
424 //Clean up memory allocated for the Content
425 if (RequestFields.Content != NULL)
426 delete[] RequestFields.Content;
427 while (RequestFields.NumOtherHeaders > 0) {
428 RequestFields.NumOtherHeaders--;
429 delete[] RequestFields.OtherHeaders[RequestFields.NumOtherHeaders].Var;
430 delete[] RequestFields.OtherHeaders[RequestFields.NumOtherHeaders].Val;
431 }
432
433 // clean up memory allocated for the IOBuffer
434 if (RequestInfo.IOBuffer != NULL) {
435 delete[] RequestInfo.IOBuffer;
436 RequestInfo.IOBuffer = NULL;
437 }
438}
Note: See TracBrowser for help on using the repository browser.