Percent escaping query url strings - ios

I am getting NSString as given http://maps.apple.com/maps?saddr=41.447910,-74.357881&daddr=719 Old Route 9 North, Wappingers Falls, NY 12590
However i need to covert the above string into this one given for showing location in map
http://maps.apple.com/maps?saddr=41.447910,-74.357881&daddr=719+Old%20Route+9+North%2CWappingers+Falls%2CNY+12590
Please let me know.

You can do this with the following...
NSString *theOrigString = #"http://maps.apple.com/maps?saddr=41.447910,-74.357881&daddr=719 Old Route 9 North, Wappingers Falls, NY 12590";
NSString *formattedString = [theOrigString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSLog(#"%#", formattedString);
Output
http://maps.apple.com/maps?saddr=41.447910,-74.357881&daddr=719%20Old%20Route%2‌​09%20North,%20Wappingers%20Falls,%20NY%2012590
This will replace spaces with %20 and so on.
You can then use this as a url string.

An old question but this is what you need:
-(NSString*) encodeToPercentEscapeString:(NSString *)string {
return (NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(NULL,
(CFStringRef) string,
NULL,
(CFStringRef) #"!*'();:#&=+$,/?%#[]",
kCFStringEncodingUTF8)); }
-(NSString*) decodeFromPercentEscapeString:(NSString *)string {
return (NSString *)CFBridgingRelease(CFURLCreateStringByReplacingPercentEscapesUsingEncoding(NULL,
(CFStringRef) string,
CFSTR(""),
kCFStringEncodingUTF8)); }

Take a look at NSString's stringByAddingPercentEscapesUsingEncoding:
NSString *urlString = #"http://maps.apple.com/maps?saddr=41.447910,-74.357881&daddr=719 Old Route 9 North, Wappingers Falls, NY 12590";
NSString *encodedString = [urlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]

The reason behind the percent encoding simply comes down from the RFC 3986. In a nutshell, you should percent escape values of your URI query params to escape the reserved characters ("!*'();:#&=+$,/?%#[]"). Doing so it is possible to properly decoding the encoded URI - supposing for the sake of simplicity same encoding on the both sides.
As pointed out by Duncan and Fogmeister, Apple's stringByAddingPercentEscapesUsingEncoding is very friendly but, in general, wrong - or, at least, say, not compliant with RFC 3986 - since doesn't percent escape chars like "/:&%?".
A more general and robust solution would be something like
NSString * HKObjectToString(id object) {
return [NSString stringWithFormat:#"%#", object];
}
NSString * HKPercentEscapedString(id object, CFStringEncoding encoding) {
NSString *string = HKObjectToString(object);
return ((NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(NULL, (__bridge CFStringRef) string, NULL,
CFSTR("!*'();:#&=+$,/?%#[]"),
encoding)));
}
...
#implementation NSDictionary (URLAdditions)
...
- (NSString *)percentEscapedQueryStringWithEncoding:(CFStringEncoding)encoding
{
NSMutableArray *queryParts = [[NSMutableArray alloc] init];
for (NSString *key in [self allKeys]) {
NSString *part = [NSString stringWithFormat:#"%#=%#", HKPercentEscapedString(key, encoding), HKPercentEscapedString(HKObjectToString(self[key]), encoding)];
[queryParts addObject:part];
}
return [queryParts componentsJoinedByString:#"&"];
}
...
#end
...
#implementation NSString (StringHelper)
...
+ (NSString *)URIEncodedWithStringURI:(NSString *)baseURI queryParameters:(NSDictionary *)queryParams URIEncoding:(CFStringEncoding)encoding
{
return [NSString stringWithFormat:#"%#?%#", baseURI, [queryParams percentEscapedQueryStringWithEncoding:encoding]];
}
...
#end
For more details try to give a look at this public repo on github.

This question is problematic because the input data is damaged. At some earlier point in the code, this URL got decoded as a single string. You should never do that, because it can lead to situations where the distinction between different parts of the URL is lost and can never be recovered. For example, suppose I have this URL:
http://www.example.com/folder/this%2Fthat.html
When decoded, this becomes
http://www.example.com/folder/this/that.html
and there's no way to turn that back into the original, because there's no way to know which of those slashes is real and which should be encoded as %2F. The distinction is simply lost.
For this reason, when you work with URLs, it is important to always preserve them in their original form whenever possible, and when you have to decode a URL, to do so piecewise—that is, to start by splitting apart all of the path components, the query string, and so on, then pulling apart the fields in the query string (where applicable), and finally decoding each part individually.
After that, you would need to reencode each part by using CFURLCreateStringByReplacingPercentEscapesUsingEncoding, because the NSString method above doesn't do percent encoding of slashes, ampersands, and other special URL characters.
But in your case, your best bet is to find the code that is decoding the URL, and rip it out so that you have the original, undamaged URL. If and only if that is not possible, the stringByAddingPercentEscapesUsingEncoding: method can make a "best guess" about what to encode, so long as you understand that for arbitrary URLs, that solution won't necessarily work.

Edited:
Sulthan has a point. You can't percent encode the whole URL if the string portion (like the address in this case) might contain reserved URL characters ("/", ":", "&", "%", "?", etc.)
The stringByAddingPercentEscapesUsingEncoding method encodes spaces, but not slashes, colons, etc. Thus if the address string contains any of those, the receiver of the URL can't tell what's part of the syntax of the URL and what's data.
I have a category of NSString that escapes these characters as well:
The header
#import <Foundation/Foundation.h>
#interface NSString (URLEncoding)
- (NSString *)stringByAddingPercentEscapes;
- (NSString *)stringByReplacingPercentEscapes;
#end
NSString *mapURLStringTemplate =
#"http://maps.apple.com/maps?saddr=%f,%f&daddr=%#";
The body:
#import "StringCategory.h"
CFStringRef charsToEscape = CFSTR(":/&=");
#implementation NSString (URLEncoding)
- (NSString *)stringByAddingPercentEscapes
{
return (NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(NULL,
(CFStringRef)self, NULL, charsToEscape,
CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding)));
}
- (NSString *)stringByReplacingPercentEscapes
{
return (NSString *)CFBridgingRelease(CFURLCreateStringByReplacingPercentEscapes(kCFAllocatorDefault,(CFStringRef)self, CFSTR("")));
}
#end
And to use it:
NSString *escapedAddressString =
[addressString stringByAddingPercentEscapes];
NSString *mapURLString =
[NSString stringWithFormat: mapURLStringTemplate,
latitude,
longitude,
escapedAddressString];

Related

CFURLCreateStringByAddingPercentEscapes is deprecated in iOS 9, how do I use "stringByAddingPercentEncodingWithAllowedCharacters"

I have the following code:
return (NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)string, NULL, CFSTR(";:#&=+$,/?%#[]"), kCFStringEncodingUTF8);
Xcode says it is deprecated in iOS 9. So, how do I use stringByAddingPercentEncodingWithAllowedCharacters ?
Thanks!
try this
NSString *value = #"<url>";
value = [value stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
The character set URLQueryAllowedCharacterSet contains all characters allowed in the query part of the URL (..?key1=value1&key2=value2) and is not limited to characters allowed in keys and values of such a query. E.g. URLQueryAllowedCharacterSet contains & and +, as these are of course allowed in query (& separates the key/value pairs and + means space), but they are not allowed in a key or a value of such a query.
Consider this code:
NSString * key = "my&name";
NSString * value = "test+test";
NSString * safeKey= [key
stringByAddingPercentEncodingWithAllowedCharacters:
[NSCharacterSet URLQueryAllowedCharacterSet]
];
NSString * safeValue= [value
stringByAddingPercentEncodingWithAllowedCharacters:
[NSCharacterSet URLQueryAllowedCharacterSet]
];
NSString * query = [NSString stringWithFormat:#"?%#=%#", safeKey, safeValue];
query will be ?my&name=test+test, which is totally wrong. It defines a key named my that has no value and a key named name whose value is test test (+ means space!).
The correct query would have been ?my%26name=test%2Btest.
As long as you only deal with ASCII strings or as long as the server can deal with UTF-8 characters in the URL (most web servers today do that), the number of chars you absolutely have to encode is actually rather small and very constant. Just try that code:
NSCharacterSet * queryKVSet = [NSCharacterSet
characterSetWithCharactersInString:#":/?&=;+!##$()',*% "
].invertedSet;
NSString * value = ...;
NSString * valueSafe = [value
stringByAddingPercentEncodingWithAllowedCharacters:queryKVSet
];
Another solution to encode those characters allowed in URLQueryAllowedCharacterSet but not allowed in a key or a value (e.g.: +):
- (NSString *)encodeByAddingPercentEscapes {
NSMutableCharacterSet *charset = [[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy];
[charset removeCharactersInString:#"!*'();:#&=+$,/?%#[]"];
NSString *encodedValue = [self stringByAddingPercentEncodingWithAllowedCharacters:charset];
return encodedValue;
}

isEqualToString returns false for identical strings?

Maybe I'm going nuts, but I can't seem to understand why isEqualToString is returning false in this case.
I have written the following method to decode strings from percent encoded URL:
-(NSString*) decodeFromURLSafe: (NSString*) urlToDecode{
NSString *safeUrl = [urlToDecode stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
safeUrl = [safeUrl stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
return safeUrl;
}
In practice it works, but I wrote some tests for it and don't understand why they are failing
Here's one such test:
-(void)testDecodingFromURLSafe{
NSString *unescapedString = #"#a!b*c(d)e;f:g&h=i+j$k,l/m#n\\p[q]r<s>t^u{v}w|x~y";
NSString *answer = [_viewController decodeFromURLSafe:#"%40a%21b%2Ac%28d%29e%3Bf%3Ag%26h%3Di%2Bj%24k%2Cl%2Fm%23n%5C%5Cp%5Bq%5Dr%3Cs%3Et%5Eu%7Bv%7Dw%7Cx%7Ey"];
NSLog(#"%#", answer);
NSLog(#"%d", [answer isEqualToString:#"#a!b*c(d)e;f:g&h=i+j$k,l/m#n\\p[q]r<s>t^u{v}w|x~y"]);
XCTAssert([answer isEqualToString:unescapedString], #"decoding works");
}
The string in the second NSLog statement is copied directly from the output of the first:
2014-11-20 14:03:46.352 ProjName[5573:60b] #a!b*c(d)e;f:g&h=i+j$k,l/m#n\\p[q]r<s>t^u{v}w|x~y
But both the comparisons return false and I'm confused as to what I'm missing.
I've tried putting the string into DiffNow to double check that they're identical and still nothing.
You need to double your backslashes, so that they escape correctly:
#a!b*c(d)e;f:g&h=i+j$k,l/m#n\\\\p[q]r<s>t^u{v}w|x~y

How do I properly encode Unicode characters in my NSString?

Problem Statement
I create a number of strings, concatenate them together into CSV format, and then email the string as an attachment.
When these strings contain only ASCII characters, the CSV file is built and emailed properly. When I include non-ASCII characters, the result string becomes malformed and the CSV file is not created properly. (The email view shows an attachment, but it is not sent.)
For instance, this works:
uncle bill's house of pancakes
But this doesn't (note the curly apostrophe):
uncle bill’s house of pancakes
Question
How do I create and encode the final string properly so that all valid unicode characters are included and the result string is formed properly?
Notes
The strings are created via a UITextField and then are written to and then read from a Core Data store.
This suggests that the problem lies in the initial creation and encoding of the string: NSString unicode encoding problem
I don't want to have to do this: remove non ASCII characters from NSString in objective-c
The strings are written and read to/from the data store fine. The strings display properly (individually) in the app's table views. The problem only manifests when concatenating the strings together for the email attachment.
String Processing Code
I concatenate my strings together like this:
[reportString appendFormat:#"%#,", category];
[reportString appendFormat:#"%#,", client];
[reportString appendFormat:#"%#\n", detail];
etc.
Replacing curly quotes with boring quotes makes it work, but I don't want to do it this way:
- (NSMutableString *)cleanString:(NSString *)activity {
NSString *temp1 = [activity stringByReplacingOccurrencesOfString:#"’" withString:#"'"];
NSString *temp2 = [temp1 stringByReplacingOccurrencesOfString:#"‘" withString:#"'"];
NSString *temp3 = [temp2 stringByReplacingOccurrencesOfString:#"”" withString:#"\""];
NSString *temp4 = [temp3 stringByReplacingOccurrencesOfString:#"“" withString:#"\""];
return [NSMutableString temp4];
}
Edit:
The email is sent:
NSString *attachment = [self formatReportCSV];
[picker addAttachmentData:[attachment dataUsingEncoding:NSStringEncodingConversionAllowLossy] mimeType:nil fileName:#"MyCSVFile.csv"];
where formatReportCSV is the function that concatenates and returns the csv string.
You seem to be running across a string encoding issue. Without seeing what your Core Data model looks like, I'd assume the issue boils down to the issue reproduced by the code below.
NSString *string1 = #"Uncle bill’s house of pancakes.";
NSString *string2 = #" Appended with some garbage's stuff.";
NSMutableString *mutableString = [NSMutableString stringWithString: string1];
[mutableString appendString: string2];
NSLog(#"We got: %#", mutableString);
// We got: Uncle bill’s house of pancakes. Appended with some garbage's stuff.
NSData *storedVersion = [mutableString dataUsingEncoding: NSStringEncodingConversionAllowLossy];
NSString *restoredString = [[NSString alloc] initWithData: storedVersion encoding: NSStringEncodingConversionAllowLossy];
NSLog(#"Restored string with NSStringEncodingConversionAllowLossy: %#", restoredString);
// Restored string with NSStringEncodingConversionAllowLossy:
storedVersion = [mutableString dataUsingEncoding: NSUTF8StringEncoding];
restoredString = [[NSString alloc] initWithData: storedVersion encoding: NSUTF8StringEncoding];
NSLog(#"Restored string with UTF8: %#", restoredString);
// Restored string with UTF8: Uncle bill’s house of pancakes. Appended with some garbage's stuff.
Note how the first string (encoded using ASCII) couldn't handle the presence of the non-ASCII character (it can if you use dataUsingEncoding:allowsLossyConversion: with the second parameter being YES).
This code should fix the issue:
NSString *attachment = [self formatReportCSV];
[picker addAttachmentData:[attachment dataUsingEncoding: NSUTF8StringEncoding] mimeType:nil fileName:#"MyCSVFile.csv"];
Note: you may need to use one of the UTF16 string encodings if you need to handle non-UTF8 languages like Japanese.

How to pass * () $ etc in NSUrl?

I tried to pass these characters to server, for example *()$
I have coded the NSUrl this way:
NSString *partialURL = [NSString stringWithFormat:#"/%#", commentBody];
NSString *fullURL = [NSString stringWithFormat:#"%#%#", CONST_URL, partialURL];
NSString *encStr = [fullURL stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSURL *url = [NSURL URLWithString:encStr];
But then, the commentBody passed in the url still has *()$ things and not encoded into utf8.
What is the correct way to do it?
Thanks!
NSString * test = (NSString *) CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault,
(CFStringRef)validUrl,
NULL,
(CFStringRef)#";/?:#&=$+{}<>,",
kCFStringEncodingUTF8);
UPDATE
Hi #Rendy. Sorry for the quickie reply yesterday. I only had time to past a 1-liner before jumping off and had hoped it would be enough for you to correct the issue you were having.
You always want to encode the parameters of the URL. IF you have a URL that is a parameter to another URL or embedded in some XML; you'll want to encode the entire url (which means parameters get double-encoded - because you take a "valid" URL and escape it so it becomes a valid parameter in another URL (ie, : and & get escaped, so parameters don't mix together.)
If you like, you can add additional chars to the string below and they'll be replaced with percent-encoded values. I believe the string below already has you covered for the invalid values.
Here's a category on NSString:
#implementation NSString (encode)
+ (NSString*)stringEncodedAsUrlParameter:(NSString *)string
{
NSString *newString = NSMakeCollectable(
[(NSString *)CFURLCreateStringByAddingPercentEscapes(
kCFAllocatorDefault,
(CFStringRef)string,
NULL, /* charactersToLeaveUnescaped */
CFSTR(":/?#[]#!$ &'()*+,;=\"<>%{}|\\^~`"),
kCFStringEncodingUTF8
) autorelease]
);
if (newString) {
return newString;
}
return #"";
}
- (NSString*)stringByEncodingAsUrlParameter
{
return [NSString stringEncodedAsUrlParameter:self];
}
#end
Call it like this:
NSString * escapedParameters = [NSString stringEncodedAsUrlParameter:unescapedParameters];
Or:
NSString * escapedParameters = [unescapedParameters stringByEncodingAsUrlParameter];
Then add your properly escaped parameters to the end of your URL. If you encode the whole URL, you'll encode the "http://" portion and it won't work.
I originally copied the above from ASIHttpRequest, but any bugs added are mine.
Hope that helps! Best of luck!!

How do I break in the NSArray with NSString substrings enclosed in quotes

I have a NSSring:
NSString *example = #"'example01','example02','example03','example04'";
How can I make from this line NSArray?
NSString *example = [example stringByReplacingOccurrencesOfString:#"'" withString:#""];
NSArray * exampleArr = [example componentsSeparatedByString:#","];
NSArray *commaSeparatedComponents = [example componentsSeparatedByString:#","];
NSCharacterSet *quotesSet = [NSCharacterSet characterSetWithCharactersInString:#"'"];
for (NSString *component in commaSeparatedComponents) {
NSString *correctlyTrimmedComponent = [component stringByTrimmingCharactersInSet:quotesSet]; // only remove 's at the edges
// ... do something with each component; maybe add to a mutable array ...
}
This has the advantage over the other answer to not remove quotes that exist inside the values, but it doesn't do anything to actually solve quotes escaping that might have been necessary inside the data, and so on. It still misbehaves with some strings, but it misbehaves for fewer cases because it is less flippant about which quotes it removes.
If this is anything above and beyond a string in that exact format, you will probably need a parser for this sort of string that can handle the semantics of how the quotes are escaped, gracefully handles garbage, etc.

Resources