Compare commits
4 commits
Author | SHA1 | Date | |
---|---|---|---|
998fefb5d8 | |||
a81a29df55 | |||
793463b826 | |||
5fb711bd85 |
15 changed files with 677 additions and 578 deletions
123
Cargo.lock
generated
123
Cargo.lock
generated
|
@ -57,9 +57,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstyle"
|
name = "anstyle"
|
||||||
version = "1.0.4"
|
version = "1.0.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
|
checksum = "2faccea4cc4ab4a667ce676a30e8ec13922a692c99bb8f5b11f1502c72e04220"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstyle-parse"
|
name = "anstyle-parse"
|
||||||
|
@ -218,6 +218,29 @@ dependencies = [
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "axum-extra"
|
||||||
|
version = "0.9.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "895ff42f72016617773af68fb90da2a9677d89c62338ec09162d4909d86fdd8f"
|
||||||
|
dependencies = [
|
||||||
|
"axum 0.7.4",
|
||||||
|
"axum-core 0.4.3",
|
||||||
|
"bytes",
|
||||||
|
"futures-util",
|
||||||
|
"http 1.0.0",
|
||||||
|
"http-body 1.0.0",
|
||||||
|
"http-body-util",
|
||||||
|
"mime",
|
||||||
|
"pin-project-lite",
|
||||||
|
"serde",
|
||||||
|
"tokio",
|
||||||
|
"tokio-util",
|
||||||
|
"tower",
|
||||||
|
"tower-layer",
|
||||||
|
"tower-service",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "axum-macros"
|
name = "axum-macros"
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
|
@ -294,6 +317,7 @@ name = "bin"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum 0.7.4",
|
"axum 0.7.4",
|
||||||
|
"axum-extra",
|
||||||
"axum-oidc",
|
"axum-oidc",
|
||||||
"bytes",
|
"bytes",
|
||||||
"chacha20",
|
"chacha20",
|
||||||
|
@ -306,6 +330,7 @@ dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"markdown",
|
"markdown",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
|
"poly1305",
|
||||||
"prometheus-client",
|
"prometheus-client",
|
||||||
"rand",
|
"rand",
|
||||||
"render",
|
"render",
|
||||||
|
@ -320,6 +345,7 @@ dependencies = [
|
||||||
"tower",
|
"tower",
|
||||||
"tower-http",
|
"tower-http",
|
||||||
"tower-sessions",
|
"tower-sessions",
|
||||||
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -407,9 +433,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "chrono"
|
name = "chrono"
|
||||||
version = "0.4.32"
|
version = "0.4.33"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "41daef31d7a747c5c847246f36de49ced6f7403b4cdabc807a97b5cc184cda7a"
|
checksum = "9f13690e35a5e4ace198e7beea2895d29f3a9cc55015fcebe6336bd2010af9eb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"android-tzdata",
|
"android-tzdata",
|
||||||
"iana-time-zone",
|
"iana-time-zone",
|
||||||
|
@ -1002,7 +1028,7 @@ dependencies = [
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"http 0.2.11",
|
"http 0.2.11",
|
||||||
"indexmap 2.1.0",
|
"indexmap 2.2.1",
|
||||||
"slab",
|
"slab",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
|
@ -1021,7 +1047,7 @@ dependencies = [
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"http 1.0.0",
|
"http 1.0.0",
|
||||||
"indexmap 2.1.0",
|
"indexmap 2.2.1",
|
||||||
"slab",
|
"slab",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
|
@ -1292,9 +1318,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "2.1.0"
|
version = "2.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
|
checksum = "433de089bd45971eecf4668ee0ee8f4cec17db4f8bd8f7bc3197a6ce37aa7d9b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown 0.14.3",
|
"hashbrown 0.14.3",
|
||||||
|
@ -1652,6 +1678,12 @@ version = "1.19.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "opaque-debug"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "open"
|
name = "open"
|
||||||
version = "5.0.1"
|
version = "5.0.1"
|
||||||
|
@ -1790,18 +1822,18 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project"
|
name = "pin-project"
|
||||||
version = "1.1.3"
|
version = "1.1.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422"
|
checksum = "0302c4a0442c456bd56f841aee5c3bfd17967563f6fadc9ceb9f9c23cf3807e0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"pin-project-internal",
|
"pin-project-internal",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project-internal"
|
name = "pin-project-internal"
|
||||||
version = "1.1.3"
|
version = "1.1.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405"
|
checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -1853,6 +1885,17 @@ version = "3.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "626dec3cac7cc0e1577a2ec3fc496277ec2baa084bebad95bb6fdbfae235f84c"
|
checksum = "626dec3cac7cc0e1577a2ec3fc496277ec2baa084bebad95bb6fdbfae235f84c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "poly1305"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf"
|
||||||
|
dependencies = [
|
||||||
|
"cpufeatures",
|
||||||
|
"opaque-debug",
|
||||||
|
"universal-hash",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "powerfmt"
|
name = "powerfmt"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
@ -2003,9 +2046,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-automata"
|
name = "regex-automata"
|
||||||
version = "0.4.4"
|
version = "0.4.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3b7fa1134405e2ec9353fd416b17f8dacd46c473d7d3fd1cf202706a14eb792a"
|
checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
|
@ -2280,9 +2323,9 @@ checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.195"
|
version = "1.0.196"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02"
|
checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
@ -2299,9 +2342,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.195"
|
version = "1.0.196"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c"
|
checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -2310,9 +2353,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.111"
|
version = "1.0.113"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4"
|
checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
"ryu",
|
"ryu",
|
||||||
|
@ -2361,15 +2404,15 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_with"
|
name = "serde_with"
|
||||||
version = "3.5.0"
|
version = "3.5.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f58c3a1b3e418f61c25b2aeb43fc6c95eaa252b8cecdda67f401943e9e08d33f"
|
checksum = "f5c9fdb6b00a489875b22efd4b78fe2b363b72265cc5f6eb2e2b9ee270e6140c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.21.7",
|
"base64 0.21.7",
|
||||||
"chrono",
|
"chrono",
|
||||||
"hex",
|
"hex",
|
||||||
"indexmap 1.9.3",
|
"indexmap 1.9.3",
|
||||||
"indexmap 2.1.0",
|
"indexmap 2.2.1",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_with_macros",
|
"serde_with_macros",
|
||||||
|
@ -2378,9 +2421,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_with_macros"
|
name = "serde_with_macros"
|
||||||
version = "3.5.0"
|
version = "3.5.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d2068b437a31fc68f25dd7edc296b078f04b45145c199d8eed9866e45f1ff274"
|
checksum = "dbff351eb4b33600a2e138dfa0b10b65a238ea8ff8fb2387c422c5022a3e8298"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling",
|
"darling",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
|
@ -2711,7 +2754,7 @@ version = "0.21.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03"
|
checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap 2.1.0",
|
"indexmap 2.2.1",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_spanned",
|
"serde_spanned",
|
||||||
"toml_datetime",
|
"toml_datetime",
|
||||||
|
@ -2907,6 +2950,16 @@ dependencies = [
|
||||||
"tinyvec",
|
"tinyvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "universal-hash"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
|
||||||
|
dependencies = [
|
||||||
|
"crypto-common",
|
||||||
|
"subtle",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "untrusted"
|
name = "untrusted"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
|
@ -3231,9 +3284,9 @@ checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winnow"
|
name = "winnow"
|
||||||
version = "0.5.34"
|
version = "0.5.35"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b7cf47b659b318dccbd69cc4797a39ae128f533dce7902a1096044d1967b9c16"
|
checksum = "1931d78a9c73861da0134f453bb1f790ce49b2e30eba8410b4b79bac72b46a2d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
@ -3253,3 +3306,17 @@ name = "zeroize"
|
||||||
version = "1.7.0"
|
version = "1.7.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d"
|
checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d"
|
||||||
|
dependencies = [
|
||||||
|
"zeroize_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zeroize_derive"
|
||||||
|
version = "1.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.48",
|
||||||
|
]
|
||||||
|
|
18
flake.lock
18
flake.lock
|
@ -7,11 +7,11 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1705974079,
|
"lastModified": 1706473964,
|
||||||
"narHash": "sha256-HyC3C2esW57j6bG0MKwX4kQi25ltslRnr6z2uvpadJo=",
|
"narHash": "sha256-Fq6xleee/TsX6NbtoRuI96bBuDHMU57PrcK9z1QEKbk=",
|
||||||
"owner": "ipetkov",
|
"owner": "ipetkov",
|
||||||
"repo": "crane",
|
"repo": "crane",
|
||||||
"rev": "0b4e511fe6e346381e31d355e03de52aa43e8cb2",
|
"rev": "c798790eabec3e3da48190ae3698ac227aab770c",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -40,11 +40,11 @@
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1705856552,
|
"lastModified": 1706371002,
|
||||||
"narHash": "sha256-JXfnuEf5Yd6bhMs/uvM67/joxYKoysyE3M2k6T3eWbg=",
|
"narHash": "sha256-dwuorKimqSYgyu8Cw6ncKhyQjUDOyuXoxDTVmAXq88s=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "612f97239e2cc474c13c9dafa0df378058c5ad8d",
|
"rev": "c002c6aa977ad22c60398daaa9be52f2203d0006",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -72,11 +72,11 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1705976279,
|
"lastModified": 1706494265,
|
||||||
"narHash": "sha256-Zx97bJ3+O8IP70uJPD//rRsr8bcxICISMTZUT/L9eFk=",
|
"narHash": "sha256-4ilEUJEwNaY9r/8BpL3VmZiaGber0j09lvvx0e/bosA=",
|
||||||
"owner": "oxalica",
|
"owner": "oxalica",
|
||||||
"repo": "rust-overlay",
|
"repo": "rust-overlay",
|
||||||
"rev": "f889dc31ef97835834bdc3662394ebdb3c96b974",
|
"rev": "246ba7102553851af60e0382f558f6bc5f63fa13",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
|
@ -54,11 +54,6 @@
|
||||||
bin = craneLib.buildPackage (commonArgs // {
|
bin = craneLib.buildPackage (commonArgs // {
|
||||||
inherit cargoArtifacts;
|
inherit cargoArtifacts;
|
||||||
pname = "bin";
|
pname = "bin";
|
||||||
installPhaseCommand = ''
|
|
||||||
mkdir -p $out/bin
|
|
||||||
cp target/release/bin $out/bin/bin
|
|
||||||
cp -r server/static $out/static
|
|
||||||
'';
|
|
||||||
});
|
});
|
||||||
binctl = craneLib.buildPackage (commonArgs // {
|
binctl = craneLib.buildPackage (commonArgs // {
|
||||||
inherit cargoArtifacts;
|
inherit cargoArtifacts;
|
||||||
|
|
|
@ -24,7 +24,7 @@ tower = "0.4.13"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
env_logger = "0.10"
|
env_logger = "0.10"
|
||||||
sailfish = "0.8.3"
|
sailfish = "0.8.3"
|
||||||
tower-http = { version="0.5", features=["fs"], default-features=false }
|
tower-http = { version="0.5", features=["fs", "cors"], default-features=false }
|
||||||
prometheus-client = "0.22.0"
|
prometheus-client = "0.22.0"
|
||||||
|
|
||||||
chacha20 = "0.9"
|
chacha20 = "0.9"
|
||||||
|
@ -32,6 +32,9 @@ sha3 = "0.10"
|
||||||
hex = "0.4"
|
hex = "0.4"
|
||||||
bytes = "1.5"
|
bytes = "1.5"
|
||||||
pin-project-lite = "0.2"
|
pin-project-lite = "0.2"
|
||||||
|
poly1305 = "0.8.0"
|
||||||
|
zeroize = { version="1.7.0", features=["zeroize_derive"]}
|
||||||
|
axum-extra = { version="0.9.2", features=["async-read-body"]}
|
||||||
|
|
||||||
reqwest = { version="0.11", default_features=false, features=["rustls-tls", "json"] }
|
reqwest = { version="0.11", default_features=false, features=["rustls-tls", "json"] }
|
||||||
jsonwebtoken = "9.2.0"
|
jsonwebtoken = "9.2.0"
|
||||||
|
|
322
server/src/aeadstream.rs
Normal file
322
server/src/aeadstream.rs
Normal file
|
@ -0,0 +1,322 @@
|
||||||
|
use std::{
|
||||||
|
io::ErrorKind,
|
||||||
|
pin::Pin,
|
||||||
|
task::{ready, Context, Poll},
|
||||||
|
};
|
||||||
|
|
||||||
|
use chacha20::cipher::{generic_array::GenericArray, KeyInit, StreamCipher, StreamCipherSeek};
|
||||||
|
use pin_project_lite::pin_project;
|
||||||
|
use poly1305::{universal_hash::UniversalHash, Poly1305, Tag};
|
||||||
|
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
|
||||||
|
use zeroize::Zeroize;
|
||||||
|
|
||||||
|
pin_project! {
|
||||||
|
pub struct AeadStreamWriter<T, C>
|
||||||
|
where
|
||||||
|
T: AsyncWrite,
|
||||||
|
C: StreamCipher,
|
||||||
|
C: StreamCipherSeek
|
||||||
|
{
|
||||||
|
#[pin]
|
||||||
|
inner: T,
|
||||||
|
buf: Vec<u8>,
|
||||||
|
to_mac: Vec<u8>,
|
||||||
|
cipher: C,
|
||||||
|
mac: Poly1305,
|
||||||
|
tag: Option<Tag>,
|
||||||
|
encrypted: usize,
|
||||||
|
authenticated_data_len: usize,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, C> AeadStreamWriter<T, C>
|
||||||
|
where
|
||||||
|
T: AsyncWrite,
|
||||||
|
C: StreamCipher + StreamCipherSeek,
|
||||||
|
{
|
||||||
|
pub fn new(inner: T, mut cipher: C, authenticated_data: &[u8]) -> Self {
|
||||||
|
let mut mac_key = poly1305::Key::default();
|
||||||
|
cipher.apply_keystream(&mut mac_key);
|
||||||
|
|
||||||
|
let mut mac = Poly1305::new(GenericArray::from_slice(&mac_key));
|
||||||
|
mac_key.zeroize();
|
||||||
|
|
||||||
|
mac.update_padded(authenticated_data);
|
||||||
|
|
||||||
|
// Set ChaCha20 counter to 1
|
||||||
|
cipher.seek(64);
|
||||||
|
Self {
|
||||||
|
inner,
|
||||||
|
buf: vec![],
|
||||||
|
to_mac: vec![],
|
||||||
|
authenticated_data_len: authenticated_data.len(),
|
||||||
|
encrypted: 0,
|
||||||
|
cipher,
|
||||||
|
mac,
|
||||||
|
tag: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn tag(&self) -> Option<&Tag> {
|
||||||
|
self.tag.as_ref()
|
||||||
|
}
|
||||||
|
pub fn size(&self) -> usize {
|
||||||
|
self.encrypted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, C> AsyncWrite for AeadStreamWriter<T, C>
|
||||||
|
where
|
||||||
|
T: AsyncWrite,
|
||||||
|
C: StreamCipher + StreamCipherSeek,
|
||||||
|
{
|
||||||
|
fn poll_write(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
cx: &mut Context<'_>,
|
||||||
|
buf: &[u8],
|
||||||
|
) -> Poll<Result<usize, std::io::Error>> {
|
||||||
|
let me = self.project();
|
||||||
|
|
||||||
|
let mut buf = Vec::from(buf);
|
||||||
|
|
||||||
|
let buf_len = buf.len();
|
||||||
|
me.cipher.apply_keystream(&mut buf);
|
||||||
|
*me.encrypted += buf_len;
|
||||||
|
|
||||||
|
me.to_mac.append(&mut buf.clone());
|
||||||
|
let macable = me
|
||||||
|
.to_mac
|
||||||
|
.drain(..align(me.to_mac.len(), 16))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
me.mac.update_padded(&macable);
|
||||||
|
|
||||||
|
if !me.buf.is_empty() {
|
||||||
|
me.buf.append(&mut buf);
|
||||||
|
std::mem::swap(me.buf, &mut buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Poll::Ready(Ok(written)) = me.inner.poll_write(cx, &buf) {
|
||||||
|
buf.truncate(buf.len() - written);
|
||||||
|
}
|
||||||
|
std::mem::swap(me.buf, &mut buf);
|
||||||
|
|
||||||
|
Poll::Ready(Ok(buf_len))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), std::io::Error>> {
|
||||||
|
let me = self.project();
|
||||||
|
|
||||||
|
if !me.buf.is_empty() {
|
||||||
|
let written = ready!(me.inner.poll_write(cx, me.buf))?;
|
||||||
|
me.buf.truncate(written);
|
||||||
|
Poll::Pending
|
||||||
|
} else {
|
||||||
|
me.inner.poll_flush(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_shutdown(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
cx: &mut Context<'_>,
|
||||||
|
) -> Poll<Result<(), std::io::Error>> {
|
||||||
|
let me = self.project();
|
||||||
|
|
||||||
|
if !me.buf.is_empty() {
|
||||||
|
let written = ready!(me.inner.poll_write(cx, me.buf))?;
|
||||||
|
me.buf.truncate(written);
|
||||||
|
Poll::Pending
|
||||||
|
} else if me.tag.is_none() {
|
||||||
|
me.mac.update_padded(me.to_mac);
|
||||||
|
me.to_mac.clear();
|
||||||
|
|
||||||
|
authenticate_lengths(me.mac, *me.authenticated_data_len, *me.encrypted);
|
||||||
|
|
||||||
|
*me.tag = Some(me.mac.clone().finalize());
|
||||||
|
me.inner.poll_shutdown(cx)
|
||||||
|
} else {
|
||||||
|
me.inner.poll_shutdown(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pin_project! {
|
||||||
|
pub struct AeadStreamReader<T, C>
|
||||||
|
where
|
||||||
|
T: AsyncRead,
|
||||||
|
C: StreamCipher,
|
||||||
|
C: StreamCipherSeek,
|
||||||
|
{
|
||||||
|
#[pin]
|
||||||
|
inner: T,
|
||||||
|
buf: Vec<u8>,
|
||||||
|
cipher: C,
|
||||||
|
mac: AeadStreamReaderMac<Poly1305>,
|
||||||
|
decrypted_data_len: usize,
|
||||||
|
authenticated_data_len: usize,
|
||||||
|
expected_data_len: Option<usize>,
|
||||||
|
tag: Tag,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, C> AeadStreamReader<T, C>
|
||||||
|
where
|
||||||
|
T: AsyncRead,
|
||||||
|
C: StreamCipher + StreamCipherSeek,
|
||||||
|
{
|
||||||
|
pub fn new(
|
||||||
|
inner: T,
|
||||||
|
mut cipher: C,
|
||||||
|
authenticated_data: &[u8],
|
||||||
|
tag: Tag,
|
||||||
|
expected_data_len: Option<usize>,
|
||||||
|
) -> Self {
|
||||||
|
let mut mac_key = poly1305::Key::default();
|
||||||
|
cipher.apply_keystream(&mut mac_key);
|
||||||
|
|
||||||
|
let mut mac = Poly1305::new(GenericArray::from_slice(&mac_key));
|
||||||
|
mac_key.zeroize();
|
||||||
|
|
||||||
|
mac.update_padded(authenticated_data);
|
||||||
|
|
||||||
|
// Set ChaCha20 counter to 1
|
||||||
|
cipher.seek(64);
|
||||||
|
Self {
|
||||||
|
inner,
|
||||||
|
buf: vec![],
|
||||||
|
cipher,
|
||||||
|
mac: AeadStreamReaderMac::Running(mac),
|
||||||
|
authenticated_data_len: authenticated_data.len(),
|
||||||
|
decrypted_data_len: 0,
|
||||||
|
expected_data_len,
|
||||||
|
tag,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum AeadStreamReaderMac<M> {
|
||||||
|
Running(M),
|
||||||
|
Finished,
|
||||||
|
Failed,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, C> AsyncRead for AeadStreamReader<T, C>
|
||||||
|
where
|
||||||
|
T: AsyncRead,
|
||||||
|
C: StreamCipher + StreamCipherSeek,
|
||||||
|
{
|
||||||
|
fn poll_read(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
cx: &mut Context<'_>,
|
||||||
|
buf: &mut ReadBuf<'_>,
|
||||||
|
) -> Poll<std::io::Result<()>> {
|
||||||
|
let me = self.project();
|
||||||
|
|
||||||
|
let mut mac = AeadStreamReaderMac::Failed;
|
||||||
|
std::mem::swap(&mut mac, me.mac);
|
||||||
|
|
||||||
|
match mac {
|
||||||
|
AeadStreamReaderMac::Failed => Poll::Ready(Err(std::io::Error::new(
|
||||||
|
ErrorKind::InvalidData,
|
||||||
|
"decryption failed",
|
||||||
|
))),
|
||||||
|
AeadStreamReaderMac::Finished => Poll::Ready(Ok(())),
|
||||||
|
AeadStreamReaderMac::Running(mut mac) => {
|
||||||
|
let unchanged = {
|
||||||
|
let prev_buf_len = me.buf.len();
|
||||||
|
me.buf.extend_from_slice(&[0_u8; 512]);
|
||||||
|
let mut read_buf = ReadBuf::new(me.buf);
|
||||||
|
read_buf.set_filled(prev_buf_len);
|
||||||
|
|
||||||
|
if let Poll::Ready(res) = me.inner.poll_read(cx, &mut read_buf) {
|
||||||
|
res?;
|
||||||
|
let buf_len = read_buf.filled().len();
|
||||||
|
drop(read_buf);
|
||||||
|
me.buf.truncate(buf_len);
|
||||||
|
prev_buf_len == buf_len
|
||||||
|
} else {
|
||||||
|
me.buf.truncate(prev_buf_len);
|
||||||
|
|
||||||
|
*me.mac = AeadStreamReaderMac::Running(mac);
|
||||||
|
return Poll::Pending;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if unchanged && me.buf.len() <= 16 {
|
||||||
|
let decryptable = me.buf;
|
||||||
|
if !decryptable.is_empty() {
|
||||||
|
mac.update_padded(decryptable);
|
||||||
|
me.cipher.apply_keystream(decryptable);
|
||||||
|
|
||||||
|
*me.decrypted_data_len += decryptable.len();
|
||||||
|
|
||||||
|
buf.put_slice(decryptable);
|
||||||
|
}
|
||||||
|
|
||||||
|
authenticate_lengths(
|
||||||
|
&mut mac,
|
||||||
|
*me.authenticated_data_len,
|
||||||
|
*me.decrypted_data_len,
|
||||||
|
);
|
||||||
|
|
||||||
|
match mac.verify(me.tag) {
|
||||||
|
Ok(_) => {
|
||||||
|
*me.mac = AeadStreamReaderMac::Finished;
|
||||||
|
Poll::Ready(Ok(()))
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
*me.mac = AeadStreamReaderMac::Failed;
|
||||||
|
Poll::Ready(Err(std::io::Error::new(
|
||||||
|
ErrorKind::InvalidData,
|
||||||
|
"decryption failed",
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let (decryptable, residual) = {
|
||||||
|
let decryptable_buffer_len = align(min(buf.remaining(), me.buf.len()), 16);
|
||||||
|
me.buf.split_at_mut(decryptable_buffer_len)
|
||||||
|
};
|
||||||
|
if !decryptable.is_empty() {
|
||||||
|
mac.update_padded(decryptable);
|
||||||
|
|
||||||
|
me.cipher.apply_keystream(decryptable);
|
||||||
|
|
||||||
|
*me.decrypted_data_len += decryptable.len();
|
||||||
|
|
||||||
|
buf.put_slice(decryptable);
|
||||||
|
|
||||||
|
{
|
||||||
|
let decryptable_len = decryptable.len();
|
||||||
|
let residual_len = residual.len();
|
||||||
|
me.buf.copy_within(decryptable_len.., 0);
|
||||||
|
me.buf.truncate(residual_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
*me.mac = AeadStreamReaderMac::Running(mac);
|
||||||
|
Poll::Ready(Ok(()))
|
||||||
|
} else {
|
||||||
|
*me.mac = AeadStreamReaderMac::Running(mac);
|
||||||
|
Poll::Pending
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn min(a: usize, b: usize) -> usize {
|
||||||
|
match a < b {
|
||||||
|
true => a,
|
||||||
|
false => b,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn align(val: usize, m: usize) -> usize {
|
||||||
|
val - (val % m)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn authenticate_lengths(mac: &mut Poly1305, associated_data_len: usize, buffer_len: usize) {
|
||||||
|
let mut block = GenericArray::default();
|
||||||
|
block[..8].copy_from_slice(&associated_data_len.to_le_bytes());
|
||||||
|
block[8..].copy_from_slice(&buffer_len.to_le_bytes());
|
||||||
|
mac.update(&[block]);
|
||||||
|
}
|
|
@ -21,6 +21,9 @@ pub enum Error {
|
||||||
#[error("file exists")]
|
#[error("file exists")]
|
||||||
DataFileExists,
|
DataFileExists,
|
||||||
|
|
||||||
|
#[error("bin verification failed")]
|
||||||
|
BinVerificationFailed,
|
||||||
|
|
||||||
#[error("hex error: {:?}", 0)]
|
#[error("hex error: {:?}", 0)]
|
||||||
Hex(#[from] hex::FromHexError),
|
Hex(#[from] hex::FromHexError),
|
||||||
|
|
||||||
|
@ -59,6 +62,11 @@ impl IntoResponse for Error {
|
||||||
fn into_response(self) -> axum::response::Response {
|
fn into_response(self) -> axum::response::Response {
|
||||||
debug!("{:?}", &self);
|
debug!("{:?}", &self);
|
||||||
match self {
|
match self {
|
||||||
|
Self::BinVerificationFailed => (
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
"bin verification failed\n",
|
||||||
|
)
|
||||||
|
.into_response(),
|
||||||
Self::PhraseInvalid => (StatusCode::BAD_REQUEST, "url is not valid\n").into_response(),
|
Self::PhraseInvalid => (StatusCode::BAD_REQUEST, "url is not valid\n").into_response(),
|
||||||
Self::BinNotFound => (StatusCode::NOT_FOUND, "bin does not exist\n").into_response(),
|
Self::BinNotFound => (StatusCode::NOT_FOUND, "bin does not exist\n").into_response(),
|
||||||
Self::DataFileExists => {
|
Self::DataFileExists => {
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
#![deny(clippy::unwrap_used)]
|
#![deny(clippy::unwrap_used)]
|
||||||
|
use aeadstream::AeadStreamReader;
|
||||||
|
use axum_extra::body::AsyncReadBody;
|
||||||
use axum_oidc::{
|
use axum_oidc::{
|
||||||
error::{ExtractorError, MiddlewareError},
|
error::{ExtractorError, MiddlewareError},
|
||||||
EmptyAdditionalClaims, OidcAuthLayer, OidcClaims, OidcLoginLayer,
|
EmptyAdditionalClaims, OidcAuthLayer, OidcClaims, OidcLoginLayer,
|
||||||
};
|
};
|
||||||
use duration_str::{deserialize_option_duration, parse_std};
|
|
||||||
use jwt::JwtApplication;
|
use jwt::JwtApplication;
|
||||||
|
use poly1305::Tag;
|
||||||
use prometheus_client::{
|
use prometheus_client::{
|
||||||
encoding::EncodeLabelSet,
|
encoding::EncodeLabelSet,
|
||||||
metrics::{counter::Counter, family::Family, gauge::Gauge},
|
metrics::{counter::Counter, family::Family, gauge::Gauge},
|
||||||
|
@ -14,58 +16,54 @@ use sailfish::TemplateOnce;
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Borrow,
|
borrow::Borrow,
|
||||||
env,
|
env,
|
||||||
|
io::SeekFrom,
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
time::{Duration, SystemTime, UNIX_EPOCH},
|
time::{Duration, SystemTime, UNIX_EPOCH},
|
||||||
};
|
};
|
||||||
use tower_http::services::ServeDir;
|
use tower_http::{cors::CorsLayer, services::ServeDir};
|
||||||
|
|
||||||
use tower::ServiceBuilder;
|
use tower::ServiceBuilder;
|
||||||
use tower_sessions::{cookie::SameSite, MemoryStore, SessionManagerLayer};
|
use tower_sessions::{cookie::SameSite, MemoryStore, SessionManagerLayer};
|
||||||
|
|
||||||
use axum::{
|
use axum::{
|
||||||
async_trait,
|
|
||||||
body::Body,
|
body::Body,
|
||||||
error_handling::HandleErrorLayer,
|
error_handling::HandleErrorLayer,
|
||||||
extract::{FromRef, FromRequest, Multipart, Path, Query, State},
|
extract::{FromRef, Path, Query, State},
|
||||||
http::{
|
http::{
|
||||||
header::{self, CONTENT_TYPE},
|
header::{self, CONTENT_TYPE},
|
||||||
HeaderMap, HeaderValue, Request, StatusCode, Uri,
|
HeaderMap, HeaderValue, Method, StatusCode, Uri,
|
||||||
},
|
},
|
||||||
response::{Html, IntoResponse, Redirect, Response},
|
response::{Html, IntoResponse, Redirect},
|
||||||
routing::{delete, get, post},
|
routing::{delete, get},
|
||||||
BoxError, Router,
|
Router,
|
||||||
};
|
};
|
||||||
|
|
||||||
use chacha20::{
|
use chacha20::{cipher::KeyIvInit, ChaCha20};
|
||||||
cipher::{KeyIvInit, StreamCipher},
|
|
||||||
ChaCha20,
|
|
||||||
};
|
|
||||||
use futures_util::StreamExt;
|
use futures_util::StreamExt;
|
||||||
use garbage_collector::GarbageCollector;
|
use garbage_collector::GarbageCollector;
|
||||||
use log::{debug, warn};
|
use log::{debug, warn};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use sha3::{Digest, Sha3_256};
|
|
||||||
use tokio::{
|
use tokio::{
|
||||||
fs::{self, File},
|
fs::{self, File},
|
||||||
io::{AsyncWriteExt, BufReader, BufWriter},
|
io::{AsyncSeekExt, AsyncWriteExt, BufReader, BufWriter},
|
||||||
net::TcpListener,
|
net::TcpListener,
|
||||||
};
|
};
|
||||||
use util::{IdSalt, KeySalt};
|
use util::{IdSalt, KeySalt};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
aeadstream::AeadStreamWriter,
|
||||||
error::Error,
|
error::Error,
|
||||||
jwt::Claims,
|
jwt::Claims,
|
||||||
metadata::Metadata,
|
metadata::Metadata,
|
||||||
util::{Id, Key, Nonce, Phrase},
|
util::{Id, Key, Nonce, Phrase},
|
||||||
web_util::DecryptingStream,
|
|
||||||
};
|
};
|
||||||
|
mod aeadstream;
|
||||||
mod error;
|
mod error;
|
||||||
mod garbage_collector;
|
mod garbage_collector;
|
||||||
mod jwt;
|
mod jwt;
|
||||||
mod metadata;
|
mod metadata;
|
||||||
mod util;
|
mod util;
|
||||||
mod web_util;
|
|
||||||
|
|
||||||
/// length of the "phrase" that is used to access the bin (https://example.com/<phrase>)
|
/// length of the "phrase" that is used to access the bin (https://example.com/<phrase>)
|
||||||
const PHRASE_LENGTH: usize = 16;
|
const PHRASE_LENGTH: usize = 16;
|
||||||
|
@ -156,6 +154,14 @@ async fn main() {
|
||||||
.await
|
.await
|
||||||
.expect("Jwt Authentication Client");
|
.expect("Jwt Authentication Client");
|
||||||
|
|
||||||
|
let cors = CorsLayer::new()
|
||||||
|
.allow_methods([Method::GET, Method::POST, Method::PUT])
|
||||||
|
.allow_origin(
|
||||||
|
application_base
|
||||||
|
.parse::<HeaderValue>()
|
||||||
|
.expect("valid APPLICATION_BASE"),
|
||||||
|
);
|
||||||
|
|
||||||
let data_path = env::var("DATA_PATH").expect("DATA_PATH env var");
|
let data_path = env::var("DATA_PATH").expect("DATA_PATH env var");
|
||||||
|
|
||||||
let mut registry = Registry::default();
|
let mut registry = Registry::default();
|
||||||
|
@ -211,7 +217,8 @@ async fn main() {
|
||||||
.nest_service("/static", ServeDir::new("static"))
|
.nest_service("/static", ServeDir::new("static"))
|
||||||
.with_state(state)
|
.with_state(state)
|
||||||
.layer(oidc_auth_service)
|
.layer(oidc_auth_service)
|
||||||
.layer(session_service);
|
.layer(session_service)
|
||||||
|
.layer(cors);
|
||||||
|
|
||||||
let listener = TcpListener::bind("[::]:8080")
|
let listener = TcpListener::bind("[::]:8080")
|
||||||
.await
|
.await
|
||||||
|
@ -242,7 +249,7 @@ async fn get_index(
|
||||||
let metadata = Metadata {
|
let metadata = Metadata {
|
||||||
subject,
|
subject,
|
||||||
nonce: nonce.to_hex(),
|
nonce: nonce.to_hex(),
|
||||||
etag: None,
|
tag: None,
|
||||||
size: None,
|
size: None,
|
||||||
content_type: None,
|
content_type: None,
|
||||||
expires_at,
|
expires_at,
|
||||||
|
@ -315,7 +322,7 @@ async fn upload_bin(
|
||||||
Query(params): Query<PostQuery>,
|
Query(params): Query<PostQuery>,
|
||||||
State(app_state): State<AppState>,
|
State(app_state): State<AppState>,
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
data: MultipartOrStream,
|
body: Body,
|
||||||
) -> HandlerResult<impl IntoResponse> {
|
) -> HandlerResult<impl IntoResponse> {
|
||||||
let phrase = Phrase::from_str(&phrase)?;
|
let phrase = Phrase::from_str(&phrase)?;
|
||||||
let id = Id::from_phrase(&phrase, &app_state.id_salt);
|
let id = Id::from_phrase(&phrase, &app_state.id_salt);
|
||||||
|
@ -329,30 +336,28 @@ async fn upload_bin(
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
let key = Key::from_phrase(&phrase, &app_state.key_salt);
|
let key = Key::from_phrase(&phrase, &app_state.key_salt);
|
||||||
let nonce = Nonce::from_hex(&metadata.nonce)?;
|
let nonce = Nonce::from_hex(&metadata.nonce)?;
|
||||||
let mut cipher = ChaCha20::new(key.borrow(), nonce.borrow());
|
|
||||||
|
|
||||||
let file = File::create(&path).await?;
|
let file = File::create(&path).await?;
|
||||||
let mut writer = BufWriter::new(file);
|
let writer = BufWriter::new(file);
|
||||||
|
|
||||||
let mut etag_hasher = Sha3_256::new();
|
let ttl = params
|
||||||
let mut size: u64 = 0;
|
|
||||||
|
|
||||||
let mut ttl = params
|
|
||||||
.ttl
|
.ttl
|
||||||
.map(|x| duration_str::parse(&x).map_err(|_| Error::InvalidTtl))
|
.map(|x| duration_str::parse(x).map_err(|_| Error::InvalidTtl))
|
||||||
.transpose()?
|
.transpose()?
|
||||||
.unwrap_or(Duration::from_secs(24 * 3600));
|
.unwrap_or(Duration::from_secs(24 * 3600));
|
||||||
|
|
||||||
match data {
|
let cipher = ChaCha20::new(key.borrow(), nonce.borrow());
|
||||||
MultipartOrStream::Stream(stream) => {
|
let mut writer = AeadStreamWriter::new(writer, cipher, &[]);
|
||||||
let mut stream = stream.into_data_stream();
|
|
||||||
|
let mut stream = body.into_data_stream();
|
||||||
while let Some(chunk) = stream.next().await {
|
while let Some(chunk) = stream.next().await {
|
||||||
let mut buf = chunk.unwrap_or_default().to_vec();
|
writer.write_all(chunk.unwrap_or_default().as_ref()).await?;
|
||||||
etag_hasher.update(&buf);
|
|
||||||
size += buf.len() as u64;
|
|
||||||
cipher.apply_keystream(&mut buf);
|
|
||||||
writer.write_all(&buf).await?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
writer.flush().await?;
|
||||||
|
writer.shutdown().await?;
|
||||||
|
let tag = writer.tag().expect("valid tag");
|
||||||
|
|
||||||
metadata.content_type = match headers.get(CONTENT_TYPE) {
|
metadata.content_type = match headers.get(CONTENT_TYPE) {
|
||||||
Some(content_type) => Some(
|
Some(content_type) => Some(
|
||||||
content_type
|
content_type
|
||||||
|
@ -363,41 +368,6 @@ async fn upload_bin(
|
||||||
),
|
),
|
||||||
None => Some("application/octet-stream".to_string()),
|
None => Some("application/octet-stream".to_string()),
|
||||||
};
|
};
|
||||||
}
|
|
||||||
MultipartOrStream::Multipart(mut multipart) => {
|
|
||||||
let mut file_read = false;
|
|
||||||
while let Some(mut field) = multipart
|
|
||||||
.next_field()
|
|
||||||
.await
|
|
||||||
.map_err(|_| Error::InvalidMultipart)?
|
|
||||||
{
|
|
||||||
if field.name().unwrap_or_default() == "file" && !file_read {
|
|
||||||
while let Some(chunk) = field.chunk().await.unwrap_or_default() {
|
|
||||||
let mut buf = chunk.to_vec();
|
|
||||||
etag_hasher.update(&buf);
|
|
||||||
size += buf.len() as u64;
|
|
||||||
cipher.apply_keystream(&mut buf);
|
|
||||||
writer.write_all(&buf).await?;
|
|
||||||
}
|
|
||||||
metadata.content_type = Some(
|
|
||||||
field
|
|
||||||
.content_type()
|
|
||||||
.map(|x| x.to_string())
|
|
||||||
.unwrap_or("application/octet-stream".to_string()),
|
|
||||||
);
|
|
||||||
file_read = true;
|
|
||||||
}
|
|
||||||
if field.name().unwrap_or_default() == "ttl" {
|
|
||||||
if let Some(mp_ttl) =
|
|
||||||
field.text().await.ok().and_then(|x| parse_std(x).ok())
|
|
||||||
{
|
|
||||||
ttl = mp_ttl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
writer.flush().await?;
|
|
||||||
|
|
||||||
if let Some(expires_at) = SystemTime::now()
|
if let Some(expires_at) = SystemTime::now()
|
||||||
.duration_since(UNIX_EPOCH)?
|
.duration_since(UNIX_EPOCH)?
|
||||||
|
@ -407,8 +377,8 @@ async fn upload_bin(
|
||||||
metadata.expires_at = expires_at;
|
metadata.expires_at = expires_at;
|
||||||
}
|
}
|
||||||
|
|
||||||
metadata.etag = Some(hex::encode(etag_hasher.finalize()));
|
metadata.tag = Some(hex::encode(tag));
|
||||||
metadata.size = Some(size);
|
metadata.size = Some(writer.size() as u64);
|
||||||
|
|
||||||
metadata.to_file(&metadata_path).await?;
|
metadata.to_file(&metadata_path).await?;
|
||||||
|
|
||||||
|
@ -459,21 +429,33 @@ async fn get_item(
|
||||||
} else {
|
} else {
|
||||||
//TODO(pfz4): Maybe add link handling
|
//TODO(pfz4): Maybe add link handling
|
||||||
let file = File::open(&path).await?;
|
let file = File::open(&path).await?;
|
||||||
let reader = BufReader::new(file);
|
let mut reader = BufReader::new(file);
|
||||||
|
|
||||||
let body = Body::from_stream(DecryptingStream::new(
|
let tag = *Tag::from_slice(
|
||||||
|
&hex::decode(metadata.tag.as_deref().unwrap_or("")).unwrap_or_default(),
|
||||||
|
);
|
||||||
|
|
||||||
|
reader.seek(SeekFrom::Start(0)).await?;
|
||||||
|
|
||||||
|
let cipher = ChaCha20::new(key.borrow(), nonce.borrow());
|
||||||
|
let body = AsyncReadBody::new(AeadStreamReader::new(
|
||||||
reader,
|
reader,
|
||||||
id.clone(),
|
cipher,
|
||||||
&metadata,
|
&[],
|
||||||
&key,
|
tag,
|
||||||
&nonce,
|
metadata.size.map(|x| x as usize),
|
||||||
));
|
));
|
||||||
|
|
||||||
let mut headers = HeaderMap::new();
|
let mut headers = HeaderMap::new();
|
||||||
headers.insert(
|
// If send, the client directly disconnects after the CONTENT_LENGTH bytes are received.
|
||||||
header::CONTENT_LENGTH,
|
// Because of that the MAC validation is skipped and the user may receive invalid data.
|
||||||
metadata.size.unwrap_or_default().into(),
|
// Not sending the CONTENT_LENGTH results in the Client asking the server again, triggering
|
||||||
);
|
// MAC validation.
|
||||||
|
// TODO: check encrypted to the size, triggering the MAC validation on the last byte
|
||||||
|
//headers.insert(
|
||||||
|
// header::CONTENT_LENGTH,
|
||||||
|
// metadata.size.unwrap_or_default().into(),
|
||||||
|
//);
|
||||||
if let Ok(subject) = metadata.subject.parse() {
|
if let Ok(subject) = metadata.subject.parse() {
|
||||||
headers.insert("CreatedBy", subject);
|
headers.insert("CreatedBy", subject);
|
||||||
}
|
}
|
||||||
|
@ -481,15 +463,6 @@ async fn get_item(
|
||||||
if let Some(content_type) = metadata.content_type.and_then(|x| x.parse().ok()) {
|
if let Some(content_type) = metadata.content_type.and_then(|x| x.parse().ok()) {
|
||||||
headers.insert(header::CONTENT_TYPE, content_type);
|
headers.insert(header::CONTENT_TYPE, content_type);
|
||||||
}
|
}
|
||||||
if let Some(etag) = metadata.etag.clone().and_then(|x| x.parse().ok()) {
|
|
||||||
headers.insert(header::ETAG, etag);
|
|
||||||
}
|
|
||||||
if let Some(digest) = metadata
|
|
||||||
.etag
|
|
||||||
.and_then(|x| format!("sha3-256={x}").parse().ok())
|
|
||||||
{
|
|
||||||
headers.insert("Digest", digest);
|
|
||||||
}
|
|
||||||
|
|
||||||
app_state
|
app_state
|
||||||
.metrics
|
.metrics
|
||||||
|
@ -515,41 +488,6 @@ async fn metrics(State(app_state): State<AppState>) -> HandlerResult<impl IntoRe
|
||||||
Ok((headers, buffer))
|
Ok((headers, buffer))
|
||||||
}
|
}
|
||||||
|
|
||||||
enum MultipartOrStream {
|
|
||||||
Multipart(Multipart),
|
|
||||||
Stream(Body),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl<S: Sync + Send> FromRequest<S> for MultipartOrStream {
|
|
||||||
type Rejection = Response;
|
|
||||||
|
|
||||||
async fn from_request(req: Request<Body>, state: &S) -> Result<Self, Self::Rejection> {
|
|
||||||
let is_multipart = req
|
|
||||||
.headers()
|
|
||||||
.get(CONTENT_TYPE)
|
|
||||||
.and_then(|x| {
|
|
||||||
x.to_str()
|
|
||||||
.ok()
|
|
||||||
.map(|y| y.starts_with("multipart/form-data"))
|
|
||||||
})
|
|
||||||
.unwrap_or_default();
|
|
||||||
if is_multipart {
|
|
||||||
Ok(Self::Multipart(
|
|
||||||
Multipart::from_request(req, state)
|
|
||||||
.await
|
|
||||||
.map_err(|x| x.into_response())?,
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
Ok(Self::Stream(
|
|
||||||
Body::from_request(req, state)
|
|
||||||
.await
|
|
||||||
.map_err(|x| x.into_response())?,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(TemplateOnce)]
|
#[derive(TemplateOnce)]
|
||||||
#[template(path = "index.stpl")]
|
#[template(path = "index.stpl")]
|
||||||
pub struct IndexTemplate<'a> {
|
pub struct IndexTemplate<'a> {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use std::{io::ErrorKind, time::Instant};
|
use std::{io::ErrorKind, time::Instant};
|
||||||
|
|
||||||
|
use log::debug;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::{fs::File, io::AsyncWriteExt};
|
use tokio::{fs::File, io::AsyncWriteExt};
|
||||||
|
|
||||||
|
@ -9,7 +10,7 @@ use crate::Error;
|
||||||
pub struct Metadata {
|
pub struct Metadata {
|
||||||
pub subject: String,
|
pub subject: String,
|
||||||
pub nonce: String,
|
pub nonce: String,
|
||||||
pub etag: Option<String>,
|
pub tag: Option<String>,
|
||||||
pub size: Option<u64>,
|
pub size: Option<u64>,
|
||||||
pub content_type: Option<String>,
|
pub content_type: Option<String>,
|
||||||
pub expires_at: u64, // seconds since UNIX_EPOCH
|
pub expires_at: u64, // seconds since UNIX_EPOCH
|
||||||
|
@ -19,7 +20,10 @@ impl Metadata {
|
||||||
pub async fn from_file(path: &str) -> Result<Self, Error> {
|
pub async fn from_file(path: &str) -> Result<Self, Error> {
|
||||||
let metadata = match tokio::fs::read_to_string(path).await {
|
let metadata = match tokio::fs::read_to_string(path).await {
|
||||||
Ok(x) => Ok(x),
|
Ok(x) => Ok(x),
|
||||||
Err(err) if err.kind() == ErrorKind::NotFound => Err(Error::BinNotFound),
|
Err(err) if err.kind() == ErrorKind::NotFound => {
|
||||||
|
debug!("{} {:?}", path, err);
|
||||||
|
Err(Error::BinNotFound)
|
||||||
|
}
|
||||||
Err(x) => Err(x.into()),
|
Err(x) => Err(x.into()),
|
||||||
}?;
|
}?;
|
||||||
Ok(toml::from_str::<Self>(&metadata)?)
|
Ok(toml::from_str::<Self>(&metadata)?)
|
||||||
|
|
|
@ -1,133 +0,0 @@
|
||||||
use std::{
|
|
||||||
borrow::Borrow,
|
|
||||||
pin::Pin,
|
|
||||||
task::{Context, Poll},
|
|
||||||
};
|
|
||||||
|
|
||||||
use bytes::{Bytes, BytesMut};
|
|
||||||
use chacha20::{
|
|
||||||
cipher::{KeyIvInit, StreamCipher},
|
|
||||||
ChaCha20,
|
|
||||||
};
|
|
||||||
use futures_util::Stream;
|
|
||||||
use log::{debug, warn};
|
|
||||||
use pin_project_lite::pin_project;
|
|
||||||
use sha3::{Digest, Sha3_256};
|
|
||||||
use tokio::io::AsyncRead;
|
|
||||||
use tokio_util::io::poll_read_buf;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
metadata::Metadata,
|
|
||||||
util::{Id, Key, Nonce},
|
|
||||||
};
|
|
||||||
pin_project! {
|
|
||||||
pub(crate) struct DecryptingStream<R> {
|
|
||||||
#[pin]
|
|
||||||
reader: Option<R>,
|
|
||||||
buf: BytesMut,
|
|
||||||
// chunk size
|
|
||||||
capacity: usize,
|
|
||||||
// chacha20 cipher
|
|
||||||
cipher: ChaCha20,
|
|
||||||
// hasher to verify file integrity
|
|
||||||
hasher: Sha3_256,
|
|
||||||
// hash to verify against
|
|
||||||
target_hash: String,
|
|
||||||
// id of the file for logging purposes
|
|
||||||
id: Id,
|
|
||||||
// total file size
|
|
||||||
size: u64,
|
|
||||||
// current position of the "reading head"
|
|
||||||
progress: u64
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<R: AsyncRead> DecryptingStream<R> {
|
|
||||||
pub(crate) fn new(reader: R, id: Id, metadata: &Metadata, key: &Key, nonce: &Nonce) -> Self {
|
|
||||||
let cipher = ChaCha20::new(key.borrow(), nonce.borrow());
|
|
||||||
Self {
|
|
||||||
reader: Some(reader),
|
|
||||||
buf: BytesMut::new(),
|
|
||||||
capacity: 1 << 22, // 4 MiB
|
|
||||||
cipher,
|
|
||||||
hasher: Sha3_256::new(),
|
|
||||||
target_hash: metadata.etag.clone().unwrap_or_default(),
|
|
||||||
id,
|
|
||||||
size: metadata.size.unwrap_or_default(),
|
|
||||||
progress: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<R: AsyncRead> Stream for DecryptingStream<R> {
|
|
||||||
type Item = std::io::Result<Bytes>;
|
|
||||||
|
|
||||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
|
||||||
let mut this = self.as_mut().project();
|
|
||||||
|
|
||||||
let reader = match this.reader.as_pin_mut() {
|
|
||||||
Some(r) => r,
|
|
||||||
None => return Poll::Ready(None),
|
|
||||||
};
|
|
||||||
|
|
||||||
if this.buf.capacity() == 0 {
|
|
||||||
this.buf.reserve(*this.capacity);
|
|
||||||
}
|
|
||||||
|
|
||||||
match poll_read_buf(reader, cx, &mut this.buf) {
|
|
||||||
Poll::Pending => Poll::Pending,
|
|
||||||
Poll::Ready(Err(err)) => {
|
|
||||||
debug!("failed to send bin {}", this.id);
|
|
||||||
self.project().reader.set(None);
|
|
||||||
Poll::Ready(Some(Err(err)))
|
|
||||||
}
|
|
||||||
Poll::Ready(Ok(0)) => {
|
|
||||||
if self.progress_check() == DecryptingStreamProgress::Failed {
|
|
||||||
// The hash is invalid, the file has been tampered with. Close reader and stream causing the download to fail
|
|
||||||
self.project().reader.set(None);
|
|
||||||
return Poll::Ready(None);
|
|
||||||
};
|
|
||||||
self.project().reader.set(None);
|
|
||||||
Poll::Ready(None)
|
|
||||||
}
|
|
||||||
Poll::Ready(Ok(n)) => {
|
|
||||||
let mut chunk = this.buf.split();
|
|
||||||
// decrypt the chunk using chacha
|
|
||||||
this.cipher.apply_keystream(&mut chunk);
|
|
||||||
// update the sha3 hasher
|
|
||||||
this.hasher.update(&chunk);
|
|
||||||
// track progress
|
|
||||||
*this.progress += n as u64;
|
|
||||||
if self.progress_check() == DecryptingStreamProgress::Failed {
|
|
||||||
// The hash is invalid, the file has been tampered with. Close reader and stream causing the download to fail
|
|
||||||
warn!("bin {} is corrupted! transmission failed", self.id);
|
|
||||||
self.project().reader.set(None);
|
|
||||||
return Poll::Ready(None);
|
|
||||||
};
|
|
||||||
Poll::Ready(Some(Ok(chunk.freeze())))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<R: AsyncRead> DecryptingStream<R> {
|
|
||||||
/// checks if the hash is correct when the last byte has been read
|
|
||||||
fn progress_check(&self) -> DecryptingStreamProgress {
|
|
||||||
if self.progress >= self.size {
|
|
||||||
let hash = hex::encode(self.hasher.clone().finalize());
|
|
||||||
if hash != self.target_hash {
|
|
||||||
DecryptingStreamProgress::Failed
|
|
||||||
} else {
|
|
||||||
DecryptingStreamProgress::Finished
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
DecryptingStreamProgress::Running
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(PartialEq, Eq)]
|
|
||||||
enum DecryptingStreamProgress {
|
|
||||||
Finished,
|
|
||||||
Failed,
|
|
||||||
Running,
|
|
||||||
}
|
|
|
@ -1,172 +0,0 @@
|
||||||
/*
|
|
||||||
* GENERAL
|
|
||||||
*/
|
|
||||||
:root {
|
|
||||||
--s0: 1px;
|
|
||||||
--s1: 2px;
|
|
||||||
--s2: 4px;
|
|
||||||
--s3: 8px;
|
|
||||||
--s4: 16px;
|
|
||||||
}
|
|
||||||
html {
|
|
||||||
font-family: monospace;
|
|
||||||
font-size: 16px;
|
|
||||||
line-height: calc(1em + var(--spacing-2));
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
min-height: 100vh;
|
|
||||||
max-width: 100%;
|
|
||||||
background-color: black;
|
|
||||||
color: #eceff4;
|
|
||||||
margin: 0;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
main {
|
|
||||||
border: 1px solid white;
|
|
||||||
padding: var(--s3);
|
|
||||||
max-width: calc(100% - 2*var(--s3) - 2*var(--s0));
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: var(--s4);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.watermark {
|
|
||||||
position: fixed;
|
|
||||||
right: 0.25rem;
|
|
||||||
bottom: 0.25rem;
|
|
||||||
filter: invert();
|
|
||||||
height: 1em;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
.watermark img {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* LAYOUT
|
|
||||||
*/
|
|
||||||
.container {
|
|
||||||
width: 100%;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
@media (min-width: 576px) {
|
|
||||||
.container {
|
|
||||||
max-width: 540px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
.container {
|
|
||||||
max-width: 720px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@media (min-width: 992px) {
|
|
||||||
.container {
|
|
||||||
max-width: 960px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@media (min-width: 1200px) {
|
|
||||||
.container {
|
|
||||||
max-width: 1140px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.centered {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* TYPOGRAPHY
|
|
||||||
*/
|
|
||||||
h1,h2,h3,h4,h5,h6,p {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
h1 {
|
|
||||||
font-size: 1.6rem;
|
|
||||||
}
|
|
||||||
h2 {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
}
|
|
||||||
h3 {
|
|
||||||
font-size: 1.4rem;
|
|
||||||
}
|
|
||||||
h4 {
|
|
||||||
font-size: 1.3rem;
|
|
||||||
}
|
|
||||||
h5 {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
}
|
|
||||||
h6 {
|
|
||||||
font-size: 1.1rem;
|
|
||||||
}
|
|
||||||
small {
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* COMPONENTS
|
|
||||||
*/
|
|
||||||
pre {
|
|
||||||
border-width: var(--s0) var(--s0) var(--s0) var(--s2);
|
|
||||||
border-color: #e5e9f0;
|
|
||||||
border-style: solid;
|
|
||||||
padding-left: var(--s1);
|
|
||||||
font-size: 0.8rem;
|
|
||||||
overflow-x: auto;
|
|
||||||
margin: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
article {
|
|
||||||
border: var(--s0) solid #e5e9f0;
|
|
||||||
padding: var(--s3);
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
background-color: white;
|
|
||||||
color: black;
|
|
||||||
border: 1px solid white;
|
|
||||||
font-weight: bold;
|
|
||||||
padding: 0.5em;
|
|
||||||
font-size: 1.1em;
|
|
||||||
cursor: pointer;
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
button:active, input[type="file"]::file-selector-button:active {
|
|
||||||
background-color: black;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Form
|
|
||||||
*/
|
|
||||||
form{
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: var(--s3);
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="file"] {
|
|
||||||
display: block;
|
|
||||||
border: 1px solid white;
|
|
||||||
font-weight: bold;
|
|
||||||
width: calc(100% - 2px);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
input[type="file"]::file-selector-button {
|
|
||||||
background-color: white;
|
|
||||||
color: black;
|
|
||||||
font-weight: bold;
|
|
||||||
border: none;
|
|
||||||
padding: 0.5em;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,60 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
||||||
|
|
||||||
<svg
|
|
||||||
width="34mm"
|
|
||||||
height="15mm"
|
|
||||||
viewBox="0 0 34 15"
|
|
||||||
version="1.1"
|
|
||||||
id="svg5"
|
|
||||||
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
|
||||||
sodipodi:docname="zettoIT_new.svg"
|
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg">
|
|
||||||
<sodipodi:namedview
|
|
||||||
id="namedview7"
|
|
||||||
pagecolor="#505050"
|
|
||||||
bordercolor="#eeeeee"
|
|
||||||
borderopacity="1"
|
|
||||||
inkscape:showpageshadow="0"
|
|
||||||
inkscape:pageopacity="0"
|
|
||||||
inkscape:pagecheckerboard="0"
|
|
||||||
inkscape:deskcolor="#505050"
|
|
||||||
inkscape:document-units="mm"
|
|
||||||
showgrid="true"
|
|
||||||
inkscape:zoom="3.659624"
|
|
||||||
inkscape:cx="94.95511"
|
|
||||||
inkscape:cy="25.002569"
|
|
||||||
inkscape:window-width="1916"
|
|
||||||
inkscape:window-height="2110"
|
|
||||||
inkscape:window-x="0"
|
|
||||||
inkscape:window-y="0"
|
|
||||||
inkscape:window-maximized="1"
|
|
||||||
inkscape:current-layer="layer1">
|
|
||||||
<inkscape:grid
|
|
||||||
type="xygrid"
|
|
||||||
id="grid4316"
|
|
||||||
empspacing="4"
|
|
||||||
originx="0"
|
|
||||||
originy="0" />
|
|
||||||
</sodipodi:namedview>
|
|
||||||
<defs
|
|
||||||
id="defs2" />
|
|
||||||
<g
|
|
||||||
inkscape:label="Ebene 1"
|
|
||||||
inkscape:groupmode="layer"
|
|
||||||
id="layer1">
|
|
||||||
<path
|
|
||||||
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal"
|
|
||||||
d="M 1.0583328,3.175 H 17.991667 l -1e-6,-2.1166667 h 2.116667 V 3.175 h 2.116666 l -1e-6,-2.1166667 h 2.116667 V 3.175 h 2.116667 6.35 v 10.583333 h -6.35 V 6.35 l 2.116666,-10e-8 10e-7,5.2916661 h 2.116666 V 5.2916666 h -6.35 v 8.4666664 h -2.116667 l 1e-6,-8.4666664 h -2.116666 v 8.4666664 h -2.116667 l 1e-6,-8.4666664 -6.350001,-2e-7 v 2.1166667 l 3.175,2e-7 v 2.116667 c -2.245125,-3e-6 -1.763889,0 -3.175,0 v 2.1166657 h 4.233333 v 2.116667 H 9.5249988 V 5.2916664 l -8.466666,2e-7 z"
|
|
||||||
id="path13059"
|
|
||||||
sodipodi:nodetypes="cccccccccccccccccccccccccccccccccccccc" />
|
|
||||||
<path
|
|
||||||
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
|
||||||
d="m 1.0583328,11.641666 v 2.116667 h 6.35 V 11.641666 H 3.1749995 L 5.2916662,6.35 l -2.1166667,-10e-8 c 0,0 -2.1166667,5.2916661 -2.1166667,5.2916661 z"
|
|
||||||
id="path13061"
|
|
||||||
sodipodi:nodetypes="cccccccc" />
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 2.5 KiB |
23
server/templates/background.stpl
Normal file
23
server/templates/background.stpl
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
<script>
|
||||||
|
window.addEventListener("resize", paint);
|
||||||
|
paint();
|
||||||
|
|
||||||
|
function paint() {
|
||||||
|
var canvas = document.getElementById("matrix");
|
||||||
|
var ctx = canvas.getContext("2d");
|
||||||
|
ctx.reset();
|
||||||
|
canvas.width = window.innerWidth;
|
||||||
|
canvas.height = window.innerHeight;
|
||||||
|
var characters = "01".split("");
|
||||||
|
var font_size = 10;
|
||||||
|
var font_spacing = 5;
|
||||||
|
var colums = canvas.width/font_size;
|
||||||
|
var rows = canvas.height/(font_size+font_spacing);
|
||||||
|
ctx.fillStyle = "rgba(255,255,255,0.1)";
|
||||||
|
for(var x = 0; x < colums; x++) {
|
||||||
|
for(var y = 0; y < rows; y++) {
|
||||||
|
ctx.fillText(characters[Math.round(Math.random())], x*font_size+5,y*(font_size+font_spacing));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -1,12 +1,13 @@
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>zettoit bin</title>
|
<title>pfzetto bin</title>
|
||||||
<link rel="icon" type="image/svg" href="/static/zettoit.svg"/>
|
<link rel="icon" type="image/svg" href="https://pfzetto.de/pfzetto.svg"/>
|
||||||
<link rel="stylesheet" href="/static/zettoit.css"/>
|
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<% include!("./style.stpl"); %>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<canvas id="matrix"></canvas>
|
||||||
<main>
|
<main>
|
||||||
<h1>Confirm Deletion</h1>
|
<h1>Confirm Deletion</h1>
|
||||||
<p>The bin will be deleted. All data will be permanently lost.</p>
|
<p>The bin will be deleted. All data will be permanently lost.</p>
|
||||||
|
@ -14,6 +15,7 @@
|
||||||
<button type="submit">Delete</button>
|
<button type="submit">Delete</button>
|
||||||
</form>
|
</form>
|
||||||
</main>
|
</main>
|
||||||
<a class="watermark" href="https://git2.zettoit.eu/zettoit"><img src="/static/zettoit.svg" alt="zettoIT Logo"></a>
|
<a class="watermark" href="https://pfzetto.de"><img src="https://pfzetto.de/pfzetto.svg" alt="pfzetto Logo"></a>
|
||||||
|
<% include!("./background.stpl"); %>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,48 +1,57 @@
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>zettoit bin</title>
|
<title>pfzetto bin</title>
|
||||||
<link rel="icon" type="image/svg" href="/static/zettoit.svg"/>
|
<link rel="icon" type="image/svg" href="https://pfzetto.de/pfzetto.svg"/>
|
||||||
<link rel="stylesheet" href="/static/zettoit.css"/>
|
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<% include!("./style.stpl"); %>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<main>
|
<canvas id="matrix"></canvas>
|
||||||
<h1>zettoIT bin</h1>
|
<main id="main">
|
||||||
|
<h1>pfzetto bin</h1>
|
||||||
<p>
|
<p>
|
||||||
An empty bin was created for you. The first HTTP POST or PUT request can upload data.
|
An empty bin was created for you. The first HTTP POST or PUT request can upload data.
|
||||||
All following requests can only read the uploaded data.
|
All following requests can only read the uploaded data.
|
||||||
</p>
|
</p>
|
||||||
<p>To change the default expiration date, you can use the `?ttl=<seconds_to_live>` parameter.</p>
|
<p>To change the default expiration date, you can use the <code>?ttl=<seconds_to_live></code> parameter.</p>
|
||||||
<p>After uploading data, you can access it by accessing <%= bin_url %> with an optional file extension that suits the data that you uploaded.</p>
|
<p>Set the <code>Content-Type</code> header of the file you upload to make viewing easier.
|
||||||
<h1>Upload a image</h1>
|
<p>After uploading data, you can access it by accessing <a href="<%= bin_url %>"><%= bin_url %></a> with an optional file extension that suits the data that you uploaded.</p>
|
||||||
|
|
||||||
|
<h1>web upload</h1>
|
||||||
|
<label class="file">
|
||||||
|
<input type="file" id="file" aria-label="File browser">
|
||||||
|
<span class="file-custom"></span>
|
||||||
|
</label>
|
||||||
|
<button type="button" onclick="upload()">Upload</button>
|
||||||
|
|
||||||
|
<h1>upload an image</h1>
|
||||||
<pre>
|
<pre>
|
||||||
$ curl -H "Content-Type: image/png" -T my-image.png <%= bin_url %>`
|
$ curl -H "Content-Type: image/png" -T my-image.png <%= bin_url %>`
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
<h1>Upload a big file</h1>
|
<h1>create and upload tar</h1>
|
||||||
<pre>
|
<pre>
|
||||||
$ curl -X POST -H "Content-Type: application/gzip" -T my-file.tar.gz <%= bin_url %>
|
$ tar -cz . | curl -T - -H "Content-Type: application/gzip" <%= bin_url %>
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
<h1>Pipe into curl</h1>
|
|
||||||
<pre>
|
|
||||||
$ tar -cz my-files | curl -H "Content-Type: application/gzip" -T - <%= bin_url %>
|
|
||||||
</pre>
|
|
||||||
|
|
||||||
<h1>Encryption</h1>
|
|
||||||
<pre>
|
|
||||||
$ tar -cz my-files | gpg -co tmp.tar.gz
|
|
||||||
$ curl -H "Content-Type: application/octet-stream" -T tmp.tar.gz <%= bin_url %>
|
|
||||||
$ curl <%= bin_url %> | gpg -d - | tar -xzf
|
|
||||||
</pre>
|
|
||||||
|
|
||||||
<h1>Browser upload</h1>
|
|
||||||
<form method="post" enctype="multipart/form-data">
|
|
||||||
<input type="file" name="file"></input>
|
|
||||||
<button type="submit">Upload</button>
|
|
||||||
</form>
|
|
||||||
</main>
|
</main>
|
||||||
<a class="watermark" href="https://git2.zettoit.eu/zettoit"><img src="/static/zettoit.svg" alt="zettoIT Logo"></a>
|
<a class="watermark" href="https://pfzetto.de"><img src="https://pfzetto.de/pfzetto.svg" alt="pfzetto Logo"></a>
|
||||||
|
<% include!("./background.stpl"); %>
|
||||||
|
<script>
|
||||||
|
function upload() {
|
||||||
|
const input = document.getElementById("file");
|
||||||
|
const body = document.getElementById("main");
|
||||||
|
body.innerHtml = "uploading file. please keep this page open.";
|
||||||
|
fetch(window.location.pathname, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
"Content-Type": input.files[0].type
|
||||||
|
},
|
||||||
|
body: input.files[0]
|
||||||
|
})
|
||||||
|
.then((response) => response.text())
|
||||||
|
.then((response) => {body.innerText = response;});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
93
server/templates/style.stpl
Normal file
93
server/templates/style.stpl
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
<style>
|
||||||
|
html {
|
||||||
|
font-family: monospace;
|
||||||
|
font-site: 12px;
|
||||||
|
text-align: justify;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
background-color: black;
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
z-index: -2;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
max-width: calc(100% - 8px);
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
max-width: 100%;
|
||||||
|
overflow-x: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
p, input, button {
|
||||||
|
margin-top: 2px;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.watermark {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 2px;
|
||||||
|
right: 2px;
|
||||||
|
}
|
||||||
|
.watermark img {
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#matrix {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: -1;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
button, input[type="file"]::file-selector-button {
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration-line: underline;
|
||||||
|
background-color: transparent;
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
button:active, input[type="file"]::file-selector-button:active {
|
||||||
|
background-color: transparent;
|
||||||
|
color: white;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="file"] {
|
||||||
|
display: block;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
</style>
|
Loading…
Reference in a new issue