diff --git a/Cargo.toml b/Cargo.toml index 366ba6e2..196377ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,5 +21,5 @@ version = "0.2" optional = true [dependencies] -rustc-serialize = "0.2" +rustc-serialize = "0.3" matches = "0.1" diff --git a/src/lib.rs b/src/lib.rs index da39f628..2fed9e89 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -119,7 +119,7 @@ assert!(css_url.serialize() == "http://servo.github.io/rust-url/main.css".to_str */ -#![feature(core, std_misc, collections, old_path)] +#![feature(core, std_misc, collections, old_path, path, os)] extern crate "rustc-serialize" as rustc_serialize; @@ -129,6 +129,7 @@ extern crate matches; use std::fmt::{self, Formatter}; use std::hash; use std::old_path as path; +use std::path as new_path; pub use host::{Host, Ipv6Address}; pub use parser::{ErrorHandler, ParseResult, ParseError}; @@ -472,7 +473,7 @@ impl Url { /// /// This returns `Err` if the given path is not absolute /// or, with a Windows path, if the prefix is not a disk prefix (e.g. `C:`). - pub fn from_file_path(path: &T) -> Result { + pub fn from_file_path(path: &T) -> Result { let path = try!(path.to_url_path()); Ok(Url::from_path_common(path)) } @@ -494,7 +495,7 @@ impl Url { /// as the base URL is `file:///var/index.html`, which might not be what was intended. /// /// (Note that `Path::new` removes any trailing slash.) - pub fn from_directory_path(path: &T) -> Result { + pub fn from_directory_path(path: &T) -> Result { let mut path = try!(path.to_url_path()); // Add an empty path component (i.e. a trailing slash in serialization) // so that the entire path is used as a base URL. @@ -921,6 +922,51 @@ pub trait ToUrlPath { } +impl ToUrlPath for new_path::Path { + #[cfg(unix)] + fn to_url_path(&self) -> Result, ()> { + use std::os::unix::prelude::*; + if !self.is_absolute() { + return Err(()) + } + // skip the root component + Ok(self.components().skip(1).map(|c| { + percent_encode(c.as_os_str().as_bytes(), DEFAULT_ENCODE_SET) + }).collect()) + } + + #[cfg(windows)] + fn to_url_path(&self) -> Result, ()> { + if !self.is_absolute() { + return Err(()) + } + let mut components = self.components(); + let disk = match components.next() { + Some(new_path::Component::Prefix { + parsed: new_path::Prefix::Disk(byte), .. + }) => byte, + + // FIXME: do something with UNC and other prefixes? + _ => return Err(()) + }; + + // Start with the prefix, e.g. "C:" + let mut path = vec![format!("{}:", disk as char)]; + + for component in components { + if component == new_path::Component::RootDir { continue } + // FIXME: somehow work with non-unicode? + let part = match component.as_os_str().to_str() { + Some(s) => s, + None => return Err(()), + }; + path.push(percent_encode(part.as_bytes(), DEFAULT_ENCODE_SET)); + } + Ok(path) + } +} + + impl ToUrlPath for path::posix::Path { fn to_url_path(&self) -> Result, ()> { if !self.is_absolute() { @@ -956,6 +1002,58 @@ pub trait FromUrlPath { } +impl FromUrlPath for new_path::PathBuf { + #[cfg(unix)] + fn from_url_path(path: &[String]) -> Result { + use std::ffi::OsStr; + use std::os::unix::prelude::*; + use std::path::PathBuf; + + if path.is_empty() { + return Ok(PathBuf::new("/")) + } + let mut bytes = Vec::new(); + for path_part in path.iter() { + bytes.push(b'/'); + percent_decode_to(path_part.as_bytes(), &mut bytes); + } + let os_str = ::from_bytes(&bytes); + let path = PathBuf::new(&os_str); + debug_assert!(path.is_absolute(), + "to_file_path() failed to produce an absolute Path"); + Ok(path) + } + + #[cfg(windows)] + fn from_url_path(path: &[String]) -> Result { + use std::path::PathBuf; + + if path.is_empty() { + return Err(()) + } + let prefix = &*path[0]; + if prefix.len() != 2 || !parser::starts_with_ascii_alpha(prefix) + || prefix.as_bytes()[1] != b':' { + return Err(()) + } + let mut string = prefix.to_string(); + for path_part in path[1..].iter() { + string.push('\\'); + + // Currently non-unicode windows paths cannot be represented + match String::from_utf8(percent_decode(path_part.as_bytes())) { + Ok(s) => string.push_str(&s), + Err(..) => return Err(()), + } + } + let path = PathBuf::new(&string); + debug_assert!(path.is_absolute(), + "to_file_path() failed to produce an absolute Path"); + Ok(path) + } +} + + impl FromUrlPath for path::posix::Path { fn from_url_path(path: &[String]) -> Result { if path.is_empty() { diff --git a/src/tests.rs b/src/tests.rs index 78c7903b..54cd1538 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -259,3 +259,89 @@ fn directory_paths() { assert_eq!(url.path(), Some(&[ "C:".to_string(), "foo".to_string(), "bar".to_string(), "".to_string()][..])); } + +#[test] +fn new_file_paths() { + use std::path::{Path, PathBuf}; + if cfg!(unix) { + assert_eq!(Url::from_file_path(Path::new("relative")), Err(())); + assert_eq!(Url::from_file_path(Path::new("../relative")), Err(())); + } else { + assert_eq!(Url::from_file_path(Path::new("relative")), Err(())); + assert_eq!(Url::from_file_path(Path::new(r"..\relative")), Err(())); + assert_eq!(Url::from_file_path(Path::new(r"\drive-relative")), Err(())); + assert_eq!(Url::from_file_path(Path::new(r"\\ucn\")), Err(())); + } + + if cfg!(unix) { + let mut url = Url::from_file_path(Path::new("/foo/bar")).unwrap(); + assert_eq!(url.host(), Some(&Host::Domain("".to_string()))); + assert_eq!(url.path(), Some(&["foo".to_string(), "bar".to_string()][..])); + assert!(url.to_file_path() == Ok(PathBuf::new("/foo/bar"))); + + url.path_mut().unwrap()[1] = "ba\0r".to_string(); + url.to_file_path::().is_ok(); + + url.path_mut().unwrap()[1] = "ba%00r".to_string(); + url.to_file_path::().is_ok(); + } +} + +#[test] +#[cfg(unix)] +fn new_path_bad_utf8() { + use std::ffi::OsStr; + use std::os::unix::prelude::*; + use std::path::{Path, PathBuf}; + + let url = Url::from_file_path(Path::new("/foo/ba%80r")).unwrap(); + let os_str = ::from_bytes(b"/foo/ba\x80r"); + assert_eq!(url.to_file_path(), Ok(PathBuf::new(&os_str))); +} + +#[test] +#[cfg(windows)] +fn new_path_windows_fun() { + use std::path::{Path, PathBuf}; + let mut url = Url::from_file_path(Path::new(r"C:\foo\bar")).unwrap(); + assert_eq!(url.host(), Some(&Host::Domain("".to_string()))); + assert_eq!(url.path(), Some(&["C:".to_string(), "foo".to_string(), "bar".to_string()][..])); + assert_eq!(url.to_file_path::(), + Ok(PathBuf::new(r"C:\foo\bar"))); + + url.path_mut().unwrap()[2] = "ba\0r".to_string(); + assert!(url.to_file_path::().is_ok()); + + url.path_mut().unwrap()[2] = "ba%00r".to_string(); + assert!(url.to_file_path::().is_ok()); + + // Invalid UTF-8 + url.path_mut().unwrap()[2] = "ba%80r".to_string(); + assert!(url.to_file_path::().is_err()); +} + + +#[test] +fn new_directory_paths() { + use std::path::Path; + + if cfg!(unix) { + assert_eq!(Url::from_directory_path(Path::new("relative")), Err(())); + assert_eq!(Url::from_directory_path(Path::new("../relative")), Err(())); + + let url = Url::from_directory_path(Path::new("/foo/bar")).unwrap(); + assert_eq!(url.host(), Some(&Host::Domain("".to_string()))); + assert_eq!(url.path(), Some(&["foo".to_string(), "bar".to_string(), + "".to_string()][..])); + } else { + assert_eq!(Url::from_directory_path(Path::new("relative")), Err(())); + assert_eq!(Url::from_directory_path(Path::new(r"..\relative")), Err(())); + assert_eq!(Url::from_directory_path(Path::new(r"\drive-relative")), Err(())); + assert_eq!(Url::from_directory_path(Path::new(r"\\ucn\")), Err(())); + + let url = Url::from_directory_path(Path::new(r"C:\foo\bar")).unwrap(); + assert_eq!(url.host(), Some(&Host::Domain("".to_string()))); + assert_eq!(url.path(), Some(&["C:".to_string(), "foo".to_string(), + "bar".to_string(), "".to_string()][..])); + } +}