diff options
Diffstat (limited to 'pigeonhole/src/lib-sieve/util/edit-mail.c')
-rw-r--r-- | pigeonhole/src/lib-sieve/util/edit-mail.c | 2251 |
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); +} |