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
111 GaiaAuthFetcherIOSBridge::GaiaAuthFetcherIOSBridge(
112 GaiaAuthFetcherIOS* fetcher,
113 web::BrowserState* browser_state)
114 : fetch_pending_(false),
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];
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;
135 [web_view_ loadRequest:GetRequest(body, headers, url)];
138 void GaiaAuthFetcherIOSBridge::Cancel() {
139 if (!fetch_pending_) {
142 [web_view_ stopLoading];
143 URLFetchFailure(true /* is_cancelled */);
146 void GaiaAuthFetcherIOSBridge::URLFetchSuccess(const std::string& data) {
147 if (!fetch_pending_) {
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_) {
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),
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,
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,
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
204 SetPendingFetch(true);
206 bridge_.reset(new GaiaAuthFetcherIOSBridge(this, browser_state_));
208 bridge_->Fetch(gaia_gurl, headers, body);
211 void GaiaAuthFetcherIOS::CancelRequest() {
212 if (!HasPendingFetch()) {
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,
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;