summaryrefslogtreecommitdiffstats
path: root/pigeonhole/src/lib-sieve/util/edit-mail.c
diff options
context:
space:
mode:
Diffstat (limited to 'pigeonhole/src/lib-sieve/util/edit-mail.c')
-rw-r--r--pigeonhole/src/lib-sieve/util/edit-mail.c2251
1 files changed, 2251 insertions, 0 deletions
diff --git a/pigeonhole/src/lib-sieve/util/edit-mail.c b/pigeonhole/src/lib-sieve/util/edit-mail.c
new file mode 100644
index 0000000..5034e58
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/util/edit-mail.c
@@ -0,0 +1,2251 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+#include "mempool.h"
+#include "llist.h"
+#include "istream-private.h"
+#include "master-service.h"
+#include "master-service-settings.h"
+#include "message-parser.h"
+#include "message-header-encode.h"
+#include "message-header-decode.h"
+#include "mail-user.h"
+#include "mail-storage-private.h"
+#include "index-mail.h"
+#include "raw-storage.h"
+
+#include "rfc2822.h"
+
+#include "edit-mail.h"
+
+/*
+ * Forward declarations
+ */
+
+struct _header_field_index;
+struct _header_field;
+struct _header_index;
+struct _header;
+
+static struct mail_vfuncs edit_mail_vfuncs;
+
+struct edit_mail_istream;
+struct istream *edit_mail_istream_create(struct edit_mail *edmail);
+
+static struct _header_index *
+edit_mail_header_clone(struct edit_mail *edmail, struct _header *header);
+
+/*
+ * Raw storage
+ */
+
+static struct mail_user *edit_mail_user = NULL;
+static unsigned int edit_mail_refcount = 0;
+
+static struct mail_user *edit_mail_raw_storage_get(struct mail_user *mail_user)
+{
+ if (edit_mail_user == NULL) {
+ void **sets =
+ master_service_settings_get_others(master_service);
+
+ edit_mail_user = raw_storage_create_from_set(
+ mail_user->set_info, sets[0]);
+ }
+
+ edit_mail_refcount++;
+
+ return edit_mail_user;
+}
+
+static void edit_mail_raw_storage_drop(void)
+{
+ i_assert(edit_mail_refcount > 0);
+
+ if (--edit_mail_refcount != 0)
+ return;
+
+ mail_user_unref(&edit_mail_user);
+ edit_mail_user = NULL;
+}
+
+/*
+ * Headers
+ */
+
+struct _header_field {
+ struct _header *header;
+
+ unsigned int refcount;
+
+ char *data;
+ size_t size;
+ size_t virtual_size;
+ uoff_t offset;
+ unsigned int lines;
+
+ uoff_t body_offset;
+
+ char *utf8_value;
+};
+
+struct _header_field_index {
+ struct _header_field_index *prev, *next;
+
+ struct _header_field *field;
+ struct _header_index *header;
+};
+
+struct _header {
+ unsigned int refcount;
+
+ char *name;
+};
+
+struct _header_index {
+ struct _header_index *prev, *next;
+
+ struct _header *header;
+
+ struct _header_field_index *first, *last;
+
+ unsigned int count;
+};
+
+static inline struct _header *_header_create(const char *name)
+{
+ struct _header *header;
+
+ header = i_new(struct _header, 1);
+ header->name = i_strdup(name);
+ header->refcount = 1;
+
+ return header;
+}
+
+static inline void _header_ref(struct _header *header)
+{
+ header->refcount++;
+}
+
+static inline void _header_unref(struct _header *header)
+{
+ i_assert(header->refcount > 0);
+ if (--header->refcount != 0)
+ return;
+
+ i_free(header->name);
+ i_free(header);
+}
+
+static inline struct _header_field *_header_field_create(struct _header *header)
+{
+ struct _header_field *hfield;
+
+ hfield = i_new(struct _header_field, 1);
+ hfield->refcount = 1;
+ hfield->header = header;
+ if (header != NULL)
+ _header_ref(header);
+
+ return hfield;
+}
+
+static inline void _header_field_ref(struct _header_field *hfield)
+{
+ hfield->refcount++;
+}
+
+static inline void _header_field_unref(struct _header_field *hfield)
+{
+ i_assert(hfield->refcount > 0);
+ if (--hfield->refcount != 0)
+ return;
+
+ if (hfield->header != NULL)
+ _header_unref(hfield->header);
+
+ if (hfield->data != NULL)
+ i_free(hfield->data);
+ if (hfield->utf8_value != NULL)
+ i_free(hfield->utf8_value);
+ i_free(hfield);
+}
+
+/*
+ * Edit mail object
+ */
+
+struct edit_mail {
+ struct mail_private mail;
+ struct mail_private *wrapped;
+
+ struct edit_mail *parent;
+ unsigned int refcount;
+
+ struct istream *wrapped_stream;
+ struct istream *stream;
+
+ struct _header_index *headers_head, *headers_tail;
+ struct _header_field_index *header_fields_head, *header_fields_tail;
+ struct message_size hdr_size, body_size;
+
+ struct message_size wrapped_hdr_size, wrapped_body_size;
+
+ struct _header_field_index *header_fields_appended;
+ struct message_size appended_hdr_size;
+
+ bool modified:1;
+ bool snapshot_modified:1;
+ bool crlf:1;
+ bool eoh_crlf:1;
+ bool headers_parsed:1;
+ bool destroying_stream:1;
+};
+
+struct edit_mail *edit_mail_wrap(struct mail *mail)
+{
+ struct mail_private *mailp = (struct mail_private *) mail;
+ struct edit_mail *edmail;
+ struct mail_user *raw_mail_user;
+ struct mailbox *raw_box = NULL;
+ struct mailbox_transaction_context *raw_trans;
+ struct message_size hdr_size, body_size;
+ struct istream *wrapped_stream;
+ uoff_t size_diff;
+ pool_t pool;
+
+ if (mail_get_stream(mail, &hdr_size, &body_size, &wrapped_stream) < 0)
+ return NULL;
+
+ /* Create dummy raw mailbox for our wrapper */
+
+ raw_mail_user = edit_mail_raw_storage_get(mail->box->storage->user);
+
+ if (raw_mailbox_alloc_stream(raw_mail_user, wrapped_stream, (time_t)-1,
+ "editor@example.com", &raw_box) < 0) {
+ i_error("edit-mail: failed to open raw box: %s",
+ mailbox_get_last_internal_error(raw_box, NULL));
+ mailbox_free(&raw_box);
+ edit_mail_raw_storage_drop();
+ return NULL;
+ }
+
+ raw_trans = mailbox_transaction_begin(raw_box, 0, __func__);
+
+ /* Create the wrapper mail */
+
+ pool = pool_alloconly_create("edit_mail", 1024);
+ edmail = p_new(pool, struct edit_mail, 1);
+ edmail->refcount = 1;
+ edmail->mail.pool = pool;
+
+ edmail->wrapped = mailp;
+ edmail->wrapped_hdr_size = hdr_size;
+ edmail->wrapped_body_size = body_size;
+
+ edmail->wrapped_stream = wrapped_stream;
+ i_stream_ref(edmail->wrapped_stream);
+
+ /* Determine whether we should use CRLF or LF for the physical message
+ */
+ size_diff = ((hdr_size.virtual_size + body_size.virtual_size) -
+ (hdr_size.physical_size + body_size.physical_size));
+ if (size_diff == 0 || size_diff <= (hdr_size.lines + body_size.lines)/2)
+ edmail->crlf = edmail->eoh_crlf = TRUE;
+
+ array_create(&edmail->mail.module_contexts, pool, sizeof(void *), 5);
+
+ edmail->mail.v = edit_mail_vfuncs;
+ edmail->mail.mail.seq = 1;
+ edmail->mail.mail.box = raw_box;
+ edmail->mail.mail.transaction = raw_trans;
+ edmail->mail.wanted_fields = mailp->wanted_fields;
+ edmail->mail.wanted_headers = mailp->wanted_headers;
+
+ return edmail;
+}
+
+struct edit_mail *edit_mail_snapshot(struct edit_mail *edmail)
+{
+ struct _header_field_index *field_idx, *field_idx_new;
+ struct edit_mail *edmail_new;
+ pool_t pool;
+
+ if (!edmail->snapshot_modified)
+ return edmail;
+
+ pool = pool_alloconly_create("edit_mail", 1024);
+ edmail_new = p_new(pool, struct edit_mail, 1);
+ edmail_new->refcount = 1;
+ edmail_new->mail.pool = pool;
+
+ edmail_new->wrapped = edmail->wrapped;
+ edmail_new->wrapped_hdr_size = edmail->wrapped_hdr_size;
+ edmail_new->wrapped_body_size = edmail->wrapped_body_size;
+ edmail_new->hdr_size = edmail->hdr_size;
+ edmail_new->body_size = edmail->body_size;
+ edmail_new->appended_hdr_size = edmail->appended_hdr_size;
+
+ edmail_new->wrapped_stream = edmail->wrapped_stream;
+ i_stream_ref(edmail_new->wrapped_stream);
+
+ edmail_new->crlf = edmail->crlf;
+ edmail_new->eoh_crlf = edmail->eoh_crlf;
+
+ array_create(&edmail_new->mail.module_contexts, pool,
+ sizeof(void *), 5);
+
+ edmail_new->mail.v = edit_mail_vfuncs;
+ edmail_new->mail.mail.seq = 1;
+ edmail_new->mail.mail.box = edmail->mail.mail.box;
+ edmail_new->mail.mail.transaction = edmail->mail.mail.transaction;
+ edmail_new->mail.wanted_fields = edmail->mail.wanted_fields;
+ edmail_new->mail.wanted_headers = edmail->mail.wanted_headers;
+
+ edmail_new->stream = NULL;
+
+ if (edmail->modified) {
+ field_idx = edmail->header_fields_head;
+ while (field_idx != NULL) {
+ struct _header_field_index *next = field_idx->next;
+
+ field_idx_new = i_new(struct _header_field_index, 1);
+
+ field_idx_new->header = edit_mail_header_clone(
+ edmail_new, field_idx->header->header);
+
+ field_idx_new->field = field_idx->field;
+ _header_field_ref(field_idx_new->field);
+
+ DLLIST2_APPEND(&edmail_new->header_fields_head,
+ &edmail_new->header_fields_tail,
+ field_idx_new);
+
+ field_idx_new->header->count++;
+ if (field_idx->header->first == field_idx)
+ field_idx_new->header->first = field_idx_new;
+ if (field_idx->header->last == field_idx)
+ field_idx_new->header->last = field_idx_new;
+
+ if (field_idx == edmail->header_fields_appended) {
+ edmail_new->header_fields_appended =
+ field_idx_new;
+ }
+
+ field_idx = next;
+ }
+
+ edmail_new->modified = TRUE;
+ }
+
+ edmail_new->headers_parsed = edmail->headers_parsed;
+ edmail_new->parent = edmail;
+
+ return edmail_new;
+}
+
+void edit_mail_reset(struct edit_mail *edmail)
+{
+ struct _header_index *header_idx;
+ struct _header_field_index *field_idx;
+
+ i_stream_unref(&edmail->stream);
+
+ field_idx = edmail->header_fields_head;
+ while (field_idx != NULL) {
+ struct _header_field_index *next = field_idx->next;
+
+ _header_field_unref(field_idx->field);
+ i_free(field_idx);
+
+ field_idx = next;
+ }
+
+ header_idx = edmail->headers_head;
+ while (header_idx != NULL) {
+ struct _header_index *next = header_idx->next;
+
+ _header_unref(header_idx->header);
+ i_free(header_idx);
+
+ header_idx = next;
+ }
+
+ edmail->modified = FALSE;
+}
+
+void edit_mail_unwrap(struct edit_mail **edmail)
+{
+ struct edit_mail *parent;
+
+ i_assert((*edmail)->refcount > 0);
+ if (--(*edmail)->refcount != 0)
+ return;
+
+ edit_mail_reset(*edmail);
+ i_stream_unref(&(*edmail)->wrapped_stream);
+
+ parent = (*edmail)->parent;
+
+ if (parent == NULL) {
+ mailbox_transaction_rollback(&(*edmail)->mail.mail.transaction);
+ mailbox_free(&(*edmail)->mail.mail.box);
+ edit_mail_raw_storage_drop();
+ }
+
+ pool_unref(&(*edmail)->mail.pool);
+ *edmail = NULL;
+
+ if (parent != NULL)
+ edit_mail_unwrap(&parent);
+}
+
+struct mail *edit_mail_get_mail(struct edit_mail *edmail)
+{
+ /* Return wrapped mail when nothing is modified yet */
+ if (!edmail->modified)
+ return &edmail->wrapped->mail;
+
+ return &edmail->mail.mail;
+}
+
+/*
+ * Editing
+ */
+
+static inline void edit_mail_modify(struct edit_mail *edmail)
+{
+ edmail->mail.mail.seq++;
+ edmail->modified = TRUE;
+ edmail->snapshot_modified = TRUE;
+}
+
+/* Header modification */
+
+static inline char *_header_value_unfold(const char *value)
+{
+ string_t *out;
+ unsigned int i;
+
+ for (i = 0; value[i] != '\0'; i++) {
+ if (value[i] == '\r' || value[i] == '\n')
+ break;
+ }
+ if (value[i] == '\0')
+ return i_strdup(value);
+
+ out = t_str_new(i + strlen(value+i) + 10);
+ str_append_data(out, value, i);
+ for (; value[i] != '\0'; i++) {
+ if (value[i] == '\n') {
+ i++;
+ if (value[i] == '\0')
+ break;
+
+ switch (value[i]) {
+ case ' ':
+ str_append_c(out, ' ');
+ break;
+ case '\t':
+ default:
+ str_append_c(out, '\t');
+ }
+ } else {
+ if (value[i] != '\r')
+ str_append_c(out, value[i]);
+ }
+ }
+
+ return i_strndup(str_c(out), str_len(out));
+}
+
+static struct _header_index *
+edit_mail_header_find(struct edit_mail *edmail, const char *field_name)
+{
+ struct _header_index *header_idx;
+
+ header_idx = edmail->headers_head;
+ while (header_idx != NULL) {
+ if (strcasecmp(header_idx->header->name, field_name) == 0)
+ return header_idx;
+
+ header_idx = header_idx->next;
+ }
+
+ return NULL;
+}
+
+static struct _header_index *
+edit_mail_header_create(struct edit_mail *edmail, const char *field_name)
+{
+ struct _header_index *header_idx;
+
+ header_idx = edit_mail_header_find(edmail, field_name);
+ if (header_idx == NULL) {
+ header_idx = i_new(struct _header_index, 1);
+ header_idx->header = _header_create(field_name);
+
+ DLLIST2_APPEND(&edmail->headers_head, &edmail->headers_tail,
+ header_idx);
+ }
+
+ return header_idx;
+}
+
+static struct _header_index *
+edit_mail_header_clone(struct edit_mail *edmail, struct _header *header)
+{
+ struct _header_index *header_idx;
+
+ header_idx = edmail->headers_head;
+ while (header_idx != NULL) {
+ if (header_idx->header == header)
+ return header_idx;
+
+ header_idx = header_idx->next;
+ }
+
+ header_idx = i_new(struct _header_index, 1);
+ header_idx->header = header;
+ _header_ref(header);
+ DLLIST2_APPEND(&edmail->headers_head, &edmail->headers_tail,
+ header_idx);
+
+ return header_idx;
+}
+
+static struct _header_field_index *
+edit_mail_header_field_create(struct edit_mail *edmail, const char *field_name,
+ const char *value)
+{
+ struct _header_index *header_idx;
+ struct _header *header;
+ struct _header_field_index *field_idx;
+ struct _header_field *field;
+ unsigned int lines;
+
+ /* Get/create header index item */
+ header_idx = edit_mail_header_create(edmail, field_name);
+ header = header_idx->header;
+
+ /* Create new field index item */
+ field_idx = i_new(struct _header_field_index, 1);
+ field_idx->header = header_idx;
+ field_idx->field = field = _header_field_create(header);
+
+ /* Create header field data (folded if necessary) */
+ T_BEGIN {
+ string_t *enc_value, *data;
+
+ enc_value = t_str_new(strlen(field_name) + strlen(value) + 64);
+ data = t_str_new(strlen(field_name) + strlen(value) + 128);
+
+ message_header_encode(value, enc_value);
+
+ lines = rfc2822_header_append(data, field_name,
+ str_c(enc_value), edmail->crlf,
+ &field->body_offset);
+
+ /* Copy to new field */
+ field->data = i_strndup(str_data(data), str_len(data));
+ field->size = str_len(data);
+ field->virtual_size = (edmail->crlf ?
+ field->size : field->size + lines);
+ field->lines = lines;
+ } T_END;
+
+ /* Record original (utf8) value */
+ field->utf8_value = _header_value_unfold(value);
+
+ return field_idx;
+}
+
+static void
+edit_mail_header_field_delete(struct edit_mail *edmail,
+ struct _header_field_index *field_idx,
+ bool update_index)
+{
+ struct _header_index *header_idx = field_idx->header;
+ struct _header_field *field = field_idx->field;
+
+ i_assert(header_idx != NULL);
+
+ edmail->hdr_size.physical_size -= field->size;
+ edmail->hdr_size.virtual_size -= field->virtual_size;
+ edmail->hdr_size.lines -= field->lines;
+
+ header_idx->count--;
+ if (update_index) {
+ if (header_idx->count == 0) {
+ DLLIST2_REMOVE(&edmail->headers_head,
+ &edmail->headers_tail, header_idx);
+ _header_unref(header_idx->header);
+ i_free(header_idx);
+ } else if (header_idx->first == field_idx) {
+ struct _header_field_index *hfield =
+ header_idx->first->next;
+
+ while (hfield != NULL && hfield->header != header_idx)
+ hfield = hfield->next;
+
+ i_assert(hfield != NULL);
+ header_idx->first = hfield;
+ } else if (header_idx->last == field_idx) {
+ struct _header_field_index *hfield =
+ header_idx->last->prev;
+
+ while (hfield != NULL && hfield->header != header_idx)
+ hfield = hfield->prev;
+
+ i_assert(hfield != NULL);
+ header_idx->last = hfield;
+ }
+ }
+
+ DLLIST2_REMOVE(&edmail->header_fields_head, &edmail->header_fields_tail,
+ field_idx);
+ _header_field_unref(field_idx->field);
+ i_free(field_idx);
+}
+
+static struct _header_field_index *
+edit_mail_header_field_replace(struct edit_mail *edmail,
+ struct _header_field_index *field_idx,
+ const char *newname, const char *newvalue,
+ bool update_index)
+{
+ struct _header_field_index *field_idx_new;
+ struct _header_index *header_idx = field_idx->header, *header_idx_new;
+ struct _header_field *field = field_idx->field, *field_new;
+
+ i_assert(header_idx != NULL);
+ i_assert(newname != NULL || newvalue != NULL);
+
+ if (newname == NULL)
+ newname = header_idx->header->name;
+ if (newvalue == NULL)
+ newvalue = field_idx->field->utf8_value;
+ field_idx_new = edit_mail_header_field_create(
+ edmail, newname, newvalue);
+ field_new = field_idx_new->field;
+ header_idx_new = field_idx_new->header;
+
+ edmail->hdr_size.physical_size -= field->size;
+ edmail->hdr_size.virtual_size -= field->virtual_size;
+ edmail->hdr_size.lines -= field->lines;
+
+ edmail->hdr_size.physical_size += field_new->size;
+ edmail->hdr_size.virtual_size += field_new->virtual_size;
+ edmail->hdr_size.lines += field_new->lines;
+
+ /* Replace header field index */
+ field_idx_new->prev = field_idx->prev;
+ field_idx_new->next = field_idx->next;
+ if (field_idx->prev != NULL)
+ field_idx->prev->next = field_idx_new;
+ if (field_idx->next != NULL)
+ field_idx->next->prev = field_idx_new;
+ if (edmail->header_fields_head == field_idx)
+ edmail->header_fields_head = field_idx_new;
+ if (edmail->header_fields_tail == field_idx)
+ edmail->header_fields_tail = field_idx_new;
+
+ if (header_idx_new == header_idx) {
+ if (header_idx->first == field_idx)
+ header_idx->first = field_idx_new;
+ if (header_idx->last == field_idx)
+ header_idx->last = field_idx_new;
+ } else {
+ header_idx->count--;
+ header_idx_new->count++;
+
+ if (update_index) {
+ if (header_idx->count == 0) {
+ DLLIST2_REMOVE(&edmail->headers_head,
+ &edmail->headers_tail,
+ header_idx);
+ _header_unref(header_idx->header);
+ i_free(header_idx);
+ } else if (header_idx->first == field_idx) {
+ struct _header_field_index *hfield =
+ header_idx->first->next;
+
+ while (hfield != NULL &&
+ hfield->header != header_idx)
+ hfield = hfield->next;
+
+ i_assert(hfield != NULL);
+ header_idx->first = hfield;
+ } else if (header_idx->last == field_idx) {
+ struct _header_field_index *hfield =
+ header_idx->last->prev;
+
+ while (hfield != NULL &&
+ hfield->header != header_idx)
+ hfield = hfield->prev;
+
+ i_assert(hfield != NULL);
+ header_idx->last = hfield;
+ }
+ if (header_idx_new->count > 0) {
+ struct _header_field_index *hfield;
+
+ hfield = edmail->header_fields_head;
+ while (hfield != NULL &&
+ hfield->header != header_idx_new)
+ hfield = hfield->next;
+
+ i_assert(hfield != NULL);
+ header_idx_new->first = hfield;
+
+ hfield = edmail->header_fields_tail;
+ while (hfield != NULL &&
+ hfield->header != header_idx_new)
+ hfield = hfield->prev;
+
+ i_assert(hfield != NULL);
+ header_idx_new->last = hfield;
+ }
+ }
+ }
+
+ _header_field_unref(field_idx->field);
+ i_free(field_idx);
+ return field_idx_new;
+}
+
+static inline char *
+_header_decode(const unsigned char *hdr_data, size_t hdr_data_len)
+{
+ string_t *str = t_str_new(512);
+
+ /* hdr_data is already unfolded */
+
+ /* Decode MIME encoded-words. */
+ message_header_decode_utf8((const unsigned char *)hdr_data,
+ hdr_data_len, str, NULL);
+ return i_strdup(str_c(str));
+}
+
+static int edit_mail_headers_parse(struct edit_mail *edmail)
+{
+ struct message_header_parser_ctx *hparser;
+ enum message_header_parser_flags hparser_flags =
+ MESSAGE_HEADER_PARSER_FLAG_SKIP_INITIAL_LWSP |
+ MESSAGE_HEADER_PARSER_FLAG_CLEAN_ONELINE;
+ struct message_header_line *hdr;
+ struct _header_index *header_idx;
+ struct _header_field_index *head = NULL, *tail = NULL, *current;
+ string_t *hdr_data;
+ uoff_t offset = 0, body_offset = 0, vsize_diff = 0;
+ unsigned int lines = 0;
+ int ret;
+
+ if (edmail->headers_parsed)
+ return 1;
+
+ i_stream_seek(edmail->wrapped_stream, 0);
+ hparser = message_parse_header_init(edmail->wrapped_stream, NULL,
+ hparser_flags);
+
+ T_BEGIN {
+ hdr_data = t_str_new(1024);
+ while ((ret = message_parse_header_next(hparser, &hdr)) > 0) {
+ struct _header_field_index *field_idx_new;
+ struct _header_field *field;
+
+ if (hdr->eoh) {
+ /* Record whether header ends in CRLF or LF */
+ edmail->eoh_crlf = hdr->crlf_newline;
+ }
+
+ if (hdr == NULL || hdr->eoh)
+ break;
+
+ /* We deny the existence of any `Content-Length:'
+ header. This header is non-standard and it can wreak
+ havok when the message is modified.
+ */
+ if (strcasecmp(hdr->name, "Content-Length" ) == 0)
+ continue;
+
+ if (hdr->continued) {
+ /* Continued line of folded header */
+ buffer_append(hdr_data, hdr->value,
+ hdr->value_len);
+ } else {
+ /* First line of header */
+ offset = hdr->name_offset;
+ body_offset = hdr->name_len + hdr->middle_len;
+ str_truncate(hdr_data, 0);
+ buffer_append(hdr_data, hdr->name,
+ hdr->name_len);
+ buffer_append(hdr_data, hdr->middle,
+ hdr->middle_len);
+ buffer_append(hdr_data, hdr->value,
+ hdr->value_len);
+ lines = 0;
+ vsize_diff = 0;
+ }
+
+ if (!hdr->no_newline) {
+ lines++;
+
+ if (hdr->crlf_newline) {
+ buffer_append(hdr_data, "\r\n", 2);
+ } else {
+ buffer_append(hdr_data, "\n", 1);
+ vsize_diff++;
+ }
+ }
+
+ if (hdr->continues) {
+ hdr->use_full_value = TRUE;
+ continue;
+ }
+
+ /* Create new header field index entry */
+
+ field_idx_new = i_new(struct _header_field_index, 1);
+
+ header_idx = edit_mail_header_create(edmail, hdr->name);
+ header_idx->count++;
+ field_idx_new->header = header_idx;
+ field_idx_new->field = field =
+ _header_field_create(header_idx->header);
+
+ i_assert(body_offset > 0);
+ field->body_offset = body_offset;
+
+ field->utf8_value = _header_decode(hdr->full_value,
+ hdr->full_value_len);
+
+ field->size = str_len(hdr_data);
+ field->virtual_size = field->size + vsize_diff;
+ field->data = i_strndup(str_data(hdr_data),
+ field->size);
+ field->offset = offset;
+ field->lines = lines;
+
+ DLLIST2_APPEND(&head, &tail, field_idx_new);
+
+ edmail->hdr_size.physical_size += field->size;
+ edmail->hdr_size.virtual_size += field->virtual_size;
+ edmail->hdr_size.lines += lines;
+ }
+ } T_END;
+
+ message_parse_header_deinit(&hparser);
+
+ /* Blocking i/o required */
+ i_assert(ret != 0);
+
+ if (ret < 0 && edmail->wrapped_stream->stream_errno != 0) {
+ /* Error; clean up */
+ i_error("read(%s) failed: %s",
+ i_stream_get_name(edmail->wrapped_stream),
+ i_stream_get_error(edmail->wrapped_stream));
+ current = head;
+ while (current != NULL) {
+ struct _header_field_index *next = current->next;
+
+ _header_field_unref(current->field);
+ i_free(current);
+
+ current = next;
+ }
+
+ return ret;
+ }
+
+ /* Insert header field index items in main list */
+ if (head != NULL && tail != NULL) {
+ if (edmail->header_fields_appended != NULL) {
+ if (edmail->header_fields_head !=
+ edmail->header_fields_appended) {
+ edmail->header_fields_appended->prev->next = head;
+ head->prev = edmail->header_fields_appended->prev;
+ } else {
+ edmail->header_fields_head = head;
+ }
+
+ tail->next = edmail->header_fields_appended;
+ edmail->header_fields_appended->prev = tail;
+ } else if (edmail->header_fields_tail != NULL) {
+ edmail->header_fields_tail->next = head;
+ head->prev = edmail->header_fields_tail;
+ edmail->header_fields_tail = tail;
+ } else {
+ edmail->header_fields_head = head;
+ edmail->header_fields_tail = tail;
+ }
+ }
+
+ /* Rebuild header index */
+ current = edmail->header_fields_head;
+ while (current != NULL) {
+ if (current->header->first == NULL)
+ current->header->first = current;
+ current->header->last = current;
+
+ current = current->next;
+ }
+
+ /* Clear appended headers */
+ edmail->header_fields_appended = NULL;
+ edmail->appended_hdr_size.physical_size = 0;
+ edmail->appended_hdr_size.virtual_size = 0;
+ edmail->appended_hdr_size.lines = 0;
+
+ /* Do not parse headers again */
+ edmail->headers_parsed = TRUE;
+
+ return 1;
+}
+
+void edit_mail_header_add(struct edit_mail *edmail, const char *field_name,
+ const char *value, bool last)
+{
+ struct _header_index *header_idx;
+ struct _header_field_index *field_idx;
+ struct _header_field *field;
+
+ edit_mail_modify(edmail);
+
+ field_idx = edit_mail_header_field_create(edmail, field_name, value);
+ header_idx = field_idx->header;
+ field = field_idx->field;
+
+ /* Add it to the header field index */
+ if (last) {
+ DLLIST2_APPEND(&edmail->header_fields_head,
+ &edmail->header_fields_tail, field_idx);
+
+ header_idx->last = field_idx;
+ if (header_idx->first == NULL)
+ header_idx->first = field_idx;
+
+ if (!edmail->headers_parsed) {
+ if (edmail->header_fields_appended == NULL) {
+ /* Record beginning of appended headers */
+ edmail->header_fields_appended = field_idx;
+ }
+
+ edmail->appended_hdr_size.physical_size += field->size;
+ edmail->appended_hdr_size.virtual_size += field->virtual_size;
+ edmail->appended_hdr_size.lines += field->lines;
+ }
+ } else {
+ DLLIST2_PREPEND(&edmail->header_fields_head,
+ &edmail->header_fields_tail, field_idx);
+
+ header_idx->first = field_idx;
+ if (header_idx->last == NULL)
+ header_idx->last = field_idx;
+ }
+
+ header_idx->count++;
+
+ edmail->hdr_size.physical_size += field->size;
+ edmail->hdr_size.virtual_size += field->virtual_size;
+ edmail->hdr_size.lines += field->lines;
+}
+
+int edit_mail_header_delete(struct edit_mail *edmail, const char *field_name,
+ int index)
+{
+ struct _header_index *header_idx;
+ struct _header_field_index *field_idx;
+ int pos = 0;
+ int ret = 0;
+
+ /* Make sure headers are parsed */
+ if (edit_mail_headers_parse(edmail) <= 0)
+ return -1;
+
+ /* Find the header entry */
+ header_idx = edit_mail_header_find(edmail, field_name);
+ if (header_idx == NULL) {
+ /* Not found */
+ return 0;
+ }
+
+ /* Signal modification */
+ edit_mail_modify(edmail);
+
+ /* Iterate through all header fields and remove those that match */
+ field_idx = (index >= 0 ? header_idx->first : header_idx->last);
+ while (field_idx != NULL) {
+ struct _header_field_index *next =
+ (index >= 0 ? field_idx->next : field_idx->prev);
+
+ if (field_idx->field->header == header_idx->header) {
+ bool final;
+
+ if (index >= 0) {
+ pos++;
+ final = (header_idx->last == field_idx);
+ } else {
+ pos--;
+ final = (header_idx->first == field_idx);
+ }
+
+ if (index == 0 || index == pos) {
+ if (header_idx->first == field_idx)
+ header_idx->first = NULL;
+ if (header_idx->last == field_idx)
+ header_idx->last = NULL;
+ edit_mail_header_field_delete(
+ edmail, field_idx, FALSE);
+ ret++;
+ }
+
+ if (final || (index != 0 && index == pos))
+ break;
+ }
+
+ field_idx = next;
+ }
+
+ if (index == 0 || header_idx->count == 0) {
+ DLLIST2_REMOVE(&edmail->headers_head,
+ &edmail->headers_tail, header_idx);
+ _header_unref(header_idx->header);
+ i_free(header_idx);
+ } else if (header_idx->first == NULL || header_idx->last == NULL) {
+ struct _header_field_index *current =
+ edmail->header_fields_head;
+
+ while (current != NULL) {
+ if (current->header == header_idx) {
+ if (header_idx->first == NULL)
+ header_idx->first = current;
+ header_idx->last = current;
+ }
+ current = current->next;
+ }
+ }
+
+ return ret;
+}
+
+int edit_mail_header_replace(struct edit_mail *edmail,
+ const char *field_name, int index,
+ const char *newname, const char *newvalue)
+{
+ struct _header_index *header_idx, *header_idx_new;
+ struct _header_field_index *field_idx, *field_idx_new;
+ int pos = 0;
+ int ret = 0;
+
+ /* Make sure headers are parsed */
+ if (edit_mail_headers_parse(edmail) <= 0)
+ return -1;
+
+ /* Find the header entry */
+ header_idx = edit_mail_header_find(edmail, field_name);
+ if (header_idx == NULL) {
+ /* Not found */
+ return 0;
+ }
+
+ /* Signal modification */
+ edit_mail_modify(edmail);
+
+ /* Iterate through all header fields and replace those that match */
+ field_idx = (index >= 0 ? header_idx->first : header_idx->last);
+ field_idx_new = NULL;
+ while (field_idx != NULL) {
+ struct _header_field_index *next =
+ (index >= 0 ? field_idx->next : field_idx->prev);
+
+ if (field_idx->field->header == header_idx->header) {
+ bool final;
+
+ if (index >= 0) {
+ pos++;
+ final = (header_idx->last == field_idx);
+ } else {
+ pos--;
+ final = (header_idx->first == field_idx);
+ }
+
+ if (index == 0 || index == pos) {
+ if (header_idx->first == field_idx)
+ header_idx->first = NULL;
+ if (header_idx->last == field_idx)
+ header_idx->last = NULL;
+ field_idx_new = edit_mail_header_field_replace(
+ edmail, field_idx, newname, newvalue,
+ FALSE);
+ ret++;
+ }
+
+ if (final || (index != 0 && index == pos))
+ break;
+ }
+
+ field_idx = next;
+ }
+
+ /* Update old header index */
+ if (header_idx->count == 0) {
+ DLLIST2_REMOVE(&edmail->headers_head, &edmail->headers_tail,
+ header_idx);
+ _header_unref(header_idx->header);
+ i_free(header_idx);
+ } else if (header_idx->first == NULL || header_idx->last == NULL) {
+ struct _header_field_index *current =
+ edmail->header_fields_head;
+
+ while (current != NULL) {
+ if (current->header == header_idx) {
+ if (header_idx->first == NULL)
+ header_idx->first = current;
+ header_idx->last = current;
+ }
+ current = current->next;
+ }
+ }
+
+ /* Update new header index */
+ if (field_idx_new != NULL) {
+ struct _header_field_index *current =
+ edmail->header_fields_head;
+
+ header_idx_new = field_idx_new->header;
+ while (current != NULL) {
+ if (current->header == header_idx_new) {
+ if (header_idx_new->first == NULL)
+ header_idx_new->first = current;
+ header_idx_new->last = current;
+ }
+ current = current->next;
+ }
+ }
+
+ return ret;
+}
+
+struct edit_mail_header_iter
+{
+ struct edit_mail *mail;
+ struct _header_index *header;
+ struct _header_field_index *current;
+
+ bool reverse:1;
+};
+
+int edit_mail_headers_iterate_init(struct edit_mail *edmail,
+ const char *field_name, bool reverse,
+ struct edit_mail_header_iter **edhiter_r)
+{
+ struct edit_mail_header_iter *edhiter;
+ struct _header_index *header_idx = NULL;
+ struct _header_field_index *current = NULL;
+
+ /* Make sure headers are parsed */
+ if (edit_mail_headers_parse(edmail) <= 0) {
+ /* Failure */
+ return -1;
+ }
+
+ header_idx = edit_mail_header_find(edmail, field_name);
+
+ if (field_name != NULL && header_idx == NULL) {
+ current = NULL;
+ } else if (!reverse) {
+ current = (header_idx != NULL ?
+ header_idx->first : edmail->header_fields_head);
+ } else {
+ current = (header_idx != NULL ?
+ header_idx->last : edmail->header_fields_tail);
+ if (current->header == NULL)
+ current = current->prev;
+ }
+
+ if (current == NULL)
+ return 0;
+
+ edhiter = i_new(struct edit_mail_header_iter, 1);
+ edhiter->mail = edmail;
+ edhiter->header = header_idx;
+ edhiter->reverse = reverse;
+ edhiter->current = current;
+
+ *edhiter_r = edhiter;
+ return 1;
+}
+
+void edit_mail_headers_iterate_deinit(struct edit_mail_header_iter **edhiter)
+{
+ i_free(*edhiter);
+ *edhiter = NULL;
+}
+
+void edit_mail_headers_iterate_get(struct edit_mail_header_iter *edhiter,
+ const char **value_r)
+{
+ const char *raw;
+ int i;
+
+ i_assert(edhiter->current != NULL && edhiter->current->header != NULL);
+
+ raw = edhiter->current->field->utf8_value;
+ for (i = strlen(raw)-1; i >= 0; i--) {
+ if (raw[i] != ' ' && raw[i] != '\t')
+ break;
+ }
+
+ *value_r = t_strndup(raw, i+1);
+}
+
+bool edit_mail_headers_iterate_next(struct edit_mail_header_iter *edhiter)
+{
+ if (edhiter->current == NULL)
+ return FALSE;
+
+ do {
+ edhiter->current = (!edhiter->reverse ?
+ edhiter->current->next :
+ edhiter->current->prev );
+ } while (edhiter->current != NULL && edhiter->current->header != NULL &&
+ edhiter->header != NULL &&
+ edhiter->current->header != edhiter->header);
+
+ return (edhiter->current != NULL && edhiter->current->header != NULL);
+}
+
+bool edit_mail_headers_iterate_remove(struct edit_mail_header_iter *edhiter)
+{
+ struct _header_field_index *field_idx;
+ bool next;
+
+ i_assert(edhiter->current != NULL && edhiter->current->header != NULL);
+
+ edit_mail_modify(edhiter->mail);
+
+ field_idx = edhiter->current;
+ next = edit_mail_headers_iterate_next(edhiter);
+ edit_mail_header_field_delete(edhiter->mail, field_idx, TRUE);
+
+ return next;
+}
+
+bool edit_mail_headers_iterate_replace(struct edit_mail_header_iter *edhiter,
+ const char *newname,
+ const char *newvalue)
+{
+ struct _header_field_index *field_idx;
+ bool next;
+
+ i_assert(edhiter->current != NULL && edhiter->current->header != NULL);
+
+ edit_mail_modify(edhiter->mail);
+
+ field_idx = edhiter->current;
+ next = edit_mail_headers_iterate_next(edhiter);
+ edit_mail_header_field_replace(edhiter->mail, field_idx,
+ newname, newvalue, TRUE);
+
+ return next;
+}
+
+/* Body modification */
+
+// FIXME: implement
+
+/*
+ * Mail API
+ */
+
+static void edit_mail_close(struct mail *mail)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ edmail->wrapped->v.close(&edmail->wrapped->mail);
+}
+
+static void edit_mail_free(struct mail *mail)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ edmail->wrapped->v.free(&edmail->wrapped->mail);
+
+ edit_mail_unwrap(&edmail);
+}
+
+static void
+edit_mail_set_seq(struct mail *mail ATTR_UNUSED, uint32_t seq ATTR_UNUSED,
+ bool saving ATTR_UNUSED)
+{
+ i_panic("edit_mail_set_seq() not implemented");
+}
+
+static bool ATTR_NORETURN
+edit_mail_set_uid(struct mail *mail ATTR_UNUSED, uint32_t uid ATTR_UNUSED)
+{
+ i_panic("edit_mail_set_uid() not implemented");
+}
+
+static void edit_mail_set_uid_cache_updates(struct mail *mail, bool set)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ edmail->wrapped->v.set_uid_cache_updates(&edmail->wrapped->mail, set);
+}
+
+static void
+edit_mail_add_temp_wanted_fields(
+ struct mail *mail ATTR_UNUSED, enum mail_fetch_field fields ATTR_UNUSED,
+ struct mailbox_header_lookup_ctx *headers ATTR_UNUSED)
+{
+ /* Nothing */
+}
+
+static enum mail_flags edit_mail_get_flags(struct mail *mail)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ return edmail->wrapped->v.get_flags(&edmail->wrapped->mail);
+}
+
+static const char *const *edit_mail_get_keywords(struct mail *mail)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ return edmail->wrapped->v.get_keywords(&edmail->wrapped->mail);
+}
+
+static const ARRAY_TYPE(keyword_indexes) *
+edit_mail_get_keyword_indexes(struct mail *mail)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ return edmail->wrapped->v.get_keyword_indexes(&edmail->wrapped->mail);
+}
+
+static uint64_t edit_mail_get_modseq(struct mail *mail)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ return edmail->wrapped->v.get_modseq(&edmail->wrapped->mail);
+}
+
+static uint64_t edit_mail_get_pvt_modseq(struct mail *mail)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ return edmail->wrapped->v.get_pvt_modseq(&edmail->wrapped->mail);
+}
+
+static int edit_mail_get_parts(struct mail *mail, struct message_part **parts_r)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ return edmail->wrapped->v.get_parts(&edmail->wrapped->mail, parts_r);
+}
+
+static int
+edit_mail_get_date(struct mail *mail, time_t *date_r, int *timezone_r)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ return edmail->wrapped->v.get_date(&edmail->wrapped->mail,
+ date_r, timezone_r);
+}
+
+static int edit_mail_get_received_date(struct mail *mail, time_t *date_r)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ return edmail->wrapped->v.get_received_date(&edmail->wrapped->mail,
+ date_r);
+}
+
+static int edit_mail_get_save_date(struct mail *mail, time_t *date_r)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ return edmail->wrapped->v.get_save_date(&edmail->wrapped->mail, date_r);
+}
+
+static int edit_mail_get_virtual_size(struct mail *mail, uoff_t *size_r)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ if (!edmail->headers_parsed) {
+ *size_r = (edmail->wrapped_hdr_size.virtual_size +
+ edmail->wrapped_body_size.virtual_size);
+
+ if (!edmail->modified)
+ return 0;
+ } else {
+ *size_r = edmail->wrapped_body_size.virtual_size + 2;
+ }
+
+ *size_r += (edmail->hdr_size.virtual_size +
+ edmail->body_size.virtual_size);
+ return 0;
+}
+
+static int edit_mail_get_physical_size(struct mail *mail, uoff_t *size_r)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ *size_r = 0;
+ if (!edmail->headers_parsed) {
+ *size_r = (edmail->wrapped_hdr_size.physical_size +
+ edmail->wrapped_body_size.physical_size);
+
+ if (!edmail->modified)
+ return 0;
+ } else {
+ *size_r = (edmail->wrapped_body_size.physical_size +
+ (edmail->eoh_crlf ? 2 : 1));
+ }
+
+ *size_r += (edmail->hdr_size.physical_size +
+ edmail->body_size.physical_size);
+ return 0;
+}
+
+static int
+edit_mail_get_first_header(struct mail *mail, const char *field_name,
+ bool decode_to_utf8, const char **value_r)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+ struct _header_index *header_idx;
+ struct _header_field *field;
+ int ret;
+
+ /* Check whether mail headers were modified at all */
+ if (!edmail->modified || edmail->headers_head == NULL) {
+ /* Unmodified */
+ return edmail->wrapped->v.get_first_header(
+ &edmail->wrapped->mail, field_name, decode_to_utf8,
+ value_r);
+ }
+
+ /* Try to find modified header */
+ header_idx = edit_mail_header_find(edmail, field_name);
+ if (header_idx == NULL || header_idx->count == 0 ) {
+ if (!edmail->headers_parsed) {
+ /* No new header */
+ return edmail->wrapped->v.get_first_header(
+ &edmail->wrapped->mail, field_name,
+ decode_to_utf8, value_r);
+ }
+
+ *value_r = NULL;
+ return 0;
+ }
+
+ /* Get the first occurrence */
+ if (edmail->header_fields_appended == NULL) {
+ /* There are no appended headers, so first is found directly */
+ field = header_idx->first->field;
+ } else {
+ struct _header_field_index *field_idx;
+
+ /* Scan prepended headers */
+ field_idx = edmail->header_fields_head;
+ while (field_idx != NULL) {
+ if (field_idx->header == header_idx)
+ break;
+
+ if (field_idx == edmail->header_fields_appended) {
+ field_idx = NULL;
+ break;
+ }
+ field_idx = field_idx->next;
+ }
+
+ if (field_idx == NULL) {
+ /* Check original message */
+ ret = edmail->wrapped->v.get_first_header(
+ &edmail->wrapped->mail, field_name,
+ decode_to_utf8, value_r);
+ if (ret != 0)
+ return ret;
+
+ /* Use first (apparently appended) header */
+ field = header_idx->first->field;
+ } else {
+ field = field_idx->field;
+ }
+ }
+
+ if (decode_to_utf8)
+ *value_r = field->utf8_value;
+ else
+ *value_r = (const char *)(field->data + field->body_offset);
+ return 1;
+}
+
+static int
+edit_mail_get_headers(struct mail *mail, const char *field_name,
+ bool decode_to_utf8, const char *const **value_r)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+ struct _header_index *header_idx;
+ struct _header_field_index *field_idx;
+ const char *const *headers;
+ ARRAY(const char *) header_values;
+
+ if (!edmail->modified || edmail->headers_head == NULL) {
+ /* Unmodified */
+ return edmail->wrapped->v.get_headers(
+ &edmail->wrapped->mail, field_name, decode_to_utf8,
+ value_r);
+ }
+
+ header_idx = edit_mail_header_find(edmail, field_name);
+ if (header_idx == NULL || header_idx->count == 0 ) {
+ if (!edmail->headers_parsed) {
+ /* No new header */
+ return edmail->wrapped->v.get_headers(
+ &edmail->wrapped->mail, field_name,
+ decode_to_utf8, value_r);
+ }
+
+ p_array_init(&header_values, edmail->mail.pool, 1);
+ (void)array_append_space(&header_values);
+ *value_r = array_idx(&header_values, 0);
+ return 0;
+ }
+
+ /* Merge */
+
+ /* Read original headers too if message headers are not parsed */
+ headers = NULL;
+ if (!edmail->headers_parsed &&
+ edmail->wrapped->v.get_headers(&edmail->wrapped->mail, field_name,
+ decode_to_utf8, &headers) < 0)
+ return -1;
+
+ /* Fill result array */
+ p_array_init(&header_values, edmail->mail.pool, 32);
+ field_idx = header_idx->first;
+ while (field_idx != NULL) {
+ /* If current field is the first appended one, we need to add
+ original headers first.
+ */
+ if (field_idx == edmail->header_fields_appended &&
+ headers != NULL) {
+ while (*headers != NULL) {
+ array_append(&header_values, headers, 1);
+ headers++;
+ }
+ }
+
+ /* Add modified header to the list */
+ if (field_idx->field->header == header_idx->header) {
+ struct _header_field *field = field_idx->field;
+
+ const char *value;
+ if (decode_to_utf8)
+ value = field->utf8_value;
+ else {
+ value = (const char *)(field->data +
+ field->body_offset);
+ }
+
+ array_append(&header_values, &value, 1);
+
+ if (field_idx == header_idx->last)
+ break;
+ }
+
+ field_idx = field_idx->next;
+ }
+
+ /* Add original headers if necessary */
+ if (headers != NULL) {
+ while (*headers != NULL) {
+ array_append(&header_values, headers, 1);
+ headers++;
+ }
+ }
+
+ (void)array_append_space(&header_values);
+ *value_r = array_idx(&header_values, 0);
+ return 1;
+}
+
+static int ATTR_NORETURN
+edit_mail_get_header_stream(
+ struct mail *mail ATTR_UNUSED,
+ struct mailbox_header_lookup_ctx *headers ATTR_UNUSED,
+ struct istream **stream_r ATTR_UNUSED)
+{
+ // FIXME: implement!
+ i_panic("edit_mail_get_header_stream() not implemented");
+}
+
+static int
+edit_mail_get_stream(struct mail *mail, bool get_body ATTR_UNUSED,
+ struct message_size *hdr_size,
+ struct message_size *body_size, struct istream **stream_r)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ if (edmail->stream == NULL)
+ edmail->stream = edit_mail_istream_create(edmail);
+
+ if (hdr_size != NULL) {
+ *hdr_size = edmail->wrapped_hdr_size;
+ hdr_size->physical_size += edmail->hdr_size.physical_size;
+ hdr_size->virtual_size += edmail->hdr_size.virtual_size;
+ hdr_size->lines += edmail->hdr_size.lines;
+ }
+
+ if (body_size != NULL)
+ *body_size = edmail->wrapped_body_size;
+
+ *stream_r = edmail->stream;
+ i_stream_seek(edmail->stream, 0);
+
+ return 0;
+}
+
+static int
+edit_mail_get_special(struct mail *mail, enum mail_fetch_field field,
+ const char **value_r)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ if (edmail->modified) {
+ /* Block certain fields when modified */
+
+ switch (field) {
+ case MAIL_FETCH_GUID:
+ /* This is in essence a new message */
+ *value_r = "";
+ return 0;
+ case MAIL_FETCH_STORAGE_ID:
+ /* Prevent hardlink copying */
+ *value_r = "";
+ return 0;
+ default:
+ break;
+ }
+ }
+
+ return edmail->wrapped->v.get_special(&edmail->wrapped->mail,
+ field, value_r);
+}
+
+static int
+edit_mail_get_backend_mail(struct mail *mail, struct mail **real_mail_r)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ *real_mail_r = edit_mail_get_mail(edmail);
+ return 0;
+}
+
+static void
+edit_mail_update_flags(struct mail *mail, enum modify_type modify_type,
+ enum mail_flags flags)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ edmail->wrapped->v.update_flags(&edmail->wrapped->mail,
+ modify_type, flags);
+}
+
+static void
+edit_mail_update_keywords(struct mail *mail, enum modify_type modify_type,
+ struct mail_keywords *keywords)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ edmail->wrapped->v.update_keywords(&edmail->wrapped->mail,
+ modify_type, keywords);
+}
+
+static void edit_mail_update_modseq(struct mail *mail, uint64_t min_modseq)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ edmail->wrapped->v.update_modseq(&edmail->wrapped->mail, min_modseq);
+}
+
+static void
+edit_mail_update_pvt_modseq(struct mail *mail, uint64_t min_pvt_modseq)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ edmail->wrapped->v.update_pvt_modseq(&edmail->wrapped->mail,
+ min_pvt_modseq);
+}
+
+static void edit_mail_update_pop3_uidl(struct mail *mail, const char *uidl)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ if (edmail->wrapped->v.update_pop3_uidl != NULL) {
+ edmail->wrapped->v.update_pop3_uidl(
+ &edmail->wrapped->mail, uidl);
+ }
+}
+
+static void edit_mail_expunge(struct mail *mail ATTR_UNUSED)
+{
+ /* NOOP */
+}
+
+static void
+edit_mail_set_cache_corrupted(struct mail *mail, enum mail_fetch_field field,
+ const char *reason)
+{
+ struct edit_mail *edmail = (struct edit_mail *)mail;
+
+ edmail->wrapped->v.set_cache_corrupted(&edmail->wrapped->mail,
+ field, reason);
+}
+
+static struct mail_vfuncs edit_mail_vfuncs = {
+ edit_mail_close,
+ edit_mail_free,
+ edit_mail_set_seq,
+ edit_mail_set_uid,
+ edit_mail_set_uid_cache_updates,
+ NULL,
+ NULL,
+ edit_mail_add_temp_wanted_fields,
+ edit_mail_get_flags,
+ edit_mail_get_keywords,
+ edit_mail_get_keyword_indexes,
+ edit_mail_get_modseq,
+ edit_mail_get_pvt_modseq,
+ edit_mail_get_parts,
+ edit_mail_get_date,
+ edit_mail_get_received_date,
+ edit_mail_get_save_date,
+ edit_mail_get_virtual_size,
+ edit_mail_get_physical_size,
+ edit_mail_get_first_header,
+ edit_mail_get_headers,
+ edit_mail_get_header_stream,
+ edit_mail_get_stream,
+ index_mail_get_binary_stream,
+ edit_mail_get_special,
+ edit_mail_get_backend_mail,
+ edit_mail_update_flags,
+ edit_mail_update_keywords,
+ edit_mail_update_modseq,
+ edit_mail_update_pvt_modseq,
+ edit_mail_update_pop3_uidl,
+ edit_mail_expunge,
+ edit_mail_set_cache_corrupted,
+ NULL,
+};
+
+/*
+ * Edit Mail Stream
+ */
+
+struct edit_mail_istream {
+ struct istream_private istream;
+ pool_t pool;
+
+ struct edit_mail *mail;
+
+ struct _header_field_index *cur_header;
+ uoff_t cur_header_v_offset;
+
+ bool parent_buffer:1;
+ bool header_read:1;
+ bool eof:1;
+};
+
+static void edit_mail_istream_destroy(struct iostream_private *stream)
+{
+ struct edit_mail_istream *edstream =
+ (struct edit_mail_istream *)stream;
+
+ i_stream_unref(&edstream->istream.parent);
+ i_stream_free_buffer(&edstream->istream);
+ pool_unref(&edstream->pool);
+}
+
+static ssize_t
+merge_from_parent(struct edit_mail_istream *edstream, uoff_t parent_v_offset,
+ uoff_t parent_end_v_offset, uoff_t copy_v_offset)
+{
+ struct istream_private *stream = &edstream->istream;
+ uoff_t v_offset, append_v_offset;
+ const unsigned char *data;
+ size_t pos, cur_pos, parent_bytes_left;
+ bool parent_buffer = edstream->parent_buffer;
+ ssize_t ret;
+
+ i_assert(parent_v_offset <= parent_end_v_offset);
+ edstream->parent_buffer = FALSE;
+
+ v_offset = stream->istream.v_offset;
+ if (v_offset >= copy_v_offset) {
+ i_assert((v_offset - copy_v_offset) <= parent_end_v_offset);
+ if ((v_offset - copy_v_offset) == parent_end_v_offset) {
+ /* Parent data is all read */
+ return 0;
+ }
+ }
+
+ /* Determine where we are appending more data to the stream */
+ append_v_offset = v_offset + (stream->pos - stream->skip);
+
+ if (v_offset >= copy_v_offset) {
+ /* Parent buffer used */
+ cur_pos = (stream->pos - stream->skip);
+ parent_v_offset += (v_offset - copy_v_offset);
+ } else {
+ cur_pos = 0;
+ i_assert(append_v_offset >= copy_v_offset);
+ parent_v_offset += (append_v_offset - copy_v_offset);
+ }
+
+ /* Seek parent to required position */
+ i_stream_seek(stream->parent, parent_v_offset);
+
+ /* Read from parent */
+ data = i_stream_get_data(stream->parent, &pos);
+ if (pos > cur_pos)
+ ret = 0;
+ else do {
+ /* Use normal read here, since parent data can be returned
+ directly to caller. */
+ ret = i_stream_read(stream->parent);
+
+ stream->istream.stream_errno = stream->parent->stream_errno;
+ stream->istream.eof = stream->parent->eof;
+ edstream->eof = stream->parent->eof;
+ data = i_stream_get_data(stream->parent, &pos);
+ /* Check again, in case the parent stream had been seeked
+ backwards and the previous read() didn't get us far
+ enough. */
+ } while (pos <= cur_pos && ret > 0);
+
+ /* Don't read beyond parent end offset */
+ if (parent_end_v_offset != (uoff_t)-1) {
+ parent_bytes_left = (size_t)(parent_end_v_offset -
+ parent_v_offset);
+ if (pos >= parent_bytes_left) {
+ pos = parent_bytes_left;
+ }
+ }
+
+ if (v_offset < copy_v_offset || ret == -2 ||
+ (parent_buffer && (append_v_offset + 1) >= parent_end_v_offset)) {
+ /* Merging with our local buffer; copying data from parent */
+ if (pos > 0) {
+ size_t avail;
+
+ if (parent_buffer) {
+ stream->pos = stream->skip = 0;
+ stream->buffer = NULL;
+ }
+ if (!i_stream_try_alloc(stream, pos, &avail))
+ return -2;
+ pos = (pos > avail ? avail : pos);
+
+ memcpy(stream->w_buffer + stream->pos, data, pos);
+ stream->pos += pos;
+ stream->buffer = stream->w_buffer;
+
+ if (cur_pos >= pos)
+ ret = 0;
+ else
+ ret = (ssize_t)(pos - cur_pos);
+ } else {
+ ret = (ret == 0 ? 0 : -1);
+ }
+ } else {
+ /* Just passing buffers from parent; no copying */
+ ret = (pos > cur_pos ?
+ (ssize_t)(pos - cur_pos) : (ret == 0 ? 0 : -1));
+ stream->buffer = data;
+ stream->pos = pos;
+ stream->skip = 0;
+ edstream->parent_buffer = TRUE;
+ }
+
+ i_assert(ret != -1 || stream->istream.eof ||
+ stream->istream.stream_errno != 0);
+ return ret;
+}
+
+static ssize_t merge_modified_headers(struct edit_mail_istream *edstream)
+{
+ struct istream_private *stream = &edstream->istream;
+ struct edit_mail *edmail = edstream->mail;
+ uoff_t v_offset = stream->istream.v_offset, append_v_offset;
+ size_t appended, written, avail, size;
+
+ if (edstream->cur_header == NULL) {
+ /* No (more) headers */
+ return 0;
+ }
+
+ /* Caller must already have committed remaining parent data to
+ our stream buffer. */
+ i_assert(!edstream->parent_buffer);
+
+ /* Add modified headers to buffer */
+ written = 0;
+ while (edstream->cur_header != NULL) {
+ size_t wsize;
+
+ /* Determine what part of the header was already buffered */
+ append_v_offset = v_offset + (stream->pos - stream->skip);
+ i_assert(append_v_offset >= edstream->cur_header_v_offset);
+ if (append_v_offset >= edstream->cur_header_v_offset)
+ appended = (size_t)(append_v_offset -
+ edstream->cur_header_v_offset);
+ else
+ appended = 0;
+ i_assert(appended <= edstream->cur_header->field->size);
+
+ /* Determine how much we want to write */
+ size = edstream->cur_header->field->size - appended;
+ if (size > 0) {
+ /* Determine how much we can write */
+ if (!i_stream_try_alloc(stream, size, &avail)) {
+ if (written == 0)
+ return -2;
+ break;
+ }
+ wsize = (size >= avail ? avail : size);
+
+ /* Write (part of) the header to buffer */
+ memcpy(stream->w_buffer + stream->pos,
+ edstream->cur_header->field->data + appended,
+ wsize);
+ stream->pos += wsize;
+ stream->buffer = stream->w_buffer;
+ written += wsize;
+
+ if (wsize < size) {
+ /* Could not write whole header; finish here */
+ break;
+ }
+ }
+
+ /* Skip to next header */
+ edstream->cur_header_v_offset +=
+ edstream->cur_header->field->size;
+ edstream->cur_header = edstream->cur_header->next;
+
+ /* Stop at end of prepended headers if original header is left
+ unparsed */
+ if (!edmail->headers_parsed &&
+ edstream->cur_header == edmail->header_fields_appended)
+ edstream->cur_header = NULL;
+ }
+
+ if (edstream->cur_header == NULL) {
+ /* Clear offset too, just to be tidy */
+ edstream->cur_header_v_offset = 0;
+ }
+
+ i_assert(written > 0);
+ return (ssize_t)written;
+}
+
+static ssize_t edit_mail_istream_read(struct istream_private *stream)
+{
+ struct edit_mail_istream *edstream =
+ (struct edit_mail_istream *)stream;
+ struct edit_mail *edmail = edstream->mail;
+ uoff_t v_offset, append_v_offset;
+ uoff_t parent_v_offset, parent_end_v_offset, copy_v_offset;
+ uoff_t prep_hdr_size, hdr_size;
+ ssize_t ret = 0;
+
+ if (edstream->eof) {
+ stream->istream.eof = TRUE;
+ return -1;
+ }
+
+ if (edstream->parent_buffer && stream->skip == stream->pos) {
+ edstream->parent_buffer = FALSE;
+ stream->pos = stream->skip = 0;
+ stream->buffer = NULL;
+ }
+
+ /* Merge prepended headers */
+ if (!edstream->parent_buffer) {
+ ret = merge_modified_headers(edstream);
+ if (ret != 0)
+ return ret;
+ }
+ v_offset = stream->istream.v_offset;
+ append_v_offset = v_offset + (stream->pos - stream->skip);
+
+ if (!edmail->headers_parsed && edmail->header_fields_appended != NULL &&
+ !edstream->header_read) {
+ /* Output headers from original stream */
+
+ /* Size of the prepended header */
+ i_assert(edmail->hdr_size.physical_size >=
+ edmail->appended_hdr_size.physical_size);
+ prep_hdr_size = (edmail->hdr_size.physical_size -
+ edmail->appended_hdr_size.physical_size);
+
+ /* Calculate offset of header end or appended header. Any final
+ CR is dealt with later.
+ */
+ hdr_size = (prep_hdr_size +
+ edmail->wrapped_hdr_size.physical_size);
+ i_assert(hdr_size > 0);
+ if (append_v_offset <= (hdr_size - 1) &&
+ edmail->wrapped_hdr_size.physical_size > 0) {
+ parent_v_offset = stream->parent_start_offset;
+ parent_end_v_offset =
+ (stream->parent_start_offset +
+ edmail->wrapped_hdr_size.physical_size - 1);
+ copy_v_offset = prep_hdr_size;
+
+ ret = merge_from_parent(edstream, parent_v_offset,
+ parent_end_v_offset,
+ copy_v_offset);
+ if (ret < 0)
+ return ret;
+ append_v_offset = (v_offset +
+ (stream->pos - stream->skip));
+ i_assert(append_v_offset <= hdr_size - 1);
+
+ if (append_v_offset == hdr_size - 1) {
+ /* Strip final CR too when it is present */
+ if (stream->buffer != NULL &&
+ stream->buffer[stream->pos-1] == '\r') {
+ stream->pos--;
+ append_v_offset--;
+ ret--;
+ }
+
+ i_assert(ret >= 0);
+ edstream->cur_header =
+ edmail->header_fields_appended;
+ edstream->cur_header_v_offset = append_v_offset;
+ if (!edstream->parent_buffer)
+ edstream->header_read = TRUE;
+ }
+
+ if (ret != 0)
+ return ret;
+ } else {
+ edstream->header_read = TRUE;
+ }
+
+ /* Merge appended headers */
+ ret = merge_modified_headers(edstream);
+ if (ret != 0)
+ return ret;
+ }
+
+ /* Header does not come from original mail at all */
+ if (edmail->headers_parsed) {
+ parent_v_offset = (stream->parent_start_offset +
+ edmail->wrapped_hdr_size.physical_size -
+ (edmail->eoh_crlf ? 2 : 1));
+ copy_v_offset = edmail->hdr_size.physical_size;
+ /* Header comes partially from original mail and headers are added
+ between header and body. */
+ } else if (edmail->header_fields_appended != NULL) {
+ parent_v_offset = (stream->parent_start_offset +
+ edmail->wrapped_hdr_size.physical_size -
+ (edmail->eoh_crlf ? 2 : 1));
+ copy_v_offset = (edmail->hdr_size.physical_size +
+ edmail->wrapped_hdr_size.physical_size -
+ (edmail->eoh_crlf ? 2 : 1));
+ /* Header comes partially from original mail, but headers are only
+ prepended. */
+ } else {
+ parent_v_offset = stream->parent_start_offset;
+ copy_v_offset = edmail->hdr_size.physical_size;
+ }
+
+ return merge_from_parent(edstream, parent_v_offset, (uoff_t)-1,
+ copy_v_offset);
+}
+
+static void
+stream_reset_to(struct edit_mail_istream *edstream, uoff_t v_offset)
+{
+ edstream->istream.istream.v_offset = v_offset;
+ edstream->istream.skip = 0;
+ edstream->istream.pos = 0;
+ edstream->istream.buffer = NULL;
+ edstream->parent_buffer = FALSE;
+ edstream->eof = FALSE;
+ i_stream_seek(edstream->istream.parent, 0);
+}
+
+static void
+edit_mail_istream_seek(struct istream_private *stream, uoff_t v_offset,
+ bool mark ATTR_UNUSED)
+{
+ struct edit_mail_istream *edstream =
+ (struct edit_mail_istream *)stream;
+ struct _header_field_index *cur_header;
+ struct edit_mail *edmail = edstream->mail;
+ uoff_t offset;
+
+ edstream->header_read = FALSE;
+ edstream->cur_header = NULL;
+ edstream->cur_header_v_offset = 0;
+
+ /* The beginning */
+ if (v_offset == 0) {
+ stream_reset_to(edstream, 0);
+
+ if (edmail->header_fields_head !=
+ edmail->header_fields_appended)
+ edstream->cur_header = edmail->header_fields_head;
+ return;
+ }
+
+ /* Inside (prepended) headers */
+ if (edmail->headers_parsed) {
+ offset = edmail->hdr_size.physical_size;
+ } else {
+ offset = (edmail->hdr_size.physical_size -
+ edmail->appended_hdr_size.physical_size);
+ }
+
+ if (v_offset < offset) {
+ stream_reset_to(edstream, v_offset);
+
+ /* Find the header */
+ cur_header = edmail->header_fields_head;
+ i_assert(cur_header != NULL &&
+ cur_header != edmail->header_fields_appended);
+ edstream->cur_header_v_offset = 0;
+ offset = cur_header->field->size;
+ while (v_offset > offset) {
+ cur_header = cur_header->next;
+ i_assert(cur_header != NULL &&
+ cur_header != edmail->header_fields_appended);
+
+ edstream->cur_header_v_offset = offset;
+ offset += cur_header->field->size;
+ }
+
+ edstream->cur_header = cur_header;
+ return;
+ }
+
+ if (!edmail->headers_parsed) {
+ /* Inside original header */
+ offset = (edmail->hdr_size.physical_size -
+ edmail->appended_hdr_size.physical_size +
+ edmail->wrapped_hdr_size.physical_size);
+ if (v_offset < offset) {
+ stream_reset_to(edstream, v_offset);
+ return;
+ }
+
+ edstream->header_read = TRUE;
+
+ /* Inside appended header */
+ offset = (edmail->hdr_size.physical_size +
+ edmail->wrapped_hdr_size.physical_size);
+ if (v_offset < offset) {
+ stream_reset_to(edstream, v_offset);
+
+ offset -= edmail->appended_hdr_size.physical_size;
+
+ cur_header = edmail->header_fields_appended;
+ i_assert(cur_header != NULL);
+ edstream->cur_header_v_offset = offset;
+ offset += cur_header->field->size;
+
+ while (v_offset > offset) {
+ cur_header = cur_header->next;
+ i_assert(cur_header != NULL);
+
+ edstream->cur_header_v_offset = offset;
+ offset += cur_header->field->size;
+ }
+
+ edstream->cur_header = cur_header;
+ return;
+ }
+ }
+
+ stream_reset_to(edstream, v_offset);
+ edstream->cur_header = NULL;
+}
+
+static void ATTR_NORETURN
+edit_mail_istream_sync(struct istream_private *stream ATTR_UNUSED)
+{
+ i_panic("edit-mail istream sync() not implemented");
+}
+
+static int
+edit_mail_istream_stat(struct istream_private *stream, bool exact)
+{
+ struct edit_mail_istream *edstream =
+ (struct edit_mail_istream *)stream;
+ struct edit_mail *edmail = edstream->mail;
+ const struct stat *st;
+
+ /* Stat the original stream */
+ if (i_stream_stat(stream->parent, exact, &st) < 0)
+ return -1;
+
+ stream->statbuf = *st;
+ if (st->st_size == -1 || !exact)
+ return 0;
+
+ if (!edmail->headers_parsed) {
+ if (!edmail->modified)
+ return 0;
+ } else {
+ stream->statbuf.st_size =
+ (edmail->wrapped_body_size.physical_size +
+ (edmail->eoh_crlf ? 2 : 1));
+ }
+
+ stream->statbuf.st_size += (edmail->hdr_size.physical_size +
+ edmail->body_size.physical_size);
+ return 0;
+}
+
+struct istream *edit_mail_istream_create(struct edit_mail *edmail)
+{
+ struct edit_mail_istream *edstream;
+ struct istream *wrapped = edmail->wrapped_stream;
+
+ edstream = i_new(struct edit_mail_istream, 1);
+ edstream->pool = pool_alloconly_create(MEMPOOL_GROWING
+ "edit mail stream", 4096);
+ edstream->mail = edmail;
+
+ edstream->istream.max_buffer_size =
+ wrapped->real_stream->max_buffer_size;
+
+ edstream->istream.iostream.destroy = edit_mail_istream_destroy;
+ edstream->istream.read = edit_mail_istream_read;
+ edstream->istream.seek = edit_mail_istream_seek;
+ edstream->istream.sync = edit_mail_istream_sync;
+ edstream->istream.stat = edit_mail_istream_stat;
+
+ edstream->istream.istream.readable_fd = FALSE;
+ edstream->istream.istream.blocking = wrapped->blocking;
+ edstream->istream.istream.seekable = wrapped->seekable;
+
+ if (edmail->header_fields_head != edmail->header_fields_appended)
+ edstream->cur_header = edmail->header_fields_head;
+
+ i_stream_seek(wrapped, 0);
+
+ return i_stream_create(&edstream->istream, wrapped, -1, 0);
+}