LCOV - code coverage report
Current view: top level - snapwebsites - email.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 1 466 0.2 %
Date: 2019-12-15 17:13:15 Functions: 2 57 3.5 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : // Snap Websites Servers -- prepare a sendmail output stream
       2             : // Copyright (c) 2016-2019  Made to Order Software Corp.  All Rights Reserved
       3             : //
       4             : // https://snapwebsites.org/
       5             : // contact@m2osw.com
       6             : //
       7             : // This program is free software; you can redistribute it and/or modify
       8             : // it under the terms of the GNU General Public License as published by
       9             : // the Free Software Foundation; either version 2 of the License, or
      10             : // (at your option) any later version.
      11             : //
      12             : // This program is distributed in the hope that it will be useful,
      13             : // but WITHOUT ANY WARRANTY; without even the implied warranty of
      14             : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      15             : // GNU General Public License for more details.
      16             : //
      17             : // You should have received a copy of the GNU General Public License
      18             : // along with this program; if not, write to the Free Software
      19             : // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
      20             : //
      21             : 
      22             : // self
      23             : //
      24             : #include "email.h"
      25             : 
      26             : // snapwebsites lib
      27             : //
      28             : #include "log.h"
      29             : #include "snap_magic.h"
      30             : #include "snap_pipe.h"
      31             : #include "process.h"
      32             : #include "quoted_printable.h"
      33             : #include "snapwebsites.h"
      34             : 
      35             : // libQtSerialization lib
      36             : //
      37             : #include <QtSerialization/QSerializationComposite.h>
      38             : #include <QtSerialization/QSerializationFieldBasicTypes.h>
      39             : #include <QtSerialization/QSerializationFieldString.h>
      40             : 
      41             : // libtld lib
      42             : //
      43             : #include <libtld/tld.h>
      44             : 
      45             : // Qt lib
      46             : //
      47             : #include <QFileInfo>
      48             : 
      49             : 
      50             : 
      51             : 
      52             : // last include
      53             : //
      54             : #include <snapdev/poison.h>
      55             : 
      56             : 
      57             : namespace snap
      58             : {
      59             : 
      60             : namespace
      61             : {
      62             : 
      63             : /** \brief Copy the filename if defined.
      64             :  *
      65             :  * Check whether the filename is defined in the Content-Disposition
      66             :  * or the Content-Type fields and make sure to duplicate it in
      67             :  * both fields. This ensures that most email systems have access
      68             :  * to the filename.
      69             :  *
      70             :  * \note
      71             :  * The valid location of the filename is the Content-Disposition,
      72             :  * but it has been saved in the 'name' sub-field of the Content-Type
      73             :  * field and some tools only check that field.
      74             :  *
      75             :  * \param[in,out] attachment_headers  The headers to be checked for
      76             :  *                                    a filename.
      77             :  */
      78           0 : void copy_filename_to_content_type(email::header_map_t & attachment_headers)
      79             : {
      80           0 :     if(attachment_headers.find(get_name(name_t::SNAP_NAME_CORE_CONTENT_DISPOSITION)) != attachment_headers.end()
      81           0 :     && attachment_headers.find(get_name(name_t::SNAP_NAME_CORE_CONTENT_TYPE_HEADER)) != attachment_headers.end())
      82             :     {
      83             :         // both fields are defined, copy the filename as required
      84           0 :         QString content_disposition(attachment_headers[get_name(name_t::SNAP_NAME_CORE_CONTENT_DISPOSITION)]);
      85           0 :         QString content_type(attachment_headers[get_name(name_t::SNAP_NAME_CORE_CONTENT_TYPE_HEADER)]);
      86             : 
      87           0 :         http_strings::WeightedHttpString content_disposition_subfields(content_disposition);
      88           0 :         http_strings::WeightedHttpString content_type_subfields(content_type);
      89             : 
      90           0 :         http_strings::WeightedHttpString::part_t::vector_t & content_disposition_parts(content_disposition_subfields.get_parts());
      91           0 :         http_strings::WeightedHttpString::part_t::vector_t & content_type_parts(content_type_subfields.get_parts());
      92             : 
      93           0 :         if(content_disposition_parts.size() > 0
      94           0 :         && content_type_parts.size() > 0)
      95             :         {
      96             :             // we only use part 1 (there should not be more than one though)
      97             :             //
      98           0 :             QString const filename(content_disposition_parts[0].get_parameter("filename"));
      99           0 :             if(!filename.isEmpty())
     100             :             {
     101             :                 // okay, we found the filename in the Content-Disposition,
     102             :                 // copy that to the Content-Type
     103             :                 //
     104             :                 // Note: we always force the name parameter so if it was
     105             :                 //       already defined, we make sure it is the same as
     106             :                 //       in the Content-Disposition field
     107             :                 //
     108           0 :                 content_type_parts[0].add_parameter("name", filename);
     109           0 :                 attachment_headers[get_name(name_t::SNAP_NAME_CORE_CONTENT_TYPE_HEADER)] = content_type_subfields.to_string();
     110             :             }
     111             :             else
     112             :             {
     113           0 :                 QString const name(content_type_parts[0].get_parameter("name"));
     114           0 :                 if(!name.isEmpty())
     115             :                 {
     116             :                     // Somehow the filename is defined in the Content-Type field
     117             :                     // but not in the Content-Disposition...
     118             :                     //
     119             :                     // copy it to the Content-Disposition too (where it should be)
     120             :                     //
     121           0 :                     content_disposition_parts[0].add_parameter("filename", name);
     122           0 :                     attachment_headers[get_name(name_t::SNAP_NAME_CORE_CONTENT_DISPOSITION)] = content_disposition_subfields.to_string();
     123             :                 }
     124             :             }
     125             :         }
     126             :     }
     127           0 : }
     128             : 
     129             : 
     130             : }
     131             : // no name namespace
     132             : 
     133             : 
     134             : 
     135             : 
     136             : //////////////////////
     137             : // EMAIL ATTACHMENT //
     138             : //////////////////////
     139             : 
     140             : 
     141             : /** \brief Initialize an email attachment object.
     142             :  *
     143             :  * You can create an email attachment object, initializes it, and then
     144             :  * add it to an email object. The number of attachments is not limited
     145             :  * although you should remember that most mail servers limit the total
     146             :  * size of an email. It may be 5, 10 or 20Mb, but if you go over, the
     147             :  * email will fail.
     148             :  */
     149           0 : email::attachment::attachment()
     150             : {
     151           0 : }
     152             : 
     153             : 
     154             : /** \brief Clean up an email attachment.
     155             :  *
     156             :  * This function is here primarily to have a clean virtual table.
     157             :  */
     158           0 : email::attachment::~attachment()
     159             : {
     160           0 : }
     161             : 
     162             : 
     163             : /** \brief The content of the binary file to attach to this email.
     164             :  *
     165             :  * This function is used to attach one binary file to the email.
     166             :  *
     167             :  * If you know the MIME type of the data, it is smart to define it when
     168             :  * calling this function so that way you avoid asking the magic library
     169             :  * for it. This will save time as the magic library is much slower and
     170             :  * if you are positive about the type, it will be correct whereas the
     171             :  * magic library could return an invalid value.
     172             :  *
     173             :  * Also, if this is a file attachment, make sure to add a
     174             :  * `Content-Disposition` header to define the filename and
     175             :  * modification date as in:
     176             :  *
     177             :  * \code
     178             :  *   Content-Disposition: attachment; filename=my-attachment.pdf;
     179             :  *     modification-date="Tue, 29 Sep 2015 16:12:15 -0800";
     180             :  * \endcode
     181             :  *
     182             :  * See the set_content_disposition() function to easily add this
     183             :  * field.
     184             :  *
     185             :  * \note
     186             :  * The mime_type can be set to the empty string (`QString()`) to let
     187             :  * the system generate the MIME type automatically using the
     188             :  * get_mime_type() function.
     189             :  *
     190             :  * \param[in] data  The data to attach to this email.
     191             :  * \param[in] mime_type  The MIME type of the data if known,
     192             :  *                       otherwise leave empty.
     193             :  *
     194             :  * \sa add_header()
     195             :  * \sa get_mime_type()
     196             :  * \sa set_content_disposition()
     197             :  */
     198           0 : void email::attachment::set_data(QByteArray const & data, QString mime_type)
     199             : {
     200           0 :     f_data = data;
     201             : 
     202             :     // if user did not define the MIME type then ask the magic library
     203           0 :     if(mime_type.isEmpty())
     204             :     {
     205           0 :         mime_type = get_mime_type(f_data);
     206             :     }
     207           0 :     f_headers[get_name(name_t::SNAP_NAME_CORE_CONTENT_TYPE_HEADER)] = mime_type;
     208           0 : }
     209             : 
     210             : 
     211             : /** \brief Set the email attachment using quoted printable encoding.
     212             :  *
     213             :  * In most cases, when you attach something else than just text, you want
     214             :  * to encode the data. Even text, if you do not control the length of each
     215             :  * line properly, it is likely to get cut at some random length and could
     216             :  * end up looking wrong.
     217             :  *
     218             :  * This function encodes the data using the quoted_printable::encode()
     219             :  * function and marks the data encoded in such a way.
     220             :  *
     221             :  * By default, all you have to do is pass a QByteArray and the rest works
     222             :  * on its own, although it is usually a good idea to specify the MIME type
     223             :  * if you knowit.
     224             :  *
     225             :  * The flags parameter can be used to tweak the encoding functionality.
     226             :  * The default works with most data, although it does not include the
     227             :  * binary flag. See the quoted_printable::encode() function for additional
     228             :  * information about these flags.
     229             :  *
     230             :  * \param[in] data  The data of this a attachment.
     231             :  * \param[in] mime_type  The MIME type of the data, if left empty, it will
     232             :  *            be determined on the fly.
     233             :  * \param[in] flags  A set of quoted_printable::encode() flags.
     234             :  */
     235           0 : void email::attachment::quoted_printable_encode_and_set_data(
     236             :                             QByteArray const & data
     237             :                           , QString mime_type
     238             :                           , int flags)
     239             : {
     240           0 :     std::string const encoded_data(quoted_printable::encode(data.data(), flags));
     241             : 
     242           0 :     QByteArray encoded_data_bytes(encoded_data.c_str(), static_cast<int>(encoded_data.length()));
     243             : 
     244           0 :     set_data(encoded_data_bytes, mime_type);
     245             : 
     246           0 :     add_header(get_name(name_t::SNAP_NAME_CORE_EMAIL_CONTENT_TRANSFER_ENCODING),
     247             :                get_name(name_t::SNAP_NAME_CORE_EMAIL_CONTENT_ENCODING_QUOTED_PRINTABLE));
     248           0 : }
     249             : 
     250             : 
     251             : /** \brief The email attachment data.
     252             :  *
     253             :  * This function retrieves the attachment data from this email attachment
     254             :  * object. This is generally UTF-8 characters when we are dealing with
     255             :  * text (HTML or plain text.)
     256             :  *
     257             :  * The data type is defined in the Content-Type header which is automatically
     258             :  * defined by the mime_type parameter of the set_data() function call. To
     259             :  * retrieve the MIME type, use the following:
     260             :  *
     261             :  * \code
     262             :  * QString mime_type(attachment->get_header(get_name(name_t::SNAP_NAME_CORE_CONTENT_TYPE_HEADER)));
     263             :  * \endcode
     264             :  *
     265             :  * \warning
     266             :  * This funtion returns the data by copy. Use with care (not repetitively?)
     267             :  *
     268             :  * \return A copy of this attachment's data.
     269             :  */
     270           0 : QByteArray email::attachment::get_data() const
     271             : {
     272           0 :     return f_data;
     273             : }
     274             : 
     275             : 
     276             : /** \brief Retrieve the value of a header.
     277             :  *
     278             :  * This function returns the value of the named header. If the header
     279             :  * is not currently defined, this function returns an empty string.
     280             :  *
     281             :  * \exception sendmail_exception_invalid_argument
     282             :  * The name of a header cannot be empty. This exception is raised if
     283             :  * \p name is empty.
     284             :  *
     285             :  * \param[in] name  A valid header name.
     286             :  *
     287             :  * \return The current value of that header or an empty string if undefined.
     288             :  */
     289           0 : QString email::attachment::get_header(QString const & name) const
     290             : {
     291           0 :     if(name.isEmpty())
     292             :     {
     293           0 :         throw snap_exception_invalid_parameter("email::attachment::get_header(): Cannot retrieve a header with an empty name");
     294             :     }
     295             : 
     296           0 :     auto const it(f_headers.find(name));
     297           0 :     if(it != f_headers.end())
     298             :     {
     299           0 :         return it->second;
     300             :     }
     301             : 
     302           0 :     return QString();
     303             : }
     304             : 
     305             : 
     306             : /** \brief Add the Content-Disposition field.
     307             :  *
     308             :  * Helper function to add the Content-Disposition without having to
     309             :  * generate the string of the field by hand, especially because the
     310             :  * filename needs special care if defined.
     311             :  *
     312             :  * The disposition is expected to be of type "attachment" by default.
     313             :  * You may change that by changing the last parameter to this function.
     314             :  *
     315             :  * The function also accepts a filename and a date. If the date is set
     316             :  * to zero (default) then time() is used.
     317             :  *
     318             :  * \code
     319             :  *      email e;
     320             :  *      ...
     321             :  *      email::attachment a;
     322             :  *      a.set_data(some_pdf_buffer, "application/pdf");
     323             :  *      a.set_content_disposition("your-file.pdf");
     324             :  *      e.add_attachment(a);
     325             :  *      ...
     326             :  *      // if in a server plugin, you can use post_email()
     327             :  *      sendmail::instance()->post_email(e);
     328             :  * \endcode
     329             :  *
     330             :  * \attention
     331             :  * The \p filename parameter can include a full path although only the
     332             :  * basename including all extensions are saved in the header. The path
     333             :  * is not useful on the destination computer and can even possibly be
     334             :  * a security issue in some cases.
     335             :  *
     336             :  * \warning
     337             :  * The modification_date is an int64_t type in microsecond
     338             :  * as most often used in Snap! However, emails only use dates with
     339             :  * a one second precision so the milli and micro seconds will
     340             :  * generally be ignored.
     341             :  *
     342             :  * \param[in] filename  The name of this attachment file.
     343             :  * \param[in] modification_date  The last modification date of this file.
     344             :  *                               Defaults to zero meaning use now.
     345             :  *                               Value is in microseconds.
     346             :  * \param[in] attachment_type  The type of attachment, defaults to "attachment",
     347             :  *                             which is all you need in most cases.
     348             :  */
     349           0 : void email::attachment::set_content_disposition(QString const & filename, int64_t modification_date, QString const & attachment_type)
     350             : {
     351             :     // TODO: make use of a WeightedHTTPString::to_string() (class to be renamed!)
     352             : 
     353             :     // type
     354             :     //
     355           0 :     if(attachment_type.isEmpty())
     356             :     {
     357           0 :         throw snap_exception_invalid_parameter("email::attachment::set_content_disposition(): The attachment type cannot be an empty string.");
     358             :     }
     359           0 :     QString content_disposition(attachment_type);
     360           0 :     content_disposition += ";";
     361             : 
     362             :     // filename (optional)
     363             :     //
     364           0 :     QFileInfo file_info(filename);
     365           0 :     QString const file_name(file_info.fileName());
     366           0 :     if(!file_name.isEmpty())
     367             :     {
     368             :         // the path is not going to be used (should not be at least) for
     369             :         // security reasons we think it is better not to include it at all
     370             :         //
     371           0 :         content_disposition += " filename=";
     372           0 :         content_disposition += snap_uri::urlencode(file_name);
     373           0 :         content_disposition += ";";
     374             :     }
     375             : 
     376             :     // modificate-date
     377             :     //
     378           0 :     if(modification_date == 0)
     379             :     {
     380           0 :         modification_date = time(nullptr) * 1000000;
     381             :     }
     382           0 :     content_disposition += " modification-date=\"";
     383           0 :     content_disposition += snap_child::date_to_string(modification_date, snap_child::date_format_t::DATE_FORMAT_EMAIL);
     384           0 :     content_disposition += "\";";
     385             : 
     386             :     // save the result in the headers
     387             :     //
     388           0 :     add_header(get_name(name_t::SNAP_NAME_CORE_CONTENT_DISPOSITION), content_disposition);
     389           0 : }
     390             : 
     391             : 
     392             : /** \brief Check whether a named header was defined in this attachment.
     393             :  *
     394             :  * Each specific attachment can be given a set of headers that are saved
     395             :  * at the beginning of that part in a multi-part email.
     396             :  *
     397             :  * This function is used to know whther a given header was already
     398             :  * defined or not.
     399             :  *
     400             :  * \note
     401             :  * The function returns true whether the header is properly defined or
     402             :  * is the empty string.
     403             :  *
     404             :  * \param[in] name  A valid header name.
     405             :  * \param[in] value  The value of this header.
     406             :  *
     407             :  * \sa set_data()
     408             :  */
     409           0 : bool email::attachment::has_header(QString const & name) const
     410             : {
     411           0 :     if(name.isEmpty())
     412             :     {
     413           0 :         throw snap_exception_invalid_parameter("email::attachment::has_header(): When check the presence of a header, the name cannot be empty.");
     414             :     }
     415             : 
     416           0 :     return f_headers.find(name) != f_headers.end();
     417             : }
     418             : 
     419             : 
     420             : /** \brief Header of this attachment.
     421             :  *
     422             :  * Each attachment can be assigned a set of headers such as the Content-Type
     423             :  * (which is automatically set by the set_data() function.)
     424             :  *
     425             :  * Headers in an attachment are similar to the headers in the main email
     426             :  * only it cannot include certain entries such as the To:, Cc:, etc.
     427             :  *
     428             :  * In most cases you want to include the filename if the attachment represents
     429             :  * a file. Plain text and HTML will generally only need the Content-Type which
     430             :  * is already set by a call to the set_data() funciton.
     431             :  *
     432             :  * Note that the name of a header is case insensitive. So the names
     433             :  * "Content-Type" and "content-type" represent the same header. Which
     434             :  * one will be used when generating the output is a non-disclosed internal
     435             :  * functionality. You probably want to use the SNAP_SENDMAIL_HEADER_...
     436             :  * names anyway (at least for those that are defined.)
     437             :  *
     438             :  * \note
     439             :  * The Content-Transfer-Encoding is managed internally and you are not
     440             :  * expected to set this value. The Content-Disposition is generally set
     441             :  * to "attachment" for files that are attached to the email.
     442             :  *
     443             :  * \exception sendmail_exception_invalid_argument
     444             :  * The name of a header cannot be empty. This exception is raised if the name
     445             :  * is empty.
     446             :  *
     447             :  * \todo
     448             :  * As we develop a functioning version of sendmail we want to add tests to
     449             :  * prevent a set of fields that we will handle internally and thus we do
     450             :  * not want users to be able to set here.
     451             :  *
     452             :  * \param[in] name  A valid header name.
     453             :  * \param[in] value  The value of this header.
     454             :  *
     455             :  * \sa set_data()
     456             :  */
     457           0 : void email::attachment::add_header(QString const & name, QString const & value)
     458             : {
     459           0 :     if(name.isEmpty())
     460             :     {
     461           0 :         throw snap_exception_invalid_parameter("email::attachment::add_header(): When adding a header, the name cannot be empty.");
     462             :     }
     463             : 
     464           0 :     f_headers[name] = value;
     465           0 : }
     466             : 
     467             : 
     468             : /** \brief Remove a header.
     469             :  *
     470             :  * This function searches for the \p name header and removes it from the
     471             :  * list of defined headers. This is different from setting the value of
     472             :  * a header to the empty string as the header continues to exist.
     473             :  *
     474             :  * \param[in] name  The name of the header to get rid of.
     475             :  */
     476           0 : void email::attachment::remove_header(QString const & name)
     477             : {
     478           0 :     auto const it(f_headers.find(name));
     479           0 :     if(it != f_headers.end())
     480             :     {
     481           0 :         f_headers.erase(it);
     482             :     }
     483           0 : }
     484             : 
     485             : 
     486             : /** \brief Get all the headers defined in this email attachment.
     487             :  *
     488             :  * This function returns the map of the headers defined in this email
     489             :  * attachment. This can be used to quickly scan all the headers.
     490             :  *
     491             :  * \note
     492             :  * It is important to remember that since this function returns a reference
     493             :  * to the map of headers, it may break if you call add_header() while going
     494             :  * through the references unless you make a copy.
     495             :  *
     496             :  * \return A direct and constant reference to the internal header map.
     497             :  */
     498           0 : email::header_map_t const & email::attachment::get_all_headers() const
     499             : {
     500           0 :     return f_headers;
     501             : }
     502             : 
     503             : 
     504             : /** \brief Add a related sub-attachment.
     505             :  *
     506             :  * This function lets you add a related sub-attachment to an email
     507             :  * attachment. At this time, this is only accepted on HTML attachments
     508             :  * (body) to attach files such as images, CSS, and scripts.
     509             :  *
     510             :  * \note
     511             :  * At this time we prevent you from adding related sub-attachments to
     512             :  * already related sub-attachments. Note that emails can have more levels,
     513             :  * but we limit the body of the email (very first attachment) to either
     514             :  * Text or HTML. If HTML, then the sendmail plugin takes care of
     515             :  * adding the Text version. Thus the sendmail email structure is somewhat
     516             :  * different from the resulting email.
     517             :  *
     518             :  * The possible structure of a resulting email is:
     519             :  *
     520             :  * \code
     521             :  * - multipart/mixed
     522             :  *   - multipart/alternative
     523             :  *     - text/plain
     524             :  *     - multipart/related
     525             :  *       - text/html
     526             :  *       - image/jpg (Images used in text/html)
     527             :  *       - image/png
     528             :  *       - image/gif
     529             :  *       - text/css (the CSS used by the HTML)
     530             :  *   - application/pdf (PDF attachment)
     531             :  * \endcode
     532             :  *
     533             :  * The structure of the sendmail attachment for such an email would be:
     534             :  *
     535             :  * \code
     536             :  * - HTML attachment
     537             :  *   - image/jpg
     538             :  *   - image/png
     539             :  *   - image/gif
     540             :  *   - text/css
     541             :  * - application/pdf
     542             :  * \endcode
     543             :  *
     544             :  * Also, you are much more likely to use the set_email_path() which
     545             :  * means you do not have to provide anything more than than the dynamic
     546             :  * file attachments (i.e. the application/pdf file in our example here.)
     547             :  * Everything else is taken care of by the sendmail plugin.
     548             :  *
     549             :  * \param[in] data  The attachment to add to this attachment by copy.
     550             :  */
     551           0 : void email::attachment::add_related(attachment const & data)
     552             : {
     553             :     // if we are a sub-attachment, we do not accept a sub-sub-attachment
     554             :     //
     555           0 :     if(f_is_sub_attachment)
     556             :     {
     557           0 :         throw email_exception_too_many_levels("email::attachment::add_related(): this attachment is already a related sub-attachment, you cannot add more levels");
     558             :     }
     559             : 
     560             :     // related sub-attachment limitation
     561             :     //
     562           0 :     if(data.get_related_count() != 0)
     563             :     {
     564           0 :         throw email_exception_too_many_levels("email::attachment::add_related(): you cannot add a related sub-attachment to an attachment when that related sub-attachment has itself a related sub-attachment");
     565             :     }
     566             : 
     567             :     // create a copy of this attachment
     568             :     //
     569             :     // note that we do not attempt to use the shared pointer, we make a
     570             :     // full copy instead, this is because some people may end up wanting
     571             :     // to modify the attachment parameter and then add anew... what will
     572             :     // have to a be a different attachment.
     573             :     //
     574           0 :     attachment copy(data);
     575             : 
     576             :     // mark this as a sub-attachment to prevent users from adding
     577             :     // sub-sub-attachments to those
     578             :     //
     579           0 :     copy.f_is_sub_attachment = true;
     580             : 
     581             :     // save the result in this attachment sub-attachments
     582             :     //
     583           0 :     f_sub_attachments.push_back(copy);
     584           0 : }
     585             : 
     586             : 
     587             : /** \brief Return the number of sub-attachments.
     588             :  *
     589             :  * Attachments can be assigned related sub-attachments. For example, an
     590             :  * HTML page can be given images, CSS files, etc.
     591             :  *
     592             :  * This function returns the number of such sub-attachments that were
     593             :  * added with the add_attachment() function. The count can be used to
     594             :  * retrieve all the sub-attachments with the get_attachment() function.
     595             :  *
     596             :  * \return The number of sub-attachments.
     597             :  *
     598             :  * \sa add_related()
     599             :  * \sa get_related()
     600             :  */
     601           0 : int email::attachment::get_related_count() const
     602             : {
     603           0 :     return f_sub_attachments.size();
     604             : }
     605             : 
     606             : 
     607             : /** \brief Get one of the related sub-attachment of this attachment.
     608             :  *
     609             :  * This function is used to retrieve the related attachments found in
     610             :  * another attachment. These are called sub-attachments.
     611             :  *
     612             :  * These attachments are viewed as related documents to the main
     613             :  * attachment. These are used with HTML at this point to add images,
     614             :  * CSS files, etc. to the HTML files.
     615             :  *
     616             :  * \warning
     617             :  * The function returns a reference to the internal object. Calling
     618             :  * add_attachment() is likely to invalidate that reference.
     619             :  *
     620             :  * \exception out_of_range
     621             :  * If the index is out of range, this exception is raised.
     622             :  *
     623             :  * \param[in] index  The attachment index.
     624             :  *
     625             :  * \return A reference to the attachment.
     626             :  *
     627             :  * \sa add_related()
     628             :  * \sa get_related_count()
     629             :  */
     630           0 : email::attachment & email::attachment::get_related(int index) const
     631             : {
     632           0 :     if(static_cast<size_t>(index) >= f_sub_attachments.size())
     633             :     {
     634           0 :         throw std::out_of_range("email::attachment::get_related() called with an invalid index");
     635             :     }
     636           0 :     return const_cast<attachment &>(f_sub_attachments[index]);
     637             : }
     638             : 
     639             : 
     640             : /** \brief Unserialize an email attachment.
     641             :  *
     642             :  * This function unserializes an email attachment that was serialized using
     643             :  * the serialize() function. This is considered an internal function as it
     644             :  * is called by the unserialize() function of the email object.
     645             :  *
     646             :  * \param[in] r  The reader used to read the input data.
     647             :  *
     648             :  * \sa serialize()
     649             :  */
     650           0 : void email::attachment::unserialize(QtSerialization::QReader & r)
     651             : {
     652           0 :     QtSerialization::QComposite comp;
     653             :     //QtSerialization::QFieldBool tag_is_sub_attachment(comp, "is_sub_attachment", f_is_sub_attachment);
     654           0 :     QtSerialization::QFieldTag tag_header(comp, "header", this);
     655           0 :     QtSerialization::QFieldTag tag_sub_attachment(comp, "sub-attachment", this);
     656           0 :     QString attachment_data;
     657           0 :     QtSerialization::QFieldString tag_data(comp, "data", attachment_data);
     658           0 :     r.read(comp);
     659           0 :     f_data = QByteArray::fromBase64(attachment_data.toUtf8().data());
     660           0 : }
     661             : 
     662             : 
     663             : /** \brief Read the contents one tag from the reader.
     664             :  *
     665             :  * This function reads the contents of the attachment tag. It handles
     666             :  * the attachment header fields.
     667             :  *
     668             :  * \param[in] name  The name of the tag being read.
     669             :  * \param[in] r  The reader used to read the input data.
     670             :  */
     671           0 : void email::attachment::readTag(QString const & name, QtSerialization::QReader & r)
     672             : {
     673           0 :     if(name == "header")
     674             :     {
     675           0 :         QtSerialization::QComposite comp;
     676           0 :         QString header_name;
     677           0 :         QtSerialization::QFieldString tag_name(comp, "name", header_name);
     678           0 :         QString header_value;
     679           0 :         QtSerialization::QFieldString tag_value(comp, "value", header_value);
     680           0 :         r.read(comp);
     681           0 :         f_headers[header_name] = header_value;
     682             :     }
     683           0 :     else if(name == "sub-attachment")
     684             :     {
     685           0 :         attachment a;
     686           0 :         a.unserialize(r);
     687           0 :         add_related(a);
     688             :     }
     689           0 : }
     690             : 
     691             : 
     692             : /** \brief Serialize an attachment to a writer.
     693             :  *
     694             :  * This function serialize an attachment so it can be saved in the database
     695             :  * in the form of a string.
     696             :  *
     697             :  * \param[in,out] w  The writer where the data gets saved.
     698             :  */
     699           0 : void email::attachment::serialize(QtSerialization::QWriter & w, bool is_sub_attachment) const
     700             : {
     701           0 :     QtSerialization::QWriter::QTag tag(w, is_sub_attachment ? "sub-attachment" : "attachment");
     702             :     //QtSerialization::writeTag(w, "is_sub_attachment", f_is_sub_attachment);
     703           0 :     for(auto const & it : f_headers)
     704             :     {
     705           0 :         QtSerialization::QWriter::QTag header(w, "header");
     706           0 :         QtSerialization::writeTag(w, "name", it.first);
     707           0 :         QtSerialization::writeTag(w, "value", it.second);
     708             :     }
     709           0 :     for(auto const & it : f_sub_attachments)
     710             :     {
     711           0 :         it.serialize(w, true);
     712             :     }
     713             : 
     714             :     // the data may be binary and thus it cannot be saved as is
     715             :     // so we encode it using base64
     716             :     //
     717           0 :     QtSerialization::writeTag(w, "data", f_data.toBase64().data());
     718           0 : }
     719             : 
     720             : 
     721             : /** \brief Compare two attachments against each others.
     722             :  *
     723             :  * This function compares two attachments against each other and returns
     724             :  * true if both are considered equal.
     725             :  *
     726             :  * \param[in] rhs  The right handside.
     727             :  *
     728             :  * \return true if both attachments are equal.
     729             :  */
     730           0 : bool email::attachment::operator == (attachment const & rhs) const
     731             : {
     732           0 :     return f_headers           == rhs.f_headers
     733           0 :         && f_data              == rhs.f_data
     734           0 :         && f_is_sub_attachment == rhs.f_is_sub_attachment
     735           0 :         && f_sub_attachments   == rhs.f_sub_attachments;
     736             : }
     737             : 
     738             : 
     739             : 
     740             : 
     741             : 
     742             : 
     743             : ///////////
     744             : // EMAIL //
     745             : ///////////
     746             : 
     747             : 
     748             : /** \brief The version used to serialize emails.
     749             :  *
     750             :  * This is the major version used when serializing emails.
     751             :  */
     752             : int const email::EMAIL_MAJOR_VERSION;
     753             : 
     754             : 
     755             : /** \brief The version used to serialize emails.
     756             :  *
     757             :  * This is the minor version used when serializing emails.
     758             :  */
     759             : int const email::EMAIL_MINOR_VERSION;
     760             : 
     761             : 
     762             : /** \brief Initialize an email object.
     763             :  *
     764             :  * This function initializes an email object making it ready to be
     765             :  * setup before processing.
     766             :  *
     767             :  * The function takes no parameter, although a certain number of
     768             :  * parameters are required and must be defined before the email
     769             :  * can be sent:
     770             :  *
     771             :  * \li From -- the name/email of the user sending this email.
     772             :  * \li To -- the name/email of the user to whom this email is being sent,
     773             :  *           there may be multiple recipients and they may be defined
     774             :  *           in Cc or Bcc as well as the To list. The To can also be
     775             :  *           defined as a list alias name in which case the backend
     776             :  *           will send the email to all the subscribers of that list.
     777             :  * \li Subject -- the subject must include something.
     778             :  * \li Content -- at least one attachment must be added as the body.
     779             :  *
     780             :  * Attachments support text emails, HTML pages, and any file (image,
     781             :  * PDF, etc.). There is no specific limit to the number of attachments
     782             :  * or the size per se, although more email systems do limit the size
     783             :  * of an email so we do enforce some limit (i.e. 25Mb).
     784             :  */
     785           0 : email::email()
     786           0 :     : f_time(time(nullptr))
     787             : {
     788           0 : }
     789             : 
     790             : 
     791             : /** \brief Clean up the email object.
     792             :  *
     793             :  * This function ensures that an email object is cleaned up before
     794             :  * getting freed.
     795             :  */
     796           0 : email::~email()
     797             : {
     798           0 : }
     799             : 
     800             : 
     801             : /** \brief Change whether the branding is to be shown or not.
     802             :  *
     803             :  * By default, the send() function includes a couple of branding
     804             :  * headers:
     805             :  *
     806             :  * \li X-Generated-By
     807             :  * \li X-Mailer
     808             :  *
     809             :  * Those two headers can be removed by setting the branding to false.
     810             :  *
     811             :  * By default the branding is turned on meaning that it will appear
     812             :  * in your emails. Obviously, your mail server can later overwrite
     813             :  * or remove those fields.
     814             :  *
     815             :  * \param[in] branding  The new value for the branding flag, true by default.
     816             :  */
     817           0 : void email::set_branding(bool branding)
     818             : {
     819           0 :     f_branding = branding;
     820           0 : }
     821             : 
     822             : 
     823             : /** \brief Retrieve the branding flag value.
     824             :  *
     825             :  * This function returns true if the branding of the Snap! system will
     826             :  * appear in the email.
     827             :  *
     828             :  * \return true if branding is on, false otherwise.
     829             :  */
     830           0 : bool email::get_branding() const
     831             : {
     832           0 :     return f_branding;
     833             : }
     834             : 
     835             : 
     836             : /** \brief Mark this email as being cumulative.
     837             :  *
     838             :  * A cumulative email is not sent immediately. Instead it is stored
     839             :  * and sent at a later time once certain thresholds are reached.
     840             :  * There are two thresholds used at this time: a time threshold, a
     841             :  * user may want to receive at most one email every few days; and
     842             :  * a count threshold, a user may want to receive an email for every
     843             :  * X events.
     844             :  *
     845             :  * Also, our system is capable to cumulate using an overwrite so
     846             :  * the receiver gets one email even if the same object was modified
     847             :  * multiple times. For example an administrator may want to know
     848             :  * when a type of pages gets modified, but he doesn't want to know
     849             :  * of each little change (i.e. the editor may change the page 5
     850             :  * times in a row as he/she finds things to tweak over and over
     851             :  * again.) The name of the \p object passed as a parameter allows
     852             :  * the mail system to cumulate using an overwrite and thus mark
     853             :  * that this information should really only be sent once (i.e.
     854             :  * instead of saying 10 times that page X changed, the mail system
     855             :  * can say it once [although we will include how many edits were
     856             :  * made as an additional piece of information.])
     857             :  *
     858             :  * Note that the user may mark all emails that he/she receives as
     859             :  * cumulative or non-cumulative so this flag is useful but it can
     860             :  * be ignored by the receivers. The priority can be used by the
     861             :  * receiver to decide what to do with an email. (i.e. send urgent
     862             :  * emails immediately.)
     863             :  *
     864             :  * \note
     865             :  * You may call the set_cumulative() function with an empty string
     866             :  * to turn off the cumulative feature for that email.
     867             :  *
     868             :  * \warning
     869             :  * This feature is not yet implemented by the sendmail plugin. Note
     870             :  * that is only for that plugin and not for the email class here
     871             :  * which has no knowledge of how to cumulate multiple emails into
     872             :  * one.
     873             :  *
     874             :  * \param[in] object  The name of the object being worked on.
     875             :  *
     876             :  * \sa get_cumulative()
     877             :  */
     878           0 : void email::set_cumulative(QString const & object)
     879             : {
     880           0 :     f_cumulative = object;
     881           0 : }
     882             : 
     883             : 
     884             : /** \brief Check the cumulative information.
     885             :  *
     886             :  * This function is used to retreive the cumulative information as saved
     887             :  * using the set_cumulative().
     888             :  *
     889             :  * \warning
     890             :  * This feature is not yet implemented by the sendmail plugin. Note
     891             :  * that is only for that plugin and not for the email class here
     892             :  * which has no knowledge of how to cumulate multiple emails into
     893             :  * one.
     894             :  *
     895             :  * \return A string representing the way the cumulative feature should work.
     896             :  *
     897             :  * \sa set_cumulative()
     898             :  */
     899           0 : QString const & email::get_cumulative() const
     900             : {
     901           0 :     return f_cumulative;
     902             : }
     903             : 
     904             : 
     905             : /** \brief Set the site key of the site sending this email.
     906             :  *
     907             :  * The site key is saved in the email whenever the post_email() function
     908             :  * is called. You do not have to define it, it will anyway be overwritten.
     909             :  *
     910             :  * The site key is used to check whether an email is being sent to a group
     911             :  * and that group is a mailing list. In that case we've got to have the
     912             :  * name of the mailing list defined as "\<site-key>: \<list-name>" thus we
     913             :  * need access to the site key that generates the email at the time we
     914             :  * manage the email (which is from the backend that has no clue what the
     915             :  * site key is when reached).
     916             :  *
     917             :  * \param[in] site_key  The name (key/URI) of the site being built.
     918             :  */
     919           0 : void email::set_site_key(QString const & site_key)
     920             : {
     921           0 :     f_site_key = site_key;
     922           0 : }
     923             : 
     924             : 
     925             : /** \brief Retrieve the site key of the site that generated this email.
     926             :  *
     927             :  * This function retrieves the value set by the set_site_key() function.
     928             :  * It returns an empty string until the post_email() function is called
     929             :  * because before that it is not set.
     930             :  *
     931             :  * The main reason for having the site key is to search the list of
     932             :  * email lists when the email gets sent to the end user.
     933             :  *
     934             :  * \return The site key of the site that generated the email.
     935             :  */
     936           0 : QString const & email::get_site_key() const
     937             : {
     938           0 :     return f_site_key;
     939             : }
     940             : 
     941             : 
     942             : /** \brief Define the path to the email in the system.
     943             :  *
     944             :  * This function sets up the path of the email subject, body, and optional
     945             :  * attachments.
     946             :  *
     947             :  * Other attachments can also be added to the email. However, when a path
     948             :  * is defined, the title and body of that page are used as the subject and
     949             :  * the body of the email.
     950             :  *
     951             :  * \note
     952             :  * At the time an email gets sent, the permissions of a page are not
     953             :  * checked.
     954             :  *
     955             :  * \warning
     956             :  * If you are not in a plugin, this feature and the post will not work for
     957             :  * you. Instead you must explicitly define the body and attach it with
     958             :  * the set_body_attachment() function. It is not required to add the
     959             :  * body attachment first, but it has to be added for the email to work
     960             :  * as expected (obviously?!)
     961             :  *
     962             :  * \param[in] email_path  The path to a page that will be used as the email subject, body, and attachments
     963             :  */
     964           0 : void email::set_email_path(QString const & email_path)
     965             : {
     966           0 :     f_email_path = email_path;
     967           0 : }
     968             : 
     969             : 
     970             : /** \brief Retrieve the path to the page used to generate the email.
     971             :  *
     972             :  * This email path is set to a page that represents the subject (title) and
     973             :  * body of the email. It may also have attachments linked to it.
     974             :  *
     975             :  * If the path is empty, then the email is generated using the email object
     976             :  * and its attachment, the first attachment being the body of the email.
     977             :  *
     978             :  * \return The path to the page to be used to generate the email subject and
     979             :  *         title.
     980             :  */
     981           0 : QString const & email::get_email_path() const
     982             : {
     983           0 :     return f_email_path;
     984             : }
     985             : 
     986             : 
     987             : /** \brief Set the email key.
     988             :  *
     989             :  * When a new email is posted, it is assigned a unique number used as a
     990             :  * key in different places.
     991             :  *
     992             :  * \param[in] email_key  The name (key/URI) of the site being built.
     993             :  */
     994           0 : void email::set_email_key(QString const & email_key)
     995             : {
     996           0 :     f_email_key = email_key;
     997           0 : }
     998             : 
     999             : 
    1000             : /** \brief Retrieve the email key.
    1001             :  *
    1002             :  * This function retrieves the value set by the set_email_key() function.
    1003             :  *
    1004             :  * The email key is set when you call the post_email() function. It is a
    1005             :  * random number that we also save in the email object so we can keep using
    1006             :  * it as we go.
    1007             :  *
    1008             :  * \return The email key.
    1009             :  */
    1010           0 : QString const & email::get_email_key() const
    1011             : {
    1012           0 :     return f_email_key;
    1013             : }
    1014             : 
    1015             : 
    1016             : /** \brief Retrieve the time when the email object was created.
    1017             :  *
    1018             :  * This function retrieves the time when the email was first created.
    1019             :  *
    1020             :  * \return The time when the email object was created.
    1021             :  */
    1022           0 : time_t email::get_time() const
    1023             : {
    1024           0 :     return f_time;
    1025             : }
    1026             : 
    1027             : 
    1028             : /** \brief Save the name and email address of the sender.
    1029             :  *
    1030             :  * This function saves the name and address of the sender. It has to
    1031             :  * be valid according to RFC 2822.
    1032             :  *
    1033             :  * If you call this function multiple times, only the last \p from
    1034             :  * information is kept.
    1035             :  *
    1036             :  * \note
    1037             :  * The set_from() function is the same as calling the add_header() with
    1038             :  * "From" as the field name and \p from as the value. To retrieve that
    1039             :  * field, you have to use the get_header() function.
    1040             :  *
    1041             :  * \exception sendmail_exception_invalid_argument
    1042             :  * If the \p from parameter is not a valid email address (as per RCF
    1043             :  * 2822) or there isn't exactly one email address in that parameter,
    1044             :  * then this exception is raised.
    1045             :  *
    1046             :  * \param[in] from  The name and email address of the sender
    1047             :  */
    1048           0 : void email::set_from(QString const & from)
    1049             : {
    1050             :     // parse the email to verify that it is valid
    1051             :     //
    1052           0 :     tld_email_list emails;
    1053           0 :     if(emails.parse(from.toUtf8().data(), 0) != TLD_RESULT_SUCCESS)
    1054             :     {
    1055           0 :         throw snap_exception_invalid_parameter(QString("email::set_from(): invalid \"From:\" email in \"%1\".").arg(from));
    1056             :     }
    1057           0 :     if(emails.count() != 1)
    1058             :     {
    1059           0 :         throw snap_exception_invalid_parameter("email::set_from(): multiple \"From:\" emails");
    1060             :     }
    1061             : 
    1062             :     // save the email as the From email address
    1063             :     //
    1064           0 :     f_headers[get_name(name_t::SNAP_NAME_CORE_EMAIL_FROM)] = from;
    1065           0 : }
    1066             : 
    1067             : 
    1068             : /** \brief Save the names and email addresses of the receivers.
    1069             :  *
    1070             :  * This function saves the names and addresses of the receivers. The list
    1071             :  * of receivers has to be valid according to RFC 2822.
    1072             :  *
    1073             :  * If you are call this function multiple times, only the last \p to
    1074             :  * information is kept.
    1075             :  *
    1076             :  * \note
    1077             :  * The set_to() function is the same as calling the add_header() with
    1078             :  * "To" as the field name and \p to as the value. To retrieve that
    1079             :  * field, you have to use the get_header() function.
    1080             :  *
    1081             :  * \warning
    1082             :  * In most cases you can enter any number of receivers, however, when
    1083             :  * using the email object directly, it is likely to fail if you do so.
    1084             :  * The sendmail plugin knows how to handle a list of destinations, though.
    1085             :  *
    1086             :  * \exception snap_exception_invalid_parameter
    1087             :  * If the \p to parameter is not a valid list of email addresses (as per
    1088             :  * RFC 2822) or there is not at least one email address then this exception
    1089             :  * is raised.
    1090             :  *
    1091             :  * \param[in] to  The list of names and email addresses of the receivers.
    1092             :  */
    1093           0 : void email::set_to(QString const & to)
    1094             : {
    1095             :     // parse the email to verify that it is valid
    1096             :     //
    1097           0 :     tld_email_list emails;
    1098           0 :     if(emails.parse(to.toUtf8().data(), 0) != TLD_RESULT_SUCCESS)
    1099             :     {
    1100           0 :         throw snap_exception_invalid_parameter("email::set_to(): invalid \"To:\" email");
    1101             :     }
    1102           0 :     if(emails.count() < 1)
    1103             :     {
    1104             :         // this should never happen because the parser will instead return
    1105             :         // a result other than TLD_RESULT_SUCCESS
    1106             :         //
    1107           0 :         throw snap_exception_invalid_parameter("email::set_to(): not even one \"To:\" email");
    1108             :     }
    1109             : 
    1110             :     // save the email as the To email address
    1111             :     //
    1112           0 :     f_headers[get_name(name_t::SNAP_NAME_CORE_EMAIL_TO)] = to;
    1113           0 : }
    1114             : 
    1115             : 
    1116             : /** \brief The priority is a somewhat arbitrary value defining the email urgency.
    1117             :  *
    1118             :  * Many mail system define a priority but it really isn't defined in the
    1119             :  * RFC 2822 so the value is not well defined.
    1120             :  *
    1121             :  * The priority is saved in the X-Priority header.
    1122             :  *
    1123             :  * \param[in] priority  The priority of this email.
    1124             :  */
    1125           0 : void email::set_priority(priority_t priority)
    1126             : {
    1127           0 :     QString name;
    1128           0 :     switch(priority)
    1129             :     {
    1130           0 :     case priority_t::EMAIL_PRIORITY_BULK:
    1131           0 :         name = QString::fromUtf8(get_name(name_t::SNAP_NAME_CORE_EMAIL_PRIORITY_BULK));
    1132           0 :         break;
    1133             : 
    1134           0 :     case priority_t::EMAIL_PRIORITY_LOW:
    1135           0 :         name = QString::fromUtf8(get_name(name_t::SNAP_NAME_CORE_EMAIL_PRIORITY_LOW));
    1136           0 :         break;
    1137             : 
    1138           0 :     case priority_t::EMAIL_PRIORITY_NORMAL:
    1139           0 :         name = QString::fromUtf8(get_name(name_t::SNAP_NAME_CORE_EMAIL_PRIORITY_NORMAL));
    1140           0 :         break;
    1141             : 
    1142           0 :     case priority_t::EMAIL_PRIORITY_HIGH:
    1143           0 :         name = QString::fromUtf8(get_name(name_t::SNAP_NAME_CORE_EMAIL_PRIORITY_HIGH));
    1144           0 :         break;
    1145             : 
    1146           0 :     case priority_t::EMAIL_PRIORITY_URGENT:
    1147           0 :         name = QString::fromUtf8(get_name(name_t::SNAP_NAME_CORE_EMAIL_PRIORITY_URGENT));
    1148           0 :         break;
    1149             : 
    1150           0 :     default:
    1151           0 :         throw snap_exception_invalid_parameter(QString("email::set_priority(): Unknown priority \"%1\".")
    1152           0 :                                                         .arg(static_cast<int>(priority)));
    1153             : 
    1154             :     }
    1155             : 
    1156           0 :     f_headers[get_name(name_t::SNAP_NAME_CORE_EMAIL_X_PRIORITY)] = QString("%1 (%2)").arg(static_cast<int>(priority)).arg(name);
    1157           0 :     f_headers[get_name(name_t::SNAP_NAME_CORE_EMAIL_X_MSMAIL_PRIORITY)] = name;
    1158           0 :     f_headers[get_name(name_t::SNAP_NAME_CORE_EMAIL_IMPORTANCE)] = name;
    1159           0 :     f_headers[get_name(name_t::SNAP_NAME_CORE_EMAIL_PRECEDENCE)] = name;
    1160           0 : }
    1161             : 
    1162             : 
    1163             : /** \brief Set the email subject.
    1164             :  *
    1165             :  * This function sets the subject of the email. Anything is permitted although
    1166             :  * you should not send emails with an empty subject.
    1167             :  *
    1168             :  * The system takes care of encoding the subject if required. It will also trim
    1169             :  * it and remove any unwanted characters (tabs, new lines, etc.)
    1170             :  *
    1171             :  * The subject line is also silently truncated to a reasonable size.
    1172             :  *
    1173             :  * Note that if the email is setup with a path to a page, the title of that
    1174             :  * page is used as the default subject. If the set_subject() function is
    1175             :  * called with a valid subject (not empty) then the page title is ignored.
    1176             :  *
    1177             :  * \note
    1178             :  * The set_subject() function is the same as calling the add_header() with
    1179             :  * "Subject" as the field name and \p subject as the value.
    1180             :  *
    1181             :  * \param[in] subject  The subject of the email.
    1182             :  */
    1183           0 : void email::set_subject(QString const & subject)
    1184             : {
    1185           0 :     f_headers[get_name(name_t::SNAP_NAME_CORE_EMAIL_SUBJECT)] = subject;
    1186           0 : }
    1187             : 
    1188             : 
    1189             : /** \brief Add a header to the email.
    1190             :  *
    1191             :  * The system takes care of most of the email headers but this function gives
    1192             :  * you the possibility to add more.
    1193             :  *
    1194             :  * Note that the priority should instead be set with the set_priority()
    1195             :  * function. This way it is more likely to work in all system that the
    1196             :  * sendmail plugin supports.
    1197             :  *
    1198             :  * The content type should not be set. The system automatically takes care of
    1199             :  * that for you including required encoding information, attachments, etc.
    1200             :  *
    1201             :  * The To, Cc, and Bcc fields are defined in this way. If multiple
    1202             :  * destinations are defined, you must concatenate them in the
    1203             :  * \p value parameter before calling this function.
    1204             :  *
    1205             :  * Note that the name of a header is case insensitive. So the names
    1206             :  * "Content-Type" and "content-type" represent the same header. Which
    1207             :  * one will be used when generating the output is a non-disclosed internal
    1208             :  * functionality. You probably want to use the SNAP_SENDMAIL_HEADER_...
    1209             :  * names anyway (at least for those that are defined.)
    1210             :  *
    1211             :  * \warning
    1212             :  * Also the function is called 'add', because you may add as many headers as you
    1213             :  * need, the function does NOT cumulate data within one field. Instead it
    1214             :  * overwrites the content of the field. This is one way to replace an unwanted
    1215             :  * value or force the content of a field for a given email.
    1216             :  *
    1217             :  * \exception sendmail_exception_invalid_argument
    1218             :  * The name of a header cannot be empty. This exception is raised if
    1219             :  * \p name is empty. The field name is also validated by the TLD library
    1220             :  * and must be composed of letters, digits, the dash character, and it
    1221             :  * has to start with a letter. The case is not important, however.
    1222             :  * Also, if the field represents an email or a list of emails, the
    1223             :  * value is also checked for validity.
    1224             :  *
    1225             :  * \param[in] name  A valid header name.
    1226             :  * \param[in] value  The value of this header.
    1227             :  */
    1228           0 : void email::add_header(QString const & name, QString const & value)
    1229             : {
    1230             :     // first define a type
    1231             :     //
    1232           0 :     tld_email_field_type type(tld_email_list::email_field_type(name.toUtf8().data()));
    1233           0 :     if(type == TLD_EMAIL_FIELD_TYPE_INVALID)
    1234             :     {
    1235             :         // this includes the case where the field name is empty
    1236           0 :         throw snap_exception_invalid_parameter("email::add_header(): Invalid header name for a header name.");
    1237             :     }
    1238             : 
    1239             :     // if type is not unknown, check the actual emails
    1240             :     //
    1241             :     // "UNKNOWN" means we don't consider the value of this header to be
    1242             :     // one or more emails
    1243             :     //
    1244           0 :     if(type != TLD_EMAIL_FIELD_TYPE_UNKNOWN)
    1245             :     {
    1246             :         // The Bcc and alike fields may be empty
    1247             :         //
    1248           0 :         if(type != TLD_EMAIL_FIELD_TYPE_ADDRESS_LIST_OPT
    1249           0 :         || !value.isEmpty())
    1250             :         {
    1251             :             // if not unknown then we should check the field value
    1252             :             // as a list of emails
    1253             :             //
    1254           0 :             tld_email_list emails;
    1255           0 :             if(emails.parse(value.toUtf8().data(), 0) != TLD_RESULT_SUCCESS)
    1256             :             {
    1257             :                 // TODO: this can happen if a TLD becomes obsolete and
    1258             :                 //       a user did not update one's email address.
    1259             :                 //
    1260           0 :                 throw snap_exception_invalid_parameter(QString("email::add_header(): Invalid emails in header field: \"%1: %2\"")
    1261           0 :                                                             .arg(name)
    1262           0 :                                                             .arg(value));
    1263             :             }
    1264             : 
    1265             :             // for many fields it can have at most one mailbox
    1266             :             //
    1267           0 :             if(type == TLD_EMAIL_FIELD_TYPE_MAILBOX
    1268           0 :             && emails.count() != 1)
    1269             :             {
    1270           0 :                 throw snap_exception_invalid_parameter(QString("email::add_header(): Header field expects exactly one email in: \"%1: %2\"")
    1271           0 :                                                             .arg(name)
    1272           0 :                                                             .arg(value));
    1273             :             }
    1274             :         }
    1275             :     }
    1276             : 
    1277           0 :     f_headers[name] = value;
    1278           0 : }
    1279             : 
    1280             : 
    1281             : /** \brief Remove a header.
    1282             :  *
    1283             :  * This function searches for the \p name header and removes it from the
    1284             :  * list of defined headers. This is different from setting the value of
    1285             :  * a header to the empty string as the header continues to exist.
    1286             :  *
    1287             :  * In most cases, you may just set a header to the empty string
    1288             :  * to delete it, however, removing it is cleaner.
    1289             :  *
    1290             :  * \param[in] name  The name of the header to get rid of.
    1291             :  */
    1292           0 : void email::remove_header(QString const & name)
    1293             : {
    1294           0 :     auto const it(f_headers.find(name));
    1295           0 :     if(it != f_headers.end())
    1296             :     {
    1297           0 :         f_headers.erase(it);
    1298             :     }
    1299           0 : }
    1300             : 
    1301             : 
    1302             : /** \brief Check whether a header is defined or not.
    1303             :  *
    1304             :  * This function returns true if the header was defined (add_header() was
    1305             :  * called at least once on that header name.)
    1306             :  *
    1307             :  * This function will return true even if the header was set to the empty
    1308             :  * string.
    1309             :  *
    1310             :  * \param[in] name  The name of the header to get rid of.
    1311             :  */
    1312           0 : bool email::has_header(QString const & name) const
    1313             : {
    1314           0 :     if(name.isEmpty())
    1315             :     {
    1316           0 :         throw snap_exception_invalid_parameter("email::has_header(): Cannot check for a header with an empty name.");
    1317             :     }
    1318             : 
    1319           0 :     return f_headers.find(name) != f_headers.end();
    1320             : }
    1321             : 
    1322             : 
    1323             : /** \brief Retrieve the value of a header.
    1324             :  *
    1325             :  * This function returns the value of the named header. If the header
    1326             :  * is not currently defined, this function returns an empty string.
    1327             :  *
    1328             :  * To know whether a header is defined, you may instead call the
    1329             :  * has_header() even if in most cases an empty string is very much
    1330             :  * similar to an undefined header.
    1331             :  *
    1332             :  * \exception sendmail_exception_invalid_argument
    1333             :  * The name of a header cannot be empty. This exception is raised if
    1334             :  * \p name is empty.
    1335             :  *
    1336             :  * \param[in] name  A valid header name.
    1337             :  *
    1338             :  * \return The current value of that header or an empty string if undefined.
    1339             :  */
    1340           0 : QString email::get_header(QString const & name) const
    1341             : {
    1342           0 :     if(name.isEmpty())
    1343             :     {
    1344           0 :         throw snap_exception_invalid_parameter("email::get_header(): Cannot retrieve a header with an empty name.");
    1345             :     }
    1346             : 
    1347           0 :     auto const it(f_headers.find(name));
    1348           0 :     if(it != f_headers.end())
    1349             :     {
    1350           0 :         return it->second;
    1351             :     }
    1352             : 
    1353             :     // return f_headers[name] -- this would create an entry for f_headers[name] for nothing
    1354           0 :     return QString();
    1355             : }
    1356             : 
    1357             : 
    1358             : /** \brief Get all the headers defined in this email.
    1359             :  *
    1360             :  * This function returns the map of the headers defined in this email. This
    1361             :  * can be used to quickly scan all the headers.
    1362             :  *
    1363             :  * \note
    1364             :  * It is important to remember that since this function returns a reference
    1365             :  * to the map of headers, it may break if you call add_header() while going
    1366             :  * through the references unless you make a copy.
    1367             :  *
    1368             :  * \return A direct constant reference to the internal header map.
    1369             :  */
    1370           0 : email::header_map_t const & email::get_all_headers() const
    1371             : {
    1372           0 :     return f_headers;
    1373             : }
    1374             : 
    1375             : 
    1376             : /** \brief Add the body attachment to this email.
    1377             :  *
    1378             :  * When creating an email with a path to a page (which is close to mandatory
    1379             :  * if you want to have translation and let users of your system to be able
    1380             :  * to edit the email in all languages.)
    1381             :  *
    1382             :  * This function should be private because it should only be used internally.
    1383             :  * Unfortunately, the function is used from the outside. But you've been
    1384             :  * warn. Really, this is using a push_front() instead of a push_back() it
    1385             :  * is otherwise the same as the add_attachment() function and you may want
    1386             :  * to read that function's documentation too.
    1387             :  *
    1388             :  * \param[in] data  The attachment to add as the body of this email.
    1389             :  *
    1390             :  * \sa email::add_attachment()
    1391             :  */
    1392           0 : void email::set_body_attachment(attachment const & data)
    1393             : {
    1394           0 :     f_attachments.insert(f_attachments.begin(), data);
    1395           0 : }
    1396             : 
    1397             : 
    1398             : /** \brief Add an attachment to this email.
    1399             :  *
    1400             :  * All data appearing in the body of the email is defined using attachments.
    1401             :  * This includes the normal plain text body if you use one. See the
    1402             :  * attachment class for details on how to create an attachment
    1403             :  * for an email.
    1404             :  *
    1405             :  * Note that if you want to add a plain text and an HTML version to your
    1406             :  * email, these are sub-attachments to one attachment of the email defined
    1407             :  * as alternatives. If only that one attachment is added to an email then
    1408             :  * it won't be made a sub-attachment in the final email buffer.
    1409             :  *
    1410             :  * \b IMPORTANT \b NOTE: the body and subject of emails are most often defined
    1411             :  * using a path to a page. This means the first attachment is to be viewed
    1412             :  * as an attachment, not the main body. Also, the attachments of the page
    1413             :  * are also viewed as attachments of the email and will appear before the
    1414             :  * attachments added here.
    1415             :  *
    1416             :  * \note
    1417             :  * It is important to note that the attachments are written in the email
    1418             :  * in the order they are defined here. It is quite customary to add the
    1419             :  * plain text first, then the HTML version, then the different files to
    1420             :  * attach to the email.
    1421             :  *
    1422             :  * \param[in] data  The email attachment to add by copy.
    1423             :  *
    1424             :  * \sa email::set_body_attachment()
    1425             :  */
    1426           0 : void email::add_attachment(attachment const & data)
    1427             : {
    1428           0 :     f_attachments.push_back(data);
    1429           0 : }
    1430             : 
    1431             : 
    1432             : /** \brief Retrieve the number of attachments defined in this email.
    1433             :  *
    1434             :  * This function defines the number of attachments that were added to this
    1435             :  * email. This is useful to retrieve the attachments with the
    1436             :  * get_attachment() function.
    1437             :  *
    1438             :  * \return The number of attachments defined in this email.
    1439             :  *
    1440             :  * \sa add_attachment()
    1441             :  * \sa get_attachment()
    1442             :  */
    1443           0 : int email::get_attachment_count() const
    1444             : {
    1445           0 :     return f_attachments.size();
    1446             : }
    1447             : 
    1448             : 
    1449             : /** \brief Retrieve the specified attachement.
    1450             :  *
    1451             :  * This function gives you a read/write reference to the specified
    1452             :  * attachment. This is used by plugins that need to access email
    1453             :  * data to filter it one way or the other (i.e. change all the tags
    1454             :  * with their corresponding values.)
    1455             :  *
    1456             :  * The \p index parameter must be a number between 0 and
    1457             :  * get_attachment_count() minus one. If no attachments were added
    1458             :  * then this function cannot be called.
    1459             :  *
    1460             :  * \exception out_of_range
    1461             :  * If the index is out of range, this exception is raised.
    1462             :  *
    1463             :  * \param[in] index  The index of the attachment to retrieve.
    1464             :  *
    1465             :  * \return A reference to the corresponding attachment.
    1466             :  *
    1467             :  * \sa add_attachment()
    1468             :  * \sa get_attachment_count()
    1469             :  */
    1470           0 : email::attachment & email::get_attachment(int index) const
    1471             : {
    1472           0 :     if(static_cast<size_t>(index) >= f_attachments.size())
    1473             :     {
    1474           0 :         throw std::out_of_range("email::get_attachment() called with an invalid index");
    1475             :     }
    1476           0 :     return const_cast<email::attachment &>(f_attachments[index]);
    1477             : }
    1478             : 
    1479             : 
    1480             : /** \brief Add a parameter to the email.
    1481             :  *
    1482             :  * Whenever you create an email, you may be able to offer additional
    1483             :  * parameters that are to be used as token replacement in the email.
    1484             :  * For example, when creating a new user, we ask the user to verify his
    1485             :  * email address. This is done by creating a session identifier and then
    1486             :  * asking the user to go to the special page /verify/\<session>. That
    1487             :  * way we know that the user received the email (although it may not
    1488             :  * exactly be the right person...)
    1489             :  *
    1490             :  * The name of the parameter should be something like "users::verify",
    1491             :  * i.e. it should be namespace specific to not clash with sendmail or
    1492             :  * other plugins parameters.
    1493             :  *
    1494             :  * All parameters have case sensitive names. So sendmail and Sendmail
    1495             :  * are not equal. However, all parameters should use lowercase only
    1496             :  * to match conventional XML tag and attribute names.
    1497             :  *
    1498             :  * \warning
    1499             :  * Also the function is called 'add', because you may add as many parameters
    1500             :  * as you have available, the function does NOT cumulate data within one field.
    1501             :  * Instead it overwrites the content of the field if set more than once. This
    1502             :  * is one way to replace an unwanted value or force the content of a field
    1503             :  * for a given email.
    1504             :  *
    1505             :  * \exception snap_exception_invalid_parameter
    1506             :  * The name of a parameter cannot be empty. This exception is raised if
    1507             :  * \p name is empty.
    1508             :  *
    1509             :  * \param[in] name  A valid parameter name.
    1510             :  * \param[in] value  The value of this header.
    1511             :  */
    1512           0 : void email::add_parameter(QString const & name, QString const & value)
    1513             : {
    1514           0 :     if(name.isEmpty())
    1515             :     {
    1516           0 :         throw snap_exception_invalid_parameter("email::add_parameter(): Cannot add a parameter with an empty name.");
    1517             :     }
    1518             : 
    1519           0 :     f_parameters[name] = value;
    1520           0 : }
    1521             : 
    1522             : 
    1523             : /** \brief Retrieve the value of a named parameter.
    1524             :  *
    1525             :  * This function returns the value of the named parameter. If the parameter
    1526             :  * is not currently defined, this function returns an empty string.
    1527             :  *
    1528             :  * \exception snap_exception_invalid_parameter
    1529             :  * The name of a parameter cannot be empty. This exception is raised if
    1530             :  * \p name is empty.
    1531             :  *
    1532             :  * \param[in] name  A valid parameter name.
    1533             :  *
    1534             :  * \return The current value of that parameter or an empty string if undefined.
    1535             :  */
    1536           0 : QString email::get_parameter(QString const & name) const
    1537             : {
    1538           0 :     if(name.isEmpty())
    1539             :     {
    1540           0 :         throw snap_exception_invalid_parameter("email::get_parameter(): Cannot retrieve a parameter with an empty name.");
    1541             :     }
    1542           0 :     auto const it(f_parameters.find(name));
    1543           0 :     if(it != f_parameters.end())
    1544             :     {
    1545           0 :         return it->second;
    1546             :     }
    1547             : 
    1548           0 :     return QString();
    1549             : }
    1550             : 
    1551             : 
    1552             : /** \brief Get all the parameters defined in this email.
    1553             :  *
    1554             :  * This function returns the map of the parameters defined in this email.
    1555             :  * This can be used to quickly scan all the parameters.
    1556             :  *
    1557             :  * \note
    1558             :  * It is important to remember that since this function returns a reference
    1559             :  * to the map of parameters, it may break if you call add_parameter() while
    1560             :  * going through the references.
    1561             :  *
    1562             :  * \return A direct reference to the internal parameter map.
    1563             :  */
    1564           0 : const email::parameter_map_t & email::get_all_parameters() const
    1565             : {
    1566           0 :     return f_parameters;
    1567             : }
    1568             : 
    1569             : 
    1570             : /** \brief Unserialize an email message.
    1571             :  *
    1572             :  * This function unserializes an email message that was serialized using
    1573             :  * the serialize() function.
    1574             :  *
    1575             :  * You are expected to first create an email object and then call this
    1576             :  * function with the data parameter set as the string that the serialize()
    1577             :  * function returned.
    1578             :  *
    1579             :  * You may setup some default headers such as the X-Mailer value in your
    1580             :  * email object before calling this function. If such header information
    1581             :  * is defined in the serialized data then it will be overwritten with
    1582             :  * that data. Otherwise it will remain the same.
    1583             :  *
    1584             :  * The function doesn't return anything. Instead it unserializes the
    1585             :  * \p data directly in this email object.
    1586             :  *
    1587             :  * \param[in] data  The serialized email data to transform.
    1588             :  *
    1589             :  * \sa serialize()
    1590             :  */
    1591           0 : void email::unserialize(QString const & data)
    1592             : {
    1593             :     // QBuffer takes a non-const QByteArray so we have to create a copy
    1594           0 :     QByteArray non_const_data(data.toUtf8().data());
    1595           0 :     QBuffer in(&non_const_data);
    1596           0 :     in.open(QIODevice::ReadOnly);
    1597           0 :     QtSerialization::QReader reader(in);
    1598           0 :     QtSerialization::QComposite comp;
    1599           0 :     QtSerialization::QFieldTag rules(comp, "email", this);
    1600           0 :     reader.read(comp);
    1601           0 : }
    1602             : 
    1603             : 
    1604             : /** \brief Read the contents of one tag from the reader.
    1605             :  *
    1606             :  * This function reads the contents of the main email tag. It calls
    1607             :  * the attachment unserialize() as required whenever an attachment
    1608             :  * is found in the stream.
    1609             :  *
    1610             :  * \param[in] name  The name of the tag being read.
    1611             :  * \param[in] r  The reader used to read the input data.
    1612             :  */
    1613           0 : void email::readTag(const QString& name, QtSerialization::QReader& r)
    1614             : {
    1615             :     //if(name == "email")
    1616             :     //{
    1617             :     //    QtSerialization::QComposite comp;
    1618             :     //    QtSerialization::QFieldTag info(comp, "content", this);
    1619             :     //    r.read(comp);
    1620             :     //}
    1621           0 :     if(name == "email")
    1622             :     {
    1623           0 :         QtSerialization::QComposite comp;
    1624           0 :         QtSerialization::QFieldBool tag_branding(comp, "branding", f_branding);
    1625           0 :         QtSerialization::QFieldString tag_cumulative(comp, "cumulative", f_cumulative);
    1626           0 :         QtSerialization::QFieldString tag_site_key(comp, "site_key", f_site_key);
    1627           0 :         QtSerialization::QFieldString tag_email_path(comp, "email_path", f_email_path);
    1628           0 :         QtSerialization::QFieldString tag_email_key(comp, "email_key", f_email_key);
    1629           0 :         QtSerialization::QFieldTag tag_header(comp, "header", this);
    1630           0 :         QtSerialization::QFieldTag tag_attachment(comp, "attachment", this);
    1631           0 :         QtSerialization::QFieldTag tag_parameter(comp, "parameter", this);
    1632           0 :         r.read(comp);
    1633             :     }
    1634           0 :     else if(name == "header")
    1635             :     {
    1636           0 :         QtSerialization::QComposite comp;
    1637           0 :         QString header_name;
    1638           0 :         QtSerialization::QFieldString tag_name(comp, "name", header_name);
    1639           0 :         QString header_value;
    1640           0 :         QtSerialization::QFieldString tag_value(comp, "value", header_value);
    1641           0 :         r.read(comp);
    1642           0 :         f_headers[header_name] = header_value;
    1643             :     }
    1644           0 :     else if(name == "attachment")
    1645             :     {
    1646           0 :         attachment a;
    1647           0 :         a.unserialize(r);
    1648           0 :         add_attachment(a);
    1649             :     }
    1650           0 :     else if(name == "parameter")
    1651             :     {
    1652           0 :         QtSerialization::QComposite comp;
    1653           0 :         QString parameter_name;
    1654           0 :         QtSerialization::QFieldString tag_name(comp, "name", parameter_name);
    1655           0 :         QString parameter_value;
    1656           0 :         QtSerialization::QFieldString tag_value(comp, "value", parameter_value);
    1657           0 :         r.read(comp);
    1658           0 :         f_parameters[parameter_name] = parameter_value;
    1659             :     }
    1660           0 : }
    1661             : 
    1662             : 
    1663             : /** \brief Transform the email in one string.
    1664             :  *
    1665             :  * This function transform the email data in one string so it can easily
    1666             :  * be saved in the Cassandra database. This is done so it can be sent to
    1667             :  * the recipients using the backend process preferably on a separate
    1668             :  * computer (i.e. a computer that is not being accessed by your web
    1669             :  * clients.)
    1670             :  *
    1671             :  * The unserialize() function can be used to restore an email that was
    1672             :  * previously serialized with this function.
    1673             :  *
    1674             :  * \return The email object in the form of a string.
    1675             :  *
    1676             :  * \sa unserialize()
    1677             :  */
    1678           0 : QString email::serialize() const
    1679             : {
    1680           0 :     QByteArray result;
    1681           0 :     QBuffer archive(&result);
    1682           0 :     archive.open(QIODevice::WriteOnly);
    1683             :     {
    1684           0 :         QtSerialization::QWriter w(archive, "email", EMAIL_MAJOR_VERSION, EMAIL_MINOR_VERSION);
    1685           0 :         QtSerialization::QWriter::QTag tag(w, "email");
    1686           0 :         QtSerialization::writeTag(w, "branding", f_branding);
    1687           0 :         if(!f_cumulative.isEmpty())
    1688             :         {
    1689           0 :             QtSerialization::writeTag(w, "cumulative", f_cumulative);
    1690             :         }
    1691           0 :         QtSerialization::writeTag(w, "site_key", f_site_key);
    1692           0 :         QtSerialization::writeTag(w, "email_path", f_email_path);
    1693           0 :         QtSerialization::writeTag(w, "email_key", f_email_key);
    1694           0 :         for(auto const & it : f_headers)
    1695             :         {
    1696           0 :             QtSerialization::QWriter::QTag header(w, "header");
    1697           0 :             QtSerialization::writeTag(w, "name", it.first);
    1698           0 :             QtSerialization::writeTag(w, "value", it.second);
    1699             :         }
    1700           0 :         for(auto const & it : f_attachments)
    1701             :         {
    1702           0 :             it.serialize(w, false);
    1703             :         }
    1704           0 :         for(auto const & it : f_parameters)
    1705             :         {
    1706           0 :             QtSerialization::QWriter::QTag parameter(w, "parameter");
    1707           0 :             QtSerialization::writeTag(w, "name", it.first);
    1708           0 :             QtSerialization::writeTag(w, "value", it.second);
    1709             :         }
    1710             :         // end the writer so everything gets saved in the buffer (result)
    1711             :     }
    1712             : 
    1713           0 :     return QString::fromUtf8(result.data());
    1714             : }
    1715             : 
    1716             : 
    1717             : /** \brief Send this email.
    1718             :  *
    1719             :  * This function sends  the specified email. It generates all the body
    1720             :  * and attachments, etc.
    1721             :  *
    1722             :  * Note that the function uses callbacks in order to retrieve the body
    1723             :  * and attachment from the database as the Snap! environment uses those
    1724             :  * for much of the data to be sent in emails. However, it is not a
    1725             :  * requirements in case you want to send an email from another server
    1726             :  * than a snap_child or snap_backend.
    1727             :  *
    1728             :  * \exception email_exception_missing_parameter
    1729             :  * If the From header or the destination email only are missing this
    1730             :  * exception is raised.
    1731             :  */
    1732           0 : bool email::send() const
    1733             : {
    1734             :     // verify that the `From` and `To` headers are defined
    1735             :     //
    1736           0 :     QString const from(get_header(get_name(name_t::SNAP_NAME_CORE_EMAIL_FROM)));
    1737           0 :     QString const to(get_header(get_name(name_t::SNAP_NAME_CORE_EMAIL_TO)));
    1738             : 
    1739           0 :     if(from.isEmpty()
    1740           0 :     || to.isEmpty())
    1741             :     {
    1742           0 :         throw snap_exception_missing_parameter("email::send() called without a From or a To header field defined. Make sure you call the set_from() and set_header() functions appropriately.");
    1743             :     }
    1744             : 
    1745             :     // verify that we have at least one attachment
    1746             :     // (the body is an attachment)
    1747             :     //
    1748           0 :     int const max_attachments(get_attachment_count());
    1749           0 :     if(max_attachments < 1)
    1750             :     {
    1751           0 :         throw snap_exception_missing_parameter("email::send() called without at least one attachment (body).");
    1752             :     }
    1753             : 
    1754             :     // we want to transform the body from HTML to text ahead of time
    1755             :     //
    1756           0 :     attachment const & body_attachment(get_attachment(0));
    1757             : 
    1758             :     // TODO: verify that the body is indeed HTML!
    1759             :     //       although html2text works against plain text but that is a waste
    1760             :     //
    1761             :     //       also, we should offer a way for the person creating an email
    1762             :     //       to specify both: a plain text body and an HTML body
    1763             :     //
    1764           0 :     QString plain_text;
    1765           0 :     QString const body_mime_type(body_attachment.get_header(get_name(name_t::SNAP_NAME_CORE_CONTENT_TYPE_HEADER)));
    1766             : 
    1767             :     // TODO: this test is wrong as it would match things like "text/html-special"
    1768             :     //
    1769           0 :     if(body_mime_type.mid(0, 9) == "text/html")
    1770             :     {
    1771           0 :         process p("html2text");
    1772           0 :         p.set_mode(process::mode_t::PROCESS_MODE_INOUT);
    1773           0 :         p.set_command("html2text");
    1774             :         //p.add_argument("-help");
    1775           0 :         p.add_argument("-nobs");
    1776           0 :         p.add_argument("-utf8");
    1777           0 :         p.add_argument("-style");
    1778           0 :         p.add_argument("pretty");
    1779           0 :         p.add_argument("-width");
    1780           0 :         p.add_argument("70");
    1781           0 :         std::string html_data;
    1782           0 :         QByteArray data(body_attachment.get_data());
    1783             : 
    1784             :         // TODO: support other encoding, err if not supported
    1785             :         //
    1786           0 :         if(body_attachment.get_header(get_name(name_t::SNAP_NAME_CORE_EMAIL_CONTENT_TRANSFER_ENCODING))
    1787           0 :                                 == get_name(name_t::SNAP_NAME_CORE_EMAIL_CONTENT_ENCODING_QUOTED_PRINTABLE))
    1788             :         {
    1789             :             // if it was quoted-printable encoded, we have to decode
    1790             :             //
    1791             :             // I know, we encode in this very function and could just
    1792             :             // keep a copy of the original, HOWEVER, the end user could
    1793             :             // build the whole email with this encoding already in place
    1794             :             // and thus we anyway would have to decode... This being said,
    1795             :             // we could have that as an optimization XXX
    1796             :             //
    1797           0 :             html_data = quoted_printable::decode(data.data());
    1798             :         }
    1799             :         else
    1800             :         {
    1801           0 :             html_data = data.data();
    1802             :         }
    1803           0 :         p.set_input(QString::fromUtf8(html_data.c_str()));
    1804             : 
    1805             :         // conver that HTML to plain text
    1806             :         //
    1807           0 :         int const r(p.run());
    1808           0 :         if(r == 0)
    1809             :         {
    1810           0 :             plain_text = p.get_output();
    1811             :         }
    1812             :         else
    1813             :         {
    1814             :             // no plain text, but let us know that something went wrong at least
    1815             :             //
    1816           0 :             SNAP_LOG_WARNING("An error occurred while executing html2text (exit code: ")(r)(")");
    1817             :         }
    1818             :     }
    1819             : 
    1820             :     // convert the "from" email address in a TLD email address so we can use
    1821             :     // the f_email_only version for the command line "sender" parameter
    1822             :     //
    1823           0 :     tld_email_list from_list;
    1824           0 :     if(from_list.parse(from.toUtf8().data(), 0) != TLD_RESULT_SUCCESS)
    1825             :     {
    1826           0 :         throw snap_exception_invalid_parameter(QString("email::send() called with invalid sender email address: \"%1\" (parsing failed).").arg(from));
    1827             :     }
    1828           0 :     tld_email_list::tld_email_t s;
    1829           0 :     if(!from_list.next(s))
    1830             :     {
    1831           0 :         throw snap_exception_invalid_parameter(QString("email::send() called with invalid sender email address: \"%1\" (no email returned).").arg(from));
    1832             :     }
    1833             : 
    1834             :     // convert the "to" email address in a TLD email address so we can use
    1835             :     // the f_email_only version for the command line "to" parameter
    1836             :     //
    1837           0 :     tld_email_list to_list;
    1838           0 :     if(to_list.parse(to.toUtf8().data(), 0) != TLD_RESULT_SUCCESS)
    1839             :     {
    1840           0 :         throw snap_exception_invalid_parameter(QString("email::send() called with invalid destination email address: \"%1\" (parsing failed).").arg(to));
    1841             :     }
    1842           0 :     tld_email_list::tld_email_t m;
    1843           0 :     if(!to_list.next(m))
    1844             :     {
    1845           0 :         throw snap_exception_invalid_parameter(QString("email::send() called with invalid destination email address: \"%1\" (no email returned).").arg(to));
    1846             :     }
    1847             : 
    1848             :     // create an output stream to send the email
    1849             :     //
    1850           0 :     QString const cmd(QString("sendmail -f \"%1\" \"%2\"")
    1851           0 :                             .arg(QString::fromUtf8(s.f_email_only.c_str()))
    1852           0 :                             .arg(QString::fromUtf8(m.f_email_only.c_str())));
    1853           0 :     SNAP_LOG_TRACE("sendmail command: [")(cmd)("]");
    1854           0 :     snap_pipe spipe(cmd, snap_pipe::mode_t::PIPE_MODE_IN);
    1855           0 :     std::ostream f(&spipe);
    1856             : 
    1857             :     // convert email data to text and send that to the sendmail command line
    1858             :     //
    1859           0 :     email::header_map_t headers(f_headers);
    1860           0 :     bool const body_only(max_attachments == 1 && plain_text.isEmpty());
    1861           0 :     QString boundary;
    1862           0 :     if(body_only)
    1863             :     {
    1864             :         // if the body is by itself, then its encoding needs to be transported
    1865             :         // to the main set of headers
    1866             :         //
    1867           0 :         if(body_attachment.get_header(get_name(name_t::SNAP_NAME_CORE_EMAIL_CONTENT_TRANSFER_ENCODING))
    1868           0 :                                 == get_name(name_t::SNAP_NAME_CORE_EMAIL_CONTENT_ENCODING_QUOTED_PRINTABLE))
    1869             :         {
    1870           0 :             headers[get_name(name_t::SNAP_NAME_CORE_EMAIL_CONTENT_TRANSFER_ENCODING)]
    1871           0 :                                 = get_name(name_t::SNAP_NAME_CORE_EMAIL_CONTENT_ENCODING_QUOTED_PRINTABLE);
    1872             :         }
    1873             :     }
    1874             :     else
    1875             :     {
    1876             :         // boundary      := 0*69<bchars> bcharsnospace
    1877             :         // bchars        := bcharsnospace / " "
    1878             :         // bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" /
    1879             :         //                  "+" / "_" / "," / "-" / "." /
    1880             :         //                  "/" / ":" / "=" / "?"
    1881             :         //
    1882             :         // Note: we generate boundaries without special characters
    1883             :         //       (and especially no spaces or dashes) to make it simpler
    1884             :         //
    1885             :         // Note: the boundary starts wity "=S" which is not a valid
    1886             :         //       quoted-printable sequence of characters (on purpose)
    1887             :         //
    1888           0 :         char const allowed[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; //'()+_,./:=?";
    1889           0 :         boundary = "=Snap.Websites=";
    1890           0 :         for(int i(0); i < 20; ++i)
    1891             :         {
    1892             :             // this is just for boundaries, so rand() is more than enough
    1893             :             // it just needs to not match anything in the emails
    1894             :             //
    1895           0 :             int const c(static_cast<int>(rand() % (sizeof(allowed) - 1)));
    1896           0 :             boundary += allowed[c];
    1897             :         }
    1898           0 :         headers[get_name(name_t::SNAP_NAME_CORE_CONTENT_TYPE_HEADER)] = "multipart/mixed;\n  boundary=\"" + boundary + "\"";
    1899           0 :         headers[get_name(name_t::SNAP_NAME_CORE_EMAIL_MIME_VERSION)] = "1.0";
    1900             :     }
    1901             : 
    1902             :     // setup the "Date: ..." field if not already defined
    1903             :     //
    1904           0 :     if(headers.find(get_name(name_t::SNAP_NAME_CORE_DATE)) == headers.end())
    1905             :     {
    1906             :         // the date must be specified in English only which prevents us from
    1907             :         // using the strftime()
    1908             :         //
    1909           0 :         headers[get_name(name_t::SNAP_NAME_CORE_DATE)] = snap_child::date_to_string(time(nullptr) * 1000000, snap_child::date_format_t::DATE_FORMAT_EMAIL);
    1910             :     }
    1911             : 
    1912             :     // setup a default "Content-Language: ..." because in general
    1913             :     // that makes things work better
    1914             :     //
    1915           0 :     if(headers.find(get_name(name_t::SNAP_NAME_CORE_CONTENT_LANGUAGE)) == headers.end())
    1916             :     {
    1917           0 :         headers[get_name(name_t::SNAP_NAME_CORE_CONTENT_LANGUAGE)] = "en-us";
    1918             :     }
    1919             : 
    1920           0 :     for(auto const & it : headers)
    1921             :     {
    1922             :         // TODO: the it.second needs to be URI encoded to be valid
    1923             :         //       in an email; if some characters appear that need
    1924             :         //       encoding, we should err (we probably want to
    1925             :         //       capture those in the add_header() though)
    1926             :         //
    1927           0 :         f << it.first << ": " << it.second << std::endl;
    1928             :     }
    1929             : 
    1930             :     // XXX: allow administrators to change the `branding` flag
    1931             :     //
    1932           0 :     if(f_branding)
    1933             :     {
    1934           0 :         f << "X-Generated-By: Snap! Websites C++ v" SNAPWEBSITES_VERSION_STRING " (https://snapwebsites.org/)" << std::endl
    1935           0 :           << "X-Mailer: Snap! Websites C++ v" SNAPWEBSITES_VERSION_STRING " (https://snapwebsites.org/)" << std::endl;
    1936             :     }
    1937             : 
    1938             :     // end the headers
    1939             :     //
    1940           0 :     f << std::endl;
    1941             : 
    1942           0 :     if(body_only)
    1943             :     {
    1944             :         // in this case we only have one entry, probably HTML, and thus we
    1945             :         // can avoid the multi-part headers and attachments
    1946             :         //
    1947           0 :         f << body_attachment.get_data().data() << std::endl;
    1948             :     }
    1949             :     else
    1950             :     {
    1951             :         // TBD: should we make this text changeable by client?
    1952             :         //
    1953           0 :         f << "The following are various parts of a multipart email." << std::endl
    1954           0 :           << "It is likely to include a text version (first part) that you should" << std::endl
    1955           0 :           << "be able to read as is." << std::endl
    1956           0 :           << "It may be followed by HTML and then various attachments." << std::endl
    1957           0 :           << "Please consider installing a MIME capable client to read this email." << std::endl
    1958           0 :           << std::endl;
    1959             : 
    1960           0 :         int i(0);
    1961           0 :         if(!plain_text.isEmpty())
    1962             :         {
    1963             :             // if we have plain text then we have alternatives
    1964             :             //
    1965           0 :             f << "--" << boundary << std::endl
    1966           0 :               << "Content-Type: multipart/alternative;" << std::endl
    1967           0 :               << "  boundary=\"" << boundary << ".msg\"" << std::endl
    1968           0 :               << std::endl
    1969           0 :               << "--" << boundary << ".msg" << std::endl
    1970           0 :               << "Content-Type: text/plain; charset=\"utf-8\"" << std::endl
    1971             :               //<< "MIME-Version: 1.0" << std::endl -- only show this one in the main header
    1972           0 :               << "Content-Transfer-Encoding: quoted-printable" << std::endl
    1973           0 :               << "Content-Description: Mail message body" << std::endl
    1974           0 :               << std::endl
    1975           0 :               << quoted_printable::encode(plain_text.toUtf8().data(), quoted_printable::QUOTED_PRINTABLE_FLAG_NO_LONE_PERIOD) << std::endl;
    1976             : 
    1977             :             // at this time, this if() should always be true
    1978             :             //
    1979           0 :             if(i < max_attachments)
    1980             :             {
    1981             :                 // now include the HTML
    1982             :                 //
    1983           0 :                 f << "--" << boundary << ".msg" << std::endl;
    1984           0 :                 for(auto const & it : body_attachment.get_all_headers())
    1985             :                 {
    1986           0 :                     f << it.first << ": " << it.second << std::endl;
    1987             :                 }
    1988             : 
    1989             :                 // one empty line before the contents
    1990             :                 // here the data is already encoded
    1991           0 :                 f << std::endl
    1992           0 :                   << body_attachment.get_data().data() << std::endl
    1993           0 :                   << "--" << boundary << ".msg--" << std::endl
    1994           0 :                   << std::endl;
    1995             : 
    1996             :                 // we used "attachment" 0, so print the others starting at 1
    1997             :                 //
    1998           0 :                 i = 1;
    1999             :             }
    2000             :         }
    2001             : 
    2002             :         // send the remaining attachments (possibly attachment 0 if
    2003             :         // we did not have plain text)
    2004             :         //
    2005           0 :         for(; i < max_attachments; ++i)
    2006             :         {
    2007             :             // work on this attachment
    2008             :             //
    2009           0 :             email::attachment const & a(get_attachment(i));
    2010             : 
    2011             :             // send the boundary
    2012             :             //
    2013           0 :             f << "--" << boundary << std::endl;
    2014             : 
    2015             :             // send  the headers for that attachment
    2016             :             //
    2017             :             // we get a copy and modify it slightly by making sure that
    2018             :             // the filename is defined in both the Content-Disposition
    2019             :             // and the Content-Type
    2020             :             //
    2021           0 :             header_map_t attachment_headers(a.get_all_headers());
    2022           0 :             copy_filename_to_content_type(attachment_headers);
    2023           0 :             for(auto const & it : attachment_headers)
    2024             :             {
    2025           0 :                 f << it.first << ": " << it.second << std::endl;
    2026             :             }
    2027             : 
    2028             :             // one empty line before the contents
    2029             :             //
    2030           0 :             f << std::endl;
    2031             : 
    2032             :             // here the data is already encoded
    2033             :             //
    2034           0 :             f << a.get_data().data() << std::endl;
    2035             :         }
    2036             : 
    2037             :         // last boundary to end them all
    2038             :         //
    2039           0 :         f << "--" << boundary << "--" << std::endl;
    2040             :     }
    2041             : 
    2042             :     // end the message
    2043             :     //
    2044           0 :     f << std::endl
    2045           0 :       << "." << std::endl;
    2046             : 
    2047             :     // make sure the ostream gets flushed or some data could be left in
    2048             :     // a cache and never written to the pipe (unlikely since we do not
    2049             :     // use the cache, but future C++ versions could have a problem.)
    2050             :     //
    2051           0 :     f.flush();
    2052             : 
    2053             :     // close pipe as soon as we are done writing to it
    2054             :     // and return true if it all worked as expected
    2055             :     //
    2056           0 :     return spipe.close_pipe() == 0;
    2057             : }
    2058             : 
    2059             : 
    2060             : /** \brief Compare two email obejcts for equality.
    2061             :  *
    2062             :  * This function checks whether two email objects are equal.
    2063             :  *
    2064             :  * \param[in] rhs  The right hand side email.
    2065             :  *
    2066             :  * \return true if both emails are considered equal.
    2067             :  */
    2068           0 : bool email::operator == (email const & rhs) const
    2069             : {
    2070           0 :     return f_branding    == rhs.f_branding
    2071           0 :         && f_cumulative  == rhs.f_cumulative
    2072           0 :         && f_site_key    == rhs.f_site_key
    2073           0 :         && f_email_path  == rhs.f_email_path
    2074           0 :         && f_email_key   == rhs.f_email_key
    2075             :         //&& f_time        == rhs.f_time -- this is pretty much never going to be equal so do not compare
    2076           0 :         && f_headers     == rhs.f_headers
    2077           0 :         && f_attachments == rhs.f_attachments
    2078           0 :         && f_parameters  == rhs.f_parameters;
    2079             : }
    2080             : 
    2081             : 
    2082           6 : }
    2083             : // snap namespace
    2084             : // vim: ts=4 sw=4 et

Generated by: LCOV version 1.13