:pserver:cvsanon@mok.lvcm.com:/CVS/ReactOS reactos
[reactos.git] / apps / utils / net / roshttpd / http.cpp
1 /*
2  * COPYRIGHT:   See COPYING in the top level directory
3  * PROJECT:     ReactOS HTTP Daemon
4  * FILE:        http.cpp
5  * PURPOSE:     HTTP 1.1 parser engine
6  * PROGRAMMERS: Casper S. Hornstrup (chorns@users.sourceforge.net)
7  * REVISIONS:
8  *   CSH  01/09/2000 Created
9  * TODO:        - Implement message-body
10  *              - Implement more generel-header entries
11  *              - Implement more request-header entries
12  *              - Implement more entity-header entries
13  */
14 #include <debug.h>
15 #include <iostream.h>
16 #include <string.h>
17 #include <http.h>
18
19 CHAR MethodTable[NUMMETHODS][8] = {"OPTIONS", "GET", "HEAD", "POST", "PUT", 
20     "DELETE", "TRACE"};
21
22 CHAR GenerelTable[NUMGENERELS][18] = {"Cache-Control", "Connection", "Date", "Pragma", 
23     "Transfer-Encoding", "Upgrade", "Via"};
24
25 CHAR RequestTable[NUMREQUESTS][20] = {"Accept", "Accept-Charset", "Accept-Encoding",
26     "Accept-Language", "Authorization", "From", "Host", "If-Modified-Since", "If-Match",
27     "If-None-Match", "If-Range", "If-Unmodified-Since", "Max-Forwards",
28     "Proxy-Authorization", "Range", "Referer", "User-Agent"};
29
30 CHAR EntityTable[NUMENTITIES][17] = {"Allow", "Content-Base", "Content-Encoding",
31     "Content-Language", "Content-Length", "Content-Location", "Content-MD5",
32     "Content-Range", "Content-Type", "ETag", "Expires", "Last-Modified"};
33
34 // *************************** CHttpParser ***************************
35
36 // Default constructor
37 CHttpParser::CHttpParser()
38 {
39     nHead = 0;
40     nTail = 0;
41 }
42
43 // Default destructor
44 CHttpParser::~CHttpParser()
45 {
46 }
47
48 // Returns TRUE if a complete HTTP message is in buffer
49 BOOL CHttpParser::Complete()
50 {
51     UINT nTmp;
52
53     /*DPRINT("--1:-%d---\n", sBuffer[nHead-2]);
54     DPRINT("--2:-%d---\n", sBuffer[nHead-1]);
55
56     sBuffer[nHead] = '!';
57     sBuffer[nHead+1] = 0;
58     DPRINT("Examining buffer: (Head: %d, Tail: %d)\n", nHead, nTail);
59     DPRINT("%s\n", (LPSTR)&sBuffer[nTail]);*/
60
61     nTmp = nTail;
62     if (!Parse()) {
63         if (!bUnknownMethod)
64             nTail = nTmp;
65         return FALSE;
66     } else
67         return TRUE;
68 }
69
70
71 // Read a character from buffer
72 BOOL CHttpParser::ReadChar(LPSTR lpsStr)
73 {
74     if (nTail <= nHead) {
75         if (nTail != nHead) {
76             lpsStr[0] = sBuffer[nTail];
77             nTail++;
78             return TRUE;
79         } else {
80             lpsStr[0] = 0;
81             return FALSE;
82         }
83     } else {
84         if (nTail == sizeof(sBuffer))
85             nTail = 0;
86         if (nTail != nHead) {
87             lpsStr[0] = sBuffer[nTail];
88             nTail++;
89             return TRUE;
90         } else {
91             lpsStr[0] = 0;
92             return FALSE;
93         }
94     }
95 }
96
97 // Peek at a character in the buffer
98 BOOL CHttpParser::PeekChar(LPSTR lpsStr)
99 {
100     UINT nFakeTail;
101     
102     if (nTail == sizeof(sBuffer))
103         nFakeTail = 0;
104     else
105         nFakeTail = nTail;
106     if (nFakeTail != nHead) {
107         lpsStr[0] = sBuffer[nFakeTail];
108         return TRUE;
109     } else {
110         lpsStr[0] = 0;
111         return FALSE;
112     }
113 }
114
115 // Read a string from buffer. Only A-Z, a-z, 0-9 and '-' are valid characters
116 BOOL CHttpParser::ReadString(LPSTR lpsStr, UINT nLength)
117 {
118     UINT i = 0;
119     CHAR sTmp;
120     
121     while (PeekChar(&sTmp)) {
122         if (((sTmp >= 'A') && (sTmp <= 'Z')) || ((sTmp >= 'a') && (sTmp <= 'z')) ||
123             ((sTmp >= '0') && (sTmp <= '9')) || (sTmp == '-')) { 
124             if (i >= (nLength - 1)) {
125                 lpsStr[0] = 0;
126                 return FALSE;
127             }
128             ReadChar(&sTmp);
129             lpsStr[i] = sTmp;
130             i++;
131         } else {
132             lpsStr[i] = 0;
133             return TRUE;
134         }
135     }
136     lpsStr[0] = 0;
137     return FALSE;
138 }
139
140 // Read a string from buffer. Stop if SP or CR is found or when there are no more 
141 // characters
142 BOOL CHttpParser::ReadSpecial(LPSTR lpsStr, UINT nLength)
143 {
144     UINT i = 0;
145     CHAR sTmp;
146  
147     while (PeekChar(&sTmp) && (sTmp != ' ') && (sTmp != 13)) {
148         if (i >= (nLength - 1)) {
149             lpsStr[nLength - 1] = 0;
150             return FALSE;
151         }
152         ReadChar(&sTmp);
153         lpsStr[i] = sTmp;
154         i++;
155     }
156     lpsStr[i] = 0;
157     return TRUE;
158 }
159
160 // Skip until "sCh" is found
161 VOID CHttpParser::Skip(CHAR sCh)
162 {
163     CHAR sTmp;
164
165     while (PeekChar(&sTmp) && (sTmp != sCh))
166         ReadChar(&sTmp);
167 }
168
169 // Return TRUE if sCh is the next character
170 BOOL CHttpParser::Expect(CHAR sCh)
171 {
172     CHAR sTmp;
173
174     if (PeekChar(&sTmp)) {
175         if (sTmp == sCh) {
176             ReadChar(&sTmp);
177             return TRUE;
178         }
179     }
180     return FALSE;
181 }
182
183 // Return TRUE if CRLF are the next characters
184 BOOL CHttpParser::ExpectCRLF()
185 {
186     return (Expect(13) && Expect(10));
187 }
188
189 // Request = RequestLine | *( GenerelHeader | RequestHeader | EntityHeader ) 
190 //           CRLF [ MessageBody ]
191 BOOL CHttpParser::Parse()
192 {
193     BOOL bStatus;
194
195
196     CHAR ch;
197
198     if (RequestLine()) {
199         do {
200             if (!ReadString(sHeader, sizeof(sHeader)))
201                 break;
202             bStatus = (GenerelHeader());
203             bStatus = (RequestHeader() || bStatus);
204             bStatus = (EntityHeader() || bStatus);
205         } while (bStatus);
206         // CRLF
207         if (!ExpectCRLF())
208             return FALSE;
209         MessageBody();
210         return TRUE;
211     }
212     return FALSE;
213 }
214
215 // RequestLine = Method SP RequestURI SP HTTP-Version CRLF
216 BOOL CHttpParser::RequestLine()
217 {
218     CHAR sCh;
219     UINT i;
220
221     bUnknownMethod = FALSE;
222
223     // RFC 2068 states that servers SHOULD ignore any empty nine(s) received where a 
224     // Request-Line is expected
225     while (PeekChar(&sCh) && ((sCh == 13) || (sCh == 10)));
226     
227     if (!ReadString(sMethod, sizeof(sMethod)))
228         return FALSE;
229
230     for (i = 0; i < NUMMETHODS; i++) {
231         if (strcmp(MethodTable[i], sMethod) == 0) {
232             nMethodNo = i;
233             if (!Expect(' '))
234                 return FALSE;
235             // URI (ie. host/directory/resource)
236             if (!ReadSpecial(sUri, sizeof(sUri)))
237                 return FALSE;
238             if (!Expect(' '))
239                 return FALSE;
240             // HTTP version (eg. HTTP/1.1)
241             if (!ReadSpecial(sVersion, sizeof(sVersion)))
242                 return FALSE;
243             // CRLF
244             if (!ExpectCRLF())
245                 return FALSE;
246
247             return TRUE;
248         }
249     }
250     bUnknownMethod = TRUE;
251     return FALSE;
252 }
253
254 // GenerelHeader = Cache-Control | Connection | Date | Pragma | Transfer-Encoding | 
255 //                 Upgrade | Via
256 BOOL CHttpParser::GenerelHeader()
257 {
258     INT i;
259
260     for (i = 0; i < NUMGENERELS; i++) {
261         if (strcmp(GenerelTable[i], sHeader) == 0) {
262             switch (i) {
263                 case 1: {
264                     //Connection
265                     Expect(':');
266                     Expect(' ');
267                     Skip(13);
268                     ExpectCRLF();
269                     break;
270                 }
271                 default: {
272                     Expect(':');
273                     Expect(' ');
274                     Skip(13);
275                     ExpectCRLF();
276                 }
277             }
278             return TRUE;
279         }
280     }
281     return FALSE;
282 }
283
284 // RequestHeader = Accept | Accept-Charset | Accept-Encoding | Accept-Language |
285 //                 Authorization | From | Host | If-Modified-Since | If-Match |
286 //                 If-None-Match | If-Range | If-Unmodified-Since | Max-Forwards |
287 //                 Proxy-Authorization | Range | Referer | User-Agent
288 BOOL CHttpParser::RequestHeader()
289 {
290     INT i;
291
292     for (i = 0; i < NUMREQUESTS; i++) {
293         if (strcmp(RequestTable[i], sHeader) == 0) {
294             switch (i) {
295                 case 0: {
296                     //Accept
297                     Expect(':');
298                     Expect(' ');
299                     Skip(13);
300                     ExpectCRLF();
301                     break;
302                 }
303                 case 2: {
304                     //Accept-Encoding
305                     Expect(':');
306                     Expect(' ');
307                     Skip(13);
308                     ExpectCRLF();
309                     break;
310                 }
311                 case 3: {
312                     //Accept-Language
313                     Expect(':');
314                     Expect(' ');
315                     Skip(13);
316                     ExpectCRLF();
317                     break;
318                 }
319                 case 6: {
320                     //Host
321                     Expect(':');
322                     Expect(' ');
323                     Skip(13);
324                     ExpectCRLF();
325                     break;
326                 }
327                 case 16: {
328                     //User-Agent
329                     Expect(':');
330                     Expect(' ');
331                     Skip(13);
332                     ExpectCRLF();
333                     break;
334                 }
335                 default: {
336                     Expect(':');
337                     Expect(' ');
338                     Skip(13);
339                     ExpectCRLF();
340                     return TRUE;
341                 }
342             }
343             return TRUE;
344         }
345     }
346     return FALSE;
347 }
348
349 // EntityHeader = Allow | Content-Base | Content-Encoding | Content-Language |
350 //                Content-Length | Content-Location | Content-MD5 |
351 //                Content-Range | Content-Type | ETag | Expires |
352 //                Last-Modified | extension-header
353 BOOL CHttpParser::EntityHeader()
354 {
355     INT i;
356
357     for (i = 0; i < NUMENTITIES; i++) {
358         if (strcmp(EntityTable[i], sHeader) == 0) {
359             switch (i) {
360                 case 0: 
361                 default: {
362                     //cout << "<Entity-Header>: #" << i << endl; 
363                     Expect(':');
364                     Expect(' ');
365                     Skip(13);
366                     ExpectCRLF();
367                     return TRUE;
368                 }
369             }
370             return FALSE;
371         }
372     }
373     return FALSE;
374 }
375
376 // MessageBody = *OCTET
377 BOOL CHttpParser::MessageBody()
378 {
379     return FALSE;
380 }