commit 2213d6976c8247d0e746f14123fbe6030a80c759
parent c0bf867669f9cea5bc731370d46272c1e582cbda
Author: Oswald Buddenhagen <ossi@users.sf.net>
Date: Wed, 25 Sep 2013 20:55:32 +0200
support backslashes and quotes in quoted IMAP strings
the RFCs require it - well hidden in the BNF at the bottom.
patch somewhat inspired by "guns" <self@sungpae.com>.
Diffstat:
M | src/drv_imap.c | | | 161 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------- |
1 file changed, 136 insertions(+), 25 deletions(-)
diff --git a/src/drv_imap.c b/src/drv_imap.c
@@ -355,6 +355,88 @@ submit_imap_cmd( imap_store_t *ctx, struct imap_cmd *cmd )
return send_imap_cmd( ctx, cmd );
}
+/* Minimal printf() replacement that supports an %\s format sequence to print backslash-escaped
+ * string literals. Note that this does not automatically add quotes around the printed string,
+ * so it is possible to concatenate multiple segments. */
+static char *
+imap_vprintf( const char *fmt, va_list ap )
+{
+ const char *s, *es;
+ char *d, *ed;
+ int maxlen;
+ char c;
+ char buf[1024]; /* Minimal supported command buffer size per IMAP spec. */
+
+ d = buf;
+ ed = d + sizeof(buf);
+ s = fmt;
+ for (;;) {
+ c = *fmt;
+ if (!c || c == '%') {
+ int l = fmt - s;
+ if (d + l > ed)
+ oob();
+ memcpy( d, s, l );
+ d += l;
+ if (!c) {
+ l = d - buf;
+ ed = nfmalloc( l + 1 );
+ memcpy( ed, buf, l );
+ ed[l] = 0;
+ return ed;
+ }
+ maxlen = INT_MAX;
+ c = *++fmt;
+ if (c == '\\') {
+ c = *++fmt;
+ if (c != 's') {
+ fputs( "Fatal: unsupported escaped format specifier. Please report a bug.\n", stderr );
+ abort();
+ }
+ s = va_arg( ap, const char * );
+ while ((c = *s++)) {
+ if (d + 2 > ed)
+ oob();
+ if (c == '\\' || c == '"')
+ *d++ = '\\';
+ *d++ = c;
+ }
+ } else { /* \\ cannot be combined with anything else. */
+ if (c == '.') {
+ c = *++fmt;
+ if (c != '*') {
+ fputs( "Fatal: unsupported string length specification. Please report a bug.\n", stderr );
+ abort();
+ }
+ maxlen = va_arg( ap , int );
+ c = *++fmt;
+ }
+ if (c == 'c') {
+ if (d + 1 > ed)
+ oob();
+ *d++ = (char)va_arg( ap , int );
+ } else if (c == 's') {
+ s = va_arg( ap, const char * );
+ es = memchr( s, 0, maxlen );
+ l = es ? es - s : maxlen;
+ if (d + l > ed)
+ oob();
+ memcpy( d, s, l );
+ d += l;
+ } else if (c == 'd') {
+ d += nfsnprintf( d, ed - d, "%d", va_arg( ap , int ) );
+ } else {
+ fputs( "Fatal: unsupported format specifier. Please report a bug.\n", stderr );
+ abort();
+ }
+ }
+ s = ++fmt;
+ } else {
+ fmt++;
+ }
+ }
+}
+
static int
imap_exec( imap_store_t *ctx, struct imap_cmd *cmdp,
void (*done)( imap_store_t *ctx, struct imap_cmd *cmd, int response ),
@@ -366,7 +448,7 @@ imap_exec( imap_store_t *ctx, struct imap_cmd *cmdp,
cmdp = new_imap_cmd( sizeof(*cmdp) );
cmdp->param.done = done;
va_start( ap, fmt );
- nfvasprintf( &cmdp->cmd, fmt, ap );
+ cmdp->cmd = imap_vprintf( fmt, ap );
va_end( ap );
return submit_imap_cmd( ctx, cmdp );
}
@@ -457,10 +539,25 @@ imap_refcounted_done_box( imap_store_t *ctx ATTR_UNUSED, struct imap_cmd *cmd, i
imap_refcounted_done( sts );
}
+static const char *
+imap_strchr( const char *s, char tc )
+{
+ for (;; s++) {
+ char c = *s;
+ if (c == '\\')
+ c = *++s;
+ if (!c)
+ return 0;
+ if (c == tc)
+ return s;
+ }
+}
+
static char *
next_arg( char **ps )
{
- char *ret, *s;
+ char *ret, *s, *d;
+ char c;
assert( ps );
s = *ps;
@@ -473,20 +570,30 @@ next_arg( char **ps )
return 0;
}
if (*s == '"') {
- ++s;
- ret = s;
- s = strchr( s, '"' );
+ s++;
+ ret = d = s;
+ while ((c = *s++) != '"') {
+ if (c == '\\')
+ c = *s++;
+ if (!c) {
+ *ps = 0;
+ return 0;
+ }
+ *d++ = c;
+ }
+ *d = 0;
} else {
ret = s;
- while (*s && !isspace( (unsigned char)*s ))
+ while ((c = *s)) {
+ if (isspace( (unsigned char)c )) {
+ *s++ = 0;
+ break;
+ }
s++;
+ }
}
- if (s) {
- if (*s)
- *s++ = 0;
- if (!*s)
- s = 0;
- }
+ if (!*s)
+ s = 0;
*ps = s;
return ret;
@@ -529,8 +636,9 @@ static int
parse_imap_list( imap_store_t *ctx, char **sp, parse_list_state_t *sts )
{
list_t *cur, **curp;
- char *s = *sp, *p;
+ char *s = *sp, *d, *p;
int bytes;
+ char c;
assert( sts );
assert( sts->level > 0 );
@@ -595,12 +703,15 @@ parse_imap_list( imap_store_t *ctx, char **sp, parse_list_state_t *sts )
} else if (*s == '"') {
/* quoted string */
s++;
- p = s;
- for (; *s != '"'; s++)
- if (!*s)
+ p = d = s;
+ while ((c = *s++) != '"') {
+ if (c == '\\')
+ c = *s++;
+ if (!c)
goto bail;
- cur->len = s - p;
- s++;
+ *d++ = c;
+ }
+ cur->len = d - p;
cur->val = nfmalloc( cur->len + 1 );
memcpy( cur->val, p, cur->len );
cur->val[cur->len] = 0;
@@ -1140,7 +1251,7 @@ imap_socket_read( void *aux )
cmd2->gen.param.high_prio = 1;
p = strchr( cmdp->cmd, '"' );
if (imap_exec( ctx, &cmd2->gen, get_cmd_result_p2,
- "CREATE %.*s", strchr( p + 1, '"' ) - p + 1, p ) < 0)
+ "CREATE %.*s", imap_strchr( p + 1, '"' ) - p + 1, p ) < 0)
return;
continue;
}
@@ -1571,7 +1682,7 @@ imap_open_store_authenticate2( imap_store_t *ctx )
#endif
warn( "*** IMAP Warning *** Password is being sent in the clear\n" );
imap_exec( ctx, 0, imap_open_store_authenticate2_p2,
- "LOGIN \"%s\" \"%s\"", srvc->user, srvc->pass );
+ "LOGIN \"%\\s\" \"%\\s\"", srvc->user, srvc->pass );
return;
bail:
@@ -1695,7 +1806,7 @@ imap_select( store_t *gctx, int create,
cmd->gen.param.create = create;
cmd->gen.param.trycreate = 1;
imap_exec( ctx, &cmd->gen, imap_done_simple_box,
- "SELECT \"%s\"", buf );
+ "SELECT \"%\\s\"", buf );
free( buf );
}
@@ -1912,7 +2023,7 @@ imap_trash_msg( store_t *gctx, message_t *msg,
return;
}
imap_exec( ctx, &cmd->gen, imap_done_simple_msg,
- "UID COPY %d \"%s\"", msg->uid, buf );
+ "UID COPY %d \"%\\s\"", msg->uid, buf );
free( buf );
}
@@ -1966,10 +2077,10 @@ imap_store_msg( store_t *gctx, msg_data_t *data, int to_trash,
# pragma GCC diagnostic pop
#endif
imap_exec( ctx, &cmd->gen, imap_store_msg_p2,
- "APPEND \"%s\" %s\"%s\" ", buf, flagstr, datestr );
+ "APPEND \"%\\s\" %s\"%\\s\" ", buf, flagstr, datestr );
} else {
imap_exec( ctx, &cmd->gen, imap_store_msg_p2,
- "APPEND \"%s\" %s", buf, flagstr );
+ "APPEND \"%\\s\" %s", buf, flagstr );
}
free( buf );
}
@@ -2023,7 +2134,7 @@ imap_list( store_t *gctx, int flags,
if (((flags & LIST_PATH) &&
imap_exec( ctx, imap_refcounted_new_cmd( sts ), imap_refcounted_done_box,
- "LIST \"\" \"%s*\"", ctx->prefix ) < 0) ||
+ "LIST \"\" \"%\\s*\"", ctx->prefix ) < 0) ||
((flags & LIST_INBOX) && (!(flags & LIST_PATH) || *ctx->prefix) &&
imap_exec( ctx, imap_refcounted_new_cmd( sts ), imap_refcounted_done_box,
"LIST \"\" INBOX*" ) < 0))