6

I don't understand how Rust concatenates file paths. Why doesn't this work:

fn main() {
    let root = std::path::Path::new("resources/");
    let uri = std::path::Path::new("/js/main.js");
    let path = root.join(uri);
    assert_eq!(path.to_str(), Some("resources/js/main.js"));
}

fails with:

thread 'main' panicked at 'assertion failed: `(left == right)`
  left: `Some("/js/main.js")`,
 right: `Some("resources/js/main.js")`', src/main.rs:5:5

I see in the docs that "pushing an absolute path replaces the existing path", but this seems like a terrible idea that will catch a lot of people.

In that case, how do I safely strip the absolute path, or make it relative?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Petrus Theron
  • 27,855
  • 36
  • 153
  • 287

1 Answers1

9

This is because "/js/main.js" is treated as an absolute path (doc)

If path is absolute, it replaces the current path.

On Windows:

  • if path has a root but no prefix (e.g. \windows), it replaces everything except for the prefix (if any) of self.
  • if path has a prefix but no root, it replaces self.

If you change your example to "js/main.js" and then use join, it will be properly constructed (playground)

hellow
  • 12,430
  • 7
  • 56
  • 79
  • Thanks, then how do I treat the incoming path as always relative? – Petrus Theron Nov 22 '18 at 12:07
  • 1
    FYI: `os.path.join` has the same behavior in Python. I am not sure *why*, but at least there's some consistency there. – Matthieu M. Nov 22 '18 at 12:08
  • 1
    @PetrusTheron I would suggest not using `/` at all. You can omit them completly. – hellow Nov 22 '18 at 12:11
  • So...how do I strip the leading slash and ensure that paths are always descendants of a given folder? As you can probably tell from my example, the slash-prefix is coming from a browser. Would also like to avoid parent "../../../../some-secret" files from leaking out. – Petrus Theron Nov 22 '18 at 12:12
  • I would suggest using a crate ;) https://crates.io/crates/path-absolutize or https://crates.io/crates/path-dedot – hellow Nov 22 '18 at 12:14
  • @hellow Wouldn't *path-canonicalize* a more precise name? *absolutize* sounds to me as *Make all paths absolut* – Tim Diekmann Nov 22 '18 at 12:15
  • 2
    @TimDiekmann quoting the [doc](https://crates.io/crates/path-absolutize): *"The difference between absolutize and canonicalize methods is that absolutize does not care about whether the file exists and what the file really is."* ;) – hellow Nov 22 '18 at 12:16
  • 4
    @PetrusTheron you can use [`Path::strip_prefix`](https://doc.rust-lang.org/stable/std/path/struct.Path.html#method.strip_prefix) to remove the leading `/`: [playground](https://play.integer32.com/?version=stable&mode=release&edition=2015&gist=b67cfdcb0ccfa3814a953fa5e0626cd3) – Jmb Nov 22 '18 at 15:54
  • Adding on to @MatthieuM. 's assertion above about Python, in the C++ [`std::filesystem::path::append`](https://en.cppreference.com/w/cpp/filesystem/path/append) (it's the equivalent method used to what's happening here), this behavior is also identical. Don't put leading slashes into your path unless it's an absolute path. – Kevin Anderson Dec 20 '21 at 15:07