summaryrefslogtreecommitdiffstats
path: root/src/lib/uri-util.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/uri-util.c')
-rw-r--r--src/lib/uri-util.c1332
1 files changed, 1332 insertions, 0 deletions
diff --git a/src/lib/uri-util.c b/src/lib/uri-util.c
new file mode 100644
index 0000000..498bc88
--- /dev/null
+++ b/src/lib/uri-util.c
@@ -0,0 +1,1332 @@
+/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+#include "net.h"
+#include "uri-util.h"
+
+#include <ctype.h>
+
+/* [URI-GEN] RFC3986 Appendix A:
+
+ URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
+ absolute-URI = scheme ":" hier-part [ "?" query ]
+ scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
+
+ URI-reference = URI / relative-ref
+ relative-ref = relative-part [ "?" query ] [ "#" fragment ]
+
+ relative-part = "//" authority path-abempty
+ / path-absolute
+ / path-noscheme
+ / path-empty
+ hier-part = "//" authority path-abempty
+ / path-absolute
+ / path-rootless
+ / path-empty
+
+ authority = [ userinfo "@" ] host [ ":" port ]
+ userinfo = *( unreserved / pct-encoded / sub-delims / ":" )
+ host = IP-literal / IPv4address / reg-name
+ port = *DIGIT
+
+ IP-literal = "[" ( IPv6address / IPvFuture ) "]"
+ IPvFuture = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" )
+ IPv6address = 6( h16 ":" ) ls32
+ / "::" 5( h16 ":" ) ls32
+ / [ h16 ] "::" 4( h16 ":" ) ls32
+ / [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32
+ / [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32
+ / [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32
+ / [ *4( h16 ":" ) h16 ] "::" ls32
+ / [ *5( h16 ":" ) h16 ] "::" h16
+ / [ *6( h16 ":" ) h16 ] "::"
+ h16 = 1*4HEXDIG
+ ls32 = ( h16 ":" h16 ) / IPv4address
+ IPv4address = dec-octet "." dec-octet "." dec-octet "." dec-octet
+ dec-octet = DIGIT ; 0-9
+ / %x31-39 DIGIT ; 10-99
+ / "1" 2DIGIT ; 100-199
+ / "2" %x30-34 DIGIT ; 200-249
+ / "25" %x30-35 ; 250-255
+ reg-name = *( unreserved / pct-encoded / sub-delims )
+
+ path = path-abempty ; begins with "/" or is empty
+ / path-absolute ; begins with "/" but not "//"
+ / path-noscheme ; begins with a non-colon segment
+ / path-rootless ; begins with a segment
+ / path-empty ; zero characters
+ path-abempty = *( "/" segment )
+ path-absolute = "/" [ segment-nz *( "/" segment ) ]
+ path-noscheme = segment-nz-nc *( "/" segment )
+ path-rootless = segment-nz *( "/" segment )
+ path-empty = 0<pchar>
+
+ segment = *pchar
+ segment-nz = 1*pchar
+ segment-nz-nc = 1*( unreserved / pct-encoded / sub-delims / "@" )
+ ; non-zero-length segment without any colon ":"
+ pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
+
+ query = *( pchar / "/" / "?" )
+ fragment = *( pchar / "/" / "?" )
+
+ pct-encoded = "%" HEXDIG HEXDIG
+ unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
+ reserved = gen-delims / sub-delims
+ gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"
+ sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
+ / "*" / "+" / "," / ";" / "="
+ */
+
+#define URI_MAX_SCHEME_NAME_LEN 64
+
+/* Character lookup table
+ *
+ * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" [bit0]
+ * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
+ * / "*" / "+" / "," / ";" / "=" [bit1]
+ * gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@" [bit2]
+ * pchar = unreserved / sub-delims / ":" / "@" [bit0|bit1|bit3]
+ * 'pfchar' = unreserved / sub-delims / ":" / "@" / "/"
+ * [bit0|bit1|bit3|bit5]
+ * 'uchar' = unreserved / sub-delims / ":" [bit0|bit1|bit4]
+ * 'qchar' = pchar / "/" / "?" [bit0|bit1|bit3|bit5|bit6]
+ *
+ */
+
+#define CHAR_MASK_UNRESERVED (1<<0)
+#define CHAR_MASK_SUB_DELIMS (1<<1)
+#define CHAR_MASK_PCHAR ((1<<0)|(1<<1)|(1<<3))
+#define CHAR_MASK_PFCHAR ((1<<0)|(1<<1)|(1<<3)|(1<<5))
+#define CHAR_MASK_UCHAR ((1<<0)|(1<<1)|(1<<4))
+#define CHAR_MASK_QCHAR ((1<<0)|(1<<1)|(1<<3)|(1<<5)|(1<<6))
+#define CHAR_MASK_UNRESERVED_PATH ((1<<0)|(1<<5))
+
+static unsigned const char _uri_char_lookup[256] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 10
+ 0, 2, 0, 4, 2, 0, 2, 2, 2, 2, 2, 2, 2, 1, 1, 36, // 20
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 28, 2, 0, 2, 0, 68, // 30
+ 12, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 40
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 0, 4, 0, 1, // 50
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 60
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, // 70
+};
+
+static inline int _decode_hex_digit(const unsigned char digit)
+{
+ switch (digit) {
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ return digit - '0';
+
+ case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
+ return digit - 'a' + 0x0a;
+
+ case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
+ return digit - 'A' + 0x0A;
+ }
+ return -1;
+}
+
+static int
+uri_parse_pct_encoded_data(struct uri_parser *parser,
+ const unsigned char **p, const unsigned char *pend,
+ unsigned char *ch_r) ATTR_NULL(3)
+{
+ int value;
+
+ if (**p != '%' || (pend != NULL && *p >= pend))
+ return 0;
+ *p += 1;
+
+ if (**p == 0 || *(*p+1) == 0 || (pend != NULL && *p+1 >= pend)) {
+ parser->error = "Unexpected URI boundary after '%'";
+ return -1;
+ }
+
+ if ((value = _decode_hex_digit(**p)) < 0) {
+ parser->error = p_strdup_printf(parser->pool,
+ "Expecting hex digit after '%%', but found '%c'", **p);
+ return -1;
+ }
+
+ *ch_r = (value & 0x0f) << 4;
+ *p += 1;
+
+ if ((value = _decode_hex_digit(**p)) < 0) {
+ parser->error = p_strdup_printf(parser->pool,
+ "Expecting hex digit after '%%%c', but found '%c'", *((*p)-1), **p);
+ return -1;
+ }
+
+ *ch_r |= (value & 0x0f);
+ *p += 1;
+
+ if (!parser->allow_pct_nul && *ch_r == '\0') {
+ parser->error =
+ "Percent encoding is not allowed to encode NUL character";
+ return -1;
+ }
+ return 1;
+}
+
+int uri_parse_pct_encoded(struct uri_parser *parser,
+ unsigned char *ch_r)
+{
+ return uri_parse_pct_encoded_data
+ (parser, &parser->cur, parser->end, ch_r);
+}
+
+static int
+uri_parse_unreserved_char(struct uri_parser *parser, unsigned char *ch_r)
+{
+ if ((*parser->cur & 0x80) != 0)
+ return 0;
+
+ if ((_uri_char_lookup[*parser->cur] & CHAR_MASK_UNRESERVED) != 0) {
+ *ch_r = *parser->cur;
+ parser->cur++;
+ return 1;
+ }
+ return 0;
+}
+
+int uri_parse_unreserved(struct uri_parser *parser, string_t *part)
+{
+ int len = 0;
+
+ while (parser->cur < parser->end) {
+ int ret;
+ unsigned char ch = 0;
+
+ if ((ret = uri_parse_unreserved_char(parser, &ch)) < 0)
+ return -1;
+ if (ret == 0)
+ break;
+
+ if (part != NULL)
+ str_append_c(part, ch);
+ len++;
+ }
+
+ return len > 0 ? 1 : 0;
+}
+
+int uri_parse_unreserved_pct(struct uri_parser *parser, string_t *part)
+{
+ int len = 0;
+
+ while (parser->cur < parser->end) {
+ int ret;
+ unsigned char ch = 0;
+
+ if ((ret=uri_parse_pct_encoded(parser, &ch)) < 0)
+ return -1;
+ else if (ret == 0 &&
+ (ret=uri_parse_unreserved_char(parser, &ch)) < 0)
+ return -1;
+ if (ret == 0)
+ break;
+
+ if (part != NULL)
+ str_append_c(part, ch);
+ len++;
+ }
+
+ return len > 0 ? 1 : 0;
+}
+
+bool uri_data_decode(struct uri_parser *parser, const char *data,
+ const char *until, const char **decoded_r)
+{
+ const unsigned char *p = (const unsigned char *)data;
+ const unsigned char *pend = (const unsigned char *)until;
+ string_t *decoded;
+ int ret;
+
+ if (pend == NULL) {
+ /* NULL means unlimited; solely rely on '\0' */
+ pend = (const unsigned char *)SIZE_MAX;
+ }
+
+ if (p >= pend || *p == '\0') {
+ if (decoded_r != NULL)
+ *decoded_r = "";
+ return TRUE;
+ }
+
+ decoded = uri_parser_get_tmpbuf(parser, 256);
+ while (p < pend && *p != '\0') {
+ unsigned char ch;
+
+ if ((ret=uri_parse_pct_encoded_data
+ (parser, &p, NULL, &ch)) != 0) {
+ if (ret < 0)
+ return FALSE;
+ str_append_c(decoded, ch);
+ } else {
+ str_append_c(decoded, *p);
+ p++;
+ }
+ }
+
+ if (decoded_r != NULL)
+ *decoded_r = p_strdup(parser->pool, str_c(decoded));
+ return TRUE;
+}
+
+int uri_parse_scheme(struct uri_parser *parser, const char **scheme_r)
+{
+ const unsigned char *first = parser->cur;
+ size_t len = 1;
+
+ /* RFC 3968:
+ * scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
+ */
+
+ if (parser->cur >= parser->end || !i_isalpha(*parser->cur))
+ return 0;
+ parser->cur++;
+
+ while (len < URI_MAX_SCHEME_NAME_LEN &&
+ parser->cur < parser->end) {
+ if (!i_isalnum(*parser->cur) &&
+ *parser->cur != '+' && *parser->cur != '-' &&
+ *parser->cur != '.')
+ break;
+ parser->cur++;
+ len++;
+ }
+
+ if (parser->cur >= parser->end || *parser->cur != ':') {
+ parser->error = "Invalid URI scheme";
+ return -1;
+ }
+ if (scheme_r != NULL)
+ *scheme_r = t_strndup(first, parser->cur - first);
+ parser->cur++;
+ return 1;
+}
+
+int uri_cut_scheme(const char **uri_p, const char **scheme_r)
+{
+ struct uri_parser parser;
+
+ uri_parser_init(&parser, NULL, *uri_p);
+ if (uri_parse_scheme(&parser, scheme_r) <= 0)
+ return -1;
+ *uri_p = (const char *)parser.cur;
+ return 0;
+}
+
+static int
+uri_parse_dec_octet(struct uri_parser *parser, string_t *literal,
+ uint8_t *octet_r) ATTR_NULL(2)
+{
+ unsigned int octet = 0;
+ int count = 0;
+
+ /* RFC 3986:
+ *
+ * dec-octet = DIGIT ; 0-9
+ * / %x31-39 DIGIT ; 10-99
+ * / "1" 2DIGIT ; 100-199
+ * / "2" %x30-34 DIGIT ; 200-249
+ * / "25" %x30-35 ; 250-255
+ */
+
+ while (parser->cur < parser->end && i_isdigit(*parser->cur)) {
+ octet = octet * 10 + (parser->cur[0] - '0');
+ if (octet > 255)
+ return -1;
+
+ if (literal != NULL)
+ str_append_c(literal, *parser->cur);
+
+ parser->cur++;
+ count++;
+ }
+
+ if (count > 0) {
+ *octet_r = octet;
+ return 1;
+ }
+ return 0;
+}
+
+static int
+uri_parse_ipv4address(struct uri_parser *parser, string_t *literal,
+ struct in_addr *ip4_r) ATTR_NULL(2,3)
+{
+ uint8_t octet;
+ uint32_t ip = 0;
+ int ret;
+ int i;
+
+ /* RFC 3986:
+ *
+ * IPv4address = dec-octet "." dec-octet "." dec-octet "." dec-octet
+ */
+
+ if ((ret = uri_parse_dec_octet(parser, literal, &octet)) <= 0)
+ return ret;
+ ip = octet;
+
+ for (i = 0; i < 3 && parser->cur < parser->end; i++) {
+ if (*parser->cur != '.')
+ return -1;
+
+ if (literal != NULL)
+ str_append_c(literal, '.');
+ parser->cur++;
+
+ if (uri_parse_dec_octet(parser, literal, &octet) <= 0)
+ return -1;
+ ip = (ip << 8) + octet;
+ }
+
+ if (ip4_r != NULL)
+ ip4_r->s_addr = htonl(ip);
+ return 1;
+}
+
+static int
+uri_do_parse_reg_name(struct uri_parser *parser,
+ string_t *reg_name) ATTR_NULL(2)
+{
+ /* RFC 3986:
+ *
+ * reg-name = *( unreserved / pct-encoded / sub-delims )
+ */
+
+ while (parser->cur < parser->end) {
+ int ret;
+ unsigned char c;
+
+ /* unreserved / pct-encoded */
+ if ((ret=uri_parse_pct_encoded(parser, &c)) < 0)
+ return -1;
+ else if (ret == 0 &&
+ (ret=uri_parse_unreserved_char(parser, &c)) < 0)
+ return -1;
+
+ if (ret > 0) {
+ if (reg_name != NULL)
+ str_append_c(reg_name, c);
+ continue;
+ }
+
+ /* sub-delims */
+ c = *parser->cur;
+ if ((c & 0x80) == 0 && (_uri_char_lookup[c] & CHAR_MASK_SUB_DELIMS) != 0) {
+ if (reg_name != NULL)
+ str_append_c(reg_name, *parser->cur);
+ parser->cur++;
+ continue;
+ }
+ break;
+ }
+ return 0;
+}
+
+int uri_parse_reg_name(struct uri_parser *parser,
+ const char **reg_name_r)
+{
+ string_t *reg_name = NULL;
+ int ret;
+
+ if (reg_name_r != NULL)
+ reg_name = uri_parser_get_tmpbuf(parser, 256);
+
+ if ((ret=uri_do_parse_reg_name(parser, reg_name)) <= 0)
+ return ret;
+
+ if (reg_name_r != NULL)
+ *reg_name_r = str_c(reg_name);
+ return 1;
+}
+
+static int uri_do_parse_host_name(struct uri_parser *parser,
+ string_t *host_name) ATTR_NULL(2)
+{
+ const unsigned char *first, *part;
+ int ret;
+
+ /* RFC 3986, Section 3.2.2:
+
+ A registered name intended for lookup in the DNS uses the syntax
+ defined in Section 3.5 of [RFC1034] and Section 2.1 of [RFC1123].
+ Such a name consists of a sequence of domain labels separated by ".",
+ each domain label starting and ending with an alphanumeric character
+ and possibly also containing "-" characters. The rightmost domain
+ label of a fully qualified domain name in DNS may be followed by a
+ single "." and should be if it is necessary to distinguish between
+ the complete domain name and some local domain.
+
+ RFC 2396, Section 3.2.2 (old URI specification):
+
+ hostname = *( domainlabel "." ) toplabel [ "." ]
+ domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum
+ toplabel = alpha | alpha *( alphanum | "-" ) alphanum
+
+ The description in RFC 3986 is more liberal, so:
+
+ hostname = *( domainlabel "." ) domainlabel [ "." ]
+ domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum
+
+ We also support percent encoding in spirit of the generic reg-name,
+ even though this should explicitly not be used according to the RFC.
+ It is, however, not strictly forbidden (unlike older RFC), so we
+ support it.
+ */
+
+ first = part = parser->cur;
+ for (;;) {
+ const unsigned char *offset;
+ unsigned char ch, pch;
+
+ /* alphanum */
+ offset = parser->cur;
+ ch = pch = *parser->cur;
+ if (parser->cur >= parser->end)
+ break;
+ if ((ret=uri_parse_pct_encoded(parser, &ch)) < 0) {
+ return -1;
+ } else if (ret > 0) {
+ if (!i_isalnum(ch))
+ return -1;
+ if (host_name != NULL)
+ str_append_c(host_name, ch);
+ part = parser->cur;
+ } else {
+ if (!i_isalnum(*parser->cur))
+ break;
+ parser->cur++;
+ }
+
+ if (parser->cur < parser->end) {
+ /* *( alphanum | "-" ) alphanum */
+ do {
+ offset = parser->cur;
+
+ if ((ret=uri_parse_pct_encoded(parser, &ch)) < 0) {
+ return -1;
+ } else if (ret > 0) {
+ if (!i_isalnum(ch) && ch != '-')
+ break;
+ if (host_name != NULL) {
+ if (offset > part)
+ str_append_data(host_name, part, offset - part);
+ str_append_c(host_name, ch);
+ }
+ part = parser->cur;
+ } else {
+ ch = *parser->cur;
+ if (!i_isalnum(ch) && ch != '-')
+ break;
+ parser->cur++;
+ }
+ pch = ch;
+ } while (parser->cur < parser->end);
+
+ if (!i_isalnum(pch)) {
+ parser->error = "Invalid domain label in hostname";
+ return -1;
+ }
+ }
+
+ if (host_name != NULL && parser->cur > part)
+ str_append_data(host_name, part, parser->cur - part);
+
+ /* "." */
+ if (parser->cur >= parser->end || ch != '.')
+ break;
+ if (host_name != NULL)
+ str_append_c(host_name, '.');
+ if (parser->cur == offset)
+ parser->cur++;
+ part = parser->cur;
+ }
+
+ if (parser->cur == first)
+ return 0;
+
+ /* remove trailing '.' */
+ if (host_name != NULL) {
+ const char *name = str_c(host_name);
+
+ i_assert(str_len(host_name) > 0);
+ if (name[str_len(host_name)-1] == '.')
+ str_truncate(host_name, str_len(host_name)-1);
+ }
+ return 1;
+}
+
+int uri_parse_host_name(struct uri_parser *parser,
+ const char **host_name_r)
+{
+ string_t *host_name = NULL;
+ int ret;
+
+ if (host_name_r != NULL)
+ host_name = uri_parser_get_tmpbuf(parser, 256);
+
+ if ((ret=uri_do_parse_host_name(parser, host_name)) <= 0)
+ return ret;
+
+ if (host_name_r != NULL)
+ *host_name_r = str_c(host_name);
+ return 1;
+}
+
+static int
+uri_parse_ip_literal(struct uri_parser *parser, string_t *literal,
+ struct in6_addr *ip6_r) ATTR_NULL(2,3)
+{
+ const unsigned char *p;
+ const char *address;
+ struct in6_addr ip6;
+
+ /* IP-literal = "[" ( IPv6address / IPvFuture ) "]"
+ * IPvFuture = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" )
+ * IPv6address = ; Syntax not relevant: parsed using inet_pton()
+ */
+
+ /* "[" already verified */
+
+ /* Scan for end of address */
+ for (p = parser->cur+1; p < parser->end; p++) {
+ if (*p == ']')
+ break;
+ }
+
+ if (p >= parser->end || *p != ']') {
+ parser->error = "Expecting ']' at end of IP-literal";
+ return -1;
+ }
+
+ if (literal != NULL)
+ str_append_data(literal, parser->cur, p-parser->cur+1);
+ address = t_strdup_until(parser->cur+1, p);
+ parser->cur = p + 1;
+
+ if (*address == '\0') {
+ parser->error = "Empty IPv6 host address";
+ return -1;
+ }
+ if (*address == 'v') {
+ parser->error = p_strdup_printf(parser->pool,
+ "Future IP host address '%s' not supported", address);
+ return -1;
+ }
+ if (inet_pton(AF_INET6, address, &ip6) <= 0) {
+ parser->error = p_strdup_printf(parser->pool,
+ "Invalid IPv6 host address '%s'", address);
+ return -1;
+ }
+ if (ip6_r != NULL)
+ *ip6_r = ip6;
+ return 1;
+}
+
+static int
+uri_do_parse_host(struct uri_parser *parser,
+ struct uri_host *host, bool host_name)
+ ATTR_NULL(2)
+{
+ const unsigned char *preserve;
+ struct in_addr ip4;
+ struct in6_addr ip6;
+ string_t *literal = NULL;
+ int ret;
+
+ /* RFC 3986:
+ *
+ * host = IP-literal / IPv4address / reg-name
+ */
+
+ if (host != NULL)
+ i_zero(host);
+
+ literal = uri_parser_get_tmpbuf(parser, 256);
+
+ /* IP-literal / */
+ if (parser->cur < parser->end && *parser->cur == '[') {
+ if (uri_parse_ip_literal(parser, literal, &ip6) <= 0)
+ return -1;
+
+ if (host != NULL) {
+ host->name = p_strdup(parser->pool, str_c(literal));;
+ host->ip.family = AF_INET6;
+ host->ip.u.ip6 = ip6;
+ }
+ return 1;
+ }
+
+ /* IPv4address /
+ *
+ * If it fails to parse, we try to parse it as a reg-name
+ */
+ preserve = parser->cur;
+ if ((ret = uri_parse_ipv4address(parser, literal, &ip4)) > 0) {
+ if (host != NULL) {
+ host->name = p_strdup(parser->pool, str_c(literal));
+ host->ip.family = AF_INET;
+ host->ip.u.ip4 = ip4;
+ }
+ return ret;
+ }
+ parser->cur = preserve;
+ str_truncate(literal, 0);
+
+ /* reg-name */
+ if (host_name) {
+ if (uri_do_parse_host_name(parser, literal) < 0)
+ return -1;
+ } else if (uri_do_parse_reg_name(parser, literal) < 0)
+ return -1;
+ if (host != NULL)
+ host->name = p_strdup(parser->pool, str_c(literal));
+ return 0;
+}
+
+int uri_parse_host(struct uri_parser *parser,
+ struct uri_host *host)
+{
+ return uri_do_parse_host(parser, host, TRUE);
+}
+
+static int
+uri_parse_port(struct uri_parser *parser,
+ struct uri_authority *auth) ATTR_NULL(2)
+{
+ const unsigned char *first;
+ in_port_t port;
+
+ /* RFC 3986:
+ *
+ * port = *DIGIT
+ */
+
+ first = parser->cur;
+ while (parser->cur < parser->end && i_isdigit(*parser->cur))
+ parser->cur++;
+
+ if (parser->cur == first)
+ return 0;
+ if (net_str2port(t_strdup_until(first, parser->cur), &port) < 0) {
+ parser->error = "Invalid port number";
+ return -1;
+ }
+
+ if (auth != NULL)
+ auth->port = port;
+ return 1;
+}
+
+static int
+uri_do_parse_authority(struct uri_parser *parser,
+ struct uri_authority *auth, bool host_name) ATTR_NULL(2)
+{
+ const unsigned char *p;
+ int ret;
+
+ /*
+ * authority = [ userinfo "@" ] host [ ":" port ]
+ */
+
+ if (auth != NULL)
+ i_zero(auth);
+
+ /* Scan ahead to check whether there is a [userinfo "@"] uri component */
+ for (p = parser->cur; p < parser->end; p++){
+ /* refuse 8bit characters */
+ if ((*p & 0x80) != 0)
+ break;
+
+ /* break at first delimiter */
+ if (*p != '%' && (_uri_char_lookup[*p] & CHAR_MASK_UCHAR) == 0)
+ break;
+ }
+
+ /* Extract userinfo */
+ if (p < parser->end && *p == '@') {
+ if (auth != NULL)
+ auth->enc_userinfo = p_strdup_until(parser->pool, parser->cur, p);
+ parser->cur = p+1;
+ }
+
+ /* host */
+ if (uri_do_parse_host(parser,
+ (auth == NULL ? NULL : &auth->host), host_name) < 0)
+ return -1;
+ if (parser->cur == parser->end)
+ return 1;
+ switch (*parser->cur) {
+ case ':': case '/': case '?': case '#':
+ break;
+ default:
+ parser->error = "Invalid host identifier";
+ return -1;
+ }
+
+ /* [":" port] */
+ if (*parser->cur == ':') {
+ parser->cur++;
+
+ if ((ret = uri_parse_port(parser, auth)) < 0)
+ return ret;
+ if (parser->cur == parser->end)
+ return 1;
+ switch (*parser->cur) {
+ case '/': case '?': case '#':
+ break;
+ default:
+ parser->error = "Invalid host port";
+ return -1;
+ }
+ }
+
+ return 1;
+}
+
+static int
+uri_do_parse_slashslash_authority(struct uri_parser *parser,
+ struct uri_authority *auth, bool host_name)
+ ATTR_NULL(2)
+{
+ /* "//" authority */
+
+ if ((parser->end - parser->cur) <= 2 || parser->cur[0] != '/' ||
+ parser->cur[1] != '/')
+ return 0;
+
+ parser->cur += 2;
+ return uri_do_parse_authority(parser, auth, host_name);
+}
+
+int uri_parse_authority(struct uri_parser *parser,
+ struct uri_authority *auth)
+{
+ return uri_do_parse_authority(parser, auth, FALSE);
+}
+
+int uri_parse_slashslash_authority(struct uri_parser *parser,
+ struct uri_authority *auth)
+{
+ return uri_do_parse_slashslash_authority(parser, auth, FALSE);
+}
+
+int uri_parse_host_authority(struct uri_parser *parser,
+ struct uri_authority *auth)
+{
+ return uri_do_parse_authority(parser, auth, TRUE);
+}
+
+int uri_parse_slashslash_host_authority(struct uri_parser *parser,
+ struct uri_authority *auth)
+{
+ return uri_do_parse_slashslash_authority(parser, auth, TRUE);
+}
+
+int uri_parse_path_segment(struct uri_parser *parser, const char **segment_r)
+{
+ const unsigned char *first = parser->cur;
+ int ret;
+
+ while (parser->cur < parser->end) {
+ if (*parser->cur == '%') {
+ unsigned char ch = 0;
+ if ((ret=uri_parse_pct_encoded(parser, &ch)) < 0)
+ return -1;
+ if (ret > 0)
+ continue;
+ }
+
+ if ((*parser->cur & 0x80) != 0 ||
+ (_uri_char_lookup[*parser->cur] & CHAR_MASK_PCHAR) == 0)
+ break;
+
+ parser->cur++;
+ }
+
+ if (parser->cur < parser->end &&
+ *parser->cur != '/' && *parser->cur != '?' && *parser->cur != '#' ) {
+ parser->error =
+ "Path component contains invalid character";
+ return -1;
+ }
+
+ if (first == parser->cur)
+ return 0;
+
+ if (segment_r != NULL)
+ *segment_r = p_strdup_until(parser->pool, first, parser->cur);
+ return 1;
+}
+
+int uri_parse_path(struct uri_parser *parser,
+ int *relative_r, const char *const **path_r)
+{
+ const unsigned char *pbegin = parser->cur;
+ ARRAY_TYPE(const_string) segments;
+ const char *segment = NULL;
+ unsigned int count;
+ int relative = 1;
+ int ret;
+
+ count = 0;
+ if (path_r != NULL)
+ p_array_init(&segments, parser->pool, 16);
+ else
+ i_zero(&segments);
+
+ /* check for a leading '/' and indicate absolute path
+ when it is present
+ */
+ if (parser->cur < parser->end && *parser->cur == '/') {
+ parser->cur++;
+ relative = 0;
+ }
+
+ /* parse first segment */
+ if ((ret = uri_parse_path_segment(parser, &segment)) < 0)
+ return -1;
+
+ for (;;) {
+ if (ret > 0) {
+ /* strip dot segments */
+ if (segment[0] == '.') {
+ if (segment[1] == '.') {
+ if (segment[2] == '\0') {
+ /* '..' -> skip and... */
+ segment = NULL;
+
+ /* ... pop last segment (if any) */
+ if (count > 0) {
+ if (path_r != NULL) {
+ i_assert(count == array_count(&segments));
+ array_delete(&segments, count-1, 1);
+ }
+ count--;
+ } else if ( relative > 0 ) {
+ relative++;
+ }
+ }
+ } else if (segment[1] == '\0') {
+ /* '.' -> skip */
+ segment = NULL;
+ }
+ }
+ } else {
+ segment = "";
+ }
+
+ if (segment != NULL) {
+ if (path_r != NULL)
+ array_push_back(&segments, &segment);
+ count++;
+ }
+
+ if (parser->cur >= parser->end || *parser->cur != '/')
+ break;
+ parser->cur++;
+
+ /* parse next path segment */
+ if ((ret = uri_parse_path_segment(parser, &segment)) < 0)
+ return -1;
+ }
+
+ if (relative_r != NULL)
+ *relative_r = relative;
+ if (path_r != NULL)
+ *path_r = NULL;
+
+ if (parser->cur == pbegin) {
+ /* path part of URI is empty */
+ return 0;
+ }
+
+ if (path_r != NULL) {
+ /* special treatment for a trailing '..' or '.' */
+ if (segment == NULL) {
+ segment = "";
+ array_push_back(&segments, &segment);
+ }
+ array_append_zero(&segments);
+ *path_r = array_get(&segments, &count);
+ }
+ if (parser->cur < parser->end &&
+ *parser->cur != '?' && *parser->cur != '#') {
+ parser->error = "Path component contains invalid character";
+ return -1;
+ }
+ return 1;
+}
+
+int uri_parse_query(struct uri_parser *parser, const char **query_r)
+{
+ const unsigned char *first = parser->cur;
+ int ret;
+
+ /* RFC 3986:
+ *
+ * URI = { ... } [ "?" query ] { ... }
+ * query = *( pchar / "/" / "?" )
+ * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
+ */
+ if (parser->cur >= parser->end || *parser->cur != '?')
+ return 0;
+ parser->cur++;
+
+ while (parser->cur < parser->end) {
+ if (*parser->cur == '%') {
+ unsigned char ch = 0;
+ if ((ret=uri_parse_pct_encoded(parser, &ch)) < 0)
+ return -1;
+ if (ret > 0)
+ continue;
+ }
+
+ if ((*parser->cur & 0x80) != 0 ||
+ (_uri_char_lookup[*parser->cur] & CHAR_MASK_QCHAR) == 0)
+ break;
+ parser->cur++;
+ }
+
+ if (parser->cur < parser->end && *parser->cur != '#') {
+ parser->error = "Query component contains invalid character";
+ return -1;
+ }
+
+ if (query_r != NULL)
+ *query_r = p_strdup_until(parser->pool, first+1, parser->cur);
+ return 1;
+}
+
+int uri_parse_fragment(struct uri_parser *parser, const char **fragment_r)
+{
+ const unsigned char *first = parser->cur;
+ int ret;
+
+ /* RFC 3986:
+ *
+ * URI = { ... } [ "#" fragment ]
+ * fragment = *( pchar / "/" / "?" )
+ * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
+ */
+
+ if (parser->cur >= parser->end || *parser->cur != '#')
+ return 0;
+ parser->cur++;
+
+ while (parser->cur < parser->end) {
+ if (*parser->cur == '%') {
+ unsigned char ch = 0;
+ if ((ret=uri_parse_pct_encoded(parser, &ch)) < 0)
+ return -1;
+ if (ret > 0)
+ continue;
+ }
+
+ if ((*parser->cur & 0x80) != 0 ||
+ (_uri_char_lookup[*parser->cur] & CHAR_MASK_QCHAR) == 0)
+ break;
+ parser->cur++;
+ }
+
+ if (parser->cur < parser->end) {
+ parser->error = "Fragment component contains invalid character";
+ return -1;
+ }
+
+ if (fragment_r != NULL)
+ *fragment_r = p_strdup_until(parser->pool, first+1, parser->cur);
+ return 1;
+}
+
+void uri_parser_init_data(struct uri_parser *parser,
+ pool_t pool, const unsigned char *data, size_t size)
+{
+ i_zero(parser);
+ parser->pool = pool;
+ parser->begin = parser->cur = data;
+ parser->end = data + size;
+}
+
+void uri_parser_init(struct uri_parser *parser,
+ pool_t pool, const char *uri)
+{
+ uri_parser_init_data
+ (parser, pool, (const unsigned char *)uri, strlen(uri));
+}
+
+string_t *uri_parser_get_tmpbuf(struct uri_parser *parser, size_t size)
+{
+ if (parser->tmpbuf == NULL)
+ parser->tmpbuf = str_new(parser->pool, size);
+ else
+ str_truncate(parser->tmpbuf, 0);
+ return parser->tmpbuf;
+}
+
+int uri_parse_absolute_generic(struct uri_parser *parser,
+ enum uri_parse_flags flags)
+{
+ int relative, aret, ret = 0;
+
+ /*
+ URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
+
+ hier-part = "//" authority path-abempty
+ / path-absolute
+ / path-rootless
+ / path-empty
+ path-abempty = *( "/" segment )
+ path-absolute = "/" [ segment-nz *( "/" segment ) ]
+ path-rootless = segment-nz *( "/" segment )
+ path-empty = 0<pchar>
+
+ segment = *pchar
+ segment-nz = 1*pchar
+ */
+
+ /* scheme ":" */
+ if ((flags & URI_PARSE_SCHEME_EXTERNAL) == 0 &&
+ (ret=uri_parse_scheme(parser, NULL)) <= 0) {
+ if (ret == 0)
+ parser->error = "Missing scheme";
+ return -1;
+ }
+
+ /* "//" authority */
+ if ((aret=uri_parse_slashslash_authority
+ (parser, NULL)) < 0)
+ return -1;
+
+ /* path-absolute / path-rootless / path-empty */
+ if (aret == 0) {
+ ret = uri_parse_path(parser, &relative, NULL);
+ /* path-abempty */
+ } else if (parser->cur < parser->end && *parser->cur == '/') {
+ ret = uri_parse_path(parser, &relative, NULL);
+ i_assert(ret <= 0 || relative == 0);
+ }
+ if (ret < 0)
+ return -1;
+
+ /* [ "?" query ] */
+ if (uri_parse_query(parser, NULL) < 0)
+ return -1;
+
+ /* [ "#" fragment ] */
+ if ((ret=uri_parse_fragment(parser, NULL)) < 0)
+ return ret;
+ if (ret > 0 && (flags & URI_PARSE_ALLOW_FRAGMENT_PART) == 0) {
+ parser->error = "Fragment part not allowed";
+ return -1;
+ }
+
+ i_assert(parser->cur == parser->end);
+ return 0;
+}
+
+/*
+ * Generic URI manipulation
+ */
+
+void uri_host_copy(pool_t pool, struct uri_host *dest,
+ const struct uri_host *src)
+{
+ const char *host_name = src->name;
+
+ /* create host name literal if caller is lazy */
+ if (host_name == NULL && src->ip.family != 0) {
+ host_name = net_ip2addr(&src->ip);
+ i_assert(*host_name != '\0');
+ }
+
+ *dest = *src;
+ dest->name = p_strdup(pool, host_name);
+}
+
+/*
+ * Check generic URI
+ */
+
+int uri_check_data(const unsigned char *data, size_t size,
+ enum uri_parse_flags flags, const char **error_r)
+{
+ struct uri_parser parser;
+ int ret;
+
+ i_zero(&parser);
+ parser.pool = pool_datastack_create();
+ parser.begin = parser.cur = data;
+ parser.end = data + size;
+
+ ret = uri_parse_absolute_generic(&parser, flags);
+ *error_r = parser.error;
+ return ret;
+}
+
+int uri_check(const char *uri, enum uri_parse_flags flags,
+ const char **error_r)
+{
+ return uri_check_data
+ ((const unsigned char *)uri, strlen(uri), flags, error_r);
+}
+
+/*
+ * Generic URI construction
+ */
+
+void uri_data_encode(string_t *out,
+ const unsigned char esc_table[256],
+ unsigned char esc_mask, const char *esc_extra,
+ const char *data)
+{
+ const unsigned char *pbegin, *p;
+
+ pbegin = p = (const unsigned char *)data;
+ while (*p != '\0') {
+ if ((*p & 0x80) != 0 || (esc_table[*p] & esc_mask) == 0 ||
+ (esc_extra != NULL && strchr(esc_extra, (char)*p) != NULL)) {
+ if ((p - pbegin) > 0)
+ str_append_data(out, pbegin, p - pbegin);
+ str_printfa(out, "%%%02x", *p);
+ p++;
+ pbegin = p;
+ } else {
+ p++;
+ }
+ }
+ if ((p - pbegin) > 0)
+ str_append_data(out, pbegin, p - pbegin);
+}
+
+void uri_append_scheme(string_t *out, const char *scheme)
+{
+ str_append(out, scheme);
+ str_append_c(out, ':');
+}
+
+void uri_append_user_data(string_t *out, const char *esc,
+ const char *data)
+{
+ uri_data_encode(out, _uri_char_lookup, CHAR_MASK_UCHAR, esc, data);
+}
+
+void uri_append_userinfo(string_t *out, const char *userinfo)
+{
+ uri_append_user_data(out, NULL, userinfo);
+ str_append_c(out, '@');
+}
+
+void uri_append_host_name(string_t *out, const char *name)
+{
+ uri_data_encode(out, _uri_char_lookup,
+ CHAR_MASK_UNRESERVED | CHAR_MASK_SUB_DELIMS, NULL, name);
+}
+
+void uri_append_host_ip(string_t *out, const struct ip_addr *host_ip)
+{
+ const char *addr = net_ip2addr(host_ip);
+
+ i_assert(host_ip->family != 0);
+
+ if (host_ip->family == AF_INET) {
+ str_append(out, addr);
+ return;
+ }
+
+ i_assert(host_ip->family == AF_INET6);
+ str_append_c(out, '[');
+ str_append(out, addr);
+ str_append_c(out, ']');
+}
+
+void uri_append_host(string_t *out, const struct uri_host *host)
+{
+ if (host->name != NULL) {
+ /* assume IPv6 literal if starts with '['; avoid encoding */
+ if (*host->name == '[')
+ str_append(out, host->name);
+ else
+ uri_append_host_name(out, host->name);
+ } else
+ uri_append_host_ip(out, &host->ip);
+}
+
+void uri_append_port(string_t *out, in_port_t port)
+{
+ if (port != 0)
+ str_printfa(out, ":%u", port);
+}
+
+void uri_append_path_segment_data(string_t *out, const char *esc,
+ const char *data)
+{
+ uri_data_encode(out, _uri_char_lookup, CHAR_MASK_PCHAR, esc, data);
+}
+
+void uri_append_path_segment(string_t *out, const char *segment)
+{
+ str_append_c(out, '/');
+ if (*segment != '\0')
+ uri_append_path_data(out, NULL, segment);
+}
+
+void uri_append_path_data(string_t *out, const char *esc,
+ const char *data)
+{
+ uri_data_encode(out, _uri_char_lookup, CHAR_MASK_PFCHAR, esc, data);
+}
+
+void uri_append_path(string_t *out, const char *path)
+{
+ str_append_c(out, '/');
+ if (*path != '\0')
+ uri_append_path_data(out, NULL, path);
+}
+
+void uri_append_query_data(string_t *out, const char *esc,
+ const char *data)
+{
+ uri_data_encode(out, _uri_char_lookup, CHAR_MASK_QCHAR, esc, data);
+}
+
+void uri_append_query(string_t *out, const char *query)
+{
+ str_append_c(out, '?');
+ if (*query != '\0')
+ uri_append_query_data(out, NULL, query);
+}
+
+void uri_append_fragment_data(string_t *out, const char *esc,
+ const char *data)
+{
+ uri_data_encode(out, _uri_char_lookup, CHAR_MASK_QCHAR, esc, data);
+}
+
+void uri_append_fragment(string_t *out, const char *fragment)
+{
+ str_append_c(out, '#');
+ if (*fragment != '\0')
+ uri_append_fragment_data(out, NULL, fragment);
+}
+
+void uri_append_unreserved(string_t *out, const char *data)
+{
+ uri_data_encode(out, _uri_char_lookup, CHAR_MASK_UNRESERVED,
+ NULL, data);
+}
+
+void uri_append_unreserved_path(string_t *out, const char *data)
+{
+ uri_data_encode(out, _uri_char_lookup, CHAR_MASK_UNRESERVED_PATH,
+ NULL, data);
+}