places/util.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use crate::error::{Error, Result};
use std::path::{Path, PathBuf};
use url::Url;
/// Equivalent to `&s[..max_len.min(s.len())]`, but handles the case where
/// `s.is_char_boundary(max_len)` is false (which would otherwise panic).
pub fn slice_up_to(s: &str, max_len: usize) -> &str {
if max_len >= s.len() {
return s;
}
let mut idx = max_len;
while !s.is_char_boundary(idx) {
idx -= 1;
}
&s[..idx]
}
/// `Path` is basically just a `str` with no validation, and so in practice it
/// could contain a file URL. Rusqlite takes advantage of this a bit, and says
/// `AsRef<Path>` but really means "anything sqlite can take as an argument".
///
/// Swift loves using file urls (the only support it has for file manipulation
/// is through file urls), so it's handy to support them if possible.
fn unurl_path(p: impl AsRef<Path>) -> PathBuf {
p.as_ref()
.to_str()
.and_then(|s| Url::parse(s).ok())
.and_then(|u| {
if u.scheme() == "file" {
u.to_file_path().ok()
} else {
None
}
})
.unwrap_or_else(|| p.as_ref().to_owned())
}
/// If `p` is a file URL, return it, otherwise try and make it one.
///
/// Errors if `p` is a relative non-url path, or if it's a URL path
/// that's isn't a `file:` URL.
pub fn ensure_url_path(p: impl AsRef<Path>) -> Result<Url> {
if let Some(u) = p.as_ref().to_str().and_then(|s| Url::parse(s).ok()) {
if u.scheme() == "file" {
Ok(u)
} else {
Err(Error::IllegalDatabasePath(p.as_ref().to_owned()))
}
} else {
let p = p.as_ref();
let u = Url::from_file_path(p).map_err(|_| Error::IllegalDatabasePath(p.to_owned()))?;
Ok(u)
}
}
/// As best as possible, convert `p` into an absolute path, resolving
/// all symlinks along the way.
///
/// If `p` is a file url, it's converted to a path before this.
pub fn normalize_path(p: impl AsRef<Path>) -> Result<PathBuf> {
let path = unurl_path(p);
if let Ok(canonical) = path.canonicalize() {
return Ok(canonical);
}
// It probably doesn't exist yet. This is an error, although it seems to
// work on some systems.
//
// We resolve this by trying to canonicalize the parent directory, and
// appending the requested file name onto that. If we can't canonicalize
// the parent, we return an error.
//
// Also, we return errors if the path ends in "..", if there is no
// parent directory, etc.
let file_name = path
.file_name()
.ok_or_else(|| Error::IllegalDatabasePath(path.clone()))?;
let parent = path
.parent()
.ok_or_else(|| Error::IllegalDatabasePath(path.clone()))?;
let mut canonical = parent.canonicalize()?;
canonical.push(file_name);
Ok(canonical)
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_slice_up_to() {
assert_eq!(slice_up_to("abcde", 4), "abcd");
assert_eq!(slice_up_to("abcde", 5), "abcde");
assert_eq!(slice_up_to("abcde", 6), "abcde");
let s = "abcd😀";
assert_eq!(s.len(), 8);
assert_eq!(slice_up_to(s, 4), "abcd");
assert_eq!(slice_up_to(s, 5), "abcd");
assert_eq!(slice_up_to(s, 6), "abcd");
assert_eq!(slice_up_to(s, 7), "abcd");
assert_eq!(slice_up_to(s, 8), s);
}
#[test]
fn test_unurl_path() {
assert_eq!(
unurl_path("file:///foo%20bar/baz").to_string_lossy(),
"/foo bar/baz"
);
assert_eq!(unurl_path("/foo bar/baz").to_string_lossy(), "/foo bar/baz");
assert_eq!(unurl_path("../baz").to_string_lossy(), "../baz");
}
#[test]
fn test_ensure_url() {
assert_eq!(
ensure_url_path("file:///foo%20bar/baz").unwrap().as_str(),
"file:///foo%20bar/baz"
);
assert_eq!(
ensure_url_path("/foo bar/baz").unwrap().as_str(),
"file:///foo%20bar/baz"
);
assert!(ensure_url_path("bar").is_err());
assert!(ensure_url_path("http://www.not-a-file.com").is_err());
}
}