1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
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;
}
|