This question already has answers here:
How to use iOS Reachability
(3 answers)
Closed 9 years ago.
I'm writing an app for a college that will likely be used on campus. The college's wifi requires credentials to access the internet through a web page (such as AT&T hotspots). I would like my app to detect whether it's 'connected' to the internet or not. In the past, I've seen other applications redirect to Safari so the user can authenticate and then go back to the application. Does anyone know how to detect this sort of thing without simply trying to grab NSData from a connection (such as google.com) and then assuming if no data is grabbed this is the issue?
iOS automatically brings up a web view when you are trying to connect to a network that has a captive portal. To make sure you are connected and authenticated in your app, you should set UIRequiresPersistentWiFi in your Info.plist.
EDIT: My answer above is only for apps that require an internet connection. If you're just checking whether you are connected and authenticated, I believe you just have to use Reachability and check that you are ReachableViaWiFi. (I believe SystemConfiguration will not say you are reachable via Wi-Fi if you are not authenticated.)
If you are looking to handle the captive network authentication in your app instead of in the iOS default web view, you can use the CaptiveNetwork API.
Since you don't want to use a NSData way (I don't like grabbing NSData either as well as using reachability) Here is what I came up with that is more lightweight because it just checks the HEAD response :) :
- (BOOL)connectedToInternet
{
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:
[NSURL URLWithString:#"http://www.google.com/"]];
[request setHTTPMethod:#"HEAD"];
NSHTTPURLResponse *response;
[NSURLConnection sendSynchronousRequest:request
returningResponse:&response error: NULL];
return ([response statusCode] == 200) ? YES : NO;
}
- (void)yourMethod
{
if([self connectedToInternet] == NO)
{
// Not connected to the internet
}
else
{
// Connected to the internet
}
}
If you don't want to use Reachability you could initiate a NSURLConnection to a random website while on the campus wifi and check for an authentication challenge.
Set up the NSURLConnection:
// Create the request.
NSURLRequest *theRequest=[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.apple.com/"]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
// create the connection with the request
// and start loading the data
NSURLConnection *theConnection=[[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
if (theConnection) {
// Create the NSMutableData to hold the received data.
// receivedData is an instance variable declared elsewhere.
receivedData = [[NSMutableData data] retain];
} else {
// Inform the user that the connection failed.
}
Implement the auth challange delegate method:
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
NSLog(#"I'm being challenged.");
}
Then do what you want after the challenge.
Without going and looking at Reachability, if you try to use it to reach a host and it is presented with an auth challenge, it may return that there is no connection because it couldn't reach the specified host. Again, not 100% sure if this statement is accurate.
Related
I have a method which starts an NSURLConnetion to read an IP-Address an return the IP after the connection did finish loading:
- (NSString *)getHansIP
{
self.returnData = [[NSMutableData alloc] init];
NSMutableURLRequest *getIpRequest = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:#"https://example.com"]];
[getIpRequest setHTTPMethod:#"GET"];
NSURLConnection *ipconn = [NSURLConnection connectionWithRequest:getIpRequest delegate:self];
[ipconn start];
return self.ipString;
}
The problem is that objective-c tries to return the IP-Address (ipString) when the connection did not finished loading yet. I know there is a simple way to fix it but with this way the NSURLConnectionDelegate methods do not getting executed and I need thedidReceiveAuthenticationChallenge Method an the canAuthenticateAgainstProtectionSpace method.
P.S. hope you understand my bad school english :P
You can't return the IP direct from the method, not without blocking the thread anyway, and you can't really do that and handle auth issues.
You need to create an object to manage this which is the delegate for the connection, maintains the connection as an instance variable while it's processing and has a delegate or a completion callback block to pass the IP back with once it's done.
You need to embrace the fact that the connection is asynchronous and not want to return the IP from the method directly.
I am retrieving data via NSURLRequest and it is working perfectly. I have added password protection to that directory now and I am not sure how to configure my code to add in the password for the directory (myStuff)
Can anyone tell me how this is done based on my current code?
Thank you
-(void) retrieve
{
NSURLRequest *theRequest=[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://XXXXXXX.com/myStuff/test.php"]cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:8.0];
NSURLConnection *theConnection=[[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
if (theConnection) {
receivedData = [[NSMutableData data] retain];
} else {
// Inform the user that the connection failed.
[self showServerAlert];
return;
}
}
//CALL THE OTHER DELEGATE METHODS
The standard URL syntax would be:
http://username:password#XXXXX.com/myStuff/test.php
When doing this though you should not use http. You really want to use https. And don't hardcode the password in your code. A hacker could easily figure it out.
If a user attempts to load a https web page in Mobile Safari and the server's certificate validation check fails (its expired, revoked, self-signed etc.) then the user is presented is presented with a warning message and asked if they want to continue or not.
Similarly NSURLConnection offers the ability for the implementator to decide firstly how to check the certificate and then decide how to proceed if it fails, so in this situation too it would be possible to display a warning to the user and offer them the opportunity to continue loading the page or not.
However it seems when loading a https page in UIWebView that fails a certificate check the behaviour is just to fail to load the page - didFailLoadWithError: gets called with kCFURLErrorServerCertificateUntrusted however nothing gets displayed to the user.
This is inconsistent - surely the UIWebView behaviour should behave in a similar way to Safari to be consistent within iPhone itself?
Its also a daft that NSURLConnection allows total flexibility with this yet NSURLRequest:setAllowsAnyHTTPSCertificate is private.
Is there anyway to implement behaviour which is consistent with Safari, can this default behavior be customized in a similar way to NSURLConnection allows?
Cheers
P.S.
Please refrain from getting into patronizing side discussions about why would anybody want to do this, thank you very much.
I found out how to do this:
1) When the page is loaded it will fail, thus add something like the following to didFailLoadWithError:
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
if ([error.domain isEqualToString: NSURLErrorDomain])
{
if (error.code == kCFURLErrorServerCertificateHasBadDate ||
error.code == kCFURLErrorServerCertificateUntrusted ||
error.code == kCFURLErrorServerCertificateHasUnknownRoot ||
error.code == kCFURLErrorServerCertificateNotYetValid)
{
display dialog to user telling them what happened and if they want to proceed
2) If the user wants to load the page then you need to connect using an NSURLConnection:
NSURLRequest *requestObj = [NSURLRequest requestWithURL:self.currentURL cachePolicy:NSURLRequestReturnCacheDataElseLoad timeoutInterval:10.0];
self.loadingUnvalidatedHTTPSPage = YES;
[self.webView loadRequest:requestObj];
3) Then make this change to shouldStartLoadWithRequest
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
if (self.loadingUnvalidatedHTTPSPage)
{
self.connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
[self.connection start];
return NO;
}
4) Implement the NSURLConnectionDelegate as:
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
SecTrustRef trust = challenge.protectionSpace.serverTrust;
NSURLCredential *cred;
cred = [NSURLCredential credentialForTrust:trust];
[challenge.sender useCredential:cred forAuthenticationChallenge:challenge];
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response;
{
NSURLRequest *requestObj = [NSURLRequest requestWithURL:self.currentURL cachePolicy:NSURLRequestReturnCacheDataElseLoad timeoutInterval:10.0];
self.loadingUnvalidatedHTTPSPage = NO;
[self.webView loadRequest: requestObj];
[self.connection cancel];
}
It all seems to work fine.
From the horse's mouth:
"UIWebView does not provide any way for an app to customize its HTTPS server trust evaluations. It is possible to work around this limitation using public APIs, but it is not easy. If you need to do this, please contact Developer Technical Support (dts#apple.com)
Source: http://developer.apple.com/library/ios/#technotes/tn2232/_index.html
This question already has answers here:
UIWebView to view self signed websites (No private api, not NSURLConnection) - is it possible?
(9 answers)
Closed 6 years ago.
We have an iOS app that uses a UIWebView to display content. We load it up with data with code that looks like this:
NSURL *url = [NSURL URLWithString:myURLString];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[_webView setDelegate:self];
[_webView loadRequest:request];
This used to work fine with HTTP requests, but now we are using HTTPS against a server with a self-signed SSL certificate. When the above is run, the webView:didFailLoadWithError: delegate method gets called, with this error:
The certificate for this server is invalid. You might be connecting to a server that is pretending to be "blah.blah.blah.com" which could put your confidential information at risk."
I would like to simply ignore the invalid certificate and go on with the request, as one can do in Mobile Safari.
I have seen how to work around this issue when using NSURLConnection (see HTTPS request on old iphone 3g, for example), but what can one do with a UIWebView?
I imagine that I could rework the code so that it uses NSURLConnection to make the requests and then puts the results into the web view by calling its loadHTMLString:baseURL: method, but that's going to get complicated when the pages have images, CSS, JavaScript, and so on. Is there an easier way?
Please note: This API is currently unsupported, and should really only be used in a safe testing environment. For further details, take a look at this CocoaNetics article.
[NSURLRequest setAllowsAnyHTTPSCertificate:YES forHost:[url host]]; will allow you to ignore certificate errors. You will also need to add the following to the beginning of your file to grant you access to these private APIs:
#interface NSURLRequest (DummyInterface)
+ (BOOL)allowsAnyHTTPSCertificateForHost:(NSString*)host;
+ (void)setAllowsAnyHTTPSCertificate:(BOOL)allow forHost:(NSString*)host;
#end
Just so everyone knows... the above use of hidden interfaces WILL NOT BE ACCEPTED BY APPLE. They look for use of private APIs and it is NOT an acceptable solution. So, please do not go posting the solution described above around as THE way to fix it because, although it works, it will buy you a rejection in the AppStore. That makes it useless.
What follows is the ACCEPTABLE method of ignoring invalid server certificates. You need to use NSURLConnection and load the data for the webpage manually like so:
.
.
.
//Create a URL object.
url = [NSURL URLWithString:urlAddress];
NSURLRequest *requestObj = [NSURLRequest requestWithURL:url];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:requestObj delegate:self];
[connection start];
}
And then, in your delegate....
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace
{
return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];
}
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
{
[challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
}
else
{
[challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
}
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[resultData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSString *htmlString = [[NSString alloc] initWithBytes:[resultData bytes] length:[resultData length] encoding:NSUTF8StringEncoding];
[webView loadHTMLString:htmlString baseURL:url];
}
#end
Where resultData is an NSMutableData you instantiated earlier and where url and urlAddress are both things you've instantiated and filled in elsewhere.
Unfortunately, I currently don't know a way to get the actual UIWebView to load a page directly without having a valid certificate.
Yours, GC
It turns out that once the site is authenticated by a cancelled NSURLConnection, the UIWebView can make requests to the site. There is a complete explanation here.
As far as I know, that isn't possible with just UIWebView. As I understand it, you need to use NSURLConnection to handle all the HTTP/HTTPS mojo and then feed its results to the UIWebView via -loadHtmlString:baseURL: or -loadData:MIMEType:textEncodingName:baseURL:.
I need to check and evaluate the HTTP Status Codes in my iPhone app. I've got an NSURLRequest object and an NSURLConnection that successfully (I think) connect to the site:
// create the request
NSURLRequest *theRequest=[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.apple.com/"]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
// create the connection with the request
// and start loading the data
NSURLConnection *theConnection=[[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
if (theConnection) {
// Create the NSMutableData that will hold
// the received data
// receivedData is declared as a method instance elsewhere
receivedData=[[NSMutableData data] retain];
} else {
// inform the user that the download could not be made
}
I got this code here: https://web.archive.org/web/20100908130503/http://developer.apple.com/iphone/library/documentation/Cocoa/Conceptual/URLLoadingSystem/Tasks/UsingNSURLConnection.html
But it doesn't (as far as I can see) say anything about accessing HTTP Status codes.
How to make a connection to a site and then check the HTTP Status Codes of that connection?
This is how I do it:
#pragma mark -
#pragma mark NSURLConnection Delegate Methods
- (void)connection:(NSURLConnection*)connection didReceiveResponse:(NSURLResponse*)response {
NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
int responseStatusCode = [httpResponse statusCode];
}
But I'm using an asynchronous NSURLConnection, so this may not help you. But I hope it does anyway!