diff options
-rw-r--r-- | Cargo.lock | 116 | ||||
-rw-r--r-- | Cargo.toml | 6 | ||||
-rw-r--r-- | src/archive.rs | 43 | ||||
-rw-r--r-- | src/args.rs | 10 | ||||
-rw-r--r-- | src/auth.rs | 87 | ||||
-rw-r--r-- | src/errors.rs | 17 | ||||
-rw-r--r-- | src/file_upload.rs | 120 | ||||
-rw-r--r-- | src/listing.rs | 90 | ||||
-rw-r--r-- | src/main.rs | 88 | ||||
-rw-r--r-- | src/renderer.rs | 221 | ||||
-rw-r--r-- | src/themes.rs | 15 | ||||
-rw-r--r-- | tests/auth.rs | 70 |
12 files changed, 615 insertions, 268 deletions
@@ -11,7 +11,7 @@ dependencies = [ "crossbeam-channel 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -36,7 +36,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "actix 0.7.9 (registry+https://github.com/rust-lang/crates.io-index)", "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)", "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", @@ -69,7 +69,7 @@ dependencies = [ "encoding 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "flate2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", "futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "h2 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", "http 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", @@ -87,7 +87,7 @@ dependencies = [ "percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", "serde_urlencoded 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", "sha1 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -408,18 +408,25 @@ dependencies = [ ] [[package]] +name = "cookie" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "cookie_store" -version = "0.5.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cookie 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cookie 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "publicsuffix 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", "try_from 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -637,7 +644,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -743,7 +750,7 @@ dependencies = [ [[package]] name = "futures" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -751,7 +758,7 @@ name = "futures-cpupool" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", "num_cpus 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -792,7 +799,7 @@ dependencies = [ "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", "http 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", "indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -861,7 +868,7 @@ version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", "futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "h2 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", "http 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", @@ -888,7 +895,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", "hyper 0.12.28 (registry+https://github.com/rust-lang/crates.io-index)", "native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1132,7 +1139,7 @@ dependencies = [ "chrono-humanize 0.0.11 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", "hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "htmlescape 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "libflate 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1142,16 +1149,16 @@ dependencies = [ "percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "port_check 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "pretty_assertions 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", - "reqwest 0.9.16 (registry+https://github.com/rust-lang/crates.io-index)", + "reqwest 0.9.17 (registry+https://github.com/rust-lang/crates.io-index)", "rstest 0.2.2 (git+https://github.com/la10736/rstest.git)", "select 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)", "sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "simplelog 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", "structopt 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", "strum 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", "strum_macros 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", - "tar 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)", + "tar 0.4.25 (registry+https://github.com/rust-lang/crates.io-index)", "yansi 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1691,16 +1698,16 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.9.16" +version = "0.9.17" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "cookie 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "cookie_store 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "cookie 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cookie_store 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "encoding_rs 0.8.17 (registry+https://github.com/rust-lang/crates.io-index)", "flate2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", "http 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", "hyper 0.12.28 (registry+https://github.com/rust-lang/crates.io-index)", "hyper-tls 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1708,7 +1715,7 @@ dependencies = [ "mime 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)", "mime_guess 2.0.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", "native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", "serde_urlencoded 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1744,7 +1751,7 @@ dependencies = [ [[package]] name = "rstest" version = "0.2.2" -source = "git+https://github.com/la10736/rstest.git#9f2ac13853e876ffef76f49443e7f2eca17cba92" +source = "git+https://github.com/la10736/rstest.git#7c7211e5d6575ab4c43cd2e35370e94c5167c8b1" dependencies = [ "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2 0.4.29 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1851,7 +1858,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "serde" -version = "1.0.90" +version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "serde_derive 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1874,7 +1881,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1884,7 +1891,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "dtoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)", "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1977,7 +1984,7 @@ dependencies = [ "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "phf_shared 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)", "precomputed-hash 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)", "string_cache_codegen 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "string_cache_shared 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2081,7 +2088,7 @@ dependencies = [ [[package]] name = "tar" -version = "0.4.24" +version = "0.4.25" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "filetime 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2166,7 +2173,7 @@ version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", "mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)", "num_cpus 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-codec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2190,7 +2197,7 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2199,7 +2206,7 @@ name = "tokio-current-thread" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-executor 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2209,7 +2216,7 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2217,7 +2224,7 @@ name = "tokio-fs" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-threadpool 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2228,7 +2235,7 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2238,7 +2245,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2255,7 +2262,7 @@ name = "tokio-signal" version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", "mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)", "mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2272,7 +2279,7 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2281,7 +2288,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2296,7 +2303,7 @@ dependencies = [ "crossbeam-deque 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "num_cpus 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2310,7 +2317,7 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-executor 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2329,7 +2336,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-codec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2343,7 +2350,7 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2359,7 +2366,7 @@ name = "tower-service" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2374,7 +2381,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", "idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2397,7 +2404,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", "idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2420,7 +2427,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", "ipconfig 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2612,7 +2619,7 @@ name = "want" version = "0.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "try-lock 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2739,7 +2746,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" "checksum constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8ff012e225ce166d4422e0e78419d901719760f62ae2b7969ca6b564d1b54a9e" "checksum cookie 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1465f8134efa296b4c19db34d909637cb2bf0f7aaf21299e23e18fa29ac557cf" -"checksum cookie_store 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b0d2f2ecb21dce00e2453268370312978af9b8024020c7a37ae2cc6dbbe64685" +"checksum cookie 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "888604f00b3db336d2af898ec3c1d5d0ddf5e6d462220f2ededc33a87ac4bbd5" +"checksum cookie_store 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "46750b3f362965f197996c4448e4a0935e791bf7d6631bfce9ee0af3d24c919c" "checksum core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "25b9e03f145fd4f2bf705e07b900cd41fc636598fe5dc452fd0db1441c3f496d" "checksum core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e7ca8a5221364ef15ce201e8ed2f609fc312682a8f4e0e3d4aa5879764e0fa3b" "checksum crc 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d663548de7f5cca343f1e0a48d14dcfb0e9eb4e079ec58883b7251539fa10aeb" @@ -2779,7 +2787,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" "checksum futf 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7c9c1ce3fa9336301af935ab852c437817d14cd33690446569392e65170aac3b" -"checksum futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)" = "62941eff9507c8177d448bd83a44d9b9760856e184081d8cd79ba9f03dd24981" +"checksum futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)" = "a2037ec1c6c1c4f79557762eab1f7eae1f64f6cb418ace90fae88f0942b60139" "checksum futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "ab90cde24b3319636588d0c35fe03b1333857621051837ed769faefb4c2162e4" "checksum generic-array 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3c0f28c2f5bfb5960175af447a2da7c18900693738343dc896ffbcabd9839592" "checksum globset 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ef4feaabe24a0a658fd9cf4a9acf6ed284f045c77df0f49020ba3245cfb7b454" @@ -2884,7 +2892,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum regex 1.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "8f0a0bcab2fd7d1d7c54fa9eae6f43eddeb9ce2e7352f8518a814a4f65d60c58" "checksum regex-syntax 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "dcfd8681eebe297b81d98498869d4aae052137651ad7b96822f09ceb690d0a96" "checksum remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3488ba1b9a2084d38645c4c08276a1752dcbf2c7130d74f1569681ad5d2799c5" -"checksum reqwest 0.9.16 (registry+https://github.com/rust-lang/crates.io-index)" = "ddcfd2c13c6af0f9c45a1086be3b9c68af79e4430b42790759e2d34cce2a6c60" +"checksum reqwest 0.9.17 (registry+https://github.com/rust-lang/crates.io-index)" = "e57803405f8ea0eb041c1567dac36127e0c8caa1251c843cb03d43fd767b3d50" "checksum resolv-conf 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b263b4aa1b5de9ffc0054a2386f96992058bb6870aab516f8cdeb8a667d56dcb" "checksum ring 0.13.5 (registry+https://github.com/rust-lang/crates.io-index)" = "2c4db68a2e35f3497146b7e4563df7d4773a2433230c5e4b448328e31740458a" "checksum rstest 0.2.2 (git+https://github.com/la10736/rstest.git)" = "<none>" @@ -2902,7 +2910,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum select 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7004292887d0a030e29abda3ae1b63a577c96a17e25d74eaa1952503e6c1c946" "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" -"checksum serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)" = "aa5f7c20820475babd2c077c3ab5f8c77a31c15e16ea38687b4c02d3e48680f4" +"checksum serde 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)" = "a72e9b96fa45ce22a4bc23da3858dfccfd60acd28a25bcd328a98fdd6bea43fd" "checksum serde_derive 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)" = "58fc82bec244f168b23d1963b45c8bf5726e9a15a9d146a067f9081aeed2de79" "checksum serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)" = "5a23aa71d4a4d43fdbfaac00eff68ba8a06a51759a89ac3304323e800c4dd40d" "checksum serde_urlencoded 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "642dd69105886af2efd227f75a520ec9b44a820d65bc133a9131f7d229fd165a" @@ -2929,7 +2937,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum syn 0.15.33 (registry+https://github.com/rust-lang/crates.io-index)" = "ec52cd796e5f01d0067225a5392e70084acc4c0013fa71d55166d38a8b307836" "checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" "checksum synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73687139bf99285483c96ac0add482c3776528beac1d97d444f6e91f203a2015" -"checksum tar 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)" = "2dd071a2604c8fd8902ca42908856821ed7a06e3cd846f84d75873c978dec7fb" +"checksum tar 0.4.25 (registry+https://github.com/rust-lang/crates.io-index)" = "7201214ded95b34e3bc00c9557b6dcec34fd1af428d343143f5db67c661762f0" "checksum tempfile 3.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "b86c784c88d98c801132806dadd3819ed29d8600836c4088e855cdf3e178ed8a" "checksum tendril 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1b72f8e2f5b73b65c315b1a70c730f24b9d7a25f39e98de8acbe2bb795caea" "checksum term 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "edd106a334b7657c10b7c540a0106114feadeb4dc314513e97df481d5d966f42" @@ -36,10 +36,10 @@ structopt = "0.2.15" chrono = "0.4.6" chrono-humanize = "0.0.11" maud = { version = "0.20.0", features = ["actix-web"] } -serde = { version = "1.0.90", features = ["derive"] } -tar = "0.4.24" +serde = { version = "1.0.91", features = ["derive"] } +tar = "0.4.25" bytes = "0.4.12" -futures = "0.1.26" +futures = "0.1.27" libflate = "0.1.21" failure = "0.1.5" log = "0.4.6" diff --git a/src/archive.rs b/src/archive.rs index a76446a..02300c5 100644 --- a/src/archive.rs +++ b/src/archive.rs @@ -7,7 +7,7 @@ use std::path::PathBuf; use strum_macros::{Display, EnumIter, EnumString}; use tar::Builder; -use crate::errors::{ContextualError}; +use crate::errors::ContextualError; /// Available compression methods #[derive(Deserialize, Clone, EnumIter, EnumString, Display)] @@ -62,17 +62,11 @@ fn tgz_compress(dir: &PathBuf, skip_symlinks: bool) -> Result<(String, Bytes), C let mut tgz_data = Bytes::new(); let tar_data = tar(src_dir, directory.to_string(), skip_symlinks).map_err(|e| { - ContextualError::ArchiveCreationError( - "tarball".to_string(), - Box::new(e), - ) + ContextualError::ArchiveCreationError("tarball".to_string(), Box::new(e)) })?; let gz_data = gzip(&tar_data).map_err(|e| { - ContextualError::ArchiveCreationError( - "GZIP archive".to_string(), - Box::new(e), - ) + ContextualError::ArchiveCreationError("GZIP archive".to_string(), Box::new(e)) })?; tgz_data.extend_from_slice(&gz_data); @@ -115,10 +109,7 @@ fn tar( })?; let tar_content = tar_builder.into_inner().map_err(|e| { - ContextualError::IOError( - "Failed to finish writing the TAR archive".to_string(), - e, - ) + ContextualError::IOError("Failed to finish writing the TAR archive".to_string(), e) })?; Ok(tar_content) @@ -126,24 +117,14 @@ fn tar( /// Compresses a stream of bytes using the GZIP algorithm, and returns the resulting stream fn gzip(mut data: &[u8]) -> Result<Vec<u8>, ContextualError> { - let mut encoder = Encoder::new(Vec::new()).map_err(|e| { - ContextualError::IOError( - "Failed to create GZIP encoder".to_string(), - e, - ) - })?; - io::copy(&mut data, &mut encoder).map_err(|e| { - ContextualError::IOError( - "Failed to write GZIP data".to_string(), - e, - ) - })?; - let data = encoder.finish().into_result().map_err(|e| { - ContextualError::IOError( - "Failed to write GZIP trailer".to_string(), - e, - ) - })?; + let mut encoder = Encoder::new(Vec::new()) + .map_err(|e| ContextualError::IOError("Failed to create GZIP encoder".to_string(), e))?; + io::copy(&mut data, &mut encoder) + .map_err(|e| ContextualError::IOError("Failed to write GZIP data".to_string(), e))?; + let data = encoder + .finish() + .into_result() + .map_err(|e| ContextualError::IOError("Failed to write GZIP trailer".to_string(), e))?; Ok(data) } diff --git a/src/args.rs b/src/args.rs index 39efe7e..11357c2 100644 --- a/src/args.rs +++ b/src/args.rs @@ -3,7 +3,7 @@ use std::path::PathBuf; use structopt::StructOpt; use crate::auth; -use crate::errors::{ContextualError}; +use crate::errors::ContextualError; use crate::themes; /// Possible characters for random routes @@ -40,6 +40,7 @@ struct CLIArgs { /// Set authentication. Currently supported formats: /// username:password, username:sha256:hash, username:sha512:hash + /// (e.g. joe:123, joe:sha256:a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3) #[structopt(short = "a", long = "auth", parse(try_from_str = "parse_auth"))] auth: Vec<auth::RequiredAuth>, @@ -98,15 +99,13 @@ fn parse_auth(src: &str) -> Result<auth::RequiredAuth, ContextualError> { let hash_bin = if let Ok(hash_bin) = hex::decode(hash_hex) { hash_bin } else { - return Err(ContextualError::InvalidPasswordHash) + return Err(ContextualError::InvalidPasswordHash); }; match second_part { "sha256" => auth::RequiredAuthPassword::Sha256(hash_bin.to_owned()), "sha512" => auth::RequiredAuthPassword::Sha512(hash_bin.to_owned()), - _ => { - return Err(ContextualError::InvalidHashMethod(second_part.to_owned())) - }, + _ => return Err(ContextualError::InvalidHashMethod(second_part.to_owned())), } } else { // To make it Windows-compatible, the password needs to be shorter than 255 characters. @@ -163,6 +162,7 @@ pub fn parse_args() -> crate::MiniserveConfig { } } +#[rustfmt::skip] #[cfg(test)] mod tests { use super::*; diff --git a/src/auth.rs b/src/auth.rs index da5f4a9..3828265 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -1,9 +1,10 @@ -use actix_web::http::header; +use actix_web::http::{header, StatusCode}; use actix_web::middleware::{Middleware, Response}; use actix_web::{HttpRequest, HttpResponse, Result}; use sha2::{Digest, Sha256, Sha512}; -use crate::errors::{ContextualError}; +use crate::errors::{self, ContextualError}; +use crate::renderer; pub struct Auth; @@ -36,10 +37,7 @@ pub fn parse_basic_auth( let basic_removed = authorization_header .to_str() .map_err(|e| { - ContextualError::ParseError( - "HTTP authentication header".to_string(), - e.to_string(), - ) + ContextualError::ParseError("HTTP authentication header".to_string(), e.to_string()) })? .replace("Basic ", ""); let decoded = base64::decode(&basic_removed).map_err(ContextualError::Base64DecodeError)?; @@ -109,7 +107,14 @@ impl Middleware<crate::MiniserveConfig> for Auth { Err(err) => { let auth_err = ContextualError::HTTPAuthenticationError(Box::new(err)); return Ok(Response::Done( - HttpResponse::BadRequest().body(auth_err.to_string()), + HttpResponse::BadRequest().body( + build_unauthorized_response( + &req, + auth_err, + true, + StatusCode::BAD_REQUEST, + ), + ), )); } }; @@ -118,20 +123,68 @@ impl Middleware<crate::MiniserveConfig> for Auth { return Ok(Response::Done(resp)); } - let new_resp = HttpResponse::Unauthorized().finish(); - return Ok(Response::Done(new_resp)); + return Ok(Response::Done( + HttpResponse::Unauthorized().body( + build_unauthorized_response( + &req, + ContextualError::InvalidHTTPCredentials, + true, + StatusCode::UNAUTHORIZED, + ) + ) + )); } - let new_resp = HttpResponse::Unauthorized() - .header( - header::WWW_AUTHENTICATE, - header::HeaderValue::from_static("Basic realm=\"miniserve\""), - ) - .finish(); - Ok(Response::Done(new_resp)) + Ok(Response::Done( + HttpResponse::Unauthorized() + .header( + header::WWW_AUTHENTICATE, + header::HeaderValue::from_static("Basic realm=\"miniserve\""), + ) + .body(build_unauthorized_response( + &req, + ContextualError::InvalidHTTPCredentials, + true, + StatusCode::UNAUTHORIZED, + )) + )) } } +/// Builds the unauthorized response body +/// The reason why log_error_chain is optional is to handle cases where the auth pop-up appears and when the user clicks Cancel. +/// In those case, we do not log the error to the terminal since it does not really matter. +fn build_unauthorized_response( + req: &HttpRequest<crate::MiniserveConfig>, + error: ContextualError, + log_error_chain: bool, + error_code: StatusCode, +) -> String { + let error = ContextualError::HTTPAuthenticationError(Box::new(error)); + + if log_error_chain { + errors::log_error_chain(error.to_string()); + } + let return_path = match &req.state().random_route { + Some(random_route) => format!("/{}", random_route), + None => "/".to_string(), + }; + + renderer::render_error( + &error.to_string(), + error_code, + &return_path, + None, + None, + req.state().default_color_scheme, + req.state().default_color_scheme, + false, + false, + ) + .into_string() +} + +#[rustfmt::skip] #[cfg(test)] mod tests { use super::*; @@ -182,7 +235,7 @@ mod tests { case(true, "obi", "hello there", "obi", "hello there", "sha256"), case(false, "obi", "hello there", "obi", "hi!", "sha256"), case(true, "obi", "hello there", "obi", "hello there", "sha512"), - case(false, "obi", "hello there", "obi", "hi!", "sha512"), + case(false, "obi", "hello there", "obi", "hi!", "sha512") )] fn test_single_auth( should_pass: bool, diff --git a/src/errors.rs b/src/errors.rs index b8af25d..2878e37 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -32,7 +32,10 @@ pub enum ContextualError { InvalidAuthFormat, /// This error might occure if the hash method is neither sha256 nor sha512 - #[fail(display = "{} is not a valid hashing method. Expected sha256 or sha512", _0)] + #[fail( + display = "{} is not a valid hashing method. Expected sha256 or sha512", + _0 + )] InvalidHashMethod(String), /// This error might occur if the HTTP auth hash password is not a valid hex code @@ -64,6 +67,18 @@ pub enum ContextualError { _0 )] HTTPAuthenticationError(Box<ContextualError>), + + /// This error might occur when the HTTP credentials are not correct + #[fail(display = "Invalid credentials for HTTP authentication")] + InvalidHTTPCredentials, + + /// This error might occur when an HTTP request is invalid + #[fail(display = "Invalid HTTP request\ncaused by: {}", _0)] + InvalidHTTPRequestError(String), + + /// This error might occur when trying to access a page that does not exist + #[fail(display = "Route {} could not be found", _0)] + RouteNotFoundError(String), } pub fn log_error_chain(description: String) { diff --git a/src/file_upload.rs b/src/file_upload.rs index 7f9cede..537c90c 100644 --- a/src/file_upload.rs +++ b/src/file_upload.rs @@ -1,9 +1,9 @@ use actix_web::{ - dev, http::header, multipart, FromRequest, FutureResponse, HttpMessage, HttpRequest, - HttpResponse, Query, + dev, + http::{header, StatusCode}, + multipart, FutureResponse, HttpMessage, HttpRequest, HttpResponse, }; use futures::{future, future::FutureResult, Future, Stream}; -use serde::Deserialize; use std::{ fs, io::Write, @@ -11,13 +11,9 @@ use std::{ }; use crate::errors::{self, ContextualError}; +use crate::listing::{self, SortingMethod, SortingOrder}; use crate::renderer; - -/// Query parameters -#[derive(Debug, Deserialize)] -struct QueryParameters { - path: PathBuf, -} +use crate::themes::ColorScheme; /// Create future to save file. fn save_file( @@ -35,7 +31,7 @@ fn save_file( Ok(file) => file, Err(e) => { return Box::new(future::err(ContextualError::IOError( - format!("Failed to create file in {}", file_path.display()), + format!("Failed to create {}", file_path.display()), e, ))); } @@ -121,37 +117,75 @@ fn handle_multipart( /// server root directory. Any path which will go outside of this directory is considered /// invalid. /// This method returns future. -pub fn upload_file(req: &HttpRequest<crate::MiniserveConfig>) -> FutureResponse<HttpResponse> { +pub fn upload_file( + req: &HttpRequest<crate::MiniserveConfig>, + default_color_scheme: ColorScheme, +) -> FutureResponse<HttpResponse> { let return_path = if let Some(header) = req.headers().get(header::REFERER) { header.to_str().unwrap_or("/").to_owned() } else { "/".to_string() }; - let app_root_dir = if let Ok(dir) = req.state().path.canonicalize() { - dir - } else { - return Box::new(create_error_response("Internal server error", &return_path)); - }; - let path = match Query::<QueryParameters>::extract(req) { - Ok(query) => { - if let Ok(stripped_path) = query.path.strip_prefix(Component::RootDir) { - stripped_path.to_owned() - } else { - query.path.clone() - } + + let query_params = listing::extract_query_parameters(req); + let color_scheme = query_params.theme.unwrap_or(default_color_scheme); + let upload_path = match query_params.path.clone() { + Some(path) => match path.strip_prefix(Component::RootDir) { + Ok(stripped_path) => stripped_path.to_owned(), + Err(_) => path.clone(), + }, + None => { + let err = ContextualError::InvalidHTTPRequestError( + "Missing query parameter 'path'".to_string(), + ); + return Box::new(create_error_response( + &err.to_string(), + StatusCode::BAD_REQUEST, + &return_path, + query_params.sort, + query_params.order, + color_scheme, + default_color_scheme, + )); } - Err(_) => { + }; + + let app_root_dir = match req.state().path.canonicalize() { + Ok(dir) => dir, + Err(e) => { + let err = ContextualError::IOError( + "Failed to resolve path served by miniserve".to_string(), + e, + ); return Box::new(create_error_response( - "Unspecified parameter path", + &err.to_string(), + StatusCode::INTERNAL_SERVER_ERROR, &return_path, - )) + query_params.sort, + query_params.order, + color_scheme, + default_color_scheme, + )); } }; // If the target path is under the app root directory, save the file. - let target_dir = match &app_root_dir.clone().join(path).canonicalize() { + let target_dir = match &app_root_dir.clone().join(upload_path).canonicalize() { Ok(path) if path.starts_with(&app_root_dir) => path.clone(), - _ => return Box::new(create_error_response("Invalid path", &return_path)), + _ => { + let err = ContextualError::InvalidHTTPRequestError( + "Invalid value for 'path' parameter".to_string(), + ); + return Box::new(create_error_response( + &err.to_string(), + StatusCode::BAD_REQUEST, + &return_path, + query_params.sort, + query_params.order, + color_scheme, + default_color_scheme, + )); + } }; let overwrite_files = req.state().overwrite_files; Box::new( @@ -166,7 +200,15 @@ pub fn upload_file(req: &HttpRequest<crate::MiniserveConfig>) -> FutureResponse< .header(header::LOCATION, return_path.to_string()) .finish(), ), - Err(e) => create_error_response(&e.to_string(), &return_path), + Err(e) => create_error_response( + &e.to_string(), + StatusCode::INTERNAL_SERVER_ERROR, + &return_path, + query_params.sort, + query_params.order, + color_scheme, + default_color_scheme, + ), }), ) } @@ -174,12 +216,30 @@ pub fn upload_file(req: &HttpRequest<crate::MiniserveConfig>) -> FutureResponse< /// Convenience method for creating response errors, if file upload fails. fn create_error_response( description: &str, + error_code: StatusCode, return_path: &str, + sorting_method: Option<SortingMethod>, + sorting_order: Option<SortingOrder>, + color_scheme: ColorScheme, + default_color_scheme: ColorScheme, ) -> FutureResult<HttpResponse, actix_web::error::Error> { errors::log_error_chain(description.to_string()); future::ok( HttpResponse::BadRequest() .content_type("text/html; charset=utf-8") - .body(renderer::render_error(description, return_path).into_string()), + .body( + renderer::render_error( + description, + error_code, + return_path, + sorting_method, + sorting_order, + color_scheme, + default_color_scheme, + true, + true, + ) + .into_string(), + ), ) } diff --git a/src/listing.rs b/src/listing.rs index 87fd8a8..49802bc 100644 --- a/src/listing.rs +++ b/src/listing.rs @@ -1,3 +1,4 @@ +use actix_web::http::StatusCode; use actix_web::{fs, http, Body, FromRequest, HttpRequest, HttpResponse, Query, Result}; use bytesize::ByteSize; use futures::stream::once; @@ -5,26 +6,27 @@ use htmlescape::encode_minimal as escape_html_entity; use percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET}; use serde::Deserialize; use std::io; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::time::SystemTime; use strum_macros::{Display, EnumString}; -use crate::archive; -use crate::errors; +use crate::archive::{self, CompressionMethod}; +use crate::errors::{self, ContextualError}; use crate::renderer; -use crate::themes; +use crate::themes::ColorScheme; /// Query parameters #[derive(Deserialize)] -struct QueryParameters { - sort: Option<SortingMethod>, - order: Option<SortingOrder>, - download: Option<archive::CompressionMethod>, - theme: Option<themes::ColorScheme>, +pub struct QueryParameters { + pub path: Option<PathBuf>, + pub sort: Option<SortingMethod>, + pub order: Option<SortingOrder>, + pub theme: Option<ColorScheme>, + download: Option<CompressionMethod>, } /// Available sorting methods -#[derive(Deserialize, Clone, EnumString, Display)] +#[derive(Deserialize, Clone, EnumString, Display, Copy)] #[serde(rename_all = "snake_case")] #[strum(serialize_all = "snake_case")] pub enum SortingMethod { @@ -39,7 +41,7 @@ pub enum SortingMethod { } /// Available sorting orders -#[derive(Deserialize, Clone, EnumString, Display)] +#[derive(Deserialize, Clone, EnumString, Display, Copy)] pub enum SortingOrder { /// Ascending order #[serde(alias = "asc")] @@ -130,7 +132,7 @@ pub fn directory_listing<S>( skip_symlinks: bool, file_upload: bool, random_route: Option<String>, - default_color_scheme: themes::ColorScheme, + default_color_scheme: ColorScheme, upload_route: String, ) -> Result<HttpResponse, io::Error> { let serve_path = req.path(); @@ -143,23 +145,13 @@ pub fn directory_listing<S>( Err(_) => base.to_path_buf(), }; - let (sort_method, sort_order, download, color_scheme) = - if let Ok(query) = Query::<QueryParameters>::extract(req) { - ( - query.sort.clone(), - query.order.clone(), - query.download.clone(), - query.theme.clone(), - ) - } else { - (None, None, None, None) - }; + let query_params = extract_query_parameters(req); let mut entries: Vec<Entry> = Vec::new(); for entry in dir.path.read_dir()? { if dir.is_visible(&entry) { - let entry = entry.unwrap(); + let entry = entry?; let p = match entry.path().strip_prefix(&dir.path) { Ok(p) => base.join(p), Err(_) => continue, @@ -211,7 +203,7 @@ pub fn directory_listing<S>( } } - if let Some(sorting_method) = &sort_method { + if let Some(sorting_method) = query_params.sort { match sorting_method { SortingMethod::Name => entries .sort_by(|e1, e2| alphanumeric_sort::compare_str(e1.name.clone(), e2.name.clone())), @@ -235,15 +227,15 @@ pub fn directory_listing<S>( entries.sort_by(|e1, e2| alphanumeric_sort::compare_str(e1.name.clone(), e2.name.clone())) } - if let Some(sorting_order) = &sort_order { + if let Some(sorting_order) = query_params.order { if let SortingOrder::Descending = sorting_order { entries.reverse() } } - let color_scheme = color_scheme.unwrap_or_else(|| default_color_scheme.clone()); + let color_scheme = query_params.theme.unwrap_or(default_color_scheme); - if let Some(compression_method) = &download { + if let Some(compression_method) = &query_params.download { log::info!( "Creating an archive ({extension}) of {path}...", extension = compression_method.extension(), @@ -267,7 +259,20 @@ pub fn directory_listing<S>( errors::log_error_chain(err.to_string()); Ok(HttpResponse::Ok() .status(http::StatusCode::INTERNAL_SERVER_ERROR) - .body(renderer::render_error(&err.to_string(), serve_path).into_string())) + .body( + renderer::render_error( + &err.to_string(), + StatusCode::INTERNAL_SERVER_ERROR, + serve_path, + query_params.sort, + query_params.order, + color_scheme, + default_color_scheme, + false, + true, + ) + .into_string(), + )) } } } else { @@ -279,8 +284,8 @@ pub fn directory_listing<S>( entries, is_root, page_parent, - sort_method, - sort_order, + query_params.sort, + query_params.order, default_color_scheme, color_scheme, file_upload, @@ -291,3 +296,26 @@ pub fn directory_listing<S>( )) } } + +pub fn extract_query_parameters<S>(req: &HttpRequest<S>) -> QueryParameters { + match Query::<QueryParameters>::extract(req) { + Ok(query) => QueryParameters { + sort: query.sort, + order: query.order, + download: query.download.clone(), + theme: query.theme, + path: query.path.clone(), + }, + Err(e) => { + let err = ContextualError::ParseError("query parameters".to_string(), e.to_string()); + errors::log_error_chain(err.to_string()); + QueryParameters { + sort: None, + order: None, + download: None, + theme: None, + path: None, + } + } + } +} diff --git a/src/main.rs b/src/main.rs index f227be9..f26369a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ #![feature(proc_macro_hygiene)] -use actix_web::http::Method; -use actix_web::{fs, middleware, server, App}; +use actix_web::http::{Method, StatusCode}; +use actix_web::{fs, middleware, server, App, HttpRequest, HttpResponse}; use clap::crate_version; use simplelog::{Config, LevelFilter, TermLogger}; use std::io::{self, Write}; @@ -19,7 +19,7 @@ mod listing; mod renderer; mod themes; -use crate::errors::{ContextualError}; +use crate::errors::ContextualError; #[derive(Clone)] /// Configuration of the Miniserve application @@ -83,12 +83,7 @@ fn run() -> Result<(), ContextualError> { && miniserve_config .path .symlink_metadata() - .map_err(|e| - ContextualError::IOError( - "Failed to retrieve symlink's metadata".to_string(), - e, - ) - )? + .map_err(|e| ContextualError::IOError("Failed to retrieve symlink's metadata".to_string(), e))? .file_type() .is_symlink() { @@ -117,10 +112,7 @@ fn run() -> Result<(), ContextualError> { .collect::<Vec<String>>(); let canon_path = miniserve_config.path.canonicalize().map_err(|e| { - ContextualError::IOError( - "Failed to resolve path to be served".to_string(), - e, - ) + ContextualError::IOError("Failed to resolve path to be served".to_string(), e) })?; let path_string = canon_path.to_string_lossy(); @@ -135,20 +127,14 @@ fn run() -> Result<(), ContextualError> { " Invoke with -h|--help to see options or invoke as `miniserve .` to hide this advice." ); print!("Starting server in "); - io::stdout().flush().map_err(|e| { - ContextualError::IOError( - "Failed to write data".to_string(), - e, - ) - })?; + io::stdout() + .flush() + .map_err(|e| ContextualError::IOError("Failed to write data".to_string(), e))?; for c in "3… 2… 1… \n".chars() { print!("{}", c); - io::stdout().flush().map_err(|e| { - ContextualError::IOError( - "Failed to write data".to_string(), - e, - ) - })?; + io::stdout() + .flush() + .map_err(|e| ContextualError::IOError("Failed to write data".to_string(), e))?; thread::sleep(Duration::from_millis(500)); } } @@ -210,12 +196,7 @@ fn run() -> Result<(), ContextualError> { .configure(configure_app) }) .bind(socket_addresses.as_slice()) - .map_err(|e| { - ContextualError::IOError( - "Failed to bind server".to_string(), - e, - ) - })? + .map_err(|e| ContextualError::IOError("Failed to bind server".to_string(), e))? .shutdown_timeout(0) .start(); @@ -239,7 +220,7 @@ fn configure_app(app: App<MiniserveConfig>) -> App<MiniserveConfig> { let path = &app.state().path; let no_symlinks = app.state().no_symlinks; let random_route = app.state().random_route.clone(); - let default_color_scheme = app.state().default_color_scheme.clone(); + let default_color_scheme = app.state().default_color_scheme; let file_upload = app.state().file_upload; upload_route = if let Some(random_route) = app.state().random_route.clone() { format!("/{}/upload", random_route) @@ -261,10 +242,11 @@ fn configure_app(app: App<MiniserveConfig>) -> App<MiniserveConfig> { no_symlinks, file_upload, random_route.clone(), - default_color_scheme.clone(), + default_color_scheme, u_r.clone(), ) - }), + }) + .default_handler(error_404), ) } }; @@ -274,18 +256,52 @@ fn configure_app(app: App<MiniserveConfig>) -> App<MiniserveConfig> { if let Some(s) = s { if app.state().file_upload { + let default_color_scheme = app.state().default_color_scheme; // Allow file upload - app.resource(&upload_route, |r| { - r.method(Method::POST).f(file_upload::upload_file) + app.resource(&upload_route, move |r| { + r.method(Method::POST) + .f(move |file| file_upload::upload_file(file, default_color_scheme)) }) // Handle directories .handler(&full_route, s) + .default_resource(|r| r.method(Method::GET).f(error_404)) } else { // Handle directories app.handler(&full_route, s) + .default_resource(|r| r.method(Method::GET).f(error_404)) } } else { // Handle single files app.resource(&full_route, |r| r.f(listing::file_handler)) + .default_resource(|r| r.method(Method::GET).f(error_404)) } } + +fn error_404(req: &HttpRequest<crate::MiniserveConfig>) -> Result<HttpResponse, io::Error> { + let err_404 = ContextualError::RouteNotFoundError(req.path().to_string()); + let default_color_scheme = req.state().default_color_scheme; + let return_address = match &req.state().random_route { + Some(random_route) => format!("/{}", random_route), + None => "/".to_string(), + }; + + let query_params = listing::extract_query_parameters(req); + let color_scheme = query_params.theme.unwrap_or(default_color_scheme); + + errors::log_error_chain(err_404.to_string()); + + Ok(actix_web::HttpResponse::NotFound().body( + renderer::render_error( + &err_404.to_string(), + StatusCode::NOT_FOUND, + &return_address, + query_params.sort, + query_params.order, + color_scheme, + default_color_scheme, + false, + true, + ) + .into_string(), + )) +} diff --git a/src/renderer.rs b/src/renderer.rs index 69224cf..d1e16ea 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -1,3 +1,4 @@ +use actix_web::http::StatusCode; use chrono::{DateTime, Duration, Utc}; use chrono_humanize::{Accuracy, HumanTime, Tense}; use maud::{html, Markup, PreEscaped, DOCTYPE}; @@ -23,8 +24,17 @@ pub fn page( upload_route: &str, current_dir: &str, ) -> Markup { + let upload_action = build_upload_action( + upload_route, + current_dir, + sort_method, + sort_order, + color_scheme, + default_color_scheme, + ); + html! { - (page_header(serve_path, &color_scheme, file_upload)) + (page_header(serve_path, color_scheme, file_upload, false)) body#drop-container { @if file_upload { div.drag-form { @@ -33,19 +43,19 @@ pub fn page( } } } - (color_scheme_selector(&sort_method, &sort_order, &color_scheme, &default_color_scheme, serve_path)) + (color_scheme_selector(sort_method, sort_order, color_scheme, default_color_scheme, serve_path)) div.container { span#top { } h1.title { "Index of " (serve_path) } div.toolbar { div.download { @for compression_method in CompressionMethod::iter() { - (archive_button(compression_method)) + (archive_button(compression_method, sort_method, sort_order, color_scheme, default_color_scheme)) } } @if file_upload { div.upload { - form id="file_submit" action={(upload_route) "?path=" (current_dir)} method="POST" enctype="multipart/form-data" { + form id="file_submit" action=(upload_action) method="POST" enctype="multipart/form-data" { p { "Select a file to upload or drag it anywhere into the window" } div { input#file-input type="file" name="file_to_upload" required="" {} @@ -57,9 +67,9 @@ pub fn page( } table { thead { - th { (build_link("name", "Name", &sort_method, &sort_order, &color_scheme, &default_color_scheme)) } - th { (build_link("size", "Size", &sort_method, &sort_order, &color_scheme, &default_color_scheme)) } - th { (build_link("date", "Last modification", &sort_method, &sort_order, &color_scheme, &default_color_scheme)) } + th { (build_link("name", "Name", sort_method, sort_order, color_scheme, default_color_scheme)) } + th { (build_link("size", "Size", sort_method, sort_order, color_scheme, default_color_scheme)) } + th { (build_link("date", "Last modification", sort_method, sort_order, color_scheme, default_color_scheme)) } } tbody { @if !is_root { @@ -67,7 +77,7 @@ pub fn page( tr { td colspan="3" { span.root-chevron { (chevron_left()) } - a.root href=(parametrized_link(&parent, &sort_method, &sort_order, &color_scheme, &default_color_scheme)) { + a.root href=(parametrized_link(&parent, sort_method, sort_order, color_scheme, default_color_scheme)) { "Parent directory" } } @@ -75,7 +85,7 @@ pub fn page( } } @for entry in entries { - (entry_row(entry, &sort_method, &sort_order, &color_scheme, &default_color_scheme)) + (entry_row(entry, sort_method, sort_order, color_scheme, default_color_scheme)) } } } @@ -87,12 +97,35 @@ pub fn page( } } +/// Build the action of the upload form +fn build_upload_action( + upload_route: &str, + current_dir: &str, + sort_method: Option<SortingMethod>, + sort_order: Option<SortingOrder>, + color_scheme: ColorScheme, + default_color_scheme: ColorScheme, +) -> String { + let mut upload_action = format!("{}?path={}", upload_route, current_dir); + if let Some(sorting_method) = sort_method { + upload_action = format!("{}&sort={}", upload_action, &sorting_method); + } + if let Some(sorting_order) = sort_order { + upload_action = format!("{}&order={}", upload_action, &sorting_order); + } + if color_scheme != default_color_scheme { + upload_action = format!("{}&theme={}", upload_action, color_scheme.to_slug()); + } + + upload_action +} + /// Partial: color scheme selector fn color_scheme_selector( - sort_method: &Option<SortingMethod>, - sort_order: &Option<SortingOrder>, - active_color_scheme: &ColorScheme, - default_color_scheme: &ColorScheme, + sort_method: Option<SortingMethod>, + sort_order: Option<SortingOrder>, + active_color_scheme: ColorScheme, + default_color_scheme: ColorScheme, serve_path: &str, ) -> Markup { html! { @@ -104,13 +137,13 @@ fn color_scheme_selector( } ul { @for color_scheme in ColorScheme::iter() { - @if active_color_scheme == &color_scheme { + @if active_color_scheme == color_scheme { li.active { - (color_scheme_link(&sort_method, &sort_order, &color_scheme, &default_color_scheme, serve_path)) + (color_scheme_link(sort_method, sort_order, color_scheme, default_color_scheme, serve_path)) } } @else { li { - (color_scheme_link(&sort_method, &sort_order, &color_scheme, &default_color_scheme, serve_path)) + (color_scheme_link(sort_method, sort_order, color_scheme, default_color_scheme, serve_path)) } } } @@ -123,18 +156,18 @@ fn color_scheme_selector( /// Partial: color scheme link fn color_scheme_link( - sort_method: &Option<SortingMethod>, - sort_order: &Option<SortingOrder>, - color_scheme: &ColorScheme, - default_color_scheme: &ColorScheme, + sort_method: Option<SortingMethod>, + sort_order: Option<SortingOrder>, + color_scheme: ColorScheme, + default_color_scheme: ColorScheme, serve_path: &str, ) -> Markup { let link = parametrized_link( serve_path, - &sort_method, - &sort_order, - &color_scheme, - &default_color_scheme, + sort_method, + sort_order, + color_scheme, + default_color_scheme, ); let title = format!("Switch to {} theme", color_scheme); @@ -152,8 +185,30 @@ fn color_scheme_link( } /// Partial: archive button -fn archive_button(compress_method: CompressionMethod) -> Markup { - let link = format!("?download={}", compress_method); +fn archive_button( + compress_method: CompressionMethod, + sort_method: Option<SortingMethod>, + sort_order: Option<SortingOrder>, + color_scheme: ColorScheme, + default_color_scheme: ColorScheme, +) -> Markup { + let link = + if sort_method.is_none() && sort_order.is_none() && color_scheme == default_color_scheme { + format!("?download={}", compress_method) + } else { + format!( + "{}&download={}", + parametrized_link( + "", + sort_method, + sort_order, + color_scheme, + default_color_scheme + ), + compress_method + ) + }; + let text = format!("Download .{}", compress_method.extension()); html! { @@ -166,10 +221,10 @@ fn archive_button(compress_method: CompressionMethod) -> Markup { /// If they are set, adds query parameters to links to keep them across pages fn parametrized_link( link: &str, - sort_method: &Option<SortingMethod>, - sort_order: &Option<SortingOrder>, - color_scheme: &ColorScheme, - default_color_scheme: &ColorScheme, + sort_method: Option<SortingMethod>, + sort_order: Option<SortingOrder>, + color_scheme: ColorScheme, + default_color_scheme: ColorScheme, ) -> String { if let Some(method) = sort_method { if let Some(order) = sort_order { @@ -194,10 +249,10 @@ fn parametrized_link( fn build_link( name: &str, title: &str, - sort_method: &Option<SortingMethod>, - sort_order: &Option<SortingOrder>, - color_scheme: &ColorScheme, - default_color_scheme: &ColorScheme, + sort_method: Option<SortingMethod>, + sort_order: Option<SortingOrder>, + color_scheme: ColorScheme, + default_color_scheme: ColorScheme, ) -> Markup { let mut link = format!("?sort={}&order=asc", name); let mut help = format!("Sort by {} in ascending order", name); @@ -232,17 +287,17 @@ fn build_link( /// Partial: row for an entry fn entry_row( entry: Entry, - sort_method: &Option<SortingMethod>, - sort_order: &Option<SortingOrder>, - color_scheme: &ColorScheme, - default_color_scheme: &ColorScheme, + sort_method: Option<SortingMethod>, + sort_order: Option<SortingOrder>, + color_scheme: ColorScheme, + default_color_scheme: ColorScheme, ) -> Markup { html! { tr { td { p { @if entry.is_dir() { - a.directory href=(parametrized_link(&entry.link, &sort_method, &sort_order, &color_scheme, &default_color_scheme)) { + a.directory href=(parametrized_link(&entry.link, sort_method, sort_order, color_scheme, default_color_scheme)) { (entry.name) "/" } } @else if entry.is_file() { @@ -257,7 +312,7 @@ fn entry_row( } } } @else if entry.is_symlink() { - a.symlink href=(parametrized_link(&entry.link, &sort_method, &sort_order, &color_scheme, &default_color_scheme)) { + a.symlink href=(parametrized_link(&entry.link, sort_method, sort_order, color_scheme, default_color_scheme)) { (entry.name) span.symlink-symbol { "⇢" } } } @@ -287,8 +342,8 @@ fn entry_row( } /// Partial: CSS -fn css(color_scheme: &ColorScheme) -> Markup { - let theme = color_scheme.clone().get_theme(); +fn css(color_scheme: ColorScheme) -> Markup { + let theme = color_scheme.get_theme(); let css = format!(" html {{ @@ -326,7 +381,7 @@ fn css(color_scheme: &ColorScheme) -> Markup { font-weight: bold; color: {directory_link_color}; }} - a.file, a.file:visited {{ + a.file, a.file:visited, .error-back, .error-back:visited {{ color: {file_link_color}; }} a.symlink, a.symlink:visited {{ @@ -582,6 +637,25 @@ fn css(color_scheme: &ColorScheme) -> Markup { width: 100%; text-align: center; }} + .error {{ + margin: 2rem; + }} + .error p {{ + margin: 1rem 0; + font-size: 0.9rem; + word-break: break-all; + }} + .error p:first-of-type {{ + font-size: 1.25rem; + color: {error_color}; + margin-bottom: 2rem; + }} + .error p:nth-of-type(2) {{ + font-weight: bold; + }} + .error-nav {{ + margin-top: 4rem; + }} @media (max-width: 760px) {{ nav {{ padding: 0 2.5rem; @@ -662,7 +736,8 @@ fn css(color_scheme: &ColorScheme) -> Markup { drag_border_color = theme.drag_border_color, drag_text_color = theme.drag_text_color, size_background_color = theme.size_background_color, - size_text_color = theme.size_text_color); + size_text_color = theme.size_text_color, + error_color = theme.error_color); (PreEscaped(css)) } @@ -687,15 +762,24 @@ fn chevron_down() -> Markup { } /// Partial: page header -fn page_header(serve_path: &str, color_scheme: &ColorScheme, file_upload: bool) -> Markup { +fn page_header( + serve_path: &str, + color_scheme: ColorScheme, + file_upload: bool, + is_error: bool, +) -> Markup { html! { (DOCTYPE) html { meta charset="utf-8"; meta http-equiv="X-UA-Compatible" content="IE=edge"; meta name="viewport" content="width=device-width, initial-scale=1"; - title { "Index of " (serve_path) } - style { (css(&color_scheme)) } + @if is_error { + title { (serve_path) } + } else { + title { "Index of " (serve_path) } + } + style { (css(color_scheme)) } @if file_upload { (PreEscaped(r#" <script> @@ -762,11 +846,46 @@ fn humanize_systemtime(src_time: Option<SystemTime>) -> Option<String> { } /// Renders an error on the webpage -pub fn render_error(error_description: &str, return_address: &str) -> Markup { +#[allow(clippy::too_many_arguments)] +pub fn render_error( + error_description: &str, + error_code: StatusCode, + return_address: &str, + sort_method: Option<SortingMethod>, + sort_order: Option<SortingOrder>, + color_scheme: ColorScheme, + default_color_scheme: ColorScheme, + has_referer: bool, + display_back_link: bool, +) -> Markup { + let link = if has_referer { + return_address.to_string() + } else { + parametrized_link( + return_address, + sort_method, + sort_order, + color_scheme, + default_color_scheme, + ) + }; + html! { - pre { (error_description) } - a href=(return_address) { - "Go back to file listing" + body { + (page_header(&error_code.to_string(), color_scheme, false, true)) + div.error { + p { (error_code.to_string()) } + @for error in error_description.lines() { + p { (error) } + } + @if display_back_link { + div.error-nav { + a.error-back href=(link) { + "Go back to file listing" + } + } + } + } } } } diff --git a/src/themes.rs b/src/themes.rs index a7b619e..65e9ab2 100644 --- a/src/themes.rs +++ b/src/themes.rs @@ -3,7 +3,7 @@ use structopt::clap::arg_enum; use strum_macros::EnumIter; arg_enum! { - #[derive(PartialEq, Deserialize, Clone, EnumIter)] + #[derive(PartialEq, Deserialize, Clone, EnumIter, Copy)] #[serde(rename_all = "lowercase")] pub enum ColorScheme { Archlinux, @@ -17,8 +17,8 @@ impl ColorScheme { /// Returns the URL-compatible name of a color scheme /// This must correspond to the name of the variant, in lowercase /// See https://github.com/svenstaro/miniserve/pull/55 for explanations - pub fn to_slug(&self) -> String { - match &self { + pub fn to_slug(self) -> String { + match self { ColorScheme::Archlinux => "archlinux", ColorScheme::Zenburn => "zenburn", ColorScheme::Monokai => "monokai", @@ -28,8 +28,8 @@ impl ColorScheme { } /// Returns wether a color scheme is dark - pub fn is_dark(&self) -> bool { - match &self { + pub fn is_dark(self) -> bool { + match self { ColorScheme::Archlinux => true, ColorScheme::Zenburn => true, ColorScheme::Monokai => true, @@ -81,6 +81,7 @@ impl ColorScheme { drag_text_color: "#fefefe".to_string(), size_background_color: "#5294e2".to_string(), size_text_color: "#fefefe".to_string(), + error_color: "#e44b4b".to_string(), }, ColorScheme::Zenburn => Theme { background: "#3f3f3f".to_string(), @@ -123,6 +124,7 @@ impl ColorScheme { drag_text_color: "#efefef".to_string(), size_background_color: "#7f9f7f".to_string(), size_text_color: "#efefef".to_string(), + error_color: "#d06565".to_string(), }, ColorScheme::Monokai => Theme { background: "#272822".to_string(), @@ -165,6 +167,7 @@ impl ColorScheme { drag_text_color: "#F8F8F2".to_string(), size_background_color: "#75715E".to_string(), size_text_color: "#F8F8F2".to_string(), + error_color: "#d02929".to_string(), }, ColorScheme::Squirrel => Theme { background: "#FFFFFF".to_string(), @@ -207,6 +210,7 @@ impl ColorScheme { drag_text_color: "#ffffff".to_string(), size_background_color: "#323232".to_string(), size_text_color: "#FFFFFF".to_string(), + error_color: "#d02424".to_string(), }, } } @@ -254,4 +258,5 @@ pub struct Theme { pub drag_text_color: String, pub size_background_color: String, pub size_text_color: String, + pub error_color: String, } diff --git a/tests/auth.rs b/tests/auth.rs index f43553b..128047d 100644 --- a/tests/auth.rs +++ b/tests/auth.rs @@ -3,6 +3,7 @@ mod fixtures; use assert_cmd::prelude::*; use assert_fs::fixture::TempDir; use fixtures::{port, tmpdir, Error, FILES}; +use reqwest::StatusCode; use rstest::rstest_parametrize; use select::document::Document; use select::predicate::Text; @@ -24,7 +25,7 @@ use std::time::Duration; "testpassword" ), )] -fn auth_works( +fn auth_accepts( tmpdir: TempDir, port: u16, cli_auth_arg: &str, @@ -43,11 +44,15 @@ fn auth_works( sleep(Duration::from_secs(1)); let client = reqwest::Client::new(); - let body = client + let response = client .get(format!("http://localhost:{}", port).as_str()) .basic_auth(client_username, Some(client_password)) - .send()? - .error_for_status()?; + .send()?; + + let status_code = response.status(); + assert_eq!(status_code, StatusCode::OK); + + let body = response.error_for_status()?; let parsed = Document::from_read(body)?; for &file in FILES { assert!(parsed.find(Text).any(|x| x.text() == file)); @@ -57,3 +62,60 @@ fn auth_works( Ok(()) } + +#[rstest_parametrize( + cli_auth_arg, client_username, client_password, + case("rightuser:rightpassword", "wronguser", "rightpassword"), + case( + "rightuser:sha256:314eee236177a721d0e58d3ca4ff01795cdcad1e8478ba8183a2e58d69c648c0", + "wronguser", + "rightpassword" + ), + case( + "rightuser:sha512:84ec4056571afeec9f5b59453305877e9a66c3f9a1d91733fde759b370c1d540b9dc58bfc88c5980ad2d020c3a8ee84f21314a180856f5a82ba29ecba29e2cab", + "wronguser", + "rightpassword" + ), + case("rightuser:rightpassword", "rightuser", "wrongpassword"), + case( + "rightuser:sha256:314eee236177a721d0e58d3ca4ff01795cdcad1e8478ba8183a2e58d69c648c0", + "rightuser", + "wrongpassword" + ), + case( + "rightuser:sha512:84ec4056571afeec9f5b59453305877e9a66c3f9a1d91733fde759b370c1d540b9dc58bfc88c5980ad2d020c3a8ee84f21314a180856f5a82ba29ecba29e2cab", + "rightuser", + "wrongpassword" + ), +)] +fn auth_rejects( + tmpdir: TempDir, + port: u16, + cli_auth_arg: &str, + client_username: &str, + client_password: &str, +) -> Result<(), Error> { + let mut child = Command::cargo_bin("miniserve")? + .arg(tmpdir.path()) + .arg("-p") + .arg(port.to_string()) + .arg("-a") + .arg(cli_auth_arg) + .stdout(Stdio::null()) + .spawn()?; + + sleep(Duration::from_secs(1)); + + let client = reqwest::Client::new(); + let status = client + .get(format!("http://localhost:{}", port).as_str()) + .basic_auth(client_username, Some(client_password)) + .send()? + .status(); + + assert_eq!(status, StatusCode::UNAUTHORIZED); + + child.kill()?; + + Ok(()) +} |