init,working
This commit is contained in:
		
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
				
			|||||||
 | 
					/target
 | 
				
			||||||
 | 
					/tmp
 | 
				
			||||||
 | 
					*.db
 | 
				
			||||||
 | 
					*.sqlite
 | 
				
			||||||
							
								
								
									
										2281
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										2281
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										12
									
								
								Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								Cargo.toml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					[package]
 | 
				
			||||||
 | 
					name = "file_upload"
 | 
				
			||||||
 | 
					version = "0.1.0"
 | 
				
			||||||
 | 
					edition = "2024"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[dependencies]
 | 
				
			||||||
 | 
					r2d2 = "0.8.10"
 | 
				
			||||||
 | 
					r2d2_sqlite = "0.31.0"
 | 
				
			||||||
 | 
					rocket = { version = "0.5.1", features = ["uuid"] }
 | 
				
			||||||
 | 
					rocket_dyn_templates = { version = "0.2.0", features = ["handlebars"] }
 | 
				
			||||||
 | 
					rusqlite = "0.37.0"
 | 
				
			||||||
 | 
					uuid = { version = "1.18.1", features = ["v4", "v7"] }
 | 
				
			||||||
							
								
								
									
										3
									
								
								Rocket.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								Rocket.toml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					[default.limits]
 | 
				
			||||||
 | 
					data-form = "5GB"
 | 
				
			||||||
 | 
					file = "5GB"
 | 
				
			||||||
							
								
								
									
										133
									
								
								src/main.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								src/main.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,133 @@
 | 
				
			|||||||
 | 
					#[macro_use]
 | 
				
			||||||
 | 
					extern crate rocket;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					mod namedfile;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use r2d2::Pool;
 | 
				
			||||||
 | 
					use r2d2_sqlite::SqliteConnectionManager;
 | 
				
			||||||
 | 
					use rocket::State;
 | 
				
			||||||
 | 
					use rocket::form::Form;
 | 
				
			||||||
 | 
					use rocket::fs::{FileServer, Options, TempFile};
 | 
				
			||||||
 | 
					use rocket_dyn_templates::{Template, context};
 | 
				
			||||||
 | 
					use rusqlite::params;
 | 
				
			||||||
 | 
					use uuid::Uuid;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug)]
 | 
				
			||||||
 | 
					struct Row {
 | 
				
			||||||
 | 
					    id: usize,
 | 
				
			||||||
 | 
					    uuid: String,
 | 
				
			||||||
 | 
					    raw_file_name_unsafe: String,
 | 
				
			||||||
 | 
					    filepath: String,
 | 
				
			||||||
 | 
					    time: String,
 | 
				
			||||||
 | 
					    visited: usize,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(FromForm)]
 | 
				
			||||||
 | 
					struct Upload<'f> {
 | 
				
			||||||
 | 
					    upload: TempFile<'f>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[post("/upload", data = "<form>")]
 | 
				
			||||||
 | 
					async fn upload(
 | 
				
			||||||
 | 
					    mut form: Form<Upload<'_>>,
 | 
				
			||||||
 | 
					    con: &State<Pool<SqliteConnectionManager>>,
 | 
				
			||||||
 | 
					) -> std::io::Result<Template> {
 | 
				
			||||||
 | 
					    // v7 is supposedly better for database keys
 | 
				
			||||||
 | 
					    // let uuid = Uuid::now_v7();
 | 
				
			||||||
 | 
					    let uuid = Uuid::new_v4();
 | 
				
			||||||
 | 
					    let path = format!("./tmp/{}", uuid);
 | 
				
			||||||
 | 
					    let raw_name = form
 | 
				
			||||||
 | 
					        .upload
 | 
				
			||||||
 | 
					        .raw_name()
 | 
				
			||||||
 | 
					        .map_or("unkown.bin".to_string(), |v| {
 | 
				
			||||||
 | 
					            v.dangerous_unsafe_unsanitized_raw().to_string()
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    con.get()
 | 
				
			||||||
 | 
					        .unwrap()
 | 
				
			||||||
 | 
					        .execute(
 | 
				
			||||||
 | 
					            "INSERT INTO files (uuid, filename, filepath) VALUES (?1, ?2, ?3)",
 | 
				
			||||||
 | 
					            params![uuid.to_string(), raw_name, path],
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        .unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    form.upload.persist_to(path).await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Ok(Template::render(
 | 
				
			||||||
 | 
					        "upload",
 | 
				
			||||||
 | 
					        context! {
 | 
				
			||||||
 | 
					            link: uuid.to_string()
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    ))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[get("/download/<uuid>")]
 | 
				
			||||||
 | 
					async fn download(
 | 
				
			||||||
 | 
					    uuid: Uuid,
 | 
				
			||||||
 | 
					    con: &State<Pool<SqliteConnectionManager>>,
 | 
				
			||||||
 | 
					) -> std::io::Result<namedfile::NamedFile> {
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // Get the row from the DB
 | 
				
			||||||
 | 
					    let row: Row = con
 | 
				
			||||||
 | 
					        .get()
 | 
				
			||||||
 | 
					        .unwrap()
 | 
				
			||||||
 | 
					        .query_one(
 | 
				
			||||||
 | 
					            "SELECT * FROM files WHERE uuid = ?1 LIMIT 1",
 | 
				
			||||||
 | 
					            params![uuid.to_string()],
 | 
				
			||||||
 | 
					            |f| {
 | 
				
			||||||
 | 
					                Ok(Row {
 | 
				
			||||||
 | 
					                    id: f.get(0)?,
 | 
				
			||||||
 | 
					                    uuid: f.get(1)?,
 | 
				
			||||||
 | 
					                    raw_file_name_unsafe: f.get(2)?,
 | 
				
			||||||
 | 
					                    filepath: f.get(3)?,
 | 
				
			||||||
 | 
					                    time: f.get(4)?,
 | 
				
			||||||
 | 
					                    visited: f.get(5)?,
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        .unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // The the file off the disk, unfortunately this doesn't modify the file's access time. 
 | 
				
			||||||
 | 
					    let mut file = namedfile::NamedFile::open(row.filepath).await?;
 | 
				
			||||||
 | 
					    // Set the "display name". Basically just what the browser will download the file as.
 | 
				
			||||||
 | 
					    file.change_name(&row.raw_file_name_unsafe);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Increment how many times this file as been downloaded++
 | 
				
			||||||
 | 
					    con.get()
 | 
				
			||||||
 | 
					        .unwrap()
 | 
				
			||||||
 | 
					        .execute(
 | 
				
			||||||
 | 
					            "UPDATE files SET visited = visited + 1 WHERE id = ?1 LIMIT 1",
 | 
				
			||||||
 | 
					            params![row.id],
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        .unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Ok(file)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[launch]
 | 
				
			||||||
 | 
					fn rocket() -> _ {
 | 
				
			||||||
 | 
					    let manager = SqliteConnectionManager::file("database.sqlite");
 | 
				
			||||||
 | 
					    let pool = r2d2::Pool::new(manager).unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pool.get()
 | 
				
			||||||
 | 
					        .unwrap()
 | 
				
			||||||
 | 
					        .execute(
 | 
				
			||||||
 | 
					            "CREATE TABLE IF NOT EXISTS files (id Integer PRIMARY KEY AUTOINCREMENT,
 | 
				
			||||||
 | 
					    uuid varchar(36),
 | 
				
			||||||
 | 
					    filename varchar(255),
 | 
				
			||||||
 | 
					    filepath varchar(255),
 | 
				
			||||||
 | 
					    Timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
 | 
				
			||||||
 | 
					    visited Integer DEFAULT 0
 | 
				
			||||||
 | 
					    )",
 | 
				
			||||||
 | 
					            params![],
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        .unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let opt = Options::Index;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    rocket::build()
 | 
				
			||||||
 | 
					        .manage(pool)
 | 
				
			||||||
 | 
					        .mount("/", FileServer::new("./www", opt))
 | 
				
			||||||
 | 
					        .mount("/", routes![upload, download,])
 | 
				
			||||||
 | 
					        .attach(Template::fairing())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										49
									
								
								src/namedfile.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								src/namedfile.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,49 @@
 | 
				
			|||||||
 | 
					use std::io;
 | 
				
			||||||
 | 
					use std::path::Path;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use rocket::http::ContentType;
 | 
				
			||||||
 | 
					use rocket::response::Responder;
 | 
				
			||||||
 | 
					use rocket::tokio::fs::File;
 | 
				
			||||||
 | 
					use rocket::{Request, response};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug)]
 | 
				
			||||||
 | 
					pub struct NamedFile {
 | 
				
			||||||
 | 
					    file: File,
 | 
				
			||||||
 | 
					    vanity_name: String,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl NamedFile {
 | 
				
			||||||
 | 
					    pub async fn open<P: AsRef<Path>>(path: P) -> io::Result<NamedFile> {
 | 
				
			||||||
 | 
					        let file = File::open(path.as_ref()).await?;
 | 
				
			||||||
 | 
					        Ok(NamedFile {
 | 
				
			||||||
 | 
					            vanity_name: path.as_ref().to_str().unwrap_or("unknown.bin").to_string(),
 | 
				
			||||||
 | 
					            file,
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn change_name(&mut self, new_name: &str) {
 | 
				
			||||||
 | 
					        self.vanity_name = new_name.to_string();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Streams the named file to the client. Sets or overrides the Content-Type in
 | 
				
			||||||
 | 
					/// the response according to the file's extension if the extension is
 | 
				
			||||||
 | 
					/// recognized. See [`ContentType::from_extension()`] for more information. If
 | 
				
			||||||
 | 
					/// you would like to stream a file with a different Content-Type than that
 | 
				
			||||||
 | 
					/// implied by its extension, use a [`File`] directly.
 | 
				
			||||||
 | 
					impl<'r> Responder<'r, 'static> for NamedFile {
 | 
				
			||||||
 | 
					    fn respond_to(self, req: &'r Request<'_>) -> response::Result<'static> {
 | 
				
			||||||
 | 
					        let mut response = self.file.respond_to(req)?;
 | 
				
			||||||
 | 
					        if let Some(ext) = self.vanity_name.split('.').last() {
 | 
				
			||||||
 | 
					            if let Some(ct) = ContentType::from_extension(ext) {
 | 
				
			||||||
 | 
					                response.set_header(ct);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        response.set_header(rocket::http::Header::new(
 | 
				
			||||||
 | 
					            "content-disposition",
 | 
				
			||||||
 | 
					            format!("attatchment; filename={}", self.vanity_name),
 | 
				
			||||||
 | 
					        ));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Ok(response)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										24
									
								
								templates/upload.html.hbs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								templates/upload.html.hbs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					<!DOCTYPE html>
 | 
				
			||||||
 | 
					<html>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
					  <meta charset='utf-8'>
 | 
				
			||||||
 | 
					  <meta http-equiv='X-UA-Compatible' content='IE=edge'>
 | 
				
			||||||
 | 
					  <title>FileShare</title>
 | 
				
			||||||
 | 
					  <meta name='viewport' content='width=device-width, initial-scale=1'>
 | 
				
			||||||
 | 
					  <link rel='stylesheet' type='text/css' media='screen' href='main.css'>
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					  <br>
 | 
				
			||||||
 | 
					  <br>
 | 
				
			||||||
 | 
					  <h1>FileShare</h1>
 | 
				
			||||||
 | 
					  <br>
 | 
				
			||||||
 | 
					  <br>
 | 
				
			||||||
 | 
					  <a href="/download/{{ link }}">Click here to download the file.</a>
 | 
				
			||||||
 | 
					  <br>
 | 
				
			||||||
 | 
					  <br>
 | 
				
			||||||
 | 
					  <p>Right-click → "copy link" to share.</p>
 | 
				
			||||||
 | 
					  <p>This screen will only be shown once.</p>
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								www/favicon.ico
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								www/favicon.ico
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 15 KiB  | 
							
								
								
									
										35
									
								
								www/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								www/index.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
				
			|||||||
 | 
					<!DOCTYPE html>
 | 
				
			||||||
 | 
					<html>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
					  <meta charset='utf-8'>
 | 
				
			||||||
 | 
					  <meta http-equiv='X-UA-Compatible' content='IE=edge'>
 | 
				
			||||||
 | 
					  <title>FileShare</title>
 | 
				
			||||||
 | 
					  <meta name='viewport' content='width=device-width, initial-scale=1'>
 | 
				
			||||||
 | 
					  <link rel='stylesheet' type='text/css' media='screen' href='main.css'>
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					  <br>
 | 
				
			||||||
 | 
					  <br>
 | 
				
			||||||
 | 
					  <h1>FileShare</h1>
 | 
				
			||||||
 | 
					  <br>
 | 
				
			||||||
 | 
					  <br>
 | 
				
			||||||
 | 
					  <form action="/upload" method="POST" enctype="multipart/form-data">
 | 
				
			||||||
 | 
					    <label for="upload" id="upload_btn">Select file</label>
 | 
				
			||||||
 | 
					    <input type="file" name="upload" id="upload">
 | 
				
			||||||
 | 
					    <label for="submit">Upload</label>
 | 
				
			||||||
 | 
					    <input type="submit" id="submit">
 | 
				
			||||||
 | 
					  </form>
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
 | 
					<script defer>
 | 
				
			||||||
 | 
					  const btn = document.getElementById("upload_btn");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  document.getElementById("upload").onchange = (e) => {
 | 
				
			||||||
 | 
					    let filename = e.target.value;
 | 
				
			||||||
 | 
					    const last = filename.lastIndexOf('\\');
 | 
				
			||||||
 | 
					    filename = filename.substring(last+1);
 | 
				
			||||||
 | 
					    btn.innerText = filename;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
							
								
								
									
										41
									
								
								www/main.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								www/main.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,41 @@
 | 
				
			|||||||
 | 
					html {
 | 
				
			||||||
 | 
					    font-family: Arial, Helvetica, sans-serif;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					body {
 | 
				
			||||||
 | 
					    max-width: 500px;
 | 
				
			||||||
 | 
					    margin: auto;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					h1 {
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					    font-size: 3rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					form {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    flex-direction: column;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					label {
 | 
				
			||||||
 | 
					    color: white;
 | 
				
			||||||
 | 
					    border: solid 2px black;
 | 
				
			||||||
 | 
					    border-radius: 10px;
 | 
				
			||||||
 | 
					    background-color: rgba(0,0,255,0.5);
 | 
				
			||||||
 | 
					    margin: 5px;
 | 
				
			||||||
 | 
					    padding: 20px;
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					label:hover {
 | 
				
			||||||
 | 
					    cursor: pointer;
 | 
				
			||||||
 | 
					    color: black;
 | 
				
			||||||
 | 
					    background-color: rgba(0, 0, 255, 0.4);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					input {
 | 
				
			||||||
 | 
					    display: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					a {
 | 
				
			||||||
 | 
					    color: rgba(0,0,255,0.5);
 | 
				
			||||||
 | 
					    font-size: 2rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					a, p {
 | 
				
			||||||
 | 
					    margin: auto;
 | 
				
			||||||
 | 
					    display: block;
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user