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. 4 5 #include "components/data_reduction_proxy/browser/data_reduction_proxy_tamper_detection.h" 6 7 #include <string.h> 8 #include <algorithm> 9 #include <map> 10 #include <vector> 11 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 "components/data_reduction_proxy/common/data_reduction_proxy_headers_test_utils.h" 19 #include "net/http/http_response_headers.h" 20 #include "testing/gtest/include/gtest/gtest.h" 21 22 #if defined(OS_ANDROID) 23 #include "base/android/jni_android.h" 24 #include "net/android/network_library.h" 25 #endif 26 27 namespace { 28 29 // Calcuates MD5 hash value for a string and then base64 encode it. Testcases 30 // contain expected fingerprint in plain text, which needs to be encoded before 31 // comparison. 32 std::string GetEncoded(const std::string& input) { 33 base::MD5Digest digest; 34 base::MD5Sum(input.c_str(), input.size(), &digest); 35 std::string base64encoded; 36 base::Base64Encode(std::string((char*)digest.a, 37 ARRAYSIZE_UNSAFE(digest.a)), &base64encoded); 38 return base64encoded; 39 } 40 41 // Replaces all contents within "[]" by corresponding base64 encoded MD5 value. 42 // It can handle nested case like: [[abc]def]. This helper function transforms 43 // fingerprint in plain text to actual encoded fingerprint. 44 void ReplaceWithEncodedString(std::string* input) { 45 size_t start, end, temp; 46 while (true) { 47 start = input->find("["); 48 if (start == std::string::npos) break; 49 while (true) { 50 temp = input->find("[", start + 1); 51 end = input->find("]", start + 1); 52 if (end != std::string::npos && end < temp) 53 break; 54 55 start = temp; 56 } 57 std::string need_to_encode = input->substr(start + 1, end - start - 1); 58 *input = input->substr(0, start) + GetEncoded(need_to_encode) + 59 input->substr(end + 1); 60 } 61 } 62 63 // Returns a vector contains all the values from a comma-separated string. 64 // Some testcases contain string representation of a vector, this helper 65 // function generates a vector from a input string. 66 std::vector<std::string> StringsToVector(const std::string& values) { 67 std::vector<std::string> ret; 68 if (values.empty()) 69 return ret; 70 size_t now = 0; 71 size_t next; 72 while ((next = values.find(",", now)) != std::string::npos) { 73 ret.push_back(values.substr(now, next - now)); 74 now = next + 1; 75 } 76 return ret; 77 } 78 79 void InitEnv() { 80 #if defined(OS_ANDROID) 81 JNIEnv* env = base::android::AttachCurrentThread(); 82 static bool inited = false; 83 if (!inited) { 84 net::android::RegisterNetworkLibrary(env); 85 inited = true; 86 } 87 #endif 88 } 89 90 } // namespace 91 92 namespace data_reduction_proxy { 93 94 class DataReductionProxyTamperDetectionTest : public testing::Test { 95 96 }; 97 98 // Tests function ValidateChromeProxyHeader. 99 TEST_F(DataReductionProxyTamperDetectionTest, ChromeProxy) { 100 // |received_fingerprint| is not the actual fingerprint from data reduction 101 // proxy, instead, the base64 encoded field is in plain text (within "[]") 102 // and needs to be encoded first. 103 struct { 104 std::string label; 105 std::string raw_header; 106 std::string received_fingerprint; 107 bool expected_tampered_with; 108 } test[] = { 109 { 110 "Checks sorting.", 111 "HTTP/1.1 200 OK\n" 112 "Chrome-Proxy: c,b,a,3,2,1,fcp=f\n", 113 "[1,2,3,a,b,c,]", 114 false, 115 }, 116 { 117 "Checks Chrome-Proxy's fingerprint removing.", 118 "HTTP/1.1 200 OK\n" 119 "Chrome-Proxy: a,b,c,d,e,3,2,1,fcp=f\n", 120 "[1,2,3,a,b,c,d,e,]", 121 false, 122 }, 123 { 124 "Checks no Chrome-Proxy header case (should not happen).", 125 "HTTP/1.1 200 OK\n", 126 "[]", 127 false, 128 }, 129 { 130 "Checks empty Chrome-Proxy header case (should not happen).", 131 "HTTP/1.1 200 OK\n" 132 "Chrome-Proxy: \n", 133 "[,]", 134 false, 135 }, 136 { 137 "Checks Chrome-Proxy header with its fingerprint only case.", 138 "HTTP/1.1 200 OK\n" 139 "Chrome-Proxy: fcp=f\n", 140 "[]", 141 false, 142 }, 143 { 144 "Checks empty Chrome-Proxy header case, with extra ',' and ' '", 145 "HTTP/1.1 200 OK\n" 146 "Chrome-Proxy: fcp=f , \n", 147 "[]", 148 false, 149 }, 150 { 151 "Changed no value to empty value.", 152 "HTTP/1.1 200 OK\n" 153 "Chrome-Proxy: fcp=f\n", 154 "[,]", 155 true, 156 }, 157 { 158 "Changed header values.", 159 "HTTP/1.1 200 OK\n" 160 "Chrome-Proxy: a,b=2,c,d=1,fcp=f\n", 161 "[a,b=3,c,d=1,]", 162 true, 163 }, 164 { 165 "Changed order of header values.", 166 "HTTP/1.1 200 OK\n" 167 "Chrome-Proxy: c,b,a,fcp=1\n", 168 "[c,b,a,]", 169 true, 170 }, 171 { 172 "Checks Chrome-Proxy header with extra ' '.", 173 "HTTP/1.1 200 OK\n" 174 "Chrome-Proxy: a , b , c, d, fcp=f\n", 175 "[a,b,c,d,]", 176 false 177 }, 178 { 179 "Check Chrome-Proxy header with multiple lines and ' '.", 180 "HTTP/1.1 200 OK\n" 181 "Chrome-Proxy: a , c , d, fcp=f \n" 182 "Chrome-Proxy: b \n", 183 "[a,b,c,d,]", 184 false 185 }, 186 { 187 "Checks Chrome-Proxy header with multiple lines, at different positions", 188 "HTTP/1.1 200 OK\n" 189 "Chrome-Proxy: a \n" 190 "Chrome-Proxy: c \n" 191 "Content-Type: 1\n" 192 "Cache-Control: 2\n" 193 "ETag: 3\n" 194 "Chrome-Proxy: b \n" 195 "Connection: 4\n" 196 "Expires: 5\n" 197 "Chrome-Proxy: fcp=f \n" 198 "Via: \n" 199 "Content-Length: 12345\n", 200 "[a,b,c,]", 201 false 202 }, 203 { 204 "Checks Chrome-Proxy header with multiple same values.", 205 "HTTP/1.1 200 OK\n" 206 "Chrome-Proxy: a \n" 207 "Chrome-Proxy: b\n" 208 "Chrome-Proxy: c\n" 209 "Chrome-Proxy: d, fcp=f \n" 210 "Chrome-Proxy: a \n", 211 "[a,a,b,c,d,]", 212 false 213 }, 214 { 215 "Changed Chrome-Proxy header with multiple lines..", 216 "HTTP/1.1 200 OK\n" 217 "Chrome-Proxy: a\n" 218 "Chrome-Proxy: a\n" 219 "Chrome-Proxy: b\n" 220 "Chrome-Proxy: c,fcp=f\n", 221 "[a,b,c,]", 222 true, 223 }, 224 { 225 "Checks case whose received fingerprint is empty.", 226 "HTTP/1.1 200 OK\n" 227 "Chrome-Proxy: a,b,c,fcp=1\n", 228 "[]", 229 true, 230 }, 231 { 232 "Checks case whose received fingerprint cannot be base64 decoded.", 233 "HTTP/1.1 200 OK\n" 234 "Chrome-Proxy: a,b,c,fcp=1\n", 235 "not_base64_encoded", 236 true, 237 }, 238 }; 239 240 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test); ++i) { 241 ReplaceWithEncodedString(&test[i].received_fingerprint); 242 243 std::string raw_headers(test[i].raw_header); 244 HeadersToRaw(&raw_headers); 245 scoped_refptr<net::HttpResponseHeaders> headers( 246 new net::HttpResponseHeaders(raw_headers)); 247 248 DataReductionProxyTamperDetection tamper_detection(headers.get(), true, 0); 249 250 bool tampered = tamper_detection.ValidateChromeProxyHeader( 251 test[i].received_fingerprint); 252 253 EXPECT_EQ(test[i].expected_tampered_with, tampered) << test[i].label; 254 } 255 } 256 257 // Tests function ValidateViaHeader. 258 TEST_F(DataReductionProxyTamperDetectionTest, Via) { 259 struct { 260 std::string label; 261 std::string raw_header; 262 std::string received_fingerprint; 263 bool expected_tampered_with; 264 bool expected_has_chrome_proxy_via_header; 265 } test[] = { 266 { 267 "Checks the case that Chrome-Compression-Proxy occurs at the last.", 268 "HTTP/1.1 200 OK\n" 269 "Via: a, b, c, 1.1 Chrome-Compression-Proxy\n", 270 "", 271 false, 272 true, 273 }, 274 { 275 "Checks when there is intermediary.", 276 "HTTP/1.1 200 OK\n" 277 "Via: a, b, c, 1.1 Chrome-Compression-Proxy, xyz\n", 278 "", 279 true, 280 true, 281 }, 282 { 283 "Checks the case of empty Via header.", 284 "HTTP/1.1 200 OK\n" 285 "Via: \n", 286 "", 287 true, 288 false, 289 }, 290 { 291 "Checks the case that only the data reduction proxy's Via header occurs.", 292 "HTTP/1.1 200 OK\n" 293 "Via: 1.1 Chrome-Compression-Proxy \n", 294 "", 295 false, 296 true, 297 }, 298 { 299 "Checks the case that there are ' ', i.e., empty value after the data" 300 " reduction proxy's Via header.", 301 "HTTP/1.1 200 OK\n" 302 "Via: 1.1 Chrome-Compression-Proxy , , \n", 303 "", 304 false, 305 true, 306 }, 307 { 308 "Checks the case when there is no Via header", 309 "HTTP/1.1 200 OK\n", 310 "", 311 true, 312 false, 313 }, 314 // Same to above test cases, but with deprecated data reduciton proxy Via 315 // header. 316 { 317 "Checks the case that Chrome Compression Proxy occurs at the last.", 318 "HTTP/1.1 200 OK\n" 319 "Via: a, b, c, 1.1 Chrome Compression Proxy\n", 320 "", 321 false, 322 true, 323 }, 324 { 325 "Checks when there is intermediary.", 326 "HTTP/1.1 200 OK\n" 327 "Via: a, b, c, 1.1 Chrome Compression Proxy, xyz\n", 328 "", 329 true, 330 true, 331 }, 332 { 333 "Checks the case of empty Via header.", 334 "HTTP/1.1 200 OK\n" 335 "Via: \n", 336 "", 337 true, 338 false, 339 }, 340 { 341 "Checks the case that only the data reduction proxy's Via header occurs.", 342 "HTTP/1.1 200 OK\n" 343 "Via: 1.1 Chrome Compression Proxy \n", 344 "", 345 false, 346 true, 347 }, 348 { 349 "Checks the case that there are ' ', i.e., empty value after the data" 350 "reduction proxy's Via header.", 351 "HTTP/1.1 200 OK\n" 352 "Via: 1.1 Chrome Compression Proxy , , \n", 353 "", 354 false, 355 true, 356 }, 357 { 358 "Checks the case when there is no Via header", 359 "HTTP/1.1 200 OK\n", 360 "", 361 true, 362 false, 363 }, 364 }; 365 366 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test); ++i) { 367 std::string raw_headers(test[i].raw_header); 368 HeadersToRaw(&raw_headers); 369 scoped_refptr<net::HttpResponseHeaders> headers( 370 new net::HttpResponseHeaders(raw_headers)); 371 372 DataReductionProxyTamperDetection tamper_detection(headers.get(), true, 0); 373 374 bool has_chrome_proxy_via_header; 375 bool tampered = tamper_detection.ValidateViaHeader( 376 test[i].received_fingerprint, &has_chrome_proxy_via_header); 377 378 EXPECT_EQ(test[i].expected_tampered_with, tampered) << test[i].label; 379 EXPECT_EQ(test[i].expected_has_chrome_proxy_via_header, 380 has_chrome_proxy_via_header) << test[i].label; 381 } 382 } 383 384 // Tests function ValidateOtherHeaders. 385 TEST_F(DataReductionProxyTamperDetectionTest, OtherHeaders) { 386 // For following testcases, |received_fingerprint| is not the actual 387 // fingerprint from data reduction proxy, instead, the base64 encoded field 388 // is in plain text (within "[]") and needs to be encoded first. For example, 389 // "[12345;]|content-length" needs to be encoded to 390 // "Base64Encoded(MD5(12345;))|content-length" before calling the checking 391 // function. 392 struct { 393 std::string label; 394 std::string raw_header; 395 std::string received_fingerprint; 396 bool expected_tampered_with; 397 } test[] = { 398 { 399 "Checks the case that only one header is requested.", 400 "HTTP/1.1 200 OK\n" 401 "Content-Length: 12345\n", 402 "[12345,;]|content-length", 403 false 404 }, 405 { 406 "Checks the case that there is only one requested header and it does not" 407 "exist.", 408 "HTTP/1.1 200 OK\n", 409 "[;]|non_exist_header", 410 false 411 }, 412 { 413 "Checks the case of multiple headers are requested.", 414 "HTTP/1.1 200 OK\n" 415 "Content-Type: 1\n" 416 "Cache-Control: 2\n" 417 "ETag: 3\n" 418 "Connection: 4\n" 419 "Expires: 5\n", 420 "[1,;2,;3,;4,;5,;]|content-type|cache-control|etag|connection|expires", 421 false 422 }, 423 { 424 "Checks the case that one header has multiple values.", 425 "HTTP/1.1 200 OK\n" 426 "Content-Type: aaa1, bbb1, ccc1\n" 427 "Cache-Control: aaa2\n", 428 "[aaa1,bbb1,ccc1,;aaa2,;]|content-type|cache-control", 429 false 430 }, 431 { 432 "Checks the case that one header has multiple lines.", 433 "HTTP/1.1 200 OK\n" 434 "Content-Type: aaa1, ccc1\n" 435 "Content-Type: xxx1, bbb1, ccc1\n" 436 "Cache-Control: aaa2\n", 437 "[aaa1,bbb1,ccc1,ccc1,xxx1,;aaa2,;]|content-type|cache-control", 438 false 439 }, 440 { 441 "Checks the case that more than one headers have multiple values.", 442 "HTTP/1.1 200 OK\n" 443 "Content-Type: aaa1, ccc1\n" 444 "Cache-Control: ccc2 , bbb2\n" 445 "Content-Type: bbb1, ccc1\n" 446 "Cache-Control: aaa2 \n", 447 "[aaa1,bbb1,ccc1,ccc1,;aaa2,bbb2,ccc2,;]|content-type|cache-control", 448 false 449 }, 450 { 451 "Checks the case that one of the requested headers is missing (Expires).", 452 "HTTP/1.1 200 OK\n" 453 "Content-Type: aaa1, ccc1\n", 454 "[aaa1,ccc1,;;]|content-type|expires", 455 false 456 }, 457 { 458 "Checks the case that some of the requested headers have empty value.", 459 "HTTP/1.1 200 OK\n" 460 "Content-Type: \n" 461 "Cache-Control: \n", 462 "[,;,;]|content-type|cache-control", 463 false 464 }, 465 { 466 "Checks the case that all the requested headers are missing.", 467 "HTTP/1.1 200 OK\n", 468 "[;;]|content-type|expires", 469 false 470 }, 471 { 472 "Checks the case that some headers are missing, some of them are empty.", 473 "HTTP/1.1 200 OK\n" 474 "Cache-Control: \n", 475 "[;,;]|content-type|cache-control", 476 false 477 }, 478 { 479 "Checks the case there is no requested header (header list is empty).", 480 "HTTP/1.1 200 OK\n" 481 "Chrome-Proxy: aut=aauutthh,bbbypas=0,aaxxx=xxx,bbbloc=1\n" 482 "Content-Type: 1\n" 483 "Cache-Control: 2\n", 484 "[]", 485 false 486 }, 487 { 488 "Checks tampered requested header values.", 489 "HTTP/1.1 200 OK\n" 490 "Content-Type: aaa1, ccc1\n" 491 "Cache-Control: ccc2 , bbb2\n", 492 "[aaa1,bbb1,;bbb2,ccc2,;]|content-type|cache-control", 493 true 494 }, 495 }; 496 497 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test); ++i) { 498 ReplaceWithEncodedString(&test[i].received_fingerprint); 499 500 std::string raw_headers(test[i].raw_header); 501 HeadersToRaw(&raw_headers); 502 scoped_refptr<net::HttpResponseHeaders> headers( 503 new net::HttpResponseHeaders(raw_headers)); 504 505 DataReductionProxyTamperDetection tamper_detection(headers.get(), true, 0); 506 507 bool tampered = tamper_detection.ValidateOtherHeaders( 508 test[i].received_fingerprint); 509 510 EXPECT_EQ(test[i].expected_tampered_with, tampered) << test[i].label; 511 } 512 } 513 514 // Tests function ValidateContentLengthHeader. 515 TEST_F(DataReductionProxyTamperDetectionTest, ContentLength) { 516 struct { 517 std::string label; 518 std::string raw_header; 519 std::string received_fingerprint; 520 bool expected_tampered_with; 521 } test[] = { 522 { 523 "Checks the case fingerprint matches received response.", 524 "HTTP/1.1 200 OK\n" 525 "Content-Length: 12345\n", 526 "12345", 527 false, 528 }, 529 { 530 "Checks case that response got modified.", 531 "HTTP/1.1 200 OK\n" 532 "Content-Length: 12345\n", 533 "125", 534 true, 535 }, 536 { 537 "Checks the case that the data reduction proxy has not sent" 538 "Content-Length header.", 539 "HTTP/1.1 200 OK\n" 540 "Content-Length: 12345\n", 541 "", 542 false, 543 }, 544 { 545 "Checks the case that the data reduction proxy sends invalid" 546 "Content-Length header.", 547 "HTTP/1.1 200 OK\n" 548 "Content-Length: 12345\n", 549 "aaa", 550 false, 551 }, 552 { 553 "Checks the case that the data reduction proxy sends invalid" 554 "Content-Length header.", 555 "HTTP/1.1 200 OK\n" 556 "Content-Length: aaa\n", 557 "aaa", 558 false, 559 }, 560 { 561 "Checks the case that Content-Length header is missing at the Chromium" 562 "client side.", 563 "HTTP/1.1 200 OK\n", 564 "123", 565 false, 566 }, 567 { 568 "Checks the case that Content-Length header are missing at both end.", 569 "HTTP/1.1 200 OK\n", 570 "", 571 false, 572 }, 573 }; 574 575 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test); ++i) { 576 std::string raw_headers(test[i].raw_header); 577 HeadersToRaw(&raw_headers); 578 scoped_refptr<net::HttpResponseHeaders> headers( 579 new net::HttpResponseHeaders(raw_headers)); 580 581 DataReductionProxyTamperDetection tamper_detection(headers.get(), true, 0); 582 583 bool tampered = tamper_detection.ValidateContentLengthHeader( 584 test[i].received_fingerprint); 585 586 EXPECT_EQ(test[i].expected_tampered_with, tampered) << test[i].label; 587 } 588 } 589 590 // Tests ValuesToSortedString function. 591 TEST_F(DataReductionProxyTamperDetectionTest, ValuesToSortedString) { 592 struct { 593 std::string label; 594 std::string input_values; 595 std::string expected_output_string; 596 } test[] = { 597 { 598 "Checks the correctness of sorting.", 599 "3,2,1,", 600 "1,2,3,", 601 }, 602 { 603 "Checks the case that there is an empty input vector.", 604 "", 605 "", 606 }, 607 { 608 "Checks the case that there is an empty string in the input vector.", 609 ",", 610 ",", 611 }, 612 }; 613 614 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test); ++i) { 615 std::vector<std::string> input_values = 616 StringsToVector(test[i].input_values); 617 std::string output_string = 618 DataReductionProxyTamperDetection::ValuesToSortedString(&input_values); 619 EXPECT_EQ(output_string, test[i].expected_output_string) << test[i].label; 620 } 621 } 622 623 // Tests GetHeaderValues function. 624 TEST_F(DataReductionProxyTamperDetectionTest, GetHeaderValues) { 625 struct { 626 std::string label; 627 std::string raw_header; 628 std::string header_name; 629 std::string expected_output_values; 630 } test[] = { 631 { 632 "Checks the correctness of getting single line header.", 633 "HTTP/1.1 200 OK\n" 634 "test: 1, 2, 3\n", 635 "test", 636 "1,2,3,", 637 }, 638 { 639 "Checks the correctness of getting multiple lines header.", 640 "HTTP/1.1 200 OK\n" 641 "test: 1, 2, 3\n" 642 "test: 4, 5, 6\n" 643 "test: 7, 8, 9\n", 644 "test", 645 "1,2,3,4,5,6,7,8,9,", 646 }, 647 { 648 "Checks the correctness of getting missing header.", 649 "HTTP/1.1 200 OK\n", 650 "test", 651 "", 652 }, 653 { 654 "Checks the correctness of getting empty header.", 655 "HTTP/1.1 200 OK\n" 656 "test: \n", 657 "test", 658 ",", 659 }, 660 }; 661 662 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test); ++i) { 663 std::string raw_headers(test[i].raw_header); 664 HeadersToRaw(&raw_headers); 665 scoped_refptr<net::HttpResponseHeaders> headers( 666 new net::HttpResponseHeaders(raw_headers)); 667 668 std::vector<std::string> expected_output_values = 669 StringsToVector(test[i].expected_output_values); 670 671 std::vector<std::string> output_values = 672 DataReductionProxyTamperDetection::GetHeaderValues(headers.get(), 673 test[i].header_name); 674 EXPECT_EQ(expected_output_values, output_values) << test[i].label; 675 } 676 } 677 678 // Tests main function DetectAndReport. 679 TEST_F(DataReductionProxyTamperDetectionTest, DetectAndReport) { 680 struct { 681 std::string label; 682 std::string raw_header; 683 bool expected_tampered_with; 684 } test[] = { 685 { 686 "Check no fingerprint added case.", 687 "HTTP/1.1 200 OK\n" 688 "Via: a1, b2, 1.1 Chrome-Compression-Proxy\n" 689 "Content-Length: 12345\n" 690 "Chrome-Proxy: bypass=0\n", 691 false, 692 }, 693 { 694 "Check the case Chrome-Proxy fingerprint doesn't match.", 695 "HTTP/1.1 200 OK\n" 696 "Via: a1, b2, 1.1 Chrome-Compression-Proxy\n" 697 "Content-Length: 12345\n" 698 "header1: header_1\n" 699 "header2: header_2\n" 700 "header3: header_3\n" 701 "Chrome-Proxy: fcl=12345, " 702 "foh=[header_1,;header_2,;header_3,;]|header1|header2|header3,fvia=0," 703 "fcp=abc\n", 704 true, 705 }, 706 { 707 "Check the case response matches the fingerprint completely.", 708 "HTTP/1.1 200 OK\n" 709 "Via: a1, b2, 1.1 Chrome-Compression-Proxy\n" 710 "Content-Length: 12345\n" 711 "header1: header_1\n" 712 "header2: header_2\n" 713 "header3: header_3\n" 714 "Chrome-Proxy: fcl=12345, " 715 "foh=[header_1,;header_2,;header_3,;]|header1|header2|header3," 716 "fvia=0, fcp=[fcl=12345,foh=[header_1,;header_2,;header_3,;]" 717 "|header1|header2|header3,fvia=0,]\n", 718 false, 719 }, 720 { 721 "Check the case that Content-Length doesn't match.", 722 "HTTP/1.1 200 OK\n" 723 "Via: a1, b2, 1.1 Chrome-Compression-Proxy\n" 724 "Content-Length: 0\n" 725 "header1: header_1\n" 726 "header2: header_2\n" 727 "header3: header_3\n" 728 "Chrome-Proxy: fcl=12345, " 729 "foh=[header_1,;header_2,;header_3,;]|header1|header2|header3, fvia=0, " 730 "fcp=[fcl=12345,foh=[header_1,;header_2,;header_3,;]|" 731 "header1|header2|header3,fvia=0,]\n", 732 true, 733 }, 734 }; 735 736 InitEnv(); 737 738 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test); ++i) { 739 std::string raw_headers(test[i].raw_header); 740 ReplaceWithEncodedString(&raw_headers); 741 HeadersToRaw(&raw_headers); 742 scoped_refptr<net::HttpResponseHeaders> headers( 743 new net::HttpResponseHeaders(raw_headers)); 744 745 EXPECT_EQ( 746 test[i].expected_tampered_with, 747 DataReductionProxyTamperDetection::DetectAndReport(headers.get(), true)) 748 << test[i].label; 749 } 750 } 751 752 } // namespace 753