The answer is actually very simple: The URL needs to be “double-escaped”, i.e.
This is because the value gets unescaped to
https://example.com/My%20App.plist before being treated as another URL. This gets unescaped by the server at
example.com to a space.
The parser does not treat + specially:
...&url=https://.../test/a+b results in
"GET /test/a+b HTTP/1.1" appearing in the Apache logs. (It is unwise to assume that all query strings are
application/x-www-form-urlencoded; this is only standardized in HTML.)
Incidentally, it looks like itms-services uses
+[NSURL URLWithString:] to validate URLs:
url=.../My%20App.plist results in no request because
[NSURL URLWithString:@"https://.../My App.plist"] returns
nil. However, there’s a long-standing bug in NSURL: It will escape a single invalid (BMP) character at the end instead of returning nil. My test cases
url=.../test/%3cresults in the log
"GET /test/< HTTP/1.1"(this is definitely invalid HTTP!)
url=.../test/%0aresults in an error on device but no log message (because Apache treats it as a malformed request)
url=.../test/%0dresults in the log
"GET /test/\r HTTP/1.1"