1/*
2 * libjingle
3 * Copyright 2004--2011, Google Inc.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 *  1. Redistributions of source code must retain the above copyright notice,
9 *     this list of conditions and the following disclaimer.
10 *  2. Redistributions in binary form must reproduce the above copyright notice,
11 *     this list of conditions and the following disclaimer in the documentation
12 *     and/or other materials provided with the distribution.
13 *  3. The name of the author may not be used to endorse or promote products
14 *     derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28#include "talk/base/gunit.h"
29#include "talk/base/httpbase.h"
30#include "talk/base/testutils.h"
31
32namespace talk_base {
33
34const char* const kHttpResponse =
35  "HTTP/1.1 200\r\n"
36  "Connection: Keep-Alive\r\n"
37  "Content-Type: text/plain\r\n"
38  "Proxy-Authorization: 42\r\n"
39  "Transfer-Encoding: chunked\r\n"
40  "\r\n"
41  "00000008\r\n"
42  "Goodbye!\r\n"
43  "0\r\n\r\n";
44
45const char* const kHttpEmptyResponse =
46  "HTTP/1.1 200\r\n"
47  "Connection: Keep-Alive\r\n"
48  "Content-Length: 0\r\n"
49  "Proxy-Authorization: 42\r\n"
50  "\r\n";
51
52const char* const kHttpResponsePrefix =
53  "HTTP/1.1 200\r\n"
54  "Connection: Keep-Alive\r\n"
55  "Content-Type: text/plain\r\n"
56  "Proxy-Authorization: 42\r\n"
57  "Transfer-Encoding: chunked\r\n"
58  "\r\n"
59  "8\r\n"
60  "Goodbye!\r\n";
61
62class HttpBaseTest : public testing::Test, public IHttpNotify {
63public:
64  enum EventType { E_HEADER_COMPLETE, E_COMPLETE, E_CLOSED };
65  struct Event {
66    EventType event;
67    bool chunked;
68    size_t data_size;
69    HttpMode mode;
70    HttpError err;
71  };
72  HttpBaseTest() : mem(NULL), obtain_stream(false), http_stream(NULL) { }
73
74  virtual void SetUp() { }
75  virtual void TearDown() {
76    delete http_stream;
77    // Avoid an ASSERT, in case a test doesn't clean up properly
78    base.abort(HE_NONE);
79  }
80
81  virtual HttpError onHttpHeaderComplete(bool chunked, size_t& data_size) {
82    LOG_F(LS_VERBOSE) << "chunked: " << chunked << " size: " << data_size;
83    Event e = { E_HEADER_COMPLETE, chunked, data_size, HM_NONE, HE_NONE};
84    events.push_back(e);
85    if (obtain_stream) {
86      ObtainDocumentStream();
87    }
88    return HE_NONE;
89  }
90  virtual void onHttpComplete(HttpMode mode, HttpError err) {
91    LOG_F(LS_VERBOSE) << "mode: " << mode << " err: " << err;
92    Event e = { E_COMPLETE, false, 0, mode, err };
93    events.push_back(e);
94  }
95  virtual void onHttpClosed(HttpError err) {
96    LOG_F(LS_VERBOSE) << "err: " << err;
97    Event e = { E_CLOSED, false, 0, HM_NONE, err };
98    events.push_back(e);
99  }
100
101  void SetupSource(const char* response);
102
103  void VerifyHeaderComplete(size_t event_count, bool empty_doc);
104  void VerifyDocumentContents(const char* expected_data,
105                              size_t expected_length = SIZE_UNKNOWN);
106
107  void ObtainDocumentStream();
108  void VerifyDocumentStreamIsOpening();
109  void VerifyDocumentStreamOpenEvent();
110  void ReadDocumentStreamData(const char* expected_data);
111  void VerifyDocumentStreamIsEOS();
112
113  void SetupDocument(const char* response);
114  void VerifySourceContents(const char* expected_data,
115                            size_t expected_length = SIZE_UNKNOWN);
116
117  void VerifyTransferComplete(HttpMode mode, HttpError error);
118
119  HttpBase base;
120  MemoryStream* mem;
121  HttpResponseData data;
122
123  // The source of http data, and source events
124  testing::StreamSource src;
125  std::vector<Event> events;
126
127  // Document stream, and stream events
128  bool obtain_stream;
129  StreamInterface* http_stream;
130  testing::StreamSink sink;
131};
132
133void HttpBaseTest::SetupSource(const char* http_data) {
134  LOG_F(LS_VERBOSE) << "Enter";
135
136  src.SetState(SS_OPENING);
137  src.QueueString(http_data);
138
139  base.notify(this);
140  base.attach(&src);
141  EXPECT_TRUE(events.empty());
142
143  src.SetState(SS_OPEN);
144  ASSERT_EQ(1U, events.size());
145  EXPECT_EQ(E_COMPLETE, events[0].event);
146  EXPECT_EQ(HM_CONNECT, events[0].mode);
147  EXPECT_EQ(HE_NONE, events[0].err);
148  events.clear();
149
150  mem = new MemoryStream;
151  data.document.reset(mem);
152  LOG_F(LS_VERBOSE) << "Exit";
153}
154
155void HttpBaseTest::VerifyHeaderComplete(size_t event_count, bool empty_doc) {
156  LOG_F(LS_VERBOSE) << "Enter";
157
158  ASSERT_EQ(event_count, events.size());
159  EXPECT_EQ(E_HEADER_COMPLETE, events[0].event);
160
161  std::string header;
162  EXPECT_EQ(HVER_1_1, data.version);
163  EXPECT_EQ(static_cast<uint32>(HC_OK), data.scode);
164  EXPECT_TRUE(data.hasHeader(HH_PROXY_AUTHORIZATION, &header));
165  EXPECT_EQ("42", header);
166  EXPECT_TRUE(data.hasHeader(HH_CONNECTION, &header));
167  EXPECT_EQ("Keep-Alive", header);
168
169  if (empty_doc) {
170    EXPECT_FALSE(events[0].chunked);
171    EXPECT_EQ(0U, events[0].data_size);
172
173    EXPECT_TRUE(data.hasHeader(HH_CONTENT_LENGTH, &header));
174    EXPECT_EQ("0", header);
175  } else {
176    EXPECT_TRUE(events[0].chunked);
177    EXPECT_EQ(SIZE_UNKNOWN, events[0].data_size);
178
179    EXPECT_TRUE(data.hasHeader(HH_CONTENT_TYPE, &header));
180    EXPECT_EQ("text/plain", header);
181    EXPECT_TRUE(data.hasHeader(HH_TRANSFER_ENCODING, &header));
182    EXPECT_EQ("chunked", header);
183  }
184  LOG_F(LS_VERBOSE) << "Exit";
185}
186
187void HttpBaseTest::VerifyDocumentContents(const char* expected_data,
188                                          size_t expected_length) {
189  LOG_F(LS_VERBOSE) << "Enter";
190
191  if (SIZE_UNKNOWN == expected_length) {
192    expected_length = strlen(expected_data);
193  }
194  EXPECT_EQ(mem, data.document.get());
195
196  size_t length;
197  mem->GetSize(&length);
198  EXPECT_EQ(expected_length, length);
199  EXPECT_TRUE(0 == memcmp(expected_data, mem->GetBuffer(), length));
200  LOG_F(LS_VERBOSE) << "Exit";
201}
202
203void HttpBaseTest::ObtainDocumentStream() {
204  LOG_F(LS_VERBOSE) << "Enter";
205  EXPECT_FALSE(http_stream);
206  http_stream = base.GetDocumentStream();
207  ASSERT_TRUE(NULL != http_stream);
208  sink.Monitor(http_stream);
209  LOG_F(LS_VERBOSE) << "Exit";
210}
211
212void HttpBaseTest::VerifyDocumentStreamIsOpening() {
213  LOG_F(LS_VERBOSE) << "Enter";
214  ASSERT_TRUE(NULL != http_stream);
215  EXPECT_EQ(0, sink.Events(http_stream));
216  EXPECT_EQ(SS_OPENING, http_stream->GetState());
217
218  size_t read = 0;
219  char buffer[5] = { 0 };
220  EXPECT_EQ(SR_BLOCK, http_stream->Read(buffer, sizeof(buffer), &read, NULL));
221  LOG_F(LS_VERBOSE) << "Exit";
222}
223
224void HttpBaseTest::VerifyDocumentStreamOpenEvent() {
225  LOG_F(LS_VERBOSE) << "Enter";
226
227  ASSERT_TRUE(NULL != http_stream);
228  EXPECT_EQ(SE_OPEN | SE_READ, sink.Events(http_stream));
229  EXPECT_EQ(SS_OPEN, http_stream->GetState());
230
231  // HTTP headers haven't arrived yet
232  EXPECT_EQ(0U, events.size());
233  EXPECT_EQ(static_cast<uint32>(HC_INTERNAL_SERVER_ERROR), data.scode);
234  LOG_F(LS_VERBOSE) << "Exit";
235}
236
237void HttpBaseTest::ReadDocumentStreamData(const char* expected_data) {
238  LOG_F(LS_VERBOSE) << "Enter";
239
240  ASSERT_TRUE(NULL != http_stream);
241  EXPECT_EQ(SS_OPEN, http_stream->GetState());
242
243  // Pump the HTTP I/O using Read, and verify the results.
244  size_t verified_length = 0;
245  const size_t expected_length = strlen(expected_data);
246  while (verified_length < expected_length) {
247    size_t read = 0;
248    char buffer[5] = { 0 };
249    size_t amt_to_read = _min(expected_length - verified_length, sizeof(buffer));
250    EXPECT_EQ(SR_SUCCESS, http_stream->Read(buffer, amt_to_read, &read, NULL));
251    EXPECT_EQ(amt_to_read, read);
252    EXPECT_TRUE(0 == memcmp(expected_data + verified_length, buffer, read));
253    verified_length += read;
254  }
255  LOG_F(LS_VERBOSE) << "Exit";
256}
257
258void HttpBaseTest::VerifyDocumentStreamIsEOS() {
259  LOG_F(LS_VERBOSE) << "Enter";
260
261  ASSERT_TRUE(NULL != http_stream);
262  size_t read = 0;
263  char buffer[5] = { 0 };
264  EXPECT_EQ(SR_EOS, http_stream->Read(buffer, sizeof(buffer), &read, NULL));
265  EXPECT_EQ(SS_CLOSED, http_stream->GetState());
266
267  // When EOS is caused by Read, we don't expect SE_CLOSE
268  EXPECT_EQ(0, sink.Events(http_stream));
269  LOG_F(LS_VERBOSE) << "Exit";
270}
271
272void HttpBaseTest::SetupDocument(const char* document_data) {
273  LOG_F(LS_VERBOSE) << "Enter";
274  src.SetState(SS_OPEN);
275
276  base.notify(this);
277  base.attach(&src);
278  EXPECT_TRUE(events.empty());
279
280  if (document_data) {
281    // Note: we could just call data.set_success("text/plain", mem), but that
282    // won't allow us to use the chunked transfer encoding.
283    mem = new MemoryStream(document_data);
284    data.document.reset(mem);
285    data.setHeader(HH_CONTENT_TYPE, "text/plain");
286    data.setHeader(HH_TRANSFER_ENCODING, "chunked");
287  } else {
288    data.setHeader(HH_CONTENT_LENGTH, "0");
289  }
290  data.scode = HC_OK;
291  data.setHeader(HH_PROXY_AUTHORIZATION, "42");
292  data.setHeader(HH_CONNECTION, "Keep-Alive");
293  LOG_F(LS_VERBOSE) << "Exit";
294}
295
296void HttpBaseTest::VerifySourceContents(const char* expected_data,
297                                        size_t expected_length) {
298  LOG_F(LS_VERBOSE) << "Enter";
299  if (SIZE_UNKNOWN == expected_length) {
300    expected_length = strlen(expected_data);
301  }
302  std::string contents = src.ReadData();
303  EXPECT_EQ(expected_length, contents.length());
304  EXPECT_TRUE(0 == memcmp(expected_data, contents.data(), expected_length));
305  LOG_F(LS_VERBOSE) << "Exit";
306}
307
308void HttpBaseTest::VerifyTransferComplete(HttpMode mode, HttpError error) {
309  LOG_F(LS_VERBOSE) << "Enter";
310  // Verify that http operation has completed
311  ASSERT_TRUE(events.size() > 0);
312  size_t last_event = events.size() - 1;
313  EXPECT_EQ(E_COMPLETE, events[last_event].event);
314  EXPECT_EQ(mode, events[last_event].mode);
315  EXPECT_EQ(error, events[last_event].err);
316  LOG_F(LS_VERBOSE) << "Exit";
317}
318
319//
320// Tests
321//
322
323TEST_F(HttpBaseTest, SupportsSend) {
324  // Queue response document
325  SetupDocument("Goodbye!");
326
327  // Begin send
328  base.send(&data);
329
330  // Send completed successfully
331  VerifyTransferComplete(HM_SEND, HE_NONE);
332  VerifySourceContents(kHttpResponse);
333}
334
335TEST_F(HttpBaseTest, SupportsSendNoDocument) {
336  // Queue response document
337  SetupDocument(NULL);
338
339  // Begin send
340  base.send(&data);
341
342  // Send completed successfully
343  VerifyTransferComplete(HM_SEND, HE_NONE);
344  VerifySourceContents(kHttpEmptyResponse);
345}
346
347TEST_F(HttpBaseTest, SignalsCompleteOnInterruptedSend) {
348  // This test is attempting to expose a bug that occurs when a particular
349  // base objects is used for receiving, and then used for sending.  In
350  // particular, the HttpParser state is different after receiving.  Simulate
351  // that here.
352  SetupSource(kHttpResponse);
353  base.recv(&data);
354  VerifyTransferComplete(HM_RECV, HE_NONE);
355
356  src.Clear();
357  data.clear(true);
358  events.clear();
359  base.detach();
360
361  // Queue response document
362  SetupDocument("Goodbye!");
363
364  // Prevent entire response from being sent
365  const size_t kInterruptedLength = strlen(kHttpResponse) - 1;
366  src.SetWriteBlock(kInterruptedLength);
367
368  // Begin send
369  base.send(&data);
370
371  // Document is mostly complete, but no completion signal yet.
372  EXPECT_TRUE(events.empty());
373  VerifySourceContents(kHttpResponse, kInterruptedLength);
374
375  src.SetState(SS_CLOSED);
376
377  // Send completed with disconnect error, and no additional data.
378  VerifyTransferComplete(HM_SEND, HE_DISCONNECTED);
379  EXPECT_TRUE(src.ReadData().empty());
380}
381
382TEST_F(HttpBaseTest, SupportsReceiveViaDocumentPush) {
383  // Queue response document
384  SetupSource(kHttpResponse);
385
386  // Begin receive
387  base.recv(&data);
388
389  // Document completed successfully
390  VerifyHeaderComplete(2, false);
391  VerifyTransferComplete(HM_RECV, HE_NONE);
392  VerifyDocumentContents("Goodbye!");
393}
394
395TEST_F(HttpBaseTest, SupportsReceiveViaStreamPull) {
396  // Switch to pull mode
397  ObtainDocumentStream();
398  VerifyDocumentStreamIsOpening();
399
400  // Queue response document
401  SetupSource(kHttpResponse);
402  VerifyDocumentStreamIsOpening();
403
404  // Begin receive
405  base.recv(&data);
406
407  // Pull document data
408  VerifyDocumentStreamOpenEvent();
409  ReadDocumentStreamData("Goodbye!");
410  VerifyDocumentStreamIsEOS();
411
412  // Document completed successfully
413  VerifyHeaderComplete(2, false);
414  VerifyTransferComplete(HM_RECV, HE_NONE);
415  VerifyDocumentContents("");
416}
417
418TEST_F(HttpBaseTest, DISABLED_AllowsCloseStreamBeforeDocumentIsComplete) {
419
420  // TODO: Remove extra logging once test failure is understood
421  int old_sev = talk_base::LogMessage::GetLogToDebug();
422  talk_base::LogMessage::LogToDebug(LS_VERBOSE);
423
424
425  // Switch to pull mode
426  ObtainDocumentStream();
427  VerifyDocumentStreamIsOpening();
428
429  // Queue response document
430  SetupSource(kHttpResponse);
431  VerifyDocumentStreamIsOpening();
432
433  // Begin receive
434  base.recv(&data);
435
436  // Pull some of the data
437  VerifyDocumentStreamOpenEvent();
438  ReadDocumentStreamData("Goodb");
439
440  // We've seen the header by now
441  VerifyHeaderComplete(1, false);
442
443  // Close the pull stream, this will transition back to push I/O.
444  http_stream->Close();
445  Thread::Current()->ProcessMessages(0);
446
447  // Remainder of document completed successfully
448  VerifyTransferComplete(HM_RECV, HE_NONE);
449  VerifyDocumentContents("ye!");
450
451  talk_base::LogMessage::LogToDebug(old_sev);
452}
453
454TEST_F(HttpBaseTest, AllowsGetDocumentStreamInResponseToHttpHeader) {
455  // Queue response document
456  SetupSource(kHttpResponse);
457
458  // Switch to pull mode in response to header arrival
459  obtain_stream = true;
460
461  // Begin receive
462  base.recv(&data);
463
464  // We've already seen the header, but not data has arrived
465  VerifyHeaderComplete(1, false);
466  VerifyDocumentContents("");
467
468  // Pull the document data
469  ReadDocumentStreamData("Goodbye!");
470  VerifyDocumentStreamIsEOS();
471
472  // Document completed successfully
473  VerifyTransferComplete(HM_RECV, HE_NONE);
474  VerifyDocumentContents("");
475}
476
477TEST_F(HttpBaseTest, AllowsGetDocumentStreamWithEmptyDocumentBody) {
478  // Queue empty response document
479  SetupSource(kHttpEmptyResponse);
480
481  // Switch to pull mode in response to header arrival
482  obtain_stream = true;
483
484  // Begin receive
485  base.recv(&data);
486
487  // We've already seen the header, but not data has arrived
488  VerifyHeaderComplete(1, true);
489  VerifyDocumentContents("");
490
491  // The document is still open, until we attempt to read
492  ASSERT_TRUE(NULL != http_stream);
493  EXPECT_EQ(SS_OPEN, http_stream->GetState());
494
495  // Attempt to read data, and discover EOS
496  VerifyDocumentStreamIsEOS();
497
498  // Document completed successfully
499  VerifyTransferComplete(HM_RECV, HE_NONE);
500  VerifyDocumentContents("");
501}
502
503TEST_F(HttpBaseTest, SignalsDocumentStreamCloseOnUnexpectedClose) {
504  // Switch to pull mode
505  ObtainDocumentStream();
506  VerifyDocumentStreamIsOpening();
507
508  // Queue response document
509  SetupSource(kHttpResponsePrefix);
510  VerifyDocumentStreamIsOpening();
511
512  // Begin receive
513  base.recv(&data);
514
515  // Pull document data
516  VerifyDocumentStreamOpenEvent();
517  ReadDocumentStreamData("Goodbye!");
518
519  // Simulate unexpected close
520  src.SetState(SS_CLOSED);
521
522  // Observe error event on document stream
523  EXPECT_EQ(testing::SSE_ERROR, sink.Events(http_stream));
524
525  // Future reads give an error
526  int error = 0;
527  char buffer[5] = { 0 };
528  EXPECT_EQ(SR_ERROR, http_stream->Read(buffer, sizeof(buffer), NULL, &error));
529  EXPECT_EQ(HE_DISCONNECTED, error);
530
531  // Document completed with error
532  VerifyHeaderComplete(2, false);
533  VerifyTransferComplete(HM_RECV, HE_DISCONNECTED);
534  VerifyDocumentContents("");
535}
536
537} // namespace talk_base
538