View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *  
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *  
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License. 
18   *  
19   */
20  package org.apache.mina.http;
21  
22  import static org.junit.Assert.assertEquals;
23  import static org.junit.Assert.assertNotEquals;
24  import static org.junit.Assert.assertTrue;
25  
26  import java.nio.charset.CharacterCodingException;
27  import java.nio.charset.Charset;
28  import java.nio.charset.CharsetEncoder;
29  
30  import org.apache.mina.core.buffer.IoBuffer;
31  import org.apache.mina.core.filterchain.IoFilter.NextFilter;
32  import org.apache.mina.core.session.DummySession;
33  import org.apache.mina.core.session.IoSession;
34  import org.apache.mina.filter.codec.AbstractProtocolDecoderOutput;
35  import org.apache.mina.filter.codec.ProtocolDecoder;
36  import org.apache.mina.http.api.HttpEndOfContent;
37  import org.apache.mina.http.api.HttpRequest;
38  import org.junit.Test;
39  
40  public class HttpServerDecoderTest {
41      private static final String DECODER_STATE_ATT = "http.ds";
42  
43      private static final CharsetEncoder encoder = Charset.forName("US-ASCII").newEncoder(); //$NON-NLS-1$
44  
45      private static final ProtocolDecoder decoder = new HttpServerDecoder();
46  
47      /*
48       * Use a single session for all requests in order to test state management better
49       */
50      private static IoSession session = new DummySession();
51  
52      /**
53       * Build an IO buffer containing a simple minimal HTTP request.
54       * 
55       * @param method the HTTP method
56       * @param body the option body
57       * @return the built IO buffer
58       * @throws CharacterCodingException if encoding fails
59       */
60      protected static IoBuffer getRequestBuffer(String method, String body) throws CharacterCodingException {
61          IoBuffer buffer = IoBuffer.allocate(0).setAutoExpand(true);
62          buffer.putString(method + " / HTTP/1.1\r\nHost: dummy\r\n", encoder);
63          
64          if (body != null) {
65              buffer.putString("Content-Length: " + body.length() + "\r\n\r\n", encoder);
66              buffer.putString(body, encoder);
67          } else {
68              buffer.putString("\r\n", encoder);
69          }
70          
71          buffer.rewind();
72          
73          return buffer;
74      }
75  
76      protected static IoBuffer getRequestBuffer(String method) throws CharacterCodingException {
77          return getRequestBuffer(method, null);
78      }
79  
80      /**
81       * Execute an HTPP request and return the queue of messages.
82       * 
83       * @param method the HTTP method
84       * @param body the optional body
85       * @return the protocol output and its queue of messages
86       * @throws Exception if error occurs (encoding,...)
87       */
88      protected static AbstractProtocolDecoderOutput executeRequest(String method, String body) throws Exception {
89          AbstractProtocolDecoderOutput out = new AbstractProtocolDecoderOutput() {
90              public void flush(NextFilter nextFilter, IoSession session) {
91              }
92          };
93  
94          IoBuffer buffer = getRequestBuffer(method, body); //$NON-NLS-1$
95          
96          while (buffer.hasRemaining()) {
97              decoder.decode(session, buffer, out);
98          }
99          
100         return out;
101     }
102 
103     @Test
104     public void testGetRequestWithoutBody() throws Exception {
105         AbstractProtocolDecoderOutput out = executeRequest("GET", null);
106         assertEquals(2, out.getMessageQueue().size());
107         assertTrue(out.getMessageQueue().poll() instanceof HttpRequest);
108         assertTrue(out.getMessageQueue().poll() instanceof HttpEndOfContent);
109     }
110 
111     @Test
112     public void testGetRequestBody() throws Exception {
113         AbstractProtocolDecoderOutput out = executeRequest("GET", "body");
114         assertEquals(3, out.getMessageQueue().size());
115         assertTrue(out.getMessageQueue().poll() instanceof HttpRequest);
116         assertTrue(out.getMessageQueue().poll() instanceof IoBuffer);
117         assertTrue(out.getMessageQueue().poll() instanceof HttpEndOfContent);
118     }
119 
120     @Test
121     public void testPutRequestWithoutBody() throws Exception {
122         AbstractProtocolDecoderOutput out = executeRequest("PUT", null);
123         assertEquals(2, out.getMessageQueue().size());
124         assertTrue(out.getMessageQueue().poll() instanceof HttpRequest);
125         assertTrue(out.getMessageQueue().poll() instanceof HttpEndOfContent);
126     }
127 
128     @Test
129     public void testPutRequestBody() throws Exception {
130         AbstractProtocolDecoderOutput out = executeRequest("PUT", "body");
131         assertEquals(3, out.getMessageQueue().size());
132         assertTrue(out.getMessageQueue().poll() instanceof HttpRequest);
133         assertTrue(out.getMessageQueue().poll() instanceof IoBuffer);
134         assertTrue(out.getMessageQueue().poll() instanceof HttpEndOfContent);
135     }
136 
137     @Test
138     public void testPostRequestWithoutBody() throws Exception {
139         AbstractProtocolDecoderOutput out = executeRequest("POST", null);
140         assertEquals(2, out.getMessageQueue().size());
141         assertTrue(out.getMessageQueue().poll() instanceof HttpRequest);
142         assertTrue(out.getMessageQueue().poll() instanceof HttpEndOfContent);
143     }
144 
145     @Test
146     public void testPostRequestBody() throws Exception {
147         AbstractProtocolDecoderOutput out = executeRequest("POST", "body");
148         assertEquals(3, out.getMessageQueue().size());
149         assertTrue(out.getMessageQueue().poll() instanceof HttpRequest);
150         assertTrue(out.getMessageQueue().poll() instanceof IoBuffer);
151         assertTrue(out.getMessageQueue().poll() instanceof HttpEndOfContent);
152     }
153 
154     @Test
155     public void testDeleteRequestWithoutBody() throws Exception {
156         AbstractProtocolDecoderOutput out = executeRequest("DELETE", null);
157         assertEquals(2, out.getMessageQueue().size());
158         assertTrue(out.getMessageQueue().poll() instanceof HttpRequest);
159         assertTrue(out.getMessageQueue().poll() instanceof HttpEndOfContent);
160     }
161 
162     @Test
163     public void testDeleteRequestBody() throws Exception {
164         AbstractProtocolDecoderOutput out = executeRequest("DELETE", "body");
165         assertEquals(3, out.getMessageQueue().size());
166         assertTrue(out.getMessageQueue().poll() instanceof HttpRequest);
167         assertTrue(out.getMessageQueue().poll() instanceof IoBuffer);
168         assertTrue(out.getMessageQueue().poll() instanceof HttpEndOfContent);
169     }
170     
171     @Test
172     public void testDIRMINA965NoContent() throws Exception {
173         AbstractProtocolDecoderOutput out = new AbstractProtocolDecoderOutput() {
174             public void flush(NextFilter nextFilter, IoSession session) {
175             }
176         };
177         IoBuffer buffer = IoBuffer.allocate(0).setAutoExpand(true);
178         buffer.putString("GET / HTTP/1.1\r\nHost: ", encoder);
179         buffer.rewind();
180         while (buffer.hasRemaining()) {
181             decoder.decode(session, buffer, out);
182         }
183         buffer = IoBuffer.allocate(0).setAutoExpand(true);
184         buffer.putString("dummy\r\n\r\n", encoder);
185         buffer.rewind();
186         while (buffer.hasRemaining()) {
187             decoder.decode(session, buffer, out);
188         }
189         assertEquals(2, out.getMessageQueue().size());
190         assertTrue(out.getMessageQueue().poll() instanceof HttpRequest);
191         assertTrue(out.getMessageQueue().poll() instanceof HttpEndOfContent);
192     }
193 
194     @Test
195     public void testDIRMINA965WithContent() throws Exception {
196         AbstractProtocolDecoderOutput out = new AbstractProtocolDecoderOutput() {
197             public void flush(NextFilter nextFilter, IoSession session) {
198             }
199         };
200         IoBuffer buffer = IoBuffer.allocate(0).setAutoExpand(true);
201         buffer.putString("GET / HTTP/1.1\r\nHost: ", encoder);
202         buffer.rewind();
203         while (buffer.hasRemaining()) {
204             decoder.decode(session, buffer, out);
205         }
206         buffer = IoBuffer.allocate(0).setAutoExpand(true);
207         buffer.putString("dummy\r\nContent-Length: 1\r\n\r\nA", encoder);
208         buffer.rewind();
209         while (buffer.hasRemaining()) {
210             decoder.decode(session, buffer, out);
211         }
212         assertEquals(3, out.getMessageQueue().size());
213         assertTrue(out.getMessageQueue().poll() instanceof HttpRequest);
214         assertTrue(out.getMessageQueue().poll() instanceof IoBuffer);
215         assertTrue(out.getMessageQueue().poll() instanceof HttpEndOfContent);
216     }
217     @Test
218     public void testDIRMINA965WithContentOnTwoChunks() throws Exception {
219         AbstractProtocolDecoderOutput out = new AbstractProtocolDecoderOutput() {
220             public void flush(NextFilter nextFilter, IoSession session) {
221             }
222         };
223         IoBuffer buffer = IoBuffer.allocate(0).setAutoExpand(true);
224         buffer.putString("GET / HTTP/1.1\r\nHost: ", encoder);
225         buffer.rewind();
226         while (buffer.hasRemaining()) {
227             decoder.decode(session, buffer, out);
228         }
229         buffer = IoBuffer.allocate(0).setAutoExpand(true);
230         buffer.putString("dummy\r\nContent-Length: 2\r\n\r\nA", encoder);
231         buffer.rewind();
232         while (buffer.hasRemaining()) {
233             decoder.decode(session, buffer, out);
234         }
235         buffer = IoBuffer.allocate(0).setAutoExpand(true);
236         buffer.putString("B", encoder);
237         buffer.rewind();
238         while (buffer.hasRemaining()) {
239             decoder.decode(session, buffer, out);
240         }
241         assertEquals(4, out.getMessageQueue().size());
242         assertTrue(out.getMessageQueue().poll() instanceof HttpRequest);
243         assertTrue(out.getMessageQueue().poll() instanceof IoBuffer);
244         assertTrue(out.getMessageQueue().poll() instanceof IoBuffer);
245         assertTrue(out.getMessageQueue().poll() instanceof HttpEndOfContent);
246     }
247     
248     @Test
249     public void testDIRMINA1035HeadersWithColons() throws Exception {
250         AbstractProtocolDecoderOutput out = new AbstractProtocolDecoderOutput() {
251             public void flush(NextFilter nextFilter, IoSession session) {
252             }
253         };
254 
255         IoBuffer buffer = IoBuffer.allocate(0).setAutoExpand(true);
256         buffer.putString("GET / HTTP/1.0\r\nHost: localhost\r\n", encoder);
257         buffer.putString("SomeHeaderA: Value-A\r\n", encoder);
258         buffer.putString("SomeHeaderB: Value-B:Has:Some:Colons\r\n", encoder);
259         buffer.putString("SomeHeaderC: Value-C\r\n", encoder);
260         buffer.putString("SomeHeaderD:\r\n\r\n", encoder);
261         buffer.rewind();
262         while (buffer.hasRemaining()) {
263             decoder.decode(session, buffer, out);
264         }
265         assertEquals(2, out.getMessageQueue().size());
266         HttpRequest request = (HttpRequest) out.getMessageQueue().poll();
267         assertEquals("Value-A", request.getHeader("SomeHeaderA".toLowerCase()));
268         assertEquals("Value-B:Has:Some:Colons", request.getHeader("SomeHeaderB".toLowerCase()));
269         assertEquals("Value-C", request.getHeader("SomeHeaderC".toLowerCase()));
270         assertEquals("", request.getHeader("SomeHeaderD".toLowerCase()));
271         assertTrue(out.getMessageQueue().poll() instanceof HttpEndOfContent);
272     }
273 
274     @Test
275     public void verifyThatHeaderWithoutLeadingSpaceIsSupported() throws Exception {
276         AbstractProtocolDecoderOutput out = new AbstractProtocolDecoderOutput() {
277             public void flush(NextFilter nextFilter, IoSession session) {
278             }
279         };
280         IoBuffer buffer = IoBuffer.allocate(0).setAutoExpand(true);
281         buffer.putString("GET / HTTP/1.0\r\nHost:localhost\r\n\r\n", encoder);
282         buffer.rewind();
283         while (buffer.hasRemaining()) {
284             decoder.decode(session, buffer, out);
285         }
286         assertEquals(2, out.getMessageQueue().size());
287         HttpRequest request = (HttpRequest) out.getMessageQueue().poll();
288         assertEquals("localhost", request.getHeader("host"));
289         assertTrue(out.getMessageQueue().poll() instanceof HttpEndOfContent);
290     }
291 
292     @Test
293     public void verifyThatLeadingSpacesAreRemovedFromHeader() throws Exception {
294         AbstractProtocolDecoderOutput out = new AbstractProtocolDecoderOutput() {
295             public void flush(NextFilter nextFilter, IoSession session) {
296             }
297         };
298         IoBuffer buffer = IoBuffer.allocate(0).setAutoExpand(true);
299         buffer.putString("GET / HTTP/1.0\r\nHost:  localhost\r\n\r\n", encoder);
300         buffer.rewind();
301         while (buffer.hasRemaining()) {
302             decoder.decode(session, buffer, out);
303         }
304         assertEquals(2, out.getMessageQueue().size());
305         HttpRequest request = (HttpRequest) out.getMessageQueue().poll();
306         assertEquals("localhost", request.getHeader("host"));
307         assertTrue(out.getMessageQueue().poll() instanceof HttpEndOfContent);
308     }
309 
310     @Test
311     public void verifyThatTrailingSpacesAreRemovedFromHeader() throws Exception {
312         AbstractProtocolDecoderOutput out = new AbstractProtocolDecoderOutput() {
313             public void flush(NextFilter nextFilter, IoSession session) {
314             }
315         };
316         IoBuffer buffer = IoBuffer.allocate(0).setAutoExpand(true);
317         buffer.putString("GET / HTTP/1.0\r\nHost:localhost  \r\n\r\n", encoder);
318         buffer.rewind();
319         while (buffer.hasRemaining()) {
320             decoder.decode(session, buffer, out);
321         }
322         assertEquals(2, out.getMessageQueue().size());
323         HttpRequest request = (HttpRequest) out.getMessageQueue().poll();
324         assertEquals("localhost", request.getHeader("host"));
325         assertTrue(out.getMessageQueue().poll() instanceof HttpEndOfContent);
326     }
327 
328     @Test
329     public void dosOnRequestWithAdditionalData() throws Exception {
330         AbstractProtocolDecoderOutput out = new AbstractProtocolDecoderOutput() {
331             public void flush(NextFilter nextFilter, IoSession session) {
332             }
333         };
334 
335         IoBuffer buffer = IoBuffer.allocate(0).setAutoExpand(true);
336         buffer.putString("GET / HTTP/1.0\r\nHost:localhost  \r\n\r\ndummy", encoder);
337         buffer.rewind();
338         int prevBufferPosition = buffer.position();
339         
340         while (buffer.hasRemaining()) {
341             decoder.decode(session, buffer, out);
342             assertNotEquals("Buffer at new position", prevBufferPosition, buffer.position());
343             prevBufferPosition = buffer.position();
344         }
345         
346         assertEquals(2, out.getMessageQueue().size());
347         HttpRequest request = (HttpRequest) out.getMessageQueue().poll();
348         assertEquals("localhost", request.getHeader("host"));
349         assertTrue(out.getMessageQueue().poll() instanceof HttpEndOfContent);
350         session.removeAttribute(DECODER_STATE_ATT); // This test leaves session in HEAD state, crashing following test
351     }
352 }