Messagelib

stringutil.cpp
1 /*
2  SPDX-FileCopyrightText: 2016-2021 Laurent Montel <[email protected]>
3  SPDX-FileCopyrightText: 2009 Thomas McGuire <[email protected]>
4 
5  SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
6 */
7 #include "stringutil.h"
8 
9 #include "MessageCore/MessageCoreSettings"
10 #include "config-enterprise.h"
11 
12 #include <KEmailAddress>
13 #include <KLocalizedString>
14 #include <KMime/HeaderParsing>
15 #include <KMime/Headers>
16 #include <KMime/Message>
17 
18 #include "messagecore_debug.h"
19 #include <KUser>
20 
21 #include <KCodecs>
22 #include <KIdentityManagement/Identity>
23 #include <KIdentityManagement/IdentityManager>
24 #include <KPIMTextEdit/TextUtils>
25 #include <QHostInfo>
26 #include <QRegularExpression>
27 #include <QUrlQuery>
28 
29 using namespace KMime;
30 using namespace KMime::Types;
31 using namespace KMime::HeaderParsing;
32 
33 namespace MessageCore
34 {
35 namespace StringUtil
36 {
37 // Removes trailing spaces and tabs at the end of the line
38 static void removeTrailingSpace(QString &line)
39 {
40  int i = line.length() - 1;
41  while ((i >= 0) && ((line[i] == QLatin1Char(' ')) || (line[i] == QLatin1Char('\t')))) {
42  i--;
43  }
44  line.truncate(i + 1);
45 }
46 
47 // Splits the line off in two parts: The quote prefixes and the actual text of the line.
48 // For example, for the string "> > > Hello", it would be split up in "> > > " as the quote
49 // prefix, and "Hello" as the actual text.
50 // The actual text is written back to the "line" parameter, and the quote prefix is returned.
51 static QString splitLine(QString &line)
52 {
53  removeTrailingSpace(line);
54  int i = 0;
55  int startOfActualText = -1;
56 
57  // TODO: Replace tabs with spaces first.
58 
59  // Loop through the chars in the line to find the place where the quote prefix stops
60  const int lineLength(line.length());
61  while (i < lineLength) {
62  const QChar c = line[i];
63  const bool isAllowedQuoteChar =
64  (c == QLatin1Char('>')) || (c == QLatin1Char(':')) || (c == QLatin1Char('|')) || (c == QLatin1Char(' ')) || (c == QLatin1Char('\t'));
65  if (isAllowedQuoteChar) {
66  startOfActualText = i + 1;
67  } else {
68  break;
69  }
70  ++i;
71  }
72 
73  // If the quote prefix only consists of whitespace, don't consider it as a quote prefix at all
74  if (line.left(startOfActualText).trimmed().isEmpty()) {
75  startOfActualText = 0;
76  }
77 
78  // No quote prefix there -> nothing to do
79  if (startOfActualText <= 0) {
80  return {};
81  }
82 
83  // Entire line consists of only the quote prefix
84  if (i == line.length()) {
85  const QString quotePrefix = line.left(startOfActualText);
86  line.clear();
87  return quotePrefix;
88  }
89 
90  // Line contains both the quote prefix and the actual text, really split it up now
91  const QString quotePrefix = line.left(startOfActualText);
92  line = line.mid(startOfActualText);
93 
94  return quotePrefix;
95 }
96 
97 // Writes all lines/text parts contained in the "textParts" list to the output text, "msg".
98 // Quote characters are added in front of each line, and no line is longer than
99 // maxLength.
100 //
101 // Although the lines in textParts are considered separate lines, they can actually be run
102 // together into a single line in some cases. This is basically the main difference to flowText().
103 //
104 // Example:
105 // textParts = "Hello World, this is a test.", "Really"
106 // indent = ">"
107 // maxLength = 20
108 // Result: "> Hello World, this\n
109 // > is a test. Really"
110 // Notice how in this example, the text line "Really" is no longer a separate line, it was run
111 // together with a previously broken line.
112 //
113 // "textParts" is cleared upon return.
114 static bool flushPart(QString &msg, QStringList &textParts, const QString &indent, int maxLength)
115 {
116  if (maxLength < 20) {
117  maxLength = 20;
118  }
119 
120  // Remove empty lines at end of quote
121  while (!textParts.isEmpty() && textParts.last().isEmpty()) {
122  textParts.removeLast();
123  }
124 
125  QString text;
126 
127  for (const QString &line : textParts) {
128  // An empty line in the input means that an empty line should be in the output as well.
129  // Therefore, we write all of our text so far to the msg.
130  if (line.isEmpty()) {
131  if (!text.isEmpty()) {
132  msg += KPIMTextEdit::TextUtils::flowText(text, indent, maxLength) + QLatin1Char('\n');
133  }
134  msg += indent + QLatin1Char('\n');
135  } else {
136  if (text.isEmpty()) {
137  text = line;
138  } else {
139  text += QLatin1Char(' ') + line.trimmed();
140  }
141  // If the line doesn't need to be wrapped at all, just write it out as-is.
142  // When a line exceeds the maximum length and therefore needs to be broken, this statement
143  // if false, and therefore we keep adding lines to our text, so they get ran together in the
144  // next flowText call, as "text" contains several text parts/lines then.
145  if ((text.length() < maxLength) || (line.length() < (maxLength - 10))) {
146  msg += KPIMTextEdit::TextUtils::flowText(text, indent, maxLength) + QLatin1Char('\n');
147  }
148  }
149  }
150 
151  // Write out pending text to the msg
152  if (!text.isEmpty()) {
153  msg += KPIMTextEdit::TextUtils::flowText(text, indent, maxLength);
154  }
155 
156  const bool appendEmptyLine = !textParts.isEmpty();
157  textParts.clear();
158 
159  return appendEmptyLine;
160 }
161 
163 {
165  if (url.scheme() != QLatin1String("mailto")) {
166  return values;
167  }
168  QString str = url.toString();
169  QStringList toStr;
170  int i = 0;
171 
172  // String can be encoded.
173  str = KCodecs::decodeRFC2047String(str);
174  // Bug 427697
175  str.replace(QStringLiteral("&#38;"), QStringLiteral("&"));
176  const QUrl newUrl = QUrl::fromUserInput(str);
177 
178  int indexTo = -1;
179  // Workaround line with # see bug 406208
180  const int indexOf = str.indexOf(QLatin1Char('?'));
181  if (indexOf != -1) {
182  str.remove(0, indexOf + 1);
183  QUrlQuery query(str);
184  const auto listQuery = query.queryItems(QUrl::FullyDecoded);
185  for (const auto &queryItem : listQuery) {
186  if (queryItem.first == QLatin1String("to")) {
187  toStr << queryItem.second;
188  indexTo = i;
189  } else {
190  if (queryItem.second.isEmpty()) {
191  // Bug 206269 => A%26B => encoded '&'
192  if (i >= 1) {
193  values[i - 1].second = values[i - 1].second + QStringLiteral("&") + queryItem.first;
194  }
195  } else {
196  QPair<QString, QString> pairElement;
197  pairElement.first = queryItem.first.toLower();
198  pairElement.second = queryItem.second;
199  values.append(pairElement);
200  i++;
201  }
202  }
203  }
204  }
206  if (!toStr.isEmpty()) {
207  to << toStr;
208  }
209  const QString fullTo = to.join(QLatin1String(", "));
210  if (!fullTo.isEmpty()) {
211  QPair<QString, QString> pairElement;
212  pairElement.first = QStringLiteral("to");
213  pairElement.second = fullTo;
214  if (indexTo != -1) {
215  values.insert(indexTo, pairElement);
216  } else {
217  values.prepend(pairElement);
218  }
219  }
220  return values;
221 }
222 
224 {
225  // Following RFC 3676, only > before --
226  // I prefer to not delete a SB instead of delete good mail content.
227  static const QRegularExpression sbDelimiterSearch(QStringLiteral("(^|\n)[> ]*-- \n"));
228  // The regular expression to look for prefix change
229  static const QRegularExpression commonReplySearch(QStringLiteral("^[ ]*>"));
230 
231  QString res = msg;
232  int posDeletingStart = 1; // to start looking at 0
233 
234  // While there are SB delimiters (start looking just before the deleted SB)
235  while ((posDeletingStart = res.indexOf(sbDelimiterSearch, posDeletingStart - 1)) >= 0) {
236  QString prefix; // the current prefix
237  QString line; // the line to check if is part of the SB
238  int posNewLine = -1;
239 
240  // Look for the SB beginning
241  int posSignatureBlock = res.indexOf(QLatin1Char('-'), posDeletingStart);
242  // The prefix before "-- "$
243  if (res.at(posDeletingStart) == QLatin1Char('\n')) {
244  ++posDeletingStart;
245  }
246 
247  prefix = res.mid(posDeletingStart, posSignatureBlock - posDeletingStart);
248  posNewLine = res.indexOf(QLatin1Char('\n'), posSignatureBlock) + 1;
249 
250  // now go to the end of the SB
251  while (posNewLine < res.size() && posNewLine > 0) {
252  // handle the undefined case for mid ( x , -n ) where n>1
253  int nextPosNewLine = res.indexOf(QLatin1Char('\n'), posNewLine);
254 
255  if (nextPosNewLine < 0) {
256  nextPosNewLine = posNewLine - 1;
257  }
258 
259  line = res.mid(posNewLine, nextPosNewLine - posNewLine);
260 
261  // check when the SB ends:
262  // * does not starts with prefix or
263  // * starts with prefix+(any substring of prefix)
264  if ((prefix.isEmpty() && line.indexOf(commonReplySearch) < 0)
265  || (!prefix.isEmpty() && line.startsWith(prefix) && line.mid(prefix.size()).indexOf(commonReplySearch) < 0)) {
266  posNewLine = res.indexOf(QLatin1Char('\n'), posNewLine) + 1;
267  } else {
268  break; // end of the SB
269  }
270  }
271 
272  // remove the SB or truncate when is the last SB
273  if (posNewLine > 0) {
274  res.remove(posDeletingStart, posNewLine - posDeletingStart);
275  } else {
276  res.truncate(posDeletingStart);
277  }
278  }
279 
280  return res;
281 }
282 
284 {
285  AddressList result;
286  const char *begin = text.begin();
287  if (!begin) {
288  return result;
289  }
290 
291  const char *const end = text.begin() + text.length();
292 
293  if (!parseAddressList(begin, end, result)) {
294  qCWarning(MESSAGECORE_LOG) << "Error in address splitting: parseAddressList returned false!" << text;
295  }
296 
297  return result;
298 }
299 
300 QString generateMessageId(const QString &address, const QString &suffix)
301 {
302  const QDateTime dateTime = QDateTime::currentDateTime();
303 
304  QString msgIdStr = QLatin1Char('<') + dateTime.toString(QStringLiteral("yyyyMMddhhmm.sszzz"));
305 
306  if (!suffix.isEmpty()) {
307  msgIdStr += QLatin1Char('@') + suffix;
308  } else {
309  msgIdStr += QLatin1Char('.') + KEmailAddress::toIdn(address);
310  }
311 
312  msgIdStr += QLatin1Char('>');
313 
314  return msgIdStr;
315 }
316 
317 QString quoteHtmlChars(const QString &str, bool removeLineBreaks)
318 {
319  QString result;
320 
321  int strLength(str.length());
322  result.reserve(6 * strLength); // maximal possible length
323  for (int i = 0; i < strLength; ++i) {
324  switch (str[i].toLatin1()) {
325  case '<':
326  result += QLatin1String("&lt;");
327  break;
328  case '>':
329  result += QLatin1String("&gt;");
330  break;
331  case '&':
332  result += QLatin1String("&amp;");
333  break;
334  case '"':
335  result += QLatin1String("&quot;");
336  break;
337  case '\n':
338  if (!removeLineBreaks) {
339  result += QLatin1String("<br>");
340  }
341  break;
342  case '\r':
343  // ignore CR
344  break;
345  default:
346  result += str[i];
347  }
348  }
349 
350  result.squeeze();
351  return result;
352 }
353 
354 void removePrivateHeaderFields(const KMime::Message::Ptr &message, bool cleanUpHeader)
355 {
356  message->removeHeader("Status");
357  message->removeHeader("X-Status");
358  message->removeHeader("X-KMail-EncryptionState");
359  message->removeHeader("X-KMail-SignatureState");
360  message->removeHeader("X-KMail-Redirect-From");
361  message->removeHeader("X-KMail-Link-Message");
362  message->removeHeader("X-KMail-Link-Type");
363  message->removeHeader("X-KMail-QuotePrefix");
364  message->removeHeader("X-KMail-CursorPos");
365  message->removeHeader("X-KMail-Templates");
366  message->removeHeader("X-KMail-Drafts");
367  message->removeHeader("X-KMail-UnExpanded-To");
368  message->removeHeader("X-KMail-UnExpanded-CC");
369  message->removeHeader("X-KMail-UnExpanded-BCC");
370  message->removeHeader("X-KMail-UnExpanded-Reply-To");
371  message->removeHeader("X-KMail-FccDisabled");
372 
373  if (cleanUpHeader) {
374  message->removeHeader("X-KMail-Fcc");
375  message->removeHeader("X-KMail-Transport");
376  message->removeHeader("X-KMail-Identity");
377  message->removeHeader("X-KMail-Transport-Name");
378  message->removeHeader("X-KMail-Identity-Name");
379  message->removeHeader("X-KMail-Dictionary");
380  }
381 }
382 
384 {
385  KMime::Message::Ptr message(new KMime::Message);
386  message->setContent(originalMessage->encodedContent());
387 
388  removePrivateHeaderFields(message);
389  message->removeHeader<KMime::Headers::Bcc>();
390 
391  return message->encodedContent();
392 }
393 
395 {
396  KMime::Message::Ptr message(new KMime::Message);
397  message->setContent(originalMessage->encodedContent());
398 
399  removePrivateHeaderFields(message);
400  message->removeHeader<KMime::Headers::Bcc>();
401 
402  return message->head();
403 }
404 
405 QString emailAddrAsAnchor(const KMime::Types::Mailbox::List &mailboxList,
406  Display display,
407  const QString &cssStyle,
408  Link link,
409  AddressMode expandable,
410  const QString &fieldName,
411  int collapseNumber)
412 {
413  QString result;
414  int numberAddresses = 0;
415  bool expandableInserted = false;
417 
418  const QString i18nMe = i18nc("signal that this email is defined in my identity", "Me");
419  const bool onlyOneIdentity = (im->identities().count() == 1);
420  for (const KMime::Types::Mailbox &mailbox : mailboxList) {
421  const QString prettyAddressStr = mailbox.prettyAddress();
422  if (!prettyAddressStr.isEmpty()) {
423  numberAddresses++;
424  if (expandable == ExpandableAddresses && !expandableInserted && numberAddresses > collapseNumber) {
425  const QString actualListAddress = result;
426  QString shortListAddress = actualListAddress;
427  if (link == ShowLink) {
428  shortListAddress.truncate(result.length() - 2);
429  }
430  result = QStringLiteral("<span><input type=\"checkbox\" class=\"addresslist_checkbox\" id=\"%1\" checked=\"checked\"/><span class=\"short%1\">")
431  .arg(fieldName)
432  + shortListAddress;
433  result += QStringLiteral("<label class=\"addresslist_label_short\" for=\"%1\"></label></span>").arg(fieldName);
434  expandableInserted = true;
435  result += QStringLiteral("<span class=\"full%1\">").arg(fieldName) + actualListAddress;
436  }
437 
438  if (link == ShowLink) {
439  result += QLatin1String("<a href=\"mailto:")
442  + QLatin1String("\" ") + cssStyle + QLatin1Char('>');
443  }
444  const bool foundMe = onlyOneIdentity && (im->identityForAddress(prettyAddressStr) != KIdentityManagement::Identity::null());
445 
446  if (display == DisplayNameOnly) {
447  if (!mailbox.name().isEmpty()) { // Fallback to the email address when the name is not set.
448  result += foundMe ? i18nMe : quoteHtmlChars(mailbox.name(), true);
449  } else {
450  result += foundMe ? i18nMe : quoteHtmlChars(prettyAddressStr, true);
451  }
452  } else {
453  result += foundMe ? i18nMe : quoteHtmlChars(mailbox.prettyAddress(KMime::Types::Mailbox::QuoteWhenNecessary), true);
454  }
455  if (link == ShowLink) {
456  result += QLatin1String("</a>, ");
457  }
458  }
459  }
460 
461  if (link == ShowLink) {
462  result.chop(2);
463  }
464 
465  if (expandableInserted) {
466  result += QStringLiteral("<label class=\"addresslist_label_full\" for=\"%1\"></label></span></span>").arg(fieldName);
467  }
468  return result;
469 }
470 
471 QString emailAddrAsAnchor(const KMime::Headers::Generics::MailboxList *mailboxList,
472  Display display,
473  const QString &cssStyle,
474  Link link,
475  AddressMode expandable,
476  const QString &fieldName,
477  int collapseNumber)
478 {
479  Q_ASSERT(mailboxList);
480  return emailAddrAsAnchor(mailboxList->mailboxes(), display, cssStyle, link, expandable, fieldName, collapseNumber);
481 }
482 
483 QString emailAddrAsAnchor(const KMime::Headers::Generics::AddressList *addressList,
484  Display display,
485  const QString &cssStyle,
486  Link link,
487  AddressMode expandable,
488  const QString &fieldName,
489  int collapseNumber)
490 {
491  Q_ASSERT(addressList);
492  return emailAddrAsAnchor(addressList->mailboxes(), display, cssStyle, link, expandable, fieldName, collapseNumber);
493 }
494 
495 bool addressIsInAddressList(const QString &address, const QStringList &addresses)
496 {
497  const QString addrSpec = KEmailAddress::extractEmailAddress(address);
498 
499  QStringList::ConstIterator end(addresses.constEnd());
500  for (QStringList::ConstIterator it = addresses.constBegin(); it != end; ++it) {
501  if (qstricmp(addrSpec.toUtf8().data(), KEmailAddress::extractEmailAddress(*it).toUtf8().data()) == 0) {
502  return true;
503  }
504  }
505 
506  return false;
507 }
508 
510 {
511  if (loginName.isEmpty()) {
512  return {};
513  }
514 
515  QString address = loginName;
516  address += QLatin1Char('@');
517  address += QHostInfo::localHostName();
518 
519  // try to determine the real name
520  const KUser user(loginName);
521  if (user.isValid()) {
522  const QString fullName = user.property(KUser::FullName).toString();
523  address = KEmailAddress::quoteNameIfNecessary(fullName) + QLatin1String(" <") + address + QLatin1Char('>');
524  }
525 
526  return address;
527 }
528 
529 QString smartQuote(const QString &msg, int maxLineLength)
530 {
531  // The algorithm here is as follows:
532  // We split up the incoming msg into lines, and then iterate over each line.
533  // We keep adding lines with the same indent ( = quote prefix, e.g. "> " ) to a
534  // "textParts" list. So the textParts list contains only lines with the same quote
535  // prefix.
536  //
537  // When all lines with the same indent are collected in "textParts", we write those out
538  // to the result by calling flushPart(), which does all the nice formatting for us.
539 
540  QStringList textParts;
541  QString oldIndent;
542  bool firstPart = true;
543  QString result;
544 
545  int lineStart = 0;
546  int lineEnd = msg.indexOf(QLatin1Char('\n'));
547  bool needToContinue = true;
548  for (; needToContinue; lineStart = lineEnd + 1, lineEnd = msg.indexOf(QLatin1Char('\n'), lineStart)) {
549  QString line;
550  if (lineEnd == -1) {
551  if (lineStart == 0) {
552  line = msg;
553  needToContinue = false;
554  } else if (lineStart != msg.length()) {
555  line = msg.mid(lineStart, msg.length() - lineStart);
556  needToContinue = false;
557  } else {
558  needToContinue = false;
559  }
560  } else {
561  line = msg.mid(lineStart, lineEnd - lineStart);
562  }
563  // Split off the indent from the line
564  const QString indent = splitLine(line);
565 
566  if (line.isEmpty()) {
567  if (!firstPart) {
568  textParts.append(QString());
569  }
570  continue;
571  }
572 
573  if (firstPart) {
574  oldIndent = indent;
575  firstPart = false;
576  }
577 
578  // The indent changed, that means we have to write everything contained in textParts to the
579  // result, which we do by calling flushPart().
580  if (oldIndent != indent) {
581  // Check if the last non-blank line is a "From" line. A from line is the line containing the
582  // attribution to a quote, e.g. "Yesterday, you wrote:". We'll just check for the last colon
583  // here, to simply things.
584  // If there is a From line, remove it from the textParts to that flushPart won't break it.
585  // We'll manually add it to the result afterwards.
586  QString fromLine;
587  if (!textParts.isEmpty()) {
588  for (int i = textParts.count() - 1; i >= 0; i--) {
589  // Check if we have found the From line
590  const QString textPartElement(textParts[i]);
591  if (textPartElement.endsWith(QLatin1Char(':'))) {
592  fromLine = oldIndent + textPartElement + QLatin1Char('\n');
593  textParts.removeAt(i);
594  break;
595  }
596 
597  // Abort on first non-empty line
598  if (!textPartElement.trimmed().isEmpty()) {
599  break;
600  }
601  }
602  }
603 
604  // Write out all lines with the same indent using flushPart(). The textParts list
605  // is cleared for us.
606  if (flushPart(result, textParts, oldIndent, maxLineLength)) {
607  if (oldIndent.length() > indent.length()) {
608  result += indent + QLatin1Char('\n');
609  } else {
610  result += oldIndent + QLatin1Char('\n');
611  }
612  }
613 
614  if (!fromLine.isEmpty()) {
615  result += fromLine;
616  }
617 
618  oldIndent = indent;
619  }
620 
621  textParts.append(line);
622  }
623 
624  // Write out anything still pending
625  flushPart(result, textParts, oldIndent, maxLineLength);
626 
627  // Remove superfluous newline which was appended in flowText
628  if (!result.isEmpty() && result.endsWith(QLatin1Char('\n'))) {
629  result.chop(1);
630  }
631 
632  return result;
633 }
634 
635 QString formatQuotePrefix(const QString &wildString, const QString &fromDisplayString)
636 {
637  QString result;
638 
639  if (wildString.isEmpty()) {
640  return wildString;
641  }
642 
643  int strLength(wildString.length());
644  for (int i = 0; i < strLength;) {
645  QChar ch = wildString[i++];
646  if (ch == QLatin1Char('%') && i < strLength) {
647  ch = wildString[i++];
648  switch (ch.toLatin1()) {
649  case 'f': { // sender's initials
650  if (fromDisplayString.isEmpty()) {
651  break;
652  }
653 
654  int j = 0;
655  const int strLengthFromDisplayString(fromDisplayString.length());
656  for (; j < strLengthFromDisplayString && fromDisplayString[j] > QLatin1Char(' '); ++j) { }
657  for (; j < strLengthFromDisplayString && fromDisplayString[j] <= QLatin1Char(' '); ++j) { }
658  result += fromDisplayString[0];
659  if (j < strLengthFromDisplayString && fromDisplayString[j] > QLatin1Char(' ')) {
660  result += fromDisplayString[j];
661  } else if (strLengthFromDisplayString > 1) {
662  if (fromDisplayString[1] > QLatin1Char(' ')) {
663  result += fromDisplayString[1];
664  }
665  }
666  break;
667  }
668  case '_':
669  result += QLatin1Char(' ');
670  break;
671  case '%':
672  result += QLatin1Char('%');
673  break;
674  default:
675  result += QLatin1Char('%');
676  result += ch;
677  break;
678  }
679  } else {
680  result += ch;
681  }
682  }
683  return result;
684 }
685 
687 {
688  QString fileName = name.trimmed();
689 
690  // We need to replace colons with underscores since those cause problems with
691  // KFileDialog (bug in KFileDialog though) and also on Windows filesystems.
692  // We also look at the special case of ": ", since converting that to "_ "
693  // would look strange, simply "_" looks better.
694  // https://issues.kolab.org/issue3805
695  fileName.replace(QLatin1String(": "), QStringLiteral("_"));
696  // replace all ':' with '_' because ':' isn't allowed on FAT volumes
697  fileName.replace(QLatin1Char(':'), QLatin1Char('_'));
698  // better not use a dir-delimiter in a filename
699  fileName.replace(QLatin1Char('/'), QLatin1Char('_'));
700  fileName.replace(QLatin1Char('\\'), QLatin1Char('_'));
701 
702 #ifdef KDEPIM_ENTERPRISE_BUILD
703  // replace all '.' with '_', not just at the start of the filename
704  // but don't replace the last '.' before the file extension.
705  int i = fileName.lastIndexOf(QLatin1Char('.'));
706  if (i != -1) {
707  i = fileName.lastIndexOf(QLatin1Char('.'), i - 1);
708  }
709 
710  while (i != -1) {
711  fileName.replace(i, 1, QLatin1Char('_'));
712  i = fileName.lastIndexOf(QLatin1Char('.'), i - 1);
713  }
714 #endif
715 
716  // replace all '~' with '_', not just leading '~' either.
717  fileName.replace(QLatin1Char('~'), QLatin1Char('_'));
718 
719  return fileName;
720 }
721 
723 {
724  return cleanSubject(msg,
725  MessageCore::MessageCoreSettings::self()->replyPrefixes() + MessageCore::MessageCoreSettings::self()->forwardPrefixes(),
726  true,
727  QString())
728  .trimmed();
729 }
730 
731 QString cleanSubject(KMime::Message *msg, const QStringList &prefixRegExps, bool replace, const QString &newPrefix)
732 {
733  return replacePrefixes(msg->subject()->asUnicodeString(), prefixRegExps, replace, newPrefix);
734 }
735 
737 {
738  return cleanSubject(msg,
739  MessageCore::MessageCoreSettings::self()->forwardPrefixes(),
740  MessageCore::MessageCoreSettings::self()->replaceForwardPrefix(),
741  QStringLiteral("Fwd:"));
742 }
743 
745 {
746  return cleanSubject(msg,
747  MessageCore::MessageCoreSettings::self()->replyPrefixes(),
748  MessageCore::MessageCoreSettings::self()->replaceReplyPrefix(),
749  QStringLiteral("Re:"));
750 }
751 
752 QString replacePrefixes(const QString &str, const QStringList &prefixRegExps, bool replace, const QString &newPrefix)
753 {
754  bool recognized = false;
755  // construct a big regexp that
756  // 1. is anchored to the beginning of str (sans whitespace)
757  // 2. matches at least one of the part regexps in prefixRegExps
758  const QString bigRegExp = QStringLiteral("^(?:\\s+|(?:%1))+\\s*").arg(prefixRegExps.join(QStringLiteral(")|(?:")));
760  if (rx.isValid()) {
761  QString tmp = str;
762  const QRegularExpressionMatch match = rx.match(tmp);
763  if (match.hasMatch()) {
764  recognized = true;
765  if (replace) {
766  return tmp.replace(0, match.capturedLength(0), newPrefix + QLatin1Char(' '));
767  }
768  }
769  } else {
770  qCWarning(MESSAGECORE_LOG) << "bigRegExp = \"" << bigRegExp << "\"\n"
771  << "prefix regexp is invalid!";
772  // try good ole Re/Fwd:
773  recognized = str.startsWith(newPrefix);
774  }
775 
776  if (!recognized) {
777  return newPrefix + QLatin1Char(' ') + str;
778  } else {
779  return str;
780  }
781 }
782 
784 {
785  const QStringList replyPrefixes = MessageCoreSettings::self()->replyPrefixes();
786 
787  const QStringList forwardPrefixes = MessageCoreSettings::self()->forwardPrefixes();
788 
789  const QStringList prefixRegExps = replyPrefixes + forwardPrefixes;
790 
791  // construct a big regexp that
792  // 1. is anchored to the beginning of str (sans whitespace)
793  // 2. matches at least one of the part regexps in prefixRegExps
794  const QString bigRegExp = QStringLiteral("^(?:\\s+|(?:%1))+\\s*").arg(prefixRegExps.join(QStringLiteral(")|(?:")));
795 
796  static QRegularExpression regex;
797 
799  if (regex.pattern() != bigRegExp) {
800  // the prefixes have changed, so update the regexp
801  regex.setPattern(bigRegExp);
802  }
803 
804  if (regex.isValid()) {
805  QRegularExpressionMatch match = regex.match(subject);
806  if (match.hasMatch()) {
807  return subject.mid(match.capturedEnd(0));
808  }
809  } else {
810  qCWarning(MESSAGECORE_LOG) << "bigRegExp = \"" << bigRegExp << "\"\n"
811  << "prefix regexp is invalid!";
812  }
813 
814  return subject;
815 }
816 
817 void setEncodingFile(QUrl &url, const QString &encoding)
818 {
819  QUrlQuery query;
820  query.addQueryItem(QStringLiteral("charset"), encoding);
821  url.setQuery(query);
822 }
823 }
824 }
void setPatternOptions(QRegularExpression::PatternOptions options)
QString toString(Qt::DateFormat format) const const
int indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
QString pattern() const const
QRegularExpressionMatch match(const QString &subject, int offset, QRegularExpression::MatchType matchType, QRegularExpression::MatchOptions matchOptions) const const
KCODECS_EXPORT QUrl encodeMailtoUrl(const QString &mailbox)
void truncate(int position)
Types::Mailbox::List mailboxes() const
FullyDecoded
QString replySubject(KMime::Message *msg)
Return this mails subject, formatted for "reply" mails.
Definition: stringutil.cpp:744
void append(const T &value)
QByteArray asSendableString(const KMime::Message::Ptr &originalMessage)
Returns the message contents with the headers that should not be sent stripped off.
Definition: stringutil.cpp:383
QList< QPair< QString, QString > > queryItems(QUrl::ComponentFormattingOptions encoding) const const
QVariant property(UserProperty which) const
int size() const const
QString stripSignature(const QString &msg)
Strips the signature blocks from a message text.
Definition: stringutil.cpp:223
void removeAt(int i)
KCODECS_EXPORT QString decodeRFC2047String(const QString &text)
void insert(int i, T &&value)
bool addressIsInAddressList(const QString &address, const QStringList &addresses)
Returns true if the given address is contained in the given address list.
Definition: stringutil.cpp:495
T & first()
KPIMTEXTEDIT_EXPORT QString flowText(QString &text, const QString &indent, int maxLength)
QString join(const QString &separator) const const
int length() const const
QString & remove(int position, int n)
void chop(int n)
QUrl fromUserInput(const QString &userInput)
QString toString(QUrl::FormattingOptions options) const const
QString replacePrefixes(const QString &str, const QStringList &prefixRegExps, bool replace, const QString &newPrefix)
Check for prefixes prefixRegExps in str.
Definition: stringutil.cpp:752
AddressMode
Used to determine if the address field should be expandable/collapsible.
Definition: stringutil.h:102
int lastIndexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
QString quoteHtmlChars(const QString &str, bool removeLineBreaks)
Quotes the following characters which have a special meaning in HTML: &#39;<&#39; &#39;>&#39; &#39;&&#39; &#39;"&#39;...
Definition: stringutil.cpp:317
void clear()
QVector< V > values(const QMultiHash< K, V > &c)
void addQueryItem(const QString &key, const QString &value)
const Identity & identityForAddress(const QString &addresses) const
int count(const T &value) const const
void append(const T &value)
QString forwardSubject(KMime::Message *msg)
Return this mails subject, formatted for "forward" mails.
Definition: stringutil.cpp:736
QString i18nc(const char *context, const char *text, const TYPE &arg...)
bool isEmpty() const const
int capturedEnd(int nth) const const
bool isEmpty() const const
QString trimmed() const const
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
KMime::Headers::Subject * subject(bool create=true)
QByteArray::iterator begin()
QString guessEmailAddressFromLoginName(const QString &loginName)
Uses the hostname as domain part and tries to determine the real name from the entries in the passwor...
Definition: stringutil.cpp:509
bool hasMatch() const const
QString smartQuote(const QString &msg, int maxLineLength)
Relayouts the given string so that the individual lines don&#39;t exceed the given maximal length...
Definition: stringutil.cpp:529
bool endsWith(const QString &s, Qt::CaseSensitivity cs) const const
QString scheme() const const
KCODECS_EXPORT QString toIdn(const QString &addrSpec)
QString formatQuotePrefix(const QString &wildString, const QString &fromDisplayString)
Convert quote wildcards into the final quote prefix.
Definition: stringutil.cpp:635
KCODECS_EXPORT QByteArray extractEmailAddress(const QByteArray &address)
QVector< QPair< QString, QString > > parseMailtoUrl(const QUrl &url)
Parses a mailto: url and extracts the information in the QMap (field name as key).
Definition: stringutil.cpp:162
QString stripOffPrefixes(const QString &subject)
Removes the forward and reply marks (e.g.
Definition: stringutil.cpp:783
char toLatin1() const const
AddressList splitAddressField(const QByteArray &text)
Splits the given address list text into separate addresses.
Definition: stringutil.cpp:283
QString & replace(int position, int n, QChar after)
void removePrivateHeaderFields(const KMime::Message::Ptr &message, bool cleanUpHeader)
Removes all private header fields (e.g.
Definition: stringutil.cpp:354
QDateTime currentDateTime()
QString mid(int position, int n) const const
QByteArray headerAsSendableString(const KMime::Message::Ptr &originalMessage)
Return the message header with the headers that should not be sent stripped off.
Definition: stringutil.cpp:394
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
const QChar at(int position) const const
T & last()
KCODECS_EXPORT QString quoteNameIfNecessary(const QString &str)
void prepend(T &&value)
typedef ConstIterator
QByteArray toPercentEncoding(const QString &input, const QByteArray &exclude, const QByteArray &include)
void removeLast()
int length() const const
void reserve(int size)
char * data()
void setQuery(const QString &query, QUrl::ParsingMode mode)
QString left(int n) const const
QString localHostName()
QString fromLatin1(const char *str, int size)
Display
Used to determine if the visible part of the anchor contains only the name part and not the given ema...
Definition: stringutil.h:92
QString cleanFileName(const QString &name)
Cleans a filename by replacing characters not allowed or wanted on the filesystem e...
Definition: stringutil.cpp:686
KCODECS_EXPORT QString decodeMailtoUrl(const QUrl &mailtoUrl)
static IdentityManager * self()
void setPattern(const QString &pattern)
QString cleanSubject(KMime::Message *msg)
Return this mails subject, with all "forward" and "reply" prefixes removed.
Definition: stringutil.cpp:722
QList::const_iterator constEnd() const const
QList::const_iterator constBegin() const const
bool isValid() const
Link
Used to determine if the address should be a link or not.
Definition: stringutil.h:97
bool isValid() const const
int capturedLength(int nth) const const
QString toString() const const
QString asUnicodeString() const override
void squeeze()
QString generateMessageId(const QString &address, const QString &suffix)
Generates the Message-Id.
Definition: stringutil.cpp:300
Types::Mailbox::List mailboxes() const
QByteArray toUtf8() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Sun Dec 5 2021 23:04:55 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.