Path to URL: easiest way?

Hey folks;

I have a tiny little problem, and I’m not sure of the best way to solve it, so I’d like some advice!

I have a path to a file on disk. Say:

foo/bar.html

But, on Windows, this would be

foo\bar.html

What’s the best way to get to the / form, so that the URL would work out?

I must be mis-reading the question. Why won’t a simple “/” -> “” string replace solve this?

I’m trying to be more disciplined about it; that would probably work in the majority of cases, and maybe even all of the ones I care about, but that’s why I’m asking!

I think the most disciplined way would be:

  1. parse the path into segments. If this is actually based on an URL, then use an URL-aware parser to get the segments out. If it is an URL, then you can split on /. If the original path is not an URL but some arbitrary platform-specific path format, then this is more complicated.
  2. construct a platform-specific path out of those segments. This is where you would std::path::MAIN_SEPARATOR to join the segments back together.
1 Like

As a side-note: Windows API for ages supports / as a valid path separator.

2 Likes

You need to be careful about encoding. Each path component has to be urlencoded: "foo bar" -> foo%20bar. Technically, there’s no standard encoding for paths in the URLs. On Unix, it usually needs to be whatever the server expects, which tends to be any bytes the filesystem has stored.

path.components().map(|c| urlencode(c.as_bytes())).join("/")
2 Likes

That doesn’t help, because that’s about input, not output. When I get a list of files, it will only give me paths with \.

Yep! I’ve got that part down. Thanks!

Yeah, so this ends up being easy on Unix, but it gets way worse on Windows, since there’s no as_bytes on OsStr.

Oh, yeah, and I forgot: OsStr doesn’t have a replace, so even this easy method is not as easy as it first looks.

This is my current best method, based on some code @alexcrichton wrote:

let mut path_with_forward_slash = OsString::new();

for (i, component) in path.components().enumerate() {
    if i > 0 {
        path_with_forward_slash.push("/");
    }
    path_with_forward_slash.push(component);
}

// if we have a non-utf8 path here, it will fail, but that's not realistically going to happen
let bytes = path_with_forward_slash.to_str().expect("found a non-UTF-8 path").as_bytes();

let encoded_filename = percent_encode(bytes, DEFAULT_ENCODE_SET);
println!("{}", encoded_filename.to_string());

Thanks for all the suggestions everyone!

2 Likes

Have you considered putting that code in a crate? It looks non-trivial to me!