From 0b59ee0df3aaa16b92bd8f48edfdacc9f55b2922 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Sat, 3 Aug 2013 15:10:57 +0200 Subject: [PATCH] support multi-character path separators this applies to both the IMAP PathDelimiter (which is needed by Lotus Domino), as well as the Flatten-ed separators. --- src/config.c | 18 ++++++------- src/drv_imap.c | 71 +++++++++++++++++++++++++++++++++----------------- src/isync.h | 4 +-- src/main.c | 13 ++++++--- src/mbsync.1 | 4 +-- src/sync.c | 5 ++-- src/util.c | 61 ++++++++++++++++++++++++++++++++++--------- 7 files changed, 119 insertions(+), 57 deletions(-) diff --git a/src/config.c b/src/config.c index 124e1ee..2c2a086 100644 --- a/src/config.c +++ b/src/config.c @@ -497,16 +497,14 @@ parse_generic_store( store_conf_t *store, conffile_t *cfg ) else if (!strcasecmp( "MapInbox", cfg->cmd )) store->map_inbox = nfstrdup( cfg->val ); else if (!strcasecmp( "Flatten", cfg->cmd )) { - int sl = strlen( cfg->val ); - if (sl != 1) { - error( "%s:%d: malformed flattened hierarchy delimiter\n", cfg->file, cfg->line ); - cfg->err = 1; - } else if (cfg->val[0] == '/') { - error( "%s:%d: flattened hierarchy delimiter cannot be the canonical delimiter '/'\n", cfg->file, cfg->line ); - cfg->err = 1; - } else { - store->flat_delim = cfg->val[0]; - } + const char *p; + for (p = cfg->val; *p; p++) + if (*p == '/') { + error( "%s:%d: flattened hierarchy delimiter cannot contain the canonical delimiter '/'\n", cfg->file, cfg->line ); + cfg->err = 1; + return; + } + store->flat_delim = nfstrdup( cfg->val ); } else { error( "%s:%d: unknown keyword '%s'\n", cfg->file, cfg->line, cfg->cmd ); cfg->err = 1; diff --git a/src/drv_imap.c b/src/drv_imap.c index 4ed8c6f..abfc760 100644 --- a/src/drv_imap.c +++ b/src/drv_imap.c @@ -52,7 +52,7 @@ typedef struct imap_store_conf { store_conf_t gen; imap_server_conf_t *server; unsigned use_namespace:1; - char delimiter; + char *delimiter; } imap_store_conf_t; typedef struct imap_message { @@ -88,7 +88,7 @@ typedef struct imap_store { /* trash folder's existence is not confirmed yet */ enum { TrashUnknown, TrashChecking, TrashKnown } trashnc; unsigned got_namespace:1; - char delimiter; /* hierarchy delimiter */ + char *delimiter; /* hierarchy delimiter */ list_t *ns_personal, *ns_other, *ns_shared; /* NAMESPACE info */ message_t **msgapp; /* FETCH results */ unsigned caps; /* CAPABILITY results */ @@ -913,13 +913,29 @@ parse_list_rsp( imap_store_t *ctx, list_t *list, char *cmd ) free_list( list ); arg = next_arg( &cmd ); if (!ctx->delimiter) - ctx->delimiter = *arg; + ctx->delimiter = nfstrdup( arg ); return parse_list( ctx, cmd, parse_list_rsp_p2 ); } +static int +is_inbox( imap_store_t *ctx, const char *arg ) +{ + int i; + char c; + + if (memcmp( arg, "INBOX", 5 )) + return 0; + if (arg[5]) + for (i = 0; (c = ctx->delimiter[i]); i++) + if (arg[i + 5] != c) + return 0; + return 1; +} + static int parse_list_rsp_p2( imap_store_t *ctx, list_t *list, char *cmd ATTR_UNUSED ) { + string_list_t *narg; char *arg; int l; @@ -929,12 +945,12 @@ parse_list_rsp_p2( imap_store_t *ctx, list_t *list, char *cmd ATTR_UNUSED ) return LIST_BAD; } arg = list->val; - if (memcmp( arg, "INBOX", 5 ) || (arg[5] && arg[5] != ctx->delimiter)) { + if (!is_inbox( ctx, arg )) { l = strlen( ctx->gen.conf->path ); if (memcmp( arg, ctx->gen.conf->path, l )) goto skip; arg += l; - if (!memcmp( arg, "INBOX", 5 ) && (!arg[5] || arg[5] == ctx->delimiter)) { + if (is_inbox( ctx, arg )) { if (!arg[5]) warn( "IMAP warning: ignoring INBOX in %s\n", ctx->gen.conf->path ); goto skip; @@ -942,36 +958,37 @@ parse_list_rsp_p2( imap_store_t *ctx, list_t *list, char *cmd ATTR_UNUSED ) } if (!memcmp( arg + strlen( arg ) - 5, ".lock", 5 )) /* workaround broken servers */ goto skip; - if (map_name( arg, ctx->delimiter, '/') < 0) { + if (map_name( arg, (char **)&narg, offsetof(string_list_t, string), ctx->delimiter, "/") < 0) { warn( "IMAP warning: ignoring mailbox %s (reserved character '/' in name)\n", arg ); goto skip; } - add_string_list( &ctx->gen.boxes, arg ); + narg->next = ctx->gen.boxes; + ctx->gen.boxes = narg; skip: free_list( list ); return LIST_OK; } static int -prepare_name( char *buf, const imap_store_t *ctx, const char *prefix, const char *name ) +prepare_name( char **buf, const imap_store_t *ctx, const char *prefix, const char *name ) { - int pl; + int pl = strlen( prefix ); - nfsnprintf( buf, 1024, "%s%n%s", prefix, &pl, name ); - switch (map_name( buf + pl, '/', ctx->delimiter )) { + switch (map_name( name, buf, pl, "/", ctx->delimiter )) { case -1: - error( "IMAP error: mailbox name %s contains server's hierarchy delimiter\n", buf + pl ); + error( "IMAP error: mailbox name %s contains server's hierarchy delimiter\n", name ); return -1; case -2: error( "IMAP error: server's hierarchy delimiter not known\n" ); return -1; default: + memcpy( *buf, prefix, pl ); return 0; } } static int -prepare_box( char *buf, const imap_store_t *ctx ) +prepare_box( char **buf, const imap_store_t *ctx ) { const char *name = ctx->gen.name; @@ -980,7 +997,7 @@ prepare_box( char *buf, const imap_store_t *ctx ) } static int -prepare_trash( char *buf, const imap_store_t *ctx ) +prepare_trash( char **buf, const imap_store_t *ctx ) { return prepare_name( buf, ctx, ctx->prefix, ctx->gen.conf->trash ); } @@ -1183,6 +1200,7 @@ imap_cancel_store( store_t *gctx ) free_list( ctx->ns_personal ); free_list( ctx->ns_other ); free_list( ctx->ns_shared ); + free( ctx->delimiter ); imap_deref( ctx ); } @@ -1330,6 +1348,7 @@ imap_open_store( store_conf_t *conf, ctx->gen.boxes = 0; ctx->gen.listed = 0; ctx->gen.conf = conf; + free( ctx->delimiter ); ctx->delimiter = 0; ctx->callbacks.imap_open = cb; ctx->callback_aux = aux; @@ -1570,7 +1589,7 @@ imap_open_store_namespace( imap_store_t *ctx ) imap_store_conf_t *cfg = (imap_store_conf_t *)ctx->gen.conf; ctx->prefix = cfg->gen.path; - ctx->delimiter = cfg->delimiter; + ctx->delimiter = cfg->delimiter ? nfstrdup( cfg->delimiter ) : 0; if (((!*ctx->prefix && cfg->use_namespace) || !cfg->delimiter) && CAP(NAMESPACE)) { /* get NAMESPACE info */ if (!ctx->got_namespace) @@ -1608,7 +1627,7 @@ imap_open_store_namespace2( imap_store_t *ctx ) if (!*ctx->prefix && cfg->use_namespace) ctx->prefix = nsp_1st_ns->val; if (!ctx->delimiter) - ctx->delimiter = *nsp_1st_dl->val; + ctx->delimiter = nfstrdup( nsp_1st_dl->val ); } imap_open_store_finalize( ctx ); } @@ -1656,12 +1675,12 @@ imap_select( store_t *gctx, int create, { imap_store_t *ctx = (imap_store_t *)gctx; struct imap_cmd_simple *cmd; - char buf[1024]; + char *buf; free_generic_messages( gctx->msgs ); gctx->msgs = 0; - if (prepare_box( buf, ctx ) < 0) { + if (prepare_box( &buf, ctx ) < 0) { cb( DRV_BOX_BAD, aux ); return; } @@ -1673,6 +1692,7 @@ imap_select( store_t *gctx, int create, cmd->gen.param.trycreate = 1; imap_exec( ctx, &cmd->gen, imap_done_simple_box, "SELECT \"%s\"", buf ); + free( buf ); } /******************* imap_load *******************/ @@ -1878,17 +1898,18 @@ imap_trash_msg( store_t *gctx, message_t *msg, { imap_store_t *ctx = (imap_store_t *)gctx; struct imap_cmd_simple *cmd; - char buf[1024]; + char *buf; INIT_IMAP_CMD(imap_cmd_simple, cmd, cb, aux) cmd->gen.param.create = 1; cmd->gen.param.to_trash = 1; - if (prepare_trash( buf, ctx ) < 0) { + if (prepare_trash( &buf, ctx ) < 0) { cb( DRV_BOX_BAD, aux ); return; } imap_exec( ctx, &cmd->gen, imap_done_simple_msg, "UID COPY %d \"%s\"", msg->uid, buf ); + free( buf ); } /******************* imap_store_msg *******************/ @@ -1901,8 +1922,9 @@ imap_store_msg( store_t *gctx, msg_data_t *data, int to_trash, { imap_store_t *ctx = (imap_store_t *)gctx; struct imap_cmd_out_uid *cmd; + char *buf; int d; - char flagstr[128], datestr[64], buf[1024]; + char flagstr[128], datestr[64]; d = 0; if (data->flags) { @@ -1919,12 +1941,12 @@ imap_store_msg( store_t *gctx, msg_data_t *data, int to_trash, if (to_trash) { cmd->gen.param.create = 1; cmd->gen.param.to_trash = 1; - if (prepare_trash( buf, ctx ) < 0) { + if (prepare_trash( &buf, ctx ) < 0) { cb( DRV_BOX_BAD, -1, aux ); return; } } else { - if (prepare_box( buf, ctx ) < 0) { + if (prepare_box( &buf, ctx ) < 0) { cb( DRV_BOX_BAD, -1, aux ); return; } @@ -1945,6 +1967,7 @@ imap_store_msg( store_t *gctx, msg_data_t *data, int to_trash, imap_exec( ctx, &cmd->gen, imap_store_msg_p2, "APPEND \"%s\" %s", buf, flagstr ); } + free( buf ); } static void @@ -2143,7 +2166,7 @@ imap_parse_store( conffile_t *cfg, store_conf_t **storep ) else if (!strcasecmp( "Path", cfg->cmd )) store->gen.path = nfstrdup( cfg->val ); else if (!strcasecmp( "PathDelimiter", cfg->cmd )) - store->delimiter = *cfg->val; + store->delimiter = nfstrdup( cfg->val ); else parse_generic_store( &store->gen, cfg ); continue; diff --git a/src/isync.h b/src/isync.h index b6c7233..5176313 100644 --- a/src/isync.h +++ b/src/isync.h @@ -142,11 +142,11 @@ typedef struct store_conf { char *name; driver_t *driver; const char *path; /* should this be here? its interpretation is driver-specific */ + const char *flat_delim; const char *map_inbox; const char *trash; unsigned max_size; /* off_t is overkill */ unsigned trash_remote_new:1, trash_only_new:1; - char flat_delim; } store_conf_t; typedef struct string_list { @@ -440,7 +440,7 @@ void ATTR_NORETURN oob( void ); char *expand_strdup( const char *s ); -int map_name( char *arg, char in, char out ); +int map_name(const char *arg, char **result, int reserve, const char *in, const char *out ); void sort_ints( int *arr, int len ); diff --git a/src/main.c b/src/main.c index 741107c..0adc45b 100644 --- a/src/main.c +++ b/src/main.c @@ -744,7 +744,7 @@ static void store_listed( int sts, void *aux ) { MVARS(aux) - string_list_t *box; + string_list_t **box; switch (sts) { case DRV_CANCELED: @@ -752,10 +752,15 @@ store_listed( int sts, void *aux ) case DRV_OK: mvars->ctx[t]->listed = 1; if (mvars->ctx[t]->conf->flat_delim) { - for (box = mvars->ctx[t]->boxes; box; box = box->next) { - if (map_name( box->string, mvars->ctx[t]->conf->flat_delim, '/' ) < 0) { - error( "Error: flattened mailbox name '%s' contains canonical hierarchy delimiter\n", box->string ); + for (box = &mvars->ctx[t]->boxes; *box; box = &(*box)->next) { + string_list_t *nbox; + if (map_name( (*box)->string, (char **)&nbox, offsetof(string_list_t, string), mvars->ctx[t]->conf->flat_delim, "/" ) < 0) { + error( "Error: flattened mailbox name '%s' contains canonical hierarchy delimiter\n", (*box)->string ); mvars->ret = mvars->skip = 1; + } else { + nbox->next = (*box)->next; + free( *box ); + *box = nbox; } } } diff --git a/src/mbsync.1 b/src/mbsync.1 index 7925c69..965e65f 100644 --- a/src/mbsync.1 +++ b/src/mbsync.1 @@ -20,7 +20,7 @@ \" As a special exception, mbsync may be linked with the OpenSSL library, \" despite that library's more restrictive license. .. -.TH mbsync 1 "2013 Apr 20" +.TH mbsync 1 "2013 Aug 3" .. .SH NAME mbsync - synchronize IMAP4 and Maildir mailboxes @@ -352,7 +352,7 @@ This option is meaningless if a \fBPath\fR was specified. .. .TP \fBPathDelimiter\fR \fIdelim\fR -Specify the server's hierarchy delimiter character. +Specify the server's hierarchy delimiter. (Default: taken from the server's first "personal" NAMESPACE) .br Do \fBNOT\fR abuse this to re-interpret the hierarchy. diff --git a/src/sync.c b/src/sync.c index d97d2c7..5633fbf 100644 --- a/src/sync.c +++ b/src/sync.c @@ -618,8 +618,9 @@ sync_boxes( store_t *ctx[], const char *names[], channel_conf_t *chan, ctx[t]->orig_name = (!names[t] || (ctx[t]->conf->map_inbox && !strcmp( ctx[t]->conf->map_inbox, names[t] ))) ? "INBOX" : names[t]; - ctx[t]->name = nfstrdup( ctx[t]->orig_name ); - if (ctx[t]->conf->flat_delim && map_name( ctx[t]->name, '/', ctx[t]->conf->flat_delim ) < 0) { + if (!ctx[t]->conf->flat_delim) { + ctx[t]->name = nfstrdup( ctx[t]->orig_name ); + } else if (map_name( ctx[t]->orig_name, &ctx[t]->name, 0, "/", ctx[t]->conf->flat_delim ) < 0) { error( "Error: canonical mailbox name '%s' contains flattened hierarchy delimiter\n", ctx[t]->name ); svars->ret = SYNC_FAIL; sync_bail3( svars ); diff --git a/src/util.c b/src/util.c index ba05efa..2b89a7d 100644 --- a/src/util.c +++ b/src/util.c @@ -361,24 +361,59 @@ expand_strdup( const char *s ) /* Return value: 0 = ok, -1 = out found in arg, -2 = in found in arg but no out specified */ int -map_name( char *arg, char in, char out ) +map_name( const char *arg, char **result, int reserve, const char *in, const char *out ) { - int l, k; + char *p; + int i, l, ll, num, inl, outl; - if (!in || in == out) + l = strlen( arg ); + if (!in) { + copy: + *result = nfmalloc( reserve + l + 1 ); + memcpy( *result + reserve, arg, l + 1 ); return 0; - for (l = 0; arg[l]; l++) - if (arg[l] == in) { - if (!out) - return -2; - arg[l] = out; - } else if (arg[l] == out) { - /* restore original name for printing error message */ - for (k = 0; k < l; k++) - if (arg[k] == out) - arg[k] = in; + } + inl = strlen( in ); + if (out) { + outl = strlen( out ); + if (inl == outl && !memcmp( in, out, inl )) + goto copy; + } + for (num = 0, i = 0; i < l; ) { + for (ll = 0; ll < inl; ll++) + if (arg[i + ll] != in[ll]) + goto fout; + num++; + i += inl; + continue; + fout: + if (out) { + for (ll = 0; ll < outl; ll++) + if (arg[i + ll] != out[ll]) + goto fnexti; return -1; } + fnexti: + i++; + } + if (!num) + goto copy; + if (!out) + return -2; + *result = nfmalloc( reserve + l + num * (outl - inl) + 1 ); + p = *result + reserve; + for (i = 0; i < l; ) { + for (ll = 0; ll < inl; ll++) + if (arg[i + ll] != in[ll]) + goto rnexti; + memcpy( p, out, outl ); + p += outl; + i += inl; + continue; + rnexti: + *p++ = arg[i++]; + } + *p = 0; return 0; }