diff options
Diffstat (limited to 'src/lib-mail/qp-encoder.c')
-rw-r--r-- | src/lib-mail/qp-encoder.c | 162 |
1 files changed, 162 insertions, 0 deletions
diff --git a/src/lib-mail/qp-encoder.c b/src/lib-mail/qp-encoder.c new file mode 100644 index 0000000..37a4412 --- /dev/null +++ b/src/lib-mail/qp-encoder.c @@ -0,0 +1,162 @@ +/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "istream.h" +#include "istream-private.h" +#include "qp-encoder.h" +#include <ctype.h> + +enum qp_encoder_last_char { + QP_ENCODER_LAST_ANY = 0, + QP_ENCODER_LAST_CR, + QP_ENCODER_LAST_WHITE_SPACE, +}; + +struct qp_encoder { + const char *linebreak; + string_t *dest; + size_t line_len; + size_t max_len; + enum qp_encoder_flag flags; + enum qp_encoder_last_char last_char; + bool add_header_preamble:1; +}; + +struct qp_encoder * +qp_encoder_init(string_t *dest, unsigned int max_len, enum qp_encoder_flag flags) +{ + i_assert(max_len > 0); + + if ((flags & QP_ENCODER_FLAG_HEADER_FORMAT) != 0 && + (flags & QP_ENCODER_FLAG_BINARY_DATA) != 0) + i_panic("qp encoder cannot do header format with binary data"); + + struct qp_encoder *qp = i_new(struct qp_encoder, 1); + qp->flags = flags; + qp->dest = dest; + qp->max_len = max_len; + + if ((flags & QP_ENCODER_FLAG_HEADER_FORMAT) != 0) { + qp->linebreak = "?=\r\n =?utf-8?Q?"; + qp->add_header_preamble = TRUE; + } else { + qp->linebreak = "=\r\n"; + } + return qp; +} + +void qp_encoder_deinit(struct qp_encoder **qp) +{ + i_free(*qp); +} + +static inline void +qp_encode_or_break(struct qp_encoder *qp, unsigned char c) +{ + bool encode = FALSE; + + if ((qp->flags & QP_ENCODER_FLAG_HEADER_FORMAT) != 0) { + if (c == ' ') + c = '_'; + else if (c != '\t' && + (c == '?' || c == '_' || c == '=' || c < 33 || c > 126)) + encode = TRUE; + } else if (c != ' ' && c != '\t' && + (c == '=' || c < 33 || c > 126)) { + encode = TRUE; + } + + /* Include terminating = as well */ + if ((c == ' ' || c == '\t') && qp->line_len + 4 >= qp->max_len) { + const char *ptr = strchr(qp->linebreak, '\n'); + str_printfa(qp->dest, "=%02X%s", c, qp->linebreak); + if (ptr != NULL) + qp->line_len = strlen(ptr+1); + else + qp->line_len = 0; + return; + } + + /* Include terminating = as well */ + if (qp->line_len + (encode?4:2) >= qp->max_len) { + str_append(qp->dest, qp->linebreak); + qp->line_len = 0; + } + + if (encode) { + str_printfa(qp->dest, "=%02X", c); + qp->line_len += 3; + } else { + str_append_c(qp->dest, c); + qp->line_len += 1; + } +} + +void qp_encoder_more(struct qp_encoder *qp, const void *_src, size_t src_size) +{ + const unsigned char *src = _src; + i_assert(_src != NULL || src_size == 0); + if (src_size == 0) + return; + if (qp->add_header_preamble) { + size_t used = qp->dest->used; + qp->add_header_preamble = FALSE; + str_append(qp->dest, "=?utf-8?Q?"); + qp->line_len = qp->dest->used - used; + } + for(unsigned int i = 0; i < src_size; i++) { + unsigned char c = src[i]; + /* if input is not binary data and we encounter newline + convert it as crlf, or if the last byte was CR, preserve + CRLF */ + if (c == '\n' && + ((qp->flags & (QP_ENCODER_FLAG_BINARY_DATA|QP_ENCODER_FLAG_HEADER_FORMAT)) == 0 || + qp->last_char == QP_ENCODER_LAST_CR)) { + str_append_c(qp->dest, '\r'); + str_append_c(qp->dest, '\n'); + /* reset line length here */ + qp->line_len = 0; + qp->last_char = QP_ENCODER_LAST_ANY; + continue; + } else if (qp->last_char == QP_ENCODER_LAST_CR) { + qp_encode_or_break(qp, '\r'); + qp->last_char = QP_ENCODER_LAST_ANY; + } + + if (c == ' ' || c == '\t') + qp->last_char = QP_ENCODER_LAST_WHITE_SPACE; + else if (c == '\r') + qp->last_char = QP_ENCODER_LAST_CR; + else + qp->last_char = QP_ENCODER_LAST_ANY; + + if (c != '\r') + qp_encode_or_break(qp, c); + } +} + +void qp_encoder_finish(struct qp_encoder *qp) +{ + switch (qp->last_char) { + case QP_ENCODER_LAST_CR: + /* Last char was CR which was not yet encoded */ + qp_encode_or_break(qp, '\r'); + break; + case QP_ENCODER_LAST_WHITE_SPACE: + /* Last char was a white space, it must be followed by + * a printable char. */ + str_append_c(qp->dest, '='); + break; + default: + break; + } + + if ((qp->flags & QP_ENCODER_FLAG_HEADER_FORMAT) != 0 && + !qp->add_header_preamble) + str_append(qp->dest, "?="); + if ((qp->flags & QP_ENCODER_FLAG_HEADER_FORMAT) != 0) + qp->add_header_preamble = TRUE; + qp->line_len = 0; + qp->last_char = QP_ENCODER_LAST_ANY; +} |