Repository: thecoshman/http (language: rust)

Issue:
MD6?
Hi, I wonder why you chose MD6 instead of a better researched, and much more widely used cryptographic hash function like BLAKE2b?


Discussion / hints:

Fix patch:
diff --git a/Cargo.toml b/Cargo.toml
index fedadc5..9448797 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -9,7 +9,7 @@ categories = ["network-programming", "web-programming::http-server"]
 license = "MIT"
 build = "build.rs"
 # Remember to also update in appveyor.yml
-version = "1.12.2"
+version = "1.12.3"
 # Remember to also update in http.md
 authors = ["thecoshman <rust@thecoshman.com>",
            "nabijaczleweli <nabijaczleweli@nabijaczleweli.xyz>",
@@ -27,10 +27,10 @@ mime_guess = "1.8"
 tabwriter = "1.1"
 itertools = "0.8"
 lazysort = "0.2"
-brotli2 = "0.3"
 unicase = "2.4"
 walkdir = "2.2"
 base64 = "0.10"
+blake3 = "1.3"
 flate2 = "1.0"
 rfsapi = "0.1"
 xml-rs = "0.8"
@@ -41,7 +41,6 @@ serde = "0.9"
 clap = "2.33"
 rand = "0.7"
 time = "0.1"
-md6 = "2.0"
 
 [dependencies.trivial_colours]
 version = "0.3"
@@ -51,6 +50,11 @@ default-features = false
 version = "0.1"
 default-features = false
 
+[dependencies.brotli]
+version = "3.3"
+default-features = false
+features = ["std"]
+
 [dependencies.iron]
 version = "0.6"
 features = ["hyper-native-tls"]
diff --git a/README.md b/README.md
index df1cb58..1bca9b0 100644
--- a/README.md
+++ b/README.md
@@ -28,6 +28,8 @@ See [the manpage](http.md) for full list.
 
 ## Installation
 
+### From Cargo
+
 If you have `cargo` installed (you're a Rust developer) all you need to do is:
 
 ```sh
@@ -36,6 +38,12 @@ cargo install https
 
 Which will install `http` and `httplz` (identical, disable one or another if they clash) in the folder where all other binaries go.
 
+### From AUR
+
+As [provided](//github.com/thecoshman/http/issues/142) by [@cyqsimon](//github.com/cyqsimon): https://aur.archlinux.org/packages/httplz
+
+### From an installer
+
 If, however, you're not a Rust developer, but you have `sh`-like shell, you can use an installer (works on Windows and Linux):
 
 ```sh
diff --git a/appveyor.yml b/appveyor.yml
index c58b9f6..88d7d4d 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,4 +1,4 @@
-version: 1.12.2-{build}
+version: 1.12.3-{build}
 
 skip_tags: false
 
@@ -28,28 +28,28 @@ install:
   - curl -SL https://win.rustup.rs/ -oC:\rustup-init.exe
   - C:\rustup-init.exe -y --default-host="x86_64-pc-windows-gnu"
   -
-  - curl -SOL https://cdn.rawgit.com/nabijaczleweli/880a4b42368c610dc5de041d73bbea58/raw/7f3a23efe680d06934e6c0d7c9fbe92216da0682/EnVar_plugin.zip
+  - curl -SOL https://gistcdn.githack.com/nabijaczleweli/880a4b42368c610dc5de041d73bbea58/raw/7f3a23efe680d06934e6c0d7c9fbe92216da0682/EnVar_plugin.zip
   - unzip -j EnVar_plugin.zip Plugins/amd64-unicode/EnVar.dll -d C:\msys64\mingw64\share\nsis\Plugins\unicode
 
 build: off
 build_script:
   - git submodule update --init --recursive
   - cargo build --verbose --release
-  - cp target\release\http.exe http-v1.12.2.exe
-  - strip --strip-all --remove-section=.comment --remove-section=.note http-v1.12.2.exe
-  - makensis -DHTTP_VERSION=v1.12.2 install.nsi
+  - cp target\release\http.exe http-v1.12.3.exe
+  - strip --strip-all --remove-section=.comment --remove-section=.note http-v1.12.3.exe
+  - makensis -DHTTP_VERSION=v1.12.3 install.nsi
 
 test: off
 test_script:
   - cargo test --verbose --release
 
 artifacts:
-  - path: http-v1.12.2.exe
-  - path: http v1.12.2 installer.exe
+  - path: http-v1.12.3.exe
+  - path: http v1.12.3 installer.exe
 
 deploy:
   provider: GitHub
-  artifact: /http.*v1.12.2.*\.exe/
+  artifact: /http.*v1.12.3.*\.exe/
   auth_token:
     secure: ZTXvCrv9y01s7Hd60w8W7NaouPnPoaw9YJt9WhWQ2Pep8HLvCikt9Exjkz8SGP9P
   on:
diff --git a/src/main.rs b/src/main.rs
index 9ebcbce..3459835 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -10,10 +10,11 @@ extern crate mime_guess;
 extern crate itertools;
 extern crate tabwriter;
 extern crate lazysort;
-extern crate brotli2;
 extern crate unicase;
 extern crate walkdir;
 extern crate base64;
+extern crate blake3;
+extern crate brotli;
 extern crate flate2;
 extern crate rfsapi;
 #[cfg(target_os = "windows")]
@@ -30,7 +31,6 @@ extern crate iron;
 extern crate libc;
 extern crate rand;
 extern crate time;
-extern crate md6;
 extern crate xml;
 
 mod error;
diff --git a/src/ops/mod.rs b/src/ops/mod.rs
index fea7642..1a712e2 100644
--- a/src/ops/mod.rs
+++ b/src/ops/mod.rs
@@ -1,4 +1,4 @@
-use md6;
+use blake3;
 use std::fmt;
 use serde_json;
 use std::ffi::OsStr;
@@ -27,7 +27,7 @@ use iron::mime::{Mime, SubLevel as MimeSubLevel, TopLevel as MimeTopLevel};
 use std::io::{self, ErrorKind as IoErrorKind, SeekFrom, Write, Error as IoError, Read, Seek};
 use iron::{headers, status, method, mime, IronResult, Listening, Response, TypeMap, Request, Handler, Iron};
 use self::super::util::{WwwAuthenticate, DisplayThree, CommaList, Spaces, Dav, url_path, file_hash, is_symlink, encode_str, encode_file, file_length,
-                        hash_string, html_response, file_binary, client_mobile, percent_decode, escape_specials, file_icon_suffix, is_actually_file, is_descendant_of,
+                        html_response, file_binary, client_mobile, percent_decode, escape_specials, file_icon_suffix, is_actually_file, is_descendant_of,
                         response_encoding, detect_file_as_dir, encoding_extension, file_time_modified, file_time_modified_p, get_raw_fs_metadata,
                         human_readable_size, encode_tail_if_trimmed, is_nonexistent_descendant_of, USER_AGENT, ERROR_HTML, INDEX_EXTENSIONS, MIN_ENCODING_GAIN,
                         MAX_ENCODING_SIZE, MIN_ENCODING_SIZE, DAV_LEVEL_1_METHODS, DIRECTORY_LISTING_HTML, MOBILE_DIRECTORY_LISTING_HTML,
@@ -110,7 +110,7 @@ pub use self::bandwidth::{LimitBandwidthMiddleware, SimpleChain};
 
 
 // TODO: ideally this String here would be Encoding instead but hyper is bad
-type CacheT<Cnt> = HashMap<([u8; 32], String), Cnt>;
+type CacheT<Cnt> = HashMap<(blake3::Hash, String), Cnt>;
 
 pub struct HttpHandler {
     pub hosted_directory: (String, PathBuf),
@@ -614,7 +614,7 @@ impl HttpHandler {
                 }
             }
 
-            let mut resp_p = self.encoded_temp_dir.as_ref().unwrap().1.join(hash_string(&cache_key.0));
+            let mut resp_p = self.encoded_temp_dir.as_ref().unwrap().1.join(cache_key.0.to_hex().as_str());
             match (req_p.extension(), encoding_extension(&encoding)) {
                 (Some(ext), Some(enc)) => resp_p.set_extension(format!("{}.{}", ext.to_str().unwrap_or("ext"), enc)),
                 (Some(ext), None) => resp_p.set_extension(format!("{}.{}", ext.to_str().unwrap_or("ext"), encoding)),
@@ -772,7 +772,7 @@ impl HttpHandler {
             format!("<a href=\"/{up_path}{up_path_slash}\" class=\"list entry top\"><span class=\"back_arrow_icon\">Parent directory</span></a> \
                      <a href=\"/{up_path}{up_path_slash}\" class=\"list entry bottom\"><span class=\"marker\">@</span>\
                        <span class=\"datetime\">{} UTC</span></a>",
-                    file_time_modified_p(req_p.parent().expect("Failed to get requested directory's parent directory"))
+                    file_time_modified_p(req_p.parent().unwrap_or(&req_p))
                         .strftime("%F %T")
                         .unwrap(),
                     up_path = escape_specials(slash_idx.map(|i| &rel_noslash[0..i]).unwrap_or("")),
@@ -877,7 +877,7 @@ impl HttpHandler {
                          <td><a href=\"/{up_path}{up_path_slash}\" class=\"datetime\">{}</a></td> \
                          <td><a href=\"/{up_path}{up_path_slash}\">&nbsp;</a></td> \
                          <td><a href=\"/{up_path}{up_path_slash}\">&nbsp;</a></td></tr>",
-                    file_time_modified_p(req_p.parent().expect("Failed to get requested directory's parent directory")).strftime("%F %T").unwrap(),
+                    file_time_modified_p(req_p.parent().unwrap_or(&req_p)).strftime("%F %T").unwrap(),
                     up_path = escape_specials(slash_idx.map(|i| &rel_noslash[0..i]).unwrap_or("")),
                     up_path_slash = if slash_idx.is_some() { "/" } else { "" })
         };
@@ -1197,8 +1197,7 @@ impl HttpHandler {
 
     fn handle_generated_response_encoding(&self, req: &mut Request, st: status::Status, resp: String) -> IronResult<Response> {
         if let Some(encoding) = req.headers.get_mut::<headers::AcceptEncoding>().and_then(|es| response_encoding(&mut **es)) {
-            let mut cache_key = ([0u8; 32], encoding.to_string());
-            md6::hash(256, resp.as_bytes(), &mut cache_key.0).expect("Failed to hash generated response");
+            let cache_key = (blake3::hash(resp.as_bytes()), encoding.to_string());
 
             {
                 if let Some(enc_resp) = self.cache_gen.read().expect("Generated file cache read lock poisoned").get(&cache_key) {
@@ -1556,7 +1555,7 @@ pub fn generate_tls_data(temp_dir: &(String, PathBuf)) -> Result<((String, PathB
 
 /// Generate random username:password auth credentials.
 pub fn generate_auth_data() -> String {
-    static PASSWORD_SET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789~!@#$%^&*()_+`-=[]{}|;',./<>?";
+    const PASSWORD_SET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789~!@#$%^&*()_+`-=[]{}|;',./<>?";
 
 
     let mut rng = thread_rng();
diff --git a/src/util/content_encoding.rs b/src/util/content_encoding.rs
index 82a9229..9ba569c 100644
--- a/src/util/content_encoding.rs
+++ b/src/util/content_encoding.rs
@@ -1,16 +1,16 @@
-use brotli2::{CompressMode as BrotliCompressMode, CompressParams as BrotliCompressParams};
+use brotli::enc::backward_references::{BrotliEncoderParams, BrotliEncoderMode};
+use brotli::enc::BrotliCompress as brotli_compress;
 use flate2::write::{DeflateEncoder, GzEncoder};
 use flate2::Compression as Flate2Compression;
 use iron::headers::{QualityItem, Encoding};
 use bzip2::Compression as BzCompression;
-use brotli2::write::BrotliEncoder;
 use std::collections::BTreeSet;
 use bzip2::write::BzEncoder;
 use std::io::{self, Write};
 use unicase::UniCase;
 use std::path::Path;
 use std::fs::File;
-use md6::Md6;
+use blake3;
 
 
 lazy_static! {
@@ -25,16 +25,21 @@ lazy_static! {
         let raw = include_str!("../../assets/encoding_blacklist");
         raw.split('\n').map(str::trim).filter(|s| !s.is_empty() && !s.starts_with('#')).map(UniCase::new).collect()
     };
+
+    pub static ref BROTLI_PARAMS: BrotliEncoderParams = BrotliEncoderParams {
+        mode: BrotliEncoderMode::BROTLI_MODE_TEXT,
+        ..Default::default()
+    };
 }
 
 /// The minimal size at which to encode filesystem files.
-pub static MIN_ENCODING_SIZE: u64 = 1024;
+pub const MIN_ENCODING_SIZE: u64 = 1024;
 
 /// The maximal size at which to encode filesystem files.
-pub static MAX_ENCODING_SIZE: u64 = 100 * 1024 * 1024;
+pub const MAX_ENCODING_SIZE: u64 = 100 * 1024 * 1024;
 
 /// The minimal size gain at which to preserve encoded filesystem files.
-pub static MIN_ENCODING_GAIN: f64 = 1.1;
+pub const MIN_ENCODING_GAIN: f64 = 1.1;
 
 
 /// Find best supported encoding to use, or `None` for identity.
@@ -46,7 +51,7 @@ pub fn response_encoding(requested: &mut [QualityItem<Encoding>]) -> Option<Enco
 /// Encode a string slice using a specified encoding or `None` if encoding failed or is not recognised.
 pub fn encode_str(dt: &str, enc: &Encoding) -> Option<Vec<u8>> {
     type EncodeT = fn(&str) -> Option<Vec<u8>>;
-    static STR_ENCODING_FNS: &[EncodeT] = &[encode_str_gzip, encode_str_deflate, encode_str_brotli, encode_str_bzip2];
+    const STR_ENCODING_FNS: &[EncodeT] = &[encode_str_gzip, encode_str_deflate, encode_str_brotli, encode_str_bzip2];
 
     encoding_idx(enc).and_then(|fi| STR_ENCODING_FNS[fi](dt))
 }
@@ -55,7 +60,7 @@ pub fn encode_str(dt: &str, enc: &Encoding) -> Option<Vec<u8>> {
 /// `false` if encoding failed, is not recognised or an I/O error occurred.
 pub fn encode_file(p: &Path, op: &Path, enc: &Encoding) -> bool {
     type EncodeT = fn(File, File) -> bool;
-    static FILE_ENCODING_FNS: &[EncodeT] = &[encode_file_gzip, encode_file_deflate, encode_file_brotli, encode_file_bzip2];
+    const FILE_ENCODING_FNS: &[EncodeT] = &[encode_file_gzip, encode_file_deflate, encode_file_brotli, encode_file_bzip2];
 
     encoding_idx(enc)
         .map(|fi| {
@@ -69,39 +74,16 @@ pub fn encode_file(p: &Path, op: &Path, enc: &Encoding) -> bool {
 
 /// Encoding extension to use for encoded files, for example "gz" for gzip, or `None` if the encoding is not recognised.
 pub fn encoding_extension(enc: &Encoding) -> Option<&'static str> {
-    static ENCODING_EXTS: &[&str] = &["gz", "dflt", "br", "bz2"];
+    const ENCODING_EXTS: &[&str] = &["gz", "dflt", "br", "bz2"];
 
     encoding_idx(enc).map(|ei| ENCODING_EXTS[ei])
 }
 
-/// Return the 256-bit MD6 hash of the file denoted by the specified path.
-pub fn file_hash(p: &Path) -> [u8; 32] {
-    let mut ctx = Md6::new(256).unwrap();
-    let mut res = [0; 32];
-
+/// Return the 256-bit BLAKE3 hash of the file denoted by the specified path.
+pub fn file_hash(p: &Path) -> blake3::Hash {
+    let mut ctx = blake3::Hasher::new();
     io::copy(&mut File::open(p).unwrap(), &mut ctx).unwrap();
-    ctx.finalise(&mut res);
-
-    res
-}
-
-/// Create a hash string out of its raw bytes.
-///
-/// # Examples
-///
-/// ```
-/// use https::util::hash_string;
-/// assert_eq!(hash_string(&[0x99, 0xAA, 0xBB, 0xCC]), "99AABBCC".to_string());
-/// assert_eq!(hash_string(&[0x09, 0x0A]), "090A".to_string());
-/// ```
-pub fn hash_string(bytes: &[u8]) -> String {
-    use std::fmt::Write;
-
-    let mut result = String::with_capacity(bytes.len() * 2);
-    for b in bytes {
-        write!(result, "{:02X}", b).unwrap();
-    }
-    result
+    ctx.finalize()
 }
 
 
@@ -142,9 +124,13 @@ macro_rules! encode_fn {
 
 encode_fn!(encode_str_gzip, encode_file_gzip, GzEncoder, Flate2Compression::default());
 encode_fn!(encode_str_deflate, encode_file_deflate, DeflateEncoder, Flate2Compression::default());
-encode_fn!(encode_str_brotli,
-           encode_file_brotli,
-           BrotliEncoder,
-           0,
-           |into| BrotliEncoder::from_params(into, BrotliCompressParams::new().mode(BrotliCompressMode::Text)));
 encode_fn!(encode_str_bzip2, encode_file_bzip2, BzEncoder, BzCompression::Default);
+
+fn encode_str_brotli(dt: &str) -> Option<Vec<u8>> {
+    let mut ret = Vec::new();
+    brotli_compress(&mut dt.as_bytes(), &mut ret, &BROTLI_PARAMS).ok().map(|_| ret)
+}
+
+fn encode_file_brotli(mut inf: File, mut outf: File) -> bool {
+    brotli_compress(&mut inf, &mut outf, &BROTLI_PARAMS).is_ok()
+}
diff --git a/src/util/mod.rs b/src/util/mod.rs
index b8c2706..4d6c154 100644
--- a/src/util/mod.rs
+++ b/src/util/mod.rs
@@ -31,13 +31,13 @@ pub use self::content_encoding::*;
 
 
 /// The generic HTML page to use as response to errors.
-pub static ERROR_HTML: &str = include_str!("../../assets/error.html");
+pub const ERROR_HTML: &str = include_str!("../../assets/error.html");
 
 /// The HTML page to use as template for a requested directory's listing.
-pub static DIRECTORY_LISTING_HTML: &str = include_str!("../../assets/directory_listing.html");
+pub const DIRECTORY_LISTING_HTML: &str = include_str!("../../assets/directory_listing.html");
 
 /// The HTML page to use as template for a requested directory's listing for mobile devices.
-pub static MOBILE_DIRECTORY_LISTING_HTML: &str = include_str!("../../assets/directory_listing_mobile.html");
+pub const MOBILE_DIRECTORY_LISTING_HTML: &str = include_str!("../../assets/directory_listing_mobile.html");
 
 lazy_static! {
     /// Collection of data to be injected into generated responses.
@@ -98,16 +98,16 @@ lazy_static! {
 }
 
 /// The port to start scanning from if no ports were given.
-pub static PORT_SCAN_LOWEST: u16 = 8000;
+pub const PORT_SCAN_LOWEST: u16 = 8000;
 
 /// The port to end scanning at if no ports were given.
-pub static PORT_SCAN_HIGHEST: u16 = 9999;
+pub const PORT_SCAN_HIGHEST: u16 = 9999;
 
 /// The app name and version to use with User-Agent or Server response header.
-pub static USER_AGENT: &str = concat!("http/", env!("CARGO_PKG_VERSION"));
+pub const USER_AGENT: &str = concat!("http/", env!("CARGO_PKG_VERSION"));
 
 /// Index file extensions to look for if `-i` was not specified and strippable extensions to look for if `-x` was specified.
-pub static INDEX_EXTENSIONS: &[&str] = &["html", "htm", "shtml"];
+pub const INDEX_EXTENSIONS: &[&str] = &["html", "htm", "shtml"];
 
 
 /// The [WWW-Authenticate header](https://tools.ietf.org/html/rfc7235#section-4.1), without parsing.
diff --git a/src/util/webdav.rs b/src/util/webdav.rs
index 243707a..9f1b995 100644
--- a/src/util/webdav.rs
+++ b/src/util/webdav.rs
@@ -24,45 +24,45 @@ lazy_static! {
 }
 
 /// Prefix and namespace URI for generic WebDAV elements
-pub static WEBDAV_XML_NAMESPACE_DAV: (&str, &str) = ("D", "DAV:");
+pub const WEBDAV_XML_NAMESPACE_DAV: (&str, &str) = ("D", "DAV:");
 
 /// Prefix and namespace URI for elements specific to Windows clients
-pub static WEBDAV_XML_NAMESPACE_MICROSOFT: (&str, &str) = ("Z", "urn:schemas-microsoft-com:");
+pub const WEBDAV_XML_NAMESPACE_MICROSOFT: (&str, &str) = ("Z", "urn:schemas-microsoft-com:");
 
 /// Prefix and namespace URI for elements for Apache emulation
-pub static WEBDAV_XML_NAMESPACE_APACHE: (&str, &str) = ("A", "http://apache.org/dav/props/");
+pub const WEBDAV_XML_NAMESPACE_APACHE: (&str, &str) = ("A", "http://apache.org/dav/props/");
 
 /// All first-class-recognised prefix/namespace pairs
 ///
 /// `WEBDAV_XML_NAMESPACE_DAV` needs to be the first here
-pub static WEBDAV_XML_NAMESPACES: &[&(&str, &str)] = &[&WEBDAV_XML_NAMESPACE_DAV, &WEBDAV_XML_NAMESPACE_MICROSOFT, &WEBDAV_XML_NAMESPACE_APACHE];
+pub const WEBDAV_XML_NAMESPACES: &[&(&str, &str)] = &[&WEBDAV_XML_NAMESPACE_DAV, &WEBDAV_XML_NAMESPACE_MICROSOFT, &WEBDAV_XML_NAMESPACE_APACHE];
 
 /// Properties to return on empty body or [`<allprop />`](https://tools.ietf.org/html/rfc2518#section-12.14.1)
 /// for non-Windows clients
 ///
 /// Based on https://github.com/miquels/webdav-handler-rs/blob/02433c1acfccd848a7de26889f6857cbad559076/src/handle_props.rs#L52
-pub static WEBDAV_ALLPROP_PROPERTIES_NON_WINDOWS: &[&[XmlName]] = &[&[xml_name!(WEBDAV_XML_NAMESPACE_DAV, "creationdate"),
-                                                                      xml_name!(WEBDAV_XML_NAMESPACE_DAV, "getcontentlength"),
-                                                                      xml_name!(WEBDAV_XML_NAMESPACE_DAV, "getcontenttype"),
-                                                                      xml_name!(WEBDAV_XML_NAMESPACE_DAV, "getlastmodified"),
-                                                                      xml_name!(WEBDAV_XML_NAMESPACE_DAV, "resourcetype")]];
+pub const WEBDAV_ALLPROP_PROPERTIES_NON_WINDOWS: &[&[XmlName]] = &[&[xml_name!(WEBDAV_XML_NAMESPACE_DAV, "creationdate"),
+                                                                     xml_name!(WEBDAV_XML_NAMESPACE_DAV, "getcontentlength"),
+                                                                     xml_name!(WEBDAV_XML_NAMESPACE_DAV, "getcontenttype"),
+                                                                     xml_name!(WEBDAV_XML_NAMESPACE_DAV, "getlastmodified"),
+                                                                     xml_name!(WEBDAV_XML_NAMESPACE_DAV, "resourcetype")]];
 
 /// Properties to return on empty body or [`<allprop />`](https://tools.ietf.org/html/rfc2518#section-12.14.1)
 /// for Windows clients
 ///
 /// Based on https://github.com/miquels/webdav-handler-rs/blob/02433c1acfccd848a7de26889f6857cbad559076/src/handle_props.rs#L66
-pub static WEBDAV_ALLPROP_PROPERTIES_WINDOWS: &[&[XmlName]] = &[&WEBDAV_ALLPROP_PROPERTIES_NON_WINDOWS[0],
-                                                                &[xml_name!(WEBDAV_XML_NAMESPACE_MICROSOFT, "Win32CreationTime"),
-                                                                  xml_name!(WEBDAV_XML_NAMESPACE_MICROSOFT, "Win32FileAttributes"),
-                                                                  xml_name!(WEBDAV_XML_NAMESPACE_MICROSOFT, "Win32LastAccessTime"),
-                                                                  xml_name!(WEBDAV_XML_NAMESPACE_MICROSOFT, "Win32LastModifiedTime")]];
+pub const WEBDAV_ALLPROP_PROPERTIES_WINDOWS: &[&[XmlName]] = &[&WEBDAV_ALLPROP_PROPERTIES_NON_WINDOWS[0],
+                                                               &[xml_name!(WEBDAV_XML_NAMESPACE_MICROSOFT, "Win32CreationTime"),
+                                                                 xml_name!(WEBDAV_XML_NAMESPACE_MICROSOFT, "Win32FileAttributes"),
+                                                                 xml_name!(WEBDAV_XML_NAMESPACE_MICROSOFT, "Win32LastAccessTime"),
+                                                                 xml_name!(WEBDAV_XML_NAMESPACE_MICROSOFT, "Win32LastModifiedTime")]];
 
 /// Properties listed for a [`<propname />`](https://tools.ietf.org/html/rfc2518#section-12.14.2) request
 ///
 /// Based on https://github.com/miquels/webdav-handler-rs/blob/02433c1acfccd848a7de26889f6857cbad559076/src/handle_props.rs#L34
-pub static WEBDAV_PROPNAME_PROPERTIES: &[&[XmlName]] = &[&WEBDAV_ALLPROP_PROPERTIES_NON_WINDOWS[0],
-                                                         &[xml_name!(WEBDAV_XML_NAMESPACE_APACHE, "executable"),
-                                                           xml_name!(WEBDAV_XML_NAMESPACE_MICROSOFT, "Win32LastAccessTime")]];
+pub const WEBDAV_PROPNAME_PROPERTIES: &[&[XmlName]] = &[&WEBDAV_ALLPROP_PROPERTIES_NON_WINDOWS[0],
+                                                        &[xml_name!(WEBDAV_XML_NAMESPACE_APACHE, "executable"),
+                                                          xml_name!(WEBDAV_XML_NAMESPACE_MICROSOFT, "Win32LastAccessTime")]];
 
 
 


Test patch: