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/route.cc
ADDED
@@ -0,0 +1,325 @@
|
|
1
|
+
extern "C" {
|
2
|
+
#include "nyara.h"
|
3
|
+
}
|
4
|
+
#include <ruby/re.h>
|
5
|
+
#include <ruby/encoding.h>
|
6
|
+
#include <vector>
|
7
|
+
#include <map>
|
8
|
+
#include "inc/str_intern.h"
|
9
|
+
|
10
|
+
struct RouteEntry {
|
11
|
+
// note on order: scope is supposed to be the last, but when searching, is_sub is checked first
|
12
|
+
bool is_sub; // = last_prefix.start_with? prefix
|
13
|
+
char* prefix;
|
14
|
+
long prefix_len;
|
15
|
+
regex_t *suffix_re;
|
16
|
+
VALUE controller;
|
17
|
+
VALUE id; // symbol
|
18
|
+
VALUE accept_exts; // {ext => true}
|
19
|
+
VALUE accept_mimes; // [[m1, m2, ext]]
|
20
|
+
std::vector<ID> conv;
|
21
|
+
VALUE scope;
|
22
|
+
char* suffix; // only for inspect
|
23
|
+
long suffix_len;
|
24
|
+
|
25
|
+
// don't make it destructor, or it could be called twice if on stack
|
26
|
+
void dealloc() {
|
27
|
+
if (prefix) {
|
28
|
+
xfree(prefix);
|
29
|
+
prefix = NULL;
|
30
|
+
}
|
31
|
+
if (suffix_re) {
|
32
|
+
onig_free(suffix_re);
|
33
|
+
suffix_re = NULL;
|
34
|
+
}
|
35
|
+
if (suffix) {
|
36
|
+
xfree(suffix);
|
37
|
+
suffix = NULL;
|
38
|
+
}
|
39
|
+
}
|
40
|
+
};
|
41
|
+
|
42
|
+
typedef std::vector<RouteEntry> RouteEntries;
|
43
|
+
static std::map<enum http_method, RouteEntries*> route_map;
|
44
|
+
static OnigRegion region; // we can reuse the region without worrying thread safety
|
45
|
+
static ID id_to_s;
|
46
|
+
static rb_encoding* u8_enc;
|
47
|
+
static VALUE str_html;
|
48
|
+
static VALUE nyara_http_methods;
|
49
|
+
|
50
|
+
static bool start_with(const char* a, long a_len, const char* b, long b_len) {
|
51
|
+
if (b_len > a_len) {
|
52
|
+
return false;
|
53
|
+
}
|
54
|
+
for (size_t i = 0; i < b_len; i++) {
|
55
|
+
if (a[i] != b[i]) {
|
56
|
+
return false;
|
57
|
+
}
|
58
|
+
}
|
59
|
+
return true;
|
60
|
+
}
|
61
|
+
|
62
|
+
static enum http_method canonicalize_http_method(VALUE m) {
|
63
|
+
VALUE method_num;
|
64
|
+
if (TYPE(m) == T_STRING) {
|
65
|
+
method_num = rb_hash_aref(nyara_http_methods, m);
|
66
|
+
} else {
|
67
|
+
method_num = m;
|
68
|
+
}
|
69
|
+
Check_Type(method_num, T_FIXNUM);
|
70
|
+
return (enum http_method)FIX2INT(method_num);
|
71
|
+
}
|
72
|
+
|
73
|
+
static VALUE ext_clear_route(VALUE req) {
|
74
|
+
for (auto i = route_map.begin(); i != route_map.end(); ++i) {
|
75
|
+
RouteEntries* entries = i->second;
|
76
|
+
for (auto j = entries->begin(); j != entries->end(); ++j) {
|
77
|
+
j->dealloc();
|
78
|
+
}
|
79
|
+
delete entries;
|
80
|
+
}
|
81
|
+
route_map.clear();
|
82
|
+
return Qnil;
|
83
|
+
}
|
84
|
+
|
85
|
+
static VALUE ext_register_route(VALUE self, VALUE v_e) {
|
86
|
+
// get route entries
|
87
|
+
enum http_method m = canonicalize_http_method(rb_iv_get(v_e, "@http_method"));
|
88
|
+
RouteEntries* route_entries;
|
89
|
+
auto map_iter = route_map.find(m);
|
90
|
+
if (map_iter == route_map.end()) {
|
91
|
+
route_entries = new RouteEntries();
|
92
|
+
route_map[m] = route_entries;
|
93
|
+
} else {
|
94
|
+
route_entries = map_iter->second;
|
95
|
+
}
|
96
|
+
|
97
|
+
// prefix
|
98
|
+
VALUE v_prefix = rb_iv_get(v_e, "@prefix");
|
99
|
+
long prefix_len = RSTRING_LEN(v_prefix);
|
100
|
+
char* prefix = ALLOC_N(char, prefix_len);
|
101
|
+
memcpy(prefix, RSTRING_PTR(v_prefix), prefix_len);
|
102
|
+
|
103
|
+
// check if prefix is substring of last entry
|
104
|
+
bool is_sub = false;
|
105
|
+
if (route_entries->size()) {
|
106
|
+
is_sub = start_with(route_entries->rbegin()->prefix, route_entries->rbegin()->prefix_len, prefix, prefix_len);
|
107
|
+
}
|
108
|
+
|
109
|
+
// suffix
|
110
|
+
VALUE v_suffix = rb_iv_get(v_e, "@suffix");
|
111
|
+
long suffix_len = RSTRING_LEN(v_suffix);
|
112
|
+
char* suffix = ALLOC_N(char, suffix_len);
|
113
|
+
memcpy(suffix, RSTRING_PTR(v_suffix), suffix_len);
|
114
|
+
regex_t* suffix_re;
|
115
|
+
OnigErrorInfo err_info;
|
116
|
+
onig_new(&suffix_re, (const UChar*)suffix, (const UChar*)(suffix + suffix_len),
|
117
|
+
ONIG_OPTION_NONE, ONIG_ENCODING_ASCII, ONIG_SYNTAX_RUBY, &err_info);
|
118
|
+
|
119
|
+
std::vector<ID> _conv;
|
120
|
+
RouteEntry e;
|
121
|
+
e.is_sub = is_sub;
|
122
|
+
e.prefix = prefix;
|
123
|
+
e.prefix_len = prefix_len;
|
124
|
+
e.suffix_re = suffix_re;
|
125
|
+
e.suffix = suffix;
|
126
|
+
e.suffix_len = suffix_len;
|
127
|
+
e.controller = rb_iv_get(v_e, "@controller");
|
128
|
+
e.id = rb_iv_get(v_e, "@id");
|
129
|
+
e.conv = _conv;
|
130
|
+
e.scope = rb_iv_get(v_e, "@scope");
|
131
|
+
|
132
|
+
// conv
|
133
|
+
VALUE v_conv = rb_iv_get(v_e, "@conv");
|
134
|
+
VALUE* conv_ptr = RARRAY_PTR(v_conv);
|
135
|
+
long conv_len = RARRAY_LEN(v_conv);
|
136
|
+
if (onig_number_of_captures(suffix_re) != conv_len) {
|
137
|
+
e.dealloc();
|
138
|
+
rb_raise(rb_eRuntimeError, "number of captures mismatch");
|
139
|
+
}
|
140
|
+
for (long i = 0; i < conv_len; i++) {
|
141
|
+
ID conv_id = SYM2ID(conv_ptr[i]);
|
142
|
+
e.conv.push_back(conv_id);
|
143
|
+
}
|
144
|
+
|
145
|
+
// accept
|
146
|
+
e.accept_exts = rb_iv_get(v_e, "@accept_exts");
|
147
|
+
e.accept_mimes = rb_iv_get(v_e, "@accept_mimes");
|
148
|
+
|
149
|
+
route_entries->push_back(e);
|
150
|
+
return Qnil;
|
151
|
+
}
|
152
|
+
|
153
|
+
static VALUE ext_list_route(VALUE self) {
|
154
|
+
// note: prevent leak with init nil
|
155
|
+
volatile VALUE arr = Qnil;
|
156
|
+
volatile VALUE e = Qnil;
|
157
|
+
volatile VALUE prefix = Qnil;
|
158
|
+
volatile VALUE conv = Qnil;
|
159
|
+
|
160
|
+
volatile VALUE route_hash = rb_hash_new();
|
161
|
+
for (auto j = route_map.begin(); j != route_map.end(); j++) {
|
162
|
+
RouteEntries* route_entries = j->second;
|
163
|
+
VALUE arr = rb_ary_new();
|
164
|
+
rb_hash_aset(route_hash, rb_str_new2(http_method_str(j->first)), arr);
|
165
|
+
for (auto i = route_entries->begin(); i != route_entries->end(); i++) {
|
166
|
+
e = rb_ary_new();
|
167
|
+
rb_ary_push(e, i->is_sub ? Qtrue : Qfalse);
|
168
|
+
rb_ary_push(e, i->scope);
|
169
|
+
rb_ary_push(e, rb_str_new(i->prefix, i->prefix_len));
|
170
|
+
rb_ary_push(e, rb_str_new(i->suffix, i->suffix_len));
|
171
|
+
rb_ary_push(e, i->controller);
|
172
|
+
rb_ary_push(e, i->id);
|
173
|
+
conv = rb_ary_new();
|
174
|
+
for (size_t j = 0; j < i->conv.size(); j++) {
|
175
|
+
rb_ary_push(conv, ID2SYM(i->conv[j]));
|
176
|
+
}
|
177
|
+
rb_ary_push(e, conv);
|
178
|
+
rb_ary_push(arr, e);
|
179
|
+
}
|
180
|
+
}
|
181
|
+
return route_hash;
|
182
|
+
}
|
183
|
+
|
184
|
+
static VALUE build_args(const char* suffix, std::vector<ID>& conv) {
|
185
|
+
volatile VALUE args = rb_ary_new();
|
186
|
+
volatile VALUE str = rb_str_new2("");
|
187
|
+
long last_len = 0;
|
188
|
+
for (size_t j = 0; j < conv.size(); j++) {
|
189
|
+
const char* capture_ptr = suffix + region.beg[j+1];
|
190
|
+
long capture_len = region.end[j+1] - region.beg[j+1];
|
191
|
+
if (conv[j] == id_to_s) {
|
192
|
+
rb_ary_push(args, rb_enc_str_new(capture_ptr, capture_len, u8_enc));
|
193
|
+
} else if (capture_len == 0) {
|
194
|
+
rb_ary_push(args, Qnil);
|
195
|
+
} else {
|
196
|
+
if (capture_len > last_len) {
|
197
|
+
RESIZE_CAPA(str, capture_len);
|
198
|
+
last_len = capture_len;
|
199
|
+
}
|
200
|
+
memcpy(RSTRING_PTR(str), capture_ptr, capture_len);
|
201
|
+
STR_SET_LEN(str, capture_len);
|
202
|
+
rb_ary_push(args, rb_funcall(str, conv[j], 0)); // hex, to_i, to_f
|
203
|
+
}
|
204
|
+
}
|
205
|
+
return args;
|
206
|
+
}
|
207
|
+
|
208
|
+
static VALUE extract_ext(const char* s, long len) {
|
209
|
+
if (s[0] != '.') {
|
210
|
+
return Qnil;
|
211
|
+
}
|
212
|
+
s++;
|
213
|
+
len--;
|
214
|
+
for (long i = 0; i < len; i++) {
|
215
|
+
if (!isalnum(s[i])) {
|
216
|
+
return Qnil;
|
217
|
+
}
|
218
|
+
}
|
219
|
+
return rb_str_new(s, len);
|
220
|
+
}
|
221
|
+
|
222
|
+
extern "C"
|
223
|
+
RouteResult nyara_lookup_route(enum http_method method_num, VALUE vpath) {
|
224
|
+
RouteResult r = {Qnil, Qnil, Qnil, Qnil};
|
225
|
+
auto map_iter = route_map.find(method_num);
|
226
|
+
if (map_iter == route_map.end()) {
|
227
|
+
return r;
|
228
|
+
}
|
229
|
+
RouteEntries* route_entries = map_iter->second;
|
230
|
+
|
231
|
+
const char* path = RSTRING_PTR(vpath);
|
232
|
+
long len = RSTRING_LEN(vpath);
|
233
|
+
// must iterate all
|
234
|
+
bool last_matched = false;
|
235
|
+
auto i = route_entries->begin();
|
236
|
+
for (; i != route_entries->end(); ++i) {
|
237
|
+
bool matched;
|
238
|
+
if (i->is_sub && last_matched) { // save a bit compare
|
239
|
+
matched = last_matched;
|
240
|
+
} else {
|
241
|
+
matched = start_with(path, len, i->prefix, i->prefix_len);
|
242
|
+
}
|
243
|
+
last_matched = matched;
|
244
|
+
|
245
|
+
if (/* prefix */ matched) {
|
246
|
+
const char* suffix = path + i->prefix_len;
|
247
|
+
long suffix_len = len - i->prefix_len;
|
248
|
+
if (i->suffix_len == 0) {
|
249
|
+
if (suffix_len) {
|
250
|
+
r.ext = extract_ext(suffix, suffix_len);
|
251
|
+
if (r.ext == Qnil) {
|
252
|
+
break;
|
253
|
+
}
|
254
|
+
}
|
255
|
+
r.args = rb_ary_new3(1, i->id);
|
256
|
+
r.controller = i->controller;
|
257
|
+
break;
|
258
|
+
} else {
|
259
|
+
long matched_len = onig_match(i->suffix_re, (const UChar*)suffix, (const UChar*)(suffix + suffix_len),
|
260
|
+
(const UChar*)suffix, ®ion, 0);
|
261
|
+
if (matched_len > 0) {
|
262
|
+
if (matched_len < suffix_len) {
|
263
|
+
r.ext = extract_ext(suffix + matched_len, suffix_len);
|
264
|
+
if (r.ext == Qnil) {
|
265
|
+
break;
|
266
|
+
}
|
267
|
+
}
|
268
|
+
r.args = build_args(suffix, i->conv);
|
269
|
+
rb_ary_push(r.args, i->id);
|
270
|
+
r.controller = i->controller;
|
271
|
+
break;
|
272
|
+
}
|
273
|
+
}
|
274
|
+
}
|
275
|
+
}
|
276
|
+
|
277
|
+
if (r.controller != Qnil) {
|
278
|
+
r.scope = i->scope;
|
279
|
+
|
280
|
+
if (r.ext == Qnil) {
|
281
|
+
if (i->accept_exts == Qnil) {
|
282
|
+
r.ext = str_html;
|
283
|
+
} else {
|
284
|
+
// NOTE maybe rejected
|
285
|
+
r.ext = i->accept_mimes;
|
286
|
+
}
|
287
|
+
} else {
|
288
|
+
if (i->accept_exts != Qnil) {
|
289
|
+
if (!RTEST(rb_hash_aref(i->accept_exts, r.ext))) {
|
290
|
+
r.controller = Qnil; // reject if ext mismatch
|
291
|
+
}
|
292
|
+
}
|
293
|
+
}
|
294
|
+
}
|
295
|
+
return r;
|
296
|
+
}
|
297
|
+
|
298
|
+
static VALUE ext_lookup_route(VALUE self, VALUE method, VALUE path) {
|
299
|
+
enum http_method method_num = canonicalize_http_method(method);
|
300
|
+
volatile RouteResult r = nyara_lookup_route(method_num, path);
|
301
|
+
volatile VALUE a = rb_ary_new();
|
302
|
+
rb_ary_push(a, r.scope);
|
303
|
+
rb_ary_push(a, r.controller);
|
304
|
+
rb_ary_push(a, r.args);
|
305
|
+
rb_ary_push(a, r.ext);
|
306
|
+
return a;
|
307
|
+
}
|
308
|
+
|
309
|
+
extern "C"
|
310
|
+
void Init_route(VALUE nyara, VALUE ext) {
|
311
|
+
nyara_http_methods = rb_const_get(nyara, rb_intern("HTTP_METHODS"));
|
312
|
+
id_to_s = rb_intern("to_s");
|
313
|
+
u8_enc = rb_utf8_encoding();
|
314
|
+
str_html = rb_str_new2("html");
|
315
|
+
OBJ_FREEZE(str_html);
|
316
|
+
rb_gc_register_mark_object(str_html);
|
317
|
+
onig_region_init(®ion);
|
318
|
+
|
319
|
+
rb_define_singleton_method(ext, "register_route", RUBY_METHOD_FUNC(ext_register_route), 1);
|
320
|
+
rb_define_singleton_method(ext, "clear_route", RUBY_METHOD_FUNC(ext_clear_route), 0);
|
321
|
+
|
322
|
+
// for test
|
323
|
+
rb_define_singleton_method(ext, "list_route", RUBY_METHOD_FUNC(ext_list_route), 0);
|
324
|
+
rb_define_singleton_method(ext, "lookup_route", RUBY_METHOD_FUNC(ext_lookup_route), 2);
|
325
|
+
}
|
data/ext/url_encoded.c
ADDED
@@ -0,0 +1,304 @@
|
|
1
|
+
// parse path / query / url-encoded body
|
2
|
+
#include "nyara.h"
|
3
|
+
#include <ruby/encoding.h>
|
4
|
+
|
5
|
+
static rb_encoding* u8_encoding;
|
6
|
+
|
7
|
+
static char _half_octet(char c) {
|
8
|
+
// there's a faster way but not validating the range:
|
9
|
+
// #define hex2c(c) ((c | 32) % 39 - 9)
|
10
|
+
if (c >= '0' && c <= '9') {
|
11
|
+
return c - '0';
|
12
|
+
} else if (c >= 'A' && c <= 'F') {
|
13
|
+
return c - 'A' + 10;
|
14
|
+
} else if (c >= 'a' && c <= 'f') {
|
15
|
+
return c - 'a' + 10;
|
16
|
+
} else {
|
17
|
+
return -1;
|
18
|
+
}
|
19
|
+
}
|
20
|
+
|
21
|
+
static size_t _decode_url_seg(VALUE path, const char*s, size_t len, char stop_char) {
|
22
|
+
const char* last_s = s;
|
23
|
+
long last_len = 0;
|
24
|
+
|
25
|
+
# define FLUSH_UNESCAPED\
|
26
|
+
if (last_len) {\
|
27
|
+
rb_str_cat(path, last_s, last_len);\
|
28
|
+
last_s += last_len;\
|
29
|
+
last_len = 0;\
|
30
|
+
}
|
31
|
+
|
32
|
+
size_t i;
|
33
|
+
for (i = 0; i < len; i++) {
|
34
|
+
if (s[i] == '%') {
|
35
|
+
if (i + 2 >= len) {
|
36
|
+
last_len++;
|
37
|
+
continue;
|
38
|
+
}
|
39
|
+
char r1 = _half_octet(s[i + 1]);
|
40
|
+
if (r1 < 0) {
|
41
|
+
last_len++;
|
42
|
+
continue;
|
43
|
+
}
|
44
|
+
char r2 = _half_octet(s[i + 2]);
|
45
|
+
if (r2 < 0) {
|
46
|
+
last_len++;
|
47
|
+
continue;
|
48
|
+
}
|
49
|
+
i += 2;
|
50
|
+
unsigned char r = ((unsigned char)r1 << 4) | (unsigned char)r2;
|
51
|
+
FLUSH_UNESCAPED;
|
52
|
+
last_s += 3;
|
53
|
+
rb_str_cat(path, (char*)&r, 1);
|
54
|
+
|
55
|
+
} else if (s[i] == stop_char) {
|
56
|
+
i++;
|
57
|
+
break;
|
58
|
+
|
59
|
+
} else if (s[i] == '+') {
|
60
|
+
FLUSH_UNESCAPED;
|
61
|
+
rb_str_cat(path, " ", 1);
|
62
|
+
|
63
|
+
} else {
|
64
|
+
last_len++;
|
65
|
+
}
|
66
|
+
}
|
67
|
+
FLUSH_UNESCAPED;
|
68
|
+
# undef FLUSH_UNESCAPED
|
69
|
+
|
70
|
+
return i;
|
71
|
+
}
|
72
|
+
|
73
|
+
// return parsed len, s + return == start of query
|
74
|
+
size_t nyara_parse_path(VALUE output, const char* s, size_t len) {
|
75
|
+
return _decode_url_seg(output, s, len, '?');
|
76
|
+
}
|
77
|
+
|
78
|
+
static VALUE ext_parse_path(VALUE self, VALUE output, VALUE input) {
|
79
|
+
size_t parsed = nyara_parse_path(output, RSTRING_PTR(input), RSTRING_LEN(input));
|
80
|
+
return ULONG2NUM(parsed);
|
81
|
+
}
|
82
|
+
|
83
|
+
static void _error(const char* msg, const char* s, long len, long segment_i) {
|
84
|
+
rb_raise(rb_eRuntimeError,
|
85
|
+
"error parsing \"%.*s\": segments[%ld] is %s",
|
86
|
+
(int)len, s, segment_i, msg);
|
87
|
+
}
|
88
|
+
|
89
|
+
static VALUE _new_child(long hash) {
|
90
|
+
return hash ? rb_class_new_instance(0, NULL, nyara_param_hash_class) : rb_ary_new();
|
91
|
+
}
|
92
|
+
|
93
|
+
// a, b, c = keys; h[a][b][c] = value
|
94
|
+
// the last 2 args are for error report
|
95
|
+
static void _aset_keys(VALUE output, VALUE keys, VALUE value, const char* kv_s, long kv_len) {
|
96
|
+
VALUE* arr = RARRAY_PTR(keys);
|
97
|
+
long len = RARRAY_LEN(keys);
|
98
|
+
if (!len) {
|
99
|
+
rb_bug("bug: aset 0 length key");
|
100
|
+
return;
|
101
|
+
}
|
102
|
+
|
103
|
+
// first key seg
|
104
|
+
volatile VALUE key = arr[0];
|
105
|
+
long is_hash_key = 1;
|
106
|
+
|
107
|
+
// middle key segs
|
108
|
+
for (long i = 0; i < len - 1; i++) {
|
109
|
+
key = arr[i];
|
110
|
+
long next_is_hash_key = RSTRING_LEN(arr[i + 1]);
|
111
|
+
if (is_hash_key) {
|
112
|
+
if (nyara_rb_hash_has_key(output, key)) {
|
113
|
+
output = rb_hash_aref(output, key);
|
114
|
+
if (next_is_hash_key) {
|
115
|
+
if (TYPE(output) != T_HASH) {
|
116
|
+
// note: StringValueCStr requires VALUE* as param, and can raise another error if there's nul in the string
|
117
|
+
_error("not array index (expect to be empty)", kv_s, kv_len, i);
|
118
|
+
}
|
119
|
+
} else {
|
120
|
+
if (TYPE(output) != T_ARRAY) {
|
121
|
+
_error("not hash key (expect to be non-empty)", kv_s, kv_len, i);
|
122
|
+
}
|
123
|
+
}
|
124
|
+
} else {
|
125
|
+
volatile VALUE child = _new_child(next_is_hash_key);
|
126
|
+
rb_hash_aset(output, key, child);
|
127
|
+
output = child;
|
128
|
+
}
|
129
|
+
} else {
|
130
|
+
volatile VALUE child = _new_child(next_is_hash_key);
|
131
|
+
rb_ary_push(output, child);
|
132
|
+
output = child;
|
133
|
+
}
|
134
|
+
is_hash_key = next_is_hash_key;
|
135
|
+
}
|
136
|
+
|
137
|
+
// terminate key seg: add value
|
138
|
+
key = arr[len - 1];
|
139
|
+
if (is_hash_key) {
|
140
|
+
rb_hash_aset(output, key, value);
|
141
|
+
} else {
|
142
|
+
rb_ary_push(output, value);
|
143
|
+
}
|
144
|
+
}
|
145
|
+
|
146
|
+
static const char* _strnchr(const char* s, long len, char c) {
|
147
|
+
for (long i = 0; i < len; i++) {
|
148
|
+
if (s[i] == c) {
|
149
|
+
return s + i;
|
150
|
+
}
|
151
|
+
}
|
152
|
+
return NULL;
|
153
|
+
}
|
154
|
+
|
155
|
+
static inline VALUE _new_blank_str() {
|
156
|
+
return rb_enc_str_new("", 0, u8_encoding);
|
157
|
+
}
|
158
|
+
|
159
|
+
static void _url_encoded_seg(VALUE output, const char* kv_s, long kv_len, int nested_mode) {
|
160
|
+
// (note if we _decode_url_seg with '&' first, then there may be multiple '='s in one kv)
|
161
|
+
const char* s = kv_s;
|
162
|
+
long len = kv_len;
|
163
|
+
if (!len) {
|
164
|
+
return;
|
165
|
+
}
|
166
|
+
|
167
|
+
volatile VALUE value = _new_blank_str();
|
168
|
+
|
169
|
+
// rule out the value part
|
170
|
+
{
|
171
|
+
// strnstr is not available on linux :(
|
172
|
+
const char* value_s = _strnchr(s, len, '=');
|
173
|
+
if (value_s) {
|
174
|
+
value_s++;
|
175
|
+
long value_len = s + len - value_s;
|
176
|
+
long parsed = _decode_url_seg(value, value_s, value_len, '&');
|
177
|
+
if (parsed != value_len) {
|
178
|
+
rb_raise(rb_eArgError, "separator & in param segment");
|
179
|
+
}
|
180
|
+
len = value_s - s - 1;
|
181
|
+
}
|
182
|
+
// starts with '='
|
183
|
+
if (value_s == s) {
|
184
|
+
rb_hash_aset(output, _new_blank_str(), value);
|
185
|
+
return;
|
186
|
+
}
|
187
|
+
}
|
188
|
+
|
189
|
+
volatile VALUE key = _new_blank_str();
|
190
|
+
if (nested_mode) {
|
191
|
+
// todo fault-tolerant?
|
192
|
+
long parsed = _decode_url_seg(key, s, len, '[');
|
193
|
+
if (parsed == len) {
|
194
|
+
rb_hash_aset(output, key, value);
|
195
|
+
return;
|
196
|
+
}
|
197
|
+
s += parsed;
|
198
|
+
len -= parsed;
|
199
|
+
volatile VALUE keys = rb_ary_new3(1, key);
|
200
|
+
while (len) {
|
201
|
+
key = _new_blank_str();
|
202
|
+
parsed = _decode_url_seg(key, s, len, ']');
|
203
|
+
rb_ary_push(keys, key);
|
204
|
+
s += parsed;
|
205
|
+
len -= parsed;
|
206
|
+
if (len) {
|
207
|
+
if (s[0] == '[') {
|
208
|
+
s++;
|
209
|
+
len--;
|
210
|
+
} else {
|
211
|
+
rb_raise(rb_eRuntimeError, "malformed params: remaining chars in key but not starting with '['");
|
212
|
+
return;
|
213
|
+
}
|
214
|
+
}
|
215
|
+
}
|
216
|
+
_aset_keys(output, keys, value, kv_s, kv_len);
|
217
|
+
} else {
|
218
|
+
_decode_url_seg(key, s, len, '=');
|
219
|
+
rb_hash_aset(output, key, value);
|
220
|
+
}
|
221
|
+
|
222
|
+
return;
|
223
|
+
}
|
224
|
+
|
225
|
+
static VALUE ext_parse_url_encoded_seg(VALUE self, VALUE output, VALUE kv, VALUE v_nested_mode) {
|
226
|
+
_url_encoded_seg(output, RSTRING_PTR(kv), RSTRING_LEN(kv), RTEST(v_nested_mode));
|
227
|
+
return output;
|
228
|
+
}
|
229
|
+
|
230
|
+
void nyara_parse_param(VALUE output, const char* s, size_t len) {
|
231
|
+
// split with /[&;] */
|
232
|
+
size_t last_i = 0;
|
233
|
+
size_t i = 0;
|
234
|
+
for (; i < len; i++) {
|
235
|
+
if (s[i] == '&' || s[i] == ';') {
|
236
|
+
if (i > last_i) {
|
237
|
+
_url_encoded_seg(output, s + last_i, i - last_i, 1);
|
238
|
+
}
|
239
|
+
while(i + 1 < len && s[i + 1] == ' ') {
|
240
|
+
i++;
|
241
|
+
}
|
242
|
+
last_i = i + 1;
|
243
|
+
}
|
244
|
+
}
|
245
|
+
if (i > last_i) {
|
246
|
+
_url_encoded_seg(output, s + last_i, i - last_i, 1);
|
247
|
+
}
|
248
|
+
}
|
249
|
+
|
250
|
+
static VALUE ext_parse_param(VALUE self, VALUE output, VALUE s) {
|
251
|
+
nyara_parse_param(output, RSTRING_PTR(s), RSTRING_LEN(s));
|
252
|
+
return output;
|
253
|
+
}
|
254
|
+
|
255
|
+
static VALUE _cookie_seg_str_new(const char* s, long len) {
|
256
|
+
// trim tailing space
|
257
|
+
for (; len > 0; len--) {
|
258
|
+
if (s[len - 1] != ' ') {
|
259
|
+
break;
|
260
|
+
}
|
261
|
+
}
|
262
|
+
return rb_enc_str_new(s, len, u8_encoding);
|
263
|
+
}
|
264
|
+
|
265
|
+
static VALUE ext_parse_cookie(VALUE self, VALUE output, VALUE str) {
|
266
|
+
volatile VALUE arr = rb_ary_new();
|
267
|
+
const char* s = RSTRING_PTR(str);
|
268
|
+
size_t len = RSTRING_LEN(str);
|
269
|
+
|
270
|
+
// split with / *[,;] */
|
271
|
+
size_t last_i = 0;
|
272
|
+
size_t i = 0;
|
273
|
+
for (; i < len; i++) {
|
274
|
+
if (s[i] == ',' || s[i] == ';') {
|
275
|
+
// char* and len parse_seg
|
276
|
+
if (i > last_i) {
|
277
|
+
rb_ary_push(arr, _cookie_seg_str_new(s + last_i, i - last_i));
|
278
|
+
}
|
279
|
+
while(i + 1 < len && s[i + 1] == ' ') {
|
280
|
+
i++;
|
281
|
+
}
|
282
|
+
last_i = i + 1;
|
283
|
+
}
|
284
|
+
}
|
285
|
+
if (i > last_i) {
|
286
|
+
rb_ary_push(arr, _cookie_seg_str_new(s + last_i, i - last_i));
|
287
|
+
}
|
288
|
+
|
289
|
+
VALUE* arr_p = RARRAY_PTR(arr);
|
290
|
+
for (long j = RARRAY_LEN(arr) - 1; j >= 0; j--) {
|
291
|
+
_url_encoded_seg(output, RSTRING_PTR(arr_p[j]), RSTRING_LEN(arr_p[j]), 0);
|
292
|
+
}
|
293
|
+
return output;
|
294
|
+
}
|
295
|
+
|
296
|
+
void Init_url_encoded(VALUE ext) {
|
297
|
+
u8_encoding = rb_utf8_encoding();
|
298
|
+
|
299
|
+
rb_define_singleton_method(ext, "parse_param", ext_parse_param, 2);
|
300
|
+
rb_define_singleton_method(ext, "parse_cookie", ext_parse_cookie, 2);
|
301
|
+
// for test
|
302
|
+
rb_define_singleton_method(ext, "parse_url_encoded_seg", ext_parse_url_encoded_seg, 3);
|
303
|
+
rb_define_singleton_method(ext, "parse_path", ext_parse_path, 2);
|
304
|
+
}
|
data/hello.rb
ADDED
data/lib/nyara/config.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
module Nyara
|
2
|
+
# other options:
|
3
|
+
# - session (see also Session)
|
4
|
+
# - host
|
5
|
+
# - views
|
6
|
+
# - public
|
7
|
+
Config = ConfigHash.new
|
8
|
+
class << Config
|
9
|
+
def map prefix, controller
|
10
|
+
Route.register_controller prefix, controller
|
11
|
+
end
|
12
|
+
|
13
|
+
def port n
|
14
|
+
n = n.to_i
|
15
|
+
assert n >= 0 && n <= 65535
|
16
|
+
Config['port'] = n
|
17
|
+
end
|
18
|
+
|
19
|
+
def workers n
|
20
|
+
n = n.to_i
|
21
|
+
assert n > 0 && n < 1000
|
22
|
+
Config['workers'] = n
|
23
|
+
end
|
24
|
+
|
25
|
+
def env
|
26
|
+
self['env'].to_s
|
27
|
+
end
|
28
|
+
|
29
|
+
def development?
|
30
|
+
e = env
|
31
|
+
e.empty? or e == 'development'
|
32
|
+
end
|
33
|
+
|
34
|
+
def production?
|
35
|
+
env == 'production'
|
36
|
+
end
|
37
|
+
|
38
|
+
def test?
|
39
|
+
env == 'test'
|
40
|
+
end
|
41
|
+
|
42
|
+
alias set []=
|
43
|
+
alias get []
|
44
|
+
|
45
|
+
def assert expr
|
46
|
+
raise ArgumentError unless expr
|
47
|
+
end
|
48
|
+
|
49
|
+
# todo env aware configure
|
50
|
+
def configure &blk
|
51
|
+
instance_eval &blk
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def configure *xs, &blk
|
57
|
+
Nyara::Config.configure *xs, &blk
|
58
|
+
end
|
59
|
+
|
60
|
+
configure do
|
61
|
+
set 'env', 'development'
|
62
|
+
set 'views', 'views'
|
63
|
+
set 'public', 'public'
|
64
|
+
end
|