From 8c4336709b96e282660221cbb9ece860aa247c11 Mon Sep 17 00:00:00 2001 From: Emi Simpson Date: Thu, 11 Aug 2022 20:00:13 -0400 Subject: [PATCH] Start repository: Currently has the ability to open, thumbnail, encrypt, and upload single images, but still needs better UX and output, as well as the ability to generate index files --- .gitignore | 1 + Cargo.lock | 870 ++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 29 ++ src/crypto.rs | 26 ++ src/main.rs | 190 ++++++++++ src/parse.rs | 17 + src/thumbnailing.rs | 25 ++ src/upload.rs | 20 + 8 files changed, 1178 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/crypto.rs create mode 100644 src/main.rs create mode 100644 src/parse.rs create mode 100644 src/thumbnailing.rs create mode 100644 src/upload.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..721c69f --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,870 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "adler32" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" + +[[package]] +name = "aead" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" +dependencies = [ + "generic-array", +] + +[[package]] +name = "aes" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", + "opaque-debug", +] + +[[package]] +name = "aes-gcm" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df5f85a83a7d8b0442b6aa7b504b8212c1733da07b98aae43d4bc21b2cb3cdf6" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] +name = "argh" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7e7e4aa7e40747e023c0761dafcb42333a9517575bbf1241747f68dd3177a62" +dependencies = [ + "argh_derive", + "argh_shared", +] + +[[package]] +name = "argh_derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f2bd7ff6ed6414f4e5521bd509bae46454bbd513801767ced3f21a751ab4bc" +dependencies = [ + "argh_shared", + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "argh_shared" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47253b98986dafc7a3e1cf3259194f1f47ac61abb57a57f46ec09e48d004ecda" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "aviary-cli" +version = "0.1.0" +dependencies = [ + "argh", + "argh_derive", + "base64", + "blurhash", + "image", + "iota-crypto", + "itertools", + "mime", + "ureq", + "webp", +] + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-modes" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cb03d1bed155d89dce0f845b7899b18a9a163e148fd004e1c28421a783e2d8e" +dependencies = [ + "block-padding", + "cipher", +] + +[[package]] +name = "block-padding" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" + +[[package]] +name = "blurhash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8671e4c8bf59f8784aa27fe4c8e152f2a45dfeb91a52d114e5d104a451494bb4" + +[[package]] +name = "bumpalo" +version = "3.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" + +[[package]] +name = "bytemuck" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5377c8865e74a160d21f29c2d40669f53286db6eab59b88540cbb12ffc8b835" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +dependencies = [ + "jobserver", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chunked_transfer" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e" + +[[package]] +name = "cipher" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +dependencies = [ + "generic-array", +] + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "cpufeatures" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "ctr" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" +dependencies = [ + "cipher", +] + +[[package]] +name = "deflate" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c86f7e25f518f4b81808a2cf1c50996a61f5c2eb394b2393bd87f2a4780a432f" +dependencies = [ + "adler32", +] + +[[package]] +name = "digest" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "either" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be" + +[[package]] +name = "flate2" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "form_urlencoded" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + +[[package]] +name = "generic-array" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "ghash" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] +name = "gif" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3edd93c6756b4dfaf2709eafcc345ba2636565295c198a9cfbf75fa5e3e00b06" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "image" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e30ca2ecf7666107ff827a8e481de6a132a9b687ed3bb20bb1c144a36c00964" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "gif", + "jpeg-decoder", + "num-rational", + "num-traits", + "png", + "scoped_threadpool", + "tiff", +] + +[[package]] +name = "iota-crypto" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "538b238fbd6fa732f526f6401656fa6a366598b9fe1ca35185e47f3978369707" +dependencies = [ + "aead", + "aes", + "aes-gcm", + "block-modes", + "digest", + "generic-array", + "getrandom", + "hmac", + "sha2", + "subtle", +] + +[[package]] +name = "itertools" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +dependencies = [ + "either", +] + +[[package]] +name = "jobserver" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" +dependencies = [ + "libc", +] + +[[package]] +name = "jpeg-decoder" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9478aa10f73e7528198d75109c8be5cd7d15fb530238040148d5f9a22d4c5b3b" + +[[package]] +name = "js-sys" +version = "0.3.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "258451ab10b34f8af53416d1fdab72c22e805f0c92a1136d59470ec0b11138b2" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64de3cc433455c14174d42e554d4027ee631c4d046d43e3ecc6efc4636cdc7a7" + +[[package]] +name = "libwebp-sys" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439fd1885aa28937e7edcd68d2e793cb4a22f8733460d2519fbafd2b215672bf" +dependencies = [ + "cc", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[package]] +name = "miniz_oxide" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" +dependencies = [ + "adler", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "png" +version = "0.17.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc38c0ad57efb786dd57b9864e5b18bae478c00c824dc55a38bbc9da95dde3ba" +dependencies = [ + "bitflags", + "crc32fast", + "deflate", + "miniz_oxide", +] + +[[package]] +name = "polyval" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "proc-macro2" +version = "1.0.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + +[[package]] +name = "rustls" +version = "0.20.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aab8ee6c7097ed6057f43c187a62418d0c05a4bd5f18b3571db50ee0f9ce033" +dependencies = [ + "log", + "ring", + "sct", + "webpki", +] + +[[package]] +name = "scoped_threadpool" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" + +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "sha2" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "syn" +version = "1.0.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tiff" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7259662e32d1e219321eb309d5f9d898b779769d81b76e762c07c8e5d38fcb65" +dependencies = [ + "flate2", + "jpeg-decoder", + "weezl", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "unicode-bidi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" + +[[package]] +name = "unicode-ident" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" + +[[package]] +name = "unicode-normalization" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" + +[[package]] +name = "universal-hash" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "ureq" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97acb4c28a254fd7a4aeec976c46a7fa404eac4d7c134b30c75144846d7cb8f" +dependencies = [ + "base64", + "chunked_transfer", + "log", + "once_cell", + "rustls", + "url", + "webpki", + "webpki-roots", +] + +[[package]] +name = "url" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", +] + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a" + +[[package]] +name = "web-sys" +version = "0.3.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed055ab27f941423197eb86b2035720b1a3ce40504df082cac2ecc6ed73335a1" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webp" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf022f821f166079a407d000ab57e84de020e66ffbbf4edde999bc7d6e371cae" +dependencies = [ + "image", + "libwebp-sys", +] + +[[package]] +name = "webpki" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1c760f0d366a6c24a02ed7816e23e691f5d92291f94d15e836006fd11b04daf" +dependencies = [ + "webpki", +] + +[[package]] +name = "weezl" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb" + +[[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" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..ae8e562 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "aviary-cli" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +argh = "0.1.8" +argh_derive = "0.1.8" +webp = "0.2.2" +itertools = "0.10.3" +blurhash = "0.1.1" +base64 = "0.13.0" +mime = "0.3.16" + +[dependencies.iota-crypto] +version = "0.13.0" +features = ["aes", "random"] + +[dependencies.ureq] +version = "2.5.0" +default-features = false +features = ["tls"] + +[dependencies.image] +version = "0.24.3" +default-features = false +features = ["gif", "jpeg", "ico", "png", "tiff", "webp", "bmp", "hdr"] diff --git a/src/crypto.rs b/src/crypto.rs new file mode 100644 index 0000000..9165d9f --- /dev/null +++ b/src/crypto.rs @@ -0,0 +1,26 @@ +use crypto::ciphers::{ + aes_gcm::Aes256Gcm, + traits::Aead, +}; +use crypto::utils::rand; + +const NONCE: [u8; 12] = [0xd0, 0xc3, 0x75, 0x56, 0x58, 0xc1, 0x7e, 0x5f, 0xd6, 0xcc, 0xb6, 0x76]; + +pub fn encrypt(key: &[u8; 32], plaintext: &[u8]) -> Vec { + let mut result = vec![0; plaintext.len() + 16]; + let (cyphertext, tag) = result.split_at_mut(plaintext.len()); + Aes256Gcm::try_encrypt( + key, + &NONCE, + &[], + plaintext, + cyphertext, + tag).expect("Unexpected cryptography error"); + result +} + +pub fn make_key() -> [u8; 32] { + let mut result = [0; 32]; + rand::fill(&mut result).expect("Unexpected error generating random data"); + result +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..f6eea77 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,190 @@ +mod parse; +mod crypto; +mod upload; +mod thumbnailing; + +use std::borrow::Cow; +use std::io::{self, ErrorKind, Read}; +use std::fs::File; +use std::path::{PathBuf, Path}; + +use itertools::{Itertools, Either}; + +#[derive(Debug)] +enum AviaryError { + /// One of the files passed by the user simply did not exist + /// + /// The attached path is the file which didn't exist + /// + /// Handling: Halt execution before any upload begins and alert the user of any + /// missing files. + FileDNE(PathBuf), + + /// The program lacks permission to read one of the images provided by the user + /// + /// Handling: Halt execution before any upload begins and alert the user of the issue + ReadPermissionDenied(PathBuf), + + /// There was an issue reading data from the disk + /// + /// Handling: Halt execution immediately and alert the user + StreamReadError(PathBuf, io::Error), + + /// One or more of the images wasn't correctly encoded + /// + /// Handling: Halt execution immediately and ask the user if they're sure the + /// provided file is an image. + ImageFormatError(PathBuf), + + /// The server is currently having some difficulties + /// + /// Represents a 5XX or 4XX error, as well as any transport error that doesn't seem + /// like it could stem from a network issue. + /// + /// Handling: Halt execution and alert the user that the server isn't working at the + /// minute, and ask them to try a different server or try again. + ServerError(String), + + /// Couldn't connect to the server + /// + /// Indicates that there was some network error which prevented the client from + /// reaching the server. + /// + /// Handling: Halt execution and ask the user to check that their computer is + /// connected to the internet, and is not having any DNS issues. + ConnectionError(String), + + /// The server URL provided by the user was invalid + /// + /// Handling: Halt execution and alert the user that the server url they provided was + /// invalid, including a couple examples + BadServerParameter, +} + +fn trim_url<'a>(base_url: &str, url: &'a str) -> Option<&'a str> { + if url.starts_with(base_url) { + let shortened = url + .trim() + .trim_start_matches(base_url) + .trim_matches('/') + .trim_end_matches(".bin"); + if shortened.len() > 50 { + None + } else { + Some(shortened) + } + } else { + None + } +} + +fn main() { + let args: parse::Args = argh::from_env(); + let (files, errors): (Vec<_>, Vec<_>) = args.images.iter() + .map(Path::new) + .map(|path| File::open(path) + .map(|file| (path, file)) + .map_err(|e| match e.kind() { + ErrorKind::NotFound => AviaryError::FileDNE(path.to_owned()), + ErrorKind::PermissionDenied => AviaryError::ReadPermissionDenied(path.to_owned()), + _ => panic!( + "Received an error kind that should be impossible for {}: {:?}", + path.display(), + e + ) + } + )) + .partition_result(); + + if !errors.is_empty() { + println!("[FATAL] The was a problem accessing some of the files you provided!"); + let (nonexistant, noread): (Vec<_>, Vec<_>) = errors.iter() + .partition_map(|e| match e { + AviaryError::FileDNE(path) => Either::Left(path), + AviaryError::ReadPermissionDenied(path) => Either::Right(path), + other => panic!("This error should not be possible! {other:?}") + }); + if !nonexistant.is_empty() { + println!("\nWe didn't see any files at the following locations:"); + nonexistant.iter().for_each(|path| println!("{}", path.display())); + } + if !noread.is_empty() { + println!("\nWe found these files, but didn't have permission to open them:"); + noread.iter().for_each(|path| println!("{}", path.display())); + } + } else { + let agent = upload::get_agent(); + let full_server = if args.server.starts_with("http") { + Cow::Borrowed(&args.server) + } else { + Cow::Owned(format!("https://{}", args.server)) + }; + let upload_records = files.into_iter() + .map(|(path, mut file)| + (|| { + let mut buff = Vec::with_capacity(file.metadata()?.len() as usize); + file.read_to_end(&mut buff)?; + Ok((path, buff)) + })().map_err(|e| AviaryError::StreamReadError(path.to_owned(), e)) + ) + .map(|r| r.and_then(|(path, raw_dat)| { + let (thumbnail, blurhash) = thumbnailing::thumbnail(&raw_dat) + .map_err(|_| AviaryError::ImageFormatError(path.to_owned()))?; + Ok((raw_dat, thumbnail, blurhash)) + })) + .map_ok(|(raw_dat, thumbnail, blurhash)| { + let key = crypto::make_key(); + ( + key, + crypto::encrypt(&key, &raw_dat), + crypto::encrypt(&key, &thumbnail), + blurhash + ) + }) + .map(|r| r.and_then(|(key, full_img, thumb, blurhash)| + upload::put_data(&agent, &*full_server, &thumb) + .and_then(|thumb_url| + upload::put_data(&agent, &*full_server, &full_img) + .map(|full_url| (key, full_url, thumb_url, blurhash))) + .map_err(|err| match err { + ureq::Error::Status(code, msg) => AviaryError::ServerError( + format!("Error code {} received from server: {}", code, + msg.into_string().unwrap_or(String::new()))), + ureq::Error::Transport(transport) => match transport.kind() { + ureq::ErrorKind::InvalidUrl => + AviaryError::BadServerParameter, + ureq::ErrorKind::Dns => + AviaryError::ConnectionError( + format!("DNS issue: {}", transport.message().unwrap_or(""))), + ureq::ErrorKind::Io => + AviaryError::ConnectionError( + format!("IO issue: {}", transport.message().unwrap_or(""))), + ureq::ErrorKind::ConnectionFailed => + AviaryError::ConnectionError( + format!("Connection issue: {}", transport.message().unwrap_or(""))), + ureq::ErrorKind::TooManyRedirects => + AviaryError::ServerError("Too many redirects".to_owned()), + ureq::ErrorKind::BadHeader => + AviaryError::ServerError("Invalid header from server".to_owned()), + ureq::ErrorKind::BadStatus => + AviaryError::ServerError("Invalid status from server".to_owned()), + unk => panic!("Unexpected transport error kind {unk}:\n{transport}") + }, + }) + )) + .map(|r| r.and_then(|(key, full_url, thumb_url, blurhash)| { + let full_trimmed = trim_url(&*full_server, &full_url); + let thmb_trimmed = trim_url(&*full_server, &thumb_url); + if let (Some(full_url), Some(thmb_url)) = (full_trimmed, thmb_trimmed) { + Ok((key, full_url.to_owned(), thmb_url.to_owned(), blurhash)) + } else { + Err(AviaryError::ServerError(format!("Received bad response from server: {}", full_url))) + } + })) + .collect::, _>>() + .unwrap(); + upload_records.into_iter() + .for_each(|(key, full_url, thumb_url, blurhash)| + println!("{}, {}, {}, {}", base64::encode(key), full_url, thumb_url, blurhash)) + } +} diff --git a/src/parse.rs b/src/parse.rs new file mode 100644 index 0000000..a2ec87d --- /dev/null +++ b/src/parse.rs @@ -0,0 +1,17 @@ +use argh_derive::FromArgs; + +#[derive(FromArgs)] +/// Create Aviary galleries +pub struct Args { + /// the title of the gallery + #[argh(option, short = 't')] + pub title: Option, + + /// the null pointer server to use + #[argh(option, short = 's', default = "\"envs.sh\".to_owned()")] + pub server: String, + + /// A list of image files to post + #[argh(positional)] + pub images: Vec, +} diff --git a/src/thumbnailing.rs b/src/thumbnailing.rs new file mode 100644 index 0000000..558dda3 --- /dev/null +++ b/src/thumbnailing.rs @@ -0,0 +1,25 @@ +use std::io::Cursor; +use image::{io::Reader, DynamicImage, ImageResult}; +use webp::{Encoder, WebPMemory}; + +pub fn thumbnail(bytes: &[u8]) -> ImageResult<(WebPMemory, String)> { + let original_image = Reader::new(Cursor::new(bytes)) + .with_guessed_format().expect("IO errors impossible with Cursor") + .decode()?; + let new_dimm = u32::min(original_image.height(), original_image.width()); + let crop_x = (original_image.width() - new_dimm) / 2; + let crop_y = (original_image.height() - new_dimm) / 2; + let cropped = original_image.crop_imm(crop_x, crop_y, new_dimm, new_dimm); + let scaled = if new_dimm < 400 { + cropped + } else { + original_image.thumbnail_exact(400, 400) + }.into_rgba8(); + let blurhash = blurhash::encode(4, 4, 400, 400, scaled.as_raw()); + Ok(( + Encoder::from_image(&DynamicImage::ImageRgba8(scaled)) + .expect("Unexpected difficulty interpretting thumbnail") + .encode(50.0), + blurhash + )) +} diff --git a/src/upload.rs b/src/upload.rs new file mode 100644 index 0000000..ee24201 --- /dev/null +++ b/src/upload.rs @@ -0,0 +1,20 @@ +use std::io::Read; + +use ureq::{Agent, AgentBuilder}; + +const DATA_HEADER: &'static [u8] = b"--FuckLiterallyAllCops\r\nContent-Disposition: form-data; name=\"file\"; filename=\"pigeon.bin\"\r\nContent-Type: application/octet-stream\r\n\r\n"; +const DATA_FOOTER: &'static [u8] = b"\x00\r\n--FuckLiterallyAllCops--\r\n"; + +pub fn get_agent() -> Agent { + AgentBuilder::new() + .max_idle_connections(3) + .redirects(2) + .user_agent("aviary-cli/0.0.1") + .build() +} + +pub fn put_data(agent: &Agent, server: &str, data: &[u8]) -> Result { + Ok(agent.post(server) + .set("Content-Type", "multipart/form-data; boundary=FuckLiterallyAllCops") + .send(DATA_HEADER.chain(data).chain(DATA_FOOTER))?.into_string()?) +}