Use integer IDs for files

This commit is contained in:
Niels Andriesse 2021-03-26 15:24:02 +11:00
parent 7ee9714846
commit 9d6c7f8566
6 changed files with 24 additions and 36 deletions

10
Cargo.lock generated
View File

@ -1323,7 +1323,6 @@ dependencies = [
"tokio",
"tokio-test",
"url",
"uuid",
"warp",
"x25519-dalek",
]
@ -1754,15 +1753,6 @@ version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05e42f7c18b8f902290b009cde6d651262f956c98bc51bca4cd1d511c9cd85c7"
[[package]]
name = "uuid"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
dependencies = [
"getrandom 0.2.2",
]
[[package]]
name = "vcpkg"
version = "0.2.11"

View File

@ -25,7 +25,6 @@ sha2 = "0.9"
structopt = "0.3"
tokio = { version = "1.3", features = ["full"] }
url = "2.2.1"
uuid = { version = "0.8", features = ["v4"] }
warp = { version = "0.3", features = ["tls"] }
x25519-dalek = "1.1"

View File

@ -8,7 +8,6 @@ use chrono;
use rand::{thread_rng, Rng};
use rusqlite::params;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use warp::{http::StatusCode, reply::Reply, reply::Response, Rejection};
use super::crypto;
@ -112,6 +111,8 @@ pub async fn get_all_rooms() -> Result<Response, Rejection> {
pub async fn store_file(
base64_encoded_bytes: &str, auth_token: &str, pool: &storage::DatabaseConnectionPool,
) -> Result<Response, Rejection> {
// It'd be nice to use the UUID crate for the file ID, but clients want an integer ID
let now = chrono::Utc::now().timestamp();
// Check authorization level
let (has_authorization_level, _) =
has_authorization_level(auth_token, AuthorizationLevel::Basic, pool).await?;
@ -126,17 +127,12 @@ pub async fn store_file(
return Err(warp::reject::custom(Error::ValidationFailed));
}
};
// Generate UUID
let id = Uuid::new_v4();
let mut buffer = Uuid::encode_buffer();
let id: String = id.to_simple().encode_lower(&mut buffer).to_string();
// Update the database
// We do this * before * storing the actual file, so that in case something goes
// wrong we're not left with files that'll never be pruned.
let now = chrono::Utc::now().timestamp();
let conn = pool.get().map_err(|_| Error::DatabaseFailedInternally)?;
let stmt = format!("INSERT INTO {} (id, timestamp) VALUES (?1, ?2)", storage::FILES_TABLE);
let _ = match conn.execute(&stmt, params![id, now]) {
let _ = match conn.execute(&stmt, params![now, now]) {
Ok(rows) => rows,
Err(e) => {
println!("Couldn't insert file record due to error: {}.", e);
@ -145,7 +141,7 @@ pub async fn store_file(
};
// Write to file
let mut pos = 0;
let raw_path = format!("files/{}", &id);
let raw_path = format!("files/{}", &now);
let path = Path::new(&raw_path);
let mut buffer = match fs::File::create(path) {
Ok(buffer) => buffer,
@ -165,12 +161,17 @@ pub async fn store_file(
pos += count;
}
// Return
let json = GenericStringResponse { status_code: StatusCode::OK.as_u16(), result: id };
return Ok(warp::reply::json(&json).into_response());
#[derive(Debug, Deserialize, Serialize)]
struct Response {
status_code: u16,
result: i64,
}
let response = Response { status_code: StatusCode::OK.as_u16(), result: now };
return Ok(warp::reply::json(&response).into_response());
}
pub async fn get_file(
id: &str, auth_token: &str, pool: &storage::DatabaseConnectionPool,
id: i64, auth_token: &str, pool: &storage::DatabaseConnectionPool,
) -> Result<GenericStringResponse, Rejection> {
// Doesn't return a response directly for testing purposes
// Check authorization level
@ -179,14 +180,6 @@ pub async fn get_file(
if !has_authorization_level {
return Err(warp::reject::custom(Error::Unauthorized));
}
// Check that the ID is a valid UUID
match Uuid::parse_str(id) {
Ok(_) => (),
Err(e) => {
println!("Couldn't parse UUID from: {} due to error: {}.", id, e);
return Err(warp::reject::custom(Error::ValidationFailed));
}
};
// Try to read the file
let raw_path = format!("files/{}", id);
let path = Path::new(&raw_path);

View File

@ -94,7 +94,13 @@ async fn handle_get_request(
println!("Invalid endpoint: {}.", rpc_call.endpoint);
return Err(warp::reject::custom(Error::InvalidRpcCall));
}
let file_id = components[1];
let file_id: i64 = match components[1].parse() {
Ok(file_id) => file_id,
Err(_) => {
println!("Invalid endpoint: {}.", rpc_call.endpoint);
return Err(warp::reject::custom(Error::InvalidRpcCall));
}
};
return handlers::get_file(file_id, &auth_token, &pool)
.await
.map(|json| warp::reply::json(&json).into_response());

View File

@ -147,7 +147,7 @@ fn create_room_tables_if_needed(conn: &DatabaseConnection) {
// Files
let files_table_cmd = format!(
"CREATE TABLE IF NOT EXISTS {} (
id STRING PRIMARY KEY,
id INTEGER PRIMARY KEY,
timestamp INTEGER
)",
FILES_TABLE
@ -261,10 +261,10 @@ pub async fn prune_files(file_expiration: i64) {
return println!("Couldn't prune files due to error: {}.", e);
}
};
let ids: Vec<String> = rows.filter_map(|result| result.ok()).collect();
let ids: Vec<i64> = rows.filter_map(|result| result.ok()).collect();
if !ids.is_empty() {
// Delete the files
let mut deleted_ids: Vec<String> = vec![];
let mut deleted_ids: Vec<i64> = vec![];
for id in ids {
match fs::remove_file(format!("files/{}", id)) {
Ok(_) => deleted_ids.push(id),

View File

@ -90,9 +90,9 @@ fn test_file_handling() {
// Check that there's a file record
let conn = pool.get().unwrap();
let raw_query = format!("SELECT id FROM {}", storage::FILES_TABLE);
let id: String = conn.query_row(&raw_query, params![], |row| Ok(row.get(0)?)).unwrap();
let id: i64 = conn.query_row(&raw_query, params![], |row| Ok(row.get(0)?)).unwrap();
// Retrieve the file and check the content
let base64_encoded_file = aw!(handlers::get_file(&id, &auth_token, &pool)).unwrap().result;
let base64_encoded_file = aw!(handlers::get_file(id, &auth_token, &pool)).unwrap().result;
assert_eq!(base64_encoded_file, TEST_FILE);
// Prune the file and check that it's gone
aw!(storage::prune_files(-60)); // Will evaluate to now + 60