decouple the filling of the read buffer from consuming it
this prepares the code for being called from a callback. notably, this makes the imap list parser have a "soft stack", so the recursion can be suspended at any time.
This commit is contained in:
parent
886cd03e37
commit
f1df2f40d1
141
src/drv_imap.c
141
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 ))
|
||||
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 );
|
||||
|
|
11
src/isync.h
11
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 );
|
||||
|
||||
|
|
108
src/socket.c
108
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. */
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user