Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
kardeiz committed Oct 24, 2018
0 parents commit c0af533
Show file tree
Hide file tree
Showing 6 changed files with 396 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/target
**/*.rs.bk
99 changes: 99 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
authors = ["Jacob Brown <[email protected]>"]
name = "sanitize-filename"
version = "0.1.0"
keywords = ["filename", "sanitizer"]
license = "MIT"
readme = "README.md"
repository = "https://github.com/kardeiz/sanitize-filename"
description = "A simple filename sanitizer, based on Node's sanitize-filename"

[dependencies]
lazy_static = "1.1.0"
regex = "1.0.5"
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# sanitize-filename

A basic filename sanitizer, based on Node's [sanitize-filename](https://www.npmjs.com/package/sanitize-filename).

Use like:

```rust
extern crate sanitize_filename;

fn main() {
println!("{}", sanitize_filename::sanitize("some-user-defined/../../../string"));
// prints some-user-defined......string
}
```

You can also configure a few basic options:

```rust
let options = sanitize_filename::Options {
truncate: true, // true by default, truncates to 255 characters
windows: true, // default value depends on the OS, removes reserved names like `con` from start of strings on Windows
replacement: "" // str to replace sanitized chars/strings
};

let sanitized = sanitize_filename::sanitize_with_options("some-user-defined/../../../string", options);
```

Also provides a basic command line binary. Use like:

```bash
cargo install sanitize-filename
sanitize-filename my_filename.txt
```

```
Pass a file name to clean to the program (also reads STDIN)
FLAGS:
-r, --replace <r> Replacement characters
--windows, --no-windows Whether to handle filenames for Windows
--truncate, --no-truncate Whether to truncate file names to 255 characters
```
180 changes: 180 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
extern crate regex;

#[macro_use]
extern crate lazy_static;

use regex::{Regex, RegexBuilder};

lazy_static! {
static ref ILLEGAL_RE: Regex = Regex::new(r#"[/\?<>\\:\*\|":]"#).unwrap();
static ref CONTROL_RE: Regex = Regex::new(r#"[\x00-\x1f\x80-\x9f]"#).unwrap();
static ref RESERVED_RE: Regex = Regex::new(r#"^\.+$"#).unwrap();
static ref WINDOWS_RESERVED_RE: Regex = RegexBuilder::new(r#"(?i)^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\..*)?$"#)
.case_insensitive(true)
.build()
.unwrap();
static ref WINDOWS_TRAILING_RE: Regex = Regex::new(r#"^\.+$"#).unwrap();
}

#[derive(Clone)]
pub struct Options<'a> {
pub windows: bool,
pub truncate: bool,
pub replacement: &'a str
}

impl<'a> Default for Options<'a> {
fn default() -> Self {
Options {
windows: cfg!(windows),
truncate: true,
replacement: ""
}
}
}

pub fn sanitize<S: AsRef<str>>(name: S) -> String {
sanitize_with_options(name, Options::default())
}

pub fn sanitize_with_options<S: AsRef<str>>(name: S, options: Options) -> String {

let Options { windows, truncate, replacement } = options;
let name = name.as_ref();

let name = ILLEGAL_RE.replace_all(&name, replacement);
let name = CONTROL_RE.replace_all(&name, replacement);
let name = RESERVED_RE.replace(&name, replacement);

let collect = |name: ::std::borrow::Cow<str>| {
if truncate {
name.chars().take(255).collect()
} else {
String::from(name)
}
};

if windows {
let name = WINDOWS_RESERVED_RE.replace(&name, replacement);
let name = WINDOWS_TRAILING_RE.replace(&name, replacement);
collect(name)
} else {
collect(name)
}

}

#[cfg(test)]
mod tests {


// From https://github.com/parshap/node-sanitize-filename/blob/master/test.js
static NAMES: &'static [&'static str] = &[
"the quick brown fox jumped over the lazy dog",
"résumé",
"hello\u{0000}world",
"hello\nworld",
"semi;colon.js",
";leading-semi.js",
"slash\\.js",
"slash/.js",
"col:on.js",
"star*.js",
"question?.js",
"quote\".js",
"singlequote'.js",
"brack<e>ts.js",
"p|pes.js",
"plus+.js",
"'five and six<seven'.js",
" space at front",
"space at end ",
".period",
"period.",
"relative/path/to/some/dir",
"/abs/path/to/some/dir",
"~/.\u{0000}notssh/authorized_keys",
"",
"h?w",
"h/w",
"h*w",
".",
"..",
"./",
"../",
"/..",
"/../",
"*.|.",
"./",
"./foobar",
"../foobar",
"../../foobar",
"./././foobar",
"|*.what",
"LPT9.asdf"
];

static NAMES_CLEANED: &'static [&'static str] = &[
"the quick brown fox jumped over the lazy dog",
"résumé",
"helloworld",
"helloworld",
"semi;colon.js",
";leading-semi.js",
"slash.js",
"slash.js",
"colon.js",
"star.js",
"question.js",
"quote.js",
"singlequote'.js",
"brackets.js",
"ppes.js",
"plus+.js",
"'five and sixseven'.js",
" space at front",
"space at end ",
".period",
"period.",
"relativepathtosomedir",
"abspathtosomedir",
"~.notsshauthorized_keys",
"",
"hw",
"hw",
"hw",
"",
"",
"",
"",
"",
"",
"",
"",
".foobar",
"..foobar",
"....foobar",
"...foobar",
".what",
""
];

#[test]
fn it_works() {

let options = super::Options {
windows: true,
truncate: true,
replacement: ""
};

for (idx, name) in NAMES.iter().enumerate() {
assert_eq!(super::sanitize_with_options(name, options.clone()), NAMES_CLEANED[idx]);
}

let long = ::std::iter::repeat('a').take(300).collect::<String>();
let shorter = ::std::iter::repeat('a').take(255).collect::<String>();
assert_eq!(super::sanitize_with_options(long, options.clone()), shorter);

}
}
Loading

0 comments on commit c0af533

Please sign in to comment.