Import upstream v0.16.22, stripped
Upstream commit: 474dd0229cb20cf513036619781ed97bd8073c3f Enterprise-only files removed or emptied: 63 Enterprise-only snippets removed: 117 in 50 files Dangling module declarations removed: 5 Cargo edits turning enterprise off: 14 Verification: clean Enterprise feature gates left for rebuilt features: 19 in 18 files Produced by tools/fork/strip.py. The full report is in docs/fork/strip-reports/ on main.
This commit is contained in:
@@ -0,0 +1,219 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::{KV_QUOTA_BLOB, Server};
|
||||
use mail_parser::{
|
||||
Encoding,
|
||||
decoders::{base64::base64_decode, quoted_printable::quoted_printable_decode},
|
||||
};
|
||||
use store::{
|
||||
U32_LEN, U64_LEN,
|
||||
dispatch::lookup::KeyValue,
|
||||
write::{BatchBuilder, BlobLink, BlobOp, now},
|
||||
};
|
||||
use trc::AddContext;
|
||||
use types::{
|
||||
blob::{BlobClass, BlobId, BlobSection},
|
||||
blob_hash::BlobHash,
|
||||
};
|
||||
|
||||
const COUNT_BYTES: u32 = 20;
|
||||
const COUNT_SHIFT: u32 = 64 - COUNT_BYTES;
|
||||
const SIZE_MASK: u64 = (1u64 << COUNT_SHIFT) - 1;
|
||||
|
||||
pub struct BlobQuotaStatus {
|
||||
pub allowed: bool,
|
||||
pub expires_in: u64,
|
||||
}
|
||||
|
||||
impl Server {
|
||||
pub async fn blob_has_quota(
|
||||
&self,
|
||||
account_id: u32,
|
||||
bytes: usize,
|
||||
) -> trc::Result<BlobQuotaStatus> {
|
||||
if self.core.jmap.upload_tmp_quota_size > 0 || self.core.jmap.upload_tmp_quota_amount > 0 {
|
||||
let now = now();
|
||||
let range_start = now / self.core.jmap.upload_tmp_ttl;
|
||||
let range_end =
|
||||
(range_start * self.core.jmap.upload_tmp_ttl) + self.core.jmap.upload_tmp_ttl;
|
||||
let expires_in = range_end - now;
|
||||
|
||||
let mut bucket = Vec::with_capacity(U32_LEN + U64_LEN + 1);
|
||||
bucket.push(KV_QUOTA_BLOB);
|
||||
bucket.extend_from_slice(account_id.to_be_bytes().as_slice());
|
||||
bucket.extend_from_slice(range_start.to_be_bytes().as_slice());
|
||||
|
||||
self.in_memory_store()
|
||||
.counter_incr(
|
||||
KeyValue::new(bucket, 1i64 << COUNT_SHIFT | bytes as i64).expires(expires_in),
|
||||
true,
|
||||
)
|
||||
.await
|
||||
.caused_by(trc::location!())
|
||||
.map(|v| {
|
||||
let v = v as u64;
|
||||
let count = v >> COUNT_SHIFT;
|
||||
let size = v & SIZE_MASK;
|
||||
|
||||
let allowed = (self.core.jmap.upload_tmp_quota_amount == 0
|
||||
|| count <= self.core.jmap.upload_tmp_quota_amount as u64)
|
||||
&& (self.core.jmap.upload_tmp_quota_size == 0
|
||||
|| size <= self.core.jmap.upload_tmp_quota_size as u64);
|
||||
|
||||
BlobQuotaStatus {
|
||||
allowed,
|
||||
expires_in,
|
||||
}
|
||||
})
|
||||
} else {
|
||||
Ok(BlobQuotaStatus {
|
||||
allowed: true,
|
||||
expires_in: 0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::blocks_in_conditions)]
|
||||
pub async fn put_jmap_blob(&self, account_id: u32, data: &[u8]) -> trc::Result<BlobId> {
|
||||
// First reserve the hash
|
||||
let hash = BlobHash::generate(data);
|
||||
let mut batch = BatchBuilder::new();
|
||||
let until = now() + self.core.jmap.upload_tmp_ttl;
|
||||
|
||||
batch.with_account_id(account_id).set(
|
||||
BlobOp::Link {
|
||||
hash: hash.clone(),
|
||||
to: BlobLink::Temporary { until },
|
||||
},
|
||||
vec![],
|
||||
);
|
||||
|
||||
self.core
|
||||
.storage
|
||||
.data
|
||||
.write(batch.build_all())
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
|
||||
if !self
|
||||
.core
|
||||
.storage
|
||||
.data
|
||||
.blob_exists(&hash)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
{
|
||||
// Upload blob to store
|
||||
self.core
|
||||
.storage
|
||||
.blob
|
||||
.put_blob(hash.as_ref(), data, self.core.email.compression)
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
|
||||
// Commit blob
|
||||
let mut batch = BatchBuilder::new();
|
||||
batch.set(BlobOp::Commit { hash: hash.clone() }, Vec::new());
|
||||
self.core
|
||||
.storage
|
||||
.data
|
||||
.write(batch.build_all())
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
}
|
||||
|
||||
Ok(BlobId {
|
||||
hash,
|
||||
class: BlobClass::Reserved {
|
||||
account_id,
|
||||
expires: until,
|
||||
},
|
||||
section: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn put_temporary_blob(
|
||||
&self,
|
||||
account_id: u32,
|
||||
data: &[u8],
|
||||
hold_for: u64,
|
||||
) -> trc::Result<(BlobHash, BlobOp)> {
|
||||
// First reserve the hash
|
||||
let hash = BlobHash::generate(data);
|
||||
let mut batch = BatchBuilder::new();
|
||||
let until = now() + hold_for;
|
||||
|
||||
batch.with_account_id(account_id).set(
|
||||
BlobOp::Link {
|
||||
hash: hash.clone(),
|
||||
to: BlobLink::Temporary { until },
|
||||
},
|
||||
vec![],
|
||||
);
|
||||
|
||||
self.core
|
||||
.storage
|
||||
.data
|
||||
.write(batch.build_all())
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
|
||||
if !self
|
||||
.core
|
||||
.storage
|
||||
.data
|
||||
.blob_exists(&hash)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
{
|
||||
// Upload blob to store
|
||||
self.core
|
||||
.storage
|
||||
.blob
|
||||
.put_blob(hash.as_ref(), data, self.core.email.compression)
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
|
||||
// Commit blob
|
||||
let mut batch = BatchBuilder::new();
|
||||
batch.set(BlobOp::Commit { hash: hash.clone() }, Vec::new());
|
||||
self.core
|
||||
.storage
|
||||
.data
|
||||
.write(batch.build_all())
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
}
|
||||
|
||||
Ok((
|
||||
hash.clone(),
|
||||
BlobOp::Link {
|
||||
hash,
|
||||
to: BlobLink::Temporary { until },
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn get_blob_section(
|
||||
&self,
|
||||
hash: &BlobHash,
|
||||
section: &BlobSection,
|
||||
) -> trc::Result<Option<Vec<u8>>> {
|
||||
Ok(self
|
||||
.blob_store()
|
||||
.get_blob(
|
||||
hash.as_slice(),
|
||||
(section.offset_start)..(section.offset_start.saturating_add(section.size)),
|
||||
)
|
||||
.await?
|
||||
.and_then(|bytes| match Encoding::from(section.encoding) {
|
||||
Encoding::None => Some(bytes),
|
||||
Encoding::Base64 => base64_decode(&bytes),
|
||||
Encoding::QuotedPrintable => quoted_printable_decode(&bytes),
|
||||
}))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user