|
| 1 | +use crate::raw::*; |
| 2 | +use crate::{Error, ErrorKind, Result}; |
| 3 | + |
| 4 | +/// SanityCheckLayer adds a simple sanity check for list operations. |
| 5 | +/// It ensures that the paths returned by underlying storage start with |
| 6 | +/// the requested prefix. If an unexpected path is returned, it will return |
| 7 | +/// an Unexpected error. |
| 8 | +/// |
| 9 | +/// This layer can detect misbehaving services that return responses for |
| 10 | +/// unrelated keys (see issue #6646). |
| 11 | +#[derive(Default)] |
| 12 | +pub struct SanityCheckLayer; |
| 13 | + |
| 14 | +impl<A: Access> Layer<A> for SanityCheckLayer { |
| 15 | + type LayeredAccess = SanityCheckAccessor<A>; |
| 16 | + |
| 17 | + fn layer(&self, inner: A) -> Self::LayeredAccess { |
| 18 | + SanityCheckAccessor { |
| 19 | + inner, |
| 20 | + } |
| 21 | + } |
| 22 | +} |
| 23 | + |
| 24 | +#[derive(Debug, Clone)] |
| 25 | +pub struct SanityCheckAccessor<A: Access> { |
| 26 | + inner: A, |
| 27 | +} |
| 28 | + |
| 29 | +impl<A: Access> LayeredAccess for SanityCheckAccessor<A> { |
| 30 | + type Inner = A; |
| 31 | + type Reader = A::Reader; |
| 32 | + type Writer = A::Writer; |
| 33 | + type Lister = SanityCheckLister<A::Lister>; |
| 34 | + type Deleter = A::Deleter; |
| 35 | + |
| 36 | + fn inner(&self) -> &Self::Inner { |
| 37 | + &self.inner |
| 38 | + } |
| 39 | + |
| 40 | + async fn read(&self, path: &str, args: OpRead) -> Result<(RpRead, Self::Reader)> { |
| 41 | + self.inner.read(path, args).await |
| 42 | + } |
| 43 | + |
| 44 | + async fn write(&self, path: &str, args: OpWrite) -> Result<(RpWrite, Self::Writer)> { |
| 45 | + self.inner.write(path, args).await |
| 46 | + } |
| 47 | + |
| 48 | + async fn delete(&self) -> Result<(RpDelete, Self::Deleter)> { |
| 49 | + self.inner.delete().await |
| 50 | + } |
| 51 | + |
| 52 | + async fn list(&self, path: &str, args: OpList) -> Result<(RpList, Self::Lister)> { |
| 53 | + let (rp, lister) = self.inner.list(path, args).await?; |
| 54 | + let prefix = path.to_string(); |
| 55 | + let checker = SanityCheckLister { |
| 56 | + inner: lister, |
| 57 | + prefix, |
| 58 | + }; |
| 59 | + Ok((rp, checker)) |
| 60 | + } |
| 61 | +} |
| 62 | + |
| 63 | +pub struct SanityCheckLister<L: oio::List> { |
| 64 | + inner: L, |
| 65 | + prefix: String, |
| 66 | +} |
| 67 | + |
| 68 | +impl<L: oio::List> oio::List for SanityCheckLister<L> { |
| 69 | + async fn next(&mut self) -> Result<Option<oio::Entry>> { |
| 70 | + match self.inner.next().await? { |
| 71 | + Some(entry) => { |
| 72 | + let p = entry.path(); |
| 73 | + if !p.starts_with(&self.prefix) { |
| 74 | + return Err(Error::new( |
| 75 | + ErrorKind::Unexpected, |
| 76 | + &format!( |
| 77 | + "sanity check failed: entry {} is outside prefix {}", |
| 78 | + p, self.prefix |
| 79 | + ), |
| 80 | + )); |
| 81 | + } |
| 82 | + Ok(Some(entry)) |
| 83 | + } |
| 84 | + None => Ok(None), |
| 85 | + } |
| 86 | + } |
| 87 | +} |
0 commit comments