#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <time.h>
#include "imapurl.h"
#include "xmalloc.h"
static const char hex[] = "0123456789ABCDEF";
#define XX 127
static const char index_hex[256] = {
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,XX,XX, XX,XX,XX,XX,
XX,10,11,12, 13,14,15,XX, XX,XX,XX,XX, XX,XX,XX,XX,
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
XX,10,11,12, 13,14,15,XX, XX,XX,XX,XX, XX,XX,XX,XX,
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
};
#define HEXCHAR(c) (index_hex[(unsigned char)(c)])
static const char urlunsafe[] = " \"#%&+:;<=>?@[\\]^`{|}";
static const char base64chars[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,";
#define UNDEFINED 64
#define UTF16MASK 0x03FFUL
#define UTF16SHIFT 10
#define UTF16BASE 0x10000UL
#define UTF16HIGHSTART 0xD800UL
#define UTF16HIGHEND 0xDBFFUL
#define UTF16LOSTART 0xDC00UL
#define UTF16LOEND 0xDFFFUL
static void MailboxToURL(char *dst, const char *src)
{
unsigned char c, i, bitcount;
unsigned long ucs4, utf16, bitbuf;
unsigned char base64[256], utf8[6];
memset(base64, UNDEFINED, sizeof (base64));
for (i = 0; i < sizeof (base64chars); ++i) {
base64[(int) base64chars[i]] = i;
}
while (*src != '\0') {
c = *src++;
if (c != '&' || *src == '-') {
if (c < ' ' || c > '~' || strchr(urlunsafe, c) != NULL) {
dst[0] = '%';
dst[1] = hex[c >> 4];
dst[2] = hex[c & 0x0f];
dst += 3;
} else {
*dst++ = c;
}
if (c == '&') ++src;
} else {
bitbuf = 0;
bitcount = 0;
ucs4 = 0;
while ((c = base64[(unsigned char) *src]) != UNDEFINED) {
++src;
bitbuf = (bitbuf << 6) | c;
bitcount += 6;
if (bitcount >= 16) {
bitcount -= 16;
utf16 = (bitcount ? bitbuf >> bitcount
: bitbuf) & 0xffff;
if
(utf16 >= UTF16HIGHSTART && utf16 <= UTF16HIGHEND) {
ucs4 = (utf16 - UTF16HIGHSTART) << UTF16SHIFT;
continue;
} else if
(utf16 >= UTF16LOSTART && utf16 <= UTF16LOEND) {
ucs4 += utf16 - UTF16LOSTART + UTF16BASE;
} else {
ucs4 = utf16;
}
if (ucs4 <= 0x7fUL) {
utf8[0] = ucs4;
i = 1;
} else if (ucs4 <= 0x7ffUL) {
utf8[0] = 0xc0 | (ucs4 >> 6);
utf8[1] = 0x80 | (ucs4 & 0x3f);
i = 2;
} else if (ucs4 <= 0xffffUL) {
utf8[0] = 0xe0 | (ucs4 >> 12);
utf8[1] = 0x80 | ((ucs4 >> 6) & 0x3f);
utf8[2] = 0x80 | (ucs4 & 0x3f);
i = 3;
} else {
utf8[0] = 0xf0 | (ucs4 >> 18);
utf8[1] = 0x80 | ((ucs4 >> 12) & 0x3f);
utf8[2] = 0x80 | ((ucs4 >> 6) & 0x3f);
utf8[3] = 0x80 | (ucs4 & 0x3f);
i = 4;
}
for (c = 0; c < i; ++c) {
dst[0] = '%';
dst[1] = hex[utf8[c] >> 4];
dst[2] = hex[utf8[c] & 0x0f];
dst += 3;
}
}
}
if (*src == '-') ++src;
}
}
*dst = '\0';
}
static int URLtoMailbox(char *dst, char *src)
{
unsigned int utf8pos = 0, utf8total, i, c, utf7mode, bitstogo, utf16flag;
unsigned long ucs4 = 0, bitbuf = 0;
utf7mode = 0;
utf8total = 0;
bitstogo = 0;
while ((c = (unsigned char)*src) != '\0') {
++src;
if (c == '%' && src[0] != '\0' && src[1] != '\0') {
c = HEXCHAR(src[0]);
i = HEXCHAR(src[1]);
if (c == XX || i == XX)
return -1;
else
c = (char)((c << 4) | i);
src += 2;
}
if (c >= ' ' && c <= '~') {
if (utf7mode) {
if (bitstogo) {
*dst++ = base64chars[(bitbuf << (6 - bitstogo)) & 0x3F];
}
*dst++ = '-';
utf7mode = 0;
bitstogo = bitbuf = 0;
}
*dst++ = c;
if (c == '&') {
*dst++ = '-';
}
continue;
}
if (!utf7mode) {
*dst++ = '&';
utf7mode = 1;
}
if (c < 0x80) {
ucs4 = c;
utf8total = 1;
} else if (utf8total) {
ucs4 = (ucs4 << 6) | (c & 0x3FUL);
if (++utf8pos < utf8total) {
continue;
}
} else {
utf8pos = 1;
if (c < 0xE0) {
utf8total = 2;
ucs4 = c & 0x1F;
} else if (c < 0xF0) {
utf8total = 3;
ucs4 = c & 0x0F;
} else {
utf8total = 4;
ucs4 = c & 0x03;
}
continue;
}
if ((ucs4 < 0x80 && utf8total > 1) ||
(ucs4 < 0x0800 && utf8total > 2) ||
(ucs4 < 0x00010000 && utf8total > 3) ||
(ucs4 < 0x00200000 && utf8total > 4) ||
(ucs4 < 0x04000000 && utf8total > 5) ||
(ucs4 < 0x80000000 && utf8total > 6)) {
utf8total = 0;
continue;
}
utf8total = 0;
do {
if (ucs4 >= UTF16BASE) {
ucs4 -= UTF16BASE;
bitbuf = (bitbuf << 16) | ((ucs4 >> UTF16SHIFT)
+ UTF16HIGHSTART);
ucs4 = (ucs4 & UTF16MASK) + UTF16LOSTART;
utf16flag = 1;
} else {
bitbuf = (bitbuf << 16) | ucs4;
utf16flag = 0;
}
bitstogo += 16;
while (bitstogo >= 6) {
bitstogo -= 6;
*dst++ = base64chars[(bitstogo ? (bitbuf >> bitstogo)
: bitbuf)
& 0x3F];
}
} while (utf16flag);
}
if (utf7mode) {
if (bitstogo) {
*dst++ = base64chars[(bitbuf << (6 - bitstogo)) & 0x3F];
}
*dst++ = '-';
}
*dst = '\0';
return 0;
}
static int decode_url(char *dst, char *src)
{
unsigned char c;
unsigned char i;
while ((c = (unsigned char)*src) != '\0') {
++src;
if (c == '%' && src[0] != '\0' && src[1] != '\0') {
c = HEXCHAR(src[0]);
i = HEXCHAR(src[1]);
if (c == XX || i == XX)
return -1;
else
c = (char)((c << 4) | i);
src += 2;
}
*dst++ = (char) c;
}
*dst = '\0';
return 0;
}
static const int numdays[] = {
31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};
#define isleap(year) (!((year) % 4) && (((year) % 100) || !((year) % 400)))
int imapurl_fromURL(struct imapurl *url, const char *s)
{
char *src;
int step = 0;
memset(url, 0, sizeof(struct imapurl));
url->freeme = xmalloc(3 * strlen(s) + 3);
src = strcpy(url->freeme, s);
if (src[0] == '{') {
char *se;
src++;
se = strchr(src, '}');
if (se == NULL) return -1;
*se = '\0';
url->server = src;
url->mailbox = se + 1;
} else {
int r;
char *se;
char *at;
char *mbox = NULL;
char *opt;
if (!strncmp(src, "imap://", 7)) {
src += 7;
se = strchr(src, '/');
if (se == NULL) return -1;
at = strchr(src, '@');
if (at) {
*at = '\0';
url->user = src;
r = decode_url((char *) url->user, (char *) url->user);
if (r) return r;
src = at + 1;
}
*se = '\0';
url->server = src;
src = mbox = ++se;
}
else {
if (*src == '/') src++;
mbox = src;
}
errno = 0;
while (src && (src = strchr(src, ';'))) {
unsigned long ul;
char *endp;
if (src[-1] == '/') src[-1] = '\0';
*src++ = '\0';
if (step == 0 && !strncasecmp(src, "uidvalidity=", 12)) {
src += 12;
ul = strtoul(src, &endp, 10);
if (errno || endp == src) return -1;
url->uidvalidity = ul;
step = 1;
}
else if (step <= 1 && !strncasecmp(src, "uid=", 4)) {
src += 4;
ul = strtoul(src, &endp, 10);
if (errno || endp == src) return -1;
url->uid = ul;
step = 2;
}
else if (step == 2 && !strncasecmp(src, "section=", 8)) {
src += 8;
url->section = src;
step = 3;
}
else if (step >= 2 && step <= 3 && !strncasecmp(src, "partial=", 8)) {
src += 8;
ul = strtoul(src, &endp, 10);
if (errno || endp == src) return -1;
url->start_octet = ul;
if (*endp == '.') {
src = endp+1;
ul = strtoul(src, &endp, 10);
if (errno || endp == src) return -1;
url->octet_count = ul;
}
step = 4;
}
else if (step >= 2 && step < 5 && !strncasecmp(src, "expire=", 7)) {
struct tm exp;
int n, tm_off, leapday;
src += 7;
memset(&exp, 0, sizeof(struct tm));
n = sscanf(src, "%4d-%2d-%2dT%2d:%2d:%2d",
&exp.tm_year, &exp.tm_mon, &exp.tm_mday,
&exp.tm_hour, &exp.tm_min, &exp.tm_sec);
if (n != 6) return -1;
src += 19;
if (*src == '.') {
while (isdigit((int) *(++src)));
}
switch (*src++) {
case 'Z': tm_off = 0; break;
case '-': tm_off = -1; break;
case '+': tm_off = 1; break;
default: return -1;
}
if (tm_off) {
int tm_houroff, tm_minoff;
n = sscanf(src, "%2d:%2d", &tm_houroff, &tm_minoff);
if (n != 2) return -1;
tm_off *= 60 * (60 * tm_houroff + tm_minoff);
}
exp.tm_year -= 1900;
exp.tm_mon--;
leapday = exp.tm_mon == 1 && isleap(exp.tm_year + 1900);
if (exp.tm_year < 70 || exp.tm_mon < 0 || exp.tm_mon > 11 ||
exp.tm_mday < 1 ||
exp.tm_mday > (numdays[exp.tm_mon] + leapday) ||
exp.tm_hour > 23 || exp.tm_min > 59 || exp.tm_sec > 60) {
return -1;
}
url->urlauth.expire = mktime(&exp) - tm_off;
step = 5;
}
else if (step >= 2 && step < 6 && !strncasecmp(src, "urlauth=", 8)) {
char *u;
src += 8;
url->urlauth.access = src;
if ((u = strchr(src, ':'))) {
url->urlauth.rump_len = (u - url->freeme);
*u++ = '\0';
url->urlauth.mech = u;
if ((u = strchr(u, ':'))) {
*u++ = '\0';
url->urlauth.token = u;
}
src = u;
}
else {
url->urlauth.rump_len = strlen(s);
}
step = 6;
}
else {
return -1;
}
}
if (mbox && *mbox) {
url->mailbox = url->freeme + strlen(s) + 1;
return URLtoMailbox((char *) url->mailbox, mbox);
}
}
return 0;
}
void imapurl_toURL(char *dst, struct imapurl *url)
{
if (url->mailbox) {
if (url->server) {
dst += sprintf(dst, "imap://");
if (url->auth) dst += sprintf(dst, ";AUTH=%s@", url->auth);
dst += sprintf(dst, "%s", url->server);
}
*dst++ = '/';
MailboxToURL(dst, url->mailbox);
dst += strlen(dst);
}
if (url->uidvalidity)
dst += sprintf(dst, ";UIDVALIDITY=%lu", url->uidvalidity);
if (url->uid) {
dst += sprintf(dst, "/;UID=%lu", url->uid);
if (url->section) dst += sprintf(dst, "/;SECTION=%s", url->section);
if (url->start_octet || url->octet_count) {
dst += sprintf(dst, "/;PARTIAL=%lu", url->start_octet);
if (url->octet_count) dst += sprintf(dst, ".%lu", url->octet_count);
}
}
if (url->urlauth.access) {
if (url->urlauth.expire) {
struct tm *exp = (struct tm *) gmtime(&url->urlauth.expire);
dst += strftime(dst, INT_MAX, ";EXPIRE=%Y-%m-%dT%H:%M:%SZ", exp);
}
dst += sprintf(dst, ";URLAUTH=%s", url->urlauth.access);
if (url->urlauth.mech) {
dst += sprintf(dst, ":%s", url->urlauth.mech);
if (url->urlauth.token) dst += sprintf(dst, ":%s", url->urlauth.token);
}
}
}