I'm working on a payment system that runs in a UIWebView. The user completes a form on one site, and is taken to a payment gateway to process their card information. Once the payment has been processed, the user is taken back to a confirmation page on the first site.
The sites work as expected when tested in a normal browser, or even Mobile Safari. The sites are black boxes, and I can't change anything inside them. Apparently, the sites use relative URLs, and my issue occurs because my UIWebView is trying to load a page on the second domain with the base URL from the first domain.
For example,
User posts form from http://theform.com/page
User is taken to http://theform.com/OrderCC.aspx?orderRef=e59d7f53a693472cad8a76dd8fb64
In the second step, the user should have been taken to http://thepaymentgateway.com/OrderCC.aspx...
I'm trying to intercept requests to the wrong base URL, and reroute them to a correct one like this:
-(BOOL)webView:(UIWebView *)inWeb shouldStartLoadWithRequest:(NSURLRequest*)inRequest navigationType:(UIWebViewNavigationType)inType {
NSString *wrongURL = #"http://theform.com/OrderCC.aspx";
NSString *request = [[inRequest URL] absoluteString];
NSString *data = [[NSString alloc] initWithData:[inRequest HTTPBody] encoding:NSASCIIStringEncoding];
if ([request length] >= 33 && [[request substringWithRange:NSMakeRange(0, 33)] isEqualToString:wrongURL]) {
[webView loadData:[inRequest HTTPBody] MIMEType:#"text/html" textEncodingName:#"UTF-8" baseURL:[NSURL URLWithString:[NSString stringWithFormat:#"https://thepaymentgateway.com/OrderCC.aspx%#", [request substringFromIndex:33]]]];
return NO;
}
return YES;
}
After this runs, it seems my UIWebView is just displaying a blank page containing the POST data string. Where am I going wrong?
Related
I have a webview that when loaded, the user is logged in by a POST request. After they are logged in, I want them to be taken to a webpage. My POST request is a URL as follows:
- (void)viewDidLoad {
[super viewDidLoad];
[_scoreWebView setDelegate:self];
NSMutableURLRequest *detailRequest = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:#"myrul"]];
[detailRequest setHTTPMethod:#"POST"];
NSString *sendInfo =[NSString stringWithFormat:#"action=login&EMAIL=email&PASSWORD=password", nil];
NSData *infoData = [sendInfo dataUsingEncoding:NSUTF8StringEncoding];
[detailRequest setHTTPBody:infoData];
[_scoreWebView loadRequest:detailRequest];
}
This log in process works fine. However, after I send the user to my webpage it is launching webviewdidfinishload infinitely. I know that it fires each time something is loaded. Is there an alternate solution to redirecting the user to my page after log in? Also, I have three different pages that the user could be redirected to based on their input, this is just one of them for simplicity. This is my finishload method:
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
//Check here if still webview is loding the content
if (webView.isLoading)
return;
else //finished
NSLog(#"finished loading");
NSLog(#"%# in calendar", _thisScriptUniqueID);
NSString *fullURL=[NSString stringWithFormat:#"myurl/%##score", _thisScriptUniqueID];
NSLog(#"%#", fullURL);
NSURL *url = [NSURL URLWithString:fullURL];
NSURLRequest *requestObj = [NSURLRequest requestWithURL:url];
[_scoreWebView loadRequest:requestObj];
}
Is there a different method that could be used to take the user to the page, or would it be possible to include both in the viewDidLoad?
After a lot of research, I decided that the functionality would work a lot better on the server side. I made it so that the same URL logs the user in and brings them to the desired page at the same time.
I'm building a small REST service to authorize users into my app.
At one point, the UIWebView I'm using to authorize the user, will go to https://myautholink.com/login.php. This page sends a JSON response with an authorization token. The thing about this page is that it receives some data via GET via my authorization form. I cannot use PHP sessions because you arrive to this page via:
header("location:https://myautholink.com/login.php?user_id=1&machine_id=machine_id&machine_name=machine_name&app_id=app_id");
Since the header function sends in headers, I cannot do a session_start(); at the same time.
I can get the UIWebView's request URL without a problem using the delegate methods:
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
NSURLRequest *request = [webView request];
NSLog(#"%#", [[request URL] relativeString]);
if([[[request URL] absoluteString] isEqualToString:SPAtajosLoginLink])
{
//Store auth token and dismiss auth web view.
}
}
The thing is none of the NSURL methods seem to return the "clean" link without the parameters. I have looked at all the NSURL url-string related methods:
- (NSString *)absoluteString;
- (NSString *)relativeString; // The relative portion of a URL. If baseURL is nil, or if the receiver is itself absolute, this is the same as absoluteString
But absoluteString is always the full URL with the GET parameters and relativeString is always nil.
I'm scratching my head with this and I can't seem to find the solution. Any help will be appreciated.
Rather than mess about with your own string manipulation, hand off to NSURLComponents:
NSURLComponents *components = [NSURLComponents componentsWithURL:url];
components.query = nil; // remove the query
components.fragments = nil; // probably want to strip this too for good measure
url = [components URL];
On iOS 6 and earlier, you can bring in KSURLComponents to achieve the same result.
Example: http://www.google.com:80/a/b/c;params?m=n&o=p#fragment
Use these methods of NSURL:
scheme: http
host: www.google.com
port: 80
path: /a/b/c
relativePath: /a/b/c
parameterString: params
query: m=n&o=p
fragment: fragment
Or, in iOS 7, build a NSURLComponents instance, then use the methods scheme, user, password, host, port, path, query, fragment, to extract part of the URL as strings. Then build the base URL back.
NSString* baseURLString = [NSString stringWithFormat:#"%#://%#/%#", URL.scheme, ...
NSURL *baseURL = [NSURL URLWithString:baseURLString];
To update this answer for iOS 7 onwards:
NSURLComponents *components = [NSURLComponents componentsWithURL: url resolvingAgainstBaseURL: NO];
components.query = nil; // remove the query
components.fragment = nil; // probably want to strip this too for good measure
url = [components URL];
Please note also that there is no 'fragments' property. It's just 'fragment'.
Otherwise, this method is great. Much better than worrying about putting the URL back together properly with string manips.
I'm an iOS newb (.NET professional), so this may be a simple issue but I couldn't find anything through the SO search or Google (and maybe not looking for the right terms).
I'm writing an app that displays information from a DD-WRT router through it's web interface. I have no problem displaying the initial page and navigating through any of the other pages, but if I make any change on a form (and it redirects to apply.cgi or applyuser.cgi), the UIWebView is blank - it's supposed to display the same page, with the form submission changes. The site works fine in Mobile Safari, which I find intriguing, but I guess UIWebView isn't totally the same.
I think the iOS code is pretty standard for display a webpage, but I'll list it below. I can't give you access to my router because, well, that's not a good idea :) Hopefully someone with a DD-WRT router can help (or know what my issue is anyway).
- (void)viewDidLoad
{
[super viewDidLoad];
NSString *sURL = #"http://user:pass#XXX.XXX.X.X";
NSURL *url = [NSURL URLWithString:sURL];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[self.webView loadRequest:request];
self.webView.delegate = self ;
}
And I'm doing a few things with Javascript in the webViewDidFinishLoad method, but I know that's not the culprit because it still happens when I comment it out.
Well I figured out the problem on my own. I think part of it was putting the username & password in the URL (which was just a temporary measure) because I found that method provided the same results in mobile Safari and desktop Chrome.
So I added MKNetworkKit to my project that provided a simple way to add authentication to my request, and found I had to make a specific request to POST the data, then reloaded the page the to see the changes.
In the (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType method, I check if ([request.HTTPMethod isEqualToString:#"POST"]) and do this:
NSString *sPostData = [[NSString alloc] initWithData:request.HTTPBody encoding:NSUTF8StringEncoding];
NSArray *aPostData = [sPostData componentsSeparatedByString:#"&"];
NSMutableDictionary *dPostData = [[NSMutableDictionary alloc] init];
//i don't know if this is the best way to set a dictionary, but it works
for (id apd in aPostData)
{
NSString *key = [apd componentsSeparatedByString:#"="][0];
NSString *val = [apd componentsSeparatedByString:#"="][1];
[dPostData setValue:val forKey:key];
}
MKNetworkEngine *engine = [[MKNetworkEngine alloc] init];
MKNetworkOperation *op = [engine operationWithURLString:[request.URL description] params:dPostData httpMethod:#"POST"];
[op setUsername:#"myUserName" password:#"myPassword" basicAuth:YES];
self.postedRequest = TRUE; //a bool I set so, when it comes to webViewDidFinishLoad, I reload the current page
[op start]; //send POST operation
I'm starting to notice a change in the way that youtube videos are being loaded into UIWebViews and I wanted to know if this is behavior we should be expecting in the future and/or if we can replicate the previous functionality.
Comparison screenshot :
Old on the right, new on the left. The added youtube button allows users to leave the youtube video and go into the youtube web interface. I would like to be able to prevent the user from leaving the video being played.
I am currently using a category on UIWebView like this :
- (void)loadYouTubeEmbed:(NSString *)videoId
{
NSString* searchQuery = [NSString stringWithFormat:#"http://www.youtube.com/embed/%#?showinfo=0&loop=1&modestbranding=1&controls=0",videoId];
searchQuery = [searchQuery stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:searchQuery]];
[self loadRequest:request];
}
I've noticed that my query will respect either modestbranding=1 or showinfo=0 but not both at the same time. Will this change as the youtube redesign rolls out?
When the Youtube video is loaded, and webView:shouldStartLoadWithRequest:navigationType: is hit, you should be able to filter out that link so it won't proceed.
-(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
if ([[[request URL] absoluteString] isEqualToString:#"<URL String Youtube spits out when video selected>"]) {
NSLog(#"Blocking YouTube...");
return NO;
} else {
NSLog(#"Link is fine, continue...");
return YES;
}
}
I'm building an iPhone app that is just a UIWebView of an existing mobile site that has a form-based login. When I login to the mobile site on iPhone Safari, I'm prompted to save my username/password, and it's then autofilled when I go back to the site later.
I'd like to enable the same functionality in the UIWebView, but for the life of me, I can't figure out how to do it. Any ideas?
Solution
Following Michael's basic model (see accepted answer), I was able to get this done. Here's what I did:
SETTING DATA
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType; {
//save form data
if(navigationType == UIWebViewNavigationTypeFormSubmitted) {
//grab the data from the page
NSString *username = [self.webView stringByEvaluatingJavaScriptFromString: #"document.myForm.username.value"];
NSString *password = [self.webView stringByEvaluatingJavaScriptFromString: #"document.myForm.password.value"];
//store values locally
[[NSUserDefaults standardUserDefaults] setObject:username forKey:#"username"];
[SFHFKeychainUtils storeUsername:username andPassword:password forServiceName:#"MyService" updateExisting:YES error:nil];
}
}
GETTING DATA
- (void)webViewDidFinishLoad:(UIWebView *)webView{
//verify view is on the login page of the site (simplified)
NSURL *requestURL = [self.webView.request URL];
if ([requestURL.host isEqualToString:#"www.mydomain.com"]) {
//check for stored login credentials
NSString *username = [[NSUserDefaults standardUserDefaults] objectForKey:#"username"];
if (username.length != 0 ) {
//create js strings
NSString *loadUsernameJS = [NSString stringWithFormat:#"document.myForm.username.value ='%#'", username];
NSString *password = [SFHFKeychainUtils getPasswordForUsername: username andServiceName:#"MyService" error:nil];
if (password.length == 0 ) password = #"";
NSString *loadPasswordJS = [NSString stringWithFormat:#"document.myForm.password.value ='%#'", password];
//autofill the form
[self.webView stringByEvaluatingJavaScriptFromString: loadUsernameJS];
[self.webView stringByEvaluatingJavaScriptFromString: loadPasswordJS];
}
}
}
Note that I'm using Buzz Andersen's awesome SFHFKeychainUtils package to store sensitive data to the iOs Keychain.
In order to get SFHFKeychainUtils working, you need to do a few things:
Add SFHFKeychainUtils.h and SFHFKeychainUtils.m to your project
Add the Security.framework to your project
#import <Security/Security.h> and #import "SFHFKeychainUtils.h"
From my looking I don't think there is an easy way to do it. Here is an idea of what might work though:
create your uiwebview
create a nsurlrequest
after your webview delegate page loaded function fires look in the request's http body
find the form for login (regex for common login forms?)
retrieve give the user the option to save it and then retrieve later