How to convert NSData deviceToken to JSON-compatible string to remote API - macos

I need to use the PushWoosh RemoteAPI to register my device.
What is the recommended way to send the deviceIDToken as JSON to the service?
The DeviceID is a NSDATA, but to send it to the remote API I need to convert it to a string.
Which encoding should I use?
NSString *tokenString = [[NSString alloc] initWithData:deviceToken encoding:NSASCIIStringEncoding];
Leads to strange data and is not accepted as a deviceToken.?

My app use this method below, I don't think it is a perfect way for it but it works
NSString *strToken = [[deviceToken description] stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:#"<>"]] ;

You can try this one for Remote API register, it might help :)
{
"request":{
"application":"APPLICATION_CODE",
"push_token":"DEVICE_PUSH_TOKEN",
"language":"en", // optional
"hwid": "hardware device id",
"timezone": 3600, // offset in seconds
"device_type":1
}
}

Related

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.

What is the encoding for URL bookmarks stored as NSData?

What is the best way to get the path from the NSData bookmark object, if the bookmark will not resolve?
Normally, you just resolve the bookmark, you get a URL, and off you go. But if the bookmark is to an NFS mount that is not currently present, it won't resolve. So now I have an NSData pointing somewhere that won't resolve, but I don't know where it points.
Here is the code block I have that loads the bookmarks, tries to resolve them, and attempts to decode the NSData if the resolve fails, but I can't figure out the encoding - is this even possible?
NSError* error = [[NSError alloc] init];
NSURL* resolvedURL = [NSURL URLByResolvingBookmarkData:bookmarkData
options:NSURLBookmarkResolutionWithSecurityScope | NSURLBookmarkResolutionWithoutUI
relativeToURL:nil
bookmarkDataIsStale:NULL
error:&error];
if (resolvedURL) {
// do some stuff
...
} else {
NSString* msg = [NSString stringWithFormat:#"Error Resolving Bookmark: %#", error];
NSLog(msg);
// the below certainly doesn't get me a path from the bookmark, any idea what will?
// NSString* path = [[NSString alloc] initWithData:bookmarkData encoding:NSUTF32StringEncoding];
}
I never did figure out the encoding, but I found a workaround.
Originally, I encoded the sandboxed NSURLs into NSData objects, and then stored those as an NSArray in NSDefaults. Therefore, I had no way to determine the path for the NSData, unless it would resolve.
The workaround was to change the design - now I encode the sandboxed NSURL, store it as an object into an NSDictionary with the key being the URL path, and store the NSDictionary in NSDefaults.
With this approach, I can easily retrieve the NSData for any given path, even if it will not resolve.

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.

Xcode: getting badge value to add it to a label

I have an app that receives push notification.
I would like to add the badge with the right value near a button inside the application menu (do you remember the old Facebook app?).
I'm trying to get the badge value from the notification in the AppDelegate, save it in NSUserDefault to use it in other view controllers.
NSString * badgeValue = [[userInfo valueForKey:#"aps"] valueForKey:#"badge"];
NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:badgeValue forKey:#"badge"];
[defaults synchronize];
The problem is that if I try to put the value in a label the app crashes
'NSInvalidArgumentException', reason: '-[__NSCFNumber isEqualToString:]: unrecognized selector sent to instance
Xcode makes me save badgeValue in a string but when I put the string in the label it says is not a proper string
if ([badgeValue isKindOfClass:[NSString class]]) {
NSLog(#"it is string !!!!!!!!!!!!!!!");
// treat it as a string object
} else {
NSLog(#"it is not a string !!!!!!!!!!!!!!!");
// treat it as a number object
}
By debugging I see that badgeValue is not a real string but __NSCFNumber and if I try to convert it in a string the return value is a long strange number.
I searched everywhere for a way to get the badge value of the notification but the only option I can think of is a php query...Any idea?
What do you mean by "Xcode makes me save badgeValue in a string"? Do you get an error or a compiler warning if you try to type badgeValue as an NSNumber? The documentation and your own logging tell you that the value of the key "badge" is an NSNumber.
NSNumber * badgeValue = [[userInfo valueForKey:#"aps"] valueForKey:#"badge"];
[label1 setIntValue:badgeNumber.intValue];
Does something like this not work?
Yes, Just yesterday I tried with
NSString *string = [NSString stringWithFormat:#"%d", [badgeValue intValue]];
And it works! Thanks for help

In-memory mime-type detection with Cocoa (OS X)?

My application is a viewer for a custom format, a zip file with a well defined XML manifest and resources, such as images and movies. I use zlib to open up the zip file in memory and then proceed to display said resources.
One problem I've ran into is that I'm unable to properly display videos, apparently because QTMovie cannot determine the mime-type. Movie loaded from file ([QTMovie movieWithFile]) works perfectly. Loaded from memory ([QTMovie movieWithData]) refuses to work.
This makes sense, because lacking the file extension, QTMovie cannot determine the mime-type information. After a bit of a search, I've resorted to using QTDataReference in the following mannner:
NSData *movieData = ...read from memory...;
QTDataReference *movieDataReference = [[QTDataReference alloc] initWithReferenceToData:movieData name:fileName MIMEType:#"video/x-m4v"];
QTMovie *mov = [QTMovie movieWithDataReference:movieDataReference error:&err];
This works nicely, however hardcoding MIMEType is far from ideal. I have access to the filename and the extension, so I've attempted to find the mime-type using UTI (thanks to the nice folks on #macdev):
- (NSString*)mimeTypeForExtension:(NSString*)ext {
CFStringRef UTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension,(CFStringRef)ext,NULL);
return NSMakeCollectable(UTTypeCopyPreferredTagWithClass((CFStringRef)UTI,kUTTagClassMIMEType));
}
This however doesn't work. Clearly, there's an internal OS X database of extensions and corresponding mime-types, somewhere. Otherwise from-disk movies wouldn't work. How do I get access to it?
Thanks!
The problem your having is that m4v and m4p dont have a mime types registered with Launch Services (probably because the mime type for m4v and m4p is not standard). In any event, what you should probably do is handle edge cases like this and then check for nil when the function returns (in case the extension is both not registered and not handled by you).
The other thing is that you're leaking memory with your current use. I'm assuming you're using garbage collection, but the first call creates a CFString that is never released. Here is a suggested implementation of your method:
-(NSString*)mimeTypeForExtension:(NSString*)ext
{
NSAssert( ext, #"Extension cannot be nil" );
NSString* mimeType = nil;
CFStringRef UTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension,
(CFStringRef)ext, NULL);
if( !UTI ) return nil;
CFStringRef registeredType = UTTypeCopyPreferredTagWithClass(UTI, kUTTagClassMIMEType);
if( !registeredType ) // check for edge case
{
if( [ext isEqualToString:#"m4v"] )
mimeType = #"video/x-m4v";
else if( [ext isEqualToString:#"m4p"] )
mimeType = #"audio/x-m4p";
// handle anything else here that you know is not registered
} else {
mimeType = NSMakeCollectable(registeredType);
}
CFRelease(UTI);
return mimeType;
}
You can use NSWorkspace to have the system guess UTI of a file.
-(NSString *)mimeTypeForFileAtPath:(NSString *)path error:(NSError **)err {
NSString *uti, *mimeType = nil;
if (!(uti = [[NSWorkspace sharedWorkspace] typeOfFile:path error:err]))
return nil;
if (err)
*err = nil;
if ((mimeType = (NSString *)UTTypeCopyPreferredTagWithClass((CFStringRef)uti, kUTTagClassMIMEType)))
mimeType = NSMakeCollectable(mimeType);
return mimeType;
}
Suggest that people change the return to [mimeType autorelease]; - some of us still use the ancient ways!
And thanks! This was a big help.

Resources