Prechádzať zdrojové kódy

Auto merge of #360 - servo:unc, r=SimonSapin+nox

Windows: implement conversion to/from UNC paths

This is a rebase of #284 with some additional fixes. Original work by @wfraser, sorry I took so long to get to this!

Fixes #284.

r? @nox for the last two commits.

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/rust-url/360)
<!-- Reviewable:end -->
bors-servo 9 rokov pred
rodič
commit
167fa4e6bb
3 zmenil súbory, kde vykonal 113 pridanie a 56 odobranie
  1. 1 1
      Cargo.toml
  2. 85 55
      src/lib.rs
  3. 27 0
      tests/unit.rs

+ 1 - 1
Cargo.toml

@@ -2,7 +2,7 @@
 
 
 name = "url"
 name = "url"
 # When updating version, also modify html_root_url in the lib.rs
 # When updating version, also modify html_root_url in the lib.rs
-version = "1.4.1"
+version = "1.5.0"
 authors = ["The rust-url developers"]
 authors = ["The rust-url developers"]
 
 
 description = "URL library for Rust, based on the WHATWG URL Standard"
 description = "URL library for Rust, based on the WHATWG URL Standard"

+ 85 - 55
src/lib.rs

@@ -90,7 +90,7 @@ let css_url = this_document.join("../main.css").unwrap();
 assert_eq!(css_url.as_str(), "http://servo.github.io/rust-url/main.css")
 assert_eq!(css_url.as_str(), "http://servo.github.io/rust-url/main.css")
 */
 */
 
 
-#![doc(html_root_url = "https://docs.rs/url/1.4.0")]
+#![doc(html_root_url = "https://docs.rs/url/1.5.1")]
 
 
 #[cfg(feature="rustc-serialize")] extern crate rustc_serialize;
 #[cfg(feature="rustc-serialize")] extern crate rustc_serialize;
 #[macro_use] extern crate matches;
 #[macro_use] extern crate matches;
@@ -1438,7 +1438,7 @@ impl Url {
     /// Convert a file name as `std::path::Path` into an URL in the `file` scheme.
     /// Convert a file name as `std::path::Path` into an URL in the `file` scheme.
     ///
     ///
     /// This returns `Err` if the given path is not absolute or,
     /// This returns `Err` if the given path is not absolute or,
-    /// on Windows, if the prefix is not a disk prefix (e.g. `C:`).
+    /// on Windows, if the prefix is not a disk prefix (e.g. `C:`) or a UNC prefix (`\\`).
     ///
     ///
     /// # Examples
     /// # Examples
     ///
     ///
@@ -1460,17 +1460,17 @@ impl Url {
     /// ```
     /// ```
     pub fn from_file_path<P: AsRef<Path>>(path: P) -> Result<Url, ()> {
     pub fn from_file_path<P: AsRef<Path>>(path: P) -> Result<Url, ()> {
         let mut serialization = "file://".to_owned();
         let mut serialization = "file://".to_owned();
-        let path_start = serialization.len() as u32;
-        path_to_file_url_segments(path.as_ref(), &mut serialization)?;
+        let host_start = serialization.len() as u32;
+        let (host_end, host) = path_to_file_url_segments(path.as_ref(), &mut serialization)?;
         Ok(Url {
         Ok(Url {
             serialization: serialization,
             serialization: serialization,
             scheme_end: "file".len() as u32,
             scheme_end: "file".len() as u32,
-            username_end: path_start,
-            host_start: path_start,
-            host_end: path_start,
-            host: HostInternal::None,
+            username_end: host_start,
+            host_start: host_start,
+            host_end: host_end,
+            host: host,
             port: None,
             port: None,
-            path_start: path_start,
+            path_start: host_end,
             query_start: None,
             query_start: None,
             fragment_start: None,
             fragment_start: None,
         })
         })
@@ -1479,7 +1479,7 @@ impl Url {
     /// Convert a directory name as `std::path::Path` into an URL in the `file` scheme.
     /// Convert a directory name as `std::path::Path` into an URL in the `file` scheme.
     ///
     ///
     /// This returns `Err` if the given path is not absolute or,
     /// This returns `Err` if the given path is not absolute or,
-    /// on Windows, if the prefix is not a disk prefix (e.g. `C:`).
+    /// on Windows, if the prefix is not a disk prefix (e.g. `C:`) or a UNC prefix (`\\`).
     ///
     ///
     /// Compared to `from_file_path`, this ensure that URL’s the path has a trailing slash
     /// Compared to `from_file_path`, this ensure that URL’s the path has a trailing slash
     /// so that the entire path is considered when using this URL as a base URL.
     /// so that the entire path is considered when using this URL as a base URL.
@@ -1568,17 +1568,23 @@ impl Url {
     /// let path = url.to_file_path();
     /// let path = url.to_file_path();
     /// ```
     /// ```
     ///
     ///
-    /// Returns `Err` if the host is neither empty nor `"localhost"`,
+    /// Returns `Err` if the host is neither empty nor `"localhost"` (except on Windows, where
+    /// `file:` URLs may have a non-local host),
     /// or if `Path::new_opt()` returns `None`.
     /// or if `Path::new_opt()` returns `None`.
     /// (That is, if the percent-decoded path contains a NUL byte or,
     /// (That is, if the percent-decoded path contains a NUL byte or,
     /// for a Windows path, is not UTF-8.)
     /// for a Windows path, is not UTF-8.)
     #[inline]
     #[inline]
     pub fn to_file_path(&self) -> Result<PathBuf, ()> {
     pub fn to_file_path(&self) -> Result<PathBuf, ()> {
-        // FIXME: Figure out what to do w.r.t host.
-        if matches!(self.host(), None | Some(Host::Domain("localhost"))) {
-            if let Some(segments) = self.path_segments() {
-                return file_url_segments_to_pathbuf(segments)
-            }
+        if let Some(segments) = self.path_segments() {
+            let host = match self.host() {
+                None | Some(Host::Domain("localhost")) => None,
+                Some(_) if cfg!(windows) && self.scheme() == "file" => {
+                    Some(&self.serialization[self.host_start as usize .. self.host_end as usize])
+                },
+                _ => return Err(())
+            };
+
+            return file_url_segments_to_pathbuf(host, segments);
         }
         }
         Err(())
         Err(())
     }
     }
@@ -1740,11 +1746,13 @@ impl serde::Deserialize for Url {
 }
 }
 
 
 #[cfg(any(unix, target_os = "redox"))]
 #[cfg(any(unix, target_os = "redox"))]
-fn path_to_file_url_segments(path: &Path, serialization: &mut String) -> Result<(), ()> {
+fn path_to_file_url_segments(path: &Path, serialization: &mut String)
+                             -> Result<(u32, HostInternal), ()> {
     use std::os::unix::prelude::OsStrExt;
     use std::os::unix::prelude::OsStrExt;
     if !path.is_absolute() {
     if !path.is_absolute() {
         return Err(())
         return Err(())
     }
     }
+    let host_end = to_u32(serialization.len()).unwrap();
     let mut empty = true;
     let mut empty = true;
     // skip the root component
     // skip the root component
     for component in path.components().skip(1) {
     for component in path.components().skip(1) {
@@ -1757,37 +1765,50 @@ fn path_to_file_url_segments(path: &Path, serialization: &mut String) -> Result<
         // An URL’s path must not be empty.
         // An URL’s path must not be empty.
         serialization.push('/');
         serialization.push('/');
     }
     }
-    Ok(())
+    Ok((host_end, HostInternal::None))
 }
 }
 
 
 #[cfg(windows)]
 #[cfg(windows)]
-fn path_to_file_url_segments(path: &Path, serialization: &mut String) -> Result<(), ()> {
+fn path_to_file_url_segments(path: &Path, serialization: &mut String)
+                             -> Result<(u32, HostInternal), ()> {
     path_to_file_url_segments_windows(path, serialization)
     path_to_file_url_segments_windows(path, serialization)
 }
 }
 
 
 // Build this unconditionally to alleviate https://github.com/servo/rust-url/issues/102
 // Build this unconditionally to alleviate https://github.com/servo/rust-url/issues/102
 #[cfg_attr(not(windows), allow(dead_code))]
 #[cfg_attr(not(windows), allow(dead_code))]
-fn path_to_file_url_segments_windows(path: &Path, serialization: &mut String) -> Result<(), ()> {
+fn path_to_file_url_segments_windows(path: &Path, serialization: &mut String)
+                                     -> Result<(u32, HostInternal), ()> {
     use std::path::{Prefix, Component};
     use std::path::{Prefix, Component};
     if !path.is_absolute() {
     if !path.is_absolute() {
         return Err(())
         return Err(())
     }
     }
     let mut components = path.components();
     let mut components = path.components();
-    let disk = match components.next() {
+
+    let host_end;
+    let host_internal;
+    match components.next() {
         Some(Component::Prefix(ref p)) => match p.kind() {
         Some(Component::Prefix(ref p)) => match p.kind() {
-            Prefix::Disk(byte) => byte,
-            Prefix::VerbatimDisk(byte) => byte,
-            _ => return Err(()),
+            Prefix::Disk(letter) | Prefix::VerbatimDisk(letter) => {
+                host_end = to_u32(serialization.len()).unwrap();
+                host_internal = HostInternal::None;
+                serialization.push('/');
+                serialization.push(letter as char);
+                serialization.push(':');
+            },
+            Prefix::UNC(server, share) | Prefix::VerbatimUNC(server, share) => {
+                let host = Host::parse(server.to_str().ok_or(())?).map_err(|_| ())?;
+                write!(serialization, "{}", host).unwrap();
+                host_end = to_u32(serialization.len()).unwrap();
+                host_internal = host.into();
+                serialization.push('/');
+                let share = share.to_str().ok_or(())?;
+                serialization.extend(percent_encode(share.as_bytes(), PATH_SEGMENT_ENCODE_SET));
+            },
+            _ => return Err(())
         },
         },
 
 
-        // FIXME: do something with UNC and other prefixes?
         _ => return Err(())
         _ => return Err(())
-    };
-
-    // Start with the prefix, e.g. "C:"
-    serialization.push('/');
-    serialization.push(disk as char);
-    serialization.push(':');
+    }
 
 
     for component in components {
     for component in components {
         if component == Component::RootDir { continue }
         if component == Component::RootDir { continue }
@@ -1796,15 +1817,19 @@ fn path_to_file_url_segments_windows(path: &Path, serialization: &mut String) ->
         serialization.push('/');
         serialization.push('/');
         serialization.extend(percent_encode(component.as_bytes(), PATH_SEGMENT_ENCODE_SET));
         serialization.extend(percent_encode(component.as_bytes(), PATH_SEGMENT_ENCODE_SET));
     }
     }
-    Ok(())
+    Ok((host_end, host_internal))
 }
 }
 
 
 #[cfg(any(unix, target_os = "redox"))]
 #[cfg(any(unix, target_os = "redox"))]
-fn file_url_segments_to_pathbuf(segments: str::Split<char>) -> Result<PathBuf, ()> {
+fn file_url_segments_to_pathbuf(host: Option<&str>, segments: str::Split<char>) -> Result<PathBuf, ()> {
     use std::ffi::OsStr;
     use std::ffi::OsStr;
     use std::os::unix::prelude::OsStrExt;
     use std::os::unix::prelude::OsStrExt;
     use std::path::PathBuf;
     use std::path::PathBuf;
 
 
+    if host.is_some() {
+        return Err(());
+    }
+
     let mut bytes = Vec::new();
     let mut bytes = Vec::new();
     for segment in segments {
     for segment in segments {
         bytes.push(b'/');
         bytes.push(b'/');
@@ -1818,37 +1843,42 @@ fn file_url_segments_to_pathbuf(segments: str::Split<char>) -> Result<PathBuf, (
 }
 }
 
 
 #[cfg(windows)]
 #[cfg(windows)]
-fn file_url_segments_to_pathbuf(segments: str::Split<char>) -> Result<PathBuf, ()> {
-    file_url_segments_to_pathbuf_windows(segments)
+fn file_url_segments_to_pathbuf(host: Option<&str>, segments: str::Split<char>) -> Result<PathBuf, ()> {
+    file_url_segments_to_pathbuf_windows(host, segments)
 }
 }
 
 
 // Build this unconditionally to alleviate https://github.com/servo/rust-url/issues/102
 // Build this unconditionally to alleviate https://github.com/servo/rust-url/issues/102
 #[cfg_attr(not(windows), allow(dead_code))]
 #[cfg_attr(not(windows), allow(dead_code))]
-fn file_url_segments_to_pathbuf_windows(mut segments: str::Split<char>) -> Result<PathBuf, ()> {
-    let first = segments.next().ok_or(())?;
+fn file_url_segments_to_pathbuf_windows(host: Option<&str>, mut segments: str::Split<char>) -> Result<PathBuf, ()> {
 
 
-    let mut string = match first.len() {
-        2 => {
-            if !first.starts_with(parser::ascii_alpha) || first.as_bytes()[1] != b':' {
-                return Err(())
-            }
+    let mut string = if let Some(host) = host {
+        r"\\".to_owned() + host
+    } else {
+        let first = segments.next().ok_or(())?;
 
 
-            first.to_owned()
-        },
+        match first.len() {
+            2 => {
+                if !first.starts_with(parser::ascii_alpha) || first.as_bytes()[1] != b':' {
+                    return Err(())
+                }
 
 
-        4 => {
-            if !first.starts_with(parser::ascii_alpha) {
-                return Err(())
-            }
-            let bytes = first.as_bytes();
-            if bytes[1] != b'%' || bytes[2] != b'3' || (bytes[3] != b'a' && bytes[3] != b'A') {
-                return Err(())
-            }
+                first.to_owned()
+            },
 
 
-            first[0..1].to_owned() + ":"
-        },
+            4 => {
+                if !first.starts_with(parser::ascii_alpha) {
+                    return Err(())
+                }
+                let bytes = first.as_bytes();
+                if bytes[1] != b'%' || bytes[2] != b'3' || (bytes[3] != b'a' && bytes[3] != b'A') {
+                    return Err(())
+                }
 
 
-        _ => return Err(()),
+                first[0..1].to_owned() + ":"
+            },
+
+            _ => return Err(()),
+        }
     };
     };
 
 
     for segment in segments {
     for segment in segments {

+ 27 - 0
tests/unit.rs

@@ -414,3 +414,30 @@ fn test_origin_hash() {
     assert_ne!(hash(&opaque_origin), hash(&same_opaque_origin));
     assert_ne!(hash(&opaque_origin), hash(&same_opaque_origin));
     assert_ne!(hash(&opaque_origin), hash(&other_opaque_origin));
     assert_ne!(hash(&opaque_origin), hash(&other_opaque_origin));
 }
 }
+
+#[test]
+fn test_windows_unc_path() {
+    if !cfg!(windows) {
+        return
+    }
+
+    let url = Url::from_file_path(Path::new(r"\\host\share\path\file.txt")).unwrap();
+    assert_eq!(url.as_str(), "file://host/share/path/file.txt");
+
+    let url = Url::from_file_path(Path::new(r"\\höst\share\path\file.txt")).unwrap();
+    assert_eq!(url.as_str(), "file://xn--hst-sna/share/path/file.txt");
+
+    let url = Url::from_file_path(Path::new(r"\\192.168.0.1\share\path\file.txt")).unwrap();
+    assert_eq!(url.host(), Some(Host::Ipv4(Ipv4Addr::new(192, 168, 0, 1))));
+
+    let path = url.to_file_path().unwrap();
+    assert_eq!(path.to_str(), Some(r"\\192.168.0.1\share\path\file.txt"));
+
+    // Another way to write these:
+    let url = Url::from_file_path(Path::new(r"\\?\UNC\host\share\path\file.txt")).unwrap();
+    assert_eq!(url.as_str(), "file://host/share/path/file.txt");
+
+    // Paths starting with "\\.\" (Local Device Paths) are intentionally not supported.
+    let url = Url::from_file_path(Path::new(r"\\.\some\path\file.txt"));
+    assert!(url.is_err());
+}