iodine 0.7.16 → 0.7.17

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of iodine might be problematic. Click here for more details.

Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +5 -4
  3. data/.yardopts +8 -0
  4. data/CHANGELOG.md +26 -0
  5. data/LICENSE.txt +1 -1
  6. data/LIMITS.md +6 -0
  7. data/README.md +93 -13
  8. data/{SPEC-Websocket-Draft.md → SPEC-WebSocket-Draft.md} +0 -0
  9. data/examples/tcp_client.rb +66 -0
  10. data/examples/x-sendfile.ru +14 -0
  11. data/exe/iodine +3 -3
  12. data/ext/iodine/extconf.rb +21 -0
  13. data/ext/iodine/fio.c +659 -69
  14. data/ext/iodine/fio.h +350 -95
  15. data/ext/iodine/fio_cli.c +4 -3
  16. data/ext/iodine/fio_json_parser.h +1 -1
  17. data/ext/iodine/fio_siphash.c +13 -11
  18. data/ext/iodine/fio_siphash.h +6 -3
  19. data/ext/iodine/fio_tls.h +129 -0
  20. data/ext/iodine/fio_tls_missing.c +634 -0
  21. data/ext/iodine/fio_tls_openssl.c +1011 -0
  22. data/ext/iodine/fio_tmpfile.h +1 -1
  23. data/ext/iodine/fiobj.h +1 -1
  24. data/ext/iodine/fiobj_ary.c +1 -1
  25. data/ext/iodine/fiobj_ary.h +1 -1
  26. data/ext/iodine/fiobj_data.c +1 -1
  27. data/ext/iodine/fiobj_data.h +1 -1
  28. data/ext/iodine/fiobj_hash.c +1 -1
  29. data/ext/iodine/fiobj_hash.h +1 -1
  30. data/ext/iodine/fiobj_json.c +18 -16
  31. data/ext/iodine/fiobj_json.h +1 -1
  32. data/ext/iodine/fiobj_mustache.c +4 -0
  33. data/ext/iodine/fiobj_mustache.h +4 -0
  34. data/ext/iodine/fiobj_numbers.c +1 -1
  35. data/ext/iodine/fiobj_numbers.h +1 -1
  36. data/ext/iodine/fiobj_str.c +3 -3
  37. data/ext/iodine/fiobj_str.h +1 -1
  38. data/ext/iodine/fiobject.c +1 -1
  39. data/ext/iodine/fiobject.h +8 -2
  40. data/ext/iodine/http.c +128 -337
  41. data/ext/iodine/http.h +11 -18
  42. data/ext/iodine/http1.c +6 -6
  43. data/ext/iodine/http1.h +1 -1
  44. data/ext/iodine/http1_parser.c +1 -1
  45. data/ext/iodine/http1_parser.h +1 -1
  46. data/ext/iodine/http_internal.c +10 -8
  47. data/ext/iodine/http_internal.h +13 -3
  48. data/ext/iodine/http_mime_parser.h +1 -1
  49. data/ext/iodine/iodine.c +806 -22
  50. data/ext/iodine/iodine.h +33 -0
  51. data/ext/iodine/iodine_connection.c +23 -18
  52. data/ext/iodine/iodine_http.c +239 -225
  53. data/ext/iodine/iodine_http.h +4 -1
  54. data/ext/iodine/iodine_mustache.c +59 -54
  55. data/ext/iodine/iodine_pubsub.c +1 -1
  56. data/ext/iodine/iodine_tcp.c +34 -100
  57. data/ext/iodine/iodine_tcp.h +4 -0
  58. data/ext/iodine/iodine_tls.c +267 -0
  59. data/ext/iodine/iodine_tls.h +13 -0
  60. data/ext/iodine/mustache_parser.h +1 -1
  61. data/ext/iodine/redis_engine.c +14 -6
  62. data/ext/iodine/redis_engine.h +1 -1
  63. data/ext/iodine/resp_parser.h +1 -1
  64. data/ext/iodine/websocket_parser.h +1 -1
  65. data/ext/iodine/websockets.c +1 -1
  66. data/ext/iodine/websockets.h +1 -1
  67. data/iodine.gemspec +2 -1
  68. data/lib/iodine.rb +19 -5
  69. data/lib/iodine/connection.rb +13 -0
  70. data/lib/iodine/mustache.rb +7 -24
  71. data/lib/iodine/tls.rb +16 -0
  72. data/lib/iodine/version.rb +1 -1
  73. data/lib/rack/handler/iodine.rb +1 -1
  74. metadata +15 -5
@@ -12,7 +12,10 @@ Feel free to copy, use and enjoy according to the license provided.
12
12
  extern VALUE IODINE_R_HIJACK;
13
13
  extern VALUE IODINE_R_HIJACK_IO;
14
14
  extern VALUE IODINE_R_HIJACK_CB;
15
- extern VALUE iodine_default_args;
16
15
  void iodine_init_http(void);
17
16
 
17
+ intptr_t iodine_http_listen(iodine_connection_args_s args);
18
+ // intptr_t iodine_http_connect(iodine_connection_args_s args); // not yet...
19
+ intptr_t iodine_ws_connect(iodine_connection_args_s args);
20
+
18
21
  #endif
@@ -9,9 +9,9 @@
9
9
 
10
10
  static ID call_func_id;
11
11
  static ID to_s_func_id;
12
- static ID filename_id;
13
- static ID data_id;
14
- static ID template_id;
12
+ static VALUE filename_id;
13
+ static VALUE data_id;
14
+ static VALUE template_id;
15
15
  /* *****************************************************************************
16
16
  C <=> Ruby Data allocation
17
17
  ***************************************************************************** */
@@ -255,20 +255,63 @@ Loading the template
255
255
  ***************************************************************************** */
256
256
 
257
257
  /**
258
- Loads a mustache template (and any partials).
258
+ Loads the mustache template found in `:filename`. If `:template` is provided it
259
+ will be used instead of reading the file's content.
260
+
261
+ Iodine::Mustache.new(filename, template = nil)
262
+
263
+ When template data is provided, filename (if any) will only be used for partial
264
+ template path resolution and the template data will be used for the template's
265
+ content. This allows, for example, for front matter to be extracted before
266
+ parsing the template.
259
267
 
260
268
  Once a template was loaded, it could be rendered using {render}.
269
+
270
+ Accepts named arguments as well:
271
+
272
+ Iodine::Mustache.new(filename: "foo.mustache", template: "{{ bar }}")
273
+
261
274
  */
262
- static VALUE iodine_mustache_new(VALUE self, VALUE filename) {
275
+ static VALUE iodine_mustache_new(int argc, VALUE *argv, VALUE self) {
276
+ VALUE filename = Qnil, template = Qnil;
277
+ if (argc == 1 && RB_TYPE_P(argv[0], T_HASH)) {
278
+ /* named arguments */
279
+ filename = rb_hash_aref(argv[0], filename_id);
280
+ template = rb_hash_aref(argv[0], template_id);
281
+ } else {
282
+ /* regular arguments */
283
+ if (argc == 0 || argc > 2)
284
+ rb_raise(rb_eArgError, "expecting 1..2 arguments or named arguments.");
285
+ filename = argv[0];
286
+ if (argc > 1) {
287
+ template = argv[1];
288
+ }
289
+ }
290
+ if (filename == Qnil && template == Qnil)
291
+ rb_raise(rb_eArgError, "need either template contents or file name.");
292
+
293
+ if (template != Qnil)
294
+ Check_Type(template, T_STRING);
295
+ if (filename != Qnil)
296
+ Check_Type(filename, T_STRING);
297
+
298
+ fio_str_s str = FIO_STR_INIT;
299
+
263
300
  mustache_s **m = NULL;
264
301
  TypedData_Get_Struct(self, mustache_s *, &iodine_mustache_data_type, m);
265
302
  if (!m) {
266
303
  rb_raise(rb_eRuntimeError, "Iodine::Mustache allocation error.");
267
304
  }
268
- Check_Type(filename, T_STRING);
305
+
269
306
  mustache_error_en err;
270
- *m = mustache_load(.filename = RSTRING_PTR(filename),
271
- .filename_len = RSTRING_LEN(filename), .err = &err);
307
+ *m = mustache_load(.filename =
308
+ (filename == Qnil ? NULL : RSTRING_PTR(filename)),
309
+ .filename_len =
310
+ (filename == Qnil ? 0 : RSTRING_LEN(filename)),
311
+ .data = (template == Qnil ? NULL : RSTRING_PTR(template)),
312
+ .data_len = (template == Qnil ? 0 : RSTRING_LEN(template)),
313
+ .err = &err);
314
+
272
315
  if (!*m)
273
316
  goto error;
274
317
  return self;
@@ -412,7 +455,7 @@ static VALUE iodine_mustache_render_klass(int argc, VALUE *argv, VALUE self) {
412
455
  }
413
456
  }
414
457
  if (filename == Qnil && template == Qnil)
415
- rb_raise(rb_eArgError, "missing both template contents and file name.");
458
+ rb_raise(rb_eArgError, "need either template contents or file name.");
416
459
 
417
460
  if (template != Qnil)
418
461
  Check_Type(template, T_STRING);
@@ -502,53 +545,15 @@ Initialize Iodine::Mustache
502
545
  void iodine_init_mustache(void) {
503
546
  call_func_id = rb_intern2("call", 4);
504
547
  to_s_func_id = rb_intern2("to_s", 4);
505
- filename_id = rb_intern2("filename", 8);
506
- data_id = rb_intern2("data", 4);
507
- template_id = rb_intern2("template", 8);
508
- /**
509
- Iodine::Mustache offers a logicless mustache template engine with strict HTML
510
- escaping (more than the basic `"<>'$`).
511
-
512
- This offers more security against XSS and protects against the chance of
513
- executing Ruby code within the template.
514
-
515
- You can test the parser using:
516
-
517
- TEMPLATE="my_template.mustache"
518
-
519
- require 'json'
520
- require 'iodine'
521
- TIMES = 100
522
- STR = IO.binread(JSON_FILENAME); nil
523
-
524
- JSON.parse(STR) == Iodine::JSON.parse(STR) # => true
525
- JSON.parse(STR,
526
- symbolize_names: true) == Iodine::JSON.parse(STR,
527
- symbolize_names: true) # => true
528
- JSON.parse!(STR) == Iodine::JSON.parse!(STR) # => true/false (unknown)
529
-
530
- # warm-up
531
- TIMES.times { JSON.parse STR }
532
- TIMES.times { Iodine::JSON.parse STR }
533
-
534
- Benchmark.bm do |b|
535
- sys = b.report("system") { TIMES.times { JSON.parse STR } }
536
- sys_sym = b.report("system sym") { TIMES.times { JSON.parse STR,
537
- symbolize_names: true } }
538
- iodine = b.report("iodine") { TIMES.times { Iodine::JSON.parse STR } }
539
- iodine_sym = b.report("iodine sym") do
540
- TIMES.times { Iodine::JSON.parse STR,
541
- symbolize_names: true }
542
- end
543
- puts "System / Iodine: #{sys/iodine}"
544
- puts "System-sym/Iodine-sym: #{sys_sym/iodine_sym}"
545
- end; nil
546
-
547
-
548
- */
548
+ filename_id = rb_id2sym(rb_intern2("filename", 8));
549
+ data_id = rb_id2sym(rb_intern2("data", 4));
550
+ template_id = rb_id2sym(rb_intern2("template", 8));
551
+ rb_global_variable(&filename_id);
552
+ rb_global_variable(&data_id);
553
+ rb_global_variable(&template_id);
549
554
  VALUE tmp = rb_define_class_under(IodineModule, "Mustache", rb_cData);
550
555
  rb_define_alloc_func(tmp, iodine_mustache_data_alloc_c);
551
- rb_define_method(tmp, "initialize", iodine_mustache_new, 1);
556
+ rb_define_method(tmp, "initialize", iodine_mustache_new, -1);
552
557
  rb_define_method(tmp, "render", iodine_mustache_render, 1);
553
558
  rb_define_singleton_method(tmp, "render", iodine_mustache_render_klass, -1);
554
559
  // rb_define_module_function(tmp, "render", iodine_mustache_render_klass, 2);
@@ -388,7 +388,7 @@ static VALUE iodine_pubsub_redis_new(int argc, VALUE *argv, VALUE self) {
388
388
  }
389
389
 
390
390
  /* parse URL assume redis://redis:password@localhost:6379 */
391
- http_url_s info = http_url_parse(RSTRING_PTR(url), RSTRING_LEN(url));
391
+ fio_url_s info = fio_url_parse(RSTRING_PTR(url), RSTRING_LEN(url));
392
392
 
393
393
  FIO_LOG_INFO("Initializing Redis engine for address: %.*s",
394
394
  (int)RSTRING_LEN(url), RSTRING_PTR(url));
@@ -100,8 +100,10 @@ static void iodine_tcp_ping(intptr_t uuid, fio_protocol_s *protocol) {
100
100
  (void)uuid;
101
101
  }
102
102
 
103
- /** called when a connection opens */
103
+ /** fio_listen callback, called when a connection opens */
104
104
  static void iodine_tcp_on_open(intptr_t uuid, void *udata) {
105
+ if (!fio_is_valid(uuid))
106
+ return;
105
107
  VALUE handler = IodineCaller.call((VALUE)udata, call_id);
106
108
  IodineStore.add(handler);
107
109
  iodine_tcp_attch_uuid(uuid, handler);
@@ -115,15 +117,12 @@ static void iodine_tcp_on_finish(intptr_t uuid, void *udata) {
115
117
  }
116
118
 
117
119
  /**
118
- * The `on_connect` callback should return a pointer to a protocol object
119
- * that will handle any connection related events.
120
- *
121
- * Should either call `fio_attach` or close the connection.
120
+ * The `on_connect` callback should either call `fio_attach` or close the
121
+ * connection.
122
122
  */
123
123
  static void iodine_tcp_on_connect(intptr_t uuid, void *udata) {
124
- VALUE handler = (VALUE)udata;
125
- iodine_tcp_attch_uuid(uuid, handler);
126
- IodineStore.remove(handler);
124
+ iodine_tcp_attch_uuid(uuid, (VALUE)udata);
125
+ IodineStore.remove((VALUE)udata);
127
126
  }
128
127
 
129
128
  /**
@@ -131,13 +130,7 @@ static void iodine_tcp_on_connect(intptr_t uuid, void *udata) {
131
130
  * is passed along.
132
131
  */
133
132
  static void iodine_tcp_on_fail(intptr_t uuid, void *udata) {
134
- VALUE handler = (VALUE)udata;
135
- if (rb_respond_to(handler, on_closed_id)) {
136
- VALUE client = Qnil;
137
- IodineCaller.call2(handler, on_closed_id, 1, &client);
138
- }
139
- IodineStore.remove(handler);
140
- (void)uuid;
133
+ IodineStore.remove((VALUE)udata);
141
134
  }
142
135
 
143
136
  /* *****************************************************************************
@@ -203,7 +196,7 @@ Here's a telnet based chat-room example:
203
196
  self
204
197
  end
205
198
  end
206
- # we can bothe the `handler` keuword or a block, anything that answers #call.
199
+ # we use can both the `handler` keyword or a block, anything that answers #call.
207
200
  Iodine.listen(port: "3000", handler: ChatHandler)
208
201
  # start the service
209
202
  Iodine.threads = 1
@@ -213,45 +206,13 @@ Here's a telnet based chat-room example:
213
206
 
214
207
  Returns the handler object used.
215
208
  */
216
- static VALUE iodine_tcp_listen(VALUE self, VALUE args) {
209
+ intptr_t iodine_tcp_listen(iodine_connection_args_s args) {
217
210
  // clang-format on
218
- Check_Type(args, T_HASH);
219
- VALUE rb_port = rb_hash_aref(args, port_id);
220
- VALUE rb_address = rb_hash_aref(args, address_id);
221
- VALUE rb_handler = rb_hash_aref(args, handler_id);
222
- fio_str_s port = FIO_STR_INIT;
223
- if (rb_handler == Qnil || rb_handler == Qfalse || rb_handler == Qtrue) {
224
- rb_need_block();
225
- rb_handler = rb_block_proc();
226
- }
227
- IodineStore.add(rb_handler);
228
- if (rb_address != Qnil) {
229
- Check_Type(rb_address, T_STRING);
230
- }
231
-
232
- if (rb_port != Qnil) {
233
- if (rb_port == Qfalse) {
234
- fio_str_write_i(&port, 0);
235
- } else if (RB_TYPE_P(rb_port, T_STRING))
236
- fio_str_write(&port, RSTRING_PTR(rb_port), RSTRING_LEN(rb_port));
237
- else if (RB_TYPE_P(rb_port, T_FIXNUM))
238
- fio_str_write_i(&port, FIX2LONG(rb_port));
239
- else
240
- rb_raise(rb_eTypeError,
241
- "The `port` property MUST be either a String or a Number");
242
- }
243
- if (fio_listen(.port = fio_str_info(&port).data,
244
- .address =
245
- (rb_address == Qnil ? NULL : StringValueCStr(rb_address)),
246
- .on_open = iodine_tcp_on_open,
247
- .on_finish = iodine_tcp_on_finish,
248
- .udata = (void *)rb_handler) == -1) {
249
- IodineStore.remove(rb_handler);
250
- rb_raise(rb_eRuntimeError,
251
- "failed to listen to requested address, unknown error.");
252
- }
253
- return rb_handler;
254
- (void)self;
211
+ IodineStore.add(args.handler);
212
+ return fio_listen(.port = args.port.data, .address = args.address.data,
213
+ .on_open = iodine_tcp_on_open,
214
+ .on_finish = iodine_tcp_on_finish, .tls = args.tls,
215
+ .udata = (void *)args.handler);
255
216
  }
256
217
 
257
218
  // clang-format off
@@ -264,6 +225,7 @@ The method accepts a single Hash argument with the following optional keys:
264
225
  :address :: The address to listen to, which could be a Unix Socket path as well as an IPv4 / IPv6 address. Deafults to 0.0.0.0 (or the IPv6 equivelant).
265
226
  :handler :: A connection callback object that supports the following same callbacks listen in the {listen} method's documentation.
266
227
  :timeout :: An integer timeout for connection establishment (doen't effect the new connection's timeout. Should be in the rand of 0..255.
228
+ :tls :: An {Iodine::TLS} object (optional) for secure connections.
267
229
 
268
230
  The method also accepts an optional block.
269
231
 
@@ -273,45 +235,13 @@ If the connection fails, only the `on_close` callback will be called (with a `ni
273
235
 
274
236
  Returns the handler object used.
275
237
  */
276
- static VALUE iodine_tcp_connect(VALUE self, VALUE args) {
238
+ intptr_t iodine_tcp_connect(iodine_connection_args_s args){
277
239
  // clang-format on
278
- Check_Type(args, T_HASH);
279
- VALUE rb_port = rb_hash_aref(args, port_id);
280
- VALUE rb_address = rb_hash_aref(args, address_id);
281
- VALUE rb_handler = rb_hash_aref(args, handler_id);
282
- VALUE rb_timeout = rb_hash_aref(args, timeout_id);
283
- uint8_t timeout = 0;
284
- fio_str_s port = FIO_STR_INIT;
285
- if (rb_handler == Qnil || rb_handler == Qfalse || rb_handler == Qtrue) {
286
- rb_raise(rb_eArgError, "A callback object (:handler) must be provided.");
287
- }
288
- IodineStore.add(rb_handler);
289
- if (rb_address != Qnil) {
290
- Check_Type(rb_address, T_STRING);
291
- }
292
- if (rb_port != Qnil) {
293
- if (rb_port == Qfalse) {
294
- fio_str_write_i(&port, 0);
295
- } else if (RB_TYPE_P(rb_port, T_STRING))
296
- fio_str_write(&port, RSTRING_PTR(rb_port), RSTRING_LEN(rb_port));
297
- else if (RB_TYPE_P(rb_port, T_FIXNUM))
298
- fio_str_write_i(&port, FIX2LONG(rb_port));
299
- else
300
- rb_raise(rb_eTypeError,
301
- "The `port` property MUST be either a String or a Number");
302
- }
303
- if (rb_timeout != Qnil) {
304
- Check_Type(rb_timeout, T_FIXNUM);
305
- timeout = NUM2USHORT(rb_timeout);
306
- }
307
- fio_connect(.port = fio_str_info(&port).data,
308
- .address =
309
- (rb_address == Qnil ? NULL : StringValueCStr(rb_address)),
310
- .on_connect = iodine_tcp_on_connect,
311
- .on_fail = iodine_tcp_on_fail, .timeout = timeout,
312
- .udata = (void *)rb_handler);
313
- return rb_handler;
314
- (void)self;
240
+ IodineStore.add(args.handler);
241
+ return fio_connect(.port = args.port.data, .address = args.address.data,
242
+ .on_connect = iodine_tcp_on_connect, .tls = args.tls,
243
+ .on_fail = iodine_tcp_on_fail, .timeout = args.ping,
244
+ .udata = (void *)args.handler);
315
245
  }
316
246
 
317
247
  // clang-format off
@@ -358,8 +288,6 @@ void iodine_init_tcp_connections(void) {
358
288
 
359
289
  IodineBinaryEncoding = rb_enc_find("binary");
360
290
 
361
- rb_define_module_function(IodineModule, "listen", iodine_tcp_listen, 1);
362
- rb_define_module_function(IodineModule, "connect", iodine_tcp_connect, 1);
363
291
  rb_define_module_function(IodineModule, "attach_fd", iodine_tcp_attach_fd, 2);
364
292
  }
365
293
 
@@ -369,16 +297,15 @@ Allow uuid attachment
369
297
 
370
298
  /** assigns a protocol and IO object to a handler */
371
299
  void iodine_tcp_attch_uuid(intptr_t uuid, VALUE handler) {
300
+ FIO_LOG_DEBUG("Iodine attaching handler %p to uuid %p", (void *)handler,
301
+ (void *)uuid);
372
302
  if (handler == Qnil || handler == Qfalse || handler == Qtrue) {
373
303
  fio_close(uuid);
374
304
  return;
375
305
  }
376
306
  /* temporary, in case `iodine_connection_new` invokes the GC */
377
307
  iodine_protocol_s *p = malloc(sizeof(*p));
378
- if (!p) {
379
- perror("FATAL ERROR: No Memory!");
380
- exit(errno);
381
- }
308
+ FIO_ASSERT_ALLOC(p);
382
309
  *p = (iodine_protocol_s){
383
310
  .p =
384
311
  {
@@ -393,6 +320,13 @@ void iodine_tcp_attch_uuid(intptr_t uuid, VALUE handler) {
393
320
  };
394
321
  /* clear away (remember the connection object manages these concerns) */
395
322
  fio_attach(uuid, &p->p);
396
- iodine_connection_fire_event(p->io, IODINE_CONNECTION_ON_OPEN, Qnil);
397
- p->p.on_ready = iodine_tcp_on_ready;
323
+ if (fio_is_valid(uuid)) {
324
+ iodine_connection_fire_event(p->io, IODINE_CONNECTION_ON_OPEN, Qnil);
325
+ p->p.on_ready = iodine_tcp_on_ready;
326
+ fio_force_event(uuid, FIO_EVENT_ON_READY);
327
+ } else {
328
+ FIO_LOG_DEBUG(
329
+ "Iodine couldn't attach handler %p to uuid %p - invalid uuid.",
330
+ (void *)handler, (void *)uuid);
331
+ }
398
332
  }
@@ -3,7 +3,11 @@
3
3
 
4
4
  #include "ruby.h"
5
5
 
6
+ #include "iodine.h"
7
+
6
8
  void iodine_init_tcp_connections(void);
7
9
  void iodine_tcp_attch_uuid(intptr_t uuid, VALUE handler);
10
+ intptr_t iodine_tcp_listen(iodine_connection_args_s args);
11
+ intptr_t iodine_tcp_connect(iodine_connection_args_s args);
8
12
 
9
13
  #endif
@@ -0,0 +1,267 @@
1
+ #include <ruby.h>
2
+
3
+ #include <fio.h>
4
+ #include <fio_tls.h>
5
+ #include <iodine.h>
6
+
7
+ static VALUE server_name_sym = Qnil, certificate_sym = Qnil,
8
+ private_key_sym = Qnil, password_sym = Qnil;
9
+ VALUE IodineTLSClass;
10
+ /* *****************************************************************************
11
+ C <=> Ruby Data allocation
12
+ ***************************************************************************** */
13
+
14
+ static size_t iodine_tls_data_size(const void *c_) {
15
+ return sizeof(fio_tls_s *);
16
+ (void)c_;
17
+ }
18
+
19
+ static void iodine_tls_data_free(void *c_) { fio_tls_destroy(c_); }
20
+
21
+ static const rb_data_type_t iodine_tls_data_type = {
22
+ .wrap_struct_name = "IodineTLSData",
23
+ .function =
24
+ {
25
+ .dmark = NULL,
26
+ .dfree = iodine_tls_data_free,
27
+ .dsize = iodine_tls_data_size,
28
+ },
29
+ .data = NULL,
30
+ // .flags = RUBY_TYPED_FREE_IMMEDIATELY,
31
+ };
32
+
33
+ /* Iodine::PubSub::Engine.allocate */
34
+ static VALUE iodine_tls_data_alloc_c(VALUE klass) {
35
+ fio_tls_s *tls = fio_tls_new(NULL, NULL, NULL, NULL);
36
+ FIO_ASSERT_ALLOC(tls);
37
+ return TypedData_Wrap_Struct(klass, &iodine_tls_data_type, tls);
38
+ }
39
+
40
+ /* *****************************************************************************
41
+ ALPN selection callback
42
+ ***************************************************************************** */
43
+
44
+ FIO_FUNC void iodine_tls_alpn_cb(intptr_t uuid, void *udata, void *block_) {
45
+ if (!fio_is_valid(uuid)) {
46
+ FIO_LOG_DEBUG("ALPN callback called for invalid connetion. SSL/TLS error?");
47
+ return;
48
+ }
49
+ VALUE new_handler = IodineCaller.call((VALUE)block_, iodine_call_id);
50
+ if (!new_handler || new_handler == Qnil || new_handler == Qtrue ||
51
+ new_handler == Qfalse) {
52
+ fio_close(uuid);
53
+ return;
54
+ }
55
+
56
+ iodine_tcp_attch_uuid(uuid, new_handler);
57
+
58
+ (void)udata; /* we can't use `udata`, since it's different in HTTP vs. TCP */
59
+ }
60
+
61
+ /* *****************************************************************************
62
+ C API
63
+ ***************************************************************************** */
64
+
65
+ fio_tls_s *iodine_tls2c(VALUE self) {
66
+ fio_tls_s *c = NULL;
67
+ if (self == Qnil || self == Qfalse)
68
+ return NULL;
69
+ TypedData_Get_Struct(self, fio_tls_s, &iodine_tls_data_type, c);
70
+ if (!c) {
71
+ rb_raise(rb_eTypeError, "Iodine::TLS error - not an Iodine::TLS object?");
72
+ }
73
+ return c;
74
+ }
75
+
76
+ /* *****************************************************************************
77
+ Ruby API
78
+ ***************************************************************************** */
79
+
80
+ /**
81
+ Assigns the TLS context a public sertificate, allowing remote parties to
82
+ validate the connection's identity.
83
+
84
+ A self signed certificate is automatically created if the `server_name` argument
85
+ is specified and either (or both) of the `certificate` or `private_ket`
86
+ arguments are missing.
87
+
88
+ Some implementations allow servers to have more than a single certificate, which
89
+ will be selected using the SNI extension. I believe the existing OpenSSL
90
+ implementation supports this option (untested).
91
+
92
+ Iodine::TLS#use_certificate(server_name,
93
+ certificate = nil,
94
+ private_key = nil,
95
+ password = nil)
96
+
97
+ Certificates and keys should be String objects leading to a PEM file.
98
+
99
+ This method also accepts named arguments. i.e.:
100
+
101
+ tls = Iodine::TLS.new
102
+ tls.use_certificate server_name: "example.com"
103
+ tls.use_certificate certificate: "my_cert.pem", private_key: "my_key.pem"
104
+
105
+ Since TLS setup is crucial for security, a missing file will result in Iodine
106
+ crashing with an error message. This is expected behavior.
107
+
108
+ */
109
+ static VALUE iodine_tls_use_certificate(int argc, VALUE *argv, VALUE self) {
110
+ VALUE server_name = Qnil, certificate = Qnil, private_key = Qnil,
111
+ password = Qnil;
112
+ if (argc == 1 && RB_TYPE_P(argv[0], T_HASH)) {
113
+ /* named arguments */
114
+ server_name = rb_hash_aref(argv[0], server_name_sym);
115
+ certificate = rb_hash_aref(argv[0], certificate_sym);
116
+ private_key = rb_hash_aref(argv[0], private_key_sym);
117
+ password = rb_hash_aref(argv[0], password_sym);
118
+ } else {
119
+ /* regular arguments */
120
+ switch (argc) {
121
+ case 4: /* overflow */
122
+ password = argv[3];
123
+ Check_Type(password, T_STRING);
124
+ case 3: /* overflow */
125
+ private_key = argv[1];
126
+ Check_Type(private_key, T_STRING);
127
+ case 2: /* overflow */
128
+ certificate = argv[2];
129
+ Check_Type(certificate, T_STRING);
130
+ case 1: /* overflow */
131
+ server_name = argv[0];
132
+ Check_Type(server_name, T_STRING);
133
+ break;
134
+ default:
135
+ rb_raise(rb_eArgError, "expecting 1..4 arguments or named arguments.");
136
+ return self;
137
+ }
138
+ }
139
+
140
+ fio_tls_s *t = iodine_tls2c(self);
141
+ char *srvname = (server_name == Qnil ? NULL : StringValueCStr(server_name));
142
+ char *pubcert = (certificate == Qnil ? NULL : StringValueCStr(certificate));
143
+ char *prvkey = (private_key == Qnil ? NULL : StringValueCStr(private_key));
144
+ char *pass = (password == Qnil ? NULL : StringValueCStr(password));
145
+
146
+ fio_tls_cert_add(t, srvname, pubcert, prvkey, pass);
147
+ return self;
148
+ }
149
+
150
+ /**
151
+ Adds a certificate PEM file to the list of trusted certificates and enforces
152
+ peer verification.
153
+
154
+ This is extremely important when using {Iodine::TLS} for client connections,
155
+ since adding the target server's
156
+
157
+ It is enough to add the Certificate Authority's (CA) certificate, there's no
158
+ need to add each client or server certificate.
159
+
160
+ When {trust} is used on a server TLS, only trusted clients will be allowed to
161
+ connect.
162
+
163
+ Since TLS setup is crucial for security, a missing file will result in Iodine
164
+ crashing with an error message. This is expected behavior.
165
+ */
166
+ static VALUE iodine_tls_trust(VALUE self, VALUE certificate) {
167
+ Check_Type(certificate, T_STRING);
168
+ fio_tls_s *t = iodine_tls2c(self);
169
+ char *pubcert =
170
+ (certificate == Qnil ? NULL : IODINE_RSTRINFO(certificate).data);
171
+ fio_tls_trust(t, pubcert);
172
+ return self;
173
+ }
174
+
175
+ /**
176
+ Adds an ALPN protocol callback for the named protocol, the required block must
177
+ return the handler for that protocol.
178
+
179
+ The first protocol added will be the default protocol in cases where ALPN
180
+ failed.
181
+
182
+ i.e.:
183
+
184
+ tls.on_protocol("http/1.1") { HTTPConnection.new }
185
+
186
+ When implementing TLS clients, this identifies the protocol(s) that should be
187
+ requested by the client.
188
+
189
+ When implementing TLS servers, this identifies the protocol(s) offered by the
190
+ server.
191
+
192
+ More than a single protocol can be set, but iodine doesn't offer, at this
193
+ moment, a way to handle these changes or to detect which protocol was selected
194
+ except by assigning a different callback per protocol.
195
+
196
+ This is implemented using the ALPN extension to TLS.
197
+ */
198
+ static VALUE iodine_tls_alpn(VALUE self, VALUE protocol_name) {
199
+ Check_Type(protocol_name, T_STRING);
200
+ rb_need_block();
201
+ fio_tls_s *t = iodine_tls2c(self);
202
+ char *prname =
203
+ (protocol_name == Qnil ? NULL : IODINE_RSTRINFO(protocol_name).data);
204
+ VALUE block = IodineStore.add(rb_block_proc());
205
+ fio_tls_alpn_add(t, prname, iodine_tls_alpn_cb, (void *)block,
206
+ (void (*)(void *))IodineStore.remove);
207
+ return self;
208
+ }
209
+
210
+ /**
211
+ Loads the mustache template found in `:filename`. If `:template` is provided
212
+ it will be used instead of reading the file's content.
213
+
214
+ Iodine::Mustache.new(filename, template = nil)
215
+
216
+ When template data is provided, filename (if any) will only be used for
217
+ partial template path resolution and the template data will be used for the
218
+ template's content. This allows, for example, for front matter to be extracted
219
+ before parsing the template.
220
+
221
+ Once a template was loaded, it could be rendered using {render}.
222
+
223
+ Accepts named arguments as well:
224
+
225
+ Iodine::Mustache.new(filename: "foo.mustache", template: "{{ bar }}")
226
+
227
+ */
228
+ static VALUE iodine_tls_new(int argc, VALUE *argv, VALUE self) {
229
+ if (argc) {
230
+ iodine_tls_use_certificate(argc, argv, self);
231
+ }
232
+ return self;
233
+ }
234
+
235
+ /* *****************************************************************************
236
+ Initialize Iodine::TLS
237
+ ***************************************************************************** */
238
+ #define IODINE_MAKE_SYM(name) \
239
+ do { \
240
+ name##_sym = rb_id2sym(rb_intern(#name)); \
241
+ rb_global_variable(&name##_sym); \
242
+ } while (0)
243
+
244
+ void iodine_init_tls(void) {
245
+
246
+ IODINE_MAKE_SYM(server_name);
247
+ IODINE_MAKE_SYM(certificate);
248
+ IODINE_MAKE_SYM(private_key);
249
+ IODINE_MAKE_SYM(password);
250
+
251
+ IodineTLSClass = rb_define_class_under(IodineModule, "TLS", rb_cData);
252
+ rb_define_alloc_func(IodineTLSClass, iodine_tls_data_alloc_c);
253
+ rb_define_method(IodineTLSClass, "initialize", iodine_tls_new, -1);
254
+ rb_define_method(IodineTLSClass, "use_certificate",
255
+ iodine_tls_use_certificate, -1);
256
+ rb_define_method(IodineTLSClass, "trust", iodine_tls_trust, 1);
257
+ rb_define_method(IodineTLSClass, "on_protocol", iodine_tls_alpn, 1);
258
+
259
+ #if HAVE_OPENSSL
260
+ rb_const_set(IodineTLSClass, rb_intern("SUPPORTED"), Qtrue);
261
+ #elif HAVE_BEARSSL
262
+ rb_const_set(IodineTLSClass, rb_intern("SUPPORTED"), Qfalse);
263
+ #else
264
+ rb_const_set(IodineTLSClass, rb_intern("SUPPORTED"), Qfalse);
265
+ #endif
266
+ }
267
+ #undef IODINE_MAKE_SYM