Convert characters to HTML Entities in Cocoa - cocoa

I am currently trying to put together an URL where I specify some GET parameters. But I want to use japanese or other characters too in this URL.
Is there a way to convert a NSString to a string containing the HTML entities for the 'special' characters in my NSString?
I am currently using the following code, which seems to work, except for 'special characters' like chinese and japanese:
NSString* url = #"/translate_a/t?client=t&sl=auto&tl=";
url = [url stringByAppendingString:destinationLanguage];
url = [url stringByAppendingString:#"&text="];
url = [url stringByAppendingString:text];
NSURL* nsurl = [[NSURL alloc] initWithScheme:#"http" host:#"translate.google.com" path:url];
NSError* error;
NSString* returnValue = [[NSString alloc] initWithContentsOfURL:nsurl encoding:NSUTF8StringEncoding error:&error];

To properly URL encode your parameters, you need to convert each name and value to UTF-8, then URL encode each name and value separately, then join names with values using '=' and name-value pairs using '&'.
I generally find it easier to put all the parameters in an NSDictionary, then build the query string from the dictionary. Here's a category that I use for doing that:
// file NSDictionary+UrlEncoding.h
#import <Cocoa/Cocoa.h>
#interface NSDictionary (UrlEncoding)
-(NSString*) urlEncodedString;
#end
// file NSDictionary+UrlEncoding.m
#import "NSDictionary+UrlEncoding.h"
// private helper function to convert any object to its string representation
static NSString *toString(id object) {
return [NSString stringWithFormat: #"%#", object];
}
// private helper function to convert string to UTF-8 and URL encode it
static NSString *urlEncode(id object) {
NSString *string = toString(object);
return [string stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding];
}
#implementation NSDictionary (UrlEncoding)
-(NSString*) urlEncodedString {
NSMutableArray *parts = [NSMutableArray array];
for (id key in self) {
id value = [self objectForKey: key];
NSString *part = [NSString stringWithFormat: #"%#=%#",
urlEncode(key), urlEncode(value)];
[parts addObject: part];
}
return [parts componentsJoinedByString: #"&"];
}
#end
The method build an array of name-value pairs called parts by URL encoding each key and value, then joining them together with '='. Then the parts in the parts array are joined together with '&' characters.
So for your example:
#import "NSDictionary+UrlEncoding.h"
// ...
NSMutableDictionary *parameters = [NSMutableDictionary dictionary];
[parameters setValue: #"t" forKey: #"client"];
[parameters setValue: #"auto" forKey: #"sl"];
[parameters setValue: destinationLanguage forKey: #"tl"];
[parameters setValue: text forKey: #"text"];
NSString *urlString = [#"/translate_a/t?" stringByAppendingString: [parameters urlEncodedString]];

NSString has a method -stringByAddingPercentEscapesUsingEncoding:

Here's NSString extension you can find over internet
http://code.google.com/p/wimframework/source/browse/trunk/WimFramework/Classes/Helpers/WimAdditions.m
The decode part has some error in mapping array index to actual entity number. But since you only need encoding, it's fine to use it.

For simple URL encoding of strings, many of the solutions I have seen, while technically correct, look a lot less easy to use than I would like. So I came up with the following NSString category:
#interface NSString (MLExtensions)
- (NSString *)urlencode;
#end
NSString *_mlfilterChars = #";/?:#&=+$,";
#implementation NSString (MLExtensions)
- (NSString *)urlencode
{
return [[NSString stringWithString: (NSString *)
CFURLCreateStringByAddingPercentEscapes(
NULL,
(CFStringRef)self,
NULL,
(CFStringRef)_mlfilterChars,
kCFStringEncodingUTF8)]
stringByReplacingOccurrencesOfString: #"%20" withString: #"+"];
}
#end
I'm kind of in a hurry with some other stuff I'm working on, so I kind of cheated with the %20 => + conversion step, but it all seems to work great and I've been using it for a while now with a good number of URLs in my app.
Usage is blessfully easy:
- (NSString *)URLForSearch: (NSString *)searchFor
{
return [#"http://example.org/search?query="
stringByAppendingString: [searchFor urlencode]];
}

Related

NSMutableDictionary entry corrupted after departing from the method where entry was added

I use setObject:forKey: to add an object of type Rresource to a NSMutableDictionary named: resourceLib.
Then I immediately look at what's actually in the dictionary and it's OK.
When I try to look at it again in another object's method, the proper key is present but a reference to a string property "url" cases a list of error messages including:
2016-09-28 11:32:42.636 testa[760:16697] -[__NSCFString url]: unrecognized selector sent to instance 0x600000456350
Rresource object is defined as:
#interface Rresource : NSObject
#property (nonatomic,strong) NSString* url;
#property (nonatomic,strong)NSMutableArray* resourceNotesArray;
#property(nonatomic,strong)NSString* name;
#property(nonatomic,strong)NSString* resourceUniqueID;
#property(nonatomic)BOOL isResourceDirty;
This method in a ViewController adds the Rresource to the NSMutableDictionary
-(void)saveResource
{
Rresource* resource = self.currentResource;
Rresource* temp;
if (resource)
{
if ( resource.isResourceDirty)
{
[self.model.resourceLib setObject:resource forKey:resource.resourceUniqueID];
temp = [self.model.resourceLib objectForKey:resource.resourceUniqueID];
}
}
}
Resource and temp contain identical info showing the info was added correctly.
In model's method the following causes the error message described above.
for (Rresource* resource in self.resourceLib)
{
NSString* string = resource.url;
}
where model contains:
#property(nonatomic,strong)NSMutableDictionary* resourceLib;
and :
#implementation Model
- (instancetype)init
{
self = [super init];
if (self)
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
self.path = [[paths objectAtIndex:0] stringByAppendingString:#"/Application Support/E2"];
BOOL exists = [[NSFileManager defaultManager] createDirectoryAtPath:self.path withIntermediateDirectories:NO attributes:nil error:nil];
if (!exists)
{
[[NSFileManager defaultManager] createDirectoryAtPath:self.path withIntermediateDirectories:NO attributes:nil error:nil];
}
self.resourceLibPath = [NSString pathWithComponents:#[self.path,#"resources"]];
self.resourceLib = [[NSMutableDictionary alloc]init];
self.noteLibPath = [NSString pathWithComponents:#[self.path, #"notes"]];
self.noteLib = [[NSMutableDictionary alloc]init];
}
return self;
I have found this question difficult to ask clearly even after spending several hours formulating it. I apologize.
I've tried pretty much everything for about a week. I'm stumped.
Any ideas?
Thanks
According to this entry on Enumeration, when you iterate over a dictionary using the fast enumeration syntax, you're iterating over its keys. In the above code sample you're assuming the enumeration happens over its values. What you're effectively doing is casting an NSString object as an Rresource, and sending it a selector only an actual Rresource object can respond to.
This should fix the loop:
for (NSString* key in self.resourceLib)
{
NSString* string = [self.resourceLib objectForKey:key].url;
}

Sorting NSJSON arrays aren't working properly

In my json file I have a title, subtitle, and url.
I sort the title to set the items alphabetically, but the url isn't sorted with the title and I don't know why.
This is what i've done:
NSDictionary *allDataDictionary = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:nil];
NSArray *arrayOfItems = [allDataDictionary objectForKey:#"items"];
for (NSDictionary *diction in arrayOfItems) {
NSString *titles = [diction objectForKey:#"title"];
NSString *station = [diction objectForKey:#"url"];
[jsonArray addObject:titles];
[jsonStations addObject:station];
// SORT JSON
NSArray *sortedArray;
sortedArray = [jsonArray sortedArrayUsingComparator:^NSComparisonResult(NSString *title1, NSString *title2)
{
if ([title1 compare:title2] > 0)
return NSOrderedDescending;
else
return NSOrderedAscending;
}];
[jsonArray setArray:sortedArray];
When I press the first item in the tableView, I get get the url from a total diffrent title.
What should I do to get the title to match the url in the tableView?
First of all this seems like a strange way of sorting, you should use a dictionary instead of 2 arrays otherwise things get messy very quickly.
Secondly you need to pass your sortedArray to the table instead of the jsonArray currently it seems to be just trying to reset the jsonArray.
I would create one method to handle it like this (I have stripped some of your sorting script to simplify this)
-(NSArray *)sortContentWithJSONData {
NSDictionary *allDataDictionary = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:nil];
NSArray *arrayOfItems = [allDataDictionary objectForKey:#"items"];
NSArray *sortedArray = [NSSortDescriptor sortDescriptorWithKey:#"title" ascending:false];
NSMutableArray *outputArray = [[NSMutableArray alloc] init];;
for (NSDictionary *diction in arrayOfItems) {
NSString *titles = [diction objectForKey:#"title"];
NSString *station = [diction objectForKey:#"url"];
[outputArray addObject:[NSDictionary dictionaryWithObjectsAndKeys:titles, #"title", station, #"station", nil]]
}
return [outputArray sortedArrayUsingDescriptors:[NSArray arrayWithObject:sortedArray]];
}
Then you could set a global array and access it in your table view using the following...
NSArray *tableContent = [self sortContentWithJSONData];
Hope that clears things up a bit :)

Caching URL - Creating appropriate NSString representation

I want to cache an image I download from a URL in the local filesystem.
What I don't know, is how can I create from the URL a NSString that is compatible with the characters the iOS file system supports. For instance, if I try to create a file with a : in the name, it will fail.
What steps should I follow to create this NSString? Is a simple hash the best way to go? If so, what hash routine is available in iOS that I can use?
I'd use hashing:
it'll make for much more readable file names
you avoid problems with file name length
md5 should be perfectly fine for your purposes. Unfortunately, the ios5-sdk contains only a C-String function for this:
CC_MD5(in, in_len, out);
The function is contained in <CommonCrypto/CommonDigest.h>, there are other hash functions there, too.
You can find infos on how to wrap this up in a function that takes/returns an NSString here.
The following code belongs to ZDS_Shared. resolveLocalURLForRemoteURL accepts a remote URL and returns a URL pointing to the file on the iOS filesystem. The filename will be an alphanumeric string that doesn't resemble the original URL, but that shouldn't be a problem.
https://github.com/ZarraStudios/ZDS_Shared/blob/master/ZSAssetManager.m#L185
- (NSURL*)resolveLocalURLForRemoteURL:(NSURL*)url
{
if (!url) return nil;
NSString *filename = [[url absoluteString] zs_digest];
NSString *filePath = [[self cachePath] stringByAppendingPathComponent:filename];
return [NSURL fileURLWithPath:filePath];
}
https://github.com/ZarraStudios/ZDS_Shared/blob/master/NSString%2BZSAdditions.m#L38
// NSString category
- (NSString*)zs_digest
{
const char *cstr = [self cStringUsingEncoding:NSASCIIStringEncoding];
return [[NSData dataWithBytes:cstr length:strlen(cstr)] zs_digest];
}
https://github.com/ZarraStudios/ZDS_Shared/blob/master/NSData%2BZSAdditions.m#L38
// NSData category
- (NSString*)zs_digest
{
uint8_t digest[CC_SHA1_DIGEST_LENGTH];
CC_SHA1([self bytes], [self length], digest);
NSMutableString* outputHolder = [[NSMutableString alloc] initWithCapacity:CC_SHA1_DIGEST_LENGTH * 2];
for (int i = 0; i < CC_SHA1_DIGEST_LENGTH; i++) {
[outputHolder appendFormat:#"%02x", digest[i]];
}
NSString *output = [outputHolder copy];
MCRelease(outputHolder);
return [output autorelease];
}

Cocoa - Calling a variadic method from another variadic one (NSString stringWithFormat call)

I have a problem with [NSString strigWithFormat:format] because it returns an id, and I have a lot of code where I changed a NSString var to an other personal type. But the compiler does not prevent me that there are places where a NSString is going to be set into another type of object.
So I'm writing a category of NSString and I'm goind to replace all my calls to stringWithFormat to myStringWithFormat.
The code is :
#interface NSString (NSStringPerso)
+ (NSString*) myStringWithFormat:(NSString *)format;
#end
#implementation NSString (NSStringPerso)
+ (NSString*) myStringWithFormat:(NSString *)format {
return (NSString*)[NSString stringWithFormat:format];
}
#end
The compiler tells me that "Format not a string literal and no format arguments".
Do you see any way to make this work ?
NSString includes a method that takes in an argument list from a variadic function. Look at this sample function:
void print (NSString *format, ...) {
va_list arguments;
va_start(arguments, format);
NSString *outout = [[NSString alloc] initWithFormat:format arguments:arguments];
fputs([output UTF8String], stdout);
[output release];
va_end(arguments);
}
Some of that code is irrelevant, but the key line is NSString *output = [[NSString alloc] initWithformat:format arguments:arguments];. That's how you can construct an NSString in a variadic function/method.
In your case, your code should look something like this:
+ (NSString *)myStringWithFormat:(NSString *)format, ... {
va_list arguments;
va_start(arguments, format);
NSString *formattedString = [[NSString alloc] initWithFormat:format arguments:arguments];
va_end(arguments);
// perform some modifications to formattedString
return [formattedString autorelease];
}
No Objective-C expert here, but the original method signature for stringWithFormat includes ellipses, which allows you to pass in the arguments that are going to be substituted to the placeholders in the format argument.
EDIT: stringWithFormat is a so-called variadic method. Here's a link to an example.
Thank you for your help.
Reading your reference documentations, I found the solution !
This works :
In the .h
#interface NSString (NSStringPerso)
+ (NSString*) strWithFormatPerso:(id)firstObject, ...;
#end
In the .m
#implementation NSString (NSStringPerso)
+ (NSString*) strWithFormatPerso:(id)firstObject, ... {
NSString* a;
va_list vl;
va_start(vl, firstObject);
a = [[[NSString alloc] initWithFormat:firstObject, vl] autorelease];
va_end(vl);
return a;
}
#end

Can plists be securely read from untrusted source?

I need simple client-server communication with iPhone app, and XML property lists seem like quite easy and simple solution that saves me trouble of dealing with XML parser delegates and building these structures myself.
I just wonder is it wise to use NSPropertyListSerialization class with external data? Are there any obscure plist features that could be exploited?
Yes, it's safe to use NSPropertyListSerialization with untrusted data, but after you turn the bag of bytes into a hiearchy of plist types, you have to validate those types to makes sure they match your expected data format.
For example, if you expect a dictionary with string keys, and NSNumbers as values, you have to validate that with something like:
NSString *errorDescription = nil;
NSPropertyListFormat format = 0;
id topObject = [NSPropertyListSerialization propertyListFromData:plistData mutabilityOption:NSPropertyListImmutable format:&format errorDescription:&errorDescription];
NSDictionary *validDictionary = nil;
if ([topObject isKindOfClass:[NSDictionary class]]) {
BOOL allNumbers = YES;
for(id value in [topObject allValues]) {
allNumbers = allNumbers && [value isKindOfClass:[NSNumber class]];
}
if (allNumbers) {
validDictionary = topObject;
}
}
return validDictionary;
If you don't do that, the source of the data could have placed plist values into the archive with mis matched types, or illegal values that could cause your client to misbehave and wind up being a security vulnerability.
#Jon Hess: thanks. I've created NSDictionary category to cut down on required number of isKindOfClasses.
#interface NSDictionary (TypedDictionary)
-(NSArray *)arrayForKey:(NSString*)key;
-(NSString *)stringForKey:(NSString*)key;
-(NSDictionary *)dictionaryForKey:(NSString*)key;
#end
#implementation NSDictionary (TypedDictionary)
-(NSArray *)arrayForKey:(NSString*)key {
NSArray *obj = [self objectForKey:key];
return [obj isKindOfClass:[NSArray class]] ? obj : nil;
}
-(NSString *)stringForKey:(NSString*)key {
NSString *obj = [self objectForKey:key];
return [obj isKindOfClass:[NSString class]] ? obj : nil;
}
-(NSDictionary *)dictionaryForKey:(NSString*)key {
NSDictionary *obj = [self objectForKey:key];
return [obj isKindOfClass:[NSDictionary class]] ? obj : nil;
}
#end

Resources