static bool try_get_mailbox_name(struct mail_storage_list_index_rebuild_ctx *ctx,
struct mailbox_list *list, const char *path,
- const char **name_r)
+ const char **name_r, uint8_t *flags_r)
{
struct mail_index *index =
mail_index_alloc(ctx->storage->event, path, MAIL_INDEX_PREFIX);
uint32_t box_name_hdr_ext_id;
bool ret = FALSE;
int rc;
+ *flags_r = 0;
if ((rc = mail_index_open(index, MAIL_INDEX_OPEN_FLAG_READONLY)) > 0) {
if (mail_index_ext_lookup(index, "box-name", &box_name_hdr_ext_id)) {
view = mail_index_view_open(index);
mail_index_get_header_ext(view, box_name_hdr_ext_id,
&name_hdr, &name_hdr_size);
*name_r = mailbox_name_hdr_decode_storage_name(list,
- name_hdr, name_hdr_size);
+ name_hdr, name_hdr_size,
+ flags_r);
ret = TRUE;
mail_index_view_close(&view);
} else {
}
static const char *get_box_name(struct mail_storage_list_index_rebuild_ctx *ctx,
- struct mail_storage_list_index_rebuild_mailbox *box)
+ struct mail_storage_list_index_rebuild_mailbox *box,
+ uint8_t *flags_r)
{
const char *path =
t_strdup_printf("%s/%s",
guid_128_to_string(box->guid));
const char *box_name;
- if (try_get_mailbox_name(ctx, box->list, path, &box_name)) {
+ *flags_r = 0;
+ if (try_get_mailbox_name(ctx, box->list, path, &box_name, flags_r)) {
e_debug(ctx->storage->event, "Found '%s' from storage %s",
box_name, path);
} else {
fallback to trying to find the box-name header from the
mailbox's index. */
const char *name = box->storage_name;
+ uint8_t name_hdr_flags = 0;
if (name == NULL)
- name = get_box_name(ctx, box);
+ name = get_box_name(ctx, box, &name_hdr_flags);
- /* Differentiate between INBOX and <ns prefix>/INBOX */
+ /* Differentiate between INBOX and <ns prefix>/INBOX. The flag
+ bit lets us recover <ns prefix>/INBOX from a box-name header
+ where the on-disk name is just "INBOX". */
const char *orig_vname;
- if ((box->list->ns->flags & NAMESPACE_FLAG_INBOX_USER) != 0 &&
- strcmp(name, "INBOX") == 0)
+ if ((name_hdr_flags & MAILBOX_NAME_HDR_FLAG_INBOX_INBOX) != 0)
+ orig_vname = t_strconcat(box->list->ns->prefix, "INBOX", NULL);
+ else if ((box->list->ns->flags & NAMESPACE_FLAG_INBOX_USER) != 0 &&
+ strcmp(name, "INBOX") == 0)
orig_vname = "INBOX";
else
orig_vname = t_strconcat(box->list->ns->prefix, name, NULL);
/* Mailbox name is corrupted. Rename it to the previous name. */
const char *newname =
mailbox_name_hdr_decode_storage_name(
- box->list, name_hdr, name_hdr_size);
+ box->list, name_hdr, name_hdr_size, NULL);
index_list_rename_corrupted(box, newname);
}
return 0;
mailbox_name_hdr_encode(struct mailbox_list *list, const char *storage_name,
size_t *name_len_r)
{
+ struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT(list);
const char sep[] = {
mailbox_list_get_hierarchy_sep(list),
'\0'
};
+ uint8_t flags = 0;
+
+ /* The on-disk name loses the <escape>49NBOX -> "INBOX" distinction
+ after per-part unescape below, so detect <ns prefix>/INBOX from the
+ storage_name before splitting and remember it as a flag bit. */
+ if (ilist != NULL && ilist->inbox_inbox_storage_name != NULL &&
+ strcmp(storage_name, ilist->inbox_inbox_storage_name) == 0)
+ flags |= MAILBOX_NAME_HDR_FLAG_INBOX_INBOX;
+
/* NOTE: The stored mailbox name may be UTF-8 or mUTF-7 depending on
mailbox_list_utf8 setting. Ideally it would be UTF-8 always.
However, the name is stored unescaped. */
str_append_c(str, '\0');
str_append(str, name_parts[i]);
}
+ if (flags != 0) {
+ /* Backwards compatible extension: append two NUL bytes after
+ the last name part and one byte of flags. The double NUL
+ distinguishes the new format unambiguously - old format
+ never produces two consecutive trailing NULs, since real
+ mailbox storage names contain no empty hierarchy parts. Old
+ Dovecot versions don't know about the flag byte and will
+ parse it as an extra trailing single-byte hierarchy
+ component, so the decoded name on old code paths becomes
+ slightly wrong. This is acceptable because old code never
+ wrote <ns prefix>/INBOX in the first place. */
+ str_append_c(str, '\0');
+ str_append_c(str, '\0');
+ str_append_c(str, flags);
+ }
*name_len_r = str_len(str);
return str_data(str);
}
const char *
mailbox_name_hdr_decode_storage_name(struct mailbox_list *list,
const unsigned char *name_hdr,
- size_t name_hdr_size)
+ size_t name_hdr_size,
+ uint8_t *flags_r)
{
ARRAY_TYPE(const_string) raw_parts;
const char *raw_part;
+ uint8_t flags = 0;
+
+ /* lib-index may grow the header with trailing zero padding, so strip
+ any trailing NULs first. The flag byte (if present) is non-zero by
+ construction, so it survives the strip. */
+ while (name_hdr_size > 0 && name_hdr[name_hdr_size-1] == '\0')
+ name_hdr_size--;
+
+ /* New-format header ends with "<name parts>\0\0<nonzero flag byte>".
+ Old-format headers never produce two consecutive trailing NULs
+ because the encoder splits storage names on the hierarchy
+ separator and real storage names contain no empty parts. So
+ data[size-3]=='\0' && data[size-2]=='\0' unambiguously marks the
+ new format - treat the trailing byte as flags even if some bits
+ are unknown to this Dovecot version, so unknown future flag bits
+ don't get misparsed as a trailing hierarchy component. */
+ if (name_hdr_size >= 3 && name_hdr[name_hdr_size-3] == '\0' &&
+ name_hdr[name_hdr_size-2] == '\0') {
+ flags = name_hdr[name_hdr_size-1];
+ name_hdr_size -= 3;
+ }
+ if (flags_r != NULL)
+ *flags_r = flags;
/* NOTE: The stored mailbox name may be UTF-8 or mUTF-7 depending on
mailbox_list_utf8 setting. Ideally it would be UTF-8 always.
const struct mailbox_list_index_node *node,
string_t *escaped_name);
+/* Flags appended to box-name header after the mailbox name's trailing NUL.
+ Old Dovecot versions wrote no trailing NUL and no flag byte. The flag byte
+ is only present when at least one bit is set, so a 0 byte never encodes
+ "no flags" - that case is encoded by omitting the trailing NUL+flag. */
+enum mailbox_name_hdr_flags {
+ /* The mailbox is <ns prefix>/INBOX (encoded in the header as just
+ "INBOX" so that old Dovecot versions still see a sensible name). */
+ MAILBOX_NAME_HDR_FLAG_INBOX_INBOX = 0x01,
+};
+
/* Return mailbox name encoded into box-name header. */
const unsigned char *
mailbox_name_hdr_encode(struct mailbox_list *list, const char *storage_name,
size_t *name_len_r);
-/* Return mailbox name decoded from box-name header. */
+/* Return mailbox name decoded from box-name header. If flags_r is non-NULL,
+ it is set to the decoded flag bits (0 if the header is in the old format
+ without a flag byte). */
const char *
mailbox_name_hdr_decode_storage_name(struct mailbox_list *list,
const unsigned char *name_hdr,
- size_t name_hdr_size);
+ size_t name_hdr_size,
+ uint8_t *flags_r);
int mailbox_list_index_index_open(struct mailbox_list *list);
bool mailbox_list_index_need_refresh(struct mailbox_list_index *ilist,