Updating trunk VERSION from 2139.0 to 2140.0
[chromium-blink-merge.git] / components / data_reduction_proxy / browser / data_reduction_proxy_tamper_detection_unittest.cc
blob6c28806d86afeb4aa2ce6d5b9bd0705d2d94cacc
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "components/data_reduction_proxy/browser/data_reduction_proxy_tamper_detection.h"
7 #include <string.h>
8 #include <algorithm>
9 #include <map>
10 #include <vector>
12 #include "base/base64.h"
13 #include "base/md5.h"
14 #include "base/memory/scoped_ptr.h"
15 #include "base/strings/string_number_conversions.h"
16 #include "base/strings/string_split.h"
17 #include "components/data_reduction_proxy/common/data_reduction_proxy_headers.h"
18 #include "net/http/http_response_headers.h"
19 #include "testing/gtest/include/gtest/gtest.h"
21 #if defined(OS_ANDROID)
22 #include "base/android/jni_android.h"
23 #include "net/android/network_library.h"
24 #endif
26 namespace {
28 void HeadersToRaw(std::string* headers) {
29 std::replace(headers->begin(), headers->end(), '\n', '\0');
30 if (!headers->empty())
31 *headers += '\0';
34 // Calcuates MD5 hash value for a string and then base64 encode it. Testcases
35 // contain expected fingerprint in plain text, which needs to be encoded before
36 // comparison.
37 std::string GetEncoded(const std::string& input) {
38 base::MD5Digest digest;
39 base::MD5Sum(input.c_str(), input.size(), &digest);
40 std::string base64encoded;
41 base::Base64Encode(std::string((char*)digest.a,
42 ARRAYSIZE_UNSAFE(digest.a)), &base64encoded);
43 return base64encoded;
46 // Replaces all contents within "[]" by corresponding base64 encoded MD5 value.
47 // It can handle nested case like: [[abc]def]. This helper function transforms
48 // fingerprint in plain text to actual encoded fingerprint.
49 void ReplaceWithEncodedString(std::string* input) {
50 size_t start, end, temp;
51 while (true) {
52 start = input->find("[");
53 if (start == std::string::npos) break;
54 while (true) {
55 temp = input->find("[", start + 1);
56 end = input->find("]", start + 1);
57 if (end != std::string::npos && end < temp)
58 break;
60 start = temp;
62 std::string need_to_encode = input->substr(start + 1, end - start - 1);
63 *input = input->substr(0, start) + GetEncoded(need_to_encode) +
64 input->substr(end + 1);
68 // Returns a vector contains all the values from a comma-separated string.
69 // Some testcases contain string representation of a vector, this helper
70 // function generates a vector from a input string.
71 std::vector<std::string> StringsToVector(const std::string& values) {
72 std::vector<std::string> ret;
73 if (values.empty())
74 return ret;
75 size_t now = 0;
76 size_t next;
77 while ((next = values.find(",", now)) != std::string::npos) {
78 ret.push_back(values.substr(now, next - now));
79 now = next + 1;
81 return ret;
84 void InitEnv() {
85 #if defined(OS_ANDROID)
86 JNIEnv* env = base::android::AttachCurrentThread();
87 static bool inited = false;
88 if (!inited) {
89 net::android::RegisterNetworkLibrary(env);
90 inited = true;
92 #endif
95 } // namespace
97 namespace data_reduction_proxy {
99 class DataReductionProxyTamperDetectionTest : public testing::Test {
103 // Tests function ValidateChromeProxyHeader.
104 TEST_F(DataReductionProxyTamperDetectionTest, ChromeProxy) {
105 // |received_fingerprint| is not the actual fingerprint from data reduction
106 // proxy, instead, the base64 encoded field is in plain text (within "[]")
107 // and needs to be encoded first.
108 struct {
109 std::string label;
110 std::string raw_header;
111 std::string received_fingerprint;
112 bool expected_tampered_with;
113 } test[] = {
115 "Checks sorting.",
116 "HTTP/1.1 200 OK\n"
117 "Chrome-Proxy: c,b,a,3,2,1,fcp=f\n",
118 "[1,2,3,a,b,c,]",
119 false,
122 "Checks Chrome-Proxy's fingerprint removing.",
123 "HTTP/1.1 200 OK\n"
124 "Chrome-Proxy: a,b,c,d,e,3,2,1,fcp=f\n",
125 "[1,2,3,a,b,c,d,e,]",
126 false,
129 "Checks no Chrome-Proxy header case (should not happen).",
130 "HTTP/1.1 200 OK\n",
131 "[]",
132 false,
135 "Checks empty Chrome-Proxy header case (should not happen).",
136 "HTTP/1.1 200 OK\n"
137 "Chrome-Proxy: \n",
138 "[,]",
139 false,
142 "Checks Chrome-Proxy header with its fingerprint only case.",
143 "HTTP/1.1 200 OK\n"
144 "Chrome-Proxy: fcp=f\n",
145 "[]",
146 false,
149 "Checks empty Chrome-Proxy header case, with extra ',' and ' '",
150 "HTTP/1.1 200 OK\n"
151 "Chrome-Proxy: fcp=f , \n",
152 "[]",
153 false,
156 "Changed no value to empty value.",
157 "HTTP/1.1 200 OK\n"
158 "Chrome-Proxy: fcp=f\n",
159 "[,]",
160 true,
163 "Changed header values.",
164 "HTTP/1.1 200 OK\n"
165 "Chrome-Proxy: a,b=2,c,d=1,fcp=f\n",
166 "[a,b=3,c,d=1,]",
167 true,
170 "Changed order of header values.",
171 "HTTP/1.1 200 OK\n"
172 "Chrome-Proxy: c,b,a,fcp=1\n",
173 "[c,b,a,]",
174 true,
177 "Checks Chrome-Proxy header with extra ' '.",
178 "HTTP/1.1 200 OK\n"
179 "Chrome-Proxy: a , b , c, d, fcp=f\n",
180 "[a,b,c,d,]",
181 false
184 "Check Chrome-Proxy header with multiple lines and ' '.",
185 "HTTP/1.1 200 OK\n"
186 "Chrome-Proxy: a , c , d, fcp=f \n"
187 "Chrome-Proxy: b \n",
188 "[a,b,c,d,]",
189 false
192 "Checks Chrome-Proxy header with multiple lines, at different positions",
193 "HTTP/1.1 200 OK\n"
194 "Chrome-Proxy: a \n"
195 "Chrome-Proxy: c \n"
196 "Content-Type: 1\n"
197 "Cache-Control: 2\n"
198 "ETag: 3\n"
199 "Chrome-Proxy: b \n"
200 "Connection: 4\n"
201 "Expires: 5\n"
202 "Chrome-Proxy: fcp=f \n"
203 "Via: \n"
204 "Content-Length: 12345\n",
205 "[a,b,c,]",
206 false
209 "Checks Chrome-Proxy header with multiple same values.",
210 "HTTP/1.1 200 OK\n"
211 "Chrome-Proxy: a \n"
212 "Chrome-Proxy: b\n"
213 "Chrome-Proxy: c\n"
214 "Chrome-Proxy: d, fcp=f \n"
215 "Chrome-Proxy: a \n",
216 "[a,a,b,c,d,]",
217 false
220 "Changed Chrome-Proxy header with multiple lines..",
221 "HTTP/1.1 200 OK\n"
222 "Chrome-Proxy: a\n"
223 "Chrome-Proxy: a\n"
224 "Chrome-Proxy: b\n"
225 "Chrome-Proxy: c,fcp=f\n",
226 "[a,b,c,]",
227 true,
230 "Checks case whose received fingerprint is empty.",
231 "HTTP/1.1 200 OK\n"
232 "Chrome-Proxy: a,b,c,fcp=1\n",
233 "[]",
234 true,
237 "Checks case whose received fingerprint cannot be base64 decoded.",
238 "HTTP/1.1 200 OK\n"
239 "Chrome-Proxy: a,b,c,fcp=1\n",
240 "not_base64_encoded",
241 true,
245 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test); ++i) {
246 ReplaceWithEncodedString(&test[i].received_fingerprint);
248 std::string raw_headers(test[i].raw_header);
249 HeadersToRaw(&raw_headers);
250 scoped_refptr<net::HttpResponseHeaders> headers(
251 new net::HttpResponseHeaders(raw_headers));
253 DataReductionProxyTamperDetection tamper_detection(headers.get(), true, 0);
255 bool tampered = tamper_detection.ValidateChromeProxyHeader(
256 test[i].received_fingerprint);
258 EXPECT_EQ(test[i].expected_tampered_with, tampered) << test[i].label;
262 // Tests function ValidateViaHeader.
263 TEST_F(DataReductionProxyTamperDetectionTest, Via) {
264 struct {
265 std::string label;
266 std::string raw_header;
267 std::string received_fingerprint;
268 bool expected_tampered_with;
269 bool expected_has_chrome_proxy_via_header;
270 } test[] = {
272 "Checks the case that Chrome-Compression-Proxy occurs at the last.",
273 "HTTP/1.1 200 OK\n"
274 "Via: a, b, c, 1.1 Chrome-Compression-Proxy\n",
276 false,
277 true,
280 "Checks when there is intermediary.",
281 "HTTP/1.1 200 OK\n"
282 "Via: a, b, c, 1.1 Chrome-Compression-Proxy, xyz\n",
284 true,
285 true,
288 "Checks the case of empty Via header.",
289 "HTTP/1.1 200 OK\n"
290 "Via: \n",
292 true,
293 false,
296 "Checks the case that only the data reduction proxy's Via header occurs.",
297 "HTTP/1.1 200 OK\n"
298 "Via: 1.1 Chrome-Compression-Proxy \n",
300 false,
301 true,
304 "Checks the case that there are ' ', i.e., empty value after the data"
305 " reduction proxy's Via header.",
306 "HTTP/1.1 200 OK\n"
307 "Via: 1.1 Chrome-Compression-Proxy , , \n",
309 false,
310 true,
313 "Checks the case when there is no Via header",
314 "HTTP/1.1 200 OK\n",
316 true,
317 false,
319 // Same to above test cases, but with deprecated data reduciton proxy Via
320 // header.
322 "Checks the case that Chrome Compression Proxy occurs at the last.",
323 "HTTP/1.1 200 OK\n"
324 "Via: a, b, c, 1.1 Chrome Compression Proxy\n",
326 false,
327 true,
330 "Checks when there is intermediary.",
331 "HTTP/1.1 200 OK\n"
332 "Via: a, b, c, 1.1 Chrome Compression Proxy, xyz\n",
334 true,
335 true,
338 "Checks the case of empty Via header.",
339 "HTTP/1.1 200 OK\n"
340 "Via: \n",
342 true,
343 false,
346 "Checks the case that only the data reduction proxy's Via header occurs.",
347 "HTTP/1.1 200 OK\n"
348 "Via: 1.1 Chrome Compression Proxy \n",
350 false,
351 true,
354 "Checks the case that there are ' ', i.e., empty value after the data"
355 "reduction proxy's Via header.",
356 "HTTP/1.1 200 OK\n"
357 "Via: 1.1 Chrome Compression Proxy , , \n",
359 false,
360 true,
363 "Checks the case when there is no Via header",
364 "HTTP/1.1 200 OK\n",
366 true,
367 false,
371 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test); ++i) {
372 std::string raw_headers(test[i].raw_header);
373 HeadersToRaw(&raw_headers);
374 scoped_refptr<net::HttpResponseHeaders> headers(
375 new net::HttpResponseHeaders(raw_headers));
377 DataReductionProxyTamperDetection tamper_detection(headers.get(), true, 0);
379 bool has_chrome_proxy_via_header;
380 bool tampered = tamper_detection.ValidateViaHeader(
381 test[i].received_fingerprint, &has_chrome_proxy_via_header);
383 EXPECT_EQ(test[i].expected_tampered_with, tampered) << test[i].label;
384 EXPECT_EQ(test[i].expected_has_chrome_proxy_via_header,
385 has_chrome_proxy_via_header) << test[i].label;
389 // Tests function ValidateOtherHeaders.
390 TEST_F(DataReductionProxyTamperDetectionTest, OtherHeaders) {
391 // For following testcases, |received_fingerprint| is not the actual
392 // fingerprint from data reduction proxy, instead, the base64 encoded field
393 // is in plain text (within "[]") and needs to be encoded first. For example,
394 // "[12345;]|content-length" needs to be encoded to
395 // "Base64Encoded(MD5(12345;))|content-length" before calling the checking
396 // function.
397 struct {
398 std::string label;
399 std::string raw_header;
400 std::string received_fingerprint;
401 bool expected_tampered_with;
402 } test[] = {
404 "Checks the case that only one header is requested.",
405 "HTTP/1.1 200 OK\n"
406 "Content-Length: 12345\n",
407 "[12345,;]|content-length",
408 false
411 "Checks the case that there is only one requested header and it does not"
412 "exist.",
413 "HTTP/1.1 200 OK\n",
414 "[;]|non_exist_header",
415 false
418 "Checks the case of multiple headers are requested.",
419 "HTTP/1.1 200 OK\n"
420 "Content-Type: 1\n"
421 "Cache-Control: 2\n"
422 "ETag: 3\n"
423 "Connection: 4\n"
424 "Expires: 5\n",
425 "[1,;2,;3,;4,;5,;]|content-type|cache-control|etag|connection|expires",
426 false
429 "Checks the case that one header has multiple values.",
430 "HTTP/1.1 200 OK\n"
431 "Content-Type: aaa1, bbb1, ccc1\n"
432 "Cache-Control: aaa2\n",
433 "[aaa1,bbb1,ccc1,;aaa2,;]|content-type|cache-control",
434 false
437 "Checks the case that one header has multiple lines.",
438 "HTTP/1.1 200 OK\n"
439 "Content-Type: aaa1, ccc1\n"
440 "Content-Type: xxx1, bbb1, ccc1\n"
441 "Cache-Control: aaa2\n",
442 "[aaa1,bbb1,ccc1,ccc1,xxx1,;aaa2,;]|content-type|cache-control",
443 false
446 "Checks the case that more than one headers have multiple values.",
447 "HTTP/1.1 200 OK\n"
448 "Content-Type: aaa1, ccc1\n"
449 "Cache-Control: ccc2 , bbb2\n"
450 "Content-Type: bbb1, ccc1\n"
451 "Cache-Control: aaa2 \n",
452 "[aaa1,bbb1,ccc1,ccc1,;aaa2,bbb2,ccc2,;]|content-type|cache-control",
453 false
456 "Checks the case that one of the requested headers is missing (Expires).",
457 "HTTP/1.1 200 OK\n"
458 "Content-Type: aaa1, ccc1\n",
459 "[aaa1,ccc1,;;]|content-type|expires",
460 false
463 "Checks the case that some of the requested headers have empty value.",
464 "HTTP/1.1 200 OK\n"
465 "Content-Type: \n"
466 "Cache-Control: \n",
467 "[,;,;]|content-type|cache-control",
468 false
471 "Checks the case that all the requested headers are missing.",
472 "HTTP/1.1 200 OK\n",
473 "[;;]|content-type|expires",
474 false
477 "Checks the case that some headers are missing, some of them are empty.",
478 "HTTP/1.1 200 OK\n"
479 "Cache-Control: \n",
480 "[;,;]|content-type|cache-control",
481 false
484 "Checks the case there is no requested header (header list is empty).",
485 "HTTP/1.1 200 OK\n"
486 "Chrome-Proxy: aut=aauutthh,bbbypas=0,aaxxx=xxx,bbbloc=1\n"
487 "Content-Type: 1\n"
488 "Cache-Control: 2\n",
489 "[]",
490 false
493 "Checks tampered requested header values.",
494 "HTTP/1.1 200 OK\n"
495 "Content-Type: aaa1, ccc1\n"
496 "Cache-Control: ccc2 , bbb2\n",
497 "[aaa1,bbb1,;bbb2,ccc2,;]|content-type|cache-control",
498 true
502 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test); ++i) {
503 ReplaceWithEncodedString(&test[i].received_fingerprint);
505 std::string raw_headers(test[i].raw_header);
506 HeadersToRaw(&raw_headers);
507 scoped_refptr<net::HttpResponseHeaders> headers(
508 new net::HttpResponseHeaders(raw_headers));
510 DataReductionProxyTamperDetection tamper_detection(headers.get(), true, 0);
512 bool tampered = tamper_detection.ValidateOtherHeaders(
513 test[i].received_fingerprint);
515 EXPECT_EQ(test[i].expected_tampered_with, tampered) << test[i].label;
519 // Tests function ValidateContentLengthHeader.
520 TEST_F(DataReductionProxyTamperDetectionTest, ContentLength) {
521 struct {
522 std::string label;
523 std::string raw_header;
524 std::string received_fingerprint;
525 bool expected_tampered_with;
526 } test[] = {
528 "Checks the case fingerprint matches received response.",
529 "HTTP/1.1 200 OK\n"
530 "Content-Length: 12345\n",
531 "12345",
532 false,
535 "Checks case that response got modified.",
536 "HTTP/1.1 200 OK\n"
537 "Content-Length: 12345\n",
538 "125",
539 true,
542 "Checks the case that the data reduction proxy has not sent"
543 "Content-Length header.",
544 "HTTP/1.1 200 OK\n"
545 "Content-Length: 12345\n",
547 false,
550 "Checks the case that the data reduction proxy sends invalid"
551 "Content-Length header.",
552 "HTTP/1.1 200 OK\n"
553 "Content-Length: 12345\n",
554 "aaa",
555 false,
558 "Checks the case that the data reduction proxy sends invalid"
559 "Content-Length header.",
560 "HTTP/1.1 200 OK\n"
561 "Content-Length: aaa\n",
562 "aaa",
563 false,
566 "Checks the case that Content-Length header is missing at the Chromium"
567 "client side.",
568 "HTTP/1.1 200 OK\n",
569 "123",
570 false,
573 "Checks the case that Content-Length header are missing at both end.",
574 "HTTP/1.1 200 OK\n",
576 false,
580 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test); ++i) {
581 std::string raw_headers(test[i].raw_header);
582 HeadersToRaw(&raw_headers);
583 scoped_refptr<net::HttpResponseHeaders> headers(
584 new net::HttpResponseHeaders(raw_headers));
586 DataReductionProxyTamperDetection tamper_detection(headers.get(), true, 0);
588 bool tampered = tamper_detection.ValidateContentLengthHeader(
589 test[i].received_fingerprint);
591 EXPECT_EQ(test[i].expected_tampered_with, tampered) << test[i].label;
595 // Tests ValuesToSortedString function.
596 TEST_F(DataReductionProxyTamperDetectionTest, ValuesToSortedString) {
597 struct {
598 std::string label;
599 std::string input_values;
600 std::string expected_output_string;
601 } test[] = {
603 "Checks the correctness of sorting.",
604 "3,2,1,",
605 "1,2,3,",
608 "Checks the case that there is an empty input vector.",
613 "Checks the case that there is an empty string in the input vector.",
614 ",",
615 ",",
619 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test); ++i) {
620 std::vector<std::string> input_values =
621 StringsToVector(test[i].input_values);
622 std::string output_string =
623 DataReductionProxyTamperDetection::ValuesToSortedString(&input_values);
624 EXPECT_EQ(output_string, test[i].expected_output_string) << test[i].label;
628 // Tests GetHeaderValues function.
629 TEST_F(DataReductionProxyTamperDetectionTest, GetHeaderValues) {
630 struct {
631 std::string label;
632 std::string raw_header;
633 std::string header_name;
634 std::string expected_output_values;
635 } test[] = {
637 "Checks the correctness of getting single line header.",
638 "HTTP/1.1 200 OK\n"
639 "test: 1, 2, 3\n",
640 "test",
641 "1,2,3,",
644 "Checks the correctness of getting multiple lines header.",
645 "HTTP/1.1 200 OK\n"
646 "test: 1, 2, 3\n"
647 "test: 4, 5, 6\n"
648 "test: 7, 8, 9\n",
649 "test",
650 "1,2,3,4,5,6,7,8,9,",
653 "Checks the correctness of getting missing header.",
654 "HTTP/1.1 200 OK\n",
655 "test",
659 "Checks the correctness of getting empty header.",
660 "HTTP/1.1 200 OK\n"
661 "test: \n",
662 "test",
663 ",",
667 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test); ++i) {
668 std::string raw_headers(test[i].raw_header);
669 HeadersToRaw(&raw_headers);
670 scoped_refptr<net::HttpResponseHeaders> headers(
671 new net::HttpResponseHeaders(raw_headers));
673 std::vector<std::string> expected_output_values =
674 StringsToVector(test[i].expected_output_values);
676 std::vector<std::string> output_values =
677 DataReductionProxyTamperDetection::GetHeaderValues(headers.get(),
678 test[i].header_name);
679 EXPECT_EQ(expected_output_values, output_values) << test[i].label;
683 // Tests main function DetectAndReport.
684 TEST_F(DataReductionProxyTamperDetectionTest, DetectAndReport) {
685 struct {
686 std::string label;
687 std::string raw_header;
688 bool expected_tampered_with;
689 } test[] = {
691 "Check no fingerprint added case.",
692 "HTTP/1.1 200 OK\n"
693 "Via: a1, b2, 1.1 Chrome-Compression-Proxy\n"
694 "Content-Length: 12345\n"
695 "Chrome-Proxy: bypass=0\n",
696 false,
699 "Check the case Chrome-Proxy fingerprint doesn't match.",
700 "HTTP/1.1 200 OK\n"
701 "Via: a1, b2, 1.1 Chrome-Compression-Proxy\n"
702 "Content-Length: 12345\n"
703 "header1: header_1\n"
704 "header2: header_2\n"
705 "header3: header_3\n"
706 "Chrome-Proxy: fcl=12345, "
707 "foh=[header_1,;header_2,;header_3,;]|header1|header2|header3,fvia=0,"
708 "fcp=abc\n",
709 true,
712 "Check the case response matches the fingerprint completely.",
713 "HTTP/1.1 200 OK\n"
714 "Via: a1, b2, 1.1 Chrome-Compression-Proxy\n"
715 "Content-Length: 12345\n"
716 "header1: header_1\n"
717 "header2: header_2\n"
718 "header3: header_3\n"
719 "Chrome-Proxy: fcl=12345, "
720 "foh=[header_1,;header_2,;header_3,;]|header1|header2|header3,"
721 "fvia=0, fcp=[fcl=12345,foh=[header_1,;header_2,;header_3,;]"
722 "|header1|header2|header3,fvia=0,]\n",
723 false,
726 "Check the case that Content-Length doesn't match.",
727 "HTTP/1.1 200 OK\n"
728 "Via: a1, b2, 1.1 Chrome-Compression-Proxy\n"
729 "Content-Length: 0\n"
730 "header1: header_1\n"
731 "header2: header_2\n"
732 "header3: header_3\n"
733 "Chrome-Proxy: fcl=12345, "
734 "foh=[header_1,;header_2,;header_3,;]|header1|header2|header3, fvia=0, "
735 "fcp=[fcl=12345,foh=[header_1,;header_2,;header_3,;]|"
736 "header1|header2|header3,fvia=0,]\n",
737 true,
741 InitEnv();
743 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test); ++i) {
744 std::string raw_headers(test[i].raw_header);
745 ReplaceWithEncodedString(&raw_headers);
746 HeadersToRaw(&raw_headers);
747 scoped_refptr<net::HttpResponseHeaders> headers(
748 new net::HttpResponseHeaders(raw_headers));
750 EXPECT_EQ(
751 test[i].expected_tampered_with,
752 DataReductionProxyTamperDetection::DetectAndReport(headers.get(), true))
753 << test[i].label;
757 } // namespace