diff --git a/submissions/tshare/.gitignore b/submissions/tshare/.gitignore new file mode 100644 index 00000000..1a14b405 --- /dev/null +++ b/submissions/tshare/.gitignore @@ -0,0 +1,2 @@ +/target +/releases \ No newline at end of file diff --git a/submissions/tshare/Cargo.lock b/submissions/tshare/Cargo.lock new file mode 100644 index 00000000..fb2a9c15 --- /dev/null +++ b/submissions/tshare/Cargo.lock @@ -0,0 +1,2668 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.6.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.59.0", +] + +[[package]] +name = "anyhow" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "axum" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5" +dependencies = [ + "axum-core", + "base64", + "bytes", + "form_urlencoded", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sha1", + "sync_wrapper", + "tokio", + "tokio-tungstenite 0.26.2", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "backtrace" +version = "0.3.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bcrypt" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92758ad6077e4c76a6cadbce5005f666df70d4f13b19976b1a8062eef880040f" +dependencies = [ + "base64", + "blowfish", + "getrandom 0.3.3", + "subtle", + "zeroize", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[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 = "blowfish" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7" +dependencies = [ + "byteorder", + "cipher", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "cc" +version = "1.2.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "clap" +version = "4.5.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "convert_case" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb402b8d4c85569410425650ce3eddc7d698ed96d39a73f941b08fb63082f1e7" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crossterm" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" +dependencies = [ + "bitflags 2.9.1", + "crossterm_winapi", + "derive_more", + "document-features", + "mio", + "parking_lot", + "rustix", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "ctrlc" +version = "3.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46f93780a459b7d656ef7f071fe699c4d3d2cb201c4b24d085b6ddc505276e73" +dependencies = [ + "nix 0.30.1", + "windows-sys 0.59.0", +] + +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "data-encoding" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" + +[[package]] +name = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "document-features" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" +dependencies = [ + "litrs", +] + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "filedescriptor" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e40758ed24c9b2eeb76c35fb0aebc66c626084edd827e07e1552279814c6682d" +dependencies = [ + "libc", + "thiserror 1.0.69", + "winapi", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-macro", + "futures-sink", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "h2" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashbrown" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "system-configuration", + "tokio", + "tower-service", + "tracing", + "windows-registry", +] + +[[package]] +name = "icu_collections" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" + +[[package]] +name = "icu_properties" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "potential_utf", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" + +[[package]] +name = "icu_provider" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +dependencies = [ + "displaydoc", + "icu_locale_core", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +dependencies = [ + "equivalent", + "hashbrown 0.15.4", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + +[[package]] +name = "io-uring" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" +dependencies = [ + "bitflags 2.9.1", + "cfg-if", + "libc", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "iri-string" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "litemap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" + +[[package]] +name = "litrs" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed" + +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +dependencies = [ + "libc", + "log", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.59.0", +] + +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nix" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" +dependencies = [ + "bitflags 2.9.1", + "cfg-if", + "cfg_aliases 0.1.1", + "libc", +] + +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags 2.9.1", + "cfg-if", + "cfg_aliases 0.2.1", + "libc", +] + +[[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 = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + +[[package]] +name = "openssl" +version = "0.10.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" +dependencies = [ + "bitflags 2.9.1", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-sys" +version = "0.9.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "parking_lot" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[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.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "portable-pty" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4a596a2b3d2752d94f51fac2d4a96737b8705dddd311a32b9af47211f08671e" +dependencies = [ + "anyhow", + "bitflags 1.3.2", + "downcast-rs", + "filedescriptor", + "lazy_static", + "libc", + "log", + "nix 0.28.0", + "serial2", + "shared_library", + "shell-words", + "winapi", + "winreg", +] + +[[package]] +name = "potential_utf" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.3", +] + +[[package]] +name = "redox_syscall" +version = "0.5.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7251471db004e509f4e75a62cca9435365b5ec7bcdff530d612ac7c87c44a792" +dependencies = [ + "bitflags 2.9.1", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "reqwest" +version = "0.12.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "js-sys", + "log", + "mime", + "native-tls", + "percent-encoding", + "pin-project-lite", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-native-tls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.16", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" + +[[package]] +name = "rustix" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +dependencies = [ + "bitflags 2.9.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.60.2", +] + +[[package]] +name = "rustls" +version = "0.23.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2491382039b29b9b11ff08b76ff6c97cf287671dbb74f0be44bda389fffe9bd1" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "schannel" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.9.1", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.141" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a" +dependencies = [ + "itoa", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serial2" +version = "0.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7d1d08630509d69f90eff4afcd02c3bd974d979225cbd815ff5942351b14375" +dependencies = [ + "cfg-if", + "libc", + "winapi", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shared_library" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a9e7e0f2bfae24d8a5b5a66c5b257a83c7412304311512a0c054cd5e619da11" +dependencies = [ + "lazy_static", + "libc", +] + +[[package]] +name = "shell-words" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.9.1", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +dependencies = [ + "fastrand", + "getrandom 0.3.3", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl 2.0.12", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "tinystr" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43864ed400b6043a4757a25c7a64a8efde741aed79a056a2fb348a406701bb35" +dependencies = [ + "backtrace", + "bytes", + "io-uring", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "slab", + "socket2", + "tokio-macros", + "windows-sys 0.59.0", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite 0.26.2", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "489a59b6730eda1b0171fcfda8b121f4bee2b35cba8645ca35c5f7ba3eb736c1" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite 0.27.0", +] + +[[package]] +name = "tokio-util" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +dependencies = [ + "bitflags 2.9.1", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "tshare" +version = "1.0.0" +dependencies = [ + "anyhow", + "clap", + "crossterm", + "ctrlc", + "futures-util", + "portable-pty", + "reqwest", + "serde", + "serde_json", + "tokio", + "tokio-tungstenite 0.27.0", + "tracing", + "tracing-subscriber", + "url", +] + +[[package]] +name = "tungstenite" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13" +dependencies = [ + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand", + "sha1", + "thiserror 2.0.12", + "utf-8", +] + +[[package]] +name = "tungstenite" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadc29d668c91fcc564941132e17b28a7ceb2f3ebf0b9dae3e03fd7a6748eb0d" +dependencies = [ + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand", + "sha1", + "thiserror 2.0.12", + "utf-8", +] + +[[package]] +name = "tunnel-server" +version = "1.0.0" +dependencies = [ + "anyhow", + "axum", + "bcrypt", + "clap", + "dashmap", + "futures-util", + "serde", + "serde_json", + "tokio", + "tokio-tungstenite 0.27.0", + "tracing", + "tracing-subscriber", + "uuid", +] + +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" +dependencies = [ + "getrandom 0.3.3", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-server" +version = "1.0.0" +dependencies = [ + "anyhow", + "axum", + "bcrypt", + "clap", + "futures-util", + "reqwest", + "serde", + "serde_json", + "tokio", + "tokio-tungstenite 0.27.0", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-registry" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.2", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags 2.9.1", +] + +[[package]] +name = "writeable" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" + +[[package]] +name = "yoke" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/submissions/tshare/Cargo.toml b/submissions/tshare/Cargo.toml new file mode 100644 index 00000000..3fbea0f1 --- /dev/null +++ b/submissions/tshare/Cargo.toml @@ -0,0 +1,53 @@ +[workspace] +members = ["src/tshare", "src/tunnel-server", "src/web-server"] +resolver = "2" + +[workspace.package] +version = "1.0.0" +authors = ["RobbyV2 &1 | sed 's/^/[tunnel-server]: /') & + TUNNEL_PID=$! + + # Give tunnel server time to start + sleep 2 + + (cargo run --bin web-server 2>&1 | sed 's/^/[web-server]: /') & + WEB_PID=$! + + # Function to cleanup processes on exit + cleanup() { + echo "" + echo "Shutting down servers..." + kill $TUNNEL_PID $WEB_PID 2>/dev/null || true + wait $TUNNEL_PID $WEB_PID 2>/dev/null || true + echo "All servers stopped." + } + + # Set up signal handlers + trap cleanup EXIT INT TERM + + # Wait for processes + wait $TUNNEL_PID $WEB_PID + +format: + cargo fmt --all + taplo fmt + @echo "Formatting HTML files..." + @if command -v djlint >/dev/null 2>&1; then \ + find . -name "*.html" -not -path "./target/*" -exec djlint --reformat {} \; ; \ + else \ + echo "No HTML formatter found. Install djlint (pip install djlint) for HTML formatting"; \ + fi + +format-check: + cargo fmt --all -- --check + taplo fmt --check + @echo "Checking HTML formatting..." + @if command -v djlint >/dev/null 2>&1; then \ + find . -name "*.html" -not -path "./target/*" -exec djlint --check {} \; ; \ + else \ + echo "No HTML formatter found. Install djlint (pip install djlint) for HTML formatting"; \ + fi + +lint: + cargo clippy --workspace --release --lib --bins --tests --examples --all-targets --all-features -- -D warnings + +fix: + cargo clippy --fix --allow-dirty --allow-staged --workspace --all-targets --all-features --release + +build: + cargo build --release --workspace --all-targets + +finalize: + just format + just lint + just build + +# Build all binaries in debug mode +build-debug: + cargo build --workspace + +# Check all code compiles +check: + cargo check --workspace + +# Run tests +test: + cargo test --workspace + +# Install all binaries +install: + cargo install --path . --bin tunnel-server + cargo install --path . --bin web-server + cargo install --path . --bin tshare + +# Clean build artifacts +clean: + cargo clean + +# View logs +logs: + #!/usr/bin/env bash + echo "=== TShare Logs ===" + echo "Logs are located in ~/.tshare/" + echo "" + if [ -f ~/.tshare/tunnel-server.log ]; then + echo "=== Tunnel Server Log ===" + tail -20 ~/.tshare/tunnel-server.log + echo "" + fi + if [ -f ~/.tshare/web-server.log ]; then + echo "=== Web Server Log ===" + tail -20 ~/.tshare/web-server.log + echo "" + fi + if [ -f ~/.tshare/tshare.log ]; then + echo "=== Client Log ===" + tail -20 ~/.tshare/tshare.log + echo "" + fi + +# Follow logs in real-time +logs-follow: + #!/usr/bin/env bash + echo "Following all TShare logs (Ctrl+C to stop)..." + echo "Logs are located in ~/.tshare/" + echo "" + if command -v multitail >/dev/null 2>&1; then + multitail -f ~/.tshare/tunnel-server.log -f ~/.tshare/web-server.log -f ~/.tshare/tshare.log + else + echo "Install multitail for better log following, falling back to tail..." + tail -f ~/.tshare/*.log 2>/dev/null || echo "No log files found" + fi + +# Clear all logs +logs-clear: + #!/usr/bin/env bash + echo "Clearing all TShare logs..." + rm -f ~/.tshare/*.log + echo "Logs cleared." + +# Build Debian package +build-deb: + #!/usr/bin/env bash + set -e + + echo "Building Debian package for TShare..." + + # Build release binaries + cargo build --release + + # Create package directory structure + PKG_NAME="tshare" + PKG_VERSION=$(grep '^version' Cargo.toml | head -1 | cut -d'"' -f2) + PKG_DIR="releases/${PKG_NAME}_${PKG_VERSION}_amd64" + + mkdir -p releases + rm -rf "$PKG_DIR" + mkdir -p "$PKG_DIR/DEBIAN" + mkdir -p "$PKG_DIR/usr/bin" + mkdir -p "$PKG_DIR/usr/share/doc/$PKG_NAME" + mkdir -p "$PKG_DIR/usr/share/man/man1" + + # Copy binaries + cp target/release/tshare "$PKG_DIR/usr/bin/" + cp target/release/tunnel-server "$PKG_DIR/usr/bin/" + cp target/release/web-server "$PKG_DIR/usr/bin/" + + # Make binaries executable + chmod +x "$PKG_DIR/usr/bin/"* + + # Copy documentation + cp README.md "$PKG_DIR/usr/share/doc/$PKG_NAME/" + cp LICENSE "$PKG_DIR/usr/share/doc/$PKG_NAME/" + + # Create control file + cat > "$PKG_DIR/DEBIAN/control" << EOF + Package: $PKG_NAME + Version: $PKG_VERSION + Section: utils + Priority: optional + Architecture: amd64 + Maintainer: RobbyV2 + Description: Terminal sharing made simple + TShare lets you share your terminal session with anyone through a simple web link. + Perfect for pair programming, debugging, teaching, or getting help. + . + This package includes: + - tshare: CLI client for sharing terminal sessions + - tunnel-server: WebSocket relay server for terminal data + - web-server: Web interface for viewing shared terminals + Homepage: https://github.com/RobbyV2/tshare + EOF + + # Create postinst script for systemd service (optional) + cat > "$PKG_DIR/DEBIAN/postinst" << 'EOF' + #!/bin/bash + set -e + + echo "TShare installed successfully!" + echo "" + echo "Quick start:" + echo "1. Start servers: tunnel-server & web-server &" + echo "2. Share terminal: tshare share" + echo "" + echo "For more information, see: https://github.com/RobbyV2/tshare" + + exit 0 + EOF + + chmod +x "$PKG_DIR/DEBIAN/postinst" + + # Build the .deb package + dpkg-deb --build "$PKG_DIR" + + echo "Debian package created: releases/${PKG_NAME}_${PKG_VERSION}_amd64.deb" + + # Verify the package + echo "Package info:" + dpkg --info "releases/${PKG_NAME}_${PKG_VERSION}_amd64.deb" \ No newline at end of file diff --git a/submissions/tshare/public/connect.html b/submissions/tshare/public/connect.html new file mode 100644 index 00000000..dc3ddf65 --- /dev/null +++ b/submissions/tshare/public/connect.html @@ -0,0 +1,285 @@ + + + + + + TShare - Terminal Session {{ session_id }} + + + + + + + + +
+
+
+
+

+ Terminal Session +

+

Session ID: {{ session_id }}

+
+
Guest
+ +
+
+
+
+
Disconnected
+
+
+
+
+
+
+
+ + + diff --git a/submissions/tshare/public/index.html b/submissions/tshare/public/index.html new file mode 100644 index 00000000..9bf3afb5 --- /dev/null +++ b/submissions/tshare/public/index.html @@ -0,0 +1,85 @@ + + + + + + TShare + + + +

TShare

+

Terminal sharing through web links.

+

Installation

+

+ Download pre-built packages from releases or build from source: +

+
cargo build --release
+# Binaries will be in target/release/
+

Usage

+

Start servers:

+
tunnel-server &
+web-server &
+

Share terminal:

+
tshare connect
+

Development

+

+ See justfile for available commands: +

+
just --list
+

Common commands:

+
just run            # Start both servers
+just client connect # Create session
+just build          # Build release
+just build-deb      # Build .deb package
+

Architecture

+ +

Configuration

+

+ All binaries accept --help for options. Default configuration works for local development. +

+

Production example:

+
tunnel-server --host 0.0.0.0
+web-server --host 0.0.0.0 --tunnel-url http://tunnel.example.com:8385
+tshare connect --tunnel-host tunnel.example.com --web-host web.example.com
+

+ Source: github.com/RobbyV2/tshare +

+ + diff --git a/submissions/tshare/src/tshare/Cargo.toml b/submissions/tshare/src/tshare/Cargo.toml new file mode 100644 index 00000000..0e2d9ecb --- /dev/null +++ b/submissions/tshare/src/tshare/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "tshare" +version.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true +homepage.workspace = true +description = "Terminal sharing client - share your terminal session through a web link" +keywords.workspace = true +categories.workspace = true +edition.workspace = true + +[[bin]] +name = "tshare" +path = "src/main.rs" + +[dependencies] +tokio = { workspace = true } +clap = { workspace = true } +portable-pty = { workspace = true } +tokio-tungstenite = { workspace = true } +futures-util = { workspace = true } +reqwest = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +anyhow = { workspace = true } +url = { workspace = true } +tracing = { workspace = true } +tracing-subscriber = { workspace = true } +crossterm = { workspace = true } +ctrlc = { workspace = true } diff --git a/submissions/tshare/src/tshare/src/main.rs b/submissions/tshare/src/tshare/src/main.rs new file mode 100644 index 00000000..b6381126 --- /dev/null +++ b/submissions/tshare/src/tshare/src/main.rs @@ -0,0 +1,440 @@ +use anyhow::{Result, ensure}; +use clap::{Parser, Subcommand}; +use crossterm::terminal::{disable_raw_mode, enable_raw_mode}; +use futures_util::{SinkExt, StreamExt}; +use portable_pty::{CommandBuilder, PtySize}; +use reqwest::Client; +use serde::{Deserialize, Serialize}; +use std::io::{Read, Write}; +use tokio::select; +use tokio::sync::mpsc; +use tokio_tungstenite::{connect_async, tungstenite::Message}; +use tracing::{error, info}; + +#[derive(Parser, Debug)] +#[command(author, version, about = "Share your terminal session via a web link.")] +struct Args { + #[command(subcommand)] + command: Commands, +} + +#[derive(Subcommand, Debug)] +enum Commands { + /// Connect and start a new terminal sharing session. + Connect { + /// Tunnel server host + #[arg(long, default_value = "127.0.0.1")] + tunnel_host: String, + + /// Tunnel server port + #[arg(long, default_value_t = 8385)] + tunnel_port: u16, + + /// Web server host + #[arg(long, default_value = "127.0.0.1")] + web_host: String, + + /// Web server port + #[arg(long, default_value_t = 8386)] + web_port: u16, + + /// Set a password required for the session owner to connect via the web. + /// Owners always have full read/write access to the terminal. + #[arg(long)] + owner_pass: Option, + + /// Set a password for guests to connect via the web. + /// Guests follow the readonly setting below. + #[arg(long)] + guest_pass: Option, + + /// Make guest sessions read-only (no input from web is forwarded). + /// If true, guests can only view terminal output. If false, guests can interact. + /// Owners always have full access regardless of this setting. + #[arg(long, default_value_t = false)] + guest_readonly: bool, + }, +} + +#[derive(Serialize)] +struct CreateSessionRequest { + owner_password: Option, + guest_password: Option, + is_guest_readonly: bool, +} + +#[derive(Deserialize)] +struct CreateSessionResponse { + session_id: String, +} + +#[derive(Debug)] +struct ConnectConfig { + tunnel_host: String, + tunnel_port: u16, + web_host: String, + web_port: u16, + owner_pass: Option, + guest_pass: Option, + guest_readonly: bool, +} + +#[tokio::main] +async fn main() -> Result<()> { + // Create ~/.tshare directory if it doesn't exist + let home_dir = std::env::var("HOME").unwrap_or_else(|_| ".".to_string()); + let tshare_dir = format!("{home_dir}/.tshare"); + std::fs::create_dir_all(&tshare_dir)?; + + // Clear previous log file + let log_path = format!("{tshare_dir}/tshare.log"); + let _ = std::fs::remove_file(&log_path); + + // Configure file-based logging with no visible output + let log_file = std::fs::OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open(&log_path)?; + + tracing_subscriber::fmt() + .with_env_filter( + tracing_subscriber::EnvFilter::try_from_default_env() + .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")), + ) + .with_writer(log_file) + .with_ansi(false) + .init(); + + let args = Args::parse(); + + info!("Starting tshare client"); + + match args.command { + Commands::Connect { + tunnel_host, + tunnel_port, + web_host, + web_port, + owner_pass, + guest_pass, + guest_readonly, + } => { + let config = ConnectConfig { + tunnel_host, + tunnel_port, + web_host, + web_port, + owner_pass, + guest_pass, + guest_readonly, + }; + run_connect(config).await + } + } +} + +async fn run_connect(config: ConnectConfig) -> Result<()> { + let api_url = format!( + "http://{}:{}/api/session", + config.tunnel_host, config.tunnel_port + ); + let web_addr = format!("http://{}:{}", config.web_host, config.web_port); + + // Validate connection to tunnel server before creating session + info!( + "Validating connection to tunnel server at: {}:{}", + config.tunnel_host, config.tunnel_port + ); + let client = Client::new(); + let test_url = format!( + "http://{}:{}/api/session/connection-test", + config.tunnel_host, config.tunnel_port + ); + + match client.get(&test_url).send().await { + Ok(response) => { + // We expect 404 for a non-existent session, which means the server is running + if response.status() == 404 || response.status().is_success() { + info!("Successfully validated tunnel server connection"); + } else { + error!( + "Tunnel server returned unexpected status: {}", + response.status() + ); + panic!( + "Cannot start client: tunnel server is not responding properly at {}:{}", + config.tunnel_host, config.tunnel_port + ); + } + } + Err(e) => { + error!("Failed to connect to tunnel server: {}", e); + panic!( + "Cannot start client: tunnel server is unreachable at {}:{}", + config.tunnel_host, config.tunnel_port + ); + } + } + + info!("Registering session with tunnel server at: {}", api_url); + + // Register session with tunnel server + let create_request = CreateSessionRequest { + owner_password: config.owner_pass.clone(), + guest_password: config.guest_pass.clone(), + is_guest_readonly: config.guest_readonly, + }; + + let response = client.post(&api_url).json(&create_request).send().await?; + + ensure!( + response.status().is_success(), + "Failed to create session: {}", + response.status() + ); + + let create_response: CreateSessionResponse = response.json().await?; + let session_id = create_response.session_id; + + info!("Session created successfully: {}", session_id); + + // Display shareable link + println!("=== TShare Session Created ==="); + println!("Session ID: {session_id}"); + println!("Share this link: {web_addr}/session/{session_id}"); + println!("=============================="); + println!("Starting shared terminal session..."); + println!("This terminal will be shared with viewers."); + println!("Either exit the terminal or end the process to end the session."); + println!("=============================="); + println!(); + + // Spawn PTY with user's shell + let pty_system = portable_pty::native_pty_system(); + let pty_pair = pty_system.openpty(PtySize { + rows: 30, + cols: 120, + pixel_width: 0, + pixel_height: 0, + })?; + + let mut cmd = CommandBuilder::new(get_user_shell()); + cmd.env("TERM", "xterm-256color"); + cmd.env("COLORTERM", "truecolor"); + let mut child = pty_pair.slave.spawn_command(cmd)?; + let master = pty_pair.master; + + // Connect to tunnel server WebSocket + let ws_url = format!( + "ws://{}:{}/ws/pty/{}", + config.tunnel_host, config.tunnel_port, session_id + ); + info!("Connecting to tunnel server WebSocket: {}", ws_url); + + let (ws_stream, _) = connect_async(&ws_url).await?; + info!("Connected to tunnel server WebSocket successfully"); + let (mut ws_sink, mut ws_stream) = ws_stream.split(); + + // Create channels for communication + let (pty_output_tx, mut pty_output_rx) = mpsc::channel::>(1024); + let (ws_input_tx, mut ws_input_rx) = mpsc::channel::>(1024); + + // Task to read from PTY and send to both WebSocket and stdout + let master_reader = master.try_clone_reader().unwrap(); + let pty_output_tx_clone = pty_output_tx.clone(); + let pty_reader_task = tokio::task::spawn_blocking(move || { + let mut reader = master_reader; + let mut buffer = [0u8; 1024]; + loop { + match reader.read(&mut buffer) { + Ok(n) if n > 0 => { + let data = buffer[..n].to_vec(); + + // Send to WebSocket + if pty_output_tx_clone.blocking_send(data.clone()).is_err() { + break; + } + + // Also write to stdout so user can see what's happening + if std::io::stdout().write_all(&data).is_err() { + break; + } + let _ = std::io::stdout().flush(); + } + Ok(_) => break, // EOF + Err(_) => break, + } + } + }); + + // Create a unified channel for all input to PTY (stdin + web) + let (pty_input_tx, mut pty_input_rx) = mpsc::channel::>(1024); + let pty_input_tx_stdin = pty_input_tx.clone(); + let pty_input_tx_web = pty_input_tx; + + // Enable raw mode for real-time character input + enable_raw_mode().unwrap_or_else(|e| { + eprintln!("Failed to enable raw mode: {e}"); + }); + + // Set up signal handler to disable raw mode on Ctrl+C + ctrlc::set_handler(move || { + let _ = disable_raw_mode(); + std::process::exit(0); + }) + .unwrap_or_else(|e| { + eprintln!("Failed to set signal handler: {e}"); + }); + + // Task to read raw bytes from stdin and send to PTY input channel + let stdin_reader_task = tokio::task::spawn_blocking(move || { + let mut stdin = std::io::stdin(); + let mut buffer = [0u8; 1]; + + while stdin.read_exact(&mut buffer).is_ok() { + // Send each byte as-is, no interpretation + let data = vec![buffer[0]]; + if pty_input_tx_stdin.blocking_send(data).is_err() { + break; + } + } + }); + + // Task to write WebSocket input to PTY input channel + let ws_input_writer_task = tokio::spawn(async move { + while let Some(data) = ws_input_rx.recv().await { + info!("Received {} bytes from web, forwarding to PTY", data.len()); + if pty_input_tx_web.send(data).await.is_err() { + break; + } + } + }); + + // Task to handle all PTY input (from both stdin and web) + let mut master_writer = master.take_writer().unwrap(); + let pty_input_task = tokio::task::spawn_blocking(move || { + while let Some(data) = pty_input_rx.blocking_recv() { + if master_writer.write_all(&data).is_err() { + break; + } + } + }); + + // Create resize channel + let (resize_tx, mut resize_rx) = mpsc::channel::<(u16, u16)>(1); + + // Task to read from WebSocket + let ws_reader_task = tokio::spawn(async move { + while let Some(msg) = ws_stream.next().await { + match msg { + Ok(Message::Binary(data)) => { + // Check if this is a resize command + if let Ok(data_str) = std::str::from_utf8(&data) { + if data_str.starts_with("RESIZE:") { + if let Some(size_part) = data_str.strip_prefix("RESIZE:") { + let parts: Vec<&str> = size_part.split(':').collect(); + if parts.len() == 2 { + if let (Ok(cols), Ok(rows)) = + (parts[0].parse::(), parts[1].parse::()) + { + if resize_tx.send((cols, rows)).await.is_err() { + error!("Failed to send resize command"); + } + continue; + } + } + } + } + } + + // Regular data for PTY input + if ws_input_tx.send(data.to_vec()).await.is_err() { + break; + } + } + Ok(Message::Close(_)) => break, + Err(_) => break, + _ => {} // Ignore other message types + } + } + }); + + // Task to check child process + let mut child_task = tokio::task::spawn_blocking(move || child.wait()); + + let session_id_for_logging = session_id.clone(); + + // Main coordination loop + loop { + select! { + // PTY output -> WebSocket + Some(data) = pty_output_rx.recv() => { + if ws_sink.send(Message::Binary(data.into())).await.is_err() { + break; + } + } + + // Handle resize commands + Some((cols, rows)) = resize_rx.recv() => { + let new_size = portable_pty::PtySize { + rows, + cols, + pixel_width: 0, + pixel_height: 0, + }; + if let Err(e) = master.resize(new_size) { + error!("Failed to resize PTY: {}", e); + } else { + info!("Resized PTY to {}x{}", cols, rows); + } + } + + // Check if child process has exited + child_result = &mut child_task => { + match child_result { + Ok(_) => { + println!("\nTerminal session ended."); + info!("Terminal session {} ended normally", session_id_for_logging); + } + Err(e) => { + println!("\nTerminal session error: {e}"); + info!("Terminal session {} ended with error: {}", session_id_for_logging, e); + } + } + break; + } + + else => break, + } + } + + // Cleanup + pty_reader_task.abort(); + stdin_reader_task.abort(); + ws_input_writer_task.abort(); + pty_input_task.abort(); + ws_reader_task.abort(); + + // Send close frame with code 1000 (normal closure) + let close_frame = Message::Close(Some(tokio_tungstenite::tungstenite::protocol::CloseFrame { + code: tokio_tungstenite::tungstenite::protocol::frame::coding::CloseCode::Normal, + reason: "Terminal session ended".into(), + })); + let _ = ws_sink.send(close_frame).await; + let _ = ws_sink.close().await; + + // Disable raw mode and clean up terminal state before exiting + let _ = disable_raw_mode(); + + // Reset cursor and clear any remaining output + print!("\r\n"); + let _ = std::io::stdout().flush(); + + // Force exit since stdin task might still be blocking + std::process::exit(0); +} + +fn get_user_shell() -> String { + std::env::var("SHELL").unwrap_or_else(|_| "/bin/bash".to_string()) +} diff --git a/submissions/tshare/src/tunnel-server/Cargo.toml b/submissions/tshare/src/tunnel-server/Cargo.toml new file mode 100644 index 00000000..4b574d03 --- /dev/null +++ b/submissions/tshare/src/tunnel-server/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "tunnel-server" +version.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true +homepage.workspace = true +description = "TShare tunnel server - handles WebSocket connections and session management" +keywords.workspace = true +categories.workspace = true +edition.workspace = true + +[[bin]] +name = "tunnel-server" +path = "src/main.rs" + +[dependencies] +tokio = { workspace = true } +axum = { workspace = true } +futures-util = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +anyhow = { workspace = true } +tracing = { workspace = true } +tracing-subscriber = { workspace = true } +uuid = { workspace = true } +dashmap = { workspace = true } +bcrypt = { workspace = true } +tokio-tungstenite = { workspace = true } +clap = { workspace = true } diff --git a/submissions/tshare/src/tunnel-server/src/main.rs b/submissions/tshare/src/tunnel-server/src/main.rs new file mode 100644 index 00000000..fdf833a7 --- /dev/null +++ b/submissions/tshare/src/tunnel-server/src/main.rs @@ -0,0 +1,546 @@ +use anyhow::Result; +use axum::extract::ws::{Message, WebSocket}; +use axum::extract::{Path, Query, State, WebSocketUpgrade}; +use axum::http::StatusCode; +use axum::response::{IntoResponse, Response}; +use axum::routing::{get, post}; +use axum::{Json, Router}; +use clap::Parser; +use dashmap::DashMap; +use futures_util::{SinkExt, StreamExt}; +use serde::{Deserialize, Serialize}; +use std::sync::Arc; +use tokio::sync::{RwLock, broadcast, mpsc}; +use tracing::{error, info, warn}; +use uuid::Uuid; + +type SessionMap = Arc>; + +#[derive(Clone)] +struct SessionState { + session_id: String, + owner_password_hash: Option, + guest_password_hash: Option, + is_guest_readonly: bool, + // Broadcast channel for PTY data going to web clients + pty_broadcast: broadcast::Sender>, + // Sender for web data going to PTY client + web_to_pty_tx: Arc>>>>, + // Terminal history buffer + history: Arc>>, +} + +#[derive(Clone)] +struct AppState { + sessions: SessionMap, +} + +#[derive(Deserialize)] +struct CreateSessionRequest { + owner_password: Option, + guest_password: Option, + is_guest_readonly: bool, +} + +#[derive(Serialize)] +struct CreateSessionResponse { + session_id: String, +} + +#[derive(Serialize)] +struct SessionDetails { + session_id: String, + owner_password_hash: Option, + guest_password_hash: Option, + is_guest_readonly: bool, +} + +#[derive(Deserialize)] +struct WebSocketQuery { + user_type: Option, +} + +#[derive(Parser, Debug)] +#[command( + author, + version, + about = "TShare tunnel server - handles PTY data streams" +)] +struct Args { + /// Host to bind the server to + #[arg(long, default_value = "127.0.0.1")] + host: String, + + /// Port for the server + #[arg(long, default_value_t = 8385)] + port: u16, +} + +#[tokio::main] +async fn main() -> Result<()> { + // Create ~/.tshare directory if it doesn't exist + let home_dir = std::env::var("HOME").unwrap_or_else(|_| ".".to_string()); + let tshare_dir = format!("{home_dir}/.tshare"); + std::fs::create_dir_all(&tshare_dir)?; + + // Clear previous log file + let log_path = format!("{tshare_dir}/tunnel-server.log"); + let _ = std::fs::remove_file(&log_path); + + // Configure logging to both console and file + let log_file = std::fs::OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open(&log_path)?; + + use tracing_subscriber::fmt::writer::MakeWriterExt; + let writer = std::io::stdout.and(log_file); + + tracing_subscriber::fmt() + .with_env_filter( + tracing_subscriber::EnvFilter::try_from_default_env() + .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")), + ) + .with_writer(writer) + .init(); + + let args = Args::parse(); + + let sessions: SessionMap = Arc::new(DashMap::new()); + let app_state = AppState { sessions }; + + // Combined server with both API and WebSocket routes + let app = Router::new() + .route("/api/session", post(create_session)) + .route("/api/session/{id}", get(get_session)) + .route("/ws/pty/{id}", get(handle_pty_ws)) + .route("/ws/web/{id}", get(handle_web_ws)) + .with_state(app_state); + + let addr = format!("{}:{}", args.host, args.port); + let listener = tokio::net::TcpListener::bind(&addr).await?; + + info!("Tunnel server starting"); + info!("Server listening on {} (API and WebSocket)", addr); + + if let Err(e) = axum::serve(listener, app).await { + error!("Server error: {:?}", e); + } + + Ok(()) +} + +async fn create_session( + State(state): State, + Json(request): Json, +) -> Result, StatusCode> { + let session_id = Uuid::new_v4().to_string(); + + info!("Creating new session: {}", session_id); + info!( + "Session config - readonly: {}, has_owner_pass: {}, has_guest_pass: {}", + request.is_guest_readonly, + request.owner_password.is_some(), + request.guest_password.is_some() + ); + + let owner_password_hash = match request.owner_password { + Some(password) => { + let hash = bcrypt::hash(password, bcrypt::DEFAULT_COST).map_err(|e| { + error!( + "Failed to hash owner password for session {}: {}", + session_id, e + ); + StatusCode::INTERNAL_SERVER_ERROR + })?; + Some(hash) + } + None => None, + }; + + let guest_password_hash = match request.guest_password { + Some(password) => { + let hash = bcrypt::hash(password, bcrypt::DEFAULT_COST).map_err(|e| { + error!( + "Failed to hash guest password for session {}: {}", + session_id, e + ); + StatusCode::INTERNAL_SERVER_ERROR + })?; + Some(hash) + } + None => None, + }; + + let (pty_broadcast, _) = broadcast::channel(1024); + let web_to_pty_tx = Arc::new(RwLock::new(None)); + let history = Arc::new(RwLock::new(Vec::new())); + + let session = SessionState { + session_id: session_id.clone(), + owner_password_hash, + guest_password_hash, + is_guest_readonly: request.is_guest_readonly, + pty_broadcast, + web_to_pty_tx, + history, + }; + + state.sessions.insert(session_id.clone(), session); + + info!( + "Successfully created session: {} (total sessions: {})", + session_id, + state.sessions.len() + ); + + Ok(Json(CreateSessionResponse { session_id })) +} + +async fn get_session( + State(state): State, + Path(session_id): Path, +) -> Result, StatusCode> { + info!("Looking up session: {}", session_id); + + let session = state.sessions.get(&session_id).ok_or_else(|| { + warn!("Session not found: {}", session_id); + StatusCode::NOT_FOUND + })?; + + let details = SessionDetails { + session_id: session.session_id.clone(), + owner_password_hash: session.owner_password_hash.clone(), + guest_password_hash: session.guest_password_hash.clone(), + is_guest_readonly: session.is_guest_readonly, + }; + + info!("Retrieved session details for: {}", session_id); + Ok(Json(details)) +} + +async fn handle_pty_ws( + State(state): State, + Path(session_id): Path, + ws: WebSocketUpgrade, +) -> Response { + let session = match state.sessions.get(&session_id) { + Some(session_ref) => session_ref.clone(), + None => return StatusCode::NOT_FOUND.into_response(), + }; + + ws.on_upgrade(move |socket| handle_pty_websocket(socket, session)) +} + +async fn handle_web_ws( + State(state): State, + Path(session_id): Path, + Query(query): Query, + ws: WebSocketUpgrade, +) -> Response { + let session = match state.sessions.get(&session_id) { + Some(session_ref) => session_ref.clone(), + None => return StatusCode::NOT_FOUND.into_response(), + }; + + let user_type = query.user_type.unwrap_or_else(|| "guest".to_string()); + ws.on_upgrade(move |socket| handle_web_websocket(socket, session, user_type)) +} + +async fn handle_pty_websocket(socket: WebSocket, session: SessionState) { + let (mut ws_sender, mut ws_receiver) = socket.split(); + let session_id = session.session_id.clone(); + + info!("PTY WebSocket connected for session: {}", session_id); + + // Create channel for receiving web-to-pty data + let (web_to_pty_tx, mut web_to_pty_rx) = mpsc::channel(1024); + + // Store the sender in the session + { + let mut tx_guard = session.web_to_pty_tx.write().await; + *tx_guard = Some(web_to_pty_tx); + info!("Stored web-to-pty channel for session: {}", session_id); + } + + // Task to forward PTY data to web clients via broadcast + let pty_broadcast = session.pty_broadcast.clone(); + let history = session.history.clone(); + let session_id_clone = session_id.clone(); + let forward_pty_to_web = tokio::spawn(async move { + info!( + "Starting PTY-to-web forwarding for session: {}", + session_id_clone + ); + let mut session_ended_sent = false; + while let Some(msg) = ws_receiver.next().await { + match msg { + Ok(Message::Binary(data)) => { + // Store in history + { + let mut history_guard = history.write().await; + history_guard.extend_from_slice(&data); + } + + let subscribers = pty_broadcast.receiver_count(); + let result = pty_broadcast.send(data.to_vec()); + if result.is_ok() && subscribers > 0 { + info!( + "Forwarded {} bytes to {} web clients for session: {}", + data.len(), + subscribers, + session_id_clone + ); + } + } + Ok(Message::Close(_)) => { + info!( + "PTY WebSocket close message received for session: {}", + session_id_clone + ); + if !session_ended_sent { + let _ = pty_broadcast.send(b"__TSHARE_SESSION_ENDED__".to_vec()); + session_ended_sent = true; + } + break; + } + Err(e) => { + warn!( + "PTY WebSocket error for session {}: {}", + session_id_clone, e + ); + if !session_ended_sent { + let _ = pty_broadcast.send(b"__TSHARE_SESSION_ENDED__".to_vec()); + session_ended_sent = true; + } + break; + } + _ => {} + } + } + info!( + "PTY-to-web forwarding ended for session: {}", + session_id_clone + ); + // Send session end notification only if not already sent + if !session_ended_sent { + let _ = pty_broadcast.send(b"__TSHARE_SESSION_ENDED__".to_vec()); + } + }); + + // Task to forward web data to PTY + let session_id_clone = session_id.clone(); + let forward_web_to_pty = tokio::spawn(async move { + info!( + "Starting web-to-PTY forwarding for session: {}", + session_id_clone + ); + while let Some(data) = web_to_pty_rx.recv().await { + info!( + "Forwarding {} bytes from web to PTY for session: {}", + data.len(), + session_id_clone + ); + if ws_sender.send(Message::Binary(data.into())).await.is_err() { + warn!( + "Failed to send data to PTY for session: {}", + session_id_clone + ); + break; + } + } + info!( + "Web-to-PTY forwarding ended for session: {}", + session_id_clone + ); + }); + + tokio::select! { + _ = forward_pty_to_web => {}, + _ = forward_web_to_pty => {}, + } + + // Clear the sender when PTY disconnects + { + let mut tx_guard = session.web_to_pty_tx.write().await; + *tx_guard = None; + info!("Cleared web-to-pty channel for session: {}", session_id); + } + + info!("PTY WebSocket disconnected for session: {}", session_id); +} + +async fn handle_web_websocket(socket: WebSocket, session: SessionState, user_type: String) { + let (mut ws_sender, mut ws_receiver) = socket.split(); + let session_id = session.session_id.clone(); + + // Determine readonly status based on user type and session settings + // Authentication logic: + // - If no passwords set: auto-authenticate as owner (full access) + // - If owner_pass provided and matches: authenticate as owner (full access) + // - If guest_pass provided and matches: authenticate as guest (follows is_guest_readonly setting) + // - Owners always have read/write access regardless of is_guest_readonly + // - Guests have read/write access only if is_guest_readonly is false + let is_readonly = match user_type.as_str() { + "owner" => false, // Owners always have write access + "guest" => session.is_guest_readonly, // Guests follow session readonly setting + _ => true, // Default to readonly for unknown user types + }; + + info!( + "Web WebSocket connected for session: {} (user_type: {}, readonly: {})", + session_id, user_type, is_readonly + ); + + // Send history to new web client first + let history_data = { + let history_guard = session.history.read().await; + history_guard.clone() + }; + + if !history_data.is_empty() { + info!( + "Sending {} bytes of history to new web client for session: {}", + history_data.len(), + session_id + ); + if ws_sender + .send(Message::Binary(history_data.into())) + .await + .is_err() + { + warn!( + "Failed to send history to web client for session: {}", + session_id + ); + return; + } + } + + // Subscribe to PTY broadcast + let mut pty_broadcast_rx = session.pty_broadcast.subscribe(); + let web_to_pty_tx_ref = session.web_to_pty_tx.clone(); + + // Task to forward PTY data to web client + let session_id_clone = session_id.clone(); + let forward_pty_to_web = tokio::spawn(async move { + info!( + "Starting PTY-to-web forwarding for web client of session: {}", + session_id_clone + ); + while let Ok(data) = pty_broadcast_rx.recv().await { + info!( + "Sending {} bytes to web client for session: {}", + data.len(), + session_id_clone + ); + if ws_sender.send(Message::Binary(data.into())).await.is_err() { + warn!( + "Failed to send data to web client for session: {}", + session_id_clone + ); + break; + } + } + info!( + "PTY-to-web forwarding ended for web client of session: {}", + session_id_clone + ); + }); + + // Task to forward web data to PTY (if not readonly) + let session_id_clone = session_id.clone(); + let forward_web_to_pty = tokio::spawn(async move { + info!( + "Starting web-to-PTY forwarding for session: {} (readonly: {})", + session_id_clone, is_readonly + ); + while let Some(msg) = ws_receiver.next().await { + match msg { + Ok(Message::Binary(data)) => { + if !is_readonly { + info!( + "Received {} bytes from web client for session: {}", + data.len(), + session_id_clone + ); + let tx_guard = web_to_pty_tx_ref.read().await; + if let Some(web_to_pty_tx) = tx_guard.as_ref() { + if web_to_pty_tx.send(data.to_vec()).await.is_err() { + warn!( + "Failed to forward web data to PTY for session: {}", + session_id_clone + ); + break; + } + } else { + warn!( + "No PTY connection available for session: {}", + session_id_clone + ); + } + } else { + info!( + "Ignoring input from web client (readonly mode) for session: {}", + session_id_clone + ); + } + } + Ok(Message::Text(text)) => { + // Handle control messages like resize + if let Ok(msg) = serde_json::from_str::(&text) { + if msg.get("type").and_then(|v| v.as_str()) == Some("resize") { + if let (Some(cols), Some(rows)) = ( + msg.get("cols").and_then(|v| v.as_u64()), + msg.get("rows").and_then(|v| v.as_u64()), + ) { + info!( + "Received resize request: {}x{} for session: {}", + cols, rows, session_id_clone + ); + // Forward resize message to PTY client + let tx_guard = web_to_pty_tx_ref.read().await; + if let Some(web_to_pty_tx) = tx_guard.as_ref() { + let resize_msg = format!("RESIZE:{cols}:{rows}"); + if web_to_pty_tx.send(resize_msg.into_bytes()).await.is_err() { + warn!( + "Failed to send resize command to PTY for session: {}", + session_id_clone + ); + } + } + } + } + } + } + Ok(Message::Close(_)) => { + info!( + "Web WebSocket close message received for session: {}", + session_id_clone + ); + break; + } + Err(e) => { + warn!( + "Web WebSocket error for session {}: {}", + session_id_clone, e + ); + break; + } + _ => {} + } + } + info!( + "Web-to-PTY forwarding ended for session: {}", + session_id_clone + ); + }); + + tokio::select! { + _ = forward_pty_to_web => {}, + _ = forward_web_to_pty => {}, + } + + info!("Web WebSocket disconnected for session: {}", session_id); +} diff --git a/submissions/tshare/src/web-server/Cargo.toml b/submissions/tshare/src/web-server/Cargo.toml new file mode 100644 index 00000000..b107f7f3 --- /dev/null +++ b/submissions/tshare/src/web-server/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "web-server" +version.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true +homepage.workspace = true +description = "TShare web server - serves the web interface for viewing terminal sessions" +keywords.workspace = true +categories.workspace = true +edition.workspace = true + +[[bin]] +name = "web-server" +path = "src/main.rs" + +[dependencies] +axum = { workspace = true } +tokio = { workspace = true } +tokio-tungstenite = { workspace = true } +futures-util = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +anyhow = { workspace = true } +tracing = { workspace = true } +tracing-subscriber = { workspace = true } +bcrypt = { workspace = true } +reqwest = { workspace = true } +clap = { workspace = true } diff --git a/submissions/tshare/src/web-server/src/main.rs b/submissions/tshare/src/web-server/src/main.rs new file mode 100644 index 00000000..31bbc8e2 --- /dev/null +++ b/submissions/tshare/src/web-server/src/main.rs @@ -0,0 +1,440 @@ +use anyhow::Result; +use axum::extract::ws::{Message, WebSocket, WebSocketUpgrade}; +use axum::extract::{Path, Query, State}; +use axum::http::StatusCode; +use axum::response::{Html, Response}; +use axum::routing::get; +use axum::{Json, Router}; +use clap::Parser; +use futures_util::{SinkExt, StreamExt}; +use serde::{Deserialize, Serialize}; + +use tokio_tungstenite::{connect_async, tungstenite::Message as TungsteniteMessage}; +use tracing::{error, info}; + +#[derive(Clone)] +struct AppState { + tunnel_base: String, +} + +#[derive(Serialize, Deserialize, Clone)] +struct SessionDetails { + session_id: String, + owner_password_hash: Option, + guest_password_hash: Option, + is_guest_readonly: bool, +} + +#[derive(Deserialize)] +struct AuthQuery { + password: Option, +} + +#[derive(Deserialize)] +struct WebSocketQuery { + user_type: Option, +} + +#[derive(Parser, Debug)] +#[command( + author, + version, + about = "TShare web server - serves terminal sessions via web interface" +)] +struct Args { + /// Host to bind the web server to + #[arg(long, default_value = "127.0.0.1")] + host: String, + + /// Port for the web server + #[arg(long, default_value_t = 8386)] + port: u16, + + /// Tunnel server base URL + #[arg(long, default_value = "http://127.0.0.1:8385")] + tunnel_url: String, +} + +#[tokio::main] +async fn main() -> Result<()> { + // Create ~/.tshare directory if it doesn't exist + let home_dir = std::env::var("HOME").unwrap_or_else(|_| ".".to_string()); + let tshare_dir = format!("{home_dir}/.tshare"); + std::fs::create_dir_all(&tshare_dir)?; + + // Clear previous log file + let log_path = format!("{tshare_dir}/web-server.log"); + let _ = std::fs::remove_file(&log_path); + + // Configure logging to both console and file + let log_file = std::fs::OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open(&log_path)?; + + use tracing_subscriber::fmt::writer::MakeWriterExt; + let writer = std::io::stdout.and(log_file); + + tracing_subscriber::fmt() + .with_env_filter( + tracing_subscriber::EnvFilter::try_from_default_env() + .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")), + ) + .with_writer(writer) + .init(); + + let args = Args::parse(); + + // Validate connection to tunnel server before starting + info!( + "Validating connection to tunnel server at: {}", + args.tunnel_url + ); + let client = reqwest::Client::new(); + let test_url = format!("{}/api/session/connection-test", args.tunnel_url); + + match client.get(&test_url).send().await { + Ok(response) => { + // We expect 404 for a non-existent session, which means the server is running + if response.status() == 404 || response.status().is_success() { + info!("Successfully validated tunnel server connection"); + } else { + error!( + "Tunnel server returned unexpected status: {}", + response.status() + ); + panic!( + "Cannot start web server: tunnel server is not responding properly at {}", + args.tunnel_url + ); + } + } + Err(e) => { + error!("Failed to connect to tunnel server: {}", e); + panic!( + "Cannot start web server: tunnel server is unreachable at {}", + args.tunnel_url + ); + } + } + + let app_state = AppState { + tunnel_base: args.tunnel_url, + }; + + let app = Router::new() + .route("/", get(home_page)) + .route("/session/{id}", get(session_page)) + .route("/ws/session/{id}", get(handle_session_ws)) + .route("/api/session/{id}", get(get_session_details)) + .route("/api/auth/{id}", get(authenticate_session)) + .with_state(app_state); + + let addr = format!("{}:{}", args.host, args.port); + let listener = tokio::net::TcpListener::bind(&addr).await?; + + info!("Web server starting on http://{}", addr); + + axum::serve(listener, app).await?; + + Ok(()) +} + +async fn home_page() -> Result, StatusCode> { + // Read index page from file - try multiple possible locations + let possible_paths = [ + "public/index.html", + "../public/index.html", + "../../public/index.html", + "/home/pi/Documents/tshare/public/index.html", + ]; + + let mut content = None; + let mut last_error = None; + + for path in possible_paths { + match std::fs::read_to_string(path) { + Ok(html) => { + content = Some(html); + break; + } + Err(e) => { + last_error = Some((path, e)); + } + } + } + + let html = content.ok_or_else(|| { + if let Some((path, e)) = last_error { + error!( + "Failed to read index.html file (tried multiple paths, last was {}): {}", + path, e + ); + } + StatusCode::INTERNAL_SERVER_ERROR + })?; + + Ok(Html(html)) +} + +async fn session_page( + Path(session_id): Path, + State(state): State, +) -> Result, StatusCode> { + // Check if session exists + let client = reqwest::Client::new(); + let url = format!("{}/api/session/{}", state.tunnel_base, session_id); + + let response = client + .get(&url) + .send() + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + + if !response.status().is_success() { + return Err(StatusCode::NOT_FOUND); + } + + let details: SessionDetails = response + .json() + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + + let needs_auth = details.owner_password_hash.is_some() || details.guest_password_hash.is_some(); + + // Read template from file - try multiple possible locations + let possible_paths = [ + "public/connect.html", + "../public/connect.html", + "../../public/connect.html", + "/home/pi/Documents/tshare/public/connect.html", + ]; + + let mut template = None; + let mut last_error = None; + + for path in possible_paths { + match std::fs::read_to_string(path) { + Ok(content) => { + template = Some(content); + break; + } + Err(e) => { + last_error = Some((path, e)); + } + } + } + + let template = template.ok_or_else(|| { + if let Some((path, e)) = last_error { + error!( + "Failed to read template file (tried multiple paths, last was {}): {}", + path, e + ); + } + StatusCode::INTERNAL_SERVER_ERROR + })?; + + let html = template + .replace("{{ session_id }}", &session_id) + .replace( + "{{ modal_display }}", + if needs_auth { "" } else { "hidden" }, + ) + .replace( + "{{ terminal_display }}", + if needs_auth { "hidden" } else { "" }, + ) + .replace( + "{{ needs_auth_js }}", + if needs_auth { "true" } else { "false" }, + ); + + Ok(Html(html)) +} + +async fn get_session_details( + Path(session_id): Path, + State(state): State, +) -> Result, StatusCode> { + let client = reqwest::Client::new(); + let url = format!("{}/api/session/{}", state.tunnel_base, session_id); + + let response = client + .get(&url) + .send() + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + + if !response.status().is_success() { + return Err(StatusCode::NOT_FOUND); + } + + let details: SessionDetails = response + .json() + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + + Ok(Json(details)) +} + +async fn authenticate_session( + Path(session_id): Path, + Query(auth): Query, + State(state): State, +) -> Result, StatusCode> { + let client = reqwest::Client::new(); + let url = format!("{}/api/session/{}", state.tunnel_base, session_id); + + let response = client + .get(&url) + .send() + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + + if !response.status().is_success() { + return Err(StatusCode::NOT_FOUND); + } + + let details: SessionDetails = response + .json() + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + + let password = auth.password.unwrap_or_default(); + let mut authenticated = false; + let mut user_type = "guest"; + + // If no passwords are set, authenticate automatically as owner + if details.owner_password_hash.is_none() && details.guest_password_hash.is_none() { + authenticated = true; + user_type = "owner"; + } else { + // Check owner password first, then guest password + if let Some(owner_hash) = &details.owner_password_hash { + if bcrypt::verify(&password, owner_hash).unwrap_or(false) { + authenticated = true; + user_type = "owner"; + } + } + + if !authenticated { + if let Some(guest_hash) = &details.guest_password_hash { + if bcrypt::verify(&password, guest_hash).unwrap_or(false) { + authenticated = true; + user_type = "guest"; + } + } + } + } + + let is_readonly = match user_type { + "owner" => false, + "guest" => details.is_guest_readonly, + _ => true, + }; + + Ok(Json(serde_json::json!({ + "authenticated": authenticated, + "user_type": user_type, + "is_readonly": is_readonly + }))) +} + +async fn handle_session_ws( + State(state): State, + Path(session_id): Path, + Query(query): Query, + ws: WebSocketUpgrade, +) -> Response { + let user_type = query.user_type.unwrap_or_else(|| "guest".to_string()); + ws.on_upgrade(move |socket| proxy_to_tunnel_server(socket, session_id, state, user_type)) +} + +async fn proxy_to_tunnel_server( + client_socket: WebSocket, + session_id: String, + state: AppState, + user_type: String, +) { + let tunnel_url = format!( + "{}/ws/web/{}?user_type={}", + state.tunnel_base.replace("http", "ws"), + session_id, + user_type + ); + + info!("Attempting to connect to tunnel server: {}", tunnel_url); + + match connect_async(&tunnel_url).await { + Ok((tunnel_stream, _)) => { + let (mut tunnel_sink, mut tunnel_stream) = tunnel_stream.split(); + let (mut client_sink, mut client_stream) = client_socket.split(); + + info!("Proxying WebSocket for session: {}", session_id); + + let client_to_tunnel = tokio::spawn(async move { + while let Some(msg) = client_stream.next().await { + match msg { + Ok(Message::Binary(data)) => { + if tunnel_sink + .send(TungsteniteMessage::Binary(data)) + .await + .is_err() + { + break; + } + } + Ok(Message::Text(text)) => { + if tunnel_sink + .send(TungsteniteMessage::Text(text.to_string().into())) + .await + .is_err() + { + break; + } + } + Ok(Message::Close(_)) => break, + Err(_) => break, + _ => {} + } + } + }); + + let tunnel_to_client = tokio::spawn(async move { + while let Some(msg) = tunnel_stream.next().await { + match msg { + Ok(TungsteniteMessage::Binary(data)) => { + if client_sink.send(Message::Binary(data)).await.is_err() { + break; + } + } + Ok(TungsteniteMessage::Text(text)) => { + if client_sink + .send(Message::Text(text.to_string().into())) + .await + .is_err() + { + break; + } + } + Ok(TungsteniteMessage::Close(_)) => break, + Err(_) => break, + _ => {} + } + } + }); + + tokio::select! { + _ = client_to_tunnel => {}, + _ = tunnel_to_client => {}, + } + } + Err(e) => { + error!("Failed to connect to tunnel server: {}", e); + } + } + + info!("WebSocket proxy ended for session: {}", session_id); +}