diff --git a/Cargo.lock b/Cargo.lock index 8a2bdec..e47f32d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -38,6 +38,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "aligned-vec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1" + [[package]] name = "allocator-api2" version = "0.2.18" @@ -114,12 +120,35 @@ version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c042108f3ed77fd83760a5fd79b53be043192bb3b9dba91d8c574c0ada7850c8" +[[package]] +name = "arbitrary" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775a8770d29db3dadcb858482cc240af7b2ffde4ac4de67d1d4955728103f0e2" + +[[package]] +name = "arg_enum_proc_macro" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "arraydeque" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + [[package]] name = "async-trait" version = "0.1.83" @@ -152,6 +181,29 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +[[package]] +name = "av1-grain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6678909d8c5d46a42abcf571271e15fdbc0a225e3646cf23762cd415046c78bf" +dependencies = [ + "anyhow", + "arrayvec", + "log", + "nom", + "num-rational", + "v_frame", +] + +[[package]] +name = "avif-serialize" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e335041290c43101ca215eed6f43ec437eb5a42125573f600fc3fa42b9bddd62" +dependencies = [ + "arrayvec", +] + [[package]] name = "axum" version = "0.7.7" @@ -172,6 +224,7 @@ dependencies = [ "matchit", "memchr", "mime", + "multer", "percent-encoding", "pin-project-lite", "rustversion", @@ -275,6 +328,18 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "bit_field" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.6.0" @@ -284,6 +349,12 @@ dependencies = [ "serde", ] +[[package]] +name = "bitstream-io" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b81e1519b0d82120d2fd469d5bfb2919a9361c48b02d82d04befc1cdd2002452" + [[package]] name = "block-buffer" version = "0.10.4" @@ -303,18 +374,36 @@ dependencies = [ "serde", ] +[[package]] +name = "built" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c360505aed52b7ec96a3636c3f039d99103c37d1d9b4f7a8c743d3ea9ffcd03b" + [[package]] name = "bumpalo" version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +[[package]] +name = "bytemuck" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8334215b81e418a0a7bdb8ef0849474f40bb10c8b71f1c4ed315cff49f32494d" + [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + [[package]] name = "bytes" version = "1.8.0" @@ -327,9 +416,21 @@ version = "1.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2e7962b54006dcfcc61cb72735f4d89bb97061dd6a7ed882ec6b8ee53714c6f" dependencies = [ + "jobserver", + "libc", "shlex", ] +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -372,6 +473,12 @@ dependencies = [ "phf_codegen", ] +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + [[package]] name = "colorchoice" version = "1.0.3" @@ -389,6 +496,7 @@ dependencies = [ "chrono", "config", "env_logger", + "image", "jsonwebtoken", "lazy_static", "log", @@ -402,6 +510,7 @@ dependencies = [ "tower", "tower-http", "url", + "uuid", ] [[package]] @@ -518,6 +627,15 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossbeam-deque" version = "0.8.5" @@ -700,12 +818,46 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "exr" +version = "1.73.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83197f59927b46c04a183a619b7c29df34e63e63c7869320862268c0ef687e0" +dependencies = [ + "bit_field", + "half", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec", + "zune-inflate", +] + [[package]] name = "fastrand" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" +[[package]] +name = "fdeflate" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07c6f4c64c1d33a3111c4466f7365ebdcc37c5bd1ea0d62aae2e3d722aacbedb" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "flate2" +version = "1.0.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "flume" version = "0.11.1" @@ -842,6 +994,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "gif" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2" +dependencies = [ + "color_quant", + "weezl", +] + [[package]] name = "gimli" version = "0.31.1" @@ -867,7 +1029,7 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757" dependencies = [ - "bitflags", + "bitflags 2.6.0", "ignore", "walkdir", ] @@ -891,6 +1053,16 @@ dependencies = [ "tracing", ] +[[package]] +name = "half" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if", + "crunchy", +] + [[package]] name = "hashbrown" version = "0.14.5" @@ -1159,6 +1331,45 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "image" +version = "0.25.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd6f44aed642f18953a158afeb30206f4d50da59fbc66ecb53c66488de73563b" +dependencies = [ + "bytemuck", + "byteorder-lite", + "color_quant", + "exr", + "gif", + "image-webp", + "num-traits", + "png", + "qoi", + "ravif", + "rayon", + "rgb", + "tiff", + "zune-core", + "zune-jpeg", +] + +[[package]] +name = "image-webp" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e031e8e3d94711a9ccb5d6ea357439ef3dcbed361798bd4071dc4d9793fbe22f" +dependencies = [ + "byteorder-lite", + "quick-error", +] + +[[package]] +name = "imgref" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408" + [[package]] name = "indexmap" version = "2.6.0" @@ -1169,6 +1380,17 @@ dependencies = [ "hashbrown 0.15.0", ] +[[package]] +name = "interpolate_name" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "ipnet" version = "2.10.1" @@ -1181,12 +1403,36 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "jobserver" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +dependencies = [ + "libc", +] + +[[package]] +name = "jpeg-decoder" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" + [[package]] name = "js-sys" version = "0.3.72" @@ -1231,12 +1477,29 @@ dependencies = [ "spin", ] +[[package]] +name = "lebe" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" + [[package]] name = "libc" version = "0.2.161" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" +[[package]] +name = "libfuzzer-sys" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" +dependencies = [ + "arbitrary", + "cc", + "once_cell", +] + [[package]] name = "libm" version = "0.2.8" @@ -1276,12 +1539,31 @@ version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +[[package]] +name = "loop9" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +dependencies = [ + "imgref", +] + [[package]] name = "matchit" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" +[[package]] +name = "maybe-rayon" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if", + "rayon", +] + [[package]] name = "md-5" version = "0.10.6" @@ -1327,6 +1609,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ "adler2", + "simd-adler32", ] [[package]] @@ -1341,6 +1624,23 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "multer" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b" +dependencies = [ + "bytes", + "encoding_rs", + "futures-util", + "http", + "httparse", + "memchr", + "mime", + "spin", + "version_check", +] + [[package]] name = "native-tls" version = "0.2.12" @@ -1358,6 +1658,12 @@ dependencies = [ "tempfile", ] +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + [[package]] name = "nom" version = "7.1.3" @@ -1368,6 +1674,12 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "noop_proc_macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" + [[package]] name = "num-bigint" version = "0.4.6" @@ -1401,6 +1713,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "num-integer" version = "0.1.46" @@ -1421,6 +1744,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -1452,7 +1786,7 @@ version = "0.10.68" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" dependencies = [ - "bitflags", + "bitflags 2.6.0", "cfg-if", "foreign-types", "libc", @@ -1697,6 +2031,19 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +[[package]] +name = "png" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52f9d46a34a05a6a57566bc2bfae066ef07585a6e3fa30fbbdff5936380623f0" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -1721,6 +2068,40 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "profiling" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afbdc74edc00b6f6a218ca6a5364d6226a259d4b8ea1af4a0ea063f27e179f4d" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a65f2e60fbf1063868558d69c6beacf412dc755f9fc020f514b7955fc914fe30" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + [[package]] name = "quote" version = "1.0.37" @@ -1760,13 +2141,83 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rav1e" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd87ce80a7665b1cce111f8a16c1f3929f6547ce91ade6addf4ec86a8dda5ce9" +dependencies = [ + "arbitrary", + "arg_enum_proc_macro", + "arrayvec", + "av1-grain", + "bitstream-io", + "built", + "cfg-if", + "interpolate_name", + "itertools", + "libc", + "libfuzzer-sys", + "log", + "maybe-rayon", + "new_debug_unreachable", + "noop_proc_macro", + "num-derive", + "num-traits", + "once_cell", + "paste", + "profiling", + "rand", + "rand_chacha", + "simd_helpers", + "system-deps", + "thiserror", + "v_frame", + "wasm-bindgen", +] + +[[package]] +name = "ravif" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2413fd96bd0ea5cdeeb37eaf446a22e6ed7b981d792828721e74ded1980a45c6" +dependencies = [ + "avif-serialize", + "imgref", + "loop9", + "quick-error", + "rav1e", + "rayon", + "rgb", +] + +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "redox_syscall" version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ - "bitflags", + "bitflags 2.6.0", ] [[package]] @@ -1841,6 +2292,12 @@ dependencies = [ "windows-registry", ] +[[package]] +name = "rgb" +version = "0.8.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" + [[package]] name = "ring" version = "0.17.8" @@ -1863,7 +2320,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" dependencies = [ "base64 0.21.7", - "bitflags", + "bitflags 2.6.0", "serde", "serde_derive", ] @@ -1910,7 +2367,7 @@ version = "0.38.38" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa260229e6538e52293eeb577aabd09945a09d6d9cc0fc550ed7529056c2e32a" dependencies = [ - "bitflags", + "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", @@ -1998,7 +2455,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags", + "bitflags 2.6.0", "core-foundation", "core-foundation-sys", "libc", @@ -2125,6 +2582,21 @@ dependencies = [ "rand_core", ] +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "simd_helpers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" +dependencies = [ + "quote", +] + [[package]] name = "simple_asn1" version = "0.6.2" @@ -2261,6 +2733,7 @@ dependencies = [ "tokio-stream", "tracing", "url", + "uuid", ] [[package]] @@ -2310,7 +2783,7 @@ checksum = "64bb4714269afa44aef2755150a0fc19d756fb580a67db8885608cf02f47d06a" dependencies = [ "atoi", "base64 0.22.1", - "bitflags", + "bitflags 2.6.0", "byteorder", "bytes", "chrono", @@ -2342,6 +2815,7 @@ dependencies = [ "stringprep", "thiserror", "tracing", + "uuid", "whoami", ] @@ -2353,7 +2827,7 @@ checksum = "6fa91a732d854c5d7726349bb4bb879bb9478993ceb764247660aee25f67c2f8" dependencies = [ "atoi", "base64 0.22.1", - "bitflags", + "bitflags 2.6.0", "byteorder", "chrono", "crc", @@ -2381,6 +2855,7 @@ dependencies = [ "stringprep", "thiserror", "tracing", + "uuid", "whoami", ] @@ -2406,6 +2881,7 @@ dependencies = [ "sqlx-core", "tracing", "url", + "uuid", ] [[package]] @@ -2457,7 +2933,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags", + "bitflags 2.6.0", "core-foundation", "system-configuration-sys", ] @@ -2472,6 +2948,25 @@ dependencies = [ "libc", ] +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck", + "pkg-config", + "toml", + "version-compare", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + [[package]] name = "tempfile" version = "3.13.0" @@ -2527,6 +3022,17 @@ dependencies = [ "syn", ] +[[package]] +name = "tiff" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e" +dependencies = [ + "flate2", + "jpeg-decoder", + "weezl", +] + [[package]] name = "time" version = "0.3.36" @@ -2712,7 +3218,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8437150ab6bbc8c5f0f519e3d5ed4aa883a83dd4cdd3d1b21f9482936046cb97" dependencies = [ - "bitflags", + "bitflags 2.6.0", "bytes", "futures-util", "http", @@ -2912,12 +3418,39 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "uuid" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" +dependencies = [ + "getrandom", + "serde", +] + +[[package]] +name = "v_frame" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f32aaa24bacd11e488aa9ba66369c7cd514885742c9fe08cfe85884db3e92b" +dependencies = [ + "aligned-vec", + "num-traits", + "wasm-bindgen", +] + [[package]] name = "vcpkg" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "version-compare" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" + [[package]] name = "version_check" version = "0.9.5" @@ -3032,6 +3565,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "weezl" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" + [[package]] name = "whoami" version = "1.5.2" @@ -3284,3 +3823,27 @@ name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zune-core" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "zune-jpeg" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16099418600b4d8f028622f73ff6e3deaabdff330fb9a2a131dea781ee8b0768" +dependencies = [ + "zune-core", +] diff --git a/Cargo.toml b/Cargo.toml index 08c766a..b665055 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,13 +5,13 @@ edition = "2021" [dependencies] tokio = { version = "1.41.0", features = ["full"] } -axum = { version = "0.7.7", features = [ "macros" ] } +axum = { version = "0.7.7", features = [ "macros", "multipart", "json" ] } axum-extra = { version = "0.9.4", features = [ "cookie" ] } tera = "1.20.0" lazy_static = "1.5.0" tower = "0.5.1" tower-http = { version = "0.6.1", features = ["fs"] } -sqlx = { version = "0.8.2", features = [ "runtime-tokio", "postgres", "chrono" ] } +sqlx = { version = "0.8.2", features = [ "runtime-tokio", "postgres", "chrono", "uuid" ] } url = { version = "2.5.2", features = [ "serde" ] } serde = { version = "1.0.214", features = [ "derive" ] } config = "0.14.1" @@ -24,3 +24,5 @@ rand = "0.8.5" rand_core = { version = "0.6.4", features = [ "getrandom" ] } base64 = "0.22.1" chrono = "0.4.38" +uuid = { version = "1.11.0", features = [ "v4", "serde" ] } +image = "0.25.5" diff --git a/src/config.rs b/src/config.rs index b90ba1c..2bd835d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,6 +1,7 @@ use config::{Config, Environment, File}; use lazy_static::lazy_static; use serde::Deserialize; +use std::path::PathBuf; #[derive(Deserialize)] pub struct OpenIdConfig { @@ -23,6 +24,7 @@ pub struct AppConfig { pub url: url::Url, /// Port for the web server. pub port: u16, + pub uploads_dir: PathBuf, pub openid: OpenIdConfig, pub database: DatabaseConfig, } diff --git a/src/internal/db/images.rs b/src/internal/db/images.rs new file mode 100644 index 0000000..ff59dbc --- /dev/null +++ b/src/internal/db/images.rs @@ -0,0 +1,68 @@ +use sqlx::PgPool; + +pub struct NewImage { + pub id: uuid::Uuid, + pub width: i32, + pub height: i32, + pub mime_type: String, + pub original_filename: Option, + pub alt: Option, +} + +#[derive(sqlx::FromRow)] +pub struct Image { + pub id: uuid::Uuid, + pub width: i32, + pub height: i32, + pub mime_type: String, + pub original_filename: String, + pub alt: Option, + pub uploaded_at: chrono::DateTime, +} + +pub async fn save_image(pool: &PgPool, img: NewImage) -> Result<(), sqlx::Error> { + sqlx::query( + " + INSERT INTO images(id, width, height, mime_type, original_filename, alt) + VALUES ($1, $2, $3, $4, $5, $6) + ", + ) + .bind(img.id) + .bind(img.width) + .bind(img.height) + .bind(img.mime_type) + .bind(img.original_filename) + .bind(img.alt) + .execute(pool) + .await?; + + Ok(()) +} + +pub async fn get_image(pool: &PgPool, id: uuid::Uuid) -> Result { + sqlx::query_as::<_, Image>( + " + SELECT * + FROM images + WHERE id = $1 + ", + ) + .bind(id) + .fetch_one(pool) + .await +} + +pub async fn delete_image(pool: &PgPool, id: uuid::Uuid) -> Result<(), sqlx::Error> { + sqlx::query_as::<_, (i32,)>( + " + DELETE FROM images + WHERE id = $1 + RETURNING 1 + ", + ) + .bind(id) + .fetch_one(pool) + .await?; + + Ok(()) +} diff --git a/src/internal/db/migrations/0002_images.sql b/src/internal/db/migrations/0002_images.sql new file mode 100644 index 0000000..b851b0c --- /dev/null +++ b/src/internal/db/migrations/0002_images.sql @@ -0,0 +1,12 @@ +CREATE TABLE images( + id UUID NOT NULL PRIMARY KEY DEFAULT gen_random_uuid(), + width INTEGER NOT NULL, + height INTEGER NOT NULL, + mime_type TEXT NOT NULL, + original_filename TEXT, + alt TEXT, + uploaded_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +ALTER TABLE users +ADD COLUMN is_admin BOOLEAN NOT NULL DEFAULT FALSE; diff --git a/src/internal/db/mod.rs b/src/internal/db/mod.rs index 5e02fd6..ecf4863 100644 --- a/src/internal/db/mod.rs +++ b/src/internal/db/mod.rs @@ -1,3 +1,4 @@ +pub mod images; pub mod users; pub async fn apply_migrations(pool: &sqlx::PgPool) -> Result<(), sqlx::migrate::MigrateError> { diff --git a/src/internal/db/users.rs b/src/internal/db/users.rs index 426aee8..54eea01 100644 --- a/src/internal/db/users.rs +++ b/src/internal/db/users.rs @@ -19,6 +19,7 @@ pub struct User { pub username: String, pub email: String, pub display_name: String, + pub is_admin: bool, } /// Create a new user using external auth provider. @@ -162,9 +163,8 @@ pub async fn generate_openid_state(pool: &PgPool) -> Result<[u8; 16], anyhow::Er } /// Check state returned by openid authorization endpoint. -/// Returns true if provided state was found and deleted. -pub async fn delete_openid_state(pool: &PgPool, state: Vec) -> Result { - let res = sqlx::query_as::<_, (i32,)>( +pub async fn delete_openid_state(pool: &PgPool, state: Vec) -> Result<(), anyhow::Error> { + sqlx::query_as::<_, (i32,)>( " DELETE FROM tokens WHERE context = $1 @@ -174,8 +174,8 @@ pub async fn delete_openid_state(pool: &PgPool, state: Vec) -> Result>, -) -> Result { +) -> Result { let token = super::ctx::extract_auth_token(cookies)?; match token { @@ -21,6 +22,10 @@ pub async fn logout( let headers = AppendHeaders([(SET_COOKIE, "comfy-session=deleted; Max-Age=0;")]); Ok((headers, Redirect::to("/"))) } - None => Err(anyhow::anyhow!("can't find session token").into()), + None => Err(ControllerError { + status_code: StatusCode::UNAUTHORIZED, + description: String::from("Can't find session token. Are you logged in?"), + details: None, + }), } } diff --git a/src/web/controllers/ctx.rs b/src/web/controllers/ctx.rs index 82f105c..95ee8fa 100644 --- a/src/web/controllers/ctx.rs +++ b/src/web/controllers/ctx.rs @@ -1,9 +1,13 @@ +use anyhow::Result; +use axum::http::StatusCode; use axum_extra::extract::cookie::CookieJar; use base64::prelude::*; +use sqlx::PgPool; -use crate::internal::db::users; +use super::ControllerError; +use crate::internal::db::users::{self, User}; -pub fn extract_auth_token(cookies: CookieJar) -> Result>, anyhow::Error> { +pub fn extract_auth_token(cookies: CookieJar) -> Result>> { let cookie = match cookies.get("comfy-session") { Some(cookie) => cookie, None => { @@ -15,10 +19,7 @@ pub fn extract_auth_token(cookies: CookieJar) -> Result>, anyhow: } /// Get current user by looking at request cookies. -pub async fn get_user( - pool: &sqlx::PgPool, - cookies: CookieJar, -) -> Result, anyhow::Error> { +pub async fn get_optional_user(pool: &PgPool, cookies: CookieJar) -> Result> { let token = extract_auth_token(cookies)?; match token { Some(token) => { @@ -29,12 +30,37 @@ pub async fn get_user( } } -/// Get context for templates with user field set. -pub async fn get_context( - pool: &sqlx::PgPool, - cookies: CookieJar, -) -> Result { +/// Get current user by looking at request headers. +/// Returns an error if the user is not authorized. +pub async fn get_user(pool: &PgPool, cookies: CookieJar) -> Result { + match get_optional_user(pool, cookies).await? { + Some(user) => Ok(user), + None => Err(ControllerError { + status_code: StatusCode::UNAUTHORIZED, + description: String::from("You need to log in to your account to access this page."), + details: None, + }), + } +} + +/// Get current user by looking at request headers. +/// Returns an error if the user is not authorized or is not an administrator.. +pub async fn get_admin_user(pool: &PgPool, cookies: CookieJar) -> Result { let user = get_user(pool, cookies).await?; + if !user.is_admin { + return Err(ControllerError { + status_code: StatusCode::FORBIDDEN, + description: String::from("Only administrators can access this page."), + details: None, + }); + } + + Ok(user) +} + +/// Get context for templates with user field set. +pub async fn get_context(pool: &sqlx::PgPool, cookies: CookieJar) -> Result { + let user = get_optional_user(pool, cookies).await?; let mut ctx = tera::Context::new(); ctx.insert("user", &user); Ok(ctx) diff --git a/src/web/controllers/error.rs b/src/web/controllers/error.rs index c468f7e..f0a8c16 100644 --- a/src/web/controllers/error.rs +++ b/src/web/controllers/error.rs @@ -4,26 +4,36 @@ use axum::{ }; use log::error; -pub struct ControllerError(anyhow::Error); +pub struct ControllerError { + pub status_code: StatusCode, + /// Description of the error available to the user. + pub description: String, + /// Detailed description of the error that is not sent to the user. + pub details: Option, +} impl IntoResponse for ControllerError { fn into_response(self) -> Response { - error!("{}", self.0); - ( - StatusCode::INTERNAL_SERVER_ERROR, - "something went wrong, see logs for details", - ) - .into_response() + if let Some(details) = self.details { + error!("{}", details); + } + + (self.status_code, self.description).into_response() } } -// This enables using `?` on functions that return `Result<_, anyhow::Error>` to turn them into `Result<_, AppError>`. -// That way you don't need to do that manually. +// This enables using `?` on functions that return `Result<_, anyhow::Error>` to turn them into `Result<_, ControllerError>`. impl From for ControllerError where - E: Into, + E: Into + ToString, { fn from(err: E) -> Self { - Self(err.into()) + Self { + status_code: StatusCode::INTERNAL_SERVER_ERROR, + description: String::from( + "Something went wrong, more information is available in the logs.", + ), + details: Some(err.to_string()), + } } } diff --git a/src/web/controllers/images.rs b/src/web/controllers/images.rs new file mode 100644 index 0000000..ef0959b --- /dev/null +++ b/src/web/controllers/images.rs @@ -0,0 +1,115 @@ +use axum::{ + extract::{Multipart, Path, Request, State}, + http::header, + response::IntoResponse, + Json, +}; +use axum_extra::extract::cookie::CookieJar; +use image::ImageReader; +use serde::Serialize; +use std::{ + fs::{self, File}, + io::{Cursor, Write}, + path, + sync::Arc, +}; +use tower_http::services::ServeFile; +use uuid::Uuid; + +use super::{AppState, ControllerError}; +use crate::config::CONFIG; +use crate::internal::db::images; + +#[derive(Serialize)] +pub struct UploadResult { + ids: Vec, +} + +#[derive(Serialize)] +pub struct DeletionResult { + id: Uuid, +} + +fn get_image_path(id: Uuid) -> path::PathBuf { + CONFIG.uploads_dir.join(format!("images/{}", id.simple())) +} + +pub async fn upload_image( + State(state): State>, + cookies: CookieJar, + mut multipart: Multipart, +) -> Result, ControllerError> { + super::get_admin_user(&state.db, cookies).await?; + + let mut res = UploadResult { ids: Vec::new() }; + + while let Some(field) = multipart.next_field().await? { + let name = field.name(); + if name.is_none() || name != Some("image") { + continue; + } + + let id = Uuid::new_v4(); + let file_path = get_image_path(id); + + let original_filename = field.file_name().map(|s| s.to_string()); + + let data = field.bytes().await?; + let img_reader = ImageReader::new(Cursor::new(&data)).with_guessed_format()?; + + let format = match img_reader.format() { + Some(format) => format, + None => continue, + }; + + let mime_type = format.to_mime_type().to_string(); + + let decoded_image = img_reader.decode()?; + let width: i32 = decoded_image.width().try_into().unwrap(); + let height: i32 = decoded_image.height().try_into().unwrap(); + + let mut file_handle = File::create(file_path)?; + file_handle.write_all(&data)?; + + let img = images::NewImage { + id, + width, + height, + mime_type, + original_filename, + alt: None, + }; + images::save_image(&state.db, img).await?; + + res.ids.push(id); + } + + Ok(Json(res)) +} + +pub async fn get_image( + State(state): State>, + Path(image_id): Path, + req: Request, +) -> Result { + let image_path = get_image_path(image_id); + let db_img = images::get_image(&state.db, image_id).await?; + let content = ServeFile::new(image_path).try_call(req).await?; + + Ok(([(header::CONTENT_TYPE, db_img.mime_type)], content)) +} + +pub async fn delete_image( + State(state): State>, + Path(image_id): Path, + cookies: CookieJar, +) -> Result, ControllerError> { + super::get_admin_user(&state.db, cookies).await?; + + images::delete_image(&state.db, image_id).await?; + + let image_path = get_image_path(image_id); + fs::remove_file(image_path)?; + + Ok(Json(DeletionResult { id: image_id })) +} diff --git a/src/web/controllers/mod.rs b/src/web/controllers/mod.rs index 185c420..b1cd69f 100644 --- a/src/web/controllers/mod.rs +++ b/src/web/controllers/mod.rs @@ -2,10 +2,12 @@ mod auth; mod ctx; mod error; mod home; +mod images; mod openid; pub use auth::*; pub use home::*; +pub use images::{delete_image, get_image, upload_image}; pub use openid::*; use ctx::*; diff --git a/src/web/controllers/openid.rs b/src/web/controllers/openid.rs index e61e3b8..cff3eb9 100644 --- a/src/web/controllers/openid.rs +++ b/src/web/controllers/openid.rs @@ -1,12 +1,13 @@ use axum::{ extract::{Query, State}, - http::header::SET_COOKIE, + http::{header::SET_COOKIE, StatusCode}, response::{AppendHeaders, IntoResponse, Redirect}, }; use base64::prelude::*; use std::{collections::HashMap, sync::Arc}; use url::Url; +use super::{AppState, ControllerError}; use crate::config::CONFIG; use crate::internal::db::users; use crate::internal::openid; @@ -18,9 +19,7 @@ fn gen_redirect_uri() -> String { } /// Redirect the user to the authorization page. -pub async fn signin( - State(state): State>, -) -> Result { +pub async fn signin(State(state): State>) -> Result { let raw_openid_state = users::generate_openid_state(&state.db).await?; let openid_state = BASE64_URL_SAFE.encode(raw_openid_state); @@ -44,20 +43,22 @@ pub async fn signin( pub async fn exchange_code( Query(params): Query>, - State(state): State>, -) -> Result { - let openid_state = params - .get("state") - .ok_or(anyhow::anyhow!("state is missing"))?; - let raw_openid_state = BASE64_URL_SAFE.decode(openid_state)?; - let state_found = users::delete_openid_state(&state.db, raw_openid_state).await?; - if !state_found { - return Err(anyhow::anyhow!("state was not found").into()); - } + State(state): State>, +) -> Result { + let openid_state = params.get("state").ok_or(ControllerError { + status_code: StatusCode::BAD_REQUEST, + description: String::from("Parameter 'state' was not found."), + details: None, + })?; + let code = params.get("code").ok_or(ControllerError { + status_code: StatusCode::BAD_REQUEST, + description: String::from("Parameter 'code' was not found."), + details: None, + })?; + + let raw_openid_state = BASE64_URL_SAFE.decode(openid_state)?; + users::delete_openid_state(&state.db, raw_openid_state).await?; - let code = params - .get("code") - .ok_or(anyhow::anyhow!("authorization code is missing"))?; let creds = openid::exchange_code(code, &gen_redirect_uri()).await?; let token = openid::parse_id_token(creds.id_token).await?;