nyara 0.0.1.pre
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
}
|