rage-iodine 1.8.0 → 2.0.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: a5b5db0d766696d96ae2730a5d954229249dbd982bceba8fb63f184a1da62beb
4
- data.tar.gz: 397f34465e987d8f63c253f61ee7e3efd098aa94af63a55eeff81c5c13f5b088
3
+ metadata.gz: 4221fdb6705dce60cb5e71da2644f639200571f690930fb9c428e29c4ae20032
4
+ data.tar.gz: c01222eb2a55634b414c456d46319e5d2072a2ed89b258e709ea1d236cf490b9
5
5
  SHA512:
6
- metadata.gz: 00a9d42202213c7c34292b5d70074d17300736004a5d14df21bf9f1f9b933d3172335e07dbb5617f38c278a1f5073333114622dacd69870f258c65181f638cd4
7
- data.tar.gz: bc5d84cf74cd4ee5078452fbd01ac67de8c0d2b7026d71af53136d47e862634fbf25d4910cb7e79280b77dab96bd91e76d92e1de41593e9ed2c11e0d0a2b27fe
6
+ metadata.gz: ad43ae3016c28c794926b71fd62b4f48c1362c7865f8c0118b8b5548ded1885b28c198a9d93f87a387873b4451b9f7310b545ba079cc26708521dc8c47497f7e
7
+ data.tar.gz: 0067566c6d7495bc2cc4652f49278d1eaa22f5afd9764cb84b654c44fd2483f7f14ccdcddd656b4ec529ad43c4d285ba8452976b075551c9683651ace0704a5a
data/ext/iodine/http.c CHANGED
@@ -19,6 +19,7 @@ Feel free to copy, use and enjoy according to the license provided.
19
19
  #include <unistd.h>
20
20
 
21
21
  #include <pthread.h>
22
+ #include <ruby/io.h>
22
23
 
23
24
  #ifndef HAVE_TM_TM_ZONE
24
25
  #if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || \
@@ -61,6 +62,13 @@ fio_tls_alpn_add(void *tls, const char *protocol_name,
61
62
  #pragma weak fio_tls_alpn_add
62
63
  #endif
63
64
 
65
+ static VALUE cTempfile;
66
+ static VALUE tempfile_args;
67
+ static VALUE cUploadedFile;
68
+ static ID create_id;
69
+ static ID path_id;
70
+ static rb_encoding *IodineBinaryEncoding;
71
+
64
72
  /* *****************************************************************************
65
73
  Small Helpers
66
74
  ***************************************************************************** */
@@ -1797,105 +1805,262 @@ typedef struct {
1797
1805
  size_t pos;
1798
1806
  size_t partial_offset;
1799
1807
  size_t partial_length;
1800
- FIOBJ partial_name;
1808
+ VALUE partial_name;
1809
+ int partial_fd;
1810
+ VALUE partial_tempfile;
1811
+ VALUE params;
1801
1812
  } http_fio_mime_s;
1802
1813
 
1803
1814
  #define http_mime_parser2fio(parser) ((http_fio_mime_s *)(parser))
1804
1815
 
1816
+ /** Parse a parameter key and add it to the `params` hash. Check `parse_nested_query_internal` for reference. */
1817
+ static void add_to_params(VALUE params, char *key, size_t key_len, VALUE value) {
1818
+ char *pos = NULL;
1819
+ if (key_len > 1) {
1820
+ pos = memchr(key + 1, '[', key_len - 1);
1821
+ }
1822
+
1823
+ if (!pos) {
1824
+ VALUE k = ID2SYM(rb_intern2(key, key_len));
1825
+ rb_hash_aset(params, k, value);
1826
+ } else {
1827
+ VALUE arr = Qnil, hsh = Qnil, hsh_key = Qnil;
1828
+ VALUE k = ID2SYM(rb_intern2(key, pos - key));
1829
+ char *k_pos, *end = key + key_len;
1830
+ uint8_t depth = 0;
1831
+
1832
+ while (pos < end) {
1833
+ if (depth++ == PARAMS_MAX_DEPTH) {
1834
+ rb_raise(rb_eRuntimeError, "Params too deep");
1835
+ }
1836
+
1837
+ if (*pos == '[' && *(pos + 1) == ']') { // array
1838
+ if (arr != Qnil) {
1839
+ VALUE tmp = rb_ary_new();
1840
+ rb_ary_push(arr, tmp);
1841
+ arr = tmp;
1842
+ } else if (hsh != Qnil) {
1843
+ arr = rb_hash_aref(hsh, hsh_key);
1844
+ if (arr == Qnil) {
1845
+ arr = rb_ary_new();
1846
+ rb_hash_aset(hsh, hsh_key, arr);
1847
+ } else {
1848
+ Check_Type(arr, T_ARRAY);
1849
+ }
1850
+ hsh = hsh_key = Qnil;
1851
+ } else {
1852
+ VALUE tmp = rb_hash_aref(params, k);
1853
+ if (tmp != Qnil) {
1854
+ Check_Type(tmp, T_ARRAY);
1855
+ arr = tmp;
1856
+ } else {
1857
+ arr = rb_ary_new();
1858
+ rb_hash_aset(params, k, arr);
1859
+ }
1860
+ }
1861
+
1862
+ pos += 2;
1863
+
1864
+ } else if (*pos == '[' && *(pos + 1) != ']') { // hash
1865
+ if (pos + 2 < end) {
1866
+ k_pos = memchr(pos + 2, ']', end - pos - 2);
1867
+ } else {
1868
+ k_pos = NULL;
1869
+ }
1870
+
1871
+ if (!k_pos) {
1872
+ rb_raise(rb_eRuntimeError, "Bad params");
1873
+ }
1874
+
1875
+ VALUE prev_hsh_key = hsh_key;
1876
+ hsh_key = ID2SYM(rb_intern2(pos + 1, k_pos - pos - 1));
1877
+
1878
+ if (hsh != Qnil) {
1879
+ VALUE existing = rb_hash_aref(hsh, prev_hsh_key);
1880
+ if (existing != Qnil) {
1881
+ Check_Type(existing, T_HASH);
1882
+ hsh = existing;
1883
+ } else {
1884
+ VALUE tmp = rb_hash_new();
1885
+ rb_hash_aset(hsh, prev_hsh_key, tmp);
1886
+ hsh = tmp;
1887
+ }
1888
+ } else if (arr != Qnil) {
1889
+ VALUE nested_val = Qnil;
1890
+ if (RARRAY_LEN(arr) != 0) {
1891
+ VALUE tmp = rb_ary_entry(arr, -1);
1892
+ Check_Type(tmp, T_HASH);
1893
+ nested_val = rb_hash_aref(tmp, hsh_key);
1894
+ }
1895
+
1896
+ if (RB_TYPE_P(nested_val, T_HASH)) {
1897
+ char *n_pos_start = k_pos + 1, *n_pos_end = k_pos + 1;
1898
+
1899
+ while (nested_val != Qnil && *n_pos_start == '[' && *(n_pos_start + 1) != ']' && *n_pos_end != '&' && *n_pos_end != '=' && n_pos_end < end) {
1900
+ n_pos_end++;
1901
+
1902
+ if (*n_pos_end == ']') {
1903
+ VALUE nested_key = ID2SYM(rb_intern2(n_pos_start + 1, n_pos_end - n_pos_start - 1));
1904
+ Check_Type(nested_val, T_HASH);
1905
+ nested_val = rb_hash_aref(nested_val, nested_key);
1906
+ n_pos_start = ++n_pos_end;
1907
+ }
1908
+ }
1909
+ }
1910
+
1911
+ if (RARRAY_LEN(arr) == 0 || nested_val != Qnil) {
1912
+ hsh = rb_hash_new();
1913
+ rb_ary_push(arr, hsh);
1914
+ } else {
1915
+ hsh = rb_ary_entry(arr, -1);
1916
+ if (hsh != Qnil) {
1917
+ Check_Type(hsh, T_HASH);
1918
+ }
1919
+ }
1920
+
1921
+ arr = Qnil;
1922
+ } else {
1923
+ VALUE tmp = rb_hash_aref(params, k);
1924
+ if (tmp != Qnil) {
1925
+ Check_Type(tmp, T_HASH);
1926
+ hsh = tmp;
1927
+ } else {
1928
+ hsh = rb_hash_new();
1929
+ rb_hash_aset(params, k, hsh);
1930
+ }
1931
+ }
1932
+
1933
+ pos = k_pos + 1;
1934
+
1935
+ } else {
1936
+ pos++;
1937
+ }
1938
+ }
1939
+
1940
+ if (arr != Qnil) {
1941
+ rb_ary_push(arr, value);
1942
+ } else {
1943
+ rb_hash_aset(hsh, hsh_key, value);
1944
+ }
1945
+ }
1946
+ }
1947
+
1948
+ static inline void cleanup_temp_file(void *r_path) {
1949
+ char *path = StringValueCStr(r_path);
1950
+ IodineStore.remove((VALUE)r_path);
1951
+ unlink(path);
1952
+ }
1953
+
1954
+ static VALUE create_temp_file(http_mime_parser_s *parser, char **path) {
1955
+ VALUE tempfile = rb_funcallv_kw(cTempfile, create_id, 1, &tempfile_args, RB_PASS_KEYWORDS);
1956
+
1957
+ VALUE r_path = rb_funcall2(tempfile, path_id, 0, NULL);
1958
+ IodineStore.add(r_path);
1959
+ *path = StringValueCStr(r_path);
1960
+
1961
+ http_s *h = http_mime_parser2fio(parser)->h;
1962
+ http_fio_protocol_s *p = (http_fio_protocol_s *)h->private_data.flag;
1963
+ fio_uuid_link(p->uuid, (void *)r_path, cleanup_temp_file); // schedule file deletion
1964
+
1965
+ return tempfile;
1966
+ }
1967
+
1968
+ static VALUE build_file_value(VALUE file, char *filename, size_t filename_len, char *mimetype, size_t mimetype_len) {
1969
+ VALUE args[3] = { file, rb_enc_str_new(filename, filename_len, IodineBinaryEncoding), rb_enc_str_new(mimetype, mimetype_len, IodineBinaryEncoding) };
1970
+ VALUE uploaded_file = rb_class_new_instance(3, args, cUploadedFile);
1971
+
1972
+ return uploaded_file;
1973
+ }
1974
+
1805
1975
  /** Called when all the data is available at once. */
1806
1976
  static void http_mime_parser_on_data(http_mime_parser_s *parser, void *name,
1807
1977
  size_t name_len, void *filename,
1808
1978
  size_t filename_len, void *mimetype,
1809
1979
  size_t mimetype_len, void *value,
1810
1980
  size_t value_len) {
1981
+ // for regular values - just add them to params
1811
1982
  if (!filename_len) {
1812
- http_add2hash(http_mime_parser2fio(parser)->h->params, name, name_len,
1813
- value, value_len, 0);
1983
+ VALUE r_value = rb_enc_str_new(value, value_len, IodineBinaryEncoding);
1984
+ add_to_params(http_mime_parser2fio(parser)->params, name, name_len, r_value);
1814
1985
  return;
1815
1986
  }
1816
- FIOBJ n = fiobj_str_new(name, name_len);
1817
- fiobj_str_write(n, "[data]", 6);
1818
- fio_str_info_s tmp = fiobj_obj2cstr(n);
1819
- http_add2hash(http_mime_parser2fio(parser)->h->params, tmp.data, tmp.len,
1820
- value, value_len, 0);
1821
- fiobj_str_resize(n, name_len);
1822
- fiobj_str_write(n, "[name]", 6);
1823
- tmp = fiobj_obj2cstr(n);
1824
- http_add2hash(http_mime_parser2fio(parser)->h->params, tmp.data, tmp.len,
1825
- filename, filename_len, 0);
1826
- if (mimetype_len) {
1827
- fiobj_str_resize(n, name_len);
1828
- fiobj_str_write(n, "[type]", 6);
1829
- tmp = fiobj_obj2cstr(n);
1830
- http_add2hash(http_mime_parser2fio(parser)->h->params, tmp.data, tmp.len,
1831
- mimetype, mimetype_len, 0);
1832
- }
1833
- fiobj_free(n);
1987
+
1988
+ // write file data into a temporary file
1989
+ char* path;
1990
+ VALUE r_tempfile = create_temp_file(parser, &path);
1991
+
1992
+ int file = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0777);
1993
+ write(file, value, value_len);
1994
+ close(file);
1995
+
1996
+ VALUE r_value = build_file_value(r_tempfile, filename, filename_len, mimetype, mimetype_len);
1997
+ add_to_params(http_mime_parser2fio(parser)->params, name, name_len, r_value);
1834
1998
  }
1835
1999
 
1836
2000
  /** Called when the data didn't fit in the buffer. Data will be streamed. */
1837
2001
  static void http_mime_parser_on_partial_start(
1838
2002
  http_mime_parser_s *parser, void *name, size_t name_len, void *filename,
1839
2003
  size_t filename_len, void *mimetype, size_t mimetype_len) {
1840
- http_mime_parser2fio(parser)->partial_length = 0;
1841
- http_mime_parser2fio(parser)->partial_offset = 0;
1842
- http_mime_parser2fio(parser)->partial_name = fiobj_str_new(name, name_len);
2004
+
2005
+ // store the parameter name
2006
+ http_mime_parser2fio(parser)->partial_name = rb_str_new(name, name_len);
1843
2007
 
1844
2008
  if (!filename)
1845
2009
  return;
1846
2010
 
1847
- fiobj_str_write(http_mime_parser2fio(parser)->partial_name, "[type]", 6);
1848
- fio_str_info_s tmp =
1849
- fiobj_obj2cstr(http_mime_parser2fio(parser)->partial_name);
1850
- http_add2hash(http_mime_parser2fio(parser)->h->params, tmp.data, tmp.len,
1851
- mimetype, mimetype_len, 0);
2011
+ // create and store the temporary file for the file data
2012
+ char* path;
2013
+ VALUE r_tempfile = create_temp_file(parser, &path);
1852
2014
 
1853
- fiobj_str_resize(http_mime_parser2fio(parser)->partial_name, name_len);
1854
- fiobj_str_write(http_mime_parser2fio(parser)->partial_name, "[name]", 6);
1855
- tmp = fiobj_obj2cstr(http_mime_parser2fio(parser)->partial_name);
1856
- http_add2hash(http_mime_parser2fio(parser)->h->params, tmp.data, tmp.len,
1857
- filename, filename_len, 0);
1858
-
1859
- fiobj_str_resize(http_mime_parser2fio(parser)->partial_name, name_len);
1860
- fiobj_str_write(http_mime_parser2fio(parser)->partial_name, "[data]", 6);
2015
+ http_mime_parser2fio(parser)->partial_fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0777);
2016
+
2017
+ VALUE r_value = build_file_value(r_tempfile, filename, filename_len, mimetype, mimetype_len);
2018
+ http_mime_parser2fio(parser)->partial_tempfile = r_value;
1861
2019
  }
1862
2020
 
1863
2021
  /** Called when partial data is available. */
1864
2022
  static void http_mime_parser_on_partial_data(http_mime_parser_s *parser,
1865
2023
  void *value, size_t value_len) {
1866
- if (!http_mime_parser2fio(parser)->partial_offset)
1867
- http_mime_parser2fio(parser)->partial_offset =
1868
- http_mime_parser2fio(parser)->pos +
1869
- ((uintptr_t)value -
1870
- (uintptr_t)http_mime_parser2fio(parser)->buffer.data);
1871
- http_mime_parser2fio(parser)->partial_length += value_len;
1872
- (void)value;
2024
+
2025
+ http_fio_mime_s *fio_parser = http_mime_parser2fio(parser);
2026
+
2027
+ // if this is a file - write into it rightaway
2028
+ if (fio_parser->partial_fd) {
2029
+ write(fio_parser->partial_fd, value, value_len);
2030
+ return;
2031
+ }
2032
+
2033
+ // if that's just a large piece of data - store the offset and length values
2034
+ if (!fio_parser->partial_offset) {
2035
+ fio_parser->partial_offset = fio_parser->pos + ((uintptr_t)value - (uintptr_t)fio_parser->buffer.data);
2036
+ }
2037
+ fio_parser->partial_length += value_len;
1873
2038
  }
1874
2039
 
1875
2040
  /** Called when the partial data is complete. */
1876
2041
  static void http_mime_parser_on_partial_end(http_mime_parser_s *parser) {
2042
+ http_fio_mime_s *fio_parser = http_mime_parser2fio(parser);
2043
+
2044
+ // for a file - add to params and close the fd
2045
+ if (fio_parser->partial_fd) {
2046
+ add_to_params(fio_parser->params, RSTRING_PTR(fio_parser->partial_name), RSTRING_LEN(fio_parser->partial_name), fio_parser->partial_tempfile);
2047
+ close(fio_parser->partial_fd);
2048
+
2049
+ fio_parser->partial_name = Qnil;
2050
+ fio_parser->partial_tempfile = Qnil;
2051
+ fio_parser->partial_fd = 0;
1877
2052
 
1878
- fio_str_info_s tmp =
1879
- fiobj_obj2cstr(http_mime_parser2fio(parser)->partial_name);
1880
- FIOBJ o = FIOBJ_INVALID;
1881
- if (!http_mime_parser2fio(parser)->partial_length)
1882
2053
  return;
1883
- if (http_mime_parser2fio(parser)->partial_length < 42) {
1884
- /* short data gets a new object */
1885
- o = fiobj_str_new(http_mime_parser2fio(parser)->buffer.data +
1886
- http_mime_parser2fio(parser)->partial_offset,
1887
- http_mime_parser2fio(parser)->partial_length);
1888
- } else {
1889
- /* longer data gets a reference object (memory collision concerns) */
1890
- o = fiobj_data_slice(http_mime_parser2fio(parser)->h->body,
1891
- http_mime_parser2fio(parser)->partial_offset,
1892
- http_mime_parser2fio(parser)->partial_length);
1893
2054
  }
1894
- http_add2hash2(http_mime_parser2fio(parser)->h->params, tmp.data, tmp.len, o,
1895
- 0);
1896
- fiobj_free(http_mime_parser2fio(parser)->partial_name);
1897
- http_mime_parser2fio(parser)->partial_name = FIOBJ_INVALID;
1898
- http_mime_parser2fio(parser)->partial_offset = 0;
2055
+
2056
+ FIOBJ slice = fiobj_data_slice(fio_parser->h->body, fio_parser->partial_offset, fio_parser->partial_length);
2057
+ fio_str_info_s s_slice = fiobj_obj2cstr(slice);
2058
+ VALUE r_value = rb_enc_str_new(s_slice.data, s_slice.len, IodineBinaryEncoding);
2059
+ add_to_params(fio_parser->params, RSTRING_PTR(fio_parser->partial_name), RSTRING_LEN(fio_parser->partial_name), r_value);
2060
+
2061
+ fio_parser->partial_name = Qnil;
2062
+ fio_parser->partial_length = 0;
2063
+ fio_parser->partial_offset = 0;
1899
2064
  }
1900
2065
 
1901
2066
  /**
@@ -1911,65 +2076,29 @@ static inline size_t http_mime_decode_url(char *dest, const char *encoded,
1911
2076
  }
1912
2077
 
1913
2078
  /**
1914
- * Attempts to decode the request's body.
1915
- *
1916
- * Supported Types include:
1917
- * * application/x-www-form-urlencoded
1918
- * * application/json
1919
- * * multipart/form-data
2079
+ * Attempts to decode a multipart/form-data encoded body.
1920
2080
  */
1921
- int http_parse_body(http_s *h) {
1922
- static uint64_t content_type_hash;
1923
- if (!h->body)
1924
- return -1;
1925
- if (!content_type_hash)
1926
- content_type_hash = fiobj_hash_string("content-type", 12);
1927
- FIOBJ ct = fiobj_hash_get2(h->headers, content_type_hash);
1928
- fio_str_info_s content_type = fiobj_obj2cstr(ct);
1929
- if (content_type.len < 16)
1930
- return -1;
1931
- if (content_type.len >= 33 &&
1932
- !strncasecmp("application/x-www-form-urlencoded", content_type.data,
1933
- 33)) {
1934
- if (!h->params)
1935
- h->params = fiobj_hash_new();
1936
- FIOBJ tmp = h->query;
1937
- h->query = h->body;
1938
- http_parse_query(h);
1939
- h->query = tmp;
1940
- return 0;
1941
- }
1942
- if (content_type.len >= 16 &&
1943
- !strncasecmp("application/json", content_type.data, 16)) {
1944
- content_type = fiobj_obj2cstr(h->body);
1945
- if (h->params)
1946
- return -1;
1947
- if (fiobj_json2obj(&h->params, content_type.data, content_type.len) == 0)
1948
- return -1;
1949
- if (FIOBJ_TYPE_IS(h->params, FIOBJ_T_HASH))
1950
- return 0;
1951
- FIOBJ tmp = h->params;
1952
- FIOBJ key = fiobj_str_new("JSON", 4);
1953
- h->params = fiobj_hash_new2(4);
1954
- fiobj_hash_set(h->params, key, tmp);
1955
- fiobj_free(key);
1956
- return 0;
2081
+ VALUE http_parse_multipart(http_s *h, char *content_type, size_t content_type_len) {
2082
+ static uint8_t http_initialized;
2083
+ if (!http_initialized) {
2084
+ http_initialized = 1;
2085
+ http_init();
1957
2086
  }
1958
2087
 
1959
- http_fio_mime_s p = {.h = h};
1960
- if (http_mime_parser_init(&p.p, content_type.data, content_type.len))
1961
- return -1;
1962
- if (!h->params)
1963
- h->params = fiobj_hash_new();
2088
+ VALUE params = rb_hash_new();
2089
+ http_fio_mime_s p = {.h = h, .params = params};
2090
+ if (http_mime_parser_init(&p.p, content_type, content_type_len))
2091
+ rb_raise(rb_eRuntimeError, "Malformed multipart request");
1964
2092
 
1965
2093
  do {
1966
2094
  size_t cons = http_mime_parse(&p.p, p.buffer.data, p.buffer.len);
1967
2095
  p.pos += cons;
1968
- p.buffer = fiobj_data_pread(h->body, p.pos, 4096);
2096
+ p.buffer = fiobj_data_pread(h->body, p.pos, 262144);
1969
2097
  } while (p.buffer.data && !p.p.done && !p.p.error);
1970
- fiobj_free(p.partial_name);
1971
- p.partial_name = FIOBJ_INVALID;
1972
- return 0;
2098
+
2099
+ p.params = Qnil;
2100
+
2101
+ return params;
1973
2102
  }
1974
2103
 
1975
2104
  /* *****************************************************************************
@@ -2489,6 +2618,24 @@ ssize_t http_decode_path_unsafe(char *dest, const char *url_data) {
2489
2618
  return pos - dest;
2490
2619
  }
2491
2620
 
2621
+ // init the http module to enable multipart/form-data parsing;
2622
+ // should be called lazily as it references `Rage`
2623
+ void http_init(void) {
2624
+ cTempfile = rb_const_get(rb_cObject, rb_intern("Tempfile"));
2625
+
2626
+ VALUE cRage = rb_const_get(rb_cObject, rb_intern("Rage"));
2627
+ cUploadedFile = rb_const_get(cRage, rb_intern("UploadedFile"));
2628
+
2629
+ tempfile_args = rb_hash_new();
2630
+ rb_hash_aset(tempfile_args, ID2SYM(rb_intern("binmode")), Qtrue);
2631
+ rb_global_variable(&tempfile_args);
2632
+
2633
+ create_id = rb_intern("create");
2634
+ path_id = rb_intern("path");
2635
+
2636
+ IodineBinaryEncoding = rb_enc_find("binary");
2637
+ }
2638
+
2492
2639
  /* *****************************************************************************
2493
2640
  Lookup Tables / functions
2494
2641
  ***************************************************************************** */
data/ext/iodine/http.h CHANGED
@@ -7,6 +7,8 @@ Feel free to copy, use and enjoy according to the license provided.
7
7
  */
8
8
  #define H_HTTP_H
9
9
 
10
+ #include "ruby.h"
11
+
10
12
  #include <fio.h>
11
13
 
12
14
  #include <fiobj.h>
@@ -58,6 +60,10 @@ Compile Time Settings
58
60
  #define FIO_HTTP_EXACT_LOGGING 0
59
61
  #endif
60
62
 
63
+ #ifndef PARAMS_MAX_DEPTH
64
+ #define PARAMS_MAX_DEPTH 5
65
+ #endif
66
+
61
67
  /** the `http_listen settings, see details in the struct definition. */
62
68
  typedef struct http_settings_s http_settings_s;
63
69
 
@@ -772,25 +778,9 @@ HTTP GET and POST parsing helpers
772
778
  ***************************************************************************** */
773
779
 
774
780
  /**
775
- * Attempts to decode the request's body.
776
- *
777
- * Supported Types include:
778
- * * application/x-www-form-urlencoded
779
- * * application/json
780
- * * multipart/form-data
781
- *
782
- * This should be called before `http_parse_query`, in order to support JSON
783
- * data.
784
- *
785
- * If the JSON data isn't an object, it will be saved under the key "JSON" in
786
- * the `params` hash.
787
- *
788
- * If the `multipart/form-data` type contains JSON files, they will NOT be
789
- * parsed (they will behave like any other file, with `data`, `type` and
790
- * `filename` keys assigned). This allows non-object JSON data (such as array)
791
- * to be handled by the app.
781
+ * Attempts to decode a multipart/form-data encoded body.
792
782
  */
793
- int http_parse_body(http_s *h);
783
+ VALUE http_parse_multipart(http_s *h, char *content_type, size_t content_type_len);
794
784
 
795
785
  /**
796
786
  * Parses the query part of an HTTP request/response. Uses `http_add2hash`.
@@ -1007,6 +997,10 @@ typedef fio_url_s http_url_s
1007
997
  */
1008
998
  #define http_url_parse(url, len) fio_url_parse((url), (len))
1009
999
 
1000
+ // init the http module to enable multipart/form-data parsing;
1001
+ // should be called lazily as it references `Rage`
1002
+ void http_init(void);
1003
+
1010
1004
  #if DEBUG
1011
1005
  void http_tests(void);
1012
1006
  #endif
@@ -209,6 +209,253 @@ static VALUE iodine_rfc2109(VALUE self, VALUE rtm) {
209
209
  (void)self;
210
210
  }
211
211
 
212
+ /**
213
+ Convert query string into a Ruby object in (almost always) one pass with no recursion.
214
+
215
+
216
+ Iodine::Rack::Utils.parse_nested_query("a=1&b[]=2&c[d]=3") # => { "a" => "1", "b" => ["2"], "c" => { "d" => "3" } }
217
+
218
+
219
+
220
+ query = "a=1&b[]=2&c[d]=3"
221
+ Benchmark.ips do |x|
222
+ x.report("Iodine") {
223
+ Iodine::Rack::Utils.parse_nested_query(query)
224
+ }
225
+
226
+ x.report("Rack") {
227
+ Rack::Utils.parse_nested_query(query)
228
+ }
229
+
230
+ x.compare!
231
+ end
232
+
233
+
234
+ Calculating -------------------------------------
235
+ Iodine 1.088M (~ 1.1%) i/s - 5.526M in 5.077759s
236
+ Rack 64.258k (~ 1.0%) i/s - 321.657k in 5.006187s
237
+
238
+ Comparison:
239
+ Iodine: 1088431.2 i/s
240
+ Rack: 64257.9 i/s - 16.94x slower
241
+
242
+ */
243
+ static VALUE parse_nested_query_internal(char *str, size_t len) {
244
+ uint8_t should_decode = 0;
245
+ VALUE params = rb_hash_new();
246
+
247
+ char *pos = str, *k_pos = str, *v_pos = str;
248
+ const char *end = str + len;
249
+
250
+ 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
+
385
+ } else {
386
+ pos++;
387
+ }
388
+ }
389
+
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
+ }
399
+
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
+ }
405
+
406
+ if (arr != Qnil) {
407
+ rb_ary_push(arr, v);
408
+ } else {
409
+ rb_hash_aset(hsh, hsh_key, v);
410
+ }
411
+
412
+ pos = k_pos = v_pos + 1;
413
+
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;
418
+ }
419
+ }
420
+
421
+ return params;
422
+ }
423
+
424
+ /**
425
+ Convert query string into a Ruby object.
426
+ */
427
+ 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
+ (void)self;
430
+ }
431
+
432
+ /**
433
+ Convert urlencoded body into a Ruby object.
434
+ */
435
+ 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
+ (void)self;
443
+ }
444
+
445
+ /**
446
+ Convert multipart/form-data into a Ruby object.
447
+ */
448
+ static VALUE parse_multipart(VALUE self, VALUE rack_io, VALUE content_type) {
449
+ http_s *h = IodineRackIO.get_handle(rack_io);
450
+
451
+ if (content_type == Qnil) {
452
+ rb_raise(rb_eRuntimeError, "Incorrect content type for multipart request");
453
+ }
454
+
455
+ return http_parse_multipart(h, RSTRING_PTR(content_type), RSTRING_LEN(content_type));
456
+ (void)self;
457
+ }
458
+
212
459
  /* *****************************************************************************
213
460
  Ruby Initialization
214
461
  ***************************************************************************** */
@@ -261,6 +508,9 @@ Results:
261
508
  rb_define_module_function(tmp, "time2str", date_str, -1);
262
509
  rb_define_module_function(tmp, "rfc2109", iodine_rfc2109, 1);
263
510
  rb_define_module_function(tmp, "rfc2822", iodine_rfc2822, 1);
511
+ rb_define_module_function(tmp, "parse_nested_query", parse_nested_query, 1);
512
+ rb_define_module_function(tmp, "parse_urlencoded_nested_query", parse_urlencoded_nested_query, 1);
513
+ rb_define_module_function(tmp, "parse_multipart", parse_multipart, 2);
264
514
 
265
515
  /*
266
516
  The monkey-patched methods are in this module, allowing Iodine::Rack::Utils to
@@ -106,6 +106,7 @@ rack_declare(XSENDFILE_TYPE); // for X-Sendfile support
106
106
  rack_declare(XSENDFILE_TYPE_HEADER); // for X-Sendfile support
107
107
  rack_declare(CONTENT_LENGTH_HEADER); // for X-Sendfile support
108
108
  rack_declare(IODINE_REQUEST_ID);
109
+ rack_declare(IODINE_HAS_BODY);
109
110
 
110
111
  /* used internally to handle requests */
111
112
  typedef struct {
@@ -341,6 +342,9 @@ static inline VALUE copy2env(iodine_http_request_handle_s *handle) {
341
342
  tmp = fiobj_obj2cstr(h->path);
342
343
  rb_hash_aset(env, PATH_INFO,
343
344
  rb_enc_str_new(tmp.data, tmp.len, IodineBinaryEncoding));
345
+
346
+ rb_hash_aset(env, IODINE_HAS_BODY, (h->body) ? Qtrue : Qfalse);
347
+
344
348
  if (h->query) {
345
349
  tmp = fiobj_obj2cstr(h->query);
346
350
  rb_hash_aset(env, QUERY_STRING,
@@ -976,6 +980,7 @@ static void initialize_env_template(void) {
976
980
  rb_hash_aset(env_template_no_upgrade, SERVER_NAME, QUERY_STRING);
977
981
  rb_hash_aset(env_template_no_upgrade, SERVER_PROTOCOL, QUERY_STRING);
978
982
  rb_hash_aset(env_template_no_upgrade, IODINE_REQUEST_ID, QUERY_STRING);
983
+ rb_hash_aset(env_template_no_upgrade, IODINE_HAS_BODY, QUERY_STRING);
979
984
 
980
985
  /* WebSocket upgrade support */
981
986
  env_template_websockets = rb_hash_dup(env_template_no_upgrade);
@@ -1235,6 +1240,7 @@ void iodine_init_http(void) {
1235
1240
  rack_autoset(HTTP_HOST);
1236
1241
 
1237
1242
  rack_autoset(IODINE_REQUEST_ID);
1243
+ rack_autoset(IODINE_HAS_BODY);
1238
1244
 
1239
1245
  rack_set(HTTP_SCHEME, "http");
1240
1246
  rack_set(HTTPS_SCHEME, "https");
@@ -270,4 +270,5 @@ struct IodineRackIO IodineRackIO = {
270
270
  .create = new_rack_io,
271
271
  .close = close_rack_io,
272
272
  .init = init_rack_io,
273
+ .get_handle = get_handle,
273
274
  };
@@ -15,6 +15,8 @@ extern struct IodineRackIO {
15
15
  VALUE (*create)(http_s *h, VALUE env);
16
16
  void (*close)(VALUE rack_io);
17
17
  void (*init)(void);
18
+ http_s * (*get_handle)(VALUE obj);
19
+
18
20
  } IodineRackIO;
19
21
 
20
22
  #endif /* RUBY_RACK_IO_H */
data/iodine.gemspec CHANGED
@@ -44,7 +44,4 @@ Gem::Specification.new do |spec|
44
44
  spec.add_development_dependency 'rspec', '>=3.9.0', '< 4.0'
45
45
  spec.add_development_dependency 'spec', '>=5.3.0', '< 6.0'
46
46
  spec.add_development_dependency 'rake-compiler', '>= 1', '< 2.0'
47
-
48
- spec.post_install_message = "Thank you for installing Iodine #{Iodine::VERSION}.\n" +
49
- "Remember: if iodine supports your business, it's only fair to give value back (code contributions / donations)."
50
47
  end
@@ -1,3 +1,3 @@
1
1
  module Iodine
2
- VERSION = '1.8.0'.freeze
2
+ VERSION = '2.0.0'.freeze
3
3
  end
data/lib/iodine.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'stringio' # Used internally as a default RackIO
2
2
  require 'socket' # TCPSocket is used internally for Hijack support
3
+ require 'tempfile' # Used to generate temporary files when parsing multipart/form-data
3
4
  # require 'openssl' # For SSL/TLS support using OpenSSL
4
5
 
5
6
  require_relative './iodine/version'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rage-iodine
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.8.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Boaz Segev
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-09-25 00:00:00.000000000 Z
11
+ date: 2023-10-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -252,9 +252,7 @@ licenses:
252
252
  - MIT
253
253
  metadata:
254
254
  allowed_push_host: https://rubygems.org
255
- post_install_message: |-
256
- Thank you for installing Iodine 1.8.0.
257
- Remember: if iodine supports your business, it's only fair to give value back (code contributions / donations).
255
+ post_install_message:
258
256
  rdoc_options: []
259
257
  require_paths:
260
258
  - lib