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