rage-iodine 5.4.0 → 5.5.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: 9ef6e88ecbc5910815537ef78329c6bc965261fb4514c479be7594fb6de61dca
4
- data.tar.gz: '09632895e2ed3a00f6300a418c46b7f10332403f83aceeaffe77af57258f9baf'
3
+ metadata.gz: ce6be54873c53f93352aa7c739c74e73f5510eaeaab015bd2afc5e0fe261ff38
4
+ data.tar.gz: 45bba38d8c14f66e780981c13a05a7ca1d036a6809f71f33833e8743e285c32b
5
5
  SHA512:
6
- metadata.gz: 6ce3edb67d9029b9a9bf73a941dcbcd7378214246cbbb5a09084f085dda5c2b41003dc75338a595f054e28db146cc713e887a3d43fffebefc80567d87348a94a
7
- data.tar.gz: 3b2f347ecc8732f23ecac55bbc5c8c3d9d51b6e814bbb9b87f23d538f211ab6a605972b879f4449c3ce10fe95bc1920930304e604f53f5f593d5d889f4095c25
6
+ metadata.gz: be4a8fd787b970fb21e30d6ec21e590ca27da1122d62f7efeac532f5b2fffd920c7016d489bd5c1e748e9d559058c23c3f567edc0e99e1da34a4a81b2bdde70c
7
+ data.tar.gz: 25e51f20a4a572caab13708ca5496e73cbcbb90810c13bf1000d8cbb0a1ec75bf797ade820d756a2c30237ac37c254e89303d0adeb7b6f060133a6cc1edc708e
data/CHANGELOG.md CHANGED
@@ -6,6 +6,16 @@ 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.5.0 (2026-07-06)
10
+
11
+ **Update**: Update `pre_start` callbacks to fail the server launch in case of errors
12
+
13
+ **Update**: Update media types in the MIME table
14
+
15
+ **Update**: Ensure users can override headers when serving files
16
+
17
+ **Update**: Support `x-sendfile-root` headers
18
+
9
19
  #### Change log v.5.4.0 (2026-06-08)
10
20
 
11
21
  **Update**: Optimize `fio_run_every` timers
data/ext/iodine/http.c CHANGED
@@ -188,6 +188,26 @@ int http_set_header2(http_s *r, fio_str_info_s n, fio_str_info_s v) {
188
188
  return ret;
189
189
  }
190
190
 
191
+ /**
192
+ * Sets a response header if it hasn't already been set.
193
+ *
194
+ * Returns -1 on error and 0 on success.
195
+ */
196
+ static inline int http_set_header_if_none(http_s *r, FIOBJ name, FIOBJ value) {
197
+ if (HTTP_INVALID_HANDLE(r) || !name) {
198
+ fiobj_free(value);
199
+ return -1;
200
+ }
201
+
202
+ if (fiobj_hash_get2(r->private_data.out_headers, fiobj_obj2hash(name))) {
203
+ fiobj_free(value);
204
+ return 0;
205
+ }
206
+
207
+ fiobj_hash_set(r->private_data.out_headers, name, value);
208
+ return 0;
209
+ }
210
+
191
211
  /**
192
212
  * Sets a response cookie, taking ownership of the value object, but NOT the
193
213
  * name object (so name objects could be reused in future responses).
@@ -461,12 +481,12 @@ no_gzip_support:
461
481
  return -1;
462
482
  found_file:
463
483
  /* set cache-control */
464
- http_set_header(h, HTTP_HEADER_CACHE_CONTROL, fiobj_dup(HTTP_HVALUE_MAX_AGE));
484
+ http_set_header_if_none(h, HTTP_HEADER_CACHE_CONTROL, fiobj_dup(HTTP_HVALUE_MAX_AGE));
465
485
  /* set last-modified */
466
486
  FIOBJ last_modified_str = fiobj_str_buf(32);
467
487
  fiobj_str_resize(
468
488
  last_modified_str, http_time2str(fiobj_obj2cstr(last_modified_str).data, file_data.st_mtime));
469
- http_set_header(h, HTTP_HEADER_LAST_MODIFIED, last_modified_str);
489
+ http_set_header_if_none(h, HTTP_HEADER_LAST_MODIFIED, last_modified_str);
470
490
  /* set & test etag */
471
491
  FIOBJ etag_str = fiobj_str_buf(1);
472
492
  fiobj_str_printf(etag_str, "%lx-%llx", file_data.st_mtime, file_data.st_size);
@@ -605,7 +625,7 @@ open_file:
605
625
  tmp = http_mimetype_find(s.data + pos, s.len - pos);
606
626
  }
607
627
  if (tmp)
608
- http_set_header(h, HTTP_HEADER_CONTENT_TYPE, tmp);
628
+ http_set_header_if_none(h, HTTP_HEADER_CONTENT_TYPE, tmp);
609
629
  }
610
630
  http_sendfile(h, file, length, offset);
611
631
  return 0;
@@ -610,7 +610,7 @@ static void http_lib_init(void *ignr_) {
610
610
  REGISTER_MIME("jpgm", "video/jpm");
611
611
  REGISTER_MIME("jpgv", "video/jpeg");
612
612
  REGISTER_MIME("jpm", "video/jpm");
613
- REGISTER_MIME("js", "application/javascript");
613
+ REGISTER_MIME("js", "text/javascript");
614
614
  REGISTER_MIME("json", "application/json");
615
615
  REGISTER_MIME("jsonml", "application/jsonml+json");
616
616
  REGISTER_MIME("kar", "audio/midi");
@@ -694,6 +694,7 @@ static void http_lib_init(void *ignr_) {
694
694
  REGISTER_MIME("mie", "application/x-mie");
695
695
  REGISTER_MIME("mif", "application/vnd.mif");
696
696
  REGISTER_MIME("mime", "message/rfc822");
697
+ REGISTER_MIME("mjs", "text/javascript");
697
698
  REGISTER_MIME("mj2", "video/mj2");
698
699
  REGISTER_MIME("mjp2", "video/mj2");
699
700
  REGISTER_MIME("mk3d", "video/x-matroska");
@@ -803,7 +804,7 @@ static void http_lib_init(void *ignr_) {
803
804
  REGISTER_MIME("osf", "application/vnd.yamaha.openscoreformat");
804
805
  REGISTER_MIME("osfpvg", "application/vnd.yamaha.openscoreformat.osfpvg+xml");
805
806
  REGISTER_MIME("otc", "application/vnd.oasis.opendocument.chart-template");
806
- REGISTER_MIME("otf", "application/x-font-otf");
807
+ REGISTER_MIME("otf", "font/otf");
807
808
  REGISTER_MIME("otg", "application/vnd.oasis.opendocument.graphics-template");
808
809
  REGISTER_MIME("oth", "application/vnd.oasis.opendocument.text-web");
809
810
  REGISTER_MIME("oti", "application/vnd.oasis.opendocument.image-template");
@@ -1066,8 +1067,8 @@ static void http_lib_init(void *ignr_) {
1066
1067
  REGISTER_MIME("trm", "application/x-msterminal");
1067
1068
  REGISTER_MIME("tsd", "application/timestamped-data");
1068
1069
  REGISTER_MIME("tsv", "text/tab-separated-values");
1069
- REGISTER_MIME("ttc", "application/x-font-ttf");
1070
- REGISTER_MIME("ttf", "application/x-font-ttf");
1070
+ REGISTER_MIME("ttc", "font/collection");
1071
+ REGISTER_MIME("ttf", "font/ttf");
1071
1072
  REGISTER_MIME("ttl", "text/turtle");
1072
1073
  REGISTER_MIME("twd", "application/vnd.simtech-mindmapper");
1073
1074
  REGISTER_MIME("twds", "application/vnd.simtech-mindmapper");
@@ -1163,7 +1164,8 @@ static void http_lib_init(void *ignr_) {
1163
1164
  REGISTER_MIME("wmx", "video/x-ms-wmx");
1164
1165
  REGISTER_MIME("wmz", "application/x-ms-wmz");
1165
1166
  // REGISTER_MIME("wmz", "application/x-msmetafile");
1166
- REGISTER_MIME("woff", "application/font-woff");
1167
+ REGISTER_MIME("woff", "font/woff");
1168
+ REGISTER_MIME("woff2", "font/woff2");
1167
1169
  REGISTER_MIME("wpd", "application/vnd.wordperfect");
1168
1170
  REGISTER_MIME("wpl", "application/vnd.ms-wpl");
1169
1171
  REGISTER_MIME("wps", "application/vnd.ms-works");
@@ -152,6 +152,18 @@ static VALUE iodine_call(VALUE obj, ID method) {
152
152
  return (VALUE)rv;
153
153
  }
154
154
 
155
+ /** Calls a Ruby method on a given object, without protecting against exceptions. */
156
+ static VALUE iodine_call_unprotected(VALUE obj, ID method) {
157
+ iodine_rb_task_s task = {
158
+ .obj = obj,
159
+ .argc = 0,
160
+ .argv = NULL,
161
+ .method = method,
162
+ };
163
+ void *rv = iodine_enterGVL((void *(*)(void *))iodine_ruby_caller_perform, &task);
164
+ return (VALUE)rv;
165
+ }
166
+
155
167
  /** Calls a Ruby method on a given object, protecting against exceptions. */
156
168
  static VALUE iodine_call2(VALUE obj, ID method, int argc, VALUE *argv) {
157
169
  iodine_rb_task_s task = {
@@ -223,6 +235,8 @@ struct IodineCaller_s IodineCaller = {
223
235
  .call = iodine_call,
224
236
  /** Calls a Ruby method on a given object, protecting against exceptions. */
225
237
  .call2 = iodine_call2,
238
+ /** Calls a Ruby method on a given object, without protecting against exceptions. */
239
+ .call_unprotected = iodine_call_unprotected,
226
240
  /** Returns the GVL state flag. */
227
241
  .in_GVL = iodine_in_GVL,
228
242
  /** Forces the GVL state flag. */
@@ -14,6 +14,8 @@ extern struct IodineCaller_s {
14
14
  VALUE (*call)(VALUE obj, ID method);
15
15
  /** Calls a Ruby method on a given object, protecting against exceptions. */
16
16
  VALUE (*call2)(VALUE obj, ID method, int argc, VALUE *argv);
17
+ /** Calls a Ruby method on a given object, without protecting against exceptions. */
18
+ VALUE (*call_unprotected)(VALUE obj, ID method);
17
19
  /** Calls a Ruby method on a given object, protecting against exceptions. */
18
20
  VALUE(*call_with_block)
19
21
  (VALUE obj, ID method, int argc, VALUE *argv, VALUE udata,
@@ -323,6 +323,12 @@ static void iodine_perform_state_callback_persist(void *blk_) {
323
323
  IodineCaller.call(blk, call_id);
324
324
  }
325
325
 
326
+ /* performs a Ruby state callback without protecting against exceptions */
327
+ static void iodine_perform_unprotected_state_callback_persist(void *blk_) {
328
+ VALUE blk = (VALUE)blk_;
329
+ IodineCaller.call_unprotected(blk, call_id);
330
+ }
331
+
326
332
  // clang-format off
327
333
  /**
328
334
  Sets a block of code to run when Iodine's core state is updated.
@@ -357,7 +363,7 @@ static VALUE iodine_on_state(VALUE self, VALUE event) {
357
363
 
358
364
  if (state == STATE_PRE_START) {
359
365
  fio_state_callback_add(FIO_CALL_PRE_START,
360
- iodine_perform_state_callback_persist,
366
+ iodine_perform_unprotected_state_callback_persist,
361
367
  (void *)block);
362
368
  } else if (state == STATE_BEFORE_FORK) {
363
369
  fio_state_callback_add(FIO_CALL_BEFORE_FORK,
@@ -102,6 +102,7 @@ rack_declare(CONTENT_TYPE);
102
102
  rack_declare(R_URL_SCHEME); // rack.url_scheme
103
103
  rack_declare(R_INPUT); // rack.input
104
104
  rack_declare(XSENDFILE); // for X-Sendfile support
105
+ rack_declare(XSENDFILE_ROOT); // for X-Sendfile support
105
106
  rack_declare(XSENDFILE_TYPE); // for X-Sendfile support
106
107
  rack_declare(XSENDFILE_TYPE_HEADER); // for X-Sendfile support
107
108
  rack_declare(CONTENT_LENGTH_HEADER); // for X-Sendfile support
@@ -112,6 +113,7 @@ rack_declare(IODINE_HAS_BODY);
112
113
  typedef struct {
113
114
  http_s *h;
114
115
  FIOBJ body;
116
+ FIOBJ root;
115
117
  enum iodine_http_response_type_enum {
116
118
  IODINE_HTTP_NONE,
117
119
  IODINE_HTTP_SENDBODY,
@@ -716,6 +718,14 @@ static inline void *iodine_handle_request_in_GVL(void *handle_) {
716
718
  handle->body = fiobj_str_new(RSTRING_PTR(xfiles), RSTRING_LEN(xfiles));
717
719
  handle->type = IODINE_HTTP_XSENDFILE;
718
720
  rb_hash_delete(response_headers, XSENDFILE);
721
+ // extract optional root directory for path validation
722
+ VALUE xroot = rb_hash_aref(response_headers, XSENDFILE_ROOT);
723
+ if (xroot != Qnil && TYPE(xroot) == T_STRING) {
724
+ handle->root = fiobj_str_new(RSTRING_PTR(xroot), RSTRING_LEN(xroot));
725
+ rb_hash_delete(response_headers, XSENDFILE_ROOT);
726
+ } else {
727
+ handle->root = FIOBJ_INVALID;
728
+ }
719
729
  // remove content length headers, as this will be controled by iodine
720
730
  rb_hash_delete(response_headers, CONTENT_LENGTH_HEADER);
721
731
  // review each header and write it to the response.
@@ -782,8 +792,22 @@ iodine_perform_handle_action(iodine_http_request_handle_s handle) {
782
792
  .len == 7)
783
793
  fiobj_hash_delete2(handle.h->private_data.out_headers,
784
794
  fiobj_obj2hash(HTTP_HEADER_CONTENT_ENCODING));
785
- fio_str_info_s data = fiobj_obj2cstr(handle.body);
786
- if (http_sendfile2(handle.h, data.data, data.len, NULL, 0)) {
795
+ fio_str_info_s file_data = fiobj_obj2cstr(handle.body);
796
+ int err;
797
+ if (handle.root != FIOBJ_INVALID) {
798
+ /* root dir specified - treat x-sendfile as relative/encoded path */
799
+ fio_str_info_s root_data = fiobj_obj2cstr(handle.root);
800
+ err = http_sendfile2(handle.h, root_data.data, root_data.len,
801
+ file_data.data, file_data.len);
802
+ fiobj_free(handle.root);
803
+ } else {
804
+ /* no root dir - original behavior (absolute path as prefix) */
805
+ err = http_sendfile2(handle.h, file_data.data, file_data.len, NULL, 0);
806
+ }
807
+ if (err) {
808
+ fiobj_hash_clear(handle.h->private_data.out_headers);
809
+ http_set_header(handle.h, HTTP_HEADER_CACHE_CONTROL,
810
+ fiobj_str_new("no-cache, max-age=0", 19));
787
811
  http_send_error(handle.h, 404);
788
812
  }
789
813
  fiobj_free(handle.body);
@@ -1181,10 +1205,11 @@ void iodine_init_http(void) {
1181
1205
  rack_set(QUERY_ESTRING, "");
1182
1206
  rack_set(R_URL_SCHEME, "rack.url_scheme");
1183
1207
  rack_set(R_INPUT, "rack.input");
1184
- rack_set(XSENDFILE, "X-Sendfile");
1208
+ rack_set(XSENDFILE, "x-sendfile");
1209
+ rack_set(XSENDFILE_ROOT, "x-sendfile-root");
1185
1210
  rack_set(XSENDFILE_TYPE, "sendfile.type");
1186
1211
  rack_set(XSENDFILE_TYPE_HEADER, "HTTP_X_SENDFILE_TYPE");
1187
- rack_set(CONTENT_LENGTH_HEADER, "Content-Length");
1212
+ rack_set(CONTENT_LENGTH_HEADER, "content-length");
1188
1213
 
1189
1214
  rack_set(IODINE_R_INPUT, "rack.input");
1190
1215
  rack_set(IODINE_R_HIJACK_IO, "rack.hijack_io");
@@ -1,3 +1,3 @@
1
1
  module Iodine
2
- VERSION = '5.4.0'.freeze
2
+ VERSION = '5.5.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.4.0
4
+ version: 5.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Boaz Segev