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/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
|