:pserver:cvsanon@mok.lvcm.com:/CVS/ReactOS reactos
[reactos.git] / apps / utils / net / roshttpd / httpd.cpp
1 /*
2  * COPYRIGHT:   See COPYING in the top level directory
3  * PROJECT:     ReactOS HTTP Daemon
4  * FILE:        httpd.cpp
5  * PURPOSE:     HTTP daemon
6  * PROGRAMMERS: Casper S. Hornstrup (chorns@users.sourceforge.net)
7  * REVISIONS:
8  *   CSH  01/09/2000 Created
9  */
10 #include <debug.h>
11 #include <new>
12 #include <malloc.h>
13 #include <string.h>
14 #include <config.h>
15 #include <httpd.h>
16 #include <error.h>
17
18 using namespace std;
19
20 CHAR HttpMsg400[] = "<HEAD><TITLE>400 Bad Request</TITLE></HEAD>\n\r<BODY><H1>400 Bad Request</H1>\n\rThe request had bad syntax.<BR>\n\r</BODY>\n\r\n\r";
21 CHAR HttpMsg404[] = "<HEAD><TITLE>404 Not Found</TITLE></HEAD>\n\r<BODY><H1>404 Not Found</H1>\n\rThe requested URL was not found on this server.<BR>\n\r</BODY>\n\r\n\r";
22 CHAR HttpMsg405[] = "<HEAD><TITLE>405 Method Not Allowed</TITLE></HEAD>\n\r<BODY><H1>405 Method Not Allowed</H1>\n\rThe requested method is not supported on this server.<BR>\n\r</BODY>\n\r\n\r";
23 CHAR HttpMsg500[] = "<HEAD><TITLE>500 Internal Server Error</TITLE></HEAD>\n\r<BODY><H1>500 Internal Server Error</H1>\n\rAn internal error occurred.<BR>\n\r</BODY>\n\r\n\r";
24 CHAR HttpMsg501[] = "<HEAD><TITLE>501 Not Implemented</TITLE></HEAD>\n\r<BODY><H1>501 Not Implemented</H1>\n\rThe feature is not implemented.<BR>\n\r</BODY>\n\r\n\r";
25
26
27 // *************************** CHttpClient ***************************
28
29 // Default constructor
30 CHttpClient::CHttpClient()
31 {
32 }
33
34 // Constructor with server socket as starter value
35 CHttpClient::CHttpClient(CServerSocket *serversocket)
36 {
37         ServerSocket = serversocket;
38 }
39
40 // Split URIs into its parts (ie. |http://|www.host.com|/resource|?parameters|)
41 VOID CHttpClient::SplitUri(LPSTR lpsUri, LPSTR lpsHost, LPSTR lpsResource, LPSTR lpsParams)
42 {
43     LPSTR lpsPos;
44         LPSTR lpsStr;
45     UINT i;
46
47         strcpy(lpsHost, "");
48         strcpy(lpsResource, "");
49         strcpy(lpsParams, "");
50
51     lpsPos = strstr(lpsUri, "://");
52     if (lpsPos != NULL)
53         lpsStr = &lpsPos[3];
54         else
55                 lpsStr = lpsUri;
56         
57     lpsPos = strstr(lpsStr, "/");
58     if (lpsPos != NULL) {
59         strncat(lpsHost, lpsPos, lpsPos - lpsStr);
60         lpsStr = &lpsPos[1];
61
62         lpsPos = strstr(lpsStr, "?");
63         if (lpsPos != NULL) {
64             strncat(lpsResource, lpsStr, lpsPos - lpsStr);
65             strcpy(lpsParams, &lpsPos[1]);
66         } else {
67             strcpy(lpsResource, lpsStr);
68             strcpy(lpsParams, "");
69         }
70
71         // Replace "/" with "\"
72                 for (i = 0; i < strlen(lpsResource); i++) {
73             if (lpsResource[i] == '/')
74                 lpsResource[i] = '\\';
75         }
76     }
77 }
78
79 // Split resource into its parts (ie. |/path/|filename|.extension|)
80 VOID CHttpClient::SplitResource(LPSTR lpsResource, LPSTR lpsPath, LPSTR lpsFilename, LPSTR lpsExtension)
81 {
82     INT i,len,fileptr,extptr;
83         
84         strcpy(lpsPath, "");
85         strcpy(lpsFilename, "");
86         strcpy(lpsExtension, "");
87         
88         len = strlen(lpsResource);
89         if (len != 0) {
90                 if (lpsResource[len - 1] == '/') {
91                         // There is only a path
92                         strcpy(lpsPath, lpsResource);
93                 } else {
94                         // Find extension
95                         i = len - 1;
96                         while ((i >= 0) && (lpsResource[i] != '.')) i--;
97                         extptr = i;
98                         while ((i >= 0) && (lpsResource[i] != '/')) i--;
99                         if (i > 0) {
100                                 // There is at least one directory in the path (besides root directory)
101                                 fileptr = i + 1;
102                                 strncat(lpsPath, lpsResource, fileptr);
103                         } else
104                                 fileptr = 1;
105                         
106                         // Get filename and possibly extension
107                         if (extptr != 0) {
108                                 strncat(lpsFilename, &lpsResource[fileptr], extptr - fileptr);
109                                 // Get extension
110                                 strncat(lpsExtension, &lpsResource[extptr + 1], len - extptr - 1);
111                         } else
112                                 strncat(lpsFilename, &lpsResource[fileptr], len - fileptr);
113                 }
114         }
115 }
116
117 // Process HTTP request
118 VOID CHttpClient::ProcessRequest()
119 {
120     CHAR sStr[255];
121         CHAR sHost[255];
122     CHAR sResource[255];
123     CHAR sParams[255];
124
125     // Which method?
126     switch (Parser.nMethodNo) {
127                 case hmGET: {
128                         SplitUri(Parser.sUri, sHost, sResource, sParams);
129                         
130                         // Default resource?
131                         if (strlen(sResource) == 0) {
132                                 CIterator<LPSTR> *i = pConfiguration->GetDefaultResources()->CreateIterator();
133
134                                 // FIXME: All default resources should be tried
135                                 // Iterate through all strings
136                                 //for (i->First(); !i->IsDone(); i->Next())
137                                 i->First();
138                                 if (!i->IsDone()) {
139                                         strcat(sResource, i->CurrentItem());
140                                         delete i;
141                                 } else {
142                                         // File not found
143                                         Report("404 Not Found", HttpMsg404);
144                                         break;
145                                 }
146                         }
147                         strcpy(sStr, pConfiguration->GetHttpBase());
148                 strcat(sStr, sResource);
149                         SendFile(sStr);
150                         break;
151                 }
152                 default: {
153                         // Method is not implemented
154                         Report("501 Not Implemented", HttpMsg501);
155                 }
156         }
157 }
158
159 // Send a file to socket
160 VOID CHttpClient::SendFile(LPSTR lpsFilename)
161 {
162     CHAR str[255];
163     CHAR str2[32];
164     union BigNum {
165       //        unsigned __int64 Big;
166       unsigned long long Big;
167         struct {
168             DWORD Low;
169             DWORD High; 
170         } u;
171     } nTotalBytes;
172         DWORD nBytesToRead;
173         DWORD nBytesRead;
174         BOOL bStatus;
175
176         // Try to open file
177     hFile = CreateFileA(lpsFilename,
178         GENERIC_READ,               // Open for reading 
179         FILE_SHARE_READ,            // Share for reading 
180         NULL,                       // No security 
181         OPEN_EXISTING,              // Existing file only 
182         FILE_ATTRIBUTE_NORMAL,      // Normal file 
183         NULL);                      // No attr. template 
184     if (hFile == INVALID_HANDLE_VALUE) { 
185         // File not found
186         Report("404 Not Found", HttpMsg404);
187         return; 
188     }
189     // Get file size
190     nTotalBytes.u.Low = GetFileSize(hFile, &nTotalBytes.u.High);
191     if ((nTotalBytes.u.Low == 0xFFFFFFFF) && ((GetLastError()) != NO_ERROR)) {
192         // Internal server error
193                 Report("500 Internal Server Error", HttpMsg500);
194                 // Close file
195                 CloseHandle(hFile);
196         return;
197     }
198
199         // Determine buffer size
200         if (nTotalBytes.Big < 65536)
201                 nBufferSize = 1024;
202         else
203                 nBufferSize = 32768;
204         // Allocate memory on heap
205         lpsBuffer = (PCHAR) malloc(nBufferSize);
206
207         if (lpsBuffer == NULL) {
208                 // Internal server error
209                 Report("500 Internal Server Error", HttpMsg500);
210                 // Close file
211                 CloseHandle(hFile);
212                 return;
213         }
214
215         SendText("HTTP/1.1 200 OK");
216     SendText("Server: ROSHTTPD");
217     SendText("MIME-version: 1.0");
218     SendText("Content-Type: text/plain");
219     SendText("Accept-Ranges: bytes");
220     strcpy(str, "Content-Length: ");
221     _itoa(nTotalBytes.u.Low, str2, 10);
222     strcat(str, str2);
223     SendText(str);
224     SendText("");
225         // Read and transmit file
226         nTotalRead = 0;
227         nFileSize = nTotalBytes.Big;
228         bStop = FALSE;
229
230         fd_set wfds;
231         FD_ZERO(&wfds);
232         FD_SET(Socket, &wfds);
233         do {
234                 MessageLoop();
235
236                 if (nTotalRead + nBufferSize < nFileSize)
237                         nBytesToRead = nBufferSize;
238                 else nBytesToRead = nFileSize - nTotalRead;
239
240                 bStatus = ReadFile(hFile, lpsBuffer, nBytesToRead, &nBytesRead, NULL);
241                 if (bStatus) {
242                         select(0, NULL, &wfds, NULL, NULL);
243                         bStatus = (Transmit(lpsBuffer, nBytesRead) == (INT)nBytesRead);
244                         nTotalRead += nBytesRead;
245                 }
246     } while ((!bStop) && (bStatus) && (nTotalRead < nFileSize));
247
248         if (bStatus)
249                 SendText("");
250         else
251                 // We can't send an error message here as we are in the process of sending a file.
252                 // We have to terminate the connection instead
253                 Close();
254         
255         // Free allocated memory
256         free(lpsBuffer);
257
258         // Close file
259     CloseHandle(hFile);
260 }
261
262 // Report something to client
263 VOID CHttpClient::Report(LPSTR lpsCode, LPSTR lpsStr)
264 {
265     CHAR sTmp[128];
266     CHAR sTmp2[16];
267         
268     strcpy(sTmp, "HTTP/1.1 ");
269     strcat(sTmp, lpsCode);
270     SendText(sTmp);
271     SendText("Server: ROSHTTPD");
272     SendText("MIME-version: 1.0");
273     SendText("Content-Type: text/html");
274     SendText("Accept-Ranges: bytes");
275     strcpy(sTmp, "Content-Length: ");
276     if (lpsStr != NULL) {
277         _itoa(strlen(lpsStr), sTmp2, 10);
278         strcat(sTmp,  sTmp2);
279     } else
280         strcat(sTmp, "0");
281     SendText(sTmp);
282     SendText("");
283     if (lpsStr != NULL)
284         SendText(lpsStr);
285     SendText("");
286 }
287
288 // OnRead event handler
289 VOID CHttpClient::OnRead()
290 {
291         LONG nCount;
292
293         nCount = Receive((LPSTR) &Parser.sBuffer[Parser.nHead],
294         sizeof(Parser.sBuffer) - Parser.nHead);
295
296     Parser.nHead += nCount;
297         if (Parser.nHead >= sizeof(Parser.sBuffer))
298                 Parser.nHead = 0;
299
300     if (Parser.Complete()) {
301                 ProcessRequest();
302     }
303
304         if (Parser.bUnknownMethod) {
305                 // Method Not Allowed
306                 Report("405 Method Not Allowed", HttpMsg405);
307                 // Terminate connection
308                 Close();
309         }
310 }
311 /*
312 // OnWrite event handler
313 VOID CHttpClient::OnWrite()
314 {
315         DWORD nBytesToRead;
316         DWORD nBytesRead;
317
318         OutputDebugString(_T("Can write\n"));
319
320         if (bSendingFile) {
321                 if (nTotalRead + nBufferSize < nFileSize)
322                         nBytesToRead = nBufferSize;
323                 else nBytesToRead = nFileSize - nTotalRead;
324
325                 bError = ReadFile(hFile, Buffer, nBytesToRead, &nBytesRead, NULL);
326                 if (!bError) {
327                         Transmit(Buffer, nBytesRead);
328                         nTotalRead += nBytesRead;
329                 }
330         }
331 }
332 */
333 // OnClose event handler
334 VOID CHttpClient::OnClose()
335 {
336         // Stop sending file if we are doing that now
337         bStop = TRUE;
338 }
339
340
341 // ************************ CHttpClientThread ************************
342
343 // Constructor with client socket as starter value
344 CHttpClientThread::CHttpClientThread(LPCServerClientSocket lpSocket)
345 {
346         ClientSocket = lpSocket;
347 }
348
349 // Execute client thread code
350 VOID CHttpClientThread::Execute()
351 {
352     MSG Msg;
353
354         while (!Terminated()) {
355                 ((  CHttpClient *) ClientSocket)->MessageLoop();
356         if (PeekMessage(&Msg, 0, 0, 0, PM_REMOVE) != 0) {
357             switch (Msg.message) {
358                 case HTTPD_START: {
359                                         // TODO: Start thread
360                     break;
361                 }
362                 case HTTPD_STOP: {
363                                         // TODO: Stop thread
364                     break;
365                 }
366                 default:
367                     DispatchMessage(&Msg);
368             }
369
370         }
371     }
372
373         if (ClientSocket != NULL) {
374                 delete ClientSocket;
375                 ClientSocket = NULL;
376         }
377 }
378
379
380 // *************************** CHttpDaemon ***************************
381
382 // Default constructor
383 CHttpDaemon::CHttpDaemon()
384 {
385     State = hsStopped;
386         Start();
387 }
388
389 // Default destructor
390 CHttpDaemon::~CHttpDaemon()
391 {
392         if (State==hsRunning)
393                 Stop();
394 }
395
396 // Return daemon state
397 HTTPdState CHttpDaemon::GetState() const
398 {
399         return State;
400 }
401
402 // Start HTTP daemon
403 BOOL CHttpDaemon::Start()
404 {
405         assert(State==hsStopped);
406
407         SetPort(pConfiguration->GetPort());
408
409         Open();
410         
411         State = hsRunning;
412         
413     return TRUE;
414 }
415
416 // Stop HTTP daemon
417 BOOL CHttpDaemon::Stop()
418 {
419         assert(State==hsRunning);
420
421         Close();
422
423         State = hsStopped;
424
425     return TRUE;
426 }
427
428 // OnGetSocket event handler
429 LPCServerClientSocket CHttpDaemon::OnGetSocket(LPCServerSocket lpServerSocket)
430 {
431         return new CHttpClient(lpServerSocket);
432 }
433
434 // OnGetThread event handler
435 LPCServerClientThread CHttpDaemon::OnGetThread(LPCServerClientSocket lpSocket)
436 {
437         return new CHttpClientThread(lpSocket);
438 }
439
440 // OnAccept event handler
441 VOID CHttpDaemon::OnAccept(LPCServerClientThread lpThread)
442 {
443 }
444
445
446 // ************************ CHttpDaemonThread ************************
447
448 // Execute daemon thread code
449 VOID CHttpDaemonThread::Execute()
450 {
451         MSG Msg;
452         
453         try {
454                 Daemon = NULL;
455                 Daemon = new CHttpDaemon;
456
457                 while (!Terminated()) {
458                         Daemon->MessageLoop();
459                         if (PeekMessage(&Msg, 0, 0, 0, PM_REMOVE) != 0) {
460                     switch (Msg.message) {
461                             case HTTPD_START: {
462                                     if (Daemon->GetState() == hsStopped)
463                                             Daemon->Start();
464                                             break;
465                                         }
466                                         case HTTPD_STOP: {
467                             if (Daemon->GetState() == hsRunning)
468                                                         Daemon->Stop();
469                                                 break;
470                                         }
471                                         case HTTPD_SUSPEND: {
472                                                 if (Daemon->GetState() == hsRunning){}
473                                                         // FIXME: Suspend service
474                                                 break;
475                                         }
476                                         case HTTPD_RESUME: {
477                                                 if (Daemon->GetState() != hsSuspended){}
478                                                         // FIXME: Resume service
479                                                 break;
480                                         }
481                                         default:
482                         DispatchMessage(&Msg);
483                                 }
484                         }
485             }
486                 delete Daemon;
487         } catch (ESocket e) {
488                 ReportErrorStr(e.what());
489         } catch (bad_alloc e) {
490                 ReportErrorStr(TS("Insufficient resources."));
491         }
492 }