View Javadoc

1   // Copyright (C) 2005 - 2012 Philip Aston
2   // Copyright (C) 2007 Venelin Mitov
3   // Copyright (C) 2009 Hitoshi Amano
4   // All rights reserved.
5   //
6   // This file is part of The Grinder software distribution. Refer to
7   // the file LICENSE which is part of The Grinder distribution for
8   // licensing details. The Grinder distribution is available on the
9   // Internet at http://grinder.sourceforge.net/
10  //
11  // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
12  // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
13  // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
14  // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
15  // COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
16  // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
17  // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
18  // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
19  // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
20  // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
21  // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
22  // OF THE POSSIBILITY OF SUCH DAMAGE.
23  
24  package net.grinder.plugin.http.tcpproxyfilter;
25  
26  import static org.junit.Assert.assertEquals;
27  import static org.junit.Assert.assertFalse;
28  import static org.junit.Assert.assertTrue;
29  import static org.mockito.Matchers.contains;
30  import static org.mockito.Matchers.eq;
31  import static org.mockito.Matchers.isA;
32  import static org.mockito.Matchers.same;
33  import static org.mockito.Mockito.times;
34  import static org.mockito.Mockito.verify;
35  import static org.mockito.Mockito.verifyNoMoreInteractions;
36  import static org.mockito.Mockito.when;
37  
38  import java.io.File;
39  import java.io.FileNotFoundException;
40  import java.nio.BufferOverflowException;
41  
42  import net.grinder.plugin.http.xml.FormFieldType;
43  import net.grinder.plugin.http.xml.RequestType;
44  import net.grinder.plugin.http.xml.ResponseTokenReferenceType;
45  import net.grinder.plugin.http.xml.TokenReferenceType;
46  import net.grinder.plugin.http.xml.TokenResponseLocationType;
47  import net.grinder.plugin.http.xml.TokenType;
48  import net.grinder.testutility.AbstractJUnit4FileTestCase;
49  import net.grinder.testutility.FileUtilities;
50  import net.grinder.tools.tcpproxy.CommentSource;
51  import net.grinder.tools.tcpproxy.CommentSourceImplementation;
52  import net.grinder.tools.tcpproxy.ConnectionDetails;
53  import net.grinder.tools.tcpproxy.EndPoint;
54  import net.grinder.util.AttributeStringParser;
55  import net.grinder.util.AttributeStringParserImplementation;
56  import net.grinder.util.SimpleStringEscaper;
57  import net.grinder.util.StringEscaper;
58  import net.grinder.util.http.URIParser;
59  import net.grinder.util.http.URIParserImplementation;
60  
61  import org.junit.Before;
62  import org.junit.Test;
63  import org.mockito.ArgumentCaptor;
64  import org.mockito.Captor;
65  import org.mockito.Mock;
66  import org.mockito.MockitoAnnotations;
67  import org.slf4j.Logger;
68  
69  
70  /**
71   * Unit tests for {@link ConnectionHandlerImplementation}.
72   *
73   * @author Philip Aston
74   */
75  public class TestConnectionHandlerImplementation
76    extends AbstractJUnit4FileTestCase {
77  
78    @Mock private HTTPRecording m_httpRecording;
79    @Mock private Logger m_logger;
80    @Captor private ArgumentCaptor<ResponseTokenReferenceType>
81      m_responseTokenRTCaptor;
82  
83    private final RegularExpressions m_regularExpressions =
84      new RegularExpressionsImplementation();
85  
86    private final URIParser m_uriParser = new URIParserImplementation();
87  
88    private final AttributeStringParser m_attributeStringParser =
89      new AttributeStringParserImplementation();
90  
91    private StringEscaper m_stringEscaper = new SimpleStringEscaper();
92  
93    private final ConnectionDetails m_connectionDetails =
94      new ConnectionDetails(
95        new EndPoint("hostA", 80),
96        new EndPoint("hostB", 80),
97        false);
98  
99    private final CommentSource m_commentSource =
100     new CommentSourceImplementation();
101 
102   @Before public void setUp() {
103     MockitoAnnotations.initMocks(this);
104 
105     final HTTPRecordingParameters parametersFromProperties =
106         new ParametersFromProperties();
107 
108     when(m_httpRecording.getParameters()).thenReturn(parametersFromProperties);
109   }
110 
111   @Test public void testRequestWithGet() throws Exception {
112     final ConnectionHandler handler =
113       new ConnectionHandlerImplementation(
114         m_httpRecording, m_logger, m_regularExpressions,
115         m_uriParser, m_attributeStringParser, null,
116         m_commentSource, m_connectionDetails);
117 
118     final RequestType request = RequestType.Factory.newInstance();
119     request.addNewHeaders();
120     request.setMethod(RequestType.Method.Enum.forString("GET"));
121 
122     final TokenType token = TokenType.Factory.newInstance();
123     token.setName("query");
124     token.setTokenId("tokenID");
125 
126     when(m_httpRecording.addRequest(m_connectionDetails,
127                                     "GET",
128                                     "/something?query=whatever"))
129       .thenReturn(request);
130 
131     final String message = "GET /something?query=whatever HTTP/1.0\r\n\r\n";
132     final byte[] buffer = message.getBytes();
133     handler.handleRequest(buffer, buffer.length);
134 
135     final String response = "HTTP/1.0 200 OK\r\n";
136     final byte[] responseBuffer = response.getBytes();
137 
138     handler.handleResponse(responseBuffer, responseBuffer.length);
139 
140     verify(m_httpRecording).markLastResponseTime();
141 
142     handler.requestFinished();
143   }
144 
145   @Test public void testAuthorization() throws Exception {
146     final ConnectionHandler handler =
147       new ConnectionHandlerImplementation(
148         m_httpRecording, m_logger, m_regularExpressions,
149         m_uriParser, m_attributeStringParser, null,
150         m_commentSource, m_connectionDetails);
151 
152     final RequestType request = RequestType.Factory.newInstance();
153     request.addNewHeaders();
154     request.setMethod(RequestType.Method.Enum.forString("GET"));
155 
156     when(m_httpRecording.addRequest(m_connectionDetails,
157                                     "GET",
158                                     "/"))
159       .thenReturn(request);
160 
161     final String message =
162       "GET / HTTP/1.1\r\n" +
163       "Authorization: Basic aBcD\r\n";
164     final byte[] buffer = message.getBytes();
165     handler.handleRequest(buffer, buffer.length);
166 
167     // Bad authorization header.
168     verify(m_logger).error(isA(String.class));
169     assertEquals(0, request.getHeaders().sizeOfAuthorizationArray());
170 
171     final String message2 =
172       "GET / HTTP/1.1\r\n" +
173       "Authorization: Basic dDpyYXVtc2NobWllcmU=\r\n";
174     final byte[] buffer2 = message2.getBytes();
175     handler.handleRequest(buffer2, buffer2.length);
176 
177     assertEquals("t", request.getHeaders().getAuthorizationArray(0).getBasic().getUserid());
178     assertEquals("raumschmiere", request.getHeaders().getAuthorizationArray(0).getBasic().getPassword());
179   }
180 
181   @Test public void testRequestWithPost() throws Exception {
182     final ConnectionHandler handler =
183       new ConnectionHandlerImplementation(
184         m_httpRecording, m_logger, m_regularExpressions,
185         m_uriParser, m_attributeStringParser, m_stringEscaper, m_commentSource,
186         m_connectionDetails);
187 
188     final RequestType request = RequestType.Factory.newInstance();
189     request.addNewHeaders();
190     request.setMethod(RequestType.Method.Enum.forString("POST"));
191 
192     when(m_httpRecording.addRequest(same(m_connectionDetails),
193                                     eq("POST"),
194                                     isA(String.class)))
195       .thenReturn(request);
196 
197 
198     final String message =
199       "POST /something HTTP/1.0\r\n" +
200       "Content-Length: 10\r\n" +
201       "Content-Type: bah\r\n" +
202       "\r\n" +
203       "0123456789";
204 
205     final byte[] buffer = message.getBytes();
206     handler.handleRequest(buffer, buffer.length);
207 
208     final String response = "HTTP/1.0 200 OK\r\n";
209     final byte[] responseBuffer = response.getBytes();
210 
211     handler.handleResponse(responseBuffer, responseBuffer.length);
212 
213     verify(m_httpRecording).markLastResponseTime();
214 
215     final String message2 =
216       "POST /more HTTP/1.0\r\n" +
217       "Content-Length: 10\r\n" +
218       "\r\n";
219 
220     final byte[] buffer2 = message2.getBytes();
221     handler.handleRequest(buffer2, buffer2.length);
222 
223     final String message3 = "0123456789";
224     final byte[] buffer3 = message3.getBytes();
225     handler.handleRequest(buffer3, buffer3.length);
226 
227     final String message4 =
228       "POST /evenmore HTTP/1.0\r\n";
229 
230     final byte[] buffer4 = message4.getBytes();
231     handler.handleRequest(buffer4, buffer4.length);
232 
233     final String message5 =
234       "Content-Length: 0\r\n" +
235       "\r\n";
236 
237     final byte[] buffer5 = message5.getBytes();
238     handler.handleRequest(buffer5, buffer5.length);
239 
240     verify(m_httpRecording, times(3)).addRequest(same(m_connectionDetails),
241                                                  eq("POST"),
242                                                  isA(String.class));
243   }
244 
245   @Test public void testResponseMessage1() throws Exception {
246     final ConnectionHandler handler =
247       new ConnectionHandlerImplementation(
248         m_httpRecording, m_logger, m_regularExpressions,
249         m_uriParser, m_attributeStringParser, null, m_commentSource, m_connectionDetails);
250 
251     final RequestType request = RequestType.Factory.newInstance();
252     request.addNewHeaders();
253     request.setMethod(RequestType.Method.Enum.forString("HEAD"));
254 
255     when (m_httpRecording.addRequest(m_connectionDetails,
256                                      "HEAD",
257                                      "/"))
258       .thenReturn(request);
259 
260     final String message = "HEAD / HTTP/1.1\r\n";
261     final byte[] buffer = message.getBytes();
262     handler.handleRequest(buffer, buffer.length);
263 
264     final String response =
265       "HTTP/1.0 302 Redirect\r\n" +
266       "Hello: world\r\n" +
267       "Location: http://somewhere/;a=b?c=d\r\n";
268 
269     final byte[] responseBuffer = response.getBytes();
270     handler.handleResponse(responseBuffer, responseBuffer.length);
271 
272     verify(m_httpRecording).markLastResponseTime();
273 
274     verify(m_httpRecording)
275       .setTokenReference(eq("a"),
276                          eq("b"),
277                          isA(ResponseTokenReferenceType.class));
278 
279     verify(m_httpRecording)
280     .setTokenReference(eq("c"),
281                        eq("d"),
282                        isA(ResponseTokenReferenceType.class));
283   }
284 
285   @Test public void testResponseMessage2() throws Exception {
286     final ConnectionHandler handler =
287       new ConnectionHandlerImplementation(
288         m_httpRecording, m_logger, m_regularExpressions,
289         m_uriParser, m_attributeStringParser, null,
290         m_commentSource, m_connectionDetails);
291 
292     final RequestType request = RequestType.Factory.newInstance();
293     request.addNewHeaders();
294     request.setMethod(RequestType.Method.Enum.forString("GET"));
295 
296     when(m_httpRecording.addRequest(m_connectionDetails,
297                                     "GET",
298                                     "/"))
299       .thenReturn(request);
300 
301     final String message = "GET / HTTP/1.1\r\n";
302     final byte[] buffer = message.getBytes();
303     handler.handleRequest(buffer, buffer.length);
304 
305     final String response =
306       "HTTP/1.0 200 OK\r\n" +
307       "Content-Length:10\r\n\r\n" +
308       "0123456789";
309 
310     final byte[] responseBuffer = response.getBytes();
311     handler.handleResponse(responseBuffer, responseBuffer.length);
312 
313     verify(m_httpRecording).markLastResponseTime();
314   }
315 
316   @Test public void testResponseMessage3() throws Exception {
317     final ConnectionHandler handler =
318       new ConnectionHandlerImplementation(
319         m_httpRecording, m_logger, m_regularExpressions,
320         m_uriParser, m_attributeStringParser, null, m_commentSource, m_connectionDetails);
321 
322     final RequestType request = RequestType.Factory.newInstance();
323     request.addNewHeaders();
324     request.setMethod(RequestType.Method.Enum.forString("GET"));
325 
326     when(m_httpRecording.addRequest(m_connectionDetails, "GET", "/"))
327       .thenReturn(request);
328 
329     final String message = "GET / HTTP/1.1\r\n";
330     final byte[] buffer = message.getBytes();
331     handler.handleRequest(buffer, buffer.length);
332 
333     final String response =
334       "HTTP/1.0 304 Not Modified\r\n";
335 
336     final byte[] responseBuffer = response.getBytes();
337     handler.handleResponse(responseBuffer, responseBuffer.length);
338 
339     verify(m_httpRecording).markLastResponseTime();
340   }
341 
342   @Test public void testResponseMessageWithTokensInLinks() throws Exception {
343     final ConnectionHandler handler =
344       new ConnectionHandlerImplementation(
345         m_httpRecording, m_logger, m_regularExpressions,
346         m_uriParser, m_attributeStringParser, null,
347         m_commentSource, m_connectionDetails);
348 
349     final RequestType request = RequestType.Factory.newInstance();
350     request.addNewHeaders();
351     request.setMethod(RequestType.Method.Enum.forString("GET"));
352 
353     when(m_httpRecording.addRequest(m_connectionDetails, "GET", "/"))
354     .thenReturn(request);
355     when(m_httpRecording.getLastValueForToken(isA(String.class)))
356     .thenReturn("1");
357 
358     final String message = "GET / HTTP/1.0\r\n";
359     final byte[] buffer = message.getBytes();
360     handler.handleRequest(buffer, buffer.length);
361 
362     final String response =
363       "HTTP/1.0 200 OK\r\n" +
364       "\r\n" +
365       "<html>" +
366       "<body><a href='./foo;session=57?token=1'>Hello world</a>" +
367       "<a href=\"http://grinder.sourceforge.net/?token=1\">something else</a>";
368 
369     final byte[] responseBuffer = response.getBytes();
370     handler.handleResponse(responseBuffer, responseBuffer.length);
371 
372     verify(m_httpRecording).markLastResponseTime();
373 
374     final String response2 =
375       "<a href=\"http://grinder.sourceforge.net/?token=2\">something else</a>" +
376       "</body>";
377     final byte[] responseBuffer2 = response2.getBytes();
378     handler.handleResponse(responseBuffer2, responseBuffer2.length);
379 
380     // Response is not flushed until the request is over.
381     handler.requestFinished();
382 
383     verify(m_httpRecording)
384       .setTokenReference(eq("session"),
385                          eq("57"),
386                          m_responseTokenRTCaptor.capture());
387 
388     assertEquals(TokenResponseLocationType.RESPONSE_BODY_URI_PATH_PARAMETER.toString(),
389                  m_responseTokenRTCaptor.getValue().getSource());
390 
391     verify(m_httpRecording)
392       .setTokenReference(eq("token"),
393                          eq("1"),
394                          m_responseTokenRTCaptor.capture());
395 
396     final ResponseTokenReferenceType responseTokenReference2 =
397       m_responseTokenRTCaptor.getValue();
398     assertEquals(1, responseTokenReference2.getConflictingValueArray().length);
399     assertEquals("2", responseTokenReference2.getConflictingValueArray()[0].getValue());
400     assertEquals(TokenResponseLocationType.RESPONSE_BODY_URI_QUERY_STRING.toString(),
401                  responseTokenReference2.getConflictingValueArray()[0].getSource());
402 
403     verify(m_httpRecording, times(2)).getLastValueForToken(isA(String.class));
404   }
405 
406   @Test public void testResponseMessageWithTokensInHiddenParameters() throws Exception {
407     final ConnectionHandler handler =
408       new ConnectionHandlerImplementation(
409         m_httpRecording, m_logger, m_regularExpressions,
410         m_uriParser, m_attributeStringParser, null, m_commentSource, m_connectionDetails);
411 
412     final RequestType request = RequestType.Factory.newInstance();
413     request.addNewHeaders();
414     request.setMethod(RequestType.Method.Enum.forString("GET"));
415 
416     when(m_httpRecording.addRequest(m_connectionDetails, "GET", "/"))
417     .thenReturn(request);
418 
419     final String message = "GET / HTTP/1.0\r\n";
420     final byte[] buffer = message.getBytes();
421     handler.handleRequest(buffer, buffer.length);
422 
423     final String response =
424       "HTTP/1.0 200 OK\r\n" +
425       "\r\n" +
426       "<html>" +
427       "<body><form>\n\n<input name=\"foo\" value=\"123\" \n" +
428       " type=\"HIDDEN\">" +
429       "<input type='hidden' name='blah'/>" +
430       "</form>" +
431       "</body></html>";
432 
433     final byte[] responseBuffer = response.getBytes();
434     handler.handleResponse(responseBuffer, responseBuffer.length);
435 
436     verify(m_httpRecording).markLastResponseTime();
437 
438     handler.requestFinished();
439 
440     verify(m_httpRecording)
441     .setTokenReference(eq("foo"),
442                        eq("123"),
443                        m_responseTokenRTCaptor.capture());
444 
445     assertEquals(TokenResponseLocationType.RESPONSE_BODY_HIDDEN_INPUT.toString(),
446       m_responseTokenRTCaptor.getValue().getSource());
447   }
448 
449   @Test public void testRequestStringBody() throws Exception {
450     final ConnectionHandler handler =
451       new ConnectionHandlerImplementation(
452         m_httpRecording, m_logger, m_regularExpressions,
453         m_uriParser, m_attributeStringParser, m_stringEscaper,
454         m_commentSource, m_connectionDetails);
455 
456     final RequestType request = RequestType.Factory.newInstance();
457     request.addNewHeaders();
458     request.setMethod(RequestType.Method.Enum.forString("POST"));
459 
460     when(m_httpRecording.addRequest(m_connectionDetails, "POST", "/something"))
461     .thenReturn(request);
462 
463     final String message =
464       "POST /something HTTP/1.0\r\n" +
465       "Content-Length: 9\r\n" +
466       "Content-Type: bah\r\n" +
467       "\r\n" +
468       "0123456789";
469 
470     final byte[] buffer = message.getBytes();
471     handler.handleRequest(buffer, buffer.length);
472 
473     verify(m_logger).error(contains("content length exceeded"));
474 
475     handler.requestFinished(); // Force body to be flushed.
476 
477     assertEquals("bah", request.getBody().getContentType());
478     assertEquals("012345678", request.getBody().getEscapedString());
479     assertFalse(request.getBody().isSetBinary());
480     assertFalse(request.getBody().isSetFile());
481     assertFalse(request.getBody().isSetForm());
482   }
483 
484   @Test public void testRequestWithUserComment() throws Exception {
485     ConnectionHandler handler = new ConnectionHandlerImplementation(
486         m_httpRecording, m_logger, m_regularExpressions,
487         m_uriParser, m_attributeStringParser, null,
488         m_commentSource, m_connectionDetails);
489 
490     //Add some user comments to the comment source. They should be added to the
491     //resulting request.
492     ((CommentSourceImplementation)m_commentSource).addComment("user comment 1");
493     ((CommentSourceImplementation)m_commentSource).addComment("user comment 2");
494 
495     final RequestType request = RequestType.Factory.newInstance();
496 
497     request.addNewHeaders();
498     request.setMethod(RequestType.Method.Enum.forString("GET"));
499     request.setCommentArray(new String[]{"user comment 1", "user comment 2"});
500 
501     final TokenType token = TokenType.Factory.newInstance();
502     token.setName("query");
503     token.setTokenId("tokenID");
504 
505     when(m_httpRecording.addRequest(m_connectionDetails,
506                                     "GET",
507                                     "/something?query=whatever"))
508     .thenReturn(request);
509 
510     final String message = "GET /something?query=whatever HTTP/1.0\r\n\r\n";
511     final byte[] buffer = message.getBytes();
512     handler.handleRequest(buffer, buffer.length);
513 
514     verify(m_httpRecording).addRequest(m_connectionDetails,
515                                        "GET",
516                                       "/something?query=whatever");
517     verifyNoMoreInteractions(m_httpRecording);
518   }
519 
520   @Test public void testRequestBinaryBody() throws Exception {
521     final ConnectionHandler handler =
522       new ConnectionHandlerImplementation(
523         m_httpRecording, m_logger, m_regularExpressions,
524         m_uriParser, m_attributeStringParser, null,
525         m_commentSource, m_connectionDetails);
526 
527     final RequestType request = RequestType.Factory.newInstance();
528     request.addNewHeaders();
529     request.setMethod(RequestType.Method.Enum.forString("POST"));
530 
531     when(m_httpRecording.addRequest(m_connectionDetails,
532                                     "POST",
533                                     "/something"))
534     .thenReturn(request);
535 
536 
537     final String message =
538       "POST /something HTTP/1.0\r\n" +
539       "Content-Length: 150\r\n" +
540       "Content-Type: bah\r\n" +
541       "\r\n";
542 
543     final byte[] buffer = message.getBytes();
544 
545     final byte[] buffer2 = new byte[50];
546 
547     for (int i = 0; i < buffer2.length; i++) {
548       buffer2[i] = (byte) i;
549     }
550 
551     // Deliberately use an oversize buffer to check handleRequest trims
552     // correctly.
553     final byte[] buffer3 = new byte[buffer.length + buffer2.length + 100];
554     System.arraycopy(buffer, 0, buffer3, 0, buffer.length);
555     System.arraycopy(buffer2, 0, buffer3, buffer.length, buffer2.length);
556 
557     handler.handleRequest(buffer3, buffer.length + buffer2.length);
558 
559     handler.handleRequest(buffer2, 50);
560     handler.handleRequest(buffer2, 50);
561 
562     handler.requestFinished(); // Force body to be flushed.
563 
564     assertEquals("bah", request.getBody().getContentType());
565     assertEquals(150, request.getBody().getBinary().length);
566     assertFalse(request.getBody().isSetEscapedString());
567     assertFalse(request.getBody().isSetFile());
568     assertFalse(request.getBody().isSetForm());
569   }
570 
571   @Test public void testRequestFormBody() throws Exception {
572     final ConnectionHandler handler =
573       new ConnectionHandlerImplementation(
574         m_httpRecording, m_logger, m_regularExpressions,
575         m_uriParser, m_attributeStringParser, null,
576         m_commentSource, m_connectionDetails);
577 
578     final RequestType request = RequestType.Factory.newInstance();
579     request.addNewHeaders();
580     request.setMethod(RequestType.Method.Enum.forString("POST"));
581 
582     when(m_httpRecording.addRequest(m_connectionDetails,
583                                     "POST",
584                                     "/something"))
585     .thenReturn(request);
586 
587     when(m_httpRecording.tokenReferenceExists(isA(String.class),
588                                               isA(String.class)))
589     .thenReturn(Boolean.FALSE);
590 
591     final String message =
592       "POST /something HTTP/1.0\r\n" +
593       "Content-Type: application/x-www-form-urlencoded; blah\r\n" +
594       "\r\n" +
595       "red=5&blue=10";
596 
597     final byte[] buffer = message.getBytes();
598     handler.handleRequest(buffer, buffer.length);
599 
600     handler.requestFinished(); // Force body to be flushed.
601 
602     final FormFieldType[] formFieldArray =
603       request.getBody().getForm().getFormFieldArray();
604     assertEquals(2, formFieldArray.length);
605     assertEquals("red", formFieldArray[0].getName());
606     assertEquals("10", formFieldArray[1].getValue());
607     assertFalse(request.getBody().getForm().getMultipart());
608     assertFalse(request.getBody().isSetBinary());
609     assertFalse(request.getBody().isSetEscapedString());
610     assertFalse(request.getBody().isSetFile());
611     assertEquals(0, request.getBody().getForm().getTokenReferenceArray().length);
612 
613     final RequestType request2 = RequestType.Factory.newInstance();
614     request2.addNewHeaders();
615     request2.setMethod(RequestType.Method.Enum.forString("POST"));
616 
617     when(m_httpRecording.addRequest(m_connectionDetails,
618                                     "POST",
619                                     "/something"))
620     .thenReturn(request2);
621     when(m_httpRecording.tokenReferenceExists(isA(String.class),
622                                               isA(String.class)))
623     .thenReturn(Boolean.TRUE);
624 
625     handler.handleRequest(buffer, buffer.length);
626 
627     handler.requestFinished(); // Force body to be flushed.
628 
629     assertEquals(0, request2.getBody().getForm().getFormFieldArray().length);
630     assertFalse(request2.getBody().getForm().getMultipart());
631     assertFalse(request2.getBody().isSetBinary());
632     assertFalse(request2.getBody().isSetEscapedString());
633     assertFalse(request2.getBody().isSetFile());
634     final TokenReferenceType[] tokenReferenceArray =
635       request2.getBody().getForm().getTokenReferenceArray();
636     assertEquals(2, tokenReferenceArray.length);
637     assertFalse(tokenReferenceArray[0].isSetSource());
638     assertFalse(tokenReferenceArray[0].isSetNewValue());
639   }
640 
641   @Test public void testRequestMultipartFormBody() throws Exception {
642     final ConnectionHandler handler =
643       new ConnectionHandlerImplementation(
644             m_httpRecording,
645             m_logger,
646             m_regularExpressions,
647             m_uriParser,
648             m_attributeStringParser, null,
649             m_commentSource,
650             m_connectionDetails);
651 
652     final RequestType request = RequestType.Factory.newInstance();
653     request.addNewHeaders();
654     request.setMethod(RequestType.Method.Enum.forString("POST"));
655 
656     when(m_httpRecording.addRequest(m_connectionDetails,
657                                     "POST",
658                                     "/something"))
659     .thenReturn(request);
660     when(m_httpRecording.tokenReferenceExists(isA(String.class),
661                                               isA(String.class)))
662     .thenReturn(Boolean.FALSE);
663 
664 
665     final String message =
666         "POST /something HTTP/1.0\r\n" +
667         "Content-Type: multipart/form-data; charset=UTF-8; boundary=---------------------------6549821653387387991112192755\r\n" +
668         "\r\n" +
669         "-----------------------------6549821653387387991112192755\r\n" +
670         "Content-Disposition: form-data; name=\"csrf_ticket\"\r\n" +
671         "\r\n" +
672         "XXXXXXXXXXXXXXXXXXXX\r\n" +
673         "-----------------------------6549821653387387991112192755\r\n" +
674         "Content-Disposition: form-data; name=\"title\"\r\n" +
675         "\r\n" +
676         "test\r\n" +
677         "-----------------------------6549821653387387991112192755\r\n" +
678         "Content-Disposition: form-data; name=\"enable_term\"\r\n" +
679         "\r\n" +
680         "0\r\n" +
681         "-----------------------------6549821653387387991112192755\r\n" +
682         "Content-Disposition: form-data; name=\"sterm_year\"\r\n" +
683         "\r\n" +
684         "2009\r\n" +
685         "-----------------------------6549821653387387991112192755\r\n" +
686         "Content-Disposition: form-data; name=\"sterm_month\"\r\n" +
687         "\r\n" +
688         "12\r\n" +
689         "-----------------------------6549821653387387991112192755\r\n" +
690         "Content-Disposition: form-data; name=\"sterm_day\"\r\n" +
691         "\r\n" +
692         "3\r\n" +
693         "-----------------------------6549821653387387991112192755\r\n" +
694         "Content-Disposition: form-data; name=\"sterm_hour\"\r\n" +
695         "\r\n" +
696         "\r\n" +
697         "-----------------------------6549821653387387991112192755\r\n" +
698         "Content-Disposition: form-data; name=\"sterm_minute\"\r\n" +
699         "\r\n" +
700         "\r\n" +
701         "-----------------------------6549821653387387991112192755\r\n" +
702         "Content-Disposition: form-data; name=\"eterm_year\"\r\n" +
703         "\r\n" +
704         "2009\r\n" +
705         "-----------------------------6549821653387387991112192755\r\n" +
706         "Content-Disposition: form-data; name=\"eterm_month\"\r\n" +
707         "\r\n" +
708         "12\r\n" +
709         "-----------------------------6549821653387387991112192755\r\n" +
710         "Content-Disposition: form-data; name=\"eterm_day\"\r\n" +
711         "\r\n" +
712         "3\r\n" +
713         "-----------------------------6549821653387387991112192755\r\n" +
714         "Content-Disposition: form-data; name=\"eterm_hour\"\r\n" +
715         "\r\n" +
716         "\r\n" +
717         "-----------------------------6549821653387387991112192755\r\n" +
718         "Content-Disposition: form-data; name=\"eterm_minute\"\r\n" +
719         "\r\n" +
720         "\r\n" +
721         "-----------------------------6549821653387387991112192755\r\n" +
722         "Content-Disposition: form-data; name=\"sterm_\"\r\n" +
723         "\r\n" +
724         "\r\n" +
725         "-----------------------------6549821653387387991112192755\r\n" +
726         "Content-Disposition: form-data; name=\"eterm_\"\r\n" +
727         "\r\n" +
728         "\r\n" +
729         "-----------------------------6549821653387387991112192755\r\n" +
730         "Content-Disposition: form-data; name=\"published\"\r\n" +
731         "\r\n" +
732         "\r\n" +
733         "-----------------------------6549821653387387991112192755\r\n" +
734         "Content-Disposition: form-data; name=\"editor\"\r\n" +
735         "\r\n" +
736         "0\r\n" +
737         "-----------------------------6549821653387387991112192755\r\n" +
738         "Content-Disposition: form-data; name=\"data\"\r\n" +
739         "\r\n" +
740         "testtesttesttesttesttesttesttesttesttesttest\r\n" +
741         "-----------------------------6549821653387387991112192755\r\n" +
742         "Content-Disposition: form-data; name=\"file0\"; filename=\"\"\r\n" +
743         "Content-Type: application/octet-stream\r\n" +
744         "\r\n" +
745         "\r\n" +
746         "-----------------------------6549821653387387991112192755\r\n" +
747         "Content-Disposition: form-data; name=\"can_follow\"\r\n" +
748         "\r\n" +
749         "1\r\n" +
750         "-----------------------------6549821653387387991112192755\r\n" +
751         "Content-Disposition: form-data; name=\"send\"\r\n" +
752         "\r\n" +
753         "AAA\r\n" +
754         "-----------------------------6549821653387387991112192755\r\n" +
755         "Content-Disposition: form-data; name=\"cid\"\r\n" +
756         "\r\n" +
757         "2\r\n" +
758         "-----------------------------6549821653387387991112192755\r\n" +
759         "Content-Disposition: form-data; name=\"aid\"\r\n" +
760         "\r\n" +
761         "\r\n" +
762         "-----------------------------6549821653387387991112192755--\r\n";
763 
764     final byte[] buffer = message.getBytes("UTF-8");
765     handler.handleRequest(buffer, buffer.length);
766 
767     handler.requestFinished(); // Force body to be flushed.
768 
769     final FormFieldType[] formFieldArray =
770       request.getBody().getForm().getFormFieldArray();
771     assertEquals(23, formFieldArray.length);
772     assertEquals("csrf_ticket", formFieldArray[0].getName());
773     assertEquals("XXXXXXXXXXXXXXXXXXXX", formFieldArray[0].getValue());
774     assertEquals("title", formFieldArray[1].getName());
775     assertEquals("test", formFieldArray[1].getValue());
776     assertEquals("enable_term", formFieldArray[2].getName());
777     assertEquals("0", formFieldArray[2].getValue());
778     assertEquals("sterm_year", formFieldArray[3].getName());
779     assertEquals("2009", formFieldArray[3].getValue());
780     assertEquals("sterm_month", formFieldArray[4].getName());
781     assertEquals("12", formFieldArray[4].getValue());
782     assertEquals("sterm_day", formFieldArray[5].getName());
783     assertEquals("3", formFieldArray[5].getValue());
784     assertEquals("sterm_hour", formFieldArray[6].getName());
785     assertEquals("", formFieldArray[6].getValue());
786     assertEquals("sterm_minute", formFieldArray[7].getName());
787     assertEquals("", formFieldArray[7].getValue());
788     assertEquals("eterm_year", formFieldArray[8].getName());
789     assertEquals("2009", formFieldArray[8].getValue());
790     assertEquals("eterm_month", formFieldArray[9].getName());
791     assertEquals("12", formFieldArray[9].getValue());
792     assertEquals("eterm_day", formFieldArray[10].getName());
793     assertEquals("3", formFieldArray[10].getValue());
794     assertEquals("eterm_hour", formFieldArray[11].getName());
795     assertEquals("", formFieldArray[11].getValue());
796     assertEquals("eterm_minute", formFieldArray[12].getName());
797     assertEquals("", formFieldArray[12].getValue());
798     assertEquals("sterm_", formFieldArray[13].getName());
799     assertEquals("", formFieldArray[13].getValue());
800     assertEquals("eterm_", formFieldArray[14].getName());
801     assertEquals("", formFieldArray[14].getValue());
802     assertEquals("published", formFieldArray[15].getName());
803     assertEquals("", formFieldArray[15].getValue());
804     assertEquals("editor", formFieldArray[16].getName());
805     assertEquals("0", formFieldArray[16].getValue());
806     assertEquals("data", formFieldArray[17].getName());
807     assertEquals("testtesttesttesttesttesttesttesttesttesttest", formFieldArray[17].getValue());
808     assertEquals("file0", formFieldArray[18].getName());
809     assertEquals("", formFieldArray[18].getValue());
810     assertEquals("can_follow", formFieldArray[19].getName());
811     assertEquals("1", formFieldArray[19].getValue());
812     assertEquals("send", formFieldArray[20].getName());
813     assertEquals("AAA", formFieldArray[20].getValue());
814     assertEquals("cid", formFieldArray[21].getName());
815     assertEquals("2", formFieldArray[21].getValue());
816     assertEquals("aid", formFieldArray[22].getName());
817     assertEquals("", formFieldArray[22].getValue());
818     assertEquals(23, formFieldArray.length);
819     assertTrue(request.getBody().getForm().getMultipart());
820     assertFalse(request.getBody().isSetBinary());
821     assertFalse(request.getBody().isSetEscapedString());
822     assertFalse(request.getBody().isSetFile());
823     assertEquals(0, request.getBody().getForm().getTokenReferenceArray().length);
824   }
825 
826   @Test public void testRequestFileBody() throws Exception {
827     final ConnectionHandler handler =
828       new ConnectionHandlerImplementation(
829         m_httpRecording, m_logger, m_regularExpressions,
830         m_uriParser, m_attributeStringParser, null,
831         m_commentSource, m_connectionDetails);
832 
833     final RequestType request = RequestType.Factory.newInstance();
834     request.addNewHeaders();
835     request.setMethod(RequestType.Method.Enum.forString("POST"));
836 
837     final File file = new File(getDirectory(), "formData");
838 
839 
840     when(m_httpRecording.addRequest(m_connectionDetails,
841                                     "POST",
842                                     "/something"))
843     .thenReturn(request);
844 
845     when(m_httpRecording.createBodyDataFileName()).thenReturn(file);
846 
847     final String message =
848       "POST /something HTTP/1.0\r\n" +
849       "\r\n";
850 
851     final byte[] buffer = message.getBytes();
852     handler.handleRequest(buffer, buffer.length);
853 
854     final byte[] buffer2 = new byte[0x8000];
855 
856     for (int i = 0; i < buffer2.length; i++) {
857       buffer2[i] = (byte) i;
858     }
859 
860     handler.handleRequest(buffer2, buffer2.length);
861 
862     handler.requestFinished(); // Force body to be flushed.
863 
864     assertEquals(file.getPath(), request.getBody().getFile());
865     assertTrue(file.exists());
866     assertTrue(file.canRead());
867     assertEquals(0x8000, file.length());
868     assertFalse(request.getBody().isSetBinary());
869     assertFalse(request.getBody().isSetForm());
870     assertFalse(request.getBody().isSetEscapedString());
871   }
872 
873   @Test public void testRequestFileBody2() throws Exception {
874     final ConnectionHandler handler =
875       new ConnectionHandlerImplementation(
876         m_httpRecording, m_logger, m_regularExpressions,
877         m_uriParser, m_attributeStringParser, null,
878         m_commentSource, m_connectionDetails);
879 
880     final RequestType request = RequestType.Factory.newInstance();
881     request.addNewHeaders();
882     request.setMethod(RequestType.Method.Enum.forString("POST"));
883 
884     final File file = new File(getDirectory(), "formData");
885     file.createNewFile();
886     FileUtilities.setCanAccess(file, false);
887 
888     when(m_httpRecording.addRequest(m_connectionDetails,
889                                     "POST",
890                                     "/something"))
891     .thenReturn(request);
892 
893     when(m_httpRecording.createBodyDataFileName()).thenReturn(file);
894 
895     final String message =
896       "POST /something HTTP/1.0\r\n" +
897       "\r\n";
898 
899     final byte[] buffer = message.getBytes();
900     handler.handleRequest(buffer, buffer.length);
901 
902     final byte[] buffer2 = new byte[0x8000];
903 
904     for (int i = 0; i < buffer2.length; i++) {
905       buffer2[i] = (byte) i;
906     }
907 
908     handler.handleRequest(buffer2, buffer2.length);
909 
910     handler.requestFinished(); // Force body to be flushed.
911 
912     assertFalse(request.getBody().isSetFile());
913     assertFalse(request.getBody().isSetBinary());
914     assertFalse(request.getBody().isSetForm());
915     assertFalse(request.getBody().isSetEscapedString());
916 
917     verify(m_logger).error(contains("Failed to write body"),
918                            isA(FileNotFoundException.class));
919   }
920 
921   @Test public void testPartitionedRequest() throws Exception {
922     final ConnectionHandler handler =
923       new ConnectionHandlerImplementation(
924         m_httpRecording, m_logger, m_regularExpressions,
925         m_uriParser, m_attributeStringParser, null,
926         m_commentSource, m_connectionDetails);
927 
928     final byte[] message1Bytes = new String("G").getBytes("US-ASCII");
929     handler.handleRequest(message1Bytes, message1Bytes.length);
930 
931     final byte[] message2Bytes =
932         new String("ET blah HTTP/1.1\n").getBytes("US-ASCII");
933 
934     final RequestType request = RequestType.Factory.newInstance();
935     request.addNewHeaders();
936     request.setMethod(RequestType.Method.Enum.forString("GET"));
937 
938     when(m_httpRecording.addRequest(m_connectionDetails,
939                                     "GET",
940                                     "blah"))
941       .thenReturn(request);
942 
943     handler.handleRequest(message2Bytes, message2Bytes.length);
944 
945     verify(m_httpRecording).addRequest(m_connectionDetails, "GET", "blah");
946   }
947 
948   @Test public void testOverflowBuffer() {
949     final ConnectionHandler handler =
950         new ConnectionHandlerImplementation(
951           m_httpRecording, m_logger, m_regularExpressions,
952           m_uriParser, m_attributeStringParser, null,
953           m_commentSource, m_connectionDetails);
954 
955     final byte[] buffer = new byte[0x10000];
956 
957     for (int i = 0; i < buffer.length; i++) {
958       buffer[i] = (byte) i;
959     }
960 
961     handler.handleRequest(buffer, buffer.length);
962 
963     verify(m_logger).error(contains("Filled buffer without matching"),
964                            isA(BufferOverflowException.class));
965 
966     // Buffer has been cleared, so we can send more stuff.
967     handler.handleRequest(buffer, 10);
968   }
969 
970   @Test public void testWithBadResponseMessages() throws Exception {
971     final ConnectionHandler handler =
972       new ConnectionHandlerImplementation(
973         m_httpRecording, m_logger, m_regularExpressions,
974         m_uriParser, m_attributeStringParser, null,
975         m_commentSource, m_connectionDetails);
976 
977     // Response with no request.
978     handler.handleResponse(new byte[0], 0);
979     verify(m_logger).error(contains("No current request"));
980 
981     final RequestType request = RequestType.Factory.newInstance();
982     request.addNewHeaders();
983     request.setMethod(RequestType.Method.Enum.forString("GET"));
984 
985     when(m_httpRecording.addRequest(m_connectionDetails,
986                                     "GET",
987                                     "/"))
988     .thenReturn(request);
989 
990     final String message = "GET / HTTP/1.1\r\n";
991     final byte[] buffer = message.getBytes();
992     handler.handleRequest(buffer, buffer.length);
993 
994     // Responses that don't start with a standard response line are logged and
995     // ignored.
996     handler.handleResponse(new byte[0], 0);
997 
998     verify(m_logger).error(contains("No current response"));
999   }
1000 }