summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Mollekopf <chrigi_1@fastmail.fm>2012-06-27 07:47:18 (GMT)
committerChristian Mollekopf <chrigi_1@fastmail.fm>2012-06-27 07:47:18 (GMT)
commit9fc556b238e0fbf12f7299439ee6b8953b975ffb (patch)
treedf54528862bb20bc4d7feadc3f71acb37451b1cd
parentebd590406589e76676064ef301c681d61f6f5b4f (diff)
downloadlibcalendaring-9fc556b238e0fbf12f7299439ee6b8953b975ffb.tar.gz
Initial import of kpimutils
commit 4fddf6f044ea74e348f1b4bf664c17c4d5236a29 Author: Allen Winter <allen.winter@kdab.com> Date: Sun Jun 24 18:33:17 2012 -0400 set version for 4.9rc1 release
-rw-r--r--kpimutils/.krazy2
-rw-r--r--kpimutils/CMakeLists.txt52
-rw-r--r--kpimutils/Mainpage.dox16
-rw-r--r--kpimutils/Messages.sh2
-rw-r--r--kpimutils/email.cpp1094
-rw-r--r--kpimutils/email.h405
-rw-r--r--kpimutils/emailvalidator.cpp50
-rw-r--r--kpimutils/emailvalidator.h46
-rw-r--r--kpimutils/kfileio.cpp350
-rw-r--r--kpimutils/kfileio.h137
-rw-r--r--kpimutils/kpimutils_export.h39
-rw-r--r--kpimutils/linklocator.cpp480
-rw-r--r--kpimutils/linklocator.h189
-rw-r--r--kpimutils/networkaccesshelper.h61
-rw-r--r--kpimutils/networkaccesshelper_fake.cpp45
-rw-r--r--kpimutils/networkaccesshelper_wince.cpp75
-rw-r--r--kpimutils/processes.cpp231
-rw-r--r--kpimutils/processes.h73
-rw-r--r--kpimutils/spellingfilter.cpp236
-rw-r--r--kpimutils/spellingfilter.h98
-rw-r--r--kpimutils/supertrait.h50
-rw-r--r--kpimutils/tests/CMakeLists.txt20
-rw-r--r--kpimutils/tests/testemail.cpp579
-rw-r--r--kpimutils/tests/testemail.h51
-rw-r--r--kpimutils/tests/testlinklocator.cpp355
-rw-r--r--kpimutils/tests/testlinklocator.h40
26 files changed, 4776 insertions, 0 deletions
diff --git a/kpimutils/.krazy b/kpimutils/.krazy
new file mode 100644
index 0000000..0cf47ca
--- /dev/null
+++ b/kpimutils/.krazy
@@ -0,0 +1,2 @@
+EXTRA style,null
+SKIP /tests/
diff --git a/kpimutils/CMakeLists.txt b/kpimutils/CMakeLists.txt
new file mode 100644
index 0000000..c2901e0
--- /dev/null
+++ b/kpimutils/CMakeLists.txt
@@ -0,0 +1,52 @@
+project(kpimutils)
+
+add_definitions(-DKDE_DEFAULT_DEBUG_AREA=5321)
+
+add_subdirectory(tests)
+
+include_directories(../kmime)
+
+set(kpimutils_LIB_SRCS
+ email.cpp
+ emailvalidator.cpp
+ linklocator.cpp
+ spellingfilter.cpp
+ kfileio.cpp
+ processes.cpp
+)
+
+# network access helper
+if(WINCE)
+ set(kpimutils_LIB_SRCS ${kpimutils_LIB_SRCS} networkaccesshelper_wince.cpp)
+else()
+ set(kpimutils_LIB_SRCS ${kpimutils_LIB_SRCS} networkaccesshelper_fake.cpp)
+endif()
+
+kde4_add_library(kpimutils ${LIBRARY_TYPE} ${kpimutils_LIB_SRCS})
+
+target_link_libraries(kpimutils ${KDE4_KDEUI_LIBS} ${KDE4_KEMOTICONS_LIBS} kmime)
+
+if(WINCE)
+ target_link_libraries(kpimutils ${WCECOMPAT_LIBRARIES} ${KDE4_SOLID_LIBS} toolhelp)
+endif()
+
+set_target_properties(kpimutils PROPERTIES
+ VERSION ${GENERIC_LIB_VERSION}
+ SOVERSION ${GENERIC_LIB_SOVERSION}
+)
+
+install(TARGETS kpimutils EXPORT kdepimlibsLibraryTargets ${INSTALL_TARGETS_DEFAULT_ARGS})
+
+########### install files ###############
+
+install(FILES
+ kpimutils_export.h
+ email.h
+ emailvalidator.h
+ linklocator.h
+ spellingfilter.h
+ kfileio.h
+ supertrait.h
+ processes.h
+ networkaccesshelper.h
+DESTINATION ${INCLUDE_INSTALL_DIR}/kpimutils COMPONENT Devel)
diff --git a/kpimutils/Mainpage.dox b/kpimutils/Mainpage.dox
new file mode 100644
index 0000000..a45f8b9
--- /dev/null
+++ b/kpimutils/Mainpage.dox
@@ -0,0 +1,16 @@
+/** @mainpage kpimutils
+
+This library contains utility functions for dealing with email addresses.
+This includes parsing, splitting, and munging email addresses represented
+as strings (e.g. in email headers); some additional functions handle
+non-latin1 upper- and lower-case conversions.
+
+Everything lives in the KPIMUtils namespace.
+
+ - @ref emailvalidation
+ - @ref emailextraction
+ - @ref emailidn
+
+*/
+
+// DOXYGEN_TOOLTIP = "A library for general purpose email handling."
diff --git a/kpimutils/Messages.sh b/kpimutils/Messages.sh
new file mode 100644
index 0000000..a49bf82
--- /dev/null
+++ b/kpimutils/Messages.sh
@@ -0,0 +1,2 @@
+#! /bin/sh
+$XGETTEXT *.cpp *.h -o $podir/libkpimutils.pot
diff --git a/kpimutils/email.cpp b/kpimutils/email.cpp
new file mode 100644
index 0000000..e2de775
--- /dev/null
+++ b/kpimutils/email.cpp
@@ -0,0 +1,1094 @@
+/*
+ This file is part of the kpimutils library.
+ Copyright (c) 2004 Matt Douhan <matt@fruitsalad.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+/**
+ @file
+ This file is part of the KDEPIM Utilities library and provides
+ static methods for email address validation.
+
+ @author Matt Douhan \<matt@fruitsalad.org\>
+*/
+#include "email.h"
+
+#include <kmime/kmime_util.h>
+
+#include <KDebug>
+#include <KLocale>
+#include <KUrl>
+
+#include <QtCore/QRegExp>
+#include <QtCore/QByteArray>
+
+#include <kglobal.h>
+
+static const KCatalogLoader loader( "libkpimutils" );
+
+using namespace KPIMUtils;
+
+//-----------------------------------------------------------------------------
+QStringList KPIMUtils::splitAddressList( const QString &aStr )
+{
+ // Features:
+ // - always ignores quoted characters
+ // - ignores everything (including parentheses and commas)
+ // inside quoted strings
+ // - supports nested comments
+ // - ignores everything (including double quotes and commas)
+ // inside comments
+
+ QStringList list;
+
+ if ( aStr.isEmpty() ) {
+ return list;
+ }
+
+ QString addr;
+ uint addrstart = 0;
+ int commentlevel = 0;
+ bool insidequote = false;
+
+ for ( int index=0; index<aStr.length(); index++ ) {
+ // the following conversion to latin1 is o.k. because
+ // we can safely ignore all non-latin1 characters
+ switch ( aStr[index].toLatin1() ) {
+ case '"' : // start or end of quoted string
+ if ( commentlevel == 0 ) {
+ insidequote = !insidequote;
+ }
+ break;
+ case '(' : // start of comment
+ if ( !insidequote ) {
+ commentlevel++;
+ }
+ break;
+ case ')' : // end of comment
+ if ( !insidequote ) {
+ if ( commentlevel > 0 ) {
+ commentlevel--;
+ } else {
+ return list;
+ }
+ }
+ break;
+ case '\\' : // quoted character
+ index++; // ignore the quoted character
+ break;
+ case ',' :
+ case ';' :
+ if ( !insidequote && ( commentlevel == 0 ) ) {
+ addr = aStr.mid( addrstart, index - addrstart );
+ if ( !addr.isEmpty() ) {
+ list += addr.simplified();
+ }
+ addrstart = index + 1;
+ }
+ break;
+ }
+ }
+ // append the last address to the list
+ if ( !insidequote && ( commentlevel == 0 ) ) {
+ addr = aStr.mid( addrstart, aStr.length() - addrstart );
+ if ( !addr.isEmpty() ) {
+ list += addr.simplified();
+ }
+ }
+
+ return list;
+}
+
+//-----------------------------------------------------------------------------
+// Used by KPIMUtils::splitAddress(...) and KPIMUtils::firstEmailAddress(...).
+KPIMUtils::EmailParseResult splitAddressInternal( const QByteArray address,
+ QByteArray &displayName,
+ QByteArray &addrSpec,
+ QByteArray &comment,
+ bool allowMultipleAddresses )
+{
+ // kDebug() << "address";
+ displayName = "";
+ addrSpec = "";
+ comment = "";
+
+ if ( address.isEmpty() ) {
+ return AddressEmpty;
+ }
+
+ // The following is a primitive parser for a mailbox-list (cf. RFC 2822).
+ // The purpose is to extract a displayable string from the mailboxes.
+ // Comments in the addr-spec are not handled. No error checking is done.
+
+ enum {
+ TopLevel,
+ InComment,
+ InAngleAddress
+ } context = TopLevel;
+ bool inQuotedString = false;
+ int commentLevel = 0;
+ bool stop = false;
+
+ for ( const char *p = address.data(); *p && !stop; ++p ) {
+ switch ( context ) {
+ case TopLevel :
+ {
+ switch ( *p ) {
+ case '"' :
+ inQuotedString = !inQuotedString;
+ displayName += *p;
+ break;
+ case '(' :
+ if ( !inQuotedString ) {
+ context = InComment;
+ commentLevel = 1;
+ } else {
+ displayName += *p;
+ }
+ break;
+ case '<' :
+ if ( !inQuotedString ) {
+ context = InAngleAddress;
+ } else {
+ displayName += *p;
+ }
+ break;
+ case '\\' : // quoted character
+ displayName += *p;
+ ++p; // skip the '\'
+ if ( *p ) {
+ displayName += *p;
+ } else {
+ return UnexpectedEnd;
+ }
+ break;
+ case ',' :
+ if ( !inQuotedString ) {
+ if ( allowMultipleAddresses ) {
+ stop = true;
+ } else {
+ return UnexpectedComma;
+ }
+ } else {
+ displayName += *p;
+ }
+ break;
+ default :
+ displayName += *p;
+ }
+ break;
+ }
+ case InComment :
+ {
+ switch ( *p ) {
+ case '(' :
+ ++commentLevel;
+ comment += *p;
+ break;
+ case ')' :
+ --commentLevel;
+ if ( commentLevel == 0 ) {
+ context = TopLevel;
+ comment += ' '; // separate the text of several comments
+ } else {
+ comment += *p;
+ }
+ break;
+ case '\\' : // quoted character
+ comment += *p;
+ ++p; // skip the '\'
+ if ( *p ) {
+ comment += *p;
+ } else {
+ return UnexpectedEnd;
+ }
+ break;
+ default :
+ comment += *p;
+ }
+ break;
+ }
+ case InAngleAddress :
+ {
+ switch ( *p ) {
+ case '"' :
+ inQuotedString = !inQuotedString;
+ addrSpec += *p;
+ break;
+ case '>' :
+ if ( !inQuotedString ) {
+ context = TopLevel;
+ } else {
+ addrSpec += *p;
+ }
+ break;
+ case '\\' : // quoted character
+ addrSpec += *p;
+ ++p; // skip the '\'
+ if ( *p ) {
+ addrSpec += *p;
+ } else {
+ return UnexpectedEnd;
+ }
+ break;
+ default :
+ addrSpec += *p;
+ }
+ break;
+ }
+ } // switch ( context )
+ }
+ // check for errors
+ if ( inQuotedString ) {
+ return UnbalancedQuote;
+ }
+ if ( context == InComment ) {
+ return UnbalancedParens;
+ }
+ if ( context == InAngleAddress ) {
+ return UnclosedAngleAddr;
+ }
+
+ displayName = displayName.trimmed();
+ comment = comment.trimmed();
+ addrSpec = addrSpec.trimmed();
+
+ if ( addrSpec.isEmpty() ) {
+ if ( displayName.isEmpty() ) {
+ return NoAddressSpec;
+ } else {
+ addrSpec = displayName;
+ displayName.truncate( 0 );
+ }
+ }
+ /*
+ kDebug() << "display-name : \"" << displayName << "\"";
+ kDebug() << "comment : \"" << comment << "\"";
+ kDebug() << "addr-spec : \"" << addrSpec << "\"";
+ */
+ return AddressOk;
+}
+
+//-----------------------------------------------------------------------------
+EmailParseResult KPIMUtils::splitAddress( const QByteArray &address,
+ QByteArray &displayName,
+ QByteArray &addrSpec,
+ QByteArray &comment )
+{
+ return splitAddressInternal( address, displayName, addrSpec, comment,
+ false/* don't allow multiple addresses */ );
+}
+
+//-----------------------------------------------------------------------------
+EmailParseResult KPIMUtils::splitAddress( const QString &address,
+ QString &displayName,
+ QString &addrSpec,
+ QString &comment )
+{
+ QByteArray d, a, c;
+ // FIXME: toUtf8() is probably not safe here, what if the second byte of a multi-byte character
+ // has the same code as one of the ASCII characters that splitAddress uses as delimiters?
+ EmailParseResult result = splitAddress( address.toUtf8(), d, a, c );
+
+ if ( result == AddressOk ) {
+ displayName = QString::fromUtf8( d );
+ addrSpec = QString::fromUtf8( a );
+ comment = QString::fromUtf8( c );
+ }
+ return result;
+}
+
+//-----------------------------------------------------------------------------
+EmailParseResult KPIMUtils::isValidAddress( const QString &aStr )
+{
+ // If we are passed an empty string bail right away no need to process
+ // further and waste resources
+ if ( aStr.isEmpty() ) {
+ return AddressEmpty;
+ }
+
+ // count how many @'s are in the string that is passed to us
+ // if 0 or > 1 take action
+ // at this point to many @'s cannot bail out right away since
+ // @ is allowed in qoutes, so we use a bool to keep track
+ // and then make a judgment further down in the parser
+ // FIXME count only @ not in double quotes
+
+ bool tooManyAtsFlag = false;
+
+ int atCount = aStr.count( '@' );
+ if ( atCount > 1 ) {
+ tooManyAtsFlag = true;
+ } else if ( atCount == 0 ) {
+ return TooFewAts;
+ }
+
+ // The main parser, try and catch all weird and wonderful
+ // mistakes users and/or machines can create
+
+ enum {
+ TopLevel,
+ InComment,
+ InAngleAddress
+ } context = TopLevel;
+ bool inQuotedString = false;
+ int commentLevel = 0;
+
+ unsigned int strlen = aStr.length();
+
+ for ( unsigned int index=0; index < strlen; index++ ) {
+ switch ( context ) {
+ case TopLevel :
+ {
+ switch ( aStr[index].toLatin1() ) {
+ case '"' :
+ inQuotedString = !inQuotedString;
+ break;
+ case '(' :
+ if ( !inQuotedString ) {
+ context = InComment;
+ commentLevel = 1;
+ }
+ break;
+ case '[' :
+ if ( !inQuotedString ) {
+ return InvalidDisplayName;
+ }
+ break;
+ case ']' :
+ if ( !inQuotedString ) {
+ return InvalidDisplayName;
+ }
+ break;
+ case ':' :
+ if ( !inQuotedString ) {
+ return DisallowedChar;
+ }
+ break;
+ case '<' :
+ if ( !inQuotedString ) {
+ context = InAngleAddress;
+ }
+ break;
+ case '\\' : // quoted character
+ ++index; // skip the '\'
+ if ( ( index + 1 ) > strlen ) {
+ return UnexpectedEnd;
+ }
+ break;
+ case ',' :
+ if ( !inQuotedString ) {
+ return UnexpectedComma;
+ }
+ break;
+ case ')' :
+ if ( !inQuotedString ) {
+ return UnbalancedParens;
+ }
+ break;
+ case '>' :
+ if ( !inQuotedString ) {
+ return UnopenedAngleAddr;
+ }
+ break;
+ case '@' :
+ if ( !inQuotedString ) {
+ if ( index == 0 ) { // Missing local part
+ return MissingLocalPart;
+ } else if ( index == strlen-1 ) {
+ return MissingDomainPart;
+ break;
+ }
+ } else if ( inQuotedString ) {
+ --atCount;
+ if ( atCount == 1 ) {
+ tooManyAtsFlag = false;
+ }
+ }
+ break;
+ }
+ break;
+ }
+ case InComment :
+ {
+ switch ( aStr[index].toLatin1() ) {
+ case '(' :
+ ++commentLevel;
+ break;
+ case ')' :
+ --commentLevel;
+ if ( commentLevel == 0 ) {
+ context = TopLevel;
+ }
+ break;
+ case '\\' : // quoted character
+ ++index; // skip the '\'
+ if ( ( index + 1 ) > strlen ) {
+ return UnexpectedEnd;
+ }
+ break;
+ }
+ break;
+ }
+
+ case InAngleAddress :
+ {
+ switch ( aStr[index].toLatin1() ) {
+ case ',' :
+ if ( !inQuotedString ) {
+ return UnexpectedComma;
+ }
+ break;
+ case '"' :
+ inQuotedString = !inQuotedString;
+ break;
+ case '@' :
+ if ( inQuotedString ) {
+ --atCount;
+ if ( atCount == 1 ) {
+ tooManyAtsFlag = false;
+ }
+ }
+ break;
+ case '>' :
+ if ( !inQuotedString ) {
+ context = TopLevel;
+ break;
+ }
+ break;
+ case '\\' : // quoted character
+ ++index; // skip the '\'
+ if ( ( index + 1 ) > strlen ) {
+ return UnexpectedEnd;
+ }
+ break;
+ }
+ break;
+ }
+ }
+ }
+
+ if ( atCount == 0 && !inQuotedString ) {
+ return TooFewAts;
+ }
+
+ if ( inQuotedString ) {
+ return UnbalancedQuote;
+ }
+
+ if ( context == InComment ) {
+ return UnbalancedParens;
+ }
+
+ if ( context == InAngleAddress ) {
+ return UnclosedAngleAddr;
+ }
+
+ if ( tooManyAtsFlag ) {
+ return TooManyAts;
+ }
+
+ return AddressOk;
+}
+
+//-----------------------------------------------------------------------------
+KPIMUtils::EmailParseResult KPIMUtils::isValidAddressList( const QString &aStr,
+ QString &badAddr )
+{
+ if ( aStr.isEmpty() ) {
+ return AddressEmpty;
+ }
+
+ const QStringList list = splitAddressList( aStr );
+
+ QStringList::const_iterator it = list.begin();
+ EmailParseResult errorCode = AddressOk;
+ for ( it = list.begin(); it != list.end(); ++it ) {
+ errorCode = isValidAddress( *it );
+ if ( errorCode != AddressOk ) {
+ badAddr = ( *it );
+ break;
+ }
+ }
+ return errorCode;
+}
+
+//-----------------------------------------------------------------------------
+QString KPIMUtils::emailParseResultToString( EmailParseResult errorCode )
+{
+ switch ( errorCode ) {
+ case TooManyAts :
+ return i18n( "The email address you entered is not valid because it "
+ "contains more than one @. "
+ "You will not create valid messages if you do not "
+ "change your address." );
+ case TooFewAts :
+ return i18n( "The email address you entered is not valid because it "
+ "does not contain a @. "
+ "You will not create valid messages if you do not "
+ "change your address." );
+ case AddressEmpty :
+ return i18n( "You have to enter something in the email address field." );
+ case MissingLocalPart :
+ return i18n( "The email address you entered is not valid because it "
+ "does not contain a local part." );
+ case MissingDomainPart :
+ return i18n( "The email address you entered is not valid because it "
+ "does not contain a domain part." );
+ case UnbalancedParens :
+ return i18n( "The email address you entered is not valid because it "
+ "contains unclosed comments/brackets." );
+ case AddressOk :
+ return i18n( "The email address you entered is valid." );
+ case UnclosedAngleAddr :
+ return i18n( "The email address you entered is not valid because it "
+ "contains an unclosed angle bracket." );
+ case UnopenedAngleAddr :
+ return i18n( "The email address you entered is not valid because it "
+ "contains too many closing angle brackets." );
+ case UnexpectedComma :
+ return i18n( "The email address you have entered is not valid because it "
+ "contains an unexpected comma." );
+ case UnexpectedEnd :
+ return i18n( "The email address you entered is not valid because it ended "
+ "unexpectedly. This probably means you have used an escaping "
+ "type character like a '\\' as the last character in your "
+ "email address." );
+ case UnbalancedQuote :
+ return i18n( "The email address you entered is not valid because it "
+ "contains quoted text which does not end." );
+ case NoAddressSpec :
+ return i18n( "The email address you entered is not valid because it "
+ "does not seem to contain an actual email address, i.e. "
+ "something of the form joe@example.org." );
+ case DisallowedChar :
+ return i18n( "The email address you entered is not valid because it "
+ "contains an illegal character." );
+ case InvalidDisplayName :
+ return i18n( "The email address you have entered is not valid because it "
+ "contains an invalid display name." );
+ }
+ return i18n( "Unknown problem with email address" );
+}
+
+//-----------------------------------------------------------------------------
+bool KPIMUtils::isValidSimpleAddress( const QString &aStr )
+{
+ // If we are passed an empty string bail right away no need to process further
+ // and waste resources
+ if ( aStr.isEmpty() ) {
+ return false;
+ }
+
+ int atChar = aStr.lastIndexOf( '@' );
+ QString domainPart = aStr.mid( atChar + 1 );
+ QString localPart = aStr.left( atChar );
+
+ // Both of these parts must be non empty
+ // after all we cannot have emails like:
+ // @kde.org, or foo@
+ if ( localPart.isEmpty() || domainPart.isEmpty() ) {
+ return false;
+ }
+
+ bool tooManyAtsFlag = false;
+ bool inQuotedString = false;
+ int atCount = localPart.count( '@' );
+
+ unsigned int strlen = localPart.length();
+ for ( unsigned int index=0; index < strlen; index++ ) {
+ switch( localPart[ index ].toLatin1() ) {
+ case '"' :
+ inQuotedString = !inQuotedString;
+ break;
+ case '@' :
+ if ( inQuotedString ) {
+ --atCount;
+ if ( atCount == 0 ) {
+ tooManyAtsFlag = false;
+ }
+ }
+ break;
+ }
+ }
+
+ QString addrRx =
+ "[a-zA-Z]*[~|{}`\\^?=/+*'&%$#!_\\w.-]*[~|{}`\\^?=/+*'&%$#!_a-zA-Z0-9-]@";
+
+ if ( localPart[ 0 ] == '\"' || localPart[ localPart.length()-1 ] == '\"' ) {
+ addrRx = "\"[a-zA-Z@]*[\\w.@-]*[a-zA-Z0-9@]\"@";
+ }
+ if ( domainPart[ 0 ] == '[' || domainPart[ domainPart.length()-1 ] == ']' ) {
+ addrRx += "\\[[0-9]{,3}(\\.[0-9]{,3}){3}\\]";
+ } else {
+ addrRx += "[\\w-#]+(\\.[\\w-#]+)*";
+ }
+ QRegExp rx( addrRx );
+ return rx.exactMatch( aStr ) && !tooManyAtsFlag;
+}
+
+//-----------------------------------------------------------------------------
+QString KPIMUtils::simpleEmailAddressErrorMsg()
+{
+ return i18n( "The email address you entered is not valid because it "
+ "does not seem to contain an actual email address, i.e. "
+ "something of the form joe@example.org." );
+}
+
+//-----------------------------------------------------------------------------
+QByteArray KPIMUtils::extractEmailAddress( const QByteArray &address )
+{
+ QByteArray dummy1, dummy2, addrSpec;
+ EmailParseResult result =
+ splitAddressInternal( address, dummy1, addrSpec, dummy2,
+ false/* don't allow multiple addresses */ );
+ if ( result != AddressOk ) {
+ addrSpec = QByteArray();
+ if ( result != AddressEmpty ) {
+ kDebug()
+ << "Input:" << address << "\nError:"
+ << emailParseResultToString( result );
+ }
+ }
+
+ return addrSpec;
+}
+
+//-----------------------------------------------------------------------------
+QString KPIMUtils::extractEmailAddress( const QString &address )
+{
+ return QString::fromUtf8( extractEmailAddress( address.toUtf8() ) );
+}
+
+//-----------------------------------------------------------------------------
+QByteArray KPIMUtils::firstEmailAddress( const QByteArray &addresses )
+{
+ QByteArray dummy1, dummy2, addrSpec;
+ EmailParseResult result =
+ splitAddressInternal( addresses, dummy1, addrSpec, dummy2,
+ true/* allow multiple addresses */ );
+ if ( result != AddressOk ) {
+ addrSpec = QByteArray();
+ if ( result != AddressEmpty ) {
+ kDebug()
+ << "Input: aStr\nError:"
+ << emailParseResultToString( result );
+ }
+ }
+
+ return addrSpec;
+}
+
+//-----------------------------------------------------------------------------
+QString KPIMUtils::firstEmailAddress( const QString &addresses )
+{
+ return QString::fromUtf8( firstEmailAddress( addresses.toUtf8() ) );
+}
+
+//-----------------------------------------------------------------------------
+bool KPIMUtils::extractEmailAddressAndName( const QString &aStr,
+ QString &mail, QString &name )
+{
+ name.clear();
+ mail.clear();
+
+ const int len = aStr.length();
+ const char cQuotes = '"';
+
+ bool bInComment = false;
+ bool bInQuotesOutsideOfEmail = false;
+ int i=0, iAd=0, iMailStart=0, iMailEnd=0;
+ QChar c;
+ unsigned int commentstack = 0;
+
+ // Find the '@' of the email address
+ // skipping all '@' inside "(...)" comments:
+ while ( i < len ) {
+ c = aStr[i];
+ if ( '(' == c ) {
+ commentstack++;
+ }
+ if ( ')' == c ) {
+ commentstack--;
+ }
+ bInComment = commentstack != 0;
+ if ( '"' == c && !bInComment ) {
+ bInQuotesOutsideOfEmail = !bInQuotesOutsideOfEmail;
+ }
+
+ if( !bInComment && !bInQuotesOutsideOfEmail ) {
+ if ( '@' == c ) {
+ iAd = i;
+ break; // found it
+ }
+ }
+ ++i;
+ }
+
+ if ( !iAd ) {
+ // We suppose the user is typing the string manually and just
+ // has not finished typing the mail address part.
+ // So we take everything that's left of the '<' as name and the rest as mail
+ for ( i = 0; len > i; ++i ) {
+ c = aStr[i];
+ if ( '<' != c ) {
+ name.append( c );
+ } else {
+ break;
+ }
+ }
+ mail = aStr.mid( i + 1 );
+ if ( mail.endsWith( '>' ) ) {
+ mail.truncate( mail.length() - 1 );
+ }
+
+ } else {
+ // Loop backwards until we find the start of the string
+ // or a ',' that is outside of a comment
+ // and outside of quoted text before the leading '<'.
+ bInComment = false;
+ bInQuotesOutsideOfEmail = false;
+ for ( i = iAd-1; 0 <= i; --i ) {
+ c = aStr[i];
+ if ( bInComment ) {
+ if ( '(' == c ) {
+ if ( !name.isEmpty() ) {
+ name.prepend( ' ' );
+ }
+ bInComment = false;
+ } else {
+ name.prepend( c ); // all comment stuff is part of the name
+ }
+ } else if ( bInQuotesOutsideOfEmail ) {
+ if ( cQuotes == c ) {
+ bInQuotesOutsideOfEmail = false;
+ } else if ( c != '\\' ) {
+ name.prepend( c );
+ }
+ } else {
+ // found the start of this addressee ?
+ if ( ',' == c ) {
+ break;
+ }
+ // stuff is before the leading '<' ?
+ if ( iMailStart ) {
+ if ( cQuotes == c ) {
+ bInQuotesOutsideOfEmail = true; // end of quoted text found
+ } else {
+ name.prepend( c );
+ }
+ } else {
+ switch ( c.toLatin1() ) {
+ case '<':
+ iMailStart = i;
+ break;
+ case ')':
+ if ( !name.isEmpty() ) {
+ name.prepend( ' ' );
+ }
+ bInComment = true;
+ break;
+ default:
+ if ( ' ' != c ) {
+ mail.prepend( c );
+ }
+ }
+ }
+ }
+ }
+
+ name = name.simplified();
+ mail = mail.simplified();
+
+ if ( mail.isEmpty() ) {
+ return false;
+ }
+
+ mail.append( '@' );
+
+ // Loop forward until we find the end of the string
+ // or a ',' that is outside of a comment
+ // and outside of quoted text behind the trailing '>'.
+ bInComment = false;
+ bInQuotesOutsideOfEmail = false;
+ int parenthesesNesting = 0;
+ for ( i = iAd+1; len > i; ++i ) {
+ c = aStr[i];
+ if ( bInComment ) {
+ if ( ')' == c ) {
+ if ( --parenthesesNesting == 0 ) {
+ bInComment = false;
+ if ( !name.isEmpty() ) {
+ name.append( ' ' );
+ }
+ } else {
+ // nested ")", add it
+ name.append( ')' ); // name can't be empty here
+ }
+ } else {
+ if ( '(' == c ) {
+ // nested "("
+ ++parenthesesNesting;
+ }
+ name.append( c ); // all comment stuff is part of the name
+ }
+ } else if ( bInQuotesOutsideOfEmail ) {
+ if ( cQuotes == c ) {
+ bInQuotesOutsideOfEmail = false;
+ } else if ( c != '\\' ) {
+ name.append( c );
+ }
+ } else {
+ // found the end of this addressee ?
+ if ( ',' == c ) {
+ break;
+ }
+ // stuff is behind the trailing '>' ?
+ if ( iMailEnd ){
+ if ( cQuotes == c ) {
+ bInQuotesOutsideOfEmail = true; // start of quoted text found
+ } else {
+ name.append( c );
+ }
+ } else {
+ switch ( c.toLatin1() ) {
+ case '>':
+ iMailEnd = i;
+ break;
+ case '(':
+ if ( !name.isEmpty() ) {
+ name.append( ' ' );
+ }
+ if ( ++parenthesesNesting > 0 ) {
+ bInComment = true;
+ }
+ break;
+ default:
+ if ( ' ' != c ) {
+ mail.append( c );
+ }
+ }
+ }
+ }
+ }
+ }
+
+ name = name.simplified();
+ mail = mail.simplified();
+
+ return ! ( name.isEmpty() || mail.isEmpty() );
+}
+
+//-----------------------------------------------------------------------------
+bool KPIMUtils::compareEmail( const QString &email1, const QString &email2,
+ bool matchName )
+{
+ QString e1Name, e1Email, e2Name, e2Email;
+
+ extractEmailAddressAndName( email1, e1Email, e1Name );
+ extractEmailAddressAndName( email2, e2Email, e2Name );
+
+ return e1Email == e2Email &&
+ ( !matchName || ( e1Name == e2Name ) );
+}
+
+//-----------------------------------------------------------------------------
+QString KPIMUtils::normalizedAddress( const QString &displayName,
+ const QString &addrSpec,
+ const QString &comment )
+{
+ const QString realDisplayName = KMime::removeBidiControlChars( displayName );
+ if ( realDisplayName.isEmpty() && comment.isEmpty() ) {
+ return addrSpec;
+ } else if ( comment.isEmpty() ) {
+ if ( !realDisplayName.startsWith( '\"' ) ) {
+ return quoteNameIfNecessary( realDisplayName ) + " <" + addrSpec + '>';
+ } else {
+ return realDisplayName + " <" + addrSpec + '>';
+ }
+ } else if ( realDisplayName.isEmpty() ) {
+ QString commentStr = comment;
+ return quoteNameIfNecessary( commentStr ) + " <" + addrSpec + '>';
+ } else {
+ return realDisplayName + " (" + comment + ") <" + addrSpec + '>';
+ }
+}
+
+//-----------------------------------------------------------------------------
+QString KPIMUtils::fromIdn( const QString &addrSpec )
+{
+ const int atPos = addrSpec.lastIndexOf( '@' );
+ if ( atPos == -1 ) {
+ return addrSpec;
+ }
+
+ QString idn = KUrl::fromAce( addrSpec.mid( atPos + 1 ).toLatin1() );
+ if ( idn.isEmpty() ) {
+ return QString();
+ }
+
+ return addrSpec.left( atPos + 1 ) + idn;
+}
+
+//-----------------------------------------------------------------------------
+QString KPIMUtils::toIdn( const QString &addrSpec )
+{
+ const int atPos = addrSpec.lastIndexOf( '@' );
+ if ( atPos == -1 ) {
+ return addrSpec;
+ }
+
+ QString idn = KUrl::toAce( addrSpec.mid( atPos + 1 ) );
+ if ( idn.isEmpty() ) {
+ return addrSpec;
+ }
+
+ return addrSpec.left( atPos + 1 ) + idn;
+}
+
+//-----------------------------------------------------------------------------
+QString KPIMUtils::normalizeAddressesAndDecodeIdn( const QString &str )
+{
+ // kDebug() << str;
+ if ( str.isEmpty() ) {
+ return str;
+ }
+
+ const QStringList addressList = splitAddressList( str );
+ QStringList normalizedAddressList;
+
+ QByteArray displayName, addrSpec, comment;
+
+ for ( QStringList::ConstIterator it = addressList.begin();
+ ( it != addressList.end() );
+ ++it ) {
+ if ( !(*it).isEmpty() ) {
+ if ( splitAddress( (*it).toUtf8(),
+ displayName, addrSpec, comment ) == AddressOk ) {
+
+ displayName = KMime::decodeRFC2047String(displayName).toUtf8();
+ comment = KMime::decodeRFC2047String(comment).toUtf8();
+
+ normalizedAddressList
+ << normalizedAddress( QString::fromUtf8( displayName ),
+ fromIdn( QString::fromUtf8( addrSpec ) ),
+ QString::fromUtf8( comment ) );
+ }
+ }
+ }
+ /*
+ kDebug() << "normalizedAddressList: \""
+ << normalizedAddressList.join( ", " )
+ << "\"";
+ */
+ return normalizedAddressList.join( ", " );
+}
+
+//-----------------------------------------------------------------------------
+QString KPIMUtils::normalizeAddressesAndEncodeIdn( const QString &str )
+{
+ //kDebug() << str;
+ if ( str.isEmpty() ) {
+ return str;
+ }
+
+ const QStringList addressList = splitAddressList( str );
+ QStringList normalizedAddressList;
+
+ QByteArray displayName, addrSpec, comment;
+
+ for ( QStringList::ConstIterator it = addressList.begin();
+ ( it != addressList.end() );
+ ++it ) {
+ if ( !(*it).isEmpty() ) {
+ if ( splitAddress( (*it).toUtf8(),
+ displayName, addrSpec, comment ) == AddressOk ) {
+
+ normalizedAddressList << normalizedAddress( QString::fromUtf8( displayName ),
+ toIdn( QString::fromUtf8( addrSpec ) ),
+ QString::fromUtf8( comment ) );
+ }
+ }
+ }
+
+ /*
+ kDebug() << "normalizedAddressList: \""
+ << normalizedAddressList.join( ", " )
+ << "\"";
+ */
+ return normalizedAddressList.join( ", " );
+}
+
+//-----------------------------------------------------------------------------
+// Escapes unescaped doublequotes in str.
+static QString escapeQuotes( const QString &str )
+{
+ if ( str.isEmpty() ) {
+ return QString();
+ }
+
+ QString escaped;
+ // reserve enough memory for the worst case ( """..."" -> \"\"\"...\"\" )
+ escaped.reserve( 2 * str.length() );
+ unsigned int len = 0;
+ for ( int i = 0; i < str.length(); ++i, ++len ) {
+ if ( str[i] == '"' ) { // unescaped doublequote
+ escaped[len] = '\\';
+ ++len;
+ } else if ( str[i] == '\\' ) { // escaped character
+ escaped[len] = '\\';
+ ++len;
+ ++i;
+ if ( i >= str.length() ) { // handle trailing '\' gracefully
+ break;
+ }
+ }
+ escaped[len] = str[i];
+ }
+ escaped.truncate( len );
+ return escaped;
+}
+
+//-----------------------------------------------------------------------------
+QString KPIMUtils::quoteNameIfNecessary( const QString &str )
+{
+ QString quoted = str;
+
+ QRegExp needQuotes( "[^ 0-9A-Za-z\\x0080-\\xFFFF]" );
+ // avoid double quoting
+ if ( ( quoted[0] == '"' ) && ( quoted[quoted.length() - 1] == '"' ) ) {
+ quoted = "\"" + escapeQuotes( quoted.mid( 1, quoted.length() - 2 ) ) + "\"";
+ } else if ( quoted.indexOf( needQuotes ) != -1 ) {
+ quoted = "\"" + escapeQuotes( quoted ) + "\"";
+ }
+
+ return quoted;
+}
+
+KUrl KPIMUtils::encodeMailtoUrl( const QString &mailbox )
+{
+ const QByteArray encodedPath = KMime::encodeRFC2047String( mailbox, "utf-8" );
+ KUrl mailtoUrl;
+ mailtoUrl.setProtocol( "mailto" );
+ mailtoUrl.setPath( encodedPath );
+ return mailtoUrl;
+}
+
+QString KPIMUtils::decodeMailtoUrl( const KUrl &mailtoUrl )
+{
+ Q_ASSERT( mailtoUrl.protocol().toLower() == "mailto" );
+ return KMime::decodeRFC2047String( mailtoUrl.path().toUtf8() );
+}
diff --git a/kpimutils/email.h b/kpimutils/email.h
new file mode 100644
index 0000000..bd48ed8
--- /dev/null
+++ b/kpimutils/email.h
@@ -0,0 +1,405 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+
+ This file is part of the kpimutils library.
+ Copyright (c) 2004 Matt Douhan <matt@fruitsalad.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+/**
+ @file
+ This file is part of the KDEPIM Utilities library and provides
+ static methods for email address validation.
+
+ @brief
+ Email address validation methods.
+
+ @author Matt Douhan \<matt@fruitsalad.org\>
+ */
+
+#ifndef KPIMUTILS_EMAIL_H
+#define KPIMUTILS_EMAIL_H
+
+#include <KDE/KUrl>
+
+#include <QtCore/QStringList>
+#include <QtCore/QByteArray>
+
+#include "kpimutils_export.h"
+
+namespace KPIMUtils {
+
+ /**
+ @defgroup emailvalidation Email Validation Functions
+
+ This collection of methods that can validate email addresses as supplied
+ by the user (typically, user input from a text box). There are also
+ functions for splitting an RFC2822 address into its component parts.
+
+ @{
+ */
+
+ /**
+ Email validation result. The only 'success' code in
+ this enumeration is AddressOK; all the other values
+ indicate some specific problem with the address which
+ is being validated.
+
+ Result type for splitAddress(), isValidAddress()
+ and isValidSimpleAddress().
+ */
+ enum EmailParseResult {
+ AddressOk, /**< Email is valid */
+ AddressEmpty, /**< The address is empty */
+ UnexpectedEnd, /**< Something is unbalanced */
+ UnbalancedParens, /**< Unbalanced ( ) */
+ MissingDomainPart, /**< No domain in address */
+ UnclosedAngleAddr, /**< \< with no matching \> */
+ UnopenedAngleAddr, /**< \> with no preceding \< */
+ TooManyAts, /**< More than one \@ in address */
+ UnexpectedComma, /**< Comma not allowed here */
+ TooFewAts, /**< Missing \@ in address */
+ MissingLocalPart, /**< No address specified, only domain */
+ UnbalancedQuote, /**< Quotes (single or double) not matched */
+ NoAddressSpec,
+ DisallowedChar, /**< An invalid character detected in address */
+ InvalidDisplayName /**< An invalid displayname detected in address */
+ };
+
+ /** Split a comma separated list of email addresses.
+
+ @param aStr a single string representing a list of addresses
+ @return a list of strings, where each string is one address
+ from the original list
+ */
+ KPIMUTILS_EXPORT
+ QStringList splitAddressList( const QString &aStr );
+
+ /**
+ Splits the given address into display name, email address and comment.
+ Returns AddressOk if no error was encountered. Otherwise an appropriate
+ error code is returned. In case of an error the values of displayName,
+ addrSpec and comment are undefined.
+
+ @param address a single email address,
+ example: Joe User (comment1) <joe.user@example.org> (comment2)
+ @param displayName only out: the display-name of the email address, i.e.
+ "Joe User" in the example; in case of an error the
+ return value is undefined
+ @param addrSpec only out: the addr-spec, i.e. "joe.user@example.org"
+ in the example; in case of an error the return value is undefined
+ @param comment only out: the space-separated comments, i.e.
+ "comment1 comment2" in the example; in case of an
+ error the return value is undefined
+
+ @return AddressOk if no error was encountered. Otherwise an
+ appropriate error code is returned.
+ */
+ KPIMUTILS_EXPORT
+ EmailParseResult splitAddress( const QByteArray &address,
+ QByteArray &displayName,
+ QByteArray &addrSpec,
+ QByteArray &comment );
+
+ /**
+ This is an overloaded member function, provided for convenience.
+ It behaves essentially like the above function.
+
+ Splits the given address into display name, email address and comment.
+ Returns AddressOk if no error was encountered. Otherwise an appropriate
+ error code is returned. In case of an error the values of displayName,
+ addrSpec and comment are undefined.
+
+ @param address a single email address,
+ example: Joe User (comment1) <joe.user@example.org> (comment2)
+ @param displayName only out: the display-name of the email address, i.e.
+ "Joe User" in the example; in case of an error the
+ return value is undefined
+ @param addrSpec only out: the addr-spec, i.e. "joe.user@example.org"
+ in the example; in case of an error the return value is undefined
+ @param comment only out: the space-separated comments, i.e.
+ "comment1 comment2" in the example; in case of an
+ error the return value is undefined
+
+ @return AddressOk if no error was encountered. Otherwise an
+ appropriate error code is returned.
+ */
+ KPIMUTILS_EXPORT
+ EmailParseResult splitAddress( const QString &address,
+ QString &displayName,
+ QString &addrSpec,
+ QString &comment );
+
+ /**
+ Validates an email address in the form of "Joe User" <joe@example.org>.
+ Returns AddressOk if no error was encountered. Otherwise an appropriate
+ error code is returned.
+
+ @param aStr a single email address,
+ example: Joe User (comment1) <joe.user@example.org>
+ @return AddressOk if no error was encountered. Otherwise an
+ appropriate error code is returned.
+ */
+ KPIMUTILS_EXPORT
+ EmailParseResult isValidAddress( const QString &aStr );
+
+ /**
+ Validates a list of email addresses, and also allow aliases and
+ distribution lists to be expanded before validation.
+
+ @param aStr a string containing a list of email addresses.
+ @param badAddr a string to hold the address that was faulty.
+
+ @return AddressOk if no error was encountered. Otherwise an
+ appropriate error code is returned.
+ */
+ KPIMUTILS_EXPORT
+ EmailParseResult isValidAddressList( const QString &aStr,
+ QString &badAddr );
+
+ /**
+ Translate the enum errorcodes from emailParseResult
+ into i18n'd strings that can be used for msg boxes.
+
+ @param errorCode an @em error code returned from one of the
+ email validation functions. Do not pass
+ AddressOk as a value, since that will yield
+ a misleading error message
+
+ @return human-readable and already translated message describing
+ the validation error.
+ */
+ KPIMUTILS_EXPORT
+ QString emailParseResultToString( EmailParseResult errorCode );
+
+ /**
+ Validates an email address in the form of joe@example.org.
+ Returns true if no error was encountered.
+ This method should be used when the input field should not
+ allow a "full" email address with comments and other special
+ cases that normally are valid in an email address.
+
+ @param aStr a single email address,
+ example: joe.user@example.org
+
+ @return true if no error was encountered.
+
+ @note This method differs from calling isValidAddress()
+ and checking that that returns AddressOk in two ways:
+ it is faster, and it does @em not allow fancy addresses.
+ */
+ KPIMUTILS_EXPORT
+ bool isValidSimpleAddress( const QString &aStr );
+
+ /**
+ Returns a i18n string to be used in msgboxes. This allows for error
+ messages to be the same across the board.
+
+ @return An i18n ready string for use in msgboxes.
+ */
+
+ KPIMUTILS_EXPORT
+ QString simpleEmailAddressErrorMsg();
+
+ /** @} */
+
+ /** @defgroup emailextraction Email Extraction Functions
+ @{
+ */
+
+ /**
+ Returns the pure email address (addr-spec in RFC2822) of the given address
+ (mailbox in RFC2822).
+
+ @param address an email address, e.g. "Joe User <joe.user@example.org>"
+ @return the addr-spec of @a address, i.e. joe.user@example.org
+ in the example
+ */
+ KPIMUTILS_EXPORT
+ QByteArray extractEmailAddress( const QByteArray & address );
+
+ /**
+ This is an overloaded member function, provided for convenience.
+ It behaves essentially like the above function.
+
+ Returns the pure email address (addr-spec in RFC2822) of the given
+ address (mailbox in RFC2822).
+
+ @param address an email address, e.g. "Joe User <joe.user@example.org>"
+ @return the addr-spec of @a address, i.e. joe.user@example.org
+ in the example
+ */
+ KPIMUTILS_EXPORT
+ QString extractEmailAddress( const QString & address );
+
+ /**
+ Returns the pure email address (addr-spec in RFC2822) of the first
+ email address of a list of addresses.
+
+ @param addresses an email address, e.g. "Joe User <joe.user@example.org>"
+ @return the addr-spec of @a addresses, i.e. joe.user@example.org
+ in the example
+ */
+ KPIMUTILS_EXPORT
+ QByteArray firstEmailAddress( const QByteArray & addresses );
+
+ /**
+ This is an overloaded member function, provided for convenience.
+ It behaves essentially like the above function.
+
+ Returns the pure email address (addr-spec in RFC2822) of the first
+ email address of a list of addresses.
+
+ @param addresses an email address, e.g. "Joe User <joe.user@example.org>"
+ @return the addr-spec of @a addresses, i.e. joe.user@example.org
+ in the example
+ */
+ KPIMUTILS_EXPORT
+ QString firstEmailAddress( const QString & addresses );
+
+ /**
+ Return email address and name from string.
+ Examples:
+ "Stefan Taferner <taferner@example.org>" returns "taferner@example.org"
+ and "Stefan Taferner". "joe@example.com" returns "joe@example.com"
+ and "". Note that this only returns the first address.
+
+ Also note that the return value is true if both the name and the
+ mail are not empty: this does NOT tell you if mail contains a
+ valid email address or just some rubbish.
+
+ @param aStr an email address, e.g "Joe User <joe.user@example.org>"
+ @param name only out: returns the displayname, "Joe User" in the example
+ @param mail only out: returns the email address "joe.user@example.org"
+ in the example
+
+ @return true if both name and email address are not empty
+ */
+ KPIMUTILS_EXPORT
+ bool extractEmailAddressAndName( const QString &aStr, QString &mail,
+ QString &name );
+
+ /**
+ Compare two email addresses. If matchName is false, it just checks
+ the email address, and returns true if this matches. If matchName
+ is true, both the name and the email must be the same.
+
+ @param email1 the first email address to use for comparison
+ @param email2 the second email address to use for comparison
+ @param matchName if set to true email address and displayname must match
+
+ @return true if the comparison matches true in all other cases
+ */
+ KPIMUTILS_EXPORT
+ bool compareEmail( const QString &email1, const QString &email2,
+ bool matchName );
+
+ /**
+ Returns a normalized address built from the given parts. The normalized
+ address is of one the following forms:
+ - displayName (comment) &lt;addrSpec&gt;
+ - displayName &lt;addrSpec&gt;
+ - comment &lt;addrSpec&gt;
+ - addrSpec
+
+ @param displayName the display name of the address
+ @param addrSpec the actual email address (addr-spec in RFC 2822)
+ @param comment a comment
+
+ @return a normalized address built from the given parts
+ */
+ KPIMUTILS_EXPORT
+ QString normalizedAddress( const QString &displayName,
+ const QString &addrSpec,
+ const QString &comment );
+
+ /** @} */
+
+ /** @defgroup emailidn Email IDN (punycode) handling
+ @{
+ */
+
+ /**
+ Decodes the punycode domain part of the given addr-spec if it's an IDN.
+
+ @param addrSpec a pure 7-bit email address (addr-spec in RFC2822)
+ @return the email address with Unicode domain
+ */
+ KPIMUTILS_EXPORT
+ QString fromIdn( const QString &addrSpec );
+
+ /**
+ Encodes the domain part of the given addr-spec in punycode if it's an IDN.
+
+ @param addrSpec a pure email address with Unicode domain
+ @return the email address with domain in punycode
+ */
+ KPIMUTILS_EXPORT
+ QString toIdn( const QString &addrSpec );
+
+ /**
+ Normalizes all email addresses in the given list and decodes all IDNs.
+
+ @param addresses a list of email addresses with punycoded IDNs
+ @return the email addresses in normalized form with Unicode IDNs
+ */
+ KPIMUTILS_EXPORT
+ QString normalizeAddressesAndDecodeIdn( const QString &addresses );
+
+ /**
+ Normalizes all email addresses in the given list and encodes all IDNs
+ in punycode.
+
+ @param str a list of email addresses
+ @return the email addresses in normalized form
+ */
+ KPIMUTILS_EXPORT
+ QString normalizeAddressesAndEncodeIdn( const QString &str );
+
+ /** @} */
+
+ /** @ingroup emailextraction
+
+ Add quote characters around the given string if it contains a
+ character that makes that necessary, in an email name, such as ",".
+
+ @param str a string that may need quoting
+ @return the string quoted if necessary
+ */
+ KPIMUTILS_EXPORT
+ QString quoteNameIfNecessary( const QString &str );
+
+ /**
+ * Creates a valid mailto: URL from the given mailbox.
+ * @param mailbox The mailbox, which means the display name and the address specification, for
+ * example "Thomas McGuire" <thomas@domain.com>. The display name is optional.
+ * @return a valid mailto: URL for the given mailbox.
+ * @since 4.4.3
+ */
+ KPIMUTILS_EXPORT
+ KUrl encodeMailtoUrl( const QString &mailbox );
+
+ /**
+ * Extracts the mailbox out of the mailto: URL.
+ * @param mailtoUrl the URL with the mailto protocol, which contains the mailbox to be extracted
+ * @return the mailbox, which means the display name and the address specification.
+ * @since 4.4.3
+ */
+ KPIMUTILS_EXPORT
+ QString decodeMailtoUrl( const KUrl &mailtoUrl );
+
+} // namespace
+
+#endif
diff --git a/kpimutils/emailvalidator.cpp b/kpimutils/emailvalidator.cpp
new file mode 100644
index 0000000..ed4b490
--- /dev/null
+++ b/kpimutils/emailvalidator.cpp
@@ -0,0 +1,50 @@
+/*
+ Copyright (C) 2010 Casey Link <unnamedrambler@gmail.com>
+ Copyright (C) 2009-2010 Klaralvdalens Datakonsult AB, a KDAB Group company <info@kdab.net>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#include "emailvalidator.h"
+#include "email.h"
+
+using namespace KPIMUtils;
+
+EmailValidator::EmailValidator( QObject *parent ) : QValidator( parent )
+{
+}
+
+QValidator::State EmailValidator::validate( QString &str, int &pos ) const
+{
+ Q_UNUSED( pos );
+
+ if ( KPIMUtils::isValidSimpleAddress( str ) ) {
+ return QValidator::Acceptable;
+ }
+
+ // we'll say any string that doesn't have whitespace
+ // is an intermediate email string
+ if ( QRegExp( "\\s" ).indexIn( str ) > -1 ) {
+ return QValidator::Invalid;
+ }
+
+ return QValidator::Intermediate;
+}
+
+void EmailValidator::fixup( QString &str ) const
+{
+ str = str.trimmed();
+}
diff --git a/kpimutils/emailvalidator.h b/kpimutils/emailvalidator.h
new file mode 100644
index 0000000..acf6d51
--- /dev/null
+++ b/kpimutils/emailvalidator.h
@@ -0,0 +1,46 @@
+/*
+ Copyright (C) 2010 Casey Link <unnamedrambler@gmail.com>
+ Copyright (C) 2009-2010 Klaralvdalens Datakonsult AB, a KDAB Group company <info@kdab.net>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#ifndef EMAILVALIDATOR_H
+#define EMAILVALIDATOR_H
+
+#include "kpimutils_export.h"
+
+#include <QtGui/QValidator>
+
+namespace KPIMUtils {
+
+/**
+ A validator that enforces correct email addresses.
+ @see KPIMUtils::isValidSimpleAddress
+*/
+class KPIMUTILS_EXPORT EmailValidator : public QValidator //krazy:exclude=dpointer
+{
+ public:
+ EmailValidator( QObject *parent );
+
+ virtual State validate( QString &str, int &pos ) const;
+
+ virtual void fixup( QString &str ) const;
+};
+
+}
+
+#endif
diff --git a/kpimutils/kfileio.cpp b/kpimutils/kfileio.cpp
new file mode 100644
index 0000000..25846bc
--- /dev/null
+++ b/kpimutils/kfileio.cpp
@@ -0,0 +1,350 @@
+/*
+ Copyright (c) 2005 Tom Albers <tomalbers@kde.nl>
+ Copyright (c) 1997-1999 Stefan Taferner <taferner@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#include "kfileio.h"
+#include "kpimutils_export.h"
+
+#include <KDebug>
+#include <KLocale>
+#include <KMessageBox>
+#include <KStandardGuiItem>
+#include <kde_file.h> //krazy:exclude=camelcase
+
+#include <QDir>
+#include <QByteArray>
+#include <QWidget>
+#include <QFile>
+#include <QFileInfo>
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <assert.h>
+
+namespace KPIMUtils {
+
+//-----------------------------------------------------------------------------
+static void msgDialog( const QString &msg )
+{
+ KMessageBox::sorry( 0, msg, i18n( "File I/O Error" ) );
+}
+
+//-----------------------------------------------------------------------------
+QByteArray kFileToByteArray( const QString &aFileName, bool aEnsureNL,
+ bool aVerbose )
+{
+ QByteArray result;
+ QFileInfo info( aFileName );
+ unsigned int readLen;
+ unsigned int len = info.size();
+ QFile file( aFileName );
+
+ //assert(aFileName!=0);
+ if( aFileName.isEmpty() ) {
+ return "";
+ }
+
+ if ( !info.exists() ) {
+ if ( aVerbose ) {
+ msgDialog( i18n( "The specified file does not exist:\n%1", aFileName ) );
+ }
+ return QByteArray();
+ }
+ if ( info.isDir() ) {
+ if ( aVerbose ) {
+ msgDialog( i18n( "This is a folder and not a file:\n%1", aFileName ) );
+ }
+ return QByteArray();
+ }
+ if ( !info.isReadable() ) {
+ if ( aVerbose ) {
+ msgDialog( i18n( "You do not have read permissions to the file:\n%1", aFileName ) );
+ }
+ return QByteArray();
+ }
+ if ( len <= 0 ) {
+ return QByteArray();
+ }
+
+ if ( !file.open( QIODevice::Unbuffered|QIODevice::ReadOnly ) ) {
+ if ( aVerbose ) {
+ switch( file.error() ) {
+ case QFile::ReadError:
+ msgDialog( i18n( "Could not read file:\n%1", aFileName ) );
+ break;
+ case QFile::OpenError:
+ msgDialog( i18n( "Could not open file:\n%1", aFileName ) );
+ break;
+ default:
+ msgDialog( i18n( "Error while reading file:\n%1", aFileName ) );
+ }
+ }
+ return QByteArray();
+ }
+
+ result.resize( len + int( aEnsureNL ) );
+ readLen = file.read( result.data(), len );
+ if ( aEnsureNL ) {
+ if ( result[readLen-1] != '\n' ) {
+ result[readLen++] = '\n';
+ len++;
+ } else {
+ result.truncate( len );
+ }
+ }
+
+ if ( readLen < len ) {
+ QString msg = i18np( "Could only read 1 byte of %2.",
+ "Could only read %1 bytes of %2.",
+ readLen, len );
+ msgDialog( msg );
+ result.truncate( readLen );
+ }
+
+ return result;
+}
+
+//-----------------------------------------------------------------------------
+bool kByteArrayToFile( const QByteArray &aBuffer, const QString &aFileName,
+ bool aAskIfExists, bool aBackup, bool aVerbose )
+{
+ // TODO: use KSaveFile
+ QFile file( aFileName );
+
+ //assert(aFileName!=0);
+ if( aFileName.isEmpty() ) {
+ return false;
+ }
+
+ if ( file.exists() ) {
+ if ( aAskIfExists ) {
+ QString str;
+ str = i18n( "File %1 exists.\nDo you want to replace it?", aFileName );
+ const int rc =
+ KMessageBox::warningContinueCancel( 0, str, i18n( "Save to File" ),
+ KGuiItem( i18n( "&Replace" ) ) );
+ if ( rc != KMessageBox::Continue ) {
+ return false;
+ }
+ }
+ if ( aBackup ) {
+ // make a backup copy
+ // TODO: use KSaveFile::backupFile()
+ QString bakName = aFileName;
+ bakName += '~';
+ QFile::remove(bakName);
+ if ( !QDir::current().rename( aFileName, bakName ) ) {
+ // failed to rename file
+ if ( !aVerbose ) {
+ return false;
+ }
+ const int rc =
+ KMessageBox::warningContinueCancel(
+ 0,
+ i18n( "Failed to make a backup copy of %1.\nContinue anyway?", aFileName ),
+ i18n( "Save to File" ), KStandardGuiItem::save() );
+
+ if ( rc != KMessageBox::Continue ) {
+ return false;
+ }
+ }
+ }
+ }
+
+ if ( !file.open( QIODevice::Unbuffered|QIODevice::WriteOnly|QIODevice::Truncate ) ) {
+ if ( aVerbose ) {
+ switch( file.error() ) {
+ case QFile::WriteError:
+ msgDialog( i18n( "Could not write to file:\n%1", aFileName ) );
+ break;
+ case QFile::OpenError:
+ msgDialog( i18n( "Could not open file for writing:\n%1", aFileName ) );
+ break;
+ default:
+ msgDialog( i18n( "Error while writing file:\n%1", aFileName ) );
+ }
+ }
+ return false;
+ }
+
+ const int writeLen = file.write( aBuffer.data(), aBuffer.size() );
+
+ if ( writeLen < 0 ) {
+ if ( aVerbose ) {
+ msgDialog( i18n( "Could not write to file:\n%1", aFileName ) );
+ }
+ return false;
+ } else if ( writeLen < aBuffer.size() ) {
+ QString msg = i18np( "Could only write 1 byte of %2.",
+ "Could only write %1 bytes of %2.",
+ writeLen, aBuffer.size() );
+ if ( aVerbose ) {
+ msgDialog( msg );
+ }
+ return false;
+ }
+
+ return true;
+}
+
+QString checkAndCorrectPermissionsIfPossible( const QString &toCheck,
+ const bool recursive,
+ const bool wantItReadable,
+ const bool wantItWritable )
+{
+ // First we have to find out which type the toCheck is. This can be
+ // a directory (follow if recursive) or a file (check permissions).
+ // Symlinks are followed as expected.
+ QFileInfo fiToCheck(toCheck);
+ fiToCheck.setCaching(false);
+ QByteArray toCheckEnc = QFile::encodeName( toCheck );
+ QString error;
+ KDE_struct_stat statbuffer;
+
+ if ( !fiToCheck.exists() ) {
+ error.append( i18n( "%1 does not exist", toCheck ) + '\n' );
+ }
+
+ // check the access bit of a folder.
+ if ( fiToCheck.isDir() ) {
+ if ( KDE_stat( toCheckEnc,&statbuffer ) != 0 ) {
+ kDebug() << "wantItA: Can't read perms of" << toCheck;
+ }
+ QDir g( toCheck );
+ if ( !g.isReadable() ) {
+ if ( chmod( toCheckEnc, statbuffer.st_mode + S_IXUSR ) != 0 ) {
+ error.append( i18n( "%1 is not accessible and that is "
+ "unchangeable.", toCheck ) + '\n' );
+ } else {
+ kDebug() << "Changed access bit for" << toCheck;
+ }
+ }
+ }
+
+ // For each file or folder we can check if the file is readable
+ // and writable, as requested.
+ if ( fiToCheck.isFile() || fiToCheck.isDir() ) {
+
+ if ( !fiToCheck.isReadable() && wantItReadable ) {
+ // Get the current permissions. No need to do anything with an
+ // error, it will het added to errors anyhow, later on.
+ if ( KDE_stat( toCheckEnc,&statbuffer ) != 0 ) {
+ kDebug() << "wantItR: Can't read perms of" << toCheck;
+ }
+
+ // Lets try changing it.
+ if ( chmod( toCheckEnc, statbuffer.st_mode + S_IRUSR ) != 0 ) {
+ error.append( i18n( "%1 is not readable and that is unchangeable.",
+ toCheck ) + '\n' );
+ } else {
+ kDebug() << "Changed the read bit for" << toCheck;
+ }
+ }
+
+ if ( !fiToCheck.isWritable() && wantItWritable ) {
+ // Gets the current persmissions. Needed because it can be changed
+ // curing previous operation.
+ if ( KDE_stat( toCheckEnc,&statbuffer ) != 0 ) {
+ kDebug() << "wantItW: Can't read perms of" << toCheck;
+ }
+
+ // Lets try changing it.
+ if ( chmod ( toCheckEnc, statbuffer.st_mode + S_IWUSR ) != 0 ) {
+ error.append( i18n( "%1 is not writable and that is unchangeable.", toCheck ) + '\n' );
+ } else {
+ kDebug() << "Changed the write bit for" << toCheck;
+ }
+ }
+ }
+
+ // If it is a folder and recursive is true, then we check the contents of
+ // the folder.
+ if ( fiToCheck.isDir() && recursive ){
+ QDir g( toCheck );
+ // First check if the folder is readable for us. If not, we get
+ // some ugly crashes.
+ if ( !g.isReadable() ){
+ error.append( i18n( "Folder %1 is inaccessible.", toCheck ) + '\n' );
+ } else {
+ foreach ( const QFileInfo &fi, g.entryInfoList() ) {
+ QString newToCheck = toCheck + '/' + fi.fileName();
+ QFileInfo fiNewToCheck(newToCheck);
+ if ( fi.fileName() != "." && fi.fileName() != ".." ) {
+ error.append (
+ checkAndCorrectPermissionsIfPossible( newToCheck, recursive,
+ wantItReadable, wantItWritable ) );
+ }
+ }
+ }
+ }
+ return error;
+}
+
+bool checkAndCorrectPermissionsIfPossibleWithErrorHandling( QWidget *parent,
+ const QString &toCheck,
+ const bool recursive,
+ const bool wantItReadable,
+ const bool wantItWritable )
+{
+ QString error =
+ checkAndCorrectPermissionsIfPossible( toCheck, recursive, wantItReadable, wantItWritable );
+
+ // There is no KMessageBox with Retry, Cancel and Details.
+ // so, I can't provide a functionality to recheck. So it now
+ // it is just a warning.
+ if ( !error.isEmpty() ) {
+ kDebug() << "checkPermissions found:" << error;
+ KMessageBox::detailedSorry( parent,
+ i18n( "Some files or folders do not have the "
+ "necessary permissions, please correct "
+ "them manually." ),
+ error, i18n( "Permissions Check" ), 0 );
+ return false;
+ } else {
+ return true;
+ }
+}
+
+bool removeDirAndContentsRecursively( const QString & path )
+{
+ bool success = true;
+
+ QDir d;
+ d.setPath( path );
+ d.setFilter( QDir::Files | QDir::Dirs | QDir::Hidden | QDir::NoSymLinks );
+
+ QFileInfoList list = d.entryInfoList();
+
+ Q_FOREACH ( const QFileInfo &fi, list ) {
+ if ( fi.isDir() ) {
+ if ( fi.fileName() != "." && fi.fileName() != ".." ) {
+ success = success && removeDirAndContentsRecursively( fi.absoluteFilePath() );
+ }
+ } else {
+ success = success && d.remove( fi.absoluteFilePath() );
+ }
+ }
+
+ if ( success ) {
+ success = success && d.rmdir( path ); // nuke ourselves, we should be empty now
+ }
+ return success;
+}
+
+}
diff --git a/kpimutils/kfileio.h b/kpimutils/kfileio.h
new file mode 100644
index 0000000..6f16b4b
--- /dev/null
+++ b/kpimutils/kfileio.h
@@ -0,0 +1,137 @@
+/*
+ Copyright (c) 2005 Tom Albers <tomalbers@kde.nl>
+ Copyright (c) 1997-1999 Stefan Taferner <taferner@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#ifndef KPIMUTILS_KFILEIO_H
+#define KPIMUTILS_KFILEIO_H
+
+#include "kpimutils_export.h"
+
+class QByteArray;
+class QString;
+class QWidget;
+
+namespace KPIMUtils {
+
+/**
+ Loads the file with the given filename. Optionally, you can force the data
+ to end with a newline character. Moreover, you can suppress warnings.
+
+ @param fileName Name of the file that should be loaded.
+ @param ensureNewline If true, then the data will always have a trailing
+ newline. Defaults to true.
+ @param withDialogs If false, then no warning dialogs are shown in case of
+ problems. Defaults to true.
+
+ @return The contents of the file or an empty QByteArray if loading failed.
+*/
+ KPIMUTILS_EXPORT QByteArray kFileToByteArray( const QString & fileName,
+ bool ensureNewline = true,
+ bool withDialogs = true );
+
+/**
+ Writes the contents of @p buffer to the file with the given filename.
+
+ @param buffer The data you want to write to the file.
+ @param fileName The output file name
+ @param askIfExists If true, then you will be asked before an existing file
+ is overwritten. If false, then an existing file is
+ overwritten without warning.
+ @param createBackup If true, then a backup of existing files will be
+ created. Otherwise, no backup will be made.
+ @param withDialogs If true, then you will be warned in case of problems.
+ Otherwise, no warnings will be issued.
+
+ @return True if writing the data to the file succeeded.
+ @return False if writing the data to the file failed.
+*/
+KPIMUTILS_EXPORT bool kByteArrayToFile( const QByteArray & buffer,
+ const QString & fileName,
+ bool askIfExists = false,
+ bool createBackup = true,
+ bool withDialogs = true );
+
+/**
+ Checks and corrects the permissions of a file or folder, and if requested
+ all files and folders below. It gives back a list of files which do not
+ have the right permissions. This list can be used to show to the user.
+
+ @param toCheck The file or folder of which the permissions should
+ be checked.
+ @param recursive Set to true, it will check the contents of a folder
+ for the permissions recursively. If false only
+ toCheck will be checked.
+ @param wantItReadable Set to true, it will check for read permissions.
+ If the read permissions are not available, there will
+ be a attempt to correct this.
+ @param wantItWritable Set to true, it will check for write permissions.
+ If the write permissions are not available, there
+ will be a attempt to correct this.
+ @return It will return a string with all files and folders which do not
+ have the right permissions. If empty, then all permissions are ok.
+*/
+KPIMUTILS_EXPORT QString checkAndCorrectPermissionsIfPossible( const QString &toCheck,
+ const bool recursive,
+ const bool wantItReadable,
+ const bool wantItWritable );
+
+/**
+ Checks and corrects the permissions of a file or folder, and if requested all
+ files and folders below. If the permissions are not ok, it tries to correct
+ them. If that fails then a warning with detailled information is given.
+
+ @param parent If parent is 0, then the message box becomes an
+ application-global modal dialog box. If parent
+ is a widget, the message box becomes modal
+ relative to parent.
+ @param toCheck The file or folder of which the permissions should
+ be checked.
+ @param recursive Set to true, it will check the contents of a folder
+ for the permissions recursively. If false only
+ toCheck will be checked.
+ @param wantItReadable Set to true, it will check for read permissions.
+ If the read permissions are not available, there will
+ be a attempt to correct this.
+ @param wantItWritable Set to true, it will check for write permissions.
+ If the write permissions are not available, there
+ will be a attempt to correct this.
+ @return It will return true if all permissions in the end are ok. If false
+ then the permissions are not ok and it was not possible to correct
+ all errors.
+*/
+KPIMUTILS_EXPORT bool checkAndCorrectPermissionsIfPossibleWithErrorHandling( QWidget *parent,
+ const QString &toCheck,
+ const bool recursive,
+ const bool wantItReadable,
+ const bool wantItWritable );
+
+/**
+ * Removed a directory on the local filesystem whether it is empty or not. All
+ * contents are irredeemably lost.
+ *
+ * @param path An absolute or relative path to the directory to be
+ * removed.
+ *
+ * @return Success or failure.
+ */
+KPIMUTILS_EXPORT bool removeDirAndContentsRecursively( const QString & path );
+
+}
+
+#endif
diff --git a/kpimutils/kpimutils_export.h b/kpimutils/kpimutils_export.h
new file mode 100644
index 0000000..aaaf505
--- /dev/null
+++ b/kpimutils/kpimutils_export.h
@@ -0,0 +1,39 @@
+/*
+ This file is part of the kpimutils library.
+ Copyright (c) 2006 Allen Winter <winter@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KPIMUTILS_EXPORT_H
+#define KPIMUTILS_EXPORT_H
+
+#include <kdemacros.h>
+
+#ifndef KPIMUTILS_EXPORT
+# if defined(KDEPIM_STATIC_LIBS)
+ /* No export/import for static libraries */
+# define KPIMUTILS_EXPORT
+# elif defined(MAKE_KPIMUTILS_LIB)
+ /* We are building this library */
+# define KPIMUTILS_EXPORT KDE_EXPORT
+# else
+ /* We are using this library */
+# define KPIMUTILS_EXPORT KDE_IMPORT
+# endif
+#endif
+
+#endif
diff --git a/kpimutils/linklocator.cpp b/kpimutils/linklocator.cpp
new file mode 100644
index 0000000..e6e5eb6
--- /dev/null
+++ b/kpimutils/linklocator.cpp
@@ -0,0 +1,480 @@
+/*
+ Copyright (c) 2002 Dave Corrie <kde@davecorrie.com>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+/**
+ @file
+ This file is part of the KDEPIM Utilities library and provides the
+ LinkLocator class.
+
+ @brief
+ Identifies URLs and email addresses embedded in plaintext.
+
+ @author Dave Corrie \<kde@davecorrie.com\>
+*/
+#include "linklocator.h"
+
+#include <KEmoticons>
+
+#include <QtCore/QCoreApplication>
+#include <QtCore/QFile>
+#include <QtCore/QRegExp>
+#include <QtGui/QTextDocument>
+
+#include <climits>
+
+using namespace KPIMUtils;
+
+/**
+ Private class that helps to provide binary compatibility between releases.
+ @internal
+*/
+//@cond PRIVATE
+class KPIMUtils::LinkLocator::Private
+{
+ public:
+ int mMaxUrlLen;
+ int mMaxAddressLen;
+};
+//@endcond
+
+// Use a static for this as calls to the KEmoticons constructor are expensive.
+K_GLOBAL_STATIC( KEmoticons, sEmoticons )
+
+LinkLocator::LinkLocator( const QString &text, int pos )
+ : mText( text ), mPos( pos ), d( new KPIMUtils::LinkLocator::Private )
+{
+ d->mMaxUrlLen = 4096;
+ d->mMaxAddressLen = 255;
+
+ // If you change either of the above values for maxUrlLen or
+ // maxAddressLen, then please also update the documentation for
+ // setMaxUrlLen()/setMaxAddressLen() in the header file AND the
+ // default values used for the maxUrlLen/maxAddressLen parameters
+ // of convertToHtml().
+}
+
+LinkLocator::~LinkLocator()
+{
+ delete d;
+}
+
+void LinkLocator::setMaxUrlLen( int length )
+{
+ d->mMaxUrlLen = length;
+}
+
+int LinkLocator::maxUrlLen() const
+{
+ return d->mMaxUrlLen;
+}
+
+void LinkLocator::setMaxAddressLen( int length )
+{
+ d->mMaxAddressLen = length;
+}
+
+int LinkLocator::maxAddressLen() const
+{
+ return d->mMaxAddressLen;
+}
+
+QString LinkLocator::getUrl()
+{
+ QString url;
+ if ( atUrl() ) {
+ // NOTE: see http://tools.ietf.org/html/rfc3986#appendix-A and especially appendix-C
+ // Appendix-C mainly says, that when extracting URLs from plain text, line breaks shall
+ // be allowed and should be ignored when the URI is extracted.
+
+ // This implementation follows this recommendation and
+ // allows the URL to be enclosed within different kind of brackets/quotes
+ // If an URL is enclosed, whitespace characters are allowed and removed, otherwise
+ // the URL ends with the first whitespace
+ // Also, if the URL is enclosed in brackets, the URL itself is not allowed
+ // to contain the closing bracket, as this would be detected as the end of the URL
+
+ QChar beforeUrl, afterUrl;
+
+ // detect if the url has been surrounded by brackets or quotes
+ if ( mPos > 0 ) {
+ beforeUrl = mText[mPos - 1];
+
+ if ( beforeUrl == '(' ) {
+ afterUrl = ')';
+ } else if ( beforeUrl == '[' ) {
+ afterUrl = ']';
+ } else if ( beforeUrl == '<' ) {
+ afterUrl = '>';
+ } else if ( beforeUrl == '>' ) { // for e.g. <link>http://.....</link>
+ afterUrl = '<';
+ } else if ( beforeUrl == '"' ) {
+ afterUrl = '"';
+ }
+ }
+
+ url.reserve( maxUrlLen() ); // avoid allocs
+ int start = mPos;
+ while ( ( mPos < (int)mText.length() ) &&
+ ( mText[mPos].isPrint() || mText[mPos].isSpace() ) &&
+ ( ( afterUrl.isNull() && !mText[mPos].isSpace() ) ||
+ ( !afterUrl.isNull() && mText[mPos] != afterUrl ) ) ) {
+ if ( !mText[mPos].isSpace() ) { // skip whitespace
+ url.append( mText[mPos] );
+ if ( url.length() > maxUrlLen() ) {
+ break;
+ }
+ }
+
+ mPos++;
+ }
+
+ if ( isEmptyUrl(url) || ( url.length() > maxUrlLen() ) ) {
+ mPos = start;
+ url = "";
+ } else {
+ --mPos;
+ }
+ }
+
+ // HACK: This is actually against the RFC. However, most people don't properly escape the URL in
+ // their text with "" or <>. That leads to people writing an url, followed immediatley by
+ // a dot to finish the sentence. That would lead the parser to include the dot in the url,
+ // even though that is not wanted. So work around that here.
+ // Most real-life URLs hopefully don't end with dots or commas.
+ if ( url.length() > 1 ) {
+ QList<QChar> wordBoundaries;
+ wordBoundaries << '.' << ',' << ':' << '!' << '?';
+ if ( wordBoundaries.contains( url.at( url.length() - 1 ) ) ) {
+ url.chop( 1 );
+ --mPos;
+ }
+ }
+
+ return url;
+}
+
+// keep this in sync with KMMainWin::slotUrlClicked()
+bool LinkLocator::atUrl() const
+{
+ // the following characters are allowed in a dot-atom (RFC 2822):
+ // a-z A-Z 0-9 . ! # $ % & ' * + - / = ? ^ _ ` { | } ~
+ const QString allowedSpecialChars = QString( ".!#$%&'*+-/=?^_`{|}~" );
+
+ // the character directly before the URL must not be a letter, a number or
+ // any other character allowed in a dot-atom (RFC 2822).
+ if ( ( mPos > 0 ) &&
+ ( mText[mPos-1].isLetterOrNumber() ||
+ ( allowedSpecialChars.indexOf( mText[mPos-1] ) != -1 ) ) ) {
+ return false;
+ }
+
+ QChar ch = mText[mPos];
+ return
+ ( ch == 'h' && ( mText.mid( mPos, 7 ) == "http://" ||
+ mText.mid( mPos, 8 ) == "https://" ) ) ||
+ ( ch == 'v' && mText.mid( mPos, 6 ) == "vnc://" ) ||
+ ( ch == 'f' && ( mText.mid( mPos, 7 ) == "fish://" ||
+ mText.mid( mPos, 6 ) == "ftp://" ||
+ mText.mid( mPos, 7 ) == "ftps://" ) ) ||
+ ( ch == 's' && ( mText.mid( mPos, 7 ) == "sftp://" ||
+ mText.mid( mPos, 6 ) == "smb://" ) ) ||
+ ( ch == 'm' && mText.mid( mPos, 7 ) == "mailto:" ) ||
+ ( ch == 'w' && mText.mid( mPos, 4 ) == "www." ) ||
+ ( ch == 'f' && ( mText.mid( mPos, 4 ) == "ftp." ||
+ mText.mid( mPos, 7 ) == "file://" ) ) ||
+ ( ch == 'n' && mText.mid( mPos, 5 ) == "news:" );
+}
+
+bool LinkLocator::isEmptyUrl( const QString &url ) const
+{
+ return url.isEmpty() ||
+ url == "http://" ||
+ url == "https://" ||
+ url == "fish://" ||
+ url == "ftp://" ||
+ url == "ftps://" ||
+ url == "sftp://" ||
+ url == "smb://" ||
+ url == "vnc://" ||
+ url == "mailto" ||
+ url == "www" ||
+ url == "ftp" ||
+ url == "news" ||
+ url == "news://";
+}
+
+QString LinkLocator::getEmailAddress()
+{
+ QString address;
+
+ if ( mText[mPos] == '@' ) {
+ // the following characters are allowed in a dot-atom (RFC 2822):
+ // a-z A-Z 0-9 . ! # $ % & ' * + - / = ? ^ _ ` { | } ~
+ const QString allowedSpecialChars = QString( ".!#$%&'*+-/=?^_`{|}~" );
+
+ // determine the local part of the email address
+ int start = mPos - 1;
+ while ( start >= 0 && mText[start].unicode() < 128 &&
+ ( mText[start].isLetterOrNumber() ||
+ mText[start] == '@' || // allow @ to find invalid email addresses
+ allowedSpecialChars.indexOf( mText[start] ) != -1 ) ) {
+ if ( mText[start] == '@' ) {
+ return QString(); // local part contains '@' -> no email address
+ }
+ --start;
+ }
+ ++start;
+ // we assume that an email address starts with a letter or a digit
+ while ( ( start < mPos ) && !mText[start].isLetterOrNumber() ) {
+ ++start;
+ }
+ if ( start == mPos ) {
+ return QString(); // local part is empty -> no email address
+ }
+
+ // determine the domain part of the email address
+ int dotPos = INT_MAX;
+ int end = mPos + 1;
+ while ( end < (int)mText.length() &&
+ ( mText[end].isLetterOrNumber() ||
+ mText[end] == '@' || // allow @ to find invalid email addresses
+ mText[end] == '.' ||
+ mText[end] == '-' ) ) {
+ if ( mText[end] == '@' ) {
+ return QString(); // domain part contains '@' -> no email address
+ }
+ if ( mText[end] == '.' ) {
+ dotPos = qMin( dotPos, end ); // remember index of first dot in domain
+ }
+ ++end;
+ }
+ // we assume that an email address ends with a letter or a digit
+ while ( ( end > mPos ) && !mText[end - 1].isLetterOrNumber() ) {
+ --end;
+ }
+ if ( end == mPos ) {
+ return QString(); // domain part is empty -> no email address
+ }
+ if ( dotPos >= end ) {
+ return QString(); // domain part doesn't contain a dot
+ }
+
+ if ( end - start > maxAddressLen() ) {
+ return QString(); // too long -> most likely no email address
+ }
+ address = mText.mid( start, end - start );
+
+ mPos = end - 1;
+ }
+ return address;
+}
+
+QString LinkLocator::convertToHtml( const QString &plainText, int flags,
+ int maxUrlLen, int maxAddressLen )
+{
+ LinkLocator locator( plainText );
+ locator.setMaxUrlLen( maxUrlLen );
+ locator.setMaxAddressLen( maxAddressLen );
+
+ QString str;
+ QString result( (QChar*)0, (int)locator.mText.length() * 2 );
+ QChar ch;
+ int x;
+ bool startOfLine = true;
+ QString emoticon;
+
+ for ( locator.mPos = 0, x = 0; locator.mPos < (int)locator.mText.length();
+ locator.mPos++, x++ ) {
+ ch = locator.mText[locator.mPos];
+ if ( flags & PreserveSpaces ) {
+ if ( ch == ' ' ) {
+ if ( locator.mPos + 1 < locator.mText.length() ) {
+ if ( locator.mText[locator.mPos + 1] != ' ' ) {
+
+ // A single space, make it breaking if not at the start or end of the line
+ const bool endOfLine = locator.mText[locator.mPos + 1] == '\n';
+ if ( !startOfLine && !endOfLine ) {
+ result += ' ';
+ } else {
+ result += "&nbsp;";
+ }
+ } else {
+
+ // Whitespace of more than one space, make it all non-breaking
+ while ( locator.mPos < locator.mText.length() && locator.mText[locator.mPos] == ' ' ) {
+ result += "&nbsp;";
+ locator.mPos++;
+ x++;
+ }
+
+ // We incremented once to often, undo that
+ locator.mPos--;
+ x--;
+ }
+ } else {
+ // Last space in the text, it is non-breaking
+ result += "&nbsp;";
+ }
+
+ if ( startOfLine ) {
+ startOfLine = false;
+ }
+ continue;
+ } else if ( ch == '\t' ) {
+ do
+ {
+ result += "&nbsp;";
+ x++;
+ }
+ while ( ( x & 7 ) != 0 );
+ x--;
+ startOfLine = false;
+ continue;
+ }
+ }
+ if ( ch == '\n' ) {
+ result += "<br />\n"; // Keep the \n, so apps can figure out the quoting levels correctly.
+ startOfLine = true;
+ x = -1;
+ continue;
+ }
+
+ startOfLine = false;
+ if ( ch == '&' ) {
+ result += "&amp;";
+ } else if ( ch == '"' ) {
+ result += "&quot;";
+ } else if ( ch == '<' ) {
+ result += "&lt;";
+ } else if ( ch == '>' ) {
+ result += "&gt;";
+ } else {
+ const int start = locator.mPos;
+ if ( !( flags & IgnoreUrls ) ) {
+ str = locator.getUrl();
+ if ( !str.isEmpty() ) {
+ QString hyperlink;
+ if ( str.left( 4 ) == "www." ) {
+ hyperlink = "http://" + str;
+ } else if ( str.left( 4 ) == "ftp." ) {
+ hyperlink = "ftp://" + str;
+ } else {
+ hyperlink = str;
+ }
+
+ result += "<a href=\"" + hyperlink + "\">" + Qt::escape( str ) + "</a>";
+ x += locator.mPos - start;
+ continue;
+ }
+ str = locator.getEmailAddress();
+ if ( !str.isEmpty() ) {
+ // len is the length of the local part
+ int len = str.indexOf( '@' );
+ QString localPart = str.left( len );
+
+ // remove the local part from the result (as '&'s have been expanded to
+ // &amp; we have to take care of the 4 additional characters per '&')
+ result.truncate( result.length() -
+ len - ( localPart.count( '&' ) * 4 ) );
+ x -= len;
+
+ result += "<a href=\"mailto:" + str + "\">" + str + "</a>";
+ x += str.length() - 1;
+ continue;
+ }
+ }
+ if ( flags & HighlightText ) {
+ str = locator.highlightedText();
+ if ( !str.isEmpty() ) {
+ result += str;
+ x += locator.mPos - start;
+ continue;
+ }
+ }
+ result += ch;
+ }
+ }
+
+ if ( flags & ReplaceSmileys ) {
+ QStringList exclude;
+ exclude << "(c)" << "(C)" << "&gt;:-(" << "&gt;:(" << "(B)" << "(b)" << "(P)" << "(p)";
+ exclude << "(O)" << "(o)" << "(D)" << "(d)" << "(E)" << "(e)" << "(K)" << "(k)";
+ exclude << "(I)" << "(i)" << "(L)" << "(l)" << "(8)" << "(T)" << "(t)" << "(G)";
+ exclude << "(g)" << "(F)" << "(f)" << "(H)";
+ exclude << "8)" << "(N)" << "(n)" << "(Y)" << "(y)" << "(U)" << "(u)" << "(W)" << "(w)";
+ static QString cachedEmoticonsThemeName;
+ if ( cachedEmoticonsThemeName.isEmpty() ) {
+ cachedEmoticonsThemeName = KEmoticons::currentThemeName();
+ }
+ result =
+ sEmoticons->theme( cachedEmoticonsThemeName ).parseEmoticons(
+ result, KEmoticonsTheme::StrictParse | KEmoticonsTheme::SkipHTML, exclude );
+ }
+
+ return result;
+}
+
+QString LinkLocator::pngToDataUrl( const QString &iconPath )
+{
+ if ( iconPath.isEmpty() ) {
+ return QString();
+ }
+
+ QFile pngFile( iconPath );
+ if ( !pngFile.open( QIODevice::ReadOnly | QIODevice::Unbuffered ) ) {
+ return QString();
+ }
+
+ QByteArray ba = pngFile.readAll();
+ pngFile.close();
+ return QString::fromLatin1( "data:image/png;base64,%1" ).arg( ba.toBase64().constData() );
+}
+
+QString LinkLocator::highlightedText()
+{
+ // formating symbols must be prepended with a whitespace
+ if ( ( mPos > 0 ) && !mText[mPos-1].isSpace() ) {
+ return QString();
+ }
+
+ const QChar ch = mText[mPos];
+ if ( ch != '/' && ch != '*' && ch != '_' ) {
+ return QString();
+ }
+
+ QRegExp re =
+ QRegExp( QString( "\\%1((\\w+)([\\s-']\\w+)*( ?[,.:\\?!;])?)\\%2" ).arg( ch ).arg( ch ) );
+ re.setMinimal(true);
+ if ( re.indexIn( mText, mPos ) == mPos ) {
+ int length = re.matchedLength();
+ // there must be a whitespace after the closing formating symbol
+ if ( mPos + length < mText.length() && !mText[mPos + length].isSpace() ) {
+ return QString();
+ }
+ mPos += length - 1;
+ switch ( ch.toLatin1() ) {
+ case '*':
+ return "<b>" + re.cap( 1 ) + "</b>";
+ case '_':
+ return "<u>" + re.cap( 1 ) + "</u>";
+ case '/':
+ return "<i>" + re.cap( 1 ) + "</i>";
+ }
+ }
+ return QString();
+}
diff --git a/kpimutils/linklocator.h b/kpimutils/linklocator.h
new file mode 100644
index 0000000..3049397
--- /dev/null
+++ b/kpimutils/linklocator.h
@@ -0,0 +1,189 @@
+/*
+ Copyright (c) 2002 Dave Corrie <kde@davecorrie.com>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+/**
+ @file
+ This file is part of the KDEPIM Utilities library and provides the
+ LinkLocator class.
+
+ @brief
+ Identifies URLs and email addresses embedded in plaintext.
+
+ @author Dave Corrie \<kde@davecorrie.com\>
+*/
+#ifndef KPIMUTILS_LINKLOCATOR_H
+#define KPIMUTILS_LINKLOCATOR_H
+
+#include "kpimutils_export.h"
+
+#include <QtCore/QString>
+
+namespace KPIMUtils {
+
+/**
+ LinkLocator assists in identifying sections of text that can usefully
+ be converted in hyperlinks in HTML. It is intended to be used in two ways:
+ either by calling convertToHtml() to convert a plaintext string into HTML,
+ or to be derived from where more control is needed.
+
+ please note that you are responsible for handling the links. That means you
+ should not execute the link directly but instead open it for example. See
+ the KRun documentation about this parameter if applicable.
+*/
+class KPIMUTILS_EXPORT LinkLocator
+{
+ public:
+ /**
+ Constructs a LinkLocator that will search a plaintext string
+ from a given starting point.
+
+ @param text The string in which to search.
+ @param pos An index into 'text' from where the search should begin.
+ */
+ explicit LinkLocator( const QString &text, int pos = 0 );
+
+ /**
+ * Destructor.
+ */
+ ~LinkLocator();
+
+ /**
+ Sets the maximum length of URLs that will be matched by getUrl().
+ By default, this is set to 4096 characters. The reason for this limit
+ is that there may be possible security implications in handling URLs of
+ unlimited length.
+ @see maxUrlLen()
+
+ @param length A new maximum length of URLs that will be matched by getUrl().
+ */
+ void setMaxUrlLen( int length );
+
+ /**
+ Returns the current limit on the maximum length of a URL.
+
+ @see setMaxUrlLen().
+ */
+ int maxUrlLen() const;
+
+ /**
+ Sets the maximum length of email addresses that will be matched by
+ getEmailAddress(). By default, this is set to 255 characters. The
+ reason for this limit is that there may be possible security implications
+ in handling addresses of unlimited length.
+ @see maxAddressLen().
+
+ @param length The new maximum length of email addresses that will be
+ matched by getEmailAddress().
+ */
+ void setMaxAddressLen( int length );
+
+ /**
+ Returns the current limit on the maximum length of an email address.
+ @see setMaxAddressLen().
+ */
+ int maxAddressLen() const;
+
+ /**
+ Attempts to grab a URL starting at the current scan position.
+ If there is no URL at the current scan position, then an empty
+ string is returned. If a URL is found, the current scan position
+ is set to the index of the last character in the URL.
+
+ @return The URL at the current scan position, or an empty string.
+ */
+ QString getUrl();
+
+ /**
+ Attempts to grab an email address. If there is an @ symbol at the
+ current scan position, then the text will be searched both backwards
+ and forwards to find the email address. If there is no @ symbol at
+ the current scan position, an empty string is returned. If an address
+ is found, then the current scan position is set to the index of the
+ last character in the address.
+
+ @return The email address at the current scan position, or an empty string.
+ */
+ QString getEmailAddress();
+
+ /**
+ Converts plaintext into html. The following characters are converted
+ to HTML entities: & " < >. Newlines are also preserved.
+
+ @param plainText The text to be converted into HTML.
+ @param flags The flags to consider when processing plainText.
+ Currently supported flags are:
+ - PreserveSpaces, preserves the appearance of sequences
+ of space and tab characters in the resulting HTML.
+ - ReplaceSmileys, replace text smileys with
+ emoticon images.
+ - IgnoreUrls, doesn't parse any URLs.
+ - HighlightText, interprets text highlighting
+ markup like *bold*, _underlined_ and /italic/.
+ @param maxUrlLen The maximum length of permitted URLs. (@see maxUrlLen().)
+ @param maxAddressLen The maximum length of permitted email addresses.
+ (@see maxAddressLen().)
+ @return An HTML version of the text supplied in the 'plainText'
+ parameter, suitable for inclusion in the BODY of an HTML document.
+ */
+ static QString convertToHtml( const QString &plainText, int flags = 0,
+ int maxUrlLen = 4096, int maxAddressLen = 255 );
+
+ static const int PreserveSpaces = 0x01;
+ static const int ReplaceSmileys = 0x02;
+ static const int IgnoreUrls = 0x04;
+ static const int HighlightText = 0x08;
+
+ /**
+ Embeds the given PNG image into a data URL.
+ @param iconPath path to the PNG image
+ @return A data URL, QString() if the image could not be read.
+ */
+ static QString pngToDataUrl( const QString & iconPath );
+
+ protected:
+ /**
+ The plaintext string being scanned for URLs and email addresses.
+ */
+ QString mText;
+
+ /**
+ The current scan position.
+ */
+ int mPos;
+
+ bool atUrl() const;
+ bool isEmptyUrl( const QString &url ) const;
+
+ /**
+ Highlight text according to *bold*, /italic/ and _underlined_ markup.
+ @return A HTML string.
+ */
+ QString highlightedText();
+
+ private:
+ private:
+ //@cond PRIVATE
+ class Private;
+ Private *const d;
+ //@endcond
+
+};
+
+}
+
+#endif
diff --git a/kpimutils/networkaccesshelper.h b/kpimutils/networkaccesshelper.h
new file mode 100644
index 0000000..4ae3759
--- /dev/null
+++ b/kpimutils/networkaccesshelper.h
@@ -0,0 +1,61 @@
+/*
+ Copyright (c) 2010 Kevin Funk <kevin.funk@kdab.com>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+
+*/
+
+#ifndef NETWORKACCESSHELPER_H
+#define NETWORKACCESSHELPER_H
+
+#include "kpimutils_export.h"
+
+#include <QtCore/QObject>
+
+namespace KPIMUtils {
+
+class NetworkAccessHelperPrivate;
+
+/**
+ * Wrapper around Solid::NetworkingControl (komobranch)
+ *
+ * This can be used for all platforms, but is just implemented for Windows CE
+ * Does nothing on other platforms
+ *
+ * Basically this is to prevent ifdef'ing all the classes that make use of
+ * the NetworkingControl class that is only available in kdelibs from the
+ * komobranch at the moment
+ */
+class KPIMUTILS_EXPORT NetworkAccessHelper
+ : public QObject
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE( NetworkAccessHelper )
+
+ public:
+ explicit NetworkAccessHelper( QObject *parent = 0 );
+ virtual ~NetworkAccessHelper();
+
+ void establishConnection();
+ void releaseConnection();
+
+ private:
+ NetworkAccessHelperPrivate *d_ptr;
+};
+
+}
+
+#endif // NETWORKACCESSHELPER_H
diff --git a/kpimutils/networkaccesshelper_fake.cpp b/kpimutils/networkaccesshelper_fake.cpp
new file mode 100644
index 0000000..01571ea
--- /dev/null
+++ b/kpimutils/networkaccesshelper_fake.cpp
@@ -0,0 +1,45 @@
+/*
+ Copyright (c) 2010 Kevin Funk <kevin.funk@kdab.com>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+
+*/
+
+#include "networkaccesshelper.h"
+
+using namespace KPIMUtils;
+
+/**
+ * Fake backend
+ */
+NetworkAccessHelper::NetworkAccessHelper( QObject *parent )
+ : QObject( parent )
+{
+}
+
+NetworkAccessHelper::~NetworkAccessHelper()
+{
+}
+
+void NetworkAccessHelper::establishConnection()
+{
+}
+
+void NetworkAccessHelper::releaseConnection()
+{
+}
+
+#include "networkaccesshelper.moc"
diff --git a/kpimutils/networkaccesshelper_wince.cpp b/kpimutils/networkaccesshelper_wince.cpp
new file mode 100644
index 0000000..928a0f8
--- /dev/null
+++ b/kpimutils/networkaccesshelper_wince.cpp
@@ -0,0 +1,75 @@
+/*
+ Copyright (c) 2010 Kevin Funk <kevin.funk@kdab.com>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+
+*/
+
+#include "networkaccesshelper.h"
+
+// from komo-kdelibs
+#include <solid/networkingsession.h>
+#include <solid/networking.h>
+
+using namespace KPIMUtils;
+
+namespace KPIMUtils {
+
+class NetworkAccessHelperPrivate
+{
+ public:
+ NetworkAccessHelperPrivate( NetworkAccessHelper *helper );
+ ~NetworkAccessHelperPrivate();
+
+ Solid::NetworkingSession *m_session;
+};
+
+}
+
+NetworkAccessHelperPrivate::NetworkAccessHelperPrivate( NetworkAccessHelper *helper )
+ : m_session( new Solid::NetworkingSession( helper ) )
+{
+}
+
+NetworkAccessHelperPrivate::~NetworkAccessHelperPrivate()
+{
+ delete m_session;
+}
+
+NetworkAccessHelper::NetworkAccessHelper( QObject *parent )
+ : QObject(parent),
+ d_ptr( new NetworkAccessHelperPrivate( this ) )
+{
+}
+
+NetworkAccessHelper::~NetworkAccessHelper()
+{
+ delete d_ptr;
+}
+
+void NetworkAccessHelper::establishConnection()
+{
+ Q_D(NetworkAccessHelper);
+ d->m_session->establishConnection();
+}
+
+void NetworkAccessHelper::releaseConnection()
+{
+ Q_D(NetworkAccessHelper);
+ d->m_session->releaseConnection();
+}
+
+#include "networkaccesshelper.moc"
diff --git a/kpimutils/processes.cpp b/kpimutils/processes.cpp
new file mode 100644
index 0000000..ac87a92
--- /dev/null
+++ b/kpimutils/processes.cpp
@@ -0,0 +1,231 @@
+/**
+ * This file is part of the kpimutils library.
+ *
+ * Copyright (C) 2008 Jarosław Staniek <staniek@kde.org>
+ * Copyright (C) 2012 Andre Heinecke <aheinecke@intevation.de>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+/**
+ @file
+ This file is part of the KDEPIM Utilities library and provides
+ static methods for process handling.
+
+ @author Jarosław Staniek \<staniek@kde.org\>
+*/
+
+#include "processes.h"
+using namespace KPIMUtils;
+
+#ifdef Q_WS_WIN
+
+#include <windows.h>
+#include <tlhelp32.h>
+#include <psapi.h>
+#include <signal.h>
+#include <unistd.h>
+
+#ifdef Q_OS_WINCE
+#include <Tlhelp32.h>
+#endif
+
+#include <QtCore/QList>
+#include <QtCore/QtDebug>
+
+#include <KDebug>
+
+// Copy from kdelibs/kinit/kinit_win.cpp
+PSID copySid(PSID from)
+{
+ if (!from)
+ return 0;
+ int sidLength = GetLengthSid(from);
+ PSID to = (PSID) malloc(sidLength);
+ CopySid(sidLength, to, from);
+ return to;
+}
+
+// Copy from kdelibs/kinit/kinit_win.cpp
+static PSID getProcessOwner(HANDLE hProcess)
+{
+#ifndef _WIN32_WCE
+ HANDLE hToken = NULL;
+ PSID sid;
+
+ OpenProcessToken(hProcess, TOKEN_READ, &hToken);
+ if(hToken)
+ {
+ DWORD size;
+ PTOKEN_USER userStruct;
+
+ // check how much space is needed
+ GetTokenInformation(hToken, TokenUser, NULL, 0, &size);
+ if( ERROR_INSUFFICIENT_BUFFER == GetLastError() )
+ {
+ userStruct = reinterpret_cast<PTOKEN_USER>( new BYTE[size] );
+ GetTokenInformation(hToken, TokenUser, userStruct, size, &size);
+
+ sid = copySid(userStruct->User.Sid);
+ CloseHandle(hToken);
+ delete [] userStruct;
+ return sid;
+ }
+ }
+#endif
+ return 0;
+}
+
+// Copy from kdelibs/kinit/kinit_win.cpp
+static HANDLE getProcessHandle(int processID)
+{
+ return OpenProcess( SYNCHRONIZE|PROCESS_QUERY_INFORMATION |
+ PROCESS_VM_READ | PROCESS_TERMINATE,
+ false, processID );
+}
+
+void KPIMUtils::getProcessesIdForName( const QString &processName, QList<int> &pids )
+{
+ HANDLE h;
+ PROCESSENTRY32 pe32;
+
+ h = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 );
+ if ( h == INVALID_HANDLE_VALUE ) {
+ return;
+ }
+
+ pe32.dwSize = sizeof(PROCESSENTRY32); // Necessary according to MSDN
+ if ( !Process32First( h, &pe32 ) ) {
+ return;
+ }
+
+ pids.clear();
+
+ do {
+ if ( QString::fromWCharArray( pe32.szExeFile ) == processName ) {
+ PSID user_sid = getProcessOwner( GetCurrentProcess() );
+ if ( user_sid ) {
+ // Also check that we are the Owner of that process
+ HANDLE hProcess = getProcessHandle( pe32.th32ProcessID );
+ if (!hProcess) {
+ continue;
+ }
+
+ PSID sid = getProcessOwner( hProcess );
+ PSID userSid = getProcessOwner( GetCurrentProcess() );
+ if (!sid || userSid && !EqualSid( userSid, sid )) {
+ free ( sid );
+ continue;
+ }
+ }
+ pids.append( (int)pe32.th32ProcessID );
+ kDebug() << "found PID: " << (int)pe32.th32ProcessID;
+ }
+ } while( Process32Next( h, &pe32 ) );
+#ifndef _WIN32_WCE
+ CloseHandle(h);
+#else
+ CloseToolhelp32Snapshot(h);
+#endif
+}
+
+bool KPIMUtils::otherProcessesExist( const QString &processName )
+{
+ QList<int> pids;
+ getProcessesIdForName( processName, pids );
+ int myPid = getpid();
+ foreach ( int pid, pids ) {
+ if ( myPid != pid ) {
+// kDebug() << "Process ID is " << pid;
+ return true;
+ }
+ }
+ return false;
+}
+
+bool KPIMUtils::killProcesses( const QString &processName )
+{
+ QList<int> pids;
+ getProcessesIdForName( processName, pids );
+ if ( pids.empty() ) {
+ return true;
+ }
+
+ qWarning() << "Killing process \"" << processName << " (pid=" << pids[0] << ")..";
+ int overallResult = 0;
+ foreach ( int pid, pids ) {
+ int result;
+#ifndef _WIN32_WCE
+ result = kill( pid, SIGTERM );
+ if ( result == 0 ) {
+ continue;
+ }
+#endif
+ result = kill( pid, SIGKILL );
+ if ( result != 0 ) {
+ overallResult = result;
+ }
+ }
+ return overallResult == 0;
+}
+
+struct EnumWindowsStruct
+{
+ EnumWindowsStruct() : windowId( 0 ) {}
+ int pid;
+ HWND windowId;
+};
+
+BOOL CALLBACK EnumWindowsProc( HWND hwnd, LPARAM lParam )
+{
+ if ( GetWindowLong( hwnd, GWL_STYLE ) & WS_VISIBLE ) {
+
+ DWORD pidwin;
+
+ GetWindowThreadProcessId( hwnd, &pidwin );
+ if ( pidwin == ( (EnumWindowsStruct *)lParam )->pid ) {
+ ( (EnumWindowsStruct *)lParam )->windowId = hwnd;
+ return FALSE; //krazy:exclude=captruefalse
+ }
+ }
+ return TRUE; //krazy:exclude=captruefalse
+}
+
+void KPIMUtils::activateWindowForProcess( const QString &executableName )
+{
+ QList<int> pids;
+ KPIMUtils::getProcessesIdForName( executableName, pids );
+ int myPid = getpid();
+ int foundPid = 0;
+ foreach ( int pid, pids ) {
+ if ( myPid != pid ) {
+ kDebug() << "activateWindowForProcess(): PID to activate:" << pid;
+ foundPid = pid;
+ break;
+ }
+ }
+ if ( foundPid == 0 ) {
+ return;
+ }
+ EnumWindowsStruct winStruct;
+ winStruct.pid = foundPid;
+ EnumWindows( EnumWindowsProc, (LPARAM)&winStruct );
+ if ( winStruct.windowId == 0 ) {
+ return;
+ }
+ SetForegroundWindow( winStruct.windowId );
+}
+
+#endif // Q_WS_WIN
diff --git a/kpimutils/processes.h b/kpimutils/processes.h
new file mode 100644
index 0000000..fde8d2c
--- /dev/null
+++ b/kpimutils/processes.h
@@ -0,0 +1,73 @@
+/**
+ * This file is part of the kpimutils library.
+ *
+ * Copyright (C) 2008 Jarosław Staniek <staniek@kde.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+/**
+ @file
+ This file is part of the KDEPIM Utilities library and provides
+ static methods for process handling.
+
+ @brief
+ Process handling methods.
+
+ @author Jarosław Staniek \<staniek@kde.org\>
+*/
+
+#ifndef KDEPIMUTILS_PROCESSES_H
+#define KDEPIMUTILS_PROCESSES_H
+
+#include "kpimutils_export.h"
+
+#include <QtCore/QList>
+
+class QString;
+
+namespace KPIMUtils {
+
+#ifdef Q_WS_WIN
+ /**
+ * Sets @a pids to a list of processes having name @a processName.
+ */
+ KPIMUTILS_EXPORT void getProcessesIdForName( const QString &processName, QList<int> &pids );
+
+ /**
+ * @return true if one or more processes (other than the current process) exist
+ * for name @a processName; false otherwise.
+ */
+ KPIMUTILS_EXPORT bool otherProcessesExist( const QString &processName );
+
+ /**
+ * Terminates or kills all processes with name @a processName.
+ * First, SIGTERM is sent to a process, then if that fails, we try with SIGKILL.
+ * @return true on successful termination of all processes or false if at least
+ * one process failed to terminate.
+ */
+ KPIMUTILS_EXPORT bool killProcesses( const QString &processName );
+
+ /**
+ * Activates window for first found process with executable @a executableName
+ * (without path and .exe extension)
+ */
+ KPIMUTILS_EXPORT void activateWindowForProcess( const QString &executableName );
+#endif
+
+}
+
+#endif
+
diff --git a/kpimutils/spellingfilter.cpp b/kpimutils/spellingfilter.cpp
new file mode 100644
index 0000000..03097c3
--- /dev/null
+++ b/kpimutils/spellingfilter.cpp
@@ -0,0 +1,236 @@
+/*
+ * spellingfilter.cpp
+ *
+ * Copyright (c) 2002 Dave Corrie <kde@davecorrie.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this library; see the file COPYING.LIB. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+/**
+ @file
+ This file is part of the KDEPIM Utilities library and provides the
+ SpellingFilter class.
+
+ @brief
+ Filters message text that should not be spellchecked.
+
+ @author Dave Corrie \<kde@davecorrie.com\>
+*/
+
+#include "spellingfilter.h"
+
+using namespace KPIMUtils;
+
+/**
+ Private class that helps to provide binary compatibility between releases.
+ @internal
+*/
+//@cond PRIVATE
+class KPIMUtils::SpellingFilter::Private
+{
+ public:
+ QString mOriginal;
+ QString mFiltered;
+};
+//@endcond
+
+//-----------------------------------------------------------------------------
+// SpellingFilter implementation
+//
+
+SpellingFilter::SpellingFilter( const QString &text,
+ const QString &quotePrefix,
+ UrlFiltering filterUrls,
+ EmailAddressFiltering filterEmailAddresses,
+ const QStringList &filterStrings )
+ : d( new KPIMUtils::SpellingFilter::Private )
+{
+ d->mOriginal = text;
+ TextCensor c( text );
+
+ if ( !quotePrefix.isEmpty() ) {
+ c.censorQuotations( quotePrefix );
+ }
+
+ if ( filterUrls ) {
+ c.censorUrls();
+ }
+
+ if ( filterEmailAddresses ) {
+ c.censorEmailAddresses();
+ }
+
+ QStringList::const_iterator iter = filterStrings.begin();
+ while ( iter != filterStrings.end() ) {
+ c.censorString( *iter );
+ ++iter;
+ }
+
+ d->mFiltered = c.censoredText();
+}
+
+SpellingFilter::~SpellingFilter()
+{
+ delete d;
+}
+
+QString SpellingFilter::originalText() const
+{
+ return d->mOriginal;
+}
+
+QString SpellingFilter::filteredText() const
+{
+ return d->mFiltered;
+}
+
+//-----------------------------------------------------------------------------
+// SpellingFilter::TextCensor implementation
+//
+
+SpellingFilter::TextCensor::TextCensor( const QString &s )
+ : LinkLocator( s )
+{
+}
+
+void SpellingFilter::TextCensor::censorQuotations( const QString &quotePrefix )
+{
+ mPos = 0;
+ while ( mPos < mText.length() ) {
+ // Find start of quotation
+ findQuotation( quotePrefix );
+ if ( mPos < mText.length() ) {
+ int start = mPos;
+ skipQuotation( quotePrefix );
+
+ // Replace quotation with spaces
+ int len = mPos - start;
+ QString spaces;
+ spaces.fill( ' ', len );
+ mText.replace( start, len, spaces );
+ }
+ }
+}
+
+void SpellingFilter::TextCensor::censorUrls()
+{
+ mPos = 0;
+ while ( mPos < mText.length() ) {
+ // Find start of url
+ QString url;
+ while ( mPos < mText.length() && url.isEmpty() ) {
+ url = getUrl();
+ ++mPos;
+ }
+
+ if ( mPos < mText.length() && !url.isEmpty() ) {
+ int start = mPos - url.length();
+
+ // Replace url with spaces
+ url.fill( ' ' );
+ mText.replace( start, url.length(), url );
+ }
+ }
+}
+
+void SpellingFilter::TextCensor::censorEmailAddresses()
+{
+ mPos = 0;
+ while ( mPos < mText.length() ) {
+ // Find start of email address
+ findEmailAddress();
+ if ( mPos < mText.length() ) {
+ QString address = getEmailAddress();
+ ++mPos;
+ if ( !address.isEmpty() ) {
+ int start = mPos - address.length();
+
+ // Replace address with spaces
+ address.fill( ' ' );
+ mText.replace( start, address.length(), address );
+ }
+ }
+ }
+}
+
+void SpellingFilter::TextCensor::censorString( const QString &s )
+{
+ mPos = 0;
+ while ( mPos != -1 ) {
+ // Find start of string
+ mPos = mText.indexOf( s, mPos );
+ if ( mPos != -1 ) {
+ // Replace string with spaces
+ QString spaces;
+ spaces.fill( ' ', s.length() );
+ mText.replace( mPos, s.length(), spaces );
+ mPos += s.length();
+ }
+ }
+}
+
+QString SpellingFilter::TextCensor::censoredText() const
+{
+ return mText;
+}
+
+//-----------------------------------------------------------------------------
+// text censorship helper functions
+//
+
+bool SpellingFilter::TextCensor::atLineStart() const
+{
+ return
+ ( mPos == 0 && mText.length() > 0 ) ||
+ ( mText[mPos - 1] == '\n' );
+}
+
+void SpellingFilter::TextCensor::skipLine()
+{
+ mPos = mText.indexOf( '\n', mPos );
+ if ( mPos == -1 ) {
+ mPos = mText.length();
+ } else {
+ ++mPos;
+ }
+}
+
+bool SpellingFilter::TextCensor::atQuotation( const QString &quotePrefix ) const
+{
+ return atLineStart() &&
+ mText.mid( mPos, quotePrefix.length() ) == quotePrefix;
+}
+
+void SpellingFilter::TextCensor::skipQuotation( const QString &quotePrefix )
+{
+ while ( atQuotation( quotePrefix ) ) {
+ skipLine();
+ }
+}
+
+void SpellingFilter::TextCensor::findQuotation( const QString &quotePrefix )
+{
+ while ( mPos < mText.length() &&
+ !atQuotation( quotePrefix ) ) {
+ skipLine();
+ }
+}
+
+void SpellingFilter::TextCensor::findEmailAddress()
+{
+ while ( mPos < mText.length() && mText[mPos] != '@' ) {
+ ++mPos;
+ }
+}
diff --git a/kpimutils/spellingfilter.h b/kpimutils/spellingfilter.h
new file mode 100644
index 0000000..2f3c04e
--- /dev/null
+++ b/kpimutils/spellingfilter.h
@@ -0,0 +1,98 @@
+/*
+ * spellingfilter.h
+ *
+ * Copyright (c) 2002 Dave Corrie <kde@davecorrie.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this library; see the file COPYING.LIB. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+/**
+ @file
+ This file is part of the KDEPIM Utilities library and provides the
+ SpellingFilter class.
+
+ @brief
+ Filters message text that should not be spellchecked.
+
+ @author Dave Corrie \<kde@davecorrie.com\>
+*/
+
+#ifndef KPIMUTILS_SPELLINGFILTER_H
+#define KPIMUTILS_SPELLINGFILTER_H
+
+#include "kpimutils_export.h"
+#include "linklocator.h"
+
+#include <QtCore/QString>
+#include <QtCore/QStringList>
+
+namespace KPIMUtils {
+
+class KPIMUTILS_EXPORT SpellingFilter
+{
+ public:
+ enum UrlFiltering {
+ DontFilterUrls,
+ FilterUrls
+ };
+ enum EmailAddressFiltering {
+ DontFilterEmailAddresses,
+ FilterEmailAddresses
+ };
+
+ SpellingFilter( const QString &text, const QString &quotePrefix,
+ UrlFiltering filterUrls = FilterUrls,
+ EmailAddressFiltering filterEmailAddresses = FilterEmailAddresses,
+ const QStringList &filterStrings = QStringList() );
+ ~SpellingFilter();
+
+ QString originalText() const;
+ QString filteredText() const;
+
+ class TextCensor;
+
+ private:
+ //@cond PRIVATE
+ class Private;
+ Private *const d;
+ //@endcond
+};
+
+class SpellingFilter::TextCensor : public LinkLocator
+{
+ public:
+ TextCensor( const QString &s );
+
+ void censorQuotations( const QString &quotePrefix );
+ void censorUrls();
+ void censorEmailAddresses();
+ void censorString( const QString &s );
+
+ QString censoredText() const;
+
+ private:
+ bool atLineStart() const;
+ void skipLine();
+
+ bool atQuotation( const QString &quotePrefix ) const;
+ void skipQuotation( const QString &quotePrefix );
+ void findQuotation( const QString &quotePrefix );
+
+ void findEmailAddress();
+};
+
+}
+
+#endif
diff --git a/kpimutils/supertrait.h b/kpimutils/supertrait.h
new file mode 100644
index 0000000..13bacc4
--- /dev/null
+++ b/kpimutils/supertrait.h
@@ -0,0 +1,50 @@
+/*
+ Copyright (c) 2009 Volker Krause <vkrause@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#ifndef KPIMUTILS_SUPERTRAIT_H
+#define KPIMUTILS_SUPERTRAIT_H
+
+namespace KPIMUtils
+{
+ /**
+ @internal
+ @see super_class
+ */
+ template <typename Super>
+ struct SuperClassTrait
+ {
+ typedef Super Type;
+ };
+
+ /**
+ Type trait to provide information about a base class for a given class.
+ Used eg. for the Akonadi payload mechanism.
+
+ To provide base class introspection for own types, extend this trait as follows:
+ @code
+ namespace KPIMUtils
+ {
+ template <> struct SuperClass<MyClass> : public SuperClassTrait<MyBaseClass>{};
+ }
+ @endcode
+ */
+ template <typename Class> struct SuperClass : public SuperClassTrait<Class>{};
+}
+
+#endif
diff --git a/kpimutils/tests/CMakeLists.txt b/kpimutils/tests/CMakeLists.txt
new file mode 100644
index 0000000..27f4eb4
--- /dev/null
+++ b/kpimutils/tests/CMakeLists.txt
@@ -0,0 +1,20 @@
+set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} )
+include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/.. )
+
+########### next target ###############
+
+set(testemail_SRCS testemail.cpp )
+
+
+kde4_add_unit_test(testemail TESTNAME kpimutils-testemail ${testemail_SRCS})
+
+target_link_libraries(testemail ${KDE4_KDECORE_LIBS} kpimutils ${QT_QTTEST_LIBRARY} ${QT_QTGUI_LIBRARY})
+
+########### next target ###############
+
+set(testlinklocator_SRCS testlinklocator.cpp )
+
+
+kde4_add_unit_test(testlinklocator TESTNAME kpimutils-testlinklocator ${testlinklocator_SRCS})
+
+target_link_libraries(testlinklocator ${KDE4_KDECORE_LIBS} kpimutils ${QT_QTTEST_LIBRARY} ${QT_QTGUI_LIBRARY})
diff --git a/kpimutils/tests/testemail.cpp b/kpimutils/tests/testemail.cpp
new file mode 100644
index 0000000..636b5d6
--- /dev/null
+++ b/kpimutils/tests/testemail.cpp
@@ -0,0 +1,579 @@
+/*
+ This file is part of the KDE project
+ Copyright (C) 2004 David Faure <faure@kde.org>
+ Copyright (C) 2009 Thomas McGuire <mcguire@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License version 2 as published by the Free Software Foundation.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+//krazy:excludeall=contractions
+
+#include "testemail.h"
+
+#include "kpimutils/email.h"
+
+#include <kdebug.h>
+
+#include <qtest_kde.h>
+
+QTEST_KDEMAIN( EMailTest, NoGUI )
+
+using namespace KPIMUtils;
+Q_DECLARE_METATYPE( EmailParseResult )
+
+void EMailTest::testGetNameAndEmail()
+{
+ QFETCH( QString, input );
+ QFETCH( QString, expName );
+ QFETCH( QString, expEmail );
+ QFETCH( bool, expRetVal );
+
+ QString name, email;
+ bool retVal = extractEmailAddressAndName( input, email, name );
+ QCOMPARE( retVal, expRetVal );
+ QCOMPARE( name, expName );
+ QCOMPARE( email, expEmail );
+}
+
+void EMailTest::testGetNameAndEmail_data()
+{
+ QTest::addColumn<QString>( "input" );
+ QTest::addColumn<QString>( "expName" );
+ QTest::addColumn<QString>( "expEmail" );
+ QTest::addColumn<bool>( "expRetVal" );
+
+ QTest::newRow( "Empty input" ) << QString() << QString() << QString() << false;
+ QTest::newRow( "Email only" ) << "faure@kde.org" << QString() << "faure@kde.org" << false;
+ QTest::newRow( "Normal case" ) << "David Faure <faure@kde.org>" << "David Faure"
+ << "faure@kde.org" << true;
+ QTest::newRow( "Double-quotes 1" ) << "\"Faure, David\" <faure@kde.org>" << "Faure, David"
+ << "faure@kde.org" << true;
+ QTest::newRow( "Double-quotes 2" ) << "<faure@kde.org> \"David Faure\"" << "David Faure"
+ << "faure@kde.org" << true;
+ QTest::newRow( "Parenthesis 1" ) << "faure@kde.org (David Faure)"
+ << "David Faure" << "faure@kde.org" << true;
+ QTest::newRow( "Parenthesis 2" ) << "(David Faure) faure@kde.org"
+ << "David Faure" << "faure@kde.org" << true;
+ QTest::newRow( "Parenthesis 3" ) << "My Name (me) <me@home.net>" << "My Name (me)"
+ << "me@home.net" << true; // #93513
+
+ // As per https://intevation.de/roundup/kolab/issue858
+ QTest::newRow( "Nested parenthesis" ) << "faure@kde.org (David (The Man) Faure)"
+ << "David (The Man) Faure" << "faure@kde.org" << true;
+ QTest::newRow( "Double-quotes inside parenthesis 1" ) << "faure@kde.org (David \"Crazy\" Faure)"
+ << "David \"Crazy\" Faure"
+ << "faure@kde.org" << true;
+ QTest::newRow( "Double-quotes inside parenthesis 2" ) << "(David \"Crazy\" Faure) faure@kde.org"
+ << "David \"Crazy\" Faure"
+ << "faure@kde.org" << true;
+ QTest::newRow( "Parenthesis inside double-quotes 1" ) << "\"Faure (David)\" <faure@kde.org>"
+ << "Faure (David)" << "faure@kde.org"
+ << true;
+ QTest::newRow( "Parenthesis inside double-quotes 2" ) << "<faure@kde.org> \"Faure (David)\""
+ << "Faure (David)" << "faure@kde.org"
+ << true;
+ QTest::newRow( "Space in email" ) << "David Faure < faure@kde.org >" << "David Faure"
+ << "faure@kde.org" << true;
+ QTest::newRow( "'@' in name 1" ) << "faure@kde.org (a@b)" << "a@b" << "faure@kde.org" << true;
+ QTest::newRow( "'@' in name 2" ) << "\"a@b\" <faure@kde.org>" << "a@b" << "faure@kde.org" << true;
+
+ // While typing, when there's no '@' yet
+ QTest::newRow( "while typing 1" ) << "foo" << "foo" << QString() << false;
+ QTest::newRow( "while typing 2" ) << "foo <" << "foo" << QString() << false;
+ QTest::newRow( "while typing 3" ) << "foo <b" << "foo" << "b" << true;
+
+ // If multiple emails are there, only return the first one
+ QTest::newRow( "multiple emails" ) << "\"Faure, David\" <faure@kde.org>, KHZ <khz@khz.khz>"
+ << "Faure, David" << "faure@kde.org" << true;
+
+ QTest::newRow( "domain literals" ) << "Matt Douhan <matt@[123.123.123.123]>"
+ << "Matt Douhan" << "matt@[123.123.123.123]" << true;
+ QTest::newRow( "@ inside the comment" ) << "\"Matt@Douhan\" <matt@fruitsalad.org>"
+ << "Matt@Douhan" << "matt@fruitsalad.org" << true;
+ QTest::newRow( "No '@'" ) << "foo <distlist>" << "foo" << "distlist" << true;
+ QTest::newRow( "Backslash in display name" ) << "\"Lastname\\, Firstname\""
+ " <firstname@lastname.com>"
+ << "Lastname, Firstname" << "firstname@lastname.com"
+ << true;
+ QTest::newRow( "# in domain" ) << "Matt Douhan <dm3tt@db0zdf.#rpl.deu.eu>" << "Matt Douhan" << "dm3tt@db0zdf.#rpl.deu.eu" << true;
+}
+
+void EMailTest::testIsValidEmailAddress()
+{
+ QFETCH( QString, input );
+ QFETCH( EmailParseResult, expErrorCode );
+
+ QCOMPARE( isValidAddress( input ), expErrorCode );
+}
+
+void EMailTest::testIsValidEmailAddress_data()
+{
+ QTest::addColumn<QString>( "input" );
+ QTest::addColumn<EmailParseResult>( "expErrorCode" );
+
+ // Too many @'s
+ QTest::newRow( "" ) << "matt@@fruitsalad.org" << TooManyAts;
+
+ // Too few @'s
+ QTest::newRow( "" ) << "mattfruitsalad.org" << TooFewAts;
+
+ // An empty string
+ QTest::newRow( "" ) << QString() << AddressEmpty;
+
+ // email address starting with a @
+ QTest::newRow( "" ) << "@mattfruitsalad.org" << MissingLocalPart;
+
+ // make sure that starting @ and an additional @ in the same email address don't conflict
+ // trap the starting @ first and break
+ QTest::newRow( "" ) << "@matt@fruitsalad.org" << MissingLocalPart;
+
+ // email address ending with a @
+ QTest::newRow( "" ) << "mattfruitsalad.org@" << MissingDomainPart;
+
+ // make sure that ending with@ and an additional @ in the email address don't conflict
+ QTest::newRow( "" ) << "matt@fruitsalad.org@" << MissingDomainPart;
+
+ // unbalanced Parens
+ QTest::newRow( "" ) << "mattjongel)@fruitsalad.org" << UnbalancedParens;
+
+ // unbalanced Parens the other way around
+ QTest::newRow( "" ) << "mattjongel(@fruitsalad.org" << UnbalancedParens;
+
+ // Correct parens just to make sure it works
+ QTest::newRow( "" ) << "matt(jongel)@fruitsalad.org" << AddressOk;
+
+ // Check that anglebrackets are closed
+ QTest::newRow( "" ) << "matt douhan<matt@fruitsalad.org" << UnclosedAngleAddr;
+
+ // Check that angle brackets are closed the other way around
+ QTest::newRow( "" ) << "matt douhan>matt@fruitsalad.org" << UnopenedAngleAddr;
+
+ // Check that angle brackets are closed the other way around, and anglebrackets in domainpart
+ // instead of local part
+ QTest::newRow( "" ) << "matt douhan matt@<fruitsalad.org" << UnclosedAngleAddr;
+
+ // check that a properly formated anglebrackets situation is OK
+ QTest::newRow( "" ) << "matt douhan<matt@fruitsalad.org>" << AddressOk;
+
+ // a full email address with comments angle brackets and the works should be valid too
+ QTest::newRow( "" ) << "Matt (jongel) Douhan <matt@fruitsalad.org>" << AddressOk;
+
+ // Double quotes
+ QTest::newRow( "" ) << "\"Matt Douhan\" <matt@fruitsalad.org>" << AddressOk;
+
+ // Double quotes inside parens
+ QTest::newRow( "" ) << "Matt (\"jongel\") Douhan <matt@fruitsalad.org>" << AddressOk;
+
+ // DOuble quotes not closed
+ QTest::newRow( "" ) << "Matt \"jongel Douhan <matt@fruitsalad.org>" << UnbalancedQuote;
+
+ // Parens inside double quotes
+ QTest::newRow( "" ) << "Matt \"(jongel)\" Douhan <matt@fruitsalad.org>" << AddressOk;
+
+ // Space in email
+ QTest::newRow( "" ) << "Matt Douhan < matt@fruitsalad.org >" << AddressOk;
+
+ // @ is allowed inisde doublequotes
+ QTest::newRow( "" ) << "\"matt@jongel\" <matt@fruitsalad.org>" << AddressOk;
+
+ // anglebrackets inside dbl quotes
+ QTest::newRow( "" ) << "\"matt<blah blah>\" <matt@fruitsalad.org>" << AddressOk;
+
+ // a , inside a double quoted string is OK, how do I know this? well Ingo says so
+ // and it makes sense since it is also a separator of email addresses
+ QTest::newRow( "" ) << "\"Douhan, Matt\" <matt@fruitsalad.org>" << AddressOk;
+
+ // Domains literals also need to work
+ QTest::newRow( "" ) << "Matt Douhan <matt@[123.123.123.123]>" << AddressOk;
+
+ // Typo in domain literal address
+ QTest::newRow( "" ) << "Matt Douhan <matt@[123.123.123,123]>" << UnexpectedComma;
+
+ // Some more insane tests but still valid so they must work
+ QTest::newRow( "" ) << "Matt Douhan <\"m@att\"@jongel.com>" << AddressOk;
+
+ // BUG 99657
+ QTest::newRow( "" ) << "matt@jongel.fibbel.com" << AddressOk;
+
+ // BUG 98720
+ QTest::newRow( "" ) << "mailto:@mydomain" << DisallowedChar;
+
+ // correct error msg when a comma is inside <>
+ QTest::newRow( "" ) << "Matt Douhan <matt@fruitsalad,org>" << UnexpectedComma;
+
+ //several commentlevels
+ QTest::newRow( "" ) << "Matt Douhan (hey(jongel)fibbel) <matt@fruitsalad.org>" << AddressOk;
+
+ // several comment levels and one (the outer) being unbalanced
+ QTest::newRow( "" ) << "Matt Douhan (hey(jongel)fibbel <matt@fruitsalad.org>"
+ << UnbalancedParens;
+
+ // several comment levels and one (the inner) being unbalanced
+ QTest::newRow( "" ) << "Matt Douhan (hey(jongelfibbel) <matt@fruitsalad.org>"
+ << UnbalancedParens;
+
+ // an error inside a double quote is no error
+ QTest::newRow( "" ) << "Matt Douhan \"(jongel\" <matt@fruitsalad.org>"
+ << AddressOk;
+
+ // inside a quoted string double quotes are only allowed in pairs as per rfc2822
+ QTest::newRow( "" ) << "Matt Douhan \"jongel\"fibbel\" <matt@fruitsalad.org>"
+ << UnbalancedQuote;
+
+ // a questionmark is valid in an atom
+ QTest::newRow( "" ) << "Matt? <matt@fruitsalad.org>" << AddressOk;
+
+ // weird but OK
+ QTest::newRow( "" ) << "\"testing, \\\"testing\" <matt@fruitsalad.org>"
+ << AddressOk;
+
+ // escape a quote to many to see if it makes it invalid
+ QTest::newRow( "" ) << "\"testing, \\\"testing\\\" <matt@fruitsalad.org>"
+ << UnbalancedQuote;
+
+ // escape a parens and thus make a comma appear
+ QTest::newRow( "" ) << "Matt (jongel, fibbel\\) <matt@fruitsalad.org>"
+ << UnbalancedParens;
+
+ // several errors inside doublequotes
+ QTest::newRow( "" ) << "Matt \"(jongel,\\\" < fibbel\\)\" <matt@fruitsalad.org>"
+ << AddressOk;
+
+ // BUG 105705
+ QTest::newRow( "" ) << "matt-@fruitsalad.org" << AddressOk;
+
+ // underscore at the end of local part
+ QTest::newRow( "" ) << "matt_@fruitsalad.org" << AddressOk;
+
+ // how about ( comment ) in the domain part
+ QTest::newRow( "" ) << "matt_@(this is a cool host)fruitsalad.org" << AddressOk;
+
+ // To quote rfc2822 the test below is aesthetically displeasing, but perfectly legal.
+ QTest::newRow( "" ) << "Pete(A wonderful \\) chap) <pete(his account)@silly.test(his host)>"
+ << AddressOk;
+
+ // quoted pair or not quoted pair
+ QTest::newRow( "" ) << "\"jongel '\\\" fibbel\" <matt@fruitsalad.org>" << AddressOk;
+ QTest::newRow( "" ) << "\"jongel '\" fibbel\" <matt@fruitsalad.org>" << UnbalancedQuote;
+
+ // full atext support according to rfc2822
+ QTest::newRow( "" ) << "!matt@fruitsalad.org" << AddressOk;
+ QTest::newRow( "" ) << "#matt@fruitsalad.org" << AddressOk;
+ QTest::newRow( "" ) << "$matt@fruitsalad.org" << AddressOk;
+ QTest::newRow( "" ) << "%matt@fruitsalad.org" << AddressOk;
+ QTest::newRow( "" ) << "&matt@fruitsalad.org" << AddressOk;
+ QTest::newRow( "" ) << "'matt@fruitsalad.org" << AddressOk;
+ QTest::newRow( "" ) << "*matt@fruitsalad.org" << AddressOk;
+ QTest::newRow( "" ) << "+matt@fruitsalad.org" << AddressOk;
+ QTest::newRow( "" ) << "/matt@fruitsalad.org" << AddressOk;
+ QTest::newRow( "" ) << "=matt@fruitsalad.org" << AddressOk;
+ QTest::newRow( "" ) << "?matt@fruitsalad.org" << AddressOk;
+ QTest::newRow( "" ) << "^matt@fruitsalad.org" << AddressOk;
+ QTest::newRow( "" ) << "_matt@fruitsalad.org" << AddressOk;
+ QTest::newRow( "" ) << "-matt@fruitsalad.org" << AddressOk;
+ QTest::newRow( "" ) << "`matt@fruitsalad.org" << AddressOk;
+ QTest::newRow( "" ) << "{matt@fruitsalad.org" << AddressOk;
+ QTest::newRow( "" ) << "|matt@fruitsalad.org" << AddressOk;
+ QTest::newRow( "" ) << "}matt@fruitsalad.org" << AddressOk;
+ QTest::newRow( "" ) << "~matt@fruitsalad.org" << AddressOk;
+ QTest::newRow( "" ) << "matt%matt@fruitsalad.org" << AddressOk;
+
+ //bug 105405
+ QTest::newRow( "" ) << "[foobar] <matt@fruitsalad.org>" << InvalidDisplayName;
+ QTest::newRow( "" ) << "matt \"[foobar]\" Douhan <matt@fruitsalad.org>" << AddressOk;
+
+ QTest::newRow( "" ) << "Matt Douhan <matt\"@@\"fruitsalad.org>" << TooFewAts;
+
+ QTest::newRow( "# in domain" ) << "dm3tt@db0zdf.#rpl.deu.eu" << AddressOk;
+}
+
+void EMailTest::testIsValidAddressList()
+{
+ QFETCH( QString, list );
+ QFETCH( EmailParseResult, expErrorCode );
+
+ QString badAddress;
+ QCOMPARE( isValidAddressList( list, badAddress ), expErrorCode );
+}
+
+void EMailTest::testIsValidAddressList_data()
+{
+ QTest::addColumn<QString>( "list" );
+ QTest::addColumn<EmailParseResult>( "expErrorCode" );
+
+ //bug 139477
+ QTest::newRow( "" ) << "martin.schulte@guug.de, msadmin@guug.de,"
+ " msnewsletter@guug.de" << AddressOk;
+ QTest::newRow( "" ) << "martin.schulte@guug.de; msadmin@guug.de;"
+ " msnewsletter@guug.de" << AddressOk;
+ QTest::newRow( "" ) << "martin.schulte@guug.de, msadmin@guug.de.,"
+ " msnewsletter@guug.de" << AddressOk;
+ QTest::newRow( "" ) << "Martin Schulte <martin.schulte@guug.de>,"
+ " MS Admin <msadmin@guug.de>, MS News <msnewsletter@guug.de>"
+ << AddressOk;
+ QTest::newRow( "" ) << "Martin Schulte <martin.schulte@guug.de>;"
+ " MS Admin <msadmin@guug.de>; MS News <msnewsletter@guug.de>"
+ << AddressOk;
+ QTest::newRow( "" ) << "Martin Schulte <martin.schulte@guug.de.>,"
+ " MS Admin <msadmin@guug.de>, MS News <msnewsletter@guug.de>"
+ << AddressOk;
+
+}
+
+void EMailTest::testIsValidSimpleEmailAddress()
+{
+ QFETCH( QString, input );
+ QFETCH( bool, expResult );
+
+ QCOMPARE( isValidSimpleAddress( input ), expResult );
+}
+
+void EMailTest::testIsValidSimpleEmailAddress_data()
+{
+ QTest::addColumn<QString>( "input" );
+ QTest::addColumn<bool>( "expResult" );
+
+ // checks for "pure" email addresses in the form of xxx@yyy.tld
+ QTest::newRow( "" ) << "matt@fruitsalad.org" << true;
+ QTest::newRow( "" ) << QString::fromUtf8( "test@täst.invalid" ) << true;
+ // non-ASCII char as first char of IDN
+ QTest::newRow( "" ) << QString::fromUtf8( "i_want@øl.invalid" ) << true;
+ QTest::newRow( "" ) << "matt@[123.123.123.123]" << true;
+ QTest::newRow( "" ) << "matt@[3.3.3.3]" << true;
+ QTest::newRow( "" ) << "matt@[4.4.4.4]" << true;
+ QTest::newRow( "" ) << "matt@[192.168.254.254]" << true;
+ QTest::newRow( "" ) << "\"matt\"@fruitsalad.org" << true;
+ QTest::newRow( "" ) << "-matt@fruitsalad.org" << true;
+ QTest::newRow( "" ) << "\"-matt\"@fruitsalad.org" << true;
+ QTest::newRow( "" ) << "matt@jongel.fibbel.com" << true;
+ QTest::newRow( "" ) << "Matt Douhan <matt@fruitsalad.org>" << false;
+ // BUG 105705
+ QTest::newRow( "" ) << "matt-@fibbel.com" << true;
+ QTest::newRow( "" ) << "matt@fibbel-is-a-geek.com" << true;
+ QTest::newRow( "" ) << "matt_@fibbel.com" << true;
+ // Check the defined chars for atext according to rfc2822
+ QTest::newRow( "" ) << "!matt@fruitsalad.org" << true;
+ QTest::newRow( "" ) << "#matt@fruitsalad.org" << true;
+ QTest::newRow( "" ) << "$matt@fruitsalad.org" << true;
+ QTest::newRow( "" ) << "%matt@fruitsalad.org" << true;
+ QTest::newRow( "" ) << "&matt@fruitsalad.org" << true;
+ QTest::newRow( "" ) << "'matt@fruitsalad.org" << true;
+ QTest::newRow( "" ) << "*matt@fruitsalad.org" << true;
+ QTest::newRow( "" ) << "+matt@fruitsalad.org" << true;
+ QTest::newRow( "" ) << "/matt@fruitsalad.org" << true;
+ QTest::newRow( "" ) << "=matt@fruitsalad.org" << true;
+ QTest::newRow( "" ) << "?matt@fruitsalad.org" << true;
+ QTest::newRow( "" ) << "^matt@fruitsalad.org" << true;
+ QTest::newRow( "" ) << "_matt@fruitsalad.org" << true;
+ QTest::newRow( "" ) << "-matt@fruitsalad.org" << true;
+ QTest::newRow( "" ) << "`matt@fruitsalad.org" << true;
+ QTest::newRow( "" ) << "{matt@fruitsalad.org" << true;
+ QTest::newRow( "" ) << "|matt@fruitsalad.org" << true;
+ QTest::newRow( "" ) << "}matt@fruitsalad.org" << true;
+ QTest::newRow( "" ) << "~matt@fruitsalad.org" << true;
+ // BUG 108476
+ QTest::newRow( "" ) << "foo+matt@fruitsalad.org" << true;
+ QTest::newRow( "" ) << "bar=matt@fruitsalad.org" << true;
+ QTest::newRow( "" ) << "jongel-matt@fruitsalad.org" << true;
+ QTest::newRow( "" ) << "matt-@fruitsalad.org" << true;
+
+ // check if the pure email address is wrong
+ QTest::newRow( "" ) << "mattfruitsalad.org" << false;
+ QTest::newRow( "" ) << "matt@[123.123.123.123" << false;
+ QTest::newRow( "" ) << "matt@123.123.123.123]" << false;
+ QTest::newRow( "" ) << "\"matt@fruitsalad.org" << false;
+ QTest::newRow( "" ) << "matt\"@fruitsalad.org" << false;
+ QTest::newRow( "" ) << QString() << false;
+
+ // BUG 203881
+ QTest::newRow( "" ) << "2advance@my-site.com" << true;
+
+ // and here some insane but still valid cases
+ QTest::newRow( "" ) << "\"m@tt\"@fruitsalad.org" << true;
+
+ QTest::newRow( "" ) << "matt\"@@\"fruitsalad.org" << false;
+ QTest::newRow( "# in domain" ) << "dm3tt@db0zdf.#rpl.deu.eu" << true;
+
+ // add tests for missing local/domain parts
+ QTest::newRow( "" ) << "@mattfruitsalad.org" << false;
+ QTest::newRow( "" ) << "matt@" << false;
+ QTest::newRow( "" ) << "@" << false;
+}
+
+void EMailTest::testGetEmailAddress()
+{
+ QFETCH( QString, input );
+ QFETCH( QString, expResult );
+
+ QCOMPARE( extractEmailAddress( input ), expResult );
+}
+
+void EMailTest::testGetEmailAddress_data()
+{
+ QTest::addColumn<QString>( "input" );
+ QTest::addColumn<QString>( "expResult" );
+
+ QTest::newRow( "" ) << "matt@fruitsalad.org" << "matt@fruitsalad.org";
+ QTest::newRow( "" ) << "Matt Douhan <matt@fruitsalad.org>" << "matt@fruitsalad.org";
+ QTest::newRow( "" ) << "\"Matt Douhan <blah blah>\" <matt@fruitsalad.org>"
+ << "matt@fruitsalad.org";
+ QTest::newRow( "" ) << "\"Matt <blah blah>\" <matt@fruitsalad.org>"
+ << "matt@fruitsalad.org";
+ QTest::newRow( "" ) << "Matt Douhan (jongel) <matt@fruitsalad.org"
+ << QString();
+ QTest::newRow( "" ) << "Matt Douhan (m@tt) <matt@fruitsalad.org>"
+ << "matt@fruitsalad.org";
+ QTest::newRow( "" ) << "\"Douhan, Matt\" <matt@fruitsalad.org>"
+ << "matt@fruitsalad.org";
+ QTest::newRow( "" ) << "\"Matt Douhan (m@tt)\" <matt@fruitsalad.org>"
+ << "matt@fruitsalad.org";
+ QTest::newRow( "" ) << "\"Matt Douhan\" (matt <matt@fruitsalad.org>"
+ << QString();
+ QTest::newRow( "" ) << "Matt Douhan <matt@[123.123.123.123]>"
+ << "matt@[123.123.123.123]";
+ QTest::newRow( "" ) << "dm3tt@db0zdf.#rpl.deu.eu" << "dm3tt@db0zdf.#rpl.deu.eu";
+
+
+}
+
+void EMailTest::testCheckSplitEmailAddrList()
+{
+ QFETCH( QString, input );
+ QFETCH( QStringList, expResult );
+
+ QCOMPARE( splitAddressList( input ), expResult );
+}
+
+void EMailTest::testCheckSplitEmailAddrList_data()
+{
+ QTest::addColumn<QString>( "input" );
+ QTest::addColumn<QStringList>( "expResult" );
+
+ QTest::newRow( "" ) << "kloecker@kde.org (Kloecker, Ingo)"
+ << ( QStringList() << "kloecker@kde.org (Kloecker, Ingo)" );
+ QTest::newRow( "" ) << "Matt Douhan <matt@fruitsalad.org>, Foo Bar <foo@bar.com>"
+ << ( QStringList()
+ << "Matt Douhan <matt@fruitsalad.org>"
+ << "Foo Bar <foo@bar.com>" );
+ QTest::newRow( "" ) << "\"Matt, Douhan\" <matt@fruitsalad.org>, Foo Bar <foo@bar.com>"
+ << ( QStringList()
+ << "\"Matt, Douhan\" <matt@fruitsalad.org>"
+ << "Foo Bar <foo@bar.com>" );
+ QTest::newRow( "" ) << "\"Lastname\\, Firstname\" <firstname.lastname@example.com>"
+ << ( QStringList()
+ << "\"Lastname\\, Firstname\" <firstname.lastname@example.com>" );
+}
+
+void EMailTest::testNormalizeAddressesAndEncodeIDNs()
+{
+ QFETCH( QString, input );
+ QFETCH( QString, expResult );
+
+ QCOMPARE( normalizeAddressesAndEncodeIdn( input ), expResult );
+}
+
+void EMailTest::testNormalizeAddressesAndEncodeIDNs_data()
+{
+ QTest::addColumn<QString>( "input" );
+ QTest::addColumn<QString>( "expResult" );
+
+ QTest::newRow( "" ) << "matt@fruitsalad.org" << "matt@fruitsalad.org";
+ QTest::newRow( "" ) << "Matt Douhan <matt@fruitsalad.org>"
+ << "Matt Douhan <matt@fruitsalad.org>";
+ QTest::newRow( "" ) << "Matt Douhan (jongel) <matt@fruitsalad.org>"
+ << "Matt Douhan (jongel) <matt@fruitsalad.org>";
+ QTest::newRow( "" ) << "Matt Douhan (jongel,fibbel) <matt@fruitsalad.org>"
+ << "Matt Douhan (jongel,fibbel) <matt@fruitsalad.org>";
+ QTest::newRow( "" ) << "matt@fruitsalad.org (jongel,fibbel)"
+ << "\"jongel,fibbel\" <matt@fruitsalad.org>";
+ QTest::newRow( "" ) << "matt@fruitsalad.org (\"jongel,fibbel\")"
+ << "\"jongel,fibbel\" <matt@fruitsalad.org>";
+}
+
+void EMailTest::testNormalizeAddressesAndDecodeIDNs()
+{
+ QFETCH( QString, input );
+ QFETCH( QString, expResult );
+
+ QCOMPARE( normalizeAddressesAndDecodeIdn( input ), expResult );
+}
+
+void EMailTest::testNormalizeAddressesAndDecodeIDNs_data()
+{
+ QTest::addColumn<QString>( "input" );
+ QTest::addColumn<QString>( "expResult" );
+
+ QTest::newRow( "" )
+ << "=?us-ascii?Q?Surname=2C=20Name?= <nobody@example.org>"
+ << "\"Surname, Name\" <nobody@example.org>";
+
+ QTest::newRow( "" )
+ << "=?iso-8859-1?B?5Hf8b2xmLPZBbmRyZWFz?= <nobody@example.org>"
+ << QString::fromUtf8( "\"äwüolf,öAndreas\" <nobody@example.org>" );
+
+ QTest::newRow( "" )
+ << QString::fromUtf8( "\"Andreas Straß\" <nobody@example.org>" )
+ << QString::fromUtf8( "\"Andreas Straß\" <nobody@example.org>" );
+
+ QTest::newRow( "" )
+ << QString::fromUtf8( "\"András\" \"Manţia\" <amantia@kde.org>" )
+ << QString::fromUtf8( "\"András\" \"Manţia\" <amantia@kde.org>" );
+}
+
+void EMailTest::testQuoteIfNecessary()
+{
+ QFETCH( QString, input );
+ QFETCH( QString, expResult );
+
+ QCOMPARE( quoteNameIfNecessary( input ), expResult );
+}
+
+void EMailTest::testQuoteIfNecessary_data()
+{
+ QTest::addColumn<QString>( "input" );
+ QTest::addColumn<QString>( "expResult" );
+
+ QTest::newRow( "" ) << "Matt Douhan" << "Matt Douhan";
+ QTest::newRow( "" ) << "Douhan, Matt" << "\"Douhan, Matt\"";
+ QTest::newRow( "" ) << "Matt \"jongel\" Douhan"
+ << "\"Matt \\\"jongel\\\" Douhan\"";
+ QTest::newRow( "" ) << "Matt \\\"jongel\\\" Douhan"
+ << "\"Matt \\\"jongel\\\" Douhan\"";
+ QTest::newRow( "" ) << "trailing '\\\\' should never occur \\"
+ << "\"trailing '\\\\' should never occur \\\"";
+ QTest::newRow( "" ) << "\"don't quote again\"" << "\"don't quote again\"";
+ QTest::newRow( "" ) << "\"leading double quote" << "\"\\\"leading double quote\"";
+ QTest::newRow( "" ) << "trailing double quote\"" << "\"trailing double quote\\\"\"";
+}
+
+void EMailTest::testMailtoUrls()
+{
+ QFETCH( QString, input );
+ const KUrl url = encodeMailtoUrl( input );
+ qDebug() << url;
+ QCOMPARE( url.protocol().toAscii().data(), "mailto" );
+ QCOMPARE( decodeMailtoUrl( url ), input );
+ qDebug() << decodeMailtoUrl( url );
+}
+
+void EMailTest::testMailtoUrls_data()
+{
+ QTest::addColumn<QString>( "input" );
+
+ QTest::newRow( "" ) << "tokoe@domain.com";
+ QTest::newRow( "" ) << QString::fromUtf8( "\"Tobias König\" <tokoe@domain.com>" );
+ QTest::newRow( "" ) << QString::fromUtf8( "\"Alberto Simões\" <alberto@example.com" );
+ QTest::newRow( "" ) << QString::fromUtf8( "Alberto Simões <alberto@example.com" );
+}
+
diff --git a/kpimutils/tests/testemail.h b/kpimutils/tests/testemail.h
new file mode 100644
index 0000000..e93ba39
--- /dev/null
+++ b/kpimutils/tests/testemail.h
@@ -0,0 +1,51 @@
+/*
+ This file is part of the KDE project
+ Copyright (C) 2004 David Faure <faure@kde.org>
+ Copyright (C) 2009 Thomas McGuire <mcguire@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License version 2 as published by the Free Software Foundation.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+#ifndef TESTEMAIL_H
+#define TESTEMAIL_H
+
+#include <QtCore/QObject>
+
+class EMailTest : public QObject
+{
+ Q_OBJECT
+ private Q_SLOTS:
+ void testGetNameAndEmail();
+ void testGetNameAndEmail_data();
+ void testIsValidEmailAddress();
+ void testIsValidEmailAddress_data();
+ void testIsValidAddressList();
+ void testIsValidAddressList_data();
+ void testIsValidSimpleEmailAddress();
+ void testIsValidSimpleEmailAddress_data();
+ void testGetEmailAddress();
+ void testGetEmailAddress_data();
+ void testCheckSplitEmailAddrList();
+ void testCheckSplitEmailAddrList_data();
+ void testNormalizeAddressesAndEncodeIDNs();
+ void testNormalizeAddressesAndEncodeIDNs_data();
+ void testNormalizeAddressesAndDecodeIDNs();
+ void testNormalizeAddressesAndDecodeIDNs_data();
+ void testQuoteIfNecessary();
+ void testQuoteIfNecessary_data();
+ void testMailtoUrls();
+ void testMailtoUrls_data();
+};
+
+#endif
diff --git a/kpimutils/tests/testlinklocator.cpp b/kpimutils/tests/testlinklocator.cpp
new file mode 100644
index 0000000..a1c0906
--- /dev/null
+++ b/kpimutils/tests/testlinklocator.cpp
@@ -0,0 +1,355 @@
+/*
+ This file is part of the kpimutils library.
+
+ Copyright (C) 2005 Ingo Kloecker <kloecker@kde.org>
+ Copyright (C) 2007 Allen Winter <winter@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License version 2 as published by the Free Software Foundation.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include "testlinklocator.h"
+
+#include "testlinklocator.moc"
+
+#include <qtest_kde.h>
+#include <kdebug.h>
+
+// GUI test, since the smileys use GUI stuff
+QTEST_KDEMAIN( LinkLocatorTest, GUI )
+
+#include "kpimutils/linklocator.h"
+using namespace KPIMUtils;
+
+void LinkLocatorTest::testGetEmailAddress()
+{
+ // empty input
+ const QString emptyQString;
+ LinkLocator ll1( emptyQString, 0 );
+ QVERIFY( ll1.getEmailAddress().isEmpty() );
+
+ // no '@' at scan position
+ LinkLocator ll2( "foo@bar.baz", 0 );
+ QVERIFY( ll2.getEmailAddress().isEmpty() );
+
+ // '@' in local part
+ LinkLocator ll3( "foo@bar@bar.baz", 7 );
+ QVERIFY( ll3.getEmailAddress().isEmpty() );
+
+ // empty local part
+ LinkLocator ll4( "@bar.baz", 0 );
+ QVERIFY( ll4.getEmailAddress().isEmpty() );
+ LinkLocator ll5( ".@bar.baz", 1 );
+ QVERIFY( ll5.getEmailAddress().isEmpty() );
+ LinkLocator ll6( " @bar.baz", 1 );
+ QVERIFY( ll6.getEmailAddress().isEmpty() );
+ LinkLocator ll7( ".!#$%&'*+-/=?^_`{|}~@bar.baz",
+ strlen( ".!#$%&'*+-/=?^_`{|}~" ) );
+ QVERIFY( ll7.getEmailAddress().isEmpty() );
+
+ // allowed special chars in local part of address
+ LinkLocator ll8( "a.!#$%&'*+-/=?^_`{|}~@bar.baz",
+ strlen( "a.!#$%&'*+-/=?^_`{|}~" ) );
+ QVERIFY( ll8.getEmailAddress() == "a.!#$%&'*+-/=?^_`{|}~@bar.baz" );
+
+ // '@' in domain part
+ LinkLocator ll9 ( "foo@bar@bar.baz", 3 );
+ QVERIFY( ll9.getEmailAddress().isEmpty() );
+
+ // domain part without dot
+ LinkLocator lla( "foo@bar", 3 );
+ QVERIFY( lla.getEmailAddress().isEmpty() );
+ LinkLocator llb( "foo@bar.", 3 );
+ QVERIFY( llb.getEmailAddress().isEmpty() );
+ LinkLocator llc( ".foo@bar", 4 );
+ QVERIFY( llc.getEmailAddress().isEmpty() );
+ LinkLocator lld( "foo@bar ", 3 );
+ QVERIFY( lld.getEmailAddress().isEmpty() );
+ LinkLocator lle( " foo@bar", 4 );
+ QVERIFY( lle.getEmailAddress().isEmpty() );
+ LinkLocator llf( "foo@bar-bar", 3 );
+ QVERIFY( llf.getEmailAddress().isEmpty() );
+
+ // empty domain part
+ LinkLocator llg( "foo@", 3 );
+ QVERIFY( llg.getEmailAddress().isEmpty() );
+ LinkLocator llh( "foo@.", 3 );
+ QVERIFY( llh.getEmailAddress().isEmpty() );
+ LinkLocator lli( "foo@-", 3 );
+ QVERIFY( lli.getEmailAddress().isEmpty() );
+
+ // simple address
+ LinkLocator llj( "foo@bar.baz", 3 );
+ QVERIFY( llj.getEmailAddress() == "foo@bar.baz" );
+ LinkLocator llk( "foo@bar.baz.", 3 );
+ QVERIFY( llk.getEmailAddress() == "foo@bar.baz" );
+ LinkLocator lll( ".foo@bar.baz", 4 );
+ QVERIFY( lll.getEmailAddress() == "foo@bar.baz" );
+ LinkLocator llm( "foo@bar.baz-", 3 );
+ QVERIFY( llm.getEmailAddress() == "foo@bar.baz" );
+ LinkLocator lln( "-foo@bar.baz", 4 );
+ QVERIFY( lln.getEmailAddress() == "foo@bar.baz" );
+ LinkLocator llo( "foo@bar.baz ", 3 );
+ QVERIFY( llo.getEmailAddress() == "foo@bar.baz" );
+ LinkLocator llp( " foo@bar.baz", 4 );
+ QVERIFY( llp.getEmailAddress() == "foo@bar.baz" );
+ LinkLocator llq( "foo@bar-bar.baz", 3 );
+ QVERIFY( llq.getEmailAddress() == "foo@bar-bar.baz" );
+}
+
+void LinkLocatorTest::testGetUrl()
+{
+ QStringList brackets;
+ brackets << "" << ""; // no brackets
+ brackets << "(" << ")";
+ brackets << "<" << ">";
+ brackets << "[" << "]";
+ brackets << "\"" << "\"";
+ brackets << "<link>" << "</link>";
+
+ for ( int i = 0; i < brackets.count(); i += 2 ) {
+ testGetUrl2( brackets[ i ], brackets[ i + 1 ] );
+ }
+}
+
+void LinkLocatorTest::testGetUrl2( const QString &left, const QString &right )
+{
+ QStringList schemas;
+ schemas << "http://";
+ schemas << "https://";
+ schemas << "vnc://";
+ schemas << "fish://";
+ schemas << "ftp://";
+ schemas << "ftps://";
+ schemas << "sftp://";
+ schemas << "smb://";
+ schemas << "file://";
+
+ QStringList urls;
+ urls << "www.kde.org";
+ urls << "user@www.kde.org";
+ urls << "user:pass@www.kde.org";
+ urls << "user:pass@www.kde.org:1234";
+ urls << "user:pass@www.kde.org:1234/sub/path";
+ urls << "user:pass@www.kde.org:1234/sub/path?a=1";
+ urls << "user:pass@www.kde.org:1234/sub/path?a=1#anchor";
+ urls << "user:pass@www.kde.org:1234/sub/\npath \n /long/ path \t ?a=1#anchor";
+ urls << "user:pass@www.kde.org:1234/sub/path/special(123)?a=1#anchor";
+ urls << "user:pass@www.kde.org:1234/sub/path:with:colon/special(123)?a=1#anchor";
+ urls << "user:pass@www.kde.org:1234/sub/path:with:colon/special(123)?a=1#anchor[bla";
+ urls << "user:pass@www.kde.org:1234/sub/path:with:colon/special(123)?a=1#anchor[bla]";
+ urls << "user:pass@www.kde.org:1234/\nsub/path:with:colon/\nspecial(123)?\na=1#anchor[bla]";
+ urls << "user:pass@www.kde.org:1234/ \n sub/path:with:colon/ \n\t \t special(123)?"
+ "\n\t \n\t a=1#anchor[bla]";
+
+ foreach ( const QString &schema, schemas ) {
+ foreach ( QString url, urls ) { //krazy:exclude=foreach
+ // by definition: if the URL is enclosed in brackets, the URL itself is not allowed
+ // to contain the closing bracket, as this would be detected as the end of the URL
+ if ( ( left.length() == 1 ) && ( url.contains( right[ 0 ] ) ) ) {
+ continue;
+ }
+
+ // if the url contains a whitespace, it must be enclosed with brackets
+ if ( ( url.contains( '\n' ) || url.contains( '\t' ) || url.contains( ' ' ) ) &&
+ left.isEmpty() ) {
+ continue;
+ }
+
+ QString test( left + schema + url + right );
+ LinkLocator ll( test, left.length() );
+ QString gotUrl = ll.getUrl();
+
+ // we want to have the url without whitespace
+ url.remove( ' ' );
+ url.remove( '\n' );
+ url.remove( '\t' );
+
+ bool ok = ( gotUrl == ( schema + url ) );
+ //qDebug() << "check:" << (ok ? "OK" : "NOK") << test << "=>" << (schema + url);
+ if ( !ok ) {
+ qDebug() << "got:" << gotUrl;
+ }
+ QVERIFY2( ok, qPrintable( test ) );
+ }
+ }
+
+ QStringList urlsWithoutSchema;
+ urlsWithoutSchema << ".kde.org";
+ urlsWithoutSchema << ".kde.org:1234/sub/path";
+ urlsWithoutSchema << ".kde.org:1234/sub/path?a=1";
+ urlsWithoutSchema << ".kde.org:1234/sub/path?a=1#anchor";
+ urlsWithoutSchema << ".kde.org:1234/sub/path/special(123)?a=1#anchor";
+ urlsWithoutSchema << ".kde.org:1234/sub/path:with:colon/special(123)?a=1#anchor";
+ urlsWithoutSchema << ".kde.org:1234/sub/path:with:colon/special(123)?a=1#anchor[bla";
+ urlsWithoutSchema << ".kde.org:1234/sub/path:with:colon/special(123)?a=1#anchor[bla]";
+ urlsWithoutSchema << ".kde.org:1234/\nsub/path:with:colon/\nspecial(123)?\na=1#anchor[bla]";
+ urlsWithoutSchema << ".kde.org:1234/ \n sub/path:with:colon/ \n\t \t special(123)?"
+ "\n\t \n\t a=1#anchor[bla]";
+
+ QStringList starts;
+ starts << "www" << "ftp" << "news:www";
+
+ foreach ( const QString &start, starts ) {
+ foreach ( QString url, urlsWithoutSchema ) { //krazy:exclude=foreach
+ // by definition: if the URL is enclosed in brackets, the URL itself is not allowed
+ // to contain the closing bracket, as this would be detected as the end of the URL
+ if ( ( left.length() == 1 ) && ( url.contains( right[ 0 ] ) ) ) {
+ continue;
+ }
+
+ // if the url contains a whitespace, it must be enclosed with brackets
+ if ( ( url.contains( '\n' ) || url.contains( '\t' ) || url.contains( ' ' ) ) &&
+ left.isEmpty() ) {
+ continue;
+ }
+
+ QString test( left + start + url + right );
+ LinkLocator ll( test, left.length() );
+ QString gotUrl = ll.getUrl();
+
+ // we want to have the url without whitespace
+ url.remove( ' ' );
+ url.remove( '\n' );
+ url.remove( '\t' );
+
+ bool ok = ( gotUrl == ( start + url ) );
+ //qDebug() << "check:" << (ok ? "OK" : "NOK") << test << "=>" << (start + url);
+ if ( !ok ) {
+ qDebug() << "got:" << gotUrl;
+ }
+ QVERIFY2( ok, qPrintable( gotUrl ) );
+ }
+ }
+
+ // test max url length
+ QString url = "http://www.kde.org/this/is/a_very_loooooong_url/test/test/test";
+ {
+ LinkLocator ll( url );
+ ll.setMaxUrlLen( 10 );
+ QVERIFY( ll.getUrl().isEmpty() ); // url too long
+ }
+ {
+ LinkLocator ll( url );
+ ll.setMaxUrlLen( url.length() - 1 );
+ QVERIFY( ll.getUrl().isEmpty() ); // url too long
+ }
+ {
+ LinkLocator ll( url );
+ ll.setMaxUrlLen( url.length() );
+ QVERIFY( ll.getUrl() == url );
+ }
+ {
+ LinkLocator ll( url );
+ ll.setMaxUrlLen( url.length() + 1 );
+ QVERIFY( ll.getUrl() == url );
+ }
+
+ // mailto
+ {
+ QString addr = "mailto:test@kde.org";
+ QString test( left + addr + right );
+ LinkLocator ll( test, left.length() );
+
+ QString gotUrl = ll.getUrl();
+
+ bool ok = ( gotUrl == addr );
+ //qDebug() << "check:" << (ok ? "OK" : "NOK") << test << "=>" << addr;
+ if ( !ok ) {
+ qDebug() << "got:" << gotUrl;
+ }
+ QVERIFY2( ok, qPrintable( gotUrl ) );
+ }
+}
+
+void LinkLocatorTest::testHtmlConvert_data()
+{
+ QTest::addColumn<QString>( "plainText" );
+ QTest::addColumn<int>( "flags" );
+ QTest::addColumn<QString>( "htmlText" );
+
+ // Linker error when using PreserveSpaces, therefore the hardcoded 0x01 or 0x09
+
+ // Test preserving whitespace correctly
+ QTest::newRow( "" ) << " foo" << 0x01 << "&nbsp;foo";
+ QTest::newRow( "" ) << " foo" << 0x01 << "&nbsp;&nbsp;foo";
+ QTest::newRow( "" ) << " foo " << 0x01 << "&nbsp;&nbsp;foo&nbsp;&nbsp;";
+ QTest::newRow( "" ) << " foo " << 0x01 << "&nbsp;&nbsp;foo&nbsp;";
+ QTest::newRow( "" ) << "bla bla bla bla bla" << 0x01 << "bla bla bla bla bla";
+ QTest::newRow( "" ) << "bla bla bla \n bla bla bla " << 0x01
+ << "bla bla bla&nbsp;<br />\n&nbsp;&nbsp;bla bla bla&nbsp;";
+ QTest::newRow( "" ) << "bla bla bla" << 0x01
+ << "bla bla&nbsp;&nbsp;bla";
+ QTest::newRow( "" ) << " bla bla \n bla bla a\n bla bla " << 0x01
+ << "&nbsp;bla bla&nbsp;<br />\n&nbsp;bla bla a<br />\n"
+ "&nbsp;&nbsp;bla bla&nbsp;";
+
+ // Test highlighting with *, / and _
+ QTest::newRow( "" ) << "Ce paragraphe _contient_ des mots ou des _groupes de mots_ à mettre en"
+ " forme…" << 0x09 << "Ce paragraphe <u>contient</u> des mots ou des"
+ " <u>groupes de mots</u> à mettre en forme…";
+ QTest::newRow( "punctation-bug" ) << "Ce texte *a l'air* de _fonctionner_, à condition"
+ " d’utiliser le guillemet ASCII." << 0x09
+ << "Ce texte <b>a l'air</b> de <u>fonctionner</u>, à"
+ " condition d’utiliser le guillemet ASCII.";
+ QTest::newRow( "punctation-bug" ) << "Un répertoire /est/ un *dossier* où on peut mettre des"
+ " *fichiers*." << 0x09 << "Un répertoire <i>est</i> un"
+ " <b>dossier</b> où on peut mettre des <b>fichiers</b>.";
+ QTest::newRow( "punctation-bug" ) << "*BLA BLA BLA BLA*." << 0x09 << "<b>BLA BLA BLA BLA</b>.";
+ QTest::newRow( "" ) << "Je vais tenter de repérer des faux positif*" << 0x09
+ << "Je vais tenter de repérer des faux positif*";
+ QTest::newRow( "" ) << "*Ouais !* *Yes!*" << 0x09 << "<b>Ouais !</b> <b>Yes!</b>";
+ QTest::newRow( "" ) << "the /etc/{rsyslog.d,syslog-ng.d}/package.rpmnew file" << 0x09
+ << "the /etc/{rsyslog.d,syslog-ng.d}/package.rpmnew file";
+
+ // This test has problems with the encoding, apparently.
+ //QTest::newRow( "" ) << "*Ça fait plaisir de pouvoir utiliser des lettres accentuées dans du"
+ // " texte mis en forme*." << 0x09 << "<b>Ça fait plaisir de pouvoir"
+ // " utiliser des lettres accentuées dans du texte mis en forme</b>.";
+
+ // Bug reported by dfaure, the <hostname> would get lost
+ QTest::newRow( "" ) << "KUrl url(\"http://strange<hostname>/\");" << ( 0x08 | 0x02 )
+ << "KUrl url(&quot;<a href=\"http://strange<hostname>/\">"
+ "http://strange&lt;hostname&gt;/</a>&quot;);";
+
+ // Bug: 211128 - plain text emails should not replace ampersand & with &amp;
+ QTest::newRow( "bug211128" ) << "https://green-site/?Ticket=85&Page=next" << 0x01
+ << "<a href=\"https://green-site/?Ticket=85&Page=next\">"
+ "https://green-site/?Ticket=85&amp;Page=next</a>";
+
+ QTest::newRow( "dotBeforeEnd" ) << "Look at this file: www.example.com/example.h" << 0x01
+ << "Look at this file: <a href=\"http://www.example.com/example.h\">"
+ "www.example.com/example.h</a>";
+ QTest::newRow( "dotInMiddle" ) << "Look at this file: www.example.com/.bashrc" << 0x01
+ << "Look at this file: <a href=\"http://www.example.com/.bashrc\">"
+ "www.example.com/.bashrc</a>";
+
+ // A dot at the end of an URL is explicitly ignored
+ QTest::newRow( "dotAtEnd" ) << "Look at this file: www.example.com/test.cpp." << 0x01
+ << "Look at this file: <a href=\"http://www.example.com/test.cpp\">"
+ "www.example.com/test.cpp</a>.";
+}
+
+void LinkLocatorTest::testHtmlConvert()
+{
+ QFETCH( QString, plainText );
+ QFETCH( int, flags );
+ QFETCH( QString, htmlText );
+
+ QEXPECT_FAIL( "punctation-bug", "Linklocator does not properly detect punctation as boundaries",
+ Continue );
+
+ QString actualHtml = LinkLocator::convertToHtml( plainText, flags );
+ QCOMPARE( actualHtml, htmlText );
+}
diff --git a/kpimutils/tests/testlinklocator.h b/kpimutils/tests/testlinklocator.h
new file mode 100644
index 0000000..2918c72
--- /dev/null
+++ b/kpimutils/tests/testlinklocator.h
@@ -0,0 +1,40 @@
+/*
+ This file is part of the kpimutils library.
+
+ Copyright (c) 2007 Allen Winter <winter@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#ifndef TESTLINKLOCATOR_H
+#define TESTLINKLOCATOR_H
+
+#include <QtCore/QObject>
+
+class LinkLocatorTest : public QObject
+{
+ Q_OBJECT
+ private Q_SLOTS:
+ void testGetEmailAddress();
+ void testGetUrl();
+ void testHtmlConvert();
+ void testHtmlConvert_data();
+
+ private:
+ void testGetUrl2( const QString &left, const QString &right );
+};
+
+#endif