Browse Source

support verbatim and real Maildir++ subfolder naming styles

the legacy style is a poorly executed attempt at Maildir++, so introduce
the latter for the sake of completeness. but most users will probably
just want to use subfolders without any additional dots.
wip/server-refactor
Oswald Buddenhagen 10 years ago
parent
commit
549e6739e8
  1. 2
      NEWS
  2. 126
      src/drv_maildir.c
  3. 20
      src/mbsync.1
  4. 1
      src/mbsyncrc.sample
  5. 6
      src/sync.c

2
NEWS

@ -2,6 +2,8 @@
Network timeout handling has been added. Network timeout handling has been added.
A Maildir sub-folder naming style without extra dots has been added.
[1.2.0] [1.2.0]
The 'isync' compatibility wrapper is now deprecated. The 'isync' compatibility wrapper is now deprecated.

126
src/drv_maildir.c

@ -50,6 +50,11 @@
#include <db.h> #include <db.h>
#endif /* USE_DB */ #endif /* USE_DB */
#define SUB_UNSET 0
#define SUB_VERBATIM 1
#define SUB_MAILDIRPP 2
#define SUB_LEGACY 3
typedef struct maildir_store_conf { typedef struct maildir_store_conf {
store_conf_t gen; store_conf_t gen;
char *inbox; char *inbox;
@ -57,6 +62,7 @@ typedef struct maildir_store_conf {
int alt_map; int alt_map;
#endif /* USE_DB */ #endif /* USE_DB */
char info_delimiter; char info_delimiter;
char sub_style;
char failed; char failed;
char *info_prefix, *info_stop; /* precalculated from info_delimiter */ char *info_prefix, *info_stop; /* precalculated from info_delimiter */
} maildir_store_conf_t; } maildir_store_conf_t;
@ -113,23 +119,50 @@ maildir_parse_flags( const char *info_prefix, const char *base )
} }
static char * static char *
maildir_join_path( const char *prefix, const char *box ) maildir_join_path( maildir_store_conf_t *conf, const char *prefix, const char *box )
{ {
char *out, *p; char *out, *p;
int pl, bl, n; int pl, bl, n, sub = 0;
char c; char c;
pl = strlen( prefix ); pl = strlen( prefix );
for (bl = 0, n = 0; (c = box[bl]); bl++) for (bl = 0, n = 0; (c = box[bl]); bl++)
if (c == '/') if (c == '/') {
if (conf->sub_style == SUB_UNSET) {
error( "Maildir error: accessing subfolder '%s', but store '%s' does not specify SubFolders style\n",
box, conf->gen.name );
return 0;
}
n++; n++;
} else if (c == '.' && conf->sub_style == SUB_MAILDIRPP) {
error( "Maildir error: store '%s', folder '%s': SubFolders style Maildir++ does not support dots in mailbox names\n",
conf->gen.name, box );
return 0;
}
out = nfmalloc( pl + bl + n + 1 ); out = nfmalloc( pl + bl + n + 1 );
memcpy( out, prefix, pl ); memcpy( out, prefix, pl );
p = out + pl; p = out + pl;
while ((c = *box++)) { while ((c = *box++)) {
*p++ = c; if (c == '/') {
if (c == '/') switch (conf->sub_style) {
*p++ = '.'; case SUB_VERBATIM:
*p++ = c;
break;
case SUB_MAILDIRPP:
if (!sub) {
sub = 1;
*p++ = c;
}
*p++ = '.';
break;
case SUB_LEGACY:
*p++ = c;
*p++ = '.';
break;
}
} else {
*p++ = c;
}
} }
*p = 0; *p = 0;
return out; return out;
@ -172,7 +205,11 @@ maildir_open_store( store_conf_t *gconf, const char *label ATTR_UNUSED,
cb( 0, aux ); cb( 0, aux );
return; return;
} }
ctx->trash = maildir_join_path( gconf->path, gconf->trash ); if (!(ctx->trash = maildir_join_path( conf, gconf->path, gconf->trash ))) {
free( ctx );
cb( 0, aux );
return;
}
} }
cb( &ctx->gen, aux ); cb( &ctx->gen, aux );
} }
@ -239,7 +276,8 @@ maildir_list_recurse( store_t *gctx, int isBox, int flags,
char *path, int pathLen, char *name, int nameLen ) char *path, int pathLen, char *name, int nameLen )
{ {
DIR *dir; DIR *dir;
int pl, nl; int style = ((maildir_store_conf_t *)gctx->conf)->sub_style;
int pl, nl, i;
struct dirent *de; struct dirent *de;
struct stat st; struct stat st;
@ -249,9 +287,20 @@ maildir_list_recurse( store_t *gctx, int isBox, int flags,
sys_error( "Maildir error: cannot list %s", path ); sys_error( "Maildir error: cannot list %s", path );
return -1; return -1;
} }
if (isBox > 1 && style == SUB_UNSET) {
error( "Maildir error: found subfolder '%.*s', but store '%s' does not specify SubFolders style\n",
nameLen - 1, name, gctx->conf->name );
closedir( dir );
return -1;
}
while ((de = readdir( dir ))) { while ((de = readdir( dir ))) {
const char *ent = de->d_name; const char *ent = de->d_name;
pl = pathLen + nfsnprintf( path + pathLen, _POSIX_PATH_MAX - pathLen, "%s", ent ); if (ent[0] == '.' && (!ent[1] || (ent[1] == '.' && !ent[2])))
continue;
pl = nfsnprintf( path + pathLen, _POSIX_PATH_MAX - pathLen, "%s", ent );
if (pl == 3 && (!memcmp( ent, "cur", 3 ) || !memcmp( ent, "new", 3 ) || !memcmp( ent, "tmp", 3 )))
continue;
pl += pathLen;
if (inbox && equals( path, pl, inbox, inboxLen )) { if (inbox && equals( path, pl, inbox, inboxLen )) {
/* Inbox nested into Path. List now if it won't be listed separately anyway. */ /* Inbox nested into Path. List now if it won't be listed separately anyway. */
if (!(flags & LIST_INBOX) && maildir_list_inbox( gctx, flags, 0 ) < 0) { if (!(flags & LIST_INBOX) && maildir_list_inbox( gctx, flags, 0 ) < 0) {
@ -265,29 +314,39 @@ maildir_list_recurse( store_t *gctx, int isBox, int flags,
return -1; return -1;
} }
} else { } else {
if (*ent == '.') { if (style == SUB_MAILDIRPP || style == SUB_LEGACY) {
if (!isBox) if (*ent == '.') {
continue; if (!isBox)
if (!ent[1] || ent[1] == '.') continue;
continue; ent++;
ent++; } else {
} else { if (isBox)
if (isBox) continue;
continue;
if (!nameLen && equals( ent, -1, "INBOX", 5 )) {
path[pathLen] = 0;
warn( "Maildir warning: ignoring INBOX in %s\n", path );
continue;
} }
} }
nl = nameLen + nfsnprintf( name + nameLen, _POSIX_PATH_MAX - nameLen, "%s", ent ); nl = nameLen + nfsnprintf( name + nameLen, _POSIX_PATH_MAX - nameLen, "%s", ent );
if (style == SUB_MAILDIRPP && isBox) {
for (i = nameLen; i < nl; i++) {
if (name[i] == '.')
name[i] = '/';
}
}
if (!nameLen && equals( name, nl, "INBOX", 5 ) && (!name[5] || name[5] == '/')) {
path[pathLen] = 0;
warn( "Maildir warning: ignoring INBOX in %s\n", path );
continue;
}
path[pl++] = '/'; path[pl++] = '/';
nfsnprintf( path + pl, _POSIX_PATH_MAX - pl, "cur" ); nfsnprintf( path + pl, _POSIX_PATH_MAX - pl, "cur" );
if (!stat( path, &st ) && S_ISDIR(st.st_mode)) if (!stat( path, &st ) && S_ISDIR(st.st_mode))
add_string_list( &gctx->boxes, name ); add_string_list( &gctx->boxes, name );
if (style == SUB_MAILDIRPP && isBox) {
/* Maildir++ subfolder - don't recurse further. */
continue;
}
path[pl] = 0; path[pl] = 0;
name[nl++] = '/'; name[nl++] = '/';
if (maildir_list_recurse( gctx, 1, flags, inbox, inboxLen, basePath, basePathLen, path, pl, name, nl ) < 0) { if (maildir_list_recurse( gctx, isBox + 1, flags, inbox, inboxLen, basePath, basePathLen, path, pl, name, nl ) < 0) {
closedir( dir ); closedir( dir );
return -1; return -1;
} }
@ -404,10 +463,6 @@ make_box_dir( char *buf, int bl )
if (!mkdir( buf, 0700 ) || errno == EEXIST) if (!mkdir( buf, 0700 ) || errno == EEXIST)
return 0; return 0;
p = memrchr( buf, '/', bl - 1 ); p = memrchr( buf, '/', bl - 1 );
if (*(p + 1) != '.') {
errno = ENOENT;
return -1;
}
*p = 0; *p = 0;
if (make_box_dir( buf, (int)(p - buf) )) if (make_box_dir( buf, (int)(p - buf) ))
return -1; return -1;
@ -1055,17 +1110,17 @@ maildir_select_box( store_t *gctx, const char *name )
#endif /* USE_DB */ #endif /* USE_DB */
ctx->fresh[0] = ctx->fresh[1] = 0; ctx->fresh[0] = ctx->fresh[1] = 0;
if (starts_with( name, -1, "INBOX", 5 ) && (!name[5] || name[5] == '/')) { if (starts_with( name, -1, "INBOX", 5 ) && (!name[5] || name[5] == '/')) {
gctx->path = maildir_join_path( conf->inbox, name + 5 ); gctx->path = maildir_join_path( conf, conf->inbox, name + 5 );
ctx->is_inbox = !name[5]; ctx->is_inbox = !name[5];
} else { } else {
if (maildir_validate_path( conf ) < 0) { if (maildir_validate_path( conf ) < 0) {
gctx->path = 0; gctx->path = 0;
return DRV_CANCELED; return DRV_CANCELED;
} }
gctx->path = maildir_join_path( gctx->conf->path, name ); gctx->path = maildir_join_path( conf, conf->gen.path, name );
ctx->is_inbox = 0; ctx->is_inbox = 0;
} }
return DRV_OK; return gctx->path ? DRV_OK : DRV_BOX_BAD;
} }
static void static void
@ -1670,6 +1725,17 @@ maildir_parse_store( conffile_t *cfg, store_conf_t **storep )
cfg->err = 1; cfg->err = 1;
continue; continue;
} }
} else if (!strcasecmp( "SubFolders", cfg->cmd )) {
if (!strcasecmp( "Verbatim", cfg->val )) {
store->sub_style = SUB_VERBATIM;
} else if (!strcasecmp( "Maildir++", cfg->val )) {
store->sub_style = SUB_MAILDIRPP;
} else if (!strcasecmp( "Legacy", cfg->val )) {
store->sub_style = SUB_LEGACY;
} else {
error( "%s:%d: Unrecognized SubFolders style\n", cfg->file, cfg->line );
cfg->err = 1;
}
} else } else
parse_generic_store( &store->gen, cfg ); parse_generic_store( &store->gen, cfg );
if (!store->inbox) if (!store->inbox)

20
src/mbsync.1

@ -256,6 +256,26 @@ The Maildir standard defines this to be the colon, but this is incompatible
with DOS/Windows file systems. with DOS/Windows file systems.
(Default: the value of \fBFieldDelimiter\fR) (Default: the value of \fBFieldDelimiter\fR)
.. ..
.TP
\fBSubFolders\fR \fBVerbatim\fR|\fBMaildir++\fR|\fBLegacy\fR
The on-disk folder naming style used for hierarchical mailboxes.
This has option has no effect when \fBFlatten\fR is used.
.br
Suppose a mailbox with the canonical path \fItop/sub/subsub\fR,
the styles will yield the following on-disk paths:
.br
\fBVerbatim\fR - \fItop/sub/subsub\fR
(this is the style you probably want to use)
.br
\fBMaildir++\fR - \fItop/.sub.subsub\fR
(this style is compatible with Courier and Dovecot - but note that
the mailbox metadata format is \fInot\fR compatible)
.br
\fBLegacy\fR - \fItop/.sub/.subsub\fR
(this is \fBmbsync\fR's historical style)
.br
(Default: unset; will error out when sub-folders are encountered)
..
.SS IMAP4 Accounts .SS IMAP4 Accounts
.TP .TP
\fBIMAPAccount\fR \fIname\fR \fBIMAPAccount\fR \fIname\fR

1
src/mbsyncrc.sample

@ -87,6 +87,7 @@ TrashRemoteNew yes
MaildirStore mirror MaildirStore mirror
Path ~/Maildir/ Path ~/Maildir/
SubFolders Verbatim
Channel o2o Channel o2o
Master :server: Master :server:

6
src/sync.c

@ -967,6 +967,7 @@ sync_boxes( store_t *ctx[], const char *names[], int present[], channel_conf_t *
svars->box_name[t] = nfstrdup( svars->orig_name[t] ); svars->box_name[t] = nfstrdup( svars->orig_name[t] );
} else if (map_name( svars->orig_name[t], &svars->box_name[t], 0, "/", ctx[t]->conf->flat_delim ) < 0) { } else if (map_name( svars->orig_name[t], &svars->box_name[t], 0, "/", ctx[t]->conf->flat_delim ) < 0) {
error( "Error: canonical mailbox name '%s' contains flattened hierarchy delimiter\n", svars->orig_name[t] ); error( "Error: canonical mailbox name '%s' contains flattened hierarchy delimiter\n", svars->orig_name[t] );
bail3:
svars->ret = SYNC_FAIL; svars->ret = SYNC_FAIL;
sync_bail3( svars ); sync_bail3( svars );
return; return;
@ -978,9 +979,12 @@ sync_boxes( store_t *ctx[], const char *names[], int present[], channel_conf_t *
/* Both boxes must be fully set up at this point, so that error exit paths /* Both boxes must be fully set up at this point, so that error exit paths
* don't run into uninitialized variables. */ * don't run into uninitialized variables. */
for (t = 0; t < 2; t++) { for (t = 0; t < 2; t++) {
if (svars->drv[t]->select_box( ctx[t], svars->box_name[t] ) == DRV_CANCELED) { switch (svars->drv[t]->select_box( ctx[t], svars->box_name[t] )) {
case DRV_CANCELED:
store_bad( AUX ); store_bad( AUX );
return; return;
case DRV_BOX_BAD:
goto bail3;
} }
} }

Loading…
Cancel
Save