rage-iodine 5.6.0 → 5.8.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7ffa46cab7e0fb7fc7550dd142f42b0850287fee60cc0d72b7f52757f52720f7
4
- data.tar.gz: c51b8ad9e316c21ed06e6803dcfb5af7bf70fc3eee84a0fd179b2730d2572473
3
+ metadata.gz: 7e2925f48b74a97ff3431fc421ebd451c1f8d4befd701ac454ceb0bb8ed2ae82
4
+ data.tar.gz: 6c101cf70146f67794adc91ca9eee8cfade412b27bbed04d2c9937e378f25fa8
5
5
  SHA512:
6
- metadata.gz: fa99ee4e7abb10b55b6f14aeeaef6bf03e85cf8aff9b33a66f7b4f64097c5ef824e0c3de6663caf00f5404b2d08b663debb60c796a0a44490138a96ac92a832c
7
- data.tar.gz: ca2a4ac6f633955b202dccf09661cb7799ae92e16078ef3776c1648e18e2b1ab620f470f4b99b3c75b7d0e8f9b064fad180fd5c9b32ab6ae0ddd60c9d601b58d
6
+ metadata.gz: bcfba0ed6461dad2478e3f35b1dc2246046d872b6159bac5f93651f7ce3fa74ef2ee86e90727e930bb48514a7266844d9eb616af253f2aef30d92646b65fe06b
7
+ data.tar.gz: 2c1fa87406f4f249551addeb72d59231684552bdc5fde2ed55539851afe9b14349497f7cc8e0dfa1969f71668e24b93d1cc591cc5a2e3d560e2b4abd67e56c7b
@@ -4,6 +4,7 @@ on:
4
4
  push:
5
5
  branches:
6
6
  - master
7
+ - release/*
7
8
  paths:
8
9
  - lib/iodine/version.rb
9
10
 
data/CHANGELOG.md CHANGED
@@ -6,6 +6,14 @@ Please notice that this change log contains changes for upcoming releases as wel
6
6
 
7
7
  ## Changes:
8
8
 
9
+ #### Change log v.5.8.0 (2026-10-09)
10
+
11
+ **Update**: Encode parameters as UTF-8
12
+
13
+ #### Change log v.5.7.0 (2026-10-04)
14
+
15
+ **Update**: Refactor parameters parsing
16
+
9
17
  #### Change log v.5.6.0 (2026-09-06)
10
18
 
11
19
  **Update**: TCP Socket Listen: Accept Queue
data/ext/iodine/http.c CHANGED
@@ -68,6 +68,7 @@ static VALUE cUploadedFile;
68
68
  static ID create_id;
69
69
  static ID path_id;
70
70
  static rb_encoding *IodineBinaryEncoding;
71
+ static rb_encoding *IodineUTF8Encoding;
71
72
 
72
73
  /* *****************************************************************************
73
74
  Small Helpers
@@ -1842,8 +1843,8 @@ typedef struct {
1842
1843
 
1843
1844
  #define http_mime_parser2fio(parser) ((http_fio_mime_s *)(parser))
1844
1845
 
1845
- /** Parse a parameter key and add it to the `params` hash. Check `parse_nested_query_internal` for reference. */
1846
- static void add_to_params(VALUE params, char *key, size_t key_len, VALUE value) {
1846
+ /** Parse a decoded parameter key and add it to the `params` hash. */
1847
+ void http_add_to_params(VALUE params, char *key, size_t key_len, VALUE value) {
1847
1848
  char *pos = NULL;
1848
1849
  if (key_len > 1) {
1849
1850
  pos = memchr(key + 1, '[', key_len - 1);
@@ -1925,7 +1926,7 @@ static void add_to_params(VALUE params, char *key, size_t key_len, VALUE value)
1925
1926
  if (RB_TYPE_P(nested_val, T_HASH)) {
1926
1927
  char *n_pos_start = k_pos + 1, *n_pos_end = k_pos + 1;
1927
1928
 
1928
- while (nested_val != Qnil && *n_pos_start == '[' && *(n_pos_start + 1) != ']' && *n_pos_end != '&' && *n_pos_end != '=' && n_pos_end < end) {
1929
+ while (nested_val != Qnil && n_pos_end < end && *n_pos_start == '[' && *(n_pos_start + 1) != ']') {
1929
1930
  n_pos_end++;
1930
1931
 
1931
1932
  if (*n_pos_end == ']') {
@@ -2010,8 +2011,8 @@ static void http_mime_parser_on_data(http_mime_parser_s *parser, void *name,
2010
2011
  size_t value_len) {
2011
2012
  // for regular values - just add them to params
2012
2013
  if (!filename_len) {
2013
- VALUE r_value = rb_enc_str_new(value, value_len, IodineBinaryEncoding);
2014
- add_to_params(http_mime_parser2fio(parser)->params, name, name_len, r_value);
2014
+ VALUE r_value = rb_enc_str_new(value, value_len, IodineUTF8Encoding);
2015
+ http_add_to_params(http_mime_parser2fio(parser)->params, name, name_len, r_value);
2015
2016
  return;
2016
2017
  }
2017
2018
 
@@ -2024,7 +2025,7 @@ static void http_mime_parser_on_data(http_mime_parser_s *parser, void *name,
2024
2025
  close(file);
2025
2026
 
2026
2027
  VALUE r_value = build_file_value(r_tempfile, filename, filename_len, mimetype, mimetype_len);
2027
- add_to_params(http_mime_parser2fio(parser)->params, name, name_len, r_value);
2028
+ http_add_to_params(http_mime_parser2fio(parser)->params, name, name_len, r_value);
2028
2029
  }
2029
2030
 
2030
2031
  /** Called when the data didn't fit in the buffer. Data will be streamed. */
@@ -2073,7 +2074,7 @@ static void http_mime_parser_on_partial_end(http_mime_parser_s *parser) {
2073
2074
 
2074
2075
  // for a file - add to params and close the fd
2075
2076
  if (fio_parser->partial_fd) {
2076
- add_to_params(fio_parser->params, RSTRING_PTR(fio_parser->partial_name), RSTRING_LEN(fio_parser->partial_name), fio_parser->partial_tempfile);
2077
+ http_add_to_params(fio_parser->params, RSTRING_PTR(fio_parser->partial_name), RSTRING_LEN(fio_parser->partial_name), fio_parser->partial_tempfile);
2077
2078
  close(fio_parser->partial_fd);
2078
2079
 
2079
2080
  fio_parser->partial_name = Qnil;
@@ -2085,8 +2086,8 @@ static void http_mime_parser_on_partial_end(http_mime_parser_s *parser) {
2085
2086
 
2086
2087
  FIOBJ slice = fiobj_data_slice(fio_parser->h->body, fio_parser->partial_offset, fio_parser->partial_length);
2087
2088
  fio_str_info_s s_slice = fiobj_obj2cstr(slice);
2088
- VALUE r_value = rb_enc_str_new(s_slice.data, s_slice.len, IodineBinaryEncoding);
2089
- add_to_params(fio_parser->params, RSTRING_PTR(fio_parser->partial_name), RSTRING_LEN(fio_parser->partial_name), r_value);
2089
+ VALUE r_value = rb_enc_str_new(s_slice.data, s_slice.len, IodineUTF8Encoding);
2090
+ http_add_to_params(fio_parser->params, RSTRING_PTR(fio_parser->partial_name), RSTRING_LEN(fio_parser->partial_name), r_value);
2090
2091
 
2091
2092
  fio_parser->partial_name = Qnil;
2092
2093
  fio_parser->partial_length = 0;
@@ -2701,6 +2702,7 @@ void http_init(void) {
2701
2702
  path_id = rb_intern("path");
2702
2703
 
2703
2704
  IodineBinaryEncoding = rb_enc_find("binary");
2705
+ IodineUTF8Encoding = rb_enc_find("UTF-8");
2704
2706
  }
2705
2707
 
2706
2708
  /* *****************************************************************************
data/ext/iodine/http.h CHANGED
@@ -781,6 +781,9 @@ HTTP GET and POST parsing helpers
781
781
  */
782
782
  VALUE http_parse_multipart(http_s *h, char *content_type, size_t content_type_len);
783
783
 
784
+ /** Adds a decoded parameter key and Ruby value to a nested parameters hash. */
785
+ void http_add_to_params(VALUE params, char *key, size_t key_len, VALUE value);
786
+
784
787
  /**
785
788
  * Parses the query part of an HTTP request/response. Uses `http_add2hash`.
786
789
  *
@@ -210,7 +210,7 @@ static VALUE iodine_rfc2109(VALUE self, VALUE rtm) {
210
210
  }
211
211
 
212
212
  /**
213
- Convert query string into a Ruby object in (almost always) one pass with no recursion.
213
+ Convert query string into a Ruby object without recursion.
214
214
 
215
215
 
216
216
  Iodine::Rack::Utils.parse_nested_query("a=1&b[]=2&c[d]=3") # => { "a" => "1", "b" => ["2"], "c" => { "d" => "3" } }
@@ -240,182 +240,68 @@ Convert query string into a Ruby object in (almost always) one pass with no recu
240
240
  Rack: 64257.9 i/s - 16.94x slower
241
241
 
242
242
  */
243
+ static VALUE build_query_component(char *str, size_t len,
244
+ uint8_t should_decode) {
245
+ VALUE result = rb_enc_str_new(str, len, IodineUTF8Encoding);
246
+ if (should_decode) {
247
+ url_decode_inplace(Qnil, result);
248
+ }
249
+ return result;
250
+ }
251
+
243
252
  static VALUE parse_nested_query_internal(char *str, size_t len) {
244
- uint8_t should_decode = 0;
245
253
  VALUE params = rb_hash_new();
246
-
247
- char *pos = str, *k_pos = str, *v_pos = str;
248
- const char *end = str + len;
254
+ char *pos = str;
255
+ char *end = str + len;
249
256
 
250
257
  while (pos < end) {
251
- k_pos++;
252
-
253
- if (*k_pos == '=') { // plain param
254
- v_pos = k_pos + 1;
255
- while (*v_pos != '&' && v_pos < end) {
256
- if (*v_pos == '%' || *v_pos == '+') {
257
- should_decode = 1;
258
- }
259
- v_pos++;
260
- }
261
-
262
- VALUE k = ID2SYM(rb_intern2(pos, k_pos - pos)), v = rb_str_new(k_pos + 1, v_pos - k_pos - 1);
263
- if (should_decode) {
264
- rb_hash_aset(params, k, url_decode_inplace(Qnil, v));
265
- should_decode = 0;
266
- } else {
267
- rb_hash_aset(params, k, v);
268
- }
269
- pos = k_pos = v_pos + 1;
270
-
271
- } else if (*k_pos == '[') { // things are about to get rough now
272
- VALUE arr = Qnil, hsh = Qnil, hsh_key = Qnil;
273
- VALUE k = ID2SYM(rb_intern2(pos, k_pos - pos)), v;
274
- pos = k_pos;
275
- uint8_t depth = 0;
276
-
277
- while (*pos != '=' && pos < end) {
278
- if (depth++ == PARAMS_MAX_DEPTH) {
279
- rb_raise(rb_eRuntimeError, "Params too deep");
280
- }
281
-
282
- if (*pos == '[' && *(pos + 1) == ']') { // array
283
- if (arr != Qnil) {
284
- VALUE tmp = rb_ary_new();
285
- rb_ary_push(arr, tmp);
286
- arr = tmp;
287
- } else if (hsh != Qnil) {
288
- arr = rb_hash_aref(hsh, hsh_key);
289
- if (arr == Qnil) {
290
- arr = rb_ary_new();
291
- rb_hash_aset(hsh, hsh_key, arr);
292
- } else {
293
- Check_Type(arr, T_ARRAY);
294
- }
295
- hsh = hsh_key = Qnil;
296
- } else {
297
- VALUE tmp = rb_hash_aref(params, k);
298
- if (tmp != Qnil) {
299
- Check_Type(tmp, T_ARRAY);
300
- arr = tmp;
301
- } else {
302
- arr = rb_ary_new();
303
- rb_hash_aset(params, k, arr);
304
- }
305
- }
306
-
307
- pos += 2;
308
-
309
- } else if (*pos == '[' && *(pos + 1) != ']') { // hash
310
- if (pos + 2 < end) {
311
- k_pos = memchr(pos + 2, ']', end - pos - 2);
312
- } else {
313
- k_pos = NULL;
314
- }
315
-
316
- if (!k_pos) {
317
- rb_raise(rb_eRuntimeError, "Bad params");
318
- }
319
-
320
- VALUE prev_hsh_key = hsh_key;
321
- hsh_key = ID2SYM(rb_intern2(pos + 1, k_pos - pos - 1));
322
-
323
- if (hsh != Qnil) {
324
- VALUE existing = rb_hash_aref(hsh, prev_hsh_key);
325
- if (existing != Qnil) {
326
- Check_Type(existing, T_HASH);
327
- hsh = existing;
328
- } else {
329
- VALUE tmp = rb_hash_new();
330
- rb_hash_aset(hsh, prev_hsh_key, tmp);
331
- hsh = tmp;
332
- }
333
- } else if (arr != Qnil) {
334
- VALUE nested_val = Qnil;
335
- if (RARRAY_LEN(arr) != 0) {
336
- VALUE tmp = rb_ary_entry(arr, -1);
337
- Check_Type(tmp, T_HASH);
338
- nested_val = rb_hash_aref(tmp, hsh_key);
339
- }
340
-
341
- // handle Rails' smart key grouping;
342
- // "users[][data][id]=11&users[][data][name]=ross&users[][data][id]=22&users[][data][name]=chandler"
343
- // should result in:
344
- // { users: [{ data: { id: "11", name: "ross" } }, { data: { id: "22", name: "chandler" } }] }
345
- if (RB_TYPE_P(nested_val, T_HASH)) {
346
- char *n_pos_start = k_pos + 1, *n_pos_end = k_pos + 1;
347
-
348
- while (nested_val != Qnil && *n_pos_start == '[' && *(n_pos_start + 1) != ']' && *n_pos_end != '&' && *n_pos_end != '=' && n_pos_end < end) {
349
- n_pos_end++;
350
-
351
- if (*n_pos_end == ']') {
352
- VALUE nested_key = ID2SYM(rb_intern2(n_pos_start + 1, n_pos_end - n_pos_start - 1));
353
- Check_Type(nested_val, T_HASH);
354
- nested_val = rb_hash_aref(nested_val, nested_key);
355
- n_pos_start = ++n_pos_end;
356
- }
357
- }
358
- }
359
-
360
- // use a new hash if there's none under this key or the existing one already has such key
361
- if (RARRAY_LEN(arr) == 0 || nested_val != Qnil) {
362
- hsh = rb_hash_new();
363
- rb_ary_push(arr, hsh);
364
- } else {
365
- hsh = rb_ary_entry(arr, -1);
366
- if (hsh != Qnil) {
367
- Check_Type(hsh, T_HASH);
368
- }
369
- }
370
-
371
- arr = Qnil;
372
- } else {
373
- VALUE tmp = rb_hash_aref(params, k);
374
- if (tmp != Qnil) {
375
- Check_Type(tmp, T_HASH);
376
- hsh = tmp;
377
- } else {
378
- hsh = rb_hash_new();
379
- rb_hash_aset(params, k, hsh);
380
- }
381
- }
382
-
383
- pos = k_pos + 1;
384
-
258
+ char *pair_end = pos;
259
+ char *separator = NULL;
260
+ uint8_t key_should_decode = 0;
261
+ uint8_t value_should_decode = 0;
262
+
263
+ while (pair_end < end && *pair_end != '&') {
264
+ if (!separator && *pair_end == '=') {
265
+ separator = pair_end;
266
+ } else if (*pair_end == '%' || *pair_end == '+') {
267
+ if (separator) {
268
+ value_should_decode = 1;
385
269
  } else {
386
- pos++;
270
+ key_should_decode = 1;
387
271
  }
388
272
  }
273
+ pair_end++;
274
+ }
389
275
 
390
- //
391
- // write the final object into `params`
392
- //
393
-
394
- for (v_pos = pos + 1; *v_pos != '&' && v_pos < end; v_pos++) {
395
- if (*v_pos == '%' || *v_pos == '+') {
396
- should_decode = 1;
397
- }
398
- }
276
+ char *key_end = separator ? separator : pair_end;
277
+ VALUE key = Qnil;
278
+ char *key_data = pos;
279
+ size_t key_len = key_end - pos;
399
280
 
400
- v = rb_str_new(pos + 1, v_pos - pos - 1);
401
- if (should_decode) {
402
- url_decode_inplace(Qnil, v);
403
- should_decode = 0;
404
- }
281
+ if (key_should_decode) {
282
+ key = build_query_component(pos, key_len, 1);
283
+ key_data = RSTRING_PTR(key);
284
+ key_len = RSTRING_LEN(key);
285
+ }
405
286
 
406
- if (arr != Qnil) {
407
- rb_ary_push(arr, v);
408
- } else {
409
- rb_hash_aset(hsh, hsh_key, v);
410
- }
287
+ VALUE value;
288
+ if (separator) {
289
+ value = build_query_component(separator + 1,
290
+ pair_end - separator - 1,
291
+ value_should_decode);
292
+ } else if (key_len > 1 && memchr(key_data + 1, '[', key_len - 1)) {
293
+ value = rb_enc_str_new(NULL, 0, IodineUTF8Encoding);
294
+ } else {
295
+ value = Qnil;
296
+ }
411
297
 
412
- pos = k_pos = v_pos + 1;
298
+ http_add_to_params(params, key_data, key_len, value);
299
+ RB_GC_GUARD(key);
413
300
 
414
- } else if (*k_pos == '&' || k_pos >= end) {
415
- VALUE k = ID2SYM(rb_intern2(pos, k_pos - pos));
416
- rb_hash_aset(params, k, Qnil);
417
- pos = k_pos + 1;
301
+ if (pair_end == end) {
302
+ break;
418
303
  }
304
+ pos = pair_end + 1;
419
305
  }
420
306
 
421
307
  return params;
@@ -425,21 +311,26 @@ static VALUE parse_nested_query_internal(char *str, size_t len) {
425
311
  Convert query string into a Ruby object.
426
312
  */
427
313
  static VALUE parse_nested_query(VALUE self, VALUE r_str) {
428
- return parse_nested_query_internal(RSTRING_PTR(r_str), RSTRING_LEN(r_str));
429
314
  (void)self;
315
+ Check_Type(r_str, T_STRING);
316
+ VALUE result =
317
+ parse_nested_query_internal(RSTRING_PTR(r_str), RSTRING_LEN(r_str));
318
+ RB_GC_GUARD(r_str);
319
+ return result;
430
320
  }
431
321
 
432
322
  /**
433
323
  Convert urlencoded body into a Ruby object.
434
324
  */
435
325
  static VALUE parse_urlencoded_nested_query(VALUE self, VALUE r_str) {
436
- ssize_t len = http_decode_url(RSTRING_PTR(r_str), RSTRING_PTR(r_str), RSTRING_LEN(r_str));
437
- if (len < 0) {
438
- rb_raise(rb_eRuntimeError, "Invalid encoding");
439
- }
440
-
441
- return parse_nested_query_internal(RSTRING_PTR(r_str), len);
442
326
  (void)self;
327
+ Check_Type(r_str, T_STRING);
328
+ char *str = RSTRING_PTR(r_str);
329
+ size_t len = RSTRING_LEN(r_str);
330
+
331
+ VALUE result = parse_nested_query_internal(str, len);
332
+ RB_GC_GUARD(r_str);
333
+ return result;
443
334
  }
444
335
 
445
336
  /**
@@ -1,3 +1,3 @@
1
1
  module Iodine
2
- VERSION = '5.6.0'.freeze
2
+ VERSION = '5.8.0'.freeze
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rage-iodine
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.6.0
4
+ version: 5.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Boaz Segev