001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License.
018 *
019 */
020package org.apache.mina.filter.codec.textline;
021
022import static org.junit.Assert.assertEquals;
023import static org.junit.Assert.assertTrue;
024import static org.junit.Assert.fail;
025
026import java.nio.charset.Charset;
027import java.nio.charset.CharsetEncoder;
028
029import org.apache.mina.core.buffer.IoBuffer;
030import org.apache.mina.filter.codec.ProtocolCodecSession;
031import org.apache.mina.filter.codec.ProtocolDecoderOutput;
032import org.apache.mina.filter.codec.RecoverableProtocolDecoderException;
033import org.junit.Test;
034
035/**
036 * Tests {@link TextLineDecoder}.
037 *
038 * @author <a href="http://mina.apache.org">Apache MINA Project</a>
039 */
040public class TextLineDecoderTest {
041    @Test
042    public void testNormalDecode() throws Exception {
043        TextLineDecoder decoder = new TextLineDecoder(Charset.forName("UTF-8"), LineDelimiter.WINDOWS);
044
045        CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder();
046        ProtocolCodecSession session = new ProtocolCodecSession();
047        ProtocolDecoderOutput out = session.getDecoderOutput();
048        IoBuffer in = IoBuffer.allocate(16);
049
050        // Test one decode and one output
051        in.putString("ABC\r\n", encoder);
052        in.flip();
053        decoder.decode(session, in, out);
054        assertEquals(1, session.getDecoderOutputQueue().size());
055        assertEquals("ABC", session.getDecoderOutputQueue().poll());
056
057        // Test two decode and one output
058        in.clear();
059        in.putString("DEF", encoder);
060        in.flip();
061        decoder.decode(session, in, out);
062        assertEquals(0, session.getDecoderOutputQueue().size());
063        in.clear();
064        in.putString("GHI\r\n", encoder);
065        in.flip();
066        decoder.decode(session, in, out);
067        assertEquals(1, session.getDecoderOutputQueue().size());
068        assertEquals("DEFGHI", session.getDecoderOutputQueue().poll());
069
070        // Test one decode and two output
071        in.clear();
072        in.putString("JKL\r\nMNO\r\n", encoder);
073        in.flip();
074        decoder.decode(session, in, out);
075        assertEquals(2, session.getDecoderOutputQueue().size());
076        assertEquals("JKL", session.getDecoderOutputQueue().poll());
077        assertEquals("MNO", session.getDecoderOutputQueue().poll());
078
079        // Test aborted delimiter (DIRMINA-506)
080        in.clear();
081        in.putString("ABC\r\r\n", encoder);
082        in.flip();
083        decoder.decode(session, in, out);
084        assertEquals(1, session.getDecoderOutputQueue().size());
085        assertEquals("ABC\r", session.getDecoderOutputQueue().poll());
086
087        // Test splitted long delimiter
088        decoder = new TextLineDecoder(Charset.forName("UTF-8"), new LineDelimiter("\n\n\n"));
089        in.clear();
090        in.putString("PQR\n", encoder);
091        in.flip();
092        decoder.decode(session, in, out);
093        assertEquals(0, session.getDecoderOutputQueue().size());
094        in.clear();
095        in.putString("\n", encoder);
096        in.flip();
097        decoder.decode(session, in, out);
098        assertEquals(0, session.getDecoderOutputQueue().size());
099        in.clear();
100        in.putString("\n", encoder);
101        in.flip();
102        decoder.decode(session, in, out);
103        assertEquals(1, session.getDecoderOutputQueue().size());
104        assertEquals("PQR", session.getDecoderOutputQueue().poll());
105
106        // Test splitted long delimiter which produces two output
107        decoder = new TextLineDecoder(Charset.forName("UTF-8"), new LineDelimiter("\n\n\n"));
108        in.clear();
109        in.putString("PQR\n", encoder);
110        in.flip();
111        decoder.decode(session, in, out);
112        assertEquals(0, session.getDecoderOutputQueue().size());
113        in.clear();
114        in.putString("\n", encoder);
115        in.flip();
116        decoder.decode(session, in, out);
117        assertEquals(0, session.getDecoderOutputQueue().size());
118        in.clear();
119        in.putString("\nSTU\n\n\n", encoder);
120        in.flip();
121        decoder.decode(session, in, out);
122        assertEquals(2, session.getDecoderOutputQueue().size());
123        assertEquals("PQR", session.getDecoderOutputQueue().poll());
124        assertEquals("STU", session.getDecoderOutputQueue().poll());
125
126        // Test splitted long delimiter mixed with partial non-delimiter.
127        decoder = new TextLineDecoder(Charset.forName("UTF-8"), new LineDelimiter("\n\n\n"));
128        in.clear();
129        in.putString("PQR\n", encoder);
130        in.flip();
131        decoder.decode(session, in, out);
132        assertEquals(0, session.getDecoderOutputQueue().size());
133        in.clear();
134        in.putString("X\n", encoder);
135        in.flip();
136        decoder.decode(session, in, out);
137        assertEquals(0, session.getDecoderOutputQueue().size());
138        in.clear();
139        in.putString("\n\nSTU\n\n\n", encoder);
140        in.flip();
141        decoder.decode(session, in, out);
142        assertEquals(2, session.getDecoderOutputQueue().size());
143        assertEquals("PQR\nX", session.getDecoderOutputQueue().poll());
144        assertEquals("STU", session.getDecoderOutputQueue().poll());
145    }
146
147    public void testAutoDecode() throws Exception {
148        TextLineDecoder decoder = new TextLineDecoder(Charset.forName("UTF-8"), LineDelimiter.AUTO);
149
150        CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder();
151        ProtocolCodecSession session = new ProtocolCodecSession();
152        ProtocolDecoderOutput out = session.getDecoderOutput();
153        IoBuffer in = IoBuffer.allocate(16);
154
155        // Test one decode and one output
156        in.putString("ABC\r\n", encoder);
157        in.flip();
158        decoder.decode(session, in, out);
159        assertEquals(1, session.getDecoderOutputQueue().size());
160        assertEquals("ABC", session.getDecoderOutputQueue().poll());
161
162        // Test two decode and one output
163        in.clear();
164        in.putString("DEF", encoder);
165        in.flip();
166        decoder.decode(session, in, out);
167        assertEquals(0, session.getDecoderOutputQueue().size());
168        in.clear();
169        in.putString("GHI\r\n", encoder);
170        in.flip();
171        decoder.decode(session, in, out);
172        assertEquals(1, session.getDecoderOutputQueue().size());
173        assertEquals("DEFGHI", session.getDecoderOutputQueue().poll());
174
175        // Test one decode and two output
176        in.clear();
177        in.putString("JKL\r\nMNO\r\n", encoder);
178        in.flip();
179        decoder.decode(session, in, out);
180        assertEquals(2, session.getDecoderOutputQueue().size());
181        assertEquals("JKL", session.getDecoderOutputQueue().poll());
182        assertEquals("MNO", session.getDecoderOutputQueue().poll());
183
184        // Test multiple '\n's
185        in.clear();
186        in.putString("\n\n\n", encoder);
187        in.flip();
188        decoder.decode(session, in, out);
189        assertEquals(3, session.getDecoderOutputQueue().size());
190        assertEquals("", session.getDecoderOutputQueue().poll());
191        assertEquals("", session.getDecoderOutputQueue().poll());
192        assertEquals("", session.getDecoderOutputQueue().poll());
193
194        // Test splitted long delimiter (\r\r\n)
195        in.clear();
196        in.putString("PQR\r", encoder);
197        in.flip();
198        decoder.decode(session, in, out);
199        assertEquals(0, session.getDecoderOutputQueue().size());
200        in.clear();
201        in.putString("\r", encoder);
202        in.flip();
203        decoder.decode(session, in, out);
204        assertEquals(0, session.getDecoderOutputQueue().size());
205        in.clear();
206        in.putString("\n", encoder);
207        in.flip();
208        decoder.decode(session, in, out);
209        assertEquals(1, session.getDecoderOutputQueue().size());
210        assertEquals("PQR", session.getDecoderOutputQueue().poll());
211
212        // Test splitted long delimiter (\r\r\n) which produces two output
213        in.clear();
214        in.putString("PQR\r", encoder);
215        in.flip();
216        decoder.decode(session, in, out);
217        assertEquals(0, session.getDecoderOutputQueue().size());
218        in.clear();
219        in.putString("\r", encoder);
220        in.flip();
221        decoder.decode(session, in, out);
222        assertEquals(0, session.getDecoderOutputQueue().size());
223        in.clear();
224        in.putString("\nSTU\r\r\n", encoder);
225        in.flip();
226        decoder.decode(session, in, out);
227        assertEquals(2, session.getDecoderOutputQueue().size());
228        assertEquals("PQR", session.getDecoderOutputQueue().poll());
229        assertEquals("STU", session.getDecoderOutputQueue().poll());
230
231        // Test splitted long delimiter mixed with partial non-delimiter.
232        in.clear();
233        in.putString("PQR\r", encoder);
234        in.flip();
235        decoder.decode(session, in, out);
236        assertEquals(0, session.getDecoderOutputQueue().size());
237        in.clear();
238        in.putString("X\r", encoder);
239        in.flip();
240        decoder.decode(session, in, out);
241        assertEquals(0, session.getDecoderOutputQueue().size());
242        in.clear();
243        in.putString("\r\nSTU\r\r\n", encoder);
244        in.flip();
245        decoder.decode(session, in, out);
246        assertEquals(2, session.getDecoderOutputQueue().size());
247        assertEquals("PQR\rX", session.getDecoderOutputQueue().poll());
248        assertEquals("STU", session.getDecoderOutputQueue().poll());
249
250        in.clear();
251        String s = new String(new byte[] { 0, 77, 105, 110, 97 });
252        in.putString(s, encoder);
253        in.flip();
254        decoder.decode(session, in, out);
255        assertEquals(1, session.getDecoderOutputQueue().size());
256        assertEquals(s, session.getDecoderOutputQueue().poll());
257    }
258
259    public void testOverflow() throws Exception {
260        TextLineDecoder decoder = new TextLineDecoder(Charset.forName("UTF-8"), LineDelimiter.AUTO);
261        decoder.setMaxLineLength(3);
262
263        CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder();
264        ProtocolCodecSession session = new ProtocolCodecSession();
265        ProtocolDecoderOutput out = session.getDecoderOutput();
266        IoBuffer in = IoBuffer.allocate(16);
267
268        // Make sure the overflow exception is not thrown until
269        // the delimiter is encountered.
270        in.putString("A", encoder).flip().mark();
271        decoder.decode(session, in.reset().mark(), out);
272        assertEquals(0, session.getDecoderOutputQueue().size());
273        decoder.decode(session, in.reset().mark(), out);
274        assertEquals(0, session.getDecoderOutputQueue().size());
275        decoder.decode(session, in.reset().mark(), out);
276        assertEquals(0, session.getDecoderOutputQueue().size());
277        decoder.decode(session, in.reset().mark(), out);
278        assertEquals(0, session.getDecoderOutputQueue().size());
279
280        in.clear().putString("A\r\nB\r\n", encoder).flip();
281
282        try {
283            decoder.decode(session, in, out);
284            fail();
285        } catch (RecoverableProtocolDecoderException e) {
286            // signifies a successful test execution
287            assertTrue(true);
288        }
289
290        decoder.decode(session, in, out);
291        assertEquals(1, session.getDecoderOutputQueue().size());
292        assertEquals("B", session.getDecoderOutputQueue().poll());
293
294        // Make sure OOM is not thrown.
295        System.gc();
296        long oldFreeMemory = Runtime.getRuntime().freeMemory();
297        in = IoBuffer.allocate(1048576 * 16).sweep((byte) ' ').mark();
298
299        for (int i = 0; i < 10; i++) {
300            decoder.decode(session, in.reset().mark(), out);
301            assertEquals(0, session.getDecoderOutputQueue().size());
302
303            // Memory consumption should be minimal.
304            assertTrue(Runtime.getRuntime().freeMemory() - oldFreeMemory < 1048576);
305        }
306
307        in.clear().putString("C\r\nD\r\n", encoder).flip();
308        try {
309            decoder.decode(session, in, out);
310            fail();
311        } catch (RecoverableProtocolDecoderException e) {
312            // signifies a successful test execution
313            assertTrue(true);
314        }
315
316        decoder.decode(session, in, out);
317        assertEquals(1, session.getDecoderOutputQueue().size());
318        assertEquals("D", session.getDecoderOutputQueue().poll());
319
320        // Memory consumption should be minimal.
321        assertTrue(Runtime.getRuntime().freeMemory() - oldFreeMemory < 1048576);
322    }
323
324    public void testSMTPDataBounds() throws Exception {
325        TextLineDecoder decoder = new TextLineDecoder(Charset.forName("ISO-8859-1"), new LineDelimiter("\r\n.\r\n"));
326
327        CharsetEncoder encoder = Charset.forName("ISO-8859-1").newEncoder();
328        ProtocolCodecSession session = new ProtocolCodecSession();
329        IoBuffer in = IoBuffer.allocate(16).setAutoExpand(true);
330
331        in.putString("\r\n", encoder).flip().mark();
332        decoder.decode(session, in.reset().mark(), session.getDecoderOutput());
333        assertEquals(0, session.getDecoderOutputQueue().size());
334        in.putString("Body\r\n.\r\n", encoder).flip().mark();
335        decoder.decode(session, in.reset().mark(), session.getDecoderOutput());
336        assertEquals(1, session.getDecoderOutputQueue().size());
337        assertEquals("\r\n\r\nBody", session.getDecoderOutputQueue().poll());
338    }
339}