1 /*
2  * This file is part of KMail.
3  *
4  * SPDX-FileCopyrightText: 2010 KDAB
5  * SPDX-FileContributor: Tobias Koenig <tokoe@kde.org>
6  *
7  * SPDX-License-Identifier: GPL-2.0-or-later
8  */
9 
10 #include "aliasesexpandjob.h"
11 
12 #include "distributionlistexpandjob.h"
13 
14 #include <Akonadi/Contact/ContactGroupExpandJob>
15 #include <Akonadi/Contact/ContactSearchJob>
16 #include <KEmailAddress>
17 
18 #include <MessageCore/StringUtil>
19 
20 using namespace MessageComposer;
21 
AliasesExpandJob(const QString & recipients,const QString & defaultDomain,QObject * parent)22 AliasesExpandJob::AliasesExpandJob(const QString &recipients, const QString &defaultDomain, QObject *parent)
23     : KJob(parent)
24     , mRecipients(KEmailAddress::splitAddressList(recipients))
25     , mDefaultDomain(defaultDomain)
26 {
27 }
28 
~AliasesExpandJob()29 AliasesExpandJob::~AliasesExpandJob()
30 {
31 }
32 
start()33 void AliasesExpandJob::start()
34 {
35     // At first we try to expand the recipient to a distribution list
36     // or nick name and save the results in a map for later lookup
37     for (const QString &recipient : std::as_const(mRecipients)) {
38         // speedup: assume aliases and list names don't contain '@'
39         if (recipient.isEmpty() || recipient.contains(QLatin1Char('@'))) {
40             continue;
41         }
42 
43         // check for distribution list
44         auto expandJob = new DistributionListExpandJob(recipient, this);
45         expandJob->setProperty("recipient", recipient);
46         connect(expandJob, &Akonadi::ContactGroupExpandJob::result, this, &AliasesExpandJob::slotDistributionListExpansionDone);
47         mDistributionListExpansionJobs++;
48         expandJob->start();
49 
50         // check for nick name
51         auto searchJob = new Akonadi::ContactSearchJob(this);
52         searchJob->setProperty("recipient", recipient);
53         searchJob->setQuery(Akonadi::ContactSearchJob::NickName, recipient.toLower());
54         connect(searchJob, &Akonadi::ContactSearchJob::result, this, &AliasesExpandJob::slotNicknameExpansionDone);
55         mNicknameExpansionJobs++;
56         searchJob->start();
57     }
58 
59     if (mDistributionListExpansionJobs == 0 && mNicknameExpansionJobs == 0) {
60         emitResult();
61     }
62 }
63 
addresses() const64 QString AliasesExpandJob::addresses() const
65 {
66     return mEmailAddresses;
67 }
68 
emptyDistributionLists() const69 QStringList AliasesExpandJob::emptyDistributionLists() const
70 {
71     return mEmptyDistributionLists;
72 }
73 
slotDistributionListExpansionDone(KJob * job)74 void AliasesExpandJob::slotDistributionListExpansionDone(KJob *job)
75 {
76     if (job->error()) {
77         setError(job->error());
78         setErrorText(job->errorText());
79         emitResult();
80         return;
81     }
82 
83     const DistributionListExpandJob *expandJob = qobject_cast<DistributionListExpandJob *>(job);
84     const QString recipient = expandJob->property("recipient").toString();
85 
86     DistributionListExpansionResult result;
87     result.addresses = expandJob->addresses();
88     result.isEmpty = expandJob->isEmpty();
89 
90     mDistListExpansionResults.insert(recipient, result);
91 
92     mDistributionListExpansionJobs--;
93     if (mDistributionListExpansionJobs == 0 && mNicknameExpansionJobs == 0) {
94         finishExpansion();
95     }
96 }
97 
slotNicknameExpansionDone(KJob * job)98 void AliasesExpandJob::slotNicknameExpansionDone(KJob *job)
99 {
100     if (job->error()) {
101         setError(job->error());
102         setErrorText(job->errorText());
103         emitResult();
104         return;
105     }
106 
107     const Akonadi::ContactSearchJob *searchJob = qobject_cast<Akonadi::ContactSearchJob *>(job);
108     const KContacts::Addressee::List contacts = searchJob->contacts();
109     const QString recipient = searchJob->property("recipient").toString();
110 
111     for (const KContacts::Addressee &contact : contacts) {
112         if (contact.nickName().toLower() == recipient.toLower()) {
113             mNicknameExpansionResults.insert(recipient, contact.fullEmail());
114             break;
115         }
116     }
117 
118     mNicknameExpansionJobs--;
119     if (mDistributionListExpansionJobs == 0 && mNicknameExpansionJobs == 0) {
120         finishExpansion();
121     }
122 }
123 
finishExpansion()124 void AliasesExpandJob::finishExpansion()
125 {
126     for (const QString &recipient : std::as_const(mRecipients)) {
127         if (recipient.isEmpty()) {
128             continue;
129         }
130         if (!mEmailAddresses.isEmpty()) {
131             mEmailAddresses += QLatin1String(", ");
132         }
133 
134         const QString receiver = recipient.trimmed();
135 
136         // take prefetched expand distribution list results
137         const DistributionListExpansionResult result = mDistListExpansionResults.value(recipient);
138         QString displayName;
139         QString addrSpec;
140         QString comment;
141 
142         if (result.isEmpty) {
143             KEmailAddress::splitAddress(receiver, displayName, addrSpec, comment);
144             mEmailAddressOnly.append(addrSpec);
145             mEmailAddresses += receiver;
146             mEmptyDistributionLists << receiver;
147             continue;
148         }
149 
150         if (!result.addresses.isEmpty()) {
151             KEmailAddress::splitAddress(result.addresses, displayName, addrSpec, comment);
152             mEmailAddressOnly.append(addrSpec);
153 
154             mEmailAddresses += result.addresses;
155             continue;
156         }
157 
158         // take prefetched expand nick name results
159         const QString recipientValue = mNicknameExpansionResults.value(recipient);
160         if (!recipientValue.isEmpty()) {
161             mEmailAddresses += recipientValue;
162             KEmailAddress::splitAddress(recipientValue, displayName, addrSpec, comment);
163             mEmailAddressOnly.append(addrSpec);
164 
165             continue;
166         }
167 
168         // check whether the address is missing the domain part
169         KEmailAddress::splitAddress(receiver, displayName, addrSpec, comment);
170         if (!addrSpec.contains(QLatin1Char('@'))) {
171             if (!mDefaultDomain.isEmpty()) {
172                 mEmailAddresses += KEmailAddress::normalizedAddress(displayName, addrSpec + QLatin1Char('@') + mDefaultDomain, comment);
173             } else {
174                 mEmailAddresses += MessageCore::StringUtil::guessEmailAddressFromLoginName(addrSpec);
175             }
176         } else {
177             mEmailAddresses += receiver;
178         }
179         mEmailAddressOnly.append(addrSpec);
180     }
181 
182     emitResult();
183 }
184 
emailAddressOnly() const185 QStringList AliasesExpandJob::emailAddressOnly() const
186 {
187     return mEmailAddressOnly;
188 }
189