From 46605fae2dcb02d573cdb3bf01a1be412c6cde32 Mon Sep 17 00:00:00 2001 From: Mike Dilger Date: Mon, 18 Nov 2024 08:37:15 +1300 Subject: [PATCH] Blossom: Implement HEAD/PUT /upload --- src/web/blossom/mod.rs | 90 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 84 insertions(+), 6 deletions(-) diff --git a/src/web/blossom/mod.rs b/src/web/blossom/mod.rs index 8f3a6b6..d692449 100644 --- a/src/web/blossom/mod.rs +++ b/src/web/blossom/mod.rs @@ -3,15 +3,16 @@ use crate::filestore::HashOutput; use crate::globals::GLOBALS; use http::header::{ ACCESS_CONTROL_ALLOW_HEADERS, ACCESS_CONTROL_ALLOW_METHODS, ACCESS_CONTROL_ALLOW_ORIGIN, - ACCESS_CONTROL_REQUEST_HEADERS, ACCESS_CONTROL_REQUEST_METHOD, ALLOW, CONTENT_LENGTH, ORIGIN, - WWW_AUTHENTICATE, + ACCESS_CONTROL_REQUEST_HEADERS, ACCESS_CONTROL_REQUEST_METHOD, ALLOW, CONTENT_LENGTH, + CONTENT_TYPE, ORIGIN, WWW_AUTHENTICATE, }; use http::{Method, StatusCode}; -//ACCEPT, AUTHORIZATION, CONTENT_TYPE, DATE, ETAG, ORIGIN +//ACCEPT, AUTHORIZATION, DATE, ETAG, ORIGIN use http_body_util::combinators::BoxBody; -use http_body_util::{BodyExt, Empty}; +use http_body_util::{BodyExt, Empty, Full}; use hyper::body::{Bytes, Incoming}; use hyper::{Request, Response}; +use serde::{Deserialize, Serialize}; mod auth; use auth::{verify_auth, AuthVerb}; @@ -162,9 +163,77 @@ pub async fn handle_upload( return options_response(request, "OPTIONS, HEAD, PUT"); } - let _auth_data = verify_auth(&request)?; + let auth_data = verify_auth(&request)?; + if auth_data.verb != Some(AuthVerb::Upload) { + return Err( + ChorusError::BlossomAuthFailure("Upload was not authorized".to_string()).into(), + ); + } - unimplemented!() + match request.method() { + &Method::HEAD => Ok(Response::builder() + .header(ACCESS_CONTROL_ALLOW_ORIGIN, "*") + .header(CONTENT_LENGTH, "0") + .status(StatusCode::NOT_IMPLEMENTED) + .body(Empty::new().map_err(|e| e.into()).boxed())?), + &Method::PUT => { + let expected_hash = auth_data.hash.map(|bytes| HashOutput::from_bytes(bytes)); + if expected_hash.is_none() { + return Err(ChorusError::BlossomAuthFailure( + "Put requires an expected hash value x tag in the authorization event" + .to_string(), + ) + .into()); + } + + let uri = request.uri().to_owned(); + + let (size, hash) = GLOBALS + .filestore + .get() + .unwrap() + .store( + request.into_body().map_err(|e| e.into()).boxed(), + expected_hash, + ) + .await?; + + let uri = { + let mut parts = GLOBALS.config.read().uri_parts(uri, true)?; + parts.path_and_query = Some(http::uri::PathAndQuery::from_maybe_shared(format!( + "/{}", + hash + ))?); + http::Uri::from_parts(parts)? + }; + + let blob_descriptor = BlobDescriptor { + url: format!("{}", uri), + sha256: format!("{}", hash), + size, + uploaded: pocket_types::Time::now().as_u64(), + }; + + let descriptor_json_string = serde_json::to_string(&blob_descriptor)?; + let body_bytes = descriptor_json_string.into_bytes(); + let len = body_bytes.len(); + let body = Full::new(Bytes::from(body_bytes)) + .map_err(|e| e.into()) + .boxed(); + + Ok(Response::builder() + .header(ACCESS_CONTROL_ALLOW_ORIGIN, "*") + .header(CONTENT_LENGTH, format!("{}", len)) + .header(CONTENT_TYPE, "application/json") + .status(StatusCode::OK) + .body(body)?) + } + _ => Ok(Response::builder() + .header(ACCESS_CONTROL_ALLOW_ORIGIN, "*") + .header(CONTENT_LENGTH, "0") + .status(StatusCode::METHOD_NOT_ALLOWED) + .body(Empty::new().map_err(|e| e.into()).boxed())?), + } } pub async fn handle_list( @@ -190,3 +259,12 @@ pub async fn handle_mirror( unimplemented!() } + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct BlobDescriptor { + pub url: String, + pub sha256: String, + pub size: u64, + // type: String + pub uploaded: u64, +}