Objective-C and Swift URL encoding - ios

I have a NSString like this:
http://www.
but I want to transform it to:
http%3A%2F%2Fwww.
How can I do this?

To escape the characters you want is a little more work.
Example code
iOS7 and above:
NSString *unescaped = #"http://www";
NSString *escapedString = [unescaped stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLHostAllowedCharacterSet]];
NSLog(#"escapedString: %#", escapedString);
NSLog output:
escapedString: http%3A%2F%2Fwww
The following are useful URL encoding character sets:
URLFragmentAllowedCharacterSet "#%<>[\]^`{|}
URLHostAllowedCharacterSet "#%/<>?#\^`{|}
URLPasswordAllowedCharacterSet "#%/:<>?#[\]^`{|}
URLPathAllowedCharacterSet "#%;<>?[\]^`{|}
URLQueryAllowedCharacterSet "#%<>[\]^`{|}
URLUserAllowedCharacterSet "#%/:<>?#[\]^`
Creating a characterset combining all of the above:
NSCharacterSet *URLCombinedCharacterSet = [[NSCharacterSet characterSetWithCharactersInString:#" \"#%/:<>?#[\\]^`{|}"] invertedSet];
Creating a Base64
In the case of Base64 characterset:
NSCharacterSet *URLBase64CharacterSet = [[NSCharacterSet characterSetWithCharactersInString:#"/+=\n"] invertedSet];
For Swift 3.0:
var escapedString = originalString.addingPercentEncoding(withAllowedCharacters:.urlHostAllowed)
For Swift 2.x:
var escapedString = originalString.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLHostAllowedCharacterSet())
Note: stringByAddingPercentEncodingWithAllowedCharacters will also encode UTF-8 characters needing encoding.
Pre iOS7 use Core Foundation
Using Core Foundation With ARC:
NSString *escapedString = (NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(
NULL,
(__bridge CFStringRef) unescaped,
NULL,
CFSTR("!*'();:#&=+$,/?%#[]\" "),
kCFStringEncodingUTF8));
Using Core Foundation Without ARC:
NSString *escapedString = (NSString *)CFURLCreateStringByAddingPercentEscapes(
NULL,
(CFStringRef)unescaped,
NULL,
CFSTR("!*'();:#&=+$,/?%#[]\" "),
kCFStringEncodingUTF8);
Note: -stringByAddingPercentEscapesUsingEncoding will not produce the correct encoding, in this case it will not encode anything returning the same string.
stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding encodes 14 characrters:
`#%^{}[]|\"<> plus the space character as percent escaped.
testString:
" `~!##$%^&*()_+-={}[]|\\:;\"'<,>.?/AZaz"
encodedString:
"%20%60~!#%23$%25%5E&*()_+-=%7B%7D%5B%5D%7C%5C:;%22'%3C,%3E.?/AZaz"
Note: consider if this set of characters meet your needs, if not change them as needed.
RFC 3986 characters requiring encoding (% added since it is the encoding prefix character):
"!#$&'()*+,/:;=?#[]%"
Some "unreserved characters" are additionally encoded:
"\n\r \"%-.<>\^_`{|}~"

It's called URL encoding. More here.
-(NSString *)urlEncodeUsingEncoding:(NSStringEncoding)encoding {
return (NSString *)CFURLCreateStringByAddingPercentEscapes(NULL,
(CFStringRef)self,
NULL,
(CFStringRef)#"!*'\"();:#&=+$,/?%#[]% ",
CFStringConvertNSStringEncodingToEncoding(encoding));
}

This is not my solution. Someone else wrote in stackoverflow but I have forgotten how.
Somehow this solution works "well". It handles diacritic, chinese characters, and pretty much anything else.
- (NSString *) URLEncodedString {
NSMutableString * output = [NSMutableString string];
const char * source = [self UTF8String];
int sourceLen = strlen(source);
for (int i = 0; i < sourceLen; ++i) {
const unsigned char thisChar = (const unsigned char)source[i];
if (false && thisChar == ' '){
[output appendString:#"+"];
} else if (thisChar == '.' || thisChar == '-' || thisChar == '_' || thisChar == '~' ||
(thisChar >= 'a' && thisChar <= 'z') ||
(thisChar >= 'A' && thisChar <= 'Z') ||
(thisChar >= '0' && thisChar <= '9')) {
[output appendFormat:#"%c", thisChar];
} else {
[output appendFormat:#"%%%02X", thisChar];
}
}
return output;
}
If someone would tell me who wrote this code, I'll really appreciate it. Basically he has some explanation why this encoded string will decode exactly as it wish.
I modified his solution a little. I like space to be represented with %20 rather than +. That's all.

NSString * encodedString = (NSString *)CFURLCreateStringByAddingPercentEscapes(NUL,(CFStringRef)#"parameter",NULL,(CFStringRef)#"!*'();#&+$,/?%#[]~=_-.:",kCFStringEncodingUTF8 );
NSURL * url = [[NSURL alloc] initWithString:[#"address here" stringByAppendingFormat:#"?cid=%#",encodedString, nil]];

This can work in Objective C ARC.Use CFBridgingRelease to cast a Core Foundation-style object as an Objective-C object and transfer ownership of the object to ARC .See Function CFBridgingRelease here.
+ (NSString *)encodeUrlString:(NSString *)string {
return CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes
(kCFAllocatorDefault,
(__bridge CFStringRef)string,
NULL,
CFSTR("!*'();:#&=+$,/?%#[]"),
kCFStringEncodingUTF8)
);}

Swift iOS:
Just For Information : I have used this:
extension String {
func urlEncode() -> CFString {
return CFURLCreateStringByAddingPercentEscapes(
nil,
self,
nil,
"!*'();:#&=+$,/?%#[]",
CFStringBuiltInEncodings.UTF8.rawValue
)
}
}// end extension String

Here's what I use. Note you have to use the #autoreleasepool feature or the program might crash or lockup the IDE. I had to restart my IDE three times until I realized the fix. It appears that this code is ARC compliant.
This question has been asked many times, and many answers given, but sadly all of the ones selected (and a few others suggested) are wrong.
Here's the test string that I used: This is my 123+ test & test2. Got it?!
These are my Objective C++ class methods:
static NSString * urlDecode(NSString *stringToDecode) {
NSString *result = [stringToDecode stringByReplacingOccurrencesOfString:#"+" withString:#" "];
result = [result stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
return result;
}
static NSString * urlEncode(NSString *stringToEncode) {
#autoreleasepool {
NSString *result = (NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(
NULL,
(CFStringRef)stringToEncode,
NULL,
(CFStringRef)#"!*'\"();:#&=+$,/?%#[]% ",
kCFStringEncodingUTF8
));
result = [result stringByReplacingOccurrencesOfString:#"%20" withString:#"+"];
return result;
}
}

NSString *str = (NSString *)CFURLCreateStringByAddingPercentEscapes(
NULL,
(CFStringRef)yourString,
NULL,
CFSTR("/:"),
kCFStringEncodingUTF8);
You will need to release or autorelease str yourself.

Google implements this in their Google Toolbox for Mac. So that's a good place to peak how they're doing it. Another option is to include the Toolbox and use their implementation.
Checkout the implementation here. (Which comes down to exactly what people have been posting here).

This is how I am doing this in swift.
extension String {
func encodeURIComponent() -> String {
return self.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())!
}
func decodeURIComponent() -> String {
return self.componentsSeparatedByString("+").joinWithSeparator(" ").stringByRemovingPercentEncoding!
}
}

This is what I did on Swift 5:
func formatPassword() -> String {
var output = "";
for ch in self {
let char = String(ch)
switch ch {
case " ":
output.append("+")
break
case ".", "-", "_", "~", "a"..."z", "A"..."Z", "0"..."9":
output.append(char)
break
default:
print(ch)
let unicode = char.unicodeScalars.first?.value ?? 0
let unicodeValue = NSNumber(value: unicode).intValue
let hexValue = String(format: "%02X", arguments: [unicodeValue])
output = output.appendingFormat("%%%#", hexValue)
}
}
return output as String
}
Then I called this function where I defined my password.

//use NSString instance method like this:
+ (NSString *)encodeURIComponent:(NSString *)string
{
NSString *s = [string stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
return s;
}
+ (NSString *)decodeURIComponent:(NSString *)string
{
NSString *s = [string stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
return s;
}
remember,you should only do encode or decode for your parameter value, not all the url you request.

int strLength = 0;
NSString *urlStr = #"http://www";
NSLog(#" urlStr : %#", urlStr );
NSMutableString *mutableUrlStr = [urlStr mutableCopy];
NSLog(#" mutableUrlStr : %#", mutableUrlStr );
strLength = [mutableUrlStr length];
[mutableUrlStr replaceOccurrencesOfString:#":" withString:#"%3A" options:NSCaseInsensitiveSearch range:NSMakeRange(0, strLength)];
NSLog(#" mutableUrlStr : %#", mutableUrlStr );
strLength = [mutableUrlStr length];
[mutableUrlStr replaceOccurrencesOfString:#"/" withString:#"%2F" options:NSCaseInsensitiveSearch range:NSMakeRange(0, strLength)];
NSLog(#" mutableUrlStr : %#", mutableUrlStr );

Related

encoding NSURL in ISO-8859-1

I have a ViewController containing TextFields and I would need to send those values to a dedicated HTTP service.
My main concern comes from the encoding type, as this app is in French and may contain some accents ('é', 'è', etc,...) but also I need to format correctly my fields as it may contain spaces as well....
I tried to use different ways but I still have a wrong encoding on the server side.
here is a sample of my code:
let url_to_request = "http://11.22.33.44:8080/SRV/addRepertoire"
var params = "owner=\(User.sharedInstance.email)&adresse=\(adresse.text!)&nom=\(nom.text!)&telephone=\(telephone.text!)&commentaires=\(commentaires.text!)"
//trying to encode in ISO-8859-1
let dt = NSString(CString: params, encoding: NSISOLatin1StringEncoding)
//preparing string to be used in a NSURL
let final_url = dt!.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())
print("URL loadRepertoire: \(url_to_request+"?"+final_url!)")
for instance, field "nom" contains "bébé" which is encoded "b%C4%82%C5%A0b%C4%82%C5%A0" whereas my server is expecting "b%E9b%E9"
EDIT2:
I tried to use the following:
let url_to_request = "http://11.22.33.44:8080/SRV/addRepertoire"
let params = "owner=\(User.sharedInstance.email)&adresse=\(adresse.text!)&nom=\(nom.text!)&telephone=\(telephone.text!)&commentaires=\(commentaires.text!)"
let tmp_url = url_to_request + "?" + params
let final_url = tmp_url.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())!
print("URL addRepertoire: \(final_url)")
but the result remains:
b%C3%83%C2%A9b%C3%83%C2%A9, diplayed bébé instead of bébé
stringByAddingPercentEncodingWithAllowedCharacters always uses UTF-8 representation, so I'm afraid you may need to do it yourself.
extension String {
func stringByAddingPercentEncodingForISOLatin1() -> String? {
let allowedCharacterSet = NSCharacterSet(charactersInString:
"0123456789"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "_-.~"
+ "=&" //You'd better remove this and encode each param.
)
if let data = self.dataUsingEncoding(NSISOLatin1StringEncoding) {
var result = ""
for i in 0..<data.length {
let ch = UnsafePointer<UInt8>(data.bytes)[i]
if ch >= 0x80 || !allowedCharacterSet.characterIsMember(unichar(ch)) {
result += String(format: "%%%02X", ch)
} else {
result.append(UnicodeScalar(ch))
}
}
return result
} else {
return nil
}
}
}
"bébé".stringByAddingPercentEncodingForISOLatin1()! //->"b%E9b%E9"
Here is a solution in Swift 4:
extension String {
// Url percent encoding according to RFC3986 specifications
// https://tools.ietf.org/html/rfc3986#section-2.1
func urlPercentEncoded(withAllowedCharacters allowedCharacters:
CharacterSet, encoding: String.Encoding) -> String {
var returnStr = ""
// Compute each char seperatly
for char in self {
let charStr = String(char)
let charScalar = charStr.unicodeScalars[charStr.unicodeScalars.startIndex]
if allowedCharacters.contains(charScalar) == false,
let bytesOfChar = charStr.data(using: encoding) {
// Get the hexStr of every notAllowed-char-byte and put a % infront of it, append the result to the returnString
for byte in bytesOfChar {
returnStr += "%" + String(format: "%02hhX", byte as CVarArg)
}
} else {
returnStr += charStr
}
}
return returnStr
}
}
Usage:
"aouäöü!".urlPercentEncoded(withAllowedCharacters: .urlQueryAllowed,
encoding: .isoLatin1)
// Results in -> "aou%E4%F6%FC!"
For Objective-C :
- (NSString *)stringByAddingPercentEncodingForISOLatin1 {
NSCharacterSet *allowedCharacterSet = [NSCharacterSet characterSetWithCharactersInString:#"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_-.~=&"];
NSData *data = [self dataUsingEncoding:NSISOLatin1StringEncoding];
if (data) {
NSMutableString *result = [#"" mutableCopy];
const char *bytes = [data bytes];
for (NSUInteger i = 0; i < [data length]; i++)
{
unsigned char ch = (unsigned char)bytes[i];
if (ch >= 0x80 || ![allowedCharacterSet characterIsMember:ch]) {
[result appendFormat:#"%%%02X", ch];
} else {
[result appendFormat:#"%c", ch];
}
}
return [result copy];
}
return nil;}

Get email address (part of Subject Alternative Name) from a X509_Extension with openSSL

I got a iOS application trying to retrieve SAN from a certificate with openSSL. I am stuck at the part that I can get the X509_Extension correctly(I can check the data in the X509_Extension and it contains the email address, mixed in a bunch of other data). I have no idea how to generally extract the email address from the X509_Extension.
Can anyone help? Thanks. Following are my code:
int loc = X509_get_ext_by_NID(cert, NID_subject_alt_name, -1);
if (loc >= 0) {
X509_EXTENSION * ext = X509_get_ext(cert, loc);
//How to extract the email address from the ext?
}
Inspired by this thread I finally got it solved. FIY:
int loc = X509_get_ext_by_NID(cert, NID_subject_alt_name, -1);
if (loc >= 0) {
X509_EXTENSION * ext = X509_get_ext(cert, loc);
BUF_MEM *bptr = NULL;
char *buf = NULL;
BIO *bio = BIO_new(BIO_s_mem());
if(!X509V3_EXT_print(bio, ext, 0, 0)){
// error handling...
}
BIO_flush(bio);
BIO_get_mem_ptr(bio, &bptr);
// now bptr contains the strings of the key_usage, take
// care that bptr->data is NOT NULL terminated, so
// to print it well, let's do something..
buf = (char *)malloc( (bptr->length + 1)*sizeof(char) );
memcpy(buf, bptr->data, bptr->length);
buf[bptr->length] = '\0';
NSString *email = [NSString stringWithUTF8String:buf];
NSRange r1 = [email rangeOfString:#"email:"];
NSRange r2 = [email rangeOfString:#","];
NSRange rSub = NSMakeRange(r1.location + r1.length, r2.location - r1.location - r1.length);
NSString *email = [email substringWithRange:rSub];
}

How do i perform an arabic search in SQL Lite ignoring diacritics?

I'm trying to extract words that are stored in my SQL Lite database with ignoring diacritics but it always return an empty result. my database contains arabic words with diacritics and i would to make a search with words which are don't contain diacritics.
NSString *queryStatement = [[NSString alloc ]initWithFormat:#"SELECT ID,ARABIC, ARABICMEANING, FRENCHINARABIC, FRENCH, BEGINARABIC,BEGINFRENCH,ISFAVORITE FROM DictionaryDB WHERE FRENCHINARABIC LIKE \"%%%#%%\"",searchedWord];
For example searchedWord can be #"أكل" and can be with diacritics #"أَكَلَ".
How can i resolve this problem?
I solved such a thing by creating my own SQLite function that does this.
The basic idea is your query becomes:
NSString *queryStatement = [[NSString alloc] initWithFormat:#"SELECT ID, ARABIC, ARABICMEANING, FRENCHINARABIC, FRENCH, BEGINARABIC, BEGINFRENCH, ISFAVORITE FROM DictionaryDB WHERE contains(FRENCHINARABIC, '%#')", searchedWord];
where contains will be your custom function.
First you need to write a C function that implements the contains SQL function:
void contains(sqlite3_context *context, int argc, sqlite3_value **argv) {
BOOL res = NO;
if (argc < 2) {
res = NO;
} else {
char *textstr = (char *)sqlite3_value_text(argv[0]);
char *substr = (char *)sqlite3_value_text(argv[1]);
if (textstr && substr) {
NSString *text = [NSString stringWithCString:textstr encoding:NSUTF8StringEncoding];
NSString *sub = [NSString stringWithCString:substr encoding:NSUTF8StringEncoding];
// Adjust the options to suit your needs
NSRange range = [text rangeOfString:sub options:NSCaseInsensitiveSearch | NSDiacriticInsensitiveSearch | NSWidthInsensitiveSearch];
if (range.location != NSNotFound) {
res = YES;
}
}
}
sqlite3_result_int(context, res ? 1 : 0);
}
When you open your database connection you need to register this function:
// dbRef is your database reference
int res = sqlite3_create_function(dbRef, "contains", 2, SQLITE_UTF8, NULL, &contains, NULL, NULL);
if (res != SQLITE_OK) {
NSAssert1(0, #"Error: failed to create function in the database: '%s'.", sqlite3_errmsg(dbRef));
}
Side note - it's a bad idea to use stringWithFormat: to create your query. You should really consider using the sqlite3_bind_xxx functions to properly bind a value to a query. Using stringWithFormat: will fail if the value has any quotes or other special values. Using the sqlite3_bind_xxx functions takes care of properly quoting and escaping values.
I solved the problem by replacing the diacritics
here my correct query
#"SELECT ID,ARABIC, ARABICMEANING, FRENCHINARABIC, FRENCH, BEGINARABIC,BEGINFRENCH,ISFAVORITE FROM DictionaryDB WHERE
replace(replace(replace(replace(replace(replace(replace (replace(ARABICMEANING, 'ِ', ''),'ٍ',''),'ْ',''),'ّ','' ),'ٌ',''),'ُ',''),'ً',''),'َ','')LIKE \"%%%#%%\"",#"أكل"];

How do I compare characters in custom sqlite collation in objective-c?

I went through lots of questions here on SO (like this one) but I still need some assistance.
I need my sqlite select to order by slovenian alphabet (letter č comes after c, letter š after s and letter ž after z).
Here is the code I use:
static int sqlite3SloCollate(void * foo, int ll, const void *l, int rl,
const void *r){
NSString *left = [NSString stringWithCharacters:l length:ll];
NSString *right = [NSString stringWithCharacters:r length:rl];
//THIS IS WHERE I DON'T KNOW HOW TO COMPARE CHARACTERS
NSComparisonResult rs = [left compare:right options:NSForcedOrderingSearch];
return rs;
}
sqlite3_create_collation(database, "SLOCOLLATE", SQLITE_UTF8, NULL, &sqlite3SloCollate);
querySQL = [NSString stringWithFormat: #"SELECT s.id FROM lyrics l INNER JOIN song s ON (l.idSong=s.id) WHERE content LIKE '%%%#%%' GROUP BY s.id ORDER BY s.title COLLATE SLOCOLLATE;",searchString];
Which NSOrdering type should I use? Or do I have to write my own compare function (can you give me an example)?
I think that this function might help you :
- (NSComparisonResult)compare:(NSString *)aString options:(NSStringCompareOptions)mask range:(NSRange)range locale:(id)locale
(From Apple documentation).
You can create a locale using :
- (id)initWithLocaleIdentifier:(NSString *)string
(From Apple NSLocale Class Documentation).
This code should do the trick :
NSRange range = NSMakeRange(0, [left length]);
id locale = [[NSLocale alloc] initWithLocaleIdentifier:#"sl_SI"];
NSComparisonResult rs = [left compare:right options:NSForcedOrderingSearch range:range locale:locale];
I hope this will help.
The #DCMaxxx answer has most of it. Plus the comment that you need to use stringWithUTF8String. But there's some more issues.
1) stringWithUTF8String uses null-terminated c-strings, whilst sqlite is suppling strings with just a length and no null termination.
2) For the number of characters to compare, we need to take the shortest length, not just the left length.
3) When the comparison is equal for the compare, we then need to consider which string is longer.
Full code here. I use an NSMutableData object to convert length coded strings to null terminated strings. It's probably quicker and easier to do it with straight c code, if you are that way inclined.
static int sqlite3SloCollate(void * foo, int ll, const void *l, int rl,
const void *r){
NSMutableData* ld = [NSMutableData dataWithBytes:l length:ll+1];
[ld resetBytesInRange:NSMakeRange(ll, 1)];
NSString *left = [NSString stringWithUTF8String:[ld bytes]];
NSMutableData* rd = [NSMutableData dataWithBytes:r length:rl+1];
[rd resetBytesInRange:NSMakeRange(rl, 1)];
NSString *right = [NSString stringWithUTF8String:[rd bytes]];
NSRange range = NSMakeRange(0, MIN([left length],[right length]));
id locale = [[NSLocale alloc] initWithLocaleIdentifier:#"sl_SI"];
NSComparisonResult result = [left compare:right options:0 range:range locale:locale];
if (result==NSOrderedSame) {
if (ll>rl) {
result = NSOrderedDescending;
} else if (ll<rl) {
result = NSOrderedAscending;
}
}
// NSLog(#"Comparison:%# - %# - %li",left,right,(long)result);
return result;
}

IOS: verify if a string is an empty string

is there a method to verify if a NSString haven't characters?
example of string without characters can be:
#"" or #" " or #"\n " or #"\n\n ", I want cosider these strings as empty strings and print a nslog that say me that these are emty, what kind of control I should use?
You can use this test:
if ([[myString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] length] == 0) {
// The string is empty
}
You can iterate through every character in the string and check if it is the space (" ") or newline ("\n") character. If not, return false. Else if you search through the whole string and didn't return false, it is "empty".
Something like this:
NSString* myStr = #"A STRING";
for(int i = 0; i < [myStr length]; i++)
{
if(!(([myStr characterAtIndex:i] == #' ') || ([myStr characterAtIndex:i] == #'\n')))
{
return false;
}
}

Resources