Re-enable GaiaAuthFetcherIOS.
[chromium-blink-merge.git] / ios / chrome / browser / signin / gaia_auth_fetcher_ios.mm
blobd589b3c5c6210140bc6bf92129b34848715cecf7
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"
24 namespace {
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|
30 // and |headers|.
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,
34                          const GURL& url) {
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())];
42   }
43   if (!body.empty()) {
44     // TODO(bzanotti): HTTPBody is currently ignored in POST Request on
45     // WKWebView. Add a workaround here.
46     NSData* post_data =
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"];
53   }
54   return request.autorelease();
58 #pragma mark - GaiaAuthFetcherNavigationDelegate
60 @implementation GaiaAuthFetcherNavigationDelegate {
61   GaiaAuthFetcherIOSBridge* bridge_;  // weak
64 - (instancetype)initWithBridge:(GaiaAuthFetcherIOSBridge*)bridge {
65   self = [super init];
66   if (self) {
67     bridge_ = bridge;
68   }
69   return self;
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);
97               if (error || !data) {
98                 DVLOG(1) << "Gaia fetcher extract body failed:"
99                          << base::SysNSStringToUTF8(error.localizedDescription);
100                 bridge_->URLFetchFailure(false /* is_cancelled */);
101               } else {
102                 bridge_->URLFetchSuccess(base::SysNSStringToUTF8(data));
103               }
104             }];
107 @end
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)
117     : pending(true),
118       url(request_url),
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);
134   ResetWKWebView();
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) {
146     return;
147   }
148   [GetWKWebView() stopLoading];
149   URLFetchFailure(true /* is_cancelled */);
152 void GaiaAuthFetcherIOSBridge::URLFetchSuccess(const std::string& data) {
153   if (!request_.pending) {
154     return;
155   }
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) {
165     return;
166   }
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)
177     return;
178   [GetWKWebView()
179       loadRequest:GetRequest(request_.body, request_.headers, request_.url)];
182 GURL GaiaAuthFetcherIOSBridge::FinishPendingRequest() {
183   GURL url = request_.url;
184   request_ = Request();
185   return url;
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.
192     return nil;
193   }
194   if (!web_view_) {
195     web_view_.reset(CreateWKWebView());
196     navigation_delegate_.reset(
197         [[GaiaAuthFetcherNavigationDelegate alloc] initWithBridge:this]);
198     [web_view_ setNavigationDelegate:navigation_delegate_];
199   }
200   return web_view_.get();
203 void GaiaAuthFetcherIOSBridge::ResetWKWebView() {
204   [web_view_ setNavigationDelegate:nil];
205   [web_view_ stopLoading];
206   web_view_.reset();
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.
222   ResetWKWebView();
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,
241                                                    int load_flags) {
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,
248                                                load_flags);
249     return;
250   }
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
258   // WKWebView.
259   SetPendingFetch(true);
260   bridge_->Fetch(gaia_gurl, headers, body);
263 void GaiaAuthFetcherIOS::CancelRequest() {
264   if (!HasPendingFetch()) {
265     return;
266   }
267   bridge_->Cancel();
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,
275                                        int response_code) {
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;