summaryrefslogtreecommitdiffstats
path: root/core/src/resource/data.rs
diff options
context:
space:
mode:
Diffstat (limited to 'core/src/resource/data.rs')
-rw-r--r--core/src/resource/data.rs350
1 files changed, 350 insertions, 0 deletions
diff --git a/core/src/resource/data.rs b/core/src/resource/data.rs
new file mode 100644
index 0000000..5affa59
--- /dev/null
+++ b/core/src/resource/data.rs
@@ -0,0 +1,350 @@
+use std::{
+ sync::Arc,
+ default::Default,
+ ops::{Deref, DerefMut}
+};
+
+#[cfg(feature="serde")]
+use serde::{
+ Serialize, Deserialize,
+ ser::{Serializer},
+ de::{Deserializer}
+};
+
+use internals::bind::{base64, quoted_printable};
+use headers::header_components::{
+ MediaType,
+ FileMeta,
+ TransferEncoding,
+ ContentId
+};
+
+
+
+/// POD type containing FileMeta, Content-Type and Content-Id
+///
+/// The file meta contains optional information like file name and read
+/// as well as last modification data.
+///
+/// The media type will be used for the content type header which is used
+/// to determine how a mail client will handle the file. It is also used
+/// to get a hint on how to best transfer encode the file.
+///
+/// The content id is used to identify the "data" and refer to it
+/// from some other place. For example in a mail the html body could
+/// refer to a image contained in the mail to embed it in the mail.
+///
+/// As Content-Id's are supposed to be world unique they could also
+/// be used for some caching and similar but that plays hardly any
+/// role any more, except maybe for "external" mail bodies.
+#[derive(Debug, Clone)]
+#[cfg_attr(feature="serde", derive(Serialize, Deserialize))]
+pub struct Metadata {
+ /// File meta like file name or file read time.
+ #[cfg_attr(feature="serde", serde(flatten))]
+ pub file_meta: FileMeta,
+
+ /// The media type of the data.
+ pub media_type: MediaType,
+
+ /// The content id associated with the data.
+ pub content_id: ContentId
+}
+
+impl Deref for Metadata {
+ type Target = FileMeta;
+
+ fn deref(&self) -> &Self::Target {
+ &self.file_meta
+ }
+}
+
+impl DerefMut for Metadata {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.file_meta
+ }
+}
+
+/// A type containing some data and metadata for it.
+///
+/// This often, but not always, corresponds to data which could potentially
+/// have been a file in a file system. For example a image or a text
+/// document.
+///
+/// This type is mainly used when having auto generated content as content
+/// provided through a file should be loaded from a source and as such
+/// will be directly loaded and transfer encoded.
+///
+/// # Clone
+///
+/// `Data` is made to be cheap to clone and share.
+/// For this it uses `Arc` internally.
+#[derive(Debug, Clone)]
+#[cfg_attr(feature="serde", derive(Serialize, Deserialize))]
+pub struct Data {
+ #[cfg_attr(feature="serde", serde(with="arc_buffer_serde"))]
+ buffer: Arc<[u8]>,
+ #[cfg_attr(feature="serde", serde(flatten))]
+ #[cfg_attr(feature="serde", serde(with="arc_serde"))]
+ meta: Arc<Metadata>
+}
+
+
+impl Data {
+
+ /// Create a new data instance.
+ pub fn new(
+ buffer: impl Into<Arc<[u8]>>,
+ meta: impl Into<Arc<Metadata>>
+ ) -> Self {
+ Data {
+ buffer: buffer.into(),
+ meta: meta.into()
+ }
+ }
+
+ pub fn plain_text(text: impl Into<String>, cid: ContentId) -> Data {
+ let text = text.into();
+ let buf = text.into_bytes();
+ let meta = Metadata {
+ file_meta: Default::default(),
+ media_type: MediaType::parse("text/plain; charset=utf-8").unwrap(),
+ content_id: cid
+ };
+ Self::new(buf, meta)
+ }
+
+ /// Access the raw data buffer of this instance.
+ pub fn buffer(&self) -> &Arc<[u8]> {
+ &self.buffer
+ }
+
+ /// Access the metadata.
+ pub fn metadata(&self) -> &Arc<Metadata> {
+ &self.meta
+ }
+
+ /// Access the file meta metadata.Fn
+ pub fn file_meta(&self) -> &FileMeta {
+ &self.meta.file_meta
+ }
+
+ /// Access the content type.
+ pub fn media_type(&self) -> &MediaType {
+ &self.meta.media_type
+ }
+
+ /// Access the content id.
+ pub fn content_id(&self) -> &ContentId {
+ &self.meta.content_id
+ }
+
+ /// Transfer encode the given data.
+ ///
+ /// This function will be called by the context implementation when
+ /// loading and/or transfer encoding data. The context implementation
+ /// might also not call it if it has a cached version of the transfer
+ /// encoded data.
+ ///
+ /// This functions expect a boundary pool and will remove all boundaries
+ /// which do appear in the encoded representation of the data.
+ #[inline(always)]
+ pub fn transfer_encode(
+ &self,
+ encoding_hint: TransferEncodingHint,
+ ) -> EncData {
+ // delegated to free function at end of file for
+ // readability
+ transfer_encode(self, encoding_hint)
+ }
+}
+
+/// `EncData` is like `Data` but the buffer contains transfer encoded data.
+///
+/// # Clone
+///
+/// `Data` is made to be cheap to clone and share.
+/// For this it uses `Arc` internally.
+#[derive(Debug, Clone)]
+#[cfg_attr(feature="serde", derive(Serialize, Deserialize))]
+pub struct EncData {
+ #[cfg_attr(feature="serde", serde(with="arc_buffer_serde"))]
+ buffer: Arc<[u8]>,
+ #[cfg_attr(feature="serde", serde(flatten))]
+ #[cfg_attr(feature="serde", serde(with="arc_serde"))]
+ meta: Arc<Metadata>,
+ encoding: TransferEncoding
+}
+
+impl EncData {
+
+ /// Create a new instance from transfer encoded data
+ /// as well as metadata and the encoding used to transfer
+ /// encode the data.
+ ///
+ /// If the `buffer` was created by transfer encoding data
+ /// from a `Data` instance the `Arc<Metadata>` from that
+ /// `Data` instance can be passed in directly as `meta`.
+ pub(crate) fn new(
+ buffer: impl Into<Arc<[u8]>>,
+ meta: impl Into<Arc<Metadata>>,
+ encoding: TransferEncoding
+ ) -> Self {
+ EncData {
+ buffer: buffer.into(),
+ meta: meta.into(),
+ encoding
+ }
+ }
+
+ /// Access the raw transfer encoded data.
+ pub fn transfer_encoded_buffer(&self) -> &Arc<[u8]> {
+ &self.buffer
+ }
+
+ /// Access the metadata.
+ pub fn metadata(&self) -> &Arc<Metadata> {
+ &self.meta
+ }
+
+ /// Access the file meta metadata.Fn
+ pub fn file_meta(&self) -> &FileMeta {
+ &self.meta.file_meta
+ }
+
+ /// Access the content type.
+ pub fn media_type(&self) -> &MediaType {
+ &self.meta.media_type
+ }
+
+
+ /// Access the transfer encoding used to encode the buffer.
+ pub fn encoding(&self) -> TransferEncoding {
+ self.encoding
+ }
+
+ /// Access the content id.
+ ///
+ /// The content id is for the data itself so it should not
+ /// change just because the data had been transfer encoded.
+ ///
+ /// # Note about fixed newlines:
+ ///
+ /// The encoding functions of this library will always "fix"
+ /// line endings even if the transfer encoding is to not have
+ /// any encoding, it could be said that this is a modification
+ /// of the data and as such the content id should change. But
+ /// as this is done _always_ and as such only the transfer encoded
+ /// data is "send" out this works out fine.
+ pub fn content_id(&self) -> &ContentId {
+ &self.meta.content_id
+ }
+}
+
+/// Hint to change how data should be transfer encoded.
+#[derive(Debug, PartialEq)]
+#[cfg_attr(feature="serde", derive(Serialize, Deserialize))]
+pub enum TransferEncodingHint {
+ /// Use Base64 encoding.
+ UseBase64,
+
+ /// Use Quoted-Printable encoding.
+ UseQuotedPrintable,
+
+ // /// Do not assume Mime8Bit is available.
+ // ///
+ // /// As such do not encode ascii/utf-8 "as is" (e.g. not encoding them).
+ // ///
+ // /// Note: This is the default until I'm more sure about the whole thing
+ // /// with puthing things in unecoded.
+ // DoNotUseNoEncoding,
+
+ /// No hint for transfer encoding.
+ NoHint,
+
+ #[cfg_attr(feature="serde", serde(skip))]
+ #[doc(hidden)]
+ __NonExhaustive { }
+}
+
+impl Default for TransferEncodingHint {
+ fn default() -> Self {
+ TransferEncodingHint::UseBase64
+ }
+}
+
+/// Transfer encodes Data.
+///
+/// Util we have a reasonable "non latin letter text" heuristic
+/// or enable none encoded text as default this will always encode
+/// with `Base64` except if asked not to do so.
+///
+/// # Panic
+///
+/// Panics if TransferEncodingHint::__NonExhaustive
+/// is passed to the function.
+fn transfer_encode(
+ data: &Data,
+ encoding_hint: TransferEncodingHint,
+) -> EncData {
+ use self::TransferEncodingHint::*;
+
+ match encoding_hint {
+ UseQuotedPrintable => tenc_quoted_printable(data),
+ UseBase64 | NoHint => tenc_base64(data),
+ __NonExhaustive { .. } => panic!("__NonExhaustive encoding should not be passed to any place")
+ }
+}
+
+fn tenc_base64(data: &Data) -> EncData {
+ let enc_data = base64::normal_encode(data.buffer())
+ .into_bytes();
+
+ EncData::new(enc_data, data.metadata().clone(),
+ TransferEncoding::Base64)
+}
+
+fn tenc_quoted_printable(data: &Data) -> EncData {
+ let enc_data = quoted_printable::normal_encode(data.buffer())
+ .into_bytes();
+
+ EncData::new(enc_data, data.metadata().clone(),
+ TransferEncoding::QuotedPrintable)
+}
+
+
+#[cfg(feature="serde")]
+mod arc_buffer_serde {
+ use super::*;
+
+ pub(crate) fn deserialize<'de, D>(deserializer: D) -> Result<Arc<[u8]>, D::Error>
+ where D: Deserializer<'de>
+ {
+ let bytes = <Vec<u8>>::deserialize(deserializer)?;
+ Ok(bytes.into())
+ }
+
+ pub(crate) fn serialize<S>(data: &Arc<[u8]>, serializer: S) -> Result<S::Ok, S::Error>
+ where S: Serializer
+ {
+ serializer.serialize_bytes(data)
+ }
+}
+
+#[cfg(feature="serde")]
+mod arc_serde {
+ use super::*;
+
+ pub(crate) fn deserialize<'de, OUT, D>(deserializer: D) -> Result<Arc<OUT>, D::Error>
+ where D: Deserializer<'de>, OUT: Deserialize<'de>
+ {
+ let value = OUT::deserialize(deserializer)?;
+ Ok(Arc::new(value))
+ }
+
+ pub(crate) fn serialize<S, IN>(data: &Arc<IN>, serializer: S) -> Result<S::Ok, S::Error>
+ where S: Serializer, IN: Serialize
+ {
+ IN::serialize(&**data, serializer)
+ }
+} \ No newline at end of file