RestKit image upload parameters - image

I've made multiple attempts to enable POST image file transfer with RestKit but have only succeeded so far using curl. The working code is below, but it is synchronous and makes the UI unresponsive.
NSArray *arguments =
[NSArrayarrayWithObjects:assetScriptFullPath,
#"-F", [NSString stringWithFormat:#"asset[file]=#%#", fullPath],
#"-F", [NSString stringWithFormat:#"asset[user_id]=%d", user_id],
#"-F", [NSString stringWithFormat:#"asset[checksum]=%s", [(NSString *)md5hash UTF8String]],
nil];
NSTask *task = [NSTasklaunchedTaskWithLaunchPath:#"/usr/bin/curl"arguments:arguments];
The curl call is received at the server as below:
{
"asset"=>{
"file"=>#<ActionDispatch::Http::UploadedFile:0xcfcc630
#original_filename="IMG_6236.JPG",
#content_type="image/jpeg",
#headers="Content-Disposition: form-data; name=\"asset[file]\"; filename=\"IMG_6272.JPG\"\r\nContent-Type: image/jpeg\r\n",
#tempfile=#<File:/tmp/RackMultipart20121117-21489-brm3b9>>,
"user_id"=>"522",
"checksum"=>"ab23bc492bac990d9022248315c743c1"
}
}
One attempt with RestKit is based on this post ( RestKit Image Upload ) but doesn't nest file within asset. My attempts to nest the params within 'asset' haven't worked or have crashed.
{
"user_id"=>"522",
"checksum"=>"ab23bc492bac990d9022248315c743c1",
"file"=>#<ActionDispatch::Http::UploadedFile:0x883eb9c
#original_filename="file",
#content_type="image/jpeg",
#headers="Content-Disposition: form-data; name=\"file\"; filename=\"file\"\r\nContent-Type: image/jpeg\r\n",
#tempfile=#<File:/tmp/RackMultipart20121125-25702-ac8ck9>>
}
Using the method described in the RestKit advanced tutorial below I either can't get the hierarchy I need (file within asset) or I can't get the image data attached without crashing.
https://github.com/RestKit/RestKit/blob/master/Docs/MobileTuts%20Advanced%20RestKit/Advanced_RestKit_Tutorial.md
One way I tried to attach the image that causes crashes is described here:
Serialize nested image in RestKit (Rails Backend)
{
"asset"=>{
"user_id"=>"522",
"file"=>"#/Users/dev/IMG_6236.JPG",
"checksum"=>"ab23bc492bac990d9022248315c743c1"
}
}
Any recommendations? Thanks!
If I could change what the server expects I can get it to work with a flat parameter hierarchy. This isn't a solution though, I can't change the hierarchy. The code is below:
[params setFile:asset forParam:#"file"];
[params setData:[name dataUsingEncoding:NSUTF8StringEncoding] forParam:#"name"];
[params setData:[user_id dataUsingEncoding:NSUTF8StringEncoding] forParam:#"user_id"];
[client post:assetScriptPath params:params delegate:self];
This is what the server sees, but I need this all within an "asset" as above.
{
"file"=>#<ActionDispatch::Http::UploadedFile:0xcfcc630
#original_filename="IMG_6236.JPG",
#content_type="image/jpeg",
#headers="Content-Disposition: form-data; name=\"file\"; filename=\"IMG_6272.JPG\"\r\nContent-Type: image/jpeg\r\n",
#tempfile=#<File:/tmp/RackMultipart20121117-21489-brm3b9>>,
"user_id"=>"522",
"checksum"=>"ab23bc492bac990d9022248315c743c1"
}

The way we do it is we encode the image into base64Encoding and decode it on the server side.
Something like this.
NSData *imageData = [NSData dataWithData:imageForUpload];
NSString *encodedString = [imageData base64Encoding];
After that it works just like a regular RestKit post.

Related

dictionaryWithContentsOfFile and Sandbox

I've created a mac app that load a xml file from an user selected folder, and after using the app, the user saves a customized file (.adgf)
When i try to load the .adgf file (that is a plist file) that has the xml path within one record i call
dictionaryWithContentsOfFile but it return me a "nil". I think the problem is the sandbox (sometime it works sometime not). The string path is correct.
Maybe when the user load the xml file should i save within of particular app "Document folder"?
Edit:
I'm trying right now the Bookmark Data solution and I retraive a NSURL but it doen't work. The code I'm using is this:
- (NSData *)bookmarkFromURL:(NSURL *)url {
NSError *error = nil;
NSData *bookmark = [url bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope
includingResourceValuesForKeys:NULL
relativeToURL:NULL
error:&error];
if (error) {
NSLog(#"Error creating bookmark for URL (%#): %#", url, error);
[NSApp presentError:error];
}
return bookmark;
}
- (NSURL *)urlFromBookmark:(NSData *)bookmark {
NSURL *url = [NSURL URLByResolvingBookmarkData:bookmark
options:NSURLBookmarkResolutionWithSecurityScope
relativeToURL:NULL
bookmarkDataIsStale:NO
error:NULL];
return url;
}
After the user stores the file you should take the bookmark data from the URL using
-[NSURL bookmarkDataWithOptions: includingResourceValuesForKeys: relativeToURL: error:]
Use NSURLBookmarkCreationWithSecurityScope for the options.
This NSData object should be stored somewhere (plist?) and when you want to read the file again in a later session you can create a sandbox compliant NSURL from the bookmark data using +[NSURL
URLByResolvingBookmarkData:options:relativeToURL:bookmarkDataIsStale:error:]

NSURLRequest with UTF8 password

Here is a method I've written to connect to a server and get a user auth token:
+ (void)getAuthTokenForUsername:(NSString *)username
password:(NSString *)password
completionHandler:(void (^)(NSString *, NSError *))completionHandler
{
username = [username URLEncodedString];
password = [password URLEncodedString];
NSString *format = #"https://%#:%##api.example.com/v1/user/api_token?format=json";
NSString *string = [NSString stringWithFormat:format, username, password];
NSURL *URL = [NSURL URLWithString:string];
[NSURLConnection sendAsynchronousRequest:[NSURLRequest requestWithURL:URL]
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *URLResponse, NSData *data, NSError *error)
{
NSString *token;
if (data) {
NSDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
token = [NSString stringWithFormat:#"%#:%#", username, dictionary[#"result"]];
}
completionHandler(token, error);
}];
}
A URL then looks something like this: https://username:hello%C2%B0#api.example.com/v1/user/api_token?format\=json, where the password is hello°. The URLEncodedString method properly encodes everything as in the example, but the request never works. The problem is not with escaping or the server, because I can curl the same URL and I get nice JSON and authentication works, even though there is a non-ASCII character in the password. It also works from other programming languages like ruby or python. But the same url never works with NSURLConnection and it also doesn't work in Safari, which of course uses NSURLConnection. I get an 'The operation could not be completed' with a '401 Forbidden' every time.
(My code works fine when the password just contains ASCII characters. I also tried using the NSURLCredential methods, same problem.)
What do I need to do for NSURLConnection to work with such a URL as https://username:hello%C2%B0#api.example.com/v1/user/api_token?format\=json where the password contains non-ASCII characters?
I have just performed several tests against my mockup server and I think I have a solution for you.
First of all, when you add username & password to an URL, they are not actually send to the server as part of the URL. They are sent as part of the Authorization header (see Basic access authentication).
The fastest workaround for you is to do
NSURLRequest* request = [NSURLRequest requestWithURL:URL];
NSString* usernamePassword = [[NSString stringWithFormat:#"%#:%#", username, password] base64Encode];
[request setValue:[NSString stringWithFormat:#"Basic %#", usernamePassword] forHTTPHeaderField:#"Authorization"]
To understand the problem, let's go a bit deeper. Let's forget NSURLConnection sendAsynchronousRequest: and let us create an old-fashioned connection with a NSURLConnectionDelegate. Then in the delegate, let's define the following methods:
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace {
return YES;
}
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
NSLog(#"Proposal: %# - %#", challenge.proposedCredential.user, challenge.proposedCredential.password);
NSURLCredential* credential = [NSURLCredential credentialWithUser:#"username"
password:#"hello°"
persistence:NSURLCredentialPersistenceNone];
[challenge.sender useCredential:credential forAuthenticationChallenge:challenge];
}
If you don't create these methods, the username & password from your URL won't ever be added to the HTTP header.
If you add them, you'll see that the proposed password is hello%C2%B0. Obviously, that's wrong.
You can see the problem directly from
NSLog(#"Password: %#", [[NSURL URLWithString:#"http://username:hello%C2%B0#www.google.com"] password]);
which prints
hello%C2%B0
I believe this is a bug in the framework. NSURL returns password encoded and NSURLCredential doesn't decode it so we are left with an invalid password.

Uploading a file with POST using AFNetworking

So I'm trying to upload an XML file to a server with POST using AFNetworking.
So using example code from their site I have this set up. When it runs, something is uploaded to the server (or at least it leaves my computer). I can monitor the upload, when the upload is finished, the server recognizes that it completed and goes to load the file, but it loads an old XML. So its connecting properly to the server, but I'm not sure why the file upload isn’t working correctly. Also I just want to send the file, the server doesn’t need any headers or parameters etc.
So I'm wondering if I’ve stored the data correctly? Or if I'm not sending it the server properly or what? Any suggestions would be helpful
NSData *iTunesXMLData = [NSData dataWithContentsOfFile:filePath];
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:url];
/* NSMutableURLRequest *request =[httpClientmultipartFormRequestWithMethod:#"POST"
path:#"/upload.php?id=5" parameters:nil constructingBodyWithBlock: ^(id <AFMultipartFormData>formData) {
[formData appendPartWithFileData:iTunesXMLData name:#"iTunes Music Library" fileName:#"iTunes Music Library.xml" mimeType:#"application/xml"];
}];*/
//I tried this way also, both did the same thing
NSMutableURLRequest *request = [httpClient multipartFormRequestWithMethod:#"POST" path:#"/upload.php?id=5" parameters:nil constructingBodyWithBlock: ^(id <AFMultipartFormData>formData) {
[formData appendPartWithFormData:iTunesXMLData name:#"iTunes Music Library"];
}];`
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];`
NSLog(#"Operation: %#", operation);
[operation setUploadProgressBlock:^(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite) {
NSLog(#"Sent %lld of %lld bytes", totalBytesWritten, totalBytesExpectedToWrite);
}];
[operation start];
Have you tried to catch the success/failure of the operation? Try this after setUploadProgressBlock:
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
// Operation ended successfully
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// Something happened!
NSLog(#"ERROR: %#, %#", operation, error);
// Here you can catch operation.responseString to see the response of your server
}];
This is an easy way to know what your server returned. If something uploads to your server, double check that you're getting the right file. AFAIK, your AFNetwork seems ok.

Using NSOutputStream to POST to url

So all I want to do is send a POST request to a url. Now I tried using NSURLRequest/NSURLConnection, but was having problems with that and decided to move to a lower level, also because I want to send large files and thought dealing directly with streams might be better. But the output stream delegate never seems to be called, and I can't seem to find examples using NSOutputStream's initWithURL.
NSURL *url = [NSURL URLWithString:NSString stringWithFormat: #"http://url"];
self.outputStream = [[NSOutputStream alloc] initWithURL:url append:NO];
[outputStream retain];
[outputStream setDelegate:self];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream open];
It seems that the outputStream is null after the init, which I can't understand because my url is a valid url--I can ping it from the terminal and send data from other sources. Am I doing something wrong, or can anyone tell me how to write a POST request to a URL using streams? Thanks.
You cannot use NSOutputStream to send a POST request to a HTTP server.
The good method is to create a NSMutableURLRequest and provide that request with a HTTPBodyStream then create a NSURLConnection to send the request.
The HTTPBodyStream is an NSInputStream the request will read the body from. You may initialize it with a NSData object or with the contents of a file.
If you want to provide a custom content (for example, you want to upload a file as part of a multipart/form-data request), you may need to subclass NSInputStream. In such case, I suggest you to have a look at How to implement CoreFoundation toll-free bridged NSInputStream subclass, which explains how to address an issue that occurs when using custom input streams with NSURLConnection. Or you may use ASIHTTPRequest which provides multipart HTTP requests out of the box.
Yes, you can send the HTTP GET/POST request using NSOutputStream.
1.make & open your stream.
2.when the stream is ready to write, the NSStreamEventHasSpaceAvailable event will be send in method:
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
https://developer.apple.com/documentation/foundation/nsstreamdelegate/1410079-stream?language=objc
3.make a CFHTTPMessageRef & and write the data.
code like this:
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode{
switch (eventCode) {
case NSStreamEventOpenCompleted:{
//
}
break;
case NSStreamEventHasSpaceAvailable:{
[self sendHTTPMessage];
}
break;
default:{
//
}
break;
}
- (void)sendHTTPMessage{
//create a http GET message
CFStringRef requestMethod = (CFStringRef)CFAutorelease(CFSTR("GET"));
CFHTTPMessageRef httpRequest = (CFHTTPMessageRef)CFAutorelease(CFHTTPMessageCreateRequest(kCFAllocatorDefault, requestMethod, (__bridge CFURLRef)yourURL,kCFHTTPVersion1_1));
//set HTTP header
CFHTTPMessageSetHeaderFieldValue(httpRequest, (__bridge CFStringRef)#"Host", (__bridge CFStringRef)#"yourhost.com");
CFHTTPMessageSetHeaderFieldValue(httpRequest, (__bridge CFStringRef)#"Connection", (__bridge CFStringRef)#"close");
//set HTTP Body
...
//let's send it
CFDataRef serializedRequest = CFHTTPMessageCopySerializedMessage(httpRequest);
NSData *requestData = (__bridge_transfer NSData *)serializedRequest;
[self.outStream write:requestData.bytes maxLength:requestData.length];
[self.outStream close];
}
Remember, the key point is converting CFHTTPMessageRef to bytes to write.

What do I do with NSData?

I am trying to send email programmatically... I have this line of code:
_mail.AddAttachmentData(nsd,"text/plain", filePath);
and I haven't a clue what goes into NSData... I tried taking the string that I was writing to a file, but apparently that doesn't work either. I believe it is preventing me from doing a good sendmail.
NSData is a class type. See here:
http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSData_Class/Reference/Reference.html
You'll probably want to construct your NSData object using something like this:
NSString *myFilePath = [[NSBundle mainBundle] pathForResource:#"MyFile" ofType:#"txt"];
NSData *myData = [NSData dataWithContentsOfFile:myFilePath];
Then pass that in as the NSData object.

Resources