summaryrefslogtreecommitdiffstats
path: root/src/lib-mail/qp-encoder.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib-mail/qp-encoder.c')
-rw-r--r--src/lib-mail/qp-encoder.c162
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;
+}