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 +4 -4
- data/ext/iodine/http.c +261 -114
- data/ext/iodine/http.h +12 -18
- data/ext/iodine/iodine_helpers.c +250 -0
- data/ext/iodine/iodine_http.c +6 -0
- data/ext/iodine/iodine_rack_io.c +1 -0
- data/ext/iodine/iodine_rack_io.h +2 -0
- data/iodine.gemspec +0 -3
- data/lib/iodine/version.rb +1 -1
- data/lib/iodine.rb +1 -0
- metadata +3 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4221fdb6705dce60cb5e71da2644f639200571f690930fb9c428e29c4ae20032
|
4
|
+
data.tar.gz: c01222eb2a55634b414c456d46319e5d2072a2ed89b258e709ea1d236cf490b9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
-
|
1813
|
-
|
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
|
-
|
1817
|
-
|
1818
|
-
|
1819
|
-
|
1820
|
-
|
1821
|
-
|
1822
|
-
|
1823
|
-
|
1824
|
-
|
1825
|
-
|
1826
|
-
|
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
|
-
|
1841
|
-
|
1842
|
-
http_mime_parser2fio(parser)->partial_name =
|
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
|
-
|
1848
|
-
|
1849
|
-
|
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
|
-
|
1854
|
-
|
1855
|
-
|
1856
|
-
|
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
|
-
|
1867
|
-
|
1868
|
-
|
1869
|
-
|
1870
|
-
|
1871
|
-
|
1872
|
-
|
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
|
-
|
1895
|
-
|
1896
|
-
|
1897
|
-
|
1898
|
-
|
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
|
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
|
-
|
1922
|
-
static
|
1923
|
-
if (!
|
1924
|
-
|
1925
|
-
|
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
|
-
|
1960
|
-
|
1961
|
-
|
1962
|
-
|
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,
|
2096
|
+
p.buffer = fiobj_data_pread(h->body, p.pos, 262144);
|
1969
2097
|
} while (p.buffer.data && !p.p.done && !p.p.error);
|
1970
|
-
|
1971
|
-
p.
|
1972
|
-
|
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
|
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
|
-
|
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
|
data/ext/iodine/iodine_helpers.c
CHANGED
@@ -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
|
data/ext/iodine/iodine_http.c
CHANGED
@@ -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");
|
data/ext/iodine/iodine_rack_io.c
CHANGED
data/ext/iodine/iodine_rack_io.h
CHANGED
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
|
data/lib/iodine/version.rb
CHANGED
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:
|
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-
|
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
|