imap-url.c revision b4167b174b4eeebceaf80e240635e601f0a9860c
/* Copyright (c) 2010-2016 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;
unsigned int relative:1;
};
static int
{
/* [IMAP4] RFC3501, Section 9
*
* number = 1*DIGIT
* ; Unsigned 32-bit integer
* ; (0 <= n < 4,294,967,296)
*/
return 1;
return -1;
}
"Value '%s' is not a valid IMAP number", data);
return -1;
}
static int
{
/* Syntax for big (uoff_t) numbers. Not strictly IMAP syntax, but this
is handled similarly for Dovecot IMAP FETCH BODY partial <.>
implementation. */
return 1;
return -1;
}
"Value '%s' is not a valid IMAP number", data);
return -1;
}
{
struct uri_authority auth;
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 */
return ret;
/* 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.
*/
return -1;
}
/* iuserinfo = enc-user [iauth] / [enc-user] iauth */
const char *p, *uend;
/* Scan for ";AUTH=" */
if (*p == ';')
break;
/* check for unallowed userinfo characters */
if (*p == ':') {
return -1;
}
}
uend = p;
if (*p == ';') {
"Stray ';' in userinfo `%s'",
return -1;
}
for (p += 6; *p != '\0'; p++) {
if (*p == ';' || *p == ':') {
return -1;
}
}
}
/* enc-user */
return -1;
}
/* ( "*" / enc-auth-type ) */
if (*uend == ';') {
p = uend + 6;
if (*p == '\0') {
return -1;
}
return -1;
}
}
}
}
return 1;
}
static int
{
const char *p, *q, *data;
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 */
return -1;
}
return -1;
}
urlext = p;
}
}
/* ";URLAUTH=" access */
return -1;
}
return 0;
}
urlext += 9;
return -1;
}
if (url_parser->relative) {
return -1;
}
if (len == 0) {
return -1;
}
} else if (p == urlext) {
return -1;
}
/* parse access */
/* application */
}
} else {
/* application "+" enc-user */
if (urlext == q) {
return -1;
}
if (q+1 == p) {
"Empty URLAUTH access user for `%s' application",
t_strdup_until(urlext, q));
return -1;
}
return -1;
}
}
/* get rump url */
} else {
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') {
return -1;
}
return -1;
}
if (p == q) {
return -1;
}
/* get mechanism */
}
/* enc-urlauth = 32*HEXDIG */
q = p+1;
if (strlen(q) < 32) {
return -1;
}
if (hex_to_binary(q, uauth_token) < 0) {
return -1;
}
}
return 1;
}
static int
bool *is_messagelist_r)
{
const char *const *segment;
bool have_partial = 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.
*/
/* Resolve relative URI path; determine what to copy from the base URI */
/* /;PARTIAL= */
}
/* /;SECTION= */
/* determine what to retain from base section path */
if (*p =='/' && --rel <= 0) break;
}
}
}
/* /;UID= */
}
/* mailbox has implicit trailing '/' */
rel--;
/* determine what to retain from base mailbox path */
if (*p =='/') {
uidvalidity = 0;
if (--rel <= 0)
break;
}
}
if (p[-1] == '/')
}
}
}
/* Scan for last mailbox-ref segment */
p = 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 */
break;
if (**segment != '\0') {
return -1;
}
segment++;
}
/* Handle ';' */
if (p != NULL) {
/* [uidvalidity] */
/* append last bit of mailbox */
if (*segment != p) {
return -1;
}
/* ";UIDVALIDITY=" nz-number */
return -1;
}
/* nz-number */
if (p[13] == '\0') {
return -1;
}
return -1;
if (uidvalidity == 0) {
return -1;
}
segment++;
} else if (p != *segment) {
return -1;
}
}
/* iuid */
/* ";UID=" nz-number */
/* not the last segment, so it cannot be extension like iurlauth */
return -1;
}
urlext = p;
}
/* nz-number */
if (*value == '\0') {
return -1;
}
return -1;
if (uid == 0) {
return -1;
}
segment++;
}
}
/* [isection] [ipartial] */
/* [isection] */
/* ";SECTION=" enc-section */
} else {
}
/* enc-section can contain slashes, so we merge path segments until one
contains ';' */
if (*value != '\0') {
return -1;
}
segment++;
break;
}
if (p != NULL) {
/* found ';' */
if (p != value) {
/* it is not at the beginning of the path segment */
/* not the last segment, so it cannot be extension like iurlauth */
return -1;
}
urlext = p;
return -1;
segment++;
}
}
return -1;
}
}
/* [ipartial] */
have_partial = TRUE;
/* ";PARTIAL=" partial-range */
urlext = p;
}
if (*value == '\0') {
return -1;
}
/* partial-range = number ["." nz-number] */
if (p[1] == '\0') {
return -1;
}
return -1;
if (partial_size == 0) {
return -1;
}
if (*value == '\0') {
return -1;
}
}
return -1;
segment++;
}
}
"Unexpected IMAP URL path segment: `%s'",
return -1;
}
}
}
/* ";" {...} at end of URL */
/* [iurlauth] */
return ret;
else if (ret == 0) {
/* something else */
"Unrecognized IMAP URL extension: %s",
return -1;
}
}
if (is_messagelist_r != NULL)
*is_messagelist_r = (uid == 0);
}
return 1;
}
{
const char *const *path;
bool is_messagelist = FALSE;
bool have_scheme = FALSE;
int relative;
const char *query;
/*
* 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:" */
const char *scheme;
} else {
return FALSE;
}
have_scheme = TRUE;
}
} else {
have_scheme = TRUE;
}
/* "//" iserver */
return FALSE;
if (have_scheme && sret == 0) {
return FALSE;
}
if (sret > 0 &&
return FALSE;
}
/* ipath-query = ["/" [ icommand ]] ; excludes `[ "?" enc-search ]` */
return FALSE;
/* Relative urls are only valid when we have a base url */
if (sret == 0) {
return FALSE;
}
}
/* Parse path, i.e. `[ icommand ]` from `*( "/" segment )` */
&is_messagelist)) < 0)
return FALSE;
}
/* [ "?" enc-search ] */
if (ret < 0)
return FALSE;
if (!is_messagelist) {
"Search query part only valid for messagelist-type IMAP URL";
return FALSE;
} else if (*query == '\0') {
return FALSE;
}
return FALSE;
}
}
/* IMAP URL has no fragment */
if (ret == 1)
return FALSE;
}
/* must be at end of URL now */
return TRUE;
}
/* Public API */
enum imap_url_parse_flags flags,
{
struct imap_url_parser url_parser;
/* base != NULL indicates whether relative URLs are allowed. However, certain
if (!imap_url_do_parse(&url_parser)) {
return -1;
}
return 0;
}
/*
* IMAP URL construction
*/
static void
{
if (url->uidvalidity != 0)
/* message list */
}
} else {
/* message part */
}
if (url->have_partial) {
if (url->partial_size == 0) {
} else {
url->partial_size);
}
}
/* urlauth */
}
}
}
}
}
{
/* scheme */
/* user */
}
}
/* server */
/* Older syntax (RFC 2192) requires this slash at all times */
/* mailbox */
}
const char *
{
}