bcb4e51a409d94ae670de96afb8483a4f7855294Stephan Bosch/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi#include "lib.h"
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi#include "str.h"
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi#include "istream.h"
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi#include "istream-private.h"
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi#include "qp-encoder.h"
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi#include <ctype.h>
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomistruct qp_encoder {
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi const char *linebreak;
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi string_t *dest;
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi size_t line_len;
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi size_t max_len;
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi enum qp_encoder_flag flags;
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi bool add_header_preamble:1;
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi bool cr_last:1;
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi};
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomistruct qp_encoder *
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomiqp_encoder_init(string_t *dest, unsigned int max_len, enum qp_encoder_flag flags)
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi{
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi i_assert(max_len > 0);
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi if ((flags & QP_ENCODER_FLAG_HEADER_FORMAT) != 0 &&
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi (flags & QP_ENCODER_FLAG_BINARY_DATA) != 0)
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi i_panic("qp encoder cannot do header format with binary data");
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi struct qp_encoder *qp = i_new(struct qp_encoder, 1);
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi qp->flags = flags;
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi qp->dest = dest;
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi qp->max_len = max_len;
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi if ((flags & QP_ENCODER_FLAG_HEADER_FORMAT) != 0) {
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi qp->linebreak = "?=\r\n =?utf-8?Q?";
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi qp->add_header_preamble = TRUE;
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi } else {
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi qp->linebreak = "=\r\n";
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi }
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi return qp;
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi}
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomivoid qp_encoder_deinit(struct qp_encoder **qp)
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi{
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi i_free(*qp);
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi}
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomistatic inline void
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomiqp_encode_or_break(struct qp_encoder *qp, unsigned char c)
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi{
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi bool encode = FALSE;
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi if ((qp->flags & QP_ENCODER_FLAG_HEADER_FORMAT) != 0) {
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi if (c == ' ')
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi c = '_';
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi else if (c != '\t' &&
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi (c == '?' || c == '_' || c == '=' || c < 33 || c > 126))
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi encode = TRUE;
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi } else if (c != ' ' && c != '\t' &&
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi (c == '=' || c < 33 || c > 126)) {
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi encode = TRUE;
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi }
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi /* Include terminating = as well */
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi if ((c == ' ' || c == '\t') && qp->line_len + 4 >= qp->max_len) {
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi const char *ptr = strchr(qp->linebreak, '\n');
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi str_printfa(qp->dest, "=%02X%s", c, qp->linebreak);
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi if (ptr != NULL)
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi qp->line_len = strlen(ptr+1);
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi else
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi qp->line_len = 0;
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi return;
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi }
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi /* Include terminating = as well */
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi if (qp->line_len + (encode?4:2) >= qp->max_len) {
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi str_append(qp->dest, qp->linebreak);
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi qp->line_len = 0;
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi }
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi if (encode) {
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi str_printfa(qp->dest, "=%02X", c);
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi qp->line_len += 3;
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi } else {
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi str_append_c(qp->dest, c);
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi qp->line_len += 1;
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi }
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi}
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomivoid qp_encoder_more(struct qp_encoder *qp, const void *_src, size_t src_size)
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi{
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi const unsigned char *src = _src;
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi i_assert(_src != NULL || src_size == 0);
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi if (src_size == 0)
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi return;
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi if (qp->add_header_preamble) {
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi size_t used = qp->dest->used;
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi qp->add_header_preamble = FALSE;
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi str_append(qp->dest, "=?utf-8?Q?");
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi qp->line_len = qp->dest->used - used;
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi }
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi for(unsigned int i = 0; i < src_size; i++) {
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi unsigned char c = src[i];
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi /* if input is not binary data and we encounter newline
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi convert it as crlf, or if the last byte was CR, preserve
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi CRLF */
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi if (c == '\n' &&
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi ((qp->flags & (QP_ENCODER_FLAG_BINARY_DATA|QP_ENCODER_FLAG_HEADER_FORMAT)) == 0 ||
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi qp->cr_last)) {
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi str_append_c(qp->dest, '\r');
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi str_append_c(qp->dest, '\n');
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi /* reset line length here */
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi qp->line_len = 0;
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi qp->cr_last = FALSE;
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi continue;
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi } else if (qp->cr_last) {
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi qp_encode_or_break(qp, '\r');
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi qp->cr_last = FALSE;
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi }
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi if (c == '\r') {
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi qp->cr_last = TRUE;
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi } else {
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi qp_encode_or_break(qp, c);
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi }
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi }
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi}
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomivoid qp_encoder_finish(struct qp_encoder *qp)
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi{
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi if (qp->cr_last)
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi qp_encode_or_break(qp, '\r');
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi if ((qp->flags & QP_ENCODER_FLAG_HEADER_FORMAT) != 0 &&
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi !qp->add_header_preamble)
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi str_append(qp->dest, "?=");
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi if ((qp->flags & QP_ENCODER_FLAG_HEADER_FORMAT) != 0)
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi qp->add_header_preamble = TRUE;
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi qp->line_len = 0;
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi qp->cr_last = FALSE;
869e8af5b722032920698743b27e89e6abd170e7Aki Tuomi}