diff --git a/include/capi/cef_request_handler_capi.h b/include/capi/cef_request_handler_capi.h index e32f9c7a8..359a92660 100644 --- a/include/capi/cef_request_handler_capi.h +++ b/include/capi/cef_request_handler_capi.h @@ -33,7 +33,7 @@ // by hand. See the translator.README.txt file in the tools directory for // more information. // -// $hash=52ba75e5a733ed0be9a064b9794c0181e714c090$ +// $hash=b8b5a62b11dbc48f0733c0522864e4dbda8b4f59$ // #ifndef CEF_INCLUDE_CAPI_CEF_REQUEST_HANDLER_CAPI_H_ @@ -247,6 +247,29 @@ typedef struct _cef_request_handler_t { const cef_string_t* scheme, struct _cef_auth_callback_t* callback); + /// + // Called on the IO thread before sending a network request with a "Cookie" + // request header. Return true (1) to allow cookies to be included in the + // network request or false (0) to block cookies. The |request| object should + // not be modified in this callback. + /// + int(CEF_CALLBACK* can_get_cookies)(struct _cef_request_handler_t* self, + struct _cef_browser_t* browser, + struct _cef_frame_t* frame, + struct _cef_request_t* request); + + /// + // Called on the IO thread when receiving a network request with a "Set- + // Cookie" response header value represented by |cookie|. Return true (1) to + // allow the cookie to be stored or false (0) to block the cookie. The + // |request| object should not be modified in this callback. + /// + int(CEF_CALLBACK* can_set_cookie)(struct _cef_request_handler_t* self, + struct _cef_browser_t* browser, + struct _cef_frame_t* frame, + struct _cef_request_t* request, + const struct _cef_cookie_t* cookie); + /// // Called on the IO thread when JavaScript requests a specific storage quota // size via the webkitStorageInfo.requestQuota function. |origin_url| is the diff --git a/include/cef_request_handler.h b/include/cef_request_handler.h index f25eb2630..2b003a861 100644 --- a/include/cef_request_handler.h +++ b/include/cef_request_handler.h @@ -252,6 +252,33 @@ class CefRequestHandler : public virtual CefBaseRefCounted { return false; } + /// + // Called on the IO thread before sending a network request with a "Cookie" + // request header. Return true to allow cookies to be included in the network + // request or false to block cookies. The |request| object should not be + // modified in this callback. + /// + /*--cef()--*/ + virtual bool CanGetCookies(CefRefPtr browser, + CefRefPtr frame, + CefRefPtr request) { + return true; + } + + /// + // Called on the IO thread when receiving a network request with a + // "Set-Cookie" response header value represented by |cookie|. Return true to + // allow the cookie to be stored or false to block the cookie. The |request| + // object should not be modified in this callback. + /// + /*--cef()--*/ + virtual bool CanSetCookie(CefRefPtr browser, + CefRefPtr frame, + CefRefPtr request, + const CefCookie& cookie) { + return true; + } + /// // Called on the IO thread when JavaScript requests a specific storage quota // size via the webkitStorageInfo.requestQuota function. |origin_url| is the diff --git a/libcef/browser/browser_host_impl.cc b/libcef/browser/browser_host_impl.cc index 8ecc5fcac..acb28c8fd 100644 --- a/libcef/browser/browser_host_impl.cc +++ b/libcef/browser/browser_host_impl.cc @@ -1583,7 +1583,7 @@ void CefBrowserHostImpl::CancelContextMenu() { } CefRefPtr CefBrowserHostImpl::GetFrameForRequest( - net::URLRequest* request) { + const net::URLRequest* request) { CEF_REQUIRE_IOT(); const content::ResourceRequestInfo* info = content::ResourceRequestInfo::ForRequest(request); diff --git a/libcef/browser/browser_host_impl.h b/libcef/browser/browser_host_impl.h index bf15a93ff..cbda5582c 100644 --- a/libcef/browser/browser_host_impl.h +++ b/libcef/browser/browser_host_impl.h @@ -303,7 +303,7 @@ class CefBrowserHostImpl : public CefBrowserHost, #endif // Returns the frame associated with the specified URLRequest. - CefRefPtr GetFrameForRequest(net::URLRequest* request); + CefRefPtr GetFrameForRequest(const net::URLRequest* request); // Navigate as specified by the |params| argument. void Navigate(const CefNavigateParams& params); diff --git a/libcef/browser/net/net_util.cc b/libcef/browser/net/net_util.cc index ea503f0dd..1291d6eca 100644 --- a/libcef/browser/net/net_util.cc +++ b/libcef/browser/net/net_util.cc @@ -9,7 +9,7 @@ namespace net_util { -bool IsInternalRequest(net::URLRequest* request) { +bool IsInternalRequest(const net::URLRequest* request) { // With PlzNavigate we now receive blob URLs. Ignore these URLs. // See https://crbug.com/776884 for details. if (request->url().SchemeIs(url::kBlobScheme)) { diff --git a/libcef/browser/net/net_util.h b/libcef/browser/net/net_util.h index de9d7122e..e2085ff96 100644 --- a/libcef/browser/net/net_util.h +++ b/libcef/browser/net/net_util.h @@ -14,7 +14,7 @@ namespace net_util { // Returns true if |request| is handled internally and should not be exposed via // the CEF API. -bool IsInternalRequest(net::URLRequest* request); +bool IsInternalRequest(const net::URLRequest* request); }; // namespace net_util diff --git a/libcef/browser/net/network_delegate.cc b/libcef/browser/net/network_delegate.cc index 68e8af58f..7c94a2689 100644 --- a/libcef/browser/net/network_delegate.cc +++ b/libcef/browser/net/network_delegate.cc @@ -9,6 +9,7 @@ #include "include/cef_urlrequest.h" #include "libcef/browser/browser_host_impl.h" +#include "libcef/browser/cookie_manager_impl.h" #include "libcef/browser/net/net_util.h" #include "libcef/browser/net/source_stream.h" #include "libcef/browser/net/url_request_user_data.h" @@ -440,6 +441,64 @@ net::NetworkDelegate::AuthRequiredResponse CefNetworkDelegate::OnAuthRequired( return AUTH_REQUIRED_RESPONSE_NO_ACTION; } +bool CefNetworkDelegate::OnCanGetCookies(const net::URLRequest& request, + const net::CookieList& cookie_list) { + if (net_util::IsInternalRequest(&request)) + return true; + + CefRefPtr browser = + CefBrowserHostImpl::GetBrowserForRequest(&request); + if (browser.get()) { + CefRefPtr client = browser->GetClient(); + if (client.get()) { + CefRefPtr handler = client->GetRequestHandler(); + if (handler.get()) { + CefRefPtr frame = browser->GetFrameForRequest(&request); + + CefRefPtr cefRequest = new CefRequestImpl(); + cefRequest->Set(&request); + cefRequest->SetReadOnly(true); + + return handler->CanGetCookies(browser.get(), frame, cefRequest.get()); + } + } + } + + return true; +} + +bool CefNetworkDelegate::OnCanSetCookie(const net::URLRequest& request, + const net::CanonicalCookie& cookie, + net::CookieOptions* options) { + if (net_util::IsInternalRequest(&request)) + return true; + + CefRefPtr browser = + CefBrowserHostImpl::GetBrowserForRequest(&request); + if (browser.get()) { + CefRefPtr client = browser->GetClient(); + if (client.get()) { + CefRefPtr handler = client->GetRequestHandler(); + if (handler.get()) { + CefRefPtr frame = browser->GetFrameForRequest(&request); + + CefRefPtr cefRequest = new CefRequestImpl(); + cefRequest->Set(&request); + cefRequest->SetReadOnly(true); + + CefCookie cefCookie; + if (!CefCookieManagerImpl::GetCefCookie(cookie, cefCookie)) + return true; + + return handler->CanSetCookie(browser.get(), frame, cefRequest.get(), + cefCookie); + } + } + } + + return true; +} + bool CefNetworkDelegate::OnCanAccessFile( const net::URLRequest& request, const base::FilePath& original_path, diff --git a/libcef/browser/net/network_delegate.h b/libcef/browser/net/network_delegate.h index 5f5a86904..73ec0626d 100644 --- a/libcef/browser/net/network_delegate.h +++ b/libcef/browser/net/network_delegate.h @@ -38,6 +38,11 @@ class CefNetworkDelegate : public net::NetworkDelegateImpl { const AuthCallback& callback, net::AuthCredentials* credentials) override; void OnCompleted(net::URLRequest* request, bool started) override; + bool OnCanGetCookies(const net::URLRequest& request, + const net::CookieList& cookie_list) override; + bool OnCanSetCookie(const net::URLRequest& request, + const net::CanonicalCookie& cookie, + net::CookieOptions* options) override; bool OnCanAccessFile(const net::URLRequest& request, const base::FilePath& original_path, const base::FilePath& absolute_path) const override; diff --git a/libcef/browser/net/resource_request_job.cc b/libcef/browser/net/resource_request_job.cc index c6ac9a06d..fba071877 100644 --- a/libcef/browser/net/resource_request_job.cc +++ b/libcef/browser/net/resource_request_job.cc @@ -411,7 +411,7 @@ void CefResourceRequestJob::DoLoadCookies() { void CefResourceRequestJob::CheckCookiePolicyAndLoad( const net::CookieList& cookie_list) { - bool can_get_cookies = CanGetCookies(cookie_list); + bool can_get_cookies = !cookie_list.empty() && CanGetCookies(cookie_list); if (can_get_cookies) { net::CookieList::const_iterator it = cookie_list.begin(); for (; it != cookie_list.end(); ++it) { diff --git a/libcef/common/request_impl.cc b/libcef/common/request_impl.cc index 9d1e4e372..39ee35426 100644 --- a/libcef/common/request_impl.cc +++ b/libcef/common/request_impl.cc @@ -445,7 +445,7 @@ uint64 CefRequestImpl::GetIdentifier() { return identifier_; } -void CefRequestImpl::Set(net::URLRequest* request) { +void CefRequestImpl::Set(const net::URLRequest* request) { base::AutoLock lock_scope(lock_); CHECK_READONLY_RETURN_VOID(); diff --git a/libcef/common/request_impl.h b/libcef/common/request_impl.h index 497b4a0e5..990a371d6 100644 --- a/libcef/common/request_impl.h +++ b/libcef/common/request_impl.h @@ -80,7 +80,7 @@ class CefRequestImpl : public CefRequest { uint64 GetIdentifier() override; // Populate this object from the URLRequest object. - void Set(net::URLRequest* request); + void Set(const net::URLRequest* request); // Populate the URLRequest object from this object. // If |changed_only| is true then only the changed fields will be updated. diff --git a/libcef_dll/cpptoc/request_handler_cpptoc.cc b/libcef_dll/cpptoc/request_handler_cpptoc.cc index 0988834e5..3cb901c05 100644 --- a/libcef_dll/cpptoc/request_handler_cpptoc.cc +++ b/libcef_dll/cpptoc/request_handler_cpptoc.cc @@ -9,7 +9,7 @@ // implementations. See the translator.README.txt file in the tools directory // for more information. // -// $hash=419fbbe47f1b127719554892609c02a3b135c44d$ +// $hash=b4c3eec00b1889871f4e13ea05d7c00a12da1fa0$ // #include "libcef_dll/cpptoc/request_handler_cpptoc.h" @@ -367,6 +367,80 @@ request_handler_get_auth_credentials(struct _cef_request_handler_t* self, return _retval; } +int CEF_CALLBACK +request_handler_can_get_cookies(struct _cef_request_handler_t* self, + cef_browser_t* browser, + cef_frame_t* frame, + cef_request_t* request) { + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + DCHECK(self); + if (!self) + return 0; + // Verify param: browser; type: refptr_diff + DCHECK(browser); + if (!browser) + return 0; + // Verify param: frame; type: refptr_diff + DCHECK(frame); + if (!frame) + return 0; + // Verify param: request; type: refptr_diff + DCHECK(request); + if (!request) + return 0; + + // Execute + bool _retval = CefRequestHandlerCppToC::Get(self)->CanGetCookies( + CefBrowserCToCpp::Wrap(browser), CefFrameCToCpp::Wrap(frame), + CefRequestCToCpp::Wrap(request)); + + // Return type: bool + return _retval; +} + +int CEF_CALLBACK +request_handler_can_set_cookie(struct _cef_request_handler_t* self, + cef_browser_t* browser, + cef_frame_t* frame, + cef_request_t* request, + const struct _cef_cookie_t* cookie) { + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + DCHECK(self); + if (!self) + return 0; + // Verify param: browser; type: refptr_diff + DCHECK(browser); + if (!browser) + return 0; + // Verify param: frame; type: refptr_diff + DCHECK(frame); + if (!frame) + return 0; + // Verify param: request; type: refptr_diff + DCHECK(request); + if (!request) + return 0; + // Verify param: cookie; type: struct_byref_const + DCHECK(cookie); + if (!cookie) + return 0; + + // Translate param: cookie; type: struct_byref_const + CefCookie cookieObj; + if (cookie) + cookieObj.Set(*cookie, false); + + // Execute + bool _retval = CefRequestHandlerCppToC::Get(self)->CanSetCookie( + CefBrowserCToCpp::Wrap(browser), CefFrameCToCpp::Wrap(frame), + CefRequestCToCpp::Wrap(request), cookieObj); + + // Return type: bool + return _retval; +} + int CEF_CALLBACK request_handler_on_quota_request(struct _cef_request_handler_t* self, cef_browser_t* browser, @@ -603,6 +677,8 @@ CefRequestHandlerCppToC::CefRequestHandlerCppToC() { GetStruct()->on_resource_load_complete = request_handler_on_resource_load_complete; GetStruct()->get_auth_credentials = request_handler_get_auth_credentials; + GetStruct()->can_get_cookies = request_handler_can_get_cookies; + GetStruct()->can_set_cookie = request_handler_can_set_cookie; GetStruct()->on_quota_request = request_handler_on_quota_request; GetStruct()->on_protocol_execution = request_handler_on_protocol_execution; GetStruct()->on_certificate_error = request_handler_on_certificate_error; diff --git a/libcef_dll/ctocpp/request_handler_ctocpp.cc b/libcef_dll/ctocpp/request_handler_ctocpp.cc index efbb8f837..6be865d88 100644 --- a/libcef_dll/ctocpp/request_handler_ctocpp.cc +++ b/libcef_dll/ctocpp/request_handler_ctocpp.cc @@ -9,7 +9,7 @@ // implementations. See the translator.README.txt file in the tools directory // for more information. // -// $hash=9bcd78135c26925b3e33662e92497d1ef7d85642$ +// $hash=fbd234d6060aadc1bf5ee0efce5d8cb2c4a0e022$ // #include "libcef_dll/ctocpp/request_handler_ctocpp.h" @@ -353,6 +353,69 @@ bool CefRequestHandlerCToCpp::GetAuthCredentials( return _retval ? true : false; } +bool CefRequestHandlerCToCpp::CanGetCookies(CefRefPtr browser, + CefRefPtr frame, + CefRefPtr request) { + cef_request_handler_t* _struct = GetStruct(); + if (CEF_MEMBER_MISSING(_struct, can_get_cookies)) + return false; + + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + // Verify param: browser; type: refptr_diff + DCHECK(browser.get()); + if (!browser.get()) + return false; + // Verify param: frame; type: refptr_diff + DCHECK(frame.get()); + if (!frame.get()) + return false; + // Verify param: request; type: refptr_diff + DCHECK(request.get()); + if (!request.get()) + return false; + + // Execute + int _retval = _struct->can_get_cookies( + _struct, CefBrowserCppToC::Wrap(browser), CefFrameCppToC::Wrap(frame), + CefRequestCppToC::Wrap(request)); + + // Return type: bool + return _retval ? true : false; +} + +bool CefRequestHandlerCToCpp::CanSetCookie(CefRefPtr browser, + CefRefPtr frame, + CefRefPtr request, + const CefCookie& cookie) { + cef_request_handler_t* _struct = GetStruct(); + if (CEF_MEMBER_MISSING(_struct, can_set_cookie)) + return false; + + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + // Verify param: browser; type: refptr_diff + DCHECK(browser.get()); + if (!browser.get()) + return false; + // Verify param: frame; type: refptr_diff + DCHECK(frame.get()); + if (!frame.get()) + return false; + // Verify param: request; type: refptr_diff + DCHECK(request.get()); + if (!request.get()) + return false; + + // Execute + int _retval = _struct->can_set_cookie( + _struct, CefBrowserCppToC::Wrap(browser), CefFrameCppToC::Wrap(frame), + CefRequestCppToC::Wrap(request), &cookie); + + // Return type: bool + return _retval ? true : false; +} + bool CefRequestHandlerCToCpp::OnQuotaRequest( CefRefPtr browser, const CefString& origin_url, diff --git a/libcef_dll/ctocpp/request_handler_ctocpp.h b/libcef_dll/ctocpp/request_handler_ctocpp.h index fd02467d7..4921add54 100644 --- a/libcef_dll/ctocpp/request_handler_ctocpp.h +++ b/libcef_dll/ctocpp/request_handler_ctocpp.h @@ -9,7 +9,7 @@ // implementations. See the translator.README.txt file in the tools directory // for more information. // -// $hash=a28a81ae22b7edb9dfb3a9df72c48a96971b7017$ +// $hash=cde69cb97f29cb75da85bb709470f73d9e57f5bc$ // #ifndef CEF_LIBCEF_DLL_CTOCPP_REQUEST_HANDLER_CTOCPP_H_ @@ -80,6 +80,13 @@ class CefRequestHandlerCToCpp const CefString& realm, const CefString& scheme, CefRefPtr callback) override; + bool CanGetCookies(CefRefPtr browser, + CefRefPtr frame, + CefRefPtr request) override; + bool CanSetCookie(CefRefPtr browser, + CefRefPtr frame, + CefRefPtr request, + const CefCookie& cookie) override; bool OnQuotaRequest(CefRefPtr browser, const CefString& origin_url, int64 new_size, diff --git a/tests/ceftests/request_handler_unittest.cc b/tests/ceftests/request_handler_unittest.cc index 364c4dc0d..fb6d657e0 100644 --- a/tests/ceftests/request_handler_unittest.cc +++ b/tests/ceftests/request_handler_unittest.cc @@ -10,8 +10,10 @@ #include "include/base/cef_bind.h" #include "include/base/cef_scoped_ptr.h" #include "include/cef_cookie.h" +#include "include/cef_server.h" #include "include/wrapper/cef_closure_task.h" #include "include/wrapper/cef_stream_resource_handler.h" +#include "tests/ceftests/routing_test_handler.h" #include "tests/ceftests/test_handler.h" #include "tests/ceftests/test_util.h" #include "tests/gtest/include/gtest/gtest.h" @@ -1681,6 +1683,910 @@ TEST(RequestHandlerTest, ResponseFilterError) { ReleaseAndWaitForDestructor(handler); } +namespace { + +const char kCookieAccessScheme[] = "http"; +const char kCookieAccessDomain[] = "test-cookies.com"; +const char kCookieAccessServerIP[] = "127.0.0.1"; +const uint16 kCookieAccessServerPort = 8099; + +std::string GetCookieAccessOrigin(bool server_backend) { + std::stringstream ss; + if (server_backend) { + ss << kCookieAccessScheme << "://" << kCookieAccessServerIP << ":" + << kCookieAccessServerPort; + } else { + ss << kCookieAccessScheme << "://" << kCookieAccessDomain; + } + ss << "/"; + return ss.str(); +} + +std::string GetCookieAccessUrl1(bool server_backend) { + return GetCookieAccessOrigin(server_backend) + "cookie1.html"; +} + +std::string GetCookieAccessUrl2(bool server_backend) { + return GetCookieAccessOrigin(server_backend) + "cookie2.html"; +} + +void TestCookieString(const std::string& cookie_str, + TrackCallback& got_cookie_js, + TrackCallback& got_cookie_net) { + if (cookie_str.find("name_js=value_js") != std::string::npos) { + got_cookie_js.yes(); + } + if (cookie_str.find("name_net=value_net") != std::string::npos) { + got_cookie_net.yes(); + } +} + +struct CookieAccessData { + CefRefPtr response; + std::string response_data; + + TrackCallback got_request_; + TrackCallback got_cookie_js_; + TrackCallback got_cookie_net_; + + // Only used with scheme handler backend. + TrackCallback got_can_set_cookie_js_; + TrackCallback got_can_set_cookie_net_; + TrackCallback got_can_get_cookie_js_; + TrackCallback got_can_get_cookie_net_; +}; + +class CookieAccessResponseHandler { + public: + CookieAccessResponseHandler() {} + virtual void AddResponse(const std::string& url, CookieAccessData* data) = 0; + + protected: + virtual ~CookieAccessResponseHandler() {} +}; + +std::string GetHeaderValue(const CefServer::HeaderMap& header_map, + const std::string& header_name) { + CefServer::HeaderMap::const_iterator it = header_map.find(header_name); + if (it != header_map.end()) + return it->second; + return std::string(); +} + +// Serves request responses. +class CookieAccessSchemeHandler : public CefResourceHandler { + public: + explicit CookieAccessSchemeHandler(CookieAccessData* data) + : data_(data), offset_(0) {} + + bool ProcessRequest(CefRefPtr request, + CefRefPtr callback) override { + EXPECT_TRUE(CefCurrentlyOn(TID_IO)); + + CefRequest::HeaderMap headerMap; + request->GetHeaderMap(headerMap); + const std::string& cookie_str = GetHeaderValue(headerMap, "Cookie"); + TestCookieString(cookie_str, data_->got_cookie_js_, data_->got_cookie_net_); + + // Continue immediately. + callback->Continue(); + return true; + } + + void GetResponseHeaders(CefRefPtr response, + int64& response_length, + CefString& redirectUrl) override { + EXPECT_TRUE(CefCurrentlyOn(TID_IO)); + + response->SetStatus(data_->response->GetStatus()); + response->SetStatusText(data_->response->GetStatusText()); + response->SetMimeType(data_->response->GetMimeType()); + + CefResponse::HeaderMap headerMap; + data_->response->GetHeaderMap(headerMap); + response->SetHeaderMap(headerMap); + + response_length = data_->response_data.length(); + } + + bool ReadResponse(void* response_data_out, + int bytes_to_read, + int& bytes_read, + CefRefPtr callback) override { + EXPECT_TRUE(CefCurrentlyOn(TID_IO)); + + bool has_data = false; + bytes_read = 0; + + size_t size = data_->response_data.length(); + if (offset_ < size) { + int transfer_size = + std::min(bytes_to_read, static_cast(size - offset_)); + memcpy(response_data_out, data_->response_data.c_str() + offset_, + transfer_size); + offset_ += transfer_size; + + bytes_read = transfer_size; + has_data = true; + } + + return has_data; + } + + bool CanGetCookie(const CefCookie& cookie) override { + EXPECT_TRUE(CefCurrentlyOn(TID_IO)); + TestCookie(cookie, data_->got_can_get_cookie_js_, + data_->got_can_get_cookie_net_); + return true; + } + + bool CanSetCookie(const CefCookie& cookie) override { + EXPECT_TRUE(CefCurrentlyOn(TID_IO)); + TestCookie(cookie, data_->got_can_set_cookie_js_, + data_->got_can_set_cookie_net_); + return true; + } + + void Cancel() override { EXPECT_TRUE(CefCurrentlyOn(TID_IO)); } + + private: + static void TestCookie(const CefCookie& cookie, + TrackCallback& got_cookie_js, + TrackCallback& got_cookie_net) { + const std::string& cookie_name = CefString(&cookie.name); + const std::string& cookie_val = CefString(&cookie.value); + if (cookie_name == "name_js") { + EXPECT_STREQ("value_js", cookie_val.c_str()); + got_cookie_js.yes(); + } else if (cookie_name == "name_net") { + EXPECT_STREQ("value_net", cookie_val.c_str()); + got_cookie_net.yes(); + } else { + ADD_FAILURE() << "Unexpected cookie: " << cookie_name; + } + } + + // |data_| is not owned by this object. + CookieAccessData* data_; + + size_t offset_; + + IMPLEMENT_REFCOUNTING(CookieAccessSchemeHandler); +}; + +class CookieAccessSchemeHandlerFactory : public CefSchemeHandlerFactory, + public CookieAccessResponseHandler { + public: + CookieAccessSchemeHandlerFactory() {} + + CefRefPtr Create(CefRefPtr browser, + CefRefPtr frame, + const CefString& scheme_name, + CefRefPtr request) override { + EXPECT_TRUE(CefCurrentlyOn(TID_IO)); + const std::string& url = request->GetURL(); + ResponseDataMap::const_iterator it = data_map_.find(url); + if (it != data_map_.end()) { + it->second->got_request_.yes(); + + // There should be no cookie data in this callback. + CefRequest::HeaderMap headerMap; + request->GetHeaderMap(headerMap); + EXPECT_TRUE(headerMap.find("Cookie") == headerMap.end()); + + return new CookieAccessSchemeHandler(it->second); + } + + // Unknown test. + ADD_FAILURE() << "Unexpected url: " << url; + return nullptr; + } + + void AddResponse(const std::string& url, CookieAccessData* data) override { + data_map_.insert(std::make_pair(url, data)); + } + + void Shutdown(const base::Closure& complete_callback) { + if (!CefCurrentlyOn(TID_IO)) { + CefPostTask(TID_IO, + base::Bind(&CookieAccessSchemeHandlerFactory::Shutdown, this, + complete_callback)); + return; + } + + complete_callback.Run(); + } + + private: + // Map of URL to Data. + typedef std::map ResponseDataMap; + ResponseDataMap data_map_; + + IMPLEMENT_REFCOUNTING(CookieAccessSchemeHandlerFactory); +}; + +// HTTP server handler. +class CookieAccessServerHandler : public CefServerHandler, + public CookieAccessResponseHandler { + public: + CookieAccessServerHandler() + : initialized_(false), + expected_connection_ct_(-1), + actual_connection_ct_(0), + expected_http_request_ct_(-1), + actual_http_request_ct_(0) {} + + virtual ~CookieAccessServerHandler() { RunCompleteCallback(); } + + // Must be called before CreateServer(). + void AddResponse(const std::string& url, CookieAccessData* data) override { + EXPECT_FALSE(initialized_); + data_map_.insert(std::make_pair(url, data)); + } + + // Must be called before CreateServer(). + void SetExpectedRequestCount(int count) { + EXPECT_FALSE(initialized_); + expected_connection_ct_ = expected_http_request_ct_ = count; + } + + // |complete_callback| will be executed on the UI thread after the server is + // started. + void CreateServer(const base::Closure& complete_callback) { + EXPECT_UI_THREAD(); + + if (expected_connection_ct_ < 0) { + // Default to the assumption of one request per registered URL. + SetExpectedRequestCount(static_cast(data_map_.size())); + } + + EXPECT_FALSE(initialized_); + initialized_ = true; + + EXPECT_TRUE(complete_callback_.is_null()); + complete_callback_ = complete_callback; + + CefServer::CreateServer(kCookieAccessServerIP, kCookieAccessServerPort, 10, + this); + } + + // Results in a call to VerifyResults() and eventual execution of the + // |complete_callback| on the UI thread via CookieAccessServerHandler + // destruction. + void ShutdownServer(const base::Closure& complete_callback) { + EXPECT_UI_THREAD(); + + EXPECT_TRUE(complete_callback_.is_null()); + complete_callback_ = complete_callback; + + EXPECT_TRUE(server_); + if (server_) + server_->Shutdown(); + } + + void OnServerCreated(CefRefPtr server) override { + EXPECT_TRUE(server); + EXPECT_TRUE(server->IsRunning()); + EXPECT_FALSE(server->HasConnection()); + + EXPECT_FALSE(got_server_created_); + got_server_created_.yes(); + + EXPECT_FALSE(server_); + server_ = server; + + EXPECT_FALSE(server_runner_); + server_runner_ = server_->GetTaskRunner(); + EXPECT_TRUE(server_runner_); + EXPECT_TRUE(server_runner_->BelongsToCurrentThread()); + + CefPostTask( + TID_UI, + base::Bind(&CookieAccessServerHandler::RunCompleteCallback, this)); + } + + void OnServerDestroyed(CefRefPtr server) override { + EXPECT_TRUE(VerifyServer(server)); + EXPECT_FALSE(server->IsRunning()); + EXPECT_FALSE(server->HasConnection()); + + EXPECT_FALSE(got_server_destroyed_); + got_server_destroyed_.yes(); + + server_ = nullptr; + + VerifyResults(); + } + + void OnClientConnected(CefRefPtr server, + int connection_id) override { + EXPECT_TRUE(VerifyServer(server)); + EXPECT_TRUE(server->HasConnection()); + EXPECT_TRUE(server->IsValidConnection(connection_id)); + + EXPECT_TRUE(connection_id_set_.find(connection_id) == + connection_id_set_.end()); + connection_id_set_.insert(connection_id); + + actual_connection_ct_++; + } + + void OnClientDisconnected(CefRefPtr server, + int connection_id) override { + EXPECT_TRUE(VerifyServer(server)); + EXPECT_FALSE(server->IsValidConnection(connection_id)); + + ConnectionIdSet::iterator it = connection_id_set_.find(connection_id); + EXPECT_TRUE(it != connection_id_set_.end()); + connection_id_set_.erase(it); + } + + void OnHttpRequest(CefRefPtr server, + int connection_id, + const CefString& client_address, + CefRefPtr request) override { + EXPECT_TRUE(VerifyServer(server)); + EXPECT_TRUE(VerifyConnection(connection_id)); + EXPECT_FALSE(client_address.empty()); + + // Log the requests for better error reporting. + request_log_ += request->GetMethod().ToString() + " " + + request->GetURL().ToString() + "\n"; + + HandleRequest(server, connection_id, request); + + actual_http_request_ct_++; + } + + void OnWebSocketRequest(CefRefPtr server, + int connection_id, + const CefString& client_address, + CefRefPtr request, + CefRefPtr callback) override { + NOTREACHED(); + } + + void OnWebSocketConnected(CefRefPtr server, + int connection_id) override { + NOTREACHED(); + } + + void OnWebSocketMessage(CefRefPtr server, + int connection_id, + const void* data, + size_t data_size) override { + NOTREACHED(); + } + + private: + bool RunningOnServerThread() { + return server_runner_ && server_runner_->BelongsToCurrentThread(); + } + + bool VerifyServer(CefRefPtr server) { + V_DECLARE(); + V_EXPECT_TRUE(RunningOnServerThread()); + V_EXPECT_TRUE(server); + V_EXPECT_TRUE(server_); + V_EXPECT_TRUE(server->GetAddress().ToString() == + server_->GetAddress().ToString()); + V_RETURN(); + } + + bool VerifyConnection(int connection_id) { + return connection_id_set_.find(connection_id) != connection_id_set_.end(); + } + + void VerifyResults() { + EXPECT_TRUE(RunningOnServerThread()); + + EXPECT_TRUE(got_server_created_); + EXPECT_TRUE(got_server_destroyed_); + EXPECT_TRUE(connection_id_set_.empty()); + EXPECT_EQ(expected_connection_ct_, actual_connection_ct_) << request_log_; + EXPECT_EQ(expected_http_request_ct_, actual_http_request_ct_) + << request_log_; + } + + void HandleRequest(CefRefPtr server, + int connection_id, + CefRefPtr request) { + const std::string& url = request->GetURL(); + ResponseDataMap::const_iterator it = data_map_.find(url); + if (it != data_map_.end()) { + it->second->got_request_.yes(); + + CefRequest::HeaderMap headerMap; + request->GetHeaderMap(headerMap); + const std::string& cookie_str = GetHeaderValue(headerMap, "cookie"); + TestCookieString(cookie_str, it->second->got_cookie_js_, + it->second->got_cookie_net_); + + SendResponse(server, connection_id, it->second->response, + it->second->response_data); + } else { + // Unknown test. + ADD_FAILURE() << "Unexpected url: " << url; + server->SendHttp500Response(connection_id, "Unknown test"); + } + } + + void SendResponse(CefRefPtr server, + int connection_id, + CefRefPtr response, + const std::string& response_data) { + int response_code = response->GetStatus(); + const CefString& content_type = response->GetMimeType(); + int64 content_length = static_cast(response_data.size()); + + CefResponse::HeaderMap extra_headers; + response->GetHeaderMap(extra_headers); + + server->SendHttpResponse(connection_id, response_code, content_type, + content_length, extra_headers); + + if (content_length != 0) { + server->SendRawData(connection_id, response_data.data(), + response_data.size()); + server->CloseConnection(connection_id); + } + + // The connection should be closed. + EXPECT_FALSE(server->IsValidConnection(connection_id)); + } + + void RunCompleteCallback() { + EXPECT_UI_THREAD(); + + EXPECT_FALSE(complete_callback_.is_null()); + complete_callback_.Run(); + complete_callback_.Reset(); + } + + // Map of URL to Data. + typedef std::map ResponseDataMap; + ResponseDataMap data_map_; + + CefRefPtr server_; + CefRefPtr server_runner_; + bool initialized_; + + // Only accessed on the UI thread. + base::Closure complete_callback_; + + // After initialization the below members are only accessed on the server + // thread. + + TrackCallback got_server_created_; + TrackCallback got_server_destroyed_; + + typedef std::set ConnectionIdSet; + ConnectionIdSet connection_id_set_; + + int expected_connection_ct_; + int actual_connection_ct_; + int expected_http_request_ct_; + int actual_http_request_ct_; + + std::string request_log_; + + IMPLEMENT_REFCOUNTING(CookieAccessServerHandler); + DISALLOW_COPY_AND_ASSIGN(CookieAccessServerHandler); +}; + +class CookieAccessTestHandler : public RoutingTestHandler { + public: + enum TestMode { + ALLOW = 0, + BLOCK_READ = 1 << 0, + BLOCK_WRITE = 1 << 1, + BLOCK_ALL = BLOCK_READ | BLOCK_WRITE, + }; + + CookieAccessTestHandler(TestMode test_mode, bool server_backend) + : test_mode_(test_mode), server_backend_(server_backend) {} + + void RunTest() override { + cookie_manager_ = CefCookieManager::GetGlobalManager(nullptr); + SetTestTimeout(); + + CefPostTask(TID_UI, + base::Bind(&CookieAccessTestHandler::StartBackend, this, + base::Bind(&CookieAccessTestHandler::RunTestContinue, + this))); + } + + void DestroyTest() override { + if (!CefCurrentlyOn(TID_UI)) { + CefPostTask(TID_UI, + base::Bind(&CookieAccessTestHandler::DestroyTest, this)); + return; + } + + cookie_manager_ = NULL; + + // Always get a call to CanSetCookie for the 1st network request due to the + // network cookie. + EXPECT_TRUE(got_can_set_cookie1_); + // Always get a call to CanGetCookies for the 2nd network request due to the + // JS cookie. + EXPECT_TRUE(got_can_get_cookies2_); + + // Always get the JS cookie via JS. + EXPECT_TRUE(got_cookie_js1_); + EXPECT_TRUE(got_cookie_js2_); + EXPECT_TRUE(got_cookie_js3_); + + // Only get the net cookie via JS if cookie write was allowed. + if (test_mode_ & BLOCK_WRITE) { + EXPECT_FALSE(got_cookie_net1_); + EXPECT_FALSE(got_cookie_net2_); + EXPECT_FALSE(got_cookie_net3_); + } else { + EXPECT_TRUE(got_cookie_net1_); + EXPECT_TRUE(got_cookie_net2_); + EXPECT_TRUE(got_cookie_net3_); + } + + // Got both network requests. + EXPECT_TRUE(data1_.got_request_); + EXPECT_TRUE(data2_.got_request_); + + // No cookies sent for the 1st network request. + EXPECT_FALSE(data1_.got_cookie_js_); + EXPECT_FALSE(data1_.got_cookie_net_); + + // 2nd network request... + if (test_mode_ & BLOCK_READ) { + // No cookies sent if reading was blocked. + EXPECT_FALSE(data2_.got_cookie_js_); + EXPECT_FALSE(data2_.got_cookie_net_); + } else if (test_mode_ & BLOCK_WRITE) { + // Only JS cookie sent if writing was blocked. + EXPECT_TRUE(data2_.got_cookie_js_); + EXPECT_FALSE(data2_.got_cookie_net_); + } else { + // All cookies sent. + EXPECT_TRUE(data2_.got_cookie_js_); + EXPECT_TRUE(data2_.got_cookie_net_); + } + + if (!server_backend_) { + // No query to get cookies with the 1st network request because none have + // been set yet. + EXPECT_FALSE(data1_.got_can_get_cookie_js_); + EXPECT_FALSE(data1_.got_can_get_cookie_net_); + + // JS cookie is not set via a network request. + EXPECT_FALSE(data1_.got_can_set_cookie_js_); + EXPECT_FALSE(data2_.got_can_set_cookie_js_); + + // No query to set the net cookie for the 1st network request if write was + // blocked. + if (test_mode_ & BLOCK_WRITE) { + EXPECT_FALSE(data1_.got_can_set_cookie_net_); + } else { + EXPECT_TRUE(data1_.got_can_set_cookie_net_); + } + + // Net cookie is not set via the 2nd network request. + EXPECT_FALSE(data2_.got_can_set_cookie_net_); + + // No query to get the JS cookie for the 2nd network request if read was + // blocked. + if (test_mode_ & BLOCK_READ) { + EXPECT_FALSE(data2_.got_can_get_cookie_js_); + } else { + EXPECT_TRUE(data2_.got_can_get_cookie_js_); + } + + // No query to get the net cookie for the 2nd network request if read or + // write (of the net cookie) was blocked. + if (test_mode_ & (BLOCK_READ | BLOCK_WRITE)) { + EXPECT_FALSE(data2_.got_can_get_cookie_net_); + } else { + EXPECT_TRUE(data2_.got_can_get_cookie_net_); + } + } + + TestHandler::DestroyTest(); + } + + bool CanGetCookies(CefRefPtr browser, + CefRefPtr frame, + CefRefPtr request) override { + EXPECT_TRUE(CefCurrentlyOn(TID_IO)); + + const std::string& url = request->GetURL(); + if (url == GetCookieAccessUrl2(server_backend_)) { + EXPECT_FALSE(got_can_get_cookies2_); + got_can_get_cookies2_.yes(); + } else { + ADD_FAILURE() << "Unexpected url: " << url; + } + + return !(test_mode_ & BLOCK_READ); + } + + bool CanSetCookie(CefRefPtr browser, + CefRefPtr frame, + CefRefPtr request, + const CefCookie& cookie) override { + EXPECT_TRUE(CefCurrentlyOn(TID_IO)); + + // Expecting the network cookie only. + EXPECT_STREQ("name_net", CefString(&cookie.name).ToString().c_str()); + EXPECT_STREQ("value_net", CefString(&cookie.value).ToString().c_str()); + + const std::string& url = request->GetURL(); + if (url == GetCookieAccessUrl1(server_backend_)) { + EXPECT_FALSE(got_can_set_cookie1_); + got_can_set_cookie1_.yes(); + } else { + ADD_FAILURE() << "Unexpected url: " << url; + } + + return !(test_mode_ & BLOCK_WRITE); + } + + bool OnQuery(CefRefPtr browser, + CefRefPtr frame, + int64 query_id, + const CefString& request, + bool persistent, + CefRefPtr callback) override { + const std::string& url = frame->GetURL(); + const std::string& cookie_str = request.ToString(); + if (url == GetCookieAccessUrl1(server_backend_)) { + TestCookieString(cookie_str, got_cookie_js1_, got_cookie_net1_); + browser->GetMainFrame()->LoadURL(GetCookieAccessUrl2(server_backend_)); + } else if (url == GetCookieAccessUrl2(server_backend_)) { + TestCookieString(cookie_str, got_cookie_js2_, got_cookie_net2_); + FinishTest(); + } else { + ADD_FAILURE() << "Unexpected url: " << url; + } + return true; + } + + private: + void AddResponses(CookieAccessResponseHandler* handler) { + // 1st request sets a cookie via net response headers and JS, then retrieves + // the cookies via JS. + { + data1_.response = CefResponse::Create(); + data1_.response->SetMimeType("text/html"); + data1_.response->SetStatus(200); + data1_.response->SetStatusText("OK"); + + CefResponse::HeaderMap headerMap; + data1_.response->GetHeaderMap(headerMap); + headerMap.insert(std::make_pair("Set-Cookie", "name_net=value_net")); + data1_.response->SetHeaderMap(headerMap); + + data1_.response_data = + "" + "" + "COOKIE ACCESS TEST 1"; + + handler->AddResponse(GetCookieAccessUrl1(server_backend_), &data1_); + } + + // 2nd request retrieves the cookies via JS. + { + data2_.response = CefResponse::Create(); + data2_.response->SetMimeType("text/html"); + data2_.response->SetStatus(200); + data2_.response->SetStatusText("OK"); + + data2_.response_data = + "" + "" + "COOKIE ACCESS TEST 2"; + + handler->AddResponse(GetCookieAccessUrl2(server_backend_), &data2_); + } + } + + void StartBackend(const base::Closure& complete_callback) { + if (server_backend_) { + StartServer(complete_callback); + } else { + StartSchemeHandler(complete_callback); + } + } + + void StartServer(const base::Closure& complete_callback) { + EXPECT_FALSE(server_handler_); + + server_handler_ = new CookieAccessServerHandler(); + AddResponses(server_handler_.get()); + server_handler_->CreateServer(complete_callback); + } + + void StartSchemeHandler(const base::Closure& complete_callback) { + // Add the factory registration. + scheme_factory_ = new CookieAccessSchemeHandlerFactory(); + AddResponses(scheme_factory_.get()); + CefRegisterSchemeHandlerFactory(kCookieAccessScheme, kCookieAccessDomain, + scheme_factory_.get()); + + complete_callback.Run(); + } + + void RunTestContinue() { + if (!CefCurrentlyOn(TID_UI)) { + CefPostTask(TID_UI, + base::Bind(&CookieAccessTestHandler::RunTestContinue, this)); + return; + } + + CreateBrowser(GetCookieAccessUrl1(server_backend_)); + } + + void FinishTest() { + // Verify that cookies were set correctly. + class TestVisitor : public CefCookieVisitor { + public: + explicit TestVisitor(CookieAccessTestHandler* handler) + : handler_(handler) {} + ~TestVisitor() override { + // Destroy the test. + CefPostTask( + TID_UI, + base::Bind( + &CookieAccessTestHandler::ShutdownBackend, handler_, + base::Bind(&CookieAccessTestHandler::DestroyTest, handler_))); + } + + bool Visit(const CefCookie& cookie, + int count, + int total, + bool& deleteCookie) override { + const std::string& name = CefString(&cookie.name); + const std::string& value = CefString(&cookie.value); + if (name == "name_js" && value == "value_js") + handler_->got_cookie_js3_.yes(); + else if (name == "name_net" && value == "value_net") + handler_->got_cookie_net3_.yes(); + + // Clean up the cookies. + deleteCookie = true; + + return true; + } + + private: + CookieAccessTestHandler* handler_; + IMPLEMENT_REFCOUNTING(TestVisitor); + }; + + cookie_manager_->VisitAllCookies(new TestVisitor(this)); + } + + void ShutdownBackend(const base::Closure& complete_callback) { + if (server_backend_) { + ShutdownServer(complete_callback); + } else { + ShutdownSchemeHandler(complete_callback); + } + } + + void ShutdownServer(const base::Closure& complete_callback) { + EXPECT_TRUE(server_handler_); + + server_handler_->ShutdownServer(complete_callback); + server_handler_ = nullptr; + } + + void ShutdownSchemeHandler(const base::Closure& complete_callback) { + EXPECT_TRUE(scheme_factory_); + + CefRegisterSchemeHandlerFactory(kCookieAccessScheme, kCookieAccessDomain, + nullptr); + scheme_factory_->Shutdown(complete_callback); + scheme_factory_ = nullptr; + } + + TestMode test_mode_; + bool server_backend_; + CefRefPtr cookie_manager_; + + CefRefPtr server_handler_; + CefRefPtr scheme_factory_; + + CookieAccessData data1_; + CookieAccessData data2_; + + // 1st request. + TrackCallback got_can_set_cookie1_; + TrackCallback got_cookie_js1_; + TrackCallback got_cookie_net1_; + + // 2nd request. + TrackCallback got_can_get_cookies2_; + TrackCallback got_cookie_js2_; + TrackCallback got_cookie_net2_; + + // From cookie manager. + TrackCallback got_cookie_js3_; + TrackCallback got_cookie_net3_; + + DISALLOW_COPY_AND_ASSIGN(CookieAccessTestHandler); + IMPLEMENT_REFCOUNTING(CookieAccessTestHandler); +}; + +} // namespace + +// Allow reading and writing of cookies with server backend. +TEST(RequestHandlerTest, CookieAccessServerAllow) { + CefRefPtr handler = + new CookieAccessTestHandler(CookieAccessTestHandler::ALLOW, true); + handler->ExecuteTest(); + ReleaseAndWaitForDestructor(handler); +} + +// Block reading of cookies with server backend. +TEST(RequestHandlerTest, CookieAccessServerBlockRead) { + CefRefPtr handler = + new CookieAccessTestHandler(CookieAccessTestHandler::BLOCK_READ, true); + handler->ExecuteTest(); + ReleaseAndWaitForDestructor(handler); +} + +// Block writing of cookies with server backend. +TEST(RequestHandlerTest, CookieAccessServerBlockWrite) { + CefRefPtr handler = + new CookieAccessTestHandler(CookieAccessTestHandler::BLOCK_WRITE, true); + handler->ExecuteTest(); + ReleaseAndWaitForDestructor(handler); +} + +// Block reading and writing of cookies with server backend. +TEST(RequestHandlerTest, CookieAccessServerBlockAll) { + CefRefPtr handler = + new CookieAccessTestHandler(CookieAccessTestHandler::BLOCK_ALL, true); + handler->ExecuteTest(); + ReleaseAndWaitForDestructor(handler); +} + +// Allow reading and writing of cookies with scheme handler backend. +TEST(RequestHandlerTest, CookieAccessSchemeAllow) { + CefRefPtr handler = + new CookieAccessTestHandler(CookieAccessTestHandler::ALLOW, false); + handler->ExecuteTest(); + ReleaseAndWaitForDestructor(handler); +} + +// Block reading of cookies with scheme handler backend. +TEST(RequestHandlerTest, CookieAccessSchemeBlockRead) { + CefRefPtr handler = + new CookieAccessTestHandler(CookieAccessTestHandler::BLOCK_READ, false); + handler->ExecuteTest(); + ReleaseAndWaitForDestructor(handler); +} + +// Block writing of cookies with scheme handler backend. +TEST(RequestHandlerTest, CookieAccessSchemeBlockWrite) { + CefRefPtr handler = + new CookieAccessTestHandler(CookieAccessTestHandler::BLOCK_WRITE, false); + handler->ExecuteTest(); + ReleaseAndWaitForDestructor(handler); +} + +// Block reading and writing of cookies with scheme handler backend. +TEST(RequestHandlerTest, CookieAccessSchemeBlockAll) { + CefRefPtr handler = + new CookieAccessTestHandler(CookieAccessTestHandler::BLOCK_ALL, false); + handler->ExecuteTest(); + ReleaseAndWaitForDestructor(handler); +} + // Entry point for creating request handler browser test objects. // Called from client_app_delegates.cc. void CreateRequestHandlerBrowserTests(