| /* |
| Copyright: Boaz segev, 2016-2017 |
| License: MIT |
| |
| Feel free to copy, use and enjoy according to the license provided. |
| */ |
| |
| #include "spnlock.inc" |
| |
| #include "http1.h" |
| #include "http_internal.h" |
| |
| #include <signal.h> |
| |
| /* ***************************************************************************** |
| The Request / Response type and functions |
| ***************************************************************************** */ |
| |
| /** |
| * Sets a response header, taking ownership of the value object, but NOT the |
| * name object (so name objects could be reused in future responses). |
| * |
| * Returns -1 on error and 0 on success. |
| */ |
| int http_set_header(http_s *r, fiobj_s *name, fiobj_s *value) { |
| if (!r || !name || !r->private.response_headers) |
| return -1; |
| fiobj_s *old = fiobj_hash_replace(r->private.response_headers, name, value); |
| if (!old) |
| return 0; |
| if (!value) { |
| fiobj_free(old); |
| return 0; |
| } |
| if (old->type != FIOBJ_T_ARRAY) { |
| fiobj_s *tmp = fiobj_ary_new(); |
| fiobj_ary_push(tmp, old); |
| old = tmp; |
| } |
| fiobj_ary_push(old, value); |
| fiobj_hash_replace(r->private.response_headers, name, old); |
| return 0; |
| } |
| /** |
| * Sets a response header, taking ownership of the value object, but NOT the |
| * name object (so name objects could be reused in future responses). |
| * |
| * Returns -1 on error and 0 on success. |
| */ |
| int http_set_header2(http_s *r, fio_cstr_s n, fio_cstr_s v) { |
| if (!r || !n.data || !n.length || (v.data && !v.length) || |
| !r->private.response_headers) |
| return -1; |
| fiobj_s *tmp = fiobj_sym_new(n.data, n.length); |
| int ret = http_set_header(r, tmp, fiobj_str_new(v.data, v.length)); |
| fiobj_free(tmp); |
| return ret; |
| } |
| /** |
| * Sets a response cookie, taking ownership of the value object, but NOT the |
| * name object (so name objects could be reused in future responses). |
| * |
| * Returns -1 on error and 0 on success. |
| */ |
| #undef http_set_cookie |
| int http_set_cookie(http_s *r, http_cookie_args_s); |
| #define http_set_cookie(http__req__, ...) \ |
| http_set_cookie((http__req__), (http_cookie_args_s){__VA_ARGS__}) |
| /** |
| * Sends the response headers and body. |
| * |
| * Returns -1 on error and 0 on success. |
| * |
| * AFTER THIS FUNCTION IS CALLED, THE `http_s` OBJECT IS NO LONGER VALID. |
| */ |
| int http_send_body(http_s *r, void *data, uintptr_t length); |
| /** |
| * Sends the response headers and the specified file (the response's body). |
| * |
| * Returns -1 on error and 0 on success. |
| * |
| * AFTER THIS FUNCTION IS CALLED, THE `http_s` OBJECT IS NO LONGER VALID. |
| */ |
| int http_sendfile(http_s *r, int fd, uintptr_t length, uintptr_t offset); |
| /** |
| * Sends the response headers and the specified file (the response's body). |
| * |
| * Returns -1 on error and 0 on success. |
| * |
| * AFTER THIS FUNCTION IS CALLED, THE `http_s` OBJECT IS NO LONGER VALID. |
| */ |
| int http_sendfile2(http_s *r, char *filename, size_t name_length); |
| |
| /** |
| * Sends an HTTP error response. |
| * |
| * Returns -1 on error and 0 on success. |
| * |
| * AFTER THIS FUNCTION IS CALLED, THE `http_s` OBJECT IS NO LONGER VALID. |
| * |
| * The `uuid` argument is optional and will be used only if the `http_s` |
| * argument is set to NULL. |
| */ |
| int http_send_error(http_s *r, intptr_t uuid, size_t error); |
| |
| /** |
| * Sends the response headers and starts streaming, creating a new and valid |
| * `http_s` object that allows further streaming. |
| * |
| * Returns NULL on error and a new valid `http_s` object on success. |
| * |
| * THE OLD `http_s` OBJECT BECOMES INVALID. |
| */ |
| http_s *http_stream(http_s *r, void *data, uintptr_t length); |
| /** |
| * Sends the response headers for a header only response. |
| * |
| * AFTER THIS FUNCTION IS CALLED, THE `http_s` OBJECT IS NO LONGER VALID. |
| */ |
| void http_finish(http_s *r); |
| /** |
| * Pushes a data response when supported (HTTP/2 only). |
| * |
| * Returns -1 on error and 0 on success. |
| */ |
| int http_push_data(http_s *r, void *data, uintptr_t length, char *mime_type, |
| uintptr_t type_length); |
| /** |
| * Pushes a file response when supported (HTTP/2 only). |
| * |
| * If `mime_type` is NULL, an attempt at automatic detection using `filename` |
| * will be made. |
| * |
| * Returns -1 on error and 0 on success. |
| */ |
| int http_push_file(http_s *r, char *filename, size_t name_length, |
| char *mime_type, uintptr_t type_length); |
| |
| /** |
| * Defers the request / response handling for later. |
| * |
| * Returns -1 on error and 0 on success. |
| */ |
| int http_defer(http_s *r, void (*task)(http_s *r)) { |
| if (r->path->type == FIOBJ_T_STRING_STATIC) |
| return -1; |
| return 0; |
| } |
| |
| /* ***************************************************************************** |
| Listening to HTTP connections |
| ***************************************************************************** */ |
| |
| static protocol_s *http_on_open(intptr_t uuid, void *set) { |
| static __thread ssize_t capa = 0; |
| if (!capa) |
| capa = sock_max_capacity(); |
| if (sock_uuid2fd(uuid) + HTTP_BUSY_UNLESS_HAS_FDS >= capa) { |
| /* TODO: use http_send_error2(uuid) */ |
| fprintf(stderr, "WARNING: HTTP server at capacity\n"); |
| sock_close(uuid); |
| return NULL; |
| } |
| return http1_new(uuid, set, NULL, 0); |
| } |
| |
| static void http_on_finish(intptr_t uuid, void *set) { |
| http_settings_s *settings = set; |
| |
| if (settings->on_finish) |
| settings->on_finish(settings); |
| |
| free((void *)settings->public_folder); |
| free(settings); |
| (void)uuid; |
| } |
| |
| /** |
| * Listens to HTTP connections at the specified `port`. |
| * |
| * Leave as NULL to ignore IP binding. |
| * |
| * Returns -1 on error and 0 on success. |
| */ |
| #undef http_listen |
| int http_listen(char *port, char *binding, |
| struct http_settings_s arg_settings) { |
| if (arg_settings.on_request == NULL) { |
| fprintf( |
| stderr, |
| "ERROR: http_listen requires the .on_request parameter to be set\n"); |
| kill(0, SIGINT), exit(11); |
| } |
| |
| http_settings_s *settings = malloc(sizeof(*settings)); |
| *settings = arg_settings; |
| |
| if (!settings->max_body_size) |
| settings->max_body_size = HTTP_DEFAULT_BODY_LIMIT; |
| if (!settings->timeout) |
| settings->timeout = 5; |
| |
| if (settings->public_folder) { |
| settings->public_folder_length = strlen(settings->public_folder); |
| if (settings->public_folder[0] == '~' && |
| settings->public_folder[1] == '/' && getenv("HOME")) { |
| char *home = getenv("HOME"); |
| size_t home_len = strlen(home); |
| char *tmp = malloc(settings->public_folder_length + home_len + 1); |
| memcpy(tmp, home, home_len); |
| if (home[home_len - 1] == '/') |
| --home_len; |
| memcpy(tmp + home_len, settings->public_folder + 1, |
| settings->public_folder_length); // copy also the NULL |
| settings->public_folder = tmp; |
| settings->public_folder_length = strlen(settings->public_folder); |
| } else { |
| settings->public_folder = malloc(settings->public_folder_length + 1); |
| memcpy((void *)settings->public_folder, arg_settings.public_folder, |
| settings->public_folder_length); |
| ((uint8_t *)settings->public_folder)[settings->public_folder_length] = 0; |
| } |
| } |
| |
| return facil_listen(.port = port, .address = binding, |
| .set_rw_hooks = arg_settings.set_rw_hooks, |
| .rw_udata = arg_settings.rw_udata, |
| .on_finish = http_on_finish, .on_open = http_on_open, |
| .udata = settings); |
| } |
| /** Listens to HTTP connections at the specified `port` and `binding`. */ |
| #define http_listen(port, binding, ...) \ |
| http_listen((port), (binding), (struct http_settings_s)(__VA_ARGS__)) |
| |
| /** |
| * Returns the settings used to setup the connection. |
| * |
| * Returns NULL on error (i.e., connection was lost). |
| */ |
| struct http_settings_s *http_settings(http_s *r) { |
| protocol_s *pr = NULL; |
| while (1) { |
| pr = facil_protocol_try_lock(r->private.uuid, FIO_PR_LOCK_STATE); |
| if (pr) |
| break; |
| if (!sock_isvalid(r->private.uuid)) |
| return NULL; |
| reschedule_thread(); |
| }; |
| struct http_settings_s *ret = ((http_protocol_s *)pr)->settings; |
| facil_protocol_unlock(pr, FIO_PR_LOCK_STATE); |
| return ret; |
| } |
| |
| /* ***************************************************************************** |
| TODO: HTTP client mode |
| ***************************************************************************** */ |
| |
| /* ***************************************************************************** |
| HTTP Helper functions that could be used globally |
| ***************************************************************************** */ |
| |
| /** |
| A faster (yet less localized) alternative to `gmtime_r`. |
| |
| See the libc `gmtime_r` documentation for details. |
| |
| Falls back to `gmtime_r` for dates before epoch. |
| */ |
| struct tm *http_gmtime(const time_t *timer, struct tm *tmbuf) { |
| // static char* DAYS[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; |
| // static char * Months = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", |
| // "Jul", |
| // "Aug", "Sep", "Oct", "Nov", "Dec"}; |
| static const uint8_t month_len[] = { |
| 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, // nonleap year |
| 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 // leap year |
| }; |
| if (*timer < 0) |
| return gmtime_r(timer, tmbuf); |
| ssize_t a, b; |
| tmbuf->tm_gmtoff = 0; |
| tmbuf->tm_zone = "UTC"; |
| tmbuf->tm_isdst = 0; |
| tmbuf->tm_year = 70; // tm_year == The number of years since 1900 |
| tmbuf->tm_mon = 0; |
| // for seconds up to weekdays, we build up, as small values clean up larger |
| // values. |
| a = ((ssize_t)*timer); |
| b = a / 60; |
| tmbuf->tm_sec = a - (b * 60); |
| a = b / 60; |
| tmbuf->tm_min = b - (a * 60); |
| b = a / 24; |
| tmbuf->tm_hour = a - (b * 24); |
| // day of epoch was a thursday. Add + 4 so sunday == 0... |
| tmbuf->tm_wday = (b + 4) % 7; |
| // tmp == number of days since epoch |
| #define DAYS_PER_400_YEARS ((400 * 365) + 97) |
| while (b >= DAYS_PER_400_YEARS) { |
| tmbuf->tm_year += 400; |
| b -= DAYS_PER_400_YEARS; |
| } |
| #undef DAYS_PER_400_YEARS |
| #define DAYS_PER_100_YEARS ((100 * 365) + 24) |
| while (b >= DAYS_PER_100_YEARS) { |
| tmbuf->tm_year += 100; |
| b -= DAYS_PER_100_YEARS; |
| if (((tmbuf->tm_year / 100) & 3) == |
| 0) // leap century divisable by 400 => add leap |
| --b; |
| } |
| #undef DAYS_PER_100_YEARS |
| #define DAYS_PER_32_YEARS ((32 * 365) + 8) |
| while (b >= DAYS_PER_32_YEARS) { |
| tmbuf->tm_year += 32; |
| b -= DAYS_PER_32_YEARS; |
| } |
| #undef DAYS_PER_32_YEARS |
| #define DAYS_PER_8_YEARS ((8 * 365) + 2) |
| while (b >= DAYS_PER_8_YEARS) { |
| tmbuf->tm_year += 8; |
| b -= DAYS_PER_8_YEARS; |
| } |
| #undef DAYS_PER_8_YEARS |
| #define DAYS_PER_4_YEARS ((4 * 365) + 1) |
| while (b >= DAYS_PER_4_YEARS) { |
| tmbuf->tm_year += 4; |
| b -= DAYS_PER_4_YEARS; |
| } |
| #undef DAYS_PER_4_YEARS |
| while (b >= 365) { |
| tmbuf->tm_year += 1; |
| b -= 365; |
| if ((tmbuf->tm_year & 3) == 0) { // leap year |
| if (b > 0) { |
| --b; |
| continue; |
| } else { |
| b += 365; |
| --tmbuf->tm_year; |
| break; |
| } |
| } |
| } |
| b++; /* day 1 of the year is 1, not 0. */ |
| tmbuf->tm_yday = b; |
| if ((tmbuf->tm_year & 3) == 1) { |
| // regular year |
| for (size_t i = 0; i < 12; i++) { |
| if (b <= month_len[i]) |
| break; |
| b -= month_len[i]; |
| ++tmbuf->tm_mon; |
| } |
| } else { |
| // leap year |
| for (size_t i = 12; i < 24; i++) { |
| if (b <= month_len[i]) |
| break; |
| b -= month_len[i]; |
| ++tmbuf->tm_mon; |
| } |
| } |
| tmbuf->tm_mday = b; |
| return tmbuf; |
| } |
| |
| static const char *DAY_NAMES[] = {"Sun", "Mon", "Tue", "Wed", |
| "Thu", "Fri", "Sat"}; |
| static const char *MONTH_NAMES[] = {"Jan ", "Feb ", "Mar ", "Apr ", |
| "May ", "Jun ", "Jul ", "Aug ", |
| "Sep ", "Oct ", "Nov ", "Dec "}; |
| static const char *GMT_STR = "GMT"; |
| |
| size_t http_date2str(char *target, struct tm *tmbuf) { |
| char *pos = target; |
| uint16_t tmp; |
| *(uint32_t *)pos = *((uint32_t *)DAY_NAMES[tmbuf->tm_wday]); |
| pos[3] = ','; |
| pos[4] = ' '; |
| pos += 5; |
| if (tmbuf->tm_mday < 10) { |
| *pos = '0' + tmbuf->tm_mday; |
| ++pos; |
| } else { |
| tmp = tmbuf->tm_mday / 10; |
| pos[0] = '0' + tmp; |
| pos[1] = '0' + (tmbuf->tm_mday - (tmp * 10)); |
| pos += 2; |
| } |
| *(pos++) = ' '; |
| *(uint32_t *)pos = *((uint32_t *)MONTH_NAMES[tmbuf->tm_mon]); |
| pos += 4; |
| // write year. |
| pos += fio_ltoa(pos, tmbuf->tm_year + 1900, 10); |
| *(pos++) = ' '; |
| tmp = tmbuf->tm_hour / 10; |
| pos[0] = '0' + tmp; |
| pos[1] = '0' + (tmbuf->tm_hour - (tmp * 10)); |
| pos[2] = ':'; |
| tmp = tmbuf->tm_min / 10; |
| pos[3] = '0' + tmp; |
| pos[4] = '0' + (tmbuf->tm_min - (tmp * 10)); |
| pos[5] = ':'; |
| tmp = tmbuf->tm_sec / 10; |
| pos[6] = '0' + tmp; |
| pos[7] = '0' + (tmbuf->tm_sec - (tmp * 10)); |
| pos += 8; |
| pos[0] = ' '; |
| *((uint32_t *)(pos + 1)) = *((uint32_t *)GMT_STR); |
| pos += 4; |
| return pos - target; |
| } |
| |
| size_t http_date2rfc2822(char *target, struct tm *tmbuf) { |
| char *pos = target; |
| uint16_t tmp; |
| *(uint32_t *)pos = *((uint32_t *)DAY_NAMES[tmbuf->tm_wday]); |
| pos[3] = ','; |
| pos[4] = ' '; |
| pos += 5; |
| if (tmbuf->tm_mday < 10) { |
| *pos = '0' + tmbuf->tm_mday; |
| ++pos; |
| } else { |
| tmp = tmbuf->tm_mday / 10; |
| pos[0] = '0' + tmp; |
| pos[1] = '0' + (tmbuf->tm_mday - (tmp * 10)); |
| pos += 2; |
| } |
| *(pos++) = '-'; |
| *(uint32_t *)pos = *((uint32_t *)MONTH_NAMES[tmbuf->tm_mon]); |
| pos += 3; |
| *(pos++) = '-'; |
| // write year. |
| pos += fio_ltoa(pos, tmbuf->tm_year + 1900, 10); |
| *(pos++) = ' '; |
| tmp = tmbuf->tm_hour / 10; |
| pos[0] = '0' + tmp; |
| pos[1] = '0' + (tmbuf->tm_hour - (tmp * 10)); |
| pos[2] = ':'; |
| tmp = tmbuf->tm_min / 10; |
| pos[3] = '0' + tmp; |
| pos[4] = '0' + (tmbuf->tm_min - (tmp * 10)); |
| pos[5] = ':'; |
| tmp = tmbuf->tm_sec / 10; |
| pos[6] = '0' + tmp; |
| pos[7] = '0' + (tmbuf->tm_sec - (tmp * 10)); |
| pos += 8; |
| pos[0] = ' '; |
| *((uint32_t *)(pos + 1)) = *((uint32_t *)GMT_STR); |
| pos += 4; |
| return pos - target; |
| } |
| |
| size_t http_date2rfc2109(char *target, struct tm *tmbuf) { |
| char *pos = target; |
| uint16_t tmp; |
| *(uint32_t *)pos = *((uint32_t *)DAY_NAMES[tmbuf->tm_wday]); |
| pos[3] = ','; |
| pos[4] = ' '; |
| pos += 5; |
| if (tmbuf->tm_mday < 10) { |
| *pos = '0' + tmbuf->tm_mday; |
| ++pos; |
| } else { |
| tmp = tmbuf->tm_mday / 10; |
| pos[0] = '0' + tmp; |
| pos[1] = '0' + (tmbuf->tm_mday - (tmp * 10)); |
| pos += 2; |
| } |
| *(pos++) = ' '; |
| *(uint32_t *)pos = *((uint32_t *)MONTH_NAMES[tmbuf->tm_mon]); |
| pos += 4; |
| // write year. |
| pos += fio_ltoa(pos, tmbuf->tm_year + 1900, 10); |
| *(pos++) = ' '; |
| tmp = tmbuf->tm_hour / 10; |
| pos[0] = '0' + tmp; |
| pos[1] = '0' + (tmbuf->tm_hour - (tmp * 10)); |
| pos[2] = ':'; |
| tmp = tmbuf->tm_min / 10; |
| pos[3] = '0' + tmp; |
| pos[4] = '0' + (tmbuf->tm_min - (tmp * 10)); |
| pos[5] = ':'; |
| tmp = tmbuf->tm_sec / 10; |
| pos[6] = '0' + tmp; |
| pos[7] = '0' + (tmbuf->tm_sec - (tmp * 10)); |
| pos += 8; |
| *pos++ = ' '; |
| *pos++ = '-'; |
| *pos++ = '0'; |
| *pos++ = '0'; |
| *pos++ = '0'; |
| *pos++ = '0'; |
| *pos = 0; |
| return pos - target; |
| } |
| |
| /** |
| * Prints Unix time to a HTTP time formatted string. |
| * |
| * This variation implements chached results for faster processeing, at the |
| * price of a less accurate string. |
| */ |
| size_t http_time2str(char *target, const time_t t) { |
| /* pre-print time every 1 or 2 seconds or so. */ |
| static __thread time_t cached_tick; |
| static __thread char cached_httpdate[48]; |
| static __thread size_t chached_len; |
| time_t last_tick = facil_last_tick(); |
| if ((t | 7) < last_tick) { |
| /* this is a custom time, not "now", pass through */ |
| struct tm tm; |
| http_gmtime(&t, &tm); |
| return http_date2str(target, &tm); |
| } |
| if (last_tick > cached_tick) { |
| struct tm tm; |
| cached_tick = last_tick | 1; |
| http_gmtime(&last_tick, &tm); |
| chached_len = http_date2str(cached_httpdate, &tm); |
| } |
| memcpy(target, cached_httpdate, chached_len); |
| return chached_len; |
| } |
| |
| /* Credit to Jonathan Leffler for the idea of a unified conditional */ |
| #define hex_val(c) \ |
| (((c) >= '0' && (c) <= '9') \ |
| ? ((c)-48) \ |
| : (((c) >= 'a' && (c) <= 'f') || ((c) >= 'A' && (c) <= 'F')) \ |
| ? (((c) | 32) - 87) \ |
| : ({ \ |
| return -1; \ |
| 0; \ |
| })) |
| static inline int hex2byte(uint8_t *dest, const uint8_t *source) { |
| if (source[0] >= '0' && source[0] <= '9') |
| *dest = (source[0] - '0'); |
| else if ((source[0] >= 'a' && source[0] <= 'f') || |
| (source[0] >= 'A' && source[0] <= 'F')) |
| *dest = (source[0] | 32) - 87; |
| else |
| return -1; |
| *dest <<= 4; |
| if (source[1] >= '0' && source[1] <= '9') |
| *dest |= (source[1] - '0'); |
| else if ((source[1] >= 'a' && source[1] <= 'f') || |
| (source[1] >= 'A' && source[1] <= 'F')) |
| *dest |= (source[1] | 32) - 87; |
| else |
| return -1; |
| return 0; |
| } |
| |
| ssize_t http_decode_url(char *dest, const char *url_data, size_t length) { |
| char *pos = dest; |
| const char *end = url_data + length; |
| while (url_data < end) { |
| if (*url_data == '+') { |
| // decode space |
| *(pos++) = ' '; |
| ++url_data; |
| } else if (*url_data == '%') { |
| // decode hex value |
| // this is a percent encoded value. |
| if (hex2byte((uint8_t *)pos, (uint8_t *)&url_data[1])) |
| return -1; |
| pos++; |
| url_data += 3; |
| } else |
| *(pos++) = *(url_data++); |
| } |
| *pos = 0; |
| return pos - dest; |
| } |
| |
| ssize_t http_decode_url_unsafe(char *dest, const char *url_data) { |
| char *pos = dest; |
| while (*url_data) { |
| if (*url_data == '+') { |
| // decode space |
| *(pos++) = ' '; |
| ++url_data; |
| } else if (*url_data == '%') { |
| // decode hex value |
| // this is a percent encoded value. |
| if (hex2byte((uint8_t *)pos, (uint8_t *)&url_data[1])) |
| return -1; |
| pos++; |
| url_data += 3; |
| } else |
| *(pos++) = *(url_data++); |
| } |
| *pos = 0; |
| return pos - dest; |
| } |
| |
| ssize_t http_decode_path(char *dest, const char *url_data, size_t length) { |
| char *pos = dest; |
| const char *end = url_data + length; |
| while (url_data < end) { |
| if (*url_data == '%') { |
| // decode hex value |
| // this is a percent encoded value. |
| if (hex2byte((uint8_t *)pos, (uint8_t *)&url_data[1])) |
| return -1; |
| pos++; |
| url_data += 3; |
| } else |
| *(pos++) = *(url_data++); |
| } |
| *pos = 0; |
| return pos - dest; |
| } |
| |
| ssize_t http_decode_path_unsafe(char *dest, const char *url_data) { |
| char *pos = dest; |
| while (*url_data) { |
| if (*url_data == '%') { |
| // decode hex value |
| // this is a percent encoded value. |
| if (hex2byte((uint8_t *)pos, (uint8_t *)&url_data[1])) |
| return -1; |
| pos++; |
| url_data += 3; |
| } else |
| *(pos++) = *(url_data++); |
| } |
| *pos = 0; |
| return pos - dest; |
| } |