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

KDE's Doxygen guidelines are available online.