Can't download image with NSURLSession with some link - objective-c

I just can't download this image:
http://img2.imgtn.bdimg.com/it/u=3742910000,2737153630&fm=15&gp=0.jpg
I use NSURLSession downloadTask, but it's invalid, Chrome and safari can get it.
My code:
task = [_session downloadTaskWithURL:attachmentURL
completionHandler: ^(NSURL *temporaryFileLocation, NSURLResponse *response, NSError *error) {
if (error != nil) {
NSLog(#"error:%#", error.localizedDescription);
} else {
NSString *filePath = [self saveFrom:...];
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"%#", filePath);
NSData *data = [NSData dataWithContentsOfFile:filePath];
NSLog(#"data length: %lu", data.length);
// data length is error! smaller than should be
});
}
}];
[task resume];

All clients transmit more information than just 'get me this resource'. They also transmit data such as the user agent that's being used (i.e. what the client is). In the case of nsurlsession, it's set the user agent to something that does not look like a web browser, and in all odds the server is replying with a 403 error in that case (try to use curl on the URL and you'll get a 403 error).
Download normally:
$ curl "http://img2.imgtn.bdimg.com/it/u=3742910000,2737153630&fm=15&gp=0.jpg" -o img.jpg
$ file img.jpg
img.jpg: XML 1.0 document text, ASCII text
When I alter the user agent string to look like firefox:
$ curl -A 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:66.0) Gecko/20100101 Firefox/66.0' "http://img2.imgtn.bdimg.com/it/u=3742910000,2737153630&fm=15&gp=0.jpg" -o img.jpg
$ file img.jpg
img.jpg: JPEG image data, JFIF standard 1.01, resolution (DPI), density 72x72, segment length 16, baseline, precision 8, 500x333, frames 3
So we need to do the same, or similar for your nsurlsession. As we have the _session variable, we can do something like (this is only example code - please make your own):
NSMutableDictionary *md = [_session.configuration.httpAdditionalHeaders mutableCopy];
if (md == nil) {
md = [[NSMutableDictionary alloc] init];
}
md[#"User-Agent"] = #"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:66.0) Gecko/20100101 Firefox/66.0";
_session.configuration.HTTPAdditionalHeaders = md;
Although, if you're creating the session with some NSURlSessionConfiguration already, it might make sense to add the headers there.

Related

NSURLCache: inconsistent behaviour

I was observirng some strange behaviour of my app sometime caching responses and sometime not caching them (all the responses have Cache-Control: max-age=600).
The test is simple: I did a test.php script that was just setting the headers and returning a simple JSON:
<?php
header('Content-Type: application/json');
header('Cache-Control: max-age=600');
?>
{
"result": {
"employeeId": "<?php echo $_GET['eId']; ?>",
"dateTime": "<?php echo date('Y-m-d H:i:s'); ?>'" }
}
This is the response I get from the PHP page:
HTTP/1.1 200 OK
Date: Thu, 28 Nov 2013 11:41:55 GMT
Server: Apache
X-Powered-By: PHP/5.3.17
Cache-Control: max-age=600
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/json
{
"result": {
"employeeId": "",
"dateTime": "2013-11-28 11:41:55'"
}
}
Then I've created a simple app and added AFNetworking library.
When I call the script with few parameters, the cache works properly:
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
NSDictionary *params = #{
#"oId": #"4011",
#"eId": self.firstTest ? #"1" : #"0",
#"status": #"2031",
};
[manager GET:#"http://www.mydomain.co.uk/test.php" parameters:params success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"JSON: %#", responseObject);
NSLog(#"Cache current memory usage (after call): %d", [cache currentMemoryUsage]);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
But when I increase the number of parameters, like:
NSDictionary *params = #{
#"organizationId": #"4011",
#"organizationId2": #"4012",
#"organizationId3": #"4013",
#"organizationId4": #"4014",
#"organizationId5": #"4015",
#"organizationId6": #"4016",
#"eId": self.firstTest ? #"1" : #"0",
#"status": #"2031",
};
it doesn't work anymore and it execute a new request each time it is called.
I've done many tests and it seems to me that it is related to the length of the URL, because if I includes this set of params:
NSDictionary *params = #{
#"oId": #"4011",
#"oId2": #"4012",
#"oId3": #"4013",
#"oId4": #"4014",
#"oId5": #"4015",
#"oId6": #"4016",
#"eId": self.firstTest ? #"1" : #"0",
#"status": #"2031",
};
It works!!
I've done many tests and that's the only pattern I've found...
To exclude AFNetworking from the equation, I've created another test program that uses NSURLConnection only and I can see the same behaviour so it's not AFNetworking and definitely NSURLCache. This is the other test:
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"http://www.mydomain.co.uk/test.php?eId=%#&organizationId=4011&organizationId2=4012&organizationId3=4013&organizationId4=4014&organizationId5=4015&organizationId6=4016", self.firstTest ? #"1" : #"0"]]; // doesn't work
//NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"http://www.mydomain.co.uk/test.php?eId=%#&oId=4011&oId2=4012&oId3=4013&oId4=4014&oId5=4015&oId6=4016", self.firstTest ? #"1" : #"0"]]; // work
//NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"http://www.mydomain.co.uk/test.php?eId=%#", self.firstTest ? #"1" : #"0"]]; // work
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLResponse *response = nil;
NSError *error = nil;
NSData *data = [NSURLConnection sendSynchronousRequest:request
returningResponse:&response
error:&error];
if (error == nil) {
// Parse data here
NSString *responseDataStr = [NSString stringWithUTF8String:[data bytes]];
NSLog(#"Response data: %#", responseDataStr);
}
I've also tried to establish how many characters in the URL will trigger the problem but even in this case I've got strange results:
This one is 112 characters long and it doesn't work:
http://www.mydomain.co.uk/test.php?eId=1&organizationId=4011&organizationId2=4012&organizationId3=4013&orgaId4=4
This one is 111 characters long and it works:
http://www.mydomain.co.uk/test.php?eId=1&organizationId=4011&organizationId2=4012&organizationId3=4013&orgId4=4
Ive renamed the PHP script to see if the first part of the URL would matter and I've got a strange behaviour again:
This one is 106 characters long and it doesn't work:
http://www.mydomain.co.uk/t.php?eId=1&organizationId=4011&organizationId2=4012&organizationId3=4013&org=40
This one is 105 characters long and it works:
http://www.mydomain.co.uk/t.php?eId=1&organizationId=4011&organizationId2=4012&organizationId3=4013&org=4
So I've removed 3 characters from the page name and I've got a working threshold 6 characters lower.
Any suggestion?
Thanks,
Dem
I am witnessing something similar with certain responses not being cached by NSURLCache and I have come up with another possible reason:
In my case I have been able to ascertain that the responses not being cached are the ones that are returned using Chunked transfer-encoding. I've read elsewhere that NSURLCache should cache those after iOS 6 but for some reason it doesn't in my case (iOS 7.1 and 8.1).
I see that your example response shown here, also has the Transfer-Encoding: chunked header.
Could it be that some of your responses are returned with chunked encoding (those that are not cached) and some are not (those that are cached)?
My back-end is also running PHP on Apache and I still can't figure out why it does that...
Probably some Apache extension...
Anyway, I think it sounds more plausible than the request URL length scenario.
EDIT:
It's been a while, but I can finally confirm that in our case, it is the chunked transfer encoding that causes the response not to be cached. I have tested that with iOS 7.1, 8.1, 8.3 and 8.4.
Since I understand that it is not always easy to change that setting on your server, I have a solution to suggest, for people who are using AFNetworking 2 and subclassing AFHTTPSessionManager.
You could add your sub-class as an observer for AFNetworking's AFNetworkingTaskDidCompleteNotification, which contains all the things you will need to cache the responses yourself. That means: the session data task, the response object and the response data before it has been processed by the response serializer.
If your server uses chunked encoding for only a few of its responses, you could add code in -(void)didCompleteTask: to only cache responses selectively. So for example you could check for the transfer-encoding response header, or cache the response based on other criteria.
The example HTTPSessionManager sub-class below caches all responses that return any data:
MyHTTPSessionManager.h
#interface MyHTTPSessionManager : AFHTTPSessionManager
#end
MyHTTPSessionManager.m
#import "MyHTTPSessionManager.h"
#implementation MyHTTPSessionManager
+ (instancetype)sharedClient {
static MyHTTPClient *_sharedClient = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[[NSNotificationCenter defaultCenter] addObserver:_sharedClient selector:#selector(didCompleteTask:) name:AFNetworkingTaskDidCompleteNotification object:nil];
});
return _sharedClient;
}
- (void)didCompleteTask:(NSNotification *)notification {
NSURLSessionDataTask *task = notification.object;
NSHTTPURLResponse *response = (NSHTTPURLResponse *)task.response;
NSData *responseData = notification.userInfo[AFNetworkingTaskDidCompleteResponseDataKey];
if (!responseData.length) {
// Do not cache empty responses.
// You could place additional checks above to cache responses selectively.
return;
}
NSCachedURLResponse *cachedResponse = [[NSCachedURLResponse alloc] initWithResponse:response data:responseData];
[[NSURLCache sharedURLCache] storeCachedResponse:cachedResponse forRequest:task.currentRequest];
}
I tried to come up with some sort of cleaner solution, but it seems that AFNetworking does not provide a callback or a delegate method that returns everything we need early enough - that is, before it has been serialized by the response serializer.
Hope people will find this helpful :)
Did you try to configure
NSURLRequestCachePolicy
for NSURLRequest
+ (id)requestWithURL:(NSURL *)theURL cachePolicy:(NSURLRequestCachePolicy)cachePolicy timeoutInterval:(NSTimeInterval)timeoutInterval
These constants are used to specify interaction with the cached responses.
enum
{
NSURLRequestUseProtocolCachePolicy = 0,
NSURLRequestReloadIgnoringLocalCacheData = 1,
NSURLRequestReloadIgnoringLocalAndRemoteCacheData =4,
NSURLRequestReloadIgnoringCacheData = NSURLRequestReloadIgnoringLocalCacheData,
NSURLRequestReturnCacheDataElseLoad = 2,
NSURLRequestReturnCacheDataDontLoad = 3,
NSURLRequestReloadRevalidatingCacheData = 5
};
typedef NSUInteger NSURLRequestCachePolicy;
You could investigate what your cached response is from the sharedURLCache by subclassing NSURLProtocol and overriding startLoading:
add in AppDelegate application:didFinishLaunchingWithOptions:
[NSURLProtocol registerClass:[CustomURLProtocol class]];
Then create a subclass of NSURLProtocol (CustomURLProtol) and override startLoading
- (void)startLoading
{
self.cachedResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:self.request];
if (self.cachedResponse) {
[self.client URLProtocol:self
didReceiveResponse:[self.cachedResponse response]
cacheStoragePolicy:[self.cachedResponse storagePolicy]];
[self.client URLProtocol:self didLoadData:[self.cachedResponse data]];
}
[self.client URLProtocolDidFinishLoading:self];
}
self.cachedResponse is a property NSCachedURLResponse i've added. You can see if anything is wrong with any cachedResponse here.

How to extract text from a CAPTCHA image in Objective-C for a Mac OS X Application?

I am developing one Mac OS X Application which has a concept of reading/extracting text from a CAPTCHA image. I searched on google and got an API named "DeathByCaptcha", which does exactly what I want to do but this API is not for Mac OS X, this API is available for .Net/C/PHP/Python etc..
'DeathByCaptcha' can be found on the web here.
I found this Stack Overflow post while searching but this does not read a CAPTCHA image, it just reads a simple image and convert into text.
Please help me extract text From a CAPTCHA image using Objective-C for a Mac OS X Application.
After looking at the API, turns out you first need to build the library.
Open a terminal, navigate to the dbc_api_v4_2_c folder and type
make
You should get a new folder called lib containing the libdeathbycaptcha.so library file.
Now, create a new Xcode project, include the library in your resources, add the deathbycaptcha.h header to your project.
Then you can use the code provided in the example, but only the code that is not for windows. For instance :
void *lib = dlopen("./libdeathbycaptcha.so", RTLD_LAZY);
if (!lib) {
fprintf(stderr, "dlopen(): %s\n", dlerror());
exit(EXIT_FAILURE);
}
dbc_init = (void *)GetProcAddress(lib, "dbc_init");
dbc_close = (void *)GetProcAddress(lib, "dbc_close");
dbc_get_balance = (void *)GetProcAddress(lib, "dbc_get_balance");
dbc_decode_file = (void *)GetProcAddress(lib, "dbc_decode_file");
dbc_report = (void *)GetProcAddress(lib, "dbc_report");
dbc_close_captcha = (void *)GetProcAddress(lib, "dbc_close_captcha");
It should compile fine. Don't forget to include the following headers:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <dlfcn.h> // not in the docs, but required for dlsym
#include "deathbycaptcha.h"
In addition, you may also need to install the Xcode command line tools (in Xcode preferences) if you haven't already done so.
Hi all, I wrote below code to solve my captcha issue and it worked fantastic for me.
Code For Reading Text From Captcha Image
You will need ASIHTTPRequest API to send HTTP Request on web and get response for the below code.
Import respective classes of ASIHTTPRequest API into your class where you are going to do process for fetching text from captcha image.
Now use below method and call to solve catpcha .
-(NSString *)CaptchaToText
{
// Captcha Image Url in NSData object
NSData *urlData=[NSData dataWithContentsOfURL:[NSURL URLWithString:#"http://oi47.tinypic.com/357hyea.jpg"]];
// base64 image data
NSString *imageBase64Data = [ASIHTTPRequest base64forData:urlData ];
//NSString *imageBase64Data =[self base64EncodedString:urlData];
NSLog(#"imageBase64Data =%#\n\n",imageBase64Data);
// prefix - base64 image data
NSString *prefixBase64Data = [NSString stringWithFormat:#"base64:%#",imageBase64Data];
NSLog(#"prefixBase64Data Value = %#\n\n",prefixBase64Data);
ASIHTTPRequest *req=[ASIHTTPRequest requestWithURL:[NSURL URLWithString:#"http://oi47.tinypic.com/357hyea.jpg"]];
[req startSynchronous];
NSError *err=[req error];
if(!err){
NSString *respo=[req responseString];
NSLog(#"respo= %#",respo);
NSURL *url = [NSURL URLWithString:#"http://api.dbcapi.me/api/captcha"];
ASIFormDataRequest *request=[ASIFormDataRequest requestWithURL:url];
[request setPostValue:#"YourDeathByCaptchaUserName" forKey:#"username"];
[request setPostValue:#"YourDeathByCaptchaPassword!##$" forKey:#"password"];
[request setPostValue:prefixBase64Data forKey:#"captchafile"];
[request setRequestMethod:#"POST"];
[request startSynchronous];
NSError *error=[request error];
if(!error){
// Here we will get "Text" data from Captcha Image in "response"
NSString *response=[request responseString];
NSLog(#"response= %#",response);
}
else{
NSLog(#"error= %#",error);
}
}
else{
NSLog(#"err= %#",err);
}
return response;
}
That's it. DONE

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 can I download data from a server in cocoa (not touch)?

Like it's written in the title, How can I download data from a server in my Cocoa Application?
So far I looked for, I found this.
If you're not downloading a lot of things in parallel and you're doing a simple GET request, the easiest way to do it is to dispatch a synchronous request to one of the global queues:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSURLRequest* request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.google.com/"]];
NSURLResponse* response = nil;
NSError* error = nil;
NSData* result = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
// There will be response data in response now, like the http status code
// etc. You should check this information to make sure you didn't get a 404
// or some other http status error
if( result ) {
// you have a good result, do something with it like create a new object or
// pass it to a method on this object, etc.
dispatch_async(dispatch_get_main_queue(), ^{
[self doSomethingWithResponseData:result];
});
} else {
// You got an error making the connection, so handle it
NSLog(#"Error making connection: %#", error);
}
});
**note: this sample code uses GCD and therefore will only run on Snow Leopard (10.6) or better. If you need to target Leopard or Tiger, you can do the same thing using dispatched thread selectors, but not as in-line.
ASIHTTPRequest works for iPhone and Mac

Generate JSON object with transactionReceipt

I've been the past days trying to test my first in-app purchse iphone application. Unfortunately I can't find the way to talk to iTunes server to verify the transactionReceipt.
Because it's my first try with this technology I chose to verify the receipt directly from the iPhone instead using server support. But after trying to send the POST request with a JSON onbject created using the JSON api from google code, itunes always returns a strange response (instead the "status = 0" string I wait for).
Here's the code that I use to verify the receipt:
- (void)recordTransaction:(SKPaymentTransaction *)transaction {
NSString *receiptStr = [[NSString alloc] initWithData:transaction.transactionReceipt encoding:NSUTF8StringEncoding];
NSDictionary *jsonDictionary = [NSDictionary dictionaryWithObjectsAndKeys:#"algo mas",#"receipt-data",nil];
NSString *jsonString = [jsonDictionary JSONRepresentation];
NSLog(#"string to send: %#",jsonString);
NSLog(#"JSON Created");
urlData = [[NSMutableData data] retain];
//NSURL *sandboxStoreURL = [[NSURL alloc] initWithString:#"https://sandbox.itunes.apple.com/verifyReceipt"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:#"https://sandbox.itunes.apple.com/verifyReceipt"]];
[request setHTTPMethod:#"POST"];
[request setHTTPBody:[jsonString dataUsingEncoding:NSUTF8StringEncoding]];
NSLog(#"will create connection");
[[NSURLConnection alloc] initWithRequest:request delegate:self];
}
maybe I'm forgetting something in the request's headers but I think that the problem is in the method I use to create the JSON object.
HEre's how the JSON object looks like before I add it to the HTTPBody :
string to send: {"receipt-data":"{\n\t\"signature\" = \"AUYMbhY
...........
D0gIjEuMCI7Cn0=\";\n\t\"pod\" = \"100\";\n\t\"signing-status\" = \"0\";\n}"}
The responses I've got:
complete response {
exception = "java.lang.IllegalArgumentException: Property list parsing failed while attempting to read unquoted string. No allowable characters were found. At line number: 1, column: 0.";
status = 21002;
}
Thanks a lot for your guidance.
I have just fixed that after 2 days of struggling. You have to encode receipt using Base64 before inserting into json object. Like that (Ruby):
dataForVerification = {"receipt-data" => Base64.encode64(receipt)}.to_json
Base64 is not mentioned anywhere in the official docs (at least for SDK 3.0), only on a couple of blogs.
For instance, here the guy encodes the receipt in Base64 before passing it to the PHP server, but does not decode it back in PHP, thus sending Base64-encoded string to iTunes.
Re: "21002: java.lang.IllegalArgumentException: propertyListFromString parsed an object, but there's still more text in the string.:"
I fixed a similar issue in my code by wrapping the receipt data in {} before encoding.
The resulting receipt looks like:
{
"signature" = "A[...]OSzQ==";
"purchase-info" = "ew[...]fQ==";
"pod" = "100";
"signing-status" = "0";
}
Here's the code I use:
receipt = "{%s}" % receipt // This step was not specified - trial and error
encoded = base64.b64encode(receipt)
fullpost = '{ "receipt-data" : "%s" }' % encoded
req = urllib2.Request(url, fullpost)
response = urllib2.urlopen(req)
Apple's Response:
{"receipt":{"item_id":"371235", "original_transaction_id":"1012307", "bvrs":"1.0", "product_id":"com.foo.cup", "purchase_date":"2010-05-25 21:05:36 Etc/GMT", "quantity":"1", "bid":"com.foo.messenger", "original_purchase_date":"2010-05-25 21:05:36 Etc/GMT", "transaction_id":"11237"}, "status":0}
Good luck!