1: /* Copyright 2009,2010 Ryan Dahl <ry@tinyclouds.org>
2: *
3: * Permission is hereby granted, free of charge, to any person obtaining a copy
4: * of this software and associated documentation files (the "Software"), to
5: * deal in the Software without restriction, including without limitation the
6: * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7: * sell copies of the Software, and to permit persons to whom the Software is
8: * furnished to do so, subject to the following conditions:
9: *
10: * The above copyright notice and this permission notice shall be included in
11: * all copies or substantial portions of the Software.
12: *
13: * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14: * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15: * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16: * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17: * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18: * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
19: * IN THE SOFTWARE.
20: */
21: #include <assert.h>
22: #include <stddef.h>
23: #include "php_http_parser.h"
24:
25:
26: #ifndef MIN
27: # define MIN(a,b) ((a) < (b) ? (a) : (b))
28: #endif
29:
30:
31: #define CALLBACK2(FOR) \
32: do { \
33: if (settings->on_##FOR) { \
34: if (0 != settings->on_##FOR(parser)) return (p - data); \
35: } \
36: } while (0)
37:
38:
39: #define MARK(FOR) \
40: do { \
41: FOR##_mark = p; \
42: } while (0)
43:
44: #define CALLBACK_NOCLEAR(FOR) \
45: do { \
46: if (FOR##_mark) { \
47: if (settings->on_##FOR) { \
48: if (0 != settings->on_##FOR(parser, \
49: FOR##_mark, \
50: p - FOR##_mark)) \
51: { \
52: return (p - data); \
53: } \
54: } \
55: } \
56: } while (0)
57:
58: #ifdef PHP_WIN32
59: # undef CALLBACK
60: #endif
61: #define CALLBACK(FOR) \
62: do { \
63: CALLBACK_NOCLEAR(FOR); \
64: FOR##_mark = NULL; \
65: } while (0)
66:
67:
68: #define PROXY_CONNECTION "proxy-connection"
69: #define CONNECTION "connection"
70: #define CONTENT_LENGTH "content-length"
71: #define TRANSFER_ENCODING "transfer-encoding"
72: #define UPGRADE "upgrade"
73: #define CHUNKED "chunked"
74: #define KEEP_ALIVE "keep-alive"
75: #define CLOSE "close"
76:
77:
78: static const char *method_strings[] =
79: { "DELETE"
80: , "GET"
81: , "HEAD"
82: , "POST"
83: , "PUT"
84: , "CONNECT"
85: , "OPTIONS"
86: , "TRACE"
87: , "COPY"
88: , "LOCK"
89: , "MKCOL"
90: , "MOVE"
91: , "PROPFIND"
92: , "PROPPATCH"
93: , "UNLOCK"
94: , "REPORT"
95: , "MKACTIVITY"
96: , "CHECKOUT"
97: , "MERGE"
98: , "M-SEARCH"
99: , "NOTIFY"
100: , "SUBSCRIBE"
101: , "UNSUBSCRIBE"
102: };
103:
104:
105: /* Tokens as defined by rfc 2616. Also lowercases them.
106: * token = 1*<any CHAR except CTLs or separators>
107: * separators = "(" | ")" | "<" | ">" | "@"
108: * | "," | ";" | ":" | "\" | <">
109: * | "/" | "[" | "]" | "?" | "="
110: * | "{" | "}" | SP | HT
111: */
112: static const char tokens[256] = {
113: /* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */
114: 0, 0, 0, 0, 0, 0, 0, 0,
115: /* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */
116: 0, 0, 0, 0, 0, 0, 0, 0,
117: /* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */
118: 0, 0, 0, 0, 0, 0, 0, 0,
119: /* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */
120: 0, 0, 0, 0, 0, 0, 0, 0,
121: /* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */
122: ' ', '!', '"', '#', '$', '%', '&', '\'',
123: /* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */
124: 0, 0, '*', '+', 0, '-', '.', '/',
125: /* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */
126: '0', '1', '2', '3', '4', '5', '6', '7',
127: /* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */
128: '8', '9', 0, 0, 0, 0, 0, 0,
129: /* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */
130: 0, 'a', 'b', 'c', 'd', 'e', 'f', 'g',
131: /* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */
132: 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
133: /* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */
134: 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
135: /* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */
136: 'x', 'y', 'z', 0, 0, 0, '^', '_',
137: /* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */
138: '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
139: /* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */
140: 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
141: /* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */
142: 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
143: /* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */
144: 'x', 'y', 'z', 0, '|', '}', '~', 0 };
145:
146:
147: static const int8_t unhex[256] =
148: {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
149: ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
150: ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
151: , 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1
152: ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1
153: ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
154: ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1
155: ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
156: };
157:
158:
159: static const uint8_t normal_url_char[256] = {
160: /* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */
161: 0, 0, 0, 0, 0, 0, 0, 0,
162: /* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */
163: 0, 0, 0, 0, 0, 0, 0, 0,
164: /* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */
165: 0, 0, 0, 0, 0, 0, 0, 0,
166: /* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */
167: 0, 0, 0, 0, 0, 0, 0, 0,
168: /* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */
169: 0, 1, 1, 0, 1, 1, 1, 1,
170: /* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */
171: 1, 1, 1, 1, 1, 1, 1, 1,
172: /* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */
173: 1, 1, 1, 1, 1, 1, 1, 1,
174: /* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */
175: 1, 1, 1, 1, 1, 1, 1, 0,
176: /* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */
177: 1, 1, 1, 1, 1, 1, 1, 1,
178: /* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */
179: 1, 1, 1, 1, 1, 1, 1, 1,
180: /* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */
181: 1, 1, 1, 1, 1, 1, 1, 1,
182: /* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */
183: 1, 1, 1, 1, 1, 1, 1, 1,
184: /* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */
185: 1, 1, 1, 1, 1, 1, 1, 1,
186: /* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */
187: 1, 1, 1, 1, 1, 1, 1, 1,
188: /* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */
189: 1, 1, 1, 1, 1, 1, 1, 1,
190: /* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */
191: 1, 1, 1, 1, 1, 1, 1, 0 };
192:
193:
194: enum state
195: { s_dead = 1 /* important that this is > 0 */
196:
197: , s_start_req_or_res
198: , s_res_or_resp_H
199: , s_start_res
200: , s_res_H
201: , s_res_HT
202: , s_res_HTT
203: , s_res_HTTP
204: , s_res_first_http_major
205: , s_res_http_major
206: , s_res_first_http_minor
207: , s_res_http_minor
208: , s_res_first_status_code
209: , s_res_status_code
210: , s_res_status
211: , s_res_line_almost_done
212:
213: , s_start_req
214:
215: , s_req_method
216: , s_req_spaces_before_url
217: , s_req_schema
218: , s_req_schema_slash
219: , s_req_schema_slash_slash
220: , s_req_host
221: , s_req_port
222: , s_req_path
223: , s_req_query_string_start
224: , s_req_query_string
225: , s_req_fragment_start
226: , s_req_fragment
227: , s_req_http_start
228: , s_req_http_H
229: , s_req_http_HT
230: , s_req_http_HTT
231: , s_req_http_HTTP
232: , s_req_first_http_major
233: , s_req_http_major
234: , s_req_first_http_minor
235: , s_req_http_minor
236: , s_req_line_almost_done
237:
238: , s_header_field_start
239: , s_header_field
240: , s_header_value_start
241: , s_header_value
242:
243: , s_header_almost_done
244:
245: , s_headers_almost_done
246: /* Important: 's_headers_almost_done' must be the last 'header' state. All
247: * states beyond this must be 'body' states. It is used for overflow
248: * checking. See the PARSING_HEADER() macro.
249: */
250: , s_chunk_size_start
251: , s_chunk_size
252: , s_chunk_size_almost_done
253: , s_chunk_parameters
254: , s_chunk_data
255: , s_chunk_data_almost_done
256: , s_chunk_data_done
257:
258: , s_body_identity
259: , s_body_identity_eof
260: };
261:
262:
263: #define PARSING_HEADER(state) (state <= s_headers_almost_done && 0 == (parser->flags & F_TRAILING))
264:
265:
266: enum header_states
267: { h_general = 0
268: , h_C
269: , h_CO
270: , h_CON
271:
272: , h_matching_connection
273: , h_matching_proxy_connection
274: , h_matching_content_length
275: , h_matching_transfer_encoding
276: , h_matching_upgrade
277:
278: , h_connection
279: , h_content_length
280: , h_transfer_encoding
281: , h_upgrade
282:
283: , h_matching_transfer_encoding_chunked
284: , h_matching_connection_keep_alive
285: , h_matching_connection_close
286:
287: , h_transfer_encoding_chunked
288: , h_connection_keep_alive
289: , h_connection_close
290: };
291:
292:
293: enum flags
294: { F_CHUNKED = 1 << 0
295: , F_CONNECTION_KEEP_ALIVE = 1 << 1
296: , F_CONNECTION_CLOSE = 1 << 2
297: , F_TRAILING = 1 << 3
298: , F_UPGRADE = 1 << 4
299: , F_SKIPBODY = 1 << 5
300: };
301:
302:
303: #define CR '\r'
304: #define LF '\n'
305: #define LOWER(c) (unsigned char)(c | 0x20)
306: #define TOKEN(c) tokens[(unsigned char)c]
307:
308:
309: #define start_state (parser->type == PHP_HTTP_REQUEST ? s_start_req : s_start_res)
310:
311:
312: #if HTTP_PARSER_STRICT
313: # define STRICT_CHECK(cond) if (cond) goto error
314: # define NEW_MESSAGE() (http_should_keep_alive(parser) ? start_state : s_dead)
315: #else
316: # define STRICT_CHECK(cond)
317: # define NEW_MESSAGE() start_state
318: #endif
319:
320:
321: size_t php_http_parser_execute (php_http_parser *parser,
322: const php_http_parser_settings *settings,
323: const char *data,
324: size_t len)
325: {
326: char c, ch;
327: const char *p = data, *pe;
328: size_t to_read;
329:
330: enum state state = (enum state) parser->state;
331: enum header_states header_state = (enum header_states) parser->header_state;
332: uint32_t index = parser->index;
333: uint32_t nread = parser->nread;
334:
335: /* technically we could combine all of these (except for url_mark) into one
336: variable, saving stack space, but it seems more clear to have them
337: separated. */
338: const char *header_field_mark = 0;
339: const char *header_value_mark = 0;
340: const char *fragment_mark = 0;
341: const char *query_string_mark = 0;
342: const char *path_mark = 0;
343: const char *url_mark = 0;
344:
345: if (len == 0) {
346: if (state == s_body_identity_eof) {
347: CALLBACK2(message_complete);
348: }
349: return 0;
350: }
351:
352: if (state == s_header_field)
353: header_field_mark = data;
354: if (state == s_header_value)
355: header_value_mark = data;
356: if (state == s_req_fragment)
357: fragment_mark = data;
358: if (state == s_req_query_string)
359: query_string_mark = data;
360: if (state == s_req_path)
361: path_mark = data;
362: if (state == s_req_path || state == s_req_schema || state == s_req_schema_slash
363: || state == s_req_schema_slash_slash || state == s_req_port
364: || state == s_req_query_string_start || state == s_req_query_string
365: || state == s_req_host
366: || state == s_req_fragment_start || state == s_req_fragment)
367: url_mark = data;
368:
369: for (p=data, pe=data+len; p != pe; p++) {
370: ch = *p;
371:
372: if (PARSING_HEADER(state)) {
373: ++nread;
374: /* Buffer overflow attack */
375: if (nread > PHP_HTTP_MAX_HEADER_SIZE) goto error;
376: }
377:
378: switch (state) {
379:
380: case s_dead:
381: /* this state is used after a 'Connection: close' message
382: * the parser will error out if it reads another message
383: */
384: goto error;
385:
386: case s_start_req_or_res:
387: {
388: if (ch == CR || ch == LF)
389: break;
390: parser->flags = 0;
391: parser->content_length = -1;
392:
393: CALLBACK2(message_begin);
394:
395: if (ch == 'H')
396: state = s_res_or_resp_H;
397: else {
398: parser->type = PHP_HTTP_REQUEST;
399: goto start_req_method_assign;
400: }
401: break;
402: }
403:
404: case s_res_or_resp_H:
405: if (ch == 'T') {
406: parser->type = PHP_HTTP_RESPONSE;
407: state = s_res_HT;
408: } else {
409: if (ch != 'E') goto error;
410: parser->type = PHP_HTTP_REQUEST;
411: parser->method = PHP_HTTP_HEAD;
412: index = 2;
413: state = s_req_method;
414: }
415: break;
416:
417: case s_start_res:
418: {
419: parser->flags = 0;
420: parser->content_length = -1;
421:
422: CALLBACK2(message_begin);
423:
424: switch (ch) {
425: case 'H':
426: state = s_res_H;
427: break;
428:
429: case CR:
430: case LF:
431: break;
432:
433: default:
434: goto error;
435: }
436: break;
437: }
438:
439: case s_res_H:
440: STRICT_CHECK(ch != 'T');
441: state = s_res_HT;
442: break;
443:
444: case s_res_HT:
445: STRICT_CHECK(ch != 'T');
446: state = s_res_HTT;
447: break;
448:
449: case s_res_HTT:
450: STRICT_CHECK(ch != 'P');
451: state = s_res_HTTP;
452: break;
453:
454: case s_res_HTTP:
455: STRICT_CHECK(ch != '/');
456: state = s_res_first_http_major;
457: break;
458:
459: case s_res_first_http_major:
460: if (ch < '1' || ch > '9') goto error;
461: parser->http_major = ch - '0';
462: state = s_res_http_major;
463: break;
464:
465: /* major HTTP version or dot */
466: case s_res_http_major:
467: {
468: if (ch == '.') {
469: state = s_res_first_http_minor;
470: break;
471: }
472:
473: if (ch < '0' || ch > '9') goto error;
474:
475: parser->http_major *= 10;
476: parser->http_major += ch - '0';
477:
478: if (parser->http_major > 999) goto error;
479: break;
480: }
481:
482: /* first digit of minor HTTP version */
483: case s_res_first_http_minor:
484: if (ch < '0' || ch > '9') goto error;
485: parser->http_minor = ch - '0';
486: state = s_res_http_minor;
487: break;
488:
489: /* minor HTTP version or end of request line */
490: case s_res_http_minor:
491: {
492: if (ch == ' ') {
493: state = s_res_first_status_code;
494: break;
495: }
496:
497: if (ch < '0' || ch > '9') goto error;
498:
499: parser->http_minor *= 10;
500: parser->http_minor += ch - '0';
501:
502: if (parser->http_minor > 999) goto error;
503: break;
504: }
505:
506: case s_res_first_status_code:
507: {
508: if (ch < '0' || ch > '9') {
509: if (ch == ' ') {
510: break;
511: }
512: goto error;
513: }
514: parser->status_code = ch - '0';
515: state = s_res_status_code;
516: break;
517: }
518:
519: case s_res_status_code:
520: {
521: if (ch < '0' || ch > '9') {
522: switch (ch) {
523: case ' ':
524: state = s_res_status;
525: break;
526: case CR:
527: state = s_res_line_almost_done;
528: break;
529: case LF:
530: state = s_header_field_start;
531: break;
532: default:
533: goto error;
534: }
535: break;
536: }
537:
538: parser->status_code *= 10;
539: parser->status_code += ch - '0';
540:
541: if (parser->status_code > 999) goto error;
542: break;
543: }
544:
545: case s_res_status:
546: /* the human readable status. e.g. "NOT FOUND"
547: * we are not humans so just ignore this */
548: if (ch == CR) {
549: state = s_res_line_almost_done;
550: break;
551: }
552:
553: if (ch == LF) {
554: state = s_header_field_start;
555: break;
556: }
557: break;
558:
559: case s_res_line_almost_done:
560: STRICT_CHECK(ch != LF);
561: state = s_header_field_start;
562: break;
563:
564: case s_start_req:
565: {
566: if (ch == CR || ch == LF)
567: break;
568: parser->flags = 0;
569: parser->content_length = -1;
570:
571: CALLBACK2(message_begin);
572:
573: if (ch < 'A' || 'Z' < ch) goto error;
574:
575: start_req_method_assign:
576: parser->method = (enum php_http_method) 0;
577: index = 1;
578: switch (ch) {
579: case 'C': parser->method = PHP_HTTP_CONNECT; /* or COPY, CHECKOUT */ break;
580: case 'D': parser->method = PHP_HTTP_DELETE; break;
581: case 'G': parser->method = PHP_HTTP_GET; break;
582: case 'H': parser->method = PHP_HTTP_HEAD; break;
583: case 'L': parser->method = PHP_HTTP_LOCK; break;
584: case 'M': parser->method = PHP_HTTP_MKCOL; /* or MOVE, MKACTIVITY, MERGE, M-SEARCH */ break;
585: case 'N': parser->method = PHP_HTTP_NOTIFY; break;
586: case 'O': parser->method = PHP_HTTP_OPTIONS; break;
587: case 'P': parser->method = PHP_HTTP_POST; /* or PROPFIND or PROPPATCH or PUT */ break;
588: case 'R': parser->method = PHP_HTTP_REPORT; break;
589: case 'S': parser->method = PHP_HTTP_SUBSCRIBE; break;
590: case 'T': parser->method = PHP_HTTP_TRACE; break;
591: case 'U': parser->method = PHP_HTTP_UNLOCK; /* or UNSUBSCRIBE */ break;
592: default: goto error;
593: }
594: state = s_req_method;
595: break;
596: }
597:
598: case s_req_method:
599: {
600: const char *matcher;
601: if (ch == '\0')
602: goto error;
603:
604: matcher = method_strings[parser->method];
605: if (ch == ' ' && matcher[index] == '\0') {
606: state = s_req_spaces_before_url;
607: } else if (ch == matcher[index]) {
608: ; /* nada */
609: } else if (parser->method == PHP_HTTP_CONNECT) {
610: if (index == 1 && ch == 'H') {
611: parser->method = PHP_HTTP_CHECKOUT;
612: } else if (index == 2 && ch == 'P') {
613: parser->method = PHP_HTTP_COPY;
614: }
615: } else if (parser->method == PHP_HTTP_MKCOL) {
616: if (index == 1 && ch == 'O') {
617: parser->method = PHP_HTTP_MOVE;
618: } else if (index == 1 && ch == 'E') {
619: parser->method = PHP_HTTP_MERGE;
620: } else if (index == 1 && ch == '-') {
621: parser->method = PHP_HTTP_MSEARCH;
622: } else if (index == 2 && ch == 'A') {
623: parser->method = PHP_HTTP_MKACTIVITY;
624: }
625: } else if (index == 1 && parser->method == PHP_HTTP_POST && ch == 'R') {
626: parser->method = PHP_HTTP_PROPFIND; /* or HTTP_PROPPATCH */
627: } else if (index == 1 && parser->method == PHP_HTTP_POST && ch == 'U') {
628: parser->method = PHP_HTTP_PUT;
629: } else if (index == 2 && parser->method == PHP_HTTP_UNLOCK && ch == 'S') {
630: parser->method = PHP_HTTP_UNSUBSCRIBE;
631: } else if (index == 4 && parser->method == PHP_HTTP_PROPFIND && ch == 'P') {
632: parser->method = PHP_HTTP_PROPPATCH;
633: } else {
634: goto error;
635: }
636:
637: ++index;
638: break;
639: }
640: case s_req_spaces_before_url:
641: {
642: if (ch == ' ') break;
643:
644: if (ch == '/' || ch == '*') {
645: MARK(url);
646: MARK(path);
647: state = s_req_path;
648: break;
649: }
650:
651: c = LOWER(ch);
652:
653: if (c >= 'a' && c <= 'z') {
654: MARK(url);
655: state = s_req_schema;
656: break;
657: }
658:
659: goto error;
660: }
661:
662: case s_req_schema:
663: {
664: c = LOWER(ch);
665:
666: if (c >= 'a' && c <= 'z') break;
667:
668: if (ch == ':') {
669: state = s_req_schema_slash;
670: break;
671: } else if (ch == '.') {
672: state = s_req_host;
673: break;
674: } else if ('0' <= ch && ch <= '9') {
675: state = s_req_host;
676: break;
677: }
678:
679: goto error;
680: }
681:
682: case s_req_schema_slash:
683: STRICT_CHECK(ch != '/');
684: state = s_req_schema_slash_slash;
685: break;
686:
687: case s_req_schema_slash_slash:
688: STRICT_CHECK(ch != '/');
689: state = s_req_host;
690: break;
691:
692: case s_req_host:
693: {
694: c = LOWER(ch);
695: if (c >= 'a' && c <= 'z') break;
696: if ((ch >= '0' && ch <= '9') || ch == '.' || ch == '-') break;
697: switch (ch) {
698: case ':':
699: state = s_req_port;
700: break;
701: case '/':
702: MARK(path);
703: state = s_req_path;
704: break;
705: case ' ':
706: /* The request line looks like:
707: * "GET http://foo.bar.com HTTP/1.1"
708: * That is, there is no path.
709: */
710: CALLBACK(url);
711: state = s_req_http_start;
712: break;
713: default:
714: goto error;
715: }
716: break;
717: }
718:
719: case s_req_port:
720: {
721: if (ch >= '0' && ch <= '9') break;
722: switch (ch) {
723: case '/':
724: MARK(path);
725: state = s_req_path;
726: break;
727: case ' ':
728: /* The request line looks like:
729: * "GET http://foo.bar.com:1234 HTTP/1.1"
730: * That is, there is no path.
731: */
732: CALLBACK(url);
733: state = s_req_http_start;
734: break;
735: default:
736: goto error;
737: }
738: break;
739: }
740:
741: case s_req_path:
742: {
743: if (normal_url_char[(unsigned char)ch]) break;
744:
745: switch (ch) {
746: case ' ':
747: CALLBACK(url);
748: CALLBACK(path);
749: state = s_req_http_start;
750: break;
751: case CR:
752: CALLBACK(url);
753: CALLBACK(path);
754: parser->http_major = 0;
755: parser->http_minor = 9;
756: state = s_req_line_almost_done;
757: break;
758: case LF:
759: CALLBACK(url);
760: CALLBACK(path);
761: parser->http_major = 0;
762: parser->http_minor = 9;
763: state = s_header_field_start;
764: break;
765: case '?':
766: CALLBACK(path);
767: state = s_req_query_string_start;
768: break;
769: case '#':
770: CALLBACK(path);
771: state = s_req_fragment_start;
772: break;
773: default:
774: goto error;
775: }
776: break;
777: }
778:
779: case s_req_query_string_start:
780: {
781: if (normal_url_char[(unsigned char)ch]) {
782: MARK(query_string);
783: state = s_req_query_string;
784: break;
785: }
786:
787: switch (ch) {
788: case '?':
789: break; /* XXX ignore extra '?' ... is this right? */
790: case ' ':
791: CALLBACK(url);
792: state = s_req_http_start;
793: break;
794: case CR:
795: CALLBACK(url);
796: parser->http_major = 0;
797: parser->http_minor = 9;
798: state = s_req_line_almost_done;
799: break;
800: case LF:
801: CALLBACK(url);
802: parser->http_major = 0;
803: parser->http_minor = 9;
804: state = s_header_field_start;
805: break;
806: case '#':
807: state = s_req_fragment_start;
808: break;
809: default:
810: goto error;
811: }
812: break;
813: }
814:
815: case s_req_query_string:
816: {
817: if (normal_url_char[(unsigned char)ch]) break;
818:
819: switch (ch) {
820: case '?':
821: /* allow extra '?' in query string */
822: break;
823: case ' ':
824: CALLBACK(url);
825: CALLBACK(query_string);
826: state = s_req_http_start;
827: break;
828: case CR:
829: CALLBACK(url);
830: CALLBACK(query_string);
831: parser->http_major = 0;
832: parser->http_minor = 9;
833: state = s_req_line_almost_done;
834: break;
835: case LF:
836: CALLBACK(url);
837: CALLBACK(query_string);
838: parser->http_major = 0;
839: parser->http_minor = 9;
840: state = s_header_field_start;
841: break;
842: case '#':
843: CALLBACK(query_string);
844: state = s_req_fragment_start;
845: break;
846: default:
847: goto error;
848: }
849: break;
850: }
851:
852: case s_req_fragment_start:
853: {
854: if (normal_url_char[(unsigned char)ch]) {
855: MARK(fragment);
856: state = s_req_fragment;
857: break;
858: }
859:
860: switch (ch) {
861: case ' ':
862: CALLBACK(url);
863: state = s_req_http_start;
864: break;
865: case CR:
866: CALLBACK(url);
867: parser->http_major = 0;
868: parser->http_minor = 9;
869: state = s_req_line_almost_done;
870: break;
871: case LF:
872: CALLBACK(url);
873: parser->http_major = 0;
874: parser->http_minor = 9;
875: state = s_header_field_start;
876: break;
877: case '?':
878: MARK(fragment);
879: state = s_req_fragment;
880: break;
881: case '#':
882: break;
883: default:
884: goto error;
885: }
886: break;
887: }
888:
889: case s_req_fragment:
890: {
891: if (normal_url_char[(unsigned char)ch]) break;
892:
893: switch (ch) {
894: case ' ':
895: CALLBACK(url);
896: CALLBACK(fragment);
897: state = s_req_http_start;
898: break;
899: case CR:
900: CALLBACK(url);
901: CALLBACK(fragment);
902: parser->http_major = 0;
903: parser->http_minor = 9;
904: state = s_req_line_almost_done;
905: break;
906: case LF:
907: CALLBACK(url);
908: CALLBACK(fragment);
909: parser->http_major = 0;
910: parser->http_minor = 9;
911: state = s_header_field_start;
912: break;
913: case '?':
914: case '#':
915: break;
916: default:
917: goto error;
918: }
919: break;
920: }
921:
922: case s_req_http_start:
923: switch (ch) {
924: case 'H':
925: state = s_req_http_H;
926: break;
927: case ' ':
928: break;
929: default:
930: goto error;
931: }
932: break;
933:
934: case s_req_http_H:
935: STRICT_CHECK(ch != 'T');
936: state = s_req_http_HT;
937: break;
938:
939: case s_req_http_HT:
940: STRICT_CHECK(ch != 'T');
941: state = s_req_http_HTT;
942: break;
943:
944: case s_req_http_HTT:
945: STRICT_CHECK(ch != 'P');
946: state = s_req_http_HTTP;
947: break;
948:
949: case s_req_http_HTTP:
950: STRICT_CHECK(ch != '/');
951: state = s_req_first_http_major;
952: break;
953:
954: /* first digit of major HTTP version */
955: case s_req_first_http_major:
956: if (ch < '1' || ch > '9') goto error;
957: parser->http_major = ch - '0';
958: state = s_req_http_major;
959: break;
960:
961: /* major HTTP version or dot */
962: case s_req_http_major:
963: {
964: if (ch == '.') {
965: state = s_req_first_http_minor;
966: break;
967: }
968:
969: if (ch < '0' || ch > '9') goto error;
970:
971: parser->http_major *= 10;
972: parser->http_major += ch - '0';
973:
974: if (parser->http_major > 999) goto error;
975: break;
976: }
977:
978: /* first digit of minor HTTP version */
979: case s_req_first_http_minor:
980: if (ch < '0' || ch > '9') goto error;
981: parser->http_minor = ch - '0';
982: state = s_req_http_minor;
983: break;
984:
985: /* minor HTTP version or end of request line */
986: case s_req_http_minor:
987: {
988: if (ch == CR) {
989: state = s_req_line_almost_done;
990: break;
991: }
992:
993: if (ch == LF) {
994: state = s_header_field_start;
995: break;
996: }
997:
998: /* XXX allow spaces after digit? */
999:
1000: if (ch < '0' || ch > '9') goto error;
1001:
1002: parser->http_minor *= 10;
1003: parser->http_minor += ch - '0';
1004:
1005: if (parser->http_minor > 999) goto error;
1006: break;
1007: }
1008:
1009: /* end of request line */
1010: case s_req_line_almost_done:
1011: {
1012: if (ch != LF) goto error;
1013: state = s_header_field_start;
1014: break;
1015: }
1016:
1017: case s_header_field_start:
1018: {
1019: if (ch == CR) {
1020: state = s_headers_almost_done;
1021: break;
1022: }
1023:
1024: if (ch == LF) {
1025: /* they might be just sending \n instead of \r\n so this would be
1026: * the second \n to denote the end of headers*/
1027: state = s_headers_almost_done;
1028: goto headers_almost_done;
1029: }
1030:
1031: c = TOKEN(ch);
1032:
1033: if (!c) goto error;
1034:
1035: MARK(header_field);
1036:
1037: index = 0;
1038: state = s_header_field;
1039:
1040: switch (c) {
1041: case 'c':
1042: header_state = h_C;
1043: break;
1044:
1045: case 'p':
1046: header_state = h_matching_proxy_connection;
1047: break;
1048:
1049: case 't':
1050: header_state = h_matching_transfer_encoding;
1051: break;
1052:
1053: case 'u':
1054: header_state = h_matching_upgrade;
1055: break;
1056:
1057: default:
1058: header_state = h_general;
1059: break;
1060: }
1061: break;
1062: }
1063:
1064: case s_header_field:
1065: {
1066: c = TOKEN(ch);
1067:
1068: if (c) {
1069: switch (header_state) {
1070: case h_general:
1071: break;
1072:
1073: case h_C:
1074: index++;
1075: header_state = (c == 'o' ? h_CO : h_general);
1076: break;
1077:
1078: case h_CO:
1079: index++;
1080: header_state = (c == 'n' ? h_CON : h_general);
1081: break;
1082:
1083: case h_CON:
1084: index++;
1085: switch (c) {
1086: case 'n':
1087: header_state = h_matching_connection;
1088: break;
1089: case 't':
1090: header_state = h_matching_content_length;
1091: break;
1092: default:
1093: header_state = h_general;
1094: break;
1095: }
1096: break;
1097:
1098: /* connection */
1099:
1100: case h_matching_connection:
1101: index++;
1102: if (index > sizeof(CONNECTION)-1
1103: || c != CONNECTION[index]) {
1104: header_state = h_general;
1105: } else if (index == sizeof(CONNECTION)-2) {
1106: header_state = h_connection;
1107: }
1108: break;
1109:
1110: /* proxy-connection */
1111:
1112: case h_matching_proxy_connection:
1113: index++;
1114: if (index > sizeof(PROXY_CONNECTION)-1
1115: || c != PROXY_CONNECTION[index]) {
1116: header_state = h_general;
1117: } else if (index == sizeof(PROXY_CONNECTION)-2) {
1118: header_state = h_connection;
1119: }
1120: break;
1121:
1122: /* content-length */
1123:
1124: case h_matching_content_length:
1125: index++;
1126: if (index > sizeof(CONTENT_LENGTH)-1
1127: || c != CONTENT_LENGTH[index]) {
1128: header_state = h_general;
1129: } else if (index == sizeof(CONTENT_LENGTH)-2) {
1130: header_state = h_content_length;
1131: }
1132: break;
1133:
1134: /* transfer-encoding */
1135:
1136: case h_matching_transfer_encoding:
1137: index++;
1138: if (index > sizeof(TRANSFER_ENCODING)-1
1139: || c != TRANSFER_ENCODING[index]) {
1140: header_state = h_general;
1141: } else if (index == sizeof(TRANSFER_ENCODING)-2) {
1142: header_state = h_transfer_encoding;
1143: }
1144: break;
1145:
1146: /* upgrade */
1147:
1148: case h_matching_upgrade:
1149: index++;
1150: if (index > sizeof(UPGRADE)-1
1151: || c != UPGRADE[index]) {
1152: header_state = h_general;
1153: } else if (index == sizeof(UPGRADE)-2) {
1154: header_state = h_upgrade;
1155: }
1156: break;
1157:
1158: case h_connection:
1159: case h_content_length:
1160: case h_transfer_encoding:
1161: case h_upgrade:
1162: if (ch != ' ') header_state = h_general;
1163: break;
1164:
1165: default:
1166: assert(0 && "Unknown header_state");
1167: break;
1168: }
1169: break;
1170: }
1171:
1172: if (ch == ':') {
1173: CALLBACK(header_field);
1174: state = s_header_value_start;
1175: break;
1176: }
1177:
1178: if (ch == CR) {
1179: state = s_header_almost_done;
1180: CALLBACK(header_field);
1181: break;
1182: }
1183:
1184: if (ch == LF) {
1185: CALLBACK(header_field);
1186: state = s_header_field_start;
1187: break;
1188: }
1189:
1190: goto error;
1191: }
1192:
1193: case s_header_value_start:
1194: {
1195: if (ch == ' ') break;
1196:
1197: MARK(header_value);
1198:
1199: state = s_header_value;
1200: index = 0;
1201:
1202: c = LOWER(ch);
1203:
1204: if (ch == CR) {
1205: CALLBACK(header_value);
1206: header_state = h_general;
1207: state = s_header_almost_done;
1208: break;
1209: }
1210:
1211: if (ch == LF) {
1212: CALLBACK(header_value);
1213: state = s_header_field_start;
1214: break;
1215: }
1216:
1217: switch (header_state) {
1218: case h_upgrade:
1219: parser->flags |= F_UPGRADE;
1220: header_state = h_general;
1221: break;
1222:
1223: case h_transfer_encoding:
1224: /* looking for 'Transfer-Encoding: chunked' */
1225: if ('c' == c) {
1226: header_state = h_matching_transfer_encoding_chunked;
1227: } else {
1228: header_state = h_general;
1229: }
1230: break;
1231:
1232: case h_content_length:
1233: if (ch < '0' || ch > '9') goto error;
1234: parser->content_length = ch - '0';
1235: break;
1236:
1237: case h_connection:
1238: /* looking for 'Connection: keep-alive' */
1239: if (c == 'k') {
1240: header_state = h_matching_connection_keep_alive;
1241: /* looking for 'Connection: close' */
1242: } else if (c == 'c') {
1243: header_state = h_matching_connection_close;
1244: } else {
1245: header_state = h_general;
1246: }
1247: break;
1248:
1249: default:
1250: header_state = h_general;
1251: break;
1252: }
1253: break;
1254: }
1255:
1256: case s_header_value:
1257: {
1258: c = LOWER(ch);
1259:
1260: if (ch == CR) {
1261: CALLBACK(header_value);
1262: state = s_header_almost_done;
1263: break;
1264: }
1265:
1266: if (ch == LF) {
1267: CALLBACK(header_value);
1268: goto header_almost_done;
1269: }
1270:
1271: switch (header_state) {
1272: case h_general:
1273: break;
1274:
1275: case h_connection:
1276: case h_transfer_encoding:
1277: assert(0 && "Shouldn't get here.");
1278: break;
1279:
1280: case h_content_length:
1281: if (ch == ' ') break;
1282: if (ch < '0' || ch > '9') goto error;
1283: parser->content_length *= 10;
1284: parser->content_length += ch - '0';
1285: break;
1286:
1287: /* Transfer-Encoding: chunked */
1288: case h_matching_transfer_encoding_chunked:
1289: index++;
1290: if (index > sizeof(CHUNKED)-1
1291: || c != CHUNKED[index]) {
1292: header_state = h_general;
1293: } else if (index == sizeof(CHUNKED)-2) {
1294: header_state = h_transfer_encoding_chunked;
1295: }
1296: break;
1297:
1298: /* looking for 'Connection: keep-alive' */
1299: case h_matching_connection_keep_alive:
1300: index++;
1301: if (index > sizeof(KEEP_ALIVE)-1
1302: || c != KEEP_ALIVE[index]) {
1303: header_state = h_general;
1304: } else if (index == sizeof(KEEP_ALIVE)-2) {
1305: header_state = h_connection_keep_alive;
1306: }
1307: break;
1308:
1309: /* looking for 'Connection: close' */
1310: case h_matching_connection_close:
1311: index++;
1312: if (index > sizeof(CLOSE)-1 || c != CLOSE[index]) {
1313: header_state = h_general;
1314: } else if (index == sizeof(CLOSE)-2) {
1315: header_state = h_connection_close;
1316: }
1317: break;
1318:
1319: case h_transfer_encoding_chunked:
1320: case h_connection_keep_alive:
1321: case h_connection_close:
1322: if (ch != ' ') header_state = h_general;
1323: break;
1324:
1325: default:
1326: state = s_header_value;
1327: header_state = h_general;
1328: break;
1329: }
1330: break;
1331: }
1332:
1333: case s_header_almost_done:
1334: header_almost_done:
1335: {
1336: STRICT_CHECK(ch != LF);
1337:
1338: state = s_header_field_start;
1339:
1340: switch (header_state) {
1341: case h_connection_keep_alive:
1342: parser->flags |= F_CONNECTION_KEEP_ALIVE;
1343: break;
1344: case h_connection_close:
1345: parser->flags |= F_CONNECTION_CLOSE;
1346: break;
1347: case h_transfer_encoding_chunked:
1348: parser->flags |= F_CHUNKED;
1349: break;
1350: default:
1351: break;
1352: }
1353: break;
1354: }
1355:
1356: case s_headers_almost_done:
1357: headers_almost_done:
1358: {
1359: STRICT_CHECK(ch != LF);
1360:
1361: if (parser->flags & F_TRAILING) {
1362: /* End of a chunked request */
1363: CALLBACK2(message_complete);
1364: state = NEW_MESSAGE();
1365: break;
1366: }
1367:
1368: nread = 0;
1369:
1370: if (parser->flags & F_UPGRADE || parser->method == PHP_HTTP_CONNECT) {
1371: parser->upgrade = 1;
1372: }
1373:
1374: /* Here we call the headers_complete callback. This is somewhat
1375: * different than other callbacks because if the user returns 1, we
1376: * will interpret that as saying that this message has no body. This
1377: * is needed for the annoying case of recieving a response to a HEAD
1378: * request.
1379: */
1380: if (settings->on_headers_complete) {
1381: switch (settings->on_headers_complete(parser)) {
1382: case 0:
1383: break;
1384:
1385: case 1:
1386: parser->flags |= F_SKIPBODY;
1387: break;
1388:
1389: default:
1390: return p - data; /* Error */
1391: }
1392: }
1393:
1394: /* Exit, the rest of the connect is in a different protocol. */
1395: if (parser->upgrade) {
1396: CALLBACK2(message_complete);
1397: return (p - data);
1398: }
1399:
1400: if (parser->flags & F_SKIPBODY) {
1401: CALLBACK2(message_complete);
1402: state = NEW_MESSAGE();
1403: } else if (parser->flags & F_CHUNKED) {
1404: /* chunked encoding - ignore Content-Length header */
1405: state = s_chunk_size_start;
1406: } else {
1407: if (parser->content_length == 0) {
1408: /* Content-Length header given but zero: Content-Length: 0\r\n */
1409: CALLBACK2(message_complete);
1410: state = NEW_MESSAGE();
1411: } else if (parser->content_length > 0) {
1412: /* Content-Length header given and non-zero */
1413: state = s_body_identity;
1414: } else {
1415: if (parser->type == PHP_HTTP_REQUEST || php_http_should_keep_alive(parser)) {
1416: /* Assume content-length 0 - read the next */
1417: CALLBACK2(message_complete);
1418: state = NEW_MESSAGE();
1419: } else {
1420: /* Read body until EOF */
1421: state = s_body_identity_eof;
1422: }
1423: }
1424: }
1425:
1426: break;
1427: }
1428:
1429: case s_body_identity:
1430: to_read = MIN(pe - p, (size_t)parser->content_length);
1431: if (to_read > 0) {
1432: if (settings->on_body) settings->on_body(parser, p, to_read);
1433: p += to_read - 1;
1434: parser->content_length -= to_read;
1435: if (parser->content_length == 0) {
1436: CALLBACK2(message_complete);
1437: state = NEW_MESSAGE();
1438: }
1439: }
1440: break;
1441:
1442: /* read until EOF */
1443: case s_body_identity_eof:
1444: to_read = pe - p;
1445: if (to_read > 0) {
1446: if (settings->on_body) settings->on_body(parser, p, to_read);
1447: p += to_read - 1;
1448: }
1449: break;
1450:
1451: case s_chunk_size_start:
1452: {
1453: assert(parser->flags & F_CHUNKED);
1454:
1455: c = unhex[(unsigned char)ch];
1456: if (c == -1) goto error;
1457: parser->content_length = c;
1458: state = s_chunk_size;
1459: break;
1460: }
1461:
1462: case s_chunk_size:
1463: {
1464: assert(parser->flags & F_CHUNKED);
1465:
1466: if (ch == CR) {
1467: state = s_chunk_size_almost_done;
1468: break;
1469: }
1470:
1471: c = unhex[(unsigned char)ch];
1472:
1473: if (c == -1) {
1474: if (ch == ';' || ch == ' ') {
1475: state = s_chunk_parameters;
1476: break;
1477: }
1478: goto error;
1479: }
1480:
1481: parser->content_length *= 16;
1482: parser->content_length += c;
1483: break;
1484: }
1485:
1486: case s_chunk_parameters:
1487: {
1488: assert(parser->flags & F_CHUNKED);
1489: /* just ignore this shit. TODO check for overflow */
1490: if (ch == CR) {
1491: state = s_chunk_size_almost_done;
1492: break;
1493: }
1494: break;
1495: }
1496:
1497: case s_chunk_size_almost_done:
1498: {
1499: assert(parser->flags & F_CHUNKED);
1500: STRICT_CHECK(ch != LF);
1501:
1502: if (parser->content_length == 0) {
1503: parser->flags |= F_TRAILING;
1504: state = s_header_field_start;
1505: } else {
1506: state = s_chunk_data;
1507: }
1508: break;
1509: }
1510:
1511: case s_chunk_data:
1512: {
1513: assert(parser->flags & F_CHUNKED);
1514:
1515: to_read = MIN(pe - p, (size_t)(parser->content_length));
1516:
1517: if (to_read > 0) {
1518: if (settings->on_body) settings->on_body(parser, p, to_read);
1519: p += to_read - 1;
1520: }
1521:
1522: if (to_read == parser->content_length) {
1523: state = s_chunk_data_almost_done;
1524: }
1525:
1526: parser->content_length -= to_read;
1527: break;
1528: }
1529:
1530: case s_chunk_data_almost_done:
1531: assert(parser->flags & F_CHUNKED);
1532: STRICT_CHECK(ch != CR);
1533: state = s_chunk_data_done;
1534: break;
1535:
1536: case s_chunk_data_done:
1537: assert(parser->flags & F_CHUNKED);
1538: STRICT_CHECK(ch != LF);
1539: state = s_chunk_size_start;
1540: break;
1541:
1542: default:
1543: assert(0 && "unhandled state");
1544: goto error;
1545: }
1546: }
1547:
1548: CALLBACK_NOCLEAR(header_field);
1549: CALLBACK_NOCLEAR(header_value);
1550: CALLBACK_NOCLEAR(fragment);
1551: CALLBACK_NOCLEAR(query_string);
1552: CALLBACK_NOCLEAR(path);
1553: CALLBACK_NOCLEAR(url);
1554:
1555: parser->state = state;
1556: parser->header_state = header_state;
1557: parser->index = index;
1558: parser->nread = nread;
1559:
1560: return len;
1561:
1562: error:
1563: parser->state = s_dead;
1564: return (p - data);
1565: }
1566:
1567:
1568: int
1569: php_http_should_keep_alive (php_http_parser *parser)
1570: {
1571: if (parser->http_major > 0 && parser->http_minor > 0) {
1572: /* HTTP/1.1 */
1573: if (parser->flags & F_CONNECTION_CLOSE) {
1574: return 0;
1575: } else {
1576: return 1;
1577: }
1578: } else {
1579: /* HTTP/1.0 or earlier */
1580: if (parser->flags & F_CONNECTION_KEEP_ALIVE) {
1581: return 1;
1582: } else {
1583: return 0;
1584: }
1585: }
1586: }
1587:
1588:
1589: const char * php_http_method_str (enum php_http_method m)
1590: {
1591: return method_strings[m];
1592: }
1593:
1594:
1595: void
1596: php_http_parser_init (php_http_parser *parser, enum php_http_parser_type t)
1597: {
1598: parser->type = t;
1599: parser->state = (t == PHP_HTTP_REQUEST ? s_start_req : (t == PHP_HTTP_RESPONSE ? s_start_res : s_start_req_or_res));
1600: parser->nread = 0;
1601: parser->upgrade = 0;
1602: parser->flags = 0;
1603: parser->method = 0;
1604: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>