iodine 0.3.6 → 0.4.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.

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/CHANGELOG.md +46 -0
  3. data/LIMITS.md +25 -0
  4. data/README.md +39 -80
  5. data/SPEC-Websocket-Draft.md +129 -4
  6. data/bin/echo +2 -2
  7. data/bin/http-hello +1 -0
  8. data/bin/updated api +113 -0
  9. data/bin/ws-echo +0 -1
  10. data/examples/broadcast.ru +56 -0
  11. data/examples/echo.ru +57 -0
  12. data/examples/hello.ru +30 -0
  13. data/examples/redis.ru +69 -0
  14. data/examples/shootout.ru +53 -0
  15. data/exe/iodine +2 -80
  16. data/ext/iodine/defer.c +11 -5
  17. data/ext/iodine/empty.h +26 -0
  18. data/ext/iodine/evio.h +1 -1
  19. data/ext/iodine/facil.c +103 -61
  20. data/ext/iodine/facil.h +20 -12
  21. data/ext/iodine/fio_dict.c +446 -0
  22. data/ext/iodine/fio_dict.h +90 -0
  23. data/ext/iodine/fio_hash_table.h +370 -0
  24. data/ext/iodine/fio_list.h +30 -3
  25. data/ext/iodine/http.c +169 -37
  26. data/ext/iodine/http.h +33 -10
  27. data/ext/iodine/http1.c +78 -42
  28. data/ext/iodine/http_request.c +6 -0
  29. data/ext/iodine/http_request.h +3 -0
  30. data/ext/iodine/http_response.c +43 -11
  31. data/ext/iodine/iodine.c +380 -0
  32. data/ext/iodine/iodine.h +62 -0
  33. data/ext/iodine/iodine_helpers.c +235 -0
  34. data/ext/iodine/iodine_helpers.h +13 -0
  35. data/ext/iodine/iodine_http.c +409 -241
  36. data/ext/iodine/iodine_http.h +7 -14
  37. data/ext/iodine/iodine_protocol.c +626 -0
  38. data/ext/iodine/iodine_protocol.h +13 -0
  39. data/ext/iodine/iodine_pubsub.c +646 -0
  40. data/ext/iodine/iodine_pubsub.h +27 -0
  41. data/ext/iodine/iodine_websockets.c +796 -0
  42. data/ext/iodine/iodine_websockets.h +19 -0
  43. data/ext/iodine/pubsub.c +544 -0
  44. data/ext/iodine/pubsub.h +215 -0
  45. data/ext/iodine/random.c +4 -4
  46. data/ext/iodine/rb-call.c +1 -5
  47. data/ext/iodine/rb-defer.c +3 -20
  48. data/ext/iodine/rb-rack-io.c +22 -22
  49. data/ext/iodine/rb-rack-io.h +3 -4
  50. data/ext/iodine/rb-registry.c +111 -118
  51. data/ext/iodine/redis_connection.c +277 -0
  52. data/ext/iodine/redis_connection.h +77 -0
  53. data/ext/iodine/redis_engine.c +398 -0
  54. data/ext/iodine/redis_engine.h +68 -0
  55. data/ext/iodine/resp.c +842 -0
  56. data/ext/iodine/resp.h +253 -0
  57. data/ext/iodine/sock.c +26 -12
  58. data/ext/iodine/sock.h +14 -3
  59. data/ext/iodine/spnlock.inc +19 -2
  60. data/ext/iodine/websockets.c +299 -11
  61. data/ext/iodine/websockets.h +159 -6
  62. data/lib/iodine.rb +104 -1
  63. data/lib/iodine/cli.rb +106 -0
  64. data/lib/iodine/monkeypatch.rb +40 -0
  65. data/lib/iodine/pubsub.rb +70 -0
  66. data/lib/iodine/version.rb +1 -1
  67. data/lib/iodine/websocket.rb +12 -0
  68. data/lib/rack/handler/iodine.rb +33 -7
  69. metadata +35 -7
  70. data/ext/iodine/iodine_core.c +0 -760
  71. data/ext/iodine/iodine_core.h +0 -79
  72. data/ext/iodine/iodine_websocket.c +0 -551
  73. data/ext/iodine/iodine_websocket.h +0 -22
  74. data/lib/iodine/http.rb +0 -4
@@ -24,6 +24,9 @@ static inline __attribute__((unused)) void fio_list_init(fio_list_s *list) {
24
24
  /** A macro that evaluates to an initialized list head. */
25
25
  #define FIO_LIST_INIT(name) \
26
26
  (fio_list_s) { .next = &(name), .prev = &(name) }
27
+ /** A macro that evaluates to an initialized list head. */
28
+ #define FIO_LIST_INIT_STATIC(name) \
29
+ { .next = &(name), .prev = &(name) }
27
30
 
28
31
  /** Adds a list reference to the list at the specified position. */
29
32
  static inline __attribute__((unused)) void fio_list_add(fio_list_s *pos,
@@ -44,15 +47,39 @@ fio_list_remove(fio_list_s *item) {
44
47
  *item = (fio_list_s){.next = item, .prev = item};
45
48
  return item;
46
49
  }
50
+ /** Switches two list items. */
51
+ static inline __attribute__((unused)) void fio_list_switch(fio_list_s *item1,
52
+ fio_list_s *item2) {
53
+ if (item1 == item2)
54
+ return;
55
+ fio_list_s tmp = *item1;
56
+ *item1 = *item2;
57
+ *item2 = tmp;
58
+ if (item1->next == item2)
59
+ item1->next = item1;
60
+ else
61
+ item1->next->prev = item1;
62
+ if (item1->prev == item2)
63
+ item1->prev = item1;
64
+ else
65
+ item1->prev->next = item1;
66
+ }
67
+
68
+ #ifndef fio_node2obj
69
+ /** Takes a node pointer (list/hash/dict, etc') and returns it's container. */
70
+ #define fio_node2obj(type, member, ptr) \
71
+ ((type *)((uintptr_t)(ptr) - (uintptr_t)(&(((type *)0)->member))))
72
+ #endif
47
73
 
48
74
  /** Takes a list pointer and returns a pointer to it's container. */
49
- #define fio_list_object(type, member, plist) \
50
- ((type *)((uintptr_t)(plist) - (uintptr_t)(&(((type *)0)->member))))
75
+ #define fio_list_object(type, member, plist) fio_node2obj(type, member, (plist))
51
76
 
52
77
  /** iterates the whole list. */
53
78
  #define fio_list_for_each(type, member, var, head) \
54
79
  for (fio_list_s *pos = (head).next->next; \
55
- &((var) = fio_list_object(type, member, pos->prev))->member != &(head); \
80
+ (&((var) = fio_list_object(type, member, pos->prev))->member != \
81
+ &(head)) || \
82
+ ((var) = NULL); \
56
83
  (var) = fio_list_object(type, member, pos), pos = pos->next)
57
84
 
58
85
  /** Removes a member from the end of the list. */
@@ -32,14 +32,19 @@ http_on_finish_func http_get_on_finish_func(http_settings_s *settings) {
32
32
  return (http_on_finish_func)route[settings->version];
33
33
  }
34
34
 
35
- void http_on_finish(void *set) {
35
+ void http_on_finish(intptr_t uuid, void *set) {
36
36
  http_settings_s *settings = set;
37
37
  if (http_get_on_finish_func(set))
38
38
  http_get_on_finish_func(set)(set);
39
- if (settings->private_metaflags & 1)
39
+
40
+ if (settings->on_finish)
41
+ settings->on_finish(uuid, settings->udata);
42
+
43
+ if (settings->private_metaflags & 2) {
40
44
  free((void *)settings->public_folder);
41
- if (settings->private_metaflags & 2)
42
45
  free(settings);
46
+ }
47
+ (void)uuid;
43
48
  }
44
49
 
45
50
  /**
@@ -83,14 +88,19 @@ int http_listen(const char *port, const char *address,
83
88
  memcpy(tmp + home_len, settings->public_folder + 1,
84
89
  settings->public_folder_length); // copy also the NULL
85
90
  settings->public_folder = tmp;
86
- settings->private_metaflags |= 1;
87
91
  settings->public_folder_length = strlen(settings->public_folder);
92
+ } else {
93
+ settings->public_folder = malloc(settings->public_folder_length + 1);
94
+ memcpy((void *)settings->public_folder, arg_settings.public_folder,
95
+ settings->public_folder_length);
96
+ ((uint8_t *)settings->public_folder)[settings->public_folder_length] = 0;
88
97
  }
89
98
  }
90
99
 
91
100
  return facil_listen(.port = port, .address = address,
92
101
  .set_rw_hooks = arg_settings.set_rw_hooks,
93
102
  .rw_udata = arg_settings.rw_udata,
103
+ .on_finish_rw = arg_settings.on_finish_rw,
94
104
  .on_finish = http_on_finish, .on_open = on_open_callback,
95
105
  .udata = settings);
96
106
  }
@@ -117,7 +127,7 @@ struct tm *http_gmtime(const time_t *timer, struct tm *tmbuf) {
117
127
  };
118
128
  if (*timer < 0)
119
129
  return gmtime_r(timer, tmbuf);
120
- ssize_t tmp;
130
+ ssize_t a, b;
121
131
  tmbuf->tm_gmtoff = 0;
122
132
  tmbuf->tm_zone = "UTC";
123
133
  tmbuf->tm_isdst = 0;
@@ -125,82 +135,83 @@ struct tm *http_gmtime(const time_t *timer, struct tm *tmbuf) {
125
135
  tmbuf->tm_mon = 0;
126
136
  // for seconds up to weekdays, we build up, as small values clean up larger
127
137
  // values.
128
- tmp = ((ssize_t)*timer);
129
- tmbuf->tm_sec = tmp % 60;
130
- tmp = tmp / 60;
131
- tmbuf->tm_min = tmp % 60;
132
- tmp = tmp / 60;
133
- tmbuf->tm_hour = tmp % 24;
134
- tmp = tmp / 24;
135
- // day of epoch was a thursday. Add + 3 so sunday == 0...
136
- tmbuf->tm_wday = (tmp + 3) % 7;
138
+ a = ((ssize_t)*timer);
139
+ b = a / 60;
140
+ tmbuf->tm_sec = a - (b * 60);
141
+ a = b / 60;
142
+ tmbuf->tm_min = b - (a * 60);
143
+ b = a / 24;
144
+ tmbuf->tm_hour = a - (b * 24);
145
+ // day of epoch was a thursday. Add + 4 so sunday == 0...
146
+ tmbuf->tm_wday = (b + 4) % 7;
137
147
  // tmp == number of days since epoch
138
148
  #define DAYS_PER_400_YEARS ((400 * 365) + 97)
139
- while (tmp >= DAYS_PER_400_YEARS) {
149
+ while (b >= DAYS_PER_400_YEARS) {
140
150
  tmbuf->tm_year += 400;
141
- tmp -= DAYS_PER_400_YEARS;
151
+ b -= DAYS_PER_400_YEARS;
142
152
  }
143
153
  #undef DAYS_PER_400_YEARS
144
154
  #define DAYS_PER_100_YEARS ((100 * 365) + 24)
145
- while (tmp >= DAYS_PER_100_YEARS) {
155
+ while (b >= DAYS_PER_100_YEARS) {
146
156
  tmbuf->tm_year += 100;
147
- tmp -= DAYS_PER_100_YEARS;
157
+ b -= DAYS_PER_100_YEARS;
148
158
  if (((tmbuf->tm_year / 100) & 3) ==
149
159
  0) // leap century divisable by 400 => add leap
150
- --tmp;
160
+ --b;
151
161
  }
152
162
  #undef DAYS_PER_100_YEARS
153
163
  #define DAYS_PER_32_YEARS ((32 * 365) + 8)
154
- while (tmp >= DAYS_PER_32_YEARS) {
164
+ while (b >= DAYS_PER_32_YEARS) {
155
165
  tmbuf->tm_year += 32;
156
- tmp -= DAYS_PER_32_YEARS;
166
+ b -= DAYS_PER_32_YEARS;
157
167
  }
158
168
  #undef DAYS_PER_32_YEARS
159
169
  #define DAYS_PER_8_YEARS ((8 * 365) + 2)
160
- while (tmp >= DAYS_PER_8_YEARS) {
170
+ while (b >= DAYS_PER_8_YEARS) {
161
171
  tmbuf->tm_year += 8;
162
- tmp -= DAYS_PER_8_YEARS;
172
+ b -= DAYS_PER_8_YEARS;
163
173
  }
164
174
  #undef DAYS_PER_8_YEARS
165
175
  #define DAYS_PER_4_YEARS ((4 * 365) + 1)
166
- while (tmp >= DAYS_PER_4_YEARS) {
176
+ while (b >= DAYS_PER_4_YEARS) {
167
177
  tmbuf->tm_year += 4;
168
- tmp -= DAYS_PER_4_YEARS;
178
+ b -= DAYS_PER_4_YEARS;
169
179
  }
170
180
  #undef DAYS_PER_4_YEARS
171
- while (tmp >= 365) {
181
+ while (b >= 365) {
172
182
  tmbuf->tm_year += 1;
173
- tmp -= 365;
183
+ b -= 365;
174
184
  if ((tmbuf->tm_year & 3) == 0) { // leap year
175
- if (tmp > 0) {
176
- --tmp;
185
+ if (b > 0) {
186
+ --b;
177
187
  continue;
178
188
  } else {
179
- tmp += 365;
189
+ b += 365;
180
190
  --tmbuf->tm_year;
181
191
  break;
182
192
  }
183
193
  }
184
194
  }
185
- tmbuf->tm_yday = tmp;
195
+ b++; /* day 1 of the year is 1, not 0. */
196
+ tmbuf->tm_yday = b;
186
197
  if ((tmbuf->tm_year & 3) == 1) {
187
198
  // regular year
188
199
  for (size_t i = 0; i < 12; i++) {
189
- if (tmp < month_len[i])
200
+ if (b <= month_len[i])
190
201
  break;
191
- tmp -= month_len[i];
202
+ b -= month_len[i];
192
203
  ++tmbuf->tm_mon;
193
204
  }
194
205
  } else {
195
206
  // leap year
196
207
  for (size_t i = 12; i < 24; i++) {
197
- if (tmp < month_len[i])
208
+ if (b <= month_len[i])
198
209
  break;
199
- tmp -= month_len[i];
210
+ b -= month_len[i];
200
211
  ++tmbuf->tm_mon;
201
212
  }
202
213
  }
203
- tmbuf->tm_mday = tmp;
214
+ tmbuf->tm_mday = b;
204
215
  return tmbuf;
205
216
  }
206
217
 
@@ -249,6 +260,91 @@ size_t http_date2str(char *target, struct tm *tmbuf) {
249
260
  return pos - target;
250
261
  }
251
262
 
263
+ size_t http_date2rfc2822(char *target, struct tm *tmbuf) {
264
+ char *pos = target;
265
+ uint16_t tmp;
266
+ *(uint32_t *)pos = *((uint32_t *)DAY_NAMES[tmbuf->tm_wday]);
267
+ pos[3] = ',';
268
+ pos[4] = ' ';
269
+ pos += 5;
270
+ if (tmbuf->tm_mday < 10) {
271
+ *pos = '0' + tmbuf->tm_mday;
272
+ ++pos;
273
+ } else {
274
+ tmp = tmbuf->tm_mday / 10;
275
+ pos[0] = '0' + tmp;
276
+ pos[1] = '0' + (tmbuf->tm_mday - (tmp * 10));
277
+ pos += 2;
278
+ }
279
+ *(pos++) = '-';
280
+ *(uint32_t *)pos = *((uint32_t *)MONTH_NAMES[tmbuf->tm_mon]);
281
+ pos += 3;
282
+ *(pos++) = '-';
283
+ // write year.
284
+ pos += http_ul2a(pos, tmbuf->tm_year + 1900);
285
+ *(pos++) = ' ';
286
+ tmp = tmbuf->tm_hour / 10;
287
+ pos[0] = '0' + tmp;
288
+ pos[1] = '0' + (tmbuf->tm_hour - (tmp * 10));
289
+ pos[2] = ':';
290
+ tmp = tmbuf->tm_min / 10;
291
+ pos[3] = '0' + tmp;
292
+ pos[4] = '0' + (tmbuf->tm_min - (tmp * 10));
293
+ pos[5] = ':';
294
+ tmp = tmbuf->tm_sec / 10;
295
+ pos[6] = '0' + tmp;
296
+ pos[7] = '0' + (tmbuf->tm_sec - (tmp * 10));
297
+ pos += 8;
298
+ pos[0] = ' ';
299
+ *((uint32_t *)(pos + 1)) = *((uint32_t *)GMT_STR);
300
+ pos += 4;
301
+ return pos - target;
302
+ }
303
+
304
+ size_t http_date2rfc2109(char *target, struct tm *tmbuf) {
305
+ char *pos = target;
306
+ uint16_t tmp;
307
+ *(uint32_t *)pos = *((uint32_t *)DAY_NAMES[tmbuf->tm_wday]);
308
+ pos[3] = ',';
309
+ pos[4] = ' ';
310
+ pos += 5;
311
+ if (tmbuf->tm_mday < 10) {
312
+ *pos = '0' + tmbuf->tm_mday;
313
+ ++pos;
314
+ } else {
315
+ tmp = tmbuf->tm_mday / 10;
316
+ pos[0] = '0' + tmp;
317
+ pos[1] = '0' + (tmbuf->tm_mday - (tmp * 10));
318
+ pos += 2;
319
+ }
320
+ *(pos++) = ' ';
321
+ *(uint32_t *)pos = *((uint32_t *)MONTH_NAMES[tmbuf->tm_mon]);
322
+ pos += 4;
323
+ // write year.
324
+ pos += http_ul2a(pos, tmbuf->tm_year + 1900);
325
+ *(pos++) = ' ';
326
+ tmp = tmbuf->tm_hour / 10;
327
+ pos[0] = '0' + tmp;
328
+ pos[1] = '0' + (tmbuf->tm_hour - (tmp * 10));
329
+ pos[2] = ':';
330
+ tmp = tmbuf->tm_min / 10;
331
+ pos[3] = '0' + tmp;
332
+ pos[4] = '0' + (tmbuf->tm_min - (tmp * 10));
333
+ pos[5] = ':';
334
+ tmp = tmbuf->tm_sec / 10;
335
+ pos[6] = '0' + tmp;
336
+ pos[7] = '0' + (tmbuf->tm_sec - (tmp * 10));
337
+ pos += 8;
338
+ *pos++ = ' ';
339
+ *pos++ = '-';
340
+ *pos++ = '0';
341
+ *pos++ = '0';
342
+ *pos++ = '0';
343
+ *pos++ = '0';
344
+ *pos = 0;
345
+ return pos - target;
346
+ }
347
+
252
348
  /* Credit to Jonathan Leffler for the idea of a unified conditional */
253
349
  #define hex_val(c) \
254
350
  (((c) >= '0' && (c) <= '9') \
@@ -277,7 +373,7 @@ static inline int hex2byte(uint8_t *dest, const uint8_t *source) {
277
373
  return -1;
278
374
  return 0;
279
375
  }
280
- #undef hex_val_tmp
376
+
281
377
  ssize_t http_decode_url(char *dest, const char *url_data, size_t length) {
282
378
  char *pos = dest;
283
379
  const char *end = url_data + length;
@@ -320,4 +416,40 @@ ssize_t http_decode_url_unsafe(char *dest, const char *url_data) {
320
416
  *pos = 0;
321
417
  return pos - dest;
322
418
  }
419
+
420
+ ssize_t http_decode_path(char *dest, const char *url_data, size_t length) {
421
+ char *pos = dest;
422
+ const char *end = url_data + length;
423
+ while (url_data < end) {
424
+ if (*url_data == '%') {
425
+ // decode hex value
426
+ // this is a percent encoded value.
427
+ if (hex2byte((uint8_t *)pos, (uint8_t *)&url_data[1]))
428
+ return -1;
429
+ pos++;
430
+ url_data += 3;
431
+ } else
432
+ *(pos++) = *(url_data++);
433
+ }
434
+ *pos = 0;
435
+ return pos - dest;
436
+ }
437
+
438
+ ssize_t http_decode_path_unsafe(char *dest, const char *url_data) {
439
+ char *pos = dest;
440
+ while (*url_data) {
441
+ if (*url_data == '%') {
442
+ // decode hex value
443
+ // this is a percent encoded value.
444
+ if (hex2byte((uint8_t *)pos, (uint8_t *)&url_data[1]))
445
+ return -1;
446
+ pos++;
447
+ url_data += 3;
448
+ } else
449
+ *(pos++) = *(url_data++);
450
+ }
451
+ *pos = 0;
452
+ return pos - dest;
453
+ }
454
+
323
455
  #undef hex_val
@@ -28,6 +28,9 @@ typedef struct {
28
28
  };
29
29
  } http_header_s;
30
30
 
31
+ /** Settings typedef */
32
+ typedef struct http_settings_s http_settings_s;
33
+
31
34
  /* *****************************************************************************
32
35
  Core include files
33
36
  */
@@ -56,14 +59,14 @@ HTTP Core API & data structure
56
59
  */
57
60
 
58
61
  /** Manages protocol settings for the HTTP protocol */
59
- typedef struct {
62
+ struct http_settings_s {
60
63
  /**
61
64
  The maximum size of an HTTP request's body (when posting data).
62
65
 
63
66
  Defaults to ~ 50Mb.
64
67
  */
65
68
  size_t max_body_size;
66
- /** the callback to be performed when requests come in. */
69
+ /** REQUIRED: the callback to be performed when requests come in. */
67
70
  void (*on_request)(http_request_s *request);
68
71
  /**
69
72
  A public folder for file transfers - allows to circumvent any application
@@ -74,20 +77,28 @@ typedef struct {
74
77
  The length of the public_folder string.
75
78
  */
76
79
  size_t public_folder_length;
77
- /** (optional)
80
+ /** Opaque user data. */
81
+ void *udata;
82
+ /** Opaque user data for the optional `set_rw_hooks`. */
83
+ void *rw_udata;
84
+ /** (optional) the callback to be performed when the HTTP service closes. */
85
+ void (*on_finish)(intptr_t uuid, void *udata);
86
+ /**
78
87
  * Allows a an implementation for the transport layer (i.e. TLS) without
79
88
  * effecting the HTTP protocol.
80
89
  */
81
90
  sock_rw_hook_s *(*set_rw_hooks)(intptr_t fduuid, void *rw_udata);
82
- /** Opaque user data for `set_rw_hooks`. */
83
- void *rw_udata;
91
+ /**
92
+ * A cleanup callback for the `rw_udata`.
93
+ */
94
+ void (*on_finish_rw)(intptr_t uuid, void *rw_udata);
84
95
  /**
85
96
  Logging flag - set to TRUE to log static file requests.
86
97
 
87
98
  Dynamic request logging is always the dynamic application's responsibility.
88
99
  */
89
100
  uint8_t log_static;
90
- /** An HTTP connection timeout. For HTTP/1.1 this defaults to ~5 seconds.*/
101
+ /** An HTTP/1.x connection timeout. Defaults to ~5 seconds.*/
91
102
  uint8_t timeout;
92
103
  /**
93
104
  The default HTTP version which a new connection will use. At the moment, only
@@ -98,7 +109,7 @@ typedef struct {
98
109
  internal flag for library use.
99
110
  */
100
111
  uint8_t private_metaflags;
101
- } http_settings_s;
112
+ };
102
113
 
103
114
  typedef protocol_s *(*http_on_open_func)(intptr_t, void *);
104
115
  typedef void (*http_on_finish_func)(void *);
@@ -139,11 +150,16 @@ struct tm *http_gmtime(const time_t *timer, struct tm *tmbuf);
139
150
  /**
140
151
  Writes an HTTP date string to the `target` buffer.
141
152
 
142
- This requires _____ bytes of space to be available at the target buffer.
153
+ This requires ~32 bytes of space to be available at the target buffer (unless
154
+ it's a super funky year, 32 bytes is about 3 more than you need).
143
155
 
144
156
  Returns the number of bytes actually written.
145
157
  */
146
158
  size_t http_date2str(char *target, struct tm *tmbuf);
159
+ /** An alternative, RFC 2109 date representation. Requires */
160
+ size_t http_date2rfc2109(char *target, struct tm *tmbuf);
161
+ /** An alternative, RFC 2822 date representation. */
162
+ size_t http_date2rfc2822(char *target, struct tm *tmbuf);
147
163
 
148
164
  /**
149
165
  A fast, inline alternative to `sprintf(dest, "%lu", num)`.
@@ -155,7 +171,7 @@ A NULL terminating byte is written.
155
171
 
156
172
  Returns the number of bytes actually written (excluding the NULL byte).
157
173
  */
158
- inline size_t http_ul2a(char *dest, size_t num) {
174
+ static inline size_t http_ul2a(char *dest, size_t num) {
159
175
  uint8_t digits = 1;
160
176
  size_t tmp = num;
161
177
  while ((tmp /= 10))
@@ -174,7 +190,14 @@ inline size_t http_ul2a(char *dest, size_t num) {
174
190
  /** Decodes a URL encoded string, no buffer overflow protection. */
175
191
  ssize_t http_decode_url_unsafe(char *dest, const char *url_data);
176
192
 
177
- /** Decodes a URL encoded string. */
193
+ /** Decodes a URL encoded string (i.e., the "query" part of a request). */
178
194
  ssize_t http_decode_url(char *dest, const char *url_data, size_t length);
179
195
 
196
+ /** Decodes the "path" part of a request, no buffer overflow protection. */
197
+ ssize_t http_decode_path_unsafe(char *dest, const char *url_data);
198
+
199
+ /** Decodes the "path" part of an HTTP request, no buffer overflow protection.
200
+ */
201
+ ssize_t http_decode_path(char *dest, const char *url_data, size_t length);
202
+
180
203
  #endif