iodine 0.8.2 → 0.8.3
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 +4 -4
- data/CHANGELOG.md +12 -0
- data/ext/iodine/fio-stl.h +6101 -3276
- data/ext/iodine/fio-stl.md +5155 -3213
- data/ext/iodine/iodine.c +1 -0
- data/ext/iodine/iodine.h +9 -4
- data/ext/iodine/iodine_connection.h +20 -1
- data/ext/iodine/iodine_json.h +469 -160
- data/ext/iodine/iodine_params.h +1362 -0
- data/lib/iodine/documentation.rb +121 -0
- data/lib/iodine/version.rb +2 -1
- metadata +3 -2
|
@@ -0,0 +1,1362 @@
|
|
|
1
|
+
#ifndef H___IODINE_PARAMS___H
|
|
2
|
+
#define H___IODINE_PARAMS___H
|
|
3
|
+
/* *****************************************************************************
|
|
4
|
+
|
|
5
|
+
Iodine::Connection — NeoRack `params`
|
|
6
|
+
|
|
7
|
+
Implements the NeoRack `params` extension (v0.0.1) on top of the C STL HTTP
|
|
8
|
+
params helpers (`fio_http_query_parse` / `fio_http_body_parse` in fio-stl.h):
|
|
9
|
+
|
|
10
|
+
e.params # => Hash (query first, then body; memoized per request)
|
|
11
|
+
e.query2params # => merges the query string (once)
|
|
12
|
+
e.body2params # => merges a supported body (once)
|
|
13
|
+
|
|
14
|
+
The same mutable, request-scoped Hash backs all three methods; it lives in the
|
|
15
|
+
connection's internal store (never in the app-facing `e[...]` KV map).
|
|
16
|
+
|
|
17
|
+
Design notes:
|
|
18
|
+
|
|
19
|
+
- Query fields parse directly into the request Hash. Body sources parse into a
|
|
20
|
+
staged Hash and merge only after the C parser returns successfully. This
|
|
21
|
+
prevents incomplete JSON members and aborted multipart files from becoming
|
|
22
|
+
visible, and lets Ruby exceptions propagate after parser-owned C resources
|
|
23
|
+
have been released.
|
|
24
|
+
- Conflict resolution is performed by this adapter using Ruby values. JSON
|
|
25
|
+
false uses Qundef only while crossing the STL's void-pointer callback API;
|
|
26
|
+
it is decoded before storage. No Ruby object is ever used as a NULL/absence
|
|
27
|
+
sentinel in the merge logic.
|
|
28
|
+
- Parser callbacks protect every allocating Ruby operation. An exception is
|
|
29
|
+
rooted in the connection, parsing stops through normal C error paths, and
|
|
30
|
+
the original exception is re-raised after the parser returns. rb_ensure
|
|
31
|
+
restores body position and temporary roots. GC remains enabled.
|
|
32
|
+
- Multipart file parts are `Iodine::Base::Upload` objects (an internal class):
|
|
33
|
+
a binary stream (`read` / `rewind` / `size` / `save` / `close` / `closed?`)
|
|
34
|
+
with metadata readers and Rack-style `[]` / `[]=` access. Uploads are scalar
|
|
35
|
+
"other" values for conflict resolution, not Hashes.
|
|
36
|
+
- Upload data lives in an `fio_data_s` store (RAM, spilling to a temporary
|
|
37
|
+
file past FIO_DATA_RAM_LIMIT). A non-owning intrusive list indexes open
|
|
38
|
+
uploads for deterministic request-end cleanup. The list never marks Ruby
|
|
39
|
+
objects; params / staging roots and the active-upload slot provide GC roots.
|
|
40
|
+
Manual close, request cleanup and Upload finalization share one idempotent
|
|
41
|
+
close/unlink path. Data operations share a native mutex with no-GVL saves.
|
|
42
|
+
- The Rack compatibility map is embedded directly in Upload. Required keys
|
|
43
|
+
read metadata directly unless a view-only override exists; MiniMap buckets
|
|
44
|
+
are allocated only for String / Symbol writes. Other key types use a lazy
|
|
45
|
+
Hash fallback so compatibility access keeps ordinary Hash semantics.
|
|
46
|
+
|
|
47
|
+
Field budget:
|
|
48
|
+
|
|
49
|
+
- Query pairs, form fields and multipart parts share one request budget. Each
|
|
50
|
+
parse is allowed one extra field; a reported count above the remaining
|
|
51
|
+
budget proves overflow without confusing malformed input or empty segments
|
|
52
|
+
with excess fields. JSON is not counted (STL behavior).
|
|
53
|
+
|
|
54
|
+
Parser errors and remaining contract deviation:
|
|
55
|
+
|
|
56
|
+
- Depth and generic query / URL-encoded parser failures raise RangeError
|
|
57
|
+
after field accounting; body-read failures raise IOError. JSON and multipart
|
|
58
|
+
ERROR combine malformed / generic failures and discard the staged body.
|
|
59
|
+
Invalid percent escapes remain literal. Direct JSON preserves the parser's
|
|
60
|
+
integer error but handles every parse error as staged discard, as permitted.
|
|
61
|
+
- The query / form name-depth bound is FIO_JSON_MAX_DEPTH (128), not the
|
|
62
|
+
extension's suggested default of 32.
|
|
63
|
+
|
|
64
|
+
***************************************************************************** */
|
|
65
|
+
|
|
66
|
+
/* *****************************************************************************
|
|
67
|
+
Upload (NeoRack `params` extension Upload interface)
|
|
68
|
+
***************************************************************************** */
|
|
69
|
+
|
|
70
|
+
typedef struct iodine_upload_s {
|
|
71
|
+
fio_data_s data; /* RAM store; spills to a temporary file past the limit */
|
|
72
|
+
VALUE filename; /* String (client basename, path components stripped) */
|
|
73
|
+
VALUE type; /* String or Qnil */
|
|
74
|
+
VALUE name; /* String (form field name) */
|
|
75
|
+
VALUE headers; /* String (raw part headers) or Qnil */
|
|
76
|
+
VALUE rack_fallback; /* Lazy Hash for non-String / non-Symbol keys */
|
|
77
|
+
iodine_minimap_s rack_view; /* Embedded; buckets allocated only when used */
|
|
78
|
+
fio_thread_mutex_t lock; /* Serializes data operations and no-GVL save */
|
|
79
|
+
struct iodine_upload_s *next;
|
|
80
|
+
struct iodine_upload_s **prev; /* Address of the link pointing to this node */
|
|
81
|
+
uint8_t closed;
|
|
82
|
+
} iodine_upload_s;
|
|
83
|
+
|
|
84
|
+
FIO_SFUNC void iodine___upload_close(iodine_upload_s *s);
|
|
85
|
+
|
|
86
|
+
static void iodine_upload_mark(void *ptr_) {
|
|
87
|
+
iodine_upload_s *s = (iodine_upload_s *)ptr_;
|
|
88
|
+
rb_gc_mark(s->filename);
|
|
89
|
+
rb_gc_mark(s->type);
|
|
90
|
+
rb_gc_mark(s->name);
|
|
91
|
+
rb_gc_mark(s->headers);
|
|
92
|
+
rb_gc_mark(s->rack_fallback);
|
|
93
|
+
iodine_minimap_gc_mark(&s->rack_view);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
static void iodine_upload_free(void *ptr_) {
|
|
97
|
+
iodine_upload_s *s = (iodine_upload_s *)ptr_;
|
|
98
|
+
iodine___upload_close(s);
|
|
99
|
+
iodine_hmap_destroy(&s->rack_view.map);
|
|
100
|
+
fio_thread_mutex_destroy(&s->lock);
|
|
101
|
+
ruby_xfree(s);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
static size_t iodine_upload_data_size(const void *ptr_) {
|
|
105
|
+
iodine_upload_s *s = (iodine_upload_s *)ptr_;
|
|
106
|
+
return sizeof(*s) + (iodine_hmap_capa(&s->rack_view.map) *
|
|
107
|
+
sizeof(s->rack_view.map.map[0]));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
static const rb_data_type_t IODINE_UPLOAD_DATA_TYPE = {
|
|
111
|
+
.wrap_struct_name = "IodineBaseUpload",
|
|
112
|
+
.function =
|
|
113
|
+
{
|
|
114
|
+
.dmark = iodine_upload_mark,
|
|
115
|
+
.dfree = iodine_upload_free,
|
|
116
|
+
.dsize = iodine_upload_data_size,
|
|
117
|
+
},
|
|
118
|
+
.data = NULL,
|
|
119
|
+
.flags = RUBY_TYPED_FREE_IMMEDIATELY,
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
static VALUE iodine_rb_BASE_UPLOAD; /* Iodine::Base::Upload */
|
|
123
|
+
static VALUE IODINE_PARAMS_EMPTY_KEY;
|
|
124
|
+
|
|
125
|
+
static ID IODINE_PARAMS_FILENAME_ID; /* :filename */
|
|
126
|
+
static ID IODINE_PARAMS_TYPE_ID; /* :type */
|
|
127
|
+
static ID IODINE_PARAMS_NAME_ID; /* :name */
|
|
128
|
+
static ID IODINE_PARAMS_TEMPFILE_ID; /* :tempfile */
|
|
129
|
+
static ID IODINE_PARAMS_HEAD_ID; /* :head */
|
|
130
|
+
|
|
131
|
+
static VALUE iodine_upload_alloc(VALUE klass) {
|
|
132
|
+
iodine_upload_s *s = NULL;
|
|
133
|
+
VALUE self = TypedData_Make_Struct(klass,
|
|
134
|
+
iodine_upload_s,
|
|
135
|
+
&IODINE_UPLOAD_DATA_TYPE,
|
|
136
|
+
s);
|
|
137
|
+
*s = (iodine_upload_s){0};
|
|
138
|
+
s->data = (fio_data_s)FIO_DATA_INIT;
|
|
139
|
+
s->lock = (fio_thread_mutex_t)FIO_THREAD_MUTEX_INIT;
|
|
140
|
+
s->filename = Qnil;
|
|
141
|
+
s->type = Qnil;
|
|
142
|
+
s->name = Qnil;
|
|
143
|
+
s->headers = Qnil;
|
|
144
|
+
s->rack_fallback = Qnil;
|
|
145
|
+
return self;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
static iodine_upload_s *iodine_upload_ptr(VALUE self) {
|
|
149
|
+
iodine_upload_s *s;
|
|
150
|
+
TypedData_Get_Struct(self, iodine_upload_s, &IODINE_UPLOAD_DATA_TYPE, s);
|
|
151
|
+
return s;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
FIO_SFUNC void iodine___upload_unlink(iodine_upload_s *s) {
|
|
155
|
+
if (!s->prev)
|
|
156
|
+
return;
|
|
157
|
+
*s->prev = s->next;
|
|
158
|
+
if (s->next)
|
|
159
|
+
s->next->prev = s->prev;
|
|
160
|
+
s->prev = NULL;
|
|
161
|
+
s->next = NULL;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/* Called with the GVL. The list is a cleanup index, never a GC root. */
|
|
165
|
+
FIO_SFUNC void iodine___upload_close(iodine_upload_s *s) {
|
|
166
|
+
fio_thread_mutex_lock(&s->lock);
|
|
167
|
+
iodine___upload_unlink(s);
|
|
168
|
+
if (!s->closed) {
|
|
169
|
+
s->closed = 1;
|
|
170
|
+
fio_data_destroy(&s->data);
|
|
171
|
+
}
|
|
172
|
+
fio_thread_mutex_unlock(&s->lock);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
FIO_SFUNC void iodine___upload_track(iodine_connection_s *c,
|
|
176
|
+
iodine_upload_s *s) {
|
|
177
|
+
s->next = c->open_uploads;
|
|
178
|
+
if (s->next)
|
|
179
|
+
s->next->prev = &s->next;
|
|
180
|
+
s->prev = &c->open_uploads;
|
|
181
|
+
c->open_uploads = s;
|
|
182
|
+
fio_atomic_or(&c->flags, IODINE_CONNECTION_PARAMS_HAS_UPLOAD);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
FIO_SFUNC VALUE iodine___upload_unlock(VALUE s_) {
|
|
186
|
+
iodine_upload_s *s = (iodine_upload_s *)s_;
|
|
187
|
+
fio_thread_mutex_unlock(&s->lock);
|
|
188
|
+
return Qnil;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
typedef struct {
|
|
192
|
+
iodine_upload_s *s;
|
|
193
|
+
VALUE out;
|
|
194
|
+
size_t limit;
|
|
195
|
+
uint8_t length_given;
|
|
196
|
+
} iodine___upload_read_s;
|
|
197
|
+
|
|
198
|
+
/* Runs with s->lock held; rb_ensure releases it on every Ruby exception. */
|
|
199
|
+
FIO_SFUNC VALUE iodine___upload_read_locked(VALUE args_) {
|
|
200
|
+
iodine___upload_read_s *args = (iodine___upload_read_s *)args_;
|
|
201
|
+
iodine_upload_s *s = args->s;
|
|
202
|
+
if (s->closed)
|
|
203
|
+
rb_raise(rb_eIOError, "can't read from a closed upload");
|
|
204
|
+
const size_t position = fio_data_pos(&s->data);
|
|
205
|
+
const size_t length = fio_data_len(&s->data);
|
|
206
|
+
const size_t remaining = position < length ? length - position : 0;
|
|
207
|
+
const size_t wanted = args->limit < remaining ? args->limit : remaining;
|
|
208
|
+
if (wanted > (size_t)LONG_MAX)
|
|
209
|
+
rb_raise(rb_eRangeError, "read length exceeds the Ruby String limit");
|
|
210
|
+
if (args->out != Qnil) {
|
|
211
|
+
rb_str_resize(args->out, 0); /* validate / clear before consuming bytes */
|
|
212
|
+
if (!args->length_given)
|
|
213
|
+
rb_enc_associate(args->out, IodineBinaryEncoding);
|
|
214
|
+
}
|
|
215
|
+
if (args->length_given && args->limit && position >= length)
|
|
216
|
+
return Qnil;
|
|
217
|
+
fio_str_info_s r = fio_data_read(&s->data, wanted);
|
|
218
|
+
if (args->limit && !r.len && fio_data_pos(&s->data) < fio_data_len(&s->data))
|
|
219
|
+
rb_raise(rb_eIOError, "upload read error (store reported a short read)");
|
|
220
|
+
if (args->out != Qnil) {
|
|
221
|
+
if (r.len)
|
|
222
|
+
rb_str_cat(args->out, r.buf, (long)r.len);
|
|
223
|
+
return args->out; /* Explicit lengths preserve out_string's encoding. */
|
|
224
|
+
}
|
|
225
|
+
return rb_enc_str_new(r.buf, (long)r.len, IodineBinaryEncoding);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Ruby: read(length = nil, out_string = nil) -> String or nil.
|
|
230
|
+
* Follows binary IO#read EOF and out_string semantics; IOError after close.
|
|
231
|
+
*/
|
|
232
|
+
static VALUE iodine_upload_read(int argc, VALUE *argv, VALUE self) {
|
|
233
|
+
iodine___upload_read_s args = {.s = iodine_upload_ptr(self),
|
|
234
|
+
.out = Qnil,
|
|
235
|
+
.limit = (size_t)-1,
|
|
236
|
+
.length_given = 0};
|
|
237
|
+
VALUE len_v = Qnil;
|
|
238
|
+
rb_scan_args(argc, argv, "02", &len_v, &args.out);
|
|
239
|
+
if (len_v != Qnil) {
|
|
240
|
+
if (!RB_INTEGER_TYPE_P(len_v))
|
|
241
|
+
rb_raise(rb_eTypeError, "length must be an Integer or nil");
|
|
242
|
+
long long l = NUM2LL(len_v);
|
|
243
|
+
if (l < 0)
|
|
244
|
+
rb_raise(rb_eArgError, "length must not be negative");
|
|
245
|
+
args.limit = (unsigned long long)l > (unsigned long long)SIZE_MAX
|
|
246
|
+
? SIZE_MAX
|
|
247
|
+
: (size_t)l;
|
|
248
|
+
args.length_given = 1;
|
|
249
|
+
}
|
|
250
|
+
if (args.out != Qnil)
|
|
251
|
+
Check_Type(args.out, T_STRING);
|
|
252
|
+
fio_thread_mutex_lock(&args.s->lock);
|
|
253
|
+
return rb_ensure(iodine___upload_read_locked,
|
|
254
|
+
(VALUE)&args,
|
|
255
|
+
iodine___upload_unlock,
|
|
256
|
+
(VALUE)args.s);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/** Ruby: rewind -> 0 (IOError after close). */
|
|
260
|
+
static VALUE iodine_upload_rewind(VALUE self) {
|
|
261
|
+
iodine_upload_s *s = iodine_upload_ptr(self);
|
|
262
|
+
fio_thread_mutex_lock(&s->lock);
|
|
263
|
+
if (s->closed) {
|
|
264
|
+
fio_thread_mutex_unlock(&s->lock);
|
|
265
|
+
rb_raise(rb_eIOError, "can't rewind a closed upload");
|
|
266
|
+
}
|
|
267
|
+
fio_data_seek(&s->data, 0);
|
|
268
|
+
fio_thread_mutex_unlock(&s->lock);
|
|
269
|
+
return INT2FIX(0);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/** Ruby: size -> Integer (never moves the read position; IOError after close).
|
|
273
|
+
*/
|
|
274
|
+
static VALUE iodine_upload_size(VALUE self) {
|
|
275
|
+
iodine_upload_s *s = iodine_upload_ptr(self);
|
|
276
|
+
fio_thread_mutex_lock(&s->lock);
|
|
277
|
+
if (s->closed) {
|
|
278
|
+
fio_thread_mutex_unlock(&s->lock);
|
|
279
|
+
rb_raise(rb_eIOError, "closed upload");
|
|
280
|
+
}
|
|
281
|
+
size_t len = fio_data_len(&s->data);
|
|
282
|
+
fio_thread_mutex_unlock(&s->lock);
|
|
283
|
+
return ULL2NUM((unsigned long long)len);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/** fio_data_save argument carrier for the no-GVL call (large uploads). */
|
|
287
|
+
typedef struct {
|
|
288
|
+
iodine_upload_s *s;
|
|
289
|
+
const char *filename;
|
|
290
|
+
size_t size;
|
|
291
|
+
int result;
|
|
292
|
+
int error;
|
|
293
|
+
uint8_t closed;
|
|
294
|
+
} iodine___upload_save_s;
|
|
295
|
+
|
|
296
|
+
/* Lock, save and unlock in one no-GVL callback. close can never wait for this
|
|
297
|
+
thread to reacquire the GVL while still holding the upload lock. */
|
|
298
|
+
static void *iodine___upload_save_no_gvl(void *p_) {
|
|
299
|
+
iodine___upload_save_s *p = (iodine___upload_save_s *)p_;
|
|
300
|
+
fio_thread_mutex_lock(&p->s->lock);
|
|
301
|
+
if (p->s->closed) {
|
|
302
|
+
p->closed = 1;
|
|
303
|
+
} else {
|
|
304
|
+
p->result = fio_data_save(&p->s->data, p->filename);
|
|
305
|
+
p->size = fio_data_len(&p->s->data);
|
|
306
|
+
p->error = p->result == -1 ? errno : 0;
|
|
307
|
+
}
|
|
308
|
+
fio_thread_mutex_unlock(&p->s->lock);
|
|
309
|
+
return NULL;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
typedef struct {
|
|
313
|
+
iodine___upload_save_s args;
|
|
314
|
+
VALUE path;
|
|
315
|
+
uint8_t path_locked;
|
|
316
|
+
} iodine___upload_save_guard_s;
|
|
317
|
+
|
|
318
|
+
FIO_SFUNC VALUE iodine___upload_save_large(VALUE guard_) {
|
|
319
|
+
iodine___upload_save_guard_s *g = (iodine___upload_save_guard_s *)guard_;
|
|
320
|
+
if (!OBJ_FROZEN(g->path)) { /* rb_str_locktmp rejects frozen Strings */
|
|
321
|
+
rb_str_locktmp(g->path);
|
|
322
|
+
g->path_locked = 1;
|
|
323
|
+
}
|
|
324
|
+
g->args.filename = RSTRING_PTR(g->path);
|
|
325
|
+
rb_thread_call_without_gvl(iodine___upload_save_no_gvl, &g->args, NULL, NULL);
|
|
326
|
+
return Qnil;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
FIO_SFUNC VALUE iodine___upload_save_large_ensure(VALUE guard_) {
|
|
330
|
+
iodine___upload_save_guard_s *g = (iodine___upload_save_guard_s *)guard_;
|
|
331
|
+
if (g->path_locked)
|
|
332
|
+
rb_str_unlocktmp(g->path);
|
|
333
|
+
STORE.release(g->path);
|
|
334
|
+
return Qnil;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Ruby: save(path) -> Integer (bytes written; IOError after close).
|
|
339
|
+
*
|
|
340
|
+
* fio_data_save writes atomically through a sibling file and preserves the
|
|
341
|
+
* upload's read position. Payloads of 256KiB or more save outside the GVL.
|
|
342
|
+
* The path String is rooted; mutable paths are locked during that window.
|
|
343
|
+
*/
|
|
344
|
+
static VALUE iodine_upload_save(VALUE self, VALUE path) {
|
|
345
|
+
iodine_upload_s *s = iodine_upload_ptr(self);
|
|
346
|
+
fio_thread_mutex_lock(&s->lock);
|
|
347
|
+
if (s->closed) {
|
|
348
|
+
fio_thread_mutex_unlock(&s->lock);
|
|
349
|
+
rb_raise(rb_eIOError, "can't save a closed upload");
|
|
350
|
+
}
|
|
351
|
+
size_t len = fio_data_len(&s->data);
|
|
352
|
+
fio_thread_mutex_unlock(&s->lock);
|
|
353
|
+
|
|
354
|
+
if (!RB_TYPE_P(path, RUBY_T_STRING))
|
|
355
|
+
rb_raise(rb_eTypeError, "`path` should be a String");
|
|
356
|
+
if (memchr(RSTRING_PTR(path), 0, (size_t)RSTRING_LEN(path)))
|
|
357
|
+
rb_raise(rb_eArgError, "path contains a NUL byte");
|
|
358
|
+
|
|
359
|
+
iodine___upload_save_s args = {
|
|
360
|
+
.s = s,
|
|
361
|
+
.filename = RSTRING_PTR(path),
|
|
362
|
+
.size = 0,
|
|
363
|
+
.result = -1,
|
|
364
|
+
.error = 0,
|
|
365
|
+
.closed = 0,
|
|
366
|
+
};
|
|
367
|
+
if (len >= (1ULL << 18)) {
|
|
368
|
+
iodine___upload_save_guard_s guard = {.args = args,
|
|
369
|
+
.path = path,
|
|
370
|
+
.path_locked = 0};
|
|
371
|
+
STORE.hold(path);
|
|
372
|
+
rb_ensure(iodine___upload_save_large,
|
|
373
|
+
(VALUE)&guard,
|
|
374
|
+
iodine___upload_save_large_ensure,
|
|
375
|
+
(VALUE)&guard);
|
|
376
|
+
args = guard.args;
|
|
377
|
+
} else {
|
|
378
|
+
fio_thread_mutex_lock(&s->lock);
|
|
379
|
+
if (s->closed) {
|
|
380
|
+
fio_thread_mutex_unlock(&s->lock);
|
|
381
|
+
rb_raise(rb_eIOError, "can't save a closed upload");
|
|
382
|
+
}
|
|
383
|
+
args.result = fio_data_save(&s->data, args.filename);
|
|
384
|
+
args.size = fio_data_len(&s->data);
|
|
385
|
+
args.error = args.result == -1 ? errno : 0;
|
|
386
|
+
fio_thread_mutex_unlock(&s->lock);
|
|
387
|
+
}
|
|
388
|
+
if (args.closed)
|
|
389
|
+
rb_raise(rb_eIOError, "can't save a closed upload");
|
|
390
|
+
if (args.result == -1) {
|
|
391
|
+
errno = args.error;
|
|
392
|
+
rb_sys_fail("upload save failed");
|
|
393
|
+
}
|
|
394
|
+
return ULL2NUM((unsigned long long)args.size);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/** Ruby: close -> nil (idempotent; releases temporary storage). */
|
|
398
|
+
static VALUE iodine_upload_close(VALUE self) {
|
|
399
|
+
iodine___upload_close(iodine_upload_ptr(self));
|
|
400
|
+
return Qnil;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/** Ruby: closed? -> true / false. */
|
|
404
|
+
static VALUE iodine_upload_closed_p(VALUE self) {
|
|
405
|
+
return iodine_upload_ptr(self)->closed ? Qtrue : Qfalse;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/* Metadata readers (String or nil, as provided by the client). */
|
|
409
|
+
static VALUE iodine_upload_filename(VALUE self) {
|
|
410
|
+
return iodine_upload_ptr(self)->filename;
|
|
411
|
+
}
|
|
412
|
+
static VALUE iodine_upload_type(VALUE self) {
|
|
413
|
+
return iodine_upload_ptr(self)->type;
|
|
414
|
+
}
|
|
415
|
+
static VALUE iodine_upload_name(VALUE self) {
|
|
416
|
+
return iodine_upload_ptr(self)->name;
|
|
417
|
+
}
|
|
418
|
+
static VALUE iodine_upload_headers(VALUE self) {
|
|
419
|
+
return iodine_upload_ptr(self)->headers;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/** Returns a required Rack value, or Qundef if the key uses the fallback. */
|
|
423
|
+
FIO_SFUNC VALUE iodine___upload_required_value(VALUE self,
|
|
424
|
+
iodine_upload_s *s,
|
|
425
|
+
VALUE key) {
|
|
426
|
+
if (!RB_SYMBOL_P(key))
|
|
427
|
+
return Qundef;
|
|
428
|
+
ID id = rb_sym2id(key);
|
|
429
|
+
if (id == IODINE_PARAMS_FILENAME_ID)
|
|
430
|
+
return s->filename;
|
|
431
|
+
if (id == IODINE_PARAMS_TYPE_ID)
|
|
432
|
+
return s->type;
|
|
433
|
+
if (id == IODINE_PARAMS_NAME_ID)
|
|
434
|
+
return s->name;
|
|
435
|
+
if (id == IODINE_PARAMS_TEMPFILE_ID)
|
|
436
|
+
return self;
|
|
437
|
+
if (id == IODINE_PARAMS_HEAD_ID)
|
|
438
|
+
return s->headers;
|
|
439
|
+
return Qundef;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
FIO_SFUNC int iodine___upload_minimap_key_p(VALUE key) {
|
|
443
|
+
return RB_SYMBOL_P(key) || RB_TYPE_P(key, RUBY_T_STRING);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/** Ruby: upload[key] -> Rack-style access (`:tempfile` returns the upload). */
|
|
447
|
+
static VALUE iodine_upload_aref(VALUE self, VALUE key) {
|
|
448
|
+
iodine_upload_s *s = iodine_upload_ptr(self);
|
|
449
|
+
if (!iodine___upload_minimap_key_p(key))
|
|
450
|
+
return s->rack_fallback == Qnil ? Qnil
|
|
451
|
+
: rb_hash_aref(s->rack_fallback, key);
|
|
452
|
+
VALUE required = iodine___upload_required_value(self, s, key);
|
|
453
|
+
if (required != Qundef && !iodine_hmap_count(&s->rack_view.map))
|
|
454
|
+
return required;
|
|
455
|
+
iodine_hmap_node_s *node = iodine_hmap_get_ptr(&s->rack_view.map, key);
|
|
456
|
+
if (node)
|
|
457
|
+
return node->value; /* preserves explicit nil / false overrides */
|
|
458
|
+
return required == Qundef ? Qnil : required;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/** Ruby: upload[key] = value (changes only the compatibility view). */
|
|
462
|
+
static VALUE iodine_upload_aset(VALUE self, VALUE key, VALUE value) {
|
|
463
|
+
iodine_upload_s *s = iodine_upload_ptr(self);
|
|
464
|
+
if (!iodine___upload_minimap_key_p(key)) {
|
|
465
|
+
if (s->rack_fallback == Qnil)
|
|
466
|
+
s->rack_fallback = rb_hash_new();
|
|
467
|
+
return rb_hash_aset(s->rack_fallback, key, value);
|
|
468
|
+
}
|
|
469
|
+
VALUE required = iodine___upload_required_value(self, s, key);
|
|
470
|
+
if (required == Qundef)
|
|
471
|
+
return iodine_minimap_store(&s->rack_view, key, value);
|
|
472
|
+
/* Required keys are Symbols and nil is an override, not a deletion. */
|
|
473
|
+
iodine_hmap_set(&s->rack_view.map, key, value, NULL);
|
|
474
|
+
return value;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/* *****************************************************************************
|
|
478
|
+
Params Parser Callbacks (Ruby construction and NeoRack conflict resolution)
|
|
479
|
+
***************************************************************************** */
|
|
480
|
+
|
|
481
|
+
typedef struct {
|
|
482
|
+
iodine_connection_s *c;
|
|
483
|
+
VALUE root;
|
|
484
|
+
uint8_t malformed;
|
|
485
|
+
uint8_t io_error;
|
|
486
|
+
} iodine_params_udata_s;
|
|
487
|
+
|
|
488
|
+
/** Returns the request's memoized params Hash, creating it if needed. */
|
|
489
|
+
FIO_SFUNC VALUE iodine___params_hash(iodine_connection_s *c) {
|
|
490
|
+
VALUE h = c->store[IODINE_CONNECTION_STORE_params];
|
|
491
|
+
if (h == Qnil) {
|
|
492
|
+
h = rb_hash_new();
|
|
493
|
+
c->store[IODINE_CONNECTION_STORE_params] = h;
|
|
494
|
+
}
|
|
495
|
+
return h;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
FIO_SFUNC int iodine___params_is_hash(VALUE value) {
|
|
499
|
+
return RB_TYPE_P(value, RUBY_T_HASH); /* subclasses included; Upload isn't */
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
FIO_SFUNC VALUE iodine___params_key(fio_buf_info_s key) {
|
|
503
|
+
return rb_str_freeze(
|
|
504
|
+
rb_enc_str_new(key.buf, (long)key.len, IodineBinaryEncoding));
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/* Merge with actual Ruby values, so false and nil never mean "missing". */
|
|
508
|
+
FIO_SFUNC void iodine___params_merge_at(VALUE dest, VALUE key, VALUE incoming);
|
|
509
|
+
|
|
510
|
+
FIO_SFUNC int iodine___params_merge_each(VALUE key, VALUE value, VALUE dest) {
|
|
511
|
+
iodine___params_merge_at(dest, key, value);
|
|
512
|
+
return ST_CONTINUE;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
FIO_SFUNC void iodine___params_merge_hash(VALUE dest, VALUE incoming) {
|
|
516
|
+
rb_hash_foreach(incoming, iodine___params_merge_each, dest);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
FIO_SFUNC void iodine___params_merge_at(VALUE dest, VALUE key, VALUE incoming) {
|
|
520
|
+
VALUE current = rb_hash_lookup2(dest, key, Qundef);
|
|
521
|
+
if (current == Qundef) {
|
|
522
|
+
rb_hash_aset(dest, key, incoming);
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
if (iodine___params_is_hash(current) && iodine___params_is_hash(incoming)) {
|
|
526
|
+
iodine___params_merge_hash(current, incoming);
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
if (iodine___params_is_hash(incoming)) {
|
|
530
|
+
/* Keep current rooted in dest until it is nested in the incoming map. */
|
|
531
|
+
iodine___params_merge_at(incoming, IODINE_PARAMS_EMPTY_KEY, current);
|
|
532
|
+
rb_hash_aset(dest, key, incoming);
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
if (iodine___params_is_hash(current)) {
|
|
536
|
+
iodine___params_merge_at(current, IODINE_PARAMS_EMPTY_KEY, incoming);
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
if (RB_TYPE_P(current, RUBY_T_ARRAY) && RB_TYPE_P(incoming, RUBY_T_ARRAY)) {
|
|
540
|
+
rb_ary_concat(current, incoming);
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
if (RB_TYPE_P(current, RUBY_T_ARRAY)) {
|
|
544
|
+
rb_ary_push(current, incoming);
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
if (RB_TYPE_P(incoming, RUBY_T_ARRAY)) {
|
|
548
|
+
rb_ary_unshift(incoming, current);
|
|
549
|
+
rb_hash_aset(dest, key, incoming);
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
VALUE ary = rb_ary_new_capa(2);
|
|
553
|
+
rb_ary_push(ary, current);
|
|
554
|
+
rb_ary_push(ary, incoming);
|
|
555
|
+
rb_hash_aset(dest, key, ary);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/* Resolve conflicts in Ruby; return no replaced value to the parser. */
|
|
559
|
+
FIO_SFUNC fio_json_push_result_s iodine___params_on_map(void *udata_,
|
|
560
|
+
void *ctx,
|
|
561
|
+
fio_buf_info_s key) {
|
|
562
|
+
iodine_params_udata_s *u = (iodine_params_udata_s *)udata_;
|
|
563
|
+
if (!ctx)
|
|
564
|
+
return (fio_json_push_result_s){.obj = (void *)u->root};
|
|
565
|
+
if (!key.buf) { /* array parent: append */
|
|
566
|
+
VALUE map = rb_hash_new();
|
|
567
|
+
rb_ary_push((VALUE)ctx, map);
|
|
568
|
+
return (fio_json_push_result_s){.obj = (void *)map};
|
|
569
|
+
}
|
|
570
|
+
VALUE k = iodine___params_key(key);
|
|
571
|
+
VALUE current = rb_hash_lookup2((VALUE)ctx, k, Qundef);
|
|
572
|
+
if (current != Qundef && iodine___params_is_hash(current))
|
|
573
|
+
return (fio_json_push_result_s){.obj = (void *)current};
|
|
574
|
+
VALUE map = rb_hash_new();
|
|
575
|
+
if (current != Qundef)
|
|
576
|
+
rb_hash_aset(map, IODINE_PARAMS_EMPTY_KEY, current);
|
|
577
|
+
rb_hash_aset((VALUE)ctx, k, map);
|
|
578
|
+
return (fio_json_push_result_s){.obj = (void *)map};
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
FIO_SFUNC VALUE iodine___params_open_array_at(VALUE dest, VALUE key) {
|
|
582
|
+
VALUE current = rb_hash_lookup2(dest, key, Qundef);
|
|
583
|
+
if (current != Qundef && RB_TYPE_P(current, RUBY_T_ARRAY))
|
|
584
|
+
return current;
|
|
585
|
+
if (current != Qundef && iodine___params_is_hash(current))
|
|
586
|
+
return iodine___params_open_array_at(current, IODINE_PARAMS_EMPTY_KEY);
|
|
587
|
+
VALUE ary = rb_ary_new();
|
|
588
|
+
if (current != Qundef)
|
|
589
|
+
rb_ary_push(ary, current);
|
|
590
|
+
rb_hash_aset(dest, key, ary);
|
|
591
|
+
return ary;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
FIO_SFUNC fio_json_push_result_s iodine___params_on_array(void *udata_,
|
|
595
|
+
void *ctx,
|
|
596
|
+
fio_buf_info_s key) {
|
|
597
|
+
(void)udata_;
|
|
598
|
+
if (!ctx)
|
|
599
|
+
return (fio_json_push_result_s){
|
|
600
|
+
.err =
|
|
601
|
+
FIO_JSON_ERROR_GENERIC}; /* only JSON objects contribute params */
|
|
602
|
+
if (!key.buf) {
|
|
603
|
+
VALUE ary = rb_ary_new();
|
|
604
|
+
rb_ary_push((VALUE)ctx, ary);
|
|
605
|
+
return (fio_json_push_result_s){.obj = (void *)ary};
|
|
606
|
+
}
|
|
607
|
+
return (fio_json_push_result_s){
|
|
608
|
+
.obj = (void *)iodine___params_open_array_at((VALUE)ctx,
|
|
609
|
+
iodine___params_key(key))};
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
FIO_SFUNC fio_json_obj_result_s iodine___params_map_push(void *udata_,
|
|
613
|
+
void *map,
|
|
614
|
+
fio_buf_info_s key,
|
|
615
|
+
void *value) {
|
|
616
|
+
iodine_params_udata_s *u = (iodine_params_udata_s *)udata_;
|
|
617
|
+
if (!value) {
|
|
618
|
+
/* The STL uses NULL for a missing JSON value; params false is Qundef. */
|
|
619
|
+
u->malformed = 1;
|
|
620
|
+
return (fio_json_obj_result_s){.err = FIO_JSON_ERROR_GENERIC};
|
|
621
|
+
}
|
|
622
|
+
VALUE incoming = (VALUE)value;
|
|
623
|
+
if (incoming == Qundef)
|
|
624
|
+
incoming = Qfalse;
|
|
625
|
+
if (RB_TYPE_P(incoming, RUBY_T_DATA) &&
|
|
626
|
+
rb_typeddata_is_kind_of(incoming, &IODINE_UPLOAD_DATA_TYPE)) {
|
|
627
|
+
iodine_upload_s *upload = (iodine_upload_s *)RTYPEDDATA_DATA(incoming);
|
|
628
|
+
fio_thread_mutex_lock(&upload->lock);
|
|
629
|
+
if (!upload->closed)
|
|
630
|
+
fio_data_seek(&upload->data, 0); /* upload starts at byte zero */
|
|
631
|
+
fio_thread_mutex_unlock(&upload->lock);
|
|
632
|
+
}
|
|
633
|
+
iodine___params_merge_at((VALUE)map, iodine___params_key(key), incoming);
|
|
634
|
+
return (fio_json_obj_result_s){0};
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
FIO_SFUNC int iodine___params_array_push(void *udata_, void *ctx, void *value) {
|
|
638
|
+
iodine_params_udata_s *u = (iodine_params_udata_s *)udata_;
|
|
639
|
+
if (!value) {
|
|
640
|
+
u->malformed = 1;
|
|
641
|
+
return 0;
|
|
642
|
+
}
|
|
643
|
+
VALUE incoming = (VALUE)value;
|
|
644
|
+
if (incoming == Qundef)
|
|
645
|
+
incoming = Qfalse;
|
|
646
|
+
rb_ary_push((VALUE)ctx, incoming);
|
|
647
|
+
return 0;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
FIO_SFUNC int iodine___params_is_array(void *udata_, void *ctx) {
|
|
651
|
+
(void)udata_;
|
|
652
|
+
return ctx && (VALUE)ctx != Qundef && RB_TYPE_P((VALUE)ctx, RUBY_T_ARRAY);
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
/** Hashes are maps (an Upload is a scalar TypedData "other" value). */
|
|
656
|
+
FIO_SFUNC int iodine___params_is_map(void *udata_, void *ctx) {
|
|
657
|
+
(void)udata_;
|
|
658
|
+
return ctx && (VALUE)ctx != Qundef && iodine___params_is_hash((VALUE)ctx);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
FIO_SFUNC void iodine___params_free_unused_object(void *udata_, void *ctx) {
|
|
662
|
+
(void)udata_;
|
|
663
|
+
VALUE value = (VALUE)ctx;
|
|
664
|
+
if (ctx && RB_TYPE_P(value, RUBY_T_DATA) &&
|
|
665
|
+
rb_typeddata_is_kind_of(value, &IODINE_UPLOAD_DATA_TYPE))
|
|
666
|
+
iodine___upload_close((iodine_upload_s *)RTYPEDDATA_DATA(value));
|
|
667
|
+
/* Other Ruby objects are GC managed. */
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/** Query / form / multipart-text values: always binary Strings. */
|
|
671
|
+
FIO_SFUNC fio_json_obj_result_s
|
|
672
|
+
iodine___params_on_string_simple(void *udata_, const void *start, size_t len) {
|
|
673
|
+
(void)udata_;
|
|
674
|
+
return (fio_json_obj_result_s){
|
|
675
|
+
.obj = (void *)rb_enc_str_new((const char *)start,
|
|
676
|
+
(long)len,
|
|
677
|
+
IodineBinaryEncoding)};
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
FIO_SFUNC fio_json_obj_result_s iodine___params_on_number(void *udata_,
|
|
681
|
+
int64_t i) {
|
|
682
|
+
(void)udata_;
|
|
683
|
+
return (fio_json_obj_result_s){.obj = (void *)LL2NUM((long long)i)};
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
FIO_SFUNC fio_json_obj_result_s iodine___params_on_float(void *udata_,
|
|
687
|
+
double f) {
|
|
688
|
+
(void)udata_;
|
|
689
|
+
return (fio_json_obj_result_s){.obj = (void *)DBL2NUM(f)};
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
FIO_SFUNC fio_json_obj_result_s iodine___params_on_false(void *udata_) {
|
|
693
|
+
(void)udata_;
|
|
694
|
+
return (fio_json_obj_result_s){
|
|
695
|
+
.obj =
|
|
696
|
+
(void *)Qundef}; /* non-NULL across the STL; decoded on insertion */
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
/* Unescape directly into a Ruby String. This avoids a temporary C allocation,
|
|
700
|
+
checks the writer result, and gives every escaped string one Ruby owner. */
|
|
701
|
+
FIO_SFUNC fio_json_obj_result_s iodine___params_on_string(void *udata_,
|
|
702
|
+
const void *start,
|
|
703
|
+
size_t len) {
|
|
704
|
+
iodine_params_udata_s *u = (iodine_params_udata_s *)udata_;
|
|
705
|
+
if (len > LONG_MAX) {
|
|
706
|
+
u->malformed = 1;
|
|
707
|
+
return (fio_json_obj_result_s){.err = FIO_JSON_ERROR_GENERIC};
|
|
708
|
+
}
|
|
709
|
+
VALUE str = rb_str_new(NULL, (long)len);
|
|
710
|
+
fio_str_info_s dest = FIO_STR_INFO3(RSTRING_PTR(str), 0, len + 1);
|
|
711
|
+
if (fio_string_write_unescape(&dest, NULL, start, len)) {
|
|
712
|
+
u->malformed = 1;
|
|
713
|
+
return (fio_json_obj_result_s){.err = FIO_JSON_ERROR_GENERIC};
|
|
714
|
+
}
|
|
715
|
+
rb_str_set_len(str, (long)dest.len);
|
|
716
|
+
return (fio_json_obj_result_s){.obj = (void *)str};
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/* Multipart file uploads. */
|
|
720
|
+
|
|
721
|
+
FIO_SFUNC void *iodine___params_on_file(void *udata_,
|
|
722
|
+
fio_str_info_s name,
|
|
723
|
+
fio_str_info_s filename,
|
|
724
|
+
fio_str_info_s content_type,
|
|
725
|
+
fio_str_info_s headers) {
|
|
726
|
+
iodine_params_udata_s *u = (iodine_params_udata_s *)udata_;
|
|
727
|
+
VALUE upload = rb_obj_alloc(iodine_rb_BASE_UPLOAD); /* open at byte zero */
|
|
728
|
+
iodine_upload_s *s = iodine_upload_ptr(upload);
|
|
729
|
+
u->c->store[IODINE_CONNECTION_STORE_tmp] = upload; /* active parser root */
|
|
730
|
+
iodine___upload_track(u->c, s);
|
|
731
|
+
s->filename =
|
|
732
|
+
rb_enc_str_new(filename.buf, (long)filename.len, IodineBinaryEncoding);
|
|
733
|
+
s->name = rb_enc_str_new(name.buf, (long)name.len, IodineBinaryEncoding);
|
|
734
|
+
s->type = content_type.buf ? rb_enc_str_new(content_type.buf,
|
|
735
|
+
(long)content_type.len,
|
|
736
|
+
IodineBinaryEncoding)
|
|
737
|
+
: Qnil;
|
|
738
|
+
s->headers =
|
|
739
|
+
headers.buf
|
|
740
|
+
? rb_enc_str_new(headers.buf, (long)headers.len, IodineBinaryEncoding)
|
|
741
|
+
: Qnil;
|
|
742
|
+
return (void *)upload;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
FIO_SFUNC int iodine___params_on_file_data(void *udata_,
|
|
746
|
+
void *file_ctx,
|
|
747
|
+
fio_buf_info_s data) {
|
|
748
|
+
iodine_params_udata_s *u = (iodine_params_udata_s *)udata_;
|
|
749
|
+
iodine_upload_s *s = (iodine_upload_s *)RTYPEDDATA_DATA((VALUE)file_ctx);
|
|
750
|
+
fio_thread_mutex_lock(&s->lock);
|
|
751
|
+
int result = s->closed ? -1 : fio_data_write(&s->data, data.buf, data.len);
|
|
752
|
+
fio_thread_mutex_unlock(&s->lock);
|
|
753
|
+
if (result) {
|
|
754
|
+
u->io_error = 1;
|
|
755
|
+
iodine___upload_close(s); /* a partial failed write is never committed */
|
|
756
|
+
return -1;
|
|
757
|
+
}
|
|
758
|
+
return 0;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
/* *****************************************************************************
|
|
762
|
+
Exception-safe callback boundary
|
|
763
|
+
|
|
764
|
+
No Ruby exception may unwind through the STL parser: its local decoded strings,
|
|
765
|
+
JSON keys and multipart pending buffers need normal C cleanup. Each allocating
|
|
766
|
+
callback uses rb_protect, records the first exception, and stops the parser.
|
|
767
|
+
***************************************************************************** */
|
|
768
|
+
|
|
769
|
+
enum iodine___params_callback_e {
|
|
770
|
+
IODINE_PARAMS_CB_NUMBER,
|
|
771
|
+
IODINE_PARAMS_CB_FLOAT,
|
|
772
|
+
IODINE_PARAMS_CB_STRING,
|
|
773
|
+
IODINE_PARAMS_CB_STRING_SIMPLE,
|
|
774
|
+
IODINE_PARAMS_CB_MAP,
|
|
775
|
+
IODINE_PARAMS_CB_ARRAY,
|
|
776
|
+
IODINE_PARAMS_CB_MAP_PUSH,
|
|
777
|
+
IODINE_PARAMS_CB_ARRAY_PUSH,
|
|
778
|
+
IODINE_PARAMS_CB_FILE,
|
|
779
|
+
};
|
|
780
|
+
|
|
781
|
+
typedef struct {
|
|
782
|
+
iodine_params_udata_s *u;
|
|
783
|
+
enum iodine___params_callback_e op;
|
|
784
|
+
void *ctx;
|
|
785
|
+
void *value;
|
|
786
|
+
void *old;
|
|
787
|
+
void *result;
|
|
788
|
+
fio_buf_info_s key;
|
|
789
|
+
const void *start;
|
|
790
|
+
size_t len;
|
|
791
|
+
int64_t integer;
|
|
792
|
+
double floating;
|
|
793
|
+
int status;
|
|
794
|
+
fio_str_info_s name;
|
|
795
|
+
fio_str_info_s filename;
|
|
796
|
+
fio_str_info_s content_type;
|
|
797
|
+
fio_str_info_s headers;
|
|
798
|
+
} iodine___params_callback_args_s;
|
|
799
|
+
|
|
800
|
+
FIO_SFUNC VALUE iodine___params_callback_dispatch(VALUE args_) {
|
|
801
|
+
iodine___params_callback_args_s *a = (iodine___params_callback_args_s *)args_;
|
|
802
|
+
switch (a->op) {
|
|
803
|
+
case IODINE_PARAMS_CB_NUMBER: {
|
|
804
|
+
fio_json_obj_result_s r = iodine___params_on_number(a->u, a->integer);
|
|
805
|
+
a->result = r.obj;
|
|
806
|
+
a->status = r.err;
|
|
807
|
+
} break;
|
|
808
|
+
case IODINE_PARAMS_CB_FLOAT: {
|
|
809
|
+
fio_json_obj_result_s r = iodine___params_on_float(a->u, a->floating);
|
|
810
|
+
a->result = r.obj;
|
|
811
|
+
a->status = r.err;
|
|
812
|
+
} break;
|
|
813
|
+
case IODINE_PARAMS_CB_STRING: {
|
|
814
|
+
fio_json_obj_result_s r = iodine___params_on_string(a->u, a->start, a->len);
|
|
815
|
+
a->result = r.obj;
|
|
816
|
+
a->status = r.err;
|
|
817
|
+
} break;
|
|
818
|
+
case IODINE_PARAMS_CB_STRING_SIMPLE: {
|
|
819
|
+
fio_json_obj_result_s r =
|
|
820
|
+
iodine___params_on_string_simple(a->u, a->start, a->len);
|
|
821
|
+
a->result = r.obj;
|
|
822
|
+
a->status = r.err;
|
|
823
|
+
} break;
|
|
824
|
+
case IODINE_PARAMS_CB_MAP: {
|
|
825
|
+
fio_json_push_result_s r = iodine___params_on_map(a->u, a->ctx, a->key);
|
|
826
|
+
a->result = r.obj;
|
|
827
|
+
a->old = r.old;
|
|
828
|
+
a->status = r.err;
|
|
829
|
+
} break;
|
|
830
|
+
case IODINE_PARAMS_CB_ARRAY: {
|
|
831
|
+
fio_json_push_result_s r = iodine___params_on_array(a->u, a->ctx, a->key);
|
|
832
|
+
a->result = r.obj;
|
|
833
|
+
a->old = r.old;
|
|
834
|
+
a->status = r.err;
|
|
835
|
+
} break;
|
|
836
|
+
case IODINE_PARAMS_CB_MAP_PUSH: {
|
|
837
|
+
fio_json_obj_result_s r =
|
|
838
|
+
iodine___params_map_push(a->u, a->ctx, a->key, a->value);
|
|
839
|
+
a->result = r.obj;
|
|
840
|
+
a->status = r.err;
|
|
841
|
+
} break;
|
|
842
|
+
case IODINE_PARAMS_CB_ARRAY_PUSH:
|
|
843
|
+
a->status = iodine___params_array_push(a->u, a->ctx, a->value);
|
|
844
|
+
break;
|
|
845
|
+
case IODINE_PARAMS_CB_FILE:
|
|
846
|
+
a->result = iodine___params_on_file(a->u,
|
|
847
|
+
a->name,
|
|
848
|
+
a->filename,
|
|
849
|
+
a->content_type,
|
|
850
|
+
a->headers);
|
|
851
|
+
break;
|
|
852
|
+
}
|
|
853
|
+
return Qnil;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
FIO_SFUNC void iodine___params_callback_protect(
|
|
857
|
+
iodine___params_callback_args_s *a) {
|
|
858
|
+
VALUE exception = a->u->c->store[IODINE_CONNECTION_STORE_params_exception];
|
|
859
|
+
if (exception == Qnil) {
|
|
860
|
+
int state = 0;
|
|
861
|
+
rb_protect(iodine___params_callback_dispatch, (VALUE)a, &state);
|
|
862
|
+
if (!state)
|
|
863
|
+
return;
|
|
864
|
+
exception = rb_errinfo();
|
|
865
|
+
a->u->c->store[IODINE_CONNECTION_STORE_params_exception] = exception;
|
|
866
|
+
rb_set_errinfo(Qnil);
|
|
867
|
+
}
|
|
868
|
+
a->result = NULL;
|
|
869
|
+
a->status = -1;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
#define IODINE_PARAMS_CALLBACK_WRAPPER_VOID(name, operation) \
|
|
873
|
+
FIO_SFUNC fio_json_push_result_s name(void *udata_, \
|
|
874
|
+
void *ctx, \
|
|
875
|
+
fio_buf_info_s key) { \
|
|
876
|
+
iodine___params_callback_args_s a = {.u = (iodine_params_udata_s *)udata_, \
|
|
877
|
+
.op = operation, \
|
|
878
|
+
.ctx = ctx, \
|
|
879
|
+
.key = key}; \
|
|
880
|
+
iodine___params_callback_protect(&a); \
|
|
881
|
+
return (fio_json_push_result_s){.obj = a.result, \
|
|
882
|
+
.old = a.old, \
|
|
883
|
+
.err = a.status}; \
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
IODINE_PARAMS_CALLBACK_WRAPPER_VOID(iodine___params_cb_on_map,
|
|
887
|
+
IODINE_PARAMS_CB_MAP)
|
|
888
|
+
IODINE_PARAMS_CALLBACK_WRAPPER_VOID(iodine___params_cb_on_array,
|
|
889
|
+
IODINE_PARAMS_CB_ARRAY)
|
|
890
|
+
#undef IODINE_PARAMS_CALLBACK_WRAPPER_VOID
|
|
891
|
+
|
|
892
|
+
FIO_SFUNC fio_json_obj_result_s iodine___params_cb_on_number(void *udata_,
|
|
893
|
+
int64_t i) {
|
|
894
|
+
iodine___params_callback_args_s a = {
|
|
895
|
+
.u = (iodine_params_udata_s *)udata_,
|
|
896
|
+
.op = IODINE_PARAMS_CB_NUMBER,
|
|
897
|
+
.integer = i,
|
|
898
|
+
};
|
|
899
|
+
iodine___params_callback_protect(&a);
|
|
900
|
+
return (fio_json_obj_result_s){.obj = a.result, .err = a.status};
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
FIO_SFUNC fio_json_obj_result_s iodine___params_cb_on_float(void *udata_,
|
|
904
|
+
double f) {
|
|
905
|
+
iodine___params_callback_args_s a = {
|
|
906
|
+
.u = (iodine_params_udata_s *)udata_,
|
|
907
|
+
.op = IODINE_PARAMS_CB_FLOAT,
|
|
908
|
+
.floating = f,
|
|
909
|
+
};
|
|
910
|
+
iodine___params_callback_protect(&a);
|
|
911
|
+
return (fio_json_obj_result_s){.obj = a.result, .err = a.status};
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
FIO_SFUNC fio_json_obj_result_s
|
|
915
|
+
iodine___params_cb_on_string(void *udata_, fio_buf_info_s str) {
|
|
916
|
+
iodine___params_callback_args_s a = {
|
|
917
|
+
.u = (iodine_params_udata_s *)udata_,
|
|
918
|
+
.op = IODINE_PARAMS_CB_STRING,
|
|
919
|
+
.start = str.buf,
|
|
920
|
+
.len = str.len,
|
|
921
|
+
};
|
|
922
|
+
iodine___params_callback_protect(&a);
|
|
923
|
+
return (fio_json_obj_result_s){.obj = a.result, .err = a.status};
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
FIO_SFUNC fio_json_obj_result_s
|
|
927
|
+
iodine___params_cb_on_string_simple(void *udata_, fio_buf_info_s str) {
|
|
928
|
+
iodine___params_callback_args_s a = {
|
|
929
|
+
.u = (iodine_params_udata_s *)udata_,
|
|
930
|
+
.op = IODINE_PARAMS_CB_STRING_SIMPLE,
|
|
931
|
+
.start = str.buf,
|
|
932
|
+
.len = str.len,
|
|
933
|
+
};
|
|
934
|
+
iodine___params_callback_protect(&a);
|
|
935
|
+
return (fio_json_obj_result_s){.obj = a.result, .err = a.status};
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
FIO_SFUNC fio_json_obj_result_s iodine___params_cb_map_push(void *udata_,
|
|
939
|
+
void *map,
|
|
940
|
+
fio_buf_info_s key,
|
|
941
|
+
void *value) {
|
|
942
|
+
iodine___params_callback_args_s a = {
|
|
943
|
+
.u = (iodine_params_udata_s *)udata_,
|
|
944
|
+
.op = IODINE_PARAMS_CB_MAP_PUSH,
|
|
945
|
+
.ctx = map,
|
|
946
|
+
.key = key,
|
|
947
|
+
.value = value,
|
|
948
|
+
};
|
|
949
|
+
iodine___params_callback_protect(&a);
|
|
950
|
+
return (fio_json_obj_result_s){.obj = a.result, .err = a.status};
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
FIO_SFUNC int iodine___params_cb_array_push(void *udata_,
|
|
954
|
+
void *ctx,
|
|
955
|
+
void *value) {
|
|
956
|
+
iodine___params_callback_args_s a = {
|
|
957
|
+
.u = (iodine_params_udata_s *)udata_,
|
|
958
|
+
.op = IODINE_PARAMS_CB_ARRAY_PUSH,
|
|
959
|
+
.ctx = ctx,
|
|
960
|
+
.value = value,
|
|
961
|
+
};
|
|
962
|
+
iodine___params_callback_protect(&a);
|
|
963
|
+
return a.status;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
FIO_SFUNC void *iodine___params_cb_on_file(void *udata_,
|
|
967
|
+
fio_str_info_s name,
|
|
968
|
+
fio_str_info_s filename,
|
|
969
|
+
fio_str_info_s content_type,
|
|
970
|
+
fio_str_info_s headers) {
|
|
971
|
+
iodine___params_callback_args_s a = {
|
|
972
|
+
.u = (iodine_params_udata_s *)udata_,
|
|
973
|
+
.op = IODINE_PARAMS_CB_FILE,
|
|
974
|
+
.name = name,
|
|
975
|
+
.filename = filename,
|
|
976
|
+
.content_type = content_type,
|
|
977
|
+
.headers = headers,
|
|
978
|
+
};
|
|
979
|
+
iodine___params_callback_protect(&a);
|
|
980
|
+
return a.result;
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
/* The STL treats this callback table as read-only. */
|
|
984
|
+
static fio_http_params_parse_callbacks_s IODINE_PARAMS_CALLBACKS = {
|
|
985
|
+
.on_null = iodine___json_on_null,
|
|
986
|
+
.on_true = iodine___json_on_true,
|
|
987
|
+
.on_false = iodine___params_on_false,
|
|
988
|
+
.on_number = iodine___params_cb_on_number,
|
|
989
|
+
.on_float = iodine___params_cb_on_float,
|
|
990
|
+
.on_string = iodine___params_cb_on_string,
|
|
991
|
+
.on_string_simple = iodine___params_cb_on_string_simple,
|
|
992
|
+
.on_map = iodine___params_cb_on_map,
|
|
993
|
+
.on_array = iodine___params_cb_on_array,
|
|
994
|
+
.map_push = iodine___params_cb_map_push,
|
|
995
|
+
.array_push = iodine___params_cb_array_push,
|
|
996
|
+
.is_array = iodine___params_is_array,
|
|
997
|
+
.is_map = iodine___params_is_map,
|
|
998
|
+
.free_unused_object = iodine___params_free_unused_object,
|
|
999
|
+
.on_file = iodine___params_cb_on_file,
|
|
1000
|
+
.on_file_data = iodine___params_on_file_data,
|
|
1001
|
+
};
|
|
1002
|
+
|
|
1003
|
+
/* *****************************************************************************
|
|
1004
|
+
Content-Type, Field Budget, and Parse Guard
|
|
1005
|
+
***************************************************************************** */
|
|
1006
|
+
|
|
1007
|
+
enum {
|
|
1008
|
+
IODINE_PARAMS_BODY_NONE = 0,
|
|
1009
|
+
IODINE_PARAMS_BODY_JSON,
|
|
1010
|
+
IODINE_PARAMS_BODY_URLENCODED,
|
|
1011
|
+
IODINE_PARAMS_BODY_MULTIPART
|
|
1012
|
+
};
|
|
1013
|
+
|
|
1014
|
+
FIO_SFUNC int iodine___params_media_eq(fio_str_info_s value,
|
|
1015
|
+
const char *lower,
|
|
1016
|
+
size_t len) {
|
|
1017
|
+
if (value.len != len)
|
|
1018
|
+
return 0;
|
|
1019
|
+
for (size_t i = 0; i < len; ++i)
|
|
1020
|
+
if (((unsigned char)value.buf[i] | 0x20) != (unsigned char)lower[i])
|
|
1021
|
+
return 0;
|
|
1022
|
+
return 1;
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
/* Length-delimited detection has no fixed media-token buffer or truncation. */
|
|
1026
|
+
FIO_SFUNC int iodine___params_body_type(fio_http_s *h) {
|
|
1027
|
+
fio_str_info_s ct =
|
|
1028
|
+
fio_http_request_header(h, FIO_STR_INFO2((char *)"content-type", 12), 0);
|
|
1029
|
+
if (!ct.buf)
|
|
1030
|
+
return IODINE_PARAMS_BODY_NONE;
|
|
1031
|
+
while (ct.len && (*ct.buf == ' ' || *ct.buf == '\t')) {
|
|
1032
|
+
++ct.buf;
|
|
1033
|
+
--ct.len;
|
|
1034
|
+
}
|
|
1035
|
+
size_t len = 0;
|
|
1036
|
+
while (len < ct.len && ct.buf[len] != ';' && ct.buf[len] != ' ' &&
|
|
1037
|
+
ct.buf[len] != '\t')
|
|
1038
|
+
++len;
|
|
1039
|
+
ct.len = len;
|
|
1040
|
+
if (iodine___params_media_eq(ct, "application/x-www-form-urlencoded", 33))
|
|
1041
|
+
return IODINE_PARAMS_BODY_URLENCODED;
|
|
1042
|
+
if (iodine___params_media_eq(ct, "multipart/form-data", 19))
|
|
1043
|
+
return IODINE_PARAMS_BODY_MULTIPART;
|
|
1044
|
+
if (iodine___params_media_eq(ct, "application/json", 16) ||
|
|
1045
|
+
iodine___params_media_eq(ct, "text/json", 9) ||
|
|
1046
|
+
iodine___params_media_eq(ct, "application/x-json", 18))
|
|
1047
|
+
return IODINE_PARAMS_BODY_JSON;
|
|
1048
|
+
if (ct.len > 17 &&
|
|
1049
|
+
iodine___params_media_eq(FIO_STR_INFO2(ct.buf, 12), "application/", 12) &&
|
|
1050
|
+
iodine___params_media_eq(FIO_STR_INFO2(ct.buf + ct.len - 5, 5),
|
|
1051
|
+
"+json",
|
|
1052
|
+
5))
|
|
1053
|
+
return IODINE_PARAMS_BODY_JSON;
|
|
1054
|
+
return IODINE_PARAMS_BODY_NONE;
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
FIO_SFUNC size_t iodine___params_limit(fio_http_s *h) {
|
|
1058
|
+
fio_http_settings_s *s = fio_http_settings(h);
|
|
1059
|
+
return (s && s->max_params) ? s->max_params : FIO_HTTP_PARAMS_MAX_FIELDS;
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
FIO_SFUNC size_t iodine___params_remaining(iodine_connection_s *c) {
|
|
1063
|
+
const size_t limit = iodine___params_limit(c->http);
|
|
1064
|
+
return c->params_spent >= limit ? 0 : limit - c->params_spent;
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
/* Charge exactly what the parser counted. Returns true only for proven excess.
|
|
1068
|
+
Saturating after rejection keeps later rescued calls within size_t. */
|
|
1069
|
+
FIO_SFUNC int iodine___params_charge(iodine_connection_s *c,
|
|
1070
|
+
fio_http_params_parse_result_s r,
|
|
1071
|
+
size_t remaining) {
|
|
1072
|
+
if (r.count > remaining) {
|
|
1073
|
+
c->params_spent = iodine___params_limit(c->http);
|
|
1074
|
+
return 1;
|
|
1075
|
+
}
|
|
1076
|
+
c->params_spent += r.count;
|
|
1077
|
+
return 0;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
typedef struct {
|
|
1081
|
+
iodine_connection_s *c;
|
|
1082
|
+
iodine_params_udata_s u;
|
|
1083
|
+
VALUE previous_tmp;
|
|
1084
|
+
VALUE previous_stage;
|
|
1085
|
+
VALUE previous_input;
|
|
1086
|
+
VALUE previous_exception;
|
|
1087
|
+
size_t body_position;
|
|
1088
|
+
size_t remaining;
|
|
1089
|
+
int body_type;
|
|
1090
|
+
uint8_t restore_body;
|
|
1091
|
+
} iodine___params_parse_s;
|
|
1092
|
+
|
|
1093
|
+
FIO_SFUNC VALUE iodine___params_parse_ensure(VALUE context_) {
|
|
1094
|
+
iodine___params_parse_s *p = (iodine___params_parse_s *)context_;
|
|
1095
|
+
iodine_connection_s *c = p->c;
|
|
1096
|
+
c->store[IODINE_CONNECTION_STORE_tmp] = p->previous_tmp;
|
|
1097
|
+
c->store[IODINE_CONNECTION_STORE_params_stage] = p->previous_stage;
|
|
1098
|
+
c->store[IODINE_CONNECTION_STORE_params_input] = p->previous_input;
|
|
1099
|
+
c->store[IODINE_CONNECTION_STORE_params_exception] = p->previous_exception;
|
|
1100
|
+
if (p->restore_body && c->http)
|
|
1101
|
+
fio_http_body_seek(c->http, (ssize_t)p->body_position);
|
|
1102
|
+
return Qnil;
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
FIO_SFUNC void iodine___params_parse_guard(iodine___params_parse_s *p,
|
|
1106
|
+
iodine_connection_s *c,
|
|
1107
|
+
int restore_body) {
|
|
1108
|
+
iodine_connection_s *connection = c;
|
|
1109
|
+
p->c = connection;
|
|
1110
|
+
p->previous_tmp = connection->store[IODINE_CONNECTION_STORE_tmp];
|
|
1111
|
+
p->previous_stage = connection->store[IODINE_CONNECTION_STORE_params_stage];
|
|
1112
|
+
p->previous_input = connection->store[IODINE_CONNECTION_STORE_params_input];
|
|
1113
|
+
p->previous_exception =
|
|
1114
|
+
connection->store[IODINE_CONNECTION_STORE_params_exception];
|
|
1115
|
+
p->restore_body = !!restore_body;
|
|
1116
|
+
if (restore_body)
|
|
1117
|
+
p->body_position = fio_http_body_pos(connection->http);
|
|
1118
|
+
connection->store[IODINE_CONNECTION_STORE_tmp] = Qnil;
|
|
1119
|
+
connection->store[IODINE_CONNECTION_STORE_params_stage] = Qnil;
|
|
1120
|
+
connection->store[IODINE_CONNECTION_STORE_params_input] = Qnil;
|
|
1121
|
+
connection->store[IODINE_CONNECTION_STORE_params_exception] = Qnil;
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
FIO_SFUNC void iodine___params_reraise(iodine_connection_s *c) {
|
|
1125
|
+
VALUE exception = c->store[IODINE_CONNECTION_STORE_params_exception];
|
|
1126
|
+
if (exception != Qnil)
|
|
1127
|
+
rb_exc_raise(exception);
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
FIO_SFUNC void iodine___params_raise_overflow(iodine_connection_s *c) {
|
|
1131
|
+
rb_raise(rb_eRangeError,
|
|
1132
|
+
"request params field limit exceeded (%zu)",
|
|
1133
|
+
iodine___params_limit(c->http));
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
FIO_SFUNC VALUE iodine___params_query_parse(VALUE context_) {
|
|
1137
|
+
iodine___params_parse_s *p = (iodine___params_parse_s *)context_;
|
|
1138
|
+
const size_t parse_limit =
|
|
1139
|
+
p->remaining == SIZE_MAX ? SIZE_MAX : p->remaining + 1;
|
|
1140
|
+
fio_http_params_parse_result_s r =
|
|
1141
|
+
fio_http_query_parse(p->c->http,
|
|
1142
|
+
.callbacks = &IODINE_PARAMS_CALLBACKS,
|
|
1143
|
+
.udata = &p->u,
|
|
1144
|
+
.limit = parse_limit);
|
|
1145
|
+
int overflow = iodine___params_charge(p->c, r, p->remaining);
|
|
1146
|
+
iodine___params_reraise(p->c);
|
|
1147
|
+
if (overflow)
|
|
1148
|
+
iodine___params_raise_overflow(p->c);
|
|
1149
|
+
if (r.err) {
|
|
1150
|
+
/* URL decoding keeps malformed escapes literal; remaining errors are
|
|
1151
|
+
name-depth / allocation resource failures, not field malformation. */
|
|
1152
|
+
rb_raise(rb_eRangeError, "request params nesting or parser limit exceeded");
|
|
1153
|
+
}
|
|
1154
|
+
return p->u.root;
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
FIO_SFUNC int iodine___params_json_object_prefix(fio_str_info_s body) {
|
|
1158
|
+
size_t i = 0;
|
|
1159
|
+
if (body.len >= 3 && (unsigned char)body.buf[0] == 0xEF &&
|
|
1160
|
+
(unsigned char)body.buf[1] == 0xBB && (unsigned char)body.buf[2] == 0xBF)
|
|
1161
|
+
i = 3;
|
|
1162
|
+
while (i < body.len && (body.buf[i] == ' ' || body.buf[i] == '\t' ||
|
|
1163
|
+
body.buf[i] == '\r' || body.buf[i] == '\n'))
|
|
1164
|
+
++i;
|
|
1165
|
+
return i < body.len && body.buf[i] == '{';
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
FIO_SFUNC VALUE iodine___params_body_parse(VALUE context_) {
|
|
1169
|
+
iodine___params_parse_s *p = (iodine___params_parse_s *)context_;
|
|
1170
|
+
VALUE destination = iodine___params_hash(p->c);
|
|
1171
|
+
p->u.root = rb_hash_new();
|
|
1172
|
+
p->c->store[IODINE_CONNECTION_STORE_params_stage] = p->u.root;
|
|
1173
|
+
fio_http_params_parse_result_s r = {.result = (void *)p->u.root};
|
|
1174
|
+
|
|
1175
|
+
if (p->body_type == IODINE_PARAMS_BODY_JSON) {
|
|
1176
|
+
const size_t len = fio_http_body_length(p->c->http);
|
|
1177
|
+
fio_http_body_seek(p->c->http, 0);
|
|
1178
|
+
fio_str_info_s body = fio_http_body_read(p->c->http, len);
|
|
1179
|
+
if (body.len != len || (len && !body.buf))
|
|
1180
|
+
rb_raise(rb_eIOError, "request body couldn't be read for JSON parsing");
|
|
1181
|
+
if (!iodine___params_json_object_prefix(body))
|
|
1182
|
+
return destination; /* non-object roots allocate no Ruby value tree */
|
|
1183
|
+
VALUE input = rb_str_new(body.buf, (long)len); /* includes a NUL guard */
|
|
1184
|
+
p->c->store[IODINE_CONNECTION_STORE_params_input] = input;
|
|
1185
|
+
fio_json_result_s json =
|
|
1186
|
+
fio_json_parse((fio_json_parser_callbacks_s *)&IODINE_PARAMS_CALLBACKS,
|
|
1187
|
+
&p->u,
|
|
1188
|
+
(void *)p->u.root,
|
|
1189
|
+
RSTRING_PTR(input),
|
|
1190
|
+
len);
|
|
1191
|
+
/* Preserve the public JSON error; all JSON parse errors discard the stage.
|
|
1192
|
+
*/
|
|
1193
|
+
r.err = (fio_http_params_parse_error_e)json.err;
|
|
1194
|
+
r.consumed = json.stop_pos;
|
|
1195
|
+
size_t i = json.stop_pos;
|
|
1196
|
+
while (i < len &&
|
|
1197
|
+
(RSTRING_PTR(input)[i] == ' ' || RSTRING_PTR(input)[i] == '\t' ||
|
|
1198
|
+
RSTRING_PTR(input)[i] == '\r' || RSTRING_PTR(input)[i] == '\n'))
|
|
1199
|
+
++i;
|
|
1200
|
+
if (i != len)
|
|
1201
|
+
p->u.malformed = 1; /* reject trailing non-whitespace contribution */
|
|
1202
|
+
} else {
|
|
1203
|
+
const size_t parse_limit =
|
|
1204
|
+
p->remaining == SIZE_MAX ? SIZE_MAX : p->remaining + 1;
|
|
1205
|
+
r = fio_http_body_parse(p->c->http,
|
|
1206
|
+
.callbacks = &IODINE_PARAMS_CALLBACKS,
|
|
1207
|
+
.udata = &p->u,
|
|
1208
|
+
.limit = parse_limit);
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
int overflow = p->body_type == IODINE_PARAMS_BODY_JSON
|
|
1212
|
+
? 0
|
|
1213
|
+
: iodine___params_charge(p->c, r, p->remaining);
|
|
1214
|
+
iodine___params_reraise(p->c);
|
|
1215
|
+
if (overflow)
|
|
1216
|
+
iodine___params_raise_overflow(p->c);
|
|
1217
|
+
if (p->u.io_error)
|
|
1218
|
+
rb_raise(rb_eIOError, "upload storage failed while parsing request body");
|
|
1219
|
+
if (p->body_type != IODINE_PARAMS_BODY_JSON) {
|
|
1220
|
+
if (r.err == FIO_HTTP_PARAMS_PARSE_IO)
|
|
1221
|
+
rb_raise(rb_eIOError, "request body couldn't be read for params parsing");
|
|
1222
|
+
if (r.err == FIO_HTTP_PARAMS_PARSE_DEPTH ||
|
|
1223
|
+
(r.err == FIO_HTTP_PARAMS_PARSE_ERROR &&
|
|
1224
|
+
p->body_type != IODINE_PARAMS_BODY_MULTIPART))
|
|
1225
|
+
rb_raise(rb_eRangeError,
|
|
1226
|
+
"request params nesting or parser limit exceeded");
|
|
1227
|
+
}
|
|
1228
|
+
if (!r.err && !p->u.malformed)
|
|
1229
|
+
iodine___params_merge_hash(destination, p->u.root);
|
|
1230
|
+
/* JSON and multipart failures discard the whole staged contribution. Custom
|
|
1231
|
+
JSON callback integers cannot be mistaken for HTTP IO / depth failures. */
|
|
1232
|
+
return destination;
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
/* *****************************************************************************
|
|
1236
|
+
Request-End Cleanup (called from iodine_connection.h lifecycle callbacks)
|
|
1237
|
+
***************************************************************************** */
|
|
1238
|
+
|
|
1239
|
+
static void *iodine___params_cleanup_in_gvl(void *c_) {
|
|
1240
|
+
iodine_connection_s *c = (iodine_connection_s *)c_;
|
|
1241
|
+
while (c->open_uploads)
|
|
1242
|
+
iodine___upload_close(c->open_uploads);
|
|
1243
|
+
fio_atomic_and(&c->flags, ~IODINE_CONNECTION_PARAMS_HAS_UPLOAD);
|
|
1244
|
+
return NULL;
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
/* The sticky flag avoids GVL acquisition for requests that never had uploads.
|
|
1248
|
+
The head itself is read and drained only with the GVL. */
|
|
1249
|
+
FIO_SFUNC void iodine_params_on_finish(iodine_connection_s *c) {
|
|
1250
|
+
if (!(fio_atomic_or(&c->flags, 0) & IODINE_CONNECTION_PARAMS_HAS_UPLOAD))
|
|
1251
|
+
return;
|
|
1252
|
+
iodine_c_call_with(iodine___params_cleanup_in_gvl, c);
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
/* Connection dfree runs with the GVL. Drain defensively so a later Upload
|
|
1256
|
+
finalizer can never unlink through a pointer into freed Connection memory. */
|
|
1257
|
+
FIO_SFUNC void iodine_params_on_connection_free(iodine_connection_s *c) {
|
|
1258
|
+
iodine___params_cleanup_in_gvl(c);
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
/* *****************************************************************************
|
|
1262
|
+
Iodine::Connection Methods (NeoRack `params` extension v0.0.1)
|
|
1263
|
+
***************************************************************************** */
|
|
1264
|
+
|
|
1265
|
+
/** Parses e.query once into the request's memoized params Hash. */
|
|
1266
|
+
FIO_SFUNC VALUE iodine_connection_params_query2params(VALUE o) {
|
|
1267
|
+
iodine_connection_s *c = iodine_connection_ptr(o);
|
|
1268
|
+
VALUE hash = iodine___params_hash(c);
|
|
1269
|
+
if (!c->http || (c->flags & IODINE_CONNECTION_PARAMS_QUERY))
|
|
1270
|
+
return hash;
|
|
1271
|
+
c->flags |= IODINE_CONNECTION_PARAMS_QUERY; /* merge once, even on error */
|
|
1272
|
+
iodine___params_parse_s p = {
|
|
1273
|
+
.u = {.c = c, .root = hash},
|
|
1274
|
+
.remaining = iodine___params_remaining(c),
|
|
1275
|
+
};
|
|
1276
|
+
iodine___params_parse_guard(&p, c, 0);
|
|
1277
|
+
return rb_ensure(iodine___params_query_parse,
|
|
1278
|
+
(VALUE)&p,
|
|
1279
|
+
iodine___params_parse_ensure,
|
|
1280
|
+
(VALUE)&p);
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
/** Parses a supported request body once, preserving body position throughout.
|
|
1284
|
+
*/
|
|
1285
|
+
FIO_SFUNC VALUE iodine_connection_params_body2params(VALUE o) {
|
|
1286
|
+
iodine_connection_s *c = iodine_connection_ptr(o);
|
|
1287
|
+
VALUE hash = iodine___params_hash(c);
|
|
1288
|
+
if (!c->http || (c->flags & IODINE_CONNECTION_PARAMS_BODY))
|
|
1289
|
+
return hash;
|
|
1290
|
+
c->flags |= IODINE_CONNECTION_PARAMS_BODY;
|
|
1291
|
+
const int type = iodine___params_body_type(c->http);
|
|
1292
|
+
if (type == IODINE_PARAMS_BODY_NONE || !fio_http_body_length(c->http))
|
|
1293
|
+
return hash; /* unknown / absent Content-Type, or no body: not parsed */
|
|
1294
|
+
iodine___params_parse_s p = {
|
|
1295
|
+
.u = {.c = c, .root = Qnil},
|
|
1296
|
+
.remaining = iodine___params_remaining(c),
|
|
1297
|
+
.body_type = type,
|
|
1298
|
+
};
|
|
1299
|
+
iodine___params_parse_guard(&p, c, 1);
|
|
1300
|
+
return rb_ensure(iodine___params_body_parse,
|
|
1301
|
+
(VALUE)&p,
|
|
1302
|
+
iodine___params_parse_ensure,
|
|
1303
|
+
(VALUE)&p);
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
/** Returns an existing Hash unchanged; otherwise calls query then body. */
|
|
1307
|
+
FIO_SFUNC VALUE iodine_connection_params(VALUE o) {
|
|
1308
|
+
iodine_connection_s *c = iodine_connection_ptr(o);
|
|
1309
|
+
if (c->store[IODINE_CONNECTION_STORE_params] != Qnil || !c->http)
|
|
1310
|
+
return iodine___params_hash(c);
|
|
1311
|
+
iodine_connection_params_query2params(o);
|
|
1312
|
+
return iodine_connection_params_body2params(o);
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
/* *****************************************************************************
|
|
1316
|
+
Init
|
|
1317
|
+
***************************************************************************** */
|
|
1318
|
+
|
|
1319
|
+
static void Init_Iodine_Params(void) {
|
|
1320
|
+
iodine_rb_BASE_UPLOAD =
|
|
1321
|
+
rb_define_class_under(iodine_rb_IODINE_BASE, "Upload", rb_cObject);
|
|
1322
|
+
rb_define_alloc_func(iodine_rb_BASE_UPLOAD, iodine_upload_alloc);
|
|
1323
|
+
rb_define_method(iodine_rb_BASE_UPLOAD, "read", iodine_upload_read, -1);
|
|
1324
|
+
rb_define_method(iodine_rb_BASE_UPLOAD, "rewind", iodine_upload_rewind, 0);
|
|
1325
|
+
rb_define_method(iodine_rb_BASE_UPLOAD, "size", iodine_upload_size, 0);
|
|
1326
|
+
rb_define_method(iodine_rb_BASE_UPLOAD, "save", iodine_upload_save, 1);
|
|
1327
|
+
rb_define_method(iodine_rb_BASE_UPLOAD, "close", iodine_upload_close, 0);
|
|
1328
|
+
rb_define_method(iodine_rb_BASE_UPLOAD, "closed?", iodine_upload_closed_p, 0);
|
|
1329
|
+
rb_define_method(iodine_rb_BASE_UPLOAD,
|
|
1330
|
+
"filename",
|
|
1331
|
+
iodine_upload_filename,
|
|
1332
|
+
0);
|
|
1333
|
+
rb_define_method(iodine_rb_BASE_UPLOAD, "type", iodine_upload_type, 0);
|
|
1334
|
+
rb_define_method(iodine_rb_BASE_UPLOAD, "name", iodine_upload_name, 0);
|
|
1335
|
+
rb_define_method(iodine_rb_BASE_UPLOAD, "headers", iodine_upload_headers, 0);
|
|
1336
|
+
rb_define_method(iodine_rb_BASE_UPLOAD, "[]", iodine_upload_aref, 1);
|
|
1337
|
+
rb_define_method(iodine_rb_BASE_UPLOAD, "[]=", iodine_upload_aset, 2);
|
|
1338
|
+
STORE.hold(iodine_rb_BASE_UPLOAD);
|
|
1339
|
+
|
|
1340
|
+
IODINE_PARAMS_EMPTY_KEY = rb_str_freeze(rb_str_new("", 0));
|
|
1341
|
+
STORE.hold(IODINE_PARAMS_EMPTY_KEY);
|
|
1342
|
+
IODINE_CONST_ID_STORE(IODINE_PARAMS_FILENAME_ID, "filename");
|
|
1343
|
+
IODINE_CONST_ID_STORE(IODINE_PARAMS_TYPE_ID, "type");
|
|
1344
|
+
IODINE_CONST_ID_STORE(IODINE_PARAMS_NAME_ID, "name");
|
|
1345
|
+
IODINE_CONST_ID_STORE(IODINE_PARAMS_TEMPFILE_ID, "tempfile");
|
|
1346
|
+
IODINE_CONST_ID_STORE(IODINE_PARAMS_HEAD_ID, "head");
|
|
1347
|
+
|
|
1348
|
+
rb_define_method(iodine_rb_IODINE_CONNECTION,
|
|
1349
|
+
"params",
|
|
1350
|
+
iodine_connection_params,
|
|
1351
|
+
0);
|
|
1352
|
+
rb_define_method(iodine_rb_IODINE_CONNECTION,
|
|
1353
|
+
"query2params",
|
|
1354
|
+
iodine_connection_params_query2params,
|
|
1355
|
+
0);
|
|
1356
|
+
rb_define_method(iodine_rb_IODINE_CONNECTION,
|
|
1357
|
+
"body2params",
|
|
1358
|
+
iodine_connection_params_body2params,
|
|
1359
|
+
0);
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
#endif /* H___IODINE_PARAMS___H */
|