Upstreaming browser/ui/uikit_ui_util from iOS.
[chromium-blink-merge.git] / ios / chrome / browser / signin / gaia_auth_fetcher_ios.mm
blobe0d18fcec25cc1b9dbdf4082ab8bbda45eba8fa2
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
111 GaiaAuthFetcherIOSBridge::GaiaAuthFetcherIOSBridge(
112     GaiaAuthFetcherIOS* fetcher,
113     web::BrowserState* browser_state)
114     : fetch_pending_(false),
115       fetcher_(fetcher),
116       url_(),
117       navigation_delegate_(
118           [[GaiaAuthFetcherNavigationDelegate alloc] initWithBridge:this]),
119       web_view_(web::CreateWKWebView(CGRectZero, browser_state)) {
120   [web_view_ setNavigationDelegate:navigation_delegate_];
123 GaiaAuthFetcherIOSBridge::~GaiaAuthFetcherIOSBridge() {
124   [web_view_ setNavigationDelegate:nil];
125   [web_view_ stopLoading];
126   web_view_.reset();
127   navigation_delegate_.reset();
130 void GaiaAuthFetcherIOSBridge::Fetch(const GURL& url,
131                                      const std::string& headers,
132                                      const std::string& body) {
133   fetch_pending_ = true;
134   url_ = url;
135   [web_view_ loadRequest:GetRequest(body, headers, url)];
138 void GaiaAuthFetcherIOSBridge::Cancel() {
139   if (!fetch_pending_) {
140     return;
141   }
142   [web_view_ stopLoading];
143   URLFetchFailure(true /* is_cancelled */);
146 void GaiaAuthFetcherIOSBridge::URLFetchSuccess(const std::string& data) {
147   if (!fetch_pending_) {
148     return;
149   }
150   fetch_pending_ = false;
151   // WKWebViewNavigationDelegate API doesn't give any way to get the HTTP
152   // response code of a navigation. Default to 200 for success.
153   fetcher_->FetchComplete(url_, data, net::ResponseCookies(),
154                           net::URLRequestStatus(), 200);
157 void GaiaAuthFetcherIOSBridge::URLFetchFailure(bool is_cancelled) {
158   if (!fetch_pending_) {
159     return;
160   }
161   fetch_pending_ = false;
162   // WKWebViewNavigationDelegate API doesn't give any way to get the HTTP
163   // response code of a navigation. Default to 500 for error.
164   int error = is_cancelled ? net::ERR_ABORTED : net::ERR_FAILED;
165   fetcher_->FetchComplete(url_, std::string(), net::ResponseCookies(),
166                           net::URLRequestStatus::FromError(error), 500);
169 #pragma mark - GaiaAuthFetcherIOS definition
171 GaiaAuthFetcherIOS::GaiaAuthFetcherIOS(GaiaAuthConsumer* consumer,
172                                        const std::string& source,
173                                        net::URLRequestContextGetter* getter,
174                                        web::BrowserState* browser_state)
175     : GaiaAuthFetcher(consumer, source, getter),
176       bridge_(),
177       browser_state_(browser_state) {
180 GaiaAuthFetcherIOS::~GaiaAuthFetcherIOS() {
183 void GaiaAuthFetcherIOS::CreateAndStartGaiaFetcher(const std::string& body,
184                                                    const std::string& headers,
185                                                    const GURL& gaia_gurl,
186                                                    int load_flags) {
187   DCHECK(!HasPendingFetch()) << "Tried to fetch two things at once!";
189   bool cookies_required = !(load_flags & (net::LOAD_DO_NOT_SEND_COOKIES |
190                                           net::LOAD_DO_NOT_SAVE_COOKIES));
191   if (!ShouldUseGaiaAuthFetcherIOS() || !cookies_required) {
192     GaiaAuthFetcher::CreateAndStartGaiaFetcher(body, headers, gaia_gurl,
193                                                load_flags);
194     return;
195   }
197   DVLOG(2) << "Gaia fetcher URL: " << gaia_gurl.spec();
198   DVLOG(2) << "Gaia fetcher headers: " << headers;
199   DVLOG(2) << "Gaia fetcher body: " << body;
201   // The fetch requires cookies and WKWebView is being used. The only way to do
202   // a network request with cookies sent and saved is by making it through a
203   // WKWebView.
204   SetPendingFetch(true);
205   if (!bridge_) {
206     bridge_.reset(new GaiaAuthFetcherIOSBridge(this, browser_state_));
207   }
208   bridge_->Fetch(gaia_gurl, headers, body);
211 void GaiaAuthFetcherIOS::CancelRequest() {
212   if (!HasPendingFetch()) {
213     return;
214   }
215   if (bridge_) {
216     bridge_->Cancel();
217   }
218   GaiaAuthFetcher::CancelRequest();
221 void GaiaAuthFetcherIOS::FetchComplete(const GURL& url,
222                                        const std::string& data,
223                                        const net::ResponseCookies& cookies,
224                                        const net::URLRequestStatus& status,
225                                        int response_code) {
226   DVLOG(2) << "Response " << url.spec() << ", code = " << response_code << "\n";
227   DVLOG(2) << "data: " << data << "\n";
228   SetPendingFetch(false);
229   DispatchFetchedRequest(url, data, cookies, status, response_code);
232 void GaiaAuthFetcherIOS::SetShouldUseGaiaAuthFetcherIOSForTesting(
233     bool use_gaia_fetcher_ios) {
234   g_should_use_gaia_auth_fetcher_ios = use_gaia_fetcher_ios;
237 bool GaiaAuthFetcherIOS::ShouldUseGaiaAuthFetcherIOS() {
238   return experimental_flags::IsWKWebViewEnabled() &&
239          g_should_use_gaia_auth_fetcher_ios;