Commit c1df0048 authored by Takeshi Yoshino's avatar Takeshi Yoshino Committed by Commit Bot

Stop reusing MemoryCache entries for requests with a different source origin.

ResourceFetcher/ResourceLoader now saves the result of the CORS check on
the Resource object. Though the result of the CORS check varies
depending on the source origin, reusing an existing resource fetched by
a different source origin is allowed by mistake.

This patch introduces a logic to prevent MemoryCache entries from being
reused for requests with a different source (requestor) origin by saving
the source origin on the Resource object and comparing that with the new
source origin in Resource::CanReuse(), so that the result of the CORS
check is reused only when the source origin is the same.

An alternative possibly-better approach is to isolate MemoryCache for
different origins by changing the cache identifier to take into account
the source origin of requests. However, to keep the patch small and fix
the issue quickly, this patch just prevents reuse.

Bug: 799477, 809350
Change-Id: Ib96c9e728abe969a53f3d80519118a83392067b4
Reviewed-on: https://chromium-review.googlesource.com/897040
Commit-Queue: Takeshi Yoshino <tyoshino@chromium.org>
Reviewed-by: default avatarTakashi Toyoshima <toyoshim@chromium.org>
Reviewed-by: default avatarYutaka Hirano <yhirano@chromium.org>
Cr-Commit-Position: refs/heads/master@{#537580}
parent d3562176
<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<body>
</body>
<script>
async_test(t => {
const img = document.createElement('img');
img.onload = t.step_func(() => {
const iframe = document.createElement('iframe');
window.onmessage = t.step_func_done(e => {
assert_equals(e.data, 'DONE');
});
iframe.src = 'http://{{domains[www1]}}:{{ports[http][0]}}/cors/resources/image-tainting-checker.sub.html';
document.body.appendChild(iframe);
});
img.src = '/images/blue-png-cachable.py';
document.body.appendChild(img);
}, 'An image resource that is same-origin to the top-level frame loaded in ' +
'the frame is not treated as same-origin for an iframe that is ' +
'cross-origin to the top-level frame, and therefore a canvas where the ' +
'image is drawn gets tainted.');
</script>
<!DOCTYPE html>
<body>
<canvas id="canvas"></canvas>
<script>
// Used by image-tainting-in-cross-origin-iframe.sub.html to check that an
// image resource loaded by the top level frame that is same-origin to the
// frame isn't treated as a same-origin resource in a cross-origin iframe.
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const img = new Image();
img.src = 'http://{{host}}:{{ports[http][0]}}/images/blue-png-cachable.py';
img.onload = () => {
ctx.drawImage(img, 0, 0);
try {
ctx.getImageData(0, 0, 1, 1);
parent.postMessage('FAIL: getImageData() didn\'t throw', '*');
} catch (e) {
parent.postMessage('DONE', '*');
}
};
</script>
</body>
import os
import time
def main(request, response):
"""Serves the contents in blue.png but with a Cache-Control header.
Emits a Cache-Control header with max-age set to 1h to allow the browser
cache the image. Used for testing behaviors involving caching logics.
"""
image_path = os.path.join(os.path.dirname(__file__), "blue.png")
response.headers.set("Cache-Control", "max-age=3600")
response.headers.set("Content-Type", "image/png")
response.content = open(image_path, mode='rb').read()
......@@ -174,7 +174,9 @@ ImageResource* ImageResource::Fetch(FetchParameters& params,
return resource;
}
bool ImageResource::CanReuse(const FetchParameters& params) const {
bool ImageResource::CanReuse(
const FetchParameters& params,
scoped_refptr<const SecurityOrigin> new_source_origin) const {
// If the image is a placeholder, but this fetch doesn't allow a
// placeholder, then do not reuse this resource.
if (params.GetPlaceholderImageRequestType() !=
......@@ -182,7 +184,7 @@ bool ImageResource::CanReuse(const FetchParameters& params) const {
placeholder_option_ != PlaceholderOption::kDoNotReloadPlaceholder)
return false;
return Resource::CanReuse(params);
return Resource::CanReuse(params, std::move(new_source_origin));
}
bool ImageResource::CanUseCacheValidator() const {
......
......@@ -78,7 +78,9 @@ class CORE_EXPORT ImageResource final
void AllClientsAndObserversRemoved() override;
bool CanReuse(const FetchParameters&) const override;
bool CanReuse(
const FetchParameters&,
scoped_refptr<const SecurityOrigin> new_source_origin) const override;
bool CanUseCacheValidator() const override;
scoped_refptr<const SharedBuffer> ResourceBuffer() const override;
......
......@@ -64,7 +64,7 @@ class MemoryCacheCorrectnessTest : public ::testing::Test {
MockResource* resource = MockResource::Create(request);
resource->SetResponse(response);
resource->FinishForTest();
GetMemoryCache()->Add(resource);
AddResourceToMemoryCache(resource);
return resource;
}
......@@ -76,10 +76,14 @@ class MemoryCacheCorrectnessTest : public ::testing::Test {
MockResource* resource = MockResource::Create(request);
resource->SetResponse(ResourceResponse(KURL(kResourceURL), "text/html"));
resource->FinishForTest();
GetMemoryCache()->Add(resource);
AddResourceToMemoryCache(resource);
return resource;
}
void AddResourceToMemoryCache(Resource* resource) {
resource->SetSourceOrigin(security_origin_);
GetMemoryCache()->Add(resource);
}
// TODO(toyoshim): Consider to use MockResource for all tests instead of
// RawResource.
RawResource* FetchRawResource() {
......@@ -102,8 +106,12 @@ class MemoryCacheCorrectnessTest : public ::testing::Test {
// Save the global memory cache to restore it upon teardown.
global_memory_cache_ = ReplaceMemoryCacheForTesting(MemoryCache::Create());
fetcher_ = ResourceFetcher::Create(
MockFetchContext::Create(MockFetchContext::kShouldNotLoadNewResource));
MockFetchContext* context =
MockFetchContext::Create(MockFetchContext::kShouldNotLoadNewResource);
security_origin_ = SecurityOrigin::CreateUnique();
context->SetSecurityOrigin(security_origin_);
fetcher_ = ResourceFetcher::Create(context);
}
void TearDown() override {
GetMemoryCache()->EvictResources();
......@@ -113,6 +121,7 @@ class MemoryCacheCorrectnessTest : public ::testing::Test {
}
Persistent<MemoryCache> global_memory_cache_;
scoped_refptr<const SecurityOrigin> security_origin_;
Persistent<ResourceFetcher> fetcher_;
ScopedTestingPlatformSupport<TestingPlatformSupportWithMockScheduler>
platform_;
......@@ -376,7 +385,7 @@ TEST_F(MemoryCacheCorrectnessTest, FreshWithFreshRedirect) {
first_resource->SetResponse(fresh200_response);
first_resource->FinishForTest();
GetMemoryCache()->Add(first_resource);
AddResourceToMemoryCache(first_resource);
AdvanceClock(500.);
......@@ -414,7 +423,7 @@ TEST_F(MemoryCacheCorrectnessTest, FreshWithStaleRedirect) {
first_resource->SetResponse(fresh200_response);
first_resource->FinishForTest();
GetMemoryCache()->Add(first_resource);
AddResourceToMemoryCache(first_resource);
AdvanceClock(500.);
......@@ -427,7 +436,7 @@ TEST_F(MemoryCacheCorrectnessTest, PostToSameURLTwice) {
request1.SetHTTPMethod(HTTPNames::POST);
RawResource* resource1 = RawResource::CreateForTest(request1, Resource::kRaw);
resource1->SetStatus(ResourceStatus::kPending);
GetMemoryCache()->Add(resource1);
AddResourceToMemoryCache(resource1);
ResourceRequest request2{KURL(kResourceURL)};
request2.SetHTTPMethod(HTTPNames::POST);
......@@ -467,7 +476,7 @@ TEST_F(MemoryCacheCorrectnessTest, 302RedirectNotImplicitlyFresh) {
first_resource->SetResponse(fresh200_response);
first_resource->FinishForTest();
GetMemoryCache()->Add(first_resource);
AddResourceToMemoryCache(first_resource);
AdvanceClock(500.);
......@@ -505,7 +514,7 @@ TEST_F(MemoryCacheCorrectnessTest, 302RedirectExplicitlyFreshMaxAge) {
first_resource->SetResponse(fresh200_response);
first_resource->FinishForTest();
GetMemoryCache()->Add(first_resource);
AddResourceToMemoryCache(first_resource);
AdvanceClock(500.);
......@@ -544,7 +553,7 @@ TEST_F(MemoryCacheCorrectnessTest, 302RedirectExplicitlyFreshExpires) {
first_resource->SetResponse(fresh200_response);
first_resource->FinishForTest();
GetMemoryCache()->Add(first_resource);
AddResourceToMemoryCache(first_resource);
AdvanceClock(500.);
......
......@@ -323,7 +323,9 @@ static bool IsCacheableHTTPMethod(const AtomicString& method) {
method != "DELETE";
}
bool RawResource::CanReuse(const FetchParameters& new_fetch_parameters) const {
bool RawResource::CanReuse(
const FetchParameters& new_fetch_parameters,
scoped_refptr<const SecurityOrigin> new_source_origin) const {
const ResourceRequest& new_request =
new_fetch_parameters.GetResourceRequest();
......@@ -364,7 +366,7 @@ bool RawResource::CanReuse(const FetchParameters& new_fetch_parameters) const {
return false;
}
return Resource::CanReuse(new_fetch_parameters);
return Resource::CanReuse(new_fetch_parameters, std::move(new_source_origin));
}
RawResourceClientStateChecker::RawResourceClientStateChecker()
......
......@@ -76,7 +76,9 @@ class PLATFORM_EXPORT RawResource final : public Resource {
}
// Resource implementation
bool CanReuse(const FetchParameters&) const override;
bool CanReuse(
const FetchParameters&,
scoped_refptr<const SecurityOrigin> new_source_origin) const override;
bool WillFollowRedirect(const ResourceRequest&,
const ResourceResponse&) override;
......
......@@ -30,6 +30,7 @@
#include "platform/loader/fetch/RawResource.h"
#include <memory>
#include "platform/SharedBuffer.h"
#include "platform/heap/Handle.h"
#include "platform/loader/fetch/MemoryCache.h"
......@@ -38,6 +39,7 @@
#include "platform/scheduler/child/web_scheduler.h"
#include "platform/testing/TestingPlatformSupportWithMockScheduler.h"
#include "platform/testing/UnitTestHelpers.h"
#include "platform/weborigin/SecurityOrigin.h"
#include "public/platform/Platform.h"
#include "public/platform/WebThread.h"
#include "public/platform/WebURL.h"
......@@ -63,13 +65,18 @@ TEST_F(RawResourceTest, DontIgnoreAcceptForCacheReuse) {
ResourceRequest jpeg_request;
jpeg_request.SetHTTPAccept("image/jpeg");
scoped_refptr<const SecurityOrigin> source_origin =
SecurityOrigin::CreateUnique();
RawResource* jpeg_resource(
RawResource::CreateForTest(jpeg_request, Resource::kRaw));
jpeg_resource->SetSourceOrigin(source_origin);
ResourceRequest png_request;
png_request.SetHTTPAccept("image/png");
EXPECT_FALSE(jpeg_resource->CanReuse(FetchParameters(png_request)));
EXPECT_FALSE(
jpeg_resource->CanReuse(FetchParameters(png_request), source_origin));
}
class DummyClient final : public GarbageCollectedFinalized<DummyClient>,
......@@ -209,12 +216,15 @@ TEST_F(RawResourceTest, RemoveClientDuringCallback) {
TEST_F(RawResourceTest,
CanReuseDevToolsEmulateNetworkConditionsClientIdHeader) {
scoped_refptr<const SecurityOrigin> source_origin =
SecurityOrigin::CreateUnique();
ResourceRequest request("data:text/html,");
request.SetHTTPHeaderField(
HTTPNames::X_DevTools_Emulate_Network_Conditions_Client_Id, "Foo");
Resource* raw = RawResource::CreateForTest(request, Resource::kRaw);
EXPECT_TRUE(
raw->CanReuse(FetchParameters(ResourceRequest("data:text/html,"))));
raw->SetSourceOrigin(source_origin);
EXPECT_TRUE(raw->CanReuse(FetchParameters(ResourceRequest("data:text/html,")),
source_origin));
}
} // namespace blink
......@@ -861,7 +861,9 @@ void Resource::FinishPendingClients() {
DCHECK(clients_awaiting_callback_.IsEmpty() || scheduled);
}
bool Resource::CanReuse(const FetchParameters& params) const {
bool Resource::CanReuse(
const FetchParameters& params,
scoped_refptr<const SecurityOrigin> new_source_origin) const {
const ResourceRequest& new_request = params.GetResourceRequest();
const ResourceLoaderOptions& new_options = params.Options();
......@@ -930,6 +932,13 @@ bool Resource::CanReuse(const FetchParameters& params) const {
return false;
}
DCHECK(source_origin_);
DCHECK(new_source_origin);
// Don't reuse an existing resource when the source origin is different.
if (!source_origin_->IsSameSchemeHostPort(new_source_origin.get()))
return false;
// securityOrigin has more complicated checks which callers are responsible
// for.
......
......@@ -292,6 +292,10 @@ class PLATFORM_EXPORT Resource : public GarbageCollectedFinalized<Resource>,
}
String CacheIdentifier() const { return cache_identifier_; }
void SetSourceOrigin(scoped_refptr<const SecurityOrigin> source_origin) {
source_origin_ = source_origin;
}
virtual void DidSendData(unsigned long long /* bytesSent */,
unsigned long long /* totalBytesToBeSent */) {}
virtual void DidDownloadData(int) {}
......@@ -308,7 +312,9 @@ class PLATFORM_EXPORT Resource : public GarbageCollectedFinalized<Resource>,
response_.SetDecodedBodyLength(value);
}
virtual bool CanReuse(const FetchParameters&) const;
virtual bool CanReuse(
const FetchParameters&,
scoped_refptr<const SecurityOrigin> new_source_origin) const;
// If cache-aware loading is activated, this callback is called when the first
// disk-cache-only request failed due to cache miss. After this callback,
......@@ -448,9 +454,34 @@ class PLATFORM_EXPORT Resource : public GarbageCollectedFinalized<Resource>,
Type type_;
ResourceStatus status_;
// A SecurityOrigin representing the origin from which the loading of the
// Resource was initiated. This is calculated and set by ResourceFetcher at
// the beginning of the loading.
//
// Unlike |security_origin| on |options_|, which:
// - holds a SecurityOrigin to override the FetchContext's SecurityOrigin
// (in case of e.g. that the script initiated the loading is in an isolated
// world)
// - may be modified during redirect as required by the CORS protocol
// this variable is stable after the loading has started.
//
// Used and should be used only for isolating resources for different origins
// in the MemoryCache.
//
// TODO(crbug.com/811669): Merge with some of the other SecurityOrigin
// variables.
scoped_refptr<const SecurityOrigin> source_origin_;
CORSStatus cors_status_;
Member<CachedMetadataHandlerImpl> cache_handler_;
// Holds the SecurityOrigin obtained from the associated FetchContext.
// ResourceFetcher sets this at the beginning of loading. The override by
// specifying a SecurityOrigin in ResourceLoaderOptions doesn't affect this.
//
// TODO(crbug.com/811669): Merge this with |source_origin_|.
scoped_refptr<const SecurityOrigin> fetcher_security_origin_;
Optional<ResourceError> error_;
......
......@@ -468,10 +468,8 @@ Resource* ResourceFetcher::ResourceForStaticData(
resource->SetCacheIdentifier(cache_identifier);
resource->Finish(0.0, Context().GetLoadingTaskRunner().get());
if (ShouldResourceBeAddedToMemoryCache(params, resource) &&
!substitute_data.IsValid()) {
GetMemoryCache()->Add(resource);
}
if (!substitute_data.IsValid())
AddToMemoryCacheIfNeeded(params, resource);
return resource;
}
......@@ -864,8 +862,26 @@ void ResourceFetcher::InitializeRevalidation(
resource->SetRevalidatingRequest(revalidating_request);
}
scoped_refptr<const SecurityOrigin> ResourceFetcher::GetSourceOrigin(
const ResourceLoaderOptions& options) const {
if (options.security_origin)
return options.security_origin;
return Context().GetSecurityOrigin();
}
void ResourceFetcher::AddToMemoryCacheIfNeeded(const FetchParameters& params,
Resource* resource) {
if (!ShouldResourceBeAddedToMemoryCache(params, resource))
return;
resource->SetSourceOrigin(GetSourceOrigin(params.Options()));
GetMemoryCache()->Add(resource);
}
Resource* ResourceFetcher::CreateResourceForLoading(
FetchParameters& params,
const FetchParameters& params,
const ResourceFactory& factory) {
const String cache_identifier = GetCacheIdentifier();
DCHECK(!IsMainThread() ||
......@@ -883,8 +899,7 @@ Resource* ResourceFetcher::CreateResourceForLoading(
}
resource->SetCacheIdentifier(cache_identifier);
if (ShouldResourceBeAddedToMemoryCache(params, resource))
GetMemoryCache()->Add(resource);
AddToMemoryCacheIfNeeded(params, resource);
return resource;
}
......@@ -970,7 +985,7 @@ Resource* ResourceFetcher::MatchPreload(const FetchParameters& params,
return nullptr;
if (IsImageResourceDisallowedToBeReused(*resource) ||
!resource->CanReuse(params))
!resource->CanReuse(params, GetSourceOrigin(params.Options())))
return nullptr;
if (!resource->MatchPreload(params, Context().GetLoadingTaskRunner().get()))
......@@ -993,12 +1008,15 @@ void ResourceFetcher::InsertAsPreloadIfNecessary(Resource* resource,
return;
}
PreloadKey key(params.Url(), type);
if (preloads_.find(key) == preloads_.end()) {
preloads_.insert(key, resource);
resource->MarkAsPreload();
if (preloaded_urls_for_test_)
preloaded_urls_for_test_->insert(resource->Url().GetString());
}
if (preloads_.find(key) != preloads_.end())
return;
resource->SetSourceOrigin(GetSourceOrigin(params.Options()));
preloads_.insert(key, resource);
resource->MarkAsPreload();
if (preloaded_urls_for_test_)
preloaded_urls_for_test_->insert(resource->Url().GetString());
}
bool ResourceFetcher::IsImageResourceDisallowedToBeReused(
......@@ -1101,7 +1119,8 @@ ResourceFetcher::DetermineRevalidationPolicyInternal(
if (is_static_data)
return kUse;
if (!existing_resource.CanReuse(fetch_params)) {
if (!existing_resource.CanReuse(fetch_params,
GetSourceOrigin(fetch_params.Options()))) {
RESOURCE_LOADING_DVLOG(1) << "ResourceFetcher::DetermineRevalidationPolicy "
"reloading due to Resource::CanReuse() "
"returning false.";
......
......@@ -181,7 +181,12 @@ class PLATFORM_EXPORT ResourceFetcher
ResourceFetcher(FetchContext*);
void InitializeRevalidation(ResourceRequest&, Resource*);
Resource* CreateResourceForLoading(FetchParameters&,
// When |security_origin| of the ResourceLoaderOptions is not a nullptr, it'll
// be used instead of the associated FetchContext's SecurityOrigin.
scoped_refptr<const SecurityOrigin> GetSourceOrigin(
const ResourceLoaderOptions&) const;
void AddToMemoryCacheIfNeeded(const FetchParameters&, Resource*);
Resource* CreateResourceForLoading(const FetchParameters&,
const ResourceFactory&);
void StorePerformanceTimingInitiatorInformation(Resource*);
ResourceLoadPriority ComputeLoadPriority(
......
......@@ -95,6 +95,12 @@ class ResourceFetcherTest : public ::testing::Test {
protected:
MockFetchContext* Context() { return platform_->Context(); }
void AddResourceToMemoryCache(
Resource* resource,
scoped_refptr<const SecurityOrigin> source_origin) {
resource->SetSourceOrigin(source_origin);
GetMemoryCache()->Add(resource);
}
ScopedTestingPlatformSupport<FetchTestingPlatformSupport> platform_;
......@@ -144,9 +150,14 @@ TEST_F(ResourceFetcherTest, UseExistingResource) {
}
TEST_F(ResourceFetcherTest, Vary) {
scoped_refptr<const SecurityOrigin> source_origin =
SecurityOrigin::CreateUnique();
Context()->SetSecurityOrigin(source_origin);
KURL url("http://127.0.0.1:8000/foo.html");
Resource* resource = RawResource::CreateForTest(url, Resource::kRaw);
GetMemoryCache()->Add(resource);
AddResourceToMemoryCache(resource, source_origin);
ResourceResponse response(url);
response.SetHTTPStatusCode(200);
response.SetHTTPHeaderField(HTTPNames::Cache_Control, "max-age=3600");
......@@ -202,11 +213,16 @@ TEST_F(ResourceFetcherTest, NavigationTimingInfo) {
}
TEST_F(ResourceFetcherTest, VaryOnBack) {
scoped_refptr<const SecurityOrigin> source_origin =
SecurityOrigin::CreateUnique();
Context()->SetSecurityOrigin(source_origin);
ResourceFetcher* fetcher = ResourceFetcher::Create(Context());
KURL url("http://127.0.0.1:8000/foo.html");
Resource* resource = RawResource::CreateForTest(url, Resource::kRaw);
GetMemoryCache()->Add(resource);
AddResourceToMemoryCache(resource, source_origin);
ResourceResponse response(url);
response.SetHTTPStatusCode(200);
response.SetHTTPHeaderField(HTTPNames::Cache_Control, "max-age=3600");
......@@ -253,7 +269,8 @@ class RequestSameResourceOnComplete
public:
explicit RequestSameResourceOnComplete(FetchParameters& params,
ResourceFetcher* fetcher)
: notify_finished_called_(false) {
: notify_finished_called_(false),
source_origin_(fetcher->Context().GetSecurityOrigin()) {
MockResource::Fetch(params, fetcher, this);
}
......@@ -261,6 +278,7 @@ class RequestSameResourceOnComplete
EXPECT_EQ(GetResource(), resource);
MockFetchContext* context =
MockFetchContext::Create(MockFetchContext::kShouldLoadNewResource);
context->SetSecurityOrigin(source_origin_);
ResourceFetcher* fetcher2 = ResourceFetcher::Create(context);
ResourceRequest resource_request2(GetResource()->Url());
resource_request2.SetCacheMode(mojom::FetchCacheMode::kValidateCache);
......@@ -280,15 +298,22 @@ class RequestSameResourceOnComplete
private:
bool notify_finished_called_;
scoped_refptr<const SecurityOrigin> source_origin_;
};
TEST_F(ResourceFetcherTest, RevalidateWhileFinishingLoading) {
scoped_refptr<const SecurityOrigin> source_origin =
SecurityOrigin::CreateUnique();
Context()->SetSecurityOrigin(source_origin);
KURL url("http://127.0.0.1:8000/foo.png");
ResourceResponse response(url);
response.SetHTTPStatusCode(200);
response.SetHTTPHeaderField(HTTPNames::Cache_Control, "max-age=3600");
response.SetHTTPHeaderField(HTTPNames::ETag, "1234567890");
RegisterMockedURLLoadWithCustomResponse(url, response);
ResourceFetcher* fetcher1 = ResourceFetcher::Create(Context());
ResourceRequest request1(url);
request1.SetHTTPHeaderField(HTTPNames::Cache_Control, "no-cache");
......@@ -693,9 +718,14 @@ TEST_F(ResourceFetcherTest, SpeculativePreloadShouldBePromotedToLinkePreload) {
}
TEST_F(ResourceFetcherTest, Revalidate304) {
scoped_refptr<const SecurityOrigin> source_origin =
SecurityOrigin::CreateUnique();
Context()->SetSecurityOrigin(source_origin);
KURL url("http://127.0.0.1:8000/foo.html");
Resource* resource = RawResource::CreateForTest(url, Resource::kRaw);
GetMemoryCache()->Add(resource);
AddResourceToMemoryCache(resource, source_origin);
ResourceResponse response(url);
response.SetHTTPStatusCode(304);
response.SetHTTPHeaderField("etag", "1234567890");
......
......@@ -371,7 +371,14 @@ class PLATFORM_EXPORT ResourceRequest final {
double timeout_interval_; // 0 is a magic value for platform default on
// platforms that have one.
KURL site_for_cookies_;
// The SecurityOrigin specified by the ResourceLoaderOptions in case e.g.
// when the fetching was initiated in an isolated world. Set by
// ResourceFetcher but only when needed.
//
// TODO(crbug.com/811669): Merge with some of the other origin variables.
scoped_refptr<const SecurityOrigin> requestor_origin_;
AtomicString http_method_;
HTTPHeaderMap http_header_fields_;
scoped_refptr<EncodedFormData> http_body_;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment