commit 0b7d6bf6df5a83dcc95d063baa04d8818eb9d7db
parent 89fe05b6ccfa577c8348f788a8e700e2b7247e77
Author: Daniel GarcĂa <dani-garcia@users.noreply.github.com>
Date: Sun, 7 Nov 2021 18:53:39 +0100
Update to rocket 0.5 and made code async, missing updating all db calls, that are currently blocking
Diffstat:
30 files changed, 1302 insertions(+), 1016 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -42,6 +42,15 @@ dependencies = [
]
[[package]]
+name = "ansi_term"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
+dependencies = [
+ "winapi 0.3.9",
+]
+
+[[package]]
name = "async-compression"
version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -56,14 +65,44 @@ dependencies = [
]
[[package]]
+name = "async-stream"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "171374e7e3b2504e0e5236e3b59260560f9fe94bfe9ac39ba5e4e929c5590625"
+dependencies = [
+ "async-stream-impl",
+ "futures-core",
+]
+
+[[package]]
+name = "async-stream-impl"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "648ed8c8d2ce5409ccd57453d9d1b214b342a0d69376a6feda1fd6cae3299308"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
name = "async-trait"
version = "0.1.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3"
dependencies = [
- "proc-macro2 1.0.36",
- "quote 1.0.15",
- "syn 1.0.86",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "atomic"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b88d82667eca772c4aa12f0f1348b3ae643424c8876448f3f7bd5787032e234c"
+dependencies = [
+ "autocfg",
]
[[package]]
@@ -106,16 +145,6 @@ checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b"
[[package]]
name = "base64"
-version = "0.9.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "489d6c0ed21b11d038c31b6ceccca973e65d73ba3bd8ecb9a2babf5546164643"
-dependencies = [
- "byteorder",
- "safemem",
-]
-
-[[package]]
-name = "base64"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7"
@@ -196,16 +225,6 @@ dependencies = [
]
[[package]]
-name = "buf_redux"
-version = "0.8.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b953a6887648bb07a535631f2bc00fbdb2a2216f135552cb3f534ed136b9c07f"
-dependencies = [
- "memchr",
- "safemem",
-]
-
-[[package]]
name = "bumpalo"
version = "3.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -311,24 +330,24 @@ checksum = "fbdcdcb6d86f71c5e97409ad45898af11cbc995b4ee8112d59095a28d376c935"
[[package]]
name = "cookie"
-version = "0.14.4"
+version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "03a5d7b21829bc7b4bf4754a978a241ae54ea55a40f92bb20216e54096f4b951"
+checksum = "d5f1c7727e460397e56abc4bddc1d49e07a1ad78fc98eb2e1c8f032a58a2f80d"
dependencies = [
"percent-encoding 2.1.0",
"time 0.2.27",
- "version_check 0.9.4",
+ "version_check",
]
[[package]]
name = "cookie"
-version = "0.15.1"
+version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d5f1c7727e460397e56abc4bddc1d49e07a1ad78fc98eb2e1c8f032a58a2f80d"
+checksum = "94d4706de1b0fa5b132270cddffa8585166037822e260a944fe161acd137ca05"
dependencies = [
"percent-encoding 2.1.0",
- "time 0.2.27",
- "version_check 0.9.4",
+ "time 0.3.7",
+ "version_check",
]
[[package]]
@@ -339,7 +358,7 @@ checksum = "b3f7034c0932dc36f5bd8ec37368d971346809435824f277cb3b8299fc56167c"
dependencies = [
"cookie 0.15.1",
"idna 0.2.3",
- "log 0.4.14",
+ "log",
"publicsuffix",
"serde",
"serde_json",
@@ -423,6 +442,16 @@ dependencies = [
]
[[package]]
+name = "ctrlc"
+version = "3.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a19c6cedffdc8c03a3346d723eb20bd85a13362bb96dc2ac000842c6381ec7bf"
+dependencies = [
+ "nix",
+ "winapi 0.3.9",
+]
+
+[[package]]
name = "dashmap"
version = "4.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -449,8 +478,9 @@ dependencies = [
[[package]]
name = "devise"
-version = "0.3.0"
-source = "git+https://github.com/SergioBenitez/Devise.git?rev=e58b3ac9a#e58b3ac9afc3b6ff10a8aaf02a3e768a8f530089"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50c7580b072f1c8476148f16e0a0d5dedddab787da98d86c5082c5e9ed8ab595"
dependencies = [
"devise_codegen",
"devise_core",
@@ -458,22 +488,25 @@ dependencies = [
[[package]]
name = "devise_codegen"
-version = "0.3.0"
-source = "git+https://github.com/SergioBenitez/Devise.git?rev=e58b3ac9a#e58b3ac9afc3b6ff10a8aaf02a3e768a8f530089"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "123c73e7a6e51b05c75fe1a1b2f4e241399ea5740ed810b0e3e6cacd9db5e7b2"
dependencies = [
"devise_core",
- "quote 1.0.15",
+ "quote",
]
[[package]]
name = "devise_core"
-version = "0.3.0"
-source = "git+https://github.com/SergioBenitez/Devise.git?rev=e58b3ac9a#e58b3ac9afc3b6ff10a8aaf02a3e768a8f530089"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "841ef46f4787d9097405cac4e70fb8644fc037b526e8c14054247c0263c400d0"
dependencies = [
"bitflags",
- "proc-macro2 1.0.36",
- "quote 1.0.15",
- "syn 1.0.86",
+ "proc-macro2",
+ "proc-macro2-diagnostics",
+ "quote",
+ "syn",
]
[[package]]
@@ -499,9 +532,9 @@ version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45f5098f628d02a7a0f68ddba586fb61e80edec3bdc1be3b921f4ceec60858d3"
dependencies = [
- "proc-macro2 1.0.36",
- "quote 1.0.15",
- "syn 1.0.86",
+ "proc-macro2",
+ "quote",
+ "syn",
]
[[package]]
@@ -566,9 +599,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c5f0096a91d210159eceb2ff5e1c4da18388a170e1e3ce948aac9c8fdbbf595"
dependencies = [
"heck",
- "proc-macro2 1.0.36",
- "quote 1.0.15",
- "syn 1.0.86",
+ "proc-macro2",
+ "quote",
+ "syn",
]
[[package]]
@@ -601,11 +634,25 @@ version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c9a4820f0ccc8a7afd67c39a0f1a0f4b07ca1725164271a64939d7aeb9af065"
dependencies = [
- "log 0.4.14",
+ "log",
"syslog",
]
[[package]]
+name = "figment"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "790b4292c72618abbab50f787a477014fe15634f96291de45672ce46afe122df"
+dependencies = [
+ "atomic",
+ "pear",
+ "serde",
+ "toml",
+ "uncased",
+ "version_check",
+]
+
+[[package]]
name = "flate2"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -734,9 +781,9 @@ version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dbd947adfffb0efc70599b3ddcf7b5597bb5fa9e245eb99f62b3a5f7bb8bd3c"
dependencies = [
- "proc-macro2 1.0.36",
- "quote 1.0.15",
- "syn 1.0.86",
+ "proc-macro2",
+ "quote",
+ "syn",
]
[[package]]
@@ -776,6 +823,19 @@ dependencies = [
]
[[package]]
+name = "generator"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1d9279ca822891c1a4dae06d185612cf8fc6acfe5dff37781b41297811b12ee"
+dependencies = [
+ "cc",
+ "libc",
+ "log",
+ "rustversion",
+ "winapi 0.3.9",
+]
+
+[[package]]
name = "generic-array"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -791,7 +851,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803"
dependencies = [
"typenum",
- "version_check 0.9.4",
+ "version_check",
]
[[package]]
@@ -876,7 +936,7 @@ version = "4.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25546a65e5cf1f471f3438796fc634650b31d7fcde01d444c309aeb28b92e3a8"
dependencies = [
- "log 0.4.14",
+ "log",
"pest",
"pest_derive",
"quick-error 2.0.1",
@@ -946,12 +1006,12 @@ version = "0.25.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aafcf38a1a36118242d29b92e1b08ef84e67e4a5ed06e0a80be20e6a32bfed6b"
dependencies = [
- "log 0.4.14",
+ "log",
"mac",
"markup5ever",
- "proc-macro2 1.0.36",
- "quote 1.0.15",
- "syn 1.0.86",
+ "proc-macro2",
+ "quote",
+ "syn",
]
[[package]]
@@ -990,25 +1050,6 @@ checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
[[package]]
name = "hyper"
-version = "0.10.16"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0a0652d9a2609a968c14be1a9ea00bf4b1d64e2e1f53a1b51b6fff3a6e829273"
-dependencies = [
- "base64 0.9.3",
- "httparse",
- "language-tags",
- "log 0.3.9",
- "mime 0.2.6",
- "num_cpus",
- "time 0.1.44",
- "traitobject",
- "typeable",
- "unicase 1.4.2",
- "url 1.7.2",
-]
-
-[[package]]
-name = "hyper"
version = "0.14.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7ec3e62bdc98a2f0393a5048e4c30ef659440ea6e0e572965103e72bd836f55"
@@ -1032,25 +1073,13 @@ dependencies = [
]
[[package]]
-name = "hyper-sync-rustls"
-version = "0.3.0-rc.17"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4cb014c4ea00486e2b62860b5e15229d37516d4924177218beafbf46583de3ab"
-dependencies = [
- "hyper 0.10.16",
- "rustls",
- "webpki",
- "webpki-roots",
-]
-
-[[package]]
name = "hyper-tls"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
dependencies = [
"bytes 1.1.0",
- "hyper 0.14.16",
+ "hyper",
"native-tls",
"tokio",
"tokio-native-tls",
@@ -1086,9 +1115,16 @@ checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223"
dependencies = [
"autocfg",
"hashbrown",
+ "serde",
]
[[package]]
+name = "inlinable_string"
+version = "0.1.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb"
+
+[[package]]
name = "instant"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1180,12 +1216,6 @@ dependencies = [
]
[[package]]
-name = "language-tags"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a"
-
-[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1208,7 +1238,7 @@ dependencies = [
"hostname",
"httpdate",
"idna 0.2.3",
- "mime 0.3.16",
+ "mime",
"native-tls",
"nom 7.1.0",
"once_cell",
@@ -1252,20 +1282,26 @@ dependencies = [
[[package]]
name = "log"
-version = "0.3.9"
+version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b"
+checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
dependencies = [
- "log 0.4.14",
+ "cfg-if 1.0.0",
]
[[package]]
-name = "log"
-version = "0.4.14"
+name = "loom"
+version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
+checksum = "edc5c7d328e32cc4954e8e01193d7f0ef5ab257b5090b70a964e099a36034309"
dependencies = [
"cfg-if 1.0.0",
+ "generator",
+ "scoped-tls",
+ "serde",
+ "serde_json",
+ "tracing",
+ "tracing-subscriber",
]
[[package]]
@@ -1304,7 +1340,7 @@ version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a24f40fb03852d1cdd84330cddcaf98e9ec08a7b7768e952fad3b4cf048ec8fd"
dependencies = [
- "log 0.4.14",
+ "log",
"phf 0.8.0",
"phf_codegen 0.8.0",
"string_cache",
@@ -1331,6 +1367,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4"
[[package]]
+name = "matchers"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
+dependencies = [
+ "regex-automata",
+]
+
+[[package]]
name = "matches"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1349,6 +1394,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
[[package]]
+name = "memoffset"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
name = "migrations_internals"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1364,18 +1418,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9753f12909fd8d923f75ae5c3258cae1ed3c8ec052e1b38c93c21a6d157f789c"
dependencies = [
"migrations_internals",
- "proc-macro2 1.0.36",
- "quote 1.0.15",
- "syn 1.0.86",
-]
-
-[[package]]
-name = "mime"
-version = "0.2.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ba626b8a6de5da682e1caa06bdb42a335aee5a84db8e5046a3e8ab17ba0a3ae0"
-dependencies = [
- "log 0.3.9",
+ "proc-macro2",
+ "quote",
+ "syn",
]
[[package]]
@@ -1385,16 +1430,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
[[package]]
-name = "mime_guess"
-version = "2.0.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2684d4c2e97d99848d30b324b00c8fcc7e5c897b7cbb5819b09e7c90e8baf212"
-dependencies = [
- "mime 0.3.16",
- "unicase 2.6.0",
-]
-
-[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1422,7 +1457,7 @@ dependencies = [
"iovec",
"kernel32-sys",
"libc",
- "log 0.4.14",
+ "log",
"miow 0.2.2",
"net2",
"slab",
@@ -1436,7 +1471,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc"
dependencies = [
"libc",
- "log 0.4.14",
+ "log",
"miow 0.3.7",
"ntapi",
"winapi 0.3.9",
@@ -1449,7 +1484,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19"
dependencies = [
"lazycell",
- "log 0.4.14",
+ "log",
"mio 0.6.23",
"slab",
]
@@ -1476,21 +1511,23 @@ dependencies = [
]
[[package]]
-name = "multipart"
-version = "0.18.0"
+name = "multer"
+version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "00dec633863867f29cb39df64a397cdf4a6354708ddd7759f70c7fb51c5f9182"
+checksum = "5f8f35e687561d5c1667590911e6698a8cb714a134a7505718a182e7bc9d3836"
dependencies = [
- "buf_redux",
+ "bytes 1.1.0",
+ "encoding_rs",
+ "futures-util",
+ "http",
"httparse",
- "log 0.4.14",
- "mime 0.3.16",
- "mime_guess",
- "quick-error 1.2.3",
- "rand 0.8.4",
- "safemem",
- "tempfile",
- "twoway",
+ "log",
+ "memchr",
+ "mime",
+ "spin 0.9.2",
+ "tokio",
+ "tokio-util",
+ "version_check",
]
[[package]]
@@ -1511,7 +1548,7 @@ checksum = "48ba9f7719b5a0f42f338907614285fb5fd70e53858141f69898a1fb7203b24d"
dependencies = [
"lazy_static",
"libc",
- "log 0.4.14",
+ "log",
"openssl",
"openssl-probe",
"openssl-sys",
@@ -1539,6 +1576,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
[[package]]
+name = "nix"
+version = "0.23.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6"
+dependencies = [
+ "bitflags",
+ "cc",
+ "cfg-if 1.0.0",
+ "libc",
+ "memoffset",
+]
+
+[[package]]
name = "no-std-compat"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1561,7 +1611,7 @@ checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109"
dependencies = [
"memchr",
"minimal-lexical",
- "version_check 0.9.4",
+ "version_check",
]
[[package]]
@@ -1596,9 +1646,9 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d"
dependencies = [
- "proc-macro2 1.0.36",
- "quote 1.0.15",
- "syn 1.0.86",
+ "proc-macro2",
+ "quote",
+ "syn",
]
[[package]]
@@ -1631,6 +1681,15 @@ dependencies = [
]
[[package]]
+name = "num_threads"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97ba99ba6393e2c3734791401b66902d981cb03bf190af674ca69949b6d5fb15"
+dependencies = [
+ "libc",
+]
+
+[[package]]
name = "object"
version = "0.27.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1718,7 +1777,7 @@ dependencies = [
"byteorder",
"bytes 0.4.12",
"httparse",
- "log 0.4.14",
+ "log",
"mio 0.6.23",
"mio-extras",
"rand 0.7.3",
@@ -1791,24 +1850,25 @@ checksum = "0744126afe1a6dd7f394cb50a716dbe086cb06e255e53d8d0185d82828358fb5"
[[package]]
name = "pear"
-version = "0.1.4"
+version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5320f212db967792b67cfe12bd469d08afd6318a249bd917d5c19bc92200ab8a"
+checksum = "15e44241c5e4c868e3eaa78b7c1848cadd6344ed4f54d029832d32b415a58702"
dependencies = [
+ "inlinable_string",
"pear_codegen",
+ "yansi",
]
[[package]]
name = "pear_codegen"
-version = "0.1.4"
+version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bfc1c836fdc3d1ef87c348b237b5b5c4dff922156fb2d968f57734f9669768ca"
+checksum = "82a5ca643c2303ecb740d506539deba189e16f2754040a42901cd8105d0282d0"
dependencies = [
- "proc-macro2 0.4.30",
- "quote 0.6.13",
- "syn 0.15.44",
- "version_check 0.9.4",
- "yansi",
+ "proc-macro2",
+ "proc-macro2-diagnostics",
+ "quote",
+ "syn",
]
[[package]]
@@ -1861,9 +1921,9 @@ checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55"
dependencies = [
"pest",
"pest_meta",
- "proc-macro2 1.0.36",
- "quote 1.0.15",
- "syn 1.0.86",
+ "proc-macro2",
+ "quote",
+ "syn",
]
[[package]]
@@ -2007,20 +2067,24 @@ checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
[[package]]
name = "proc-macro2"
-version = "0.4.30"
+version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759"
+checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
dependencies = [
- "unicode-xid 0.1.0",
+ "unicode-xid",
]
[[package]]
-name = "proc-macro2"
-version = "1.0.36"
+name = "proc-macro2-diagnostics"
+version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
+checksum = "4bf29726d67464d49fa6224a1d07936a8c08bb3fba727c7493f6cf1616fdaada"
dependencies = [
- "unicode-xid 0.2.2",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "version_check",
+ "yansi",
]
[[package]]
@@ -2071,20 +2135,11 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
[[package]]
name = "quote"
-version = "0.6.13"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1"
-dependencies = [
- "proc-macro2 0.4.30",
-]
-
-[[package]]
-name = "quote"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145"
dependencies = [
- "proc-macro2 1.0.36",
+ "proc-macro2",
]
[[package]]
@@ -2099,7 +2154,7 @@ version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "545c5bc2b880973c9c10e4067418407a0ccaa3091781d1671d46eb35107cb26f"
dependencies = [
- "log 0.4.14",
+ "log",
"parking_lot 0.11.2",
"scheduled-thread-pool",
]
@@ -2251,6 +2306,26 @@ dependencies = [
]
[[package]]
+name = "ref-cast"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "300f2a835d808734ee295d45007adacb9ebb29dd3ae2424acfa17930cae541da"
+dependencies = [
+ "ref-cast-impl",
+]
+
+[[package]]
+name = "ref-cast-impl"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c38e3aecd2b21cb3959637b883bb3714bc7e43f0268b9a29d3743ee3e55cdd2"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
name = "regex"
version = "1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2262,6 +2337,15 @@ dependencies = [
]
[[package]]
+name = "regex-automata"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
+dependencies = [
+ "regex-syntax",
+]
+
+[[package]]
name = "regex-syntax"
version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2293,13 +2377,13 @@ dependencies = [
"h2",
"http",
"http-body",
- "hyper 0.14.16",
+ "hyper",
"hyper-tls",
"ipnet",
"js-sys",
"lazy_static",
- "log 0.4.14",
- "mime 0.3.16",
+ "log",
+ "mime",
"native-tls",
"percent-encoding 2.1.0",
"pin-project-lite",
@@ -2338,7 +2422,7 @@ dependencies = [
"cc",
"libc",
"once_cell",
- "spin",
+ "spin 0.5.2",
"untrusted",
"web-sys",
"winapi 0.3.9",
@@ -2366,65 +2450,82 @@ dependencies = [
[[package]]
name = "rocket"
-version = "0.5.0-dev"
-source = "git+https://github.com/SergioBenitez/Rocket?rev=263e39b5b429de1913ce7e3036575a7b4d88b6d7#263e39b5b429de1913ce7e3036575a7b4d88b6d7"
+version = "0.5.0-rc.1"
+source = "git+https://github.com/SergioBenitez/Rocket?rev=8cae077ba1d54b92cdef3e171a730b819d5eeb8e#8cae077ba1d54b92cdef3e171a730b819d5eeb8e"
dependencies = [
+ "async-stream",
+ "async-trait",
+ "atomic",
"atty",
"binascii",
- "log 0.4.14",
+ "bytes 1.1.0",
+ "either",
+ "figment",
+ "futures",
+ "indexmap",
+ "log",
"memchr",
+ "multer",
"num_cpus",
- "pear",
+ "parking_lot 0.11.2",
+ "pin-project-lite",
+ "rand 0.8.4",
+ "ref-cast",
"rocket_codegen",
"rocket_http",
+ "serde",
+ "serde_json",
"state",
- "time 0.2.27",
- "toml",
- "version_check 0.9.4",
+ "tempfile",
+ "time 0.3.7",
+ "tokio",
+ "tokio-stream",
+ "tokio-util",
+ "ubyte",
+ "version_check",
"yansi",
]
[[package]]
name = "rocket_codegen"
-version = "0.5.0-dev"
-source = "git+https://github.com/SergioBenitez/Rocket?rev=263e39b5b429de1913ce7e3036575a7b4d88b6d7#263e39b5b429de1913ce7e3036575a7b4d88b6d7"
+version = "0.5.0-rc.1"
+source = "git+https://github.com/SergioBenitez/Rocket?rev=8cae077ba1d54b92cdef3e171a730b819d5eeb8e#8cae077ba1d54b92cdef3e171a730b819d5eeb8e"
dependencies = [
"devise",
"glob",
"indexmap",
- "quote 1.0.15",
+ "proc-macro2",
+ "quote",
"rocket_http",
- "version_check 0.9.4",
- "yansi",
-]
-
-[[package]]
-name = "rocket_contrib"
-version = "0.5.0-dev"
-source = "git+https://github.com/SergioBenitez/Rocket?rev=263e39b5b429de1913ce7e3036575a7b4d88b6d7#263e39b5b429de1913ce7e3036575a7b4d88b6d7"
-dependencies = [
- "log 0.4.14",
- "rocket",
- "serde",
- "serde_json",
+ "syn",
+ "unicode-xid",
]
[[package]]
name = "rocket_http"
-version = "0.5.0-dev"
-source = "git+https://github.com/SergioBenitez/Rocket?rev=263e39b5b429de1913ce7e3036575a7b4d88b6d7#263e39b5b429de1913ce7e3036575a7b4d88b6d7"
+version = "0.5.0-rc.1"
+source = "git+https://github.com/SergioBenitez/Rocket?rev=8cae077ba1d54b92cdef3e171a730b819d5eeb8e#8cae077ba1d54b92cdef3e171a730b819d5eeb8e"
dependencies = [
- "cookie 0.14.4",
- "hyper 0.10.16",
- "hyper-sync-rustls",
+ "cookie 0.16.0",
+ "either",
+ "http",
+ "hyper",
"indexmap",
+ "log",
+ "memchr",
"pear",
- "percent-encoding 1.0.1",
+ "percent-encoding 2.1.0",
+ "pin-project-lite",
+ "ref-cast",
"rustls",
+ "serde",
"smallvec 1.8.0",
+ "stable-pattern",
"state",
- "time 0.2.27",
- "unicode-xid 0.2.2",
+ "time 0.3.7",
+ "tokio",
+ "tokio-rustls",
+ "uncased",
]
[[package]]
@@ -2444,28 +2545,28 @@ dependencies = [
[[package]]
name = "rustls"
-version = "0.17.0"
+version = "0.19.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c0d4a31f5d68413404705d6982529b0e11a9aacd4839d1d6222ee3b8cb4015e1"
+checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7"
dependencies = [
- "base64 0.11.0",
- "log 0.4.14",
+ "base64 0.13.0",
+ "log",
"ring",
"sct",
"webpki",
]
[[package]]
-name = "ryu"
-version = "1.0.9"
+name = "rustversion"
+version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f"
+checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f"
[[package]]
-name = "safemem"
-version = "0.3.3"
+name = "ryu"
+version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072"
+checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f"
[[package]]
name = "same-file"
@@ -2496,6 +2597,12 @@ dependencies = [
]
[[package]]
+name = "scoped-tls"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
+
+[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2574,9 +2681,9 @@ version = "1.0.136"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9"
dependencies = [
- "proc-macro2 1.0.36",
- "quote 1.0.15",
- "syn 1.0.86",
+ "proc-macro2",
+ "quote",
+ "syn",
]
[[package]]
@@ -2656,6 +2763,24 @@ dependencies = [
]
[[package]]
+name = "sharded-slab"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
+dependencies = [
+ "libc",
+]
+
+[[package]]
name = "simple_asn1"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2721,6 +2846,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
+name = "spin"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "511254be0c5bcf062b019a6c89c01a664aa359ded62f78aa72c6fc137c0590e5"
+
+[[package]]
+name = "stable-pattern"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4564168c00635f88eaed410d5efa8131afa8d8699a612c80c455a0ba05c21045"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
name = "stable_deref_trait"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2732,14 +2872,17 @@ version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e113fb6f3de07a243d434a56ec6f186dfd51cb08448239fe7bcae73f87ff28ff"
dependencies = [
- "version_check 0.9.4",
+ "version_check",
]
[[package]]
name = "state"
-version = "0.4.2"
+version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3015a7d0a5fd5105c91c3710d42f9ccf0abfb287d62206484dcc67f9569a6483"
+checksum = "87cf4f5369e6d3044b5e365c9690f451516ac8f0954084622b49ea3fde2f6de5"
+dependencies = [
+ "loom",
+]
[[package]]
name = "stdweb"
@@ -2761,11 +2904,11 @@ version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef"
dependencies = [
- "proc-macro2 1.0.36",
- "quote 1.0.15",
+ "proc-macro2",
+ "quote",
"serde",
"serde_derive",
- "syn 1.0.86",
+ "syn",
]
[[package]]
@@ -2775,13 +2918,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11"
dependencies = [
"base-x",
- "proc-macro2 1.0.36",
- "quote 1.0.15",
+ "proc-macro2",
+ "quote",
"serde",
"serde_derive",
"serde_json",
"sha1",
- "syn 1.0.86",
+ "syn",
]
[[package]]
@@ -2812,8 +2955,8 @@ checksum = "f24c8e5e19d22a726626f1a5e16fe15b132dcf21d10177fa5a45ce7962996b97"
dependencies = [
"phf_generator 0.8.0",
"phf_shared 0.8.0",
- "proc-macro2 1.0.36",
- "quote 1.0.15",
+ "proc-macro2",
+ "quote",
]
[[package]]
@@ -2824,24 +2967,13 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
[[package]]
name = "syn"
-version = "0.15.44"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5"
-dependencies = [
- "proc-macro2 0.4.30",
- "quote 0.6.13",
- "unicode-xid 0.1.0",
-]
-
-[[package]]
-name = "syn"
version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b"
dependencies = [
- "proc-macro2 1.0.36",
- "quote 1.0.15",
- "unicode-xid 0.2.2",
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
]
[[package]]
@@ -2852,7 +2984,7 @@ checksum = "a0641142b4081d3d44beffa4eefd7346a228cdf91ed70186db2ca2cef762d327"
dependencies = [
"error-chain",
"libc",
- "log 0.4.14",
+ "log",
"time 0.1.44",
]
@@ -2896,9 +3028,18 @@ version = "1.0.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
dependencies = [
- "proc-macro2 1.0.36",
- "quote 1.0.15",
- "syn 1.0.86",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "thread_local"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180"
+dependencies = [
+ "once_cell",
]
[[package]]
@@ -2931,12 +3072,24 @@ dependencies = [
"libc",
"standback",
"stdweb",
- "time-macros",
- "version_check 0.9.4",
+ "time-macros 0.1.1",
+ "version_check",
"winapi 0.3.9",
]
[[package]]
+name = "time"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "004cbc98f30fa233c61a38bc77e96a9106e65c88f2d3bef182ae952027e5753d"
+dependencies = [
+ "itoa 1.0.1",
+ "libc",
+ "num_threads",
+ "time-macros 0.2.3",
+]
+
+[[package]]
name = "time-macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2947,16 +3100,22 @@ dependencies = [
]
[[package]]
+name = "time-macros"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25eb0ca3468fc0acc11828786797f6ef9aa1555e4a211a60d64cc8e4d1be47d6"
+
+[[package]]
name = "time-macros-impl"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd3c141a1b43194f3f56a1411225df8646c55781d5f26db825b3d98507eb482f"
dependencies = [
"proc-macro-hack",
- "proc-macro2 1.0.36",
- "quote 1.0.15",
+ "proc-macro2",
+ "quote",
"standback",
- "syn 1.0.86",
+ "syn",
]
[[package]]
@@ -2985,11 +3144,26 @@ dependencies = [
"memchr",
"mio 0.7.14",
"num_cpus",
+ "once_cell",
+ "parking_lot 0.11.2",
"pin-project-lite",
+ "signal-hook-registry",
+ "tokio-macros",
"winapi 0.3.9",
]
[[package]]
+name = "tokio-macros"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
name = "tokio-native-tls"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3000,6 +3174,17 @@ dependencies = [
]
[[package]]
+name = "tokio-rustls"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6"
+dependencies = [
+ "rustls",
+ "tokio",
+ "webpki",
+]
+
+[[package]]
name = "tokio-socks"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3012,6 +3197,17 @@ dependencies = [
]
[[package]]
+name = "tokio-stream"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3"
+dependencies = [
+ "futures-core",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
name = "tokio-util"
version = "0.6.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3020,16 +3216,16 @@ dependencies = [
"bytes 1.1.0",
"futures-core",
"futures-sink",
- "log 0.4.14",
+ "log",
"pin-project-lite",
"tokio",
]
[[package]]
name = "toml"
-version = "0.4.10"
+version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "758664fc71a3a69038656bee8b6be6477d2a6c315a6b81f7081f591bffa4111f"
+checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
dependencies = [
"serde",
]
@@ -3059,7 +3255,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105"
dependencies = [
"cfg-if 1.0.0",
- "log 0.4.14",
+ "log",
"pin-project-lite",
"tracing-attributes",
"tracing-core",
@@ -3071,9 +3267,9 @@ version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4f480b8f81512e825f337ad51e94c1eb5d3bbdf2b363dcd01e2b19a9ffe3f8e"
dependencies = [
- "proc-macro2 1.0.36",
- "quote 1.0.15",
- "syn 1.0.86",
+ "proc-macro2",
+ "quote",
+ "syn",
]
[[package]]
@@ -3086,10 +3282,33 @@ dependencies = [
]
[[package]]
-name = "traitobject"
-version = "0.1.0"
+name = "tracing-log"
+version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079"
+checksum = "a6923477a48e41c1951f1999ef8bb5a3023eb723ceadafe78ffb65dc366761e3"
+dependencies = [
+ "lazy_static",
+ "log",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-subscriber"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5312f325fe3588e277415f5a6cca1f4ccad0f248c4cd5a4bd33032d7286abc22"
+dependencies = [
+ "ansi_term",
+ "lazy_static",
+ "matchers",
+ "regex",
+ "sharded-slab",
+ "smallvec 1.8.0",
+ "thread_local",
+ "tracing",
+ "tracing-core",
+ "tracing-log",
+]
[[package]]
name = "trust-dns-proto"
@@ -3107,7 +3326,7 @@ dependencies = [
"idna 0.2.3",
"ipnet",
"lazy_static",
- "log 0.4.14",
+ "log",
"rand 0.8.4",
"smallvec 1.8.0",
"thiserror",
@@ -3126,7 +3345,7 @@ dependencies = [
"futures-util",
"ipconfig",
"lazy_static",
- "log 0.4.14",
+ "log",
"lru-cache",
"parking_lot 0.11.2",
"resolv-conf",
@@ -3143,21 +3362,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
[[package]]
-name = "twoway"
-version = "0.1.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "59b11b2b5241ba34be09c3cc85a36e56e48f9888862e19cedf23336d35316ed1"
-dependencies = [
- "memchr",
-]
-
-[[package]]
-name = "typeable"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1410f6f91f21d1612654e7cc69193b0334f909dcf2c790c4826254fbb86f8887"
-
-[[package]]
name = "typenum"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3181,6 +3385,15 @@ dependencies = [
]
[[package]]
+name = "ubyte"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42756bb9e708855de2f8a98195643dff31a97f0485d90d8467b39dc24be9e8fe"
+dependencies = [
+ "serde",
+]
+
+[[package]]
name = "ucd-trie"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3192,25 +3405,8 @@ version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5baeed7327e25054889b9bd4f975f32e5f4c5d434042d59ab6cd4142c0a76ed0"
dependencies = [
- "version_check 0.9.4",
-]
-
-[[package]]
-name = "unicase"
-version = "1.4.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f4765f83163b74f957c797ad9253caf97f103fb064d3999aea9568d09fc8a33"
-dependencies = [
- "version_check 0.1.5",
-]
-
-[[package]]
-name = "unicase"
-version = "2.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
-dependencies = [
- "version_check 0.9.4",
+ "serde",
+ "version_check",
]
[[package]]
@@ -3236,12 +3432,6 @@ checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b"
[[package]]
name = "unicode-xid"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
-
-[[package]]
-name = "unicode-xid"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
@@ -3302,12 +3492,14 @@ dependencies = [
"chrono-tz",
"cookie 0.15.1",
"cookie_store",
+ "ctrlc",
"data-encoding",
"data-url",
"diesel",
"diesel_migrations",
"dotenv",
"fern",
+ "futures",
"governor",
"handlebars",
"html5ever",
@@ -3316,9 +3508,8 @@ dependencies = [
"jsonwebtoken",
"lettre",
"libsqlite3-sys",
- "log 0.4.14",
+ "log",
"markup5ever_rcdom",
- "multipart",
"num-derive",
"num-traits",
"once_cell",
@@ -3333,11 +3524,11 @@ dependencies = [
"ring",
"rmpv",
"rocket",
- "rocket_contrib",
"serde",
"serde_json",
"syslog",
"time 0.2.27",
+ "tokio",
"totp-lite",
"tracing",
"u2f",
@@ -3355,12 +3546,6 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "version_check"
-version = "0.1.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd"
-
-[[package]]
-name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
@@ -3382,7 +3567,7 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0"
dependencies = [
- "log 0.4.14",
+ "log",
"try-lock",
]
@@ -3416,10 +3601,10 @@ checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca"
dependencies = [
"bumpalo",
"lazy_static",
- "log 0.4.14",
- "proc-macro2 1.0.36",
- "quote 1.0.15",
- "syn 1.0.86",
+ "log",
+ "proc-macro2",
+ "quote",
+ "syn",
"wasm-bindgen-shared",
]
@@ -3441,7 +3626,7 @@ version = "0.2.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01"
dependencies = [
- "quote 1.0.15",
+ "quote",
"wasm-bindgen-macro-support",
]
@@ -3451,9 +3636,9 @@ version = "0.2.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc"
dependencies = [
- "proc-macro2 1.0.36",
- "quote 1.0.15",
- "syn 1.0.86",
+ "proc-macro2",
+ "quote",
+ "syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@@ -3504,15 +3689,6 @@ dependencies = [
]
[[package]]
-name = "webpki-roots"
-version = "0.19.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f8eff4b7516a57307f9349c64bf34caa34b940b66fed4b2fb3136cb7386e5739"
-dependencies = [
- "webpki",
-]
-
-[[package]]
name = "widestring"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3595,7 +3771,7 @@ version = "0.16.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9234163818fd8e2418fcde330655e757900d4236acd8cc70fef345ef91f6d865"
dependencies = [
- "log 0.4.14",
+ "log",
"mac",
"markup5ever",
"time 0.1.44",
diff --git a/Cargo.toml b/Cargo.toml
@@ -3,7 +3,7 @@ name = "vaultwarden"
version = "1.0.0"
authors = ["Daniel GarcĂa <dani-garcia@users.noreply.github.com>"]
edition = "2021"
-rust-version = "1.60"
+rust-version = "1.56"
resolver = "2"
repository = "https://github.com/dani-garcia/vaultwarden"
@@ -13,6 +13,7 @@ publish = false
build = "build.rs"
[features]
+# default = ["sqlite"]
# Empty to keep compatibility, prefer to set USE_SYSLOG=true
enable_syslog = []
mysql = ["diesel/mysql", "diesel_migrations/mysql"]
@@ -29,22 +30,22 @@ unstable = []
syslog = "4.0.1"
[dependencies]
-# Web framework for nightly with a focus on ease-of-use, expressibility, and speed.
-rocket = { version = "=0.5.0-dev", features = ["tls"], default-features = false }
-rocket_contrib = "=0.5.0-dev"
-
-# HTTP client
-reqwest = { version = "0.11.9", features = ["blocking", "json", "gzip", "brotli", "socks", "cookies", "trust-dns"] }
+# Web framework
+rocket = { version = "0.5.0-rc.1", features = ["tls", "json"], default-features = false }
+
+# Async futures
+futures = "0.3.19"
+tokio = { version = "1.16.1", features = ["rt-multi-thread", "fs", "io-util", "parking_lot"] }
+
+ # HTTP client
+reqwest = { version = "0.11.9", features = ["stream", "json", "gzip", "brotli", "socks", "cookies", "trust-dns"] }
+bytes = "1.1.0"
# Used for custom short lived cookie jar
cookie = "0.15.1"
cookie_store = "0.15.1"
-bytes = "1.1.0"
url = "2.2.2"
-# multipart/form-data support
-multipart = { version = "0.18.0", features = ["server"], default-features = false }
-
# WebSockets library
ws = { version = "0.11.1", package = "parity-ws" }
@@ -141,10 +142,10 @@ backtrace = "0.3.64"
paste = "1.0.6"
governor = "0.4.1"
+ctrlc = { version = "3.2.1", features = ["termination"] }
+
[patch.crates-io]
-# Use newest ring
-rocket = { git = 'https://github.com/SergioBenitez/Rocket', rev = '263e39b5b429de1913ce7e3036575a7b4d88b6d7' }
-rocket_contrib = { git = 'https://github.com/SergioBenitez/Rocket', rev = '263e39b5b429de1913ce7e3036575a7b4d88b6d7' }
+rocket = { git = 'https://github.com/SergioBenitez/Rocket', rev = '8cae077ba1d54b92cdef3e171a730b819d5eeb8e' }
# The maintainer of the `job_scheduler` crate doesn't seem to have responded
# to any issues or PRs for almost a year (as of April 2021). This hopefully
diff --git a/Rocket.toml b/Rocket.toml
@@ -1,2 +0,0 @@
-[global.limits]
-json = 10485760 # 10 MiB
diff --git a/rust-toolchain b/rust-toolchain
@@ -1 +1 @@
-nightly-2022-01-23
+stable
diff --git a/src/api/admin.rs b/src/api/admin.rs
@@ -3,13 +3,14 @@ use serde::de::DeserializeOwned;
use serde_json::Value;
use std::env;
+use rocket::serde::json::Json;
use rocket::{
- http::{Cookie, Cookies, SameSite, Status},
- request::{self, FlashMessage, Form, FromRequest, Outcome, Request},
- response::{content::Html, Flash, Redirect},
+ form::Form,
+ http::{Cookie, CookieJar, SameSite, Status},
+ request::{self, FlashMessage, FromRequest, Outcome, Request},
+ response::{content::RawHtml as Html, Flash, Redirect},
Route,
};
-use rocket_contrib::json::Json;
use crate::{
api::{ApiResult, EmptyResult, JsonResult, NumberOrString},
@@ -85,10 +86,11 @@ fn admin_path() -> String {
struct Referer(Option<String>);
-impl<'a, 'r> FromRequest<'a, 'r> for Referer {
+#[rocket::async_trait]
+impl<'r> FromRequest<'r> for Referer {
type Error = ();
- fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, Self::Error> {
+ async fn from_request(request: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
Outcome::Success(Referer(request.headers().get_one("Referer").map(str::to_string)))
}
}
@@ -96,10 +98,11 @@ impl<'a, 'r> FromRequest<'a, 'r> for Referer {
#[derive(Debug)]
struct IpHeader(Option<String>);
-impl<'a, 'r> FromRequest<'a, 'r> for IpHeader {
+#[rocket::async_trait]
+impl<'r> FromRequest<'r> for IpHeader {
type Error = ();
- fn from_request(req: &'a Request<'r>) -> Outcome<Self, Self::Error> {
+ async fn from_request(req: &'r Request<'_>) -> Outcome<Self, Self::Error> {
if req.headers().get_one(&CONFIG.ip_header()).is_some() {
Outcome::Success(IpHeader(Some(CONFIG.ip_header())))
} else if req.headers().get_one("X-Client-IP").is_some() {
@@ -138,7 +141,7 @@ fn admin_url(referer: Referer) -> String {
#[get("/", rank = 2)]
fn admin_login(flash: Option<FlashMessage>) -> ApiResult<Html<String>> {
// If there is an error, show it
- let msg = flash.map(|msg| format!("{}: {}", msg.name(), msg.msg()));
+ let msg = flash.map(|msg| format!("{}: {}", msg.kind(), msg.message()));
let json = json!({
"page_content": "admin/login",
"version": VERSION,
@@ -159,7 +162,7 @@ struct LoginForm {
#[post("/", data = "<data>")]
fn post_admin_login(
data: Form<LoginForm>,
- mut cookies: Cookies,
+ cookies: &CookieJar,
ip: ClientIp,
referer: Referer,
) -> Result<Redirect, Flash<Redirect>> {
@@ -180,7 +183,7 @@ fn post_admin_login(
let cookie = Cookie::build(COOKIE_NAME, jwt)
.path(admin_path())
- .max_age(time::Duration::minutes(20))
+ .max_age(rocket::time::Duration::minutes(20))
.same_site(SameSite::Strict)
.http_only(true)
.finish();
@@ -297,7 +300,7 @@ fn test_smtp(data: Json<InviteData>, _token: AdminToken) -> EmptyResult {
}
#[get("/logout")]
-fn logout(mut cookies: Cookies, referer: Referer) -> Redirect {
+fn logout(cookies: &CookieJar, referer: Referer) -> Redirect {
cookies.remove(Cookie::named(COOKIE_NAME));
Redirect::to(admin_url(referer))
}
@@ -462,23 +465,23 @@ struct GitCommit {
sha: String,
}
-fn get_github_api<T: DeserializeOwned>(url: &str) -> Result<T, Error> {
+async fn get_github_api<T: DeserializeOwned>(url: &str) -> Result<T, Error> {
let github_api = get_reqwest_client();
- Ok(github_api.get(url).send()?.error_for_status()?.json::<T>()?)
+ Ok(github_api.get(url).send().await?.error_for_status()?.json::<T>().await?)
}
-fn has_http_access() -> bool {
+async fn has_http_access() -> bool {
let http_access = get_reqwest_client();
- match http_access.head("https://github.com/dani-garcia/vaultwarden").send() {
+ match http_access.head("https://github.com/dani-garcia/vaultwarden").send().await {
Ok(r) => r.status().is_success(),
_ => false,
}
}
#[get("/diagnostics")]
-fn diagnostics(_token: AdminToken, ip_header: IpHeader, conn: DbConn) -> ApiResult<Html<String>> {
+async fn diagnostics(_token: AdminToken, ip_header: IpHeader, conn: DbConn) -> ApiResult<Html<String>> {
use crate::util::read_file_string;
use chrono::prelude::*;
use std::net::ToSocketAddrs;
@@ -497,7 +500,7 @@ fn diagnostics(_token: AdminToken, ip_header: IpHeader, conn: DbConn) -> ApiResu
// Execute some environment checks
let running_within_docker = is_running_in_docker();
- let has_http_access = has_http_access();
+ let has_http_access = has_http_access().await;
let uses_proxy = env::var_os("HTTP_PROXY").is_some()
|| env::var_os("http_proxy").is_some()
|| env::var_os("HTTPS_PROXY").is_some()
@@ -513,11 +516,14 @@ fn diagnostics(_token: AdminToken, ip_header: IpHeader, conn: DbConn) -> ApiResu
// TODO: Maybe we need to cache this using a LazyStatic or something. Github only allows 60 requests per hour, and we use 3 here already.
let (latest_release, latest_commit, latest_web_build) = if has_http_access {
(
- match get_github_api::<GitRelease>("https://api.github.com/repos/dani-garcia/vaultwarden/releases/latest") {
+ match get_github_api::<GitRelease>("https://api.github.com/repos/dani-garcia/vaultwarden/releases/latest")
+ .await
+ {
Ok(r) => r.tag_name,
_ => "-".to_string(),
},
- match get_github_api::<GitCommit>("https://api.github.com/repos/dani-garcia/vaultwarden/commits/main") {
+ match get_github_api::<GitCommit>("https://api.github.com/repos/dani-garcia/vaultwarden/commits/main").await
+ {
Ok(mut c) => {
c.sha.truncate(8);
c.sha
@@ -531,7 +537,9 @@ fn diagnostics(_token: AdminToken, ip_header: IpHeader, conn: DbConn) -> ApiResu
} else {
match get_github_api::<GitRelease>(
"https://api.github.com/repos/dani-garcia/bw_web_builds/releases/latest",
- ) {
+ )
+ .await
+ {
Ok(r) => r.tag_name.trim_start_matches('v').to_string(),
_ => "-".to_string(),
}
@@ -562,7 +570,7 @@ fn diagnostics(_token: AdminToken, ip_header: IpHeader, conn: DbConn) -> ApiResu
"ip_header_config": &CONFIG.ip_header(),
"uses_proxy": uses_proxy,
"db_type": *DB_TYPE,
- "db_version": get_sql_server_version(&conn),
+ "db_version": get_sql_server_version(&conn).await,
"admin_url": format!("{}/diagnostics", admin_url(Referer(None))),
"overrides": &CONFIG.get_overrides().join(", "),
"server_time_local": Local::now().format("%Y-%m-%d %H:%M:%S %Z").to_string(),
@@ -591,9 +599,9 @@ fn delete_config(_token: AdminToken) -> EmptyResult {
}
#[post("/config/backup_db")]
-fn backup_db(_token: AdminToken, conn: DbConn) -> EmptyResult {
+async fn backup_db(_token: AdminToken, conn: DbConn) -> EmptyResult {
if *CAN_BACKUP {
- backup_database(&conn)
+ backup_database(&conn).await
} else {
err!("Can't back up current DB (Only SQLite supports this feature)");
}
@@ -601,21 +609,22 @@ fn backup_db(_token: AdminToken, conn: DbConn) -> EmptyResult {
pub struct AdminToken {}
-impl<'a, 'r> FromRequest<'a, 'r> for AdminToken {
+#[rocket::async_trait]
+impl<'r> FromRequest<'r> for AdminToken {
type Error = &'static str;
- fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, Self::Error> {
+ async fn from_request(request: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
if CONFIG.disable_admin_token() {
Outcome::Success(AdminToken {})
} else {
- let mut cookies = request.cookies();
+ let cookies = request.cookies();
let access_token = match cookies.get(COOKIE_NAME) {
Some(cookie) => cookie.value(),
None => return Outcome::Forward(()), // If there is no cookie, redirect to login
};
- let ip = match request.guard::<ClientIp>() {
+ let ip = match ClientIp::from_request(request).await {
Outcome::Success(ip) => ip.ip,
_ => err_handler!("Error getting Client IP"),
};
diff --git a/src/api/core/accounts.rs b/src/api/core/accounts.rs
@@ -1,5 +1,5 @@
use chrono::Utc;
-use rocket_contrib::json::Json;
+use rocket::serde::json::Json;
use serde_json::Value;
use crate::{
diff --git a/src/api/core/ciphers.rs b/src/api/core/ciphers.rs
@@ -1,13 +1,14 @@
use std::collections::{HashMap, HashSet};
-use std::path::{Path, PathBuf};
use chrono::{NaiveDateTime, Utc};
-use rocket::{http::ContentType, request::Form, Data, Route};
-use rocket_contrib::json::Json;
+use rocket::fs::TempFile;
+use rocket::serde::json::Json;
+use rocket::{
+ form::{Form, FromForm},
+ Route,
+};
use serde_json::Value;
-use multipart::server::{save::SavedData, Multipart, SaveResult};
-
use crate::{
api::{self, EmptyResult, JsonResult, JsonUpcase, Notify, PasswordData, UpdateType},
auth::Headers,
@@ -79,9 +80,9 @@ pub fn routes() -> Vec<Route> {
]
}
-pub fn purge_trashed_ciphers(pool: DbPool) {
+pub async fn purge_trashed_ciphers(pool: DbPool) {
debug!("Purging trashed ciphers");
- if let Ok(conn) = pool.get() {
+ if let Ok(conn) = pool.get().await {
Cipher::purge_trash(&conn);
} else {
error!("Failed to get DB connection while purging trashed ciphers")
@@ -90,12 +91,12 @@ pub fn purge_trashed_ciphers(pool: DbPool) {
#[derive(FromForm, Default)]
struct SyncData {
- #[form(field = "excludeDomains")]
+ #[field(name = "excludeDomains")]
exclude_domains: bool, // Default: 'false'
}
#[get("/sync?<data..>")]
-fn sync(data: Form<SyncData>, headers: Headers, conn: DbConn) -> Json<Value> {
+fn sync(data: SyncData, headers: Headers, conn: DbConn) -> Json<Value> {
let user_json = headers.user.to_json(&conn);
let folders = Folder::find_by_user(&headers.user.uuid, &conn);
@@ -828,6 +829,12 @@ fn post_attachment_v2(
})))
}
+#[derive(FromForm)]
+struct UploadData<'f> {
+ key: Option<String>,
+ data: TempFile<'f>,
+}
+
/// Saves the data content of an attachment to a file. This is common code
/// shared between the v2 and legacy attachment APIs.
///
@@ -836,22 +843,21 @@ fn post_attachment_v2(
///
/// When used with the v2 API, post_attachment_v2() has already created the
/// database record, which is passed in as `attachment`.
-fn save_attachment(
+async fn save_attachment(
mut attachment: Option<Attachment>,
cipher_uuid: String,
- data: Data,
- content_type: &ContentType,
+ data: Form<UploadData<'_>>,
headers: &Headers,
- conn: &DbConn,
- nt: Notify,
-) -> Result<Cipher, crate::error::Error> {
- let cipher = match Cipher::find_by_uuid(&cipher_uuid, conn) {
+ conn: DbConn,
+ nt: Notify<'_>,
+) -> Result<(Cipher, DbConn), crate::error::Error> {
+ let cipher = match Cipher::find_by_uuid(&cipher_uuid, &conn) {
Some(cipher) => cipher,
- None => err_discard!("Cipher doesn't exist", data),
+ None => err!("Cipher doesn't exist"),
};
- if !cipher.is_write_accessible_to_user(&headers.user.uuid, conn) {
- err_discard!("Cipher is not write accessible", data)
+ if !cipher.is_write_accessible_to_user(&headers.user.uuid, &conn) {
+ err!("Cipher is not write accessible")
}
// In the v2 API, the attachment record has already been created,
@@ -863,11 +869,11 @@ fn save_attachment(
let size_limit = if let Some(ref user_uuid) = cipher.user_uuid {
match CONFIG.user_attachment_limit() {
- Some(0) => err_discard!("Attachments are disabled", data),
+ Some(0) => err!("Attachments are disabled"),
Some(limit_kb) => {
- let left = (limit_kb * 1024) - Attachment::size_by_user(user_uuid, conn) + size_adjust;
+ let left = (limit_kb * 1024) - Attachment::size_by_user(user_uuid, &conn) + size_adjust;
if left <= 0 {
- err_discard!("Attachment storage limit reached! Delete some attachments to free up space", data)
+ err!("Attachment storage limit reached! Delete some attachments to free up space")
}
Some(left as u64)
}
@@ -875,130 +881,78 @@ fn save_attachment(
}
} else if let Some(ref org_uuid) = cipher.organization_uuid {
match CONFIG.org_attachment_limit() {
- Some(0) => err_discard!("Attachments are disabled", data),
+ Some(0) => err!("Attachments are disabled"),
Some(limit_kb) => {
- let left = (limit_kb * 1024) - Attachment::size_by_org(org_uuid, conn) + size_adjust;
+ let left = (limit_kb * 1024) - Attachment::size_by_org(org_uuid, &conn) + size_adjust;
if left <= 0 {
- err_discard!("Attachment storage limit reached! Delete some attachments to free up space", data)
+ err!("Attachment storage limit reached! Delete some attachments to free up space")
}
Some(left as u64)
}
None => None,
}
} else {
- err_discard!("Cipher is neither owned by a user nor an organization", data);
+ err!("Cipher is neither owned by a user nor an organization");
};
- let mut params = content_type.params();
- let boundary_pair = params.next().expect("No boundary provided");
- let boundary = boundary_pair.1;
-
- let base_path = Path::new(&CONFIG.attachments_folder()).join(&cipher_uuid);
- let mut path = PathBuf::new();
+ let mut data = data.into_inner();
- let mut attachment_key = None;
- let mut error = None;
-
- Multipart::with_body(data.open(), boundary)
- .foreach_entry(|mut field| {
- match &*field.headers.name {
- "key" => {
- use std::io::Read;
- let mut key_buffer = String::new();
- if field.data.read_to_string(&mut key_buffer).is_ok() {
- attachment_key = Some(key_buffer);
- }
- }
- "data" => {
- // In the legacy API, this is the encrypted filename
- // provided by the client, stored to the database as-is.
- // In the v2 API, this value doesn't matter, as it was
- // already provided and stored via an earlier API call.
- let encrypted_filename = field.headers.filename;
-
- // This random ID is used as the name of the file on disk.
- // In the legacy API, we need to generate this value here.
- // In the v2 API, we use the value from post_attachment_v2().
- let file_id = match &attachment {
- Some(attachment) => attachment.id.clone(), // v2 API
- None => crypto::generate_attachment_id(), // Legacy API
- };
- path = base_path.join(&file_id);
-
- let size =
- match field.data.save().memory_threshold(0).size_limit(size_limit).with_path(path.clone()) {
- SaveResult::Full(SavedData::File(_, size)) => size as i32,
- SaveResult::Full(other) => {
- error = Some(format!("Attachment is not a file: {:?}", other));
- return;
- }
- SaveResult::Partial(_, reason) => {
- error = Some(format!("Attachment storage limit exceeded with this file: {:?}", reason));
- return;
- }
- SaveResult::Error(e) => {
- error = Some(format!("Error: {:?}", e));
- return;
- }
- };
-
- if let Some(attachment) = &mut attachment {
- // v2 API
-
- // Check the actual size against the size initially provided by
- // the client. Upstream allows +/- 1 MiB deviation from this
- // size, but it's not clear when or why this is needed.
- const LEEWAY: i32 = 1024 * 1024; // 1 MiB
- let min_size = attachment.file_size - LEEWAY;
- let max_size = attachment.file_size + LEEWAY;
-
- if min_size <= size && size <= max_size {
- if size != attachment.file_size {
- // Update the attachment with the actual file size.
- attachment.file_size = size;
- attachment.save(conn).expect("Error updating attachment");
- }
- } else {
- attachment.delete(conn).ok();
+ if let Some(size_limit) = size_limit {
+ if data.data.len() > size_limit {
+ err!("Attachment storage limit exceeded with this file");
+ }
+ }
- let err_msg = "Attachment size mismatch".to_string();
- error!("{} (expected within [{}, {}], got {})", err_msg, min_size, max_size, size);
- error = Some(err_msg);
- }
- } else {
- // Legacy API
+ let file_id = match &attachment {
+ Some(attachment) => attachment.id.clone(), // v2 API
+ None => crypto::generate_attachment_id(), // Legacy API
+ };
- if encrypted_filename.is_none() {
- error = Some("No filename provided".to_string());
- return;
- }
- if attachment_key.is_none() {
- error = Some("No attachment key provided".to_string());
- return;
- }
- let attachment = Attachment::new(
- file_id,
- cipher_uuid.clone(),
- encrypted_filename.unwrap(),
- size,
- attachment_key.clone(),
- );
- attachment.save(conn).expect("Error saving attachment");
- }
- }
- _ => error!("Invalid multipart name"),
+ let folder_path = tokio::fs::canonicalize(&CONFIG.attachments_folder()).await?.join(&cipher_uuid);
+ let file_path = folder_path.join(&file_id);
+ tokio::fs::create_dir_all(&folder_path).await?;
+
+ let size = data.data.len() as i32;
+ if let Some(attachment) = &mut attachment {
+ // v2 API
+
+ // Check the actual size against the size initially provided by
+ // the client. Upstream allows +/- 1 MiB deviation from this
+ // size, but it's not clear when or why this is needed.
+ const LEEWAY: i32 = 1024 * 1024; // 1 MiB
+ let min_size = attachment.file_size - LEEWAY;
+ let max_size = attachment.file_size + LEEWAY;
+
+ if min_size <= size && size <= max_size {
+ if size != attachment.file_size {
+ // Update the attachment with the actual file size.
+ attachment.file_size = size;
+ attachment.save(&conn).expect("Error updating attachment");
}
- })
- .expect("Error processing multipart data");
+ } else {
+ attachment.delete(&conn).ok();
+
+ err!(format!("Attachment size mismatch (expected within [{}, {}], got {})", min_size, max_size, size));
+ }
+ } else {
+ // Legacy API
+ let encrypted_filename = data.data.raw_name().map(|s| s.dangerous_unsafe_unsanitized_raw().to_string());
- if let Some(ref e) = error {
- std::fs::remove_file(path).ok();
- err!(e);
+ if encrypted_filename.is_none() {
+ err!("No filename provided")
+ }
+ if data.key.is_none() {
+ err!("No attachment key provided")
+ }
+ let attachment = Attachment::new(file_id, cipher_uuid.clone(), encrypted_filename.unwrap(), size, data.key);
+ attachment.save(&conn).expect("Error saving attachment");
}
- nt.send_cipher_update(UpdateType::CipherUpdate, &cipher, &cipher.update_users_revision(conn));
+ data.data.persist_to(file_path).await?;
+
+ nt.send_cipher_update(UpdateType::CipherUpdate, &cipher, &cipher.update_users_revision(&conn));
- Ok(cipher)
+ Ok((cipher, conn))
}
/// v2 API for uploading the actual data content of an attachment.
@@ -1006,14 +960,13 @@ fn save_attachment(
/// /ciphers/<uuid>/attachment/v2 route, which would otherwise conflict
/// with this one.
#[post("/ciphers/<uuid>/attachment/<attachment_id>", format = "multipart/form-data", data = "<data>", rank = 1)]
-fn post_attachment_v2_data(
+async fn post_attachment_v2_data(
uuid: String,
attachment_id: String,
- data: Data,
- content_type: &ContentType,
+ data: Form<UploadData<'_>>,
headers: Headers,
conn: DbConn,
- nt: Notify,
+ nt: Notify<'_>,
) -> EmptyResult {
let attachment = match Attachment::find_by_id(&attachment_id, &conn) {
Some(attachment) if uuid == attachment.cipher_uuid => Some(attachment),
@@ -1021,54 +974,51 @@ fn post_attachment_v2_data(
None => err!("Attachment doesn't exist"),
};
- save_attachment(attachment, uuid, data, content_type, &headers, &conn, nt)?;
+ save_attachment(attachment, uuid, data, &headers, conn, nt).await?;
Ok(())
}
/// Legacy API for creating an attachment associated with a cipher.
#[post("/ciphers/<uuid>/attachment", format = "multipart/form-data", data = "<data>")]
-fn post_attachment(
+async fn post_attachment(
uuid: String,
- data: Data,
- content_type: &ContentType,
+ data: Form<UploadData<'_>>,
headers: Headers,
conn: DbConn,
- nt: Notify,
+ nt: Notify<'_>,
) -> JsonResult {
// Setting this as None signifies to save_attachment() that it should create
// the attachment database record as well as saving the data to disk.
let attachment = None;
- let cipher = save_attachment(attachment, uuid, data, content_type, &headers, &conn, nt)?;
+ let (cipher, conn) = save_attachment(attachment, uuid, data, &headers, conn, nt).await?;
Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, &conn)))
}
#[post("/ciphers/<uuid>/attachment-admin", format = "multipart/form-data", data = "<data>")]
-fn post_attachment_admin(
+async fn post_attachment_admin(
uuid: String,
- data: Data,
- content_type: &ContentType,
+ data: Form<UploadData<'_>>,
headers: Headers,
conn: DbConn,
- nt: Notify,
+ nt: Notify<'_>,
) -> JsonResult {
- post_attachment(uuid, data, content_type, headers, conn, nt)
+ post_attachment(uuid, data, headers, conn, nt).await
}
#[post("/ciphers/<uuid>/attachment/<attachment_id>/share", format = "multipart/form-data", data = "<data>")]
-fn post_attachment_share(
+async fn post_attachment_share(
uuid: String,
attachment_id: String,
- data: Data,
- content_type: &ContentType,
+ data: Form<UploadData<'_>>,
headers: Headers,
conn: DbConn,
- nt: Notify,
+ nt: Notify<'_>,
) -> JsonResult {
_delete_cipher_attachment_by_id(&uuid, &attachment_id, &headers, &conn, &nt)?;
- post_attachment(uuid, data, content_type, headers, conn, nt)
+ post_attachment(uuid, data, headers, conn, nt).await
}
#[post("/ciphers/<uuid>/attachment/<attachment_id>/delete-admin")]
@@ -1248,13 +1198,13 @@ fn move_cipher_selected_put(
#[derive(FromForm)]
struct OrganizationId {
- #[form(field = "organizationId")]
+ #[field(name = "organizationId")]
org_id: String,
}
#[post("/ciphers/purge?<organization..>", data = "<data>")]
fn delete_all(
- organization: Option<Form<OrganizationId>>,
+ organization: Option<OrganizationId>,
data: JsonUpcase<PasswordData>,
headers: Headers,
conn: DbConn,
diff --git a/src/api/core/emergency_access.rs b/src/api/core/emergency_access.rs
@@ -1,6 +1,6 @@
use chrono::{Duration, Utc};
+use rocket::serde::json::Json;
use rocket::Route;
-use rocket_contrib::json::Json;
use serde_json::Value;
use std::borrow::Borrow;
@@ -709,13 +709,13 @@ fn check_emergency_access_allowed() -> EmptyResult {
Ok(())
}
-pub fn emergency_request_timeout_job(pool: DbPool) {
+pub async fn emergency_request_timeout_job(pool: DbPool) {
debug!("Start emergency_request_timeout_job");
if !CONFIG.emergency_access_allowed() {
return;
}
- if let Ok(conn) = pool.get() {
+ if let Ok(conn) = pool.get().await {
let emergency_access_list = EmergencyAccess::find_all_recoveries(&conn);
if emergency_access_list.is_empty() {
@@ -756,13 +756,13 @@ pub fn emergency_request_timeout_job(pool: DbPool) {
}
}
-pub fn emergency_notification_reminder_job(pool: DbPool) {
+pub async fn emergency_notification_reminder_job(pool: DbPool) {
debug!("Start emergency_notification_reminder_job");
if !CONFIG.emergency_access_allowed() {
return;
}
- if let Ok(conn) = pool.get() {
+ if let Ok(conn) = pool.get().await {
let emergency_access_list = EmergencyAccess::find_all_recoveries(&conn);
if emergency_access_list.is_empty() {
diff --git a/src/api/core/folders.rs b/src/api/core/folders.rs
@@ -1,4 +1,4 @@
-use rocket_contrib::json::Json;
+use rocket::serde::json::Json;
use serde_json::Value;
use crate::{
diff --git a/src/api/core/mod.rs b/src/api/core/mod.rs
@@ -31,8 +31,8 @@ pub fn routes() -> Vec<Route> {
//
// Move this somewhere else
//
+use rocket::serde::json::Json;
use rocket::Route;
-use rocket_contrib::json::Json;
use serde_json::Value;
use crate::{
@@ -144,7 +144,7 @@ fn put_eq_domains(data: JsonUpcase<EquivDomainData>, headers: Headers, conn: DbC
}
#[get("/hibp/breach?<username>")]
-fn hibp_breach(username: String) -> JsonResult {
+async fn hibp_breach(username: String) -> JsonResult {
let url = format!(
"https://haveibeenpwned.com/api/v3/breachedaccount/{}?truncateResponse=false&includeUnverified=false",
username
@@ -153,14 +153,14 @@ fn hibp_breach(username: String) -> JsonResult {
if let Some(api_key) = crate::CONFIG.hibp_api_key() {
let hibp_client = get_reqwest_client();
- let res = hibp_client.get(&url).header("hibp-api-key", api_key).send()?;
+ let res = hibp_client.get(&url).header("hibp-api-key", api_key).send().await?;
// If we get a 404, return a 404, it means no breached accounts
if res.status() == 404 {
return Err(Error::empty().with_code(404));
}
- let value: Value = res.error_for_status()?.json()?;
+ let value: Value = res.error_for_status()?.json().await?;
Ok(Json(value))
} else {
Ok(Json(json!([{
diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs
@@ -1,6 +1,6 @@
use num_traits::FromPrimitive;
-use rocket::{request::Form, Route};
-use rocket_contrib::json::Json;
+use rocket::serde::json::Json;
+use rocket::Route;
use serde_json::Value;
use crate::{
@@ -469,12 +469,12 @@ fn put_collection_users(
#[derive(FromForm)]
struct OrgIdData {
- #[form(field = "organizationId")]
+ #[field(name = "organizationId")]
organization_id: String,
}
#[get("/ciphers/organization-details?<data..>")]
-fn get_org_details(data: Form<OrgIdData>, headers: Headers, conn: DbConn) -> Json<Value> {
+fn get_org_details(data: OrgIdData, headers: Headers, conn: DbConn) -> Json<Value> {
let ciphers = Cipher::find_by_org(&data.organization_id, &conn);
let ciphers_json: Vec<Value> =
ciphers.iter().map(|c| c.to_json(&headers.host, &headers.user.uuid, &conn)).collect();
@@ -1097,14 +1097,14 @@ struct RelationsData {
#[post("/ciphers/import-organization?<query..>", data = "<data>")]
fn post_org_import(
- query: Form<OrgIdData>,
+ query: OrgIdData,
data: JsonUpcase<ImportData>,
headers: AdminHeaders,
conn: DbConn,
nt: Notify,
) -> EmptyResult {
let data: ImportData = data.into_inner().data;
- let org_id = query.into_inner().organization_id;
+ let org_id = query.organization_id;
// Read and create the collections
let collections: Vec<_> = data
diff --git a/src/api/core/sends.rs b/src/api/core/sends.rs
@@ -1,9 +1,10 @@
-use std::{io::Read, path::Path};
+use std::path::Path;
use chrono::{DateTime, Duration, Utc};
-use multipart::server::{save::SavedData, Multipart, SaveResult};
-use rocket::{http::ContentType, response::NamedFile, Data};
-use rocket_contrib::json::Json;
+use rocket::form::Form;
+use rocket::fs::NamedFile;
+use rocket::fs::TempFile;
+use rocket::serde::json::Json;
use serde_json::Value;
use crate::{
@@ -31,9 +32,9 @@ pub fn routes() -> Vec<rocket::Route> {
]
}
-pub fn purge_sends(pool: DbPool) {
+pub async fn purge_sends(pool: DbPool) {
debug!("Purging sends");
- if let Ok(conn) = pool.get() {
+ if let Ok(conn) = pool.get().await {
Send::purge(&conn);
} else {
error!("Failed to get DB connection while purging sends")
@@ -177,25 +178,23 @@ fn post_send(data: JsonUpcase<SendData>, headers: Headers, conn: DbConn, nt: Not
Ok(Json(send.to_json()))
}
+#[derive(FromForm)]
+struct UploadData<'f> {
+ model: Json<crate::util::UpCase<SendData>>,
+ data: TempFile<'f>,
+}
+
#[post("/sends/file", format = "multipart/form-data", data = "<data>")]
-fn post_send_file(data: Data, content_type: &ContentType, headers: Headers, conn: DbConn, nt: Notify) -> JsonResult {
+async fn post_send_file(data: Form<UploadData<'_>>, headers: Headers, conn: DbConn, nt: Notify<'_>) -> JsonResult {
enforce_disable_send_policy(&headers, &conn)?;
- let boundary = content_type.params().next().expect("No boundary provided").1;
-
- let mut mpart = Multipart::with_body(data.open(), boundary);
-
- // First entry is the SendData JSON
- let mut model_entry = match mpart.read_entry()? {
- Some(e) if &*e.headers.name == "model" => e,
- Some(_) => err!("Invalid entry name"),
- None => err!("No model entry present"),
- };
+ let UploadData {
+ model,
+ mut data,
+ } = data.into_inner();
+ let model = model.into_inner().data;
- let mut buf = String::new();
- model_entry.data.read_to_string(&mut buf)?;
- let data = serde_json::from_str::<crate::util::UpCase<SendData>>(&buf)?;
- enforce_disable_hide_email_policy(&data.data, &headers, &conn)?;
+ enforce_disable_hide_email_policy(&model, &headers, &conn)?;
// Get the file length and add an extra 5% to avoid issues
const SIZE_525_MB: u64 = 550_502_400;
@@ -212,45 +211,27 @@ fn post_send_file(data: Data, content_type: &ContentType, headers: Headers, conn
None => SIZE_525_MB,
};
- // Create the Send
- let mut send = create_send(data.data, headers.user.uuid)?;
- let file_id = crate::crypto::generate_send_id();
-
+ let mut send = create_send(model, headers.user.uuid)?;
if send.atype != SendType::File as i32 {
err!("Send content is not a file");
}
- let file_path = Path::new(&CONFIG.sends_folder()).join(&send.uuid).join(&file_id);
-
- // Read the data entry and save the file
- let mut data_entry = match mpart.read_entry()? {
- Some(e) if &*e.headers.name == "data" => e,
- Some(_) => err!("Invalid entry name"),
- None => err!("No model entry present"),
- };
+ let size = data.len();
+ if size > size_limit {
+ err!("Attachment storage limit exceeded with this file");
+ }
- let size = match data_entry.data.save().memory_threshold(0).size_limit(size_limit).with_path(&file_path) {
- SaveResult::Full(SavedData::File(_, size)) => size as i32,
- SaveResult::Full(other) => {
- std::fs::remove_file(&file_path).ok();
- err!(format!("Attachment is not a file: {:?}", other));
- }
- SaveResult::Partial(_, reason) => {
- std::fs::remove_file(&file_path).ok();
- err!(format!("Attachment storage limit exceeded with this file: {:?}", reason));
- }
- SaveResult::Error(e) => {
- std::fs::remove_file(&file_path).ok();
- err!(format!("Error: {:?}", e));
- }
- };
+ let file_id = crate::crypto::generate_send_id();
+ let folder_path = tokio::fs::canonicalize(&CONFIG.sends_folder()).await?.join(&send.uuid);
+ let file_path = folder_path.join(&file_id);
+ tokio::fs::create_dir_all(&folder_path).await?;
+ data.persist_to(&file_path).await?;
- // Set ID and sizes
let mut data_value: Value = serde_json::from_str(&send.data)?;
if let Some(o) = data_value.as_object_mut() {
o.insert(String::from("Id"), Value::String(file_id));
o.insert(String::from("Size"), Value::Number(size.into()));
- o.insert(String::from("SizeName"), Value::String(crate::util::get_display_size(size)));
+ o.insert(String::from("SizeName"), Value::String(crate::util::get_display_size(size as i32)));
}
send.data = serde_json::to_string(&data_value)?;
@@ -367,10 +348,10 @@ fn post_access_file(
}
#[get("/sends/<send_id>/<file_id>?<t>")]
-fn download_send(send_id: SafeString, file_id: SafeString, t: String) -> Option<NamedFile> {
+async fn download_send(send_id: SafeString, file_id: SafeString, t: String) -> Option<NamedFile> {
if let Ok(claims) = crate::auth::decode_send(&t) {
if claims.sub == format!("{}/{}", send_id, file_id) {
- return NamedFile::open(Path::new(&CONFIG.sends_folder()).join(send_id).join(file_id)).ok();
+ return NamedFile::open(Path::new(&CONFIG.sends_folder()).join(send_id).join(file_id)).await.ok();
}
}
None
diff --git a/src/api/core/two_factor/authenticator.rs b/src/api/core/two_factor/authenticator.rs
@@ -1,6 +1,6 @@
use data_encoding::BASE32;
+use rocket::serde::json::Json;
use rocket::Route;
-use rocket_contrib::json::Json;
use crate::{
api::{
diff --git a/src/api/core/two_factor/duo.rs b/src/api/core/two_factor/duo.rs
@@ -1,7 +1,7 @@
use chrono::Utc;
use data_encoding::BASE64;
+use rocket::serde::json::Json;
use rocket::Route;
-use rocket_contrib::json::Json;
use crate::{
api::{core::two_factor::_generate_recover_code, ApiResult, EmptyResult, JsonResult, JsonUpcase, PasswordData},
@@ -152,7 +152,7 @@ fn check_duo_fields_custom(data: &EnableDuoData) -> bool {
}
#[post("/two-factor/duo", data = "<data>")]
-fn activate_duo(data: JsonUpcase<EnableDuoData>, headers: Headers, conn: DbConn) -> JsonResult {
+async fn activate_duo(data: JsonUpcase<EnableDuoData>, headers: Headers, conn: DbConn) -> JsonResult {
let data: EnableDuoData = data.into_inner().data;
let mut user = headers.user;
@@ -163,7 +163,7 @@ fn activate_duo(data: JsonUpcase<EnableDuoData>, headers: Headers, conn: DbConn)
let (data, data_str) = if check_duo_fields_custom(&data) {
let data_req: DuoData = data.into();
let data_str = serde_json::to_string(&data_req)?;
- duo_api_request("GET", "/auth/v2/check", "", &data_req).map_res("Failed to validate Duo credentials")?;
+ duo_api_request("GET", "/auth/v2/check", "", &data_req).await.map_res("Failed to validate Duo credentials")?;
(data_req.obscure(), data_str)
} else {
(DuoData::secret(), String::new())
@@ -185,11 +185,11 @@ fn activate_duo(data: JsonUpcase<EnableDuoData>, headers: Headers, conn: DbConn)
}
#[put("/two-factor/duo", data = "<data>")]
-fn activate_duo_put(data: JsonUpcase<EnableDuoData>, headers: Headers, conn: DbConn) -> JsonResult {
- activate_duo(data, headers, conn)
+async fn activate_duo_put(data: JsonUpcase<EnableDuoData>, headers: Headers, conn: DbConn) -> JsonResult {
+ activate_duo(data, headers, conn).await
}
-fn duo_api_request(method: &str, path: &str, params: &str, data: &DuoData) -> EmptyResult {
+async fn duo_api_request(method: &str, path: &str, params: &str, data: &DuoData) -> EmptyResult {
use reqwest::{header, Method};
use std::str::FromStr;
@@ -209,7 +209,8 @@ fn duo_api_request(method: &str, path: &str, params: &str, data: &DuoData) -> Em
.basic_auth(username, Some(password))
.header(header::USER_AGENT, "vaultwarden:Duo/1.0 (Rust)")
.header(header::DATE, date)
- .send()?
+ .send()
+ .await?
.error_for_status()?;
Ok(())
diff --git a/src/api/core/two_factor/email.rs b/src/api/core/two_factor/email.rs
@@ -1,6 +1,6 @@
use chrono::{Duration, NaiveDateTime, Utc};
+use rocket::serde::json::Json;
use rocket::Route;
-use rocket_contrib::json::Json;
use crate::{
api::{core::two_factor::_generate_recover_code, EmptyResult, JsonResult, JsonUpcase, PasswordData},
diff --git a/src/api/core/two_factor/mod.rs b/src/api/core/two_factor/mod.rs
@@ -1,7 +1,7 @@
use chrono::{Duration, Utc};
use data_encoding::BASE32;
+use rocket::serde::json::Json;
use rocket::Route;
-use rocket_contrib::json::Json;
use serde_json::Value;
use crate::{
@@ -158,14 +158,14 @@ fn disable_twofactor_put(data: JsonUpcase<DisableTwoFactorData>, headers: Header
disable_twofactor(data, headers, conn)
}
-pub fn send_incomplete_2fa_notifications(pool: DbPool) {
+pub async fn send_incomplete_2fa_notifications(pool: DbPool) {
debug!("Sending notifications for incomplete 2FA logins");
if CONFIG.incomplete_2fa_time_limit() <= 0 || !CONFIG.mail_enabled() {
return;
}
- let conn = match pool.get() {
+ let conn = match pool.get().await {
Ok(conn) => conn,
_ => {
error!("Failed to get DB connection in send_incomplete_2fa_notifications()");
diff --git a/src/api/core/two_factor/u2f.rs b/src/api/core/two_factor/u2f.rs
@@ -1,6 +1,6 @@
use once_cell::sync::Lazy;
+use rocket::serde::json::Json;
use rocket::Route;
-use rocket_contrib::json::Json;
use serde_json::Value;
use u2f::{
messages::{RegisterResponse, SignResponse, U2fSignRequest},
diff --git a/src/api/core/two_factor/webauthn.rs b/src/api/core/two_factor/webauthn.rs
@@ -1,5 +1,5 @@
+use rocket::serde::json::Json;
use rocket::Route;
-use rocket_contrib::json::Json;
use serde_json::Value;
use url::Url;
use webauthn_rs::{base64_data::Base64UrlSafeData, proto::*, AuthenticationState, RegistrationState, Webauthn};
diff --git a/src/api/core/two_factor/yubikey.rs b/src/api/core/two_factor/yubikey.rs
@@ -1,5 +1,5 @@
+use rocket::serde::json::Json;
use rocket::Route;
-use rocket_contrib::json::Json;
use serde_json::Value;
use yubico::{config::Config, verify};
diff --git a/src/api/icons.rs b/src/api/icons.rs
@@ -1,19 +1,19 @@
use std::{
collections::HashMap,
- fs::{create_dir_all, remove_file, symlink_metadata, File},
- io::prelude::*,
net::{IpAddr, ToSocketAddrs},
sync::{Arc, RwLock},
time::{Duration, SystemTime},
};
+use bytes::{Buf, Bytes, BytesMut};
+use futures::{stream::StreamExt, TryFutureExt};
use once_cell::sync::Lazy;
use regex::Regex;
-use reqwest::{blocking::Client, blocking::Response, header};
-use rocket::{
- http::ContentType,
- response::{Content, Redirect},
- Route,
+use reqwest::{header, Client, Response};
+use rocket::{http::ContentType, response::Redirect, Route};
+use tokio::{
+ fs::{create_dir_all, remove_file, symlink_metadata, File},
+ io::{AsyncReadExt, AsyncWriteExt},
};
use crate::{
@@ -104,27 +104,23 @@ fn icon_google(domain: String) -> Option<Redirect> {
}
#[get("/<domain>/icon.png")]
-fn icon_internal(domain: String) -> Cached<Content<Vec<u8>>> {
+async fn icon_internal(domain: String) -> Cached<(ContentType, Vec<u8>)> {
const FALLBACK_ICON: &[u8] = include_bytes!("../static/images/fallback-icon.png");
if !is_valid_domain(&domain) {
warn!("Invalid domain: {}", domain);
return Cached::ttl(
- Content(ContentType::new("image", "png"), FALLBACK_ICON.to_vec()),
+ (ContentType::new("image", "png"), FALLBACK_ICON.to_vec()),
CONFIG.icon_cache_negttl(),
true,
);
}
- match get_icon(&domain) {
+ match get_icon(&domain).await {
Some((icon, icon_type)) => {
- Cached::ttl(Content(ContentType::new("image", icon_type), icon), CONFIG.icon_cache_ttl(), true)
+ Cached::ttl((ContentType::new("image", icon_type), icon), CONFIG.icon_cache_ttl(), true)
}
- _ => Cached::ttl(
- Content(ContentType::new("image", "png"), FALLBACK_ICON.to_vec()),
- CONFIG.icon_cache_negttl(),
- true,
- ),
+ _ => Cached::ttl((ContentType::new("image", "png"), FALLBACK_ICON.to_vec()), CONFIG.icon_cache_negttl(), true),
}
}
@@ -317,15 +313,15 @@ fn is_domain_blacklisted(domain: &str) -> bool {
is_blacklisted
}
-fn get_icon(domain: &str) -> Option<(Vec<u8>, String)> {
+async fn get_icon(domain: &str) -> Option<(Vec<u8>, String)> {
let path = format!("{}/{}.png", CONFIG.icon_cache_folder(), domain);
// Check for expiration of negatively cached copy
- if icon_is_negcached(&path) {
+ if icon_is_negcached(&path).await {
return None;
}
- if let Some(icon) = get_cached_icon(&path) {
+ if let Some(icon) = get_cached_icon(&path).await {
let icon_type = match get_icon_type(&icon) {
Some(x) => x,
_ => "x-icon",
@@ -338,31 +334,31 @@ fn get_icon(domain: &str) -> Option<(Vec<u8>, String)> {
}
// Get the icon, or None in case of error
- match download_icon(domain) {
+ match download_icon(domain).await {
Ok((icon, icon_type)) => {
- save_icon(&path, &icon);
- Some((icon, icon_type.unwrap_or("x-icon").to_string()))
+ save_icon(&path, &icon).await;
+ Some((icon.to_vec(), icon_type.unwrap_or("x-icon").to_string()))
}
Err(e) => {
warn!("Unable to download icon: {:?}", e);
let miss_indicator = path + ".miss";
- save_icon(&miss_indicator, &[]);
+ save_icon(&miss_indicator, &[]).await;
None
}
}
}
-fn get_cached_icon(path: &str) -> Option<Vec<u8>> {
+async fn get_cached_icon(path: &str) -> Option<Vec<u8>> {
// Check for expiration of successfully cached copy
- if icon_is_expired(path) {
+ if icon_is_expired(path).await {
return None;
}
// Try to read the cached icon, and return it if it exists
- if let Ok(mut f) = File::open(path) {
+ if let Ok(mut f) = File::open(path).await {
let mut buffer = Vec::new();
- if f.read_to_end(&mut buffer).is_ok() {
+ if f.read_to_end(&mut buffer).await.is_ok() {
return Some(buffer);
}
}
@@ -370,22 +366,22 @@ fn get_cached_icon(path: &str) -> Option<Vec<u8>> {
None
}
-fn file_is_expired(path: &str, ttl: u64) -> Result<bool, Error> {
- let meta = symlink_metadata(path)?;
+async fn file_is_expired(path: &str, ttl: u64) -> Result<bool, Error> {
+ let meta = symlink_metadata(path).await?;
let modified = meta.modified()?;
let age = SystemTime::now().duration_since(modified)?;
Ok(ttl > 0 && ttl <= age.as_secs())
}
-fn icon_is_negcached(path: &str) -> bool {
+async fn icon_is_negcached(path: &str) -> bool {
let miss_indicator = path.to_owned() + ".miss";
- let expired = file_is_expired(&miss_indicator, CONFIG.icon_cache_negttl());
+ let expired = file_is_expired(&miss_indicator, CONFIG.icon_cache_negttl()).await;
match expired {
// No longer negatively cached, drop the marker
Ok(true) => {
- if let Err(e) = remove_file(&miss_indicator) {
+ if let Err(e) = remove_file(&miss_indicator).await {
error!("Could not remove negative cache indicator for icon {:?}: {:?}", path, e);
}
false
@@ -397,8 +393,8 @@ fn icon_is_negcached(path: &str) -> bool {
}
}
-fn icon_is_expired(path: &str) -> bool {
- let expired = file_is_expired(path, CONFIG.icon_cache_ttl());
+async fn icon_is_expired(path: &str) -> bool {
+ let expired = file_is_expired(path, CONFIG.icon_cache_ttl()).await;
expired.unwrap_or(true)
}
@@ -521,13 +517,13 @@ struct IconUrlResult {
/// let icon_result = get_icon_url("github.com")?;
/// let icon_result = get_icon_url("vaultwarden.discourse.group")?;
/// ```
-fn get_icon_url(domain: &str) -> Result<IconUrlResult, Error> {
+async fn get_icon_url(domain: &str) -> Result<IconUrlResult, Error> {
// Default URL with secure and insecure schemes
let ssldomain = format!("https://{}", domain);
let httpdomain = format!("http://{}", domain);
// First check the domain as given during the request for both HTTPS and HTTP.
- let resp = match get_page(&ssldomain).or_else(|_| get_page(&httpdomain)) {
+ let resp = match get_page(&ssldomain).or_else(|_| get_page(&httpdomain)).await {
Ok(c) => Ok(c),
Err(e) => {
let mut sub_resp = Err(e);
@@ -546,7 +542,7 @@ fn get_icon_url(domain: &str) -> Result<IconUrlResult, Error> {
let httpbase = format!("http://{}", base_domain);
debug!("[get_icon_url]: Trying without subdomains '{}'", base_domain);
- sub_resp = get_page(&sslbase).or_else(|_| get_page(&httpbase));
+ sub_resp = get_page(&sslbase).or_else(|_| get_page(&httpbase)).await;
}
// When the domain is not an IP, and has less then 2 dots, try to add www. infront of it.
@@ -557,7 +553,7 @@ fn get_icon_url(domain: &str) -> Result<IconUrlResult, Error> {
let httpwww = format!("http://{}", www_domain);
debug!("[get_icon_url]: Trying with www. prefix '{}'", www_domain);
- sub_resp = get_page(&sslwww).or_else(|_| get_page(&httpwww));
+ sub_resp = get_page(&sslwww).or_else(|_| get_page(&httpwww)).await;
}
}
@@ -581,7 +577,7 @@ fn get_icon_url(domain: &str) -> Result<IconUrlResult, Error> {
iconlist.push(Icon::new(35, String::from(url.join("/favicon.ico").unwrap())));
// 384KB should be more than enough for the HTML, though as we only really need the HTML header.
- let mut limited_reader = content.take(384 * 1024);
+ let mut limited_reader = stream_to_bytes_limit(content, 384 * 1024).await?.reader();
use html5ever::tendril::TendrilSink;
let dom = html5ever::parse_document(markup5ever_rcdom::RcDom::default(), Default::default())
@@ -607,11 +603,11 @@ fn get_icon_url(domain: &str) -> Result<IconUrlResult, Error> {
})
}
-fn get_page(url: &str) -> Result<Response, Error> {
- get_page_with_referer(url, "")
+async fn get_page(url: &str) -> Result<Response, Error> {
+ get_page_with_referer(url, "").await
}
-fn get_page_with_referer(url: &str, referer: &str) -> Result<Response, Error> {
+async fn get_page_with_referer(url: &str, referer: &str) -> Result<Response, Error> {
if is_domain_blacklisted(url::Url::parse(url).unwrap().host_str().unwrap_or_default()) {
warn!("Favicon '{}' resolves to a blacklisted domain or IP!", url);
}
@@ -621,7 +617,7 @@ fn get_page_with_referer(url: &str, referer: &str) -> Result<Response, Error> {
client = client.header("Referer", referer)
}
- match client.send() {
+ match client.send().await {
Ok(c) => c.error_for_status().map_err(Into::into),
Err(e) => err_silent!(format!("{}", e)),
}
@@ -706,14 +702,14 @@ fn parse_sizes(sizes: Option<&str>) -> (u16, u16) {
(width, height)
}
-fn download_icon(domain: &str) -> Result<(Vec<u8>, Option<&str>), Error> {
+async fn download_icon(domain: &str) -> Result<(Bytes, Option<&str>), Error> {
if is_domain_blacklisted(domain) {
err_silent!("Domain is blacklisted", domain)
}
- let icon_result = get_icon_url(domain)?;
+ let icon_result = get_icon_url(domain).await?;
- let mut buffer = Vec::new();
+ let mut buffer = Bytes::new();
let mut icon_type: Option<&str> = None;
use data_url::DataUrl;
@@ -722,8 +718,12 @@ fn download_icon(domain: &str) -> Result<(Vec<u8>, Option<&str>), Error> {
if icon.href.starts_with("data:image") {
let datauri = DataUrl::process(&icon.href).unwrap();
// Check if we are able to decode the data uri
- match datauri.decode_to_vec() {
- Ok((body, _fragment)) => {
+ let mut body = BytesMut::new();
+ match datauri.decode::<_, ()>(|bytes| {
+ body.extend_from_slice(bytes);
+ Ok(())
+ }) {
+ Ok(_) => {
// Also check if the size is atleast 67 bytes, which seems to be the smallest png i could create
if body.len() >= 67 {
// Check if the icon type is allowed, else try an icon from the list.
@@ -733,17 +733,17 @@ fn download_icon(domain: &str) -> Result<(Vec<u8>, Option<&str>), Error> {
continue;
}
info!("Extracted icon from data:image uri for {}", domain);
- buffer = body;
+ buffer = body.freeze();
break;
}
}
_ => debug!("Extracted icon from data:image uri is invalid"),
};
} else {
- match get_page_with_referer(&icon.href, &icon_result.referer) {
- Ok(mut res) => {
- res.copy_to(&mut buffer)?;
- // Check if the icon type is allowed, else try an icon from the list.
+ match get_page_with_referer(&icon.href, &icon_result.referer).await {
+ Ok(res) => {
+ buffer = stream_to_bytes_limit(res, 512 * 1024).await?; // 512 KB for each icon max
+ // Check if the icon type is allowed, else try an icon from the list.
icon_type = get_icon_type(&buffer);
if icon_type.is_none() {
buffer.clear();
@@ -765,13 +765,13 @@ fn download_icon(domain: &str) -> Result<(Vec<u8>, Option<&str>), Error> {
Ok((buffer, icon_type))
}
-fn save_icon(path: &str, icon: &[u8]) {
- match File::create(path) {
+async fn save_icon(path: &str, icon: &[u8]) {
+ match File::create(path).await {
Ok(mut f) => {
- f.write_all(icon).expect("Error writing icon file");
+ f.write_all(icon).await.expect("Error writing icon file");
}
Err(ref e) if e.kind() == std::io::ErrorKind::NotFound => {
- create_dir_all(&CONFIG.icon_cache_folder()).expect("Error creating icon cache folder");
+ create_dir_all(&CONFIG.icon_cache_folder()).await.expect("Error creating icon cache folder");
}
Err(e) => {
warn!("Unable to save icon: {:?}", e);
@@ -820,8 +820,6 @@ impl reqwest::cookie::CookieStore for Jar {
}
fn cookies(&self, url: &url::Url) -> Option<header::HeaderValue> {
- use bytes::Bytes;
-
let cookie_store = self.0.read().unwrap();
let s = cookie_store
.get_request_values(url)
@@ -836,3 +834,12 @@ impl reqwest::cookie::CookieStore for Jar {
header::HeaderValue::from_maybe_shared(Bytes::from(s)).ok()
}
}
+
+async fn stream_to_bytes_limit(res: Response, max_size: usize) -> Result<Bytes, reqwest::Error> {
+ let mut stream = res.bytes_stream().take(max_size);
+ let mut buf = BytesMut::new();
+ while let Some(chunk) = stream.next().await {
+ buf.extend(chunk?);
+ }
+ Ok(buf.freeze())
+}
diff --git a/src/api/identity.rs b/src/api/identity.rs
@@ -1,10 +1,10 @@
use chrono::Utc;
use num_traits::FromPrimitive;
+use rocket::serde::json::Json;
use rocket::{
- request::{Form, FormItems, FromForm},
+ form::{Form, FromForm},
Route,
};
-use rocket_contrib::json::Json;
use serde_json::Value;
use crate::{
@@ -455,66 +455,57 @@ fn _json_err_twofactor(providers: &[i32], user_uuid: &str, conn: &DbConn) -> Api
// https://github.com/bitwarden/jslib/blob/master/common/src/models/request/tokenRequest.ts
// https://github.com/bitwarden/mobile/blob/master/src/Core/Models/Request/TokenRequest.cs
-#[derive(Debug, Clone, Default)]
+#[derive(Debug, Clone, Default, FromForm)]
#[allow(non_snake_case)]
struct ConnectData {
- // refresh_token, password, client_credentials (API key)
- grant_type: String,
+ #[field(name = uncased("grant_type"))]
+ #[field(name = uncased("granttype"))]
+ grant_type: String, // refresh_token, password, client_credentials (API key)
// Needed for grant_type="refresh_token"
+ #[field(name = uncased("refresh_token"))]
+ #[field(name = uncased("refreshtoken"))]
refresh_token: Option<String>,
// Needed for grant_type = "password" | "client_credentials"
- client_id: Option<String>, // web, cli, desktop, browser, mobile
- client_secret: Option<String>, // API key login (cli only)
+ #[field(name = uncased("client_id"))]
+ #[field(name = uncased("clientid"))]
+ client_id: Option<String>, // web, cli, desktop, browser, mobile
+ #[field(name = uncased("client_secret"))]
+ #[field(name = uncased("clientsecret"))]
+ client_secret: Option<String>,
+ #[field(name = uncased("password"))]
password: Option<String>,
+ #[field(name = uncased("scope"))]
scope: Option<String>,
+ #[field(name = uncased("username"))]
username: Option<String>,
+ #[field(name = uncased("device_identifier"))]
+ #[field(name = uncased("deviceidentifier"))]
device_identifier: Option<String>,
+ #[field(name = uncased("device_name"))]
+ #[field(name = uncased("devicename"))]
device_name: Option<String>,
+ #[field(name = uncased("device_type"))]
+ #[field(name = uncased("devicetype"))]
device_type: Option<String>,
+ #[field(name = uncased("device_push_token"))]
+ #[field(name = uncased("devicepushtoken"))]
device_push_token: Option<String>, // Unused; mobile device push not yet supported.
// Needed for two-factor auth
+ #[field(name = uncased("two_factor_provider"))]
+ #[field(name = uncased("twofactorprovider"))]
two_factor_provider: Option<i32>,
+ #[field(name = uncased("two_factor_token"))]
+ #[field(name = uncased("twofactortoken"))]
two_factor_token: Option<String>,
+ #[field(name = uncased("two_factor_remember"))]
+ #[field(name = uncased("twofactorremember"))]
two_factor_remember: Option<i32>,
}
-impl<'f> FromForm<'f> for ConnectData {
- type Error = String;
-
- fn from_form(items: &mut FormItems<'f>, _strict: bool) -> Result<Self, Self::Error> {
- let mut form = Self::default();
- for item in items {
- let (key, value) = item.key_value_decoded();
- let mut normalized_key = key.to_lowercase();
- normalized_key.retain(|c| c != '_'); // Remove '_'
-
- match normalized_key.as_ref() {
- "granttype" => form.grant_type = value,
- "refreshtoken" => form.refresh_token = Some(value),
- "clientid" => form.client_id = Some(value),
- "clientsecret" => form.client_secret = Some(value),
- "password" => form.password = Some(value),
- "scope" => form.scope = Some(value),
- "username" => form.username = Some(value),
- "deviceidentifier" => form.device_identifier = Some(value),
- "devicename" => form.device_name = Some(value),
- "devicetype" => form.device_type = Some(value),
- "devicepushtoken" => form.device_push_token = Some(value),
- "twofactorprovider" => form.two_factor_provider = value.parse().ok(),
- "twofactortoken" => form.two_factor_token = Some(value),
- "twofactorremember" => form.two_factor_remember = value.parse().ok(),
- key => warn!("Detected unexpected parameter during login: {}", key),
- }
- }
-
- Ok(form)
- }
-}
-
fn _check_is_some<T>(value: &Option<T>, msg: &str) -> EmptyResult {
if value.is_none() {
err!(msg)
diff --git a/src/api/mod.rs b/src/api/mod.rs
@@ -5,7 +5,7 @@ mod identity;
mod notifications;
mod web;
-use rocket_contrib::json::Json;
+use rocket::serde::json::Json;
use serde_json::Value;
pub use crate::api::{
diff --git a/src/api/notifications.rs b/src/api/notifications.rs
@@ -1,7 +1,7 @@
use std::sync::atomic::{AtomicBool, Ordering};
+use rocket::serde::json::Json;
use rocket::Route;
-use rocket_contrib::json::Json;
use serde_json::Value as JsonValue;
use crate::{api::EmptyResult, auth::Headers, Error, CONFIG};
@@ -417,7 +417,7 @@ pub enum UpdateType {
}
use rocket::State;
-pub type Notify<'a> = State<'a, WebSocketUsers>;
+pub type Notify<'a> = &'a State<WebSocketUsers>;
pub fn start_notification_server() -> WebSocketUsers {
let factory = WsFactory::init();
@@ -430,12 +430,11 @@ pub fn start_notification_server() -> WebSocketUsers {
settings.queue_size = 2;
settings.panic_on_internal = false;
- ws::Builder::new()
- .with_settings(settings)
- .build(factory)
- .unwrap()
- .listen((CONFIG.websocket_address().as_str(), CONFIG.websocket_port()))
- .unwrap();
+ let ws = ws::Builder::new().with_settings(settings).build(factory).unwrap();
+ CONFIG.set_ws_shutdown_handle(ws.broadcaster());
+ ws.listen((CONFIG.websocket_address().as_str(), CONFIG.websocket_port())).unwrap();
+
+ warn!("WS Server stopped!");
});
}
diff --git a/src/api/web.rs b/src/api/web.rs
@@ -1,7 +1,7 @@
use std::path::{Path, PathBuf};
-use rocket::{http::ContentType, response::content::Content, response::NamedFile, Route};
-use rocket_contrib::json::Json;
+use rocket::serde::json::Json;
+use rocket::{fs::NamedFile, http::ContentType, Route};
use serde_json::Value;
use crate::{
@@ -21,16 +21,16 @@ pub fn routes() -> Vec<Route> {
}
#[get("/")]
-fn web_index() -> Cached<Option<NamedFile>> {
- Cached::short(NamedFile::open(Path::new(&CONFIG.web_vault_folder()).join("index.html")).ok(), false)
+async fn web_index() -> Cached<Option<NamedFile>> {
+ Cached::short(NamedFile::open(Path::new(&CONFIG.web_vault_folder()).join("index.html")).await.ok(), false)
}
#[get("/app-id.json")]
-fn app_id() -> Cached<Content<Json<Value>>> {
+fn app_id() -> Cached<(ContentType, Json<Value>)> {
let content_type = ContentType::new("application", "fido.trusted-apps+json");
Cached::long(
- Content(
+ (
content_type,
Json(json!({
"trustedFacets": [
@@ -58,13 +58,13 @@ fn app_id() -> Cached<Content<Json<Value>>> {
}
#[get("/<p..>", rank = 10)] // Only match this if the other routes don't match
-fn web_files(p: PathBuf) -> Cached<Option<NamedFile>> {
- Cached::long(NamedFile::open(Path::new(&CONFIG.web_vault_folder()).join(p)).ok(), true)
+async fn web_files(p: PathBuf) -> Cached<Option<NamedFile>> {
+ Cached::long(NamedFile::open(Path::new(&CONFIG.web_vault_folder()).join(p)).await.ok(), true)
}
#[get("/attachments/<uuid>/<file_id>")]
-fn attachments(uuid: SafeString, file_id: SafeString) -> Option<NamedFile> {
- NamedFile::open(Path::new(&CONFIG.attachments_folder()).join(uuid).join(file_id)).ok()
+async fn attachments(uuid: SafeString, file_id: SafeString) -> Option<NamedFile> {
+ NamedFile::open(Path::new(&CONFIG.attachments_folder()).join(uuid).join(file_id)).await.ok()
}
// We use DbConn here to let the alive healthcheck also verify the database connection.
@@ -78,25 +78,20 @@ fn alive(_conn: DbConn) -> Json<String> {
}
#[get("/vw_static/<filename>")]
-fn static_files(filename: String) -> Result<Content<&'static [u8]>, Error> {
+fn static_files(filename: String) -> Result<(ContentType, &'static [u8]), Error> {
match filename.as_ref() {
- "mail-github.png" => Ok(Content(ContentType::PNG, include_bytes!("../static/images/mail-github.png"))),
- "logo-gray.png" => Ok(Content(ContentType::PNG, include_bytes!("../static/images/logo-gray.png"))),
- "error-x.svg" => Ok(Content(ContentType::SVG, include_bytes!("../static/images/error-x.svg"))),
- "hibp.png" => Ok(Content(ContentType::PNG, include_bytes!("../static/images/hibp.png"))),
- "vaultwarden-icon.png" => {
- Ok(Content(ContentType::PNG, include_bytes!("../static/images/vaultwarden-icon.png")))
- }
-
- "bootstrap.css" => Ok(Content(ContentType::CSS, include_bytes!("../static/scripts/bootstrap.css"))),
- "bootstrap-native.js" => {
- Ok(Content(ContentType::JavaScript, include_bytes!("../static/scripts/bootstrap-native.js")))
- }
- "identicon.js" => Ok(Content(ContentType::JavaScript, include_bytes!("../static/scripts/identicon.js"))),
- "datatables.js" => Ok(Content(ContentType::JavaScript, include_bytes!("../static/scripts/datatables.js"))),
- "datatables.css" => Ok(Content(ContentType::CSS, include_bytes!("../static/scripts/datatables.css"))),
+ "mail-github.png" => Ok((ContentType::PNG, include_bytes!("../static/images/mail-github.png"))),
+ "logo-gray.png" => Ok((ContentType::PNG, include_bytes!("../static/images/logo-gray.png"))),
+ "error-x.svg" => Ok((ContentType::SVG, include_bytes!("../static/images/error-x.svg"))),
+ "hibp.png" => Ok((ContentType::PNG, include_bytes!("../static/images/hibp.png"))),
+ "vaultwarden-icon.png" => Ok((ContentType::PNG, include_bytes!("../static/images/vaultwarden-icon.png"))),
+ "bootstrap.css" => Ok((ContentType::CSS, include_bytes!("../static/scripts/bootstrap.css"))),
+ "bootstrap-native.js" => Ok((ContentType::JavaScript, include_bytes!("../static/scripts/bootstrap-native.js"))),
+ "identicon.js" => Ok((ContentType::JavaScript, include_bytes!("../static/scripts/identicon.js"))),
+ "datatables.js" => Ok((ContentType::JavaScript, include_bytes!("../static/scripts/datatables.js"))),
+ "datatables.css" => Ok((ContentType::CSS, include_bytes!("../static/scripts/datatables.css"))),
"jquery-3.6.0.slim.js" => {
- Ok(Content(ContentType::JavaScript, include_bytes!("../static/scripts/jquery-3.6.0.slim.js")))
+ Ok((ContentType::JavaScript, include_bytes!("../static/scripts/jquery-3.6.0.slim.js")))
}
_ => err!(format!("Static file not found: {}", filename)),
}
diff --git a/src/auth.rs b/src/auth.rs
@@ -257,7 +257,10 @@ pub fn generate_send_claims(send_id: &str, file_id: &str) -> BasicJwtClaims {
//
// Bearer token authentication
//
-use rocket::request::{FromRequest, Outcome, Request};
+use rocket::{
+ outcome::try_outcome,
+ request::{FromRequest, Outcome, Request},
+};
use crate::db::{
models::{CollectionUser, Device, User, UserOrgStatus, UserOrgType, UserOrganization, UserStampException},
@@ -268,10 +271,11 @@ pub struct Host {
pub host: String,
}
-impl<'a, 'r> FromRequest<'a, 'r> for Host {
+#[rocket::async_trait]
+impl<'r> FromRequest<'r> for Host {
type Error = &'static str;
- fn from_request(request: &'a Request<'r>) -> Outcome<Self, Self::Error> {
+ async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
let headers = request.headers();
// Get host
@@ -314,17 +318,14 @@ pub struct Headers {
pub user: User,
}
-impl<'a, 'r> FromRequest<'a, 'r> for Headers {
+#[rocket::async_trait]
+impl<'r> FromRequest<'r> for Headers {
type Error = &'static str;
- fn from_request(request: &'a Request<'r>) -> Outcome<Self, Self::Error> {
+ async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
let headers = request.headers();
- let host = match Host::from_request(request) {
- Outcome::Forward(_) => return Outcome::Forward(()),
- Outcome::Failure(f) => return Outcome::Failure(f),
- Outcome::Success(host) => host.host,
- };
+ let host = try_outcome!(Host::from_request(request).await).host;
// Get access_token
let access_token: &str = match headers.get_one("Authorization") {
@@ -344,7 +345,7 @@ impl<'a, 'r> FromRequest<'a, 'r> for Headers {
let device_uuid = claims.device;
let user_uuid = claims.sub;
- let conn = match request.guard::<DbConn>() {
+ let conn = match DbConn::from_request(request).await {
Outcome::Success(conn) => conn,
_ => err_handler!("Error getting DB"),
};
@@ -363,7 +364,7 @@ impl<'a, 'r> FromRequest<'a, 'r> for Headers {
if let Some(stamp_exception) =
user.stamp_exception.as_deref().and_then(|s| serde_json::from_str::<UserStampException>(s).ok())
{
- let current_route = match request.route().and_then(|r| r.name) {
+ let current_route = match request.route().and_then(|r| r.name.as_deref()) {
Some(name) => name,
_ => err_handler!("Error getting current route for stamp exception"),
};
@@ -411,13 +412,13 @@ pub struct OrgHeaders {
// but there are cases where it is a query value.
// First check the path, if this is not a valid uuid, try the query values.
fn get_org_id(request: &Request) -> Option<String> {
- if let Some(Ok(org_id)) = request.get_param::<String>(1) {
+ if let Some(Ok(org_id)) = request.param::<String>(1) {
if uuid::Uuid::parse_str(&org_id).is_ok() {
return Some(org_id);
}
}
- if let Some(Ok(org_id)) = request.get_query_value::<String>("organizationId") {
+ if let Some(Ok(org_id)) = request.query_value::<String>("organizationId") {
if uuid::Uuid::parse_str(&org_id).is_ok() {
return Some(org_id);
}
@@ -426,52 +427,48 @@ fn get_org_id(request: &Request) -> Option<String> {
None
}
-impl<'a, 'r> FromRequest<'a, 'r> for OrgHeaders {
+#[rocket::async_trait]
+impl<'r> FromRequest<'r> for OrgHeaders {
type Error = &'static str;
- fn from_request(request: &'a Request<'r>) -> Outcome<Self, Self::Error> {
- match request.guard::<Headers>() {
- Outcome::Forward(_) => Outcome::Forward(()),
- Outcome::Failure(f) => Outcome::Failure(f),
- Outcome::Success(headers) => {
- match get_org_id(request) {
- Some(org_id) => {
- let conn = match request.guard::<DbConn>() {
- Outcome::Success(conn) => conn,
- _ => err_handler!("Error getting DB"),
- };
-
- let user = headers.user;
- let org_user = match UserOrganization::find_by_user_and_org(&user.uuid, &org_id, &conn) {
- Some(user) => {
- if user.status == UserOrgStatus::Confirmed as i32 {
- user
- } else {
- err_handler!("The current user isn't confirmed member of the organization")
- }
- }
- None => err_handler!("The current user isn't member of the organization"),
- };
-
- Outcome::Success(Self {
- host: headers.host,
- device: headers.device,
- user,
- org_user_type: {
- if let Some(org_usr_type) = UserOrgType::from_i32(org_user.atype) {
- org_usr_type
- } else {
- // This should only happen if the DB is corrupted
- err_handler!("Unknown user type in the database")
- }
- },
- org_user,
- org_id,
- })
+ async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
+ let headers = try_outcome!(Headers::from_request(request).await);
+ match get_org_id(request) {
+ Some(org_id) => {
+ let conn = match DbConn::from_request(request).await {
+ Outcome::Success(conn) => conn,
+ _ => err_handler!("Error getting DB"),
+ };
+
+ let user = headers.user;
+ let org_user = match UserOrganization::find_by_user_and_org(&user.uuid, &org_id, &conn) {
+ Some(user) => {
+ if user.status == UserOrgStatus::Confirmed as i32 {
+ user
+ } else {
+ err_handler!("The current user isn't confirmed member of the organization")
+ }
}
- _ => err_handler!("Error getting the organization id"),
- }
+ None => err_handler!("The current user isn't member of the organization"),
+ };
+
+ Outcome::Success(Self {
+ host: headers.host,
+ device: headers.device,
+ user,
+ org_user_type: {
+ if let Some(org_usr_type) = UserOrgType::from_i32(org_user.atype) {
+ org_usr_type
+ } else {
+ // This should only happen if the DB is corrupted
+ err_handler!("Unknown user type in the database")
+ }
+ },
+ org_user,
+ org_id,
+ })
}
+ _ => err_handler!("Error getting the organization id"),
}
}
}
@@ -483,25 +480,21 @@ pub struct AdminHeaders {
pub org_user_type: UserOrgType,
}
-impl<'a, 'r> FromRequest<'a, 'r> for AdminHeaders {
+#[rocket::async_trait]
+impl<'r> FromRequest<'r> for AdminHeaders {
type Error = &'static str;
- fn from_request(request: &'a Request<'r>) -> Outcome<Self, Self::Error> {
- match request.guard::<OrgHeaders>() {
- Outcome::Forward(_) => Outcome::Forward(()),
- Outcome::Failure(f) => Outcome::Failure(f),
- Outcome::Success(headers) => {
- if headers.org_user_type >= UserOrgType::Admin {
- Outcome::Success(Self {
- host: headers.host,
- device: headers.device,
- user: headers.user,
- org_user_type: headers.org_user_type,
- })
- } else {
- err_handler!("You need to be Admin or Owner to call this endpoint")
- }
- }
+ async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
+ let headers = try_outcome!(OrgHeaders::from_request(request).await);
+ if headers.org_user_type >= UserOrgType::Admin {
+ Outcome::Success(Self {
+ host: headers.host,
+ device: headers.device,
+ user: headers.user,
+ org_user_type: headers.org_user_type,
+ })
+ } else {
+ err_handler!("You need to be Admin or Owner to call this endpoint")
}
}
}
@@ -520,13 +513,13 @@ impl From<AdminHeaders> for Headers {
// but there could be cases where it is a query value.
// First check the path, if this is not a valid uuid, try the query values.
fn get_col_id(request: &Request) -> Option<String> {
- if let Some(Ok(col_id)) = request.get_param::<String>(3) {
+ if let Some(Ok(col_id)) = request.param::<String>(3) {
if uuid::Uuid::parse_str(&col_id).is_ok() {
return Some(col_id);
}
}
- if let Some(Ok(col_id)) = request.get_query_value::<String>("collectionId") {
+ if let Some(Ok(col_id)) = request.query_value::<String>("collectionId") {
if uuid::Uuid::parse_str(&col_id).is_ok() {
return Some(col_id);
}
@@ -545,46 +538,38 @@ pub struct ManagerHeaders {
pub org_user_type: UserOrgType,
}
-impl<'a, 'r> FromRequest<'a, 'r> for ManagerHeaders {
+#[rocket::async_trait]
+impl<'r> FromRequest<'r> for ManagerHeaders {
type Error = &'static str;
- fn from_request(request: &'a Request<'r>) -> Outcome<Self, Self::Error> {
- match request.guard::<OrgHeaders>() {
- Outcome::Forward(_) => Outcome::Forward(()),
- Outcome::Failure(f) => Outcome::Failure(f),
- Outcome::Success(headers) => {
- if headers.org_user_type >= UserOrgType::Manager {
- match get_col_id(request) {
- Some(col_id) => {
- let conn = match request.guard::<DbConn>() {
- Outcome::Success(conn) => conn,
- _ => err_handler!("Error getting DB"),
- };
-
- if !headers.org_user.has_full_access() {
- match CollectionUser::find_by_collection_and_user(
- &col_id,
- &headers.org_user.user_uuid,
- &conn,
- ) {
- Some(_) => (),
- None => err_handler!("The current user isn't a manager for this collection"),
- }
- }
+ async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
+ let headers = try_outcome!(OrgHeaders::from_request(request).await);
+ if headers.org_user_type >= UserOrgType::Manager {
+ match get_col_id(request) {
+ Some(col_id) => {
+ let conn = match DbConn::from_request(request).await {
+ Outcome::Success(conn) => conn,
+ _ => err_handler!("Error getting DB"),
+ };
+
+ if !headers.org_user.has_full_access() {
+ match CollectionUser::find_by_collection_and_user(&col_id, &headers.org_user.user_uuid, &conn) {
+ Some(_) => (),
+ None => err_handler!("The current user isn't a manager for this collection"),
}
- _ => err_handler!("Error getting the collection id"),
}
-
- Outcome::Success(Self {
- host: headers.host,
- device: headers.device,
- user: headers.user,
- org_user_type: headers.org_user_type,
- })
- } else {
- err_handler!("You need to be a Manager, Admin or Owner to call this endpoint")
}
+ _ => err_handler!("Error getting the collection id"),
}
+
+ Outcome::Success(Self {
+ host: headers.host,
+ device: headers.device,
+ user: headers.user,
+ org_user_type: headers.org_user_type,
+ })
+ } else {
+ err_handler!("You need to be a Manager, Admin or Owner to call this endpoint")
}
}
}
@@ -608,25 +593,21 @@ pub struct ManagerHeadersLoose {
pub org_user_type: UserOrgType,
}
-impl<'a, 'r> FromRequest<'a, 'r> for ManagerHeadersLoose {
+#[rocket::async_trait]
+impl<'r> FromRequest<'r> for ManagerHeadersLoose {
type Error = &'static str;
- fn from_request(request: &'a Request<'r>) -> Outcome<Self, Self::Error> {
- match request.guard::<OrgHeaders>() {
- Outcome::Forward(_) => Outcome::Forward(()),
- Outcome::Failure(f) => Outcome::Failure(f),
- Outcome::Success(headers) => {
- if headers.org_user_type >= UserOrgType::Manager {
- Outcome::Success(Self {
- host: headers.host,
- device: headers.device,
- user: headers.user,
- org_user_type: headers.org_user_type,
- })
- } else {
- err_handler!("You need to be a Manager, Admin or Owner to call this endpoint")
- }
- }
+ async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
+ let headers = try_outcome!(OrgHeaders::from_request(request).await);
+ if headers.org_user_type >= UserOrgType::Manager {
+ Outcome::Success(Self {
+ host: headers.host,
+ device: headers.device,
+ user: headers.user,
+ org_user_type: headers.org_user_type,
+ })
+ } else {
+ err_handler!("You need to be a Manager, Admin or Owner to call this endpoint")
}
}
}
@@ -647,24 +628,20 @@ pub struct OwnerHeaders {
pub user: User,
}
-impl<'a, 'r> FromRequest<'a, 'r> for OwnerHeaders {
+#[rocket::async_trait]
+impl<'r> FromRequest<'r> for OwnerHeaders {
type Error = &'static str;
- fn from_request(request: &'a Request<'r>) -> Outcome<Self, Self::Error> {
- match request.guard::<OrgHeaders>() {
- Outcome::Forward(_) => Outcome::Forward(()),
- Outcome::Failure(f) => Outcome::Failure(f),
- Outcome::Success(headers) => {
- if headers.org_user_type == UserOrgType::Owner {
- Outcome::Success(Self {
- host: headers.host,
- device: headers.device,
- user: headers.user,
- })
- } else {
- err_handler!("You need to be Owner to call this endpoint")
- }
- }
+ async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
+ let headers = try_outcome!(OrgHeaders::from_request(request).await);
+ if headers.org_user_type == UserOrgType::Owner {
+ Outcome::Success(Self {
+ host: headers.host,
+ device: headers.device,
+ user: headers.user,
+ })
+ } else {
+ err_handler!("You need to be Owner to call this endpoint")
}
}
}
@@ -678,10 +655,11 @@ pub struct ClientIp {
pub ip: IpAddr,
}
-impl<'a, 'r> FromRequest<'a, 'r> for ClientIp {
+#[rocket::async_trait]
+impl<'r> FromRequest<'r> for ClientIp {
type Error = ();
- fn from_request(req: &'a Request<'r>) -> Outcome<Self, Self::Error> {
+ async fn from_request(req: &'r Request<'_>) -> Outcome<Self, Self::Error> {
let ip = if CONFIG._ip_header_enabled() {
req.headers().get_one(&CONFIG.ip_header()).and_then(|ip| {
match ip.find(',') {
diff --git a/src/config.rs b/src/config.rs
@@ -36,6 +36,9 @@ macro_rules! make_config {
pub struct Config { inner: RwLock<Inner> }
struct Inner {
+ rocket_shutdown_handle: Option<rocket::Shutdown>,
+ ws_shutdown_handle: Option<ws::Sender>,
+
templates: Handlebars<'static>,
config: ConfigItems,
@@ -332,6 +335,8 @@ make_config! {
attachments_folder: String, false, auto, |c| format!("{}/{}", c.data_folder, "attachments");
/// Sends folder
sends_folder: String, false, auto, |c| format!("{}/{}", c.data_folder, "sends");
+ /// Temp folder |> Used for storing temporary file uploads
+ tmp_folder: String, false, auto, |c| format!("{}/{}", c.data_folder, "tmp");
/// Templates folder
templates_folder: String, false, auto, |c| format!("{}/{}", c.data_folder, "templates");
/// Session JWT key
@@ -509,6 +514,9 @@ make_config! {
/// Max database connection retries |> Number of times to retry the database connection during startup, with 1 second between each retry, set to 0 to retry indefinitely
db_connection_retries: u32, false, def, 15;
+ /// Timeout when aquiring database connection
+ database_timeout: u64, false, def, 30;
+
/// Database connection pool size
database_max_conns: u32, false, def, 10;
@@ -743,6 +751,8 @@ impl Config {
Ok(Config {
inner: RwLock::new(Inner {
+ rocket_shutdown_handle: None,
+ ws_shutdown_handle: None,
templates: load_templates(&config.templates_folder),
config,
_env,
@@ -907,6 +917,27 @@ impl Config {
hb.render(name, data).map_err(Into::into)
}
}
+
+ pub fn set_rocket_shutdown_handle(&self, handle: rocket::Shutdown) {
+ self.inner.write().unwrap().rocket_shutdown_handle = Some(handle);
+ }
+
+ pub fn set_ws_shutdown_handle(&self, handle: ws::Sender) {
+ self.inner.write().unwrap().ws_shutdown_handle = Some(handle);
+ }
+
+ pub fn shutdown(&self) {
+ if let Ok(c) = self.inner.read() {
+ if let Some(handle) = c.ws_shutdown_handle.clone() {
+ handle.shutdown().ok();
+ }
+ // Wait a bit before stopping the web server
+ std::thread::sleep(std::time::Duration::from_secs(1));
+ if let Some(handle) = c.rocket_shutdown_handle.clone() {
+ handle.notify();
+ }
+ }
+ }
}
use handlebars::{Context, Handlebars, Helper, HelperResult, Output, RenderContext, RenderError, Renderable};
diff --git a/src/db/mod.rs b/src/db/mod.rs
@@ -1,8 +1,16 @@
+use std::{sync::Arc, time::Duration};
+
use diesel::r2d2::{ConnectionManager, Pool, PooledConnection};
use rocket::{
http::Status,
+ outcome::IntoOutcome,
request::{FromRequest, Outcome},
- Request, State,
+ Request,
+};
+
+use tokio::{
+ sync::{Mutex, OwnedSemaphorePermit, Semaphore},
+ time::timeout,
};
use crate::{
@@ -22,6 +30,23 @@ pub mod __mysql_schema;
#[path = "schemas/postgresql/schema.rs"]
pub mod __postgresql_schema;
+// There changes are based on Rocket 0.5-rc wrapper of Diesel: https://github.com/SergioBenitez/Rocket/blob/v0.5-rc/contrib/sync_db_pools
+
+// A wrapper around spawn_blocking that propagates panics to the calling code.
+pub async fn run_blocking<F, R>(job: F) -> R
+where
+ F: FnOnce() -> R + Send + 'static,
+ R: Send + 'static,
+{
+ match tokio::task::spawn_blocking(job).await {
+ Ok(ret) => ret,
+ Err(e) => match e.try_into_panic() {
+ Ok(panic) => std::panic::resume_unwind(panic),
+ Err(_) => unreachable!("spawn_blocking tasks are never cancelled"),
+ },
+ }
+}
+
// This is used to generate the main DbConn and DbPool enums, which contain one variant for each database supported
macro_rules! generate_connections {
( $( $name:ident: $ty:ty ),+ ) => {
@@ -29,12 +54,53 @@ macro_rules! generate_connections {
#[derive(Eq, PartialEq)]
pub enum DbConnType { $( $name, )+ }
+ pub struct DbConn {
+ conn: Arc<Mutex<Option<DbConnInner>>>,
+ permit: Option<OwnedSemaphorePermit>,
+ }
+
#[allow(non_camel_case_types)]
- pub enum DbConn { $( #[cfg($name)] $name(PooledConnection<ConnectionManager< $ty >>), )+ }
+ pub enum DbConnInner { $( #[cfg($name)] $name(PooledConnection<ConnectionManager< $ty >>), )+ }
+
+
+ #[derive(Clone)]
+ pub struct DbPool {
+ // This is an 'Option' so that we can drop the pool in a 'spawn_blocking'.
+ pool: Option<DbPoolInner>,
+ semaphore: Arc<Semaphore>
+ }
#[allow(non_camel_case_types)]
#[derive(Clone)]
- pub enum DbPool { $( #[cfg($name)] $name(Pool<ConnectionManager< $ty >>), )+ }
+ pub enum DbPoolInner { $( #[cfg($name)] $name(Pool<ConnectionManager< $ty >>), )+ }
+
+ impl Drop for DbConn {
+ fn drop(&mut self) {
+ let conn = self.conn.clone();
+ let permit = self.permit.take();
+
+ // Since connection can't be on the stack in an async fn during an
+ // await, we have to spawn a new blocking-safe thread...
+ tokio::task::spawn_blocking(move || {
+ // And then re-enter the runtime to wait on the async mutex, but in a blocking fashion.
+ let mut conn = tokio::runtime::Handle::current().block_on(conn.lock_owned());
+
+ if let Some(conn) = conn.take() {
+ drop(conn);
+ }
+
+ // Drop permit after the connection is dropped
+ drop(permit);
+ });
+ }
+ }
+
+ impl Drop for DbPool {
+ fn drop(&mut self) {
+ let pool = self.pool.take();
+ tokio::task::spawn_blocking(move || drop(pool));
+ }
+ }
impl DbPool {
// For the given database URL, guess it's type, run migrations create pool and return it
@@ -50,9 +116,13 @@ macro_rules! generate_connections {
let manager = ConnectionManager::new(&url);
let pool = Pool::builder()
.max_size(CONFIG.database_max_conns())
+ .connection_timeout(Duration::from_secs(CONFIG.database_timeout()))
.build(manager)
.map_res("Failed to create pool")?;
- return Ok(Self::$name(pool));
+ return Ok(DbPool {
+ pool: Some(DbPoolInner::$name(pool)),
+ semaphore: Arc::new(Semaphore::new(CONFIG.database_max_conns() as usize)),
+ });
}
#[cfg(not($name))]
#[allow(unreachable_code)]
@@ -61,10 +131,26 @@ macro_rules! generate_connections {
)+ }
}
// Get a connection from the pool
- pub fn get(&self) -> Result<DbConn, Error> {
- match self { $(
+ pub async fn get(&self) -> Result<DbConn, Error> {
+ let duration = Duration::from_secs(CONFIG.database_timeout());
+ let permit = match timeout(duration, self.semaphore.clone().acquire_owned()).await {
+ Ok(p) => p.expect("Semaphore should be open"),
+ Err(_) => {
+ err!("Timeout waiting for database connection");
+ }
+ };
+
+ match self.pool.as_ref().expect("DbPool.pool should always be Some()") { $(
#[cfg($name)]
- Self::$name(p) => Ok(DbConn::$name(p.get().map_res("Error retrieving connection from pool")?)),
+ DbPoolInner::$name(p) => {
+ let pool = p.clone();
+ let c = run_blocking(move || pool.get_timeout(duration)).await.map_res("Error retrieving connection from pool")?;
+
+ return Ok(DbConn {
+ conn: Arc::new(Mutex::new(Some(DbConnInner::$name(c)))),
+ permit: Some(permit)
+ });
+ },
)+ }
}
}
@@ -113,42 +199,95 @@ macro_rules! db_run {
db_run! { $conn: sqlite, mysql, postgresql $body }
};
- // Different code for each db
- ( $conn:ident: $( $($db:ident),+ $body:block )+ ) => {{
- #[allow(unused)] use diesel::prelude::*;
- match $conn {
- $($(
- #[cfg($db)]
- crate::db::DbConn::$db(ref $conn) => {
- paste::paste! {
- #[allow(unused)] use crate::db::[<__ $db _schema>]::{self as schema, *};
- #[allow(unused)] use [<__ $db _model>]::*;
- #[allow(unused)] use crate::db::FromDb;
- }
- $body
- },
- )+)+
- }}
- };
-
- // Same for all dbs
( @raw $conn:ident: $body:block ) => {
db_run! { @raw $conn: sqlite, mysql, postgresql $body }
};
// Different code for each db
- ( @raw $conn:ident: $( $($db:ident),+ $body:block )+ ) => {
+ ( $conn:ident: $( $($db:ident),+ $body:block )+ ) => {{
#[allow(unused)] use diesel::prelude::*;
- #[allow(unused_variables)]
- match $conn {
- $($(
- #[cfg($db)]
- crate::db::DbConn::$db(ref $conn) => {
- $body
- },
- )+)+
- }
- };
+
+ // It is important that this inner Arc<Mutex<>> (or the OwnedMutexGuard
+ // derived from it) never be a variable on the stack at an await point,
+ // where Drop might be called at any time. This causes (synchronous)
+ // Drop to be called from asynchronous code, which some database
+ // wrappers do not or can not handle.
+ let conn = $conn.conn.clone();
+
+ // Since connection can't be on the stack in an async fn during an
+ // await, we have to spawn a new blocking-safe thread...
+ /*
+ run_blocking(move || {
+ // And then re-enter the runtime to wait on the async mutex, but in
+ // a blocking fashion.
+ let mut conn = tokio::runtime::Handle::current().block_on(conn.lock_owned());
+ let conn = conn.as_mut().expect("internal invariant broken: self.connection is Some");
+ */
+ let mut __conn_mutex = conn.try_lock_owned().unwrap();
+ let conn = __conn_mutex.as_mut().unwrap();
+ match conn {
+ $($(
+ #[cfg($db)]
+ crate::db::DbConnInner::$db($conn) => {
+ paste::paste! {
+ #[allow(unused)] use crate::db::[<__ $db _schema>]::{self as schema, *};
+ #[allow(unused)] use [<__ $db _model>]::*;
+ #[allow(unused)] use crate::db::FromDb;
+ }
+
+ /*
+ // Since connection can't be on the stack in an async fn during an
+ // await, we have to spawn a new blocking-safe thread...
+ run_blocking(move || {
+ // And then re-enter the runtime to wait on the async mutex, but in
+ // a blocking fashion.
+ let mut conn = tokio::runtime::Handle::current().block_on(async {
+ conn.lock_owned().await
+ });
+
+ let conn = conn.as_mut().expect("internal invariant broken: self.connection is Some");
+ f(conn)
+ }).await;*/
+
+ $body
+ },
+ )+)+
+ }
+ // }).await
+ }};
+
+ ( @raw $conn:ident: $( $($db:ident),+ $body:block )+ ) => {{
+ #[allow(unused)] use diesel::prelude::*;
+
+ // It is important that this inner Arc<Mutex<>> (or the OwnedMutexGuard
+ // derived from it) never be a variable on the stack at an await point,
+ // where Drop might be called at any time. This causes (synchronous)
+ // Drop to be called from asynchronous code, which some database
+ // wrappers do not or can not handle.
+ let conn = $conn.conn.clone();
+
+ // Since connection can't be on the stack in an async fn during an
+ // await, we have to spawn a new blocking-safe thread...
+ run_blocking(move || {
+ // And then re-enter the runtime to wait on the async mutex, but in
+ // a blocking fashion.
+ let mut conn = tokio::runtime::Handle::current().block_on(conn.lock_owned());
+ match conn.as_mut().expect("internal invariant broken: self.connection is Some") {
+ $($(
+ #[cfg($db)]
+ crate::db::DbConnInner::$db($conn) => {
+ paste::paste! {
+ #[allow(unused)] use crate::db::[<__ $db _schema>]::{self as schema, *};
+ // @RAW: #[allow(unused)] use [<__ $db _model>]::*;
+ #[allow(unused)] use crate::db::FromDb;
+ }
+
+ $body
+ },
+ )+)+
+ }
+ }).await
+ }};
}
pub trait FromDb {
@@ -227,9 +366,10 @@ pub mod models;
/// Creates a back-up of the sqlite database
/// MySQL/MariaDB and PostgreSQL are not supported.
-pub fn backup_database(conn: &DbConn) -> Result<(), Error> {
+pub async fn backup_database(conn: &DbConn) -> Result<(), Error> {
db_run! {@raw conn:
postgresql, mysql {
+ let _ = conn;
err!("PostgreSQL and MySQL/MariaDB do not support this backup feature");
}
sqlite {
@@ -244,7 +384,7 @@ pub fn backup_database(conn: &DbConn) -> Result<(), Error> {
}
/// Get the SQL Server version
-pub fn get_sql_server_version(conn: &DbConn) -> String {
+pub async fn get_sql_server_version(conn: &DbConn) -> String {
db_run! {@raw conn:
postgresql, mysql {
no_arg_sql_function!(version, diesel::sql_types::Text);
@@ -260,15 +400,14 @@ pub fn get_sql_server_version(conn: &DbConn) -> String {
/// Attempts to retrieve a single connection from the managed database pool. If
/// no pool is currently managed, fails with an `InternalServerError` status. If
/// no connections are available, fails with a `ServiceUnavailable` status.
-impl<'a, 'r> FromRequest<'a, 'r> for DbConn {
+#[rocket::async_trait]
+impl<'r> FromRequest<'r> for DbConn {
type Error = ();
- fn from_request(request: &'a Request<'r>) -> Outcome<DbConn, ()> {
- // https://github.com/SergioBenitez/Rocket/commit/e3c1a4ad3ab9b840482ec6de4200d30df43e357c
- let pool = try_outcome!(request.guard::<State<DbPool>>());
- match pool.get() {
- Ok(conn) => Outcome::Success(conn),
- Err(_) => Outcome::Failure((Status::ServiceUnavailable, ())),
+ async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
+ match request.rocket().state::<DbPool>() {
+ Some(p) => p.get().await.map_err(|_| ()).into_outcome(Status::ServiceUnavailable),
+ None => Outcome::Failure((Status::InternalServerError, ())),
}
}
}
diff --git a/src/error.rs b/src/error.rs
@@ -45,6 +45,7 @@ use lettre::transport::smtp::Error as SmtpErr;
use openssl::error::ErrorStack as SSLErr;
use regex::Error as RegexErr;
use reqwest::Error as ReqErr;
+use rocket::error::Error as RocketErr;
use serde_json::{Error as SerdeErr, Value};
use std::io::Error as IoErr;
use std::time::SystemTimeError as TimeErr;
@@ -84,6 +85,7 @@ make_error! {
Address(AddrErr): _has_source, _api_error,
Smtp(SmtpErr): _has_source, _api_error,
OpenSSL(SSLErr): _has_source, _api_error,
+ Rocket(RocketErr): _has_source, _api_error,
DieselCon(DieselConErr): _has_source, _api_error,
DieselMig(DieselMigErr): _has_source, _api_error,
@@ -193,8 +195,8 @@ use rocket::http::{ContentType, Status};
use rocket::request::Request;
use rocket::response::{self, Responder, Response};
-impl<'r> Responder<'r> for Error {
- fn respond_to(self, _: &Request) -> response::Result<'r> {
+impl<'r> Responder<'r, 'static> for Error {
+ fn respond_to(self, _: &Request) -> response::Result<'static> {
match self.error {
ErrorKind::Empty(_) => {} // Don't print the error in this situation
ErrorKind::Simple(_) => {} // Don't print the error in this situation
@@ -202,8 +204,8 @@ impl<'r> Responder<'r> for Error {
};
let code = Status::from_code(self.error_code).unwrap_or(Status::BadRequest);
-
- Response::build().status(code).header(ContentType::JSON).sized_body(Cursor::new(format!("{}", self))).ok()
+ let body = self.to_string();
+ Response::build().status(code).header(ContentType::JSON).sized_body(Some(body.len()), Cursor::new(body)).ok()
}
}
diff --git a/src/main.rs b/src/main.rs
@@ -20,8 +20,15 @@ extern crate diesel;
#[macro_use]
extern crate diesel_migrations;
-use job_scheduler::{Job, JobScheduler};
-use std::{fs::create_dir_all, panic, path::Path, process::exit, str::FromStr, thread, time::Duration};
+use std::{
+ fs::{canonicalize, create_dir_all},
+ panic,
+ path::Path,
+ process::exit,
+ str::FromStr,
+ thread,
+ time::Duration,
+};
#[macro_use]
mod error;
@@ -37,9 +44,11 @@ mod util;
pub use config::CONFIG;
pub use error::{Error, MapResult};
+use rocket::data::{Limits, ToByteUnit};
pub use util::is_running_in_docker;
-fn main() {
+#[rocket::main]
+async fn main() -> Result<(), Error> {
parse_args();
launch_info();
@@ -56,13 +65,16 @@ fn main() {
});
check_web_vault();
- create_icon_cache_folder();
+ create_dir(&CONFIG.icon_cache_folder(), "icon cache");
+ create_dir(&CONFIG.tmp_folder(), "tmp folder");
+ create_dir(&CONFIG.sends_folder(), "sends folder");
+ create_dir(&CONFIG.attachments_folder(), "attachments folder");
let pool = create_db_pool();
- schedule_jobs(pool.clone());
- crate::db::models::TwoFactor::migrate_u2f_to_webauthn(&pool.get().unwrap()).unwrap();
+ schedule_jobs(pool.clone()).await;
+ crate::db::models::TwoFactor::migrate_u2f_to_webauthn(&pool.get().await.unwrap()).unwrap();
- launch_rocket(pool, extra_debug); // Blocks until program termination.
+ launch_rocket(pool, extra_debug).await // Blocks until program termination.
}
const HELP: &str = "\
@@ -127,10 +139,12 @@ fn init_logging(level: log::LevelFilter) -> Result<(), fern::InitError> {
.level_for("hyper::server", log::LevelFilter::Warn)
// Silence rocket logs
.level_for("_", log::LevelFilter::Off)
- .level_for("launch", log::LevelFilter::Off)
- .level_for("launch_", log::LevelFilter::Off)
- .level_for("rocket::rocket", log::LevelFilter::Off)
- .level_for("rocket::fairing", log::LevelFilter::Off)
+ .level_for("rocket::launch", log::LevelFilter::Error)
+ .level_for("rocket::launch_", log::LevelFilter::Error)
+ .level_for("rocket::rocket", log::LevelFilter::Warn)
+ .level_for("rocket::server", log::LevelFilter::Warn)
+ .level_for("rocket::fairing::fairings", log::LevelFilter::Warn)
+ .level_for("rocket::shield::shield", log::LevelFilter::Warn)
// Never show html5ever and hyper::proto logs, too noisy
.level_for("html5ever", log::LevelFilter::Off)
.level_for("hyper::proto", log::LevelFilter::Off)
@@ -243,10 +257,6 @@ fn create_dir(path: &str, description: &str) {
create_dir_all(path).expect(&err_msg);
}
-fn create_icon_cache_folder() {
- create_dir(&CONFIG.icon_cache_folder(), "icon cache");
-}
-
fn check_data_folder() {
let data_folder = &CONFIG.data_folder();
let path = Path::new(data_folder);
@@ -314,51 +324,73 @@ fn create_db_pool() -> db::DbPool {
}
}
-fn launch_rocket(pool: db::DbPool, extra_debug: bool) {
+async fn launch_rocket(pool: db::DbPool, extra_debug: bool) -> Result<(), Error> {
let basepath = &CONFIG.domain_path();
+ let mut config = rocket::Config::from(rocket::Config::figment());
+ config.address = std::net::IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED); // TODO: Allow this to be changed, keep ROCKET_ADDRESS for compat
+ config.temp_dir = canonicalize(CONFIG.tmp_folder()).unwrap().into();
+ config.limits = Limits::new() //
+ .limit("json", 10.megabytes())
+ .limit("data-form", 150.megabytes())
+ .limit("file", 150.megabytes());
+
// If adding more paths here, consider also adding them to
// crate::utils::LOGGED_ROUTES to make sure they appear in the log
- let result = rocket::ignite()
- .mount(&[basepath, "/"].concat(), api::web_routes())
- .mount(&[basepath, "/api"].concat(), api::core_routes())
- .mount(&[basepath, "/admin"].concat(), api::admin_routes())
- .mount(&[basepath, "/identity"].concat(), api::identity_routes())
- .mount(&[basepath, "/icons"].concat(), api::icons_routes())
- .mount(&[basepath, "/notifications"].concat(), api::notifications_routes())
+ let instance = rocket::custom(config)
+ .mount([basepath, "/"].concat(), api::web_routes())
+ .mount([basepath, "/api"].concat(), api::core_routes())
+ .mount([basepath, "/admin"].concat(), api::admin_routes())
+ .mount([basepath, "/identity"].concat(), api::identity_routes())
+ .mount([basepath, "/icons"].concat(), api::icons_routes())
+ .mount([basepath, "/notifications"].concat(), api::notifications_routes())
.manage(pool)
.manage(api::start_notification_server())
.attach(util::AppHeaders())
.attach(util::Cors())
.attach(util::BetterLogging(extra_debug))
- .launch();
+ .ignite()
+ .await?;
+
+ CONFIG.set_rocket_shutdown_handle(instance.shutdown());
+ ctrlc::set_handler(move || {
+ info!("Exiting vaultwarden!");
+ CONFIG.shutdown();
+ })
+ .expect("Error setting Ctrl-C handler");
- // Launch and print error if there is one
- // The launch will restore the original logging level
- error!("Launch error {:#?}", result);
+ instance.launch().await?;
+
+ info!("Vaultwarden process exited!");
+ Ok(())
}
-fn schedule_jobs(pool: db::DbPool) {
+async fn schedule_jobs(pool: db::DbPool) {
if CONFIG.job_poll_interval_ms() == 0 {
info!("Job scheduler disabled.");
return;
}
+
+ let runtime = tokio::runtime::Handle::current();
+
thread::Builder::new()
.name("job-scheduler".to_string())
.spawn(move || {
+ use job_scheduler::{Job, JobScheduler};
+
let mut sched = JobScheduler::new();
// Purge sends that are past their deletion date.
if !CONFIG.send_purge_schedule().is_empty() {
sched.add(Job::new(CONFIG.send_purge_schedule().parse().unwrap(), || {
- api::purge_sends(pool.clone());
+ runtime.spawn(api::purge_sends(pool.clone()));
}));
}
// Purge trashed items that are old enough to be auto-deleted.
if !CONFIG.trash_purge_schedule().is_empty() {
sched.add(Job::new(CONFIG.trash_purge_schedule().parse().unwrap(), || {
- api::purge_trashed_ciphers(pool.clone());
+ runtime.spawn(api::purge_trashed_ciphers(pool.clone()));
}));
}
@@ -366,7 +398,7 @@ fn schedule_jobs(pool: db::DbPool) {
// indicates that a user's master password has been compromised.
if !CONFIG.incomplete_2fa_schedule().is_empty() {
sched.add(Job::new(CONFIG.incomplete_2fa_schedule().parse().unwrap(), || {
- api::send_incomplete_2fa_notifications(pool.clone());
+ runtime.spawn(api::send_incomplete_2fa_notifications(pool.clone()));
}));
}
@@ -375,7 +407,7 @@ fn schedule_jobs(pool: db::DbPool) {
// sending reminders for requests that are about to be granted anyway.
if !CONFIG.emergency_request_timeout_schedule().is_empty() {
sched.add(Job::new(CONFIG.emergency_request_timeout_schedule().parse().unwrap(), || {
- api::emergency_request_timeout_job(pool.clone());
+ runtime.spawn(api::emergency_request_timeout_job(pool.clone()));
}));
}
@@ -383,7 +415,7 @@ fn schedule_jobs(pool: db::DbPool) {
// emergency access requests.
if !CONFIG.emergency_notification_reminder_schedule().is_empty() {
sched.add(Job::new(CONFIG.emergency_notification_reminder_schedule().parse().unwrap(), || {
- api::emergency_notification_reminder_job(pool.clone());
+ runtime.spawn(api::emergency_notification_reminder_job(pool.clone()));
}));
}
diff --git a/src/util.rs b/src/util.rs
@@ -5,10 +5,10 @@ use std::io::Cursor;
use rocket::{
fairing::{Fairing, Info, Kind},
- http::{ContentType, Header, HeaderMap, Method, RawStr, Status},
+ http::{ContentType, Header, HeaderMap, Method, Status},
request::FromParam,
response::{self, Responder},
- Data, Request, Response, Rocket,
+ Data, Orbit, Request, Response, Rocket,
};
use std::thread::sleep;
@@ -18,6 +18,7 @@ use crate::CONFIG;
pub struct AppHeaders();
+#[rocket::async_trait]
impl Fairing for AppHeaders {
fn info(&self) -> Info {
Info {
@@ -26,7 +27,7 @@ impl Fairing for AppHeaders {
}
}
- fn on_response(&self, _req: &Request, res: &mut Response) {
+ async fn on_response<'r>(&self, _req: &'r Request<'_>, res: &mut Response<'r>) {
res.set_raw_header("Permissions-Policy", "accelerometer=(), ambient-light-sensor=(), autoplay=(), camera=(), encrypted-media=(), fullscreen=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), midi=(), payment=(), picture-in-picture=(), sync-xhr=(self \"https://haveibeenpwned.com\" \"https://2fa.directory\"), usb=(), vr=()");
res.set_raw_header("Referrer-Policy", "same-origin");
res.set_raw_header("X-Frame-Options", "SAMEORIGIN");
@@ -72,6 +73,7 @@ impl Cors {
}
}
+#[rocket::async_trait]
impl Fairing for Cors {
fn info(&self) -> Info {
Info {
@@ -80,7 +82,7 @@ impl Fairing for Cors {
}
}
- fn on_response(&self, request: &Request, response: &mut Response) {
+ async fn on_response<'r>(&self, request: &'r Request<'_>, response: &mut Response<'r>) {
let req_headers = request.headers();
if let Some(origin) = Cors::get_allowed_origin(req_headers) {
@@ -97,7 +99,7 @@ impl Fairing for Cors {
response.set_header(Header::new("Access-Control-Allow-Credentials", "true"));
response.set_status(Status::Ok);
response.set_header(ContentType::Plain);
- response.set_sized_body(Cursor::new(""));
+ response.set_sized_body(Some(0), Cursor::new(""));
}
}
}
@@ -134,25 +136,21 @@ impl<R> Cached<R> {
}
}
-impl<'r, R: Responder<'r>> Responder<'r> for Cached<R> {
- fn respond_to(self, req: &Request) -> response::Result<'r> {
+impl<'r, R: 'r + Responder<'r, 'static> + Send> Responder<'r, 'static> for Cached<R> {
+ fn respond_to(self, request: &'r Request<'_>) -> response::Result<'static> {
+ let mut res = self.response.respond_to(request)?;
+
let cache_control_header = if self.is_immutable {
format!("public, immutable, max-age={}", self.ttl)
} else {
format!("public, max-age={}", self.ttl)
};
+ res.set_raw_header("Cache-Control", cache_control_header);
let time_now = chrono::Local::now();
-
- match self.response.respond_to(req) {
- Ok(mut res) => {
- res.set_raw_header("Cache-Control", cache_control_header);
- let expiry_time = time_now + chrono::Duration::seconds(self.ttl.try_into().unwrap());
- res.set_raw_header("Expires", format_datetime_http(&expiry_time));
- Ok(res)
- }
- e @ Err(_) => e,
- }
+ let expiry_time = time_now + chrono::Duration::seconds(self.ttl.try_into().unwrap());
+ res.set_raw_header("Expires", format_datetime_http(&expiry_time));
+ Ok(res)
}
}
@@ -175,11 +173,9 @@ impl<'r> FromParam<'r> for SafeString {
type Error = ();
#[inline(always)]
- fn from_param(param: &'r RawStr) -> Result<Self, Self::Error> {
- let s = param.percent_decode().map(|cow| cow.into_owned()).map_err(|_| ())?;
-
- if s.chars().all(|c| matches!(c, 'a'..='z' | 'A'..='Z' |'0'..='9' | '-')) {
- Ok(SafeString(s))
+ fn from_param(param: &'r str) -> Result<Self, Self::Error> {
+ if param.chars().all(|c| matches!(c, 'a'..='z' | 'A'..='Z' |'0'..='9' | '-')) {
+ Ok(SafeString(param.to_string()))
} else {
Err(())
}
@@ -193,15 +189,16 @@ const LOGGED_ROUTES: [&str; 6] =
// Boolean is extra debug, when true, we ignore the whitelist above and also print the mounts
pub struct BetterLogging(pub bool);
+#[rocket::async_trait]
impl Fairing for BetterLogging {
fn info(&self) -> Info {
Info {
name: "Better Logging",
- kind: Kind::Launch | Kind::Request | Kind::Response,
+ kind: Kind::Liftoff | Kind::Request | Kind::Response,
}
}
- fn on_launch(&self, rocket: &Rocket) {
+ async fn on_liftoff(&self, rocket: &Rocket<Orbit>) {
if self.0 {
info!(target: "routes", "Routes loaded:");
let mut routes: Vec<_> = rocket.routes().collect();
@@ -225,34 +222,36 @@ impl Fairing for BetterLogging {
info!(target: "start", "Rocket has launched from {}", addr);
}
- fn on_request(&self, request: &mut Request<'_>, _data: &Data) {
+ async fn on_request(&self, request: &mut Request<'_>, _data: &mut Data<'_>) {
let method = request.method();
if !self.0 && method == Method::Options {
return;
}
let uri = request.uri();
let uri_path = uri.path();
- let uri_subpath = uri_path.strip_prefix(&CONFIG.domain_path()).unwrap_or(uri_path);
+ let uri_path_str = uri_path.url_decode_lossy();
+ let uri_subpath = uri_path_str.strip_prefix(&CONFIG.domain_path()).unwrap_or(&uri_path_str);
if self.0 || LOGGED_ROUTES.iter().any(|r| uri_subpath.starts_with(r)) {
match uri.query() {
- Some(q) => info!(target: "request", "{} {}?{}", method, uri_path, &q[..q.len().min(30)]),
- None => info!(target: "request", "{} {}", method, uri_path),
+ Some(q) => info!(target: "request", "{} {}?{}", method, uri_path_str, &q[..q.len().min(30)]),
+ None => info!(target: "request", "{} {}", method, uri_path_str),
};
}
}
- fn on_response(&self, request: &Request, response: &mut Response) {
+ async fn on_response<'r>(&self, request: &'r Request<'_>, response: &mut Response<'r>) {
if !self.0 && request.method() == Method::Options {
return;
}
let uri_path = request.uri().path();
- let uri_subpath = uri_path.strip_prefix(&CONFIG.domain_path()).unwrap_or(uri_path);
+ let uri_path_str = uri_path.url_decode_lossy();
+ let uri_subpath = uri_path_str.strip_prefix(&CONFIG.domain_path()).unwrap_or(&uri_path_str);
if self.0 || LOGGED_ROUTES.iter().any(|r| uri_subpath.starts_with(r)) {
let status = response.status();
- if let Some(route) = request.route() {
- info!(target: "response", "{} => {} {}", route, status.code, status.reason)
+ if let Some(ref route) = request.route() {
+ info!(target: "response", "{} => {}", route, status)
} else {
- info!(target: "response", "{} {}", status.code, status.reason)
+ info!(target: "response", "{}", status)
}
}
}
@@ -614,10 +613,7 @@ where
}
}
-use reqwest::{
- blocking::{Client, ClientBuilder},
- header,
-};
+use reqwest::{header, Client, ClientBuilder};
pub fn get_reqwest_client() -> Client {
get_reqwest_client_builder().build().expect("Failed to build client")