diff --git a/.gitignore b/.gitignore index ea8c4bf..7f4b915 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,8 @@ -/target +# Compiled files and executables +/target/ + +# Backup files generated by rustfmt +**/*.rs.bk + +# Default upload directory +/upload/ diff --git a/Cargo.lock b/Cargo.lock index 09be4e9..8a92b9d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -37,6 +37,26 @@ dependencies = [ "trust-dns-resolver", ] +[[package]] +name = "actix-files" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c51e8a9146c12fce92a6e4c24b8c4d9b05268130bfd8d61bc587e822c32ce689" +dependencies = [ + "actix-service", + "actix-web", + "bitflags", + "bytes 0.5.6", + "derive_more", + "futures-core", + "futures-util", + "log", + "mime", + "mime_guess", + "percent-encoding", + "v_htmlescape", +] + [[package]] name = "actix-http" version = "2.2.0" @@ -74,9 +94,9 @@ dependencies = [ "mime", "percent-encoding", "pin-project 1.0.7", - "rand", + "rand 0.7.3", "regex", - "serde", + "serde 1.0.126", "serde_json", "serde_urlencoded", "sha-1", @@ -94,6 +114,24 @@ dependencies = [ "syn", ] +[[package]] +name = "actix-multipart" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "774bfeb11b54bf9c857a005b8ab893293da4eaff79261a66a9200dab7f5ab6e3" +dependencies = [ + "actix-service", + "actix-utils", + "actix-web", + "bytes 0.5.6", + "derive_more", + "futures-util", + "httparse", + "log", + "mime", + "twoway", +] + [[package]] name = "actix-router" version = "0.2.7" @@ -104,7 +142,7 @@ dependencies = [ "http", "log", "regex", - "serde", + "serde 1.0.126", ] [[package]] @@ -243,7 +281,7 @@ dependencies = [ "mime", "pin-project 1.0.7", "regex", - "serde", + "serde 1.0.126", "serde_json", "serde_urlencoded", "socket2", @@ -278,6 +316,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + [[package]] name = "async-trait" version = "0.1.50" @@ -324,8 +368,8 @@ dependencies = [ "log", "mime", "percent-encoding", - "rand", - "serde", + "rand 0.7.3", + "serde 1.0.126", "serde_json", "serde_urlencoded", ] @@ -377,12 +421,31 @@ dependencies = [ "libc", ] +[[package]] +name = "buf-min" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa17aa1cf56bdd6bb30518767d00e58019d326f3f05d8c3e0730b549d332ea83" +dependencies = [ + "bytes 0.5.6", +] + [[package]] name = "bumpalo" version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c59e7af012c713f529e7a3ee57ce9b31ddd858d4b512923602f74608b009631" +[[package]] +name = "byte-unit" +version = "4.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063197e6eb4b775b64160dedde7a0986bb2836cce140e9492e9e96f28e18bcd8" +dependencies = [ + "serde 1.0.126", + "utf8-width", +] + [[package]] name = "byteorder" version = "1.4.3" @@ -428,6 +491,33 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clap" +version = "2.33.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" +dependencies = [ + "bitflags", + "textwrap", + "unicode-width", +] + +[[package]] +name = "config" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1b9d958c2b1368a663f05538fc1b5975adce1e19f435acceae987aceeeb369" +dependencies = [ + "lazy_static", + "nom 5.1.2", + "rust-ini", + "serde 1.0.126", + "serde-hjson", + "serde_json", + "toml", + "yaml-rust", +] + [[package]] name = "const_fn" version = "0.4.8" @@ -448,7 +538,7 @@ checksum = "03a5d7b21829bc7b4bf4754a978a241ae54ea55a40f92bb20216e54096f4b951" dependencies = [ "percent-encoding", "time", - "version_check", + "version_check 0.9.3", ] [[package]] @@ -685,7 +775,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" dependencies = [ "typenum", - "version_check", + "version_check 0.9.3", ] [[package]] @@ -696,7 +786,18 @@ checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" dependencies = [ "cfg-if 1.0.0", "libc", - "wasi", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi 0.10.2+wasi-snapshot-preview1", ] [[package]] @@ -828,6 +929,15 @@ dependencies = [ "winreg", ] +[[package]] +name = "itertools" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "0.4.7" @@ -856,6 +966,19 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "lexical-core" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe" +dependencies = [ + "arrayvec", + "bitflags", + "cfg-if 1.0.0", + "ryu", + "static_assertions", +] + [[package]] name = "libc" version = "0.2.98" @@ -919,6 +1042,16 @@ version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" +[[package]] +name = "mime_guess" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2684d4c2e97d99848d30b324b00c8fcc7e5c897b7cbb5819b09e7c90e8baf212" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "miniz_oxide" version = "0.4.4" @@ -982,6 +1115,45 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "nom" +version = "4.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" +dependencies = [ + "memchr", + "version_check 0.1.5", +] + +[[package]] +name = "nom" +version = "5.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" +dependencies = [ + "lexical-core", + "memchr", + "version_check 0.9.3", +] + +[[package]] +name = "num-traits" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" +dependencies = [ + "num-traits 0.2.14", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + [[package]] name = "num_cpus" version = "1.13.0" @@ -996,9 +1168,16 @@ dependencies = [ name = "oops" version = "0.1.0" dependencies = [ + "actix-files", + "actix-multipart", "actix-web", + "byte-unit", + "config", "env_logger", + "futures-util", "log", + "petname", + "serde 1.0.126", ] [[package]] @@ -1047,6 +1226,17 @@ dependencies = [ "ucd-trie", ] +[[package]] +name = "petname" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9ed8e1c7b7cdd47819f76bbec141b4d4b59bb8ffd26c372cc236c90cd90becc" +dependencies = [ + "clap", + "itertools", + "rand 0.8.4", +] + [[package]] name = "pin-project" version = "0.4.28" @@ -1153,11 +1343,23 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" dependencies = [ - "getrandom", + "getrandom 0.1.16", "libc", - "rand_chacha", - "rand_core", - "rand_hc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc 0.2.0", +] + +[[package]] +name = "rand" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.3", + "rand_hc 0.3.1", ] [[package]] @@ -1167,7 +1369,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.3", ] [[package]] @@ -1176,7 +1388,16 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" dependencies = [ - "getrandom", + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom 0.2.3", ] [[package]] @@ -1185,7 +1406,16 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" dependencies = [ - "rand_core", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_hc" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" +dependencies = [ + "rand_core 0.6.3", ] [[package]] @@ -1224,6 +1454,12 @@ dependencies = [ "quick-error", ] +[[package]] +name = "rust-ini" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2" + [[package]] name = "rustc_version" version = "0.2.3" @@ -1287,6 +1523,12 @@ dependencies = [ "pest", ] +[[package]] +name = "serde" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8" + [[package]] name = "serde" version = "1.0.126" @@ -1296,6 +1538,18 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-hjson" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a3a4e0ea8a88553209f6cc6cfe8724ecad22e1acf372793c27d995290fe74f8" +dependencies = [ + "lazy_static", + "num-traits 0.1.43", + "regex", + "serde 0.8.23", +] + [[package]] name = "serde_derive" version = "1.0.126" @@ -1315,7 +1569,7 @@ checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" dependencies = [ "itoa", "ryu", - "serde", + "serde 1.0.126", ] [[package]] @@ -1327,7 +1581,7 @@ dependencies = [ "form_urlencoded", "itoa", "ryu", - "serde", + "serde 1.0.126", ] [[package]] @@ -1387,9 +1641,15 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e113fb6f3de07a243d434a56ec6f186dfd51cb08448239fe7bcae73f87ff28ff" dependencies = [ - "version_check", + "version_check 0.9.3", ] +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "stdweb" version = "0.4.20" @@ -1412,7 +1672,7 @@ checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" dependencies = [ "proc-macro2", "quote", - "serde", + "serde 1.0.126", "serde_derive", "syn", ] @@ -1426,7 +1686,7 @@ dependencies = [ "base-x", "proc-macro2", "quote", - "serde", + "serde 1.0.126", "serde_derive", "serde_json", "sha1", @@ -1459,6 +1719,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + [[package]] name = "thiserror" version = "1.0.26" @@ -1499,7 +1768,7 @@ dependencies = [ "standback", "stdweb", "time-macros", - "version_check", + "version_check 0.9.3", "winapi 0.3.9", ] @@ -1575,6 +1844,15 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +dependencies = [ + "serde 1.0.126", +] + [[package]] name = "tracing" version = "0.1.26" @@ -1619,7 +1897,7 @@ dependencies = [ "idna", "lazy_static", "log", - "rand", + "rand 0.7.3", "smallvec", "thiserror", "tokio", @@ -1645,6 +1923,16 @@ dependencies = [ "trust-dns-proto", ] +[[package]] +name = "twoway" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c57ffb460d7c24cd6eda43694110189030a3d1dfe418416d9468fd1c1d290b47" +dependencies = [ + "memchr", + "unchecked-index", +] + [[package]] name = "typenum" version = "1.13.0" @@ -1657,6 +1945,21 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" +[[package]] +name = "unchecked-index" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeba86d422ce181a719445e51872fa30f1f7413b62becb52e95ec91aa262d85c" + +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check 0.9.3", +] + [[package]] name = "unicode-bidi" version = "0.3.5" @@ -1681,6 +1984,12 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" +[[package]] +name = "unicode-width" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" + [[package]] name = "unicode-xid" version = "0.2.2" @@ -1699,6 +2008,50 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "utf8-width" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cf7d77f457ef8dfa11e4cd5933c5ddb5dc52a94664071951219a97710f0a32b" + +[[package]] +name = "v_escape" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3e0ab5fab1db278a9413d2ea794cb66f471f898c5b020c3c394f6447625d9d4" +dependencies = [ + "buf-min", + "v_escape_derive", +] + +[[package]] +name = "v_escape_derive" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c860ad1273f4eee7006cee05db20c9e60e5d24cba024a32e1094aa8e574f3668" +dependencies = [ + "nom 4.2.3", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "v_htmlescape" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f9a8af610ad6f7fc9989c9d2590d9764bc61f294884e9ee93baa58795174572" +dependencies = [ + "cfg-if 1.0.0", + "v_escape", +] + +[[package]] +name = "version_check" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" + [[package]] name = "version_check" version = "0.9.3" @@ -1711,6 +2064,12 @@ version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + [[package]] name = "wasm-bindgen" version = "0.2.74" @@ -1832,3 +2191,12 @@ dependencies = [ "winapi 0.2.8", "winapi-build", ] + +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] diff --git a/Cargo.toml b/Cargo.toml index 9c367e2..a56410c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,13 +2,23 @@ name = "oops" version = "0.1.0" edition = "2018" -description = "Orhun's obscure pastebin server" +description = "An obscure pastebin server" authors = ["Orhun Parmaksız "] [dependencies] actix-web = "3.3.2" +actix-multipart = "0.3.0" +actix-files = "0.5.0" env_logger = "0.9.0" log = "0.4.14" +serde = "1.0.126" +futures-util = "0.3.15" +config = "0.11.0" +petname = "1.1.0" + +[dependencies.byte-unit] +version = "4.0.12" +features = ["serde"] [profile.dev] opt-level = 0 diff --git a/config.toml b/config.toml new file mode 100644 index 0000000..0f09af6 --- /dev/null +++ b/config.toml @@ -0,0 +1,10 @@ +[server] +address="127.0.0.1:8000" +#workers=4 +max_content_length="10MB" +upload_path="./upload" +#auth_token="" # OOPS_SERVER__AUTH_TOKEN= + +[paste] +pet_names = true +default_extension = "txt" diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..874628f --- /dev/null +++ b/src/config.rs @@ -0,0 +1,47 @@ +use byte_unit::Byte; +use config::{self, ConfigError}; +use std::path::PathBuf; + +/// Configuration values. +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct Config { + /// Server configuration. + pub server: ServerConfig, + /// Paste configuration. + pub paste: PasteConfig, +} + +/// Server configuration. +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct ServerConfig { + /// The socket address to bind. + pub address: String, + /// Number of workers to start. + pub workers: Option, + /// Maximum content length. + pub max_content_length: Byte, + /// Storage path. + pub upload_path: PathBuf, + /// Authentication token. + pub auth_token: Option, +} + +/// Paste configuration. +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct PasteConfig { + /// Use pet names instead of original file names. + pub pet_names: bool, + /// Default file extension. + pub default_extension: String, +} + +impl Config { + /// Parses the config file and returns the values. + pub fn parse(file_name: &str) -> Result { + let mut config = config::Config::default(); + config + .merge(config::File::with_name(file_name))? + .merge(config::Environment::with_prefix(env!("CARGO_PKG_NAME")).separator("__"))?; + config.try_into() + } +} diff --git a/src/file.rs b/src/file.rs new file mode 100644 index 0000000..f10053e --- /dev/null +++ b/src/file.rs @@ -0,0 +1,39 @@ +use crate::config::Config; +use std::fs::File; +use std::io::{Result as IoResult, Write}; + +/// Writes the bytes to a file in upload directory. +/// +/// - If `file_name` does not have an extension, it is replaced with [`default_extension`]. +/// - If `file_name` is "-", it is replaced with "stdin". +/// - If [`pet_names`] is `true`, `file_name` is replaced with a pet name. +/// +/// [`default_extension`]: crate::config::PasteConfig::default_extension +/// [`pet_names`]: crate::config::PasteConfig::pet_names +pub fn save(mut file_name: &str, bytes: &[u8], config: &Config) -> IoResult { + if file_name == "-" { + file_name = "stdin"; + } + let mut path = config.server.upload_path.join(file_name); + match path.clone().extension() { + Some(extension) => { + if config.paste.pet_names { + path.set_file_name(petname::petname(2, "-")); + path.set_extension(extension); + } + } + None => { + if config.paste.pet_names { + path.set_file_name(petname::petname(2, "-")); + } + path.set_extension(&config.paste.default_extension); + } + } + let mut buffer = File::create(&path)?; + buffer.write_all(bytes)?; + Ok(path + .file_name() + .map(|v| v.to_string_lossy()) + .unwrap_or_default() + .to_string()) +} diff --git a/src/header.rs b/src/header.rs new file mode 100644 index 0000000..f7d1b3e --- /dev/null +++ b/src/header.rs @@ -0,0 +1,49 @@ +use actix_web::http::header::{ + ContentDisposition as ActixContentDisposition, DispositionParam, DispositionType, +}; +use actix_web::{error, Error as ActixError}; +use std::convert::TryFrom; + +/// Wrapper for Actix content disposition header. +/// +/// Aims to parse the file data from multipart body. +/// +/// e.g. `Content-Disposition: form-data; name="field_name"; filename="filename.jpg"` +pub struct ContentDisposition { + inner: ActixContentDisposition, +} + +impl TryFrom> for ContentDisposition { + type Error = ActixError; + fn try_from(content_disposition: Option) -> Result { + match content_disposition { + Some(inner) => Ok(Self { inner }), + None => Err(error::ErrorUnprocessableEntity( + "content disposition does not exist", + )), + } + } +} + +impl ContentDisposition { + /// Checks if the content disposition is a form data + /// and has the field `field_name`. + pub fn has_form_field(&self, field_name: &str) -> bool { + self.inner.disposition == DispositionType::FormData + && self + .inner + .parameters + .contains(&DispositionParam::Name(field_name.to_string())) + } + + /// Parses the file name from parameters if it exists. + pub fn get_file_name(&self) -> Result<&str, ActixError> { + self.inner + .parameters + .iter() + .find(|param| param.is_filename()) + .map(|param| param.as_filename()) + .flatten() + .ok_or_else(|| error::ErrorUnprocessableEntity("file data not present")) + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..586d2f1 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,14 @@ +//! oops is a file upload/pastebin service. +#![warn(missing_docs, clippy::unwrap_used)] + +/// Configuration file parser. +pub mod config; + +/// Server routes. +pub mod server; + +/// File handler. +pub mod file; + +/// HTTP headers. +pub mod header; diff --git a/src/main.rs b/src/main.rs index 7fce91e..2be9f10 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,25 +1,25 @@ -#[macro_use] extern crate log; use actix_web::middleware::Logger; -use actix_web::{web, App, HttpResponse, HttpServer, Responder}; - -async fn index() -> impl Responder { - HttpResponse::Ok().body("Hello world!") -} - -fn config(cfg: &mut web::ServiceConfig) { - cfg.service( - web::resource("/") - .route(web::get().to(index)) - .route(web::head().to(|| HttpResponse::MethodNotAllowed())), - ); -} +use actix_web::{App, HttpServer}; +use oops::config::Config; +use oops::server; +use std::fs; +use std::io::Result as IoResult; #[actix_web::main] -async fn main() -> std::io::Result<()> { +async fn main() -> IoResult<()> { env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); - HttpServer::new(|| App::new().wrap(Logger::default()).configure(config)) - .bind("127.0.0.1:8000")? - .workers(4) - .run() - .await + let config = Config::parse("config").expect("failed to parse config"); + let server_config = config.server.clone(); + fs::create_dir_all(server_config.upload_path)?; + let mut http_server = HttpServer::new(move || { + App::new() + .data(config.clone()) + .wrap(Logger::default()) + .configure(server::configure_routes) + }) + .bind(server_config.address)?; + if let Some(workers) = server_config.workers { + http_server = http_server.workers(workers); + } + http_server.run().await } diff --git a/src/server.rs b/src/server.rs new file mode 100644 index 0000000..76f9a23 --- /dev/null +++ b/src/server.rs @@ -0,0 +1,90 @@ +use crate::config::Config; +use crate::file; +use crate::header::ContentDisposition; +use actix_files::NamedFile; +use actix_multipart::Multipart; +use actix_web::http::header::AUTHORIZATION; +use actix_web::{error, get, post, web, Error, HttpRequest, HttpResponse, Responder}; +use byte_unit::Byte; +use futures_util::stream::StreamExt; +use std::convert::TryFrom; + +/// Shows the landing page. +#[get("/")] +async fn index() -> impl Responder { + HttpResponse::Ok().body("oops!") +} + +/// Serves a file from the upload directory. +#[get("/{file}")] +async fn serve( + request: HttpRequest, + path: web::Path, + config: web::Data, +) -> Result { + let path = config.server.upload_path.join(&*path); + let file = NamedFile::open(&path)? + .disable_content_disposition() + .prefer_utf8(true); + let response = file.into_response(&request)?; + Ok(response) +} + +/// Handles file upload by processing `multipart/form-data`. +#[post("/")] +async fn upload( + request: HttpRequest, + mut payload: Multipart, + config: web::Data, +) -> Result { + if let Some(token) = &config.server.auth_token { + if request + .headers() + .get(AUTHORIZATION) + .map(|v| v.to_str().unwrap_or_default()) + .map(|v| v.split_whitespace().last().unwrap_or_default()) + != Some(token) + { + return Err(error::ErrorUnauthorized("unauthorized")); + } + } + let mut urls: Vec = Vec::new(); + while let Some(item) = payload.next().await { + let mut field = item?; + let content = ContentDisposition::try_from(field.content_disposition())?; + if content.has_form_field("file") { + let mut bytes = Vec::::new(); + while let Some(chunk) = field.next().await { + bytes.append(&mut chunk?.to_vec()); + } + if bytes.len() as u128 > config.server.max_content_length.get_bytes() { + return Err(error::ErrorPayloadTooLarge("upload limit exceeded")); + } + let file_name = &file::save(content.get_file_name()?, &bytes, &config)?; + let connection = request.connection_info(); + log::info!( + "{} ({}) is uploaded from {}", + file_name, + Byte::from_bytes(bytes.len() as u128).get_appropriate_unit(false), + connection.remote_addr().unwrap_or("unknown host") + ); + urls.push(format!( + "{}://{}/{}\n", + connection.scheme(), + connection.host(), + file_name + )); + } else { + return Err(error::ErrorUnprocessableEntity("invalid form parameters")); + } + } + Ok(HttpResponse::Ok().body(urls.join(""))) +} + +/// Configures the server routes. +pub fn configure_routes(cfg: &mut web::ServiceConfig) { + cfg.service(index) + .service(serve) + .service(upload) + .route("", web::head().to(HttpResponse::MethodNotAllowed)); +}