diff --git a/Cargo.lock b/Cargo.lock index 2661ba96..9ded68ac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + [[package]] name = "android_system_properties" version = "0.1.5" @@ -29,6 +35,17 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" +[[package]] +name = "async-trait" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76464446b8bc32758d7e88ee1a804d9914cd9b1cb264c029899680b0be29826f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "atty" version = "0.2.14" @@ -46,6 +63,66 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "axum" +version = "0.5.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e3356844c4d6a6d6467b8da2cffb4a2820be256f50a3a386c9d152bab31043" +dependencies = [ + "async-trait", + "axum-core", + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "hyper", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde", + "sync_wrapper", + "tokio", + "tower", + "tower-http", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9f0c0a60006f2a293d82d571f635042a72edf927539b7685bd62d361963839b" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "mime", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-server" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba6170b61f7b086609dabcae68d2e07352539c6ef04a7c82980bdfa01a159d" +dependencies = [ + "bytes", + "futures-util", + "http", + "http-body", + "hyper", + "tokio", + "tower-service", +] + [[package]] name = "base-x" version = "0.2.11" @@ -142,6 +219,12 @@ version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +[[package]] +name = "bytes" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" + [[package]] name = "cc" version = "1.0.73" @@ -167,7 +250,7 @@ dependencies = [ "js-sys", "num-integer", "num-traits", - "time", + "time 0.1.44", "wasm-bindgen", "winapi", ] @@ -204,6 +287,15 @@ dependencies = [ "libc", ] +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossbeam-channel" version = "0.5.6" @@ -303,6 +395,22 @@ dependencies = [ "instant", ] +[[package]] +name = "flate2" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "form_urlencoded" version = "1.1.0" @@ -312,6 +420,45 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures-channel" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30bdd20c28fadd505d0fd6712cdfcb0d4b5648baf45faef7f852afb2399bb050" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf" + +[[package]] +name = "futures-sink" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b20ba5a92e727ba30e72834706623d94ac93a725410b6a6b6fbc1b07f7ba56" + +[[package]] +name = "futures-task" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6508c467c73851293f390476d4491cf4d227dbabcd4170f3bb6044959b294f1" + +[[package]] +name = "futures-util" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + [[package]] name = "generic-array" version = "0.14.6" @@ -391,6 +538,25 @@ dependencies = [ "url", ] +[[package]] +name = "h2" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca32592cf21ac7ccab1825cd87f6c9b3d9022c44d086172ed0966bec8af30be" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -415,6 +581,70 @@ dependencies = [ "winapi", ] +[[package]] +name = "http" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "http-range-header" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29" + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "hyper" +version = "0.14.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + [[package]] name = "iana-time-zone" version = "0.1.50" @@ -538,12 +768,54 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "matchit" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73cbba799671b762df5a175adf59ce145165747bb891505c43d09aefbbf38beb" + [[package]] name = "memchr" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[package]] +name = "miniz_oxide" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" +dependencies = [ + "libc", + "log", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys", +] + [[package]] name = "multibase" version = "0.9.1" @@ -603,6 +875,16 @@ dependencies = [ "serde", ] +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num-integer" version = "0.1.45" @@ -622,6 +904,25 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_threads" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +dependencies = [ + "libc", +] + [[package]] name = "olpc-cjson" version = "0.1.2" @@ -645,12 +946,50 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "percent-encoding" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +[[package]] +name = "pin-project" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "pkg-config" version = "0.3.25" @@ -780,6 +1119,25 @@ dependencies = [ "thiserror", ] +[[package]] +name = "radicle-httpd" +version = "0.1.0" +dependencies = [ + "anyhow", + "axum", + "axum-server", + "flate2", + "hyper", + "lexopt", + "radicle", + "thiserror", + "tokio", + "tower-http", + "tracing", + "tracing-logfmt", + "tracing-subscriber", +] + [[package]] name = "radicle-node" version = "0.2.0" @@ -868,6 +1226,30 @@ dependencies = [ "bitflags", ] +[[package]] +name = "regex" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" + [[package]] name = "remove_dir_all" version = "0.5.3" @@ -964,12 +1346,36 @@ dependencies = [ "opaque-debug", ] +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + [[package]] name = "siphasher" version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" +[[package]] +name = "slab" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + [[package]] name = "socket2" version = "0.4.7" @@ -1021,6 +1427,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20518fe4a4c9acf048008599e464deb21beeae3d3578418951a189c235a7a9a8" + [[package]] name = "tempfile" version = "3.3.0" @@ -1055,6 +1467,15 @@ dependencies = [ "syn", ] +[[package]] +name = "thread_local" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +dependencies = [ + "once_cell", +] + [[package]] name = "time" version = "0.1.44" @@ -1066,6 +1487,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "time" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d634a985c4d4238ec39cacaed2e7ae552fbd3c476b552c1deac3021b7d7eaf0c" +dependencies = [ + "itoa", + "libc", + "num_threads", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -1081,6 +1513,178 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +[[package]] +name = "tokio" +version = "1.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099" +dependencies = [ + "autocfg", + "bytes", + "libc", + "memchr", + "mio", + "num_cpus", + "pin-project-lite", + "socket2", + "tokio-macros", + "winapi", +] + +[[package]] +name = "tokio-macros" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-util" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c530c8675c1dbf98facee631536fa116b5fb6382d7dd6dc1b118d970eafe3ba" +dependencies = [ + "bitflags", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-range-header", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + +[[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.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-logfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08aacc136419ba433b3f9bfd434a1bb62fe385328935e6ac11d952122b8a8cb" +dependencies = [ + "time 0.3.15", + "tracing", + "tracing-core", + "tracing-subscriber", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + [[package]] name = "typenum" version = "1.15.0" @@ -1125,6 +1729,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + [[package]] name = "vcpkg" version = "0.2.15" @@ -1137,6 +1747,16 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + [[package]] name = "wasi" version = "0.10.0+wasi-snapshot-preview1" @@ -1225,6 +1845,49 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + [[package]] name = "zeroize" version = "1.5.7" diff --git a/Cargo.toml b/Cargo.toml index 36b5b6b2..02119d5a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,12 @@ [workspace] -members = ["radicle", "radicle-node", "radicle-tools", "radicle-ssh", "radicle-remote-helper"] +members = [ + "radicle", + "radicle-node", + "radicle-tools", + "radicle-ssh", + "radicle-remote-helper", + "radicle-httpd" +] [patch.crates-io.nakamoto-net] git = "https://github.com/cloudhead/nakamoto" diff --git a/build.rs b/build.rs new file mode 100644 index 00000000..fc6352c2 --- /dev/null +++ b/build.rs @@ -0,0 +1,23 @@ +use std::process::Command; + +fn main() { + // Set a build-time `GIT_HEAD` env var which includes the commit id; + // such that we can tell which code is running. + let hash = Command::new("git") + .arg("rev-parse") + .arg("--short") + .arg("HEAD") + .output() + .ok() + .and_then(|output| { + if output.status.success() { + String::from_utf8(output.stdout).ok() + } else { + None + } + }) + .unwrap_or_else(|| String::from("unknown")); + + println!("cargo:rustc-env=GIT_HEAD={}", hash); + println!("cargo:rustc-rerun-if-changed=.git/HEAD"); +} diff --git a/radicle-httpd/Cargo.toml b/radicle-httpd/Cargo.toml new file mode 100644 index 00000000..36f41aaf --- /dev/null +++ b/radicle-httpd/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "radicle-httpd" +license = "MIT OR Apache-2.0" +version = "0.1.0" +authors = ["Alexis Sellier "] +edition = "2021" +default-run = "radicle-httpd" +build = "../build.rs" + +[features] +default = [] +logfmt = [ + "tracing-logfmt", + "tracing-subscriber/env-filter" +] + +[dependencies] +anyhow = { version = "1" } +flate2 = { version = "1" } +lexopt = { version = "0.2.1" } +thiserror = { version = "1" } +tokio = { version = "1.21", default-features = false, features = ["macros", "rt-multi-thread"] } +tracing = { version = "0.1.37", default-features = false, features = ["std", "log"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["std", "ansi", "fmt"] } +tracing-logfmt = { version = "0.2", optional = true } +axum = { version = "0.5.16", default-features = false } +axum-server = { version = "0.4.2", default-features = false } +hyper = { version = "0.14.17", default-features = false } +tower-http = { version = "0.3.4", default-features = false, features = ["trace"] } + +[dependencies.radicle] +path = "../radicle" +version = "0.2.0" diff --git a/radicle-httpd/src/error.rs b/radicle-httpd/src/error.rs new file mode 100644 index 00000000..01f11f6a --- /dev/null +++ b/radicle-httpd/src/error.rs @@ -0,0 +1,52 @@ +use axum::http; +use axum::response::{IntoResponse, Response}; + +/// Errors relating to the HTTP backend. +#[derive(Debug, thiserror::Error)] +pub enum Error { + /// I/O error. + #[error("i/o error: {0}")] + Io(#[from] std::io::Error), + + /// The service is not available. + #[error("service '{0}' not available")] + ServiceUnavailable(&'static str), + + /// Invalid identifier. + #[error("invalid radicle identifier: {0}")] + Id(#[from] radicle::identity::IdError), + + /// Git backend error. + #[error("backend error")] + Backend, + + /// Id is not valid. + #[error("id is not valid")] + InvalidId, + + /// HeaderName error. + #[error(transparent)] + InvalidHeaderName(#[from] axum::http::header::InvalidHeaderName), + + /// HeaderValue error. + #[error(transparent)] + InvalidHeaderValue(#[from] axum::http::header::InvalidHeaderValue), +} + +impl Error { + pub fn status(&self) -> http::StatusCode { + match self { + Error::ServiceUnavailable(_) => http::StatusCode::SERVICE_UNAVAILABLE, + Error::InvalidId => http::StatusCode::NOT_FOUND, + _ => http::StatusCode::INTERNAL_SERVER_ERROR, + } + } +} + +impl IntoResponse for Error { + fn into_response(self) -> Response { + tracing::error!("{}", self); + + self.status().into_response() + } +} diff --git a/radicle-httpd/src/lib.rs b/radicle-httpd/src/lib.rs new file mode 100644 index 00000000..554cbf15 --- /dev/null +++ b/radicle-httpd/src/lib.rs @@ -0,0 +1,251 @@ +#![allow(clippy::type_complexity)] +#![allow(clippy::too_many_arguments)] +pub mod error; + +use std::collections::HashMap; +use std::io::prelude::*; +use std::net::SocketAddr; +use std::path::Path; +use std::process::{Command, Stdio}; +use std::sync::Arc; +use std::time::Duration; +use std::{io, net, str}; + +use anyhow::Context as _; +use axum::body::Body; +use axum::body::{BoxBody, Bytes}; +use axum::extract::{ConnectInfo, Path as AxumPath, RawQuery}; +use axum::http::header::HeaderName; +use axum::http::HeaderMap; +use axum::http::{Method, StatusCode}; +use axum::http::{Request, Response}; +use axum::response::IntoResponse; +use axum::routing::any; +use axum::{Extension, Router}; +use flate2::write::GzDecoder; +use hyper::body::Buf as _; +use tower_http::trace::TraceLayer; +use tracing::Span; + +use radicle::identity::Id; +use radicle::profile::Profile; + +use error::Error; + +pub const VERSION: &str = env!("CARGO_PKG_VERSION"); + +#[derive(Debug, Clone)] +pub struct Options { + pub listen: net::SocketAddr, +} + +/// Run the Git Server. +pub async fn run(options: Options) -> anyhow::Result<()> { + let git_version = Command::new("git") + .arg("version") + .output() + .context("'git' command must be available")? + .stdout; + tracing::info!("{}", str::from_utf8(&git_version)?.trim()); + + let profile = radicle::Profile::load()?; + tracing::info!("using radicle home at {}", profile.home.display()); + + let app = Router::new() + .route("/:project/*request", any(git_handler)) + .layer(Extension(Arc::new(profile))) + .layer( + TraceLayer::new_for_http() + .make_span_with(|request: &Request| { + tracing::info_span!( + "request", + method = %request.method(), + uri = %request.uri(), + status = tracing::field::Empty, + latency = tracing::field::Empty, + ) + }) + .on_response( + |response: &Response, latency: Duration, span: &Span| { + span.record("status", &tracing::field::debug(response.status())); + span.record("latency", &tracing::field::debug(latency)); + + tracing::info!("Processed"); + }, + ), + ) + .into_make_service_with_connect_info::(); + + tracing::info!("listening on http://{}", options.listen); + + axum::Server::bind(&options.listen) + .serve(app) + .await + .map_err(anyhow::Error::from) +} + +async fn git_handler( + Extension(profile): Extension>, + AxumPath((project, request)): AxumPath<(String, String)>, + method: Method, + headers: HeaderMap, + body: Bytes, + ConnectInfo(remote): ConnectInfo, + query: RawQuery, +) -> impl IntoResponse { + let query = query.0.unwrap_or_default(); + let id: Id = project.strip_suffix(".git").unwrap_or(&project).parse()?; + + let (status, headers, body) = + git_http_backend(&profile, method, headers, body, remote, id, &request, query).await?; + + let mut response_headers = HeaderMap::new(); + for (name, vec) in headers.iter() { + for value in vec { + let header: HeaderName = name.try_into()?; + response_headers.insert(header, value.parse()?); + } + } + + Ok::<_, Error>((status, response_headers, body)) +} + +async fn git_http_backend( + profile: &Profile, + method: Method, + headers: HeaderMap, + mut body: Bytes, + remote: net::SocketAddr, + id: Id, + path: &str, + query: String, +) -> Result<(StatusCode, HashMap>, Vec), Error> { + let git_dir = radicle::storage::git::paths::repository(&profile.storage, &id); + let content_type = + if let Some(Ok(content_type)) = headers.get("Content-Type").map(|h| h.to_str()) { + content_type + } else { + "" + }; + + // Reject push requests. + match (path, query.as_str()) { + ("git-receive-pack", _) | (_, "service=git-receive-pack") => { + return Err(Error::ServiceUnavailable("git-receive-pack")); + } + _ => {} + }; + + tracing::debug!("id: {:?}", id); + tracing::debug!("headers: {:?}", headers); + tracing::debug!("path: {:?}", path); + tracing::debug!("method: {:?}", method.as_str()); + tracing::debug!("remote: {:?}", remote.to_string()); + + let mut cmd = Command::new("git"); + let mut child = cmd + .arg("http-backend") + .env("REQUEST_METHOD", method.as_str()) + .env("GIT_PROJECT_ROOT", git_dir) + // "The GIT_HTTP_EXPORT_ALL environmental variable may be passed to git-http-backend to bypass + // the check for the "git-daemon-export-ok" file in each repository before allowing export of + // that repository." + .env("GIT_HTTP_EXPORT_ALL", String::default()) + .env("PATH_INFO", Path::new("/").join(path)) + .env("CONTENT_TYPE", content_type) + .env("QUERY_STRING", query) + .stderr(Stdio::piped()) + .stdout(Stdio::piped()) + .stdin(Stdio::piped()) + .spawn()?; + + // Whether the request body is compressed. + let gzip = matches!( + headers.get("Content-Encoding").map(|h| h.to_str()), + Some(Ok("gzip")) + ); + + { + // This is safe because we captured the child's stdin. + let mut stdin = child.stdin.take().unwrap(); + + // Copy the request body to git-http-backend's stdin. + if gzip { + let mut decoder = GzDecoder::new(&mut stdin); + let mut reader = body.reader(); + + io::copy(&mut reader, &mut decoder)?; + decoder.finish()?; + } else { + while body.has_remaining() { + let mut chunk = body.chunk(); + let count = chunk.len(); + + io::copy(&mut chunk, &mut stdin)?; + body.advance(count); + } + } + } + + match child.wait_with_output() { + Ok(output) if output.status.success() => { + tracing::info!("git-http-backend: exited successfully for {}", id); + + let mut reader = std::io::Cursor::new(output.stdout); + let mut headers = HashMap::new(); + + // Parse headers returned by git so that we can use them in the client response. + for line in io::Read::by_ref(&mut reader).lines() { + let line = line?; + + if line.is_empty() || line == "\r" { + break; + } + + let mut parts = line.splitn(2, ':'); + let key = parts.next(); + let value = parts.next(); + + if let (Some(key), Some(value)) = (key, value) { + let value = &value[1..]; + + headers + .entry(key.to_string()) + .or_insert_with(Vec::new) + .push(value.to_string()); + } else { + return Err(Error::Backend); + } + } + + let status = { + tracing::debug!("git-http-backend: {:?}", &headers); + + let line = headers.remove("Status").unwrap_or_default(); + let line = line.into_iter().next().unwrap_or_default(); + let mut parts = line.split(' '); + + parts + .next() + .and_then(|p| p.parse().ok()) + .unwrap_or(StatusCode::OK) + }; + + let position = reader.position() as usize; + let body = reader.into_inner().split_off(position); + + Ok((status, headers, body)) + } + Ok(output) => { + tracing::error!("git-http-backend: exited with code {}", output.status); + + if let Ok(output) = std::str::from_utf8(&output.stderr) { + tracing::error!("git-http-backend: stderr: {}", output.trim_end()); + } + Err(Error::Backend) + } + Err(err) => { + panic!("failed to wait for git-http-backend: {}", err); + } + } +} diff --git a/radicle-httpd/src/main.rs b/radicle-httpd/src/main.rs new file mode 100644 index 00000000..ab670475 --- /dev/null +++ b/radicle-httpd/src/main.rs @@ -0,0 +1,82 @@ +use std::{net, process}; + +use tracing::dispatcher::Dispatch; + +use radicle_httpd as httpd; + +#[derive(Debug)] +pub struct Options { + pub listen: net::SocketAddr, +} + +impl Options { + fn from_env() -> Result { + use lexopt::prelude::*; + + let mut parser = lexopt::Parser::from_env(); + let mut listen = None; + + while let Some(arg) = parser.next()? { + match arg { + Long("listen") => { + let addr = parser.value()?.parse()?; + listen = Some(addr); + } + Long("help") => { + println!("usage: radicle-httpd [--listen ]"); + process::exit(0); + } + _ => return Err(arg.unexpected()), + } + } + Ok(Self { + listen: listen.unwrap_or_else(|| ([0, 0, 0, 0], 8080).into()), + }) + } +} + +impl From for httpd::Options { + fn from(other: Options) -> Self { + Self { + listen: other.listen, + } + } +} + +#[cfg(feature = "logfmt")] +mod logger { + use tracing_subscriber::layer::SubscriberExt as _; + use tracing_subscriber::EnvFilter; + + pub fn subscriber() -> impl tracing::Subscriber { + tracing_subscriber::Registry::default() + .with(EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"))) + .with(tracing_logfmt::layer()) + } +} + +#[cfg(not(feature = "logfmt"))] +mod logger { + pub fn subscriber() -> impl tracing::Subscriber { + tracing_subscriber::FmtSubscriber::new() + } +} + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + let options = Options::from_env()?; + + tracing::dispatcher::set_global_default(Dispatch::new(logger::subscriber())) + .expect("Global logger hasn't already been set"); + + tracing::info!("version {}-{}", env!("CARGO_PKG_VERSION"), env!("GIT_HEAD")); + + match httpd::run(options.into()).await { + Ok(()) => {} + Err(err) => { + tracing::error!("Fatal: {:#}", err); + process::exit(1); + } + } + Ok(()) +}