View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
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   * Quoted-printable codec test cases
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          // @formatter:off
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          // @formatter:on
55      }
56  
57      static Stream<Arguments> malformedLineBreaks() {
58          // @formatter:off
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          // @formatter:on
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 }