/* Copyright (c) 2004-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "buffer.h" #include "str.h" #include "message-parser.h" #include "index-storage.h" #include "index-sync-changes.h" #include "mbox-storage.h" #include "mbox-sync-private.h" /* Line length when to wrap X-IMAP, X-IMAPbase and X-Keywords headers to next line. Keep this pretty long, as after we wrap we lose compatibility with UW-IMAP */ #define KEYWORD_WRAP_LINE_LENGTH 1024 static void status_flags_append(struct mbox_sync_mail_context *ctx, const struct mbox_flag_type *flags_list) { int i; for (i = 0; flags_list[i].chr != 0; i++) { if ((ctx->mail.flags & flags_list[i].flag) != 0) str_append_c(ctx->header, flags_list[i].chr); } } void mbox_sync_move_buffer(struct mbox_sync_mail_context *ctx, size_t pos, size_t need, size_t have) { ssize_t diff = (ssize_t)need - (ssize_t)have; int i; i_assert(have < SSIZE_T_MAX); if (diff == 0) { if (ctx->header_last_change < pos + have || ctx->header_last_change == SIZE_MAX) ctx->header_last_change = pos + have; } else { /* FIXME: if (diff < ctx->space && pos < ctx->offset) then move the data only up to space offset and give/take the space from there. update header_last_change accordingly. (except pos and offset can't be compared directly) */ ctx->header_last_change = SIZE_MAX; for (i = 0; i < MBOX_HDR_COUNT; i++) { if (ctx->hdr_pos[i] > pos && ctx->hdr_pos[i] != SIZE_MAX) ctx->hdr_pos[i] = (ssize_t)ctx->hdr_pos[i] + diff; } if (ctx->mail.space > 0) { i_assert(ctx->mail.offset + ctx->mail.space <= ctx->hdr_offset + pos || ctx->mail.offset > ctx->hdr_offset + pos + have); if (ctx->mail.offset > ctx->hdr_offset + pos) { /* free space offset moves */ ctx->mail.offset = (ssize_t)ctx->mail.offset + diff; } } if (diff < 0) str_delete(ctx->header, pos, -diff); else { ctx->header_last_change = SIZE_MAX; buffer_copy(ctx->header, pos + diff, ctx->header, pos, SIZE_MAX); } } } static void status_flags_replace(struct mbox_sync_mail_context *ctx, size_t pos, const struct mbox_flag_type *flags_list) { unsigned char *data; size_t size; int i, need, have; ctx->mail.flags ^= MBOX_NONRECENT_KLUDGE; if (ctx->header_first_change > pos) ctx->header_first_change = pos; /* how many bytes do we need? */ for (i = 0, need = 0; flags_list[i].chr != 0; i++) { if ((ctx->mail.flags & flags_list[i].flag) != 0) need++; } /* how many bytes do we have now? */ data = buffer_get_modifiable_data(ctx->header, &size); for (have = 0; pos < size; pos++) { if (data[pos] == '\n' || data[pos] == '\r') break; /* see if this is unknown flag for us */ for (i = 0; flags_list[i].chr != 0; i++) { if (flags_list[i].chr == (char)data[pos]) break; } if (flags_list[i].chr != 0) have++; else { /* save this one */ data[pos-have] = data[pos]; } } pos -= have; mbox_sync_move_buffer(ctx, pos, need, have); /* @UNSAFE */ data = buffer_get_space_unsafe(ctx->header, pos, need); for (i = 0; flags_list[i].chr != 0; i++) { if ((ctx->mail.flags & flags_list[i].flag) != 0) *data++ = flags_list[i].chr; } ctx->mail.flags ^= MBOX_NONRECENT_KLUDGE; } static void keywords_append(struct mbox_sync_context *sync_ctx, string_t *dest, const ARRAY_TYPE(keyword_indexes) *keyword_indexes_arr) { struct index_mailbox_context *ibox = INDEX_STORAGE_CONTEXT(&sync_ctx->mbox->box); const char *const *keyword_names; const unsigned int *keyword_indexes; unsigned int i, idx_count, keywords_count; size_t last_break; keyword_names = array_get(ibox->keyword_names, &keywords_count); keyword_indexes = array_get(keyword_indexes_arr, &idx_count); for (i = 0, last_break = str_len(dest); i < idx_count; i++) { i_assert(keyword_indexes[i] < keywords_count); /* wrap the line whenever it gets too long */ if (str_len(dest) - last_break < KEYWORD_WRAP_LINE_LENGTH) { if (i > 0) str_append_c(dest, ' '); } else { str_append(dest, "\n\t"); last_break = str_len(dest); } str_append(dest, keyword_names[keyword_indexes[i]]); } } static void keywords_append_all(struct mbox_sync_mail_context *ctx, string_t *dest, size_t startpos) { struct index_mailbox_context *ibox = INDEX_STORAGE_CONTEXT(&ctx->sync_ctx->mbox->box); const char *const *names; const unsigned char *p; unsigned int i, count; size_t last_break; p = str_data(dest); if (str_len(dest) - startpos < KEYWORD_WRAP_LINE_LENGTH) last_break = startpos; else { /* set last_break to beginning of line */ for (last_break = str_len(dest); last_break > 0; last_break--) { if (p[last_break-1] == '\n') break; } } names = array_get(ibox->keyword_names, &count); for (i = 0; i < count; i++) { /* wrap the line whenever it gets too long */ if (str_len(dest) - last_break < KEYWORD_WRAP_LINE_LENGTH) str_append_c(dest, ' '); else { str_append(dest, "\n\t"); last_break = str_len(dest); } str_append(dest, names[i]); } } static void mbox_sync_add_missing_headers(struct mbox_sync_mail_context *ctx) { size_t new_hdr_size, startpos; new_hdr_size = str_len(ctx->header); if (new_hdr_size > 0 && str_data(ctx->header)[new_hdr_size-1] != '\n') { /* broken header - doesn't end with \n. fix it. */ str_append_c(ctx->header, '\n'); } if (ctx->sync_ctx->dest_first_mail && ctx->hdr_pos[MBOX_HDR_X_IMAPBASE] == SIZE_MAX) { i_assert(ctx->sync_ctx->base_uid_validity != 0); str_append(ctx->header, "X-IMAPbase: "); ctx->hdr_pos[MBOX_HDR_X_IMAPBASE] = str_len(ctx->header); /* startpos must start from identical position as when updating */ startpos = str_len(ctx->header); str_printfa(ctx->header, "%u ", ctx->sync_ctx->base_uid_validity); ctx->last_uid_updated_value = ctx->sync_ctx->next_uid-1; ctx->last_uid_value_start_pos = str_len(ctx->header) - ctx->hdr_pos[MBOX_HDR_X_IMAPBASE]; ctx->imapbase_updated = TRUE; str_printfa(ctx->header, "%010u", ctx->last_uid_updated_value); keywords_append_all(ctx, ctx->header, startpos); str_append_c(ctx->header, '\n'); } if (ctx->hdr_pos[MBOX_HDR_X_UID] == SIZE_MAX && !ctx->mail.pseudo) { str_append(ctx->header, "X-UID: "); ctx->hdr_pos[MBOX_HDR_X_UID] = str_len(ctx->header); str_printfa(ctx->header, "%u\n", ctx->mail.uid); } ctx->mail.flags ^= MBOX_NONRECENT_KLUDGE; if (ctx->hdr_pos[MBOX_HDR_STATUS] == SIZE_MAX && (ctx->mail.flags & STATUS_FLAGS_MASK) != 0) { str_append(ctx->header, "Status: "); ctx->hdr_pos[MBOX_HDR_STATUS] = str_len(ctx->header); status_flags_append(ctx, mbox_status_flags); str_append_c(ctx->header, '\n'); } if (ctx->hdr_pos[MBOX_HDR_X_STATUS] == SIZE_MAX && (ctx->mail.flags & XSTATUS_FLAGS_MASK) != 0) { str_append(ctx->header, "X-Status: "); ctx->hdr_pos[MBOX_HDR_X_STATUS] = str_len(ctx->header); status_flags_append(ctx, mbox_xstatus_flags); str_append_c(ctx->header, '\n'); } ctx->mail.flags ^= MBOX_NONRECENT_KLUDGE; if (ctx->hdr_pos[MBOX_HDR_X_KEYWORDS] == SIZE_MAX && array_is_created(&ctx->mail.keywords) && array_count(&ctx->mail.keywords) > 0) { str_append(ctx->header, "X-Keywords: "); ctx->hdr_pos[MBOX_HDR_X_KEYWORDS] = str_len(ctx->header); keywords_append(ctx->sync_ctx, ctx->header, &ctx->mail.keywords); str_append_c(ctx->header, '\n'); } if (ctx->content_length == UOFF_T_MAX && ctx->mail.body_size >= MBOX_MIN_CONTENT_LENGTH_SIZE) { str_printfa(ctx->header, "Content-Length: %"PRIuUOFF_T"\n", ctx->mail.body_size); } if (str_len(ctx->header) != new_hdr_size) { if (ctx->header_first_change == SIZE_MAX) ctx->header_first_change = new_hdr_size; ctx->header_last_change = SIZE_MAX; } if (ctx->have_eoh) str_append_c(ctx->header, '\n'); } static void mbox_sync_update_status(struct mbox_sync_mail_context *ctx) { if (ctx->hdr_pos[MBOX_HDR_STATUS] != SIZE_MAX) { status_flags_replace(ctx, ctx->hdr_pos[MBOX_HDR_STATUS], mbox_status_flags); } } static void mbox_sync_update_xstatus(struct mbox_sync_mail_context *ctx) { if (ctx->hdr_pos[MBOX_HDR_X_STATUS] != SIZE_MAX) { status_flags_replace(ctx, ctx->hdr_pos[MBOX_HDR_X_STATUS], mbox_xstatus_flags); } } static void mbox_sync_update_line(struct mbox_sync_mail_context *ctx, size_t pos, string_t *new_line) { const char *hdr, *p; uoff_t file_pos; if (ctx->header_first_change > pos) ctx->header_first_change = pos; /* set p = end of header, handle also wrapped headers */ hdr = p = str_c(ctx->header) + pos; for (;;) { p = strchr(p, '\n'); if (p == NULL) { /* shouldn't really happen, but allow anyway.. */ p = hdr + strlen(hdr); break; } if (p[1] != '\t' && p[1] != ' ') break; p += 2; } file_pos = pos + ctx->hdr_offset; if (ctx->mail.space > 0 && ctx->mail.offset >= file_pos && ctx->mail.offset < file_pos + (p - hdr)) { /* extra space points to this line. remove it. */ ctx->mail.offset = ctx->hdr_offset; ctx->mail.space = 0; } mbox_sync_move_buffer(ctx, pos, str_len(new_line), p - hdr + 1); buffer_copy(ctx->header, pos, new_line, 0, SIZE_MAX); } static void mbox_sync_update_xkeywords(struct mbox_sync_mail_context *ctx) { string_t *str; if (ctx->hdr_pos[MBOX_HDR_X_KEYWORDS] == SIZE_MAX) return; str = t_str_new(256); if (array_is_created(&ctx->mail.keywords)) keywords_append(ctx->sync_ctx, str, &ctx->mail.keywords); str_append_c(str, '\n'); mbox_sync_update_line(ctx, ctx->hdr_pos[MBOX_HDR_X_KEYWORDS], str); } static void mbox_sync_update_x_imap_base(struct mbox_sync_mail_context *ctx) { struct mbox_sync_context *sync_ctx = ctx->sync_ctx; string_t *str; i_assert(sync_ctx->base_uid_validity != 0); if (!sync_ctx->dest_first_mail || ctx->hdr_pos[MBOX_HDR_X_IMAPBASE] == SIZE_MAX) return; if (!ctx->imapbase_rewrite) { /* uid-last might need updating, but we'll do it later by writing it directly where needed. */ return; } /* a) keyword list changed, b) uid-last didn't use 10 digits */ str = t_str_new(200); str_printfa(str, "%u ", sync_ctx->base_uid_validity); ctx->last_uid_updated_value = sync_ctx->next_uid-1; ctx->last_uid_value_start_pos = str_len(str); ctx->imapbase_updated = TRUE; str_printfa(str, "%010u", ctx->last_uid_updated_value); keywords_append_all(ctx, str, 0); str_append_c(str, '\n'); mbox_sync_update_line(ctx, ctx->hdr_pos[MBOX_HDR_X_IMAPBASE], str); } static void mbox_sync_update_x_uid(struct mbox_sync_mail_context *ctx) { string_t *str; if (ctx->hdr_pos[MBOX_HDR_X_UID] == SIZE_MAX || ctx->mail.uid == ctx->parsed_uid) return; str = t_str_new(64); str_printfa(str, "%u\n", ctx->mail.uid); mbox_sync_update_line(ctx, ctx->hdr_pos[MBOX_HDR_X_UID], str); } static void mbox_sync_update_header_real(struct mbox_sync_mail_context *ctx) { i_assert(ctx->mail.uid != 0 || ctx->mail.pseudo); if (!ctx->sync_ctx->keep_recent) ctx->mail.flags &= ENUM_NEGATE(MAIL_RECENT); mbox_sync_update_status(ctx); mbox_sync_update_xstatus(ctx); mbox_sync_update_xkeywords(ctx); mbox_sync_update_x_imap_base(ctx); mbox_sync_update_x_uid(ctx); mbox_sync_add_missing_headers(ctx); ctx->updated = TRUE; } void mbox_sync_update_header(struct mbox_sync_mail_context *ctx) { T_BEGIN { mbox_sync_update_header_real(ctx); } T_END; } static void mbox_sync_update_header_from_real(struct mbox_sync_mail_context *ctx, const struct mbox_sync_mail *mail) { if (mail->status_broken || (ctx->mail.flags & STATUS_FLAGS_MASK) != (mail->flags & STATUS_FLAGS_MASK) || (ctx->mail.flags & MAIL_RECENT) != 0) { ctx->mail.flags = (ctx->mail.flags & ENUM_NEGATE(STATUS_FLAGS_MASK)) | (mail->flags & STATUS_FLAGS_MASK); if (!ctx->sync_ctx->keep_recent) ctx->mail.flags &= ENUM_NEGATE(MAIL_RECENT); mbox_sync_update_status(ctx); } if (mail->xstatus_broken || (ctx->mail.flags & XSTATUS_FLAGS_MASK) != (mail->flags & XSTATUS_FLAGS_MASK)) { ctx->mail.flags = (ctx->mail.flags & ENUM_NEGATE(XSTATUS_FLAGS_MASK)) | (mail->flags & XSTATUS_FLAGS_MASK); mbox_sync_update_xstatus(ctx); } if (!array_is_created(&mail->keywords) || array_count(&mail->keywords) == 0) { /* no keywords for this mail */ if (array_is_created(&ctx->mail.keywords)) { array_clear(&ctx->mail.keywords); mbox_sync_update_xkeywords(ctx); } } else if (!array_is_created(&ctx->mail.keywords)) { /* adding first keywords */ p_array_init(&ctx->mail.keywords, ctx->sync_ctx->mail_keyword_pool, array_count(&mail->keywords)); array_append_array(&ctx->mail.keywords, &mail->keywords); mbox_sync_update_xkeywords(ctx); } else if (!array_cmp(&ctx->mail.keywords, &mail->keywords)) { /* keywords changed. */ array_clear(&ctx->mail.keywords); array_append_array(&ctx->mail.keywords, &mail->keywords); mbox_sync_update_xkeywords(ctx); } i_assert(ctx->mail.uid == 0 || ctx->mail.uid == mail->uid); ctx->mail.uid = mail->uid; mbox_sync_update_x_imap_base(ctx); mbox_sync_update_x_uid(ctx); mbox_sync_add_missing_headers(ctx); } void mbox_sync_update_header_from(struct mbox_sync_mail_context *ctx, const struct mbox_sync_mail *mail) { T_BEGIN { mbox_sync_update_header_from_real(ctx, mail); } T_END; }