Hello, last time I shared my dirty code of pastebin and people suggested me a lot of things so I have implemented those. now the code is reduced to only 42 lines of code :D

last post: https://lemmy.ca/post/36410861

here is the code:

use axum::{extract::Path, routing::get, routing::post, Router};
use std::fs::{read_to_string, File};
use std::io::prelude::*;
use std::sync::atomic::{AtomicUsize, Ordering};

const MAX_FILE_SIZE: usize = 1024 * 1024 * 10;
static mut FILE_COUNT: AtomicUsize = AtomicUsize::new(0);

async fn handle(Path(id): Path<String>) -> String {
    if let Ok(content) = read_to_string(id) {
        return content;
    }
    return String::from("ERROR: File not found");
}

async fn submit_handle(bytes: String) -> String {
    dbg!(&bytes);
    if bytes.len() > MAX_FILE_SIZE {
        // Don't store the file if it exceeds max size
        return String::from("ERROR: max size exceeded");
    }
    unsafe {
        let path = FILE_COUNT.load(Ordering::Relaxed);
        FILE_COUNT.store(path+1, Ordering::Relaxed);
        let mut output = File::create(path.to_string()).unwrap();
        write!(output, "{}", bytes).unwrap();
        let mut url = String::from("http://localhost:3000/");
        url.push_str(&path.to_string());
        return url;
    }
}

#[tokio::main]
async fn main() {
    let app = Router::new()
        .route("/", get(|| async { "Paste something in pastebin! use curl -X POST http://localhost:3000/submit -d 'this is some data'" }))
        .route("/{id}", get(handle))
        .route("/submit", post(submit_handle));

    let listener = tokio::net::TcpListener::bind("127.0.0.1:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

  • beeb@lemm.ee
    link
    fedilink
    arrow-up
    1
    ·
    edit-2
    14 hours ago

    Here’s a slightly more idiomatic version:

    use std::{
        fs,
        sync::atomic::{AtomicUsize, Ordering},
    };
    
    use axum::{extract::Path, http::StatusCode, routing::get, routing::post, Router};
    
    const MAX_FILE_SIZE: usize = 1024 * 1024 * 10;
    static FILE_COUNT: AtomicUsize = AtomicUsize::new(0);
    
    async fn handle(Path(id): Path<String>) -> (StatusCode, String) {
        match fs::read_to_string(id) {
            Ok(content) => (StatusCode::OK, content),
            Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()),
        }
    }
    
    async fn submit_handle(bytes: String) -> (StatusCode, String) {
        dbg!(&bytes);
        if bytes.len() > MAX_FILE_SIZE {
            // Don't store the file if it exceeds max size
            return (
                StatusCode::BAD_REQUEST,
                "ERROR: max size exceeded".to_string(),
            );
        }
        let path = FILE_COUNT.fetch_add(1, Ordering::SeqCst);
        if let Err(e) = fs::write(path.to_string(), bytes) {
            return (StatusCode::INTERNAL_SERVER_ERROR, e.to_string());
        }
        (StatusCode::CREATED, format!("http://localhost:3000/%7Bpath%7D"))
    }
    
    #[tokio::main]
    async fn main() {
        let app = Router::new()
            .route("/", get(|| async { "Paste something in pastebin! use curl -X POST http://localhost:3000/submit -d 'this is some data'" }))
            .route("/{id}", get(handle))
            .route("/submit", post(submit_handle));
    
        let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
            .await
            .unwrap();
        axum::serve(listener, app).await.unwrap();
    }
    

    Note that there are no unwrap in the handlers which would absolutely want to avoid (it would crash your server). The endpoints now also return the correct HTTP code for each case. Some minor changes regarding creating the string values (use of format! and to_string() on string slices). Lemmy messes with the curly braces in the format! macro, there should be curly braces around the path variable name.

    • beeb@lemm.ee
      link
      fedilink
      arrow-up
      1
      ·
      14 hours ago

      Note that there are many security concerns with this, notably the fact that there is no input validation on the id path segment which means you can get the content of any file (e.g. http://localhost:3000/src%2Fmain.rs). It’s also very easy to scrape the content of all the files because the IDs are easy to predict. When the server reboots, you will overwrite previously written files because the counter starts back at zero. Using a UUID would probably mostly solve both these issues.

      • whoareuOP
        link
        fedilink
        arrow-up
        1
        ·
        8 hours ago

        it shouldn’t be an issue because I will be running it inside a chroot. I might use UUID though.