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;
|
int len;
|
||||||
} list_t;
|
} 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;
|
struct imap_cmd;
|
||||||
|
|
||||||
typedef struct imap_store {
|
typedef struct imap_store {
|
||||||
|
@ -79,6 +86,7 @@ typedef struct imap_store {
|
||||||
list_t *ns_personal, *ns_other, *ns_shared; /* NAMESPACE info */
|
list_t *ns_personal, *ns_other, *ns_shared; /* NAMESPACE info */
|
||||||
message_t **msgapp; /* FETCH results */
|
message_t **msgapp; /* FETCH results */
|
||||||
unsigned caps; /* CAPABILITY results */
|
unsigned caps; /* CAPABILITY results */
|
||||||
|
parse_list_state_t parse_list_sts;
|
||||||
/* command queue */
|
/* command queue */
|
||||||
int nexttag, num_in_progress, literal_pending;
|
int nexttag, num_in_progress, literal_pending;
|
||||||
struct imap_cmd *in_progress, **in_progress_append;
|
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
|
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;
|
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 (;;) {
|
for (;;) {
|
||||||
while (isspace( (unsigned char)*s ))
|
while (isspace( (unsigned char)*s ))
|
||||||
s++;
|
s++;
|
||||||
if (level && *s == ')') {
|
if (sts->level && *s == ')') {
|
||||||
s++;
|
s++;
|
||||||
break;
|
curp = sts->stack[--sts->level];
|
||||||
|
goto next;
|
||||||
}
|
}
|
||||||
*curp = cur = nfmalloc( sizeof(*cur) );
|
*curp = cur = nfmalloc( sizeof(*cur) );
|
||||||
curp = &cur->next;
|
|
||||||
cur->val = 0; /* for clean bail */
|
cur->val = 0; /* for clean bail */
|
||||||
|
curp = &cur->next;
|
||||||
|
*curp = 0; /* ditto */
|
||||||
if (*s == '(') {
|
if (*s == '(') {
|
||||||
/* sublist */
|
/* sublist */
|
||||||
|
if (sts->level == MAX_LIST_DEPTH)
|
||||||
|
goto bail;
|
||||||
s++;
|
s++;
|
||||||
cur->val = LIST;
|
cur->val = LIST;
|
||||||
if (parse_imap_list_l( ctx, &s, &cur->child, level + 1 ))
|
sts->stack[sts->level++] = curp;
|
||||||
goto bail;
|
curp = &cur->child;
|
||||||
|
*curp = 0; /* for clean bail */
|
||||||
|
goto next2;
|
||||||
} else if (ctx && *s == '{') {
|
} else if (ctx && *s == '{') {
|
||||||
/* literal */
|
/* literal */
|
||||||
bytes = cur->len = strtol( s + 1, &s, 10 );
|
bytes = cur->len = strtol( s + 1, &s, 10 );
|
||||||
if (*s != '}')
|
if (*s != '}' || *++s)
|
||||||
goto bail;
|
goto bail;
|
||||||
|
|
||||||
s = cur->val = nfmalloc( cur->len );
|
s = cur->val = nfmalloc( cur->len );
|
||||||
|
|
||||||
/* dump whats left over in the input buffer */
|
getbytes:
|
||||||
n = ctx->conn.bytes - ctx->conn.offset;
|
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) {
|
if (DFlags & XVERBOSE) {
|
||||||
puts( "=========" );
|
puts( "=========" );
|
||||||
fwrite( cur->val, cur->len, 1, stdout );
|
fwrite( cur->val, cur->len, 1, stdout );
|
||||||
puts( "=========" );
|
puts( "=========" );
|
||||||
}
|
}
|
||||||
|
|
||||||
if (buffer_gets( &ctx->conn, &s ))
|
getline:
|
||||||
goto bail;
|
if (!(s = socket_read_line( &ctx->conn )))
|
||||||
|
goto postpone;
|
||||||
} else if (*s == '"') {
|
} else if (*s == '"') {
|
||||||
/* quoted string */
|
/* quoted string */
|
||||||
s++;
|
s++;
|
||||||
|
@ -509,7 +527,7 @@ parse_imap_list_l( imap_store_t *ctx, char **sp, list_t **curp, int level )
|
||||||
/* atom */
|
/* atom */
|
||||||
p = s;
|
p = s;
|
||||||
for (; *s && !isspace( (unsigned char)*s ); s++)
|
for (; *s && !isspace( (unsigned char)*s ); s++)
|
||||||
if (level && *s == ')')
|
if (sts->level && *s == ')')
|
||||||
break;
|
break;
|
||||||
cur->len = s - p;
|
cur->len = s - p;
|
||||||
if (cur->len == 3 && !memcmp ("NIL", p, 3))
|
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;
|
break;
|
||||||
|
next2:
|
||||||
if (!*s)
|
if (!*s)
|
||||||
goto bail;
|
goto bail;
|
||||||
}
|
}
|
||||||
*sp = s;
|
*sp = s;
|
||||||
*curp = 0;
|
return LIST_OK;
|
||||||
return 0;
|
|
||||||
|
|
||||||
|
postpone:
|
||||||
|
if (sts->level < MAX_LIST_DEPTH) {
|
||||||
|
sts->stack[sts->level++] = curp;
|
||||||
|
sts->need_bytes = bytes;
|
||||||
|
return LIST_PARTIAL;
|
||||||
|
}
|
||||||
bail:
|
bail:
|
||||||
*curp = 0;
|
free_list( sts->head );
|
||||||
return -1;
|
return LIST_BAD;
|
||||||
}
|
}
|
||||||
|
|
||||||
static list_t *
|
static void
|
||||||
parse_imap_list( imap_store_t *ctx, char **sp )
|
parse_list_init( parse_list_state_t *sts )
|
||||||
{
|
{
|
||||||
list_t *head;
|
sts->need_bytes = -1;
|
||||||
|
sts->level = 1;
|
||||||
if (!parse_imap_list_l( ctx, sp, &head, 0 ))
|
sts->head = 0;
|
||||||
return head;
|
sts->stack[0] = &sts->head;
|
||||||
free_list( head );
|
|
||||||
return NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static list_t *
|
static list_t *
|
||||||
parse_list( char **sp )
|
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
|
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;
|
char *body = 0;
|
||||||
imap_message_t *cur;
|
imap_message_t *cur;
|
||||||
msg_data_t *msgdata;
|
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;
|
int uid = 0, mask = 0, status = 0, size = 0;
|
||||||
unsigned i;
|
unsigned i;
|
||||||
|
|
||||||
list = parse_imap_list( ctx, &cmd );
|
|
||||||
|
|
||||||
if (!is_list( list )) {
|
if (!is_list( list )) {
|
||||||
error( "IMAP error: bogus FETCH response\n" );
|
error( "IMAP error: bogus FETCH response\n" );
|
||||||
free_list( list );
|
free_list( list );
|
||||||
|
@ -784,8 +809,11 @@ get_cmd_result( imap_store_t *ctx, struct imap_cmd *tcmd )
|
||||||
|
|
||||||
greeted = ctx->greeting;
|
greeted = ctx->greeting;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
if (buffer_gets( &ctx->conn, &cmd ))
|
if (!(cmd = socket_read_line( &ctx->conn ))) {
|
||||||
|
if (socket_fill( &ctx->conn ) < 0)
|
||||||
break;
|
break;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
arg = next_arg( &cmd );
|
arg = next_arg( &cmd );
|
||||||
if (*arg == '*') {
|
if (*arg == '*') {
|
||||||
|
@ -820,8 +848,17 @@ get_cmd_result( imap_store_t *ctx, struct imap_cmd *tcmd )
|
||||||
else if (!strcmp( "RECENT", arg1 ))
|
else if (!strcmp( "RECENT", arg1 ))
|
||||||
ctx->gen.recent = atoi( arg );
|
ctx->gen.recent = atoi( arg );
|
||||||
else if(!strcmp ( "FETCH", arg1 )) {
|
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 */
|
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 {
|
} else {
|
||||||
error( "IMAP error: unrecognized untagged response '%s'\n", arg );
|
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;
|
SSL *ssl;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
int bytes;
|
int offset; /* start of filled bytes in buffer */
|
||||||
int offset;
|
int bytes; /* number of filled bytes in buffer */
|
||||||
|
int scanoff; /* offset to continue scanning for newline at, relative to 'offset' */
|
||||||
char buf[1024];
|
char buf[1024];
|
||||||
} conn_t;
|
} conn_t;
|
||||||
|
|
||||||
|
@ -332,13 +333,13 @@ extern const char *Home;
|
||||||
int socket_connect( const server_conf_t *conf, conn_t *sock );
|
int socket_connect( const server_conf_t *conf, conn_t *sock );
|
||||||
int socket_start_tls( 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 );
|
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;
|
typedef enum { KeepOwn = 0, GiveOwn } ownership_t;
|
||||||
int socket_write( conn_t *sock, char *buf, int len, ownership_t takeOwn );
|
int socket_write( conn_t *sock, char *buf, int len, ownership_t takeOwn );
|
||||||
int socket_pending( conn_t *sock );
|
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,
|
void cram( const char *challenge, const char *user, const char *pass,
|
||||||
char **_final, int *_finallen );
|
char **_final, int *_finallen );
|
||||||
|
|
||||||
|
|
108
src/socket.c
108
src/socket.c
|
@ -354,11 +354,17 @@ socket_close( conn_t *sock )
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
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 );
|
assert( sock->fd >= 0 );
|
||||||
|
buf = sock->buf + n;
|
||||||
n =
|
n =
|
||||||
#ifdef HAVE_LIBSSL
|
#ifdef HAVE_LIBSSL
|
||||||
sock->ssl ? SSL_read( sock->ssl, buf, len ) :
|
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 );
|
socket_perror( "read", sock, n );
|
||||||
close( sock->fd );
|
close( sock->fd );
|
||||||
sock->fd = -1;
|
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;
|
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
|
int
|
||||||
socket_write( conn_t *sock, char *buf, int len, ownership_t takeOwn )
|
socket_write( conn_t *sock, char *buf, int len, ownership_t takeOwn )
|
||||||
{
|
{
|
||||||
|
@ -410,57 +461,6 @@ socket_pending( conn_t *sock )
|
||||||
return 0;
|
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
|
#ifdef HAVE_LIBSSL
|
||||||
/* this isn't strictly socket code, but let's have all OpenSSL use in one file. */
|
/* this isn't strictly socket code, but let's have all OpenSSL use in one file. */
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user