Wrapping an NSString (file path) in NSURL breaks Unicode? - ios

I've had an iPhone app in the store for 2 years that loaded local HTML files into a webview. While updating it I've run into an issue with the file paths. One of the elements in the path is in Russian, which still looks fine in _finalPath, but when I wrap it in an NSURL, the Cyrillic letters are converted to Unicode entities, breaking the path to the HTML file.
//finalPath is figured in and passed on from tenseListViewController
//ACK - this now seems to turn брать into %D0%B1%D1%80%D0%B0%D1%82%D1%8C in the middle of the path!
NSLog(#"-->starting showInfo: _finalPath is %#", _finalPath);
NSURL *url = [NSURL fileURLWithPath:_finalPath];
NSLog(#"*url is %#", url);
Here's the output in the log:
2013-06-05 13:51:39.409 NewTryout[29609:c07] -->starting showInfo: _finalPath is /Users/cford/Library/Application Support/iPhone Simulator/6.1/Applications/8C06E53D-BFA6-4EA5-823A-0EBDBB3B51B2/NewTryout.app/verbs/брать/imperfective/Present.html
2013-06-05 13:51:39.409 NewTryout[29609:c07] *url is file://localhost/Users/cford/Library/Application%20Support/iPhone%20Simulator/6.1/Applications/8C06E53D-BFA6-4EA5-823A-0EBDBB3B51B2/NewTryout.app/verbs/%D0%B1%D1%80%D0%B0%D1%82%D1%8C/imperfective/Present.html
Is this a change in iOS5 or 6, or should I be looking at something else in my code? Any suggestions?

Non-ASCII characters are simply not allowed in URLs to begin with (IRIs were invented to replace URLs, in particular to support Unicode). Your Russian characters are being encoded to UTF-8 octets and then those octets are being URL-encoded. That is correct behavior for URLs, and works just fine in HTML links. If the WebView is not opening the HTML file correctly when the link is clicked, then the WebView itself must be broken when processing URLs. It should be URL-decoding and UTF8-decoding the data before then using it. If it is not, then that is a bug in the WebView. Unless the WebView is just giving you the raw URL text when the link is clicked, and you are opening the file yourself, in which case it is your responsibility to decode the URL data before then using it in the rest of your code.

Related

Loading local images into WKWebView

I'm trying to get WKWebView to display locally downloaded images in a WKWebView. The webview normally displays HTML, which is retrieved remotely. The contents of the HTML can sometimes contain remote links to images. My app parses the HTML and looks for these HTML tags, downloads the file it is referencing and subsequently replaces the remote link with a local one.
Normally speaking, this wouldn't be very difficult but the images aren't being displayed, presumably due to the images and the local HTML files for the webview being in two separate directories (the documents directory and the app bundle directory respectively).
I've seen people suggest moving the download destination of the images to the same directory as where the HTML files are but this isn't an option for me as I don't want to start mixing up files downloaded by the user with local assets.
What would be my best course of action here?
Well, I've found a workaround. Instead of locally storing the images and referencing them in the HTML files, I'm now instead converting the images to Base64 and then adding them to the HTML. It's not ideal but it gets the job done. I'm going to leave this question open in case someone ever manages to find an actual solution.
To display cached HTML referencing cached resources in a WKWebView:
For each of the resources within your HTML content string, cache it into the directory as provided by NSTemporaryDirectory(). So an image tag like:
...<img src='https://www.myimage.com/example_image.png'/>...
should be cached and replaced into something like this:
...<img src='/private/var/mobile/Containers/Data/Application/527CF4FC-9319-4DFF-AB55-9E276890F5DC/tmp/example_image.png'/>...
Now cache the HTML content string with the replaced resource URLs. It must also be cached in the directory provided by NSTemporaryDirectory(). One difference here is that it must be cached (and later referenced) using the file:// protocol as a restriction of caching the string using NSData (see sample code).
For example file:///private/var/mobile/Containers/Data/Application/527CF4FC-9319-4DFF-AB55-9E276890F5DC/tmp/my_html_content_string.html
A few things to point out:
You cannot load the HTML as a raw string (loadHTMLString:baseURL:).
You cannot reference the cached resource within your HTML string using the file:// protocol. That may work in a UIWebView, but will not work in the WKWebView.
Objective-C
// To cache the HTML string:
NSString *HTML = <HTML CONTENT WITH CACHED RESOURCES>;
NSData *data = [HTML dataUsingEncoding: NSUTF8StringEncoding];
[data writeToURL: cachedHTMLURL atomically: YES];
// To load the store HTML file:
[myWKWebView loadRequest: [NSURLRequest requestWithURL: cachedHTMLURL]]; // (file://.../tmp/my_html_content_string.html)
Swift
// To cache the HTML string:
let HTML = <HTML CONTENT WITH CACHED RESOURCES>
let data = HTML.data(using: String.Encoding.utf8)
do {
try data.write(to: cachedHTMLURL, options: .atomic)
} catch {
print(error)
}
// To load the store HTML file:
myWKWebView.load(URLRequest(url: cachedHTMLURL)) // (file://.../tmp/my_html_content_string.html)
I had the same problem with WKWebView as it can not load both html strings and images at the same time for security purposes. I switched to UIWebView, which is deprecated, but I was able to load both html strings and referenced images at the same time.
I developed a definitive solution for the company I work for. But it relies on the html / javascript side. Anywhere inside your html code where you will reference to a local image <img src="..."/> you should set this "src" dynamically, and it will work seamlessly.
function getLocalURL(path) {
let origin = window.location.origin
if (origin == "file://") {
return origin + window.location.pathname.replace("/index.html","") + path
}
return path
}
You should, clearly, rename index.html to whatever is your main .htm(l) filename :)
Usage:
getLocalURL("/local_images/location_icon.png")
Will return a WKWebView working path for the referenced local image path:
"file:///Users/arthurdapaz/Library/Developer/CoreSimulator/Devices/5073AF19-26A0-460E-BC82-E89100B8E1AB/data/Containers/Data/Application/2B099343-0BF5-4849-B1C2-2512377A9772/Documents/distDriver/local_images/location_icon.png"

Loading local file with dataTaskWithURL throws unsupported url - Swift [duplicate]

I've seen many questions on SO concerning converting between NSURL and NSString. They all involve using either NSString *path = [myURL absoluteString]; or NSString *path = [myURL path];. What is the actual difference between these methods? Is there a time when one should be used over the other? I tried consulting the Apple Docs, but I found it less than helpful.
I'm used to URL's only being mentioned in discussions concerning websites and other topics regarding sending information between different machines, and never being mentioned when dealing with just the file structure on a single machine. Perhaps this is where some of my confusion is coming from, since NSURL seems to be the preferred way of accessing files, regardless of whether that file exists on a network or on the local device. Or maybe that's a totally unrelated topic. I'm not even sure.
Question 1:
What is the actual difference between these methods?
Let's analyze this writing 6 lines of code - 3 for a local and 3 for http URL - and playing around with them a little bit.
Let's create an NSURL using the file:// scheme. If you ask yourself why there are 3 / after file: you should remember that a complete URL exists of a scheme (file:// and absolute or relative path (you can find more information on creating URLs in RFC 1808 on page 3). We use an absolute path which starts with a / so that we end up with ///.
NSURL *aLocalURL = [NSURL URLWithString:#"file:///Users/dennis/Desktop/"];
NSLog(#"absolute string: %#", aLocalURL.absoluteString);
NSLog(#"path: %#", aLocalURL.path);
Output:
absolute string: file:///Users/dennis/Desktop/
path: /Users/dennis/Desktop
So we see that absoluteString still knows its scheme whereas path doesn't have this information anymore.
Note: path is a file (directory) URL and as the docs state, the trailing slash it is stripped.
Now let's take a look at remote URLs. With these type of URLs most people are more familiar. We create it using the same procedure as for local URLs. Our scheme is now http:// and our path is www.apple.com/.
NSURL *anHTTPURL = [NSURL URLWithString:#"http://www.apple.com/"];
NSLog(#"absolute string: %#", anHTTPURL.absoluteString);
NSLog(#"path: %#", anHTTPURL.path);
Output:
absolute string: http://www.apple.com/
path: /
Again, we see that the absolute string still knows its scheme but path is now /. So path seems to be not an appropriate way when working with remote URLs.
However, when we have an URL like http://www.apple.com/index.html we get
absolute string: http://www.apple.com/index.html
path: /index.html
Reading the docs helps here, too:
Per RFC 3986, the leading slash after the authority (host name and port) portion is treated as part of the path.
So the path is everything beginning (and including) at the slash after the authority which is www.apple.com in our case.
Question 2
Is there a time when one should be used over the other?
From the docs: (method: path)
If this URL object contains a file URL (as determined with isFileURL), the return value of this method is suitable for input into methods of NSFileManager or NSPathUtilities.
In my opinion that sentence states clearly that you should use path when you work with NSFileManager or NSPathUtilities.
Conclusion:
When you work with remote URLs you (generally) use absoluteString, otherwise the result is not what you (generally) want.
When you work with local URLs use path.
Sources:
http://www.ietf.org/rfc/rfc1808.txt
http://www.ietf.org/rfc/rfc3986.txt
NSURL Class Reference
Adding to HAS' response -- the Apple docs mention that Path-based URLs are simpler in some ways, however file reference URLs have the advantage that the reference remains valid if the file is moved or renamed while your app is running.
From the documentation for "Accessing Files and Directories":
"Path-based URLs are easier to manipulate, easier to debug, and are generally preferred by classes such as NSFileManager. An advantage of file reference URLs is that they are less fragile than path-based URLs while your app is running. If the user moves a file in the Finder, any path-based URLs that refer to the file immediately become invalid and must be updated to the new path. However, as long as the file moved to another location on the same disk, its unique ID does not change and any file reference URLs remain valid."
https://developer.apple.com/library/content/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/AccessingFilesandDirectories/AccessingFilesandDirectories.html
One further note, and I've only tried this for Swift and URL not NSURL. The relativeTo form of URL:
URL(fileURLWithPath: aPath, relativeTo: URL)
generates a URL that behaves not fully like a remote URL (as in #HAS above) and not like a file URL.
So, for example:
let url0 = URL(fileURLWithPath: "/Foo")
let url1 = URL(fileURLWithPath: "Bar", relativeTo: url0)
print("\(url1.path)")
// Output: "/Bar\n"
(similar to results for a remote URL, but not a file URL).
If we use absoluteString, we get:
print("\(url1.absoluteString)")
// Output: "file:///Bar\n"
(not similar to either a file URL or a remote URL).

How to Hardcode a NSURL path?

So, this seems like it should be super easy, but i'm stumped...
I have an iOS application that I am developing where I am downloading video content from a server, storing it in a custom folder in the Documents, and upon a certain trigger, trying to play the video. I am experiencing some loading errors in my MovieViewController, so am trying to debug the NSURL that is constructed from the file path in a separate standalone application. It is worth noting that the same content works fine when added to the Bundle, but there's quite a lot, so can't afford to do that.
Anyways...
I have set breakpoints in my app and copied out the path of the NSURL that gets loaded into my movie player, and am attempting to hardcode it into an NSURL in my test app. But I can't seem to just assign it with a preexisting value... Eg.
NSURL *url = "file:///var/mobile/Containers/Data/Application/21078F3B-12C5-4D42-8B8B-3C85CB7A0A91/Documents/SecondStory/BloodAlley/MEDIA/copperthief.mp4"
(which is what I copied out of the Variables View of the Debug area).
It gives me the error:
*"Implicit conversion of a non-Objective-C pointer type 'char *' to 'NSURL ' is disallowed with ARC"
... which I cannot seem to track down on SO ....
Of course, I have the NSString representation of the file path before it gets converted to an NSURL, so can just try to reconstruct it, but this got me curious why I couldn't just assign a value...
As the error states, "file:///var/.../copperthief.mp4" is a char * not an NSURL.
To convert it to an NSURL, first you have to get the char *'s NSString representation by adding the # prefix:
#"file:///var/.../copperthief.mp4"
then convert the NSString to an NSURL using URLWithString::
[NSURL URLWithString:#"file:///var/.../copperthief.mp4"];
The first thing is that "..." is not an NSString. It's a C string. An NSString would be #"...", wouldn't it? And even then, if you wanted an NSURL, you'd have to convert from NSString to NSURL, wouldn't you? I mean, you can't assign a string of any kind to an NSSURL variable and expect it to work, can you? This is a computer language; you can't say just any old thing that comes into your head - you have to obey the rules.

ios issue with stringByAddingPercentEscapesUsingEncoding

In my app I need to send some parameters to the url, when I am trying with the stringByAddingPercentEscapesUsingEncoding it is not converting correctly. If I am not using this encoding I am getting null(Exception) from the nsurl.Here is me code.
http://www.mycompurl.co?message=xyz&id=____ here I am sending the id 1 or 2 or any number.
when I convert this string to url by using stringByAddingPercentEscapesUsingEncoding I got
"http://www.mycompurl.co?message=xyz&id=**%E2%80%8B**1" (when I send 1 as parameter). Then I got the 0 data from the Url.
str = [NSString stringWithFormat:#"%#?message=xyz&id=​%#",Application_URL,bootupdateNew];
str = [str stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding];
url=[NSURL URLWithString:str];
NSError* error = nil;
data1 = [NSData dataWithContentsOfURL:url options:NSDataReadingUncached error:&error];
Thank you In advance
Basics
A URL is composed of several components.
Each component has its own rule how the component's source string must be encoded, so that this component becomes valid within the URL string.
Applying stringByAddingPercentEscapesUsingEncoding: will never always produce a correct URL if the string consists of more than one component (and if we assume, we have an unbounded set of source strings - so that the encoded string actually differs from the source string).
It even won't work always with a string which represents any single component.
In other words, for what's worth, stringByAddingPercentEscapesUsingEncoding: should not be used to try to make a URL out of several components. Even getting the URL query component correctly encoded is at least error prone, and when utilizing stringByAddingPercentEscapesUsingEncoding: it still remains wonky. (You may find correct implementations on SO, though - and I posted one myself).
But now, just forget about it:
It took awhile for Apple to recognize this failure, and invented NSURLComponents. It's available since iOS 7. Take a look! ;)

Get request length limit in iOS

I am using Omniture SiteCatalyst in my iPhone app.It uses get request to hit the servers internally via its sdk.However i am facing an issue where some of the request are not reaching the Omniture servers.The get request which is being sent is of variable length depending on the type of request(around 900 + characters).
My question is whether there any limit for the get request length in an iOS app? and if yes
how it would behave in case the request crosses the limit?
Theoretically if URL conforms to RFC 2396 it is fine. According to documentation
The NSURL class fails to create a new NSURL object if the path being
passed is not well-formed; the path must comply with RFC 2396.
Examples of cases that will not succeed are strings containing space
characters and high-bit characters. Should creating an NSURL object
fail, the creation methods return nil, which you must be prepared to
handle. If you are creating NSURL objects using file system paths, you
should use fileURLWithPath: or initFileURLWithPath:, which handle the
subtle differences between URL paths and file system paths. If you
wish to be tolerant of malformed path strings, you’ll need to use
functions provided by the Core Foundation framework to clean up the
strings.
But some time there is issue with specail character e.g. space, accents and others. You must [NSURL URLWithString:[urlString stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding]];
It is also possible server could not handle very long urls, if there are any limitation on server, server will simple truncate the rest of url string, if this is an issue then server will return 414 error url too long.

Resources