diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:51:24 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:51:24 +0000 |
commit | f7548d6d28c313cf80e6f3ef89aed16a19815df1 (patch) | |
tree | a3f6f2a3f247293bee59ecd28e8cd8ceb6ca064a /src/lib-imap/imap-url.c | |
parent | Initial commit. (diff) | |
download | dovecot-f7548d6d28c313cf80e6f3ef89aed16a19815df1.tar.xz dovecot-f7548d6d28c313cf80e6f3ef89aed16a19815df1.zip |
Adding upstream version 1:2.3.19.1+dfsg1.upstream/1%2.3.19.1+dfsg1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/lib-imap/imap-url.c')
-rw-r--r-- | src/lib-imap/imap-url.c | 1009 |
1 files changed, 1009 insertions, 0 deletions
diff --git a/src/lib-imap/imap-url.c b/src/lib-imap/imap-url.c new file mode 100644 index 0000000..6da6e21 --- /dev/null +++ b/src/lib-imap/imap-url.c @@ -0,0 +1,1009 @@ +/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "str.h" +#include "strfuncs.h" +#include "str-sanitize.h" +#include "hex-binary.h" +#include "net.h" +#include "iso8601-date.h" +#include "uri-util.h" + +#include "imap-url.h" + +#include <ctype.h> + +/* + * IMAP URL parsing + */ + +/* +IMAP URL Grammar overview + +RFC5092 Section 11: + +imapurl = "imap://" iserver ipath-query + ; Defines an absolute IMAP URL +iserver = [iuserinfo "@"] host [ ":" port ] + ; This is the same as "authority" defined in [URI-GEN]. +iuserinfo = enc-user [iauth] / [enc-user] iauth + ; conforms to the generic syntax of "userinfo" as + ; defined in [URI-GEN]. +enc-user = 1*achar + ; %-encoded version of [IMAP4] authorization identity or + ; "userid". +iauth = ";AUTH=" ( "*" / enc-auth-type ) +enc-auth-type = 1*achar + ; %-encoded version of [IMAP4] "auth-type" +ipath-query = ["/" [ icommand ]] + ; Corresponds to "path-abempty [ "?" query ]" in + ; [URI-GEN] +icommand = imessagelist / + imessagepart [iurlauth] +imessagelist = imailbox-ref [ "?" enc-search ] + ; "enc-search" is [URI-GEN] "query". +imessagepart = imailbox-ref iuid [isection] [ipartial] +imailbox-ref = enc-mailbox [uidvalidity] +uidvalidity = ";UIDVALIDITY=" nz-number + ; See [IMAP4] for "nz-number" definition +iuid = "/" iuid-only +iuid-only = ";UID=" nz-number + ; See [IMAP4] for "nz-number" definition +isection = "/" isection-only +isection-only = ";SECTION=" enc-section +ipartial = "/" ipartial-only +ipartial-only = ";PARTIAL=" partial-range +enc-search = 1*bchar + ; %-encoded version of [IMAPABNF] + ; "search-program". Note that IMAP4 + ; literals may not be used in + ; a "search-program", i.e., only + ; quoted or non-synchronizing + ; literals (if the server supports + ; LITERAL+ [LITERAL+]) are allowed. +enc-mailbox = 1*bchar + ; %-encoded version of [IMAP4] "mailbox" +enc-section = 1*bchar + ; %-encoded version of [IMAP4] "section-spec" +partial-range = number ["." nz-number] + ; partial FETCH. The first number is + ; the offset of the first byte, + ; the second number is the length of + ; the fragment. +bchar = achar / ":" / "@" / "/" +achar = uchar / "&" / "=" + ;; Same as [URI-GEN] 'unreserved / sub-delims / + ;; pct-encoded', but ";" is disallowed. +uchar = unreserved / sub-delims-sh / pct-encoded +sub-delims-sh = "!" / "$" / "'" / "(" / ")" / + "*" / "+" / "," + ;; Same as [URI-GEN] sub-delims, + ;; but without ";", "&" and "=". + +The following rules are only used in the presence of the IMAP +[URLAUTH] extension: + +authimapurl = "imap://" iserver "/" imessagepart + ; Same as "imapurl" when "[icommand]" is + ; "imessagepart" +authimapurlfull = authimapurl iurlauth + ; Same as "imapurl" when "[icommand]" is + ; "imessagepart iurlauth" +authimapurlrump = authimapurl iurlauth-rump + +iurlauth = iurlauth-rump iua-verifier +enc-urlauth = 32*HEXDIG +iua-verifier = ":" uauth-mechanism ":" enc-urlauth +iurlauth-rump = [expire] ";URLAUTH=" access +access = ("submit+" enc-user) / ("user+" enc-user) / + "authuser" / "anonymous" +expire = ";EXPIRE=" date-time + ; date-time is defined in [DATETIME] +uauth-mechanism = "INTERNAL" / 1*(ALPHA / DIGIT / "-" / ".") + ; Case-insensitive. + +[URI-GEN] RFC3986 Appendix A: + +Implemented in src/lib/uri-util.c + +*/ + +/* + * Imap URL parser + */ + +struct imap_url_parser { + struct uri_parser parser; + + enum imap_url_parse_flags flags; + + struct imap_url *url; + const struct imap_url *base; + + bool relative:1; +}; + +static int +imap_url_parse_number(struct uri_parser *parser, const char *data, + uint32_t *number_r) +{ + /* [IMAP4] RFC3501, Section 9 + * + * number = 1*DIGIT + * ; Unsigned 32-bit integer + * ; (0 <= n < 4,294,967,296) + */ + + if (i_isdigit(*data)) { + if (str_to_uint32(data, number_r) == 0) + return 1; + parser->error = "IMAP number is too high"; + return -1; + } + + parser->error = t_strdup_printf( + "Value '%s' is not a valid IMAP number", data); + return -1; +} + +static int +imap_url_parse_offset(struct uri_parser *parser, const char *data, + uoff_t *number_r) +{ + /* Syntax for big (uoff_t) numbers. Not strictly IMAP syntax, but this + is handled similarly for Dovecot IMAP FETCH BODY partial <.> + implementation. */ + if (i_isdigit(*data)) { + if (str_to_uoff(data, number_r) == 0) + return 1; + parser->error = "IMAP number is too high"; + return -1; + } + + parser->error = t_strdup_printf( + "Value '%s' is not a valid IMAP number", data); + return -1; +} + +static int imap_url_parse_iserver(struct imap_url_parser *url_parser) +{ + struct uri_parser *parser = &url_parser->parser; + struct uri_authority auth; + struct imap_url *url = url_parser->url; + const char *data; + int ret = 0; + + /* imapurl = "imap://" iserver {...} + * inetwork-path = "//" iserver {...} + * iserver = [iuserinfo "@"] host [":" port] + * ; This is the same as "authority" defined + * ; in [URI-GEN]. + * iuserinfo = enc-user [iauth] / [enc-user] iauth + * ; conforms to the generic syntax of "userinfo" as + * ; defined in [URI-GEN]. + * enc-user = 1*achar + * ; %-encoded version of [IMAP4] authorization identity or + * ; "userid". + * iauth = ";AUTH=" ( "*" / enc-auth-type ) + * enc-auth-type = 1*achar + * ; %-encoded version of [IMAP4] "auth-type" + */ + + /* "//" iserver */ + if ((ret = uri_parse_slashslash_host_authority + (parser, &auth)) <= 0) + return ret; + if (auth.host.name == NULL || *auth.host.name == '\0') { + /* This situation is not documented anywhere, but it is not + currently useful either and potentially problematic if not + handled explicitly everywhere. So, it is denied hier for now. + */ + parser->error = "IMAP URL does not allow empty host identifier"; + return -1; + } + /* iuserinfo = enc-user [iauth] / [enc-user] iauth */ + if (auth.enc_userinfo != NULL) { + const char *p, *uend; + + /* Scan for ";AUTH=" */ + for (p = auth.enc_userinfo; *p != '\0'; p++) { + if (*p == ';') + break; + /* check for unallowed userinfo characters */ + if (*p == ':') { + parser->error = t_strdup_printf( + "Stray ':' in userinfo `%s'", auth.enc_userinfo); + return -1; + } + } + + uend = p; + + if (*p == ';') { + if (strncasecmp(p, ";AUTH=", 6) != 0) { + parser->error = t_strdup_printf( + "Stray ';' in userinfo `%s'", + auth.enc_userinfo); + return -1; + } + + for (p += 6; *p != '\0'; p++) { + if (*p == ';' || *p == ':') { + parser->error = t_strdup_printf( + "Stray '%c' in userinfo `%s'", *p, auth.enc_userinfo); + return -1; + } + } + } + + /* enc-user */ + if (url != NULL && uend > auth.enc_userinfo) { + if (!uri_data_decode(parser, auth.enc_userinfo, uend, &data)) + return -1; + url->userid = p_strdup(parser->pool, data); + } + + /* ( "*" / enc-auth-type ) */ + if (*uend == ';') { + p = uend + 6; + if (*p == '\0') { + parser->error = "Empty auth-type value after ';AUTH='"; + return -1; + } + if (url != NULL) { + if (!uri_data_decode(parser, p, NULL, &data)) + return -1; + url->auth_type = p_strdup(parser->pool, data); + } + } + } + + if (url != NULL) { + url->host = auth.host; + url->port = auth.port; + } + return 1; +} + +static int +imap_url_parse_urlauth(struct imap_url_parser *url_parser, const char *urlext) +{ + struct uri_parser *parser = &url_parser->parser; + struct imap_url *url = url_parser->url; + const char *p, *q, *data; + buffer_t *uauth_token; + time_t expire = (time_t)-1; + int tz; + + /* iurlauth = iurlauth-rump iua-verifier + * enc-urlauth = 32*HEXDIG + * iua-verifier = ":" uauth-mechanism ":" enc-urlauth + * iurlauth-rump = [expire] ";URLAUTH=" access + * access = ("submit+" enc-user) / ("user+" enc-user) / + * "authuser" / "anonymous" + * expire = ";EXPIRE=" date-time + * ; date-time is defined in [DATETIME] + * uauth-mechanism = "INTERNAL" / 1*(ALPHA / DIGIT / "-" / ".") + * ; Case-insensitive. + */ + + /* ";EXPIRE=" date-time */ + if (strncasecmp(urlext, ";EXPIRE=", 8) == 0) { + if ((url_parser->flags & IMAP_URL_PARSE_ALLOW_URLAUTH) == 0) { + parser->error = "`;EXPIRE=' is not allowed in this context"; + return -1; + } + + if ((p = strchr(urlext+8, ';')) != NULL) { + if (!iso8601_date_parse((const unsigned char *)urlext+8, + p-urlext-8, &expire, &tz)) { + parser->error = "invalid date-time for `;EXPIRE='"; + return -1; + } + urlext = p; + } + } + + /* ";URLAUTH=" access */ + if (strncasecmp(urlext, ";URLAUTH=", 9) != 0) { + if (expire != (time_t)-1) { + parser->error = "`;EXPIRE=' without `;URLAUTH='"; + return -1; + } + return 0; + } + urlext += 9; + + if (url != NULL) + url->uauth_expire = expire; + + if ((url_parser->flags & IMAP_URL_PARSE_ALLOW_URLAUTH) == 0) { + parser->error = "`;URLAUTH=' is not allowed in this context"; + return -1; + } + + if (url_parser->relative) { + parser->error = "IMAP URLAUTH requires absolute URL"; + return -1; + } + + if ((p = strchr(urlext, ':')) == NULL) { + size_t len = strlen(urlext); + if (len == 0) { + parser->error = "Missing URLAUTH access specifier"; + return -1; + } + p = urlext+len; + } else if (p == urlext) { + parser->error = "Empty URLAUTH access specifier"; + return -1; + } + + /* parse access */ + if ((q = strchr(urlext, '+')) == NULL) { + /* application */ + if (url != NULL) { + url->uauth_access_application = + p_strdup_until(parser->pool, urlext, p); + } + } else { + /* application "+" enc-user */ + if (urlext == q) { + parser->error = "Empty URLAUTH access application"; + return -1; + } + if (q+1 == p) { + parser->error = t_strdup_printf( + "Empty URLAUTH access user for `%s' application", + t_strdup_until(urlext, q)); + return -1; + } + if (!uri_data_decode(parser, q+1, p, &data)) + return -1; + if (url != NULL) { + url->uauth_access_application = + p_strdup_until(parser->pool, urlext, q); + url->uauth_access_user = p_strdup(parser->pool, data); + } + } + + if (url != NULL) { + /* get rump url */ + if ((url_parser->flags & IMAP_URL_PARSE_SCHEME_EXTERNAL) == 0) { + url->uauth_rumpurl = p_strdup_until(parser->pool, + parser->begin, parser->end-strlen(p)); + } else { + url->uauth_rumpurl = p_strconcat(parser->pool, "imap:", + t_strdup_until(parser->begin, parser->end-strlen(p)), + NULL); + } + } + + if (*p == '\0') { + /* rump url; caller should check whether this is appropriate */ + return 1; + } + + /* iua-verifier = ":" uauth-mechanism ":" enc-urlauth */ + + q = p + 1; + if (*q == '\0') { + parser->error = "Missing URLAUTH verifier"; + return -1; + } + if ((p = strchr(q, ':')) == NULL || p[1] == '\0') { + parser->error = "Missing URLAUTH token"; + return -1; + } + if (p == q) { + parser->error = "Missing URLAUTH mechanism"; + return -1; + } + if (url != NULL) { + /* get mechanism */ + url->uauth_mechanism = p_strdup_until(parser->pool, q, p); + } + + /* enc-urlauth = 32*HEXDIG */ + + q = p+1; + if (strlen(q) < 32) { + parser->error = "Too short URLAUTH token"; + return -1; + } + + uauth_token = t_buffer_create(64); + if (hex_to_binary(q, uauth_token) < 0) { + parser->error = "Invalid URLAUTH token"; + return -1; + } + + if (url != NULL) { + url->uauth_token = uauth_token->data; + url->uauth_token_size = uauth_token->used; + } + return 1; +} + +static int +imap_url_parse_path(struct imap_url_parser *url_parser, + const char *const *path, int relative, + bool *is_messagelist_r) +{ + struct uri_parser *parser = &url_parser->parser; + struct imap_url *url = url_parser->url; + const char *const *segment; + string_t *mailbox, *section = NULL; + uint32_t uid = 0, uidvalidity = 0; + uoff_t partial_offset = 0, partial_size = 0; + bool have_partial = FALSE; + const char *p, *value, *urlext = NULL; + bool mailbox_endslash = FALSE, section_endslash = FALSE; + int ret; + + /* icommand = imessagelist / + * imessagepart [iurlauth] + * imessagelist = imailbox-ref [ "?" enc-search ] + * ; "enc-search" is [URI-GEN] "query". + * imessagepart = imailbox-ref iuid [isection] [ipartial] + * imailbox-ref = enc-mailbox [uidvalidity] + * uidvalidity = ";UIDVALIDITY=" nz-number + * iuid = "/" iuid-only + * iuid-only = ";UID=" nz-number + * ; See [IMAP4] for "nz-number" definition + * isection = "/" isection-only + * isection-only = ";SECTION=" enc-section + * ipartial = "/" ipartial-only + * ipartial-only = ";PARTIAL=" partial-range + * enc-mailbox = 1*bchar + * ; %-encoded version of [IMAP4] "mailbox" + * enc-section = 1*bchar + * ; %-encoded version of [IMAP4] "section-spec" + * partial-range = number ["." nz-number] + * ; partial FETCH. The first number is + * ; the offset of the first byte, + * ; the second number is the length of + * ; the fragment. + */ + + /* IMAP URL syntax is quite horrible to parse. It relies upon the + generic URI path resolution, but the icommand syntax also relies on + ';' separators. We use the generic URI path parse functions to + adhere to the URI path resolution rules and glue back together path + segments when these are part of the same (mailbox or section) value. + */ + + mailbox = t_str_new(256); + segment = path; + + /* Resolve relative URI path; determine what to copy from the base URI */ + if (url != NULL && url_parser->base != NULL && relative > 0) { + const struct imap_url *base = url_parser->base; + int rel = relative; + + /* /;PARTIAL= */ + if (base->have_partial && --rel <= 0) { + have_partial = base->have_partial; + partial_offset = base->partial_offset; + partial_size = base->partial_size; + } + /* /;SECTION= */ + if (base->section != NULL) { + p = base->section + strlen(base->section); + /* determine what to retain from base section path */ + for (; p > base->section && rel > 0; p--) { + if (*p =='/' && --rel <= 0) break; + } + if (--rel <= 0 && p > base->section) { + if (p[-1] == '/') section_endslash = TRUE; + if (section == NULL) + section = t_str_new(256); + str_append_data(section, base->section, p-base->section); + } + } + /* /;UID= */ + if (base->uid > 0 && --rel <= 0) { + uid = base->uid; + } + /* /mail/box;UIDVALIDITY= */ + if (base->mailbox != NULL) { + uidvalidity = base->uidvalidity; + p = base->mailbox + strlen(base->mailbox); + /* mailbox has implicit trailing '/' */ + if (p[-1] != '/' && base->uid == 0 && rel > 0) + rel--; + /* determine what to retain from base mailbox path */ + for (; p > base->mailbox && rel > 0; p--) { + if (*p =='/') { + uidvalidity = 0; + if (--rel <= 0) + break; + } + } + if (--rel <= 0 && p > base->mailbox) { + if (p[-1] == '/') + mailbox_endslash = TRUE; + str_append_data(mailbox, base->mailbox, + p - base->mailbox); + } + } + } + + /* Scan for last mailbox-ref segment */ + if (segment != NULL) { + if (relative == 0 || (!have_partial && section == NULL)) { + p = NULL; + while (*segment != NULL) { + /* ';' must be pct-encoded; if it is not, this is + either the last mailbox-ref path segment containing + ';UIDVALIDITY=' or the subsequent iuid ';UID=' path + segment */ + if ((p = strchr(*segment, ';')) != NULL) + break; + + if (**segment != '\0') { + if (segment > path || + (!mailbox_endslash && str_len(mailbox) > 0)) + str_append_c(mailbox, '/'); + if (!uri_data_decode(parser, *segment, NULL, &value)) + return -1; + str_append(mailbox, value); + mailbox_endslash = FALSE; + } + segment++; + } + + /* Handle ';' */ + if (p != NULL) { + /* [uidvalidity] */ + if (strncasecmp(p, ";UIDVALIDITY=", 13) == 0) { + /* append last bit of mailbox */ + if (*segment != p) { + if (segment > path || + (!mailbox_endslash && str_len(mailbox) > 0)) + str_append_c(mailbox, '/'); + if (!uri_data_decode(parser, *segment, p, &value)) + return -1; + str_append(mailbox, value); + } + + /* ";UIDVALIDITY=" nz-number */ + if (strchr(p+13, ';') != NULL) { + parser->error = "Encountered stray ';' after UIDVALIDITY"; + return -1; + } + + /* nz-number */ + if (p[13] == '\0') { + parser->error = "Empty UIDVALIDITY value"; + return -1; + } + if (imap_url_parse_number(parser, p+13, &uidvalidity) <= 0) + return -1; + if (uidvalidity == 0) { + parser->error = "UIDVALIDITY cannot be zero"; + return -1; + } + segment++; + } else if (p != *segment) { + parser->error = "Encountered stray ';' in mailbox reference"; + return -1; + } + } + + /* iuid */ + if (*segment != NULL && strncasecmp(*segment, ";UID=", 5) == 0) { + /* ";UID=" nz-number */ + value = (*segment)+5; + if ((p = strchr(value,';')) != NULL) { + if (segment[1] != NULL ) { + /* not the last segment, so it cannot be extension like iurlauth */ + parser->error = "Encountered stray ';' in UID path segment"; + return -1; + } + urlext = p; + value = t_strdup_until(value, p); + } + /* nz-number */ + if (*value == '\0') { + parser->error = "Empty UID value"; + return -1; + } + if (imap_url_parse_number(parser, value, &uid) <= 0) + return -1; + if (uid == 0) { + parser->error = "UID cannot be zero"; + return -1; + } + segment++; + } + } + + /* [isection] [ipartial] */ + if (*segment != NULL && uid > 0) { + /* [isection] */ + if (section != NULL || + strncasecmp(*segment, ";SECTION=", 9) == 0) { + /* ";SECTION=" enc-section */ + if (section == NULL) { + section = t_str_new(256); + value = (*segment) + 9; + } else { + value = *segment; + } + + /* enc-section can contain slashes, so we merge path segments until one + contains ';' */ + while ((p = strchr(value,';')) == NULL) { + if (!section_endslash && str_len(section) > 0) + str_append_c(section, '/'); + if (*value != '\0') { + if (!uri_data_decode(parser, value, NULL, &value)) + return -1; + str_append(section, value); + section_endslash = FALSE; + } + + segment++; + if (*segment == NULL) + break; + value = *segment; + } + + if (p != NULL) { + /* found ';' */ + if (p != value) { + /* it is not at the beginning of the path segment */ + if (segment[1] != NULL) { + /* not the last segment, so it cannot be extension like iurlauth */ + parser->error = "Encountered stray ';' in SECTION path segment"; + return -1; + } + urlext = p; + value = t_strdup_until(value, p); + if (!section_endslash && str_len(section) > 0) + str_append_c(section, '/'); + if (!uri_data_decode(parser, value, NULL, &value)) + return -1; + str_append(section, value); + segment++; + } + } + + if (str_len(section) == 0) { + parser->error = "Empty SECTION value"; + return -1; + } + } + + /* [ipartial] */ + if (*segment != NULL && + strncasecmp(*segment, ";PARTIAL=", 9) == 0) { + have_partial = TRUE; + + /* ";PARTIAL=" partial-range */ + value = (*segment) + 9; + if ((p = strchr(value,';')) != NULL) { + urlext = p; + value = t_strdup_until(value, p); + } + if (*value == '\0') { + parser->error = "Empty PARTIAL value"; + return -1; + } + /* partial-range = number ["." nz-number] */ + if ((p = strchr(value,'.')) != NULL) { + if (p[1] == '\0') { + parser->error = "Empty PARTIAL size"; + return -1; + } + if (imap_url_parse_offset(parser, p+1, &partial_size) <= 0) + return -1; + if (partial_size == 0) { + parser->error = "PARTIAL size cannot be zero"; + return -1; + } + value = t_strdup_until(value, p); + if (*value == '\0') { + parser->error = "Empty PARTIAL offset"; + return -1; + } + } + if (imap_url_parse_offset(parser,value, &partial_offset) <= 0) + return -1; + segment++; + } + } + + if (*segment != NULL) { + if (urlext != NULL || **segment != '\0' || *(segment+1) != NULL ) { + parser->error = t_strdup_printf( + "Unexpected IMAP URL path segment: `%s'", + str_sanitize(*segment, 80)); + return -1; + } + } + } + + /* ";" {...} at end of URL */ + if (urlext != NULL) { + /* [iurlauth] */ + if ((ret = imap_url_parse_urlauth(url_parser, urlext)) < 0) + return ret; + else if (ret == 0) { + /* something else */ + parser->error = t_strdup_printf( + "Unrecognized IMAP URL extension: %s", + str_sanitize(urlext, 80)); + return -1; + } + } + + if (is_messagelist_r != NULL) + *is_messagelist_r = (uid == 0); + + if (url != NULL) { + if (str_len(mailbox) > 0) + url->mailbox = p_strdup(parser->pool, str_c(mailbox)); + url->uidvalidity = uidvalidity; + url->uid = uid; + if (section != NULL) + url->section = p_strdup(parser->pool, str_c(section)); + url->have_partial = have_partial; + url->partial_offset = partial_offset; + url->partial_size = partial_size; + } + return 1; +} + +static bool imap_url_do_parse(struct imap_url_parser *url_parser) +{ + struct uri_parser *parser = &url_parser->parser; + const char *const *path; + bool is_messagelist = FALSE; + bool have_scheme = FALSE; + int relative; + const char *query; + int ret, sret; + + /* + * imapurl = "imap://" iserver ipath-query + * ; Defines an absolute IMAP URL + * iserver = [iuserinfo "@"] host [":" port] + * ; This is the same as "authority" defined + * ; in [URI-GEN]. + * ipath-query = ["/" [ icommand ]] + * ; Corresponds to "path-abempty [ "?" query ]" in + * ; [URI-GEN] + * icommand = imessagelist / + * imessagepart [iurlauth] + * imessagelist = imailbox-ref [ "?" enc-search ] + * ; "enc-search" is [URI-GEN] "query". + * imessagepart = imailbox-ref iuid [isection] [ipartial] + * enc-search = 1*bchar + * ; %-encoded version of [IMAPABNF] + * ; "search-program". Note that IMAP4 + * ; literals may not be used in + * ; a "search-program", i.e., only + * ; quoted or non-synchronizing + * ; literals (if the server supports + * ; LITERAL+ [LITERAL+]) are allowed. + */ + + /* "imap:" */ + if ((url_parser->flags & IMAP_URL_PARSE_SCHEME_EXTERNAL) == 0) { + const char *scheme; + + if (uri_parse_scheme(parser, &scheme) <= 0) { + parser->cur = parser->begin; + } else { + if (strcasecmp(scheme, "imap") != 0) { + parser->error = "Not an IMAP URL"; + return FALSE; + } + have_scheme = TRUE; + } + } else { + have_scheme = TRUE; + } + + /* "//" iserver */ + if ((sret = imap_url_parse_iserver(url_parser)) < 0) + return FALSE; + + if (have_scheme && sret == 0) { + parser->error = "Absolute IMAP URL requires `//' after `imap:'"; + return FALSE; + } + + if (sret > 0 && + (url_parser->flags & IMAP_URL_PARSE_REQUIRE_RELATIVE) != 0) { + parser->error = "Relative URL required"; + return FALSE; + } + + /* ipath-query = ["/" [ icommand ]] ; excludes `[ "?" enc-search ]` */ + if ((ret = uri_parse_path(parser, &relative, &path)) < 0) + return FALSE; + + /* Relative urls are only valid when we have a base url */ + if (sret == 0) { + if (url_parser->base == NULL) { + parser->error = "Relative URL not allowed"; + return FALSE; + } else if (url_parser->url != NULL) { + struct imap_url *url = url_parser->url; + const struct imap_url *base = url_parser->base; + + uri_host_copy(parser->pool, &url->host, &base->host); + url->port = base->port; + url->userid = p_strdup_empty(parser->pool, base->userid); + url->auth_type = p_strdup_empty(parser->pool, base->auth_type); + } + + url_parser->relative = TRUE; + } + + /* Parse path, i.e. `[ icommand ]` from `*( "/" segment )` */ + if (ret > 0 || url_parser->relative) { + if (imap_url_parse_path(url_parser, path, relative, + &is_messagelist) < 0) + return FALSE; + } + + /* [ "?" enc-search ] */ + if ((ret = uri_parse_query(parser, &query)) != 0) { + if (ret < 0) + return FALSE; + + if (!is_messagelist) { + parser->error = + "Search query part only valid for messagelist-type IMAP URL"; + return FALSE; + } else if (*query == '\0') { + parser->error = "Empty IMAP URL search query not allowed"; + return FALSE; + } + + if (url_parser->url != NULL) { + if (!uri_data_decode(parser, query, NULL, &query)) + return FALSE; + url_parser->url->search_program = + p_strdup(parser->pool, query); + } + } + + /* IMAP URL has no fragment */ + if ((ret = uri_parse_fragment(parser, &query)) != 0) { + if (ret == 1) + parser->error = "Fragment component not allowed in IMAP URL"; + return FALSE; + } + + /* must be at end of URL now */ + i_assert(parser->cur == parser->end); + + return TRUE; +} + +/* Public API */ + +int imap_url_parse(const char *url, const struct imap_url *base, + enum imap_url_parse_flags flags, + struct imap_url **url_r, const char **error_r) +{ + struct imap_url_parser url_parser; + + /* base != NULL indicates whether relative URLs are allowed. However, certain + flags may also dictate whether relative URLs are allowed/required. */ + i_assert((flags & IMAP_URL_PARSE_REQUIRE_RELATIVE) == 0 || base != NULL); + i_assert((flags & IMAP_URL_PARSE_SCHEME_EXTERNAL) == 0 || base == NULL); + + i_zero(&url_parser); + uri_parser_init(&url_parser.parser, pool_datastack_create(), url); + + url_parser.url = t_new(struct imap_url, 1); + url_parser.url->uauth_expire = (time_t)-1; + url_parser.base = base; + url_parser.flags = flags; + + if (!imap_url_do_parse(&url_parser)) { + *error_r = url_parser.parser.error; + return -1; + } + *url_r = url_parser.url; + return 0; +} + +/* + * IMAP URL construction + */ + +static void +imap_url_append_mailbox(const struct imap_url *url, string_t *urlstr) +{ + uri_append_path_data(urlstr, ";", url->mailbox); + if (url->uidvalidity != 0) + str_printfa(urlstr, ";UIDVALIDITY=%u", url->uidvalidity); + if (url->uid == 0) { + /* message list */ + if (url->search_program != NULL) { + str_append_c(urlstr, '?'); + uri_append_query_data(urlstr, ";", url->search_program); + } + } else { + /* message part */ + str_printfa(urlstr, "/;UID=%u", url->uid); + if (url->section != NULL) { + str_append(urlstr, "/;SECTION="); + uri_append_path_data(urlstr, ";", url->section); + } + if (url->have_partial) { + str_append(urlstr, "/;PARTIAL="); + if (url->partial_size == 0) { + str_printfa(urlstr, "%"PRIuUOFF_T, + url->partial_offset); + } else { + str_printfa(urlstr, "%"PRIuUOFF_T".%"PRIuUOFF_T, + url->partial_offset, + url->partial_size); + } + } + + /* urlauth */ + if (url->uauth_access_application != NULL) { + if (url->uauth_expire != (time_t)-1) { + str_append(urlstr, ";EXPIRE="); + str_append(urlstr, iso8601_date_create(url->uauth_expire)); + } + str_append(urlstr, ";URLAUTH="); + str_append(urlstr, url->uauth_access_application); + if (url->uauth_access_user != NULL) { + str_append_c(urlstr, '+'); + uri_append_user_data(urlstr, ";", + url->uauth_access_user); + } + } + } +} + +const char *imap_url_create(const struct imap_url *url) +{ + string_t *urlstr = t_str_new(512); + + /* scheme */ + uri_append_scheme(urlstr, "imap"); + str_append(urlstr, "//"); + + /* user */ + if (url->userid != NULL || url->auth_type != NULL) { + if (url->userid != NULL) + uri_append_user_data(urlstr, ";:", url->userid); + if (url->auth_type != NULL) { + str_append(urlstr, ";AUTH="); + uri_append_user_data(urlstr, ";:", url->auth_type); + } + str_append_c(urlstr, '@'); + } + + /* server */ + uri_append_host(urlstr, &url->host); + uri_append_port(urlstr, url->port); + + /* Older syntax (RFC 2192) requires this slash at all times */ + str_append_c(urlstr, '/'); + + /* mailbox */ + if (url->mailbox != NULL) + imap_url_append_mailbox(url, urlstr); + return str_c(urlstr); +} + +const char * +imap_url_add_urlauth(const char *rumpurl, const char *mechanism, + const unsigned char *token, size_t token_len) +{ + return t_strconcat(rumpurl, ":", t_str_lcase(mechanism), ":", + binary_to_hex(token, token_len), NULL); +} |