diff --git a/src/drv_imap.c b/src/drv_imap.c index 2d922dc..71c432f 100644 --- a/src/drv_imap.c +++ b/src/drv_imap.c @@ -67,6 +67,13 @@ typedef struct _list { int len; } list_t; +#define MAX_LIST_DEPTH 5 + +typedef struct parse_list_state { + list_t *head, **stack[MAX_LIST_DEPTH]; + int level, need_bytes; +} parse_list_state_t; + struct imap_cmd; typedef struct imap_store { @@ -79,6 +86,7 @@ typedef struct imap_store { list_t *ns_personal, *ns_other, *ns_shared; /* NAMESPACE info */ message_t **msgapp; /* FETCH results */ unsigned caps; /* CAPABILITY results */ + parse_list_state_t parse_list_sts; /* command queue */ int nexttag, num_in_progress, literal_pending; struct imap_cmd *in_progress, **in_progress_append; @@ -433,66 +441,76 @@ free_list( list_t *list ) } } +enum { + LIST_OK, + LIST_PARTIAL, + LIST_BAD +}; + static int -parse_imap_list_l( imap_store_t *ctx, char **sp, list_t **curp, int level ) +parse_imap_list( imap_store_t *ctx, char **sp, parse_list_state_t *sts ) { - list_t *cur; + list_t *cur, **curp; char *s = *sp, *p; - int n, bytes; + int bytes; + + assert( sts ); + assert( sts->level > 0 ); + curp = sts->stack[--sts->level]; + bytes = sts->need_bytes; + if (bytes >= 0) { + sts->need_bytes = -1; + if (!bytes) + goto getline; + cur = (list_t *)((char *)curp - offsetof(list_t, next)); + s = cur->val + cur->len - bytes; + goto getbytes; + } for (;;) { while (isspace( (unsigned char)*s )) s++; - if (level && *s == ')') { + if (sts->level && *s == ')') { s++; - break; + curp = sts->stack[--sts->level]; + goto next; } *curp = cur = nfmalloc( sizeof(*cur) ); - curp = &cur->next; cur->val = 0; /* for clean bail */ + curp = &cur->next; + *curp = 0; /* ditto */ if (*s == '(') { /* sublist */ + if (sts->level == MAX_LIST_DEPTH) + goto bail; s++; cur->val = LIST; - if (parse_imap_list_l( ctx, &s, &cur->child, level + 1 )) - goto bail; + sts->stack[sts->level++] = curp; + curp = &cur->child; + *curp = 0; /* for clean bail */ + goto next2; } else if (ctx && *s == '{') { /* literal */ bytes = cur->len = strtol( s + 1, &s, 10 ); - if (*s != '}') + if (*s != '}' || *++s) goto bail; s = cur->val = nfmalloc( cur->len ); - /* dump whats left over in the input buffer */ - n = ctx->conn.bytes - ctx->conn.offset; + getbytes: + bytes -= socket_read( &ctx->conn, s, bytes ); + if (bytes > 0) + goto postpone; - if (n > bytes) - /* the entire message fit in the buffer */ - n = bytes; - - memcpy( s, ctx->conn.buf + ctx->conn.offset, n ); - s += n; - bytes -= n; - - /* mark that we used part of the buffer */ - ctx->conn.offset += n; - - /* now read the rest of the message */ - while (bytes > 0) { - if ((n = socket_read( &ctx->conn, s, bytes )) <= 0) - goto bail; - s += n; - bytes -= n; - } if (DFlags & XVERBOSE) { puts( "=========" ); fwrite( cur->val, cur->len, 1, stdout ); puts( "=========" ); } - if (buffer_gets( &ctx->conn, &s )) - goto bail; + getline: + if (!(s = socket_read_line( &ctx->conn ))) + goto postpone; } else if (*s == '"') { /* quoted string */ s++; @@ -509,7 +527,7 @@ parse_imap_list_l( imap_store_t *ctx, char **sp, list_t **curp, int level ) /* atom */ p = s; for (; *s && !isspace( (unsigned char)*s ); s++) - if (level && *s == ')') + if (sts->level && *s == ')') break; cur->len = s - p; if (cur->len == 3 && !memcmp ("NIL", p, 3)) @@ -521,41 +539,50 @@ parse_imap_list_l( imap_store_t *ctx, char **sp, list_t **curp, int level ) } } - if (!level) + next: + if (!sts->level) break; + next2: if (!*s) goto bail; } *sp = s; - *curp = 0; - return 0; + return LIST_OK; + postpone: + if (sts->level < MAX_LIST_DEPTH) { + sts->stack[sts->level++] = curp; + sts->need_bytes = bytes; + return LIST_PARTIAL; + } bail: - *curp = 0; - return -1; + free_list( sts->head ); + return LIST_BAD; } -static list_t * -parse_imap_list( imap_store_t *ctx, char **sp ) +static void +parse_list_init( parse_list_state_t *sts ) { - list_t *head; - - if (!parse_imap_list_l( ctx, sp, &head, 0 )) - return head; - free_list( head ); - return NULL; + sts->need_bytes = -1; + sts->level = 1; + sts->head = 0; + sts->stack[0] = &sts->head; } static list_t * parse_list( char **sp ) { - return parse_imap_list( 0, sp ); + parse_list_state_t sts; + parse_list_init( &sts ); + if (parse_imap_list( 0, sp, &sts ) == LIST_OK) + return sts.head; + return NULL; } static int -parse_fetch( imap_store_t *ctx, char *cmd ) /* move this down */ +parse_fetch( imap_store_t *ctx, list_t *list ) { - list_t *tmp, *list, *flags; + list_t *tmp, *flags; char *body = 0; imap_message_t *cur; msg_data_t *msgdata; @@ -563,8 +590,6 @@ parse_fetch( imap_store_t *ctx, char *cmd ) /* move this down */ int uid = 0, mask = 0, status = 0, size = 0; unsigned i; - list = parse_imap_list( ctx, &cmd ); - if (!is_list( list )) { error( "IMAP error: bogus FETCH response\n" ); free_list( list ); @@ -784,8 +809,11 @@ get_cmd_result( imap_store_t *ctx, struct imap_cmd *tcmd ) greeted = ctx->greeting; for (;;) { - if (buffer_gets( &ctx->conn, &cmd )) - break; + if (!(cmd = socket_read_line( &ctx->conn ))) { + if (socket_fill( &ctx->conn ) < 0) + break; + continue; + } arg = next_arg( &cmd ); if (*arg == '*') { @@ -820,8 +848,17 @@ get_cmd_result( imap_store_t *ctx, struct imap_cmd *tcmd ) else if (!strcmp( "RECENT", arg1 )) ctx->gen.recent = atoi( arg ); else if(!strcmp ( "FETCH", arg1 )) { - if (parse_fetch( ctx, cmd )) + parse_list_init( &ctx->parse_list_sts ); + do_fetch: + if ((resp = parse_imap_list( ctx, &cmd, &ctx->parse_list_sts )) == LIST_BAD) break; /* stream is likely to be useless now */ + if (resp == LIST_PARTIAL) { + if (socket_fill( &ctx->conn ) < 0) + break; + goto do_fetch; + } + if (parse_fetch( ctx, ctx->parse_list_sts.head ) < 0) + break; /* this may mean anything, so prefer not to spam the log */ } } else { error( "IMAP error: unrecognized untagged response '%s'\n", arg ); diff --git a/src/isync.h b/src/isync.h index c074f56..7de6f33 100644 --- a/src/isync.h +++ b/src/isync.h @@ -79,8 +79,9 @@ typedef struct { SSL *ssl; #endif - int bytes; - int offset; + int offset; /* start of filled bytes in buffer */ + int bytes; /* number of filled bytes in buffer */ + int scanoff; /* offset to continue scanning for newline at, relative to 'offset' */ char buf[1024]; } conn_t; @@ -332,13 +333,13 @@ extern const char *Home; int socket_connect( const server_conf_t *conf, conn_t *sock ); int socket_start_tls( const server_conf_t *conf, conn_t *sock ); void socket_close( conn_t *sock ); -int socket_read( conn_t *sock, char *buf, int len ); +int socket_fill( conn_t *sock ); +int socket_read( conn_t *sock, char *buf, int len ); /* never waits */ +char *socket_read_line( conn_t *sock ); /* don't free return value; never waits */ typedef enum { KeepOwn = 0, GiveOwn } ownership_t; int socket_write( conn_t *sock, char *buf, int len, ownership_t takeOwn ); int socket_pending( conn_t *sock ); -int buffer_gets( conn_t *b, char **s ); - void cram( const char *challenge, const char *user, const char *pass, char **_final, int *_finallen ); diff --git a/src/socket.c b/src/socket.c index b2a1ee9..076c075 100644 --- a/src/socket.c +++ b/src/socket.c @@ -354,11 +354,17 @@ socket_close( conn_t *sock ) } int -socket_read( conn_t *sock, char *buf, int len ) +socket_fill( conn_t *sock ) { - int n; - + char *buf; + int n = sock->offset + sock->bytes; + int len = sizeof(sock->buf) - n; + if (!len) { + error( "Socket error: receive buffer full. Probably protocol error.\n" ); + return -1; + } assert( sock->fd >= 0 ); + buf = sock->buf + n; n = #ifdef HAVE_LIBSSL sock->ssl ? SSL_read( sock->ssl, buf, len ) : @@ -368,10 +374,55 @@ socket_read( conn_t *sock, char *buf, int len ) socket_perror( "read", sock, n ); close( sock->fd ); sock->fd = -1; + return -1; + } else { + sock->bytes += n; + return 0; } +} + +int +socket_read( conn_t *conn, char *buf, int len ) +{ + int n = conn->bytes; + if (n > len) + n = len; + memcpy( buf, conn->buf + conn->offset, n ); + if (!(conn->bytes -= n)) + conn->offset = 0; + else + conn->offset += n; return n; } +char * +socket_read_line( conn_t *b ) +{ + char *p, *s; + int n; + + s = b->buf + b->offset; + p = memchr( s + b->scanoff, '\n', b->bytes - b->scanoff ); + if (!p) { + b->scanoff = b->bytes; + if (b->offset + b->bytes == sizeof(b->buf)) { + memmove( b->buf, b->buf + b->offset, b->bytes ); + b->offset = 0; + } + return 0; + } + n = p + 1 - s; + b->offset += n; + b->bytes -= n; + b->scanoff = 0; + if (p != s && p[-1] == '\r') + p--; + *p = 0; + if (DFlags & VERBOSE) + puts( s ); + return s; +} + int socket_write( conn_t *sock, char *buf, int len, ownership_t takeOwn ) { @@ -410,57 +461,6 @@ socket_pending( conn_t *sock ) return 0; } -/* simple line buffering */ -int -buffer_gets( conn_t *b, char **s ) -{ - int n; - int start = b->offset; - - *s = b->buf + start; - - for (;;) { - /* make sure we have enough data to read the \r\n sequence */ - if (b->offset + 1 >= b->bytes) { - if (start) { - /* shift down used bytes */ - *s = b->buf; - - assert( start <= b->bytes ); - n = b->bytes - start; - - if (n) - memmove( b->buf, b->buf + start, n ); - b->offset -= start; - b->bytes = n; - start = 0; - } - - n = socket_read( b, b->buf + b->bytes, - sizeof(b->buf) - b->bytes ); - - if (n <= 0) - return -1; - - b->bytes += n; - } - - if (b->buf[b->offset] == '\r') { - assert( b->offset + 1 < b->bytes ); - if (b->buf[b->offset + 1] == '\n') { - b->buf[b->offset] = 0; /* terminate the string */ - b->offset += 2; /* next line */ - if (DFlags & VERBOSE) - puts( *s ); - return 0; - } - } - - b->offset++; - } - /* not reached */ -} - #ifdef HAVE_LIBSSL /* this isn't strictly socket code, but let's have all OpenSSL use in one file. */