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