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 // TODO(bzanotti): Having this be false is a temporary solution to
29 bool g_should_use_gaia_auth_fetcher_ios = false;
31 // Creates an NSURLRequest to |url| that can be loaded by a WebView from |body|
33 // The request is a GET if |body| is empty and a POST otherwise.
34 NSURLRequest* GetRequest(const std::string& body,
35 const std::string& headers,
37 base::scoped_nsobject<NSMutableURLRequest> request(
38 [[NSMutableURLRequest alloc] initWithURL:net::NSURLWithGURL(url)]);
39 net::HttpRequestHeaders request_headers;
40 request_headers.AddHeadersFromString(headers);
41 for (net::HttpRequestHeaders::Iterator it(request_headers); it.GetNext();) {
42 [request setValue:base::SysUTF8ToNSString(it.value())
43 forHTTPHeaderField:base::SysUTF8ToNSString(it.name())];
46 // TODO(bzanotti): HTTPBody is currently ignored in POST Request on
47 // WKWebView. Add a workaround here.
49 [base::SysUTF8ToNSString(body) dataUsingEncoding:NSUTF8StringEncoding];
50 [request setHTTPBody:post_data];
51 [request setHTTPMethod:@"POST"];
52 DCHECK(![[request allHTTPHeaderFields] objectForKey:@"Content-Type"]);
53 [request setValue:@"application/x-www-form-urlencoded"
54 forHTTPHeaderField:@"Content-Type"];
56 return request.autorelease();
60 #pragma mark - GaiaAuthFetcherNavigationDelegate
62 @implementation GaiaAuthFetcherNavigationDelegate {
63 GaiaAuthFetcherIOSBridge* bridge_; // weak
66 - (instancetype)initWithBridge:(GaiaAuthFetcherIOSBridge*)bridge {
74 #pragma mark WKNavigationDelegate
76 - (void)webView:(WKWebView*)webView
77 didFailNavigation:(WKNavigation*)navigation
78 withError:(NSError*)error {
79 DVLOG(1) << "Gaia fetcher navigation failed: "
80 << base::SysNSStringToUTF8(error.localizedDescription);
81 bridge_->URLFetchFailure(false /* is_cancelled */);
84 - (void)webView:(WKWebView*)webView
85 didFailProvisionalNavigation:(WKNavigation*)navigation
86 withError:(NSError*)error {
87 DVLOG(1) << "Gaia fetcher provisional navigation failed: "
88 << base::SysNSStringToUTF8(error.localizedDescription);
89 bridge_->URLFetchFailure(false /* is_cancelled */);
92 - (void)webView:(WKWebView*)webView
93 didFinishNavigation:(WKNavigation*)navigation {
94 // A WKNavigation is an opaque object. The only way to access the body of the
95 // response is via Javascript.
96 [webView evaluateJavaScript:@"document.body.innerText"
97 completionHandler:^(id result, NSError* error) {
98 NSString* data = base::mac::ObjCCast<NSString>(result);
100 DVLOG(1) << "Gaia fetcher extract body failed:"
101 << base::SysNSStringToUTF8(error.localizedDescription);
102 bridge_->URLFetchFailure(false /* is_cancelled */);
104 bridge_->URLFetchSuccess(base::SysNSStringToUTF8(data));
111 #pragma mark - GaiaAuthFetcherIOSBridge
113 GaiaAuthFetcherIOSBridge::GaiaAuthFetcherIOSBridge(
114 GaiaAuthFetcherIOS* fetcher,
115 web::BrowserState* browser_state)
116 : fetch_pending_(false),
119 navigation_delegate_(
120 [[GaiaAuthFetcherNavigationDelegate alloc] initWithBridge:this]),
121 web_view_(web::CreateWKWebView(CGRectZero, browser_state)) {
122 [web_view_ setNavigationDelegate:navigation_delegate_];
125 GaiaAuthFetcherIOSBridge::~GaiaAuthFetcherIOSBridge() {
126 [web_view_ setNavigationDelegate:nil];
127 [web_view_ stopLoading];
129 navigation_delegate_.reset();
132 void GaiaAuthFetcherIOSBridge::Fetch(const GURL& url,
133 const std::string& headers,
134 const std::string& body) {
135 fetch_pending_ = true;
137 [web_view_ loadRequest:GetRequest(body, headers, url)];
140 void GaiaAuthFetcherIOSBridge::Cancel() {
141 if (!fetch_pending_) {
144 [web_view_ stopLoading];
145 URLFetchFailure(true /* is_cancelled */);
148 void GaiaAuthFetcherIOSBridge::URLFetchSuccess(const std::string& data) {
149 if (!fetch_pending_) {
152 fetch_pending_ = false;
153 // WKWebViewNavigationDelegate API doesn't give any way to get the HTTP
154 // response code of a navigation. Default to 200 for success.
155 fetcher_->FetchComplete(url_, data, net::ResponseCookies(),
156 net::URLRequestStatus(), 200);
159 void GaiaAuthFetcherIOSBridge::URLFetchFailure(bool is_cancelled) {
160 if (!fetch_pending_) {
163 fetch_pending_ = false;
164 // WKWebViewNavigationDelegate API doesn't give any way to get the HTTP
165 // response code of a navigation. Default to 500 for error.
166 int error = is_cancelled ? net::ERR_ABORTED : net::ERR_FAILED;
167 fetcher_->FetchComplete(url_, std::string(), net::ResponseCookies(),
168 net::URLRequestStatus::FromError(error), 500);
171 #pragma mark - GaiaAuthFetcherIOS definition
173 GaiaAuthFetcherIOS::GaiaAuthFetcherIOS(GaiaAuthConsumer* consumer,
174 const std::string& source,
175 net::URLRequestContextGetter* getter,
176 web::BrowserState* browser_state)
177 : GaiaAuthFetcher(consumer, source, getter),
179 browser_state_(browser_state) {
182 GaiaAuthFetcherIOS::~GaiaAuthFetcherIOS() {
185 void GaiaAuthFetcherIOS::CreateAndStartGaiaFetcher(const std::string& body,
186 const std::string& headers,
187 const GURL& gaia_gurl,
189 DCHECK(!HasPendingFetch()) << "Tried to fetch two things at once!";
191 bool cookies_required = !(load_flags & (net::LOAD_DO_NOT_SEND_COOKIES |
192 net::LOAD_DO_NOT_SAVE_COOKIES));
193 if (!ShouldUseGaiaAuthFetcherIOS() || !cookies_required) {
194 GaiaAuthFetcher::CreateAndStartGaiaFetcher(body, headers, gaia_gurl,
199 DVLOG(2) << "Gaia fetcher URL: " << gaia_gurl.spec();
200 DVLOG(2) << "Gaia fetcher headers: " << headers;
201 DVLOG(2) << "Gaia fetcher body: " << body;
203 // The fetch requires cookies and WKWebView is being used. The only way to do
204 // a network request with cookies sent and saved is by making it through a
206 SetPendingFetch(true);
208 bridge_.reset(new GaiaAuthFetcherIOSBridge(this, browser_state_));
210 bridge_->Fetch(gaia_gurl, headers, body);
213 void GaiaAuthFetcherIOS::CancelRequest() {
214 if (!HasPendingFetch()) {
220 GaiaAuthFetcher::CancelRequest();
223 void GaiaAuthFetcherIOS::FetchComplete(const GURL& url,
224 const std::string& data,
225 const net::ResponseCookies& cookies,
226 const net::URLRequestStatus& status,
228 DVLOG(2) << "Response " << url.spec() << ", code = " << response_code << "\n";
229 DVLOG(2) << "data: " << data << "\n";
230 SetPendingFetch(false);
231 DispatchFetchedRequest(url, data, cookies, status, response_code);
234 void GaiaAuthFetcherIOS::SetShouldUseGaiaAuthFetcherIOSForTesting(
235 bool use_gaia_fetcher_ios) {
236 // TODO(bzanotti): Commenting the below line is a temporary solution to
238 // g_should_use_gaia_auth_fetcher_ios = use_gaia_fetcher_ios;
241 bool GaiaAuthFetcherIOS::ShouldUseGaiaAuthFetcherIOS() {
242 return experimental_flags::IsWKWebViewEnabled() &&
243 g_should_use_gaia_auth_fetcher_ios;