Akonadi Notes

noteutils.cpp
1 /* This file is part of the KDE project
2  SPDX-FileCopyrightText: 2011 Christian Mollekopf <[email protected]>
3 
4  SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "noteutils.h"
8 
9 #include "akonadi_notes_debug.h"
10 #include <KLocalizedString>
11 #include <QDateTime>
12 #include <kmime/kmime_message.h>
13 
14 #include <QRegularExpression>
15 #include <QString>
16 #include <QUuid>
17 #include <qdom.h>
18 
19 namespace Akonadi
20 {
21 namespace NoteUtils
22 {
23 #define X_NOTES_UID_HEADER "X-Akonotes-UID"
24 #define X_NOTES_LASTMODIFIED_HEADER "X-Akonotes-LastModified"
25 #define X_NOTES_CLASSIFICATION_HEADER "X-Akonotes-Classification"
26 #define X_NOTES_CUSTOM_HEADER "X-Akonotes-Custom"
27 
28 #define CLASSIFICATION_PUBLIC QStringLiteral("Public")
29 #define CLASSIFICATION_PRIVATE QStringLiteral("Private")
30 #define CLASSIFICATION_CONFIDENTIAL QStringLiteral("Confidential")
31 
32 #define X_NOTES_URL_HEADER "X-Akonotes-Url"
33 #define X_NOTES_LABEL_HEADER "X-Akonotes-Label"
34 #define X_NOTES_CONTENTTYPE_HEADER "X-Akonotes-Type"
35 #define CONTENT_TYPE_CUSTOM QStringLiteral("custom")
36 #define CONTENT_TYPE_ATTACHMENT QStringLiteral("attachment")
37 
38 #define ENCODING "utf-8"
39 
40 class Q_DECL_HIDDEN Attachment::AttachmentPrivate
41 {
42 public:
43  AttachmentPrivate(const QUrl &url, const QString &mimetype)
44  : mUrl(url)
45  , mMimetype(mimetype)
46  {
47  }
48 
49  AttachmentPrivate(const QByteArray &data, const QString &mimetype)
50  : mData(data)
51  , mMimetype(mimetype)
52  {
53  }
54 
55  AttachmentPrivate(const AttachmentPrivate &other)
56  {
57  *this = other;
58  }
59 
60  QUrl mUrl;
61  QByteArray mData;
62  bool mDataBase64Encoded = false;
63  QString mMimetype;
64  QString mLabel;
65  QString mContentID;
66 };
67 
69  : d_ptr(new Attachment::AttachmentPrivate(QUrl(), QString()))
70 {
71 }
72 
74  : d_ptr(new Attachment::AttachmentPrivate(url, mimetype))
75 {
76 }
77 
79  : d_ptr(new Attachment::AttachmentPrivate(data, mimetype))
80 {
81 }
82 
84  : d_ptr(new AttachmentPrivate(*other.d_func()))
85 {
86 }
87 
88 Attachment::~Attachment()
89 {
90  delete d_ptr;
91 }
92 
93 bool Attachment::operator==(const Attachment &a) const
94 {
95  Q_D(const Attachment);
96  if (d->mUrl.isEmpty()) {
97  return d->mUrl == a.d_func()->mUrl && d->mDataBase64Encoded == a.d_func()->mDataBase64Encoded && d->mMimetype == a.d_func()->mMimetype
98  && d->mContentID == a.d_func()->mContentID && d->mLabel == a.d_func()->mLabel;
99  }
100  return d->mData == a.d_func()->mData && d->mDataBase64Encoded == a.d_func()->mDataBase64Encoded && d->mMimetype == a.d_func()->mMimetype
101  && d->mContentID == a.d_func()->mContentID && d->mLabel == a.d_func()->mLabel;
102 }
103 
104 void Attachment::operator=(const Attachment &a)
105 {
106  *d_ptr = *a.d_ptr;
107 }
108 
110 {
111  Q_D(const Attachment);
112  return d->mUrl;
113 }
114 
116 {
117  Q_D(const Attachment);
118  return d->mData;
119 }
120 
122 {
123  Q_D(Attachment);
124  d->mDataBase64Encoded = true;
125 }
126 
128 {
129  Q_D(const Attachment);
130  return d->mDataBase64Encoded;
131 }
132 
134 {
135  Q_D(Attachment);
136  d->mContentID = contentID;
137 }
138 
140 {
141  Q_D(const Attachment);
142  return d->mContentID;
143 }
144 
146 {
147  Q_D(const Attachment);
148  return d->mMimetype;
149 }
150 
152 {
153  Q_D(Attachment);
154  d->mLabel = label;
155 }
156 
158 {
159  Q_D(const Attachment);
160  return d->mLabel;
161 }
162 
163 class Q_DECL_HIDDEN NoteMessageWrapper::NoteMessageWrapperPrivate
164 {
165 public:
166  NoteMessageWrapperPrivate()
167  {
168  }
169 
170  NoteMessageWrapperPrivate(const KMime::MessagePtr &msg)
171  {
172  readMimeMessage(msg);
173  }
174 
175  void readMimeMessage(const KMime::MessagePtr &msg);
176 
177  KMime::Content *createCustomPart() const;
178  void parseCustomPart(KMime::Content *);
179 
180  KMime::Content *createAttachmentPart(const Attachment &) const;
181  void parseAttachmentPart(KMime::Content *);
182 
183  QString uid;
184  QString title;
185  QString text;
186  QString from;
187  QDateTime creationDate;
188  QDateTime lastModifiedDate;
189  QMap<QString, QString> custom;
190  QVector<Attachment> attachments;
191  Classification classification = Public;
192  Qt::TextFormat textFormat = Qt::PlainText;
193 };
194 
195 void NoteMessageWrapper::NoteMessageWrapperPrivate::readMimeMessage(const KMime::MessagePtr &msg)
196 {
197  if (!msg.data()) {
198  qCWarning(AKONADINOTES_LOG) << "Empty message";
199  return;
200  }
201  title = msg->subject(true)->asUnicodeString();
202  text = msg->mainBodyPart()->decodedText(true); // remove trailing whitespace, so we get rid of " " in empty notes
203  if (msg->from(false)) {
204  from = msg->from(false)->asUnicodeString();
205  }
206  creationDate = msg->date(true)->dateTime();
207  if (msg->mainBodyPart()->contentType(false) && msg->mainBodyPart()->contentType()->mimeType() == "text/html") {
208  textFormat = Qt::RichText;
209  }
210 
211  if (KMime::Headers::Base *lastmod = msg->headerByType(X_NOTES_LASTMODIFIED_HEADER)) {
212  lastModifiedDate = QDateTime::fromString(lastmod->asUnicodeString(), Qt::RFC2822Date);
213  if (!lastModifiedDate.isValid()) {
214  qCWarning(AKONADINOTES_LOG) << "failed to parse lastModifiedDate";
215  }
216  }
217 
218  if (KMime::Headers::Base *uidHeader = msg->headerByType(X_NOTES_UID_HEADER)) {
219  uid = uidHeader->asUnicodeString();
220  }
221 
222  if (KMime::Headers::Base *classificationHeader = msg->headerByType(X_NOTES_CLASSIFICATION_HEADER)) {
223  const QString &c = classificationHeader->asUnicodeString();
224  if (c == CLASSIFICATION_PRIVATE) {
225  classification = Private;
226  } else if (c == CLASSIFICATION_CONFIDENTIAL) {
227  classification = Confidential;
228  }
229  }
230 
231  const auto list = msg->contents();
232  for (KMime::Content *c : list) {
233  if (KMime::Headers::Base *typeHeader = c->headerByType(X_NOTES_CONTENTTYPE_HEADER)) {
234  const QString &type = typeHeader->asUnicodeString();
235  if (type == CONTENT_TYPE_CUSTOM) {
236  parseCustomPart(c);
237  } else if (type == CONTENT_TYPE_ATTACHMENT) {
238  parseAttachmentPart(c);
239  } else {
240  qCWarning(AKONADINOTES_LOG) << "unknown type " << type;
241  }
242  }
243  }
244 }
245 
246 QDomDocument createXMLDocument()
247 {
248  QDomDocument document;
249  const QString p = QStringLiteral("version=\"1.0\" encoding=\"UTF-8\"");
250  document.appendChild(document.createProcessingInstruction(QStringLiteral("xml"), p));
251  return document;
252 }
253 
254 QDomDocument loadDocument(KMime::Content *part)
255 {
256  QString errorMsg;
257  int errorLine, errorColumn;
258  QDomDocument document;
259  bool ok = document.setContent(part->body(), &errorMsg, &errorLine, &errorColumn);
260  if (!ok) {
261  qCWarning(AKONADINOTES_LOG) << part->body();
262  qWarning("Error loading document: %s, line %d, column %d", qPrintable(errorMsg), errorLine, errorColumn);
263  return QDomDocument();
264  }
265  return document;
266 }
267 
268 KMime::Content *NoteMessageWrapper::NoteMessageWrapperPrivate::createCustomPart() const
269 {
270  KMime::Content *content = new KMime::Content();
271  auto header = new KMime::Headers::Generic(X_NOTES_CONTENTTYPE_HEADER);
272  header->fromUnicodeString(CONTENT_TYPE_CUSTOM, ENCODING);
273  content->appendHeader(header);
274  QDomDocument document = createXMLDocument();
275  QDomElement element = document.createElement(QStringLiteral("custom"));
276  element.setAttribute(QStringLiteral("version"), QStringLiteral("1.0"));
277  QMap<QString, QString>::const_iterator end = custom.end();
278  for (QMap<QString, QString>::const_iterator it = custom.begin(); it != end; ++it) {
279  QDomElement e = element.ownerDocument().createElement(it.key());
280  QDomText t = element.ownerDocument().createTextNode(it.value());
281  e.appendChild(t);
282  element.appendChild(e);
283  document.appendChild(element);
284  }
285  content->setBody(document.toString().toLatin1());
286  return content;
287 }
288 
289 void NoteMessageWrapper::NoteMessageWrapperPrivate::parseCustomPart(KMime::Content *part)
290 {
291  QDomDocument document = loadDocument(part);
292  if (document.isNull()) {
293  return;
294  }
295  QDomElement top = document.documentElement();
296  if (top.tagName() != QLatin1String("custom")) {
297  qWarning("XML error: Top tag was %s instead of the expected custom", top.tagName().toLatin1().data());
298  return;
299  }
300 
301  for (QDomNode n = top.firstChild(); !n.isNull(); n = n.nextSibling()) {
302  if (n.isElement()) {
303  QDomElement e = n.toElement();
304  custom.insert(e.tagName(), e.text());
305  } else {
306  qCDebug(AKONADINOTES_LOG) << "Node is not an element";
307  Q_ASSERT(false);
308  }
309  }
310 }
311 
312 KMime::Content *NoteMessageWrapper::NoteMessageWrapperPrivate::createAttachmentPart(const Attachment &a) const
313 {
314  KMime::Content *content = new KMime::Content();
315  auto header = new KMime::Headers::Generic(X_NOTES_CONTENTTYPE_HEADER);
316  header->fromUnicodeString(CONTENT_TYPE_ATTACHMENT, ENCODING);
317  content->appendHeader(header);
318  if (a.url().isValid()) {
319  header = new KMime::Headers::Generic(X_NOTES_URL_HEADER);
320  header->fromUnicodeString(a.url().toString(), ENCODING);
321  content->appendHeader(header);
322  } else {
323  content->setBody(a.data());
324  }
325  content->contentType()->setMimeType(a.mimetype().toLatin1());
326  if (!a.label().isEmpty()) {
327  header = new KMime::Headers::Generic(X_NOTES_LABEL_HEADER);
328  header->fromUnicodeString(a.label(), ENCODING);
329  content->appendHeader(header);
330  }
331  content->contentTransferEncoding()->setEncoding(KMime::Headers::CEbase64);
332  if (a.dataBase64Encoded()) {
333  content->contentTransferEncoding()->setDecoded(false);
334  }
335  content->contentDisposition()->setDisposition(KMime::Headers::CDattachment);
336  content->contentDisposition()->setFilename(QStringLiteral("attachment"));
337  if (!a.contentID().isEmpty()) {
338  content->contentID()->setIdentifier(a.contentID().toLatin1());
339  }
340  return content;
341 }
342 
343 void NoteMessageWrapper::NoteMessageWrapperPrivate::parseAttachmentPart(KMime::Content *part)
344 {
345  QString label;
346  if (KMime::Headers::Base *labelHeader = part->headerByType(X_NOTES_LABEL_HEADER)) {
347  label = labelHeader->asUnicodeString();
348  }
349  if (KMime::Headers::Base *header = part->headerByType(X_NOTES_URL_HEADER)) {
350  Attachment attachment(QUrl(header->asUnicodeString()), QLatin1String(part->contentType()->mimeType()));
351  attachment.setLabel(label);
352  attachment.setContentID(QString::fromLatin1(part->contentID()->identifier()));
353  attachments.append(attachment);
354  } else {
355  Attachment attachment(part->decodedContent(), QLatin1String(part->contentType()->mimeType()));
356  attachment.setLabel(label);
357  attachment.setContentID(QString::fromLatin1(part->contentID()->identifier()));
358  attachments.append(attachment);
359  }
360 }
361 
362 NoteMessageWrapper::NoteMessageWrapper()
363  : d_ptr(new NoteMessageWrapperPrivate())
364 {
365 }
366 
367 NoteMessageWrapper::NoteMessageWrapper(const KMime::MessagePtr &msg)
368  : d_ptr(new NoteMessageWrapperPrivate(msg))
369 {
370 }
371 
372 NoteMessageWrapper::~NoteMessageWrapper()
373 {
374  delete d_ptr;
375 }
376 
378 {
379  Q_D(const NoteMessageWrapper);
381 
382  QString title = i18nc("The default name for new notes.", "New Note");
383  if (!d->title.isEmpty()) {
384  title = d->title;
385  }
386  // Need a non-empty body part so that the serializer regards this as a valid message.
387  QString text = QStringLiteral(" ");
388  if (!d->text.isEmpty()) {
389  text = d->text;
390  }
391 
392  QDateTime creationDate = QDateTime::currentDateTime();
393  if (d->creationDate.isValid()) {
394  creationDate = d->creationDate;
395  }
396 
397  QDateTime lastModifiedDate = QDateTime::currentDateTime();
398  if (d->lastModifiedDate.isValid()) {
399  lastModifiedDate = d->lastModifiedDate;
400  }
401 
402  QString uid;
403  if (!d->uid.isEmpty()) {
404  uid = d->uid;
405  } else {
406  uid = QUuid::createUuid().toString().mid(1, 36);
407  }
408 
409  msg->subject(true)->fromUnicodeString(title, ENCODING);
410  msg->date(true)->setDateTime(creationDate);
411  msg->from(true)->fromUnicodeString(d->from, ENCODING);
412  const QString formatDate = QLocale::c().toString(lastModifiedDate, QStringLiteral("ddd, ")) + lastModifiedDate.toString(Qt::RFC2822Date);
413 
414  auto header = new KMime::Headers::Generic(X_NOTES_LASTMODIFIED_HEADER);
415  header->fromUnicodeString(formatDate, ENCODING);
416  msg->appendHeader(header);
417  header = new KMime::Headers::Generic(X_NOTES_UID_HEADER);
418  header->fromUnicodeString(uid, ENCODING);
419  msg->appendHeader(header);
420 
421  QString classification = CLASSIFICATION_PUBLIC;
422  switch (d->classification) {
423  case Private:
424  classification = CLASSIFICATION_PRIVATE;
425  break;
426  case Confidential:
427  classification = CLASSIFICATION_CONFIDENTIAL;
428  break;
429  default:
430  // do nothing
431  break;
432  }
433  header = new KMime::Headers::Generic(X_NOTES_CLASSIFICATION_HEADER);
434  header->fromUnicodeString(classification, ENCODING);
435  msg->appendHeader(header);
436 
437  for (const Attachment &a : std::as_const(d->attachments)) {
438  msg->addContent(d->createAttachmentPart(a));
439  }
440 
441  if (!d->custom.isEmpty()) {
442  msg->addContent(d->createCustomPart());
443  }
444 
445  msg->mainBodyPart()->contentType(true)->setCharset(ENCODING);
446  msg->mainBodyPart()->fromUnicodeString(text);
447  msg->mainBodyPart()->contentType(true)->setMimeType(d->textFormat == Qt::RichText ? "text/html" : "text/plain");
448 
449  msg->assemble();
450  return msg;
451 }
452 
454 {
455  Q_D(NoteMessageWrapper);
456  d->uid = uid;
457 }
458 
460 {
461  Q_D(const NoteMessageWrapper);
462  return d->uid;
463 }
464 
465 void NoteMessageWrapper::setClassification(NoteMessageWrapper::Classification classification)
466 {
467  Q_D(NoteMessageWrapper);
468  d->classification = classification;
469 }
470 
471 NoteMessageWrapper::Classification NoteMessageWrapper::classification() const
472 {
473  Q_D(const NoteMessageWrapper);
474  return d->classification;
475 }
476 
478 {
479  Q_D(NoteMessageWrapper);
480  d->lastModifiedDate = lastModifiedDate;
481 }
482 
484 {
485  Q_D(const NoteMessageWrapper);
486  return d->lastModifiedDate;
487 }
488 
490 {
491  Q_D(NoteMessageWrapper);
492  d->creationDate = creationDate;
493 }
494 
496 {
497  Q_D(const NoteMessageWrapper);
498  return d->creationDate;
499 }
500 
502 {
503  Q_D(NoteMessageWrapper);
504  d->from = from;
505 }
506 
508 {
509  Q_D(const NoteMessageWrapper);
510  return d->from;
511 }
512 
514 {
515  Q_D(NoteMessageWrapper);
516  d->title = title;
517 }
518 
520 {
521  Q_D(const NoteMessageWrapper);
522  return d->title;
523 }
524 
526 {
527  Q_D(NoteMessageWrapper);
528  d->text = text;
529  d->textFormat = format;
530 }
531 
533 {
534  Q_D(const NoteMessageWrapper);
535  return d->text;
536 }
537 
539 {
540  Q_D(const NoteMessageWrapper);
541  return d->textFormat;
542 }
543 
545 {
546  Q_D(const NoteMessageWrapper);
547  if (d->textFormat == Qt::PlainText) {
548  return d->text;
549  }
550 
551  // From cleanHtml in kdepimlibs/kcalutils/incidenceformatter.cpp
552  const QRegularExpression rx(QStringLiteral("<body[^>]*>(.*)</body>"), QRegularExpression::CaseInsensitiveOption);
553  QString body = rx.match(d->text).captured(1);
554 
555  return body.remove(QRegularExpression(QStringLiteral("<[^>]*>"))).trimmed().toHtmlEscaped();
556 }
557 
559 {
560  Q_D(NoteMessageWrapper);
561  return d->attachments;
562 }
563 
565 {
566  Q_D(NoteMessageWrapper);
567  return d->custom;
568 }
569 
570 QString noteIconName()
571 {
572  return QStringLiteral("text-plain");
573 }
574 
575 QString noteMimeType()
576 {
577  return QStringLiteral("text/x-vnd.akonadi.note");
578 }
579 
580 } // End Namespace
581 } // End Namespace
QString captured(int nth) const const
QString toString(Qt::DateFormat format) const const
void setFrom(const QString &from)
Set the origin (creator) of the note (stored in the mime header) This is usually the application crea...
Definition: noteutils.cpp:501
Classification classification() const
Returns the classification of the note.
Definition: noteutils.cpp:471
QRegularExpressionMatch match(const QString &subject, int offset, QRegularExpression::MatchType matchType, QRegularExpression::MatchOptions matchOptions) const const
QDomProcessingInstruction createProcessingInstruction(const QString &target, const QString &data)
bool dataBase64Encoded() const
Returns true if data is already base64-encoded.
Definition: noteutils.cpp:127
QString text() const
Returns the text of the note.
Definition: noteutils.cpp:532
QDomNode appendChild(const QDomNode &newChild)
Qt::TextFormat textFormat() const
Definition: noteutils.cpp:538
QString toString(qlonglong i) const const
KMime::MessagePtr message() const
Assemble a KMime message with the given values.
Definition: noteutils.cpp:377
QString uid() const
Returns the uid of the note.
Definition: noteutils.cpp:459
void setMimeType(const QByteArray &mimeType)
void setLastModifiedDate(const QDateTime &lastModifiedDate)
Set the lastModified-date of the note.
Definition: noteutils.cpp:477
void setIdentifier(const QByteArray &id)
QString toString(int indent) const const
RFC2822Date
TextFormat
QVector< Attachment > & attachments()
Returns a reference to the list of attachments of the note.
Definition: noteutils.cpp:558
void setTitle(const QString &title)
Set the title of the note.
Definition: noteutils.cpp:513
QByteArray data() const
Returns the date for inline attachments.
Definition: noteutils.cpp:115
void setText(const QString &text, Qt::TextFormat format=Qt::PlainText)
Set the text of the note.
Definition: noteutils.cpp:525
QByteArray body() const
QDomElement documentElement() const const
QDateTime creationDate() const
Returns the creation date of the note.
Definition: noteutils.cpp:495
Headers::ContentDisposition * contentDisposition(bool create=true)
QString & remove(int position, int n)
QByteArray mimeType() const
T * data() const const
QString toString(QUrl::FormattingOptions options) const const
void setDisposition(contentDisposition disp)
void setClassification(Classification)
Set the classification of the note.
Definition: noteutils.cpp:465
QDomElement toElement() const const
void setUid(const QString &uid)
Set the uid of the note.
Definition: noteutils.cpp:453
QLocale c()
QByteArray decodedContent()
QDomDocument ownerDocument() const const
QString text() const const
QUrl url() const
Returns the url for url-only attachments.
Definition: noteutils.cpp:109
QString i18nc(const char *context, const char *text, const TYPE &arg...)
void setAttribute(const QString &name, const QString &value)
bool isEmpty() const const
QString trimmed() const const
void setBody(const QByteArray &body)
void setLabel(const QString &label)
Sets the label to be presented to the user.
Definition: noteutils.cpp:151
Headers::ContentID * contentID(bool create=true)
QString contentID() const
Returns the unique identifier for inline attachment.
Definition: noteutils.cpp:139
Headers::ContentTransferEncoding * contentTransferEncoding(bool create=true)
QMap::iterator begin()
QString from() const
Returns the origin (creator) of the note.
Definition: noteutils.cpp:507
Headers::ContentType * contentType(bool create=true)
Attachment()
Create an attachment referencing a url only.
Definition: noteutils.cpp:68
QDomText createTextNode(const QString &value)
QString toHtmlEscaped() const const
An attachment for a note.
Definition: noteutils.h:43
Headers::Base * headerByType(const char *type) const
QDateTime fromString(const QString &string, Qt::DateFormat format)
QString title() const
Returns the title of the note.
Definition: noteutils.cpp:519
bool isNull() const const
QMap< QString, QString > & custom()
Returns a reference to the custom-value map.
Definition: noteutils.cpp:564
QString label() const
Returns the label of the attachment.
Definition: noteutils.cpp:157
void setCreationDate(const QDateTime &creationDate)
Set the creation date of the note (stored in the mime header)
Definition: noteutils.cpp:489
bool isValid() const const
QDateTime currentDateTime()
QDomNode firstChild() const const
QByteArray toLatin1() const const
QString mid(int position, int n) const const
void setDecoded(bool isDecoded=true)
QDateTime lastModifiedDate() const
Returns the lastModified-date of the note.
Definition: noteutils.cpp:483
void setContentID(const QString &contentID)
Sets the unique identifier of the attachment.
Definition: noteutils.cpp:133
char * data()
void setEncoding(contentEncoding e)
void appendHeader(Headers::Base *h)
QString fromLatin1(const char *str, int size)
void setDataBase64Encoded(bool encoded)
Set this to true if inline data provided via ctor is already base64 encoded.
Definition: noteutils.cpp:121
QString tagName() const const
QString mimetype() const
Returns the mimetype.
Definition: noteutils.cpp:145
QDomElement createElement(const QString &tagName)
QString toString() const const
QUuid createUuid()
void setFilename(const QString &filename)
bool setContent(const QByteArray &data, bool namespaceProcessing, QString *errorMsg, int *errorLine, int *errorColumn)
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Tue Aug 3 2021 23:12:45 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.