diff --git a/.woodpecker.yml b/.woodpecker.yml new file mode 100644 index 0000000..19c16b4 --- /dev/null +++ b/.woodpecker.yml @@ -0,0 +1,37 @@ +pipeline: + fmt: + group: default + image: rust_full + commands: + - cargo fmt --all -- --check + + clippy: + group: default + image: rust_full + commands: + - cargo clippy --all-features + + test: + group: default + image: rust_full + commands: + - cargo test + + build: + group: default + image: rust_full + commands: + - cargo build + - cargo build --release + + build-deb: + group: default + image: rust_full + commands: + - cargo deb + + doc: + group: default + image: rust_full + commands: + - cargo doc diff --git a/Cargo.lock b/Cargo.lock index e7c10da..3d535bc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,43 +4,84 @@ version = 3 [[package]] name = "aho-corasick" -version = "0.7.18" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" dependencies = [ "memchr", ] [[package]] -name = "ansi_term" -version = "0.12.1" +name = "anstream" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" dependencies = [ - "winapi", + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is-terminal", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" + +[[package]] +name = "anstyle-parse" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" +dependencies = [ + "anstyle", + "windows-sys", ] [[package]] name = "anyhow" -version = "1.0.45" +version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee10e43ae4a853c0a3591d4e2ada1719e553be18199d9da9d4a83f5927c2f5c7" +checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" [[package]] name = "async-stream" -version = "0.3.2" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "171374e7e3b2504e0e5236e3b59260560f9fe94bfe9ac39ba5e4e929c5590625" +checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" dependencies = [ "async-stream-impl", "futures-core", + "pin-project-lite", ] [[package]] name = "async-stream-impl" -version = "0.3.2" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "648ed8c8d2ce5409ccd57453d9d1b214b342a0d69376a6feda1fd6cae3299308" +checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", @@ -49,9 +90,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.51" +version = "0.1.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44318e776df68115a881de9a8fd1b9e53368d7a4a5ce4cc48517da3393233a5e" +checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ "proc-macro2", "quote", @@ -60,12 +101,9 @@ dependencies = [ [[package]] name = "atomic" -version = "0.5.1" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b88d82667eca772c4aa12f0f1348b3ae643424c8876448f3f7bd5787032e234c" -dependencies = [ - "autocfg", -] +checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" [[package]] name = "atty" @@ -73,28 +111,22 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.19", "libc", "winapi", ] [[package]] name = "autocfg" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" - -[[package]] -name = "base-x" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "base64" -version = "0.13.0" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" [[package]] name = "binascii" @@ -109,31 +141,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] -name = "block-buffer" -version = "0.9.0" +name = "bitflags" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +checksum = "6776fc96284a0bb647b615056fc496d1fe1644a7ab01829818a6d91cae888b84" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] [[package]] name = "bumpalo" -version = "3.8.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" [[package]] name = "bytes" -version = "1.1.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" [[package]] name = "cc" -version = "1.0.72" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" [[package]] name = "cfg-if" @@ -142,59 +180,58 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] -name = "chrono" -version = "0.4.19" +name = "clap" +version = "4.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +checksum = "ca8f255e4b8027970e78db75e78831229c9815fdbfa67eb1a1b777a62e24b4a0" dependencies = [ - "libc", - "num-integer", - "num-traits", - "winapi", + "clap_builder", + "clap_derive", + "once_cell", ] [[package]] -name = "clap" -version = "3.0.0-beta.5" +name = "clap_builder" +version = "4.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "feff3878564edb93745d58cf63e17b63f24142506e7a20c87a5521ed7bfb1d63" +checksum = "acd4f3c17c83b0ba34ffbc4f8bbd74f079413f747f84a6f89292f138057e36ab" dependencies = [ - "atty", - "bitflags", - "clap_derive", - "indexmap", - "lazy_static", - "os_str_bytes", + "anstream", + "anstyle", + "bitflags 1.3.2", + "clap_lex", "strsim", - "termcolor", - "textwrap", - "unicase", ] [[package]] name = "clap_derive" -version = "3.0.0-beta.5" +version = "4.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b15c6b4f786ffb6192ffe65a36855bc1fc2444bcd0945ae16748dcd6ed7d0d3" +checksum = "b8cd2b2a819ad6eec39e8f1d6b53001af1e5469f8c177579cdaeb313115b825f" dependencies = [ "heck", - "proc-macro-error", "proc-macro2", "quote", "syn", ] [[package]] -name = "const_fn" -version = "0.4.8" +name = "clap_lex" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f92cfa0fd5690b3cf8c1ef2cabbd9b7ef22fa53cf5e1f92b05103f6d5d1cf6e7" +checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "cookie" -version = "0.15.1" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f1c7727e460397e56abc4bddc1d49e07a1ad78fc98eb2e1c8f032a58a2f80d" +checksum = "7efb37c3e1ccb1ff97164ad95ac1606e8ccd35b3fa0a7d99a304c7f4a428cc24" dependencies = [ "percent-encoding", "time", @@ -203,28 +240,28 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.1" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" +checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" dependencies = [ "libc", ] [[package]] -name = "crypto-mac" -version = "0.11.1" +name = "crypto-common" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", - "subtle", + "typenum", ] [[package]] name = "devise" -version = "0.3.1" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50c7580b072f1c8476148f16e0a0d5dedddab787da98d86c5082c5e9ed8ab595" +checksum = "d6eacefd3f541c66fc61433d65e54e0e46e0a029a819a7dbbc7a7b489e8a85f8" dependencies = [ "devise_codegen", "devise_core", @@ -232,9 +269,9 @@ dependencies = [ [[package]] name = "devise_codegen" -version = "0.3.1" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "123c73e7a6e51b05c75fe1a1b2f4e241399ea5740ed810b0e3e6cacd9db5e7b2" +checksum = "9c8cf4b8dd484ede80fd5c547592c46c3745a617c8af278e2b72bea86b2dfed6" dependencies = [ "devise_core", "quote", @@ -242,11 +279,11 @@ dependencies = [ [[package]] name = "devise_core" -version = "0.3.1" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841ef46f4787d9097405cac4e70fb8644fc037b526e8c14054247c0263c400d0" +checksum = "35b50dba0afdca80b187392b24f2499a88c336d5a8493e4b4ccfb608708be56a" dependencies = [ - "bitflags", + "bitflags 2.3.1", "proc-macro2", "proc-macro2-diagnostics", "quote", @@ -255,11 +292,13 @@ dependencies = [ [[package]] name = "digest" -version = "0.9.0" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "generic-array", + "block-buffer", + "crypto-common", + "subtle", ] [[package]] @@ -273,9 +312,9 @@ dependencies = [ [[package]] name = "dirs-sys" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" dependencies = [ "libc", "redox_users", @@ -283,43 +322,31 @@ dependencies = [ ] [[package]] -name = "discard" +name = "dunce" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" - -[[package]] -name = "dtoa" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" - -[[package]] -name = "dunce" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "453440c271cf5577fd2a40e4942540cb7d0d2f85e27c8d07dd0023c925a67541" +checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" [[package]] name = "either" -version = "1.6.1" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" [[package]] name = "encoding_rs" -version = "0.8.29" +version = "0.8.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a74ea89a0a1b98f6332de42c95baff457ada66d1cb4030f9ff151b2041a1c746" +checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" dependencies = [ "cfg-if", ] [[package]] name = "env_logger" -version = "0.9.0" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" +checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" dependencies = [ "atty", "humantime", @@ -329,10 +356,40 @@ dependencies = [ ] [[package]] -name = "figment" -version = "0.10.6" +name = "errno" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "790b4292c72618abbab50f787a477014fe15634f96291de45672ce46afe122df" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + +[[package]] +name = "figment" +version = "0.10.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4547e226f4c9ab860571e070a9034192b3175580ecea38da34fcdb53a018c9a5" dependencies = [ "atomic", "pear", @@ -350,24 +407,22 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "fsio" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09e87827efaf94c7a44b562ff57de06930712fe21b530c3797cdede26e6377eb" +checksum = "de6fce87c901c64837f745e7fffddeca1de8e054b544ba82c419905d40a0e1be" dependencies = [ "dunce", "rand", - "users", ] [[package]] name = "futures" -version = "0.3.17" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a12aa0eb539080d55c3f2d45a67c3b58b6b0773c1a3ca2dfec66d58c97fd66ca" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" dependencies = [ "futures-channel", "futures-core", - "futures-executor", "futures-io", "futures-sink", "futures-task", @@ -376,9 +431,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.17" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5da6ba8c3bb3c165d3c7319fc1cc8304facf1fb8db99c5de877183c08a273888" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" dependencies = [ "futures-core", "futures-sink", @@ -386,91 +441,63 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.17" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d1c26957f23603395cd326b0ffe64124b818f4449552f960d815cfba83a53d" - -[[package]] -name = "futures-executor" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45025be030969d763025784f7f355043dc6bc74093e4ecc5000ca4dc50d8745c" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" [[package]] name = "futures-io" -version = "0.3.17" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "522de2a0fe3e380f1bc577ba0474108faf3f6b18321dbf60b3b9c39a75073377" - -[[package]] -name = "futures-macro" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18e4a4b95cea4b4ccbcf1c5675ca7c4ee4e9e75eb79944d07defde18068f79bb" -dependencies = [ - "autocfg", - "proc-macro-hack", - "proc-macro2", - "quote", - "syn", -] +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" [[package]] name = "futures-sink" -version = "0.3.17" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36ea153c13024fe480590b3e3d4cad89a0cfacecc24577b68f86c6ced9c2bc11" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" [[package]] name = "futures-task" -version = "0.3.17" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d3d00f4eddb73e498a54394f228cd55853bdf059259e8e7bc6e69d408892e99" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" [[package]] name = "futures-util" -version = "0.3.17" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36568465210a3a6ee45e1f165136d68671471a501e632e9a98d96872222b5481" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ - "autocfg", "futures-channel", "futures-core", "futures-io", - "futures-macro", "futures-sink", "futures-task", "memchr", "pin-project-lite", "pin-utils", - "proc-macro-hack", - "proc-macro-nested", "slab", ] [[package]] name = "generator" -version = "0.7.0" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1d9279ca822891c1a4dae06d185612cf8fc6acfe5dff37781b41297811b12ee" +checksum = "f3e123d9ae7c02966b4d892e550bdc32164f05853cd40ab570650ad600596a8a" dependencies = [ "cc", "libc", "log", "rustversion", - "winapi", + "windows", ] [[package]] name = "generic-array" -version = "0.14.4" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -478,9 +505,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.3" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", "libc", @@ -489,15 +516,15 @@ dependencies = [ [[package]] name = "glob" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "h2" -version = "0.3.7" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fd819562fcebdac5afc5c113c3ec36f902840b70fd4fc458799c8ce4607ae55" +checksum = "d357c7ae988e7d2182f7d7871d0b963962420b0678b0997ce7de72001aeab782" dependencies = [ "bytes", "fnv", @@ -514,18 +541,15 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.11.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "heck" -version = "0.3.3" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" -dependencies = [ - "unicode-segmentation", -] +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" @@ -536,6 +560,21 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + [[package]] name = "hex" version = "0.4.3" @@ -544,19 +583,18 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hmac" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "crypto-mac", "digest", ] [[package]] name = "http" -version = "0.2.5" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1323096b05d41827dadeaee54c9981958c0f94e670bc94ed80037d1a7b8b186b" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" dependencies = [ "bytes", "fnv", @@ -565,9 +603,9 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ "bytes", "http", @@ -576,15 +614,15 @@ dependencies = [ [[package]] name = "httparse" -version = "1.5.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6456b8a6c8f33fee7d958fcd1b60d55b11940a79e63ae87013e6d22e26034440" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" [[package]] name = "humantime" @@ -594,9 +632,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.14" +version = "0.14.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b91bb1f221b6ea1f1e4371216b70f40748774c2fb5971b450c07773fb92d26b" +checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4" dependencies = [ "bytes", "futures-channel", @@ -618,9 +656,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.7.0" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown", @@ -629,9 +667,9 @@ dependencies = [ [[package]] name = "inlinable_string" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3094308123a0e9fd59659ce45e22de9f53fc1d2ac6e1feb9fef988e4f76cad77" +checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" [[package]] name = "instant" @@ -643,25 +681,48 @@ dependencies = [ ] [[package]] -name = "ipnet" -version = "2.3.1" +name = "io-lifetimes" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi 0.3.1", + "libc", + "windows-sys", +] + +[[package]] +name = "ipnet" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f" dependencies = [ "serde", ] [[package]] -name = "itoa" -version = "0.4.8" +name = "is-terminal" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" +checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" +dependencies = [ + "hermit-abi 0.3.1", + "io-lifetimes", + "rustix", + "windows-sys", +] + +[[package]] +name = "itoa" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "js-sys" -version = "0.3.55" +version = "0.3.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" +checksum = "2f37a4a5928311ac501dee68b3c7613a1037d0edb30c8e5427bd832d55d1b790" dependencies = [ "wasm-bindgen", ] @@ -674,39 +735,43 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.107" +version = "0.2.146" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbe5e23404da5b4f555ef85ebed98fb4083e55a00c317800bc2a50ede9f3d219" +checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" [[package]] name = "linked-hash-map" -version = "0.5.4" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "lock_api" -version = "0.4.5" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" dependencies = [ + "autocfg", "scopeguard", ] [[package]] name = "log" -version = "0.4.14" +version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" -dependencies = [ - "cfg-if", -] +checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" [[package]] name = "loom" -version = "0.5.2" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2b9df80a3804094bf49bb29881d18f6f05048db72127e84e09c26fc7c2324f5" +checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5" dependencies = [ "cfg-if", "generator", @@ -719,52 +784,41 @@ dependencies = [ [[package]] name = "matchers" -version = "0.0.1" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f099785f7595cc4b4553a174ce30dd7589ef93391ff414dbb67f62392b9e0ce1" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" dependencies = [ "regex-automata", ] [[package]] name = "memchr" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "mime" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "mio" -version = "0.7.14" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", - "log", - "miow", - "ntapi", - "winapi", -] - -[[package]] -name = "miow" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" -dependencies = [ - "winapi", + "wasi", + "windows-sys", ] [[package]] name = "multer" -version = "2.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "408327e2999b839cd1af003fc01b2019a6c10a1361769542203f6fedc5179680" +checksum = "01acbdc23469fd8fe07ab135923371d5f5a422fbf9c522158677c8eb15bc51c2" dependencies = [ "bytes", "encoding_rs", @@ -772,103 +826,74 @@ dependencies = [ "http", "httparse", "log", + "memchr", "mime", - "spin 0.9.2", + "spin 0.9.8", "tokio", "tokio-util", - "twoway", "version_check", ] [[package]] -name = "ntapi" -version = "0.3.6" +name = "nu-ansi-term" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" dependencies = [ + "overload", "winapi", ] -[[package]] -name = "num-integer" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" -dependencies = [ - "autocfg", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" -dependencies = [ - "autocfg", -] - [[package]] name = "num_cpus" -version = "1.13.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ - "hermit-abi", + "hermit-abi 0.2.6", "libc", ] [[package]] name = "once_cell" -version = "1.8.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] -name = "opaque-debug" -version = "0.3.0" +name = "overload" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" - -[[package]] -name = "os_str_bytes" -version = "4.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "addaa943333a514159c80c97ff4a93306530d965d27e139188283cd13e06a799" -dependencies = [ - "memchr", -] +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "parking_lot" -version = "0.11.2" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ - "instant", "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" -version = "0.8.5" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" dependencies = [ "cfg-if", - "instant", "libc", - "redox_syscall", + "redox_syscall 0.3.5", "smallvec", - "winapi", + "windows-targets", ] [[package]] name = "pear" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15e44241c5e4c868e3eaa78b7c1848cadd6344ed4f54d029832d32b415a58702" +checksum = "0ec95680a7087503575284e5063e14b694b7a9c0b065e5dceec661e0497127e8" dependencies = [ "inlinable_string", "pear_codegen", @@ -877,9 +902,9 @@ dependencies = [ [[package]] name = "pear_codegen" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82a5ca643c2303ecb740d506539deba189e16f2754040a42901cd8105d0282d0" +checksum = "9661a3a53f93f09f2ea882018e4d7c88f6ff2956d809a276060476fd8c879d3c" dependencies = [ "proc-macro2", "proc-macro2-diagnostics", @@ -889,15 +914,15 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.1.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "pin-project-lite" -version = "0.2.7" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" [[package]] name = "pin-utils" @@ -907,60 +932,24 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "ppv-lite86" -version = "0.2.15" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba" - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - -[[package]] -name = "proc-macro-hack" -version = "0.5.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" - -[[package]] -name = "proc-macro-nested" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.32" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43" +checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406" dependencies = [ - "unicode-xid", + "unicode-ident", ] [[package]] name = "proc-macro2-diagnostics" -version = "0.9.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bf29726d67464d49fa6224a1d07936a8c08bb3fba727c7493f6cf1616fdaada" +checksum = "606c4ba35817e2922a308af55ad51bab3645b59eae5c570d4a6cf07e36bd493b" dependencies = [ "proc-macro2", "quote", @@ -971,23 +960,22 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.10" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" +checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" dependencies = [ "proc-macro2", ] [[package]] name = "rand" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", "rand_core", - "rand_hc", ] [[package]] @@ -1002,55 +990,56 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom", ] -[[package]] -name = "rand_hc" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" -dependencies = [ - "rand_core", -] - [[package]] name = "redox_syscall" -version = "0.2.10" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ - "bitflags", + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", ] [[package]] name = "redox_users" -version = "0.4.0" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ "getrandom", - "redox_syscall", + "redox_syscall 0.2.16", + "thiserror", ] [[package]] name = "ref-cast" -version = "1.0.6" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "300f2a835d808734ee295d45007adacb9ebb29dd3ae2424acfa17930cae541da" +checksum = "f43faa91b1c8b36841ee70e97188a869d37ae21759da6846d4be66de5bf7b12c" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" -version = "1.0.6" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c38e3aecd2b21cb3959637b883bb3714bc7e43f0268b9a29d3743ee3e55cdd2" +checksum = "8d2275aab483050ab2a7364c1a46604865ee7d6906684e08db0f090acf74f9e7" dependencies = [ "proc-macro2", "quote", @@ -1059,13 +1048,13 @@ dependencies = [ [[package]] name = "regex" -version = "1.5.4" +version = "1.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.7.2", ] [[package]] @@ -1074,23 +1063,20 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" dependencies = [ - "regex-syntax", + "regex-syntax 0.6.29", ] [[package]] name = "regex-syntax" -version = "0.6.25" +version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] -name = "remove_dir_all" -version = "0.5.3" +name = "regex-syntax" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -dependencies = [ - "winapi", -] +checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" [[package]] name = "ring" @@ -1109,20 +1095,20 @@ dependencies = [ [[package]] name = "rocket" -version = "0.5.0-rc.1" +version = "0.5.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a71c18c42a0eb15bf3816831caf0dad11e7966f2a41aaf486a701979c4dd1f2" +checksum = "58734f7401ae5cfd129685b48f61182331745b357b96f2367f01aebaf1cc9cc9" dependencies = [ "async-stream", "async-trait", "atomic", - "atty", "binascii", "bytes", "either", "figment", "futures", "indexmap", + "is-terminal", "log", "memchr", "multer", @@ -1147,9 +1133,9 @@ dependencies = [ [[package]] name = "rocket_codegen" -version = "0.5.0-rc.1" +version = "0.5.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66f5fa462f7eb958bba8710c17c5d774bbbd59809fa76fb1957af7e545aea8bb" +checksum = "7093353f14228c744982e409259fb54878ba9563d08214f2d880d59ff2fc508b" dependencies = [ "devise", "glob", @@ -1163,23 +1149,24 @@ dependencies = [ [[package]] name = "rocket_http" -version = "0.5.0-rc.1" +version = "0.5.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23c8b7d512d2fcac2316ebe590cde67573844b99e6cc9ee0f53375fa16e25ebd" +checksum = "936012c99162a03a67f37f9836d5f938f662e26f2717809761a9ac46432090f4" dependencies = [ "cookie", "either", + "futures", "http", "hyper", "indexmap", "log", "memchr", - "mime", - "parking_lot", "pear", "percent-encoding", "pin-project-lite", "ref-cast", + "rustls", + "rustls-pemfile", "serde", "smallvec", "stable-pattern", @@ -1200,21 +1187,25 @@ dependencies = [ ] [[package]] -name = "rustc_version" -version = "0.2.3" +name = "rustix" +version = "0.37.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +checksum = "b96e891d04aa506a6d1f318d2771bcb1c7dfda84e126660ace067c9b474bb2c0" dependencies = [ - "semver", + "bitflags 1.3.2", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys", ] [[package]] name = "rustls" -version = "0.19.1" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" +checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" dependencies = [ - "base64", "log", "ring", "sct", @@ -1222,22 +1213,31 @@ dependencies = [ ] [[package]] -name = "rustversion" -version = "1.0.5" +name = "rustls-pemfile" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61b3909d758bb75c79f23d4736fac9433868679d3ad2ea7a61e3c25cfda9a088" +checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" +dependencies = [ + "base64", +] + +[[package]] +name = "rustversion" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" [[package]] name = "ryu" -version = "1.0.5" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" [[package]] name = "scoped-tls" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] name = "scopeguard" @@ -1247,43 +1247,28 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "sct" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" dependencies = [ "ring", "untrusted", ] -[[package]] -name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -dependencies = [ - "semver-parser", -] - -[[package]] -name = "semver-parser" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" - [[package]] name = "serde" -version = "1.0.130" +version = "1.0.164" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" +checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.130" +version = "1.0.164" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" +checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" dependencies = [ "proc-macro2", "quote", @@ -1292,9 +1277,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.70" +version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e277c495ac6cd1a01a58d0a0c574568b4d1ddf14f59965c6a58b8d96400b54f3" +checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" dependencies = [ "itoa", "ryu", @@ -1302,34 +1287,45 @@ dependencies = [ ] [[package]] -name = "serde_yaml" -version = "0.8.21" +name = "serde_regex" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8c608a35705a5d3cdc9fbe403147647ff34b921f8e833e49306df898f9b20af" +checksum = "a8136f1a4ea815d7eac4101cfd0b16dc0cb5e1fe1b8609dfd728058656b7badf" +dependencies = [ + "regex", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93107647184f6027e3b7dcb2e11034cf95ffa1e3a682c67951963ac69c1c007d" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_yaml" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578a7433b776b56a35785ed5ce9a7e777ac0598aac5a6dd1b4b18a307c7fc71b" dependencies = [ - "dtoa", "indexmap", + "ryu", "serde", "yaml-rust", ] -[[package]] -name = "sha1" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" - [[package]] name = "sha2" -version = "0.9.8" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ - "block-buffer", "cfg-if", "cpufeatures", "digest", - "opaque-debug", ] [[package]] @@ -1343,30 +1339,33 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" dependencies = [ "libc", ] [[package]] name = "slab" -version = "0.4.5" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +dependencies = [ + "autocfg", +] [[package]] name = "smallvec" -version = "1.7.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "socket2" -version = "0.4.2" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dc90fe6c7be1a323296982db1836d1ea9e47b6839496dde9a541bc496df3516" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" dependencies = [ "libc", "winapi", @@ -1380,9 +1379,9 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] name = "spin" -version = "0.9.2" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "511254be0c5bcf062b019a6c89c01a664aa359ded62f78aa72c6fc137c0590e5" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" [[package]] name = "stable-pattern" @@ -1393,73 +1392,15 @@ dependencies = [ "memchr", ] -[[package]] -name = "standback" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e113fb6f3de07a243d434a56ec6f186dfd51cb08448239fe7bcae73f87ff28ff" -dependencies = [ - "version_check", -] - [[package]] name = "state" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cf4f5369e6d3044b5e365c9690f451516ac8f0954084622b49ea3fde2f6de5" +checksum = "dbe866e1e51e8260c9eed836a042a5e7f6726bb2b411dffeaa712e19c388f23b" dependencies = [ "loom", ] -[[package]] -name = "stdweb" -version = "0.4.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5" -dependencies = [ - "discard", - "rustc_version", - "stdweb-derive", - "stdweb-internal-macros", - "stdweb-internal-runtime", - "wasm-bindgen", -] - -[[package]] -name = "stdweb-derive" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" -dependencies = [ - "proc-macro2", - "quote", - "serde", - "serde_derive", - "syn", -] - -[[package]] -name = "stdweb-internal-macros" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" -dependencies = [ - "base-x", - "proc-macro2", - "quote", - "serde", - "serde_derive", - "serde_json", - "sha1", - "syn", -] - -[[package]] -name = "stdweb-internal-runtime" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" - [[package]] name = "strsim" version = "0.10.0" @@ -1468,67 +1409,58 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "subtle" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "syn" -version = "1.0.81" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966" +checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" dependencies = [ "proc-macro2", "quote", - "unicode-xid", + "unicode-ident", ] [[package]] name = "tempfile" -version = "3.2.0" +version = "3.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" +checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" dependencies = [ + "autocfg", "cfg-if", - "libc", - "rand", - "redox_syscall", - "remove_dir_all", - "winapi", + "fastrand", + "redox_syscall 0.3.5", + "rustix", + "windows-sys", ] [[package]] name = "termcolor" -version = "1.1.2" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" dependencies = [ "winapi-util", ] -[[package]] -name = "textwrap" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80" -dependencies = [ - "unicode-width", -] - [[package]] name = "thiserror" -version = "1.0.30" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.30" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", @@ -1537,75 +1469,64 @@ dependencies = [ [[package]] name = "thread_local" -version = "1.1.3" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" dependencies = [ + "cfg-if", "once_cell", ] [[package]] name = "time" -version = "0.2.27" +version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4752a97f8eebd6854ff91f1c1824cd6160626ac4bd44287f7f4ea2035a02a242" +checksum = "ea9e1b3cf1243ae005d9e74085d4d542f3125458f3a81af210d901dcd7411efd" dependencies = [ - "const_fn", - "libc", - "standback", - "stdweb", + "itoa", + "serde", + "time-core", "time-macros", - "version_check", - "winapi", ] +[[package]] +name = "time-core" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" + [[package]] name = "time-macros" -version = "0.1.1" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957e9c6e26f12cb6d0dd7fc776bb67a706312e7299aed74c8dd5b17ebb27e2f1" +checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b" dependencies = [ - "proc-macro-hack", - "time-macros-impl", -] - -[[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", - "quote", - "standback", - "syn", + "time-core", ] [[package]] name = "tokio" -version = "1.13.0" +version = "1.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "588b2d10a336da58d877567cd8fb8a14b463e2104910f8132cd054b4b96e29ee" +checksum = "94d7b1cfd2aa4011f2de74c2c4c63665e27a71006b0a192dcd2710272e73dfa2" dependencies = [ "autocfg", "bytes", "libc", - "memchr", "mio", "num_cpus", - "once_cell", "pin-project-lite", "signal-hook-registry", + "socket2", "tokio-macros", - "winapi", + "windows-sys", ] [[package]] name = "tokio-macros" -version = "1.5.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "114383b041aa6212c579467afa0075fbbdd0718de036100bc0ba7961d8cb9095" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", @@ -1614,9 +1535,9 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.22.0" +version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" +checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" dependencies = [ "rustls", "tokio", @@ -1625,9 +1546,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.8" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" dependencies = [ "futures-core", "pin-project-lite", @@ -1636,38 +1557,63 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.6.9" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e99e1983e5d376cd8eb4b66604d2e99e79f5bd988c3055891dcd8c9e2604cc0" +checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" dependencies = [ "bytes", "futures-core", "futures-sink", - "log", "pin-project-lite", "tokio", + "tracing", ] [[package]] name = "toml" -version = "0.5.8" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +checksum = "d6135d499e69981f9ff0ef2167955a5333c35e36f6937d382974566b3d5b94ec" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a76a9312f5ba4c2dec6b9161fdf25d87ad8a09256ccea5a556fef03c706a10f" dependencies = [ "serde", ] [[package]] -name = "tower-service" -version = "0.3.1" +name = "toml_edit" +version = "0.19.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" +checksum = "2380d56e8670370eee6566b0bfd4265f65b3f432e8c6d85623f728d4fa31f739" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.29" +version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if", "pin-project-lite", @@ -1677,9 +1623,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.18" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f480b8f81512e825f337ad51e94c1eb5d3bbdf2b363dcd01e2b19a9ffe3f8e" +checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" dependencies = [ "proc-macro2", "quote", @@ -1688,129 +1634,85 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.21" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f4ed65637b8390770814083d20756f87bfa2c21bf2f110babdc5438351746e4" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" dependencies = [ - "lazy_static", + "once_cell", + "valuable", ] [[package]] name = "tracing-log" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6923477a48e41c1951f1999ef8bb5a3023eb723ceadafe78ffb65dc366761e3" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" dependencies = [ "lazy_static", "log", "tracing-core", ] -[[package]] -name = "tracing-serde" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb65ea441fbb84f9f6748fd496cf7f63ec9af5bca94dd86456978d055e8eb28b" -dependencies = [ - "serde", - "tracing-core", -] - [[package]] name = "tracing-subscriber" -version = "0.2.25" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" +checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" dependencies = [ - "ansi_term", - "chrono", - "lazy_static", "matchers", + "nu-ansi-term", + "once_cell", "regex", - "serde", - "serde_json", "sharded-slab", "smallvec", "thread_local", "tracing", "tracing-core", "tracing-log", - "tracing-serde", ] [[package]] name = "try-lock" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" - -[[package]] -name = "twoway" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c57ffb460d7c24cd6eda43694110189030a3d1dfe418416d9468fd1c1d290b47" -dependencies = [ - "memchr", - "unchecked-index", -] +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" [[package]] name = "typenum" -version = "1.14.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "ubyte" -version = "0.10.1" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42756bb9e708855de2f8a98195643dff31a97f0485d90d8467b39dc24be9e8fe" +checksum = "c81f0dae7d286ad0d9366d7679a77934cfc3cf3a8d67e82669794412b2368fe6" dependencies = [ "serde", ] [[package]] name = "uncased" -version = "0.9.6" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baeed7327e25054889b9bd4f975f32e5f4c5d434042d59ab6cd4142c0a76ed0" +checksum = "9b9bc53168a4be7402ab86c3aad243a84dd7381d09be0eddc81280c1da95ca68" dependencies = [ "serde", "version_check", ] [[package]] -name = "unchecked-index" -version = "0.2.2" +name = "unicode-ident" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eeba86d422ce181a719445e51872fa30f1f7413b62becb52e95ec91aa262d85c" - -[[package]] -name = "unicase" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" -dependencies = [ - "version_check", -] - -[[package]] -name = "unicode-segmentation" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" - -[[package]] -name = "unicode-width" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" +checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" [[package]] name = "unicode-xid" -version = "0.2.2" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" [[package]] name = "untrusted" @@ -1819,20 +1721,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] -name = "users" -version = "0.11.0" +name = "utf8parse" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24cc0f6d6f267b73e5a2cadf007ba8f9bc39c6a6f9666f8cf25ea809a153b032" -dependencies = [ - "libc", - "log", -] +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "version_check" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "want" @@ -1846,15 +1750,15 @@ dependencies = [ [[package]] name = "wasi" -version = "0.10.2+wasi-snapshot-preview1" +version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.78" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" +checksum = "5bba0e8cb82ba49ff4e229459ff22a191bbe9a1cb3a341610c9c33efc27ddf73" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -1862,13 +1766,13 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.78" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" +checksum = "19b04bc93f9d6bdee709f6bd2118f57dd6679cf1176a1af464fca3ab0d66d8fb" dependencies = [ "bumpalo", - "lazy_static", "log", + "once_cell", "proc-macro2", "quote", "syn", @@ -1877,9 +1781,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.78" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" +checksum = "14d6b024f1a526bb0234f52840389927257beb670610081360e5a03c5df9c258" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1887,9 +1791,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.78" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" +checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8" dependencies = [ "proc-macro2", "quote", @@ -1900,15 +1804,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.78" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" +checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93" [[package]] name = "web-sys" -version = "0.3.55" +version = "0.3.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" +checksum = "3bdd9ef4e984da1187bf8110c5cf5b845fbc87a23602cdf912386a76fcd3a7c2" dependencies = [ "js-sys", "wasm-bindgen", @@ -1916,7 +1820,7 @@ dependencies = [ [[package]] name = "webhookey" -version = "0.1.5" +version = "0.1.6" dependencies = [ "anyhow", "clap", @@ -1931,6 +1835,7 @@ dependencies = [ "run_script", "serde", "serde_json", + "serde_regex", "serde_yaml", "sha2", "thiserror", @@ -1938,9 +1843,9 @@ dependencies = [ [[package]] name = "webpki" -version = "0.21.4" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" dependencies = [ "ring", "untrusted", @@ -1977,6 +1882,90 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + +[[package]] +name = "winnow" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61de7bac303dc551fe038e2b3cef0f571087a47571ea6e79a87692ac99b99699" +dependencies = [ + "memchr", +] + [[package]] name = "yaml-rust" version = "0.4.5" @@ -1988,6 +1977,6 @@ dependencies = [ [[package]] name = "yansi" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc79f4a1e39857fc00c3f662cbf2651c771f00e9c15fe2abc341806bd46bd71" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" diff --git a/Cargo.toml b/Cargo.toml index d49ab4e..b7396f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "webhookey" -version = "0.1.5" +version = "0.1.6" authors = ["finga "] edition = "2021" license = "GPL-3.0-or-later" @@ -11,22 +11,23 @@ description = "Trigger scripts via http(s) requests" tls = ["rocket/tls"] [dependencies] +anyhow = "1.0" +clap = { version = "4.3", features = ["derive"] } +dirs = "4.0" +env_logger = "0.9" +hex = "0.4" +hmac = "0.12" +ipnet = { version = "2.3", features = ["serde"] } +log = "0.4" +regex = "1.5" rocket = "0.5.0-rc.1" +run_script = "0.9" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" +serde_regex = "1.1" serde_yaml = "0.8" -regex = "1.5" -dirs = "4.0" -anyhow = "1.0" -log = "0.4" -env_logger = "0.9" -hmac = "0.11" -sha2 = "0.9" -hex = "0.4" -ipnet = { version = "2.3", features = ["serde"] } +sha2 = "0.10" thiserror = "1.0" -run_script = "0.9" -clap = "3.0.0-beta.5" [package.metadata.deb] extended-description = "Webhookey receives requests in form of a so called Webhook as for example sent by Gitea. Those requests are matched against configured filters, if a filter matches, values from the header and the body can be passed to scripts as parameters which are then executed subsequently." @@ -36,6 +37,7 @@ assets = [ ["config.yml", "etc/webhookey/", "644"], ["target/release/webhookey", "usr/bin/", "755"], ["README.md", "usr/share/doc/webhookey/README", "644"], + ["webhookey.1", "usr/share/man/man1/", "644"], ["debian/service", "lib/systemd/system/webhookey.service", "644"], ] conf-files = ["/etc/webhookey/config.yml"] diff --git a/README.md b/README.md index 9990ed7..c48045a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,9 @@ # Webhookey -Webhookey is a web server listening for requests as for example sent by -gitea's webhooks. Further, Webhookey allows you to specify rules +![status-badge](https://ci.onders.org/api/badges/finga/webhookey/status.svg?branch=main) + +Webhookey is a web server listening for +[webhooks](https://en.wikipedia.org/wiki/Webhook) as for example sent +by Gitea's webhooks. Further, Webhookey allows you to specify rules which are matched against the data received to trigger certain actions. @@ -62,7 +65,10 @@ Whereas `` depends on the platform: #### Metrics A metrics page can optionally enabled to query stats of the currently running webhookey instance. Note that stats are lost between restarts -of webhookey as those are not stored persistently. +of webhookey as those are not stored persistently. The `metrics` +structure is optional as well as the `ip_filter`. The `ip_filter` +supports either `allow` or `deny` containing a list of IPv4 and IPv6 +addresses and networks. Example: ```yaml @@ -98,16 +104,17 @@ hooks: - secret_key_02 filter: or: - - json: - pointer: /ref - regex: refs/heads/master + - not: + json: + pointer: /ref + regex: refs/heads/dev - and: - json: pointer: /ref regex: refs/heads/a_branch - - json: - pointer: /after - regex: f6e5fe4fe37df76629112d55cc210718b6a55e7e + - header: + field: X-Gitea-Event + regex: push ``` ##### Command @@ -126,25 +133,28 @@ Use values from header fields sent with the HTTP request. Example: `{{ header X-Gitea-Event }}`. -##### Allow and Deny -To allow or deny specific network ranges source is an optional -configuration parameter which either contains an allow or a deny field -with sequences containing networks. Note that IPv6 addresses have to -be put in single quotes due to the colons. +##### IP Filter +Specific IPv4 and IPv6 addresses and/or ranges ranges can be allowed +or denied. The `ip_filter` is optional and has to contain either an +`allow` or a `deny` field which contains a sequence of IPv4 or IPv6 +addresses or CIDR network ranges. Note that IPv6 addresses have to be +quoted due to the colons. Example: ```yaml -allow: - - 127.0.0.1 - - 127.0.0.1/31 - - "::1" +ip_filter: + allow: + - 127.0.0.1 + - 127.0.0.1/31 + - "::1" ``` ```yaml -deny: - - 127.0.0.1 - - 127.0.0.1/31 - - "::1" +ip_filter: + deny: + - 127.0.0.1 + - 127.0.0.1/31 + - "::1" ``` ##### Signature @@ -163,10 +173,20 @@ hook should be executed. ###### Conjunction Filters Conjunction filters contain lists of other filters. +- `not`: Logical negation. - `and`: Logical conjunction. - `or`: Logical disjunction. ###### Concrete Filters +- `header`: + + The `header` filter matches a regular expression on a field from the + received http(s) request header. + + - `field`: The header field which should be matched. + - `regex`: Regular expression which has to match the specified + header field. + - `json`: The `json` filter matches a regular expression on a field from the diff --git a/Rocket.toml b/Rocket.toml new file mode 100644 index 0000000..ebb03f7 --- /dev/null +++ b/Rocket.toml @@ -0,0 +1,2 @@ +[default] +ident = "Webhookey" diff --git a/src/cli.rs b/src/cli.rs index 63c024d..49a6d69 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,4 +1,4 @@ -use clap::{crate_authors, crate_version, AppSettings, Parser}; +use clap::Parser; #[derive(Debug, Parser)] pub enum Command { @@ -7,16 +7,10 @@ pub enum Command { } #[derive(Debug, Parser)] -#[clap( - version = crate_version!(), - author = crate_authors!(", "), - global_setting = AppSettings::InferSubcommands, - global_setting = AppSettings::PropagateVersion, -)] pub struct Opts { /// Provide a path to the configuration file - #[clap(short, long, value_name = "FILE")] + #[arg(short, long, value_name = "FILE")] pub config: Option, - #[clap(subcommand)] + #[command(subcommand)] pub command: Option, } diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..64b0eb4 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,52 @@ +use crate::{filters::IpFilter, hooks::Hook}; +use anyhow::{bail, Result}; +use log::info; +use serde::{Deserialize, Serialize}; +use std::{collections::BTreeMap, fs::File}; + +#[derive(Debug, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] +pub struct MetricsConfig { + pub enabled: bool, + pub ip_filter: Option, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] +pub struct Config { + pub metrics: Option, + pub hooks: BTreeMap, +} + +pub fn get_config() -> Result { + // Look for config in CWD.. + if let Ok(config) = File::open("config.yml") { + info!("Loading configuration from `./config.yml`"); + + return Ok(config); + } + + // ..look for user path config.. + if let Some(mut path) = dirs::config_dir() { + path.push("webhookey/config.yml"); + + if let Ok(config) = File::open(&path) { + info!( + "Loading configuration from `{}`", + path.to_str().unwrap_or(""), + ); + + return Ok(config); + } + } + + // ..look for systemwide config.. + if let Ok(config) = File::open("/etc/webhookey/config.yml") { + info!("Loading configuration from `/etc/webhookey/config.yml`"); + + return Ok(config); + } + + // ..you had your chance. + bail!("No configuration file found."); +} diff --git a/src/filters.rs b/src/filters.rs new file mode 100644 index 0000000..dc74864 --- /dev/null +++ b/src/filters.rs @@ -0,0 +1,157 @@ +use crate::WebhookeyError; +use anyhow::Result; +use ipnet::IpNet; +use log::{debug, error, trace}; +use regex::Regex; +use rocket::{http::HeaderMap, Request}; +use serde::{Deserialize, Serialize}; +use std::net::IpAddr; + +#[derive(Debug, Deserialize, Serialize)] +#[serde(deny_unknown_fields, untagged)] +pub enum AddrType { + IpAddr(IpAddr), + IpNet(IpNet), +} + +impl AddrType { + pub fn matches(&self, client_ip: &IpAddr) -> bool { + match self { + AddrType::IpAddr(addr) => addr == client_ip, + AddrType::IpNet(net) => net.contains(client_ip), + } + } +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(deny_unknown_fields, rename_all = "lowercase")] +pub enum IpFilter { + Allow(Vec), + Deny(Vec), +} + +impl IpFilter { + pub fn validate(&self, client_ip: &IpAddr) -> bool { + match self { + IpFilter::Allow(list) => list.iter().any(|i| i.matches(client_ip)), + IpFilter::Deny(list) => !list.iter().any(|i| i.matches(client_ip)), + } + } +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct HeaderFilter { + pub field: String, + #[serde(with = "serde_regex")] + pub regex: Regex, +} + +impl HeaderFilter { + pub fn evaluate(&self, headers: &HeaderMap) -> Result { + trace!( + "Matching `{}` on `{}` from received header", + &self.regex, + &self.field, + ); + + if let Some(value) = headers.get_one(&self.field) { + if self.regex.is_match(value) { + debug!("Regex `{}` for `{}` matches", &self.regex, &self.field); + + return Ok(true); + } + } + + debug!( + "Regex `{}` for header field `{}` does not match", + &self.regex, &self.field + ); + + Ok(false) + } +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] +pub struct JsonFilter { + pub pointer: String, + #[serde(with = "serde_regex")] + pub regex: Regex, +} + +impl JsonFilter { + pub fn evaluate(&self, data: &serde_json::Value) -> Result { + trace!( + "Matching `{}` on `{}` from received json", + &self.regex, + &self.pointer, + ); + + if let Some(value) = data.pointer(&self.pointer) { + if self.regex.is_match(&crate::get_string(value)?) { + debug!("Regex `{}` for `{}` matches", &self.regex, &self.pointer); + + return Ok(true); + } + } + + debug!( + "Regex `{}` for json field `{}` does not match", + &self.regex, &self.pointer + ); + + Ok(false) + } +} + +macro_rules! interrelate { + ($request:expr, $data:expr, $filters:expr, $relation:ident) => {{ + let (mut results, mut errors) = (Vec::new(), Vec::new()); + + $filters + .iter() + .map(|filter| filter.evaluate($request, $data)) + .for_each(|item| match item { + Ok(o) => results.push(o), + Err(e) => errors.push(e), + }); + + if errors.is_empty() { + Ok(results.iter().$relation(|r| *r)) + } else { + errors + .iter() + .for_each(|e| error!("Could not evaluate Filter: {}", e)); + + Err(WebhookeyError::InvalidFilter) + } + }}; +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(deny_unknown_fields, rename_all = "lowercase")] +pub enum FilterType { + Not(Box), + And(Vec), + Or(Vec), + #[serde(rename = "header")] + HeaderFilter(HeaderFilter), + #[serde(rename = "json")] + JsonFilter(JsonFilter), +} + +impl FilterType { + pub fn evaluate( + &self, + request: &Request, + data: &serde_json::Value, + ) -> Result { + match self { + FilterType::Not(filter) => Ok(!filter.evaluate(request, data)?), + FilterType::And(filters) => interrelate!(request, data, filters, all), + FilterType::Or(filters) => interrelate!(request, data, filters, any), + FilterType::HeaderFilter(filter) => filter.evaluate(request.headers()), + FilterType::JsonFilter(filter) => filter.evaluate(data), + } + } +} diff --git a/src/hooks.rs b/src/hooks.rs new file mode 100644 index 0000000..b653b00 --- /dev/null +++ b/src/hooks.rs @@ -0,0 +1,804 @@ +use crate::{ + filters::{FilterType, IpFilter}, + Config, Metrics, WebhookeyError, +}; +use anyhow::{anyhow, bail, Result}; +use hmac::{Hmac, Mac}; +use log::{debug, error, info, trace, warn}; +use rocket::{ + data::{FromData, ToByteUnit}, + futures::TryFutureExt, + http::{HeaderMap, Status}, + outcome::Outcome::{self, Failure, Success}, + post, + tokio::io::AsyncReadExt, + Data, Request, State, +}; +use run_script::ScriptOptions; +use serde::{Deserialize, Serialize}; +use sha2::Sha256; +use std::{ + collections::BTreeMap, + net::{IpAddr, Ipv4Addr, SocketAddr}, + sync::atomic::Ordering, +}; + +fn accept_ip(hook_name: &str, client_ip: &IpAddr, ip: &IpFilter) -> bool { + if ip.validate(client_ip) { + info!("Allow hook `{}` from {}", &hook_name, &client_ip); + return true; + } + + warn!("Deny hook `{}` from {}", &hook_name, &client_ip); + false +} + +fn get_header_field<'a>(headers: &'a HeaderMap, param: &str) -> Result<&'a str> { + headers + .get_one(param) + .ok_or_else(|| anyhow!("Could not extract event parameter from header")) +} + +fn validate_request(secret: &str, signature: &str, data: &[u8]) -> Result<()> { + let mut mac = Hmac::::new_from_slice(secret.as_bytes()) + .map_err(|e| anyhow!("Could not create hasher with secret: {}", e))?; + mac.update(data); + let raw_signature = hex::decode(signature.as_bytes())?; + mac.verify_slice(&raw_signature) + .map_err(|e| anyhow!("{}", e)) +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] +pub struct Hook { + command: String, + signature: String, + ip_filter: Option, + secrets: Vec, + filter: FilterType, +} + +impl Hook { + fn get_command( + &self, + hook_name: &str, + request: &Request, + data: &mut serde_json::Value, + ) -> Result { + debug!("Replacing parameters for command of hook `{}`", hook_name); + + Hook::replace_parameters(&self.command, request.headers(), data) + } + + fn replace_parameters( + input: &str, + headers: &HeaderMap, + data: &serde_json::Value, + ) -> Result { + let mut command = String::new(); + let command_template = &mut input.chars(); + + while let Some(i) = command_template.next() { + if i == '{' { + if let Some('{') = command_template.next() { + let mut token = String::new(); + + while let Some(i) = command_template.next() { + if i == '}' { + if let Some('}') = command_template.next() { + let expr = token.trim().split(' ').collect::>(); + + let replaced = match expr.first() { + Some(&"header") => get_header_field( + headers, + expr.get(1).ok_or_else(|| { + anyhow!("Missing parameter for `header` expression") + })?, + )? + .to_string(), + Some(pointer) => crate::get_string( + data.pointer(pointer).ok_or_else(|| { + anyhow!( + "Could not find field refered to in parameter `{}`", + pointer + ) + })?, + )?, + None => bail!("Invalid expression `{}`", token), + }; + + command.push_str(&replaced); + + trace!("Replace `{}` with: {}", token, replaced); + + break; + } else { + command.push('}'); + command.push(i); + } + } else { + token.push(i); + } + } + } else { + command.push('{'); + command.push(i); + } + } else { + command.push(i); + } + } + + Ok(command) + } +} + +#[derive(Debug)] +pub struct Hooks { + pub inner: BTreeMap, +} + +impl Hooks { + pub async fn get_commands( + request: &Request<'_>, + data: Data<'_>, + ) -> Result { + let mut buffer = Vec::new(); + let size = data + .open(1_i32.megabytes()) + .read_to_end(&mut buffer) + .map_err(WebhookeyError::Io) + .await?; + info!("Data of size {} received", size); + + let config = request.guard::<&State>().await.unwrap(); // should never fail + let mut valid = false; + let mut result = BTreeMap::new(); + let client_ip = &request + .client_ip() + .unwrap_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED)); + + let hooks = config.hooks.iter().filter(|(name, hook)| { + if let Some(ip) = &hook.ip_filter { + accept_ip(name, client_ip, ip) + } else { + info!( + "Allow hook `{}` from {}, no IP filter was configured", + &name, &client_ip + ); + true + } + }); + + for (hook_name, hook) in hooks { + let signature = request + .headers() + .get_one(&hook.signature) + .ok_or(WebhookeyError::InvalidSignature)?; + + let secrets = hook + .secrets + .iter() + .map(|secret| validate_request(secret, signature, &buffer)); + + for secret in secrets { + match secret { + Ok(()) => { + trace!("Valid signature found for hook `{}`", hook_name); + + valid = true; + + let mut data: serde_json::Value = + serde_json::from_slice(&buffer).map_err(WebhookeyError::Serde)?; + + match hook.filter.evaluate(request, &data) { + Ok(true) => match hook.get_command(hook_name, request, &mut data) { + Ok(command) => { + info!("Filter for `{}` matched", &hook_name); + result.insert(hook_name.to_string(), command); + break; + } + Err(e) => error!("{}", e), + }, + Ok(false) => info!("Filter for `{}` did not match", &hook_name), + Err(error) => { + error!("Could not match filter for `{}`: {}", &hook_name, error) + } + } + } + Err(e) => trace!("Hook `{}` could not validate request: {}", &hook_name, e), + } + } + } + + if !valid { + return Err(WebhookeyError::Unauthorized(*client_ip)); + } + + Ok(Hooks { inner: result }) + } +} + +#[rocket::async_trait] +impl<'r> FromData<'r> for Hooks { + type Error = WebhookeyError; + + async fn from_data( + request: &'r Request<'_>, + data: Data<'r>, + ) -> Outcome> { + { + request + .guard::<&State>() + .await + .unwrap() // TODO: Check if unwrap need to be fixed + .requests_received + .fetch_add(1, Ordering::Relaxed); + } + + match Hooks::get_commands(request, data).await { + Ok(hooks) => { + if hooks.inner.is_empty() { + let client_ip = &request + .client_ip() + .unwrap_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED)); + + request + .guard::<&State>() + .await + .unwrap() // TODO: Check if unwrap need to be fixed + .hooks_unmatched + .fetch_add(1, Ordering::Relaxed); + + warn!("Unmatched hook from {}", &client_ip); + return Failure((Status::NotFound, WebhookeyError::UnmatchedHook(*client_ip))); + } + + Success(hooks) + } + Err(WebhookeyError::Unauthorized(e)) => { + error!("{}", WebhookeyError::Unauthorized(e)); + + request + .guard::<&State>() + .await + .unwrap() // TODO: Check if unwrap need to be fixed + .hooks_forbidden + .fetch_add(1, Ordering::Relaxed); + + Failure((Status::Unauthorized, WebhookeyError::Unauthorized(e))) + } + Err(e) => { + error!("{}", e); + + request + .guard::<&State>() + .await + .unwrap() // TODO: Check if unwrap need to be fixed + .requests_invalid + .fetch_add(1, Ordering::Relaxed); + + Failure((Status::BadRequest, e)) + } + } + } +} + +#[post("/", format = "json", data = "")] +pub async fn receive_hook<'a>( + address: SocketAddr, + hooks: Hooks, + metrics: &State, +) -> Status { + info!("Post request received from: {}", address); + + hooks.inner.iter().for_each(|(name, command)| { + info!("Execute `{}` from hook `{}`", &command, &name); + + match run_script::run(command, &vec![], &ScriptOptions::new()) { + Ok((status, stdout, stderr)) => { + info!("Command `{}` exited with return code: {}", &command, status); + trace!("Output of command `{}` on stdout: {:?}", &command, &stdout); + debug!("Output of command `{}` on stderr: {:?}", &command, &stderr); + + metrics.commands_executed.fetch_add(1, Ordering::Relaxed); + + let _ = match status { + 0 => metrics.commands_successful.fetch_add(1, Ordering::Relaxed), + _ => metrics.commands_failed.fetch_add(1, Ordering::Relaxed), + }; + } + Err(e) => { + error!("Execution of `{}` failed: {}", &command, e); + + metrics + .commands_execution_failed + .fetch_add(1, Ordering::Relaxed); + } + } + + metrics.hooks_successful.fetch_add(1, Ordering::Relaxed); + }); + + Status::Ok +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + config::MetricsConfig, + filters::{AddrType, FilterType, HeaderFilter, JsonFilter}, + hooks::Hook, + Metrics, + }; + use regex::Regex; + use rocket::{ + http::{ContentType, Header, Status}, + local::asynchronous::Client, + routes, + }; + use serde_json::json; + use std::collections::BTreeMap; + + #[rocket::async_test] + async fn secret() { + let mut hooks = BTreeMap::new(); + + hooks.insert( + "test_hook".to_string(), + Hook { + command: "".to_string(), + signature: "X-Gitea-Signature".to_string(), + ip_filter: None, + secrets: vec!["valid".to_string()], + filter: FilterType::JsonFilter(JsonFilter { + pointer: "*".to_string(), + regex: Regex::new(".*").unwrap(), + }), + }, + ); + + let config = Config { + metrics: None, + hooks: hooks, + }; + + let rocket = rocket::build() + .mount("/", routes![receive_hook]) + .manage(config) + .manage(Metrics::default()); + + let client = Client::tracked(rocket).await.unwrap(); + let response = client + .post("/") + .header(Header::new( + "X-Gitea-Signature", + "28175a0035f637f3cbb85afee9f9d319631580e7621cf790cd16ca063a2f820e", + )) + .header(ContentType::JSON) + .remote("127.0.0.1:8000".parse().unwrap()) + .body(&serde_json::to_string(&json!({ "foo": "bar" })).unwrap()) + .dispatch(); + + assert_eq!(response.await.status(), Status::NotFound); + + let response = client + .post("/") + .header(Header::new("X-Gitea-Signature", "beef")) + .header(ContentType::JSON) + .remote("127.0.0.1:8000".parse().unwrap()) + .body(&serde_json::to_string(&json!({ "foo": "bar" })).unwrap()) + .dispatch(); + + assert_eq!(response.await.status(), Status::Unauthorized); + + let response = client + .post("/") + .header(Header::new( + "X-Gitea-Signature", + "c5c315d76318362ec129ca629b50b626bba09ad3d7ba4cc0f4c0afe4a90537a0", + )) + .header(ContentType::JSON) + .remote("127.0.0.1:8000".parse().unwrap()) + .body(r#"{ "not_secret": "invalid" "#) + .dispatch(); + + assert_eq!(response.await.status(), Status::BadRequest); + + let response = client + .post("/") + .header(Header::new("X-Gitea-Signature", "foobar")) + .header(ContentType::JSON) + .remote("127.0.0.1:8000".parse().unwrap()) + .dispatch(); + + assert_eq!(response.await.status(), Status::Unauthorized); + } + + #[rocket::async_test] + async fn parse_command_request() { + let mut hooks = BTreeMap::new(); + + hooks.insert( + "test_hook0".to_string(), + Hook { + command: + "/usr/bin/echo {{ /repository/full_name }} --foo {{ /pull_request/base/ref }}" + .to_string(), + signature: "X-Gitea-Signature".to_string(), + ip_filter: None, + secrets: vec!["valid".to_string()], + filter: FilterType::JsonFilter(JsonFilter { + pointer: "/foo".to_string(), + regex: Regex::new("bar").unwrap(), + }), + }, + ); + + hooks.insert( + "test_hook2".to_string(), + Hook { + command: "/usr/bin/echo {{ /repository/full_name }} {{ /pull_request/base/ref }}" + .to_string(), + signature: "X-Gitea-Signature".to_string(), + ip_filter: None, + secrets: vec!["valid".to_string()], + filter: FilterType::JsonFilter(JsonFilter { + pointer: "/foo".to_string(), + regex: Regex::new("bar").unwrap(), + }), + }, + ); + + hooks.insert( + "test_hook3".to_string(), + Hook { + command: "/usr/bin/echo {{ /repository/full_name }} {{ /pull_request/base/ref }}" + .to_string(), + signature: "X-Gitea-Signature".to_string(), + ip_filter: None, + secrets: vec!["valid".to_string()], + filter: FilterType::Not(Box::new(FilterType::JsonFilter(JsonFilter { + pointer: "/foobar".to_string(), + regex: Regex::new("bar").unwrap(), + }))), + }, + ); + + let config = Config { + metrics: None, + hooks: hooks, + }; + + let rocket = rocket::build() + .mount("/", routes![receive_hook]) + .manage(config) + .manage(Metrics::default()); + + let client = Client::tracked(rocket).await.unwrap(); + + let response = client + .post("/") + .header(Header::new( + "X-Gitea-Signature", + "693b733871ecb684651a813c82936df683c9e4a816581f385353e06170545400", + )) + .header(ContentType::JSON) + .remote("127.0.0.1:8000".parse().unwrap()) + .body( + &serde_json::to_string(&json!({ + "foo": "bar", + "repository": { + "full_name": "keith" + }, + "pull_request": { + "base": { + "ref": "main" + } + } + })) + .unwrap(), + ) + .dispatch(); + + assert_eq!(response.await.status(), Status::Ok); + } + + #[rocket::async_test] + async fn parse_invalid_command_request() { + let mut hooks = BTreeMap::new(); + + hooks.insert( + "test_hook".to_string(), + Hook { + command: "/usr/bin/echo {{ /repository/full }} {{ /pull_request/base/ref }}" + .to_string(), + signature: "X-Gitea-Signature".to_string(), + ip_filter: None, + secrets: vec!["valid".to_string()], + filter: FilterType::JsonFilter(JsonFilter { + pointer: "/foo".to_string(), + regex: Regex::new("bar").unwrap(), + }), + }, + ); + + let config = Config { + metrics: None, + hooks: hooks, + }; + + let rocket = rocket::build() + .mount("/", routes![receive_hook]) + .manage(config) + .manage(Metrics::default()); + + let client = Client::tracked(rocket).await.unwrap(); + + let response = client + .post("/") + .header(Header::new( + "X-Gitea-Signature", + "693b733871ecb684651a813c82936df683c9e4a816581f385353e06170545400", + )) + .header(ContentType::JSON) + .remote("127.0.0.1:8000".parse().unwrap()) + .body( + &serde_json::to_string(&json!({ + "foo": "bar", + "repository": { + "full_name": "keith" + }, + "pull_request": { + "base": { + "ref": "main" + } + } + })) + .unwrap(), + ) + .dispatch(); + + assert_eq!(response.await.status(), Status::NotFound); + } + + #[test] + fn parse_command() { + let mut headers = HeaderMap::new(); + headers.add_raw("X-Gitea-Event", "something"); + + assert_eq!( + Hook::replace_parameters("command", &headers, &serde_json::Value::Null).unwrap(), + "command" + ); + + assert_eq!( + Hook::replace_parameters(" command", &headers, &serde_json::Value::Null).unwrap(), + " command" + ); + + assert_eq!( + Hook::replace_parameters("command ", &headers, &serde_json::Value::Null).unwrap(), + "command " + ); + + assert_eq!( + Hook::replace_parameters(" command ", &headers, &serde_json::Value::Null) + .unwrap(), + " command " + ); + + assert_eq!( + Hook::replace_parameters("command command ", &headers, &serde_json::Value::Null) + .unwrap(), + "command command " + ); + + assert_eq!( + Hook::replace_parameters("{{ /foo }} command", &headers, &json!({ "foo": "bar" })) + .unwrap(), + "bar command" + ); + + assert_eq!( + Hook::replace_parameters( + " command {{ /foo }} ", + &headers, + &json!({ "foo": "bar" }) + ) + .unwrap(), + " command bar " + ); + + assert_eq!( + Hook::replace_parameters( + "{{ /foo }} command{{/field1/foo}}", + &headers, + &json!({ "foo": "bar", "field1": { "foo": "baz" } }) + ) + .unwrap(), + "bar commandbaz" + ); + + assert_eq!( + Hook::replace_parameters( + " command {{ /foo }} ", + &headers, + &json!({ "foo": "bar" }) + ) + .unwrap(), + " command bar " + ); + + assert_eq!( + Hook::replace_parameters( + " {{ /field1/foo }} command", + &headers, + &json!({ "field1": { "foo": "bar" } }) + ) + .unwrap(), + " bar command" + ); + + assert_eq!( + Hook::replace_parameters( + " {{ header X-Gitea-Event }} command", + &headers, + &json!({ "field1": { "foo": "bar" } }) + ) + .unwrap(), + " something command" + ); + + assert_eq!( + Hook::replace_parameters( + " {{ header X-Gitea-Event }} {{ /field1/foo }} command", + &headers, + &json!({ "field1": { "foo": "bar" } }) + ) + .unwrap(), + " something bar command" + ); + + assert_eq!( + Hook::replace_parameters( + " {{ header X-Gitea-Event }} {{ /field1/foo }} {{ /field1/bar }} {{ /field2/foo }} --command{{ /cmd }}", + &headers, + &json!({ "field1": { "foo": "bar", "bar": "baz" }, "field2": { "foo": "qux" }, "cmd": " else"}) + ) + .unwrap(), + " something bar baz qux --command else" + ); + } + + #[test] + fn parse_config() { + let config: Config = serde_yaml::from_str( + r#"--- +hooks: + hook1: + command: /usr/bin/local/script_xy.sh {{ /field2/foo }} asdfasdf + signature: X-Gitea-Signature + ip_filter: + allow: + - 127.0.0.1/31 + secrets: + - secret_key_01 + - secret_key_02 + filter: + json: + pointer: /ref + regex: refs/heads/master + hook2: + command: /usr/bin/local/script_xy.sh asdfasdf + signature: X-Gitea-Signature + secrets: + - secret_key_01 + - secret_key_02 + filter: + and: + - json: + pointer: /ref + regex: refs/heads/master + - header: + field: X-Gitea-Signature + regex: f6e5fe4fe37df76629112d55cc210718b6a55e7e"#, + ) + .unwrap(); + + assert_eq!( + serde_yaml::to_string(&config).unwrap(), + serde_yaml::to_string(&Config { + metrics: None, + hooks: BTreeMap::from([ + ( + "hook1".to_string(), + Hook { + command: "/usr/bin/local/script_xy.sh {{ /field2/foo }} asdfasdf" + .to_string(), + signature: "X-Gitea-Signature".to_string(), + ip_filter: Some(IpFilter::Allow(vec![AddrType::IpNet( + "127.0.0.1/31".parse().unwrap() + )])), + secrets: vec!["secret_key_01".to_string(), "secret_key_02".to_string()], + filter: FilterType::JsonFilter(JsonFilter { + pointer: "/ref".to_string(), + regex: Regex::new("refs/heads/master").unwrap(), + }), + } + ), + ( + "hook2".to_string(), + Hook { + command: "/usr/bin/local/script_xy.sh asdfasdf".to_string(), + signature: "X-Gitea-Signature".to_string(), + ip_filter: None, + secrets: vec!["secret_key_01".to_string(), "secret_key_02".to_string()], + filter: FilterType::And(vec![ + FilterType::JsonFilter(JsonFilter { + pointer: "/ref".to_string(), + regex: Regex::new("refs/heads/master").unwrap(), + }), + FilterType::HeaderFilter(HeaderFilter { + field: "X-Gitea-Signature".to_string(), + regex: Regex::new("f6e5fe4fe37df76629112d55cc210718b6a55e7e") + .unwrap(), + }), + ]), + } + ) + ]) + }) + .unwrap() + ); + + let config: Config = serde_yaml::from_str( + r#"--- +metrics: + enabled: true +hooks: + hook1: + command: /usr/bin/local/script_xy.sh {{ /field2/foo }} asdfasdf + signature: X-Gitea-Signature + ip_filter: + allow: + - 127.0.0.1/31 + secrets: + - secret_key_01 + - secret_key_02 + filter: + json: + pointer: /ref + regex: refs/heads/master"#, + ) + .unwrap(); + + assert_eq!( + serde_yaml::to_string(&config).unwrap(), + serde_yaml::to_string(&Config { + metrics: Some(MetricsConfig { + enabled: true, + ip_filter: None + }), + hooks: BTreeMap::from([( + "hook1".to_string(), + Hook { + command: "/usr/bin/local/script_xy.sh {{ /field2/foo }} asdfasdf" + .to_string(), + signature: "X-Gitea-Signature".to_string(), + ip_filter: Some(IpFilter::Allow(vec![AddrType::IpNet( + "127.0.0.1/31".parse().unwrap() + )])), + secrets: vec!["secret_key_01".to_string(), "secret_key_02".to_string()], + filter: FilterType::JsonFilter(JsonFilter { + pointer: "/ref".to_string(), + regex: Regex::new("refs/heads/master").unwrap(), + }), + } + ),]) + }) + .unwrap() + ); + } +} diff --git a/src/main.rs b/src/main.rs index 7b161c4..a18261b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,454 +1,43 @@ -use anyhow::{anyhow, bail, Result}; -use clap::Parser; -use hmac::{Hmac, Mac, NewMac}; -use log::{debug, error, info, trace, warn}; -use rocket::{ - data::{FromData, ToByteUnit}, - futures::TryFutureExt, - get, - http::{HeaderMap, Status}, - outcome::Outcome::{self, Failure, Success}, - post, routes, - tokio::io::AsyncReadExt, - Data, Request, State, -}; -use run_script::ScriptOptions; -use serde::{Deserialize, Serialize}; -use sha2::Sha256; - -use std::{ - collections::BTreeMap, - fs::File, - io::BufReader, - net::{IpAddr, Ipv4Addr, SocketAddr}, - sync::atomic::{AtomicUsize, Ordering}, -}; - mod cli; -mod webhooks; +mod config; +mod filters; +mod hooks; +mod metrics; -use crate::{ - cli::Opts, - webhooks::{FilterType, IpFilter, WebhookeyError}, -}; +use crate::{cli::Opts, config::Config, metrics::Metrics}; +use anyhow::Result; +use clap::Parser; +use log::{debug, error, trace}; +use rocket::routes; +use std::{fs::File, io::BufReader, net::IpAddr}; +use thiserror::Error; -#[derive(Debug, Default)] -struct WebhookeyMetrics { - requests_received: AtomicUsize, - requests_invalid: AtomicUsize, - hooks_successful: AtomicUsize, - hooks_forbidden: AtomicUsize, - hooks_unmatched: AtomicUsize, - commands_executed: AtomicUsize, - commands_execution_failed: AtomicUsize, - commands_successful: AtomicUsize, - commands_failed: AtomicUsize, +#[derive(Debug, Error)] +pub enum WebhookeyError { + #[error("Could not extract signature from header")] + InvalidSignature, + #[error("Unauthorized request from `{0}`")] + Unauthorized(IpAddr), + #[error("Unmatched hook from `{0}`")] + UnmatchedHook(IpAddr), + #[error("Could not evaluate filter request")] + InvalidFilter, + #[error("IO Error")] + Io(#[from] std::io::Error), + #[error("Serde Error")] + Serde(#[from] serde_json::Error), } -#[derive(Debug, Deserialize, Serialize)] -#[serde(deny_unknown_fields)] -struct MetricsConfig { - enabled: bool, - ip_filter: Option, -} - -#[derive(Debug, Deserialize, Serialize)] -#[serde(deny_unknown_fields)] -struct Config { - metrics: Option, - hooks: BTreeMap, -} - -#[derive(Debug, Deserialize, Serialize)] -#[serde(deny_unknown_fields)] -struct Hook { - command: String, - signature: String, - ip_filter: Option, - secrets: Vec, - filter: FilterType, -} - -impl Hook { - fn get_command( - &self, - hook_name: &str, - request: &Request, - data: &mut serde_json::Value, - ) -> Result { - debug!("Replacing parameters for command of hook `{}`", hook_name); - - Hook::replace_parameters(&self.command, request.headers(), data) - } - - fn replace_parameters( - input: &str, - headers: &HeaderMap, - data: &serde_json::Value, - ) -> Result { - let mut command = String::new(); - let command_template = &mut input.chars(); - - while let Some(i) = command_template.next() { - if i == '{' { - if let Some('{') = command_template.next() { - let mut token = String::new(); - - while let Some(i) = command_template.next() { - if i == '}' { - if let Some('}') = command_template.next() { - let expr = token.trim().split(' ').collect::>(); - - let replaced = match expr.get(0) { - Some(&"header") => get_header_field( - headers, - expr.get(1).ok_or_else(|| { - anyhow!("Missing parameter for `header` expression") - })?, - )? - .to_string(), - Some(pointer) => webhooks::get_string( - data.pointer(pointer).ok_or_else(|| { - anyhow!( - "Could not find field refered to in parameter `{}`", - pointer - ) - })?, - )?, - None => bail!("Missing expression in variable `{}`", token), - }; - - command.push_str(&replaced); - - trace!("Replace `{}` with: {}", token, replaced); - - break; - } else { - command.push('}'); - command.push(i); - } - } else { - token.push(i); - } - } - } else { - command.push('{'); - command.push(i); - } - } else { - command.push(i); - } - } - - Ok(command) - } -} - -#[derive(Debug)] -struct Hooks { - inner: BTreeMap, -} - -impl Hooks { - async fn get_commands(request: &Request<'_>, data: Data<'_>) -> Result { - let mut buffer = Vec::new(); - let size = data - .open(256_i32.kilobytes()) - .read_to_end(&mut buffer) - .map_err(WebhookeyError::Io) - .await?; - info!("Data of size {} received", size); - - let config = request.guard::<&State>().await.unwrap(); // should never fail - let mut valid = false; - let mut result = BTreeMap::new(); - let client_ip = &request - .client_ip() - .unwrap_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED)); - - let hooks = config.hooks.iter().filter(|(name, hook)| { - if let Some(ip) = &hook.ip_filter { - accept_ip(name, client_ip, ip) - } else { - info!( - "Allow hook `{}` from {}, no IP filter was configured", - &name, &client_ip - ); - true - } - }); - - for (hook_name, hook) in hooks { - let signature = request - .headers() - .get_one(&hook.signature) - .ok_or(WebhookeyError::InvalidSignature)?; - - let secrets = hook - .secrets - .iter() - .map(|secret| validate_request(secret, signature, &buffer)); - - for secret in secrets { - match secret { - Ok(()) => { - trace!("Valid signature found for hook `{}`", hook_name); - - valid = true; - - let mut data: serde_json::Value = - serde_json::from_slice(&buffer).map_err(WebhookeyError::Serde)?; - - match hook.filter.evaluate(&data) { - Ok(true) => match hook.get_command(hook_name, request, &mut data) { - Ok(command) => { - info!("Filter for `{}` matched", &hook_name); - result.insert(hook_name.to_string(), command); - break; - } - Err(e) => error!("{}", e), - }, - Ok(false) => info!("Filter for `{}` did not match", &hook_name), - Err(error) => { - error!("Could not match filter for `{}`: {}", &hook_name, error) - } - } - } - Err(e) => trace!("Hook `{}` could not validate request: {}", &hook_name, e), - } - } - } - - if !valid { - return Err(WebhookeyError::Unauthorized(*client_ip)); - } - - Ok(Hooks { inner: result }) - } -} - -fn accept_ip(hook_name: &str, client_ip: &IpAddr, ip: &IpFilter) -> bool { - if ip.validate(client_ip) { - info!("Allow hook `{}` from {}", &hook_name, &client_ip); - return true; - } - - warn!("Deny hook `{}` from {}", &hook_name, &client_ip); - false -} - -fn validate_request(secret: &str, signature: &str, data: &[u8]) -> Result<()> { - let mut mac = Hmac::::new_from_slice(secret.as_bytes()) - .map_err(|e| anyhow!("Could not create hasher with secret: {}", e))?; - mac.update(data); - let raw_signature = hex::decode(signature.as_bytes())?; - mac.verify(&raw_signature).map_err(|e| anyhow!("{}", e)) -} - -fn get_header_field<'a>(headers: &'a HeaderMap, param: &str) -> Result<&'a str> { - headers - .get_one(param) - .ok_or_else(|| anyhow!("Could not extract event parameter from header")) -} - -fn get_config() -> Result { - // Look for config in CWD.. - if let Ok(config) = File::open("config.yml") { - info!("Loading configuration from `./config.yml`"); - - return Ok(config); - } - - // ..look for user path config.. - if let Some(mut path) = dirs::config_dir() { - path.push("webhookey/config.yml"); - - if let Ok(config) = File::open(&path) { - info!( - "Loading configuration from `{}`", - path.to_str().unwrap_or(""), - ); - - return Ok(config); +pub fn get_string(data: &serde_json::Value) -> Result { + match &data { + serde_json::Value::Bool(bool) => Ok(bool.to_string()), + serde_json::Value::Number(number) => Ok(number.to_string()), + serde_json::Value::String(string) => Ok(string.as_str().to_string()), + x => { + error!("Could not get string from: {:?}", x); + unimplemented!() } } - - // ..look for systemwide config.. - if let Ok(config) = File::open("/etc/webhookey/config.yml") { - info!("Loading configuration from `/etc/webhookey/config.yml`"); - - return Ok(config); - } - - // ..you had your chance. - bail!("No configuration file found."); -} - -fn get_metrics(metrics: &WebhookeyMetrics) -> String { - format!( - r"# HELP webhookey_requests_received Number of requests received -# TYPE webhookey_requests_received gauge -webhookey_requests_received {} -# HELP webhookey_requests_invalid Number of invalid requests received -# TYPE webhookey_requests_invalid gauge -webhookey_requests_invalid {} -# HELP webhookey_hooks_successful Number of successfully executed hooks -# TYPE webhookey_hooks_successful gauge -webhookey_hooks_sucessful {} -# HELP webhookey_hooks_forbidden Number of forbidden requests -# TYPE webhookey_hooks_forbidden gauge -webhookey_hooks_forbidden {} -# HELP webhookey_hooks_unmatched Number of unmatched requests -# TYPE webhookey_hooks_unmatched gauge -webhookey_hooks_unmatched {} -# HELP webhookey_commands_executed Number of commands executed -# TYPE webhookey_commands_executed gauge -webhookey_commands_executed {} -# HELP webhookey_commands_execution_failed Number of commands failed to execute -# TYPE webhookey_commands_execution_failed gauge -webhookey_commands_execution_failed {} -# HELP webhookey_commands_successful Number of executed commands returning return code 0 -# TYPE webhookey_commands_successful gauge -webhookey_commands_successful {} -# HELP webhookey_commands_failed Number of executed commands returning different return code than 0 -# TYPE webhookey_commands_failed gauge -webhookey_commands_failed {} -", - metrics.requests_received.load(Ordering::Relaxed), - metrics.requests_invalid.load(Ordering::Relaxed), - metrics.hooks_successful.load(Ordering::Relaxed), - metrics.hooks_forbidden.load(Ordering::Relaxed), - metrics.hooks_unmatched.load(Ordering::Relaxed), - metrics.commands_executed.load(Ordering::Relaxed), - metrics.commands_execution_failed.load(Ordering::Relaxed), - metrics.commands_successful.load(Ordering::Relaxed), - metrics.commands_failed.load(Ordering::Relaxed), - ) -} - -#[rocket::async_trait] -impl<'r> FromData<'r> for Hooks { - type Error = WebhookeyError; - - async fn from_data( - request: &'r Request<'_>, - data: Data<'r>, - ) -> Outcome> { - { - request - .guard::<&State>() - .await - .unwrap() // TODO: Check if unwrap need to be fixed - .requests_received - .fetch_add(1, Ordering::Relaxed); - } - - match Hooks::get_commands(request, data).await { - Ok(hooks) => { - if hooks.inner.is_empty() { - let client_ip = &request - .client_ip() - .unwrap_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED)); - - request - .guard::<&State>() - .await - .unwrap() // TODO: Check if unwrap need to be fixed - .hooks_unmatched - .fetch_add(1, Ordering::Relaxed); - - warn!("Unmatched hook from {}", &client_ip); - return Failure((Status::NotFound, WebhookeyError::UnmatchedHook(*client_ip))); - } - - Success(hooks) - } - Err(WebhookeyError::Unauthorized(e)) => { - error!("{}", WebhookeyError::Unauthorized(e)); - - request - .guard::<&State>() - .await - .unwrap() // TODO: Check if unwrap need to be fixed - .hooks_forbidden - .fetch_add(1, Ordering::Relaxed); - - Failure((Status::Unauthorized, WebhookeyError::Unauthorized(e))) - } - Err(e) => { - error!("{}", e); - - request - .guard::<&State>() - .await - .unwrap() // TODO: Check if unwrap need to be fixed - .requests_invalid - .fetch_add(1, Ordering::Relaxed); - - Failure((Status::BadRequest, e)) - } - } - } -} - -#[post("/", format = "json", data = "")] -async fn receive_hook<'a>( - address: SocketAddr, - hooks: Hooks, - metrics: &State, -) -> Status { - info!("Post request received from: {}", address); - - hooks.inner.iter().for_each(|(name, command)| { - info!("Execute `{}` from hook `{}`", &command, &name); - - match run_script::run(command, &vec![], &ScriptOptions::new()) { - Ok((status, stdout, stderr)) => { - info!("Command `{}` exited with return code: {}", &command, status); - trace!("Output of command `{}` on stdout: {:?}", &command, &stdout); - debug!("Output of command `{}` on stderr: {:?}", &command, &stderr); - - metrics.commands_executed.fetch_add(1, Ordering::Relaxed); - - let _ = match status { - 0 => metrics.commands_successful.fetch_add(1, Ordering::Relaxed), - _ => metrics.commands_failed.fetch_add(1, Ordering::Relaxed), - }; - } - Err(e) => { - error!("Execution of `{}` failed: {}", &command, e); - - metrics - .commands_execution_failed - .fetch_add(1, Ordering::Relaxed); - } - } - }); - - Status::Ok -} - -#[get("/metrics")] -async fn metrics( - address: SocketAddr, - metrics: &State, - config: &State, -) -> Option { - if let Some(metrics_config) = &config.metrics { - if metrics_config.enabled { - if let Some(filter) = &metrics_config.ip_filter { - if filter.validate(&address.ip()) { - return Some(get_metrics(metrics)); - } - } else { - return Some(get_metrics(metrics)); - } - } - } - - warn!("Forbidden request for metrics: {:?}", address); - - None } #[rocket::main] @@ -459,7 +48,7 @@ async fn main() -> Result<()> { let config: Config = match cli.config { Some(config) => serde_yaml::from_reader(BufReader::new(File::open(config)?))?, - _ => serde_yaml::from_reader(BufReader::new(get_config()?))?, + _ => serde_yaml::from_reader(BufReader::new(config::get_config()?))?, }; trace!("Parsed configuration:\n{}", serde_yaml::to_string(&config)?); @@ -471,467 +60,11 @@ async fn main() -> Result<()> { } rocket::build() - .mount("/", routes![receive_hook, metrics]) + .mount("/", routes![hooks::receive_hook, metrics::metrics]) .manage(config) - .manage(WebhookeyMetrics::default()) + .manage(Metrics::default()) .launch() .await?; Ok(()) } - -#[cfg(test)] -mod tests { - use super::*; - use crate::webhooks::{AddrType, JsonFilter}; - use rocket::{ - http::{ContentType, Header}, - local::asynchronous::Client, - }; - use serde_json::json; - - #[rocket::async_test] - async fn secret() { - let mut hooks = BTreeMap::new(); - - hooks.insert( - "test_hook".to_string(), - Hook { - command: "".to_string(), - signature: "X-Gitea-Signature".to_string(), - ip_filter: None, - secrets: vec!["valid".to_string()], - filter: FilterType::JsonFilter(JsonFilter { - pointer: "*".to_string(), - regex: "*".to_string(), - }), - }, - ); - - let config = Config { - metrics: None, - hooks: hooks, - }; - - let rocket = rocket::build() - .mount("/", routes![receive_hook]) - .manage(config) - .manage(WebhookeyMetrics::default()); - - let client = Client::tracked(rocket).await.unwrap(); - let response = client - .post("/") - .header(Header::new( - "X-Gitea-Signature", - "28175a0035f637f3cbb85afee9f9d319631580e7621cf790cd16ca063a2f820e", - )) - .header(ContentType::JSON) - .remote("127.0.0.1:8000".parse().unwrap()) - .body(&serde_json::to_string(&json!({ "foo": "bar" })).unwrap()) - .dispatch(); - - assert_eq!(response.await.status(), Status::NotFound); - - let response = client - .post("/") - .header(Header::new("X-Gitea-Signature", "beef")) - .header(ContentType::JSON) - .remote("127.0.0.1:8000".parse().unwrap()) - .body(&serde_json::to_string(&json!({ "foo": "bar" })).unwrap()) - .dispatch(); - - assert_eq!(response.await.status(), Status::Unauthorized); - - let response = client - .post("/") - .header(Header::new( - "X-Gitea-Signature", - "c5c315d76318362ec129ca629b50b626bba09ad3d7ba4cc0f4c0afe4a90537a0", - )) - .header(ContentType::JSON) - .remote("127.0.0.1:8000".parse().unwrap()) - .body(r#"{ "not_secret": "invalid" "#) - .dispatch(); - - assert_eq!(response.await.status(), Status::BadRequest); - - let response = client - .post("/") - .header(Header::new("X-Gitea-Signature", "foobar")) - .header(ContentType::JSON) - .remote("127.0.0.1:8000".parse().unwrap()) - .dispatch(); - - assert_eq!(response.await.status(), Status::Unauthorized); - } - - #[test] - fn parse_command() { - let mut headers = HeaderMap::new(); - headers.add_raw("X-Gitea-Event", "something"); - - assert_eq!( - Hook::replace_parameters("command", &headers, &serde_json::Value::Null).unwrap(), - "command" - ); - - assert_eq!( - Hook::replace_parameters(" command", &headers, &serde_json::Value::Null).unwrap(), - " command" - ); - - assert_eq!( - Hook::replace_parameters("command ", &headers, &serde_json::Value::Null).unwrap(), - "command " - ); - - assert_eq!( - Hook::replace_parameters(" command ", &headers, &serde_json::Value::Null) - .unwrap(), - " command " - ); - - assert_eq!( - Hook::replace_parameters("command command ", &headers, &serde_json::Value::Null) - .unwrap(), - "command command " - ); - - assert_eq!( - Hook::replace_parameters("{{ /foo }} command", &headers, &json!({ "foo": "bar" })) - .unwrap(), - "bar command" - ); - - assert_eq!( - Hook::replace_parameters( - " command {{ /foo }} ", - &headers, - &json!({ "foo": "bar" }) - ) - .unwrap(), - " command bar " - ); - - assert_eq!( - Hook::replace_parameters( - "{{ /foo }} command{{/field1/foo}}", - &headers, - &json!({ "foo": "bar", "field1": { "foo": "baz" } }) - ) - .unwrap(), - "bar commandbaz" - ); - - assert_eq!( - Hook::replace_parameters( - " command {{ /foo }} ", - &headers, - &json!({ "foo": "bar" }) - ) - .unwrap(), - " command bar " - ); - - assert_eq!( - Hook::replace_parameters( - " {{ /field1/foo }} command", - &headers, - &json!({ "field1": { "foo": "bar" } }) - ) - .unwrap(), - " bar command" - ); - - assert_eq!( - Hook::replace_parameters( - " {{ header X-Gitea-Event }} command", - &headers, - &json!({ "field1": { "foo": "bar" } }) - ) - .unwrap(), - " something command" - ); - - assert_eq!( - Hook::replace_parameters( - " {{ header X-Gitea-Event }} {{ /field1/foo }} command", - &headers, - &json!({ "field1": { "foo": "bar" } }) - ) - .unwrap(), - " something bar command" - ); - - assert_eq!( - Hook::replace_parameters( - " {{ header X-Gitea-Event }} {{ /field1/foo }} {{ /field1/bar }} {{ /field2/foo }} --command{{ /cmd }}", - &headers, - &json!({ "field1": { "foo": "bar", "bar": "baz" }, "field2": { "foo": "qux" }, "cmd": " else"}) - ) - .unwrap(), - " something bar baz qux --command else" - ); - } - - #[rocket::async_test] - async fn parse_command_request() { - let mut hooks = BTreeMap::new(); - - hooks.insert( - "test_hook".to_string(), - Hook { - command: - "/usr/bin/echo {{ /repository/full_name }} --foo {{ /pull_request/base/ref }}" - .to_string(), - signature: "X-Gitea-Signature".to_string(), - ip_filter: None, - secrets: vec!["valid".to_string()], - filter: FilterType::JsonFilter(JsonFilter { - pointer: "/foo".to_string(), - regex: "bar".to_string(), - }), - }, - ); - - hooks.insert( - "test_hook".to_string(), - Hook { - command: "/usr/bin/echo {{ /repository/full_name }} {{ /pull_request/base/ref }}" - .to_string(), - signature: "X-Gitea-Signature".to_string(), - ip_filter: None, - secrets: vec!["valid".to_string()], - filter: FilterType::JsonFilter(JsonFilter { - pointer: "/foo".to_string(), - regex: "bar".to_string(), - }), - }, - ); - - let config = Config { - metrics: None, - hooks: hooks, - }; - - let rocket = rocket::build() - .mount("/", routes![receive_hook]) - .manage(config) - .manage(WebhookeyMetrics::default()); - - let client = Client::tracked(rocket).await.unwrap(); - - let response = client - .post("/") - .header(Header::new( - "X-Gitea-Signature", - "693b733871ecb684651a813c82936df683c9e4a816581f385353e06170545400", - )) - .header(ContentType::JSON) - .remote("127.0.0.1:8000".parse().unwrap()) - .body( - &serde_json::to_string(&json!({ - "foo": "bar", - "repository": { - "full_name": "keith" - }, - "pull_request": { - "base": { - "ref": "main" - } - } - })) - .unwrap(), - ) - .dispatch(); - - assert_eq!(response.await.status(), Status::Ok); - } - - #[rocket::async_test] - async fn parse_invalid_command_request() { - let mut hooks = BTreeMap::new(); - - hooks.insert( - "test_hook".to_string(), - Hook { - command: "/usr/bin/echo {{ /repository/full }} {{ /pull_request/base/ref }}" - .to_string(), - signature: "X-Gitea-Signature".to_string(), - ip_filter: None, - secrets: vec!["valid".to_string()], - filter: FilterType::JsonFilter(JsonFilter { - pointer: "/foo".to_string(), - regex: "bar".to_string(), - }), - }, - ); - - let config = Config { - metrics: None, - hooks: hooks, - }; - - let rocket = rocket::build() - .mount("/", routes![receive_hook]) - .manage(config) - .manage(WebhookeyMetrics::default()); - - let client = Client::tracked(rocket).await.unwrap(); - - let response = client - .post("/") - .header(Header::new( - "X-Gitea-Signature", - "693b733871ecb684651a813c82936df683c9e4a816581f385353e06170545400", - )) - .header(ContentType::JSON) - .remote("127.0.0.1:8000".parse().unwrap()) - .body( - &serde_json::to_string(&json!({ - "foo": "bar", - "repository": { - "full_name": "keith" - }, - "pull_request": { - "base": { - "ref": "main" - } - } - })) - .unwrap(), - ) - .dispatch(); - - assert_eq!(response.await.status(), Status::NotFound); - } - - #[test] - fn parse_config() { - let config: Config = serde_yaml::from_str( - r#"--- -hooks: - hook1: - command: /usr/bin/local/script_xy.sh {{ /field2/foo }} asdfasdf - signature: X-Gitea-Signature - ip_filter: - allow: - - 127.0.0.1/31 - secrets: - - secret_key_01 - - secret_key_02 - filter: - json: - pointer: /ref - regex: refs/heads/master - hook2: - command: /usr/bin/local/script_xy.sh asdfasdf - signature: X-Gitea-Signature - secrets: - - secret_key_01 - - secret_key_02 - filter: - and: - - json: - pointer: /ref - regex: refs/heads/master - - json: - pointer: /after - regex: f6e5fe4fe37df76629112d55cc210718b6a55e7e"#, - ) - .unwrap(); - - assert_eq!( - serde_yaml::to_string(&config).unwrap(), - serde_yaml::to_string(&Config { - metrics: None, - hooks: BTreeMap::from([ - ( - "hook1".to_string(), - Hook { - command: "/usr/bin/local/script_xy.sh {{ /field2/foo }} asdfasdf" - .to_string(), - signature: "X-Gitea-Signature".to_string(), - ip_filter: Some(IpFilter::Allow(vec![AddrType::IpNet( - "127.0.0.1/31".parse().unwrap() - )])), - secrets: vec!["secret_key_01".to_string(), "secret_key_02".to_string()], - filter: FilterType::JsonFilter(JsonFilter { - pointer: "/ref".to_string(), - regex: "refs/heads/master".to_string(), - }), - } - ), - ( - "hook2".to_string(), - Hook { - command: "/usr/bin/local/script_xy.sh asdfasdf".to_string(), - signature: "X-Gitea-Signature".to_string(), - ip_filter: None, - secrets: vec!["secret_key_01".to_string(), "secret_key_02".to_string()], - filter: FilterType::And(vec![ - FilterType::JsonFilter(JsonFilter { - pointer: "/ref".to_string(), - regex: "refs/heads/master".to_string(), - }), - FilterType::JsonFilter(JsonFilter { - pointer: "/after".to_string(), - regex: "f6e5fe4fe37df76629112d55cc210718b6a55e7e".to_string(), - }), - ]), - } - ) - ]) - }) - .unwrap() - ); - - let config: Config = serde_yaml::from_str( - r#"--- -metrics: - enabled: true -hooks: - hook1: - command: /usr/bin/local/script_xy.sh {{ /field2/foo }} asdfasdf - signature: X-Gitea-Signature - ip_filter: - allow: - - 127.0.0.1/31 - secrets: - - secret_key_01 - - secret_key_02 - filter: - json: - pointer: /ref - regex: refs/heads/master"#, - ) - .unwrap(); - - assert_eq!( - serde_yaml::to_string(&config).unwrap(), - serde_yaml::to_string(&Config { - metrics: Some(MetricsConfig { - enabled: true, - ip_filter: None - }), - hooks: BTreeMap::from([( - "hook1".to_string(), - Hook { - command: "/usr/bin/local/script_xy.sh {{ /field2/foo }} asdfasdf" - .to_string(), - signature: "X-Gitea-Signature".to_string(), - ip_filter: Some(IpFilter::Allow(vec![AddrType::IpNet( - "127.0.0.1/31".parse().unwrap() - )])), - secrets: vec!["secret_key_01".to_string(), "secret_key_02".to_string()], - filter: FilterType::JsonFilter(JsonFilter { - pointer: "/ref".to_string(), - regex: "refs/heads/master".to_string(), - }), - } - ),]) - }) - .unwrap() - ); - } -} diff --git a/src/metrics.rs b/src/metrics.rs new file mode 100644 index 0000000..71c4fae --- /dev/null +++ b/src/metrics.rs @@ -0,0 +1,91 @@ +use crate::Config; +use log::warn; +use rocket::{get, State}; +use std::{ + net::SocketAddr, + sync::atomic::{AtomicUsize, Ordering}, +}; + +#[derive(Debug, Default)] +pub struct Metrics { + pub requests_received: AtomicUsize, + pub requests_invalid: AtomicUsize, + pub hooks_successful: AtomicUsize, + pub hooks_forbidden: AtomicUsize, + pub hooks_unmatched: AtomicUsize, + pub commands_executed: AtomicUsize, + pub commands_execution_failed: AtomicUsize, + pub commands_successful: AtomicUsize, + pub commands_failed: AtomicUsize, +} + +#[get("/metrics")] +pub async fn metrics( + address: SocketAddr, + metrics: &State, + config: &State, +) -> Option { + // Are metrics configured? + if let Some(metrics_config) = &config.metrics { + // Are metrics enabled? + if metrics_config.enabled { + // Is a filter configured? + if let Some(filter) = &metrics_config.ip_filter { + // Does the request match the filter? + if filter.validate(&address.ip()) { + return Some(metrics.get_metrics()); + } + } else { + return Some(metrics.get_metrics()); + } + } + } + + warn!("Forbidden request for metrics: {:?}", address); + + None +} + +impl Metrics { + fn get_metrics(&self) -> String { + format!( + r"# HELP webhookey_requests_received Number of requests received +# TYPE webhookey_requests_received gauge +webhookey_requests_received {} +# HELP webhookey_requests_invalid Number of invalid requests received +# TYPE webhookey_requests_invalid gauge +webhookey_requests_invalid {} +# HELP webhookey_hooks_successful Number of successfully executed hooks +# TYPE webhookey_hooks_successful gauge +webhookey_hooks_sucessful {} +# HELP webhookey_hooks_forbidden Number of forbidden requests +# TYPE webhookey_hooks_forbidden gauge +webhookey_hooks_forbidden {} +# HELP webhookey_hooks_unmatched Number of unmatched requests +# TYPE webhookey_hooks_unmatched gauge +webhookey_hooks_unmatched {} +# HELP webhookey_commands_executed Number of commands executed +# TYPE webhookey_commands_executed gauge +webhookey_commands_executed {} +# HELP webhookey_commands_execution_failed Number of commands failed to execute +# TYPE webhookey_commands_execution_failed gauge +webhookey_commands_execution_failed {} +# HELP webhookey_commands_successful Number of executed commands returning return code 0 +# TYPE webhookey_commands_successful gauge +webhookey_commands_successful {} +# HELP webhookey_commands_failed Number of executed commands returning different return code than 0 +# TYPE webhookey_commands_failed gauge +webhookey_commands_failed {} +", + self.requests_received.load(Ordering::Relaxed), + self.requests_invalid.load(Ordering::Relaxed), + self.hooks_successful.load(Ordering::Relaxed), + self.hooks_forbidden.load(Ordering::Relaxed), + self.hooks_unmatched.load(Ordering::Relaxed), + self.commands_executed.load(Ordering::Relaxed), + self.commands_execution_failed.load(Ordering::Relaxed), + self.commands_successful.load(Ordering::Relaxed), + self.commands_failed.load(Ordering::Relaxed), + ) + } +} diff --git a/src/webhooks.rs b/src/webhooks.rs deleted file mode 100644 index efd9fb6..0000000 --- a/src/webhooks.rs +++ /dev/null @@ -1,163 +0,0 @@ -use anyhow::Result; -use ipnet::IpNet; -use log::{debug, error, trace}; -use regex::Regex; -use serde::{Deserialize, Serialize}; -use std::net::IpAddr; -use thiserror::Error; - -#[derive(Debug, Error)] -pub enum WebhookeyError { - #[error("Could not extract signature from header")] - InvalidSignature, - #[error("Unauthorized request from `{0}`")] - Unauthorized(IpAddr), - #[error("Unmatched hook from `{0}`")] - UnmatchedHook(IpAddr), - #[error("Could not evaluate filter request")] - InvalidFilter, - #[error("IO Error")] - Io(std::io::Error), - #[error("Serde Error")] - Serde(serde_json::Error), - #[error("Regex Error")] - Regex(regex::Error), -} - -#[derive(Debug, Deserialize, Serialize)] -#[serde(deny_unknown_fields, untagged)] -pub enum AddrType { - IpAddr(IpAddr), - IpNet(IpNet), -} - -impl AddrType { - pub fn matches(&self, client_ip: &IpAddr) -> bool { - match self { - AddrType::IpAddr(addr) => addr == client_ip, - AddrType::IpNet(net) => net.contains(client_ip), - } - } -} - -#[derive(Debug, Deserialize, Serialize)] -#[serde(deny_unknown_fields, rename_all = "lowercase")] -pub enum IpFilter { - Allow(Vec), - Deny(Vec), -} - -impl IpFilter { - pub fn validate(&self, client_ip: &IpAddr) -> bool { - match self { - IpFilter::Allow(list) => list.iter().any(|i| i.matches(client_ip)), - IpFilter::Deny(list) => !list.iter().any(|i| i.matches(client_ip)), - } - } -} - -#[derive(Debug, Deserialize, Serialize)] -#[serde(deny_unknown_fields)] -pub struct JsonFilter { - pub pointer: String, - pub regex: String, -} - -impl JsonFilter { - pub fn evaluate(&self, data: &serde_json::Value) -> Result { - trace!( - "Matching `{}` on `{}` from received json", - &self.regex, - &self.pointer, - ); - - let regex = Regex::new(&self.regex).map_err(WebhookeyError::Regex)?; - - if let Some(value) = data.pointer(&self.pointer) { - if regex.is_match(&get_string(value)?) { - debug!("Regex `{}` for `{}` matches", &self.regex, &self.pointer); - - return Ok(true); - } - } - - debug!( - "Regex `{}` for `{}` does not match", - &self.regex, &self.pointer - ); - - Ok(false) - } -} - -#[derive(Debug, Deserialize, Serialize)] -#[serde(deny_unknown_fields, rename_all = "lowercase")] -pub enum FilterType { - And(Vec), - Or(Vec), - #[serde(rename = "json")] - JsonFilter(JsonFilter), -} - -impl FilterType { - pub fn evaluate(&self, data: &serde_json::Value) -> Result { - match self { - FilterType::And(filters) => { - let (mut results, mut errors) = (Vec::new(), Vec::new()); - - filters - .iter() - .map(|filter| filter.evaluate(data)) - .for_each(|item| match item { - Ok(o) => results.push(o), - Err(e) => errors.push(e), - }); - - if errors.is_empty() { - Ok(results.iter().all(|r| *r)) - } else { - errors - .iter() - .for_each(|e| error!("Could not evaluate Filter: {}", e)); - - Err(WebhookeyError::InvalidFilter) - } - } - FilterType::Or(filters) => { - let (mut results, mut errors) = (Vec::new(), Vec::new()); - - filters - .iter() - .map(|filter| filter.evaluate(data)) - .for_each(|item| match item { - Ok(o) => results.push(o), - Err(e) => errors.push(e), - }); - - if errors.is_empty() { - Ok(results.iter().any(|r| *r)) - } else { - errors - .iter() - .for_each(|e| error!("Could not evaluate Filter: {}", e)); - - Err(WebhookeyError::InvalidFilter) - } - } - // FilterType::HeaderFilter(filter) => todo!(), - FilterType::JsonFilter(filter) => filter.evaluate(data), - } - } -} - -pub fn get_string(data: &serde_json::Value) -> Result { - match &data { - serde_json::Value::Bool(bool) => Ok(bool.to_string()), - serde_json::Value::Number(number) => Ok(number.to_string()), - serde_json::Value::String(string) => Ok(string.as_str().to_string()), - x => { - error!("Could not get string from: {:?}", x); - unimplemented!() - } - } -} diff --git a/webhookey.1 b/webhookey.1 new file mode 100644 index 0000000..e0ca688 --- /dev/null +++ b/webhookey.1 @@ -0,0 +1,57 @@ +.TH WEBHOOKEY 1 "26 Nov 2021" "webhookey" "Linux" +.SH NAME +webhookey \- Receive webhooks and act upon them +.SH SYNOPSIS +.B webhookey [OPTIONS] [SUBCOMMAND] +.SH DESCRIPTION +\fBwebhookey\fR receives http(s) requests in form of webhooks. Those +webhooks are matched against configured filters. If a filter matches, +a command (which can also incorporate data contained in the received +header or body) is executed. +.SH OPTIONS +.TP +.BR "-c", " --config " +Provide a path to the configuration file. +.TP +.BR "-h", " --help" +Print help information. +.TP +.BR "-V", " --version" +Print version information. +.SH SUBCOMMAND +.TP +.B configtest +- Verifies if the configuration can be parsed without errors. +.TP +.B help +- Print the general help message or the help of the given subcommand(s). +.SH ENVIRONMENT +.TP +.B ROCKET_ADDRESS +The IP address webhookey listens on (default: 127.0.0.1). +.TP +.B ROCKET_PORT +The port webhookey listens on (default: 8000). +.TP +.B ROCKET_WORKERS +The numbers of threads to use (default: CPU core count). +.TP +.B RUST_LOG +Set the Log level, which can be one one of "error", "warn", "info", +"debug", "trace" (default: "error"). +.SH EXAMPLES +.PP +webhookey configtest +.RS 4 +Return either "Config is OK" and return code 0 or an error description +and return code 1. +.RE +.PP +webhookey +.RS 4 +Start webhookey. +.RE +.SH REPORTING BUGS +To report any bugs file an issue at +https://git.onders.org/finga/webhookey/issues/new?template=bug.md or +send an email to .