From ce63ad36c703dad8f0639f9c53c9197419dec95c Mon Sep 17 00:00:00 2001 From: Philipp Korber Date: Thu, 22 Nov 2018 14:20:48 +0100 Subject: refactor: prepared for merging with another repo --- .gitignore | 5 - .travis.yml | 8 - .vscode/settings.json | 9 + Cargo.toml | 39 ---- LICENSE-APACHE | 201 ------------------ LICENSE-MIT | 25 --- README.md | 126 ------------ notes/notes.md | 331 ------------------------------ src/additional_cid.rs | 60 ------ src/base_dir.rs | 153 -------------- src/handlebars.rs | 96 --------- src/lib.rs | 411 ------------------------------------- src/path_rebase.rs | 191 ------------------ src/serde_impl.rs | 447 ----------------------------------------- template/Cargo.toml | 39 ++++ template/README.md | 126 ++++++++++++ template/notes/notes.md | 331 ++++++++++++++++++++++++++++++ template/src/additional_cid.rs | 60 ++++++ template/src/base_dir.rs | 153 ++++++++++++++ template/src/handlebars.rs | 96 +++++++++ template/src/lib.rs | 411 +++++++++++++++++++++++++++++++++++++ template/src/path_rebase.rs | 191 ++++++++++++++++++ template/src/serde_impl.rs | 447 +++++++++++++++++++++++++++++++++++++++++ 23 files changed, 1863 insertions(+), 2093 deletions(-) delete mode 100644 .gitignore delete mode 100644 .travis.yml create mode 100644 .vscode/settings.json delete mode 100644 Cargo.toml delete mode 100644 LICENSE-APACHE delete mode 100644 LICENSE-MIT delete mode 100644 README.md delete mode 100644 notes/notes.md delete mode 100644 src/additional_cid.rs delete mode 100644 src/base_dir.rs delete mode 100644 src/handlebars.rs delete mode 100644 src/lib.rs delete mode 100644 src/path_rebase.rs delete mode 100644 src/serde_impl.rs create mode 100644 template/Cargo.toml create mode 100644 template/README.md create mode 100644 template/notes/notes.md create mode 100644 template/src/additional_cid.rs create mode 100644 template/src/base_dir.rs create mode 100644 template/src/handlebars.rs create mode 100644 template/src/lib.rs create mode 100644 template/src/path_rebase.rs create mode 100644 template/src/serde_impl.rs diff --git a/.gitignore b/.gitignore deleted file mode 100644 index a10770d..0000000 --- a/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -/target -Cargo.lock -.idea/ -.vscode/ -*.iml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 6e09135..0000000 --- a/.travis.yml +++ /dev/null @@ -1,8 +0,0 @@ -language: rust -rust: - - stable - - beta - - nightly -matrix: - allow_failures: - - rust: nightly \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..ab9b2b2 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,9 @@ +{ + "cSpell.words": [ + "Deserializer", + "Serializer", + "deserialize", + "embeddings", + "serde" + ] +} \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml deleted file mode 100644 index 18ef13f..0000000 --- a/Cargo.toml +++ /dev/null @@ -1,39 +0,0 @@ -[package] -name = "mail-template" -version = "0.2.0" -description = "[mail] provides a way to create bind string template engines to produce mails" -authors = ["Philipp Korber "] -keywords = ["mail-api", "template"] -categories = [] -license = "MIT OR Apache-2.0" -readme = "./README.md" -documentation = "https://docs.rs/mail-template" -repository = "https://github.com/1aim/mail-template" - -[features] -default = [] -handlebars-bindings = ["handlebars"] - -[dependencies] -mail-core = { git="https://github.com/1aim/mail", features=["serde-impl"] } -mail-internals = { git="https://github.com/1aim/mail" } -mail-headers = { git="https://github.com/1aim/mail", features=["serde-impl"] } - -failure = "0.1.1" -futures = "0.1.14" -vec1 = { version="1.1", features=["serde"]} -soft-ascii-string = "1.0" -serde = { version="1", features=["derive"] } -toml = "0.4.8" -maybe-owned = "0.3.2" - -handlebars = { version="1.1.0", optional=true } - -[dependencies.mime] -git="https://github.com/1aim/mime" -branch="parser_revamp" -version="0.4.0" - - -[dev-dependencies] - diff --git a/LICENSE-APACHE b/LICENSE-APACHE deleted file mode 100644 index 56434b8..0000000 --- a/LICENSE-APACHE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT deleted file mode 100644 index b25ff75..0000000 --- a/LICENSE-MIT +++ /dev/null @@ -1,25 +0,0 @@ -Copyright (c) - -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md deleted file mode 100644 index 104c9a8..0000000 --- a/README.md +++ /dev/null @@ -1,126 +0,0 @@ - -# mail-template - -**Provides mechanisms for generating mails based on templates** - ---- - -This crate provides a general interface for using template engine with the mail crate. - -It's core is the `TemplateEngine` trait which can be implemented to bind a template engine. -When rendering a template the template engine implementing the `TemplateEngine` trait will -produce a number of (wrapped) `Resource` instances representing the alternate bodies of amail as well as a number of additional `Resources` used for embedded content (e.g. logoimages) and attachments. This crate then takes this parts and composes a multipart mime mail from -it. - -## Template Engine implementations - -A mail template engine has to do more then just taking a single text -template (e.g. a handlebars template) and produce some output using -string magic. It has to: - -1. consider alternate bodies, so it should render at last two - "text templates" (one text/plain, one html) - -2. consider which additional embeddings/attachments should be included - (all in the given template data are included, but you might - add additional ones, e.g. some logo image) - -As such text template engines like `Handle` bar can not directly -be bound to the `TemplateEngine` trait. - -For using text template engine it's recommended to use -the `mail-template-render-engine` (also exposed through the -mail facade) which implements this overhead for any engine -which can "just" render some text and provides default -bindings to some common template engines (e.g. Handlebars). - -## Derive - -This crate requires template data to implement `InspectEmbeddedResources` -which combined with some typing/generic design decisions allows to bind -not just to template engines which use serialization to access template -data but also to such which use static typing (like `askama`). - -As such it re-exports the `InspectEmbeddedResources` derive from -`mail-derive`. Note that if you use the mail facade it also does -re-export the derive. - -## Features - -- `askama-engine`, includes bindings for the askama template engine. -- `serialize-to-content-id`, implements Serialize for `Embedded`, - `EmbeddedWithCId` which serializes the embedded type **into its - content id**. E.g. a image with content id `"q09cu3@example.com"` - will be serialized to the string `"q09cu3@example.com"`. This is - extremely useful for all template engines which use serialization - as their way to access template data. - -## Example - -```rust -``` - -## Road Map - -The current implementation has a number of limitations which should be lifted with -future versions: - -- Only a limited subset of headers are/can be set through the template engine - (`Sender`, `From`, `To`, `Subject`) while some headers are set implicitly - when encoding the mail (e.g. `Date`, `Content-Type`, `Content-Disposition`). - But sometimes it would be useful to add some custom headers through the template - engine (both on the outermost and inner bodies). -- `From`, `To`, `Subject` have to be given, but sometimes you might want to just - create the `Mail` type and then set them by yourself (through you _can_ currently - override them) -- Re-use/integration of existing mail instances: Some times you might want to - use a `Mail` instance created some where else as a body for a multipart mail - generated from a template (e.g. some thing generating "special" attachments). - -Also there are some parts which are likely to change: - -- `MailSendData`/`MailSendDataBuilder` the name is - not very good it also needs to change to handle - the thinks listed above -- `Embedded`, `EmbeddedWithCid`, embeddings and attachments - currently a `Embedded` instance is a wrapper around `Resource` - representing something which will become a mail body but is not - a main body (i.e. it not the text/html/.. you send) instead it - something embedded in the mail which is either used as attachment - or as a embedding (e.g. a logo image). Through the content disposition - the `Embedded` instance differs between thing embedded and internally - used or embedded and used as attachment at the same time many different - arrays are sometimes used to differ between them (e.g. in `MailParts`) - but there is no (type system) check to make sure a array of thinks used - as attachments can not contain a `Embedded` instance with content disposition - inline. The result will still be a valid mail, but likely not in the - way you expect it to be. This should be fixed one way or another (making - the different array type safe and lifting disposition to the type level - had been used but didn't play out nicely). -- `serialize-to-content-id`, is a nice idea but has some problems in - some edge cases (e.g. when you want to serialize anything containing - a `Embedded` type for any usage BUT accessing it in an template). So - it might be removed, which means a import like `cid:{{data.mything}}` - (using some mustach pseudo template syntax) would become `cid:{{data.mything.cid}}`. - - -## Documentation - - -Documentation can be [viewed on docs.rs](https://docs.rs/mail-template). -(once published) - -## License - -Licensed under either of - -* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) -* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) - -at your option. - -### Contribution - -Unless you explicitly state otherwise, any contribution intentionally submitted -for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any -additional terms or conditions. diff --git a/notes/notes.md b/notes/notes.md deleted file mode 100644 index a6d68ea..0000000 --- a/notes/notes.md +++ /dev/null @@ -1,331 +0,0 @@ - -# Outer Most interface - -something like a Mailer which might implement tokio_servie::Service (if -so multiple parameters are wrapped into a tupple) - -mailer contains information like `from` - -`mailer.send_mails( recipients_data, mail_gen )` - -where recipients_data is a iterable mapping from address to recipient specific data, -e.g. `Vec<(Address, Data)>` - -and mail_gen is something like `trait MailGen { fn gen_mail( from, to, data, bits8support ) -> MailBody; }` - -`MailBody` is not `tokio_smtp::MailBody` but has to implement nessesray contraints, -(e.g. implemnting `toki_smtp::IntoMailBody` not that for the beginning this will be -hard encoded but later one a generic variation allowing `smtp` to be switched out -by something else is also possible`) - -MailGen implementations are not done by hand but implemented ontop of something -like a template spec e.g. `struct TemplateSpec { id_template: TemplateId, additional_appendixes: Vec }` - -Where `TemplateId` can is e.g. `reset_link` leading to the creation of a `html` with alternate `plain` -mail iff there is a `reset_link.html` and a `reset_link.plain` template. A `reset_link.html.data` -folder could be used to define inline (mime related) appendixes like embedded images, -but we might want to have a way to define such embeddigns through the data ( -E.g. by mapping `Data => TemplateEnginData` and replacing `EmbeddedFile` variations -by a new related id and adding the `EmbeddedFile(data)` data to the list of embeddings) - - - -# List of parts possible non-ascii and not ascii encodable - -- local-part (address/addr-spec/local-part) - -# Limitations - -Line length limit: - -SHOULD be no more than 78 chars (excluding CRLF!) -MUST NOT be more than 998 chars (excluding CRLF) - -# Orphan `\n`,`\r` - -MUST NOT occur in header (except for folding) -MUST NOT occur in body (except for newline) - -## Header specific limitations - -- encoded word max length of 75 chars -- spaces around encoed words are ignored?? - - -# Email Address part (a@b.e) - -- there is a `domain-literal` version which does use somthing like `[some_thing]`, - we can use puny code for converting domains into ascii but probably can't use - this with `domain-literal`'s - -- `local-part` is `dot-atom` which has leading and trailing `[CFWS]` so comments are alowed - -- MessageId uses a email address like syntax but without supporting spaces/comments - - -# MIME - -fields containing mime types can have parameters with a `; key=value` style -this is mainly used for `multipart/mixed; boundary=blablabla` and similar. - -You have to make sure the boundary does not appear in any of the "sub-bodies", -this is kinda easy for bodies with e.g. content transfer encoding Base64, -but can be tricky in combination with some other content as normal text -can totally contain the boundary. To prevent this: - -- use long boundary strings -- encode the body with base64 even if it's "just" ascii - - OR check the content and encode parts of it if necessary - -you can have multipart in multipart creating a tree, -make sure you don't mix up the boundaries - - -A body part does not have to have any headers, assume default values if -there is no header, bodies which have no header _have to start with a -blank line_ separating 0 headers from the body. - -Header fields of bodies which do not start with `Content-` _are ignored_! - -Contend types: - -- `mixed`, list of sub-bodies with mixed mime types, might be displayed inline or as appendix - - use >>`Content-Disposition` (RFC 2183)<< to controll this, even through it's not standarized yet (or is it by now?) - - default body mime type is `text/plain` -- `digest` for combining muliple messages of content type `message/rfc822` - - e.g. `(multipar/mixed ("table of content") (multipart/digest "message1", "message2"))` - - `message` (mainly `message/rfc822`) contains _another_ email, e.g. for digest - - wait is there a `multipart/message`?? proably not! -- `alternative` multiple alternative versions of the "same" information - - e.g. `(multipart/alternative (text/plain ...) (text/html ...))` - - _place preferred form last!_ (i.e. increasing order of preference) - - interesting usage with `application/X-FixedRecord`+`application/octet-stream` -- `related` (RFC 2387) all bodies are part of one howl, making no (less) sense if placed alone - - the first part is normally the entry point, but this can be chaged through parameters - - (only relevant for parsing AND interpreting it, but not for generating as we can always use the default) - - Content-ID is used to specify a id on each body respectivly which can be used to refer to it (e.g. in HTML) - - in html use e.g. `' ... ) - (image/png (Content-ID ) ... ) - ... )) - (image/png (Content-Disposition attachment) ...) - (image/png (Content-Disposition attachment) ...)) -``` - -Possible alternate structure: - -``` -(multipart/mixed - (multipart/related - - (multipart/alternative - (text/plain ... '[cid:ContentId@1aim.com]' ... ) - (text/html ... '' ... ) ) - - (image/png (Content-ID ) ... ) ) - - (image/png (Content-Disposition attachment) ...) - (image/png (Content-Disposition attachment) ...)) -``` - -but I have not seen the `[cid:...]` for text/plain in any standard, through it might be there. -Also if se we might still have a related specific for the html (for html only stuff) so: -- place Embedding in Data in the outer `multipart/related` -- place Embedding returned by the template in inner `multipart/related` - -# Attatchment - -proposed filenames for attachments can be given through parameters of the disposition header - -it does not allow non ascii character there! - -see rfc2231 for more information, it extends some part wrt.: - -- splitting long parameters (e.g. long file names) -- specifying language and character set -- specifying language for encoded words - -# Encoded Words - -extended by rfc2231 - -additional limits in header fields - -header containing encoded words are limited to 76 bytes - -a "big" text chunk can be split in multiple encoded words seperated by b'\r\n ' - -non encoded words and encoded words can apear in the same header field, but -must be seperate by "linear-white-space" (space) which is NOT removed when -decoding encoded words - -encoded words can appear in: - -- `text` sections where `text` is based on RFC 822! (e.g. Content-Description ) - - in context of RFC 5322 this means `unstructured` count's as text -- `comments` (as alternative to `ctext`,`quoted-pair`,`comment` -- `word`'s within a `phrase` - -**Therefor it MUST NOT appear in any structured header field except withing a `comment` or `phrase`!** - -**You have to encode text which looks like an encoded word** - - - -limitations: - -- in comment's no ')',')' and '"' -- in headers no ' ' - - -# Other - -there is no `[CFWS]` after the `:` in Header fields, -but most (all?) of the parts following them are allowed -to start with a `[CFWS]`. (exception is unstructured where -a `CFWS` can be allowed but also MIGHT be part of the -string) - -CFWS -> (un-) foldable whitespace allowing comments -FWS -> (un-) foldable whitespace without comments - - -# Relevant RFCs -5321, 5322, 6854, 3492, 2045, 2046, 2047, 4288, 4289, 2049, 6531, 5890 - -make sure to not use the outdated versions - - -# Parsing Notes - -be strict when parsing (e.g. only ws and printable in subject line) - -if "some other" strings should still be supported do not do zero -copy, but instead add the data to a new buff _replacing invalid -chars with replacement symbol or just stripping them_ - - -# Non-utf8 Non-Ascci bytes in Mail body - -The mail body can contain non-utf8, non-ascii data (e.g. -utf16 data, images etc.) WITHOUT base64 encoding if -8BITMIME is supported (note there is also BINARY and CHUNKING) - -smtp still considers _the bytes_ corresponding to CR LF and DOT special. - -- there is a line length limit, lines terminate with b'CRLF' -- b'.CRLF' does sill end the body (if preceeded by CRLF, or body starts with it) - - so dot-staching is still done on protocol level - - - -## Hot to handle `obs-` parsings - -we have to be able to parse mails with obsolete syntax (theoretically) -but should never genrate such mails, the encder excepts its underlying -data to be correct, but it might not be if we directly place `obs-` -parsed data there. For many parts this is no problem as the -`obs-` syntax is a lot about having FWS at other positions, -_between components_ (so we won't have a problem with the encoder). -Or some additional obsolete infromations (which we often/allways can just -"skip" over). So we have to check if there are any braking cases and if -we have to not zero copy them when parsing but instead transform them -into a valide representation, in worst case we could add a `not_encodable` -field to some structs. - -# TODO -check if some parts are empty and error if encode is called on them -e.g. empty domain - -make sure trace and resend fields are: - -1. encoded in order (MUST) -2. encoded as blocks (MUST?) -3. encoded before other fields (SHOULD) - -as people may come up with their own trace like fileds, -rule 1 and 2 should appy to all fields - - -make sure trace,resent-* are multi fields - -add a RawUnstructured not doing any encoding, but only validity checking - -# Postponded - -`component::Disposition` should have a `Other` variant, using `Token` (which -means a general extension token type is needed) - -other features like signature, encryption etc. - -check what happens if I "execute" a async/mio/>tokio< -based future in a CPU pool? Does it just do live -polling in the thread? Or does it act more intelligent? -or does it simply fail? - -just before encoding singlepart bodies, resource is resolved, -therefore: - -1. we now have the MediaType + File meta + TransferEncoding -2. add* ContentType header to headers -3. add* ContentTransferEncoding header to headers -4. add* file meta infor to ContentDisposition header if it exists -5. note that >add*< is not modifying Mail, but adds it to the list of headers to encode - - -warn when encoding a Disposition of kind Attachment which's -file_meta has no name set - - -// From RFC 2183: -// NOTE ON PARAMETER VALUE LENGHTS: A short (length <= 78 characters) -// parameter value containing only non-`tspecials' characters SHOULD be -// represented as a single `token'. A short parameter value containing -// only ASCII characters, but including `tspecials' characters, SHOULD -// be represented as `quoted-string'. Parameter values longer than 78 -// characters, or which contain non-ASCII characters, MUST be encoded as -// specified in [RFC 2184]. -provide a gnneral way for encoding header parameter which follow the scheme: -` *(";" "=" )` this are ContentType and ContentDisposition - for now - - -IF Item::Encoded only appears as encoded word, make it Item::Encoded word, -possible checking for "more" validity then noew - - -email::quote => do not escape WSP, and use FWS when encoding -also make quote, generally available for library useers a -create_quoted_string( .. ) - -# Dependencies - -quoted_printable and base64 have some problems: -1. it's speaking of a 76 character limit where it is 78 - it seems they treated the RFC as 78 character including - CRLF where the RFC speaks of 78 characters EXCLUDING - CRLF -2. it's only suited for content transfer encoding the body - as there is a limit of the length of encoded words (75) - which can't be handled by both - -also quoted_printable has another problem: -3. in headers the number of character which can be displayed without - encoding is more limited (e.g. no ' ' ) quoted_printable does not - respect this? (TODO CHECK THIS) diff --git a/src/additional_cid.rs b/src/additional_cid.rs deleted file mode 100644 index eecf2e7..0000000 --- a/src/additional_cid.rs +++ /dev/null @@ -1,60 +0,0 @@ -use std::collections::{ - HashMap, HashSet -}; - -use serde::{Serialize, Serializer}; - -use mail_core::Resource; -use mail_headers::header_components::ContentId; - -pub struct AdditionalCIds<'a> { - additional_resources: &'a [&'a HashMap] -} - -impl<'a> AdditionalCIds<'a> { - - /// Creates a new `AdditionalCIds` instance. - /// - /// All resources in the all hash maps have to be loaded to the - /// `Data` or `EncData` variants or using `get` can panic. - pub(crate) fn new(additional_resources: &'a [&'a HashMap]) -> Self { - AdditionalCIds { additional_resources } - } - - - /// Returns the content id associated with the given name. - /// - /// If multiple of the maps used to create this type contain the - /// key the first match is returned and all later ones are ignored. - /// - /// # Panic - /// - /// If the resource exists but is not loaded (i.e. has no content id) - /// this will panic as this can only happen if there is a bug in the - /// mail code, or this type was used externally. - pub fn get(&self, name: &str) -> Option<&ContentId> { - for possible_source in self.additional_resources { - if let Some(res) = possible_source.get(name) { - return Some(res.content_id().expect("all resources should be loaded/have a content id")); - } - } - return None; - } -} - -impl<'a> Serialize for AdditionalCIds<'a> { - fn serialize(&self, serializer: S) -> Result - where S: Serializer - { - let mut existing_keys = HashSet::new(); - serializer.collect_map( - self.additional_resources - .iter() - .flat_map(|m| m.iter().map(|(k, resc)| { - (k, resc.content_id().expect("all resources should be loaded/have a content id")) - })) - .filter(|key| existing_keys.insert(key.to_owned())) - ) - } -} - diff --git a/src/base_dir.rs b/src/base_dir.rs deleted file mode 100644 index 91e824a..0000000 --- a/src/base_dir.rs +++ /dev/null @@ -1,153 +0,0 @@ -use std::{ - path::{Path, PathBuf}, - ops::{Deref, DerefMut}, - env, io -}; - -use serde::{ - ser::{Serialize, Serializer}, - de::{Deserialize, Deserializer} -}; - -#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct CwdBaseDir(PathBuf); - -impl CwdBaseDir { - - /// Creates a new `CwdBaseDir` instance containing exactly the given path. - pub fn new_unchanged(path: PathBuf) -> Self { - CwdBaseDir(path) - } - - /// Creates a `CwdBaseDir` from a path by prefixing the path with the - /// current working dir if it's relative. - /// - /// If the path is not relative it's directly used. - /// - /// # Os state side effects - /// - /// As this function accesses the current working directory (CWD) it's - /// not pure as the CWD can be changed (e.g. by `std::env::set_current_dir`). - /// - /// # Error - /// - /// As getting the CWD can fail this function can fail with a I/O Error, too. - pub fn from_path

(path: P) -> Result - where P: AsRef + Into - { - let path = - if path.as_ref().is_absolute() { - path.into() - } else { - let mut cwd = env::current_dir()?; - cwd.push(path.as_ref()); - cwd - }; - - Ok(CwdBaseDir(path)) - } - - /// Turns this path into a `PathBuf` by stripping the current working dir - /// if it starts with it. - /// - /// If this path does not start with the CWD it's returned directly. - /// - /// # Os state side effects - /// - /// As this function used the current working dir (CWD) it is affected - /// by any function changing the CWD as a side effect. - /// - /// # Error - /// - /// Accessing the current working dir can fail, as such this function - /// can fail. - pub fn to_base_path(&self) -> Result<&Path, io::Error> { - let cwd = env::current_dir()?; - self.strip_prefix(&cwd) - .or_else(|_err_does_not_has_that_prefix| { - Ok(&self) - }) - } - - /// Turns this instance into the `PathBuf` it dereferences to. - pub fn into_inner_with_prefix(self) -> PathBuf { - let CwdBaseDir(path) = self; - path - } -} - -impl Deref for CwdBaseDir { - type Target = PathBuf; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for CwdBaseDir { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl AsRef for CwdBaseDir { - fn as_ref(&self) -> &Path { - &self.0 - } -} - - - -impl<'de> Deserialize<'de> for CwdBaseDir { - fn deserialize(deserializer: D) -> Result - where D: Deserializer<'de> - { - use serde::de::Error; - let path_buf = PathBuf::deserialize(deserializer)?; - Self::from_path(path_buf) - .map_err(|err| D::Error::custom(err)) - } -} - -impl Serialize for CwdBaseDir { - fn serialize(&self, serializer: S) -> Result - where S: Serializer, - { - use serde::ser::Error; - let path = self.to_base_path() - .map_err(|err| S::Error::custom(err))?; - - path.serialize(serializer) - } -} - - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn from_path_does_not_affect_absolute_paths() { - let path = Path::new("/the/dog"); - let base_dir = CwdBaseDir::from_path(path).unwrap(); - assert_eq!(&*base_dir, Path::new("/the/dog")) - } - - #[test] - fn from_path_prefixes_with_cwd() { - let cwd = env::current_dir().unwrap(); - let expected = cwd.join("./the/dog"); - - let base_dir = CwdBaseDir::from_path("./the/dog").unwrap(); - assert_eq!(&*base_dir, &expected); - } - - #[test] - fn to_base_path_removes_cwd_prefix() { - let cwd = env::current_dir().unwrap(); - let dir = cwd.join("hy/there"); - let base_dir = CwdBaseDir::new_unchanged(dir); - let path = base_dir.to_base_path().unwrap(); - assert_eq!(path, Path::new("hy/there")); - } -} \ No newline at end of file diff --git a/src/handlebars.rs b/src/handlebars.rs deleted file mode 100644 index 887dce1..0000000 --- a/src/handlebars.rs +++ /dev/null @@ -1,96 +0,0 @@ -use hbs; -use serde::{Serialize, Deserialize}; -use galemu::{Bound, BoundExt, create_gal_wrapper_type, Ref}; -use failure::Error; - - -use super::{ - TemplateEngine, - TemplateEngineCanHandleData, - BodyTemplate, - PreparationData, - AdditionalCIds, - PathRebaseable, - UnsupportedPathError, - serde_impl -}; - - - -pub struct Handlebars { - inner: hbs::Handlebars, - name_counter: usize -} - -impl Handlebars { - - fn next_body_template_name(&mut self) -> String { - let name = format!("body_{}", self.name_counter); - self.name_counter += 1; - name - } -} -impl TemplateEngine for Handlebars { - type Id = String; - - type LazyBodyTemplate = serde_impl::StandardLazyBodyTemplate; - - fn load_body_template(&mut self, tmpl: Self::LazyBodyTemplate) - -> Result, Error> - { - let StandardLazyBodyTemplate { - path, embeddings, media_type - } = tmpl; - - let name = self.next_body_template_name(); - self.inner.register_template_file(name, path)?; - - let media_type = - if let Some(media_type) = media_type { - media_type - } else if let Some(extension) = path.extension().and_then(|osstr| osstr.as_str()) { - match extension { - "html" => "text/html; charset=utf-8".parse().unwrap(), - "txt" => "text/plain; charset=utf-8".parse().unwrap() - } - } else { - return Err(failure::err_msg( - "handlebars requires html/txt file extension or media type given in template spec" - )); - }; - - Ok(BodyTemplate { - template_id: name, - media_type: TODO, - inline_embeddings: Default::default(), - }) - } - - fn load_subject_template(&mut self, template_string: String) - -> Result - { - Ok(self.inner.register_template_string("subject".to_owned(), template_string)?) - } -} - -/// Additional trait a template engine needs to implement for the types it can process as input. -/// -/// This could for example be implemented in a wild card impl for the template engine for -/// any data `D` which implements `Serialize`. -impl TemplateEngineCanHandleData for Handlebars - where D: Serialize -{ - fn render<'r, 'a>( - &'r self, - id: &'r Self::Id, - data: &'r D, - additional_cids: AdditionalCIds<'r> - ) -> Result { - Ok(self.inner.render(id, SerHelper { data, cids: additional_cid })?) - } -} - -struct SerHelper<'r, D> { - data: &'r D, - cids: AdditionalCIds<'r> -} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index b889213..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,411 +0,0 @@ -extern crate failure; -extern crate serde; -extern crate futures; -extern crate mail_core; -extern crate mail_headers; -extern crate vec1; -extern crate toml; -extern crate maybe_owned; -#[cfg(feature="handlebars")] -extern crate handlebars as hbs; - -#[cfg(all(feature="handlebars", not(feature="handlebars-bindings")))] -compile_error!("use feature `handlebars-bindings` instead of opt-dep-auto-feature `handlebars`"); - -use std::{ - fs, - collections::HashMap, - fmt::Debug, - path::{Path, PathBuf}, - ops::Deref -}; - -use serde::{ - Serialize, - Deserialize -}; -use failure::{Fail, Error}; -use futures::{ - Future, Poll, Async, - try_ready, - future::{self, Join, Either} -}; -use vec1::Vec1; -use maybe_owned::MaybeOwned; - -use mail_core::{ - Resource, - Data, Metadata, - Context, ResourceContainerLoadingFuture, - compose::{MailParts, BodyPart}, - Mail -}; -use mail_headers::{ - HeaderKind, Header, - header_components::MediaType, - headers -}; - -pub mod serde_impl; -mod base_dir; -mod path_rebase; -mod additional_cid; - -// #[cfg(feature="handlebars")] -// pub mod handlebars; - -pub use self::base_dir::*; -pub use self::path_rebase::*; -pub use self::additional_cid::*; - -/// Trait used to bind/implement template engines. -pub trait TemplateEngine: Sized { - type Id: Debug; - - type LazyBodyTemplate: PathRebaseable + Debug + Send + Serialize + for<'a> Deserialize<'a>; - - fn load_body_template(&mut self, tmpl: Self::LazyBodyTemplate) - -> Result, Error>; - - fn load_subject_template(&mut self, template_string: String) - -> Result; -} - -/// Additional trait a template engine needs to implement for the types it can process as input. -/// -/// This could for example be implemented in a wild card impl for the template engine for -/// any data `D` which implements `Serialize`. -pub trait TemplateEngineCanHandleData: TemplateEngine { - - fn render<'r, 'a>( - &'r self, - id: &'r Self::Id, - data: &'r D, - additional_cids: AdditionalCIds<'r> - ) -> Result; -} - -/// Load a template as described in a toml file. -pub fn load_toml_template_from_path( - engine: TE, - path: PathBuf, - ctx: &C -) -> impl Future, Error=Error> - where TE: TemplateEngine + 'static, C: Context -{ - - let ctx2 = ctx.clone(); - ctx.offload_fn(move || { - let content = fs::read_to_string(&path)?; - let base: serde_impl::TemplateBase = toml::from_str(&content)?; - let base_dir = path.parent().unwrap_or_else(||Path::new(".")); - let base_dir = CwdBaseDir::from_path(base_dir)?; - Ok((base, base_dir)) - }).and_then(move |(base, base_dir)| base.load(engine, base_dir, &ctx2)) -} - -/// Load a template as described in a toml string; -pub fn load_toml_template_from_str( - engine: TE, - content: &str, - ctx: &C -) -> impl Future, Error=Error> - where TE: TemplateEngine, C: Context -{ - let base: serde_impl::TemplateBase = - match toml::from_str(content) { - Ok(base) => base, - Err(err) => { return Either::B(future::err(Error::from(err))); } - }; - - let base_dir = - match CwdBaseDir::from_path(Path::new(".")) { - Ok(base_dir) => base_dir, - Err(err) => { return Either::B(future::err(Error::from(err))) } - }; - - Either::A(base.load(engine, base_dir, ctx)) -} - - -/// A Mail template. -#[derive(Debug)] -pub struct Template { - template_name: String, - base_dir: CwdBaseDir, - subject: Subject, - /// This can only be in the loaded form _iff_ this is coupled - /// with a template engine instance, as using it with the wrong - /// template engine will lead to potential bugs and panics. - bodies: Vec1>, - //TODO: make sure - embeddings: HashMap, - attachments: Vec, - engine: TE, -} - -impl Template - where TE: TemplateEngine -{ - pub fn inline_embeddings(&self) -> &HashMap { - &self.embeddings - } - - pub fn attachments(&self) -> &[Resource] { - &self.attachments - } - - pub fn engine(&self) -> &TE { - &self.engine - } - - pub fn bodies(&self) -> &[BodyTemplate] { - &self.bodies - } - - pub fn subject_template_id(&self) -> &TE::Id { - &self.subject.template_id - } -} - - -/// Represents one of potentially many alternate bodies in a template. -#[derive(Debug)] -pub struct BodyTemplate { - pub template_id: TE::Id, - pub media_type: MediaType, - pub inline_embeddings: HashMap -} - -impl BodyTemplate - where TE: TemplateEngine -{ - pub fn template_id(&self) -> &TE::Id { - &self.template_id - } - - pub fn media_type(&self) -> &MediaType { - &self.media_type - } - - pub fn inline_embeddings(&self) -> &HashMap { - &self.inline_embeddings - } -} - -/// Represents a template used for generating the subject of a mail. -#[derive(Debug)] -pub struct Subject { - template_id: TE::Id -} - -impl Subject - where TE: TemplateEngine -{ - pub fn template_id(&self) -> &TE::Id { - &self.template_id - } -} - -/// Automatically provides the `prepare_to_render` method for all `Templates` -/// -/// This trait is implemented for all `Templates`/`D`(data) combinations where -/// the templates template engine can handle the given data (impl. `TemplateEngineCanHandleData`) -/// -/// This trait should not be implemented by hand. -pub trait TemplateExt - where TE: TemplateEngine + TemplateEngineCanHandleData -{ - - fn render_to_mail_parts<'r>(&self, data: LoadedTemplateData<'r, D>, ctx: &impl Context) - -> Result<(MailParts, Header), Error>; - - fn render<'r>(&self, data: LoadedTemplateData<'r, D>, ctx: &impl Context) -> Result { - let (parts, subject) = self.render_to_mail_parts(data, ctx)?; - let mut mail = parts.compose(); - mail.insert_header(subject); - Ok(mail) - } -} - - -impl TemplateExt for Template - where TE: TemplateEngine + TemplateEngineCanHandleData -{ - fn render_to_mail_parts<'r>(&self, data: LoadedTemplateData<'r, D>, ctx: &impl Context) - -> Result<(MailParts, Header), Error> - { - let TemplateData { - data, - inline_embeddings, - mut attachments - } = data.into(); - - let subject = self.engine().render( - self.subject_template_id(), - &data, - AdditionalCIds::new(&[]) - )?; - - let subject = headers::Subject::auto_body(subject)?; - - //TODO use Vec1 try_map instead of loop - let mut bodies = Vec::new(); - for body in self.bodies().iter() { - let raw = self.engine().render( - body.template_id(), - &data, - AdditionalCIds::new(&[ - &inline_embeddings, - body.inline_embeddings(), - self.inline_embeddings() - ]) - )?; - - let data = Data::new( - raw.into_bytes(), - Metadata { - file_meta: Default::default(), - media_type: body.media_type().clone(), - content_id: ctx.generate_content_id() - } - ); - - let inline_embeddings = body.inline_embeddings() - .values() - .cloned() - .collect(); - - bodies.push(BodyPart { - resource: Resource::Data(data), - inline_embeddings, - attachments: Vec::new() - }); - } - - attachments.extend(self.attachments().iter().cloned()); - - let mut inline_embeddings_vec = Vec::new(); - for (key, val) in self.inline_embeddings() { - if !inline_embeddings.contains_key(key) { - inline_embeddings_vec.push(val.clone()) - } - } - - inline_embeddings_vec.extend(inline_embeddings.into_iter().map(|(_,v)|v)); - - let parts = MailParts { - //UNWRAP_SAFE (complexly mapping a Vec1 is safe) - alternative_bodies: Vec1::from_vec(bodies).unwrap(), - inline_embeddings: inline_embeddings_vec, - attachments - }; - - Ok((parts, subject)) - } -} - -pub struct TemplateData<'a, D: 'a> { - pub data: MaybeOwned<'a, D>, - pub attachments: Vec, - pub inline_embeddings: HashMap -} - -impl<'a, D> TemplateData<'a, D> { - - pub fn load(self, ctx: &impl Context) -> DataLoadingFuture<'a, D> { - let TemplateData { - data, - attachments, - inline_embeddings - } = self; - - let loading_fut = Resource::load_container(inline_embeddings, ctx) - .join(Resource::load_container(attachments, ctx)); - - DataLoadingFuture { - payload: Some(data), - loading_fut - } - } -} -impl From for TemplateData<'static, D> { - fn from(data: D) -> Self { - TemplateData { - data: data.into(), - attachments: Default::default(), - inline_embeddings: Default::default() - } - } -} - -impl<'a, D> From<&'a D> for TemplateData<'a, D> { - fn from(data: &'a D) -> Self { - TemplateData { - data: data.into(), - attachments: Default::default(), - inline_embeddings: Default::default() - } - } -} - -pub struct LoadedTemplateData<'a, D: 'a>(TemplateData<'a, D>); - -impl<'a, D> From<&'a D> for LoadedTemplateData<'a, D> { - fn from(data: &'a D) -> Self { - LoadedTemplateData(TemplateData::from(data)) - } -} - -impl From for LoadedTemplateData<'static, D> { - fn from(data: D) -> Self { - LoadedTemplateData(TemplateData::from(data)) - } -} - -impl<'a, D> Deref for LoadedTemplateData<'a, D> { - type Target = TemplateData<'a, D>; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl<'a, D> Into> for LoadedTemplateData<'a, D> { - fn into(self) -> TemplateData<'a, D> { - let LoadedTemplateData(data) = self; - data - } -} - -/// Future returned when preparing a template for rendering. -pub struct DataLoadingFuture<'a, D: 'a> { - payload: Option>, - loading_fut: Join< - ResourceContainerLoadingFuture>, - ResourceContainerLoadingFuture> - > -} - -impl<'a, D> Future for DataLoadingFuture<'a, D> { - type Item = LoadedTemplateData<'a, D>; - type Error = Error; - - fn poll(&mut self) -> Poll { - let ( - inline_embeddings, - attachments - ) = try_ready!(self.loading_fut.poll()); - - //UNWRAP_SAFE only non if polled after resolved - let data = self.payload.take().unwrap(); - - let inner = TemplateData { - data, - inline_embeddings, - attachments - }; - - Ok(Async::Ready(LoadedTemplateData(inner))) - } -} diff --git a/src/path_rebase.rs b/src/path_rebase.rs deleted file mode 100644 index 9818aa3..0000000 --- a/src/path_rebase.rs +++ /dev/null @@ -1,191 +0,0 @@ -use std::{ - path::{Path, PathBuf}, - mem -}; - -use mail_core::{IRI, Resource}; -use failure::{Fail, Context}; - -#[derive(Fail, Debug)] -#[fail(display = "unsupported path, only paths with following constraint are allowed: {}", _0)] -pub struct UnsupportedPathError(Context<&'static str>); - -impl UnsupportedPathError { - pub fn new(violated_constraint: &'static str) -> Self { - UnsupportedPathError(Context::new(violated_constraint)) - } -} - -pub trait PathRebaseable { - /// Prefixes path in the type with `base_dir`. - /// - /// # Error - /// - /// Some implementors might not support all paths. - /// For example a implementor requiring rust string - /// compatible paths might return a - /// `Err(UnsupportedPathError::new("utf-8"))`. - fn rebase_to_include_base_dir(&mut self, base_dir: impl AsRef) - -> Result<(), UnsupportedPathError>; - - /// Removes the `base_dir` prefix. - /// - /// # Error - /// - /// Some implementors might not support all paths. - /// For example a implementor requiring rust string - /// compatible paths might return a - /// `Err(UnsupportedPathError::new("utf-8"))`. - fn rebase_to_exclude_base_dir(&mut self, base_dir: impl AsRef) - -> Result<(), UnsupportedPathError>; -} - -impl PathRebaseable for PathBuf { - fn rebase_to_include_base_dir(&mut self, base_dir: impl AsRef) - -> Result<(), UnsupportedPathError> - { - let new_path; - if self.is_relative() { - new_path = base_dir.as_ref().join(&self); - } else { - return Ok(()); - } - mem::replace(self, new_path); - Ok(()) - } - - fn rebase_to_exclude_base_dir(&mut self, base_dir: impl AsRef) - -> Result<(), UnsupportedPathError> - { - let new_path; - if let Ok(path) = self.strip_prefix(base_dir) { - new_path = path.to_owned(); - } else { - return Ok(()); - } - mem::replace(self, new_path); - Ok(()) - } -} - -impl PathRebaseable for IRI { - fn rebase_to_include_base_dir(&mut self, base_dir: impl AsRef) - -> Result<(), UnsupportedPathError> - { - if self.scheme() != "path" { - return Ok(()); - } - - let new_tail = { - let path = Path::new(self.tail()); - if path.is_relative() { - base_dir.as_ref().join(path) - } else { - return Ok(()); - } - }; - - let new_tail = new_tail.to_str() - .ok_or_else(|| UnsupportedPathError::new("utf-8"))?; - - let new_iri = self.with_tail(new_tail); - mem::replace(self, new_iri); - Ok(()) - } - - fn rebase_to_exclude_base_dir(&mut self, base_dir: impl AsRef) - -> Result<(), UnsupportedPathError> - { - if self.scheme() != "path" { - return Ok(()); - } - - let new_iri = { - let path = Path::new(self.tail()); - - if let Ok(path) = path.strip_prefix(base_dir) { - //UNWRAP_SAFE: we just striped some parts, this can - // not make it lose it's string-ness - let new_tail = path.to_str().unwrap(); - self.with_tail(new_tail) - } else { - return Ok(()); - } - }; - - mem::replace(self, new_iri); - Ok(()) - } -} - -impl PathRebaseable for Resource { - fn rebase_to_include_base_dir(&mut self, base_dir: impl AsRef) - -> Result<(), UnsupportedPathError> - { - if let &mut Resource::Source(ref mut source) = self { - source.iri.rebase_to_include_base_dir(base_dir)?; - } - Ok(()) - } - - fn rebase_to_exclude_base_dir(&mut self, base_dir: impl AsRef) - -> Result<(), UnsupportedPathError> - { - if let &mut Resource::Source(ref mut source) = self { - source.iri.rebase_to_exclude_base_dir(base_dir)?; - } - Ok(()) - } - -} - - - -#[cfg(test)] -mod test { - use super::*; - use mail_core::Source; - - #[test] - fn rebase_on_path() { - let mut path = Path::new("/prefix/suffix.yup").to_owned(); - path.rebase_to_exclude_base_dir("/prefix").unwrap(); - assert_eq!(path, Path::new("suffix.yup")); - path.rebase_to_include_base_dir("./nfix").unwrap(); - path.rebase_to_include_base_dir("/mfix").unwrap(); - assert_eq!(path, Path::new("/mfix/nfix/suffix.yup")); - path.rebase_to_exclude_base_dir("/wrong").unwrap(); - assert_eq!(path, Path::new("/mfix/nfix/suffix.yup")); - } - - #[test] - fn rebase_on_iri() { - let mut iri: IRI = "path:/prefix/suffix.yup".parse().unwrap(); - iri.rebase_to_exclude_base_dir("/prefix").unwrap(); - assert_eq!(iri.as_str(), "path:suffix.yup"); - iri.rebase_to_include_base_dir("nfix").unwrap(); - iri.rebase_to_include_base_dir("/mfix").unwrap(); - assert_eq!(iri.as_str(), "path:/mfix/nfix/suffix.yup"); - iri.rebase_to_exclude_base_dir("/wrong").unwrap(); - assert_eq!(iri.as_str(), "path:/mfix/nfix/suffix.yup"); - } - - #[test] - fn rebase_on_resource() { - let mut resource = Resource::Source(Source { - iri: "path:abc/def".parse().unwrap(), - use_media_type: Default::default(), - use_file_name: Default::default() - }); - - resource.rebase_to_include_base_dir("./abc").unwrap(); - resource.rebase_to_include_base_dir("/pre").unwrap(); - resource.rebase_to_exclude_base_dir("/pre").unwrap(); - resource.rebase_to_exclude_base_dir("abc").unwrap(); - resource.rebase_to_include_base_dir("abc").unwrap(); - - if let Resource::Source(Source { iri, ..}) = resource { - assert_eq!(iri.as_str(), "path:abc/abc/def"); - } else { unreachable!() } - } -} \ No newline at end of file diff --git a/src/serde_impl.rs b/src/serde_impl.rs deleted file mode 100644 index 2ab709d..0000000 --- a/src/serde_impl.rs +++ /dev/null @@ -1,447 +0,0 @@ -use std::{ - collections::HashMap, - path::{Path, PathBuf} -}; - -use serde::{ - Serialize, Deserialize, - de::{ - Deserializer, - }, -}; -use failure::Error; -use futures::{Future, future::{self, Either}}; -use vec1::Vec1; - -use mail_core::{Resource, Source, IRI, Context}; -use mail_headers::header_components::MediaType; - -use super::{ - Template, - TemplateEngine, - CwdBaseDir, - PathRebaseable, - Subject, - UnsupportedPathError, -}; - -/// Type used when deserializing a template using serde. -/// -/// This type should only be used as intermediate type -/// used for deserialization as templates need to be -/// bundled with a template engine. -/// -/// # Serialize/Deserialize -/// -/// The derserialization currently only works with -/// self-describing data formats. -/// -/// There are a number of shortcuts to deserialize -/// resources (from emebddings and/or attachments): -/// -/// - Resources can be deserialized normally (from a externally tagged enum) -/// - Resources can be deserialized from the serialized repr of `Source` -/// - Resources can be deserialized from a string which is used to create -/// a `Resource::Source` with a iri using the `path` scheme and the string -/// content as the iris "tail". -#[derive(Debug, Serialize, Deserialize)] -pub struct TemplateBase { - #[serde(rename="name")] - template_name: String, - #[serde(default)] - base_dir: Option, - subject: LazySubject, - bodies: Vec1, - //TODO impl. deserialize where - // resource:String -> IRI::new("path", resource) -> Resource::Source - #[serde(deserialize_with="deserialize_embeddings")] - embeddings: HashMap, - #[serde(deserialize_with="deserialize_attachments")] - attachments: Vec, -} - -impl TemplateBase - where TE: TemplateEngine -{ - - //TODO!! make this load all embeddings/attachments and make it a future - /// Couples the template base with a specific engine instance. - pub fn load(self, mut engine: TE, default_base_dir: CwdBaseDir, ctx: &impl Context) -> impl Future, Error=Error> { - let TemplateBase { - template_name, - base_dir, - subject, - bodies, - mut e