Download file (using NSURLDownload) after Login (using POST Request) - objective-c

I am trying to download a file using NDURL Download. For that, I have to log in to a site.
I do this using a NSMutableURLRequest that I send using sendSynchronousRequest of NSURLConnection
The data that I receive from that message call is indeed the html page confirming my successful login.
To download the file I use the following code:
NSURLRequest *theRequest = [NSURLRequest requestWithURL:[NSURL URLWithString: #"http://www.domain.com/getfile.php?file=1"]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
// Create the connection with the request and start loading the data.
NSURLDownload *theDownload = [[NSURLDownload alloc] initWithRequest:theRequest
delegate:self];
if (theDownload) {
// Set the destination file.
NSLog(#"Starting Download...");
NSLog(#"%#", [theDownload description]);
[theDownload setDestination:destinationFilename allowOverwrite:YES];
pathToZipFile = destinationFilename;
} else {
NSLog(#"Download failed...");
return nil;
}
But the data I receive is the HTML page telling me I have to be logged in to download the file.
Any idea on this one?
Does the NSURLDownload have an different session than the NSURLConnection?
Thanks in advance!

Okey, so you have logged in and then you trying to download a file. But how the server knows you are the same user that has logged in before?
There are different ways how it can know it. Some cookie, some request parameter, some HTTP header. But you have to add something to the request, that says "I am the user that has logged in a minute ago".

I feel you have to implement delegates for NSURLDownload, like this :
- (void)downloadDidBegin:(NSURLDownload *)download{
}
- (void)download:(NSURLDownload *)download didReceiveResponse:(NSURLResponse *)response{
_totalLength = response.expectedContentLength;
}
- (void)download:(NSURLDownload *)download willResumeWithResponse:(NSURLResponse *)response fromByte:(long long)startingByte{
}
- (void)download:(NSURLDownload *)download didReceiveDataOfLength:(NSUInteger)length{
_recievedLength = _recievedLength + length;
}
- (void)downloadDidFinish:(NSURLDownload *)download{
//Completed//
}
- (void)download:(NSURLDownload *)download didFailWithError:(NSError *)error{
//Error
}

Related

How can I check if my OS X application can connect to a specific server-- assuming there is internet connection?

I currently am using AFNetworking to determine if my application has network reachability.
NSNumber *s = notification.userInfo[AFNetworkingReachabilityNotificationStatusItem];
AFNetworkReachabilityStatus status = [s integerValue];
if (status == AFNetworkReachabilityStatusReachableViaWWAN || status == AFNetworkReachabilityStatusReachableViaWiFi) {
But, now I also need to know if my application can reach a specific server. More specifically, the server I am connecting to may be down and I need a way to determine if this is the case, from the client side, so I can notify my users appropriately.
It's a very tough google because all searches I do just point me to "How to determine network reachability". Has anybody dealt with this before, and have a solution in mind?
EDIT: #AvT recommended a promising looking solution, so I tried it like this:
self.testTSCReachabilityManager = [AFNetworkReachabilityManager managerForDomain:#"www.asdasfjsldfkjslefjslkjslfs.com"];
__weak MyObject *weakSelf = self;
[self.testReachabilityManager setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
if (weakSelf.testReachabilityManager.reachable) {
NSLog(#"YES");
}
else
{
NSLog(#"NO");
}
}];
But unfortunately, it is logging out "YES" for me, even after I have confirmed it is most definitely not reachable.
Instantiate AFNetworkReachabilityManager with class method
+ (instancetype)managerForDomain:(NSString *)domain;
and pass string with the required domain. AFNetworkReachabilityManager will check reachability of this domain.
If serverURL is an url of your server you should use it the following way:
[AFNetworkReachabilityManager managerForDomain:serverURL.host]
Update
Following code works as expected:
static AFNetworkReachabilityManager *testTSCReachabilityManager;
testTSCReachabilityManager = [AFNetworkReachabilityManager managerForDomain:#"www.asdasfjsldfkjslefjslkjslfs.com"];
[testTSCReachabilityManager setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
if (testTSCReachabilityManager.reachable) {
NSLog(#"YES");
}
else
{
NSLog(#"NO");
}
}];
[testTSCReachabilityManager startMonitoring];
Update: I actually ended up going w/ a different implementation than what Avt recommended, and did what matt recommended in the comments instead
I created an NSURLRequest and make a request to my server, then used the delegate callbacks to determine if the server was reachable. Works like a charm
-(void)checkConnectionToServers
{
NSMutableURLRequest* request = [NSMutableURLRequest new];
request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:#"www.myserver.com"] cachePolicy:0 timeoutInterval:(NSTimeInterval)5.0];
[request setHTTPMethod:#"GET"];
[NSURLConnection connectionWithRequest:request delegate:self];
}
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
NSLog(#"SUCCESS");
}
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
NSLog(#"FAIL");
}

ConnectionKit and SFTP: How to authenticate CK2FileManager

For my Mac OS X Cocoa app, I am trying to
connect to a SFTP server that only accepts username/password credentials
get the contents of a remote directory
upload files
and find it surprisingly complicated.
After trying ConnectionKit (nearly no documentation), NMSSH (crashed once too often with simultaneous uploads), rsync (not supported by the server), sftp (needs key authentication if scripted, doesn't work with username/password), I am now back to ConnectionKit: https://github.com/karelia/ConnectionKit
However, I am struggling with the authentication challenge, as I don’t know what to do with my credential in the delegate method.
I downloaded and compiled ConnectionKit (apparently version 2).
I am trying to use CK2FileManager as the Readme indicates (is this the right approach at all? Or should I use the libssh2_sftp-Cocoa-wrapper instead?… however I had troubles with libssh2 blocking methods in NMSSH before)
I am successfully setting up my connection URL and
my delegates' -didReceiveAuthenticationChallenge is called
But this is where I struggle: I know how to create a NSURLCredential, however, I can’t figure out what to do with it =>
- (void)fileManager:(CK2FileManager *)manager
didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
NSURLCredential *credentials = [NSURLCredential
credentialWithUser:self.username
password:[self getPassword]
persistence:NSURLCredentialPersistenceForSession];
// what to do now?
// [manager useCredential:] doesn’t exist, nor is there a manager.connection?
// ...
}
I already read the header, I searched the archives of this list, but all answers seem to be outdated.
I also searched Google, Bing and StackOverflow and found one promising example from 2011 using CKFTPConnection, which doesn’t seem to be included in the current framework anymore.
Thanks so much for any pointer to the right direction.
tl;dr
I don't know how to respond to ConnectionKit's CK2FileManager authenticationChallenge:
see the comment in the code example
For CK2:
- (void)listDirectoryAtPath:(NSString *)path
{
// path is here #"download"
NSURL *ftpServer = [NSURL URLWithString:#"sftp://companyname.topLevelDomain"];
NSURL *directory = [CK2FileManager URLWithPath:path isDirectory:YES hostURL:ftpServer];
CK2FileManager *fileManager = [[CK2FileManager alloc] init];
fileManager.delegate = self;
[fileManager contentsOfDirectoryAtURL:directory
includingPropertiesForKeys:nil
options:NSDirectoryEnumerationSkipsHiddenFiles
completionHandler:^(NSArray *contents, NSError *error) {
if (!error) {
NSLog(#"%#", contents);
} else {
NSLog(#"ERROR: %#", error.localizedDescription);
}
}];
}
and than you have to implement the following protocol
- (void)fileManager:(CK2FileManager *)manager operation:(CK2FileOperation *)operation
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(CK2AuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
if (![challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodDefault]) {
completionHandler(CK2AuthChallengePerformDefaultHandling, nil);
return;
}
NSString * username = #"<username>";
NSString * pathToPrivateSSHKey = #"</Users/myNameOnLocalMaschine/.ssh/id_rsa>"
NSURLCredential *cred = [NSURLCredential ck2_credentialWithUser:username
publicKeyURL:nil
privateKeyURL:[NSURL fileURLWithPath:pathToPrivateSSHKey]
password:nil
persistence:NSURLCredentialPersistenceForSession];
completionHandler(CK2AuthChallengeUseCredential, cred);
}
That's it.
Call -listDirectoryAtPath: and then you will get in the Completion Handler Block in contents array all the files located on the given path :)
Okay, that was easy and I could have found out that on my own; just for the reference: [[challenge sender] useCredential:credentials forAuthenticationChallenge:challenge];
Sorry to reward myself for my own question, but maybe this code snippet helps filling the missing docs, this is how I connect to my SFTP server with ConnectionKit:
- (void)connectWithCompletionBlock:(void (^)(void))completionBlock {
if(!self.cFileManager) {
self.cFileManager = [[CK2FileManager alloc] init];
self.cFileManager.delegate = self;
}
NSURL *sftpServer = [NSURL URLWithString:[#"sftp://" stringByAppendingString:self.server]];
self.remoteFolder = [CK2FileManager URLWithPath:self.remotePath relativeToURL:sftpServer];
// try to get the contents of the current directory
[self.cFileManager contentsOfDirectoryAtURL:self.remoteFolder
includingPropertiesForKeys:nil
options:NSDirectoryEnumerationSkipsHiddenFiles
completionHandler:^(NSArray *contents, NSError *error)
{
NSLog(#"remote folder contents: \n%#", contents);
// invoke completion block
completionBlock();
}];
}
- (void)fileManager:(CK2FileManager *)manager
didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
NSURLCredential *credentials = [NSURLCredential
credentialWithUser:self.username
password:[self getPassword]
persistence:NSURLCredentialPersistenceForSession];
[[challenge sender] useCredential:credentials forAuthenticationChallenge:challenge]
}

AFNetworking POST Request Sending Blank Parameters to Server

I am trying to send a POST request to a server using AFNetworking, and everything seems to be working, i.e. the application is successfully pinging the server. However, the parameter values that it is sending are blank when it reaches the server even though after stepping through my code below using the debugger, the values appear to be being passed successfully. Any help on this would be greatly appreciated.
APIClient.m
#import "APIClient.h"
#import "AFJSONRequestOperation.h"
// Removed URL for privacy purposes.
static NSString * const kAPIBaseURLString = #"string goes here";
#implementation APIClient
+ (APIClient *)sharedClient {
static APIClient *_sharedClient;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedClient = [[APIClient alloc] initWithBaseURL:[NSURL URLWithString:kAPIBaseURLString]];
});
return _sharedClient;
}
- (id)initWithBaseURL:(NSURL *)url {
self = [super initWithBaseURL:url];
if (self) {
[self registerHTTPOperationClass:[AFJSONRequestOperation class]];
// Accept HTTP Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
[self setDefaultHeader:#"Accept" value:#"application/json"];
}
return self;
}
#end
Login Method in LoginBrain.m
- (void)loginUsingEmail:(NSString *)email andPassword:(NSString *)password withBlock:(void (^)(NSDictionary *loginResults))block {
self.email = email;
self.password = password;
// Removed path for privacy purposes
[[APIClient sharedClient] postPath:#"insert path here" parameters:[NSDictionary dictionaryWithObjectsAndKeys:email, #"uname", password, #"pw", nil] success:^(AFHTTPRequestOperation *operation, id responseJSON) {
if (block) {
block(responseJSON);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
if (block) {
block(nil);
}
}];
// Store user data in app?
}
Login Called Method in LoginViewController.m
- (IBAction)loginPressed {
[self.loginProgressIndicator startAnimating];
NSString *email = self.emailTextField.text;
NSString *password = self.passwordTextField.text;
[self.brain loginUsingEmail:email andPassword:password withBlock:^(NSDictionary *loginResults) {
[self.loginProgressIndicator stopAnimating];
[self.delegate uloopLoginViewController:self didLoginUserWithEmail:email andPassword:password];
}];
}
UPDATE
I tried changing the parameterEncoding as recommended here, but it did not fix the problem.
SECOND UPDATE
Here is the PHP code from the server side that is accessing the POST data. This was written by a co-worker of mine, as I don't do anything on the server side and am very unfamiliar with how it works.
header('Content-type: application/json');
$username = $_POST['uname'];
$pw = $_POST['pw'];
The server code is pretty straight forward. He has some sort of log script that checks to see what the variable values are, and he says that the client is hitting the server, but the variable values are blank.
THIRD UPDATE
This is a dump of the HTTP request by generating a print_r of the $_REQUEST variable:
Array ( [sid] => FwAqvZrfckw )
And here is a dump of the $_POST variable. As you can see, it's completely blank:
Array ( )
FOURTH UPDATE
I used Wireshark to capture the packet before it's being sent to the server, and everything appears to be in order:
Accept: application/json
Content-Type: application/x-www-form-urlencoded; charset=utf-8
And the POST parameters were all there as well. We also created a test file on the server side and just did a test POST to make sure that the code there is working, and it is.
Thank you.
With the same problem, using AFFormURLParameterEncoding was what I needed.
So just to simplify all the thread, you have to use :
[[APIClient sharedClient] setParameterEncoding:AFFormURLParameterEncoding];
I don't see anything in particular that would cause a problem here but I'll start off by giving you the steps I used to solve a similar problem.
To start, checkout the tool, Charles, which is a Debugging Web Proxy that will intercept the response from the server and should give you a more clear idea of what's going wrong. There's a 30 day free trial and it really helped me pick out the little bugs. To use it, press the sequence button and filter the results via your server url. From there you can see the request and response sent and received from the server. If the following doesn't fix your problem, post the request and response that Charles spits out.
Fix wise, try adding [[APIClient sharedClient] setParameterEncoding:AFJSONParameterEncoding] right before you send the POST request. It looks like yall are using JSON as the server-side format.
So in loginUsingEmail:
self.email = email;
self.password = password;
[[APIClient sharedClient] setParameterEncoding:AFJSONParameterEncoding];
[[APIClient sharedClient] postPath:#"insert path here" parameters:[NSDictionary dictionaryWithObjectsAndKeys:email, #"uname", password, #"pw", nil] success:^(AFHTTPRequestOperation *operation, id responseJSON) {
if (block) {
block(responseJSON);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
if (block) {
block(nil);
}
}];
// Store user data in app?
}

Download a cocoa bundle from server

how can I download a Cocoa .bundle file from a server and then load it into a app? I've tried using a zip but the shouldDecodeSourceDataOfMIMEType function doesn't get called.
- (IBAction)testDownload:(id)sender {
// Create the request.
NSURLRequest *theRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://localhost/SampleBundle.zip"]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
// Create the connection with the request and start loading the data.
NSURLDownload *theDownload = [[NSURLDownload alloc] initWithRequest:theRequest
delegate:self];
if (theDownload) {
// Set the destination file.
[theDownload setDestination:#"/Users/developer/Desktop/Test-Downloads/SampleBundle" allowOverwrite:YES];
} else {
// inform the user that the download failed.
}
}
- (void)download:(NSURLDownload *)download didFailWithError:(NSError *)error
{
download = nil;
// Inform the user.
NSLog(#"Download failed! Error - %# %#",
[error localizedDescription],
[[error userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey]);
}
- (void)downloadDidFinish:(NSURLDownload *)download
{
download = nil;
// Do something with the data.
NSLog(#"%#",#"downloadDidFinish");
}
- (BOOL)download:(NSURLDownload *)download shouldDecodeSourceDataOfMIMEType:(NSString *)encodingType;
{
BOOL shouldDecode = YES;
NSLog(#"EncodingType: %#",encodingType);
return shouldDecode;
}
So how can I download a .bundle from a server uncompress it and load it into the application?
Thanks
According to the documentation of download:shouldDecodeSourceDataOfMIMEType:
This method is not called if the downloaded file is not encoded.
So, I'm guessing that might have something to do with it. You're probably better off implementing download:didReceiveResponse: and examining the NSURLResponse object, especially the status code -- if that's not 200 than something is going wrong and you need to look at HTTP codes to see what exactly the problem is.
Also, I'm not sure of this, do you need elevated permissions to install a bundle, it being an executable?
The shouldDecodeSourceDataOfMIMEType delegate works great but only on gzip (.gz) archives. I've tested extensively with both .zip & .gz.
It should also be noted that it doesn't call tar, so if you applied compression and tar at the same time as in:
tar czvf ArchiveName.tar.gz ./ArchiveName/
the shouldDecodeSourceDataOfMIMEType delegate will leave you with:
ArchiveName.tar
So, the archive will not be immediately useable.
For .zip archives, as others have pointed out, your best bet is MiniZip (C API) or a Objective-C framework based on it like ZipArchive (2005) or the more recent SSZipArchive (2013).

How to find the 404 error cause, when I am using a webview based application and the server fails in iphone sdk

I am implementing a webview based application, in that I need to find out a way when the 404 error occurred.
Anyone's help will be much appreciated.
Thanks to all,
Monish.
In the webViewDidFinishLoad method, you can also check it this way:
NSCachedURLResponse *resp = [[NSURLCache sharedURLCache] cachedResponseForRequest:webView.request];
NSLog(#"status code: %ld", (long)[(NSHTTPURLResponse*)resp.response statusCode]);
webViewDidFinishLoad() method writes following code and checks status code...
- (void)webViewDidFinishLoad:(UIWebView *)webview {
NSCachedURLResponse *urlResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:webview.request];
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*) urlResponse.response;
NSInteger statusCode = httpResponse.statusCode;
}
Here you just need to check the status of the request when it finishes or fails in webview delegate method.`
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
int status = [[[webView request] valueForHTTPHeaderField:#"Status"] intValue];
if (status == 404) {
}
}
If this doesn't help you out. Check this one.
Create an NSURLRequest with the URL you want to load. Then make the connection using NSURLConnection.
NSURLConnection has a delegate method
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
which will give the the response from the server. Please note that if you are making the connection over HTTP, the response will actually be of class NSHTTPURLResponse. The NSHTTPURLResponse can be used to get the status using the following instance method
- (NSInteger)statusCode
Then check if status = 404 or not and if yes then show your alert view. In this way you will be able to show the html page and the alert view both.
Try this, I think that if URL is wrong then the return html is nil. you can handle it there only
NSString *htmlCode = [[NSString alloc] initWithContentsOfURL:[NSURL URLWithString:loadUrl]];
if (htmlCode==nil)
{
// you can handle here with an alert or any message in webview to load.
}
else
{
[myWebView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:loadUrl]]];
}
[htmlCode release];
You can handle using htmlCode in other method like
- (void)webViewDidFinishLoad:(UIWebView *)webView
if You want to handle it after request.