| // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <algorithm> |
| |
| #include "base/pickle.h" |
| #include "base/time.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/safe_browsing/malware_details.h" |
| #include "chrome/browser/safe_browsing/report.pb.h" |
| #include "chrome/common/render_messages.h" |
| #include "chrome/common/safe_browsing/safebrowsing_messages.h" |
| #include "chrome/test/test_url_request_context_getter.h" |
| #include "chrome/test/testing_profile.h" |
| #include "content/browser/browser_thread.h" |
| #include "content/browser/renderer_host/test_render_view_host.h" |
| #include "content/browser/tab_contents/navigation_entry.h" |
| #include "content/browser/tab_contents/test_tab_contents.h" |
| #include "net/base/io_buffer.h" |
| #include "net/base/test_completion_callback.h" |
| #include "net/disk_cache/disk_cache.h" |
| #include "net/http/http_cache.h" |
| #include "net/http/http_response_headers.h" |
| #include "net/http/http_response_info.h" |
| #include "net/http/http_util.h" |
| #include "net/url_request/url_request_context.h" |
| #include "net/url_request/url_request_context_getter.h" |
| |
| static const char* kOriginalLandingURL = "http://www.originallandingpage.com/"; |
| static const char* kHttpsURL = "https://www.url.com/"; |
| static const char* kDOMChildURL = "http://www.domparent.com/"; |
| static const char* kDOMParentURL = "http://www.domchild.com/"; |
| static const char* kFirstRedirectURL = "http://redirectone.com/"; |
| static const char* kSecondRedirectURL = "http://redirecttwo.com/"; |
| |
| static const char* kMalwareURL = "http://www.malware.com/"; |
| static const char* kMalwareHeaders = |
| "HTTP/1.1 200 OK\n" |
| "Content-Type: image/jpeg\n"; |
| static const char* kMalwareData = "exploit();"; |
| |
| static const char* kLandingURL = "http://www.landingpage.com/"; |
| static const char* kLandingHeaders = |
| "HTTP/1.1 200 OK\n" |
| "Content-Type: text/html\n" |
| "Content-Length: 1024\n" |
| "Set-Cookie: tastycookie\n"; // This header is stripped. |
| static const char* kLandingData = "<iframe src='http://www.malware.com'>"; |
| |
| using safe_browsing::ClientMalwareReportRequest; |
| |
| namespace { |
| |
| void WriteHeaders(disk_cache::Entry* entry, const std::string headers) { |
| net::HttpResponseInfo responseinfo; |
| std::string raw_headers = net::HttpUtil::AssembleRawHeaders( |
| headers.c_str(), headers.size()); |
| responseinfo.headers = new net::HttpResponseHeaders(raw_headers); |
| |
| Pickle pickle; |
| responseinfo.Persist(&pickle, false, false); |
| |
| scoped_refptr<net::WrappedIOBuffer> buf(new net::WrappedIOBuffer( |
| reinterpret_cast<const char*>(pickle.data()))); |
| int len = static_cast<int>(pickle.size()); |
| |
| TestCompletionCallback cb; |
| int rv = entry->WriteData(0, 0, buf, len, &cb, true); |
| ASSERT_EQ(len, cb.GetResult(rv)); |
| } |
| |
| void WriteData(disk_cache::Entry* entry, const std::string data) { |
| if (data.empty()) |
| return; |
| |
| int len = data.length(); |
| scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(len)); |
| memcpy(buf->data(), data.data(), data.length()); |
| |
| TestCompletionCallback cb; |
| int rv = entry->WriteData(1, 0, buf, len, &cb, true); |
| ASSERT_EQ(len, cb.GetResult(rv)); |
| } |
| |
| void WriteToEntry(disk_cache::Backend* cache, const std::string key, |
| const std::string headers, const std::string data) { |
| TestCompletionCallback cb; |
| disk_cache::Entry* entry; |
| int rv = cache->CreateEntry(key, &entry, &cb); |
| rv = cb.GetResult(rv); |
| if (rv != net::OK) { |
| rv = cache->OpenEntry(key, &entry, &cb); |
| ASSERT_EQ(net::OK, cb.GetResult(rv)); |
| } |
| |
| WriteHeaders(entry, headers); |
| WriteData(entry, data); |
| |
| entry->Close(); |
| } |
| |
| void FillCache(net::URLRequestContext* context) { |
| TestCompletionCallback cb; |
| disk_cache::Backend* cache; |
| int rv = |
| context->http_transaction_factory()->GetCache()->GetBackend(&cache, &cb); |
| ASSERT_EQ(net::OK, cb.GetResult(rv)); |
| |
| std::string empty; |
| WriteToEntry(cache, kMalwareURL, kMalwareHeaders, kMalwareData); |
| WriteToEntry(cache, kLandingURL, kLandingHeaders, kLandingData); |
| } |
| |
| void QuitUIMessageLoop() { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| BrowserThread::PostTask(BrowserThread::UI, |
| FROM_HERE, |
| new MessageLoop::QuitTask()); |
| } |
| |
| // Lets us provide a MockURLRequestContext with an HTTP Cache we pre-populate. |
| // Also exposes the constructor. |
| class MalwareDetailsWrap : public MalwareDetails { |
| public: |
| MalwareDetailsWrap(SafeBrowsingService* sb_service, |
| TabContents* tab_contents, |
| const SafeBrowsingService::UnsafeResource& unsafe_resource, |
| net::URLRequestContextGetter* request_context_getter) |
| : MalwareDetails(sb_service, tab_contents, unsafe_resource) { |
| request_context_getter_ = request_context_getter; |
| } |
| |
| virtual ~MalwareDetailsWrap() {} |
| }; |
| |
| class MockSafeBrowsingService : public SafeBrowsingService { |
| public: |
| MockSafeBrowsingService() {} |
| virtual ~MockSafeBrowsingService() {} |
| |
| // When the MalwareDetails is done, this is called. |
| virtual void SendSerializedMalwareDetails(const std::string& serialized) { |
| DVLOG(1) << "SendSerializedMalwareDetails"; |
| // Notify WaitForSerializedReport. |
| BrowserThread::PostTask(BrowserThread::IO, |
| FROM_HERE, |
| NewRunnableFunction(&QuitUIMessageLoop)); |
| serialized_ = serialized; |
| } |
| |
| const std::string& GetSerialized() { |
| return serialized_; |
| } |
| |
| private: |
| std::string serialized_; |
| DISALLOW_COPY_AND_ASSIGN(MockSafeBrowsingService); |
| }; |
| |
| } // namespace. |
| |
| class MalwareDetailsTest : public RenderViewHostTestHarness { |
| public: |
| MalwareDetailsTest() |
| : ui_thread_(BrowserThread::UI, &message_loop_), |
| io_thread_(BrowserThread::IO), |
| sb_service_(new MockSafeBrowsingService()) { |
| } |
| |
| virtual void SetUp() { |
| RenderViewHostTestHarness::SetUp(); |
| // request_context_getter_ = new TestURLRequestContextGetter(); |
| |
| // The URLFetcher checks that the messageloop type is IO. |
| ASSERT_TRUE(io_thread_.StartWithOptions( |
| base::Thread::Options(MessageLoop::TYPE_IO, 0))); |
| } |
| |
| virtual void TearDown() { |
| io_thread_.Stop(); |
| RenderViewHostTestHarness::TearDown(); |
| } |
| |
| static bool ResourceLessThan( |
| const ClientMalwareReportRequest::Resource* lhs, |
| const ClientMalwareReportRequest::Resource* rhs) { |
| return lhs->id() < rhs->id(); |
| } |
| |
| std::string WaitForSerializedReport(MalwareDetails* report) { |
| BrowserThread::PostTask( |
| BrowserThread::IO, |
| FROM_HERE, |
| NewRunnableMethod( |
| report, &MalwareDetails::FinishCollection)); |
| // Wait for the callback (SendSerializedMalwareDetails). |
| DVLOG(1) << "Waiting for SendSerializedMalwareDetails"; |
| MessageLoop::current()->Run(); |
| return sb_service_->GetSerialized(); |
| } |
| |
| protected: |
| void InitResource(SafeBrowsingService::UnsafeResource* resource, |
| ResourceType::Type resource_type, |
| const GURL& url) { |
| resource->client = NULL; |
| resource->url = url; |
| resource->resource_type = resource_type; |
| resource->threat_type = SafeBrowsingService::URL_MALWARE; |
| resource->render_process_host_id = contents()->GetRenderProcessHost()->id(); |
| resource->render_view_id = contents()->render_view_host()->routing_id(); |
| } |
| |
| void VerifyResults(const ClientMalwareReportRequest& report_pb, |
| const ClientMalwareReportRequest& expected_pb) { |
| EXPECT_EQ(expected_pb.malware_url(), report_pb.malware_url()); |
| EXPECT_EQ(expected_pb.page_url(), report_pb.page_url()); |
| EXPECT_EQ(expected_pb.referrer_url(), report_pb.referrer_url()); |
| |
| ASSERT_EQ(expected_pb.resources_size(), report_pb.resources_size()); |
| // Sort the resources, to make the test deterministic |
| std::vector<const ClientMalwareReportRequest::Resource*> resources; |
| for (int i = 0; i < report_pb.resources_size(); ++i) { |
| const ClientMalwareReportRequest::Resource& resource = |
| report_pb.resources(i); |
| resources.push_back(&resource); |
| } |
| std::sort(resources.begin(), resources.end(), |
| &MalwareDetailsTest::ResourceLessThan); |
| |
| std::vector<const ClientMalwareReportRequest::Resource*> expected; |
| for (int i = 0; i < report_pb.resources_size(); ++i) { |
| const ClientMalwareReportRequest::Resource& resource = |
| expected_pb.resources(i); |
| expected.push_back(&resource); |
| } |
| std::sort(expected.begin(), expected.end(), |
| &MalwareDetailsTest::ResourceLessThan); |
| |
| for (uint32 i = 0; i < expected.size(); ++i) { |
| VerifyResource(resources[i], expected[i]); |
| } |
| |
| EXPECT_EQ(expected_pb.complete(), report_pb.complete()); |
| } |
| |
| void VerifyResource(const ClientMalwareReportRequest::Resource* resource, |
| const ClientMalwareReportRequest::Resource* expected) { |
| EXPECT_EQ(expected->id(), resource->id()); |
| EXPECT_EQ(expected->url(), resource->url()); |
| EXPECT_EQ(expected->parent_id(), resource->parent_id()); |
| ASSERT_EQ(expected->child_ids_size(), resource->child_ids_size()); |
| for (int i = 0; i < expected->child_ids_size(); i++) { |
| EXPECT_EQ(expected->child_ids(i), resource->child_ids(i)); |
| } |
| |
| // Verify HTTP Responses |
| if (expected->has_response()) { |
| ASSERT_TRUE(resource->has_response()); |
| EXPECT_EQ(expected->response().firstline().code(), |
| resource->response().firstline().code()); |
| |
| ASSERT_EQ(expected->response().headers_size(), |
| resource->response().headers_size()); |
| for (int i = 0; i < expected->response().headers_size(); ++i) { |
| EXPECT_EQ(expected->response().headers(i).name(), |
| resource->response().headers(i).name()); |
| EXPECT_EQ(expected->response().headers(i).value(), |
| resource->response().headers(i).value()); |
| } |
| |
| EXPECT_EQ(expected->response().body(), resource->response().body()); |
| EXPECT_EQ(expected->response().bodylength(), |
| resource->response().bodylength()); |
| EXPECT_EQ(expected->response().bodydigest(), |
| resource->response().bodydigest()); |
| } |
| } |
| |
| BrowserThread ui_thread_; |
| BrowserThread io_thread_; |
| scoped_refptr<MockSafeBrowsingService> sb_service_; |
| }; |
| |
| // Tests creating a simple malware report. |
| TEST_F(MalwareDetailsTest, MalwareSubResource) { |
| // Start a load. |
| controller().LoadURL(GURL(kLandingURL), GURL(), PageTransition::TYPED); |
| |
| SafeBrowsingService::UnsafeResource resource; |
| InitResource(&resource, ResourceType::SUB_RESOURCE, GURL(kMalwareURL)); |
| |
| scoped_refptr<MalwareDetailsWrap> report = new MalwareDetailsWrap( |
| sb_service_, contents(), resource, NULL); |
| |
| std::string serialized = WaitForSerializedReport(report); |
| |
| ClientMalwareReportRequest actual; |
| actual.ParseFromString(serialized); |
| |
| ClientMalwareReportRequest expected; |
| expected.set_malware_url(kMalwareURL); |
| expected.set_page_url(kLandingURL); |
| expected.set_referrer_url(""); |
| |
| ClientMalwareReportRequest::Resource* pb_resource = expected.add_resources(); |
| pb_resource->set_id(0); |
| pb_resource->set_url(kLandingURL); |
| pb_resource = expected.add_resources(); |
| pb_resource->set_id(1); |
| pb_resource->set_url(kMalwareURL); |
| |
| VerifyResults(actual, expected); |
| } |
| |
| // Tests creating a simple malware report where the subresource has a |
| // different original_url. |
| TEST_F(MalwareDetailsTest, MalwareSubResourceWithOriginalUrl) { |
| controller().LoadURL(GURL(kLandingURL), GURL(), PageTransition::TYPED); |
| |
| SafeBrowsingService::UnsafeResource resource; |
| InitResource(&resource, ResourceType::SUB_RESOURCE, GURL(kMalwareURL)); |
| resource.original_url = GURL(kOriginalLandingURL); |
| |
| scoped_refptr<MalwareDetailsWrap> report = new MalwareDetailsWrap( |
| sb_service_.get(), contents(), resource, NULL); |
| |
| std::string serialized = WaitForSerializedReport(report); |
| |
| ClientMalwareReportRequest actual; |
| actual.ParseFromString(serialized); |
| |
| ClientMalwareReportRequest expected; |
| expected.set_malware_url(kMalwareURL); |
| expected.set_page_url(kLandingURL); |
| expected.set_referrer_url(""); |
| |
| ClientMalwareReportRequest::Resource* pb_resource = expected.add_resources(); |
| pb_resource->set_id(0); |
| pb_resource->set_url(kLandingURL); |
| |
| pb_resource = expected.add_resources(); |
| pb_resource->set_id(1); |
| pb_resource->set_url(kOriginalLandingURL); |
| |
| pb_resource = expected.add_resources(); |
| pb_resource->set_id(2); |
| pb_resource->set_url(kMalwareURL); |
| // The Resource for kMmalwareUrl should have the Resource for |
| // kOriginalLandingURL (with id 1) as parent. |
| pb_resource->set_parent_id(1); |
| |
| VerifyResults(actual, expected); |
| } |
| |
| // Tests creating a malware report with data from the renderer. |
| TEST_F(MalwareDetailsTest, MalwareDOMDetails) { |
| controller().LoadURL(GURL(kLandingURL), GURL(), PageTransition::TYPED); |
| |
| SafeBrowsingService::UnsafeResource resource; |
| InitResource(&resource, ResourceType::SUB_RESOURCE, GURL(kMalwareURL)); |
| |
| scoped_refptr<MalwareDetailsWrap> report = new MalwareDetailsWrap( |
| sb_service_.get(), contents(), resource, NULL); |
| |
| // Send a message from the DOM, with 2 nodes, a parent and a child. |
| std::vector<SafeBrowsingHostMsg_MalwareDOMDetails_Node> params; |
| SafeBrowsingHostMsg_MalwareDOMDetails_Node child_node; |
| child_node.url = GURL(kDOMChildURL); |
| child_node.tag_name = "iframe"; |
| child_node.parent = GURL(kDOMParentURL); |
| params.push_back(child_node); |
| SafeBrowsingHostMsg_MalwareDOMDetails_Node parent_node; |
| parent_node.url = GURL(kDOMParentURL); |
| parent_node.children.push_back(GURL(kDOMChildURL)); |
| params.push_back(parent_node); |
| report->OnReceivedMalwareDOMDetails(params); |
| |
| MessageLoop::current()->RunAllPending(); |
| |
| std::string serialized = WaitForSerializedReport(report); |
| ClientMalwareReportRequest actual; |
| actual.ParseFromString(serialized); |
| |
| ClientMalwareReportRequest expected; |
| expected.set_malware_url(kMalwareURL); |
| expected.set_page_url(kLandingURL); |
| expected.set_referrer_url(""); |
| |
| ClientMalwareReportRequest::Resource* pb_resource = expected.add_resources(); |
| pb_resource->set_id(0); |
| pb_resource->set_url(kLandingURL); |
| |
| pb_resource = expected.add_resources(); |
| pb_resource->set_id(1); |
| pb_resource->set_url(kMalwareURL); |
| |
| pb_resource = expected.add_resources(); |
| pb_resource->set_id(2); |
| pb_resource->set_url(kDOMChildURL); |
| pb_resource->set_parent_id(3); |
| |
| pb_resource = expected.add_resources(); |
| pb_resource->set_id(3); |
| pb_resource->set_url(kDOMParentURL); |
| pb_resource->add_child_ids(2); |
| expected.set_complete(false); // Since the cache was missing. |
| |
| VerifyResults(actual, expected); |
| } |
| |
| // Verify that https:// urls are dropped. |
| TEST_F(MalwareDetailsTest, NotPublicUrl) { |
| controller().LoadURL(GURL(kHttpsURL), GURL(), PageTransition::TYPED); |
| SafeBrowsingService::UnsafeResource resource; |
| InitResource(&resource, ResourceType::SUB_RESOURCE, GURL(kMalwareURL)); |
| scoped_refptr<MalwareDetailsWrap> report = new MalwareDetailsWrap( |
| sb_service_.get(), contents(), resource, NULL); |
| |
| std::string serialized = WaitForSerializedReport(report); |
| ClientMalwareReportRequest actual; |
| actual.ParseFromString(serialized); |
| |
| ClientMalwareReportRequest expected; |
| expected.set_malware_url(kMalwareURL); // No page_url |
| expected.set_referrer_url(""); |
| |
| ClientMalwareReportRequest::Resource* pb_resource = expected.add_resources(); |
| pb_resource->set_url(kMalwareURL); // Only one resource |
| |
| VerifyResults(actual, expected); |
| } |
| |
| // Tests creating a malware report where there are redirect urls to an unsafe |
| // resource url |
| TEST_F(MalwareDetailsTest, MalwareWithRedirectUrl) { |
| controller().LoadURL(GURL(kLandingURL), GURL(), PageTransition::TYPED); |
| |
| SafeBrowsingService::UnsafeResource resource; |
| InitResource(&resource, ResourceType::SUB_RESOURCE, GURL(kMalwareURL)); |
| resource.original_url = GURL(kOriginalLandingURL); |
| |
| // add some redirect urls |
| resource.redirect_urls.push_back(GURL(kFirstRedirectURL)); |
| resource.redirect_urls.push_back(GURL(kSecondRedirectURL)); |
| resource.redirect_urls.push_back(GURL(kMalwareURL)); |
| |
| scoped_refptr<MalwareDetailsWrap> report = new MalwareDetailsWrap( |
| sb_service_.get(), contents(), resource, NULL); |
| |
| std::string serialized = WaitForSerializedReport(report); |
| ClientMalwareReportRequest actual; |
| actual.ParseFromString(serialized); |
| |
| ClientMalwareReportRequest expected; |
| expected.set_malware_url(kMalwareURL); |
| expected.set_page_url(kLandingURL); |
| expected.set_referrer_url(""); |
| |
| ClientMalwareReportRequest::Resource* pb_resource = expected.add_resources(); |
| pb_resource->set_id(0); |
| pb_resource->set_url(kLandingURL); |
| |
| pb_resource = expected.add_resources(); |
| pb_resource->set_id(1); |
| pb_resource->set_url(kOriginalLandingURL); |
| |
| pb_resource = expected.add_resources(); |
| pb_resource->set_id(2); |
| pb_resource->set_url(kMalwareURL); |
| pb_resource->set_parent_id(4); |
| |
| pb_resource = expected.add_resources(); |
| pb_resource->set_id(3); |
| pb_resource->set_url(kFirstRedirectURL); |
| pb_resource->set_parent_id(1); |
| |
| pb_resource = expected.add_resources(); |
| pb_resource->set_id(4); |
| pb_resource->set_url(kSecondRedirectURL); |
| pb_resource->set_parent_id(3); |
| |
| VerifyResults(actual, expected); |
| } |
| |
| // Tests the interaction with the HTTP cache. |
| TEST_F(MalwareDetailsTest, HTTPCache) { |
| controller().LoadURL(GURL(kLandingURL), GURL(), PageTransition::TYPED); |
| |
| SafeBrowsingService::UnsafeResource resource; |
| InitResource(&resource, ResourceType::SUB_RESOURCE, GURL(kMalwareURL)); |
| |
| profile()->CreateRequestContext(); |
| scoped_refptr<MalwareDetailsWrap> report = new MalwareDetailsWrap( |
| sb_service_.get(), contents(), resource |
| , profile()->GetRequestContext()); |
| |
| FillCache(profile()->GetRequestContext()->GetURLRequestContext()); |
| |
| // The cache collection starts after the IPC from the DOM is fired. |
| std::vector<SafeBrowsingHostMsg_MalwareDOMDetails_Node> params; |
| report->OnReceivedMalwareDOMDetails(params); |
| |
| // Let the cache callbacks complete |
| MessageLoop::current()->RunAllPending(); |
| |
| DVLOG(1) << "Getting serialized report"; |
| std::string serialized = WaitForSerializedReport(report); |
| ClientMalwareReportRequest actual; |
| actual.ParseFromString(serialized); |
| |
| ClientMalwareReportRequest expected; |
| expected.set_malware_url(kMalwareURL); |
| expected.set_page_url(kLandingURL); |
| expected.set_referrer_url(""); |
| |
| ClientMalwareReportRequest::Resource* pb_resource = expected.add_resources(); |
| pb_resource->set_id(0); |
| pb_resource->set_url(kLandingURL); |
| safe_browsing::ClientMalwareReportRequest::HTTPResponse* pb_response = |
| pb_resource->mutable_response(); |
| pb_response->mutable_firstline()->set_code(200); |
| safe_browsing::ClientMalwareReportRequest::HTTPHeader* pb_header = |
| pb_response->add_headers(); |
| pb_header->set_name("Content-Type"); |
| pb_header->set_value("text/html"); |
| pb_header = pb_response->add_headers(); |
| pb_header->set_name("Content-Length"); |
| pb_header->set_value("1024"); |
| pb_header = pb_response->add_headers(); |
| pb_header->set_name("Set-Cookie"); |
| pb_header->set_value(""); // The cookie is dropped. |
| pb_response->set_body(kLandingData); |
| pb_response->set_bodylength(37); |
| pb_response->set_bodydigest("9ca97475598a79bc1e8fc9bd6c72cd35"); |
| |
| pb_resource = expected.add_resources(); |
| pb_resource->set_id(1); |
| pb_resource->set_url(kMalwareURL); |
| pb_response = pb_resource->mutable_response(); |
| pb_response->mutable_firstline()->set_code(200); |
| pb_header = pb_response->add_headers(); |
| pb_header->set_name("Content-Type"); |
| pb_header->set_value("image/jpeg"); |
| pb_response->set_body(kMalwareData); |
| pb_response->set_bodylength(10); |
| pb_response->set_bodydigest("581373551c43d4cf33bfb3b26838ff95"); |
| expected.set_complete(true); |
| |
| VerifyResults(actual, expected); |
| } |
| |
| // Tests the interaction with the HTTP cache (where the cache is empty). |
| TEST_F(MalwareDetailsTest, HTTPCacheNoEntries) { |
| controller().LoadURL(GURL(kLandingURL), GURL(), PageTransition::TYPED); |
| |
| SafeBrowsingService::UnsafeResource resource; |
| InitResource(&resource, ResourceType::SUB_RESOURCE, GURL(kMalwareURL)); |
| |
| profile()->CreateRequestContext(); |
| scoped_refptr<MalwareDetailsWrap> report = new MalwareDetailsWrap( |
| sb_service_.get(), contents(), resource, |
| profile()->GetRequestContext()); |
| |
| // No call to FillCache |
| |
| // The cache collection starts after the IPC from the DOM is fired. |
| std::vector<SafeBrowsingHostMsg_MalwareDOMDetails_Node> params; |
| report->OnReceivedMalwareDOMDetails(params); |
| |
| // Let the cache callbacks complete |
| MessageLoop::current()->RunAllPending(); |
| |
| DVLOG(1) << "Getting serialized report"; |
| std::string serialized = WaitForSerializedReport(report); |
| ClientMalwareReportRequest actual; |
| actual.ParseFromString(serialized); |
| |
| ClientMalwareReportRequest expected; |
| expected.set_malware_url(kMalwareURL); |
| expected.set_page_url(kLandingURL); |
| expected.set_referrer_url(""); |
| |
| ClientMalwareReportRequest::Resource* pb_resource = expected.add_resources(); |
| pb_resource->set_id(0); |
| pb_resource->set_url(kLandingURL); |
| pb_resource = expected.add_resources(); |
| pb_resource->set_id(1); |
| pb_resource->set_url(kMalwareURL); |
| expected.set_complete(true); |
| |
| VerifyResults(actual, expected); |
| } |