- converted 1st stage setup stub from message box style to property sheet style
[reactos.git] / rosapps / applications / 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(LPCSTR 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(TS("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 }