{"id":51238,"date":"2026-03-16T16:06:24","date_gmt":"2026-03-16T20:06:24","guid":{"rendered":"https:\/\/mjtsai.com\/blog\/?p=51238"},"modified":"2026-03-16T16:07:14","modified_gmt":"2026-03-16T20:07:14","slug":"url-nsurl-double-encodes-characters-unnecessarily","status":"publish","type":"post","link":"https:\/\/mjtsai.com\/blog\/2026\/03\/16\/url-nsurl-double-encodes-characters-unnecessarily\/","title":{"rendered":"URL\/NSURL Double-Encodes Characters Unnecessarily"},"content":{"rendered":"<p><a href=\"https:\/\/lapcatsoftware.com\/articles\/2025\/9\/5.html\">Jeff Johnson<\/a>:<\/p>\n<blockquote cite=\"https:\/\/lapcatsoftware.com\/articles\/2025\/9\/5.html\">\n<p>The older, simpler API <code>[NSURL URLWithString:URLString]<\/code> behaves the same as <code>[NSURL URLWithString:URLString encodingInvalidCharacters:YES]<\/code> when your code is compiled with the iOS 17 or macOS 14 SDK. So much for backward compatibility! <code>[NSURL URLWithString:URLString]<\/code> continues to work fine with example 1, leaving the URL string untouched, but it mangles example 2:<\/p>\n<blockquote><code>https:\/\/example.org?url=https%253A%252F%252Fexample.org%253Ffoo%5B0%5D%253Dbar<\/code><\/blockquote>\n<p>Notice that the brackets are encoded as <code>%5B<\/code> and <code>%5D<\/code>, which is good, but the preexisting <code>%<\/code> characters are also encoded as <code>%25<\/code>, thereby transforming <code>%3A<\/code> (an encoded <code>:<\/code> character) into <code>%253A<\/code>, which is bad, indeed bonkers! The <code>%<\/code> characters did not need to be encoded, because they are already valid characters in a URL query.<\/p>\n<\/blockquote>\n\n<p>I&rsquo;ve seen other weird behavior, even using an old SDK, such as <code>NSURLComponents<\/code> returning <code>nil<\/code> instead of a valid URL.<\/p>\n\n<p>Previously:<\/p>\n<ul>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2024\/03\/07\/eaglefiler-1-9-13\/\">EagleFiler 1.9.13<\/a><\/li>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2022\/07\/26\/mail-links-and-percentages\/\">Mail Links and Percentages<\/a><\/li>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2013\/07\/13\/url-path-retrieval-cheat-sheet\/\">URL Path Retrieval Cheat Sheet<\/a><\/li>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2013\/06\/29\/url-encoding\/\">What Every Web Developer Must Know About URL Encoding<\/a><\/li>\n<\/ul>","protected":false},"excerpt":{"rendered":"<p>Jeff Johnson: The older, simpler API [NSURL URLWithString:URLString] behaves the same as [NSURL URLWithString:URLString encodingInvalidCharacters:YES] when your code is compiled with the iOS 17 or macOS 14 SDK. So much for backward compatibility! [NSURL URLWithString:URLString] continues to work fine with example 1, leaving the URL string untouched, but it mangles example 2: https:\/\/example.org?url=https%253A%252F%252Fexample.org%253Ffoo%5B0%5D%253Dbar Notice that [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"apple_news_api_created_at":"2026-03-16T20:06:28Z","apple_news_api_id":"75b6570e-3bce-4583-9909-60bb21e64858","apple_news_api_modified_at":"2026-03-16T20:06:29Z","apple_news_api_revision":"AAAAAAAAAAD\/\/\/\/\/\/\/\/\/\/w==","apple_news_api_share_url":"https:\/\/apple.news\/AdbZXDjvORYOZCWC7IeZIWA","apple_news_coverimage":0,"apple_news_coverimage_caption":"","apple_news_is_hidden":false,"apple_news_is_paid":false,"apple_news_is_preview":false,"apple_news_is_sponsored":false,"apple_news_maturity_rating":"","apple_news_metadata":"\"\"","apple_news_pullquote":"","apple_news_pullquote_position":"","apple_news_slug":"","apple_news_sections":"\"\"","apple_news_suppress_video_url":false,"apple_news_use_image_component":false,"footnotes":""},"categories":[4],"tags":[69,31,2741,30,2742,71,489],"class_list":["post-51238","post","type-post","status-publish","format-standard","hentry","category-programming-category","tag-cocoa","tag-ios","tag-ios-26","tag-mac","tag-macos-tahoe-26","tag-programming","tag-url"],"apple_news_notices":[],"_links":{"self":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/51238","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/comments?post=51238"}],"version-history":[{"count":2,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/51238\/revisions"}],"predecessor-version":[{"id":51246,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/51238\/revisions\/51246"}],"wp:attachment":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/media?parent=51238"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/categories?post=51238"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/tags?post=51238"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}