feat(server): support file upload and serving

This commit is contained in:
orhun 2021-07-23 22:52:00 +03:00
parent df0d22dcb3
commit 1413335519
No known key found for this signature in database
GPG Key ID: F83424824B3E4B90
10 changed files with 679 additions and 45 deletions

9
.gitignore vendored
View File

@ -1 +1,8 @@
/target
# Compiled files and executables
/target/
# Backup files generated by rustfmt
**/*.rs.bk
# Default upload directory
/upload/

414
Cargo.lock generated
View File

@ -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",
]

View File

@ -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 <orhunparmaksiz@gmail.com>"]
[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

10
config.toml Normal file
View File

@ -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"

47
src/config.rs Normal file
View File

@ -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<usize>,
/// Maximum content length.
pub max_content_length: Byte,
/// Storage path.
pub upload_path: PathBuf,
/// Authentication token.
pub auth_token: Option<String>,
}
/// 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<Config, ConfigError> {
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()
}
}

39
src/file.rs Normal file
View File

@ -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<String> {
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())
}

49
src/header.rs Normal file
View File

@ -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<Option<ActixContentDisposition>> for ContentDisposition {
type Error = ActixError;
fn try_from(content_disposition: Option<ActixContentDisposition>) -> Result<Self, Self::Error> {
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"))
}
}

14
src/lib.rs Normal file
View File

@ -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;

View File

@ -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
}

90
src/server.rs Normal file
View File

@ -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<String>,
config: web::Data<Config>,
) -> Result<HttpResponse, Error> {
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<Config>,
) -> Result<HttpResponse, Error> {
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<String> = 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::<u8>::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));
}