2 * utf7.c - routines to handle UTF-7 (RFC 1642 / RFC 2152).
11 * This array is generated by a piece of Perl:
13 perl -e 'for $i (0..32) { $a[$i] |= 2; } $a[32] |= 1;' \
14 -e 'for $i ("a".."z","A".."Z","0".."9","'\''","(",' \
15 -e ' ")",",","-",".","/",":","?") { $a[ord $i] |= 1; }' \
16 -e 'for $i ("!","\"","#","\$","%","&","*",";","<","=",">","\@",' \
17 -e ' "[","]","^","_","`","{","|","}") { $a[ord $i] |= 2; }' \
18 -e 'for $i ("a".."z","A".."Z","0".."9","+","/") { $a[ord $i] |= 4; }' \
19 -e 'for $i (0..127) { printf "%s%d,%s", $i%32?"":" ", $a[$i],' \
20 -e ' ($i+1)%32?"":"\n"; }'
23 static const unsigned char utf7_ascii_properties
[128] = {
24 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
25 3,2,2,2,2,2,2,1,1,1,2,4,1,1,1,5,5,5,5,5,5,5,5,5,5,5,1,2,2,2,2,1,
26 2,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,2,0,2,2,2,
27 2,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,2,2,2,0,0,
29 #define SET_D(c) ((c) >= 0 && (c) < 0x80 && (utf7_ascii_properties[(c)] & 1))
30 #define SET_O(c) ((c) >= 0 && (c) < 0x80 && (utf7_ascii_properties[(c)] & 2))
31 #define SET_B(c) ((c) >= 0 && (c) < 0x80 && (utf7_ascii_properties[(c)] & 4))
33 #define base64_value(c) ( (c) >= 'A' && (c) <= 'Z' ? (c) - 'A' : \
34 (c) >= 'a' && (c) <= 'z' ? (c) - 'a' + 26 : \
35 (c) >= '0' && (c) <= '9' ? (c) - '0' + 52 : \
36 (c) == '+' ? 62 : 63 )
38 static const char *const base64_chars
=
39 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
41 static void read_utf7(charset_spec
const *charset
, long int input_chr
,
43 void (*emit
)(void *ctx
, long int output
), void *emitctx
)
50 * state->s0 is used to handle the conversion of the UTF-7
51 * transport format into a stream of halfwords. Its layout is:
53 * - In normal ASCII mode, it is zero.
55 * - Otherwise, it holds a leading 1 followed by all the bits
56 * so far accumulated in base64 digits.
58 * - Special case: when we have only just seen the initial `+'
59 * which enters base64 mode, it is set to 2 rather than 1
60 * (this is an otherwise unused value since base64 always
61 * accumulates an even number of bits at a time), so that
62 * the special sequence `+-' can be made to encode `+'
65 * state->s1 is used to handle the conversion of those
66 * halfwords into Unicode values. It contains a high surrogate
67 * value if we've just seen one, and 0 otherwise.
74 emit(emitctx
, input_chr
);
77 if (!SET_B(input_chr
)) {
79 * base64 mode ends here. Emit the character we have,
80 * unless it's a minus in which case we should swallow
84 emit(emitctx
, input_chr
);
85 else if (state
->s0
== 2)
86 emit(emitctx
, '+'); /* special case */
92 * Now we have a base64 character, so add it to our state,
93 * first correcting the special case value of s0.
97 state
->s0
= (state
->s0
<< 6) | base64_value(input_chr
);
101 * If we don't have a whole halfword at this point, bale out.
103 if (!(state
->s0
& 0xFFFF0000))
107 * Otherwise, extract the halfword. There are three
108 * possibilities for where the top set bit might be.
110 if (state
->s0
& 0x00100000) {
111 hw
= (state
->s0
>> 4) & 0xFFFF;
112 state
->s0
= (state
->s0
& 0xF) | 0x10;
113 } else if (state
->s0
& 0x00040000) {
114 hw
= (state
->s0
>> 2) & 0xFFFF;
115 state
->s0
= (state
->s0
& 3) | 4;
117 hw
= state
->s0
& 0xFFFF;
122 * Now what reaches this point should be a stream of halfwords
123 * in sensible numeric form. So now we process surrogates.
127 * We have already seen a high surrogate, so we expect a
128 * low surrogate. Whinge if we didn't get it.
130 if (hw
< 0xDC00 || hw
>= 0xE000) {
131 emit(emitctx
, ERROR
);
134 hw
|= (state
->s1
& 0x3FF) << 10;
135 emit(emitctx
, hw
+ 0x10000);
140 * Any low surrogate is an error.
142 if (hw
>= 0xDC00 && hw
< 0xE000) {
143 emit(emitctx
, ERROR
);
148 * Any high surrogate is simply stored until we see the
151 if (hw
>= 0xD800 && hw
< 0xDC00) {
157 * Anything else we simply output.
164 * For writing UTF-7, we supply two charset definitions, one of
165 * which will directly encode Set O characters and the other of
166 * which will cautiously base64 them.
168 static int write_utf7(charset_spec
const *charset
, long int input_chr
,
169 charset_state
*state
,
170 void (*emit
)(void *ctx
, long int output
),
173 unsigned long hws
[2];
178 * For writing: state->s0 contains accumulated base64 data with
179 * a 1 in front, and state->s1 indicates how many bits of it we
183 if ((input_chr
>= 0xD800 && input_chr
< 0xE000) ||
184 input_chr
>= 0x110000) {
186 * We can't output surrogates, or anything above 0x10FFFF.
192 * Look for characters which we output in ASCII mode. A special
193 * case here is +, which can be encoded as the empty base64
194 * escape sequence `+-': if we're _already_ in ASCII mode we do
195 * that, but if we're in base64 mode at the point we see the +
196 * then we simply stay in base64 mode and output it as a
197 * halfword. (Switching back would cost three bytes, whereas
198 * staying in base64 costs only 2 2/3.)
200 if (input_chr
== -1 || SET_D(input_chr
) ||
201 (charset
->charset
== CS_UTF7
&& SET_O(input_chr
)) ||
202 (!state
->s0
&& input_chr
== '+')) {
205 * These characters are output in ASCII mode, so flush any
206 * lingering base64 data.
208 state
->s0
<<= 6 - state
->s1
;
209 emit(emitctx
, base64_chars
[state
->s0
& 0x3F]);
211 * I'm going to arbitrarily decide to always use the
212 * terminating minus sign. It's easier than figuring out
213 * whether to do so or not, and looks prettier besides.
216 state
->s0
= state
->s1
= 0;
220 * Now output the character.
222 if (input_chr
!= -1) /* special case: just reset state */
223 emit(emitctx
, input_chr
);
224 if (input_chr
== '+')
225 emit(emitctx
, '-'); /* +- encodes + */
230 * Now we know we have a character that needs to be output as
231 * either one base64-encoded halfword or two. So first figure
234 if (input_chr
< 0x10000) {
238 input_chr
-= 0x10000;
239 if (input_chr
>= 0x100000) {
240 /* Anything above 0x10FFFF is outside UTF-7 range. */
245 hws
[0] = 0xD800 | ((input_chr
>> 10) & 0x3FF);
246 hws
[1] = 0xDC00 | (input_chr
& 0x3FF);
250 * ... switch into base64 mode if required ...
259 * ... and do the base64 output.
261 for (i
= 0; i
< nhws
; i
++) {
262 state
->s0
= (state
->s0
<< 16) | hws
[i
];
265 while (state
->s1
>= 6) {
267 * The top set bit must be in position 16, 18 or 20.
269 unsigned long out
, topbit
;
271 out
= (state
->s0
>> (state
->s1
- 6)) & 0x3F;
273 topbit
= 1 << state
->s1
;
274 state
->s0
= (state
->s0
& (topbit
-1)) | topbit
;
276 emit(emitctx
, base64_chars
[out
]);
282 const charset_spec charset_CS_UTF7
= {
283 CS_UTF7
, read_utf7
, write_utf7
, NULL
286 const charset_spec charset_CS_UTF7_CONSERVATIVE
= {
287 CS_UTF7_CONSERVATIVE
, read_utf7
, write_utf7
, NULL
290 #else /* ENUM_CHARSETS */
292 ENUM_CHARSET(CS_UTF7
)
293 ENUM_CHARSET(CS_UTF7_CONSERVATIVE
)
295 #endif /* ENUM_CHARSETS */