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 static org.junit.jupiter.api.Assertions.assertArrayEquals;
21 import static org.junit.jupiter.api.Assertions.assertEquals;
22 import static org.junit.jupiter.api.Assertions.assertNull;
23 import static org.junit.jupiter.api.Assertions.assertThrows;
24
25 import java.nio.charset.StandardCharsets;
26 import java.nio.charset.UnsupportedCharsetException;
27 import java.util.stream.Stream;
28
29 import org.apache.commons.codec.CharEncoding;
30 import org.apache.commons.codec.DecoderException;
31 import org.apache.commons.codec.EncoderException;
32 import org.apache.commons.codec.binary.StringUtils;
33 import org.junit.jupiter.api.Test;
34 import org.junit.jupiter.params.ParameterizedTest;
35 import org.junit.jupiter.params.provider.Arguments;
36 import org.junit.jupiter.params.provider.MethodSource;
37 import org.junit.jupiter.params.provider.ValueSource;
38
39
40
41
42 class QuotedPrintableCodecTest {
43
44 static final int[] SWISS_GERMAN_STUFF_UNICODE = { 0x47, 0x72, 0xFC, 0x65, 0x7A, 0x69, 0x5F, 0x7A, 0xE4, 0x6D, 0xE4 };
45
46 static final int[] RUSSIAN_STUFF_UNICODE = { 0x412, 0x441, 0x435, 0x43C, 0x5F, 0x43F, 0x440, 0x438, 0x432, 0x435, 0x442 };
47
48 private static Stream<Arguments> softLineBreakBoundaryCases() {
49
50 return Stream.of(
51 Arguments.of("=\r\n", ""),
52 Arguments.of("=\r\nA=\r\n", "A"),
53 Arguments.of("A=\r\n=\r\nB", "AB"),
54 Arguments.of("A=\r\n\r\nB", "A\r\nB"),
55 Arguments.of("A\r\n=\r\nB", "A\r\nB"),
56 Arguments.of("A=0D=0AB", "A\r\nB"));
57
58 }
59
60 private String constructString(final int[] unicodeChars) {
61 final StringBuilder buffer = new StringBuilder();
62 if (unicodeChars != null) {
63 for (final int unicodeChar : unicodeChars) {
64 buffer.append((char) unicodeChar);
65 }
66 }
67 return buffer.toString();
68 }
69
70 @Test
71 void testBasicEncodeDecode() throws Exception {
72 final QuotedPrintableCodec qpcodec = new QuotedPrintableCodec();
73 final String plain = "= Hello there =\r\n";
74 final String encoded = qpcodec.encode(plain);
75 assertEquals("=3D Hello there =3D=0D=0A", encoded, "Basic quoted-printable encoding test");
76 assertEquals(plain, qpcodec.decode(encoded), "Basic quoted-printable decoding test");
77 }
78
79 @Test
80 void testDecodeInvalid() {
81 final QuotedPrintableCodec qpcodec = new QuotedPrintableCodec();
82 assertThrows(DecoderException.class, () -> qpcodec.decode("="));
83 assertThrows(DecoderException.class, () -> qpcodec.decode("=A"));
84 assertThrows(DecoderException.class, () -> qpcodec.decode("=WW"));
85 }
86
87 @Test
88 void testDecodeObjects() throws Exception {
89 final QuotedPrintableCodec qpcodec = new QuotedPrintableCodec();
90 final String plain = "1+1 =3D 2";
91 String decoded = (String) qpcodec.decode((Object) plain);
92 assertEquals("1+1 = 2", decoded, "Basic quoted-printable decoding test");
93
94 final byte[] plainBA = plain.getBytes(StandardCharsets.UTF_8);
95 final byte[] decodedBA = (byte[]) qpcodec.decode((Object) plainBA);
96 decoded = new String(decodedBA);
97 assertEquals("1+1 = 2", decoded, "Basic quoted-printable decoding test");
98
99 final Object result = qpcodec.decode((Object) null);
100 assertNull(result, "Decoding a null Object should return null");
101
102 assertThrows(DecoderException.class, () -> qpcodec.decode(Double.valueOf(3.0d)), "Trying to url encode a Double object should cause an exception.");
103 }
104
105 @Test
106 void testDecodeStringWithNull() throws Exception {
107 final QuotedPrintableCodec qpcodec = new QuotedPrintableCodec();
108 final String test = null;
109 final String result = qpcodec.decode(test, "charset");
110 assertNull(result, "Result should be null");
111 }
112
113 @Test
114 void testDecodeWithNullArray() throws Exception {
115 final byte[] plain = null;
116 final byte[] result = QuotedPrintableCodec.decodeQuotedPrintable(plain);
117 assertNull(result, "Result should be null");
118 }
119
120 @Test
121 void testDefaultEncoding() throws Exception {
122 final String plain = "Hello there!";
123 final QuotedPrintableCodec qpcodec = new QuotedPrintableCodec("UnicodeBig");
124 qpcodec.encode(plain);
125 final String encoded1 = qpcodec.encode(plain, "UnicodeBig");
126 final String encoded2 = qpcodec.encode(plain);
127 assertEquals(encoded1, encoded2);
128 }
129
130 @Test
131 void testEncodeDecodeNull() throws Exception {
132 final QuotedPrintableCodec qpcodec = new QuotedPrintableCodec();
133 assertNull(qpcodec.encode((String) null), "Null string quoted-printable encoding test");
134 assertNull(qpcodec.decode((String) null), "Null string quoted-printable decoding test");
135 }
136
137 @Test
138 void testEncodeNull() throws Exception {
139 final QuotedPrintableCodec qpcodec = new QuotedPrintableCodec();
140 final byte[] plain = null;
141 final byte[] encoded = qpcodec.encode(plain);
142 assertNull(encoded, "Encoding a null string should return null");
143 }
144
145 @Test
146 void testEncodeObjects() throws Exception {
147 final QuotedPrintableCodec qpcodec = new QuotedPrintableCodec();
148 final String plain = "1+1 = 2";
149 String encoded = (String) qpcodec.encode((Object) plain);
150
151 assertEquals("1+1 =3D 2", encoded, "Basic quoted-printable encoding test");
152 final byte[] plainBA = plain.getBytes(StandardCharsets.UTF_8);
153 final byte[] encodedBA = (byte[]) qpcodec.encode((Object) plainBA);
154 encoded = new String(encodedBA);
155 assertEquals("1+1 =3D 2", encoded, "Basic quoted-printable encoding test");
156
157 final Object result = qpcodec.encode((Object) null);
158 assertNull(result, "Encoding a null Object should return null");
159
160 assertThrows(EncoderException.class, () -> qpcodec.encode(Double.valueOf(3.0d)), "Trying to url encode a Double object should cause an exception.");
161 }
162
163 @Test
164 void testEncodeStringWithNull() throws Exception {
165 final QuotedPrintableCodec qpcodec = new QuotedPrintableCodec();
166 final String test = null;
167 final String result = qpcodec.encode(test, "charset");
168 assertNull(result, "Result should be null");
169 }
170
171 @Test
172 void testEncodeUrlWithNullBitSet() throws Exception {
173 final QuotedPrintableCodec qpcodec = new QuotedPrintableCodec();
174 final String plain = "1+1 = 2";
175 final String encoded = new String(QuotedPrintableCodec.encodeQuotedPrintable(null, plain.getBytes(StandardCharsets.UTF_8)));
176 assertEquals("1+1 =3D 2", encoded, "Basic quoted-printable encoding test");
177 assertEquals(plain, qpcodec.decode(encoded), "Basic quoted-printable decoding test");
178 }
179
180 @Test
181 void testFinalBytes() throws Exception {
182
183 final String plain = "This is a example of a quoted=printable text file. There is no tt";
184 final String expected = "This is a example of a quoted=3Dprintable text file. There is no tt";
185
186 assertEquals(expected, new QuotedPrintableCodec(true).encode(plain));
187 }
188
189 @ParameterizedTest
190 @ValueSource(strings = { "Line one\r\nLine two", "SEC\nRET", "SEC\rRET", "\r\n", "\r", "\n", "\r\nA\r\n\r\n" })
191 void testHardLineBreakDecode(final String input) throws Exception {
192 final byte[] bytes = StringUtils.getBytesUsAscii(input);
193 assertArrayEquals(bytes, QuotedPrintableCodec.decodeQuotedPrintable(bytes));
194 for (final boolean strict : new boolean[] { false, true }) {
195 assertEquals(input, new QuotedPrintableCodec(strict).decode(input));
196 }
197 }
198
199 @Test
200 void testInvalidEncoding() {
201 assertThrows(UnsupportedCharsetException.class, () -> new QuotedPrintableCodec("NONSENSE"));
202 }
203
204 @ParameterizedTest
205 @ValueSource(strings = { "foo=\rbar", "foo=\r", "foo=\nbar", "=\r", "=\n", "=\r\r\n" })
206 void testInvalidSoftLineBreakDecode(final String input) {
207 assertThrows(DecoderException.class, () -> QuotedPrintableCodec.decodeQuotedPrintable(StringUtils.getBytesUsAscii(input)));
208 for (final boolean strict : new boolean[] { false, true }) {
209 assertThrows(DecoderException.class, () -> new QuotedPrintableCodec(strict).decode(input));
210 }
211 }
212
213 @Test
214 void testPreserveNotEncodedCRLF() throws Exception {
215 final String qpdata = "CRLF in an\n encoded text should be=20=\r\n\rpreserved in the\r decoding.";
216 final String expected = "CRLF in an\n encoded text should be \rpreserved in the\r decoding.";
217
218 final QuotedPrintableCodec qpcodec = new QuotedPrintableCodec(true);
219 assertEquals(expected, qpcodec.decode(qpdata));
220
221 final String encoded = qpcodec.encode(expected);
222 assertEquals(expected, qpcodec.decode(encoded));
223 }
224
225 @Test
226 void testSafeCharEncodeDecode() throws Exception {
227 final QuotedPrintableCodec qpcodec = new QuotedPrintableCodec();
228 final String plain = "abc123_-.*~!@#$%^&()+{}\"\\;:`,/[]";
229 final String encoded = qpcodec.encode(plain);
230 assertEquals(plain, encoded, "Safe chars quoted-printable encoding test");
231 assertEquals(plain, qpcodec.decode(encoded), "Safe chars quoted-printable decoding test");
232 }
233
234 @ParameterizedTest
235 @MethodSource("softLineBreakBoundaryCases")
236 void testSoftLineBreakBoundaries(final String input, final String expected) throws Exception {
237 assertArrayEquals(StringUtils.getBytesUsAscii(expected), QuotedPrintableCodec.decodeQuotedPrintable(StringUtils.getBytesUsAscii(input)));
238 for (final boolean strict : new boolean[] { false, true }) {
239 assertEquals(expected, new QuotedPrintableCodec(strict).decode(input));
240 }
241 }
242
243 @Test
244 void testSoftLineBreakDecode() throws Exception {
245 final String qpdata = "If you believe that truth=3Dbeauty, then surely=20=\r\nmathematics is the most beautiful branch of philosophy.";
246 final String expected = "If you believe that truth=beauty, then surely mathematics is the most beautiful branch of philosophy.";
247
248 final QuotedPrintableCodec qpcodec = new QuotedPrintableCodec();
249 assertEquals(expected, qpcodec.decode(qpdata));
250
251 final String encoded = qpcodec.encode(expected);
252 assertEquals(expected, qpcodec.decode(encoded));
253 }
254
255 @Test
256 void testSoftLineBreakEncode() throws Exception {
257 final String qpdata = "If you believe that truth=3Dbeauty, then surely mathematics is the most b=\r\neautiful branch of philosophy.";
258 final String expected = "If you believe that truth=beauty, then surely mathematics is the most beautiful branch of philosophy.";
259
260 final QuotedPrintableCodec qpcodec = new QuotedPrintableCodec(true);
261 assertEquals(qpdata, qpcodec.encode(expected));
262
263 final String decoded = qpcodec.decode(qpdata);
264 assertEquals(qpdata, qpcodec.encode(decoded));
265 }
266
267 @Test
268 void testTooShortByteArray() throws Exception {
269 final QuotedPrintableCodec qpcodec = new QuotedPrintableCodec(true);
270 assertNull(qpcodec.encode("AA"), "Result should be null.");
271 }
272
273 @Test
274 void testTrailingSpecial() throws Exception {
275 final QuotedPrintableCodec qpcodec = new QuotedPrintableCodec(true);
276
277 String plain = "This is a example of a quoted-printable text file. This might contain sp=cial chars.";
278 String expected = "This is a example of a quoted-printable text file. This might contain sp=3D=\r\ncial chars.";
279 assertEquals(expected, qpcodec.encode(plain));
280
281 plain = "This is a example of a quoted-printable text file. This might contain ta\tbs as well.";
282 expected = "This is a example of a quoted-printable text file. This might contain ta=09=\r\nbs as well.";
283 assertEquals(expected, qpcodec.encode(plain));
284 }
285
286 @Test
287 void testUltimateSoftBreak() throws Exception {
288 final QuotedPrintableCodec qpcodec = new QuotedPrintableCodec(true);
289
290 String plain = "This is a example of a quoted-printable text file. There is no end to it\t";
291 String expected = "This is a example of a quoted-printable text file. There is no end to i=\r\nt=09";
292
293 assertEquals(expected, qpcodec.encode(plain));
294
295 plain = "This is a example of a quoted-printable text file. There is no end to it ";
296 expected = "This is a example of a quoted-printable text file. There is no end to i=\r\nt=20";
297
298 assertEquals(expected, qpcodec.encode(plain));
299
300
301 plain = "This is a example of a quoted-printable text file. There is no end to ";
302 expected = "This is a example of a quoted-printable text file. There is no end to=20=\r\n =20";
303
304 assertEquals(expected, qpcodec.encode(plain));
305
306
307 plain = "This is a example of a quoted-printable text file. There is no end to= ";
308 expected = "This is a example of a quoted-printable text file. There is no end to=3D=\r\n =20";
309
310 assertEquals(expected, qpcodec.encode(plain));
311 }
312
313 @Test
314 void testUnsafeEncodeDecode() throws Exception {
315 final QuotedPrintableCodec qpcodec = new QuotedPrintableCodec();
316 final String plain = "=\r\n";
317 final String encoded = qpcodec.encode(plain);
318 assertEquals("=3D=0D=0A", encoded, "Unsafe chars quoted-printable encoding test");
319 assertEquals(plain, qpcodec.decode(encoded), "Unsafe chars quoted-printable decoding test");
320 }
321
322 @Test
323 void testUTF8RoundTrip() throws Exception {
324
325 final String ru_msg = constructString(RUSSIAN_STUFF_UNICODE);
326 final String ch_msg = constructString(SWISS_GERMAN_STUFF_UNICODE);
327
328 final QuotedPrintableCodec qpcodec = new QuotedPrintableCodec();
329
330 assertEquals("=D0=92=D1=81=D0=B5=D0=BC_=D0=BF=D1=80=D0=B8=D0=B2=D0=B5=D1=82", qpcodec.encode(ru_msg, CharEncoding.UTF_8));
331 assertEquals("Gr=C3=BCezi_z=C3=A4m=C3=A4", qpcodec.encode(ch_msg, CharEncoding.UTF_8));
332
333 assertEquals(ru_msg, qpcodec.decode(qpcodec.encode(ru_msg, CharEncoding.UTF_8), CharEncoding.UTF_8));
334 assertEquals(ch_msg, qpcodec.decode(qpcodec.encode(ch_msg, CharEncoding.UTF_8), CharEncoding.UTF_8));
335 }
336 }