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.assertEquals;
21 import static org.junit.jupiter.api.Assertions.assertFalse;
22 import static org.junit.jupiter.api.Assertions.assertNull;
23 import static org.junit.jupiter.api.Assertions.assertThrows;
24 import static org.junit.jupiter.api.Assertions.assertTrue;
25
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.junit.jupiter.api.Named;
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 QCodecTest {
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 static Stream<Arguments> literalLineBreaks() {
49
50 return Stream.of(false, true).flatMap(underscore -> Stream.of(
51 Arguments.of(underscore, Named.of("CRLF", "\r\n")),
52 Arguments.of(underscore, Named.of("CR", "\r")),
53 Arguments.of(underscore, Named.of("LF", "\n"))));
54
55 }
56
57 static Stream<Arguments> malformedLineBreaks() {
58
59 return Stream.of(false, true).flatMap(underscore -> Stream.of(
60 Arguments.of(underscore, Named.of("CR without LF", "foo=\rbar")),
61 Arguments.of(underscore, Named.of("trailing CR", "foo=\r")),
62 Arguments.of(underscore, Named.of("LF without CR", "foo=\nbar"))));
63
64 }
65
66 private String constructString(final int[] unicodeChars) {
67 final StringBuilder buffer = new StringBuilder();
68 if (unicodeChars != null) {
69 for (final int unicodeChar : unicodeChars) {
70 buffer.append((char) unicodeChar);
71 }
72 }
73 return buffer.toString();
74 }
75
76 @Test
77 void testBasicEncodeDecode() throws Exception {
78 final QCodec qcodec = new QCodec();
79 final String plain = "= Hello there =\r\n";
80 final String encoded = qcodec.encode(plain);
81 assertEquals("=?UTF-8?Q?=3D Hello there =3D=0D=0A?=", encoded, "Basic Q encoding test");
82 assertEquals(plain, qcodec.decode(encoded), "Basic Q decoding test");
83 }
84
85 @ParameterizedTest(name = "{0}")
86 @ValueSource(strings = { "=?UTF-8?Q?ABC?DEF?=", "=?UTF-8?Q?ABC??=", "=?UTF-8?Q???=" })
87 void testDecodeEmbeddedQuestionMark(final String encoded) {
88 assertThrows(DecoderException.class, () -> new QCodec().decode(encoded));
89 }
90
91 @Test
92 void testDecodeEncodedQuestionMark() throws DecoderException {
93 assertEquals("ABC?DEF", new QCodec().decode("=?UTF-8?Q?ABC=3FDEF?="));
94 }
95
96 @ParameterizedTest(name = "underscore={0}")
97 @ValueSource(booleans = { false, true })
98 void testDecodeEscapedCrlf(final boolean underscore) throws DecoderException {
99 final String suffix = underscore ? "_" : "";
100 final String decodedSuffix = underscore ? " " : "";
101 assertEquals("SEC\r\nRET" + decodedSuffix, new QCodec().decode("=?UTF-8?Q?SEC=0D=0ARET" + suffix + "?="));
102 }
103
104 @ParameterizedTest(name = "underscore={0}: {1}")
105 @MethodSource("literalLineBreaks")
106 void testDecodeLiteralLineBreaks(final boolean underscore, final String lineBreak) throws DecoderException {
107 final String suffix = underscore ? "_" : "";
108 final String decodedSuffix = underscore ? " " : "";
109 assertEquals("SEC" + lineBreak + "RET" + decodedSuffix, new QCodec().decode("=?UTF-8?Q?SEC" + lineBreak + "RET" + suffix + "?="));
110 }
111
112 @ParameterizedTest(name = "underscore={0}: {1}")
113 @MethodSource("malformedLineBreaks")
114 void testDecodeMalformedLineBreaks(final boolean underscore, final String encoded) {
115 final String prefix = underscore ? "_" : "";
116 assertThrows(DecoderException.class, () -> new QCodec().decode("=?UTF-8?Q?" + prefix + encoded + "?="));
117 }
118
119 @Test
120 void testDecodeObjects() throws Exception {
121 final QCodec qcodec = new QCodec();
122 final String decoded = "=?UTF-8?Q?1+1 =3D 2?=";
123 final String plain = (String) qcodec.decode((Object) decoded);
124 assertEquals("1+1 = 2", plain, "Basic Q decoding test");
125
126 final Object result = qcodec.decode((Object) null);
127 assertNull(result, "Decoding a null Object should return null");
128 assertThrows(DecoderException.class, () -> qcodec.decode(Double.valueOf(3.0d)), "Trying to url encode a Double object should cause an exception.");
129 }
130
131 @Test
132 void testDecodeSeparatorNotFoundThrowsDecoderException() {
133 assertThrows(DecoderException.class, () -> new QCodec().decode("=?="));
134 }
135
136 @Test
137 void testDecodeStringWithNull() throws Exception {
138 final QCodec qcodec = new QCodec();
139 final String test = null;
140 final String result = qcodec.decode(test);
141 assertNull(result, "Result should be null");
142 }
143
144 @Test
145 void testEncodeDecodeBlanks() throws Exception {
146 final String plain = "Mind those pesky blanks";
147 final String encoded1 = "=?UTF-8?Q?Mind those pesky blanks?=";
148 final String encoded2 = "=?UTF-8?Q?Mind_those_pesky_blanks?=";
149 final QCodec qcodec = new QCodec();
150 qcodec.setEncodeBlanks(false);
151 String s = qcodec.encode(plain);
152 assertEquals(encoded1, s, "Blanks encoding with the Q codec test");
153 qcodec.setEncodeBlanks(true);
154 s = qcodec.encode(plain);
155 assertEquals(encoded2, s, "Blanks encoding with the Q codec test");
156 s = qcodec.decode(encoded1);
157 assertEquals(plain, s, "Blanks decoding with the Q codec test");
158 s = qcodec.decode(encoded2);
159 assertEquals(plain, s, "Blanks decoding with the Q codec test");
160 }
161
162 @Test
163 void testEncodeDecodeNull() throws Exception {
164 final QCodec qcodec = new QCodec();
165 assertNull(qcodec.encode((String) null), "Null string Q encoding test");
166 assertNull(qcodec.decode((String) null), "Null string Q decoding test");
167 }
168
169 @Test
170 void testEncodeObjects() throws Exception {
171 final QCodec qcodec = new QCodec();
172 final String plain = "1+1 = 2";
173 final String encoded = (String) qcodec.encode((Object) plain);
174
175 assertEquals("=?UTF-8?Q?1+1 =3D 2?=", encoded, "Basic Q encoding test");
176 final Object result = qcodec.encode((Object) null);
177 assertNull(result, "Encoding a null Object should return null");
178 assertThrows(EncoderException.class, () -> qcodec.encode(Double.valueOf(3.0d)), "Trying to url encode a Double object should cause an exception.");
179 }
180
181 @Test
182 void testEncodeStringWithNull() throws Exception {
183 final QCodec qcodec = new QCodec();
184 final String test = null;
185 final String result = qcodec.encode(test, "charset");
186 assertNull(result, "Result should be null");
187 }
188
189 @Test
190 void testInvalidEncoding() {
191 assertThrows(UnsupportedCharsetException.class, () -> new QCodec("NONSENSE"));
192 }
193
194 @Test
195 void testLetUsMakeCloverHappy() throws Exception {
196 final QCodec qcodec = new QCodec();
197 qcodec.setEncodeBlanks(true);
198 assertTrue(qcodec.isEncodeBlanks());
199 qcodec.setEncodeBlanks(false);
200 assertFalse(qcodec.isEncodeBlanks());
201 }
202
203 @Test
204 void testNullInput() throws Exception {
205 final QCodec qcodec = new QCodec();
206 assertNull(qcodec.doDecoding(null));
207 assertNull(qcodec.doEncoding(null));
208 }
209
210 @Test
211 void testUnsafeEncodeDecode() throws Exception {
212 final QCodec qcodec = new QCodec();
213 final String plain = "?_=\r\n";
214 final String encoded = qcodec.encode(plain);
215 assertEquals("=?UTF-8?Q?=3F=5F=3D=0D=0A?=", encoded, "Unsafe chars Q encoding test");
216 assertEquals(plain, qcodec.decode(encoded), "Unsafe chars Q decoding test");
217 }
218
219 @Test
220 void testUTF8RoundTrip() throws Exception {
221
222 final String ru_msg = constructString(RUSSIAN_STUFF_UNICODE);
223 final String ch_msg = constructString(SWISS_GERMAN_STUFF_UNICODE);
224
225 final QCodec qcodec = new QCodec(CharEncoding.UTF_8);
226
227 assertEquals("=?UTF-8?Q?=D0=92=D1=81=D0=B5=D0=BC=5F=D0=BF=D1=80=D0=B8=D0=B2=D0=B5=D1=82?=", qcodec.encode(ru_msg));
228 assertEquals("=?UTF-8?Q?Gr=C3=BCezi=5Fz=C3=A4m=C3=A4?=", qcodec.encode(ch_msg));
229
230 assertEquals(ru_msg, qcodec.decode(qcodec.encode(ru_msg)));
231 assertEquals(ch_msg, qcodec.decode(qcodec.encode(ch_msg)));
232 }
233
234 }