aboutsummaryrefslogtreecommitdiff
path: root/src/api
diff options
context:
space:
mode:
Diffstat (limited to 'src/api')
-rw-r--r--src/api/admin/error.rs15
-rw-r--r--src/api/common_error.rs37
-rw-r--r--src/api/k2v/api_server.rs8
-rw-r--r--src/api/k2v/batch.rs7
-rw-r--r--src/api/k2v/error.rs9
-rw-r--r--src/api/k2v/item.rs8
-rw-r--r--src/api/router_macros.rs2
-rw-r--r--src/api/s3/api_server.rs3
-rw-r--r--src/api/s3/checksum.rs4
-rw-r--r--src/api/s3/copy.rs5
-rw-r--r--src/api/s3/cors.rs16
-rw-r--r--src/api/s3/error.rs13
-rw-r--r--src/api/s3/get.rs13
-rw-r--r--src/api/s3/list.rs4
-rw-r--r--src/api/s3/post_object.rs5
-rw-r--r--src/api/s3/put.rs2
-rw-r--r--src/api/s3/website.rs2
-rw-r--r--src/api/signature/payload.rs8
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(&params)?;
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" {