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