bcb4e51a409d94ae670de96afb8483a4f7855294Stephan Bosch/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * IMAP URL parsing
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan BoschIMAP URL Grammar overview
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan BoschRFC5092 Section 11:
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Boschimapurl = "imap://" iserver ipath-query
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch ; Defines an absolute IMAP URL
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Boschiserver = [iuserinfo "@"] host [ ":" port ]
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch ; This is the same as "authority" defined in [URI-GEN].
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Boschiuserinfo = enc-user [iauth] / [enc-user] iauth
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch ; conforms to the generic syntax of "userinfo" as
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch ; defined in [URI-GEN].
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Boschenc-user = 1*achar
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch ; %-encoded version of [IMAP4] authorization identity or
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Boschiauth = ";AUTH=" ( "*" / enc-auth-type )
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Boschenc-auth-type = 1*achar
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch ; %-encoded version of [IMAP4] "auth-type"
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Boschipath-query = ["/" [ icommand ]]
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch ; Corresponds to "path-abempty [ "?" query ]" in
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Boschicommand = imessagelist /
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch imessagepart [iurlauth]
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Boschimessagelist = imailbox-ref [ "?" enc-search ]
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch ; "enc-search" is [URI-GEN] "query".
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Boschimessagepart = imailbox-ref iuid [isection] [ipartial]
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Boschimailbox-ref = enc-mailbox [uidvalidity]
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Boschuidvalidity = ";UIDVALIDITY=" nz-number
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch ; See [IMAP4] for "nz-number" definition
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Boschiuid = "/" iuid-only
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Boschiuid-only = ";UID=" nz-number
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch ; See [IMAP4] for "nz-number" definition
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Boschisection = "/" isection-only
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Boschisection-only = ";SECTION=" enc-section
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Boschipartial = "/" ipartial-only
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Boschipartial-only = ";PARTIAL=" partial-range
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Boschenc-search = 1*bchar
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch ; %-encoded version of [IMAPABNF]
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch ; "search-program". Note that IMAP4
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch ; literals may not be used in
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch ; a "search-program", i.e., only
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch ; quoted or non-synchronizing
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch ; literals (if the server supports
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch ; LITERAL+ [LITERAL+]) are allowed.
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Boschenc-mailbox = 1*bchar
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch ; %-encoded version of [IMAP4] "mailbox"
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Boschenc-section = 1*bchar
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch ; %-encoded version of [IMAP4] "section-spec"
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Boschpartial-range = number ["." nz-number]
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch ; partial FETCH. The first number is
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch ; the offset of the first byte,
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch ; the second number is the length of
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch ; the fragment.
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Boschbchar = achar / ":" / "@" / "/"
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Boschachar = uchar / "&" / "="
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch ;; Same as [URI-GEN] 'unreserved / sub-delims /
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch ;; pct-encoded', but ";" is disallowed.
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Boschuchar = unreserved / sub-delims-sh / pct-encoded
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Boschsub-delims-sh = "!" / "$" / "'" / "(" / ")" /
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch "*" / "+" / ","
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch ;; Same as [URI-GEN] sub-delims,
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch ;; but without ";", "&" and "=".
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan BoschThe following rules are only used in the presence of the IMAP
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch[URLAUTH] extension:
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Boschauthimapurl = "imap://" iserver "/" imessagepart
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch ; Same as "imapurl" when "[icommand]" is
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch ; "imessagepart"
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Boschauthimapurlfull = authimapurl iurlauth
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch ; Same as "imapurl" when "[icommand]" is
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch ; "imessagepart iurlauth"
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Boschauthimapurlrump = authimapurl iurlauth-rump
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Boschiurlauth = iurlauth-rump iua-verifier
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Boschenc-urlauth = 32*HEXDIG
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Boschiua-verifier = ":" uauth-mechanism ":" enc-urlauth
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Boschiurlauth-rump = [expire] ";URLAUTH=" access
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Boschaccess = ("submit+" enc-user) / ("user+" enc-user) /
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch "authuser" / "anonymous"
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Boschexpire = ";EXPIRE=" date-time
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch ; date-time is defined in [DATETIME]
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Boschuauth-mechanism = "INTERNAL" / 1*(ALPHA / DIGIT / "-" / ".")
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch ; Case-insensitive.
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch[URI-GEN] RFC3986 Appendix A:
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * Imap URL parser
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Boschimap_url_parse_number(struct uri_parser *parser, const char *data,
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch /* [IMAP4] RFC3501, Section 9
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * number = 1*DIGIT
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * ; Unsigned 32-bit integer
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * ; (0 <= n < 4,294,967,296)
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch "Value '%s' is not a valid IMAP number", data);
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Boschimap_url_parse_offset(struct uri_parser *parser, const char *data,
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch /* Syntax for big (uoff_t) numbers. Not strictly IMAP syntax, but this
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch is handled similarly for Dovecot IMAP FETCH BODY partial <.>
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch implementation. */
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch "Value '%s' is not a valid IMAP number", data);
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Boschstatic int imap_url_parse_iserver(struct imap_url_parser *url_parser)
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch struct uri_parser *parser = &url_parser->parser;
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch /* imapurl = "imap://" iserver {...}
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * inetwork-path = "//" iserver {...}
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * iserver = [iuserinfo "@"] host [":" port]
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * ; This is the same as "authority" defined
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * ; in [URI-GEN].
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * iuserinfo = enc-user [iauth] / [enc-user] iauth
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * ; conforms to the generic syntax of "userinfo" as
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * ; defined in [URI-GEN].
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * enc-user = 1*achar
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * ; %-encoded version of [IMAP4] authorization identity or
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * ; "userid".
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * iauth = ";AUTH=" ( "*" / enc-auth-type )
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * enc-auth-type = 1*achar
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * ; %-encoded version of [IMAP4] "auth-type"
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch /* "//" iserver */
d3b0b5d2389acc43c75b63d2960daf82cf1f8aa7Stephan Bosch if ((ret = uri_parse_slashslash_host_authority
8d2d2780c9e71581ff9c3e8bce527b492c295ec1Stephan Bosch if (auth.host.name == NULL || *auth.host.name == '\0') {
f3cf2f02155c4bac23fd50f0de96c0cae9c46478Stephan Bosch /* This situation is not documented anywhere, but it is not
f3cf2f02155c4bac23fd50f0de96c0cae9c46478Stephan Bosch currently useful either and potentially problematic if not
f3cf2f02155c4bac23fd50f0de96c0cae9c46478Stephan Bosch handled explicitly everywhere. So, it is denied hier for now.
f3cf2f02155c4bac23fd50f0de96c0cae9c46478Stephan Bosch parser->error = "IMAP URL does not allow empty host identifier";
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch /* iuserinfo = enc-user [iauth] / [enc-user] iauth */
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch /* Scan for ";AUTH=" */
4c8646ca7a1bfe3e5a43f1f3e329f8ff8c24d851Stephan Bosch for (p = auth.enc_userinfo; *p != '\0'; p++) {
4c8646ca7a1bfe3e5a43f1f3e329f8ff8c24d851Stephan Bosch if (*p == ';')
4c8646ca7a1bfe3e5a43f1f3e329f8ff8c24d851Stephan Bosch /* check for unallowed userinfo characters */
4c8646ca7a1bfe3e5a43f1f3e329f8ff8c24d851Stephan Bosch if (*p == ':') {
4c8646ca7a1bfe3e5a43f1f3e329f8ff8c24d851Stephan Bosch "Stray ':' in userinfo `%s'", auth.enc_userinfo);
4c8646ca7a1bfe3e5a43f1f3e329f8ff8c24d851Stephan Bosch if (*p == ';') {
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch "Stray ';' in userinfo `%s'",
4c8646ca7a1bfe3e5a43f1f3e329f8ff8c24d851Stephan Bosch "Stray '%c' in userinfo `%s'", *p, auth.enc_userinfo);
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch /* enc-user */
4c8646ca7a1bfe3e5a43f1f3e329f8ff8c24d851Stephan Bosch if (url != NULL && uend > auth.enc_userinfo) {
4c8646ca7a1bfe3e5a43f1f3e329f8ff8c24d851Stephan Bosch if (!uri_data_decode(parser, auth.enc_userinfo, uend, &data))
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch /* ( "*" / enc-auth-type ) */
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch if (*p == '\0') {
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch parser->error = "Empty auth-type value after ';AUTH='";
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch url->auth_type = p_strdup(parser->pool, data);
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Boschimap_url_parse_urlauth(struct imap_url_parser *url_parser, const char *urlext)
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch struct uri_parser *parser = &url_parser->parser;
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch /* iurlauth = iurlauth-rump iua-verifier
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * enc-urlauth = 32*HEXDIG
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * iua-verifier = ":" uauth-mechanism ":" enc-urlauth
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * iurlauth-rump = [expire] ";URLAUTH=" access
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * access = ("submit+" enc-user) / ("user+" enc-user) /
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * "authuser" / "anonymous"
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * expire = ";EXPIRE=" date-time
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * ; date-time is defined in [DATETIME]
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * uauth-mechanism = "INTERNAL" / 1*(ALPHA / DIGIT / "-" / ".")
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * ; Case-insensitive.
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch /* ";EXPIRE=" date-time */
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch if (strncasecmp(urlext, ";EXPIRE=", 8) == 0) {
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch if ((url_parser->flags & IMAP_URL_PARSE_ALLOW_URLAUTH) == 0) {
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch parser->error = "`;EXPIRE=' is not allowed in this context";
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch if (!iso8601_date_parse((const unsigned char *)urlext+8,
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch parser->error = "invalid date-time for `;EXPIRE='";
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch /* ";URLAUTH=" access */
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch if (strncasecmp(urlext, ";URLAUTH=", 9) != 0) {
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch parser->error = "`;EXPIRE=' without `;URLAUTH='";
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch if ((url_parser->flags & IMAP_URL_PARSE_ALLOW_URLAUTH) == 0) {
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch parser->error = "`;URLAUTH=' is not allowed in this context";
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch parser->error = "IMAP URLAUTH requires absolute URL";
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch parser->error = "Missing URLAUTH access specifier";
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch } else if (p == urlext) {
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch parser->error = "Empty URLAUTH access specifier";
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch /* parse access */
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch /* application */
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch /* application "+" enc-user */
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch parser->error = "Empty URLAUTH access application";
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch if (q+1 == p) {
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch "Empty URLAUTH access user for `%s' application",
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch url->uauth_access_user = p_strdup(parser->pool, data);
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch /* get rump url */
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch if ((url_parser->flags & IMAP_URL_PARSE_SCHEME_EXTERNAL) == 0) {
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch url->uauth_rumpurl = p_strdup_until(parser->pool,
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch url->uauth_rumpurl = p_strconcat(parser->pool, "imap:",
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch t_strdup_until(parser->begin, parser->end-strlen(p)),
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch if (*p == '\0') {
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch /* rump url; caller should check whether this is appropriate */
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch /* iua-verifier = ":" uauth-mechanism ":" enc-urlauth */
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch if (*q == '\0') {
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch if ((p = strchr(q, ':')) == NULL || p[1] == '\0') {
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch if (p == q) {
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch /* get mechanism */
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch url->uauth_mechanism = p_strdup_until(parser->pool, q, p);
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch /* enc-urlauth = 32*HEXDIG */
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Boschimap_url_parse_path(struct imap_url_parser *url_parser,
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch struct uri_parser *parser = &url_parser->parser;
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch bool mailbox_endslash = FALSE, section_endslash = FALSE;
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch /* icommand = imessagelist /
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * imessagepart [iurlauth]
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * imessagelist = imailbox-ref [ "?" enc-search ]
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * ; "enc-search" is [URI-GEN] "query".
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * imessagepart = imailbox-ref iuid [isection] [ipartial]
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * imailbox-ref = enc-mailbox [uidvalidity]
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * uidvalidity = ";UIDVALIDITY=" nz-number
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * iuid = "/" iuid-only
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * iuid-only = ";UID=" nz-number
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * ; See [IMAP4] for "nz-number" definition
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * isection = "/" isection-only
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * isection-only = ";SECTION=" enc-section
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * ipartial = "/" ipartial-only
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * ipartial-only = ";PARTIAL=" partial-range
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * enc-mailbox = 1*bchar
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * ; %-encoded version of [IMAP4] "mailbox"
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * enc-section = 1*bchar
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * ; %-encoded version of [IMAP4] "section-spec"
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * partial-range = number ["." nz-number]
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * ; partial FETCH. The first number is
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * ; the offset of the first byte,
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * ; the second number is the length of
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * ; the fragment.
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch /* IMAP URL syntax is quite horrible to parse. It relies upon the
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch generic URI path resolution, but the icommand syntax also relies on
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch ';' separators. We use the generic URI path parse functions to
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch adhere to the URI path resolution rules and glue back together path
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch segments when these are part of the same (mailbox or section) value.
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch /* Resolve relative URI path; determine what to copy from the base URI */
8d35582f2577c64517b2341c5d6477c7010e0a0cPhil Carmody if (url != NULL && url_parser->base != NULL && relative > 0) {
dc78180b54a05d5736d0e0e444cba0332265eb62Phil Carmody const struct imap_url *base = url_parser->base;
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch /* /;PARTIAL= */
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch /* /;SECTION= */
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch /* determine what to retain from base section path */
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch str_append_n(section, base->section, p-base->section);
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch /* /mail/box;UIDVALIDITY= */
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch /* mailbox has implicit trailing '/' */
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch if (p[-1] != '/' && base->uid == 0 && rel > 0)
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch /* determine what to retain from base mailbox path */
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch if (*p =='/') {
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch /* Scan for last mailbox-ref segment */
62aa68310d6f42467ca26880f678173bf1d26a83Stephan Bosch if (relative == 0 || (!have_partial && section == NULL)) {
62aa68310d6f42467ca26880f678173bf1d26a83Stephan Bosch /* ';' must be pct-encoded; if it is not, this is
62aa68310d6f42467ca26880f678173bf1d26a83Stephan Bosch either the last mailbox-ref path segment containing
62aa68310d6f42467ca26880f678173bf1d26a83Stephan Bosch ';UIDVALIDITY=' or the subsequent iuid ';UID=' path
62aa68310d6f42467ca26880f678173bf1d26a83Stephan Bosch if (!uri_data_decode(parser, *segment, NULL, &value))
62aa68310d6f42467ca26880f678173bf1d26a83Stephan Bosch /* Handle ';' */
62aa68310d6f42467ca26880f678173bf1d26a83Stephan Bosch /* [uidvalidity] */
62aa68310d6f42467ca26880f678173bf1d26a83Stephan Bosch if (strncasecmp(p, ";UIDVALIDITY=", 13) == 0) {
62aa68310d6f42467ca26880f678173bf1d26a83Stephan Bosch /* append last bit of mailbox */
62aa68310d6f42467ca26880f678173bf1d26a83Stephan Bosch if (!uri_data_decode(parser, *segment, p, &value))
62aa68310d6f42467ca26880f678173bf1d26a83Stephan Bosch /* ";UIDVALIDITY=" nz-number */
62aa68310d6f42467ca26880f678173bf1d26a83Stephan Bosch parser->error = "Encountered stray ';' after UIDVALIDITY";
62aa68310d6f42467ca26880f678173bf1d26a83Stephan Bosch /* nz-number */
62aa68310d6f42467ca26880f678173bf1d26a83Stephan Bosch if (imap_url_parse_number(parser, p+13, &uidvalidity) <= 0)
62aa68310d6f42467ca26880f678173bf1d26a83Stephan Bosch } else if (p != *segment) {
62aa68310d6f42467ca26880f678173bf1d26a83Stephan Bosch parser->error = "Encountered stray ';' in mailbox reference";
62aa68310d6f42467ca26880f678173bf1d26a83Stephan Bosch if (*segment != NULL && strncasecmp(*segment, ";UID=", 5) == 0) {
62aa68310d6f42467ca26880f678173bf1d26a83Stephan Bosch /* ";UID=" nz-number */
62aa68310d6f42467ca26880f678173bf1d26a83Stephan Bosch /* not the last segment, so it cannot be extension like iurlauth */
62aa68310d6f42467ca26880f678173bf1d26a83Stephan Bosch parser->error = "Encountered stray ';' in UID path segment";
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch /* nz-number */
62aa68310d6f42467ca26880f678173bf1d26a83Stephan Bosch if (imap_url_parse_number(parser, value, &uid) <= 0)
62aa68310d6f42467ca26880f678173bf1d26a83Stephan Bosch /* [isection] [ipartial] */
62aa68310d6f42467ca26880f678173bf1d26a83Stephan Bosch /* [isection] */
62aa68310d6f42467ca26880f678173bf1d26a83Stephan Bosch /* ";SECTION=" enc-section */
62aa68310d6f42467ca26880f678173bf1d26a83Stephan Bosch /* enc-section can contain slashes, so we merge path segments until one
62aa68310d6f42467ca26880f678173bf1d26a83Stephan Bosch contains ';' */
62aa68310d6f42467ca26880f678173bf1d26a83Stephan Bosch if (!section_endslash && str_len(section) > 0)
62aa68310d6f42467ca26880f678173bf1d26a83Stephan Bosch if (!uri_data_decode(parser, value, NULL, &value))
62aa68310d6f42467ca26880f678173bf1d26a83Stephan Bosch /* found ';' */
62aa68310d6f42467ca26880f678173bf1d26a83Stephan Bosch /* it is not at the beginning of the path segment */
62aa68310d6f42467ca26880f678173bf1d26a83Stephan Bosch /* not the last segment, so it cannot be extension like iurlauth */
62aa68310d6f42467ca26880f678173bf1d26a83Stephan Bosch parser->error = "Encountered stray ';' in SECTION path segment";
62aa68310d6f42467ca26880f678173bf1d26a83Stephan Bosch if (!section_endslash && str_len(section) > 0)
62aa68310d6f42467ca26880f678173bf1d26a83Stephan Bosch if (!uri_data_decode(parser, value, NULL, &value))
62aa68310d6f42467ca26880f678173bf1d26a83Stephan Bosch /* [ipartial] */
62aa68310d6f42467ca26880f678173bf1d26a83Stephan Bosch /* ";PARTIAL=" partial-range */
62aa68310d6f42467ca26880f678173bf1d26a83Stephan Bosch /* partial-range = number ["." nz-number] */
62aa68310d6f42467ca26880f678173bf1d26a83Stephan Bosch if (imap_url_parse_offset(parser, p+1, &partial_size) <= 0)
62aa68310d6f42467ca26880f678173bf1d26a83Stephan Bosch parser->error = "PARTIAL size cannot be zero";
62aa68310d6f42467ca26880f678173bf1d26a83Stephan Bosch if (imap_url_parse_offset(parser,value, &partial_offset) <= 0)
62aa68310d6f42467ca26880f678173bf1d26a83Stephan Bosch if (urlext != NULL || **segment != '\0' || *(segment+1) != NULL ) {
62aa68310d6f42467ca26880f678173bf1d26a83Stephan Bosch "Unexpected IMAP URL path segment: `%s'",
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch /* ";" {...} at end of URL */
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch /* [iurlauth] */
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch if ((ret = imap_url_parse_urlauth(url_parser, urlext)) < 0)
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch else if (ret == 0) {
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch /* something else */
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch "Unrecognized IMAP URL extension: %s",
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch url->mailbox = p_strdup(parser->pool, str_c(mailbox));
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch url->section = p_strdup(parser->pool, str_c(section));
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Boschstatic bool imap_url_do_parse(struct imap_url_parser *url_parser)
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch struct uri_parser *parser = &url_parser->parser;
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * imapurl = "imap://" iserver ipath-query
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * ; Defines an absolute IMAP URL
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * iserver = [iuserinfo "@"] host [":" port]
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * ; This is the same as "authority" defined
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * ; in [URI-GEN].
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * ipath-query = ["/" [ icommand ]]
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * ; Corresponds to "path-abempty [ "?" query ]" in
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * ; [URI-GEN]
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * icommand = imessagelist /
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * imessagepart [iurlauth]
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * imessagelist = imailbox-ref [ "?" enc-search ]
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * ; "enc-search" is [URI-GEN] "query".
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * imessagepart = imailbox-ref iuid [isection] [ipartial]
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * enc-search = 1*bchar
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * ; %-encoded version of [IMAPABNF]
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * ; "search-program". Note that IMAP4
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * ; literals may not be used in
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * ; a "search-program", i.e., only
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * ; quoted or non-synchronizing
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * ; literals (if the server supports
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * ; LITERAL+ [LITERAL+]) are allowed.
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch /* "imap:" */
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch if ((url_parser->flags & IMAP_URL_PARSE_SCHEME_EXTERNAL) == 0) {
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch /* "//" iserver */
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch if ((sret = imap_url_parse_iserver(url_parser)) < 0)
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch parser->error = "Absolute IMAP URL requires `//' after `imap:'";
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch (url_parser->flags & IMAP_URL_PARSE_REQUIRE_RELATIVE) != 0) {
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch /* ipath-query = ["/" [ icommand ]] ; excludes `[ "?" enc-search ]` */
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch if ((ret = uri_parse_path(parser, &relative, &path)) < 0)
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch /* Relative urls are only valid when we have a base url */
dc78180b54a05d5736d0e0e444cba0332265eb62Phil Carmody const struct imap_url *base = url_parser->base;
66134fbce11778241ca9f8458ee2a0488a05bde0Stephan Bosch uri_host_copy(parser->pool, &url->host, &base->host);
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch url->userid = p_strdup_empty(parser->pool, base->userid);
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch url->auth_type = p_strdup_empty(parser->pool, base->auth_type);
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch /* Parse path, i.e. `[ icommand ]` from `*( "/" segment )` */
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch if ((ret = imap_url_parse_path(url_parser, path, relative,
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch /* [ "?" enc-search ] */
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch if ((ret = uri_parse_query(parser, &query)) != 0) {
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch "Search query part only valid for messagelist-type IMAP URL";
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch parser->error = "Empty IMAP URL search query not allowed";
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch if (!uri_data_decode(parser, query, NULL, &query))
bcd6c13936df11167350ac86598a781dce9038c3Stephan Bosch /* IMAP URL has no fragment */
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch if ((ret = uri_parse_fragment(parser, &query)) != 0) {
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch parser->error = "Fragment component not allowed in IMAP URL";
bcd6c13936df11167350ac86598a781dce9038c3Stephan Bosch /* must be at end of URL now */
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch/* Public API */
dc78180b54a05d5736d0e0e444cba0332265eb62Phil Carmodyint imap_url_parse(const char *url, const struct imap_url *base,
639bb36b12b9f9bb54c8bb1be50eac623622f8a0Timo Sirainen struct imap_url **url_r, const char **error_r)
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch /* base != NULL indicates whether relative URLs are allowed. However, certain
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch flags may also dictate whether relative URLs are allowed/required. */
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch i_assert((flags & IMAP_URL_PARSE_REQUIRE_RELATIVE) == 0 || base != NULL);
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch i_assert((flags & IMAP_URL_PARSE_SCHEME_EXTERNAL) == 0 || base == NULL);
639bb36b12b9f9bb54c8bb1be50eac623622f8a0Timo Sirainen uri_parser_init(&url_parser.parser, pool_datastack_create(), url);
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch * IMAP URL construction
4d955db590c3d76a631dfc5d37bcdf578a43e55aStephan Boschimap_url_append_mailbox(const struct imap_url *url, string_t *urlstr)
4d955db590c3d76a631dfc5d37bcdf578a43e55aStephan Bosch uri_append_path_data(urlstr, ";", url->mailbox);
4d955db590c3d76a631dfc5d37bcdf578a43e55aStephan Bosch str_printfa(urlstr, ";UIDVALIDITY=%u", url->uidvalidity);
4d955db590c3d76a631dfc5d37bcdf578a43e55aStephan Bosch /* message list */
4d955db590c3d76a631dfc5d37bcdf578a43e55aStephan Bosch uri_append_query_data(urlstr, ";", url->search_program);
4d955db590c3d76a631dfc5d37bcdf578a43e55aStephan Bosch /* message part */
4d955db590c3d76a631dfc5d37bcdf578a43e55aStephan Bosch uri_append_path_data(urlstr, ";", url->section);
4d955db590c3d76a631dfc5d37bcdf578a43e55aStephan Bosch str_printfa(urlstr, "%"PRIuUOFF_T".%"PRIuUOFF_T,
4d955db590c3d76a631dfc5d37bcdf578a43e55aStephan Bosch /* urlauth */
4d955db590c3d76a631dfc5d37bcdf578a43e55aStephan Bosch str_append(urlstr, iso8601_date_create(url->uauth_expire));
4d955db590c3d76a631dfc5d37bcdf578a43e55aStephan Bosch str_append(urlstr, url->uauth_access_application);
4d955db590c3d76a631dfc5d37bcdf578a43e55aStephan Boschconst char *imap_url_create(const struct imap_url *url)
4d955db590c3d76a631dfc5d37bcdf578a43e55aStephan Bosch if (url->userid != NULL || url->auth_type != NULL) {
4c8646ca7a1bfe3e5a43f1f3e329f8ff8c24d851Stephan Bosch uri_append_user_data(urlstr, ";:", url->userid);
4c8646ca7a1bfe3e5a43f1f3e329f8ff8c24d851Stephan Bosch uri_append_user_data(urlstr, ";:", url->auth_type);
4d955db590c3d76a631dfc5d37bcdf578a43e55aStephan Bosch /* Older syntax (RFC 2192) requires this slash at all times */
4d955db590c3d76a631dfc5d37bcdf578a43e55aStephan Bosch /* mailbox */
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Boschimap_url_add_urlauth(const char *rumpurl, const char *mechanism,
6ae6496c225238a2c55a8cd96744ad976c44a726Stephan Bosch return t_strconcat(rumpurl, ":", t_str_lcase(mechanism), ":",