1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "ios/chrome/browser/signin/gaia_auth_fetcher_ios.h"
7 #import <WebKit/WebKit.h>
9 #include "base/logging.h"
10 #import "base/mac/foundation_util.h"
11 #include "base/mac/scoped_block.h"
12 #import "base/mac/scoped_nsobject.h"
13 #include "base/strings/sys_string_conversions.h"
14 #include "ios/chrome/browser/experimental_flags.h"
15 #include "ios/chrome/browser/signin/gaia_auth_fetcher_ios_private.h"
16 #include "ios/web/public/browser_state.h"
17 #import "ios/web/public/web_view_creation_util.h"
18 #include "net/base/load_flags.h"
19 #import "net/base/mac/url_conversions.h"
20 #include "net/base/net_errors.h"
21 #include "net/http/http_request_headers.h"
22 #include "net/url_request/url_request_status.h"
26 // Whether the iOS specialization of the GaiaAuthFetcher should be used.
27 bool g_should_use_gaia_auth_fetcher_ios = true;
29 // Creates an NSURLRequest to |url| that can be loaded by a WebView from |body|
31 // The request is a GET if |body| is empty and a POST otherwise.
32 NSURLRequest* GetRequest(const std::string& body,
33 const std::string& headers,
35 base::scoped_nsobject<NSMutableURLRequest> request(
36 [[NSMutableURLRequest alloc] initWithURL:net::NSURLWithGURL(url)]);
37 net::HttpRequestHeaders request_headers;
38 request_headers.AddHeadersFromString(headers);
39 for (net::HttpRequestHeaders::Iterator it(request_headers); it.GetNext();) {
40 [request setValue:base::SysUTF8ToNSString(it.value())
41 forHTTPHeaderField:base::SysUTF8ToNSString(it.name())];
44 // TODO(bzanotti): HTTPBody is currently ignored in POST Request on
45 // WKWebView. Add a workaround here.
47 [base::SysUTF8ToNSString(body) dataUsingEncoding:NSUTF8StringEncoding];
48 [request setHTTPBody:post_data];
49 [request setHTTPMethod:@"POST"];
50 DCHECK(![[request allHTTPHeaderFields] objectForKey:@"Content-Type"]);
51 [request setValue:@"application/x-www-form-urlencoded"
52 forHTTPHeaderField:@"Content-Type"];
54 return request.autorelease();
58 #pragma mark - GaiaAuthFetcherNavigationDelegate
60 @implementation GaiaAuthFetcherNavigationDelegate {
61 GaiaAuthFetcherIOSBridge* bridge_; // weak
64 - (instancetype)initWithBridge:(GaiaAuthFetcherIOSBridge*)bridge {
72 #pragma mark WKNavigationDelegate
74 - (void)webView:(WKWebView*)webView
75 didFailNavigation:(WKNavigation*)navigation
76 withError:(NSError*)error {
77 DVLOG(1) << "Gaia fetcher navigation failed: "
78 << base::SysNSStringToUTF8(error.localizedDescription);
79 bridge_->URLFetchFailure(false /* is_cancelled */);
82 - (void)webView:(WKWebView*)webView
83 didFailProvisionalNavigation:(WKNavigation*)navigation
84 withError:(NSError*)error {
85 DVLOG(1) << "Gaia fetcher provisional navigation failed: "
86 << base::SysNSStringToUTF8(error.localizedDescription);
87 bridge_->URLFetchFailure(false /* is_cancelled */);
90 - (void)webView:(WKWebView*)webView
91 didFinishNavigation:(WKNavigation*)navigation {
92 // A WKNavigation is an opaque object. The only way to access the body of the
93 // response is via Javascript.
94 [webView evaluateJavaScript:@"document.body.innerText"
95 completionHandler:^(id result, NSError* error) {
96 NSString* data = base::mac::ObjCCast<NSString>(result);
98 DVLOG(1) << "Gaia fetcher extract body failed:"
99 << base::SysNSStringToUTF8(error.localizedDescription);
100 bridge_->URLFetchFailure(false /* is_cancelled */);
102 bridge_->URLFetchSuccess(base::SysNSStringToUTF8(data));
109 #pragma mark - GaiaAuthFetcherIOSBridge::Request
111 GaiaAuthFetcherIOSBridge::Request::Request()
112 : pending(false), url(), headers(), body() {}
114 GaiaAuthFetcherIOSBridge::Request::Request(const GURL& request_url,
115 const std::string& request_headers,
116 const std::string& request_body)
119 headers(request_headers),
120 body(request_body) {}
122 #pragma mark - GaiaAuthFetcherIOSBridge
124 GaiaAuthFetcherIOSBridge::GaiaAuthFetcherIOSBridge(
125 GaiaAuthFetcherIOS* fetcher,
126 web::BrowserState* browser_state)
127 : browser_state_(browser_state), fetcher_(fetcher), request_() {
128 web::BrowserState::GetActiveStateManager(browser_state_)->AddObserver(this);
131 GaiaAuthFetcherIOSBridge::~GaiaAuthFetcherIOSBridge() {
132 web::BrowserState::GetActiveStateManager(browser_state_)
133 ->RemoveObserver(this);
137 void GaiaAuthFetcherIOSBridge::Fetch(const GURL& url,
138 const std::string& headers,
139 const std::string& body) {
140 request_ = Request(url, headers, body);
141 FetchPendingRequest();
144 void GaiaAuthFetcherIOSBridge::Cancel() {
145 if (!request_.pending) {
148 [GetWKWebView() stopLoading];
149 URLFetchFailure(true /* is_cancelled */);
152 void GaiaAuthFetcherIOSBridge::URLFetchSuccess(const std::string& data) {
153 if (!request_.pending) {
156 GURL url = FinishPendingRequest();
157 // WKWebViewNavigationDelegate API doesn't give any way to get the HTTP
158 // response code of a navigation. Default to 200 for success.
159 fetcher_->FetchComplete(url, data, net::ResponseCookies(),
160 net::URLRequestStatus(), 200);
163 void GaiaAuthFetcherIOSBridge::URLFetchFailure(bool is_cancelled) {
164 if (!request_.pending) {
167 GURL url = FinishPendingRequest();
168 // WKWebViewNavigationDelegate API doesn't give any way to get the HTTP
169 // response code of a navigation. Default to 500 for error.
170 int error = is_cancelled ? net::ERR_ABORTED : net::ERR_FAILED;
171 fetcher_->FetchComplete(url, std::string(), net::ResponseCookies(),
172 net::URLRequestStatus::FromError(error), 500);
175 void GaiaAuthFetcherIOSBridge::FetchPendingRequest() {
176 if (!request_.pending)
179 loadRequest:GetRequest(request_.body, request_.headers, request_.url)];
182 GURL GaiaAuthFetcherIOSBridge::FinishPendingRequest() {
183 GURL url = request_.url;
184 request_ = Request();
188 WKWebView* GaiaAuthFetcherIOSBridge::GetWKWebView() {
189 if (!web::BrowserState::GetActiveStateManager(browser_state_)->IsActive()) {
190 // |browser_state_| is not active, WKWebView linked to this browser state
191 // should not exist or be created.
195 web_view_.reset(CreateWKWebView());
196 navigation_delegate_.reset(
197 [[GaiaAuthFetcherNavigationDelegate alloc] initWithBridge:this]);
198 [web_view_ setNavigationDelegate:navigation_delegate_];
200 return web_view_.get();
203 void GaiaAuthFetcherIOSBridge::ResetWKWebView() {
204 [web_view_ setNavigationDelegate:nil];
205 [web_view_ stopLoading];
207 navigation_delegate_.reset();
210 WKWebView* GaiaAuthFetcherIOSBridge::CreateWKWebView() {
211 return web::CreateWKWebView(CGRectZero, browser_state_);
214 void GaiaAuthFetcherIOSBridge::OnActive() {
215 // |browser_state_| is now active. If there is a pending request, restart it.
216 FetchPendingRequest();
219 void GaiaAuthFetcherIOSBridge::OnInactive() {
220 // |browser_state_| is now inactive. Stop using |web_view_| and don't create
221 // a new one until it is active.
225 #pragma mark - GaiaAuthFetcherIOS definition
227 GaiaAuthFetcherIOS::GaiaAuthFetcherIOS(GaiaAuthConsumer* consumer,
228 const std::string& source,
229 net::URLRequestContextGetter* getter,
230 web::BrowserState* browser_state)
231 : GaiaAuthFetcher(consumer, source, getter),
232 bridge_(new GaiaAuthFetcherIOSBridge(this, browser_state)),
233 browser_state_(browser_state) {}
235 GaiaAuthFetcherIOS::~GaiaAuthFetcherIOS() {
238 void GaiaAuthFetcherIOS::CreateAndStartGaiaFetcher(const std::string& body,
239 const std::string& headers,
240 const GURL& gaia_gurl,
242 DCHECK(!HasPendingFetch()) << "Tried to fetch two things at once!";
244 bool cookies_required = !(load_flags & (net::LOAD_DO_NOT_SEND_COOKIES |
245 net::LOAD_DO_NOT_SAVE_COOKIES));
246 if (!ShouldUseGaiaAuthFetcherIOS() || !cookies_required) {
247 GaiaAuthFetcher::CreateAndStartGaiaFetcher(body, headers, gaia_gurl,
252 DVLOG(2) << "Gaia fetcher URL: " << gaia_gurl.spec();
253 DVLOG(2) << "Gaia fetcher headers: " << headers;
254 DVLOG(2) << "Gaia fetcher body: " << body;
256 // The fetch requires cookies and WKWebView is being used. The only way to do
257 // a network request with cookies sent and saved is by making it through a
259 SetPendingFetch(true);
260 bridge_->Fetch(gaia_gurl, headers, body);
263 void GaiaAuthFetcherIOS::CancelRequest() {
264 if (!HasPendingFetch()) {
268 GaiaAuthFetcher::CancelRequest();
271 void GaiaAuthFetcherIOS::FetchComplete(const GURL& url,
272 const std::string& data,
273 const net::ResponseCookies& cookies,
274 const net::URLRequestStatus& status,
276 DVLOG(2) << "Response " << url.spec() << ", code = " << response_code << "\n";
277 DVLOG(2) << "data: " << data << "\n";
278 SetPendingFetch(false);
279 DispatchFetchedRequest(url, data, cookies, status, response_code);
282 void GaiaAuthFetcherIOS::SetShouldUseGaiaAuthFetcherIOSForTesting(
283 bool use_gaia_fetcher_ios) {
284 g_should_use_gaia_auth_fetcher_ios = use_gaia_fetcher_ios;
287 bool GaiaAuthFetcherIOS::ShouldUseGaiaAuthFetcherIOS() {
288 return experimental_flags::IsWKWebViewEnabled() &&
289 g_should_use_gaia_auth_fetcher_ios;