diff --git a/meson.build b/meson.build index 16a42150..0bf3a4ac 100644 --- a/meson.build +++ b/meson.build @@ -130,6 +130,7 @@ executable( 'spawn.c', 'spawn.h', 'terminal.c', 'terminal.h', 'tokenize.c', 'tokenize.h', + 'uri.c', 'uri.h', 'user-notification.h', 'vt.c', 'vt.h', 'wayland.c', 'wayland.h', diff --git a/uri.c b/uri.c new file mode 100644 index 00000000..1d46faf1 --- /dev/null +++ b/uri.c @@ -0,0 +1,274 @@ +#include "uri.h" + +#include +#include +#include +#include +#include +#include + +#define LOG_MODULE "uri" +#define LOG_ENABLE_DBG 0 +#include "log.h" +#include "xmalloc.h" + +static uint8_t +nibble2hex(char c) +{ + switch (c) { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + return c - '0'; + + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': + return c - 'a' + 10; + + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': + return c - 'A' + 10; + } + + assert(false); + return 0; +} + +bool +uri_parse(const char *uri, size_t len, + char **scheme, char **user, char **password, char **host, + uint16_t *port, char **path, char **query, char **fragment) +{ + LOG_DBG("parse URI: \"%.*s\"", (int)len, uri); + + if (scheme != NULL) *scheme = NULL; + if (user != NULL) *user = NULL; + if (password != NULL) *password = NULL; + if (host != NULL) *host = NULL; + if (port != NULL) *port = 0; + if (path != NULL) *path = NULL; + if (query != NULL) *query = NULL; + if (fragment != NULL) *fragment = NULL; + + size_t left = len; + const char *start = uri; + const char *end = NULL; + + if ((end = memchr(start, ':', left)) == NULL) + goto err; + + size_t scheme_len = end - start; + if (scheme_len == 0) + goto err; + + if (scheme != NULL) + *scheme = xstrndup(start, scheme_len); + + LOG_DBG("scheme: \"%.*s\"", (int)scheme_len, start); + + start = end + 1; + left = len - (start - uri); + + /* Authinfo */ + if (left >= 2 && start[0] == '/' && start[1] == '/') { + start += 2; + left -= 2; + + /* [user[:pasword]@]@host[:port] */ + + /* Find beginning of path segment (required component + * following the authinfo) */ + const char *path_segment = memchr(start, '/', left); + if (path_segment == NULL) + goto err; + + size_t auth_left = path_segment - start; + + /* Do we have a user (and optionally a password)? */ + const char *user_pw_end = memchr(start, '@', auth_left); + if (user_pw_end != NULL) { + size_t user_pw_len = user_pw_end - start; + + /* Do we have a password? */ + const char *user_end = memchr(start, ':', user_pw_end - start); + if (user_end != NULL) { + size_t user_len = user_end - start; + if (user_len == 0) + goto err; + + if (user != NULL) + *user = xstrndup(start, user_len); + + const char *pw = user_end + 1; + size_t pw_len = user_pw_end - pw; + if (pw_len == 0) + goto err; + + if (password != NULL) + *password = xstrndup(pw, pw_len); + + LOG_DBG("user: \"%.*s\"", (int)user_len, start); + LOG_DBG("password: \"%.*s\"", (int)pw_len, pw); + } else { + size_t user_len = user_pw_end - start; + if (user_len == 0) + goto err; + + if (user != NULL) + *user = xstrndup(start, user_len); + + LOG_DBG("user: \"%.*s\"", (int)user_len, start); + } + + start = user_pw_end + 1; + left = len - (start - uri); + auth_left -= user_pw_len + 1; + } + + const char *host_end = memchr(start, ':', auth_left); + if (host_end != NULL) { + size_t host_len = host_end - start; + if (host != NULL) + *host = xstrndup(start, host_len); + + const char *port_str = host_end + 1; + size_t port_len = path_segment - port_str; + if (port_len == 0) + goto err; + + uint16_t _port = 0; + for (size_t i = 0; i < port_len; i++) { + if (!(port_str[i] >= '0' && port_str[i] <= '9')) + goto err; + + _port *= 10; + _port += port_str[i] - '0'; + } + + if (port != NULL) + *port = _port; + + LOG_DBG("host: \"%.*s\"", (int)host_len, start); + LOG_DBG("port: \"%.*s\" (%hu)", (int)port_len, port_str, _port); + } else { + size_t host_len = path_segment - start; + if (host != NULL) + *host = xstrndup(start, host_len); + + LOG_DBG("host: \"%.*s\"", (int)host_len, start); + } + + start = path_segment; + left = len - (start - uri); + } + + /* Do we have a query? */ + const char *query_start = memchr(start, '?', left); + const char *fragment_start = memchr(start, '#', left); + + size_t path_len = + query_start != NULL ? query_start - start : + fragment_start != NULL ? fragment_start - start : + left; + + if (path_len == 0) + goto err; + + /* Path - decode %xx encoded characters */ + if (path != NULL) { + const char *encoded = start; + char *decoded = xmalloc(path_len + 1); + char *p = decoded; + + size_t encoded_len = path_len; + size_t decoded_len = 0; + + while (true) { + /* Find next '%' */ + const char *next = memchr(encoded, '%', encoded_len); + + if (next == NULL) { + strncpy(p, encoded, encoded_len); + decoded_len += encoded_len; + p += encoded_len; + break; + } + + /* Copy everything leading up to the '%' */ + size_t prefix_len = next - encoded; + memcpy(p, encoded, prefix_len); + + p += prefix_len; + encoded_len -= prefix_len; + decoded_len += prefix_len; + + if (isxdigit(next[1]) && isxdigit(next[2])) { + *p++ = nibble2hex(next[1]) << 4 | nibble2hex(next[2]); + decoded_len++; + encoded_len -= 3; + encoded = next + 3; + } else { + *p++ = *next; + decoded_len++; + encoded_len -= 1; + encoded = next + 1; + } + } + + *p = '\0'; + *path = decoded; + + LOG_DBG("path: encoded=\"%.*s\", decoded=\"%s\"", (int)path_len, start, decoded); + } else + LOG_DBG("path: encoded=\"%.*s\", decoded=", (int)path_len, start); + + start = query_start != NULL ? query_start + 1 : fragment_start != NULL ? fragment_start + 1 : uri + len; + left = len - (start - uri); + + if (query_start != NULL) { + size_t query_len = fragment_start != NULL + ? fragment_start - start : left; + + if (query_len == 0) + goto err; + + if (query != NULL) + *query = xstrndup(start, query_len); + + LOG_DBG("query: \"%.*s\"", (int)query_len, start); + + start = fragment_start != NULL ? fragment_start + 1 : uri + len; + left = len - (start - uri); + } + + if (fragment_start != NULL) { + if (left == 0) + goto err; + + if (fragment != NULL) + *fragment = xstrndup(start, left); + + LOG_DBG("fragment: \"%.*s\"", (int)left, start); + } + + return true; + +err: + if (scheme != NULL) free(*scheme); + if (user != NULL) free(*user); + if (password != NULL) free(*password); + if (host != NULL) free(*host); + if (path != NULL) free(*path); + if (query != NULL) free(*query); + if (fragment != NULL) free(*fragment); + return false; +} + +bool +hostname_is_localhost(const char *hostname) +{ + char this_host[HOST_NAME_MAX]; + if (gethostname(this_host, sizeof(this_host)) < 0) + this_host[0] = '\0'; + + return (strcmp(hostname, "") == 0 || + strcmp(hostname, "localhost") == 0 || + strcmp(hostname, this_host) == 0); +} diff --git a/uri.h b/uri.h new file mode 100644 index 00000000..b63290c5 --- /dev/null +++ b/uri.h @@ -0,0 +1,11 @@ +#pragma once + +#include +#include +#include + +bool uri_parse(const char *uri, size_t len, + char **scheme, char **user, char **password, char **host, + uint16_t *port, char **path, char **query, char **fragment); + +bool hostname_is_localhost(const char *hostname);