diff options
Diffstat (limited to 'src/api')
-rw-r--r-- | src/api/admin/error.rs | 15 | ||||
-rw-r--r-- | src/api/common_error.rs | 37 | ||||
-rw-r--r-- | src/api/k2v/api_server.rs | 8 | ||||
-rw-r--r-- | src/api/k2v/batch.rs | 7 | ||||
-rw-r--r-- | src/api/k2v/error.rs | 9 | ||||
-rw-r--r-- | src/api/k2v/item.rs | 8 | ||||
-rw-r--r-- | src/api/router_macros.rs | 2 | ||||
-rw-r--r-- | src/api/s3/api_server.rs | 3 | ||||
-rw-r--r-- | src/api/s3/checksum.rs | 4 | ||||
-rw-r--r-- | src/api/s3/copy.rs | 5 | ||||
-rw-r--r-- | src/api/s3/cors.rs | 16 | ||||
-rw-r--r-- | src/api/s3/error.rs | 13 | ||||
-rw-r--r-- | src/api/s3/get.rs | 13 | ||||
-rw-r--r-- | src/api/s3/list.rs | 4 | ||||
-rw-r--r-- | src/api/s3/post_object.rs | 5 | ||||
-rw-r--r-- | src/api/s3/put.rs | 2 | ||||
-rw-r--r-- | src/api/s3/website.rs | 2 | ||||
-rw-r--r-- | src/api/signature/payload.rs | 8 |
18 files changed, 116 insertions, 45 deletions
diff --git a/src/api/admin/error.rs b/src/api/admin/error.rs index 2668b42d..40d686e3 100644 --- a/src/api/admin/error.rs +++ b/src/api/admin/error.rs @@ -1,3 +1,5 @@ +use std::convert::TryFrom; + use err_derive::Error; use hyper::header::HeaderValue; use hyper::{HeaderMap, StatusCode}; @@ -38,6 +40,19 @@ where } } +/// FIXME: helper errors are transformed into their corresponding variants +/// in the Error struct, but in many case a helper error should be considered +/// an internal error. +impl From<HelperError> for Error { + fn from(err: HelperError) -> Error { + match CommonError::try_from(err) { + Ok(ce) => Self::Common(ce), + Err(HelperError::NoSuchAccessKey(k)) => Self::NoSuchAccessKey(k), + Err(_) => unreachable!(), + } + } +} + impl CommonErrorDerivative for Error {} impl Error { diff --git a/src/api/common_error.rs b/src/api/common_error.rs index c47555d4..0c8006dc 100644 --- a/src/api/common_error.rs +++ b/src/api/common_error.rs @@ -1,3 +1,5 @@ +use std::convert::TryFrom; + use err_derive::Error; use hyper::StatusCode; @@ -97,18 +99,39 @@ impl CommonError { } } -impl From<HelperError> for CommonError { - fn from(err: HelperError) -> Self { +impl TryFrom<HelperError> for CommonError { + type Error = HelperError; + + fn try_from(err: HelperError) -> Result<Self, HelperError> { match err { - HelperError::Internal(i) => Self::InternalError(i), - HelperError::BadRequest(b) => Self::BadRequest(b), - HelperError::InvalidBucketName(n) => Self::InvalidBucketName(n), - HelperError::NoSuchBucket(n) => Self::NoSuchBucket(n), - e => Self::bad_request(format!("{}", e)), + HelperError::Internal(i) => Ok(Self::InternalError(i)), + HelperError::BadRequest(b) => Ok(Self::BadRequest(b)), + HelperError::InvalidBucketName(n) => Ok(Self::InvalidBucketName(n)), + HelperError::NoSuchBucket(n) => Ok(Self::NoSuchBucket(n)), + e => Err(e), } } } +/// This function converts HelperErrors into CommonErrors, +/// for variants that exist in CommonError. +/// This is used for helper functions that might return InvalidBucketName +/// or NoSuchBucket for instance, and we want to pass that error +/// up to our caller. +pub(crate) fn pass_helper_error(err: HelperError) -> CommonError { + match CommonError::try_from(err) { + Ok(e) => e, + Err(e) => panic!("Helper error `{}` should hot have happenned here", e), + } +} + +pub(crate) fn helper_error_as_internal(err: HelperError) -> CommonError { + match err { + HelperError::Internal(e) => CommonError::InternalError(e), + e => CommonError::InternalError(GarageError::Message(e.to_string())), + } +} + pub trait CommonErrorDerivative: From<CommonError> { fn internal_error<M: ToString>(msg: M) -> Self { Self::from(CommonError::InternalError(GarageError::Message( diff --git a/src/api/k2v/api_server.rs b/src/api/k2v/api_server.rs index 658cfcc8..de6e5f06 100644 --- a/src/api/k2v/api_server.rs +++ b/src/api/k2v/api_server.rs @@ -77,7 +77,7 @@ impl ApiHandler for K2VApiServer { } = endpoint; let garage = self.garage.clone(); - // The OPTIONS method is procesed early, before we even check for an API key + // The OPTIONS method is processed early, before we even check for an API key if let Endpoint::Options = endpoint { let options_res = handle_options_api(garage, &req, Some(bucket_name)) .await @@ -90,11 +90,13 @@ impl ApiHandler for K2VApiServer { let bucket_id = garage .bucket_helper() .resolve_bucket(&bucket_name, &api_key) - .await?; + .await + .map_err(pass_helper_error)?; let bucket = garage .bucket_helper() .get_existing_bucket(bucket_id) - .await?; + .await + .map_err(helper_error_as_internal)?; let bucket_params = bucket.state.into_option().unwrap(); let allowed = match endpoint.authorization_type() { diff --git a/src/api/k2v/batch.rs b/src/api/k2v/batch.rs index 02b7ae8b..e4d0b0e5 100644 --- a/src/api/k2v/batch.rs +++ b/src/api/k2v/batch.rs @@ -4,12 +4,12 @@ use serde::{Deserialize, Serialize}; use garage_table::{EnumerationOrder, TableSchema}; -use garage_model::k2v::causality::*; use garage_model::k2v::item_table::*; use crate::helpers::*; use crate::k2v::api_server::{ReqBody, ResBody}; use crate::k2v::error::*; +use crate::k2v::item::parse_causality_token; use crate::k2v::range::read_range; pub async fn handle_insert_batch( @@ -23,7 +23,7 @@ pub async fn handle_insert_batch( let mut items2 = vec![]; for it in items { - let ct = it.ct.map(|s| CausalContext::parse_helper(&s)).transpose()?; + let ct = it.ct.map(|s| parse_causality_token(&s)).transpose()?; let v = match it.v { Some(vs) => DvvsValue::Value( BASE64_STANDARD @@ -281,7 +281,8 @@ pub(crate) async fn handle_poll_range( query.seen_marker, timeout_msec, ) - .await?; + .await + .map_err(pass_helper_error)?; if let Some((items, seen_marker)) = resp { let resp = PollRangeResponse { diff --git a/src/api/k2v/error.rs b/src/api/k2v/error.rs index 16479227..dbe4be2c 100644 --- a/src/api/k2v/error.rs +++ b/src/api/k2v/error.rs @@ -3,6 +3,7 @@ use hyper::header::HeaderValue; use hyper::{HeaderMap, StatusCode}; use crate::common_error::CommonError; +pub(crate) use crate::common_error::{helper_error_as_internal, pass_helper_error}; pub use crate::common_error::{CommonErrorDerivative, OkOrBadRequest, OkOrInternalError}; use crate::generic_server::ApiError; use crate::helpers::*; @@ -28,6 +29,10 @@ pub enum Error { #[error(display = "Invalid base64: {}", _0)] InvalidBase64(#[error(source)] base64::DecodeError), + /// Invalid causality token + #[error(display = "Invalid causality token")] + InvalidCausalityToken, + /// The client asked for an invalid return format (invalid Accept header) #[error(display = "Not acceptable: {}", _0)] NotAcceptable(String), @@ -72,6 +77,7 @@ impl Error { Error::AuthorizationHeaderMalformed(_) => "AuthorizationHeaderMalformed", Error::InvalidBase64(_) => "InvalidBase64", Error::InvalidUtf8Str(_) => "InvalidUtf8String", + Error::InvalidCausalityToken => "CausalityToken", } } } @@ -85,7 +91,8 @@ impl ApiError for Error { Error::NotAcceptable(_) => StatusCode::NOT_ACCEPTABLE, Error::AuthorizationHeaderMalformed(_) | Error::InvalidBase64(_) - | Error::InvalidUtf8Str(_) => StatusCode::BAD_REQUEST, + | Error::InvalidUtf8Str(_) + | Error::InvalidCausalityToken => StatusCode::BAD_REQUEST, } } diff --git a/src/api/k2v/item.rs b/src/api/k2v/item.rs index af3af4e4..87371727 100644 --- a/src/api/k2v/item.rs +++ b/src/api/k2v/item.rs @@ -18,6 +18,10 @@ pub enum ReturnFormat { Either, } +pub(crate) fn parse_causality_token(s: &str) -> Result<CausalContext, Error> { + CausalContext::parse(s).ok_or(Error::InvalidCausalityToken) +} + impl ReturnFormat { pub fn from(req: &Request<ReqBody>) -> Result<Self, Error> { let accept = match req.headers().get(header::ACCEPT) { @@ -136,7 +140,7 @@ pub async fn handle_insert_item( .get(X_GARAGE_CAUSALITY_TOKEN) .map(|s| s.to_str()) .transpose()? - .map(CausalContext::parse_helper) + .map(parse_causality_token) .transpose()?; let body = http_body_util::BodyExt::collect(req.into_body()) @@ -176,7 +180,7 @@ pub async fn handle_delete_item( .get(X_GARAGE_CAUSALITY_TOKEN) .map(|s| s.to_str()) .transpose()? - .map(CausalContext::parse_helper) + .map(parse_causality_token) .transpose()?; let value = DvvsValue::Deleted; diff --git a/src/api/router_macros.rs b/src/api/router_macros.rs index cfecbc92..8f10a4f5 100644 --- a/src/api/router_macros.rs +++ b/src/api/router_macros.rs @@ -204,7 +204,7 @@ macro_rules! generateQueryParameters { } /// Get an error message in case not all parameters where used when extracting them to - /// build an Enpoint variant + /// build an Endpoint variant fn nonempty_message(&self) -> Option<&str> { if self.keyword.is_some() { Some("Keyword not used") diff --git a/src/api/s3/api_server.rs b/src/api/s3/api_server.rs index 1737af33..f9dafa10 100644 --- a/src/api/s3/api_server.rs +++ b/src/api/s3/api_server.rs @@ -150,7 +150,8 @@ impl ApiHandler for S3ApiServer { let bucket_id = garage .bucket_helper() .resolve_bucket(&bucket_name, &api_key) - .await?; + .await + .map_err(pass_helper_error)?; let bucket = garage .bucket_helper() .get_existing_bucket(bucket_id) diff --git a/src/api/s3/checksum.rs b/src/api/s3/checksum.rs index c9dc001c..c7527163 100644 --- a/src/api/s3/checksum.rs +++ b/src/api/s3/checksum.rs @@ -340,8 +340,8 @@ pub(crate) fn request_checksum_value( Ok(ret.pop()) } -/// Checks for the presense of x-amz-checksum-algorithm -/// if so extract the corrseponding x-amz-checksum-* value +/// Checks for the presence of x-amz-checksum-algorithm +/// if so extract the corresponding x-amz-checksum-* value pub(crate) fn request_checksum_algorithm_value( headers: &HeaderMap<HeaderValue>, ) -> Result<Option<ChecksumValue>, Error> { diff --git a/src/api/s3/copy.rs b/src/api/s3/copy.rs index 411a6917..b67ace88 100644 --- a/src/api/s3/copy.rs +++ b/src/api/s3/copy.rs @@ -63,7 +63,7 @@ pub async fn handle_copy( let source_checksum_algorithm = source_checksum.map(|x| x.algorithm()); // If source object has a checksum, the destination object must as well. - // The x-amz-checksum-algorihtm header allows to change that algorithm, + // The x-amz-checksum-algorithm header allows to change that algorithm, // but if it is absent, we must use the same as before let checksum_algorithm = checksum_algorithm.or(source_checksum_algorithm); @@ -655,7 +655,8 @@ async fn get_copy_source(ctx: &ReqCtx, req: &Request<ReqBody>) -> Result<Object, let source_bucket_id = garage .bucket_helper() .resolve_bucket(&source_bucket.to_string(), api_key) - .await?; + .await + .map_err(pass_helper_error)?; if !api_key.allow_read(&source_bucket_id) { return Err(Error::forbidden(format!( diff --git a/src/api/s3/cors.rs b/src/api/s3/cors.rs index 173b7ffe..32dcc0d5 100644 --- a/src/api/s3/cors.rs +++ b/src/api/s3/cors.rs @@ -1,6 +1,7 @@ -use quick_xml::de::from_reader; use std::sync::Arc; +use quick_xml::de::from_reader; + use http::header::{ ACCESS_CONTROL_ALLOW_HEADERS, ACCESS_CONTROL_ALLOW_METHODS, ACCESS_CONTROL_ALLOW_ORIGIN, ACCESS_CONTROL_EXPOSE_HEADERS, ACCESS_CONTROL_REQUEST_HEADERS, ACCESS_CONTROL_REQUEST_METHOD, @@ -14,7 +15,7 @@ use http_body_util::BodyExt; use serde::{Deserialize, Serialize}; -use crate::common_error::CommonError; +use crate::common_error::{helper_error_as_internal, CommonError}; use crate::helpers::*; use crate::s3::api_server::{ReqBody, ResBody}; use crate::s3::error::*; @@ -116,9 +117,16 @@ pub async fn handle_options_api( // OPTIONS calls are not auhtenticated). if let Some(bn) = bucket_name { let helper = garage.bucket_helper(); - let bucket_id = helper.resolve_global_bucket_name(&bn).await?; + let bucket_id = helper + .resolve_global_bucket_name(&bn) + .await + .map_err(helper_error_as_internal)?; if let Some(id) = bucket_id { - let bucket = garage.bucket_helper().get_existing_bucket(id).await?; + let bucket = garage + .bucket_helper() + .get_existing_bucket(id) + .await + .map_err(helper_error_as_internal)?; let bucket_params = bucket.state.into_option().unwrap(); handle_options_for_bucket(req, &bucket_params) } else { diff --git a/src/api/s3/error.rs b/src/api/s3/error.rs index 2855e0b3..22d2fe14 100644 --- a/src/api/s3/error.rs +++ b/src/api/s3/error.rs @@ -4,7 +4,10 @@ use err_derive::Error; use hyper::header::HeaderValue; use hyper::{HeaderMap, StatusCode}; -use crate::common_error::CommonError; +use garage_model::helper::error::Error as HelperError; + +pub(crate) use crate::common_error::pass_helper_error; +use crate::common_error::{helper_error_as_internal, CommonError}; pub use crate::common_error::{CommonErrorDerivative, OkOrBadRequest, OkOrInternalError}; use crate::generic_server::ApiError; use crate::helpers::*; @@ -87,6 +90,14 @@ where } } +// Helper errors are always passed as internal errors by default. +// To pass the specific error code back to the client, use `pass_helper_error`. +impl From<HelperError> for Error { + fn from(err: HelperError) -> Error { + Error::Common(helper_error_as_internal(err)) + } +} + impl CommonErrorDerivative for Error {} impl From<roxmltree::Error> for Error { diff --git a/src/api/s3/get.rs b/src/api/s3/get.rs index f5d3cf11..f61aae11 100644 --- a/src/api/s3/get.rs +++ b/src/api/s3/get.rs @@ -68,14 +68,11 @@ fn object_headers( // See: https://docs.aws.amazon.com/AmazonS3/latest/userguide/UsingMetadata.html let mut headers_by_name = BTreeMap::new(); for (name, value) in meta_inner.headers.iter() { - match headers_by_name.get_mut(name) { - None => { - headers_by_name.insert(name, vec![value.as_str()]); - } - Some(headers) => { - headers.push(value.as_str()); - } - } + let name_lower = name.to_ascii_lowercase(); + headers_by_name + .entry(name_lower) + .or_insert(vec![]) + .push(value.as_str()); } for (name, values) in headers_by_name { diff --git a/src/api/s3/list.rs b/src/api/s3/list.rs index 648bace2..68d6cbe6 100644 --- a/src/api/s3/list.rs +++ b/src/api/s3/list.rs @@ -398,7 +398,7 @@ enum ExtractionResult { key: String, }, // Fallback key is used for legacy APIs that only support - // exlusive pagination (and not inclusive one). + // exclusive pagination (and not inclusive one). SkipTo { key: String, fallback_key: Option<String>, @@ -408,7 +408,7 @@ enum ExtractionResult { #[derive(PartialEq, Clone, Debug)] enum RangeBegin { // Fallback key is used for legacy APIs that only support - // exlusive pagination (and not inclusive one). + // exclusive pagination (and not inclusive one). IncludingKey { key: String, fallback_key: Option<String>, diff --git a/src/api/s3/post_object.rs b/src/api/s3/post_object.rs index ff2361f1..5279ec6a 100644 --- a/src/api/s3/post_object.rs +++ b/src/api/s3/post_object.rs @@ -107,7 +107,8 @@ pub async fn handle_post_object( let bucket_id = garage .bucket_helper() .resolve_bucket(&bucket_name, &api_key) - .await?; + .await + .map_err(pass_helper_error)?; if !api_key.allow_write(&bucket_id) { return Err(Error::forbidden("Operation is not allowed for this key.")); @@ -213,7 +214,7 @@ pub async fn handle_post_object( } // if we ever start supporting ACLs, we likely want to map "acl" to x-amz-acl" somewhere - // arround here to make sure the rest of the machinery takes our acl into account. + // around here to make sure the rest of the machinery takes our acl into account. let headers = get_headers(¶ms)?; let expected_checksums = ExpectedChecksums { diff --git a/src/api/s3/put.rs b/src/api/s3/put.rs index 1e3b1b44..bfb0dc9b 100644 --- a/src/api/s3/put.rs +++ b/src/api/s3/put.rs @@ -622,7 +622,7 @@ pub(crate) fn get_headers(headers: &HeaderMap<HeaderValue>) -> Result<HeaderList for (name, value) in headers.iter() { if name.as_str().starts_with("x-amz-meta-") { ret.push(( - name.to_string(), + name.as_str().to_ascii_lowercase(), std::str::from_utf8(value.as_bytes())?.to_string(), )); } diff --git a/src/api/s3/website.rs b/src/api/s3/website.rs index 6af55677..fa36bc32 100644 --- a/src/api/s3/website.rs +++ b/src/api/s3/website.rs @@ -276,7 +276,7 @@ impl Redirect { return Err(Error::bad_request("Bad XML: invalid protocol")); } } - // TODO there are probably more invalide cases, but which ones? + // TODO there are probably more invalid cases, but which ones? Ok(()) } } diff --git a/src/api/signature/payload.rs b/src/api/signature/payload.rs index d6ff62f0..9e5a6043 100644 --- a/src/api/signature/payload.rs +++ b/src/api/signature/payload.rs @@ -47,8 +47,8 @@ pub async fn check_payload_signature( let query = parse_query_map(request.uri())?; if query.contains_key(&X_AMZ_ALGORITHM) { - // We check for presigned-URL-style authentification first, because - // the browser or someting else could inject an Authorization header + // We check for presigned-URL-style authentication first, because + // the browser or something else could inject an Authorization header // that is totally unrelated to AWS signatures. check_presigned_signature(garage, service, request, query).await } else if request.headers().contains_key(AUTHORIZATION) { @@ -132,7 +132,7 @@ async fn check_presigned_signature( let authorization = Authorization::parse_presigned(&algorithm.value, &query)?; // Verify that all necessary request headers are included in signed_headers - // For AWSv4 pre-signed URLs, the following must be incldued: + // For AWSv4 pre-signed URLs, the following must be included: // - the Host header (mandatory) // - all x-amz-* headers used in the request let signed_headers = split_signed_headers(&authorization)?; @@ -306,7 +306,7 @@ pub fn canonical_request( // Note that there is also the issue of path normalization, which I hope is unrelated to the // one of URI-encoding. At least in aws-sigv4 both parameters can be set independently, // and rusoto_signature does not seem to do any effective path normalization, even though - // it mentions it in the comments (same link to the souce code as above). + // it mentions it in the comments (same link to the source code as above). // We make the explicit choice of NOT normalizing paths in the K2V API because doing so // would make non-normalized paths invalid K2V partition keys, and we don't want that. let canonical_uri: std::borrow::Cow<str> = if service != "s3" { |