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"
12 #include "base/base64.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"
28 void HeadersToRaw(std::string
* headers
) {
29 std::replace(headers
->begin(), headers
->end(), '\n', '\0');
30 if (!headers
->empty())
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
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
);
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
;
52 start
= input
->find("[");
53 if (start
== std::string::npos
) break;
55 temp
= input
->find("[", start
+ 1);
56 end
= input
->find("]", start
+ 1);
57 if (end
!= std::string::npos
&& end
< 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
;
77 while ((next
= values
.find(",", now
)) != std::string::npos
) {
78 ret
.push_back(values
.substr(now
, next
- now
));
85 #if defined(OS_ANDROID)
86 JNIEnv
* env
= base::android::AttachCurrentThread();
87 static bool inited
= false;
89 net::android::RegisterNetworkLibrary(env
);
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.
110 std::string raw_header
;
111 std::string received_fingerprint
;
112 bool expected_tampered_with
;
117 "Chrome-Proxy: c,b,a,3,2,1,fcp=f\n",
122 "Checks Chrome-Proxy's fingerprint removing.",
124 "Chrome-Proxy: a,b,c,d,e,3,2,1,fcp=f\n",
125 "[1,2,3,a,b,c,d,e,]",
129 "Checks no Chrome-Proxy header case (should not happen).",
135 "Checks empty Chrome-Proxy header case (should not happen).",
142 "Checks Chrome-Proxy header with its fingerprint only case.",
144 "Chrome-Proxy: fcp=f\n",
149 "Checks empty Chrome-Proxy header case, with extra ',' and ' '",
151 "Chrome-Proxy: fcp=f , \n",
156 "Changed no value to empty value.",
158 "Chrome-Proxy: fcp=f\n",
163 "Changed header values.",
165 "Chrome-Proxy: a,b=2,c,d=1,fcp=f\n",
170 "Changed order of header values.",
172 "Chrome-Proxy: c,b,a,fcp=1\n",
177 "Checks Chrome-Proxy header with extra ' '.",
179 "Chrome-Proxy: a , b , c, d, fcp=f\n",
184 "Check Chrome-Proxy header with multiple lines and ' '.",
186 "Chrome-Proxy: a , c , d, fcp=f \n"
187 "Chrome-Proxy: b \n",
192 "Checks Chrome-Proxy header with multiple lines, at different positions",
202 "Chrome-Proxy: fcp=f \n"
204 "Content-Length: 12345\n",
209 "Checks Chrome-Proxy header with multiple same values.",
214 "Chrome-Proxy: d, fcp=f \n"
215 "Chrome-Proxy: a \n",
220 "Changed Chrome-Proxy header with multiple lines..",
225 "Chrome-Proxy: c,fcp=f\n",
230 "Checks case whose received fingerprint is empty.",
232 "Chrome-Proxy: a,b,c,fcp=1\n",
237 "Checks case whose received fingerprint cannot be base64 decoded.",
239 "Chrome-Proxy: a,b,c,fcp=1\n",
240 "not_base64_encoded",
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
) {
266 std::string raw_header
;
267 std::string received_fingerprint
;
268 bool expected_tampered_with
;
269 bool expected_has_chrome_proxy_via_header
;
272 "Checks the case that Chrome-Compression-Proxy occurs at the last.",
274 "Via: a, b, c, 1.1 Chrome-Compression-Proxy\n",
280 "Checks when there is intermediary.",
282 "Via: a, b, c, 1.1 Chrome-Compression-Proxy, xyz\n",
288 "Checks the case of empty Via header.",
296 "Checks the case that only the data reduction proxy's Via header occurs.",
298 "Via: 1.1 Chrome-Compression-Proxy \n",
304 "Checks the case that there are ' ', i.e., empty value after the data"
305 " reduction proxy's Via header.",
307 "Via: 1.1 Chrome-Compression-Proxy , , \n",
313 "Checks the case when there is no Via header",
319 // Same to above test cases, but with deprecated data reduciton proxy Via
322 "Checks the case that Chrome Compression Proxy occurs at the last.",
324 "Via: a, b, c, 1.1 Chrome Compression Proxy\n",
330 "Checks when there is intermediary.",
332 "Via: a, b, c, 1.1 Chrome Compression Proxy, xyz\n",
338 "Checks the case of empty Via header.",
346 "Checks the case that only the data reduction proxy's Via header occurs.",
348 "Via: 1.1 Chrome Compression Proxy \n",
354 "Checks the case that there are ' ', i.e., empty value after the data"
355 "reduction proxy's Via header.",
357 "Via: 1.1 Chrome Compression Proxy , , \n",
363 "Checks the case when there is no Via header",
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
399 std::string raw_header
;
400 std::string received_fingerprint
;
401 bool expected_tampered_with
;
404 "Checks the case that only one header is requested.",
406 "Content-Length: 12345\n",
407 "[12345,;]|content-length",
411 "Checks the case that there is only one requested header and it does not"
414 "[;]|non_exist_header",
418 "Checks the case of multiple headers are requested.",
425 "[1,;2,;3,;4,;5,;]|content-type|cache-control|etag|connection|expires",
429 "Checks the case that one header has multiple values.",
431 "Content-Type: aaa1, bbb1, ccc1\n"
432 "Cache-Control: aaa2\n",
433 "[aaa1,bbb1,ccc1,;aaa2,;]|content-type|cache-control",
437 "Checks the case that one header has multiple lines.",
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",
446 "Checks the case that more than one headers have multiple values.",
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",
456 "Checks the case that one of the requested headers is missing (Expires).",
458 "Content-Type: aaa1, ccc1\n",
459 "[aaa1,ccc1,;;]|content-type|expires",
463 "Checks the case that some of the requested headers have empty value.",
467 "[,;,;]|content-type|cache-control",
471 "Checks the case that all the requested headers are missing.",
473 "[;;]|content-type|expires",
477 "Checks the case that some headers are missing, some of them are empty.",
480 "[;,;]|content-type|cache-control",
484 "Checks the case there is no requested header (header list is empty).",
486 "Chrome-Proxy: aut=aauutthh,bbbypas=0,aaxxx=xxx,bbbloc=1\n"
488 "Cache-Control: 2\n",
493 "Checks tampered requested header values.",
495 "Content-Type: aaa1, ccc1\n"
496 "Cache-Control: ccc2 , bbb2\n",
497 "[aaa1,bbb1,;bbb2,ccc2,;]|content-type|cache-control",
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
) {
523 std::string raw_header
;
524 std::string received_fingerprint
;
525 bool expected_tampered_with
;
528 "Checks the case fingerprint matches received response.",
530 "Content-Length: 12345\n",
535 "Checks case that response got modified.",
537 "Content-Length: 12345\n",
542 "Checks the case that the data reduction proxy has not sent"
543 "Content-Length header.",
545 "Content-Length: 12345\n",
550 "Checks the case that the data reduction proxy sends invalid"
551 "Content-Length header.",
553 "Content-Length: 12345\n",
558 "Checks the case that the data reduction proxy sends invalid"
559 "Content-Length header.",
561 "Content-Length: aaa\n",
566 "Checks the case that Content-Length header is missing at the Chromium"
573 "Checks the case that Content-Length header are missing at both end.",
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
) {
599 std::string input_values
;
600 std::string expected_output_string
;
603 "Checks the correctness of sorting.",
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.",
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
) {
632 std::string raw_header
;
633 std::string header_name
;
634 std::string expected_output_values
;
637 "Checks the correctness of getting single line header.",
644 "Checks the correctness of getting multiple lines header.",
650 "1,2,3,4,5,6,7,8,9,",
653 "Checks the correctness of getting missing header.",
659 "Checks the correctness of getting empty header.",
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
) {
687 std::string raw_header
;
688 bool expected_tampered_with
;
691 "Check no fingerprint added case.",
693 "Via: a1, b2, 1.1 Chrome-Compression-Proxy\n"
694 "Content-Length: 12345\n"
695 "Chrome-Proxy: bypass=0\n",
699 "Check the case Chrome-Proxy fingerprint doesn't match.",
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,"
712 "Check the case response matches the fingerprint completely.",
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",
726 "Check the case that Content-Length doesn't match.",
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",
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
));
751 test
[i
].expected_tampered_with
,
752 DataReductionProxyTamperDetection::DetectAndReport(headers
.get(), true))