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
|