#include "lib.h"
#include "array.h"
#include "str.h"
#include "str-sanitize.h"
#include "rfc2822.h"
#include "sieve-common.h"
#include "sieve-error.h"
#include "sieve-address.h"
#include "sieve-message.h"
#include "uri-mailto.h"
#define uri_mailto_error(PARSER, ...) \
sieve_error((PARSER)->ehandler, NULL, "invalid mailto URI: " __VA_ARGS__ )
#define uri_mailto_warning(PARSER, ...) \
sieve_warning((PARSER)->ehandler, NULL, "mailto URI: " __VA_ARGS__ )
struct uri_mailto_parser {
pool_t pool;
struct sieve_error_handler *ehandler;
struct uri_mailto *uri;
const char **reserved_headers;
const char **unique_headers;
int max_recipients;
int max_headers;
};
static inline bool uri_mailto_header_is_reserved
(struct uri_mailto_parser *parser, const char *field_name)
{
const char **hdr = parser->reserved_headers;
if ( hdr == NULL ) return FALSE;
while ( *hdr != NULL ) {
if ( strcasecmp(field_name, *hdr) == 0 )
return TRUE;
hdr++;
}
return FALSE;
}
static inline bool uri_mailto_header_is_unique
(struct uri_mailto_parser *parser, const char *field_name)
{
const char **hdr = parser->unique_headers;
if ( hdr == NULL ) return FALSE;
while ( *hdr != NULL ) {
if ( strcasecmp(field_name, *hdr) == 0 )
return TRUE;
hdr++;
}
return FALSE;
}
static const char _qchar_lookup[256] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };
static inline bool _is_qchar(unsigned char c)
{
return _qchar_lookup[c];
}
static inline int _decode_hex_digit(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 (int) digit - '0';
case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
return (int) digit - 'a' + 0x0a;
case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
return (int) digit - 'A' + 0x0A;
}
return -1;
}
static bool _parse_hex_value(const char **in, char *out)
{
int value, digit;
if ( (digit=_decode_hex_digit((unsigned char) **in)) < 0 )
return FALSE;
value = digit << 4;
(*in)++;
if ( (digit=_decode_hex_digit((unsigned char) **in)) < 0 )
return FALSE;
value |= digit;
(*in)++;
if ( value == 0 )
return FALSE;
*out = (char) value;
return TRUE;
}
static bool uri_mailto_add_valid_recipient
(struct uri_mailto_parser *parser, string_t *recipient, bool cc)
{
struct uri_mailto *uri = parser->uri;
struct uri_mailto_recipient *new_recipient;
struct uri_mailto_recipient *rcpts;
unsigned int count, i;
const char *error;
const char *normalized;
if ( (normalized=sieve_address_normalize(recipient, &error)) == NULL ) {
uri_mailto_error(parser, "invalid recipient '%s': %s",
str_sanitize(str_c(recipient), 80), error);
return FALSE;
}
if ( uri != NULL ) {
rcpts = array_get_modifiable(&uri->recipients, &count);
if ( parser->max_recipients > 0 && (int)count >= parser->max_recipients ) {
if ( (int)count == parser->max_recipients) {
uri_mailto_warning(parser,
"more than the maximum %u recipients specified; "
"rest is discarded", parser->max_recipients);
}
return TRUE;
}
for ( i = 0; i < count; i++ ) {
if ( sieve_address_compare(rcpts[i].normalized, normalized, TRUE) == 0 )
{
rcpts[i].carbon_copy = ( rcpts[i].carbon_copy && cc );
uri_mailto_warning(parser, "ignored duplicate recipient '%s'",
str_sanitize(str_c(recipient), 80));
return TRUE;
}
}
new_recipient = array_append_space(&uri->recipients);
new_recipient->carbon_copy = cc;
new_recipient->full = p_strdup(parser->pool, str_c(recipient));
new_recipient->normalized = p_strdup(parser->pool, normalized);
}
return TRUE;
}
static bool uri_mailto_parse_recipients
(struct uri_mailto_parser *parser, const char **uri_p)
{
string_t *to = t_str_new(128);
const char *p = *uri_p;
if ( *p == '\0' || *p == '?' )
return TRUE;
while ( *p != '\0' && *p != '?' ) {
if ( *p == '%' ) {
char ch;
p++;
if ( !_parse_hex_value(&p, &ch) ) {
uri_mailto_error(parser, "invalid %% encoding");
return FALSE;
}
if ( ch == ',' ) {
if ( !uri_mailto_add_valid_recipient(parser, to, FALSE) )
return FALSE;
str_truncate(to, 0);
} else {
str_append_c(to, ch);
}
} else {
if ( *p == ':' || *p == ';' || *p == ',' || !_is_qchar(*p) ) {
uri_mailto_error
(parser, "invalid character '%c' in 'to' part", *p);
return FALSE;
}
str_append_c(to, *p);
p++;
}
}
if ( *p != '\0' ) p++;
if ( !uri_mailto_add_valid_recipient(parser, to, FALSE) )
return FALSE;
*uri_p = p;
return TRUE;
}
static bool uri_mailto_parse_header_recipients
(struct uri_mailto_parser *parser, string_t *rcpt_header, bool cc)
{
string_t *to = t_str_new(128);
const char *p = (const char *) str_data(rcpt_header);
const char *pend = p + str_len(rcpt_header);
while ( p < pend ) {
if ( *p == ',' ) {
if ( !uri_mailto_add_valid_recipient(parser, to, cc) )
return FALSE;
str_truncate(to, 0);
} else {
str_append_c(to, *p);
}
p++;
}
if ( !uri_mailto_add_valid_recipient(parser, to, cc) )
return FALSE;
return TRUE;
}
static bool uri_mailto_header_is_duplicate
(struct uri_mailto_parser *parser, const char *field_name)
{
struct uri_mailto *uri = parser->uri;
if ( uri == NULL ) return FALSE;
if ( uri_mailto_header_is_unique(parser, field_name) ) {
const struct uri_mailto_header_field *hdrs;
unsigned int count, i;
hdrs = array_get(&uri->headers, &count);
for ( i = 0; i < count; i++ ) {
if ( strcasecmp(hdrs[i].name, field_name) == 0 )
return TRUE;
}
}
return FALSE;
}
static bool uri_mailto_parse_headers
(struct uri_mailto_parser *parser, const char **uri_p)
{
struct uri_mailto *uri = parser->uri;
unsigned int header_count = 0;
string_t *field = t_str_new(128);
const char *p = *uri_p;
while ( *p != '\0' ) {
enum {
_HNAME_IGNORED,
_HNAME_GENERIC,
_HNAME_TO,
_HNAME_CC,
_HNAME_SUBJECT,
_HNAME_BODY
} hname_type = _HNAME_GENERIC;
struct uri_mailto_header_field *hdrf = NULL;
const char *field_name;
while ( *p != '\0' && *p != '=' ) {
char ch = *p;
p++;
if ( ch == '%' ) {
if ( !_parse_hex_value(&p, &ch) ) {
uri_mailto_error(parser, "invalid %% encoding");
return FALSE;
}
} else if ( ch != '=' && !_is_qchar(ch) ) {
uri_mailto_error
(parser, "invalid character '%c' in header field name part", ch);
return FALSE;
}
str_append_c(field, ch);
}
if ( *p != '\0' ) p++;
if ( !rfc2822_header_field_name_verify(str_c(field), str_len(field)) ) {
uri_mailto_error(parser, "invalid header field name");
return FALSE;
}
if ( parser->max_headers > -1 &&
(int)header_count >= parser->max_headers ) {
if ( (int)header_count == parser->max_headers ) {
uri_mailto_warning(parser, "more than the maximum %u headers specified; "
"rest is discarded", parser->max_headers);
}
hname_type = _HNAME_IGNORED;
} else {
field_name = str_c(field);
if ( strcasecmp(field_name, "to") == 0 )
hname_type = _HNAME_TO;
else if ( strcasecmp(field_name, "cc") == 0 )
hname_type = _HNAME_CC;
else if ( strcasecmp(field_name, "subject") == 0 )
hname_type = _HNAME_SUBJECT;
else if ( strcasecmp(field_name, "body") == 0 )
hname_type = _HNAME_BODY;
else if ( !uri_mailto_header_is_reserved(parser, field_name) ) {
if ( uri != NULL ) {
if ( !uri_mailto_header_is_duplicate(parser, field_name) ) {
hdrf = array_append_space(&uri->headers);
hdrf->name = p_strdup(parser->pool, field_name);
} else {
uri_mailto_warning(parser,
"ignored duplicate for unique header field '%s'",
str_sanitize(field_name, 32));
hname_type = _HNAME_IGNORED;
}
} else {
hname_type = _HNAME_IGNORED;
}
} else {
uri_mailto_warning(parser, "ignored reserved header field '%s'",
str_sanitize(field_name, 32));
hname_type = _HNAME_IGNORED;
}
}
header_count++;
str_truncate(field, 0);
while ( *p != '\0' && *p != '&' ) {
char ch = *p;
p++;
if ( ch == '%' ) {
if ( !_parse_hex_value(&p, &ch) ) {
uri_mailto_error(parser, "invalid %% encoding");
return FALSE;
}
} else if ( ch != '=' && !_is_qchar(ch) ) {
uri_mailto_error
(parser, "invalid character '%c' in header field value part", ch);
return FALSE;
}
str_append_c(field, ch);
}
if ( *p != '\0' ) p++;
if ( hname_type == _HNAME_BODY ) {
} else {
if ( !rfc2822_header_field_body_verify(str_c(field), str_len(field)) ) {
uri_mailto_error(parser, "invalid header field body");
return FALSE;
}
}
switch ( hname_type ) {
case _HNAME_IGNORED:
break;
case _HNAME_TO:
if ( !uri_mailto_parse_header_recipients(parser, field, FALSE) )
return FALSE;
break;
case _HNAME_CC:
if ( !uri_mailto_parse_header_recipients(parser, field, TRUE) )
return FALSE;
break;
case _HNAME_SUBJECT:
if ( uri != NULL ) {
if ( uri->subject == NULL )
uri->subject = p_strdup(parser->pool, str_c(field));
else
uri_mailto_warning(parser, "ignored duplicate subject field");
}
break;
case _HNAME_BODY:
if ( uri != NULL ) {
if ( uri->body == NULL )
uri->body = p_strdup(parser->pool, str_c(field));
else
uri_mailto_warning(parser, "ignored duplicate body field");
}
break;
case _HNAME_GENERIC:
if ( uri != NULL && hdrf != NULL )
hdrf->body = p_strdup(parser->pool, str_c(field));
break;
}
str_truncate(field, 0);
}
if ( *p != '\0' ) p++;
*uri_p = p;
return TRUE;
}
static bool uri_mailto_parse_uri
(struct uri_mailto_parser *parser, const char *uri_body)
{
const char *p = uri_body;
if ( !uri_mailto_parse_recipients(parser, &p) )
return FALSE;
while ( *p != '\0' ) {
if ( !uri_mailto_parse_headers(parser, &p) )
return FALSE;
}
return TRUE;
}
bool uri_mailto_validate
(const char *uri_body, const char **reserved_headers,
const char **unique_headers, int max_recipients, int max_headers,
struct sieve_error_handler *ehandler)
{
struct uri_mailto_parser parser;
memset(&parser, 0, sizeof(parser));
parser.ehandler = ehandler;
parser.max_recipients = max_recipients;
parser.max_headers = max_headers;
parser.reserved_headers = reserved_headers;
parser.unique_headers = unique_headers;
if ( ehandler != NULL ) {
parser.pool = pool_datastack_create();
parser.uri = p_new(parser.pool, struct uri_mailto, 1);
p_array_init(&parser.uri->recipients, parser.pool, max_recipients);
p_array_init(&parser.uri->headers, parser.pool, max_headers);
}
if ( !uri_mailto_parse_uri(&parser, uri_body) )
return FALSE;
if ( ehandler != NULL ) {
if ( array_count(&parser.uri->recipients) == 0 )
uri_mailto_warning(&parser, "notification URI specifies no recipients");
}
return TRUE;
}
struct uri_mailto *uri_mailto_parse
(const char *uri_body, pool_t pool, const char **reserved_headers,
const char **unique_headers, int max_recipients, int max_headers,
struct sieve_error_handler *ehandler)
{
struct uri_mailto_parser parser;
parser.pool = pool;
parser.ehandler = ehandler;
parser.max_recipients = max_recipients;
parser.max_headers = max_headers;
parser.reserved_headers = reserved_headers;
parser.unique_headers = unique_headers;
parser.uri = p_new(pool, struct uri_mailto, 1);
p_array_init(&parser.uri->recipients, pool, max_recipients);
p_array_init(&parser.uri->headers, pool, max_headers);
if ( !uri_mailto_parse_uri(&parser, uri_body) )
return FALSE;
if ( ehandler != NULL ) {
if ( array_count(&parser.uri->recipients) == 0 )
uri_mailto_warning(&parser, "notification URI specifies no recipients");
}
return parser.uri;
}