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