From 4cbcb4615a074fe5089889e1e801cc2a6420cacc Mon Sep 17 00:00:00 2001 From: Felix Bruns Date: Thu, 7 Mar 2019 22:05:36 +0000 Subject: [PATCH] Add 'UR_FLAG_DISABLE_CACHE' and support for equivalent 'Cache-Control: no-store' (issue #2283) --- include/internal/cef_types.h | 21 ++- libcef/common/request_impl.cc | 21 ++- tests/ceftests/urlrequest_unittest.cc | 244 ++++++++++++++++++++++---- 3 files changed, 239 insertions(+), 47 deletions(-) diff --git a/include/internal/cef_types.h b/include/internal/cef_types.h index 9d93ec958..de476a8d6 100644 --- a/include/internal/cef_types.h +++ b/include/internal/cef_types.h @@ -1261,38 +1261,47 @@ typedef enum { // If set the request will fail if it cannot be served from the cache (or some // equivalent local store). Setting this value is equivalent to specifying the // "Cache-Control: only-if-cached" request header. Setting this value in - // combination with UR_FLAG_SKIP_CACHE will cause the request to fail. + // combination with UR_FLAG_SKIP_CACHE or UR_FLAG_DISABLE_CACHE will cause the + // request to fail. /// UR_FLAG_ONLY_FROM_CACHE = 1 << 1, + /// + // If set the cache will not be used at all. Setting this value is equivalent + // to specifying the "Cache-Control: no-store" request header. Setting this + // value in combination with UR_FLAG_ONLY_FROM_CACHE will cause the request to + // fail. + /// + UR_FLAG_DISABLE_CACHE = 1 << 2, + /// // If set user name, password, and cookies may be sent with the request, and // cookies may be saved from the response. /// - UR_FLAG_ALLOW_STORED_CREDENTIALS = 1 << 2, + UR_FLAG_ALLOW_STORED_CREDENTIALS = 1 << 3, /// // If set upload progress events will be generated when a request has a body. /// - UR_FLAG_REPORT_UPLOAD_PROGRESS = 1 << 3, + UR_FLAG_REPORT_UPLOAD_PROGRESS = 1 << 4, /// // If set the CefURLRequestClient::OnDownloadData method will not be called. /// - UR_FLAG_NO_DOWNLOAD_DATA = 1 << 4, + UR_FLAG_NO_DOWNLOAD_DATA = 1 << 5, /// // If set 5XX redirect errors will be propagated to the observer instead of // automatically re-tried. This currently only applies for requests // originated in the browser process. /// - UR_FLAG_NO_RETRY_ON_5XX = 1 << 5, + UR_FLAG_NO_RETRY_ON_5XX = 1 << 6, /// // If set 3XX responses will cause the fetch to halt immediately rather than // continue through the redirect. /// - UR_FLAG_STOP_ON_REDIRECT = 1 << 6, + UR_FLAG_STOP_ON_REDIRECT = 1 << 7, } cef_urlrequest_flags_t; /// diff --git a/libcef/common/request_impl.cc b/libcef/common/request_impl.cc index e586c8107..080f567b7 100644 --- a/libcef/common/request_impl.cc +++ b/libcef/common/request_impl.cc @@ -48,11 +48,13 @@ const char kReferrerLowerCase[] = "referer"; const char kContentTypeLowerCase[] = "content-type"; const char kCacheControlLowerCase[] = "cache-control"; const char kCacheControlDirectiveNoCacheLowerCase[] = "no-cache"; +const char kCacheControlDirectiveNoStoreLowerCase[] = "no-store"; const char kCacheControlDirectiveOnlyIfCachedLowerCase[] = "only-if-cached"; const char kApplicationFormURLEncoded[] = "application/x-www-form-urlencoded"; // Mask of values that configure the cache policy. -const int kURCachePolicyMask = (UR_FLAG_SKIP_CACHE | UR_FLAG_ONLY_FROM_CACHE); +const int kURCachePolicyMask = + (UR_FLAG_SKIP_CACHE | UR_FLAG_ONLY_FROM_CACHE | UR_FLAG_DISABLE_CACHE); // A subclass of net::UploadBytesElementReader that keeps the associated // UploadElement alive until the request completes. @@ -142,6 +144,9 @@ int GetCacheControlHeaderPolicy(CefRequest::HeaderMap headerMap) { } else if (base::LowerCaseEqualsASCII( piece, kCacheControlDirectiveOnlyIfCachedLowerCase)) { flags |= UR_FLAG_ONLY_FROM_CACHE; + } else if (base::LowerCaseEqualsASCII( + piece, kCacheControlDirectiveNoStoreLowerCase)) { + flags |= UR_FLAG_DISABLE_CACHE; } } } @@ -151,12 +156,17 @@ int GetCacheControlHeaderPolicy(CefRequest::HeaderMap headerMap) { // Convert cef_urlrequest_flags_t to blink::WebCachePolicy. blink::mojom::FetchCacheMode GetFetchCacheMode(int ur_flags) { - if ((ur_flags & kURCachePolicyMask) == kURCachePolicyMask) { + const bool skip_cache{ur_flags & UR_FLAG_SKIP_CACHE}; + const bool only_from_cache{ur_flags & UR_FLAG_ONLY_FROM_CACHE}; + const bool disable_cache{ur_flags & UR_FLAG_DISABLE_CACHE}; + if (only_from_cache && (skip_cache || disable_cache)) { return blink::mojom::FetchCacheMode::kUnspecifiedForceCacheMiss; - } else if (ur_flags & UR_FLAG_SKIP_CACHE) { + } else if (skip_cache) { return blink::mojom::FetchCacheMode::kBypassCache; - } else if (ur_flags & UR_FLAG_ONLY_FROM_CACHE) { + } else if (only_from_cache) { return blink::mojom::FetchCacheMode::kOnlyIfCached; + } else if (disable_cache) { + return blink::mojom::FetchCacheMode::kNoStore; } return blink::mojom::FetchCacheMode::kDefault; } @@ -756,6 +766,9 @@ void CefRequestImpl::Get(net::URLFetcher& fetcher, if (flags & UR_FLAG_ONLY_FROM_CACHE) { net_flags |= net::LOAD_ONLY_FROM_CACHE | net::LOAD_SKIP_CACHE_VALIDATION; } + if (flags & UR_FLAG_DISABLE_CACHE) { + net_flags |= net::LOAD_DISABLE_CACHE; + } if (!(flags & UR_FLAG_ALLOW_STORED_CREDENTIALS)) { net_flags |= net::LOAD_DO_NOT_SEND_AUTH_DATA | diff --git a/tests/ceftests/urlrequest_unittest.cc b/tests/ceftests/urlrequest_unittest.cc index dc7984eea..c24490d05 100644 --- a/tests/ceftests/urlrequest_unittest.cc +++ b/tests/ceftests/urlrequest_unittest.cc @@ -72,6 +72,8 @@ enum RequestTestMode { REQTEST_CACHE_ONLY_FAILURE_HEADER, REQTEST_CACHE_ONLY_SUCCESS_FLAG, REQTEST_CACHE_ONLY_SUCCESS_HEADER, + REQTEST_CACHE_DISABLE_FLAG, + REQTEST_CACHE_DISABLE_HEADER, }; enum ContextTestMode { @@ -1086,6 +1088,10 @@ class RequestTestRunner : public base::RefCountedThreadSafe { SetupCacheOnlySuccessFlagTest, MultipleRunTest); REGISTER_TEST(REQTEST_CACHE_ONLY_SUCCESS_HEADER, SetupCacheOnlySuccessHeaderTest, MultipleRunTest); + REGISTER_TEST(REQTEST_CACHE_DISABLE_FLAG, SetupCacheDisableFlagTest, + MultipleRunTest); + REGISTER_TEST(REQTEST_CACHE_DISABLE_HEADER, SetupCacheDisableHeaderTest, + MultipleRunTest); } void Destroy() { @@ -1466,11 +1472,49 @@ class RequestTestRunner : public base::RefCountedThreadSafe { SetupCacheShared("CacheSkipFlagTest.html", true); // Skip the cache despite the the Cache-Control response header. + // This will not read from the cache, but still write to the cache. settings_.request->SetFlags(UR_FLAG_SKIP_CACHE); - // Send multiple requests. All should be received. + // Send multiple requests. The 1st request will be handled normally, + // but not result in any reads from the cache. The 2nd request will + // expect a cached response and the 3nd request will skip the cache + // again. settings_.expected_send_count = 3; - settings_.expected_receive_count = 3; + settings_.expected_receive_count = 2; + settings_.setup_next_request = + base::Bind(&RequestTestRunner::SetupCacheSkipFlagTestNext, this); + + complete_callback.Run(); + } + + void SetupCacheSkipFlagTestNext(int next_send_count, + const base::Closure& complete_callback) { + // Recreate the request object because the existing object will now be + // read-only. + EXPECT_TRUE(settings_.request->IsReadOnly()); + SetupCacheShared("CacheSkipFlagTest.html", true); + + // Expect a cached response. + settings_.expect_response_was_cached = true; + settings_.setup_next_request = + base::Bind(&RequestTestRunner::SetupCacheSkipFlagTestLast, this); + + complete_callback.Run(); + } + + void SetupCacheSkipFlagTestLast(int next_send_count, + const base::Closure& complete_callback) { + // Recreate the request object because the existing object will now be + // read-only. + EXPECT_TRUE(settings_.request->IsReadOnly()); + SetupCacheShared("CacheSkipFlagTest.html", true); + + // Skip the cache despite the the Cache-Control response header. + settings_.request->SetFlags(UR_FLAG_SKIP_CACHE); + + // Expect the cache to be skipped. + settings_.expect_response_was_cached = false; + settings_.setup_next_request.Reset(); complete_callback.Run(); } @@ -1479,13 +1523,51 @@ class RequestTestRunner : public base::RefCountedThreadSafe { SetupCacheShared("CacheSkipHeaderTest.html", true); // Skip the cache despite the the Cache-Control response header. + // This will not read from the cache, but still write to the cache. CefRequest::HeaderMap headerMap; headerMap.insert(std::make_pair(kCacheControlHeader, "no-cache")); settings_.request->SetHeaderMap(headerMap); - // Send multiple requests. All should be received. + // Send multiple requests. The 1st request will be handled normally, + // but not result in any reads from the cache. The 2nd request will + // expect a cached response and the 3nd request will skip the cache + // again. settings_.expected_send_count = 3; - settings_.expected_receive_count = 3; + settings_.expected_receive_count = 2; + settings_.setup_next_request = + base::Bind(&RequestTestRunner::SetupCacheSkipHeaderTestNext, this); + + complete_callback.Run(); + } + + void SetupCacheSkipHeaderTestNext(int next_send_count, + const base::Closure& complete_callback) { + // Recreate the request object because the existing object will now be + // read-only. + EXPECT_TRUE(settings_.request->IsReadOnly()); + SetupCacheShared("CacheSkipHeaderTest.html", true); + + // Expect a cached response. + settings_.expect_response_was_cached = true; + settings_.setup_next_request = + base::Bind(&RequestTestRunner::SetupCacheSkipHeaderTestLast, this); + + complete_callback.Run(); + } + + void SetupCacheSkipHeaderTestLast(int next_send_count, + const base::Closure& complete_callback) { + // Recreate the request object because the existing object will now be + // read-only. + EXPECT_TRUE(settings_.request->IsReadOnly()); + SetupCacheShared("CacheSkipHeaderTest.html", true); + + // Skip the cache despite the the Cache-Control response header. + settings_.request->SetFlags(UR_FLAG_SKIP_CACHE); + + // Expect the cache to be skipped. + settings_.expect_response_was_cached = false; + settings_.setup_next_request.Reset(); complete_callback.Run(); } @@ -1592,6 +1674,86 @@ class RequestTestRunner : public base::RefCountedThreadSafe { complete_callback.Run(); } + void SetupCacheDisableFlagTest(const base::Closure& complete_callback) { + SetupCacheShared("CacheDisableFlagTest.html", true); + + // Disable the cache despite the the Cache-Control response header. + settings_.request->SetFlags(UR_FLAG_DISABLE_CACHE); + + // Send multiple requests. The 1st request will be handled normally, + // but not result in any reads from or writes to the cache. + // Therefore all following requests that are set to be only handled + // from the cache should fail. + settings_.expected_send_count = 3; + settings_.expected_receive_count = 1; + settings_.setup_next_request = + base::Bind(&RequestTestRunner::SetupCacheDisableFlagTestNext, this); + + complete_callback.Run(); + } + + void SetupCacheDisableFlagTestNext(int next_send_count, + const base::Closure& complete_callback) { + // Recreate the request object because the existing object will now be + // read-only. + EXPECT_TRUE(settings_.request->IsReadOnly()); + SetupCacheShared("CacheDisableFlagTest.html", true); + + // Only handle from the cache. + settings_.request->SetFlags(UR_FLAG_ONLY_FROM_CACHE); + + // The request is expected to fail. + settings_.SetRequestFailureExpected(ERR_CACHE_MISS); + + // The following requests will use the same setup, so no more callbacks + // are required. + settings_.setup_next_request.Reset(); + + complete_callback.Run(); + } + + void SetupCacheDisableHeaderTest(const base::Closure& complete_callback) { + SetupCacheShared("CacheDisableHeaderTest.html", true); + + // Disable the cache despite the the Cache-Control response header. + CefRequest::HeaderMap headerMap; + headerMap.insert(std::make_pair(kCacheControlHeader, "no-store")); + settings_.request->SetHeaderMap(headerMap); + + // Send multiple requests. The 1st request will be handled normally, + // but not result in any reads from or writes to the cache. + // Therefore all following requests that are set to be only handled + // from the cache should fail. + settings_.expected_send_count = 3; + settings_.expected_receive_count = 1; + settings_.setup_next_request = + base::Bind(&RequestTestRunner::SetupCacheDisableHeaderTestNext, this); + + complete_callback.Run(); + } + + void SetupCacheDisableHeaderTestNext(int next_send_count, + const base::Closure& complete_callback) { + // Recreate the request object because the existing object will now be + // read-only. + EXPECT_TRUE(settings_.request->IsReadOnly()); + SetupCacheShared("CacheDisableHeaderTest.html", true); + + // Only handle from the cache. + CefRequest::HeaderMap headerMap; + headerMap.insert(std::make_pair(kCacheControlHeader, "only-if-cached")); + settings_.request->SetHeaderMap(headerMap); + + // The request is expected to fail. + settings_.SetRequestFailureExpected(ERR_CACHE_MISS); + + // The following requests will use the same setup, so no more callbacks + // are required. + settings_.setup_next_request.Reset(); + + complete_callback.Run(); + } + // Send a request. |complete_callback| will be executed on request completion. void SendRequest( const RequestClient::RequestCompleteCallback& complete_callback) { @@ -2320,39 +2482,47 @@ REQ_TEST_SET(ContextInMemoryServer, CONTEXT_INMEMORY, true); REQ_TEST_SET(ContextOnDiskServer, CONTEXT_ONDISK, true); // Cache tests can only be run with the server backend. -#define REQ_TEST_CACHE_SET(suffix, context_mode) \ - REQ_TEST(BrowserGETCacheWithControl##suffix, REQTEST_CACHE_WITH_CONTROL, \ - context_mode, true, true); \ - REQ_TEST(BrowserGETCacheWithoutControl##suffix, \ - REQTEST_CACHE_WITHOUT_CONTROL, context_mode, true, true); \ - REQ_TEST(BrowserGETCacheSkipFlag##suffix, REQTEST_CACHE_SKIP_FLAG, \ - context_mode, true, true); \ - REQ_TEST(BrowserGETCacheSkipHeader##suffix, REQTEST_CACHE_SKIP_HEADER, \ - context_mode, true, true); \ - REQ_TEST(BrowserGETCacheOnlyFailureFlag##suffix, \ - REQTEST_CACHE_ONLY_FAILURE_FLAG, context_mode, true, true); \ - REQ_TEST(BrowserGETCacheOnlyFailureHeader##suffix, \ - REQTEST_CACHE_ONLY_FAILURE_HEADER, context_mode, true, true); \ - REQ_TEST(BrowserGETCacheOnlySuccessFlag##suffix, \ - REQTEST_CACHE_ONLY_SUCCESS_FLAG, context_mode, true, true) \ - REQ_TEST(BrowserGETCacheOnlySuccessHeader##suffix, \ - REQTEST_CACHE_ONLY_SUCCESS_HEADER, context_mode, true, true) \ - REQ_TEST(RendererGETCacheWithControl##suffix, REQTEST_CACHE_WITH_CONTROL, \ - context_mode, false, true); \ - REQ_TEST(RendererGETCacheWithoutControl##suffix, \ - REQTEST_CACHE_WITHOUT_CONTROL, context_mode, false, true); \ - REQ_TEST(RendererGETCacheSkipFlag##suffix, REQTEST_CACHE_SKIP_FLAG, \ - context_mode, false, true); \ - REQ_TEST(RendererGETCacheSkipHeader##suffix, REQTEST_CACHE_SKIP_HEADER, \ - context_mode, false, true); \ - REQ_TEST(RendererGETCacheOnlyFailureFlag##suffix, \ - REQTEST_CACHE_ONLY_FAILURE_FLAG, context_mode, false, true); \ - REQ_TEST(RendererGETCacheOnlyFailureHeader##suffix, \ - REQTEST_CACHE_ONLY_FAILURE_HEADER, context_mode, false, true); \ - REQ_TEST(RendererGETCacheOnlySuccessFlag##suffix, \ - REQTEST_CACHE_ONLY_SUCCESS_FLAG, context_mode, false, true); \ - REQ_TEST(RendererGETCacheOnlySuccessHeader##suffix, \ - REQTEST_CACHE_ONLY_SUCCESS_HEADER, context_mode, false, true) +#define REQ_TEST_CACHE_SET(suffix, context_mode) \ + REQ_TEST(BrowserGETCacheWithControl##suffix, REQTEST_CACHE_WITH_CONTROL, \ + context_mode, true, true); \ + REQ_TEST(BrowserGETCacheWithoutControl##suffix, \ + REQTEST_CACHE_WITHOUT_CONTROL, context_mode, true, true); \ + REQ_TEST(BrowserGETCacheSkipFlag##suffix, REQTEST_CACHE_SKIP_FLAG, \ + context_mode, true, true); \ + REQ_TEST(BrowserGETCacheSkipHeader##suffix, REQTEST_CACHE_SKIP_HEADER, \ + context_mode, true, true); \ + REQ_TEST(BrowserGETCacheOnlyFailureFlag##suffix, \ + REQTEST_CACHE_ONLY_FAILURE_FLAG, context_mode, true, true); \ + REQ_TEST(BrowserGETCacheOnlyFailureHeader##suffix, \ + REQTEST_CACHE_ONLY_FAILURE_HEADER, context_mode, true, true); \ + REQ_TEST(BrowserGETCacheOnlySuccessFlag##suffix, \ + REQTEST_CACHE_ONLY_SUCCESS_FLAG, context_mode, true, true) \ + REQ_TEST(BrowserGETCacheOnlySuccessHeader##suffix, \ + REQTEST_CACHE_ONLY_SUCCESS_HEADER, context_mode, true, true) \ + REQ_TEST(BrowserGETCacheDisableFlag##suffix, REQTEST_CACHE_DISABLE_FLAG, \ + context_mode, true, true); \ + REQ_TEST(BrowserGETCacheDisableHeader##suffix, REQTEST_CACHE_DISABLE_HEADER, \ + context_mode, true, true); \ + REQ_TEST(RendererGETCacheWithControl##suffix, REQTEST_CACHE_WITH_CONTROL, \ + context_mode, false, true); \ + REQ_TEST(RendererGETCacheWithoutControl##suffix, \ + REQTEST_CACHE_WITHOUT_CONTROL, context_mode, false, true); \ + REQ_TEST(RendererGETCacheSkipFlag##suffix, REQTEST_CACHE_SKIP_FLAG, \ + context_mode, false, true); \ + REQ_TEST(RendererGETCacheSkipHeader##suffix, REQTEST_CACHE_SKIP_HEADER, \ + context_mode, false, true); \ + REQ_TEST(RendererGETCacheOnlyFailureFlag##suffix, \ + REQTEST_CACHE_ONLY_FAILURE_FLAG, context_mode, false, true); \ + REQ_TEST(RendererGETCacheOnlyFailureHeader##suffix, \ + REQTEST_CACHE_ONLY_FAILURE_HEADER, context_mode, false, true); \ + REQ_TEST(RendererGETCacheOnlySuccessFlag##suffix, \ + REQTEST_CACHE_ONLY_SUCCESS_FLAG, context_mode, false, true); \ + REQ_TEST(RendererGETCacheOnlySuccessHeader##suffix, \ + REQTEST_CACHE_ONLY_SUCCESS_HEADER, context_mode, false, true) \ + REQ_TEST(RendererGETCacheDisableFlag##suffix, REQTEST_CACHE_DISABLE_FLAG, \ + context_mode, false, true); \ + REQ_TEST(RendererGETCacheDisableHeader##suffix, \ + REQTEST_CACHE_DISABLE_HEADER, context_mode, false, true); REQ_TEST_CACHE_SET(ContextGlobalServer, CONTEXT_GLOBAL); REQ_TEST_CACHE_SET(ContextInMemoryServer, CONTEXT_INMEMORY);