1use crate::auth::{extract_tokens, handle_unauthorized_error, unauthorized_error};
2use crate::config::{Config, LandingPageConfig, TokenType};
3use crate::file::Directory;
4use crate::header::{self, ContentDisposition};
5use crate::mime as mime_util;
6use crate::paste::{Paste, PasteType};
7use crate::util::{self, safe_path_join};
8use actix_files::NamedFile;
9use actix_multipart::Multipart;
10use actix_web::http::StatusCode;
11use actix_web::middleware::ErrorHandlers;
12use actix_web::{delete, error, get, post, web, Error, HttpRequest, HttpResponse};
13use actix_web_grants::GrantsMiddleware;
14use awc::Client;
15use byte_unit::{Byte, UnitType};
16use futures_util::stream::StreamExt;
17use mime::TEXT_PLAIN_UTF_8;
18use serde::{Deserialize, Serialize};
19use std::convert::TryFrom;
20use std::env;
21use std::fs;
22use std::path::PathBuf;
23use std::sync::RwLock;
24use std::time::{Duration, UNIX_EPOCH};
25use uts2ts;
26
27#[get("/")]
29#[allow(deprecated)]
30async fn index(config: web::Data<RwLock<Config>>) -> Result<HttpResponse, Error> {
31 let mut config = config
32 .read()
33 .map_err(|_| error::ErrorInternalServerError("cannot acquire config"))?
34 .clone();
35 let redirect = HttpResponse::Found()
36 .append_header(("Location", env!("CARGO_PKG_HOMEPAGE")))
37 .finish();
38 if config.server.landing_page.is_some() {
39 if config.landing_page.is_none() {
40 config.landing_page = Some(LandingPageConfig::default());
41 }
42 if let Some(ref mut landing_page) = config.landing_page {
43 landing_page.text = config.server.landing_page;
44 }
45 }
46 if config.server.landing_page_content_type.is_some() {
47 if config.landing_page.is_none() {
48 config.landing_page = Some(LandingPageConfig::default());
49 }
50 if let Some(ref mut landing_page) = config.landing_page {
51 landing_page.content_type = config.server.landing_page_content_type;
52 }
53 }
54 if let Some(mut landing_page) = config.landing_page {
55 if let Some(file) = landing_page.file {
56 landing_page.text = fs::read_to_string(file).ok();
57 }
58 match landing_page.text {
59 Some(page) => Ok(HttpResponse::Ok()
60 .content_type(
61 landing_page
62 .content_type
63 .unwrap_or(TEXT_PLAIN_UTF_8.to_string()),
64 )
65 .body(page)),
66 None => Ok(redirect),
67 }
68 } else {
69 Ok(redirect)
70 }
71}
72
73#[derive(Debug, Deserialize)]
75struct ServeOptions {
76 download: bool,
79}
80
81#[get("/{file}")]
83async fn serve(
84 request: HttpRequest,
85 file: web::Path<String>,
86 options: Option<web::Query<ServeOptions>>,
87 config: web::Data<RwLock<Config>>,
88) -> Result<HttpResponse, Error> {
89 let config = config
90 .read()
91 .map_err(|_| error::ErrorInternalServerError("cannot acquire config"))?;
92 let mut path = util::glob_match_file(safe_path_join(&config.server.upload_path, &*file)?)?;
93 let mut paste_type = PasteType::File;
94 if !path.exists() || path.is_dir() {
95 for type_ in &[PasteType::Url, PasteType::Oneshot, PasteType::OneshotUrl] {
96 let alt_path = safe_path_join(type_.get_path(&config.server.upload_path)?, &*file)?;
97 let alt_path = util::glob_match_file(alt_path)?;
98 if alt_path.exists()
99 || path.file_name().and_then(|v| v.to_str()) == Some(&type_.get_dir())
100 {
101 path = alt_path;
102 paste_type = *type_;
103 break;
104 }
105 }
106 }
107 if !path.is_file() || !path.exists() {
108 return Err(error::ErrorNotFound("file is not found or expired :(\n"));
109 }
110 match paste_type {
111 PasteType::File | PasteType::RemoteFile | PasteType::Oneshot => {
112 let mime_type = if options.map(|v| v.download).unwrap_or(false) {
113 mime::APPLICATION_OCTET_STREAM
114 } else {
115 mime_util::get_mime_type(&config.paste.mime_override, file.to_string())
116 .map_err(error::ErrorInternalServerError)?
117 };
118 let response = NamedFile::open(&path)?
119 .disable_content_disposition()
120 .set_content_type(mime_type)
121 .prefer_utf8(true)
122 .into_response(&request);
123 if paste_type.is_oneshot() {
124 fs::rename(
125 &path,
126 path.with_file_name(format!(
127 "{}.{}",
128 file,
129 util::get_system_time()?.as_millis()
130 )),
131 )?;
132 }
133 Ok(response)
134 }
135 PasteType::Url => Ok(HttpResponse::Found()
136 .append_header(("Location", fs::read_to_string(&path)?))
137 .finish()),
138 PasteType::OneshotUrl => {
139 let resp = HttpResponse::Found()
140 .append_header(("Location", fs::read_to_string(&path)?))
141 .finish();
142 fs::rename(
143 &path,
144 path.with_file_name(format!("{}.{}", file, util::get_system_time()?.as_millis())),
145 )?;
146 Ok(resp)
147 }
148 }
149}
150
151#[delete("/{file}")]
153#[actix_web_grants::protect("TokenType::Delete", ty = TokenType, error = unauthorized_error)]
154async fn delete(
155 file: web::Path<String>,
156 config: web::Data<RwLock<Config>>,
157) -> Result<HttpResponse, Error> {
158 let config = config
159 .read()
160 .map_err(|_| error::ErrorInternalServerError("cannot acquire config"))?;
161 let path = util::glob_match_file(safe_path_join(&config.server.upload_path, &*file)?)?;
162 if !path.is_file() || !path.exists() {
163 return Err(error::ErrorNotFound("file is not found or expired :(\n"));
164 }
165 match fs::remove_file(path) {
166 Ok(_) => info!("deleted file: {:?}", file.to_string()),
167 Err(e) => {
168 error!("cannot delete file: {}", e);
169 return Err(error::ErrorInternalServerError("cannot delete file"));
170 }
171 }
172 Ok(HttpResponse::Ok().body(String::from("file deleted\n")))
173}
174
175#[get("/version")]
177#[actix_web_grants::protect("TokenType::Auth", ty = TokenType, error = unauthorized_error)]
178async fn version(config: web::Data<RwLock<Config>>) -> Result<HttpResponse, Error> {
179 let config = config
180 .read()
181 .map_err(|_| error::ErrorInternalServerError("cannot acquire config"))?;
182 if !config.server.expose_version.unwrap_or(false) {
183 warn!("server is not configured to expose version endpoint");
184 Err(error::ErrorNotFound(""))?;
185 }
186
187 let version = env!("CARGO_PKG_VERSION");
188 Ok(HttpResponse::Ok().body(version.to_owned() + "\n"))
189}
190
191#[post("/")]
193#[actix_web_grants::protect("TokenType::Auth", ty = TokenType, error = unauthorized_error)]
194async fn upload(
195 request: HttpRequest,
196 mut payload: Multipart,
197 client: web::Data<Client>,
198 config: web::Data<RwLock<Config>>,
199) -> Result<HttpResponse, Error> {
200 let connection = request.connection_info().clone();
201 let host = connection.realip_remote_addr().unwrap_or("unknown host");
202 let server_url = match config
203 .read()
204 .map_err(|_| error::ErrorInternalServerError("cannot acquire config"))?
205 .server
206 .url
207 .clone()
208 {
209 Some(v) => v,
210 None => {
211 format!("{}://{}", connection.scheme(), connection.host(),)
212 }
213 };
214 let time = util::get_system_time()?;
215 let mut expiry_date = header::parse_expiry_date(request.headers(), time)?;
216 if expiry_date.is_none() {
217 expiry_date = config
218 .read()
219 .map_err(|_| error::ErrorInternalServerError("cannot acquire config"))?
220 .paste
221 .default_expiry
222 .and_then(|v| time.checked_add(v).map(|t| t.as_millis()));
223 }
224 let mut urls: Vec<String> = Vec::new();
225 while let Some(item) = payload.next().await {
226 let header_filename = header::parse_header_filename(request.headers())?;
227 let mut field = item?;
228 let content = ContentDisposition::from(
229 field
230 .content_disposition()
231 .ok_or_else(|| {
232 error::ErrorInternalServerError("payload must contain content disposition")
233 })?
234 .clone(),
235 );
236 if let Ok(paste_type) = PasteType::try_from(&content) {
237 let mut bytes = Vec::<u8>::new();
238 while let Some(chunk) = field.next().await {
239 bytes.append(&mut chunk?.to_vec());
240 }
241 if bytes.is_empty() {
242 warn!("{} sent zero bytes", host);
243 return Err(error::ErrorBadRequest("invalid file size"));
244 }
245 if paste_type != PasteType::Oneshot
246 && paste_type != PasteType::RemoteFile
247 && paste_type != PasteType::OneshotUrl
248 && expiry_date.is_none()
249 && !config
250 .read()
251 .map_err(|_| error::ErrorInternalServerError("cannot acquire config"))?
252 .paste
253 .duplicate_files
254 .unwrap_or(true)
255 {
256 let bytes_checksum = util::sha256_digest(&*bytes)?;
257 let config = config
258 .read()
259 .map_err(|_| error::ErrorInternalServerError("cannot acquire config"))?;
260 if let Some(file) = Directory::try_from(config.server.upload_path.as_path())?
261 .get_file(bytes_checksum)
262 {
263 urls.push(format!(
264 "{}/{}\n",
265 server_url,
266 file.path
267 .file_name()
268 .map(|v| v.to_string_lossy())
269 .unwrap_or_default()
270 ));
271 continue;
272 }
273 }
274 let mut paste = Paste {
275 data: bytes.to_vec(),
276 type_: paste_type,
277 };
278 let mut file_name = match paste.type_ {
279 PasteType::File | PasteType::Oneshot => {
280 let config = config
281 .read()
282 .map_err(|_| error::ErrorInternalServerError("cannot acquire config"))?;
283 paste.store_file(
284 content.get_file_name()?,
285 expiry_date,
286 header_filename,
287 &config,
288 )?
289 }
290 PasteType::RemoteFile => {
291 paste
292 .store_remote_file(expiry_date, &client, &config)
293 .await?
294 }
295 PasteType::Url | PasteType::OneshotUrl => {
296 let config = config
297 .read()
298 .map_err(|_| error::ErrorInternalServerError("cannot acquire config"))?;
299 paste.store_url(expiry_date, header_filename, &config)?
300 }
301 };
302 info!(
303 "{} ({}) is uploaded from {}",
304 file_name,
305 Byte::from_u128(paste.data.len() as u128)
306 .unwrap_or_default()
307 .get_appropriate_unit(UnitType::Decimal),
308 host
309 );
310 let config = config
311 .read()
312 .map_err(|_| error::ErrorInternalServerError("cannot acquire config"))?;
313 if let Some(handle_spaces_config) = config.server.handle_spaces {
314 file_name = handle_spaces_config.process_filename(&file_name);
315 }
316 urls.push(format!("{}/{}\n", server_url, file_name));
317 } else {
318 warn!("{} sent an invalid form field", host);
319 return Err(error::ErrorBadRequest("invalid form field"));
320 }
321 }
322 Ok(HttpResponse::Ok().body(urls.join("")))
323}
324
325#[derive(Serialize, Deserialize)]
327pub struct ListItem {
328 pub file_name: PathBuf,
330 pub file_size: u64,
332 pub creation_date_utc: Option<String>,
334 pub expires_at_utc: Option<String>,
336}
337
338#[get("/list")]
340#[actix_web_grants::protect("TokenType::Auth", ty = TokenType, error = unauthorized_error)]
341async fn list(config: web::Data<RwLock<Config>>) -> Result<HttpResponse, Error> {
342 let config = config
343 .read()
344 .map_err(|_| error::ErrorInternalServerError("cannot acquire config"))?
345 .clone();
346 if !config.server.expose_list.unwrap_or(false) {
347 warn!("server is not configured to expose list endpoint");
348 Err(error::ErrorNotFound(""))?;
349 }
350 let entries: Vec<ListItem> = fs::read_dir(config.server.upload_path)?
351 .filter_map(|entry| {
352 entry.ok().and_then(|e| {
353 let metadata = match e.metadata() {
354 Ok(metadata) => {
355 if metadata.is_dir() {
356 return None;
357 }
358 metadata
359 }
360 Err(e) => {
361 error!("failed to read metadata: {e}");
362 return None;
363 }
364 };
365 let mut file_name = PathBuf::from(e.file_name());
366
367 let creation_date_utc = metadata.created().ok().map(|v| {
368 let millis = v
369 .duration_since(UNIX_EPOCH)
370 .expect("Time since UNIX epoch should be valid.")
371 .as_millis();
372 uts2ts::uts2ts(
373 i64::try_from(millis).expect("UNIX time should be smaller than i64::MAX")
374 / 1000,
375 )
376 .as_string()
377 });
378
379 let expires_at_utc = if let Some(expiration) = file_name
380 .extension()
381 .and_then(|ext| ext.to_str())
382 .and_then(|v| v.parse::<i64>().ok())
383 {
384 file_name.set_extension("");
385 if util::get_system_time().ok()?
386 > Duration::from_millis(expiration.try_into().ok()?)
387 {
388 return None;
389 }
390 Some(uts2ts::uts2ts(expiration / 1000).as_string())
391 } else {
392 None
393 };
394 Some(ListItem {
395 file_name,
396 file_size: metadata.len(),
397 creation_date_utc,
398 expires_at_utc,
399 })
400 })
401 })
402 .collect();
403 Ok(HttpResponse::Ok().json(entries))
404}
405
406pub fn configure_routes(cfg: &mut web::ServiceConfig) {
408 cfg.service(
409 web::scope("")
410 .service(index)
411 .service(version)
412 .service(list)
413 .service(serve)
414 .service(upload)
415 .service(delete)
416 .route("", web::head().to(HttpResponse::MethodNotAllowed))
417 .wrap(GrantsMiddleware::with_extractor(extract_tokens))
418 .wrap(
419 ErrorHandlers::new().handler(StatusCode::UNAUTHORIZED, handle_unauthorized_error),
420 ),
421 );
422}
423
424#[cfg(test)]
425mod tests {
426 use super::*;
427 use crate::config::LandingPageConfig;
428 use crate::middleware::ContentLengthLimiter;
429 use crate::random::{RandomURLConfig, RandomURLType};
430 use actix_web::body::MessageBody;
431 use actix_web::body::{BodySize, BoxBody};
432 use actix_web::error::Error;
433 use actix_web::http::header::AUTHORIZATION;
434 use actix_web::http::{header, StatusCode};
435 use actix_web::test::{self, TestRequest};
436 use actix_web::web::Data;
437 use actix_web::App;
438 use awc::ClientBuilder;
439 use glob::glob;
440 use std::fs::File;
441 use std::io::Write;
442 use std::path::PathBuf;
443 use std::str;
444 use std::thread;
445 use std::time::Duration;
446
447 fn get_multipart_request(data: &str, name: &str, filename: &str) -> TestRequest {
448 let multipart_data = format!(
449 "\r\n\
450 --multipart_bound\r\n\
451 Content-Disposition: form-data; name=\"{}\"; filename=\"{}\"\r\n\
452 Content-Type: text/plain; charset=utf-8\r\nContent-Length: {}\r\n\r\n\
453 {}\r\n\
454 --multipart_bound--\r\n",
455 name,
456 filename,
457 data.bytes().len(),
458 data,
459 );
460 TestRequest::post()
461 .insert_header((
462 header::CONTENT_TYPE,
463 header::HeaderValue::from_static("multipart/mixed; boundary=\"multipart_bound\""),
464 ))
465 .insert_header((
466 header::CONTENT_LENGTH,
467 header::HeaderValue::from_str(&data.bytes().len().to_string())
468 .expect("cannot create header value"),
469 ))
470 .set_payload(multipart_data)
471 }
472
473 async fn assert_body(body: BoxBody, expected: &str) -> Result<(), Error> {
474 if let BodySize::Sized(size) = body.size() {
475 assert_eq!(size, expected.len() as u64);
476 let body_bytes = actix_web::body::to_bytes(body).await?;
477 let body_text = str::from_utf8(&body_bytes)?;
478 assert_eq!(expected, body_text);
479 Ok(())
480 } else {
481 Err(error::ErrorInternalServerError("unexpected body type"))
482 }
483 }
484
485 #[actix_web::test]
486 async fn test_index() {
487 let config = Config::default();
488 let app = test::init_service(
489 App::new()
490 .app_data(Data::new(RwLock::new(config)))
491 .service(index),
492 )
493 .await;
494 let request = TestRequest::default()
495 .insert_header(("content-type", "text/plain"))
496 .to_request();
497 let response = test::call_service(&app, request).await;
498 assert_eq!(StatusCode::FOUND, response.status());
499 }
500
501 #[actix_web::test]
502 async fn test_index_with_landing_page() -> Result<(), Error> {
503 let config = Config {
504 landing_page: Some(LandingPageConfig {
505 text: Some(String::from("landing page")),
506 ..Default::default()
507 }),
508 ..Default::default()
509 };
510 let app = test::init_service(
511 App::new()
512 .app_data(Data::new(RwLock::new(config)))
513 .service(index),
514 )
515 .await;
516 let request = TestRequest::default()
517 .insert_header(("content-type", "text/plain"))
518 .to_request();
519 let response = test::call_service(&app, request).await;
520 assert_eq!(StatusCode::OK, response.status());
521 assert_body(response.into_body(), "landing page").await?;
522 Ok(())
523 }
524
525 #[actix_web::test]
526 async fn test_index_with_landing_page_file() -> Result<(), Error> {
527 let filename = "landing_page.txt";
528 let config = Config {
529 landing_page: Some(LandingPageConfig {
530 file: Some(filename.to_string()),
531 ..Default::default()
532 }),
533 ..Default::default()
534 };
535 let mut file = File::create(filename)?;
536 file.write_all("landing page from file".as_bytes())?;
537 let app = test::init_service(
538 App::new()
539 .app_data(Data::new(RwLock::new(config)))
540 .service(index),
541 )
542 .await;
543 let request = TestRequest::default()
544 .insert_header(("content-type", "text/plain"))
545 .to_request();
546 let response = test::call_service(&app, request).await;
547 assert_eq!(StatusCode::OK, response.status());
548 assert_body(response.into_body(), "landing page from file").await?;
549 fs::remove_file(filename)?;
550 Ok(())
551 }
552
553 #[actix_web::test]
554 async fn test_index_with_landing_page_file_not_found() -> Result<(), Error> {
555 let filename = "landing_page.txt";
556 let config = Config {
557 landing_page: Some(LandingPageConfig {
558 text: Some(String::from("landing page")),
559 file: Some(filename.to_string()),
560 ..Default::default()
561 }),
562 ..Default::default()
563 };
564 let app = test::init_service(
565 App::new()
566 .app_data(Data::new(RwLock::new(config)))
567 .service(index),
568 )
569 .await;
570 let request = TestRequest::default()
571 .insert_header(("content-type", "text/plain"))
572 .to_request();
573 let response = test::call_service(&app, request).await;
574 assert_eq!(StatusCode::FOUND, response.status());
575 Ok(())
576 }
577
578 #[actix_web::test]
579 async fn test_version_without_auth() -> Result<(), Error> {
580 let mut config = Config::default();
581 config.server.auth_tokens = Some(["test".to_string()].into());
582 let app = test::init_service(
583 App::new()
584 .app_data(Data::new(RwLock::new(config)))
585 .app_data(Data::new(Client::default()))
586 .configure(configure_routes),
587 )
588 .await;
589
590 let request = TestRequest::default()
591 .insert_header(("content-type", "text/plain"))
592 .uri("/version")
593 .to_request();
594 let response = test::call_service(&app, request).await;
595 assert_eq!(StatusCode::UNAUTHORIZED, response.status());
596 assert_body(response.into_body(), "unauthorized\n").await?;
597 Ok(())
598 }
599
600 #[actix_web::test]
601 async fn test_version_without_config() -> Result<(), Error> {
602 let app = test::init_service(
603 App::new()
604 .app_data(Data::new(RwLock::new(Config::default())))
605 .app_data(Data::new(Client::default()))
606 .configure(configure_routes),
607 )
608 .await;
609
610 let request = TestRequest::default()
611 .insert_header(("content-type", "text/plain"))
612 .uri("/version")
613 .to_request();
614 let response = test::call_service(&app, request).await;
615 assert_eq!(StatusCode::NOT_FOUND, response.status());
616 assert_body(response.into_body(), "").await?;
617 Ok(())
618 }
619
620 #[actix_web::test]
621 async fn test_version() -> Result<(), Error> {
622 let mut config = Config::default();
623 config.server.expose_version = Some(true);
624 let app = test::init_service(
625 App::new()
626 .app_data(Data::new(RwLock::new(config)))
627 .app_data(Data::new(Client::default()))
628 .configure(configure_routes),
629 )
630 .await;
631
632 let request = TestRequest::default()
633 .insert_header(("content-type", "text/plain"))
634 .uri("/version")
635 .to_request();
636 let response = test::call_service(&app, request).await;
637 assert_eq!(StatusCode::OK, response.status());
638 assert_body(
639 response.into_body(),
640 &(env!("CARGO_PKG_VERSION").to_owned() + "\n"),
641 )
642 .await?;
643 Ok(())
644 }
645
646 #[actix_web::test]
647 async fn test_list() -> Result<(), Error> {
648 let mut config = Config::default();
649 config.server.expose_list = Some(true);
650
651 let test_upload_dir = "test_upload";
652 fs::create_dir(test_upload_dir)?;
653 config.server.upload_path = PathBuf::from(test_upload_dir);
654
655 let app = test::init_service(
656 App::new()
657 .app_data(Data::new(RwLock::new(config)))
658 .app_data(Data::new(Client::default()))
659 .configure(configure_routes),
660 )
661 .await;
662
663 let filename = "test_file.txt";
664 let timestamp = util::get_system_time()?.as_secs().to_string();
665 test::call_service(
666 &app,
667 get_multipart_request(×tamp, "file", filename).to_request(),
668 )
669 .await;
670
671 let request = TestRequest::default()
672 .insert_header(("content-type", "text/plain"))
673 .uri("/list")
674 .to_request();
675 let result: Vec<ListItem> = test::call_and_read_body_json(&app, request).await;
676
677 assert_eq!(result.len(), 1);
678 assert_eq!(
679 result.first().expect("json object").file_name,
680 PathBuf::from(filename)
681 );
682
683 fs::remove_dir_all(test_upload_dir)?;
684
685 Ok(())
686 }
687
688 #[actix_web::test]
689 async fn test_list_expired() -> Result<(), Error> {
690 let mut config = Config::default();
691 config.server.expose_list = Some(true);
692
693 let test_upload_dir = "test_upload";
694 fs::create_dir(test_upload_dir)?;
695 config.server.upload_path = PathBuf::from(test_upload_dir);
696
697 let app = test::init_service(
698 App::new()
699 .app_data(Data::new(RwLock::new(config)))
700 .app_data(Data::new(Client::default()))
701 .configure(configure_routes),
702 )
703 .await;
704
705 let filename = "test_file.txt";
706 let timestamp = util::get_system_time()?.as_secs().to_string();
707 test::call_service(
708 &app,
709 get_multipart_request(×tamp, "file", filename)
710 .insert_header((
711 header::HeaderName::from_static("expire"),
712 header::HeaderValue::from_static("50ms"),
713 ))
714 .to_request(),
715 )
716 .await;
717
718 thread::sleep(Duration::from_millis(500));
719
720 let request = TestRequest::default()
721 .insert_header(("content-type", "text/plain"))
722 .uri("/list")
723 .to_request();
724 let result: Vec<ListItem> = test::call_and_read_body_json(&app, request).await;
725
726 assert!(result.is_empty());
727
728 fs::remove_dir_all(test_upload_dir)?;
729
730 Ok(())
731 }
732
733 #[actix_web::test]
734 async fn test_auth() -> Result<(), Error> {
735 let mut config = Config::default();
736 config.server.auth_tokens = Some(["test".to_string()].into());
737
738 let app = test::init_service(
739 App::new()
740 .app_data(Data::new(RwLock::new(config)))
741 .app_data(Data::new(Client::default()))
742 .configure(configure_routes),
743 )
744 .await;
745
746 let response =
747 test::call_service(&app, get_multipart_request("", "", "").to_request()).await;
748 assert_eq!(StatusCode::UNAUTHORIZED, response.status());
749 assert_body(response.into_body(), "unauthorized\n").await?;
750
751 Ok(())
752 }
753
754 #[actix_web::test]
755 async fn test_payload_limit() -> Result<(), Error> {
756 let app = test::init_service(
757 App::new()
758 .app_data(Data::new(RwLock::new(Config::default())))
759 .app_data(Data::new(Client::default()))
760 .wrap(ContentLengthLimiter::new(Byte::from_u64(1)))
761 .configure(configure_routes),
762 )
763 .await;
764
765 let response = test::call_service(
766 &app,
767 get_multipart_request("test", "file", "test").to_request(),
768 )
769 .await;
770 assert_eq!(StatusCode::PAYLOAD_TOO_LARGE, response.status());
771 assert_body(response.into_body().boxed(), "upload limit exceeded").await?;
772
773 Ok(())
774 }
775
776 #[actix_web::test]
777 async fn test_delete_file() -> Result<(), Error> {
778 let mut config = Config::default();
779 config.server.delete_tokens = Some(["test".to_string()].into());
780 config.server.upload_path = env::current_dir()?;
781
782 let app = test::init_service(
783 App::new()
784 .app_data(Data::new(RwLock::new(config)))
785 .app_data(Data::new(Client::default()))
786 .configure(configure_routes),
787 )
788 .await;
789
790 let file_name = "test_file.txt";
791 let timestamp = util::get_system_time()?.as_secs().to_string();
792 test::call_service(
793 &app,
794 get_multipart_request(×tamp, "file", file_name).to_request(),
795 )
796 .await;
797
798 let request = TestRequest::delete()
799 .insert_header((AUTHORIZATION, header::HeaderValue::from_static("test")))
800 .uri(&format!("/{file_name}"))
801 .to_request();
802 let response = test::call_service(&app, request).await;
803
804 assert_eq!(StatusCode::OK, response.status());
805 assert_body(response.into_body(), "file deleted\n").await?;
806
807 let path = PathBuf::from(file_name);
808 assert!(!path.exists());
809
810 Ok(())
811 }
812
813 #[actix_web::test]
814 async fn test_delete_file_without_token_in_config() -> Result<(), Error> {
815 let mut config = Config::default();
816 config.server.upload_path = env::current_dir()?;
817
818 let app = test::init_service(
819 App::new()
820 .app_data(Data::new(RwLock::new(config)))
821 .app_data(Data::new(Client::default()))
822 .configure(configure_routes),
823 )
824 .await;
825
826 let file_name = "test_file.txt";
827 let request = TestRequest::delete()
828 .insert_header((AUTHORIZATION, header::HeaderValue::from_static("test")))
829 .uri(&format!("/{file_name}"))
830 .to_request();
831 let response = test::call_service(&app, request).await;
832
833 assert_eq!(StatusCode::NOT_FOUND, response.status());
834 assert_body(response.into_body(), "").await?;
835
836 Ok(())
837 }
838
839 #[actix_web::test]
840 async fn test_upload_file() -> Result<(), Error> {
841 let mut config = Config::default();
842 config.server.upload_path = env::current_dir()?;
843
844 let app = test::init_service(
845 App::new()
846 .app_data(Data::new(RwLock::new(config)))
847 .app_data(Data::new(Client::default()))
848 .configure(configure_routes),
849 )
850 .await;
851
852 let file_name = "test_file.txt";
853 let timestamp = util::get_system_time()?.as_secs().to_string();
854 let response = test::call_service(
855 &app,
856 get_multipart_request(×tamp, "file", file_name).to_request(),
857 )
858 .await;
859 assert_eq!(StatusCode::OK, response.status());
860 assert_body(
861 response.into_body(),
862 &format!("http://localhost:8080/{file_name}\n"),
863 )
864 .await?;
865
866 let serve_request = TestRequest::get()
867 .uri(&format!("/{file_name}"))
868 .to_request();
869 let response = test::call_service(&app, serve_request).await;
870 assert_eq!(StatusCode::OK, response.status());
871 assert_body(response.into_body(), ×tamp).await?;
872
873 fs::remove_file(file_name)?;
874 let serve_request = TestRequest::get()
875 .uri(&format!("/{file_name}"))
876 .to_request();
877 let response = test::call_service(&app, serve_request).await;
878 assert_eq!(StatusCode::NOT_FOUND, response.status());
879
880 Ok(())
881 }
882
883 #[actix_web::test]
884 async fn test_upload_file_override_filename() -> Result<(), Error> {
885 let mut config = Config::default();
886 config.server.upload_path = env::current_dir()?;
887
888 let app = test::init_service(
889 App::new()
890 .app_data(Data::new(RwLock::new(config)))
891 .app_data(Data::new(Client::default()))
892 .configure(configure_routes),
893 )
894 .await;
895
896 let file_name = "test_file.txt";
897 let header_filename = "fn_from_header.txt";
898 let timestamp = util::get_system_time()?.as_secs().to_string();
899 let response = test::call_service(
900 &app,
901 get_multipart_request(×tamp, "file", file_name)
902 .insert_header((
903 header::HeaderName::from_static("filename"),
904 header::HeaderValue::from_static("fn_from_header.txt"),
905 ))
906 .to_request(),
907 )
908 .await;
909 assert_eq!(StatusCode::OK, response.status());
910 assert_body(
911 response.into_body(),
912 &format!("http://localhost:8080/{header_filename}\n"),
913 )
914 .await?;
915
916 let serve_request = TestRequest::get()
917 .uri(&format!("/{header_filename}"))
918 .to_request();
919 let response = test::call_service(&app, serve_request).await;
920 assert_eq!(StatusCode::OK, response.status());
921 assert_body(response.into_body(), ×tamp).await?;
922
923 fs::remove_file(header_filename)?;
924 let serve_request = TestRequest::get()
925 .uri(&format!("/{header_filename}"))
926 .to_request();
927 let response = test::call_service(&app, serve_request).await;
928 assert_eq!(StatusCode::NOT_FOUND, response.status());
929
930 Ok(())
931 }
932
933 #[actix_web::test]
934 async fn test_upload_same_filename() -> Result<(), Error> {
935 let mut config = Config::default();
936 config.server.upload_path = env::current_dir()?;
937
938 let app = test::init_service(
939 App::new()
940 .app_data(Data::new(RwLock::new(config)))
941 .app_data(Data::new(Client::default()))
942 .configure(configure_routes),
943 )
944 .await;
945
946 let file_name = "test_file.txt";
947 let header_filename = "fn_from_header.txt";
948 let timestamp = util::get_system_time()?.as_secs().to_string();
949 let response = test::call_service(
950 &app,
951 get_multipart_request(×tamp, "file", file_name)
952 .insert_header((
953 header::HeaderName::from_static("filename"),
954 header::HeaderValue::from_static("fn_from_header.txt"),
955 ))
956 .to_request(),
957 )
958 .await;
959 assert_eq!(StatusCode::OK, response.status());
960 assert_body(
961 response.into_body(),
962 &format!("http://localhost:8080/{header_filename}\n"),
963 )
964 .await?;
965
966 let timestamp = util::get_system_time()?.as_secs().to_string();
967 let response = test::call_service(
968 &app,
969 get_multipart_request(×tamp, "file", file_name)
970 .insert_header((
971 header::HeaderName::from_static("filename"),
972 header::HeaderValue::from_static("fn_from_header.txt"),
973 ))
974 .to_request(),
975 )
976 .await;
977 assert_eq!(StatusCode::CONFLICT, response.status());
978 assert_body(response.into_body(), "file already exists\n").await?;
979
980 fs::remove_file(header_filename)?;
981
982 Ok(())
983 }
984
985 #[actix_web::test]
986 #[allow(deprecated)]
987 async fn test_upload_duplicate_file() -> Result<(), Error> {
988 let test_upload_dir = "test_upload";
989 fs::create_dir(test_upload_dir)?;
990
991 let mut config = Config::default();
992 config.server.upload_path = PathBuf::from(&test_upload_dir);
993 config.paste.duplicate_files = Some(false);
994 config.paste.random_url = Some(RandomURLConfig {
995 enabled: Some(true),
996 type_: RandomURLType::Alphanumeric,
997 ..Default::default()
998 });
999
1000 let app = test::init_service(
1001 App::new()
1002 .app_data(Data::new(RwLock::new(config)))
1003 .app_data(Data::new(Client::default()))
1004 .configure(configure_routes),
1005 )
1006 .await;
1007
1008 let response = test::call_service(
1009 &app,
1010 get_multipart_request("test", "file", "x").to_request(),
1011 )
1012 .await;
1013 assert_eq!(StatusCode::OK, response.status());
1014 let body = response.into_body();
1015 let first_body_bytes = actix_web::body::to_bytes(body).await?;
1016
1017 let response = test::call_service(
1018 &app,
1019 get_multipart_request("test", "file", "x").to_request(),
1020 )
1021 .await;
1022 assert_eq!(StatusCode::OK, response.status());
1023 let body = response.into_body();
1024 let second_body_bytes = actix_web::body::to_bytes(body).await?;
1025
1026 assert_eq!(first_body_bytes, second_body_bytes);
1027
1028 fs::remove_dir_all(test_upload_dir)?;
1029
1030 Ok(())
1031 }
1032
1033 #[actix_web::test]
1034 async fn test_upload_expiring_file() -> Result<(), Error> {
1035 let mut config = Config::default();
1036 config.server.upload_path = env::current_dir()?;
1037
1038 let app = test::init_service(
1039 App::new()
1040 .app_data(Data::new(RwLock::new(config)))
1041 .app_data(Data::new(Client::default()))
1042 .configure(configure_routes),
1043 )
1044 .await;
1045
1046 let file_name = "test_file.txt";
1047 let timestamp = util::get_system_time()?.as_secs().to_string();
1048 let response = test::call_service(
1049 &app,
1050 get_multipart_request(×tamp, "file", file_name)
1051 .insert_header((
1052 header::HeaderName::from_static("expire"),
1053 header::HeaderValue::from_static("20ms"),
1054 ))
1055 .to_request(),
1056 )
1057 .await;
1058 assert_eq!(StatusCode::OK, response.status());
1059 assert_body(
1060 response.into_body(),
1061 &format!("http://localhost:8080/{file_name}\n"),
1062 )
1063 .await?;
1064
1065 let serve_request = TestRequest::get()
1066 .uri(&format!("/{file_name}"))
1067 .to_request();
1068 let response = test::call_service(&app, serve_request).await;
1069 assert_eq!(StatusCode::OK, response.status());
1070 assert_body(response.into_body(), ×tamp).await?;
1071
1072 thread::sleep(Duration::from_millis(40));
1073
1074 let serve_request = TestRequest::get()
1075 .uri(&format!("/{file_name}"))
1076 .to_request();
1077 let response = test::call_service(&app, serve_request).await;
1078 assert_eq!(StatusCode::NOT_FOUND, response.status());
1079
1080 if let Some(glob_path) = glob(&format!("{file_name}.[0-9]*"))
1081 .map_err(error::ErrorInternalServerError)?
1082 .next()
1083 {
1084 fs::remove_file(glob_path.map_err(error::ErrorInternalServerError)?)?;
1085 }
1086
1087 Ok(())
1088 }
1089
1090 #[actix_web::test]
1091 async fn test_upload_remote_file() -> Result<(), Error> {
1092 let mut config = Config::default();
1093 config.server.upload_path = env::current_dir()?;
1094 config.server.max_content_length = Byte::from_u128(30000).unwrap_or_default();
1095
1096 let app = test::init_service(
1097 App::new()
1098 .app_data(Data::new(RwLock::new(config)))
1099 .app_data(Data::new(
1100 ClientBuilder::new()
1101 .timeout(Duration::from_secs(30))
1102 .finish(),
1103 ))
1104 .configure(configure_routes),
1105 )
1106 .await;
1107
1108 let file_name =
1109 "rp_test_3b5eeeee7a7326cd6141f54820e6356a0e9d1dd4021407cb1d5e9de9f034ed2f.png";
1110 let response = test::call_service(
1111 &app,
1112 get_multipart_request(
1113 "https://raw.githubusercontent.com/orhun/rustypaste/refs/heads/master/img/rp_test_3b5eeeee7a7326cd6141f54820e6356a0e9d1dd4021407cb1d5e9de9f034ed2f.png",
1114 "remote",
1115 file_name,
1116 )
1117 .to_request(),
1118 )
1119 .await;
1120 assert_eq!(StatusCode::OK, response.status());
1121 assert_body(
1122 response.into_body().boxed(),
1123 &format!("http://localhost:8080/{file_name}\n"),
1124 )
1125 .await?;
1126
1127 let serve_request = TestRequest::get()
1128 .uri(&format!("/{file_name}"))
1129 .to_request();
1130 let response = test::call_service(&app, serve_request).await;
1131 assert_eq!(StatusCode::OK, response.status());
1132
1133 let body = response.into_body();
1134 let body_bytes = actix_web::body::to_bytes(body).await?;
1135 assert_eq!(
1136 "3b5eeeee7a7326cd6141f54820e6356a0e9d1dd4021407cb1d5e9de9f034ed2f",
1137 util::sha256_digest(&*body_bytes)?
1138 );
1139
1140 fs::remove_file(file_name)?;
1141
1142 let serve_request = TestRequest::get()
1143 .uri(&format!("/{file_name}"))
1144 .to_request();
1145 let response = test::call_service(&app, serve_request).await;
1146 assert_eq!(StatusCode::NOT_FOUND, response.status());
1147
1148 Ok(())
1149 }
1150
1151 #[actix_web::test]
1152 async fn test_upload_url() -> Result<(), Error> {
1153 let mut config = Config::default();
1154 config.server.upload_path = env::current_dir()?;
1155
1156 let app = test::init_service(
1157 App::new()
1158 .app_data(Data::new(RwLock::new(config.clone())))
1159 .app_data(Data::new(Client::default()))
1160 .configure(configure_routes),
1161 )
1162 .await;
1163
1164 let url_upload_path = PasteType::Url
1165 .get_path(&config.server.upload_path)
1166 .expect("Bad upload path");
1167 fs::create_dir_all(&url_upload_path)?;
1168
1169 let response = test::call_service(
1170 &app,
1171 get_multipart_request(env!("CARGO_PKG_HOMEPAGE"), "url", "").to_request(),
1172 )
1173 .await;
1174 assert_eq!(StatusCode::OK, response.status());
1175 assert_body(response.into_body(), "http://localhost:8080/url\n").await?;
1176
1177 let serve_request = TestRequest::get().uri("/url").to_request();
1178 let response = test::call_service(&app, serve_request).await;
1179 assert_eq!(StatusCode::FOUND, response.status());
1180
1181 fs::remove_file(url_upload_path.join("url"))?;
1182 fs::remove_dir(url_upload_path)?;
1183
1184 let serve_request = TestRequest::get().uri("/url").to_request();
1185 let response = test::call_service(&app, serve_request).await;
1186 assert_eq!(StatusCode::NOT_FOUND, response.status());
1187
1188 Ok(())
1189 }
1190
1191 #[actix_web::test]
1192 async fn test_upload_oneshot() -> Result<(), Error> {
1193 let mut config = Config::default();
1194 config.server.upload_path = env::current_dir()?;
1195
1196 let app = test::init_service(
1197 App::new()
1198 .app_data(Data::new(RwLock::new(config.clone())))
1199 .app_data(Data::new(Client::default()))
1200 .configure(configure_routes),
1201 )
1202 .await;
1203
1204 let oneshot_upload_path = PasteType::Oneshot
1205 .get_path(&config.server.upload_path)
1206 .expect("Bad upload path");
1207 fs::create_dir_all(&oneshot_upload_path)?;
1208
1209 let file_name = "oneshot.txt";
1210 let timestamp = util::get_system_time()?.as_secs().to_string();
1211 let response = test::call_service(
1212 &app,
1213 get_multipart_request(×tamp, "oneshot", file_name).to_request(),
1214 )
1215 .await;
1216 assert_eq!(StatusCode::OK, response.status());
1217 assert_body(
1218 response.into_body(),
1219 &format!("http://localhost:8080/{file_name}\n"),
1220 )
1221 .await?;
1222
1223 let serve_request = TestRequest::get()
1224 .uri(&format!("/{file_name}"))
1225 .to_request();
1226 let response = test::call_service(&app, serve_request).await;
1227 assert_eq!(StatusCode::OK, response.status());
1228 assert_body(response.into_body(), ×tamp).await?;
1229
1230 let serve_request = TestRequest::get()
1231 .uri(&format!("/{file_name}"))
1232 .to_request();
1233 let response = test::call_service(&app, serve_request).await;
1234 assert_eq!(StatusCode::NOT_FOUND, response.status());
1235
1236 if let Some(glob_path) = glob(
1237 &oneshot_upload_path
1238 .join(format!("{file_name}.[0-9]*"))
1239 .to_string_lossy(),
1240 )
1241 .map_err(error::ErrorInternalServerError)?
1242 .next()
1243 {
1244 fs::remove_file(glob_path.map_err(error::ErrorInternalServerError)?)?;
1245 }
1246 fs::remove_dir(oneshot_upload_path)?;
1247
1248 Ok(())
1249 }
1250
1251 #[actix_web::test]
1252 async fn test_upload_oneshot_url() -> Result<(), Error> {
1253 let mut config = Config::default();
1254 config.server.upload_path = env::current_dir()?;
1255
1256 let oneshot_url_suffix = "oneshot_url";
1257
1258 let app = test::init_service(
1259 App::new()
1260 .app_data(Data::new(RwLock::new(config.clone())))
1261 .app_data(Data::new(Client::default()))
1262 .configure(configure_routes),
1263 )
1264 .await;
1265
1266 let url_upload_path = PasteType::OneshotUrl
1267 .get_path(&config.server.upload_path)
1268 .expect("Bad upload path");
1269 fs::create_dir_all(&url_upload_path)?;
1270
1271 let response = test::call_service(
1272 &app,
1273 get_multipart_request(
1274 env!("CARGO_PKG_HOMEPAGE"),
1275 oneshot_url_suffix,
1276 oneshot_url_suffix,
1277 )
1278 .to_request(),
1279 )
1280 .await;
1281 assert_eq!(StatusCode::OK, response.status());
1282 assert_body(
1283 response.into_body(),
1284 &format!("http://localhost:8080/{}\n", oneshot_url_suffix),
1285 )
1286 .await?;
1287
1288 let serve_request = TestRequest::with_uri(&format!("/{}", oneshot_url_suffix)).to_request();
1290 let response = test::call_service(&app, serve_request).await;
1291 assert_eq!(StatusCode::FOUND, response.status());
1292
1293 let serve_request = TestRequest::with_uri(&format!("/{}", oneshot_url_suffix)).to_request();
1295 let response = test::call_service(&app, serve_request).await;
1296 assert_eq!(StatusCode::NOT_FOUND, response.status());
1297
1298 fs::remove_dir_all(url_upload_path)?;
1300
1301 Ok(())
1302 }
1303}