nyara 0.0.1.pre
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/example/design.rb +62 -0
- data/example/fib.rb +15 -0
- data/example/hello.rb +5 -0
- data/example/stream.rb +10 -0
- data/ext/accept.c +133 -0
- data/ext/event.c +89 -0
- data/ext/extconf.rb +34 -0
- data/ext/hashes.c +130 -0
- data/ext/http-parser/AUTHORS +41 -0
- data/ext/http-parser/CONTRIBUTIONS +4 -0
- data/ext/http-parser/LICENSE-MIT +23 -0
- data/ext/http-parser/contrib/parsertrace.c +156 -0
- data/ext/http-parser/contrib/url_parser.c +44 -0
- data/ext/http-parser/http_parser.c +2175 -0
- data/ext/http-parser/http_parser.h +304 -0
- data/ext/http-parser/test.c +3425 -0
- data/ext/http_parser.c +1 -0
- data/ext/inc/epoll.h +60 -0
- data/ext/inc/kqueue.h +77 -0
- data/ext/inc/status_codes.inc +64 -0
- data/ext/inc/str_intern.h +66 -0
- data/ext/inc/version.inc +1 -0
- data/ext/mime.c +107 -0
- data/ext/multipart-parser-c/README.md +18 -0
- data/ext/multipart-parser-c/multipart_parser.c +309 -0
- data/ext/multipart-parser-c/multipart_parser.h +48 -0
- data/ext/multipart_parser.c +1 -0
- data/ext/nyara.c +56 -0
- data/ext/nyara.h +59 -0
- data/ext/request.c +474 -0
- data/ext/route.cc +325 -0
- data/ext/url_encoded.c +304 -0
- data/hello.rb +5 -0
- data/lib/nyara/config.rb +64 -0
- data/lib/nyara/config_hash.rb +51 -0
- data/lib/nyara/controller.rb +336 -0
- data/lib/nyara/cookie.rb +31 -0
- data/lib/nyara/cpu_counter.rb +65 -0
- data/lib/nyara/header_hash.rb +18 -0
- data/lib/nyara/mime_types.rb +612 -0
- data/lib/nyara/nyara.rb +82 -0
- data/lib/nyara/param_hash.rb +5 -0
- data/lib/nyara/request.rb +144 -0
- data/lib/nyara/route.rb +138 -0
- data/lib/nyara/route_entry.rb +43 -0
- data/lib/nyara/session.rb +104 -0
- data/lib/nyara/view.rb +317 -0
- data/lib/nyara.rb +25 -0
- data/nyara.gemspec +20 -0
- data/rakefile +91 -0
- data/readme.md +35 -0
- data/spec/ext_mime_match_spec.rb +27 -0
- data/spec/ext_parse_accept_value_spec.rb +29 -0
- data/spec/ext_parse_spec.rb +138 -0
- data/spec/ext_route_spec.rb +70 -0
- data/spec/hashes_spec.rb +71 -0
- data/spec/path_helper_spec.rb +77 -0
- data/spec/request_delegate_spec.rb +67 -0
- data/spec/request_spec.rb +56 -0
- data/spec/route_entry_spec.rb +12 -0
- data/spec/route_spec.rb +84 -0
- data/spec/session_spec.rb +66 -0
- data/spec/spec_helper.rb +52 -0
- data/spec/view_spec.rb +87 -0
- data/tools/bench-cookie.rb +22 -0
- metadata +111 -0
data/ext/http_parser.c
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
#include "http-parser/http_parser.c"
|
data/ext/inc/epoll.h
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
#pragma once
|
2
|
+
|
3
|
+
#include <sys/epoll.h>
|
4
|
+
|
5
|
+
static struct epoll_event qevents[MAX_E];
|
6
|
+
|
7
|
+
static void ADD_E(int fd, uint64_t etype) {
|
8
|
+
struct epoll_event e;
|
9
|
+
e.events = EPOLLIN;
|
10
|
+
e.data.u64 = (etype << 32) | (uint64_t)fd;
|
11
|
+
|
12
|
+
// todo timeout
|
13
|
+
# ifdef NDEBUG
|
14
|
+
epoll_ctl(qfd, EPOLL_CTL_ADD, fd, &e);
|
15
|
+
# else
|
16
|
+
if (epoll_ctl(qfd, EPOLL_CTL_ADD, fd, &e))
|
17
|
+
printf("%s: %s\n", __func__, strerror(errno));
|
18
|
+
# endif
|
19
|
+
}
|
20
|
+
|
21
|
+
static void DEL_E(int fd) {
|
22
|
+
struct epoll_event e;
|
23
|
+
e.events = EPOLLIN;
|
24
|
+
e.data.ptr = NULL;
|
25
|
+
|
26
|
+
# ifdef NDEBUG
|
27
|
+
epoll_ctl(qfd, EPOLL_CTL_DEL, fd, &e);
|
28
|
+
# else
|
29
|
+
if (epoll_ctl(qfd, EPOLL_CTL_DEL, fd, &e))
|
30
|
+
printf("%s: %s\n", __func__, strerror(errno));
|
31
|
+
# endif
|
32
|
+
}
|
33
|
+
|
34
|
+
static void INIT_E() {
|
35
|
+
qfd = epoll_create(10); // size not important
|
36
|
+
if (qfd == -1) {
|
37
|
+
printf("%s\n", strerror(errno));
|
38
|
+
exit(-1);
|
39
|
+
}
|
40
|
+
}
|
41
|
+
|
42
|
+
static void LOOP_E() {
|
43
|
+
while (1) {
|
44
|
+
// heart beat of 0.1 sec, allow ruby signal interrupts to be inserted
|
45
|
+
int sz = epoll_wait(qfd, qevents, MAX_E, 100);
|
46
|
+
|
47
|
+
for (int i = 0; i < sz; i++) {
|
48
|
+
switch (qevents[i].events) {
|
49
|
+
case EPOLLIN: {
|
50
|
+
int fd = (int)(qevents[i].data.u64 & 0xFFFFFFFF);
|
51
|
+
int etype = (int)(qevents[i].data.u64 >> 32);
|
52
|
+
loop_body(fd, etype);
|
53
|
+
break;
|
54
|
+
}
|
55
|
+
}
|
56
|
+
}
|
57
|
+
// execute other thread / interrupts
|
58
|
+
rb_thread_schedule();
|
59
|
+
}
|
60
|
+
}
|
data/ext/inc/kqueue.h
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
#pragma once
|
2
|
+
|
3
|
+
#include <sys/types.h>
|
4
|
+
#include <sys/time.h>
|
5
|
+
#ifdef HAVE_SYS_EVENT_H
|
6
|
+
# include <sys/event.h>
|
7
|
+
#else
|
8
|
+
# include <sys/queue.h>
|
9
|
+
#endif
|
10
|
+
|
11
|
+
static void loop_body(int fd, int etype);
|
12
|
+
|
13
|
+
#define MAX_E 1024
|
14
|
+
static int qfd;
|
15
|
+
static struct kevent qevents[MAX_E];
|
16
|
+
|
17
|
+
static void ADD_E(int fd, uint64_t etype) {
|
18
|
+
struct kevent e;
|
19
|
+
EV_SET(&e, fd, EVFILT_READ, EV_ADD, 0, 0, (void*)etype);
|
20
|
+
// todo timeout
|
21
|
+
# ifdef NDEBUG
|
22
|
+
kevent(qfd, &e, 1, NULL, 0, NULL);
|
23
|
+
# else
|
24
|
+
if (kevent(qfd, &e, 1, NULL, 0, NULL))
|
25
|
+
printf("%s: %s\n", __func__, strerror(errno));
|
26
|
+
# endif
|
27
|
+
}
|
28
|
+
|
29
|
+
static void DEL_E(int fd) {
|
30
|
+
struct kevent e;
|
31
|
+
EV_SET(&e, fd, EVFILT_READ, EV_DELETE, 0, 0, NULL);
|
32
|
+
# ifdef NDEBUG
|
33
|
+
kevent(qfd, &e, 1, NULL, 0, NULL);
|
34
|
+
# else
|
35
|
+
if (kevent(qfd, &e, 1, NULL, 0, NULL))
|
36
|
+
printf("%s: %s\n", __func__, strerror(errno));
|
37
|
+
# endif
|
38
|
+
}
|
39
|
+
|
40
|
+
static void INIT_E() {
|
41
|
+
qfd = kqueue();
|
42
|
+
if (qfd == -1) {
|
43
|
+
printf("%s\n", strerror(errno));
|
44
|
+
exit(-1);
|
45
|
+
}
|
46
|
+
}
|
47
|
+
|
48
|
+
static void LOOP_E() {
|
49
|
+
// printf("%d,%d,%d,\n%d,%d,%d,\n%d,%d,%d,\n",
|
50
|
+
// EV_ADD, EV_ENABLE, EV_DISABLE,
|
51
|
+
// EV_DELETE, EV_RECEIPT, EV_ONESHOT,
|
52
|
+
// EV_CLEAR, EV_EOF, EV_ERROR);
|
53
|
+
|
54
|
+
struct timespec ts = {0, 1000 * 1000 * 100};
|
55
|
+
while (1) {
|
56
|
+
// heart beat of 0.1 sec, allow ruby signal interrupts to be inserted
|
57
|
+
int sz = kevent(qfd, NULL, 0, qevents, MAX_E, &ts);
|
58
|
+
|
59
|
+
for (int i = 0; i < sz; i++) {
|
60
|
+
switch (qevents[i].filter) {
|
61
|
+
case EVFILT_READ: {
|
62
|
+
int fd = (int)qevents[i].ident;
|
63
|
+
// EV_EOF is set if the read side of the socket is shutdown
|
64
|
+
// the event can keep flipping back to consume cpu if we don't remove it
|
65
|
+
if ((qevents[i].flags & EV_EOF)) {
|
66
|
+
DEL_E(fd);
|
67
|
+
} else {
|
68
|
+
loop_body(fd, (int)qevents[i].udata);
|
69
|
+
}
|
70
|
+
break;
|
71
|
+
}
|
72
|
+
}
|
73
|
+
}
|
74
|
+
// execute other thread / interrupts
|
75
|
+
rb_thread_schedule();
|
76
|
+
}
|
77
|
+
}
|
@@ -0,0 +1,64 @@
|
|
1
|
+
#define HTTP_STATUS_CODES(XX)\
|
2
|
+
XX(100, "Continue");\
|
3
|
+
XX(101, "Switching Protocols");\
|
4
|
+
XX(102, "Processing");\
|
5
|
+
XX(200, "OK");\
|
6
|
+
XX(201, "Created");\
|
7
|
+
XX(202, "Accepted");\
|
8
|
+
XX(203, "Non-Authoritative Information");\
|
9
|
+
XX(204, "No Content");\
|
10
|
+
XX(205, "Reset Content");\
|
11
|
+
XX(206, "Partial Content");\
|
12
|
+
XX(207, "Multi-Status");\
|
13
|
+
XX(208, "Already Reported");\
|
14
|
+
XX(226, "IM Used");\
|
15
|
+
XX(300, "Multiple Choices");\
|
16
|
+
XX(301, "Moved Permanently");\
|
17
|
+
XX(302, "Found");\
|
18
|
+
XX(303, "See Other");\
|
19
|
+
XX(304, "Not Modified");\
|
20
|
+
XX(305, "Use Proxy");\
|
21
|
+
XX(306, "Reserved");\
|
22
|
+
XX(307, "Temporary Redirect");\
|
23
|
+
XX(308, "Permanent Redirect");\
|
24
|
+
XX(400, "Bad Request");\
|
25
|
+
XX(401, "Unauthorized");\
|
26
|
+
XX(402, "Payment Required");\
|
27
|
+
XX(403, "Forbidden");\
|
28
|
+
XX(404, "Not Found");\
|
29
|
+
XX(405, "Method Not Allowed");\
|
30
|
+
XX(406, "Not Acceptable");\
|
31
|
+
XX(407, "Proxy Authentication Required");\
|
32
|
+
XX(408, "Request Timeout");\
|
33
|
+
XX(409, "Conflict");\
|
34
|
+
XX(410, "Gone");\
|
35
|
+
XX(411, "Length Required");\
|
36
|
+
XX(412, "Precondition Failed");\
|
37
|
+
XX(413, "Request Entity Too Large");\
|
38
|
+
XX(414, "Request-URI Too Long");\
|
39
|
+
XX(415, "Unsupported Media Type");\
|
40
|
+
XX(416, "Requested Range Not Satisfiable");\
|
41
|
+
XX(417, "Expectation Failed");\
|
42
|
+
XX(422, "Unprocessable Entity");\
|
43
|
+
XX(423, "Locked");\
|
44
|
+
XX(424, "Failed Dependency");\
|
45
|
+
XX(425, "Unassigned");\
|
46
|
+
XX(426, "Upgrade Required");\
|
47
|
+
XX(427, "Unassigned");\
|
48
|
+
XX(428, "Precondition Required");\
|
49
|
+
XX(429, "Too Many Requests");\
|
50
|
+
XX(430, "Unassigned");\
|
51
|
+
XX(431, "Request Header Fields Too Large");\
|
52
|
+
XX(500, "Internal Server Error");\
|
53
|
+
XX(501, "Not Implemented");\
|
54
|
+
XX(502, "Bad Gateway");\
|
55
|
+
XX(503, "Service Unavailable");\
|
56
|
+
XX(504, "Gateway Timeout");\
|
57
|
+
XX(505, "HTTP Version Not Supported");\
|
58
|
+
XX(506, "Variant Also Negotiates (Experimental)");\
|
59
|
+
XX(507, "Insufficient Storage");\
|
60
|
+
XX(508, "Loop Detected");\
|
61
|
+
XX(509, "Unassigned");\
|
62
|
+
XX(510, "Not Extended");\
|
63
|
+
XX(511, "Network Authentication Required");\
|
64
|
+
// end define
|
@@ -0,0 +1,66 @@
|
|
1
|
+
#pragma once
|
2
|
+
|
3
|
+
// from string.c
|
4
|
+
|
5
|
+
#define RUBY_MAX_CHAR_LEN 16
|
6
|
+
#define STR_TMPLOCK FL_USER7
|
7
|
+
#define STR_NOEMBED FL_USER1
|
8
|
+
#define STR_SHARED FL_USER2 /* = ELTS_SHARED */
|
9
|
+
#define STR_ASSOC FL_USER3
|
10
|
+
#define STR_SHARED_P(s) FL_ALL((s), STR_NOEMBED|ELTS_SHARED)
|
11
|
+
#define STR_ASSOC_P(s) FL_ALL((s), STR_NOEMBED|STR_ASSOC)
|
12
|
+
#define STR_NOCAPA (STR_NOEMBED|ELTS_SHARED|STR_ASSOC)
|
13
|
+
#define STR_NOCAPA_P(s) (FL_TEST((s),STR_NOEMBED) && FL_ANY((s),ELTS_SHARED|STR_ASSOC))
|
14
|
+
#define STR_UNSET_NOCAPA(s) do {\
|
15
|
+
if (FL_TEST((s),STR_NOEMBED)) FL_UNSET((s),(ELTS_SHARED|STR_ASSOC));\
|
16
|
+
} while (0)
|
17
|
+
|
18
|
+
#define STR_SET_NOEMBED(str) do {\
|
19
|
+
FL_SET((str), STR_NOEMBED);\
|
20
|
+
STR_SET_EMBED_LEN((str), 0);\
|
21
|
+
} while (0)
|
22
|
+
#define STR_SET_EMBED(str) FL_UNSET((str), STR_NOEMBED)
|
23
|
+
#define STR_EMBED_P(str) (!FL_TEST((str), STR_NOEMBED))
|
24
|
+
#define STR_SET_EMBED_LEN(str, n) do { \
|
25
|
+
long tmp_n = (n);\
|
26
|
+
RBASIC(str)->flags &= ~RSTRING_EMBED_LEN_MASK;\
|
27
|
+
RBASIC(str)->flags |= (tmp_n) << RSTRING_EMBED_LEN_SHIFT;\
|
28
|
+
} while (0)
|
29
|
+
|
30
|
+
#define STR_SET_LEN(str, n) do { \
|
31
|
+
if (STR_EMBED_P(str)) {\
|
32
|
+
STR_SET_EMBED_LEN((str), (n));\
|
33
|
+
}\
|
34
|
+
else {\
|
35
|
+
RSTRING(str)->as.heap.len = (n);\
|
36
|
+
}\
|
37
|
+
} while (0)
|
38
|
+
|
39
|
+
#define STR_DEC_LEN(str) do {\
|
40
|
+
if (STR_EMBED_P(str)) {\
|
41
|
+
long n = RSTRING_LEN(str);\
|
42
|
+
n--;\
|
43
|
+
STR_SET_EMBED_LEN((str), n);\
|
44
|
+
}\
|
45
|
+
else {\
|
46
|
+
RSTRING(str)->as.heap.len--;\
|
47
|
+
}\
|
48
|
+
} while (0)
|
49
|
+
|
50
|
+
#define RESIZE_CAPA(str,capacity) do {\
|
51
|
+
if (STR_EMBED_P(str)) {\
|
52
|
+
if ((capacity) > RSTRING_EMBED_LEN_MAX) {\
|
53
|
+
char *tmp = ALLOC_N(char, (capacity)+1);\
|
54
|
+
memcpy(tmp, RSTRING_PTR(str), RSTRING_LEN(str));\
|
55
|
+
RSTRING(str)->as.heap.ptr = tmp;\
|
56
|
+
RSTRING(str)->as.heap.len = RSTRING_LEN(str);\
|
57
|
+
STR_SET_NOEMBED(str);\
|
58
|
+
RSTRING(str)->as.heap.aux.capa = (capacity);\
|
59
|
+
}\
|
60
|
+
}\
|
61
|
+
else {\
|
62
|
+
REALLOC_N(RSTRING(str)->as.heap.ptr, char, (capacity)+1);\
|
63
|
+
if (!STR_NOCAPA_P(str))\
|
64
|
+
RSTRING(str)->as.heap.aux.capa = (capacity);\
|
65
|
+
}\
|
66
|
+
} while (0)
|
data/ext/inc/version.inc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
#define NYARA_VERSION "0.0.1.pre"
|
data/ext/mime.c
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
#include "nyara.h"
|
2
|
+
#include <string.h>
|
3
|
+
#include <ctype.h>
|
4
|
+
#include "inc/str_intern.h"
|
5
|
+
|
6
|
+
static const char* _strnchr(const char* s, long len, char c) {
|
7
|
+
for (long i = 0; i < len; i++) {
|
8
|
+
if (s[i] == c) {
|
9
|
+
return s + i;
|
10
|
+
}
|
11
|
+
}
|
12
|
+
return NULL;
|
13
|
+
}
|
14
|
+
|
15
|
+
static bool mime_match_seg(const char* m1_ptr, long m1_len, VALUE v1, VALUE v2) {
|
16
|
+
const char* m2_ptr = _strnchr(m1_ptr, m1_len, '/');
|
17
|
+
long m2_len;
|
18
|
+
if (m2_ptr) {
|
19
|
+
m2_ptr++;
|
20
|
+
m2_len = m1_len - (m2_ptr - m1_ptr);
|
21
|
+
m1_len = (m2_ptr - m1_ptr) - 1;
|
22
|
+
} else {
|
23
|
+
m2_len = 0;
|
24
|
+
}
|
25
|
+
|
26
|
+
const char* v1_ptr = RSTRING_PTR(v1);
|
27
|
+
const char* v2_ptr = RSTRING_PTR(v2);
|
28
|
+
const long v1_len = RSTRING_LEN(v1);
|
29
|
+
const long v2_len = RSTRING_LEN(v2);
|
30
|
+
|
31
|
+
# define EQL_STAR(s, len) (len == 1 && s[0] == '*')
|
32
|
+
# define EQL(s1, len1, s2, len2) (len1 == len2 && strncmp(s1, s2, len1) == 0)
|
33
|
+
|
34
|
+
/*
|
35
|
+
if m1 == '*'
|
36
|
+
if m2.nil? || m2 == '*'
|
37
|
+
return true
|
38
|
+
elsif m2 == v2
|
39
|
+
return true
|
40
|
+
else
|
41
|
+
return false
|
42
|
+
end
|
43
|
+
end
|
44
|
+
return false if v1 != m1
|
45
|
+
return true if m2.nil? || m2 == '*'
|
46
|
+
m2 == v2
|
47
|
+
*/
|
48
|
+
|
49
|
+
if (EQL_STAR(m1_ptr, m1_len)) {
|
50
|
+
if (m2_len == 0 || EQL_STAR(m2_ptr, m2_len)) {
|
51
|
+
return true;
|
52
|
+
} else if (EQL(m2_ptr, m2_len, v2_ptr, v2_len)) {
|
53
|
+
return true;
|
54
|
+
} else {
|
55
|
+
return false;
|
56
|
+
}
|
57
|
+
}
|
58
|
+
if (!EQL(v1_ptr, v1_len, m1_ptr, m1_len)) {
|
59
|
+
return false;
|
60
|
+
}
|
61
|
+
if (m2_len == 0 || EQL_STAR(m2_ptr, m2_len)) {
|
62
|
+
return true;
|
63
|
+
}
|
64
|
+
return EQL(m2_ptr, m2_len, v2_ptr, v2_len);
|
65
|
+
|
66
|
+
# undef EQL
|
67
|
+
# undef EQL_STAR
|
68
|
+
}
|
69
|
+
|
70
|
+
// for test
|
71
|
+
static VALUE ext_mime_match_seg(VALUE self, VALUE m, VALUE v1, VALUE v2) {
|
72
|
+
if (mime_match_seg(RSTRING_PTR(m), RSTRING_LEN(m), v1, v2)) {
|
73
|
+
return Qtrue;
|
74
|
+
} else {
|
75
|
+
return Qfalse;
|
76
|
+
}
|
77
|
+
}
|
78
|
+
|
79
|
+
// returns matched ext or nil
|
80
|
+
VALUE ext_mime_match(VALUE self, VALUE request_accept, VALUE accept_mimes) {
|
81
|
+
Check_Type(request_accept, T_ARRAY);
|
82
|
+
Check_Type(accept_mimes, T_ARRAY);
|
83
|
+
|
84
|
+
VALUE* accepts = RARRAY_PTR(request_accept);
|
85
|
+
long accepts_len = RARRAY_LEN(request_accept);
|
86
|
+
VALUE* values = RARRAY_PTR(accept_mimes);
|
87
|
+
long values_len = RARRAY_LEN(accept_mimes);
|
88
|
+
|
89
|
+
for (long j = 0; j < accepts_len; j++) {
|
90
|
+
char* s = RSTRING_PTR(accepts[j]);
|
91
|
+
long len = RSTRING_LEN(accepts[j]);
|
92
|
+
for (long i = 0; i < values_len; i++) {
|
93
|
+
Check_Type(values[i], T_ARRAY);
|
94
|
+
VALUE* arr = RARRAY_PTR(values[i]);
|
95
|
+
if (mime_match_seg(s, len, arr[0], arr[1])) {
|
96
|
+
return arr[2];
|
97
|
+
}
|
98
|
+
}
|
99
|
+
}
|
100
|
+
return Qnil;
|
101
|
+
}
|
102
|
+
|
103
|
+
void Init_mime(VALUE ext) {
|
104
|
+
rb_define_singleton_method(ext, "mime_match", ext_mime_match, 2);
|
105
|
+
// for test
|
106
|
+
rb_define_singleton_method(ext, "mime_match_seg", ext_mime_match_seg, 3);
|
107
|
+
}
|
@@ -0,0 +1,18 @@
|
|
1
|
+
## Multipart form data parser
|
2
|
+
|
3
|
+
### Features
|
4
|
+
* No dependencies
|
5
|
+
* Works with chunks of a data - no need to buffer the whole request
|
6
|
+
* Almost no internal buffering. Buffer size doesn't exceed the size of the boundary (~60-70 bytes)
|
7
|
+
|
8
|
+
Tested as part of [Cosmonaut](https://github.com/iafonov/cosmonaut) HTTP server.
|
9
|
+
|
10
|
+
Implementation based on [node-formidable](https://github.com/felixge/node-formidable) by [Felix Geisendörfer](https://github.com/felixge).
|
11
|
+
|
12
|
+
Inspired by [http-parser](https://github.com/joyent/http-parser) by [Ryan Dahl](https://github.com/ry).
|
13
|
+
|
14
|
+
### Contributors:
|
15
|
+
* [Daniel T. Wagner](http://www.danieltwagner.de/)
|
16
|
+
* [James McLaughlin](http://udp.github.com/)
|
17
|
+
|
18
|
+
© 2012 [Igor Afonov](http://iafonov.github.com)
|
@@ -0,0 +1,309 @@
|
|
1
|
+
/* Based on node-formidable by Felix Geisendörfer
|
2
|
+
* Igor Afonov - afonov@gmail.com - 2012
|
3
|
+
* MIT License - http://www.opensource.org/licenses/mit-license.php
|
4
|
+
*/
|
5
|
+
|
6
|
+
#include "multipart_parser.h"
|
7
|
+
|
8
|
+
#include <stdio.h>
|
9
|
+
#include <stdarg.h>
|
10
|
+
#include <string.h>
|
11
|
+
|
12
|
+
static void multipart_log(const char * format, ...)
|
13
|
+
{
|
14
|
+
#ifdef DEBUG_MULTIPART
|
15
|
+
va_list args;
|
16
|
+
va_start(args, format);
|
17
|
+
|
18
|
+
fprintf(stderr, "[HTTP_MULTIPART_PARSER] %s:%d: ", __FILE__, __LINE__);
|
19
|
+
vfprintf(stderr, format, args);
|
20
|
+
fprintf(stderr, "\n");
|
21
|
+
#endif
|
22
|
+
}
|
23
|
+
|
24
|
+
#define NOTIFY_CB(FOR) \
|
25
|
+
do { \
|
26
|
+
if (p->settings->on_##FOR) { \
|
27
|
+
if (p->settings->on_##FOR(p) != 0) { \
|
28
|
+
return i; \
|
29
|
+
} \
|
30
|
+
} \
|
31
|
+
} while (0)
|
32
|
+
|
33
|
+
#define EMIT_DATA_CB(FOR, ptr, len) \
|
34
|
+
do { \
|
35
|
+
if (p->settings->on_##FOR) { \
|
36
|
+
if (p->settings->on_##FOR(p, ptr, len) != 0) { \
|
37
|
+
return i; \
|
38
|
+
} \
|
39
|
+
} \
|
40
|
+
} while (0)
|
41
|
+
|
42
|
+
|
43
|
+
#define LF 10
|
44
|
+
#define CR 13
|
45
|
+
|
46
|
+
struct multipart_parser {
|
47
|
+
void * data;
|
48
|
+
|
49
|
+
size_t index;
|
50
|
+
size_t boundary_length;
|
51
|
+
|
52
|
+
unsigned char state;
|
53
|
+
|
54
|
+
const multipart_parser_settings* settings;
|
55
|
+
|
56
|
+
char* lookbehind;
|
57
|
+
char multipart_boundary[1];
|
58
|
+
};
|
59
|
+
|
60
|
+
enum state {
|
61
|
+
s_uninitialized = 1,
|
62
|
+
s_start,
|
63
|
+
s_start_boundary,
|
64
|
+
s_header_field_start,
|
65
|
+
s_header_field,
|
66
|
+
s_headers_almost_done,
|
67
|
+
s_header_value_start,
|
68
|
+
s_header_value,
|
69
|
+
s_header_value_almost_done,
|
70
|
+
s_part_data_start,
|
71
|
+
s_part_data,
|
72
|
+
s_part_data_almost_boundary,
|
73
|
+
s_part_data_boundary,
|
74
|
+
s_part_data_almost_end,
|
75
|
+
s_part_data_end,
|
76
|
+
s_part_data_final_hyphen,
|
77
|
+
s_end
|
78
|
+
};
|
79
|
+
|
80
|
+
multipart_parser* multipart_parser_init
|
81
|
+
(const char *boundary, const multipart_parser_settings* settings) {
|
82
|
+
|
83
|
+
multipart_parser* p = malloc(sizeof(multipart_parser) +
|
84
|
+
strlen(boundary) +
|
85
|
+
strlen(boundary) + 9);
|
86
|
+
|
87
|
+
strcpy(p->multipart_boundary, boundary);
|
88
|
+
p->boundary_length = strlen(boundary);
|
89
|
+
|
90
|
+
p->lookbehind = (p->multipart_boundary + p->boundary_length + 1);
|
91
|
+
|
92
|
+
p->index = 0;
|
93
|
+
p->state = s_start;
|
94
|
+
p->settings = settings;
|
95
|
+
|
96
|
+
return p;
|
97
|
+
}
|
98
|
+
|
99
|
+
void multipart_parser_free(multipart_parser* p) {
|
100
|
+
free(p);
|
101
|
+
}
|
102
|
+
|
103
|
+
void multipart_parser_set_data(multipart_parser *p, void *data) {
|
104
|
+
p->data = data;
|
105
|
+
}
|
106
|
+
|
107
|
+
void *multipart_parser_get_data(multipart_parser *p) {
|
108
|
+
return p->data;
|
109
|
+
}
|
110
|
+
|
111
|
+
size_t multipart_parser_execute(multipart_parser* p, const char *buf, size_t len) {
|
112
|
+
size_t i = 0;
|
113
|
+
size_t mark = 0;
|
114
|
+
char c, cl;
|
115
|
+
int is_last = 0;
|
116
|
+
|
117
|
+
while(!is_last) {
|
118
|
+
c = buf[i];
|
119
|
+
is_last = (i == (len - 1));
|
120
|
+
switch (p->state) {
|
121
|
+
case s_start:
|
122
|
+
multipart_log("s_start");
|
123
|
+
p->index = 0;
|
124
|
+
p->state = s_start_boundary;
|
125
|
+
|
126
|
+
/* fallthrough */
|
127
|
+
case s_start_boundary:
|
128
|
+
multipart_log("s_start_boundary");
|
129
|
+
if (p->index == p->boundary_length) {
|
130
|
+
if (c != CR) {
|
131
|
+
return i;
|
132
|
+
}
|
133
|
+
p->index++;
|
134
|
+
break;
|
135
|
+
} else if (p->index == (p->boundary_length + 1)) {
|
136
|
+
if (c != LF) {
|
137
|
+
return i;
|
138
|
+
}
|
139
|
+
p->index = 0;
|
140
|
+
NOTIFY_CB(part_data_begin);
|
141
|
+
p->state = s_header_field_start;
|
142
|
+
break;
|
143
|
+
}
|
144
|
+
if (c != p->multipart_boundary[p->index]) {
|
145
|
+
return i;
|
146
|
+
}
|
147
|
+
p->index++;
|
148
|
+
break;
|
149
|
+
|
150
|
+
case s_header_field_start:
|
151
|
+
multipart_log("s_header_field_start");
|
152
|
+
mark = i;
|
153
|
+
p->state = s_header_field;
|
154
|
+
|
155
|
+
/* fallthrough */
|
156
|
+
case s_header_field:
|
157
|
+
multipart_log("s_header_field");
|
158
|
+
if (c == CR) {
|
159
|
+
p->state = s_headers_almost_done;
|
160
|
+
break;
|
161
|
+
}
|
162
|
+
|
163
|
+
if (c == '-') {
|
164
|
+
break;
|
165
|
+
}
|
166
|
+
|
167
|
+
if (c == ':') {
|
168
|
+
EMIT_DATA_CB(header_field, buf + mark, i - mark);
|
169
|
+
p->state = s_header_value_start;
|
170
|
+
break;
|
171
|
+
}
|
172
|
+
|
173
|
+
cl = tolower(c);
|
174
|
+
if (cl < 'a' || cl > 'z') {
|
175
|
+
multipart_log("invalid character in header name");
|
176
|
+
return i;
|
177
|
+
}
|
178
|
+
if (is_last)
|
179
|
+
EMIT_DATA_CB(header_field, buf + mark, (i - mark) + 1);
|
180
|
+
break;
|
181
|
+
|
182
|
+
case s_headers_almost_done:
|
183
|
+
multipart_log("s_headers_almost_done");
|
184
|
+
if (c != LF) {
|
185
|
+
return i;
|
186
|
+
}
|
187
|
+
|
188
|
+
p->state = s_part_data_start;
|
189
|
+
break;
|
190
|
+
|
191
|
+
case s_header_value_start:
|
192
|
+
multipart_log("s_header_value_start");
|
193
|
+
if (c == ' ') {
|
194
|
+
break;
|
195
|
+
}
|
196
|
+
|
197
|
+
mark = i;
|
198
|
+
p->state = s_header_value;
|
199
|
+
|
200
|
+
/* fallthrough */
|
201
|
+
case s_header_value:
|
202
|
+
multipart_log("s_header_value");
|
203
|
+
if (c == CR) {
|
204
|
+
EMIT_DATA_CB(header_value, buf + mark, i - mark);
|
205
|
+
p->state = s_header_value_almost_done;
|
206
|
+
}
|
207
|
+
if (is_last)
|
208
|
+
EMIT_DATA_CB(header_value, buf + mark, (i - mark) + 1);
|
209
|
+
break;
|
210
|
+
|
211
|
+
case s_header_value_almost_done:
|
212
|
+
multipart_log("s_header_value_almost_done");
|
213
|
+
if (c != LF) {
|
214
|
+
return i;
|
215
|
+
}
|
216
|
+
p->state = s_header_field_start;
|
217
|
+
break;
|
218
|
+
|
219
|
+
case s_part_data_start:
|
220
|
+
multipart_log("s_part_data_start");
|
221
|
+
NOTIFY_CB(headers_complete);
|
222
|
+
mark = i;
|
223
|
+
p->state = s_part_data;
|
224
|
+
|
225
|
+
/* fallthrough */
|
226
|
+
case s_part_data:
|
227
|
+
multipart_log("s_part_data");
|
228
|
+
if (c == CR) {
|
229
|
+
EMIT_DATA_CB(part_data, buf + mark, i - mark);
|
230
|
+
mark = i;
|
231
|
+
p->state = s_part_data_almost_boundary;
|
232
|
+
p->lookbehind[0] = CR;
|
233
|
+
break;
|
234
|
+
}
|
235
|
+
if (is_last)
|
236
|
+
EMIT_DATA_CB(part_data, buf + mark, (i - mark) + 1);
|
237
|
+
break;
|
238
|
+
|
239
|
+
case s_part_data_almost_boundary:
|
240
|
+
multipart_log("s_part_data_almost_boundary");
|
241
|
+
if (c == LF) {
|
242
|
+
p->state = s_part_data_boundary;
|
243
|
+
p->lookbehind[1] = LF;
|
244
|
+
p->index = 0;
|
245
|
+
break;
|
246
|
+
}
|
247
|
+
EMIT_DATA_CB(part_data, p->lookbehind, 1);
|
248
|
+
p->state = s_part_data;
|
249
|
+
mark = i --;
|
250
|
+
break;
|
251
|
+
|
252
|
+
case s_part_data_boundary:
|
253
|
+
multipart_log("s_part_data_boundary");
|
254
|
+
if (p->multipart_boundary[p->index] != c) {
|
255
|
+
EMIT_DATA_CB(part_data, p->lookbehind, 2 + p->index);
|
256
|
+
p->state = s_part_data;
|
257
|
+
mark = i --;
|
258
|
+
break;
|
259
|
+
}
|
260
|
+
p->lookbehind[2 + p->index] = c;
|
261
|
+
if ((++ p->index) == p->boundary_length) {
|
262
|
+
NOTIFY_CB(part_data_end);
|
263
|
+
p->state = s_part_data_almost_end;
|
264
|
+
}
|
265
|
+
break;
|
266
|
+
|
267
|
+
case s_part_data_almost_end:
|
268
|
+
multipart_log("s_part_data_almost_end");
|
269
|
+
if (c == '-') {
|
270
|
+
p->state = s_part_data_final_hyphen;
|
271
|
+
break;
|
272
|
+
}
|
273
|
+
if (c == CR) {
|
274
|
+
p->state = s_part_data_end;
|
275
|
+
break;
|
276
|
+
}
|
277
|
+
return i;
|
278
|
+
|
279
|
+
case s_part_data_final_hyphen:
|
280
|
+
multipart_log("s_part_data_final_hyphen");
|
281
|
+
if (c == '-') {
|
282
|
+
NOTIFY_CB(body_end);
|
283
|
+
p->state = s_end;
|
284
|
+
break;
|
285
|
+
}
|
286
|
+
return i;
|
287
|
+
|
288
|
+
case s_part_data_end:
|
289
|
+
multipart_log("s_part_data_end");
|
290
|
+
if (c == LF) {
|
291
|
+
p->state = s_header_field_start;
|
292
|
+
NOTIFY_CB(part_data_begin);
|
293
|
+
break;
|
294
|
+
}
|
295
|
+
return i;
|
296
|
+
|
297
|
+
case s_end:
|
298
|
+
multipart_log("s_end: %02X", (int) c);
|
299
|
+
break;
|
300
|
+
|
301
|
+
default:
|
302
|
+
multipart_log("Multipart parser unrecoverable error");
|
303
|
+
return 0;
|
304
|
+
}
|
305
|
+
++ i;
|
306
|
+
}
|
307
|
+
|
308
|
+
return len;
|
309
|
+
}
|