{"id":291,"date":"2009-04-10T10:25:00","date_gmt":"2009-04-10T15:25:00","guid":{"rendered":"http:\/\/vgable.com\/blog\/2009\/04\/10\/percent-escapes-gotcha\/"},"modified":"2009-04-10T10:25:02","modified_gmt":"2009-04-10T15:25:02","slug":"percent-escapes-gotcha","status":"publish","type":"post","link":"https:\/\/vgable.com\/blog\/2009\/04\/10\/percent-escapes-gotcha\/","title":{"rendered":"Percent Escapes Gotcha"},"content":{"rendered":"<p>If you use <code><a href=\"http:\/\/developer.apple.com\/documentation\/Cocoa\/Reference\/Foundation\/Classes\/NSString_Class\/Reference\/NSString.html#\/\/apple_ref\/occ\/instm\/NSString\/stringByAddingPercentEscapesUsingEncoding:\">stringByAddingPercentEscapesUsingEncoding:<\/a><\/code> more than once on a string, the resulting string will <em>not<\/em> decode correctly from just one call to <code>stringByReplacingPercentEscapesUsingEncoding:<\/code>. (stringByAddingPercentEscapesUsingEncoding: is not <a href=\"http:\/\/en.wikipedia.org\/wiki\/Indempotent\">indempotent<\/a>).<\/p>\n<pre>\nNSString *string = @\"100%\";\nNSString *escapedOnce = [string stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];\nNSString *escapedTwice = [escapedOnce stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];\nNSLog(@\"%@ escaped once: %@, escaped twice: %@\", string, escapedOnce, escapedTwice);\n<\/pre>\n<blockquote><p>100% escaped once: <strong>100%25<\/strong>, escaped twice: <strong>100%2525<\/strong><\/p><\/blockquote>\n<p>I thought I was programming defensively by eagerly adding percent-escapes to any string that would become part of a URL.  But this caused some annoying bugs resulting form a string being percent-escaped more then once. My solution was to create an indempotent replacement for <code>stringByAddingPercentEscapesUsingEncoding:<\/code> (I also simplified things a little by removing the encoding parameter, because I <em>never<\/em> used any encoding other then <code>NSUTF8StringEncoding<\/code>),<\/p>\n<pre>\n@implementation NSString (IndempotentPercentEscapes)\n- (NSString*) stringByReplacingPercentEscapesOnce;\n{\n\tNSString *unescaped = [self stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];\n\t\/\/self may be a string that looks like an invalidly escaped string,\n\t\/\/eg @\"100%\", in that case it clearly wasn't escaped,\n\t\/\/so we return it as our unescaped string.\n\treturn unescaped ? unescaped : self;\n}\n\n- (NSString*) stringByAddingPercentEscapesOnce;\n{\n\treturn [[self stringByReplacingPercentEscapesOnce] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];\n}\n@end\n<\/pre>\n<p>Usage example,<\/p>\n<pre>NSString *string = @\"100%\";\nNSString *escapedOnce = [string stringByAddingPercentEscapesOnce];\nNSString *escapedTwice = [escapedOnce stringByAddingPercentEscapesOnce];\nNSLog(@\"%@ escaped once: %@, escaped twice: %@\", string, escapedOnce, escapedTwice);<\/pre>\n<blockquote><p>100% escaped once: 100%25, escaped twice: 100%25<\/p><\/blockquote>\n<p>The paranoid have probably noticed that <code>[aBadlyEncodedString stringByReplacingPercentEscapesOnce]<\/code> will return <code>aBadlyEncodedString<\/code> not <code>nil<\/code>, <strong>This could make it harder to detect an error.<\/strong><\/p>\n<p>But it&#8217;s not something that I&#8217;m worried about for my application. Since I only ever use a UTF8 encoding, and it can represent <em>any<\/em> unicode character, it&#8217;s not possible to have an invalid string. But it&#8217;s certainly something to be aware of in situations where you might have strings with different encodings.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>If you use stringByAddingPercentEscapesUsingEncoding: more than once on a string, the resulting string will not decode correctly from just one call to stringByReplacingPercentEscapesUsingEncoding:. (stringByAddingPercentEscapesUsingEncoding: is not indempotent). NSString *string = @&#8221;100%&#8221;; NSString *escapedOnce = [string stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; NSString *escapedTwice = [escapedOnce stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; NSLog(@&#8221;%@ escaped once: %@, escaped twice: %@&#8221;, string, escapedOnce, escapedTwice); 100% escaped once: 100%25, [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[18,6,4,13],"tags":[78,163],"class_list":["post-291","post","type-post","status-publish","format-standard","hentry","category-bug-bite","category-cocoa","category-programming","category-sample-code","tag-nsstring","tag-unicode"],"_links":{"self":[{"href":"https:\/\/vgable.com\/blog\/wp-json\/wp\/v2\/posts\/291","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/vgable.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/vgable.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/vgable.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/vgable.com\/blog\/wp-json\/wp\/v2\/comments?post=291"}],"version-history":[{"count":0,"href":"https:\/\/vgable.com\/blog\/wp-json\/wp\/v2\/posts\/291\/revisions"}],"wp:attachment":[{"href":"https:\/\/vgable.com\/blog\/wp-json\/wp\/v2\/media?parent=291"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/vgable.com\/blog\/wp-json\/wp\/v2\/categories?post=291"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/vgable.com\/blog\/wp-json\/wp\/v2\/tags?post=291"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}