1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.commons.codec.net;
19
20 import java.io.UnsupportedEncodingException;
21 import java.nio.charset.Charset;
22 import java.nio.charset.StandardCharsets;
23 import java.util.BitSet;
24
25 import org.apache.commons.codec.DecoderException;
26 import org.apache.commons.codec.EncoderException;
27 import org.apache.commons.codec.StringDecoder;
28 import org.apache.commons.codec.StringEncoder;
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 public class QCodec extends RFC1522Codec implements StringEncoder, StringDecoder {
54
55
56
57 private static final BitSet PRINTABLE_CHARS = new BitSet(256);
58
59
60 static {
61
62 PRINTABLE_CHARS.set(' ');
63 PRINTABLE_CHARS.set('!');
64 PRINTABLE_CHARS.set('"');
65 PRINTABLE_CHARS.set('#');
66 PRINTABLE_CHARS.set('$');
67 PRINTABLE_CHARS.set('%');
68 PRINTABLE_CHARS.set('&');
69 PRINTABLE_CHARS.set('\'');
70 PRINTABLE_CHARS.set('(');
71 PRINTABLE_CHARS.set(')');
72 PRINTABLE_CHARS.set('*');
73 PRINTABLE_CHARS.set('+');
74 PRINTABLE_CHARS.set(',');
75 PRINTABLE_CHARS.set('-');
76 PRINTABLE_CHARS.set('.');
77 PRINTABLE_CHARS.set('/');
78 for (int i = '0'; i <= '9'; i++) {
79 PRINTABLE_CHARS.set(i);
80 }
81 PRINTABLE_CHARS.set(':');
82 PRINTABLE_CHARS.set(';');
83 PRINTABLE_CHARS.set('<');
84 PRINTABLE_CHARS.set('>');
85 PRINTABLE_CHARS.set('@');
86 for (int i = 'A'; i <= 'Z'; i++) {
87 PRINTABLE_CHARS.set(i);
88 }
89 PRINTABLE_CHARS.set('[');
90 PRINTABLE_CHARS.set('\\');
91 PRINTABLE_CHARS.set(']');
92 PRINTABLE_CHARS.set('^');
93 PRINTABLE_CHARS.set('`');
94 for (int i = 'a'; i <= 'z'; i++) {
95 PRINTABLE_CHARS.set(i);
96 }
97 PRINTABLE_CHARS.set('{');
98 PRINTABLE_CHARS.set('|');
99 PRINTABLE_CHARS.set('}');
100 PRINTABLE_CHARS.set('~');
101 }
102 private static final byte SPACE = 32;
103
104 private static final byte UNDERSCORE = 95;
105
106
107
108
109 private final Charset charset;
110
111 private boolean encodeBlanks;
112
113
114
115
116 public QCodec() {
117 this(StandardCharsets.UTF_8);
118 }
119
120
121
122
123
124
125
126
127
128
129 public QCodec(final Charset charset) {
130 this.charset = charset;
131 }
132
133
134
135
136
137
138
139
140
141
142
143 public QCodec(final String charsetName) {
144 this(Charset.forName(charsetName));
145 }
146
147
148
149
150
151
152
153
154
155
156
157
158 @Override
159 public Object decode(final Object obj) throws DecoderException {
160 if (obj == null) {
161 return null;
162 }
163 if (obj instanceof String) {
164 return decode((String) obj);
165 }
166 throw new DecoderException("Objects of type " +
167 obj.getClass().getName() +
168 " cannot be decoded using Q codec");
169 }
170
171
172
173
174
175
176
177
178
179
180
181 @Override
182 public String decode(final String str) throws DecoderException {
183 if (str == null) {
184 return null;
185 }
186 try {
187 return decodeText(str);
188 } catch (final UnsupportedEncodingException e) {
189 throw new DecoderException(e.getMessage(), e);
190 }
191 }
192
193 @Override
194 protected byte[] doDecoding(final byte[] bytes) throws DecoderException {
195 if (bytes == null) {
196 return null;
197 }
198 boolean hasUnderscores = false;
199 for (final byte b : bytes) {
200 if (b == UNDERSCORE) {
201 hasUnderscores = true;
202 break;
203 }
204 }
205 if (hasUnderscores) {
206 final byte[] tmp = new byte[bytes.length];
207 for (int i = 0; i < bytes.length; i++) {
208 final byte b = bytes[i];
209 if (b != UNDERSCORE) {
210 tmp[i] = b;
211 } else {
212 tmp[i] = SPACE;
213 }
214 }
215 return QuotedPrintableCodec.decodeQuotedPrintable(tmp);
216 }
217 return QuotedPrintableCodec.decodeQuotedPrintable(bytes);
218 }
219
220 @Override
221 protected byte[] doEncoding(final byte[] bytes) {
222 if (bytes == null) {
223 return null;
224 }
225 final byte[] data = QuotedPrintableCodec.encodeQuotedPrintable(PRINTABLE_CHARS, bytes);
226 if (this.encodeBlanks) {
227 for (int i = 0; i < data.length; i++) {
228 if (data[i] == SPACE) {
229 data[i] = UNDERSCORE;
230 }
231 }
232 }
233 return data;
234 }
235
236
237
238
239
240
241
242
243
244
245 @Override
246 public Object encode(final Object obj) throws EncoderException {
247 if (obj == null) {
248 return null;
249 }
250 if (obj instanceof String) {
251 return encode((String) obj);
252 }
253 throw new EncoderException("Objects of type " +
254 obj.getClass().getName() +
255 " cannot be encoded using Q codec");
256 }
257
258
259
260
261
262
263
264
265
266
267 @Override
268 public String encode(final String sourceStr) throws EncoderException {
269 if (sourceStr == null) {
270 return null;
271 }
272 return encode(sourceStr, getCharset());
273 }
274
275
276
277
278
279
280
281
282
283
284
285
286
287 public String encode(final String sourceStr, final Charset sourceCharset) throws EncoderException {
288 if (sourceStr == null) {
289 return null;
290 }
291 return encodeText(sourceStr, sourceCharset);
292 }
293
294
295
296
297
298
299
300
301
302
303
304
305 public String encode(final String sourceStr, final String sourceCharset) throws EncoderException {
306 if (sourceStr == null) {
307 return null;
308 }
309 try {
310 return encodeText(sourceStr, sourceCharset);
311 } catch (final UnsupportedEncodingException e) {
312 throw new EncoderException(e.getMessage(), e);
313 }
314 }
315
316
317
318
319
320
321
322 public Charset getCharset() {
323 return this.charset;
324 }
325
326
327
328
329
330
331 public String getDefaultCharset() {
332 return this.charset.name();
333 }
334
335 @Override
336 protected String getEncoding() {
337 return "Q";
338 }
339
340
341
342
343
344
345 public boolean isEncodeBlanks() {
346 return this.encodeBlanks;
347 }
348
349
350
351
352
353
354
355 public void setEncodeBlanks(final boolean b) {
356 this.encodeBlanks = b;
357 }
358 }