1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6 // as does this
7 #include "msgCore.h" // for pre-compiled headers
8 #include "nsMsgUtils.h"
9
10 #include "nsImapStringBundle.h"
11 #include "nsVersionComparator.h"
12
13 #include "nsMsgImapCID.h"
14 #include "nsThreadUtils.h"
15 #include "nsIMsgStatusFeedback.h"
16 #include "nsImapCore.h"
17 #include "nsImapProtocol.h"
18 #include "nsIMsgMailNewsUrl.h"
19 #include "nsIImapHostSessionList.h"
20 #include "nsImapBodyShell.h"
21 #include "nsImapMailFolder.h"
22 #include "nsIMsgAccountManager.h"
23 #include "nsImapServerResponseParser.h"
24 #include "nspr.h"
25 #include "plbase64.h"
26 #include "nsIEventTarget.h"
27 #include "nsIImapService.h"
28 #include "nsISocketTransportService.h"
29 #include "nsIStreamListenerTee.h"
30 #include "nsIInputStreamPump.h"
31 #include "nsNetUtil.h"
32 #include "nsIDBFolderInfo.h"
33 #include "nsIPipe.h"
34 #include "nsIMsgFolder.h"
35 #include "nsMsgMessageFlags.h"
36 #include "nsTextFormatter.h"
37 #include "nsTransportUtils.h"
38 #include "nsIMsgHdr.h"
39 #include "nsMsgI18N.h"
40 // for the memory cache...
41 #include "nsICacheEntry.h"
42 #include "nsICacheStorage.h"
43 #include "nsICacheEntryOpenCallback.h"
44 #include "nsIURIMutator.h"
45
46 #include "nsIDocShell.h"
47 #include "nsILoadInfo.h"
48 #include "nsCOMPtr.h"
49 #include "nsMimeTypes.h"
50 #include "nsIInterfaceRequestor.h"
51 #include "nsXPCOMCIDInternal.h"
52 #include "nsIXULAppInfo.h"
53 #include "nsSocketTransportService2.h"
54 #include "nsSyncRunnableHelpers.h"
55 #include "nsICancelable.h"
56
57 // netlib required files
58 #include "nsIStreamListener.h"
59 #include "nsIMsgIncomingServer.h"
60 #include "nsIImapIncomingServer.h"
61 #include "nsIPrefBranch.h"
62 #include "nsIPrefService.h"
63 #include "nsIPrefLocalizedString.h"
64 #include "nsImapUtils.h"
65 #include "nsIStreamConverterService.h"
66 #include "nsIProxyInfo.h"
67 #include "nsISSLSocketControl.h"
68 #include "nsITransportSecurityInfo.h"
69 #include "nsProxyRelease.h"
70 #include "nsDebug.h"
71 #include "nsMsgCompressIStream.h"
72 #include "nsMsgCompressOStream.h"
73 #include "mozilla/Logging.h"
74 #include "mozilla/Attributes.h"
75 #include "mozilla/SlicedInputStream.h"
76 #include "nsIPrincipal.h"
77 #include "nsContentSecurityManager.h"
78
79 // imap event sinks
80 #include "nsIImapMailFolderSink.h"
81 #include "nsIImapServerSink.h"
82 #include "nsIImapMessageSink.h"
83
84 #include "mozilla/dom/InternalResponse.h"
85 #include "mozilla/NullPrincipal.h"
86
87 // TLS alerts
88 #include "NSSErrorsService.h"
89
90 using namespace mozilla;
91
92 LazyLogModule IMAP("IMAP");
93 LazyLogModule IMAP_CS("IMAP_CS");
94 LazyLogModule IMAPCache("IMAPCache");
95
96 #define ONE_SECOND ((uint32_t)1000) // one second
97
98 #define OUTPUT_BUFFER_SIZE (4096 * 2)
99
100 #define IMAP_ENV_HEADERS "From To Cc Bcc Subject Date Message-ID "
101 #define IMAP_DB_HEADERS \
102 "Priority X-Priority References Newsgroups In-Reply-To Content-Type " \
103 "Reply-To"
104 #define IMAP_ENV_AND_DB_HEADERS IMAP_ENV_HEADERS IMAP_DB_HEADERS
105 static const PRIntervalTime kImapSleepTime = PR_MillisecondsToInterval(60000);
106 static int32_t gPromoteNoopToCheckCount = 0;
107 static const uint32_t kFlagChangesBeforeCheck = 10;
108 static const int32_t kMaxSecondsBeforeCheck = 600;
109
110 class AutoProxyReleaseMsgWindow {
111 public:
AutoProxyReleaseMsgWindow()112 AutoProxyReleaseMsgWindow() : mMsgWindow() {}
~AutoProxyReleaseMsgWindow()113 ~AutoProxyReleaseMsgWindow() {
114 NS_ReleaseOnMainThread("AutoProxyReleaseMsgWindow::mMsgWindow",
115 dont_AddRef(mMsgWindow));
116 }
StartAssignment()117 nsIMsgWindow** StartAssignment() {
118 MOZ_ASSERT(!mMsgWindow);
119 return &mMsgWindow;
120 }
operator nsIMsgWindow*()121 operator nsIMsgWindow*() { return mMsgWindow; }
122
123 private:
124 nsIMsgWindow* mMsgWindow;
125 };
126
getter_AddRefs(AutoProxyReleaseMsgWindow & aSmartPtr)127 nsIMsgWindow** getter_AddRefs(AutoProxyReleaseMsgWindow& aSmartPtr) {
128 return aSmartPtr.StartAssignment();
129 }
130
NS_IMPL_ISUPPORTS(nsMsgImapHdrXferInfo,nsIImapHeaderXferInfo)131 NS_IMPL_ISUPPORTS(nsMsgImapHdrXferInfo, nsIImapHeaderXferInfo)
132
133 nsMsgImapHdrXferInfo::nsMsgImapHdrXferInfo() : m_hdrInfos(kNumHdrsToXfer) {
134 m_nextFreeHdrInfo = 0;
135 }
136
~nsMsgImapHdrXferInfo()137 nsMsgImapHdrXferInfo::~nsMsgImapHdrXferInfo() {}
138
GetNumHeaders(int32_t * aNumHeaders)139 NS_IMETHODIMP nsMsgImapHdrXferInfo::GetNumHeaders(int32_t* aNumHeaders) {
140 *aNumHeaders = m_nextFreeHdrInfo;
141 return NS_OK;
142 }
143
GetHeader(int32_t hdrIndex,nsIImapHeaderInfo ** aResult)144 NS_IMETHODIMP nsMsgImapHdrXferInfo::GetHeader(int32_t hdrIndex,
145 nsIImapHeaderInfo** aResult) {
146 // If the header index is more than (or equal to) our next free pointer, then
147 // its a header we haven't really got and the caller has done something
148 // wrong.
149 NS_ENSURE_TRUE(hdrIndex < m_nextFreeHdrInfo, NS_ERROR_NULL_POINTER);
150
151 NS_IF_ADDREF(*aResult = m_hdrInfos.SafeObjectAt(hdrIndex));
152 if (!*aResult) return NS_ERROR_NULL_POINTER;
153 return NS_OK;
154 }
155
156 static const int32_t kInitLineHdrCacheSize = 512; // should be about right
157
StartNewHdr()158 nsIImapHeaderInfo* nsMsgImapHdrXferInfo::StartNewHdr() {
159 if (m_nextFreeHdrInfo >= kNumHdrsToXfer) return nullptr;
160
161 nsIImapHeaderInfo* result = m_hdrInfos.SafeObjectAt(m_nextFreeHdrInfo++);
162 if (result) return result;
163
164 nsMsgImapLineDownloadCache* lineCache = new nsMsgImapLineDownloadCache();
165 if (!lineCache) return nullptr;
166
167 lineCache->GrowBuffer(kInitLineHdrCacheSize);
168
169 m_hdrInfos.AppendObject(lineCache);
170
171 return lineCache;
172 }
173
174 // maybe not needed...
FinishCurrentHdr()175 void nsMsgImapHdrXferInfo::FinishCurrentHdr() {
176 // nothing to do?
177 }
178
ResetAll()179 void nsMsgImapHdrXferInfo::ResetAll() {
180 int32_t count = m_hdrInfos.Count();
181 for (int32_t i = 0; i < count; i++) {
182 nsIImapHeaderInfo* hdrInfo = m_hdrInfos[i];
183 if (hdrInfo) hdrInfo->ResetCache();
184 }
185 m_nextFreeHdrInfo = 0;
186 }
187
ReleaseAll()188 void nsMsgImapHdrXferInfo::ReleaseAll() {
189 m_hdrInfos.Clear();
190 m_nextFreeHdrInfo = 0;
191 }
192
NS_IMPL_ISUPPORTS(nsMsgImapLineDownloadCache,nsIImapHeaderInfo)193 NS_IMPL_ISUPPORTS(nsMsgImapLineDownloadCache, nsIImapHeaderInfo)
194
195 // **** helper class for downloading line ****
196 nsMsgImapLineDownloadCache::nsMsgImapLineDownloadCache() {
197 fLineInfo = (msg_line_info*)PR_CALLOC(sizeof(msg_line_info));
198 fLineInfo->uidOfMessage = nsMsgKey_None;
199 m_msgSize = 0;
200 }
201
~nsMsgImapLineDownloadCache()202 nsMsgImapLineDownloadCache::~nsMsgImapLineDownloadCache() {
203 PR_Free(fLineInfo);
204 }
205
CurrentUID()206 uint32_t nsMsgImapLineDownloadCache::CurrentUID() {
207 return fLineInfo->uidOfMessage;
208 }
209
SpaceAvailable()210 uint32_t nsMsgImapLineDownloadCache::SpaceAvailable() {
211 MOZ_ASSERT(kDownLoadCacheSize >= m_bufferPos);
212 if (kDownLoadCacheSize <= m_bufferPos) return 0;
213 return kDownLoadCacheSize - m_bufferPos;
214 }
215
GetCurrentLineInfo()216 msg_line_info* nsMsgImapLineDownloadCache::GetCurrentLineInfo() {
217 AppendBuffer("", 1); // null terminate the buffer
218 fLineInfo->adoptedMessageLine = GetBuffer();
219 return fLineInfo;
220 }
221
ResetCache()222 NS_IMETHODIMP nsMsgImapLineDownloadCache::ResetCache() {
223 ResetWritePos();
224 return NS_OK;
225 }
226
CacheEmpty()227 bool nsMsgImapLineDownloadCache::CacheEmpty() { return m_bufferPos == 0; }
228
CacheLine(const char * line,uint32_t uid)229 NS_IMETHODIMP nsMsgImapLineDownloadCache::CacheLine(const char* line,
230 uint32_t uid) {
231 NS_ASSERTION((PL_strlen(line) + 1) <= SpaceAvailable(),
232 "Oops... line length greater than space available");
233
234 fLineInfo->uidOfMessage = uid;
235
236 AppendString(line);
237 return NS_OK;
238 }
239
240 /* attribute nsMsgKey msgUid; */
GetMsgUid(nsMsgKey * aMsgUid)241 NS_IMETHODIMP nsMsgImapLineDownloadCache::GetMsgUid(nsMsgKey* aMsgUid) {
242 *aMsgUid = fLineInfo->uidOfMessage;
243 return NS_OK;
244 }
SetMsgUid(nsMsgKey aMsgUid)245 NS_IMETHODIMP nsMsgImapLineDownloadCache::SetMsgUid(nsMsgKey aMsgUid) {
246 fLineInfo->uidOfMessage = aMsgUid;
247 return NS_OK;
248 }
249
250 /* attribute long msgSize; */
GetMsgSize(int32_t * aMsgSize)251 NS_IMETHODIMP nsMsgImapLineDownloadCache::GetMsgSize(int32_t* aMsgSize) {
252 *aMsgSize = m_msgSize;
253 return NS_OK;
254 }
255
SetMsgSize(int32_t aMsgSize)256 NS_IMETHODIMP nsMsgImapLineDownloadCache::SetMsgSize(int32_t aMsgSize) {
257 m_msgSize = aMsgSize;
258 return NS_OK;
259 }
260
261 /* attribute string msgHdrs; */
GetMsgHdrs(const char ** aMsgHdrs)262 NS_IMETHODIMP nsMsgImapLineDownloadCache::GetMsgHdrs(const char** aMsgHdrs) {
263 // this doesn't copy the string
264 AppendBuffer("", 1); // null terminate the buffer
265 *aMsgHdrs = GetBuffer();
266 return NS_OK;
267 }
268
269 // The following macros actually implement addref, release and query interface
270 // for our component.
271 NS_IMPL_ADDREF_INHERITED(nsImapProtocol, nsMsgProtocol)
272 NS_IMPL_RELEASE_INHERITED(nsImapProtocol, nsMsgProtocol)
273
274 NS_INTERFACE_MAP_BEGIN(nsImapProtocol)
275 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIImapProtocol)
276 NS_INTERFACE_MAP_ENTRY(nsIImapProtocol)
277 NS_INTERFACE_MAP_ENTRY(nsIProtocolProxyCallback)
278 NS_INTERFACE_MAP_ENTRY(nsIInputStreamCallback)
279 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
280 NS_INTERFACE_MAP_ENTRY(nsIImapProtocolSink)
281 NS_INTERFACE_MAP_ENTRY(nsIMsgAsyncPromptListener)
282 NS_INTERFACE_MAP_END
283
284 static int32_t gTooFastTime = 2;
285 static int32_t gIdealTime = 4;
286 static int32_t gChunkAddSize = 16384;
287 static int32_t gChunkSize = 250000;
288 static int32_t gChunkThreshold = gChunkSize + gChunkSize / 2;
289 static bool gChunkSizeDirty = false;
290 static bool gFetchByChunks = true;
291 static bool gInitialized = false;
292 static bool gHideUnusedNamespaces = true;
293 static bool gHideOtherUsersFromList = false;
294 static bool gUseEnvelopeCmd = false;
295 static bool gUseLiteralPlus = true;
296 static bool gExpungeAfterDelete = false;
297 static bool gCheckDeletedBeforeExpunge = false; // bug 235004
298 static int32_t gResponseTimeout = 100;
299 static int32_t gAppendTimeout = gResponseTimeout / 5;
300 static nsCString gForceSelectDetect;
301 static nsTArray<nsCString> gForceSelectServersArray;
302 static nsImapProtocol::TCPKeepalive gTCPKeepalive;
303
304 // let delete model control expunging, i.e., don't ever expunge when the
305 // user chooses the imap delete model, otherwise, expunge when over the
306 // threshold. This is the normal TB behavior.
307 static const int32_t kAutoExpungeDeleteModel = 0;
308 // Expunge whenever the folder is opened
309 static const int32_t kAutoExpungeAlways = 1;
310 // Expunge when over the threshold, independent of the delete model.
311 static const int32_t kAutoExpungeOnThreshold = 2;
312 static int32_t gExpungeOption = kAutoExpungeDeleteModel;
313 static int32_t gExpungeThreshold = 20;
314
315 const int32_t kAppBufSize = 100;
316 // can't use static nsCString because it shows up as a leak.
317 static char gAppName[kAppBufSize];
318 static char gAppVersion[kAppBufSize];
319
GlobalInitialization(nsIPrefBranch * aPrefBranch)320 nsresult nsImapProtocol::GlobalInitialization(nsIPrefBranch* aPrefBranch) {
321 gInitialized = true;
322
323 aPrefBranch->GetIntPref("mail.imap.chunk_fast",
324 &gTooFastTime); // secs we read too little too fast
325 aPrefBranch->GetIntPref("mail.imap.chunk_ideal",
326 &gIdealTime); // secs we read enough in good time
327 aPrefBranch->GetIntPref(
328 "mail.imap.chunk_add",
329 &gChunkAddSize); // buffer size to add when wasting time
330 aPrefBranch->GetIntPref("mail.imap.chunk_size", &gChunkSize);
331 aPrefBranch->GetIntPref("mail.imap.min_chunk_size_threshold",
332 &gChunkThreshold);
333 aPrefBranch->GetBoolPref("mail.imap.hide_other_users",
334 &gHideOtherUsersFromList);
335 aPrefBranch->GetBoolPref("mail.imap.hide_unused_namespaces",
336 &gHideUnusedNamespaces);
337 aPrefBranch->GetIntPref("mail.imap.noop_check_count",
338 &gPromoteNoopToCheckCount);
339 aPrefBranch->GetBoolPref("mail.imap.use_envelope_cmd", &gUseEnvelopeCmd);
340 aPrefBranch->GetBoolPref("mail.imap.use_literal_plus", &gUseLiteralPlus);
341 aPrefBranch->GetBoolPref("mail.imap.expunge_after_delete",
342 &gExpungeAfterDelete);
343 aPrefBranch->GetBoolPref("mail.imap.check_deleted_before_expunge",
344 &gCheckDeletedBeforeExpunge);
345 aPrefBranch->GetIntPref("mail.imap.expunge_option", &gExpungeOption);
346 aPrefBranch->GetIntPref("mail.imap.expunge_threshold_number",
347 &gExpungeThreshold);
348 aPrefBranch->GetIntPref("mailnews.tcptimeout", &gResponseTimeout);
349 gAppendTimeout = gResponseTimeout / 5;
350 aPrefBranch->GetCharPref("mail.imap.force_select_detect", gForceSelectDetect);
351 ParseString(gForceSelectDetect, ';', gForceSelectServersArray);
352
353 gTCPKeepalive.enabled.store(false, std::memory_order_relaxed);
354 gTCPKeepalive.idleTimeS.store(-1, std::memory_order_relaxed);
355 gTCPKeepalive.retryIntervalS.store(-1, std::memory_order_relaxed);
356
357 nsCOMPtr<nsIXULAppInfo> appInfo(do_GetService(XULAPPINFO_SERVICE_CONTRACTID));
358
359 if (appInfo) {
360 nsCString appName, appVersion;
361 appInfo->GetName(appName);
362 appInfo->GetVersion(appVersion);
363 PL_strncpyz(gAppName, appName.get(), kAppBufSize);
364 PL_strncpyz(gAppVersion, appVersion.get(), kAppBufSize);
365 }
366 return NS_OK;
367 }
368
369 class nsImapTransportEventSink final : public nsITransportEventSink {
370 public:
371 NS_DECL_THREADSAFE_ISUPPORTS
372 NS_DECL_NSITRANSPORTEVENTSINK
373
374 private:
375 friend class nsImapProtocol;
376
377 virtual ~nsImapTransportEventSink() = default;
378 nsresult ApplyTCPKeepalive(nsISocketTransport* aTransport);
379
380 nsCOMPtr<nsITransportEventSink> m_proxy;
381 };
382
NS_IMPL_ISUPPORTS(nsImapTransportEventSink,nsITransportEventSink)383 NS_IMPL_ISUPPORTS(nsImapTransportEventSink, nsITransportEventSink)
384
385 NS_IMETHODIMP
386 nsImapTransportEventSink::OnTransportStatus(nsITransport* aTransport,
387 nsresult aStatus, int64_t aProgress,
388 int64_t aProgressMax) {
389 if (aStatus == NS_NET_STATUS_CONNECTED_TO) {
390 nsCOMPtr<nsISocketTransport> sockTrans(do_QueryInterface(aTransport));
391 if (!NS_WARN_IF(!sockTrans)) ApplyTCPKeepalive(sockTrans);
392 }
393
394 if (NS_WARN_IF(!m_proxy)) return NS_OK;
395
396 return m_proxy->OnTransportStatus(aTransport, aStatus, aProgress,
397 aProgressMax);
398 }
399
ApplyTCPKeepalive(nsISocketTransport * aTransport)400 nsresult nsImapTransportEventSink::ApplyTCPKeepalive(
401 nsISocketTransport* aTransport) {
402 nsresult rv;
403
404 bool kaEnabled = gTCPKeepalive.enabled.load(std::memory_order_relaxed);
405 if (kaEnabled) {
406 // TCP keepalive idle time, don't mistake with IMAP IDLE.
407 int32_t kaIdleTime =
408 gTCPKeepalive.idleTimeS.load(std::memory_order_relaxed);
409 int32_t kaRetryInterval =
410 gTCPKeepalive.retryIntervalS.load(std::memory_order_relaxed);
411
412 if (kaIdleTime < 0 || kaRetryInterval < 0) {
413 if (NS_WARN_IF(!net::gSocketTransportService))
414 return NS_ERROR_NOT_INITIALIZED;
415 }
416 if (kaIdleTime < 0) {
417 rv = net::gSocketTransportService->GetKeepaliveIdleTime(&kaIdleTime);
418 if (NS_FAILED(rv)) {
419 MOZ_LOG(IMAP, LogLevel::Error,
420 ("GetKeepaliveIdleTime() failed, %" PRIx32,
421 static_cast<uint32_t>(rv)));
422 return rv;
423 }
424 }
425 if (kaRetryInterval < 0) {
426 rv = net::gSocketTransportService->GetKeepaliveRetryInterval(
427 &kaRetryInterval);
428 if (NS_FAILED(rv)) {
429 MOZ_LOG(IMAP, LogLevel::Error,
430 ("GetKeepaliveRetryInterval() failed, %" PRIx32,
431 static_cast<uint32_t>(rv)));
432 return rv;
433 }
434 }
435
436 MOZ_ASSERT(kaIdleTime > 0);
437 MOZ_ASSERT(kaRetryInterval > 0);
438 rv = aTransport->SetKeepaliveVals(kaIdleTime, kaRetryInterval);
439 if (NS_FAILED(rv)) {
440 MOZ_LOG(IMAP, LogLevel::Error,
441 ("SetKeepaliveVals(%" PRId32 ", %" PRId32 ") failed, %" PRIx32,
442 kaIdleTime, kaRetryInterval, static_cast<uint32_t>(rv)));
443 return rv;
444 }
445 }
446
447 rv = aTransport->SetKeepaliveEnabled(kaEnabled);
448 if (NS_FAILED(rv)) {
449 MOZ_LOG(IMAP, LogLevel::Error,
450 ("SetKeepaliveEnabled(%s) failed, %" PRIx32,
451 kaEnabled ? "true" : "false", static_cast<uint32_t>(rv)));
452 return rv;
453 }
454 return NS_OK;
455 }
456
457 // This runnable runs on IMAP thread.
458 class nsImapProtocolMainLoopRunnable final : public mozilla::Runnable {
459 public:
nsImapProtocolMainLoopRunnable(nsImapProtocol * aProtocol)460 explicit nsImapProtocolMainLoopRunnable(nsImapProtocol* aProtocol)
461 : mozilla::Runnable("nsImapProtocolEventLoopRunnable"),
462 mProtocol(aProtocol) {}
463
Run()464 NS_IMETHOD Run() {
465 MOZ_ASSERT(!NS_IsMainThread());
466
467 if (!mProtocol->RunImapThreadMainLoop()) {
468 // We already run another IMAP event loop.
469 return NS_OK;
470 }
471
472 // Release protocol object on the main thread to avoid destruction of
473 // nsImapProtocol on the IMAP thread, which causes grief for weak
474 // references.
475 NS_ReleaseOnMainThread("nsImapProtocol::this", mProtocol.forget());
476
477 // shutdown this thread, but do it from the main thread
478 nsCOMPtr<nsIThread> imapThread(do_GetCurrentThread());
479 if (NS_FAILED(NS_DispatchToMainThread(
480 NS_NewRunnableFunction("nsImapProtorolMainLoopRunnable::Run",
481 [imapThread = std::move(imapThread)]() {
482 imapThread->Shutdown();
483 })))) {
484 NS_WARNING("Failed to dispatch nsImapThreadShutdownEvent");
485 }
486 return NS_OK;
487 }
488
489 private:
490 RefPtr<nsImapProtocol> mProtocol;
491 };
492
nsImapProtocol()493 nsImapProtocol::nsImapProtocol()
494 : nsMsgProtocol(nullptr),
495 m_dataAvailableMonitor("imapDataAvailable"),
496 m_urlReadyToRunMonitor("imapUrlReadyToRun"),
497 m_pseudoInterruptMonitor("imapPseudoInterrupt"),
498 m_dataMemberMonitor("imapDataMember"),
499 m_threadDeathMonitor("imapThreadDeath"),
500 m_waitForBodyIdsMonitor("imapWaitForBodyIds"),
501 m_fetchBodyListMonitor("imapFetchBodyList"),
502 m_passwordReadyMonitor("imapPasswordReady"),
503 mLock("nsImapProtocol.mLock"),
504 m_parser(*this) {
505 m_urlInProgress = false;
506 m_idle = false;
507 m_retryUrlOnError = false;
508 m_useIdle = true; // by default, use it
509 m_useCondStore = true;
510 m_useCompressDeflate = true;
511 m_ignoreExpunges = false;
512 m_prefAuthMethods = kCapabilityUndefined;
513 m_failedAuthMethods = 0;
514 m_currentAuthMethod = kCapabilityUndefined;
515 m_socketType = nsMsgSocketType::trySTARTTLS;
516 m_connectionStatus = NS_OK;
517 m_safeToCloseConnection = false;
518 m_hostSessionList = nullptr;
519 m_isGmailServer = false;
520 m_fetchingWholeMessage = false;
521 m_allowUTF8Accept = false;
522
523 nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID));
524 NS_ASSERTION(prefBranch, "FAILED to create the preference service");
525
526 // read in the accept languages preference
527 if (prefBranch) {
528 if (!gInitialized) GlobalInitialization(prefBranch);
529
530 nsCOMPtr<nsIPrefLocalizedString> prefString;
531 prefBranch->GetComplexValue("intl.accept_languages",
532 NS_GET_IID(nsIPrefLocalizedString),
533 getter_AddRefs(prefString));
534 if (prefString) prefString->ToString(getter_Copies(mAcceptLanguages));
535
536 nsCString customDBHeaders;
537 prefBranch->GetCharPref("mailnews.customDBHeaders", customDBHeaders);
538
539 ParseString(customDBHeaders, ' ', mCustomDBHeaders);
540 prefBranch->GetBoolPref("mailnews.display.prefer_plaintext",
541 &m_preferPlainText);
542
543 nsAutoCString customHeaders;
544 prefBranch->GetCharPref("mailnews.customHeaders", customHeaders);
545 customHeaders.StripWhitespace();
546 ParseString(customHeaders, ':', mCustomHeaders);
547
548 nsresult rv;
549 bool bVal = false;
550 rv = prefBranch->GetBoolPref("mail.imap.tcp_keepalive.enabled", &bVal);
551 if (NS_SUCCEEDED(rv))
552 gTCPKeepalive.enabled.store(bVal, std::memory_order_relaxed);
553
554 if (bVal) {
555 int32_t val;
556 // TCP keepalive idle time, don't mistake with IMAP IDLE.
557 rv = prefBranch->GetIntPref("mail.imap.tcp_keepalive.idle_time", &val);
558 if (NS_SUCCEEDED(rv) && val >= 0)
559 gTCPKeepalive.idleTimeS.store(
560 std::min<int32_t>(std::max(val, 1), net::kMaxTCPKeepIdle),
561 std::memory_order_relaxed);
562
563 rv = prefBranch->GetIntPref("mail.imap.tcp_keepalive.retry_interval",
564 &val);
565 if (NS_SUCCEEDED(rv) && val >= 0)
566 gTCPKeepalive.retryIntervalS.store(
567 std::min<int32_t>(std::max(val, 1), net::kMaxTCPKeepIntvl),
568 std::memory_order_relaxed);
569 }
570 }
571
572 // ***** Thread support *****
573 m_thread = nullptr;
574 m_imapThreadIsRunning = false;
575 m_currentServerCommandTagNumber = 0;
576 m_active = false;
577 m_folderNeedsSubscribing = false;
578 m_folderNeedsACLRefreshed = false;
579 m_threadShouldDie = false;
580 m_inThreadShouldDie = false;
581 m_pseudoInterrupted = false;
582 m_nextUrlReadyToRun = false;
583 m_trackingTime = false;
584 m_curFetchSize = 0;
585 m_startTime = 0;
586 m_endTime = 0;
587 m_lastActiveTime = 0;
588 m_lastProgressTime = 0;
589 ResetProgressInfo();
590
591 m_tooFastTime = 0;
592 m_idealTime = 0;
593 m_chunkAddSize = 0;
594 m_chunkStartSize = 0;
595 m_fetchByChunks = true;
596 m_sendID = true;
597 m_chunkSize = 0;
598 m_chunkThreshold = 0;
599 m_fromHeaderSeen = false;
600 m_closeNeededBeforeSelect = false;
601 m_needNoop = false;
602 m_noopCount = 0;
603 m_fetchBodyListIsNew = false;
604 m_flagChangeCount = 0;
605 m_lastCheckTime = PR_Now();
606
607 m_hierarchyNameState = kNoOperationInProgress;
608 m_discoveryStatus = eContinue;
609
610 // m_dataOutputBuf is used by Send Data
611 m_dataOutputBuf = (char*)PR_CALLOC(sizeof(char) * OUTPUT_BUFFER_SIZE);
612
613 // used to buffer incoming data by ReadNextLine
614 m_inputStreamBuffer = new nsMsgLineStreamBuffer(
615 OUTPUT_BUFFER_SIZE, true /* allocate new lines */,
616 false /* leave CRLFs on the returned string */);
617 m_currentBiffState = nsIMsgFolder::nsMsgBiffState_Unknown;
618 m_progressStringName.Truncate();
619 m_stringIndex = IMAP_EMPTY_STRING_INDEX;
620 m_progressExpectedNumber = 0;
621 memset(m_progressCurrentNumber, 0, sizeof m_progressCurrentNumber);
622
623 // since these are embedded in the nsImapProtocol object, but passed
624 // through proxied xpcom methods, just AddRef them here.
625 m_hdrDownloadCache = new nsMsgImapHdrXferInfo();
626 m_downloadLineCache = new nsMsgImapLineDownloadCache();
627
628 // subscription
629 m_autoSubscribe = true;
630 m_autoUnsubscribe = true;
631 m_autoSubscribeOnOpen = true;
632 m_deletableChildren = nullptr;
633
634 mFolderLastModSeq = 0;
635
636 Configure(gTooFastTime, gIdealTime, gChunkAddSize, gChunkSize,
637 gChunkThreshold, gFetchByChunks);
638 m_forceSelect = false;
639 m_capabilityResponseOccurred = true;
640 }
641
Configure(int32_t TooFastTime,int32_t IdealTime,int32_t ChunkAddSize,int32_t ChunkSize,int32_t ChunkThreshold,bool FetchByChunks)642 nsresult nsImapProtocol::Configure(int32_t TooFastTime, int32_t IdealTime,
643 int32_t ChunkAddSize, int32_t ChunkSize,
644 int32_t ChunkThreshold, bool FetchByChunks) {
645 m_tooFastTime = TooFastTime; // secs we read too little too fast
646 m_idealTime = IdealTime; // secs we read enough in good time
647 m_chunkAddSize = ChunkAddSize; // buffer size to add when wasting time
648 m_chunkStartSize = m_chunkSize = ChunkSize;
649 m_chunkThreshold = ChunkThreshold;
650 m_fetchByChunks = FetchByChunks;
651
652 return NS_OK;
653 }
654
655 NS_IMETHODIMP
Initialize(nsIImapHostSessionList * aHostSessionList,nsIImapIncomingServer * aServer)656 nsImapProtocol::Initialize(nsIImapHostSessionList* aHostSessionList,
657 nsIImapIncomingServer* aServer) {
658 NS_ASSERTION(
659 aHostSessionList && aServer,
660 "oops...trying to initialize with a null host session list or server!");
661 if (!aHostSessionList || !aServer) return NS_ERROR_NULL_POINTER;
662
663 nsresult rv = m_downloadLineCache->GrowBuffer(kDownLoadCacheSize);
664 NS_ENSURE_SUCCESS(rv, rv);
665
666 m_flagState = new nsImapFlagAndUidState(kImapFlagAndUidStateSize);
667 if (!m_flagState) return NS_ERROR_OUT_OF_MEMORY;
668
669 aServer->GetUseIdle(&m_useIdle);
670 aServer->GetForceSelect(m_forceSelectValue);
671 aServer->GetUseCondStore(&m_useCondStore);
672 aServer->GetUseCompressDeflate(&m_useCompressDeflate);
673 aServer->GetAllowUTF8Accept(&m_allowUTF8Accept);
674
675 m_hostSessionList = aHostSessionList;
676 m_parser.SetHostSessionList(aHostSessionList);
677 m_parser.SetFlagState(m_flagState);
678
679 // Initialize the empty mime part string on the main thread.
680 nsCOMPtr<nsIStringBundle> bundle;
681 rv = IMAPGetStringBundle(getter_AddRefs(bundle));
682 NS_ENSURE_SUCCESS(rv, rv);
683
684 rv = bundle->GetStringFromName("imapEmptyMimePart", m_emptyMimePartString);
685 NS_ENSURE_SUCCESS(rv, rv);
686
687 // Now initialize the thread for the connection
688 if (m_thread == nullptr) {
689 nsCOMPtr<nsIThread> imapThread;
690 nsresult rv = NS_NewNamedThread("IMAP", getter_AddRefs(imapThread));
691 if (NS_FAILED(rv)) {
692 NS_ASSERTION(imapThread, "Unable to create imap thread.");
693 return rv;
694 }
695 RefPtr<nsImapProtocolMainLoopRunnable> runnable =
696 new nsImapProtocolMainLoopRunnable(this);
697 imapThread->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
698 imapThread->GetPRThread(&m_thread);
699 }
700 return NS_OK;
701 }
702
~nsImapProtocol()703 nsImapProtocol::~nsImapProtocol() {
704 PR_Free(m_dataOutputBuf);
705
706 // **** We must be out of the thread main loop function
707 NS_ASSERTION(!m_imapThreadIsRunning, "Oops, thread is still running.");
708 MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
709 }
710
GetImapHostName()711 const nsCString& nsImapProtocol::GetImapHostName() {
712 if (m_runningUrl && m_hostName.IsEmpty()) {
713 nsCOMPtr<nsIURI> url = do_QueryInterface(m_runningUrl);
714 url->GetAsciiHost(m_hostName);
715 }
716
717 return m_hostName;
718 }
719
GetImapUserName()720 const nsCString& nsImapProtocol::GetImapUserName() {
721 if (m_userName.IsEmpty() && m_imapServerSink) {
722 m_imapServerSink->GetOriginalUsername(m_userName);
723 }
724 return m_userName;
725 }
726
GetImapServerKey()727 const char* nsImapProtocol::GetImapServerKey() {
728 if (m_serverKey.IsEmpty() && m_imapServerSink) {
729 m_imapServerSink->GetServerKey(m_serverKey);
730 }
731 return m_serverKey.get();
732 }
733
SetupSinkProxy()734 nsresult nsImapProtocol::SetupSinkProxy() {
735 nsresult res;
736 if (m_runningUrl) {
737 if (!m_imapMailFolderSink) {
738 nsCOMPtr<nsIImapMailFolderSink> aImapMailFolderSink;
739 (void)m_runningUrl->GetImapMailFolderSink(
740 getter_AddRefs(aImapMailFolderSink));
741 if (aImapMailFolderSink) {
742 m_imapMailFolderSink = new ImapMailFolderSinkProxy(aImapMailFolderSink);
743 }
744 }
745
746 if (!m_imapMessageSink) {
747 nsCOMPtr<nsIImapMessageSink> aImapMessageSink;
748 (void)m_runningUrl->GetImapMessageSink(getter_AddRefs(aImapMessageSink));
749 if (aImapMessageSink) {
750 m_imapMessageSink = new ImapMessageSinkProxy(aImapMessageSink);
751 } else {
752 return NS_ERROR_ILLEGAL_VALUE;
753 }
754 }
755 if (!m_imapServerSink) {
756 nsCOMPtr<nsIImapServerSink> aImapServerSink;
757 res = m_runningUrl->GetImapServerSink(getter_AddRefs(aImapServerSink));
758 if (aImapServerSink) {
759 m_imapServerSink = new ImapServerSinkProxy(aImapServerSink);
760 m_imapServerSinkLatest = m_imapServerSink;
761 } else {
762 return NS_ERROR_ILLEGAL_VALUE;
763 }
764 }
765 if (!m_imapProtocolSink) {
766 nsCOMPtr<nsIImapProtocolSink> anImapProxyHelper(do_QueryInterface(
767 NS_ISUPPORTS_CAST(nsIImapProtocolSink*, this), &res));
768 m_imapProtocolSink = new ImapProtocolSinkProxy(anImapProxyHelper);
769 }
770 }
771 return NS_OK;
772 }
773
SetSecurityCallbacksFromChannel(nsISocketTransport * aTrans,nsIChannel * aChannel)774 static void SetSecurityCallbacksFromChannel(nsISocketTransport* aTrans,
775 nsIChannel* aChannel) {
776 nsCOMPtr<nsIInterfaceRequestor> callbacks;
777 aChannel->GetNotificationCallbacks(getter_AddRefs(callbacks));
778
779 nsCOMPtr<nsILoadGroup> loadGroup;
780 aChannel->GetLoadGroup(getter_AddRefs(loadGroup));
781
782 nsCOMPtr<nsIInterfaceRequestor> securityCallbacks;
783 NS_NewNotificationCallbacksAggregation(callbacks, loadGroup,
784 getter_AddRefs(securityCallbacks));
785 if (securityCallbacks) aTrans->SetSecurityCallbacks(securityCallbacks);
786 }
787
788 // Setup With Url is intended to set up data which is held on a PER URL basis
789 // and not a per connection basis. If you have data which is independent of the
790 // url we are currently running, then you should put it in Initialize(). This is
791 // only ever called from the UI thread. It is called from LoadImapUrl, right
792 // before the url gets run - i.e., the url is next in line to run.
793 // See also ReleaseUrlState(), which frees a bunch of the things set up in here.
SetupWithUrl(nsIURI * aURL,nsISupports * aConsumer)794 nsresult nsImapProtocol::SetupWithUrl(nsIURI* aURL, nsISupports* aConsumer) {
795 nsresult rv = NS_ERROR_FAILURE;
796 NS_ASSERTION(aURL, "null URL passed into Imap Protocol");
797 if (aURL) {
798 m_runningUrl = do_QueryInterface(aURL, &rv);
799 m_runningUrlLatest = m_runningUrl;
800 if (NS_FAILED(rv)) return rv;
801
802 nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningUrl);
803 nsCOMPtr<nsIMsgIncomingServer> server = do_QueryReferent(m_server);
804 if (!server) {
805 rv = mailnewsUrl->GetServer(getter_AddRefs(server));
806 NS_ENSURE_SUCCESS(rv, rv);
807 m_server = do_GetWeakReference(server);
808 }
809 nsCOMPtr<nsIMsgFolder> folder;
810 mailnewsUrl->GetFolder(getter_AddRefs(folder));
811 mFolderLastModSeq = 0;
812 mFolderTotalMsgCount = 0;
813 mFolderHighestUID = 0;
814 m_uidValidity = kUidUnknown;
815 if (folder) {
816 nsCOMPtr<nsIMsgDatabase> folderDB;
817 nsCOMPtr<nsIDBFolderInfo> folderInfo;
818 folder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo),
819 getter_AddRefs(folderDB));
820 if (folderInfo) {
821 nsCString modSeqStr;
822 folderInfo->GetCharProperty(kModSeqPropertyName, modSeqStr);
823 mFolderLastModSeq = ParseUint64Str(modSeqStr.get());
824 folderInfo->GetNumMessages(&mFolderTotalMsgCount);
825 folderInfo->GetUint32Property(kHighestRecordedUIDPropertyName, 0,
826 &mFolderHighestUID);
827 folderInfo->GetImapUidValidity(&m_uidValidity);
828 }
829 }
830 nsCOMPtr<nsIImapIncomingServer> imapServer = do_QueryInterface(server);
831 nsCOMPtr<nsIStreamListener> aRealStreamListener =
832 do_QueryInterface(aConsumer);
833 m_runningUrl->GetMockChannel(getter_AddRefs(m_mockChannel));
834 imapServer->GetIsGMailServer(&m_isGmailServer);
835 if (!m_mockChannel) {
836 nsCOMPtr<nsIPrincipal> nullPrincipal =
837 NullPrincipal::CreateWithoutOriginAttributes();
838
839 // there are several imap operations that aren't initiated via a
840 // nsIChannel::AsyncOpen call on the mock channel. such as selecting a
841 // folder. nsImapProtocol now insists on a mock channel when processing a
842 // url.
843 nsCOMPtr<nsIChannel> channel;
844 rv =
845 NS_NewChannel(getter_AddRefs(channel), aURL, nullPrincipal,
846 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
847 nsIContentPolicy::TYPE_OTHER);
848 m_mockChannel = do_QueryInterface(channel);
849
850 // Certain imap operations (not initiated by the IO Service via AsyncOpen)
851 // can be interrupted by the stop button on the toolbar. We do this by
852 // using the loadgroup of the docshell for the message pane. We really
853 // shouldn't be doing this.. See the comment in
854 // nsMsgMailNewsUrl::GetLoadGroup.
855 nsCOMPtr<nsILoadGroup> loadGroup;
856 mailnewsUrl->GetLoadGroup(
857 getter_AddRefs(loadGroup)); // get the message pane load group
858 if (loadGroup)
859 loadGroup->AddRequest(m_mockChannel, nullptr /* context isupports */);
860 }
861
862 if (m_mockChannel) {
863 m_mockChannel->SetImapProtocol(this);
864 // if we have a listener from a mock channel, over-ride the consumer that
865 // was passed in
866 nsCOMPtr<nsIStreamListener> channelListener;
867 m_mockChannel->GetChannelListener(getter_AddRefs(channelListener));
868 if (channelListener) // only over-ride if we have a non null channel
869 // listener
870 aRealStreamListener = channelListener;
871 nsCOMPtr<nsIMsgWindow> msgWindow;
872 GetMsgWindow(getter_AddRefs(msgWindow));
873 if (!msgWindow) GetTopmostMsgWindow(getter_AddRefs(msgWindow));
874 if (msgWindow) {
875 // Set up the MockChannel to attempt nsIProgressEventSink callbacks on
876 // the messageWindow, with fallback to the docShell (and the
877 // loadgroup).
878 nsCOMPtr<nsIDocShell> docShell;
879 msgWindow->GetMessageWindowDocShell(getter_AddRefs(docShell));
880 nsCOMPtr<nsIInterfaceRequestor> ir(do_QueryInterface(docShell));
881 nsCOMPtr<nsIInterfaceRequestor> interfaceRequestor;
882 msgWindow->GetNotificationCallbacks(getter_AddRefs(interfaceRequestor));
883 nsCOMPtr<nsIInterfaceRequestor> aggregateIR;
884 NS_NewInterfaceRequestorAggregation(interfaceRequestor, ir,
885 getter_AddRefs(aggregateIR));
886 m_mockChannel->SetNotificationCallbacks(aggregateIR);
887 }
888 }
889
890 // since we'll be making calls directly from the imap thread to the channel
891 // listener, we need to turn it into a proxy object....we'll assume that the
892 // listener is on the same thread as the event sink queue
893 if (aRealStreamListener) {
894 NS_ASSERTION(!m_channelListener,
895 "shouldn't already have a channel listener");
896 m_channelListener = new StreamListenerProxy(aRealStreamListener);
897 }
898
899 server->GetRealHostName(m_realHostName);
900 int32_t authMethod;
901 (void)server->GetAuthMethod(&authMethod);
902 InitPrefAuthMethods(authMethod, server);
903 (void)server->GetSocketType(&m_socketType);
904 bool shuttingDown;
905 (void)imapServer->GetShuttingDown(&shuttingDown);
906 if (!shuttingDown)
907 (void)imapServer->GetUseIdle(&m_useIdle);
908 else
909 m_useIdle = false;
910 imapServer->GetFetchByChunks(&m_fetchByChunks);
911 imapServer->GetSendID(&m_sendID);
912
913 nsAutoString trashFolderPath;
914 if (NS_SUCCEEDED(imapServer->GetTrashFolderName(trashFolderPath))) {
915 if (m_allowUTF8Accept)
916 CopyUTF16toUTF8(trashFolderPath, m_trashFolderPath);
917 else
918 CopyUTF16toMUTF7(trashFolderPath, m_trashFolderPath);
919 }
920
921 nsCOMPtr<nsIPrefBranch> prefBranch(
922 do_GetService(NS_PREFSERVICE_CONTRACTID));
923 if (prefBranch) {
924 bool preferPlainText;
925 prefBranch->GetBoolPref("mailnews.display.prefer_plaintext",
926 &preferPlainText);
927 // If the pref has changed since the last time we ran a url,
928 // clear the shell cache for this host.
929 if (preferPlainText != m_preferPlainText) {
930 m_hostSessionList->ClearShellCacheForHost(GetImapServerKey());
931 m_preferPlainText = preferPlainText;
932 }
933 }
934 // If enabled, retrieve the clientid so that we can use it later.
935 bool clientidEnabled = false;
936 if (NS_SUCCEEDED(server->GetClientidEnabled(&clientidEnabled)) &&
937 clientidEnabled)
938 server->GetClientid(m_clientId);
939 else {
940 m_clientId.Truncate();
941 }
942
943 bool proxyCallback = false;
944 if (m_runningUrl && !m_transport /* and we don't have a transport yet */) {
945 if (m_mockChannel) {
946 rv = MsgExamineForProxyAsync(m_mockChannel, this,
947 getter_AddRefs(m_proxyRequest));
948 if (NS_FAILED(rv)) {
949 rv = SetupWithUrlCallback(nullptr);
950 } else {
951 proxyCallback = true;
952 }
953 }
954 }
955
956 if (!proxyCallback) rv = LoadImapUrlInternal();
957 }
958
959 return rv;
960 }
961
962 // nsIProtocolProxyCallback
963 NS_IMETHODIMP
OnProxyAvailable(nsICancelable * aRequest,nsIChannel * aChannel,nsIProxyInfo * aProxyInfo,nsresult aStatus)964 nsImapProtocol::OnProxyAvailable(nsICancelable* aRequest, nsIChannel* aChannel,
965 nsIProxyInfo* aProxyInfo, nsresult aStatus) {
966 // If we're called with NS_BINDING_ABORTED, the IMAP thread already died,
967 // so we can't carry on. Otherwise, no checking of 'aStatus' here, see
968 // nsHttpChannel::OnProxyAvailable(). Status is non-fatal and we just kick on.
969 if (aStatus == NS_BINDING_ABORTED) return NS_ERROR_FAILURE;
970
971 nsresult rv = SetupWithUrlCallback(aProxyInfo);
972 if (NS_FAILED(rv)) {
973 // Cancel the protocol and be done.
974 if (m_mockChannel) m_mockChannel->Cancel(rv);
975 return rv;
976 }
977
978 rv = LoadImapUrlInternal();
979 if (NS_FAILED(rv)) {
980 if (m_mockChannel) m_mockChannel->Cancel(rv);
981 }
982
983 return rv;
984 }
985
SetupWithUrlCallback(nsIProxyInfo * aProxyInfo)986 nsresult nsImapProtocol::SetupWithUrlCallback(nsIProxyInfo* aProxyInfo) {
987 m_proxyRequest = nullptr;
988
989 nsresult rv;
990
991 nsCOMPtr<nsISocketTransportService> socketService =
992 do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
993 if (NS_FAILED(rv)) return rv;
994
995 Log("SetupWithUrlCallback", nullptr, "clearing IMAP_CONNECTION_IS_OPEN");
996 ClearFlag(IMAP_CONNECTION_IS_OPEN);
997 const char* connectionType = nullptr;
998
999 if (m_socketType == nsMsgSocketType::SSL)
1000 connectionType = "ssl";
1001 else if (m_socketType == nsMsgSocketType::alwaysSTARTTLS)
1002 connectionType = "starttls";
1003 // This can go away once we think everyone is migrated
1004 // away from the trySTARTTLS socket type.
1005 else if (m_socketType == nsMsgSocketType::trySTARTTLS)
1006 connectionType = "starttls";
1007
1008 int32_t port = -1;
1009 nsCOMPtr<nsIURI> uri = do_QueryInterface(m_runningUrl, &rv);
1010 if (NS_FAILED(rv)) return rv;
1011 uri->GetPort(&port);
1012
1013 AutoTArray<nsCString, 1> connectionTypeArray;
1014 if (connectionType) connectionTypeArray.AppendElement(connectionType);
1015 // NOTE: Some errors won't show up until the first read attempt (SSL bad
1016 // certificate errors, for example).
1017 rv = socketService->CreateTransport(connectionTypeArray, m_realHostName, port,
1018 aProxyInfo, nullptr,
1019 getter_AddRefs(m_transport));
1020 if (NS_FAILED(rv) && m_socketType == nsMsgSocketType::trySTARTTLS) {
1021 connectionType = nullptr;
1022 m_socketType = nsMsgSocketType::plain;
1023 rv = socketService->CreateTransport(connectionTypeArray, m_realHostName,
1024 port, aProxyInfo, nullptr,
1025 getter_AddRefs(m_transport));
1026 }
1027
1028 // remember so we can know whether we can issue a start tls or not...
1029 m_connectionType = connectionType;
1030 if (m_transport && m_mockChannel) {
1031 uint8_t qos;
1032 rv = GetQoSBits(&qos);
1033 if (NS_SUCCEEDED(rv)) m_transport->SetQoSBits(qos);
1034
1035 // Ensure that the socket can get the notification callbacks
1036 SetSecurityCallbacksFromChannel(m_transport, m_mockChannel);
1037
1038 // open buffered, blocking input stream
1039 rv = m_transport->OpenInputStream(nsITransport::OPEN_BLOCKING, 0, 0,
1040 getter_AddRefs(m_inputStream));
1041 if (NS_FAILED(rv)) return rv;
1042
1043 // open buffered, blocking output stream
1044 rv = m_transport->OpenOutputStream(nsITransport::OPEN_BLOCKING, 0, 0,
1045 getter_AddRefs(m_outputStream));
1046 if (NS_FAILED(rv)) return rv;
1047 SetFlag(IMAP_CONNECTION_IS_OPEN);
1048 }
1049
1050 return rv;
1051 }
1052
1053 // when the connection is done processing the current state, free any per url
1054 // state data...
ReleaseUrlState(bool rerunning)1055 void nsImapProtocol::ReleaseUrlState(bool rerunning) {
1056 // clear out the socket's reference to the notification callbacks for this
1057 // transaction
1058 {
1059 MutexAutoLock mon(mLock);
1060 if (m_transport) {
1061 m_transport->SetSecurityCallbacks(nullptr);
1062 m_transport->SetEventSink(nullptr, nullptr);
1063 }
1064 }
1065
1066 if (m_mockChannel && !rerunning) {
1067 // Proxy the close of the channel to the ui thread.
1068 if (m_imapMailFolderSink)
1069 m_imapMailFolderSink->CloseMockChannel(m_mockChannel);
1070 else
1071 m_mockChannel->Close();
1072
1073 {
1074 // grab a lock so m_mockChannel doesn't get cleared out
1075 // from under us.
1076 MutexAutoLock mon(mLock);
1077 if (m_mockChannel) {
1078 // Proxy the release of the channel to the main thread. This is
1079 // something that the xpcom proxy system should do for us!
1080 NS_ReleaseOnMainThread("nsImapProtocol::m_mockChannel",
1081 m_mockChannel.forget());
1082 }
1083 }
1084 }
1085
1086 m_imapMessageSink = nullptr;
1087
1088 // Proxy the release of the listener to the main thread. This is something
1089 // that the xpcom proxy system should do for us!
1090 {
1091 // grab a lock so the m_channelListener doesn't get cleared.
1092 MutexAutoLock mon(mLock);
1093 if (m_channelListener) {
1094 NS_ReleaseOnMainThread("nsImapProtocol::m_channelListener",
1095 m_channelListener.forget());
1096 }
1097 }
1098 m_channelInputStream = nullptr;
1099 m_channelOutputStream = nullptr;
1100
1101 nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl;
1102 nsCOMPtr<nsIImapMailFolderSink> saveFolderSink;
1103
1104 {
1105 MutexAutoLock mon(mLock);
1106 if (m_runningUrl) {
1107 mailnewsurl = do_QueryInterface(m_runningUrl);
1108 // It is unclear what 'saveFolderSink' is used for, most likely to hold
1109 // a reference for a little longer. See bug 1324893 and bug 391259.
1110 saveFolderSink = m_imapMailFolderSink;
1111
1112 m_runningUrl =
1113 nullptr; // force us to release our last reference on the url
1114 m_urlInProgress = false;
1115 }
1116 }
1117 // Need to null this out whether we have an m_runningUrl or not
1118 m_imapMailFolderSink = nullptr;
1119
1120 // we want to make sure the imap protocol's last reference to the url gets
1121 // released back on the UI thread. This ensures that the objects the imap url
1122 // hangs on to properly get released back on the UI thread.
1123 if (mailnewsurl) {
1124 NS_ReleaseOnMainThread("nsImapProtocol::m_runningUrl",
1125 mailnewsurl.forget());
1126 }
1127 saveFolderSink = nullptr;
1128 }
1129
1130 class nsImapCancelProxy : public mozilla::Runnable {
1131 public:
nsImapCancelProxy(nsICancelable * aProxyRequest)1132 explicit nsImapCancelProxy(nsICancelable* aProxyRequest)
1133 : mozilla::Runnable("nsImapCancelProxy"), mRequest(aProxyRequest) {}
Run()1134 NS_IMETHOD Run() {
1135 if (mRequest) mRequest->Cancel(NS_BINDING_ABORTED);
1136 return NS_OK;
1137 }
1138
1139 private:
1140 nsCOMPtr<nsICancelable> mRequest;
1141 };
1142
RunImapThreadMainLoop()1143 bool nsImapProtocol::RunImapThreadMainLoop() {
1144 PR_CEnterMonitor(this);
1145 NS_ASSERTION(!m_imapThreadIsRunning,
1146 "Oh. oh. thread is already running. What's wrong here?");
1147 if (m_imapThreadIsRunning) {
1148 PR_CExitMonitor(this);
1149 return false;
1150 }
1151
1152 m_imapThreadIsRunning = true;
1153 PR_CExitMonitor(this);
1154
1155 // call the platform specific main loop ....
1156 ImapThreadMainLoop();
1157
1158 if (m_proxyRequest) {
1159 // Cancel proxy on main thread.
1160 RefPtr<nsImapCancelProxy> cancelProxy =
1161 new nsImapCancelProxy(m_proxyRequest);
1162 NS_DispatchToMainThread(cancelProxy, NS_DISPATCH_SYNC);
1163 m_proxyRequest = nullptr;
1164 }
1165
1166 if (m_runningUrl) {
1167 NS_ReleaseOnMainThread("nsImapProtocol::m_runningUrl",
1168 m_runningUrl.forget());
1169 }
1170
1171 // close streams via UI thread if it's not already done
1172 if (m_imapProtocolSink) m_imapProtocolSink->CloseStreams();
1173
1174 m_imapMailFolderSink = nullptr;
1175 m_imapMessageSink = nullptr;
1176
1177 return true;
1178 }
1179
1180 //
1181 // Must be called from UI thread only
1182 //
CloseStreams()1183 NS_IMETHODIMP nsImapProtocol::CloseStreams() {
1184 // make sure that it is called by the UI thread
1185 MOZ_ASSERT(NS_IsMainThread(),
1186 "CloseStreams() should not be called from an off UI thread");
1187
1188 {
1189 MutexAutoLock mon(mLock);
1190 if (m_transport) {
1191 // make sure the transport closes (even if someone is still indirectly
1192 // referencing it).
1193 m_transport->Close(NS_ERROR_ABORT);
1194 m_transport = nullptr;
1195 }
1196 m_inputStream = nullptr;
1197 m_outputStream = nullptr;
1198 m_channelListener = nullptr;
1199 if (m_mockChannel) {
1200 m_mockChannel->Close();
1201 m_mockChannel = nullptr;
1202 }
1203 m_channelInputStream = nullptr;
1204 m_channelOutputStream = nullptr;
1205
1206 // Close scope because we must let go of the monitor before calling
1207 // RemoveConnection to unblock anyone who tries to get a monitor to the
1208 // protocol object while holding onto a monitor to the server.
1209 }
1210 nsCOMPtr<nsIMsgIncomingServer> me_server = do_QueryReferent(m_server);
1211 if (me_server) {
1212 nsresult result;
1213 nsCOMPtr<nsIImapIncomingServer> aImapServer(
1214 do_QueryInterface(me_server, &result));
1215 if (NS_SUCCEEDED(result)) aImapServer->RemoveConnection(this);
1216 me_server = nullptr;
1217 }
1218 m_server = nullptr;
1219 // take this opportunity of being on the UI thread to
1220 // persist chunk prefs if they've changed
1221 if (gChunkSizeDirty) {
1222 nsCOMPtr<nsIPrefBranch> prefBranch(
1223 do_GetService(NS_PREFSERVICE_CONTRACTID));
1224 if (prefBranch) {
1225 prefBranch->SetIntPref("mail.imap.chunk_size", gChunkSize);
1226 prefBranch->SetIntPref("mail.imap.min_chunk_size_threshold",
1227 gChunkThreshold);
1228 gChunkSizeDirty = false;
1229 }
1230 }
1231 return NS_OK;
1232 }
1233
GetUrlWindow(nsIMsgMailNewsUrl * aUrl,nsIMsgWindow ** aMsgWindow)1234 NS_IMETHODIMP nsImapProtocol::GetUrlWindow(nsIMsgMailNewsUrl* aUrl,
1235 nsIMsgWindow** aMsgWindow) {
1236 NS_ENSURE_ARG_POINTER(aUrl);
1237 NS_ENSURE_ARG_POINTER(aMsgWindow);
1238 return aUrl->GetMsgWindow(aMsgWindow);
1239 }
1240
SetupMainThreadProxies()1241 NS_IMETHODIMP nsImapProtocol::SetupMainThreadProxies() {
1242 return SetupSinkProxy();
1243 }
1244
OnInputStreamReady(nsIAsyncInputStream * inStr)1245 NS_IMETHODIMP nsImapProtocol::OnInputStreamReady(nsIAsyncInputStream* inStr) {
1246 // should we check if it's a close vs. data available?
1247 if (m_idle) {
1248 uint64_t bytesAvailable = 0;
1249 (void)inStr->Available(&bytesAvailable);
1250 // check if data available - might be a close
1251 if (bytesAvailable != 0) {
1252 ReentrantMonitorAutoEnter mon(m_urlReadyToRunMonitor);
1253 m_lastActiveTime = PR_Now();
1254 m_nextUrlReadyToRun = true;
1255 mon.Notify();
1256 }
1257 }
1258 return NS_OK;
1259 }
1260
1261 // this is to be called from the UI thread. It sets m_threadShouldDie,
1262 // and then signals the imap thread, which, when it wakes up, should exit.
1263 // The imap thread cleanup code will check m_safeToCloseConnection.
1264 NS_IMETHODIMP
TellThreadToDie(bool aIsSafeToClose)1265 nsImapProtocol::TellThreadToDie(bool aIsSafeToClose) {
1266 MOZ_DIAGNOSTIC_ASSERT(
1267 NS_IsMainThread(),
1268 "TellThreadToDie(aIsSafeToClose) should only be called from UI thread");
1269 MutexAutoLock mon(mLock);
1270
1271 nsCOMPtr<nsIMsgIncomingServer> me_server = do_QueryReferent(m_server);
1272 if (me_server) {
1273 nsresult rv;
1274 nsCOMPtr<nsIImapIncomingServer> aImapServer(
1275 do_QueryInterface(me_server, &rv));
1276 if (NS_SUCCEEDED(rv)) aImapServer->RemoveConnection(this);
1277 m_server = nullptr;
1278 me_server = nullptr;
1279 }
1280 {
1281 ReentrantMonitorAutoEnter deathMon(m_threadDeathMonitor);
1282 m_safeToCloseConnection = aIsSafeToClose;
1283 m_threadShouldDie = true;
1284 }
1285 ReentrantMonitorAutoEnter readyMon(m_urlReadyToRunMonitor);
1286 m_nextUrlReadyToRun = true;
1287 readyMon.Notify();
1288 return NS_OK;
1289 }
1290
TellThreadToDie()1291 void nsImapProtocol::TellThreadToDie() {
1292 nsresult rv = NS_OK;
1293 MOZ_DIAGNOSTIC_ASSERT(
1294 !NS_IsMainThread(),
1295 "TellThreadToDie() should not be called from UI thread");
1296
1297 // prevent re-entering this method because it may lock the UI.
1298 if (m_inThreadShouldDie) return;
1299 m_inThreadShouldDie = true;
1300
1301 // This routine is called only from the imap protocol thread.
1302 // The UI thread causes this to be called by calling TellThreadToDie.
1303 // In that case, m_safeToCloseConnection will be FALSE if it's dropping a
1304 // timed out connection, true when closing a cached connection.
1305 // We're using PR_CEnter/ExitMonitor because Monitors don't like having
1306 // us to hold one monitor and call code that gets a different monitor. And
1307 // some of the methods we call here use Monitors.
1308 PR_CEnterMonitor(this);
1309
1310 m_urlInProgress = true; // let's say it's busy so no one tries to use
1311 // this about to die connection.
1312 bool urlWritingData = false;
1313 bool connectionIdle = !m_runningUrl;
1314
1315 if (!connectionIdle)
1316 urlWritingData = m_imapAction == nsIImapUrl::nsImapAppendMsgFromFile ||
1317 m_imapAction == nsIImapUrl::nsImapAppendDraftFromFile;
1318
1319 bool closeNeeded = GetServerStateParser().GetIMAPstate() ==
1320 nsImapServerResponseParser::kFolderSelected &&
1321 m_safeToCloseConnection;
1322 nsCString command;
1323 // if a url is writing data, we can't even logout, so we're just
1324 // going to close the connection as if the user pressed stop.
1325 if (m_currentServerCommandTagNumber > 0 && !urlWritingData) {
1326 bool isAlive = false;
1327 if (m_transport) rv = m_transport->IsAlive(&isAlive);
1328
1329 if (TestFlag(IMAP_CONNECTION_IS_OPEN) && m_idle && isAlive) EndIdle(false);
1330
1331 if (NS_SUCCEEDED(rv) && isAlive && closeNeeded &&
1332 GetDeleteIsMoveToTrash() && TestFlag(IMAP_CONNECTION_IS_OPEN) &&
1333 m_outputStream)
1334 Close(true, connectionIdle);
1335
1336 if (NS_SUCCEEDED(rv) && isAlive && TestFlag(IMAP_CONNECTION_IS_OPEN) &&
1337 NS_SUCCEEDED(GetConnectionStatus()) && m_outputStream)
1338 Logout(true, connectionIdle);
1339 }
1340 PR_CExitMonitor(this);
1341 // close streams via UI thread
1342 if (m_imapProtocolSink) {
1343 m_imapProtocolSink->CloseStreams();
1344 m_imapProtocolSink = nullptr;
1345 }
1346 Log("TellThreadToDie", nullptr, "close socket connection");
1347
1348 {
1349 ReentrantMonitorAutoEnter mon(m_threadDeathMonitor);
1350 m_threadShouldDie = true;
1351 }
1352 {
1353 ReentrantMonitorAutoEnter dataMon(m_dataAvailableMonitor);
1354 dataMon.Notify();
1355 }
1356 ReentrantMonitorAutoEnter urlReadyMon(m_urlReadyToRunMonitor);
1357 urlReadyMon.NotifyAll();
1358 }
1359
1360 NS_IMETHODIMP
GetLastActiveTimeStamp(PRTime * aTimeStamp)1361 nsImapProtocol::GetLastActiveTimeStamp(PRTime* aTimeStamp) {
1362 if (aTimeStamp) *aTimeStamp = m_lastActiveTime;
1363 return NS_OK;
1364 }
1365
1366 static void DoomCacheEntry(nsIMsgMailNewsUrl* url);
1367 NS_IMETHODIMP
PseudoInterruptMsgLoad(nsIMsgFolder * aImapFolder,nsIMsgWindow * aMsgWindow,bool * interrupted)1368 nsImapProtocol::PseudoInterruptMsgLoad(nsIMsgFolder* aImapFolder,
1369 nsIMsgWindow* aMsgWindow,
1370 bool* interrupted) {
1371 NS_ENSURE_ARG(interrupted);
1372
1373 *interrupted = false;
1374
1375 PR_CEnterMonitor(this);
1376
1377 if (m_runningUrl && !TestFlag(IMAP_CLEAN_UP_URL_STATE)) {
1378 nsImapAction imapAction;
1379 m_runningUrl->GetImapAction(&imapAction);
1380
1381 if (imapAction == nsIImapUrl::nsImapMsgFetch) {
1382 nsresult rv = NS_OK;
1383 nsCOMPtr<nsIImapUrl> runningImapURL;
1384
1385 rv = GetRunningImapURL(getter_AddRefs(runningImapURL));
1386 if (NS_SUCCEEDED(rv) && runningImapURL) {
1387 nsCOMPtr<nsIMsgFolder> runningImapFolder;
1388 nsCOMPtr<nsIMsgWindow> msgWindow;
1389 nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl =
1390 do_QueryInterface(runningImapURL);
1391 mailnewsUrl->GetMsgWindow(getter_AddRefs(msgWindow));
1392 mailnewsUrl->GetFolder(getter_AddRefs(runningImapFolder));
1393 if (aImapFolder == runningImapFolder && msgWindow == aMsgWindow) {
1394 MOZ_LOG(IMAPCache, LogLevel::Debug,
1395 ("PseudoInterruptMsgLoad(): Set PseudoInterrupt"));
1396 PseudoInterrupt(true);
1397 *interrupted = true;
1398 }
1399 // If we're interrupted, doom any incomplete cache entry.
1400 MOZ_LOG(IMAPCache, LogLevel::Debug,
1401 ("PseudoInterruptMsgLoad(): Call DoomCacheEntry()"));
1402 DoomCacheEntry(mailnewsUrl);
1403 }
1404 }
1405 }
1406 PR_CExitMonitor(this);
1407 #ifdef DEBUG_bienvenu
1408 printf("interrupt msg load : %s\n", (*interrupted) ? "TRUE" : "FALSE");
1409 #endif
1410 return NS_OK;
1411 }
1412
ImapThreadMainLoop()1413 void nsImapProtocol::ImapThreadMainLoop() {
1414 MOZ_LOG(IMAP, LogLevel::Debug,
1415 ("ImapThreadMainLoop entering [this=%p]", this));
1416
1417 PRIntervalTime sleepTime = kImapSleepTime;
1418 while (!DeathSignalReceived()) {
1419 nsresult rv = NS_OK;
1420 bool readyToRun;
1421
1422 // wait for an URL to process...
1423 {
1424 ReentrantMonitorAutoEnter mon(m_urlReadyToRunMonitor);
1425
1426 while (NS_SUCCEEDED(rv) && !DeathSignalReceived() &&
1427 !m_nextUrlReadyToRun && !m_threadShouldDie)
1428 rv = mon.Wait(sleepTime);
1429
1430 readyToRun = m_nextUrlReadyToRun;
1431 m_nextUrlReadyToRun = false;
1432 }
1433 // This will happen if the UI thread signals us to die
1434 if (m_threadShouldDie) {
1435 TellThreadToDie();
1436 break;
1437 }
1438
1439 if (NS_FAILED(rv) && PR_PENDING_INTERRUPT_ERROR == PR_GetError()) {
1440 printf("error waiting for monitor\n");
1441 break;
1442 }
1443
1444 if (readyToRun && m_runningUrl) {
1445 if (m_currentServerCommandTagNumber && m_transport) {
1446 bool isAlive;
1447 rv = m_transport->IsAlive(&isAlive);
1448 // if the transport is not alive, and we've ever sent a command with
1449 // this connection, kill it. otherwise, we've probably just not finished
1450 // setting it so don't kill it!
1451 if (NS_FAILED(rv) || !isAlive) {
1452 // This says we never started running the url, which is the case.
1453 m_runningUrl->SetRerunningUrl(false);
1454 RetryUrl();
1455 return;
1456 }
1457 }
1458 //
1459 // NOTE: Though we cleared m_nextUrlReadyToRun above, it may have been
1460 // set by LoadImapUrl, which runs on the main thread. Because of
1461 // this, we must not try to clear m_nextUrlReadyToRun here.
1462 //
1463 if (ProcessCurrentURL()) {
1464 m_nextUrlReadyToRun = true;
1465 m_imapMailFolderSink = nullptr;
1466 } else {
1467 // see if we want to go into idle mode. Might want to check a pref here
1468 // too.
1469 if (m_useIdle && !m_urlInProgress &&
1470 GetServerStateParser().GetCapabilityFlag() & kHasIdleCapability &&
1471 GetServerStateParser().GetIMAPstate() ==
1472 nsImapServerResponseParser::kFolderSelected) {
1473 Idle(); // for now, lets just do it. We'll probably want to use a
1474 // timer
1475 if (!m_idle) {
1476 // Server rejected IDLE. Treat like IDLE not enabled or available.
1477 m_imapMailFolderSink = nullptr;
1478 }
1479 } else // if not idle, don't need to remember folder sink
1480 m_imapMailFolderSink = nullptr;
1481 }
1482 } else if (m_idle && !m_threadShouldDie) {
1483 HandleIdleResponses();
1484 }
1485 if (!GetServerStateParser().Connected()) break;
1486 #ifdef DEBUG_bienvenu
1487 else
1488 printf("ready to run but no url and not idle\n");
1489 #endif
1490 // This can happen if the UI thread closes cached connections in the
1491 // OnStopRunningUrl notification.
1492 if (m_threadShouldDie) TellThreadToDie();
1493 }
1494 m_imapThreadIsRunning = false;
1495
1496 MOZ_LOG(IMAP, LogLevel::Debug,
1497 ("ImapThreadMainLoop leaving [this=%p]", this));
1498 }
1499
HandleIdleResponses()1500 void nsImapProtocol::HandleIdleResponses() {
1501 // int32_t oldRecent = GetServerStateParser().NumberOfRecentMessages();
1502 nsAutoCString commandBuffer(GetServerCommandTag());
1503 commandBuffer.AppendLiteral(" IDLE" CRLF);
1504
1505 do {
1506 ParseIMAPandCheckForNewMail(commandBuffer.get());
1507 } while (m_inputStreamBuffer->NextLineAvailable() &&
1508 GetServerStateParser().Connected());
1509
1510 // if (oldRecent != GetServerStateParser().NumberOfRecentMessages())
1511 // We might check that something actually changed, but for now we can
1512 // just assume it. OnNewIdleMessages must run a url, so that
1513 // we'll go back into asyncwait mode.
1514 if (GetServerStateParser().Connected() && m_imapMailFolderSink)
1515 m_imapMailFolderSink->OnNewIdleMessages();
1516 }
1517
EstablishServerConnection()1518 void nsImapProtocol::EstablishServerConnection() {
1519 #define ESC_LENGTH(x) (sizeof(x) - 1)
1520 #define ESC_OK "* OK"
1521 #define ESC_OK_LEN ESC_LENGTH(ESC_OK)
1522 #define ESC_PREAUTH "* PREAUTH"
1523 #define ESC_PREAUTH_LEN ESC_LENGTH(ESC_PREAUTH)
1524 #define ESC_CAPABILITY_STAR "* "
1525 #define ESC_CAPABILITY_STAR_LEN ESC_LENGTH(ESC_CAPABILITY_STAR)
1526 #define ESC_CAPABILITY_OK "* OK ["
1527 #define ESC_CAPABILITY_OK_LEN ESC_LENGTH(ESC_CAPABILITY_OK)
1528 #define ESC_CAPABILITY_GREETING (ESC_CAPABILITY_OK "CAPABILITY")
1529 #define ESC_CAPABILITY_GREETING_LEN ESC_LENGTH(ESC_CAPABILITY_GREETING)
1530
1531 char* serverResponse = CreateNewLineFromSocket(); // read in the greeting
1532 // record the fact that we've received a greeting for this connection so we
1533 // don't ever try to do it again..
1534 if (serverResponse) SetFlag(IMAP_RECEIVED_GREETING);
1535
1536 if (!PL_strncasecmp(serverResponse, ESC_OK, ESC_OK_LEN)) {
1537 SetConnectionStatus(NS_OK);
1538
1539 if (!PL_strncasecmp(serverResponse, ESC_CAPABILITY_GREETING,
1540 ESC_CAPABILITY_GREETING_LEN)) {
1541 nsAutoCString tmpstr(serverResponse);
1542 int32_t endIndex = tmpstr.FindChar(']', ESC_CAPABILITY_GREETING_LEN);
1543 if (endIndex >= 0) {
1544 // Allocate the new buffer here. This buffer will be passed to
1545 // ParseIMAPServerResponse() where it will be used to fill the
1546 // fCurrentLine field and will be freed by the next call to
1547 // ResetLexAnalyzer().
1548 char* fakeServerResponse = (char*)PR_Malloc(PL_strlen(serverResponse));
1549 // Munge the greeting into something that would pass for an IMAP
1550 // server's response to a "CAPABILITY" command.
1551 strcpy(fakeServerResponse, ESC_CAPABILITY_STAR);
1552 strcat(fakeServerResponse, serverResponse + ESC_CAPABILITY_OK_LEN);
1553 fakeServerResponse[endIndex - ESC_CAPABILITY_OK_LEN +
1554 ESC_CAPABILITY_STAR_LEN] = '\0';
1555 // Tell the response parser that we just issued a "CAPABILITY" and
1556 // got the following back.
1557 GetServerStateParser().ParseIMAPServerResponse("1 CAPABILITY", true,
1558 fakeServerResponse);
1559 }
1560 }
1561 } else if (!PL_strncasecmp(serverResponse, ESC_PREAUTH, ESC_PREAUTH_LEN)) {
1562 // PREAUTH greeting received. We've been pre-authenticated by the server.
1563 // We can skip sending a password and transition right into the
1564 // kAuthenticated state; but we won't if the user has configured STARTTLS.
1565 // (STARTTLS can only occur with the server in non-authenticated state.)
1566 if (!(m_socketType == nsMsgSocketType::alwaysSTARTTLS ||
1567 m_socketType == nsMsgSocketType::trySTARTTLS)) {
1568 GetServerStateParser().PreauthSetAuthenticatedState();
1569
1570 if (GetServerStateParser().GetCapabilityFlag() == kCapabilityUndefined)
1571 Capability();
1572
1573 if (!(GetServerStateParser().GetCapabilityFlag() &
1574 (kIMAP4Capability | kIMAP4rev1Capability | kIMAP4other))) {
1575 // AlertUserEventUsingId(MK_MSG_IMAP_SERVER_NOT_IMAP4);
1576 SetConnectionStatus(NS_ERROR_FAILURE); // stop netlib
1577 } else {
1578 // let's record the user as authenticated.
1579 m_imapServerSink->SetUserAuthenticated(true);
1580
1581 ProcessAfterAuthenticated();
1582 // the connection was a success
1583 SetConnectionStatus(NS_OK);
1584 }
1585 } else {
1586 // STARTTLS is configured so don't transition to authenticated state. Just
1587 // alert the user, log the error and drop the connection. This may
1588 // indicate a man-in-the middle attack if the user is not expecting
1589 // PREAUTH. The user must change the connection security setting to other
1590 // than STARTTLS to allow PREAUTH to be accepted on subsequent IMAP
1591 // connections.
1592 AlertUserEventUsingName("imapServerDisconnected");
1593 const nsCString& hostName = GetImapHostName();
1594 MOZ_LOG(
1595 IMAP, LogLevel::Error,
1596 ("PREAUTH received from IMAP server %s because STARTTLS selected. "
1597 "Connection dropped",
1598 hostName.get()));
1599 SetConnectionStatus(NS_ERROR_FAILURE); // stop netlib
1600 }
1601 }
1602
1603 PR_Free(serverResponse); // we don't care about the greeting yet...
1604
1605 #undef ESC_LENGTH
1606 #undef ESC_OK
1607 #undef ESC_OK_LEN
1608 #undef ESC_PREAUTH
1609 #undef ESC_PREAUTH_LEN
1610 #undef ESC_CAPABILITY_STAR
1611 #undef ESC_CAPABILITY_STAR_LEN
1612 #undef ESC_CAPABILITY_OK
1613 #undef ESC_CAPABILITY_OK_LEN
1614 #undef ESC_CAPABILITY_GREETING
1615 #undef ESC_CAPABILITY_GREETING_LEN
1616 }
1617
1618 // This can get called from the UI thread or an imap thread.
1619 // It makes sure we don't get left with partial messages in
1620 // the memory cache.
DoomCacheEntry(nsIMsgMailNewsUrl * url)1621 static void DoomCacheEntry(nsIMsgMailNewsUrl* url) {
1622 bool readingFromMemCache = false;
1623 nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(url);
1624 imapUrl->GetMsgLoadingFromCache(&readingFromMemCache);
1625 if (!readingFromMemCache) {
1626 nsCOMPtr<nsICacheEntry> cacheEntry;
1627 url->GetMemCacheEntry(getter_AddRefs(cacheEntry));
1628 if (cacheEntry) {
1629 MOZ_LOG(IMAPCache, LogLevel::Debug,
1630 ("DoomCacheEntry(): Call AsyncDoom()"));
1631 cacheEntry->AsyncDoom(nullptr);
1632 }
1633 }
1634 }
1635
1636 // returns true if another url was run, false otherwise.
ProcessCurrentURL()1637 bool nsImapProtocol::ProcessCurrentURL() {
1638 nsresult rv = NS_OK;
1639 if (m_idle) EndIdle();
1640
1641 if (m_retryUrlOnError) {
1642 // we clear this flag if we're re-running immediately, because that
1643 // means we never sent a start running url notification, and later we
1644 // don't send start running notification if we think we're rerunning
1645 // the url (see first call to SetUrlState below). This means we won't
1646 // send a start running notification, which means our stop running
1647 // notification will be ignored because we don't think we were running.
1648 m_runningUrl->SetRerunningUrl(false);
1649 return RetryUrl();
1650 }
1651 Log("ProcessCurrentURL", nullptr, "entering");
1652 (void)GetImapHostName(); // force m_hostName to get set.
1653
1654 bool logonFailed = false;
1655 bool anotherUrlRun = false;
1656 bool rerunningUrl = false;
1657 bool isExternalUrl;
1658 bool validUrl = true;
1659
1660 PseudoInterrupt(false); // clear this if left over from previous url.
1661
1662 m_runningUrl->GetRerunningUrl(&rerunningUrl);
1663 m_runningUrl->GetExternalLinkUrl(&isExternalUrl);
1664 m_runningUrl->GetValidUrl(&validUrl);
1665 m_runningUrl->GetImapAction(&m_imapAction);
1666
1667 if (isExternalUrl) {
1668 if (m_imapAction == nsIImapUrl::nsImapSelectFolder) {
1669 // we need to send a start request so that the doc loader
1670 // will call HandleContent on the imap service so we
1671 // can abort this url, and run a new url in a new msg window
1672 // to run the folder load url and get off this crazy merry-go-round.
1673 if (m_channelListener) {
1674 m_channelListener->OnStartRequest(m_mockChannel);
1675 }
1676 return false;
1677 }
1678 }
1679
1680 if (!m_imapMailFolderSink && m_imapProtocolSink) {
1681 // This occurs when running another URL in the main thread loop
1682 rv = m_imapProtocolSink->SetupMainThreadProxies();
1683 NS_ENSURE_SUCCESS(rv, false);
1684 }
1685
1686 // Reinitialize the parser
1687 GetServerStateParser().InitializeState();
1688 GetServerStateParser().SetConnected(true);
1689
1690 // acknowledge that we are running the url now..
1691 nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl =
1692 do_QueryInterface(m_runningUrl, &rv);
1693 nsAutoCString urlSpec;
1694 rv = mailnewsurl->GetSpec(urlSpec);
1695 NS_ENSURE_SUCCESS(rv, false);
1696 Log("ProcessCurrentURL", urlSpec.get(),
1697 (validUrl) ? " = currentUrl" : " is not valid");
1698 if (!validUrl) return false;
1699
1700 if (NS_SUCCEEDED(rv) && mailnewsurl && m_imapMailFolderSink && !rerunningUrl)
1701 m_imapMailFolderSink->SetUrlState(this, mailnewsurl, true, false, NS_OK);
1702
1703 // if we are set up as a channel, we should notify our channel listener that
1704 // we are starting... so pass in ourself as the channel and not the underlying
1705 // socket or file channel the protocol happens to be using
1706 if (m_channelListener) // ### not sure we want to do this if rerunning url...
1707 {
1708 m_channelListener->OnStartRequest(m_mockChannel);
1709 }
1710 // If we haven't received the greeting yet, we need to make sure we strip
1711 // it out of the input before we start to do useful things...
1712 if (!TestFlag(IMAP_RECEIVED_GREETING)) EstablishServerConnection();
1713
1714 // Step 1: If we have not moved into the authenticated state yet then do so
1715 // by attempting to logon.
1716 if (!DeathSignalReceived() && NS_SUCCEEDED(GetConnectionStatus()) &&
1717 (GetServerStateParser().GetIMAPstate() ==
1718 nsImapServerResponseParser::kNonAuthenticated)) {
1719 /* if we got here, the server's greeting should not have been PREAUTH */
1720 // If greeting did not contain a capability response and if user has not
1721 // configured STARTTLS, request capabilites. If STARTTLS configured,
1722 // capabilities will be requested after TLS handshakes are complete.
1723 if ((GetServerStateParser().GetCapabilityFlag() == kCapabilityUndefined) &&
1724 (m_socketType != nsMsgSocketType::alwaysSTARTTLS)) {
1725 Capability();
1726 }
1727
1728 // If capability response has yet to occur and STARTTLS is not
1729 // configured then drop the connection since this should not happen. Also
1730 // drop the connection if capability response has occurred and
1731 // the imap version is unacceptable. Show alert only for wrong version.
1732 if (((GetServerStateParser().GetCapabilityFlag() == kCapabilityUndefined) &&
1733 (m_socketType != nsMsgSocketType::alwaysSTARTTLS)) ||
1734 (GetServerStateParser().GetCapabilityFlag() &&
1735 !(GetServerStateParser().GetCapabilityFlag() &
1736 (kIMAP4Capability | kIMAP4rev1Capability | kIMAP4other)))) {
1737 if (!DeathSignalReceived() && NS_SUCCEEDED(GetConnectionStatus()) &&
1738 GetServerStateParser().GetCapabilityFlag())
1739 AlertUserEventUsingName("imapServerNotImap4");
1740
1741 SetConnectionStatus(NS_ERROR_FAILURE); // stop netlib
1742 } else {
1743 if ((m_connectionType.EqualsLiteral("starttls") &&
1744 (m_socketType == nsMsgSocketType::trySTARTTLS &&
1745 (GetServerStateParser().GetCapabilityFlag() &
1746 kHasStartTLSCapability))) ||
1747 m_socketType == nsMsgSocketType::alwaysSTARTTLS) {
1748 StartTLS();
1749 if (GetServerStateParser().LastCommandSuccessful()) {
1750 nsCOMPtr<nsISupports> secInfo;
1751
1752 NS_ENSURE_TRUE(m_transport, false);
1753 rv = m_transport->GetSecurityInfo(getter_AddRefs(secInfo));
1754
1755 if (NS_SUCCEEDED(rv) && secInfo) {
1756 nsCOMPtr<nsISSLSocketControl> sslControl =
1757 do_QueryInterface(secInfo, &rv);
1758
1759 if (NS_SUCCEEDED(rv) && sslControl) {
1760 rv = sslControl->StartTLS();
1761 if (NS_SUCCEEDED(rv)) {
1762 // Transition to secure state is now enabled but handshakes and
1763 // negotiation has not yet occurred. Make sure that
1764 // the stream input response buffer is drained to avoid false
1765 // responses to subsequent commands (capability, login etc),
1766 // i.e., due to possible MitM attack doing pre-TLS response
1767 // injection. We are discarding any possible malicious data
1768 // stored prior to sslControl->StartTLS().
1769 // Note: If any non-TLS related data arrives while transitioning
1770 // to secure state (after sslControl->StartTLS()), it will cause
1771 // the TLS negotiation to fail so any injected data is never
1772 // accessed since the transport connection will be dropped.
1773 char discardBuf[80];
1774 uint64_t numBytesInStream = 0;
1775 uint32_t numBytesRead;
1776 rv = m_inputStream->Available(&numBytesInStream);
1777 nsCOMPtr<nsIInputStream> kungFuGrip = m_inputStream;
1778 // Read and discard any data available in socket buffer.
1779 while (numBytesInStream > 0 && NS_SUCCEEDED(rv)) {
1780 rv = m_inputStream->Read(
1781 discardBuf,
1782 std::min(uint64_t(sizeof discardBuf), numBytesInStream),
1783 &numBytesRead);
1784 numBytesInStream -= numBytesRead;
1785 }
1786 kungFuGrip = nullptr;
1787
1788 // Discard any data lines previously read from socket buffer.
1789 m_inputStreamBuffer->ClearBuffer();
1790
1791 // Force re-issue of "capability", because servers may
1792 // enable other auth features (e.g. remove LOGINDISABLED
1793 // and add AUTH=PLAIN). Sending imap data here first triggers
1794 // the TLS negotiation handshakes.
1795 Capability();
1796
1797 // If user has set pref mail.server.serverX.socketType to 1
1798 // (trySTARTTLS, now depricated in UI) and Capability()
1799 // succeeds, indicating TLS handshakes succeeded, set and
1800 // latch the socketType to 2 (alwaysSTARTTLS) for this server.
1801 if ((m_socketType == nsMsgSocketType::trySTARTTLS) &&
1802 GetServerStateParser().LastCommandSuccessful())
1803 m_imapServerSink->UpdateTrySTARTTLSPref(true);
1804
1805 // Courier imap doesn't return STARTTLS capability if we've done
1806 // a STARTTLS! But we need to remember this capability so we'll
1807 // try to use STARTTLS next time.
1808 // Update: This may not be a problem since "next time" will be
1809 // on a new connection that is not yet in secure state. So the
1810 // capability greeting *will* contain STARTTLS. I observed and
1811 // tested this on Courier imap server. But keep this to be sure.
1812 eIMAPCapabilityFlags capabilityFlag =
1813 GetServerStateParser().GetCapabilityFlag();
1814 if (!(capabilityFlag & kHasStartTLSCapability)) {
1815 capabilityFlag |= kHasStartTLSCapability;
1816 GetServerStateParser().SetCapabilityFlag(capabilityFlag);
1817 CommitCapability();
1818 }
1819 }
1820 }
1821 }
1822 if (NS_FAILED(rv)) {
1823 nsAutoCString logLine("Enable of STARTTLS failed. Error 0x");
1824 logLine.AppendInt(static_cast<uint32_t>(rv), 16);
1825 Log("ProcessCurrentURL", nullptr, logLine.get());
1826 if (m_socketType == nsMsgSocketType::alwaysSTARTTLS) {
1827 SetConnectionStatus(rv); // stop netlib
1828 if (m_transport) m_transport->Close(rv);
1829 } else if (m_socketType == nsMsgSocketType::trySTARTTLS)
1830 m_imapServerSink->UpdateTrySTARTTLSPref(false);
1831 }
1832 } else if (m_socketType == nsMsgSocketType::alwaysSTARTTLS) {
1833 SetConnectionStatus(NS_ERROR_FAILURE); // stop netlib
1834 if (m_transport) m_transport->Close(rv);
1835 } else if (m_socketType == nsMsgSocketType::trySTARTTLS) {
1836 // STARTTLS failed, so downgrade socket type
1837 m_imapServerSink->UpdateTrySTARTTLSPref(false);
1838 }
1839 } else if (m_socketType == nsMsgSocketType::trySTARTTLS) {
1840 // we didn't know the server supported TLS when we created
1841 // the socket, so we're going to retry with a STARTTLS socket
1842 if (GetServerStateParser().GetCapabilityFlag() &
1843 kHasStartTLSCapability) {
1844 ClearFlag(IMAP_CONNECTION_IS_OPEN);
1845 TellThreadToDie();
1846 SetConnectionStatus(NS_ERROR_FAILURE);
1847 return RetryUrl();
1848 }
1849 // trySTARTTLS set, but server doesn't have TLS capability,
1850 // so downgrade socket type
1851 m_imapServerSink->UpdateTrySTARTTLSPref(false);
1852 m_socketType = nsMsgSocketType::plain;
1853 }
1854 if (!DeathSignalReceived() && (NS_SUCCEEDED(GetConnectionStatus()))) {
1855 logonFailed = !TryToLogon();
1856 }
1857 if (m_retryUrlOnError) return RetryUrl();
1858 }
1859 } // if death signal not received
1860
1861 if (!DeathSignalReceived() && (NS_SUCCEEDED(GetConnectionStatus()))) {
1862 // if the server supports a language extension then we should
1863 // attempt to issue the language extension.
1864 if (GetServerStateParser().GetCapabilityFlag() & kHasLanguageCapability)
1865 Language();
1866
1867 if (m_runningUrl) {
1868 bool foundMailboxesAlready = false;
1869 m_hostSessionList->GetHaveWeEverDiscoveredFoldersForHost(
1870 GetImapServerKey(), foundMailboxesAlready);
1871 if (!foundMailboxesAlready) FindMailboxesIfNecessary();
1872 }
1873
1874 nsImapState imapState = nsIImapUrl::ImapStatusNone;
1875 if (m_runningUrl) m_runningUrl->GetRequiredImapState(&imapState);
1876
1877 if (imapState == nsIImapUrl::nsImapAuthenticatedState)
1878 ProcessAuthenticatedStateURL();
1879 else // must be a url that requires us to be in the selected state
1880 ProcessSelectedStateURL();
1881
1882 if (m_retryUrlOnError) return RetryUrl();
1883
1884 // The URL has now been processed
1885 if ((!logonFailed && NS_FAILED(GetConnectionStatus())) ||
1886 DeathSignalReceived())
1887 HandleCurrentUrlError();
1888
1889 } else if (!logonFailed)
1890 HandleCurrentUrlError();
1891
1892 // if we are set up as a channel, we should notify our channel listener that
1893 // we are stopping... so pass in ourself as the channel and not the underlying
1894 // socket or file channel the protocol happens to be using
1895 if (m_channelListener) {
1896 NS_ASSERTION(m_mockChannel, "no request");
1897 if (m_mockChannel) {
1898 nsresult status;
1899 m_mockChannel->GetStatus(&status);
1900 if (!GetServerStateParser().LastCommandSuccessful() &&
1901 NS_SUCCEEDED(status))
1902 status = NS_MSG_ERROR_IMAP_COMMAND_FAILED;
1903 rv = m_channelListener->OnStopRequest(m_mockChannel, status);
1904 }
1905 }
1906 bool suspendUrl = false;
1907 m_runningUrl->GetMoreHeadersToDownload(&suspendUrl);
1908 if (mailnewsurl && m_imapMailFolderSink) {
1909 rv = GetConnectionStatus();
1910 // There are error conditions to check even if the connection is OK.
1911 if (NS_SUCCEEDED(rv)) {
1912 if (logonFailed) {
1913 rv = NS_ERROR_FAILURE;
1914 } else if (GetServerStateParser().CommandFailed()) {
1915 rv = NS_MSG_ERROR_IMAP_COMMAND_FAILED;
1916 }
1917 }
1918 if (NS_FAILED(rv)) {
1919 MOZ_LOG(
1920 IMAP, LogLevel::Debug,
1921 ("URL failed with code 0x%" PRIx32 " (%s)", static_cast<uint32_t>(rv),
1922 mailnewsurl->GetSpecOrDefault().get()));
1923 // If discovery URL fails, clear the in-progress flag.
1924 if (m_imapAction == nsIImapUrl::nsImapDiscoverAllBoxesUrl) {
1925 m_hostSessionList->SetDiscoveryForHostInProgress(GetImapServerKey(),
1926 false);
1927 }
1928 }
1929 // Inform any nsIUrlListeners that the URL has finished. This will invoke
1930 // nsIUrlListener.onStopRunningUrl().
1931 m_imapMailFolderSink->SetUrlState(this, mailnewsurl, false, suspendUrl, rv);
1932 // doom the cache entry
1933 if (NS_FAILED(rv) && DeathSignalReceived() && m_mockChannel) {
1934 MOZ_LOG(IMAPCache, LogLevel::Debug,
1935 ("ProcessCurrentURL(): Call DoomCacheEntry()"));
1936 DoomCacheEntry(mailnewsurl);
1937 }
1938 } else {
1939 // That's seen at times in debug sessions.
1940 NS_WARNING("missing url or sink");
1941 }
1942
1943 // disable timeouts before caching connection.
1944 if (m_transport)
1945 m_transport->SetTimeout(nsISocketTransport::TIMEOUT_READ_WRITE,
1946 PR_UINT32_MAX);
1947
1948 SetFlag(IMAP_CLEAN_UP_URL_STATE);
1949
1950 nsCOMPtr<nsISupports> copyState;
1951 if (m_runningUrl) m_runningUrl->GetCopyState(getter_AddRefs(copyState));
1952 // this is so hokey...we MUST clear any local references to the url
1953 // BEFORE calling ReleaseUrlState
1954 mailnewsurl = nullptr;
1955
1956 if (suspendUrl) m_imapServerSink->SuspendUrl(m_runningUrl);
1957 // save the imap folder sink since we need it to do the CopyNextStreamMessage
1958 RefPtr<ImapMailFolderSinkProxy> imapMailFolderSink = m_imapMailFolderSink;
1959 // release the url as we are done with it...
1960 ReleaseUrlState(false);
1961 ResetProgressInfo();
1962
1963 ClearFlag(IMAP_CLEAN_UP_URL_STATE);
1964
1965 if (imapMailFolderSink) {
1966 if (copyState) {
1967 rv = imapMailFolderSink->CopyNextStreamMessage(
1968 GetServerStateParser().LastCommandSuccessful() &&
1969 NS_SUCCEEDED(GetConnectionStatus()),
1970 copyState);
1971 if (NS_FAILED(rv))
1972 MOZ_LOG(IMAP, LogLevel::Info,
1973 ("CopyNextStreamMessage failed: %" PRIx32,
1974 static_cast<uint32_t>(rv)));
1975
1976 NS_ReleaseOnMainThread("nsImapProtocol, copyState", copyState.forget());
1977 }
1978 // we might need this to stick around for IDLE support
1979 m_imapMailFolderSink = imapMailFolderSink;
1980 imapMailFolderSink = nullptr;
1981 } else
1982 MOZ_LOG(IMAP, LogLevel::Info, ("null imapMailFolderSink"));
1983
1984 // now try queued urls, now that we've released this connection.
1985 if (m_imapServerSink) {
1986 if (NS_SUCCEEDED(GetConnectionStatus()))
1987 rv = m_imapServerSink->LoadNextQueuedUrl(this, &anotherUrlRun);
1988 else // if we don't do this, they'll just sit and spin until
1989 // we run some other url on this server.
1990 {
1991 Log("ProcessCurrentURL", nullptr, "aborting queued urls");
1992 rv = m_imapServerSink->AbortQueuedUrls();
1993 }
1994 }
1995
1996 // if we didn't run another url, release the server sink to
1997 // cut circular refs.
1998 if (!anotherUrlRun) m_imapServerSink = nullptr;
1999
2000 if (NS_FAILED(GetConnectionStatus()) || !GetServerStateParser().Connected() ||
2001 GetServerStateParser().SyntaxError()) {
2002 if (m_imapServerSink) m_imapServerSink->RemoveServerConnection(this);
2003
2004 if (!DeathSignalReceived()) {
2005 TellThreadToDie();
2006 }
2007 } else {
2008 if (m_imapServerSink) {
2009 bool shuttingDown;
2010 m_imapServerSink->GetServerShuttingDown(&shuttingDown);
2011 if (shuttingDown) m_useIdle = false;
2012 }
2013 }
2014 return anotherUrlRun;
2015 }
2016
RetryUrl()2017 bool nsImapProtocol::RetryUrl() {
2018 nsCOMPtr<nsIImapUrl> kungFuGripImapUrl = m_runningUrl;
2019 nsCOMPtr<nsIImapMockChannel> saveMockChannel;
2020
2021 // the mock channel might be null - that's OK.
2022 if (m_imapServerSink)
2023 (void)m_imapServerSink->PrepareToRetryUrl(kungFuGripImapUrl,
2024 getter_AddRefs(saveMockChannel));
2025
2026 ReleaseUrlState(true);
2027 if (m_imapServerSink) {
2028 m_imapServerSink->RemoveServerConnection(this);
2029 m_imapServerSink->RetryUrl(kungFuGripImapUrl, saveMockChannel);
2030 }
2031
2032 // Hack for Bug 1586494.
2033 // (this is a workaround to try and prevent a specific crash, and
2034 // does nothing clarify the threading mess!)
2035 // RetryUrl() is only ever called from the imap thread.
2036 // Mockchannel dtor insists upon being run on the main thread.
2037 // So make sure we don't accidentally cause the mockchannel to die right now.
2038 if (saveMockChannel) {
2039 NS_ReleaseOnMainThread("nsImapProtocol::RetryUrl",
2040 saveMockChannel.forget());
2041 }
2042
2043 return (m_imapServerSink != nullptr); // we're running a url (the same url)
2044 }
2045
2046 // ignoreBadAndNOResponses --> don't throw a error dialog if this command
2047 // results in a NO or Bad response from the server..in other words the command
2048 // is "exploratory" and we don't really care if it succeeds or fails.
ParseIMAPandCheckForNewMail(const char * commandString,bool aIgnoreBadAndNOResponses)2049 void nsImapProtocol::ParseIMAPandCheckForNewMail(
2050 const char* commandString, bool aIgnoreBadAndNOResponses) {
2051 if (commandString)
2052 GetServerStateParser().ParseIMAPServerResponse(commandString,
2053 aIgnoreBadAndNOResponses);
2054 else
2055 GetServerStateParser().ParseIMAPServerResponse(m_currentCommand.get(),
2056 aIgnoreBadAndNOResponses);
2057 // **** fix me for new mail biff state *****
2058 }
2059
2060 /////////////////////////////////////////////////////////////////////////////////////////////
2061 // End of nsIStreamListenerSupport
2062 //////////////////////////////////////////////////////////////////////////////////////////////
2063
2064 NS_IMETHODIMP
GetRunningUrl(nsIURI ** result)2065 nsImapProtocol::GetRunningUrl(nsIURI** result) {
2066 if (result && m_runningUrl)
2067 return m_runningUrl->QueryInterface(NS_GET_IID(nsIURI), (void**)result);
2068 return NS_ERROR_NULL_POINTER;
2069 }
2070
GetRunningImapURL(nsIImapUrl ** aImapUrl)2071 NS_IMETHODIMP nsImapProtocol::GetRunningImapURL(nsIImapUrl** aImapUrl) {
2072 if (aImapUrl && m_runningUrl)
2073 return m_runningUrl->QueryInterface(NS_GET_IID(nsIImapUrl),
2074 (void**)aImapUrl);
2075 return NS_ERROR_NULL_POINTER;
2076 }
2077
2078 /*
2079 * Writes the data contained in dataBuffer into the current output stream. It
2080 * also informs the transport layer that this data is now available for
2081 * transmission. Returns a positive number for success, 0 for failure (not all
2082 * the bytes were written to the stream, etc). We need to make another pass
2083 * through this file to install an error system (mscott)
2084 */
2085
SendData(const char * dataBuffer,bool aSuppressLogging)2086 nsresult nsImapProtocol::SendData(const char* dataBuffer,
2087 bool aSuppressLogging) {
2088 nsresult rv = NS_ERROR_NULL_POINTER;
2089
2090 if (!m_transport) {
2091 Log("SendData", nullptr, "clearing IMAP_CONNECTION_IS_OPEN");
2092 // the connection died unexpectedly! so clear the open connection flag
2093 ClearFlag(IMAP_CONNECTION_IS_OPEN);
2094 TellThreadToDie();
2095 SetConnectionStatus(NS_ERROR_FAILURE);
2096 return NS_ERROR_FAILURE;
2097 }
2098
2099 if (dataBuffer && m_outputStream) {
2100 m_currentCommand = dataBuffer;
2101 if (!aSuppressLogging)
2102 Log("SendData", nullptr, dataBuffer);
2103 else
2104 Log("SendData", nullptr,
2105 "Logging suppressed for this command (it probably contained "
2106 "authentication information)");
2107
2108 {
2109 // don't allow someone to close the stream/transport out from under us
2110 // this can happen when the ui thread calls TellThreadToDie.
2111 PR_CEnterMonitor(this);
2112 uint32_t n;
2113 if (m_outputStream)
2114 rv = m_outputStream->Write(dataBuffer, PL_strlen(dataBuffer), &n);
2115 PR_CExitMonitor(this);
2116 }
2117 if (NS_FAILED(rv)) {
2118 Log("SendData", nullptr, "clearing IMAP_CONNECTION_IS_OPEN");
2119 // the connection died unexpectedly! so clear the open connection flag
2120 ClearFlag(IMAP_CONNECTION_IS_OPEN);
2121 TellThreadToDie();
2122 SetConnectionStatus(rv);
2123 if (m_runningUrl && !m_retryUrlOnError) {
2124 bool alreadyRerunningUrl;
2125 m_runningUrl->GetRerunningUrl(&alreadyRerunningUrl);
2126 if (!alreadyRerunningUrl) {
2127 m_runningUrl->SetRerunningUrl(true);
2128 m_retryUrlOnError = true;
2129 }
2130 }
2131 }
2132 }
2133
2134 return rv;
2135 }
2136
2137 /////////////////////////////////////////////////////////////////////////////////////////////
2138 // Begin protocol state machine functions...
2139 //////////////////////////////////////////////////////////////////////////////////////////////
2140
2141 // ProcessProtocolState - we override this only so we'll link - it should never
2142 // get called.
2143
ProcessProtocolState(nsIURI * url,nsIInputStream * inputStream,uint64_t sourceOffset,uint32_t length)2144 nsresult nsImapProtocol::ProcessProtocolState(nsIURI* url,
2145 nsIInputStream* inputStream,
2146 uint64_t sourceOffset,
2147 uint32_t length) {
2148 return NS_OK;
2149 }
2150
2151 class UrlListenerNotifierEvent : public mozilla::Runnable {
2152 public:
UrlListenerNotifierEvent(nsIMsgMailNewsUrl * aUrl,nsIImapProtocol * aProtocol)2153 UrlListenerNotifierEvent(nsIMsgMailNewsUrl* aUrl, nsIImapProtocol* aProtocol)
2154 : mozilla::Runnable("UrlListenerNotifierEvent"),
2155 mUrl(aUrl),
2156 mProtocol(aProtocol) {}
2157
Run()2158 NS_IMETHOD Run() {
2159 if (mUrl) {
2160 nsCOMPtr<nsIMsgFolder> folder;
2161 mUrl->GetFolder(getter_AddRefs(folder));
2162 NS_ENSURE_TRUE(folder, NS_OK);
2163 nsCOMPtr<nsIImapMailFolderSink> folderSink(do_QueryInterface(folder));
2164 // This causes the url listener to get OnStart and Stop notifications.
2165 folderSink->SetUrlState(mProtocol, mUrl, true, false, NS_OK);
2166 folderSink->SetUrlState(mProtocol, mUrl, false, false, NS_OK);
2167 }
2168 return NS_OK;
2169 }
2170
2171 private:
2172 nsCOMPtr<nsIMsgMailNewsUrl> mUrl;
2173 nsCOMPtr<nsIImapProtocol> mProtocol;
2174 };
2175
TryToRunUrlLocally(nsIURI * aURL,nsISupports * aConsumer)2176 bool nsImapProtocol::TryToRunUrlLocally(nsIURI* aURL, nsISupports* aConsumer) {
2177 nsresult rv;
2178 nsCOMPtr<nsIImapUrl> imapUrl(do_QueryInterface(aURL, &rv));
2179 NS_ENSURE_SUCCESS(rv, false);
2180 nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(aURL);
2181 nsCString messageIdString;
2182 imapUrl->GetListOfMessageIds(messageIdString);
2183 bool useLocalCache = false;
2184 if (!messageIdString.IsEmpty() &&
2185 !HandlingMultipleMessages(messageIdString)) {
2186 nsImapAction action;
2187 imapUrl->GetImapAction(&action);
2188 nsCOMPtr<nsIMsgFolder> folder;
2189 mailnewsUrl->GetFolder(getter_AddRefs(folder));
2190 NS_ENSURE_TRUE(folder, false);
2191
2192 folder->HasMsgOffline(strtoul(messageIdString.get(), nullptr, 10),
2193 &useLocalCache);
2194 mailnewsUrl->SetMsgIsInLocalCache(useLocalCache);
2195 // We're downloading a single message for offline use, and it's
2196 // already offline. So we shouldn't do anything, but we do
2197 // need to notify the url listener.
2198 if (useLocalCache && action == nsIImapUrl::nsImapMsgDownloadForOffline) {
2199 nsCOMPtr<nsIRunnable> event =
2200 new UrlListenerNotifierEvent(mailnewsUrl, this);
2201 // Post this as an event because it can lead to re-entrant calls to
2202 // LoadNextQueuedUrl if the listener runs a new url.
2203 if (event) NS_DispatchToCurrentThread(event);
2204 return true;
2205 }
2206 }
2207 if (!useLocalCache) return false;
2208
2209 nsCOMPtr<nsIImapMockChannel> mockChannel;
2210 imapUrl->GetMockChannel(getter_AddRefs(mockChannel));
2211 if (!mockChannel) return false;
2212
2213 nsImapMockChannel* imapChannel =
2214 static_cast<nsImapMockChannel*>(mockChannel.get());
2215 if (!imapChannel) return false;
2216
2217 nsCOMPtr<nsILoadGroup> loadGroup;
2218 imapChannel->GetLoadGroup(getter_AddRefs(loadGroup));
2219 if (!loadGroup) // if we don't have one, the url will snag one from the msg
2220 // window...
2221 mailnewsUrl->GetLoadGroup(getter_AddRefs(loadGroup));
2222
2223 if (loadGroup)
2224 loadGroup->RemoveRequest((nsIRequest*)mockChannel,
2225 nullptr /* context isupports */, NS_OK);
2226
2227 if (imapChannel->ReadFromLocalCache()) {
2228 (void)imapChannel->NotifyStartEndReadFromCache(true);
2229 return true;
2230 }
2231 return false;
2232 }
2233
2234 // LoadImapUrl takes a url, initializes all of our url specific data by calling
2235 // SetupUrl. Finally, we signal the url to run monitor to let the imap main
2236 // thread loop process the current url (it is waiting on this monitor). There
2237 // is a contract that the imap thread has already been started before we
2238 // attempt to load a url...
2239 // LoadImapUrl() is called by nsImapIncomingServer to run a queued url on a free
2240 // connection.
LoadImapUrl(nsIURI * aURL,nsISupports * aConsumer)2241 NS_IMETHODIMP nsImapProtocol::LoadImapUrl(nsIURI* aURL,
2242 nsISupports* aConsumer) {
2243 nsresult rv = NS_ERROR_FAILURE;
2244 if (aURL) {
2245 #ifdef DEBUG_bienvenu
2246 printf("loading url %s\n", aURL->GetSpecOrDefault().get());
2247 #endif
2248 // We might be able to fulfil the request locally (e.g. fetching a message
2249 // which is already stored offline).
2250 if (TryToRunUrlLocally(aURL, aConsumer)) return NS_OK;
2251 m_urlInProgress = true;
2252 m_imapMailFolderSink = nullptr;
2253 rv = SetupWithUrl(aURL, aConsumer);
2254 m_lastActiveTime = PR_Now();
2255 }
2256 return rv;
2257 }
2258
LoadImapUrlInternal()2259 nsresult nsImapProtocol::LoadImapUrlInternal() {
2260 nsresult rv = NS_ERROR_FAILURE;
2261
2262 if (m_transport && m_mockChannel) {
2263 m_transport->SetTimeout(nsISocketTransport::TIMEOUT_CONNECT,
2264 gResponseTimeout + 60);
2265 int32_t readWriteTimeout = gResponseTimeout;
2266 if (m_runningUrl) {
2267 m_runningUrl->GetImapAction(&m_imapAction);
2268 // This is a silly hack, but the default of 100 seconds is typically way
2269 // too long for things like APPEND, which should come back immediately.
2270 // However, for large messages on some servers the final append response
2271 // time can be longer. So now it is one-fifth of the configured
2272 // `mailnews.tcptimeout' which defaults to 20 seconds.
2273 if (m_imapAction == nsIImapUrl::nsImapAppendMsgFromFile ||
2274 m_imapAction == nsIImapUrl::nsImapAppendDraftFromFile) {
2275 readWriteTimeout = gAppendTimeout;
2276 } else if (m_imapAction == nsIImapUrl::nsImapOnlineMove ||
2277 m_imapAction == nsIImapUrl::nsImapOnlineCopy) {
2278 nsCString messageIdString;
2279 m_runningUrl->GetListOfMessageIds(messageIdString);
2280 uint32_t copyCount = CountMessagesInIdString(messageIdString.get());
2281 // If we're move/copying a large number of messages,
2282 // which should be rare, increase the timeout based on number
2283 // of messages. 40 messages per second should be sufficiently slow.
2284 if (copyCount > 2400) // 40 * 60, 60 is default read write timeout
2285 readWriteTimeout =
2286 std::max(readWriteTimeout, (int32_t)copyCount / 40);
2287 }
2288 }
2289 m_transport->SetTimeout(nsISocketTransport::TIMEOUT_READ_WRITE,
2290 readWriteTimeout);
2291 // set the security info for the mock channel to be the security status for
2292 // our underlying transport.
2293 nsCOMPtr<nsISupports> securityInfo;
2294 m_transport->GetSecurityInfo(getter_AddRefs(securityInfo));
2295 m_mockChannel->SetSecurityInfo(securityInfo);
2296
2297 SetSecurityCallbacksFromChannel(m_transport, m_mockChannel);
2298
2299 nsCOMPtr<nsITransportEventSink> sinkMC = do_QueryInterface(m_mockChannel);
2300 if (sinkMC) {
2301 nsCOMPtr<nsIThread> thread = do_GetMainThread();
2302 RefPtr<nsImapTransportEventSink> sink = new nsImapTransportEventSink;
2303 rv = net_NewTransportEventSinkProxy(getter_AddRefs(sink->m_proxy), sinkMC,
2304 thread);
2305 NS_ENSURE_SUCCESS(rv, rv);
2306 m_transport->SetEventSink(sink, nullptr);
2307 }
2308
2309 // and if we have a cache entry that we are saving the message to, set the
2310 // security info on it too. since imap only uses the memory cache, passing
2311 // this on is the right thing to do.
2312 nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningUrl);
2313 if (mailnewsUrl) {
2314 nsCOMPtr<nsICacheEntry> cacheEntry;
2315 mailnewsUrl->GetMemCacheEntry(getter_AddRefs(cacheEntry));
2316 if (cacheEntry) cacheEntry->SetSecurityInfo(securityInfo);
2317 }
2318 }
2319
2320 rv = SetupSinkProxy(); // generate proxies for all of the event sinks in the
2321 // url
2322 if (NS_FAILED(rv)) // URL can be invalid.
2323 return rv;
2324
2325 if (m_transport && m_runningUrl) {
2326 nsImapAction imapAction;
2327 m_runningUrl->GetImapAction(&imapAction);
2328 // if we're shutting down, and not running the kinds of urls we run at
2329 // shutdown, then this should fail because running urls during
2330 // shutdown will very likely fail and potentially hang.
2331 nsCOMPtr<nsIMsgAccountManager> accountMgr =
2332 do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
2333 NS_ENSURE_SUCCESS(rv, rv);
2334 bool shuttingDown = false;
2335 (void)accountMgr->GetShutdownInProgress(&shuttingDown);
2336 if (shuttingDown && imapAction != nsIImapUrl::nsImapExpungeFolder &&
2337 imapAction != nsIImapUrl::nsImapDeleteAllMsgs &&
2338 imapAction != nsIImapUrl::nsImapDeleteFolder)
2339 return NS_ERROR_FAILURE;
2340
2341 // if we're running a select or delete all, do a noop first.
2342 // this should really be in the connection cache code when we know
2343 // we're pulling out a selected state connection, but maybe we
2344 // can get away with this.
2345 m_needNoop = (imapAction == nsIImapUrl::nsImapSelectFolder ||
2346 imapAction == nsIImapUrl::nsImapDeleteAllMsgs);
2347
2348 // We now have a url to run so signal the monitor for url ready to be
2349 // processed...
2350 ReentrantMonitorAutoEnter urlReadyMon(m_urlReadyToRunMonitor);
2351 m_nextUrlReadyToRun = true;
2352 urlReadyMon.Notify();
2353
2354 } // if we have an imap url and a transport
2355 else {
2356 NS_ASSERTION(false, "missing channel or running url");
2357 }
2358
2359 return rv;
2360 }
2361
IsBusy(bool * aIsConnectionBusy,bool * isInboxConnection)2362 NS_IMETHODIMP nsImapProtocol::IsBusy(bool* aIsConnectionBusy,
2363 bool* isInboxConnection) {
2364 if (!aIsConnectionBusy || !isInboxConnection) return NS_ERROR_NULL_POINTER;
2365 nsresult rv = NS_OK;
2366 *aIsConnectionBusy = false;
2367 *isInboxConnection = false;
2368 if (!m_transport) {
2369 // this connection might not be fully set up yet.
2370 rv = NS_ERROR_FAILURE;
2371 } else {
2372 if (m_urlInProgress) // do we have a url? That means we're working on it...
2373 *aIsConnectionBusy = true;
2374
2375 if (GetServerStateParser().GetIMAPstate() ==
2376 nsImapServerResponseParser::kFolderSelected &&
2377 GetServerStateParser().GetSelectedMailboxName() &&
2378 PL_strcasecmp(GetServerStateParser().GetSelectedMailboxName(),
2379 "Inbox") == 0)
2380 *isInboxConnection = true;
2381 }
2382 return rv;
2383 }
2384
2385 #define IS_SUBSCRIPTION_RELATED_ACTION(action) \
2386 (action == nsIImapUrl::nsImapSubscribe || \
2387 action == nsIImapUrl::nsImapUnsubscribe || \
2388 action == nsIImapUrl::nsImapDiscoverAllBoxesUrl || \
2389 action == nsIImapUrl::nsImapListFolder)
2390
2391 // canRunUrl means the connection is not busy, and is in the selected state
2392 // for the desired folder (or authenticated).
2393 // has to wait means it's in the right selected state, but busy.
CanHandleUrl(nsIImapUrl * aImapUrl,bool * aCanRunUrl,bool * hasToWait)2394 NS_IMETHODIMP nsImapProtocol::CanHandleUrl(nsIImapUrl* aImapUrl,
2395 bool* aCanRunUrl, bool* hasToWait) {
2396 if (!aCanRunUrl || !hasToWait || !aImapUrl) return NS_ERROR_NULL_POINTER;
2397 nsresult rv = NS_OK;
2398 MutexAutoLock mon(mLock);
2399
2400 *aCanRunUrl = false; // assume guilty until proven otherwise...
2401 *hasToWait = false;
2402
2403 if (DeathSignalReceived()) return NS_ERROR_FAILURE;
2404
2405 bool isBusy = false;
2406 bool isInboxConnection = false;
2407
2408 if (!m_transport) {
2409 // this connection might not be fully set up yet.
2410 return NS_ERROR_FAILURE;
2411 }
2412 IsBusy(&isBusy, &isInboxConnection);
2413 bool inSelectedState = GetServerStateParser().GetIMAPstate() ==
2414 nsImapServerResponseParser::kFolderSelected;
2415
2416 nsAutoCString curSelectedUrlFolderName;
2417 nsAutoCString pendingUrlFolderName;
2418 if (inSelectedState)
2419 curSelectedUrlFolderName = GetServerStateParser().GetSelectedMailboxName();
2420
2421 if (isBusy) {
2422 nsImapState curUrlImapState;
2423 NS_ASSERTION(m_runningUrl, "isBusy, but no running url.");
2424 if (m_runningUrl) {
2425 m_runningUrl->GetRequiredImapState(&curUrlImapState);
2426 if (curUrlImapState == nsIImapUrl::nsImapSelectedState) {
2427 char* folderName = GetFolderPathString();
2428 if (!curSelectedUrlFolderName.Equals(folderName))
2429 pendingUrlFolderName.Assign(folderName);
2430 inSelectedState = true;
2431 PR_Free(folderName);
2432 }
2433 }
2434 }
2435
2436 nsImapState imapState;
2437 nsImapAction actionForProposedUrl;
2438 aImapUrl->GetImapAction(&actionForProposedUrl);
2439 aImapUrl->GetRequiredImapState(&imapState);
2440
2441 // OK, this is a bit of a hack - we're going to pretend that
2442 // these types of urls requires a selected state connection on
2443 // the folder in question. This isn't technically true,
2444 // but we would much rather use that connection for several reasons,
2445 // one is that some UW servers require us to use that connection
2446 // the other is that we don't want to leave a connection dangling in
2447 // the selected state for the deleted folder.
2448 // If we don't find a connection in that selected state,
2449 // we'll fall back to the first free connection.
2450 bool isSelectedStateUrl =
2451 imapState == nsIImapUrl::nsImapSelectedState ||
2452 actionForProposedUrl == nsIImapUrl::nsImapDeleteFolder ||
2453 actionForProposedUrl == nsIImapUrl::nsImapRenameFolder ||
2454 actionForProposedUrl == nsIImapUrl::nsImapMoveFolderHierarchy ||
2455 actionForProposedUrl == nsIImapUrl::nsImapAppendDraftFromFile ||
2456 actionForProposedUrl == nsIImapUrl::nsImapAppendMsgFromFile ||
2457 actionForProposedUrl == nsIImapUrl::nsImapFolderStatus;
2458
2459 nsCOMPtr<nsIMsgMailNewsUrl> msgUrl = do_QueryInterface(aImapUrl);
2460 nsCOMPtr<nsIMsgIncomingServer> server;
2461 rv = msgUrl->GetServer(getter_AddRefs(server));
2462 if (NS_SUCCEEDED(rv)) {
2463 // compare host/user between url and connection.
2464 nsCString urlHostName;
2465 nsCString urlUserName;
2466 rv = server->GetHostName(urlHostName);
2467 NS_ENSURE_SUCCESS(rv, rv);
2468 rv = server->GetUsername(urlUserName);
2469 NS_ENSURE_SUCCESS(rv, rv);
2470
2471 if ((GetImapHostName().IsEmpty() ||
2472 urlHostName.Equals(GetImapHostName(),
2473 nsCaseInsensitiveCStringComparator)) &&
2474 (GetImapUserName().IsEmpty() ||
2475 urlUserName.Equals(GetImapUserName(),
2476 nsCaseInsensitiveCStringComparator))) {
2477 if (isSelectedStateUrl) {
2478 if (inSelectedState) {
2479 // *** jt - in selected state can only run url with
2480 // matching foldername
2481 char* folderNameForProposedUrl = nullptr;
2482 rv = aImapUrl->CreateServerSourceFolderPathString(
2483 &folderNameForProposedUrl);
2484 if (NS_SUCCEEDED(rv) && folderNameForProposedUrl) {
2485 bool isInbox =
2486 PL_strcasecmp("Inbox", folderNameForProposedUrl) == 0;
2487 if (!curSelectedUrlFolderName.IsEmpty() ||
2488 !pendingUrlFolderName.IsEmpty()) {
2489 bool matched = isInbox
2490 ? PL_strcasecmp(curSelectedUrlFolderName.get(),
2491 folderNameForProposedUrl) == 0
2492 : PL_strcmp(curSelectedUrlFolderName.get(),
2493 folderNameForProposedUrl) == 0;
2494 if (!matched && !pendingUrlFolderName.IsEmpty()) {
2495 matched = isInbox ? PL_strcasecmp(pendingUrlFolderName.get(),
2496 folderNameForProposedUrl) == 0
2497 : PL_strcmp(pendingUrlFolderName.get(),
2498 folderNameForProposedUrl) == 0;
2499 }
2500 if (matched) {
2501 if (isBusy)
2502 *hasToWait = true;
2503 else
2504 *aCanRunUrl = true;
2505 }
2506 }
2507 }
2508 MOZ_LOG(IMAP, LogLevel::Debug,
2509 ("proposed url = %s folder for connection %s has To Wait = "
2510 "%s can run = %s",
2511 folderNameForProposedUrl, curSelectedUrlFolderName.get(),
2512 (*hasToWait) ? "true" : "false",
2513 (*aCanRunUrl) ? "true" : "false"));
2514 PR_FREEIF(folderNameForProposedUrl);
2515 }
2516 } else // *** jt - an authenticated state url can be run in either
2517 // authenticated or selected state
2518 {
2519 nsImapAction actionForRunningUrl;
2520
2521 // If proposed url is subscription related, and we are currently running
2522 // a subscription url, then we want to queue the proposed url after the
2523 // current url. Otherwise, we can run this url if we're not busy. If we
2524 // never find a running subscription-related url, the caller will just
2525 // use whatever free connection it can find, which is what we want.
2526 if (IS_SUBSCRIPTION_RELATED_ACTION(actionForProposedUrl)) {
2527 if (isBusy && m_runningUrl) {
2528 m_runningUrl->GetImapAction(&actionForRunningUrl);
2529 if (IS_SUBSCRIPTION_RELATED_ACTION(actionForRunningUrl)) {
2530 *aCanRunUrl = false;
2531 *hasToWait = true;
2532 }
2533 }
2534 } else {
2535 if (!isBusy) *aCanRunUrl = true;
2536 }
2537 }
2538 }
2539 }
2540 return rv;
2541 }
2542
2543 // Command tag handling stuff.
2544 // Zero tag number indicates never used so set it to an initial random number
2545 // between 1 and 100. Otherwise just increment the uint32_t value unless it
2546 // rolls to zero then set it to 1. Then convert the tag number to a string for
2547 // use in IMAP commands.
IncrementCommandTagNumber()2548 void nsImapProtocol::IncrementCommandTagNumber() {
2549 if (m_currentServerCommandTagNumber == 0) {
2550 srand((unsigned)m_lastCheckTime);
2551 m_currentServerCommandTagNumber = 1 + (rand() % 100);
2552 } else if (++m_currentServerCommandTagNumber == 0) {
2553 m_currentServerCommandTagNumber = 1;
2554 }
2555 sprintf(m_currentServerCommandTag, "%u", m_currentServerCommandTagNumber);
2556 }
2557
GetServerCommandTag()2558 const char* nsImapProtocol::GetServerCommandTag() {
2559 return m_currentServerCommandTag;
2560 }
2561
ProcessSelectedStateURL()2562 void nsImapProtocol::ProcessSelectedStateURL() {
2563 nsCString mailboxName;
2564 bool bMessageIdsAreUids = true;
2565 bool moreHeadersToDownload;
2566 imapMessageFlagsType msgFlags = 0;
2567 nsCString urlHost;
2568
2569 // this can't fail, can it?
2570 nsresult res;
2571 res = m_runningUrl->GetImapAction(&m_imapAction);
2572 m_runningUrl->MessageIdsAreUids(&bMessageIdsAreUids);
2573 m_runningUrl->GetMsgFlags(&msgFlags);
2574 m_runningUrl->GetMoreHeadersToDownload(&moreHeadersToDownload);
2575
2576 res = CreateServerSourceFolderPathString(getter_Copies(mailboxName));
2577 if (NS_FAILED(res))
2578 Log("ProcessSelectedStateURL", nullptr,
2579 "error getting source folder path string");
2580
2581 if (NS_SUCCEEDED(res) && !DeathSignalReceived()) {
2582 bool selectIssued = false;
2583 if (GetServerStateParser().GetIMAPstate() ==
2584 nsImapServerResponseParser::kFolderSelected) {
2585 if (GetServerStateParser().GetSelectedMailboxName() &&
2586 PL_strcmp(GetServerStateParser().GetSelectedMailboxName(),
2587 mailboxName.get())) { // we are selected in another folder
2588 if (m_closeNeededBeforeSelect) Close();
2589 if (GetServerStateParser().LastCommandSuccessful()) {
2590 selectIssued = true;
2591 SelectMailbox(mailboxName.get());
2592 }
2593 } else if (!GetServerStateParser()
2594 .GetSelectedMailboxName()) { // why are we in the
2595 // selected state with no
2596 // box name?
2597 SelectMailbox(mailboxName.get());
2598 selectIssued = true;
2599 } else if (moreHeadersToDownload &&
2600 m_imapMailFolderSink) // we need to fetch older headers
2601 {
2602 nsTArray<nsMsgKey> msgIdList;
2603 bool more;
2604 m_imapMailFolderSink->GetMsgHdrsToDownload(
2605 &more, &m_progressExpectedNumber, msgIdList);
2606 if (msgIdList.Length() > 0) {
2607 FolderHeaderDump(msgIdList.Elements(), msgIdList.Length());
2608 m_runningUrl->SetMoreHeadersToDownload(more);
2609 // We're going to be re-running this url.
2610 if (more) m_runningUrl->SetRerunningUrl(true);
2611 }
2612 HeaderFetchCompleted();
2613 } else {
2614 // get new message counts, if any, from server
2615 if (m_needNoop) {
2616 // For some IMAP servers, to detect new email we must send imap
2617 // SELECT even if already SELECTed on the same mailbox. For other
2618 // servers that simply don't support IDLE, doing select here will
2619 // cause emails to be properly marked "read" after they have been
2620 // read in another email client.
2621 if (m_forceSelect) {
2622 SelectMailbox(mailboxName.get());
2623 selectIssued = true;
2624 }
2625
2626 m_noopCount++;
2627 if ((gPromoteNoopToCheckCount > 0 &&
2628 (m_noopCount % gPromoteNoopToCheckCount) == 0) ||
2629 CheckNeeded())
2630 Check();
2631 else
2632 Noop(); // I think this is needed when we're using a cached
2633 // connection
2634 m_needNoop = false;
2635 }
2636 }
2637 } else {
2638 // go to selected state
2639 SelectMailbox(mailboxName.get());
2640 selectIssued = GetServerStateParser().LastCommandSuccessful();
2641 }
2642
2643 if (selectIssued) RefreshACLForFolderIfNecessary(mailboxName.get());
2644
2645 bool uidValidityOk = true;
2646 if (GetServerStateParser().LastCommandSuccessful() && selectIssued &&
2647 (m_imapAction != nsIImapUrl::nsImapSelectFolder) &&
2648 (m_imapAction != nsIImapUrl::nsImapLiteSelectFolder)) {
2649 // error on the side of caution, if the fe event fails to set
2650 // uidStruct->returnValidity, then assume that UIDVALIDITY did not roll.
2651 // This is a common case event for attachments that are fetched within a
2652 // browser context.
2653 if (!DeathSignalReceived())
2654 uidValidityOk = m_uidValidity == kUidUnknown ||
2655 m_uidValidity == GetServerStateParser().FolderUID();
2656 }
2657
2658 if (!uidValidityOk)
2659 Log("ProcessSelectedStateURL", nullptr, "uid validity not ok");
2660 if (GetServerStateParser().LastCommandSuccessful() &&
2661 !DeathSignalReceived() &&
2662 (uidValidityOk || m_imapAction == nsIImapUrl::nsImapDeleteAllMsgs)) {
2663 if (GetServerStateParser().CurrentFolderReadOnly()) {
2664 Log("ProcessSelectedStateURL", nullptr, "current folder read only");
2665 if (m_imapAction == nsIImapUrl::nsImapAddMsgFlags ||
2666 m_imapAction == nsIImapUrl::nsImapSubtractMsgFlags) {
2667 bool canChangeFlag = false;
2668 if (GetServerStateParser().ServerHasACLCapability() &&
2669 m_imapMailFolderSink) {
2670 uint32_t aclFlags = 0;
2671
2672 if (NS_SUCCEEDED(m_imapMailFolderSink->GetAclFlags(&aclFlags)) &&
2673 aclFlags != 0) // make sure we have some acl flags
2674 canChangeFlag = ((msgFlags & kImapMsgSeenFlag) &&
2675 (aclFlags & IMAP_ACL_STORE_SEEN_FLAG));
2676 } else
2677 canChangeFlag = (GetServerStateParser().SettablePermanentFlags() &
2678 msgFlags) == msgFlags;
2679 if (!canChangeFlag) return;
2680 }
2681 if (m_imapAction == nsIImapUrl::nsImapExpungeFolder ||
2682 m_imapAction == nsIImapUrl::nsImapDeleteMsg ||
2683 m_imapAction == nsIImapUrl::nsImapDeleteAllMsgs)
2684 return;
2685 }
2686 switch (m_imapAction) {
2687 case nsIImapUrl::nsImapLiteSelectFolder:
2688 if (GetServerStateParser().LastCommandSuccessful() &&
2689 m_imapMailFolderSink && !moreHeadersToDownload) {
2690 m_imapMailFolderSink->SetUidValidity(
2691 GetServerStateParser().FolderUID());
2692 ProcessMailboxUpdate(false); // handle uidvalidity change
2693 }
2694 break;
2695 case nsIImapUrl::nsImapSaveMessageToDisk:
2696 case nsIImapUrl::nsImapMsgFetch:
2697 case nsIImapUrl::nsImapMsgFetchPeek:
2698 case nsIImapUrl::nsImapMsgDownloadForOffline:
2699 case nsIImapUrl::nsImapMsgPreview: {
2700 nsCString messageIdString;
2701 m_runningUrl->GetListOfMessageIds(messageIdString);
2702 // we don't want to send the flags back in a group
2703 if (HandlingMultipleMessages(messageIdString) ||
2704 m_imapAction == nsIImapUrl::nsImapMsgDownloadForOffline ||
2705 m_imapAction == nsIImapUrl::nsImapMsgPreview) {
2706 // multiple messages, fetch them all
2707 SetProgressString(IMAP_MESSAGES_STRING_INDEX);
2708
2709 m_progressCurrentNumber[m_stringIndex] = 0;
2710 m_progressExpectedNumber =
2711 CountMessagesInIdString(messageIdString.get());
2712
2713 // we need to set this so we'll get the msg from the memory cache.
2714 if (m_imapAction == nsIImapUrl::nsImapMsgFetchPeek) {
2715 MOZ_LOG(IMAPCache, LogLevel::Debug,
2716 ("ProcessSelectedStateURL(): Set "
2717 "IMAP_CONTENT_NOT_MODIFIED; action nsImapMsgFetchPeek"));
2718 SetContentModified(IMAP_CONTENT_NOT_MODIFIED);
2719 }
2720
2721 FetchMessage(messageIdString,
2722 (m_imapAction == nsIImapUrl::nsImapMsgPreview)
2723 ? kBodyStart
2724 : kEveryThingRFC822Peek);
2725 if (m_imapAction == nsIImapUrl::nsImapMsgPreview)
2726 HeaderFetchCompleted();
2727 SetProgressString(IMAP_EMPTY_STRING_INDEX);
2728 } else {
2729 // A single message ID
2730 nsIMAPeFetchFields whatToFetch = kEveryThingRFC822;
2731 if (m_imapAction == nsIImapUrl::nsImapMsgFetchPeek)
2732 whatToFetch = kEveryThingRFC822Peek;
2733
2734 // First, let's see if we're requesting a specific MIME part
2735 char* imappart = nullptr;
2736 m_runningUrl->GetImapPartToFetch(&imappart);
2737 if (imappart) {
2738 if (bMessageIdsAreUids) {
2739 // We actually want a specific MIME part of the message.
2740 // The Body Shell will generate it, even though we haven't
2741 // downloaded it yet.
2742
2743 IMAP_ContentModifiedType modType =
2744 GetShowAttachmentsInline()
2745 ? IMAP_CONTENT_MODIFIED_VIEW_INLINE
2746 : IMAP_CONTENT_MODIFIED_VIEW_AS_LINKS;
2747
2748 nsCString messageIdValString(messageIdString);
2749 messageIdValString.AppendInt(m_uidValidity);
2750 RefPtr<nsImapBodyShell> foundShell;
2751 res = m_hostSessionList->FindShellInCacheForHost(
2752 GetImapServerKey(),
2753 GetServerStateParser().GetSelectedMailboxName(),
2754 messageIdValString.get(), modType,
2755 getter_AddRefs(foundShell));
2756 if (!foundShell) {
2757 // The shell wasn't in the cache. Deal with this case later.
2758 Log("SHELL", NULL, "Loading part, shell not found in cache!");
2759 // MOZ_LOG(IMAP, out, ("BODYSHELL: Loading part, shell not
2760 // found in cache!"));
2761 // The parser will extract the part number from the current
2762 // URL.
2763 MOZ_LOG(IMAPCache, LogLevel::Debug,
2764 ("ProcessSelectedStateURL(): Set "
2765 "IMAP_CONTENT_MODIFIED_*; fetch bodystructure, one "
2766 "mime part"));
2767 SetContentModified(modType);
2768 Bodystructure(messageIdString, bMessageIdsAreUids);
2769 } else {
2770 Log("SHELL", NULL, "Loading Part, using cached shell.");
2771 // MOZ_LOG(IMAP, out, ("BODYSHELL: Loading part, using cached
2772 // shell."));
2773 SetContentModified(modType);
2774 MOZ_LOG(IMAPCache, LogLevel::Debug,
2775 ("ProcessSelectedStateURL(): Set "
2776 "IMAP_CONTENT_MODIFIED_*: fetch one mime part"));
2777 foundShell->SetConnection(this);
2778 GetServerStateParser().UseCachedShell(foundShell);
2779 // Set the current uid in server state parser (in case it was
2780 // used for new mail msgs earlier).
2781 GetServerStateParser().SetCurrentResponseUID(
2782 strtoul(messageIdString.get(), nullptr, 10));
2783 foundShell->Generate(imappart);
2784 GetServerStateParser().UseCachedShell(NULL);
2785 }
2786 } else {
2787 // Message IDs are not UIDs.
2788 NS_ASSERTION(false, "message ids aren't uids");
2789 }
2790 PR_Free(imappart);
2791 } else {
2792 // downloading a single message: try to do it by bodystructure,
2793 // and/or do it by chunks
2794 uint32_t messageSize = GetMessageSize(messageIdString);
2795 // We need to check the format_out bits to see if we are allowed
2796 // to leave out parts, or if we are required to get the whole
2797 // thing. Some instances where we are allowed to do it by parts:
2798 // when viewing a message, replying to a message, or viewing its
2799 // source Some times when we're NOT allowed: when forwarding a
2800 // message, saving it, moving it, etc. need to set a flag in the
2801 // url, I guess, equiv to allow_content_changed.
2802 bool allowedToBreakApart =
2803 true; // (ce && !DeathSignalReceived()) ?
2804 // ce->URL_s->allow_content_change : false;
2805 bool mimePartSelectorDetected;
2806 bool urlOKToFetchByParts = false;
2807 m_runningUrl->GetMimePartSelectorDetected(
2808 &mimePartSelectorDetected);
2809 m_runningUrl->GetFetchPartsOnDemand(&urlOKToFetchByParts);
2810
2811 {
2812 nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl =
2813 do_QueryInterface(m_runningUrl);
2814 if (mailnewsurl) {
2815 MOZ_LOG(IMAP, LogLevel::Debug,
2816 ("SHELL: URL %s, OKToFetchByParts %d, "
2817 "allowedToBreakApart %d, ShouldFetchAllParts %d",
2818 mailnewsurl->GetSpecOrDefault().get(),
2819 urlOKToFetchByParts, allowedToBreakApart,
2820 GetShouldFetchAllParts()));
2821 }
2822 }
2823
2824 if (urlOKToFetchByParts &&
2825 allowedToBreakApart &&
2826 !GetShouldFetchAllParts() &&
2827 GetServerStateParser().ServerHasIMAP4Rev1Capability() /* &&
2828 !mimePartSelectorDetected */) // if a ?part=, don't do BS.
2829 {
2830 // OK, we're doing bodystructure
2831
2832 // Before fetching the bodystructure, let's check our body shell
2833 // cache to see if we already have it around.
2834 RefPtr<nsImapBodyShell> foundShell;
2835 IMAP_ContentModifiedType modType =
2836 GetShowAttachmentsInline()
2837 ? IMAP_CONTENT_MODIFIED_VIEW_INLINE
2838 : IMAP_CONTENT_MODIFIED_VIEW_AS_LINKS;
2839
2840 bool wasStoringMsgOffline;
2841 m_runningUrl->GetStoreResultsOffline(&wasStoringMsgOffline);
2842 m_runningUrl->SetStoreOfflineOnFallback(wasStoringMsgOffline);
2843 m_runningUrl->SetStoreResultsOffline(false);
2844 if (MOZ_LOG_TEST(IMAPCache, LogLevel::Debug)) {
2845 // For logging the running URL.
2846 nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl =
2847 do_QueryInterface(m_runningUrl);
2848 if (mailnewsurl) {
2849 // clang-format off
2850 MOZ_LOG(IMAPCache, LogLevel::Debug,
2851 ("ProcessSelectedStateURL(): Fetch parts; URL = |%s|",
2852 mailnewsurl->GetSpecOrDefault().get()));
2853 // clang-format on
2854 }
2855 }
2856 MOZ_LOG(IMAPCache, LogLevel::Debug,
2857 ("ProcessSelectedStateURL(): Set "
2858 "IMAP_CONTENT_MODIFIED_*; fetch message by parts"));
2859 SetContentModified(
2860 modType); // This will be looked at by the cache
2861 if (bMessageIdsAreUids) {
2862 nsCString messageIdValString(messageIdString);
2863 messageIdValString.AppendInt(m_uidValidity);
2864 res = m_hostSessionList->FindShellInCacheForHost(
2865 GetImapServerKey(),
2866 GetServerStateParser().GetSelectedMailboxName(),
2867 messageIdValString.get(), modType,
2868 getter_AddRefs(foundShell));
2869 if (foundShell) {
2870 Log("SHELL", NULL, "Loading message, using cached shell.");
2871 foundShell->SetConnection(this);
2872 GetServerStateParser().UseCachedShell(foundShell);
2873 // Set the current uid in server state parser (in case it
2874 // was used for new mail msgs earlier).
2875 GetServerStateParser().SetCurrentResponseUID(
2876 strtoul(messageIdString.get(), nullptr, 10));
2877 foundShell->Generate(NULL);
2878 MOZ_LOG(IMAPCache, LogLevel::Debug,
2879 ("ProcessSelectedStateURL(): Generated parts fetch "
2880 "from cached shell)"));
2881 GetServerStateParser().UseCachedShell(NULL);
2882 }
2883 }
2884
2885 if (!foundShell) {
2886 MOZ_LOG(IMAPCache, LogLevel::Debug,
2887 ("ProcessSelectedStateURL(): Fetch bodystructure and "
2888 "generated parts fetch"));
2889 Bodystructure(messageIdString, bMessageIdsAreUids);
2890 }
2891 } else {
2892 // Not doing bodystructure. Fetch the whole thing, and try to
2893 // do it in chunks.
2894 if (MOZ_LOG_TEST(IMAPCache, LogLevel::Debug)) {
2895 // For logging the running URL.
2896 nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl =
2897 do_QueryInterface(m_runningUrl);
2898 if (mailnewsurl) {
2899 MOZ_LOG(IMAPCache, LogLevel::Debug,
2900 ("ProcessSelectedStateURL(): Fetch entire message; "
2901 "URL = |%s|",
2902 mailnewsurl->GetSpecOrDefault().get()));
2903 }
2904 }
2905 // clang-format off
2906 MOZ_LOG(IMAPCache, LogLevel::Debug,
2907 ("ProcessSelectedStateURL(): Set IMAP_CONTENT_NOT "
2908 "MODIFIED; fetch entire message with FetchTryChunking()"));
2909 // clang-format on
2910 SetContentModified(IMAP_CONTENT_NOT_MODIFIED);
2911 FetchTryChunking(messageIdString, whatToFetch,
2912 bMessageIdsAreUids, NULL, messageSize, true);
2913 }
2914 }
2915 if (GetServerStateParser().LastCommandSuccessful() &&
2916 m_imapAction != nsIImapUrl::nsImapMsgPreview &&
2917 m_imapAction != nsIImapUrl::nsImapMsgFetchPeek) {
2918 uint32_t uid = strtoul(messageIdString.get(), nullptr, 10);
2919 int32_t index;
2920 bool foundIt;
2921 imapMessageFlagsType flags =
2922 m_flagState->GetMessageFlagsFromUID(uid, &foundIt, &index);
2923 if (foundIt) {
2924 flags |= kImapMsgSeenFlag;
2925 m_flagState->SetMessageFlags(index, flags);
2926 }
2927 }
2928 }
2929 } break;
2930 case nsIImapUrl::nsImapExpungeFolder:
2931 Expunge();
2932 // note fall through to next cases.
2933 [[fallthrough]];
2934 case nsIImapUrl::nsImapSelectFolder:
2935 case nsIImapUrl::nsImapSelectNoopFolder:
2936 if (!moreHeadersToDownload) ProcessMailboxUpdate(true);
2937 break;
2938 case nsIImapUrl::nsImapMsgHeader: {
2939 nsCString messageIds;
2940 m_runningUrl->GetListOfMessageIds(messageIds);
2941
2942 FetchMessage(messageIds, kHeadersRFC822andUid);
2943 // if we explicitly ask for headers, as opposed to getting them as a
2944 // result of selecting the folder, or biff, send the
2945 // headerFetchCompleted notification to flush out the header cache.
2946 HeaderFetchCompleted();
2947 } break;
2948 case nsIImapUrl::nsImapSearch: {
2949 nsAutoCString searchCriteriaString;
2950 m_runningUrl->CreateSearchCriteriaString(
2951 getter_Copies(searchCriteriaString));
2952 Search(searchCriteriaString.get(), bMessageIdsAreUids);
2953 // drop the results on the floor for now
2954 } break;
2955 case nsIImapUrl::nsImapUserDefinedMsgCommand: {
2956 nsCString messageIdString;
2957 nsCString command;
2958
2959 m_runningUrl->GetCommand(command);
2960 m_runningUrl->GetListOfMessageIds(messageIdString);
2961 IssueUserDefinedMsgCommand(command.get(), messageIdString.get());
2962 } break;
2963 case nsIImapUrl::nsImapUserDefinedFetchAttribute: {
2964 nsCString messageIdString;
2965 nsCString attribute;
2966
2967 m_runningUrl->GetCustomAttributeToFetch(attribute);
2968 m_runningUrl->GetListOfMessageIds(messageIdString);
2969 FetchMsgAttribute(messageIdString, attribute);
2970 } break;
2971 case nsIImapUrl::nsImapMsgStoreCustomKeywords: {
2972 // If the server doesn't support user defined flags, don't try to
2973 // define/set new ones. But if this is an attempt by TB to set or
2974 // reset flags "Junk" or "NonJunk", change "Junk" or "NonJunk" to
2975 // "$Junk" or "$NotJunk" respectively and store the modified flag
2976 // name if the server doesn't support storing user defined flags
2977 // and the server does allow storing the almost-standard flag names
2978 // "$Junk" and "$NotJunk". Yahoo imap server is an example of this.
2979 uint16_t userFlags = 0;
2980 GetSupportedUserFlags(&userFlags);
2981 bool userDefinedSettable = userFlags & kImapMsgSupportUserFlag;
2982 bool stdJunkOk = GetServerStateParser().IsStdJunkNotJunkUseOk();
2983
2984 nsCString messageIdString;
2985 nsCString addFlags;
2986 nsCString subtractFlags;
2987
2988 m_runningUrl->GetListOfMessageIds(messageIdString);
2989 m_runningUrl->GetCustomAddFlags(addFlags);
2990 m_runningUrl->GetCustomSubtractFlags(subtractFlags);
2991 if (!addFlags.IsEmpty()) {
2992 if (!userDefinedSettable) {
2993 if (stdJunkOk) {
2994 if (addFlags.EqualsIgnoreCase("junk"))
2995 addFlags = "$Junk";
2996 else if (addFlags.EqualsIgnoreCase("nonjunk"))
2997 addFlags = "$NotJunk";
2998 else
2999 break;
3000 } else
3001 break;
3002 }
3003 nsAutoCString storeString("+FLAGS (");
3004 storeString.Append(addFlags);
3005 storeString.Append(')');
3006 Store(messageIdString, storeString.get(), true);
3007 }
3008 if (!subtractFlags.IsEmpty()) {
3009 if (!userDefinedSettable) {
3010 if (stdJunkOk) {
3011 if (subtractFlags.EqualsIgnoreCase("junk"))
3012 subtractFlags = "$Junk";
3013 else if (subtractFlags.EqualsIgnoreCase("nonjunk"))
3014 subtractFlags = "$NotJunk";
3015 else
3016 break;
3017 } else
3018 break;
3019 }
3020 nsAutoCString storeString("-FLAGS (");
3021 storeString.Append(subtractFlags);
3022 storeString.Append(')');
3023 Store(messageIdString, storeString.get(), true);
3024 }
3025 } break;
3026 case nsIImapUrl::nsImapDeleteMsg: {
3027 nsCString messageIdString;
3028 m_runningUrl->GetListOfMessageIds(messageIdString);
3029
3030 ProgressEventFunctionUsingName(
3031 HandlingMultipleMessages(messageIdString)
3032 ? "imapDeletingMessages"
3033 : "imapDeletingMessage");
3034
3035 Store(messageIdString, "+FLAGS (\\Deleted)", bMessageIdsAreUids);
3036
3037 if (GetServerStateParser().LastCommandSuccessful()) {
3038 nsCString canonicalName;
3039 const char* selectedMailboxName =
3040 GetServerStateParser().GetSelectedMailboxName();
3041 if (selectedMailboxName) {
3042 m_runningUrl->AllocateCanonicalPath(
3043 selectedMailboxName, kOnlineHierarchySeparatorUnknown,
3044 getter_Copies(canonicalName));
3045 }
3046
3047 if (m_imapMessageSink)
3048 m_imapMessageSink->NotifyMessageDeleted(
3049 canonicalName.get(), false, messageIdString.get());
3050 // notice we don't wait for this to finish...
3051 } else
3052 HandleMemoryFailure();
3053 } break;
3054 case nsIImapUrl::nsImapDeleteFolderAndMsgs:
3055 DeleteFolderAndMsgs(mailboxName.get());
3056 break;
3057 case nsIImapUrl::nsImapDeleteAllMsgs: {
3058 uint32_t numberOfMessages = GetServerStateParser().NumberOfMessages();
3059 if (numberOfMessages) {
3060 Store("1:*"_ns, "+FLAGS.SILENT (\\Deleted)",
3061 false); // use sequence #'s
3062
3063 if (GetServerStateParser().LastCommandSuccessful())
3064 Expunge(); // expunge messages with deleted flag
3065 if (GetServerStateParser().LastCommandSuccessful()) {
3066 nsCString canonicalName;
3067 const char* selectedMailboxName =
3068 GetServerStateParser().GetSelectedMailboxName();
3069 if (selectedMailboxName) {
3070 m_runningUrl->AllocateCanonicalPath(
3071 selectedMailboxName, kOnlineHierarchySeparatorUnknown,
3072 getter_Copies(canonicalName));
3073 }
3074
3075 if (m_imapMessageSink)
3076 m_imapMessageSink->NotifyMessageDeleted(canonicalName.get(),
3077 true, nullptr);
3078 }
3079 }
3080 bool deleteSelf = false;
3081 DeleteSubFolders(mailboxName.get(), deleteSelf); // don't delete self
3082 } break;
3083 case nsIImapUrl::nsImapAppendDraftFromFile: {
3084 OnAppendMsgFromFile();
3085 } break;
3086 case nsIImapUrl::nsImapAddMsgFlags: {
3087 nsCString messageIdString;
3088 m_runningUrl->GetListOfMessageIds(messageIdString);
3089
3090 ProcessStoreFlags(messageIdString, bMessageIdsAreUids, msgFlags,
3091 true);
3092 } break;
3093 case nsIImapUrl::nsImapSubtractMsgFlags: {
3094 nsCString messageIdString;
3095 m_runningUrl->GetListOfMessageIds(messageIdString);
3096
3097 ProcessStoreFlags(messageIdString, bMessageIdsAreUids, msgFlags,
3098 false);
3099 } break;
3100 case nsIImapUrl::nsImapSetMsgFlags: {
3101 nsCString messageIdString;
3102 m_runningUrl->GetListOfMessageIds(messageIdString);
3103
3104 ProcessStoreFlags(messageIdString, bMessageIdsAreUids, msgFlags,
3105 true);
3106 ProcessStoreFlags(messageIdString, bMessageIdsAreUids, ~msgFlags,
3107 false);
3108 } break;
3109 case nsIImapUrl::nsImapBiff:
3110 PeriodicBiff();
3111 break;
3112 case nsIImapUrl::nsImapOnlineCopy:
3113 case nsIImapUrl::nsImapOnlineMove: {
3114 nsCString messageIdString;
3115 m_runningUrl->GetListOfMessageIds(messageIdString);
3116 char* destinationMailbox =
3117 OnCreateServerDestinationFolderPathString();
3118
3119 if (destinationMailbox) {
3120 if (m_imapAction == nsIImapUrl::nsImapOnlineMove) {
3121 if (HandlingMultipleMessages(messageIdString))
3122 ProgressEventFunctionUsingNameWithString("imapMovingMessages",
3123 destinationMailbox);
3124 else
3125 ProgressEventFunctionUsingNameWithString("imapMovingMessage",
3126 destinationMailbox);
3127 } else {
3128 if (HandlingMultipleMessages(messageIdString))
3129 ProgressEventFunctionUsingNameWithString("imapCopyingMessages",
3130 destinationMailbox);
3131 else
3132 ProgressEventFunctionUsingNameWithString("imapCopyingMessage",
3133 destinationMailbox);
3134 }
3135 Copy(messageIdString.get(), destinationMailbox, bMessageIdsAreUids);
3136 PR_FREEIF(destinationMailbox);
3137 ImapOnlineCopyState copyState;
3138 if (DeathSignalReceived())
3139 copyState = ImapOnlineCopyStateType::kInterruptedState;
3140 else
3141 copyState = GetServerStateParser().LastCommandSuccessful()
3142 ? (ImapOnlineCopyState)
3143 ImapOnlineCopyStateType::kSuccessfulCopy
3144 : (ImapOnlineCopyState)
3145 ImapOnlineCopyStateType::kFailedCopy;
3146 if (m_imapMailFolderSink)
3147 m_imapMailFolderSink->OnlineCopyCompleted(this, copyState);
3148 // Don't mark message 'Deleted' for AOL servers or standard imap
3149 // servers that support MOVE since we already issued an 'xaol-move'
3150 // or 'move' command.
3151 if (GetServerStateParser().LastCommandSuccessful() &&
3152 (m_imapAction == nsIImapUrl::nsImapOnlineMove) &&
3153 !(GetServerStateParser().ServerIsAOLServer() ||
3154 GetServerStateParser().GetCapabilityFlag() &
3155 kHasMoveCapability)) {
3156 // Simulate MOVE for servers that don't support MOVE: do
3157 // COPY-DELETE-EXPUNGE.
3158 Store(messageIdString, "+FLAGS (\\Deleted \\Seen)",
3159 bMessageIdsAreUids);
3160 bool storeSuccessful =
3161 GetServerStateParser().LastCommandSuccessful();
3162 if (storeSuccessful) {
3163 if (gExpungeAfterDelete) {
3164 // This will expunge all emails marked as deleted in mailbox,
3165 // not just the ones marked as deleted above.
3166 Expunge();
3167 } else {
3168 // Check if UIDPLUS capable so we can just expunge emails we
3169 // just copied and marked as deleted. This prevents expunging
3170 // emails that other clients may have marked as deleted in the
3171 // mailbox and don't want them to disappear. Only do
3172 // UidExpunge() when user selected delete method is "Move it
3173 // to this folder" or "Remove it immediately", not when the
3174 // delete method is "Just mark it as deleted".
3175 if (!GetShowDeletedMessages() &&
3176 (GetServerStateParser().GetCapabilityFlag() &
3177 kUidplusCapability)) {
3178 UidExpunge(messageIdString);
3179 }
3180 }
3181 }
3182 if (m_imapMailFolderSink) {
3183 copyState = storeSuccessful
3184 ? (ImapOnlineCopyState)
3185 ImapOnlineCopyStateType::kSuccessfulDelete
3186 : (ImapOnlineCopyState)
3187 ImapOnlineCopyStateType::kFailedDelete;
3188 m_imapMailFolderSink->OnlineCopyCompleted(this, copyState);
3189 }
3190 }
3191 } else
3192 HandleMemoryFailure();
3193 } break;
3194 case nsIImapUrl::nsImapOnlineToOfflineCopy:
3195 case nsIImapUrl::nsImapOnlineToOfflineMove: {
3196 nsCString messageIdString;
3197 nsresult rv = m_runningUrl->GetListOfMessageIds(messageIdString);
3198 if (NS_SUCCEEDED(rv)) {
3199 SetProgressString(IMAP_MESSAGES_STRING_INDEX);
3200 m_progressCurrentNumber[m_stringIndex] = 0;
3201 m_progressExpectedNumber =
3202 CountMessagesInIdString(messageIdString.get());
3203
3204 FetchMessage(messageIdString, kEveryThingRFC822Peek);
3205
3206 SetProgressString(IMAP_EMPTY_STRING_INDEX);
3207 if (m_imapMailFolderSink) {
3208 ImapOnlineCopyState copyStatus;
3209 copyStatus = GetServerStateParser().LastCommandSuccessful()
3210 ? ImapOnlineCopyStateType::kSuccessfulCopy
3211 : ImapOnlineCopyStateType::kFailedCopy;
3212
3213 m_imapMailFolderSink->OnlineCopyCompleted(this, copyStatus);
3214 if (GetServerStateParser().LastCommandSuccessful() &&
3215 (m_imapAction == nsIImapUrl::nsImapOnlineToOfflineMove)) {
3216 Store(messageIdString, "+FLAGS (\\Deleted \\Seen)",
3217 bMessageIdsAreUids);
3218 if (GetServerStateParser().LastCommandSuccessful()) {
3219 copyStatus = ImapOnlineCopyStateType::kSuccessfulDelete;
3220 if (gExpungeAfterDelete) Expunge();
3221 } else
3222 copyStatus = ImapOnlineCopyStateType::kFailedDelete;
3223
3224 m_imapMailFolderSink->OnlineCopyCompleted(this, copyStatus);
3225 }
3226 }
3227 } else
3228 HandleMemoryFailure();
3229 } break;
3230 default:
3231 if (GetServerStateParser().LastCommandSuccessful() && !uidValidityOk)
3232 ProcessMailboxUpdate(false); // handle uidvalidity change
3233 break;
3234 }
3235 }
3236 } else if (!DeathSignalReceived())
3237 HandleMemoryFailure();
3238 }
3239
BeginMessageDownLoad(uint32_t total_message_size,const char * content_type)3240 nsresult nsImapProtocol::BeginMessageDownLoad(
3241 uint32_t total_message_size, // for user, headers and body
3242 const char* content_type) {
3243 nsresult rv = NS_OK;
3244 char* sizeString = PR_smprintf("OPEN Size: %ld", total_message_size);
3245 Log("STREAM", sizeString, "Begin Message Download Stream");
3246 PR_Free(sizeString);
3247 // start counting how many bytes we see in this message after all
3248 // transformations
3249 m_bytesToChannel = 0;
3250
3251 if (content_type) {
3252 m_fromHeaderSeen = false;
3253 if (GetServerStateParser().GetDownloadingHeaders()) {
3254 // if we get multiple calls to BeginMessageDownload w/o intervening
3255 // calls to NormalEndMessageDownload or Abort, then we're just
3256 // going to fake a NormalMessageEndDownload. This will most likely
3257 // cause an empty header to get written to the db, and the user
3258 // will have to delete the empty header themselves, which
3259 // should remove the message from the server as well.
3260 if (m_curHdrInfo) NormalMessageEndDownload();
3261 if (!m_curHdrInfo) m_curHdrInfo = m_hdrDownloadCache->StartNewHdr();
3262 if (m_curHdrInfo) m_curHdrInfo->SetMsgSize(total_message_size);
3263 return NS_OK;
3264 }
3265 // if we have a mock channel, that means we have a channel listener who
3266 // wants the message. So set up a pipe. We'll write the message into one end
3267 // of the pipe and they will read it out of the other end.
3268 if (m_channelListener) {
3269 // create a pipe to pump the message into...the output will go to whoever
3270 // is consuming the message display
3271 // we create an "infinite" pipe in case we get extremely long lines from
3272 // the imap server, and the consumer is waiting for a whole line
3273 nsCOMPtr<nsIPipe> pipe = do_CreateInstance("@mozilla.org/pipe;1");
3274 rv = pipe->Init(false, false, 4096, PR_UINT32_MAX);
3275 NS_ENSURE_SUCCESS(rv, rv);
3276
3277 // These always succeed because the pipe is initialized above.
3278 MOZ_ALWAYS_SUCCEEDS(
3279 pipe->GetInputStream(getter_AddRefs(m_channelInputStream)));
3280 MOZ_ALWAYS_SUCCEEDS(
3281 pipe->GetOutputStream(getter_AddRefs(m_channelOutputStream)));
3282 }
3283 // else, if we are saving the message to disk!
3284 else if (m_imapMessageSink /* && m_imapAction == nsIImapUrl::nsImapSaveMessageToDisk */)
3285 {
3286 // we get here when download the inbox for offline use
3287 nsCOMPtr<nsIFile> file;
3288 bool addDummyEnvelope = true;
3289 nsCOMPtr<nsIMsgMessageUrl> msgurl = do_QueryInterface(m_runningUrl);
3290 msgurl->GetMessageFile(getter_AddRefs(file));
3291 msgurl->GetAddDummyEnvelope(&addDummyEnvelope);
3292 if (file)
3293 rv = m_imapMessageSink->SetupMsgWriteStream(file, addDummyEnvelope);
3294 }
3295 if (m_imapMailFolderSink && m_runningUrl) {
3296 nsCOMPtr<nsISupports> copyState;
3297 if (m_runningUrl) {
3298 m_runningUrl->GetCopyState(getter_AddRefs(copyState));
3299 if (copyState) // only need this notification during copy
3300 {
3301 nsCOMPtr<nsIMsgMailNewsUrl> mailurl = do_QueryInterface(m_runningUrl);
3302 m_imapMailFolderSink->StartMessage(mailurl);
3303 }
3304 }
3305 }
3306
3307 } else
3308 HandleMemoryFailure();
3309 return rv;
3310 }
3311
GetShouldDownloadAllHeaders(bool * aResult)3312 void nsImapProtocol::GetShouldDownloadAllHeaders(bool* aResult) {
3313 if (m_imapMailFolderSink)
3314 m_imapMailFolderSink->GetShouldDownloadAllHeaders(aResult);
3315 }
3316
GetArbitraryHeadersToDownload(nsCString & aResult)3317 void nsImapProtocol::GetArbitraryHeadersToDownload(nsCString& aResult) {
3318 if (m_imapServerSink) m_imapServerSink->GetArbitraryHeaders(aResult);
3319 }
3320
AdjustChunkSize()3321 void nsImapProtocol::AdjustChunkSize() {
3322 int32_t deltaInSeconds;
3323
3324 m_endTime = PR_Now();
3325 PRTime2Seconds(m_endTime - m_startTime, &deltaInSeconds);
3326 m_trackingTime = false;
3327 if (deltaInSeconds < 0) return; // bogus for some reason
3328
3329 if (deltaInSeconds <= m_tooFastTime && m_curFetchSize >= m_chunkSize) {
3330 m_chunkSize += m_chunkAddSize;
3331 m_chunkThreshold = m_chunkSize + (m_chunkSize / 2);
3332 // we used to have a max for the chunk size - I don't think that's needed.
3333 } else if (deltaInSeconds <= m_idealTime)
3334 return;
3335 else {
3336 if (m_chunkSize > m_chunkStartSize)
3337 m_chunkSize = m_chunkStartSize;
3338 else if (m_chunkSize > (m_chunkAddSize * 2))
3339 m_chunkSize -= m_chunkAddSize;
3340 m_chunkThreshold = m_chunkSize + (m_chunkSize / 2);
3341 }
3342 // remember these new values globally so new connections
3343 // can take advantage of them.
3344 if (gChunkSize != m_chunkSize) {
3345 // will cause chunk size pref to be written in CloseStream.
3346 gChunkSizeDirty = true;
3347 gChunkSize = m_chunkSize;
3348 gChunkThreshold = m_chunkThreshold;
3349 }
3350 }
3351
3352 // authenticated state commands
3353
3354 // escape any backslashes or quotes. Backslashes are used a lot with our NT
3355 // server
CreateEscapedMailboxName(const char * rawName,nsCString & escapedName)3356 void nsImapProtocol::CreateEscapedMailboxName(const char* rawName,
3357 nsCString& escapedName) {
3358 escapedName.Assign(rawName);
3359
3360 for (int32_t strIndex = 0; *rawName; strIndex++) {
3361 char currentChar = *rawName++;
3362 if ((currentChar == '\\') || (currentChar == '\"'))
3363 escapedName.Insert('\\', strIndex++);
3364 }
3365 }
SelectMailbox(const char * mailboxName)3366 void nsImapProtocol::SelectMailbox(const char* mailboxName) {
3367 ProgressEventFunctionUsingNameWithString("imapStatusSelectingMailbox",
3368 mailboxName);
3369 IncrementCommandTagNumber();
3370
3371 m_closeNeededBeforeSelect = false; // initial value
3372 GetServerStateParser().ResetFlagInfo();
3373 nsCString escapedName;
3374 CreateEscapedMailboxName(mailboxName, escapedName);
3375 nsCString commandBuffer(GetServerCommandTag());
3376 commandBuffer.AppendLiteral(" select \"");
3377 commandBuffer.Append(escapedName.get());
3378 commandBuffer.Append('"');
3379 if (UseCondStore()) commandBuffer.AppendLiteral(" (CONDSTORE)");
3380 commandBuffer.Append(CRLF);
3381
3382 nsresult res;
3383 res = SendData(commandBuffer.get());
3384 if (NS_FAILED(res)) return;
3385 ParseIMAPandCheckForNewMail();
3386
3387 int32_t numOfMessagesInFlagState = 0;
3388 nsImapAction imapAction;
3389 m_flagState->GetNumberOfMessages(&numOfMessagesInFlagState);
3390 res = m_runningUrl->GetImapAction(&imapAction);
3391 // if we've selected a mailbox, and we're not going to do an update because of
3392 // the url type, but don't have the flags, go get them!
3393 if (GetServerStateParser().LastCommandSuccessful() && NS_SUCCEEDED(res) &&
3394 imapAction != nsIImapUrl::nsImapSelectFolder &&
3395 imapAction != nsIImapUrl::nsImapExpungeFolder &&
3396 imapAction != nsIImapUrl::nsImapLiteSelectFolder &&
3397 imapAction != nsIImapUrl::nsImapDeleteAllMsgs &&
3398 ((GetServerStateParser().NumberOfMessages() !=
3399 numOfMessagesInFlagState) &&
3400 (numOfMessagesInFlagState == 0))) {
3401 ProcessMailboxUpdate(false);
3402 }
3403 }
3404
3405 // Please call only with a single message ID
Bodystructure(const nsCString & messageId,bool idIsUid)3406 void nsImapProtocol::Bodystructure(const nsCString& messageId, bool idIsUid) {
3407 IncrementCommandTagNumber();
3408
3409 nsCString commandString(GetServerCommandTag());
3410 if (idIsUid) commandString.AppendLiteral(" UID");
3411 commandString.AppendLiteral(" fetch ");
3412
3413 commandString.Append(messageId);
3414 commandString.AppendLiteral(" (BODYSTRUCTURE)" CRLF);
3415
3416 nsresult rv = SendData(commandString.get());
3417 if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail(commandString.get());
3418 }
3419
PipelinedFetchMessageParts(const char * uid,const nsTArray<nsIMAPMessagePartID> & parts)3420 void nsImapProtocol::PipelinedFetchMessageParts(
3421 const char* uid, const nsTArray<nsIMAPMessagePartID>& parts) {
3422 // assumes no chunking
3423
3424 // build up a string to fetch
3425 nsCString stringToFetch, what;
3426 uint32_t currentPartNum = 0;
3427 while ((parts.Length() > currentPartNum) && !DeathSignalReceived()) {
3428 nsIMAPMessagePartID currentPart = parts[currentPartNum];
3429 // Do things here depending on the type of message part
3430 // Append it to the fetch string
3431 if (currentPartNum > 0) stringToFetch.Append(' ');
3432
3433 switch (currentPart.GetFields()) {
3434 case kMIMEHeader:
3435 what = "BODY.PEEK[";
3436 what.Append(currentPart.GetPartNumberString());
3437 what.AppendLiteral(".MIME]");
3438 stringToFetch.Append(what);
3439 break;
3440 case kRFC822HeadersOnly:
3441 if (currentPart.GetPartNumberString()) {
3442 what = "BODY.PEEK[";
3443 what.Append(currentPart.GetPartNumberString());
3444 what.AppendLiteral(".HEADER]");
3445 stringToFetch.Append(what);
3446 } else {
3447 // headers for the top-level message
3448 stringToFetch.AppendLiteral("BODY.PEEK[HEADER]");
3449 }
3450 break;
3451 default:
3452 NS_ASSERTION(
3453 false,
3454 "we should only be pipelining MIME headers and Message headers");
3455 break;
3456 }
3457 currentPartNum++;
3458 }
3459
3460 // Run the single, pipelined fetch command
3461 if ((parts.Length() > 0) && !DeathSignalReceived() &&
3462 !GetPseudoInterrupted() && stringToFetch.get()) {
3463 IncrementCommandTagNumber();
3464
3465 nsCString commandString(GetServerCommandTag());
3466 commandString.AppendLiteral(" UID fetch ");
3467 commandString.Append(uid, 10);
3468 commandString.AppendLiteral(" (");
3469 commandString.Append(stringToFetch);
3470 commandString.AppendLiteral(")" CRLF);
3471 nsresult rv = SendData(commandString.get());
3472 if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail(commandString.get());
3473 }
3474 }
3475
FetchMsgAttribute(const nsCString & messageIds,const nsCString & attribute)3476 void nsImapProtocol::FetchMsgAttribute(const nsCString& messageIds,
3477 const nsCString& attribute) {
3478 IncrementCommandTagNumber();
3479
3480 nsAutoCString commandString(GetServerCommandTag());
3481 commandString.AppendLiteral(" UID fetch ");
3482 commandString.Append(messageIds);
3483 commandString.AppendLiteral(" (");
3484 commandString.Append(attribute);
3485 commandString.AppendLiteral(")" CRLF);
3486 nsresult rv = SendData(commandString.get());
3487
3488 if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail(commandString.get());
3489 GetServerStateParser().SetFetchingFlags(false);
3490 // Always clear this flag after every fetch.
3491 m_fetchingWholeMessage = false;
3492 }
3493
3494 // this routine is used to fetch a message or messages, or headers for a
3495 // message...
3496
FallbackToFetchWholeMsg(const nsCString & messageId,uint32_t messageSize)3497 void nsImapProtocol::FallbackToFetchWholeMsg(const nsCString& messageId,
3498 uint32_t messageSize) {
3499 if (m_imapMessageSink && m_runningUrl) {
3500 bool shouldStoreMsgOffline;
3501 m_runningUrl->GetStoreOfflineOnFallback(&shouldStoreMsgOffline);
3502 m_runningUrl->SetStoreResultsOffline(shouldStoreMsgOffline);
3503 }
3504 FetchTryChunking(messageId,
3505 m_imapAction == nsIImapUrl::nsImapMsgFetchPeek
3506 ? kEveryThingRFC822Peek
3507 : kEveryThingRFC822,
3508 true, nullptr, messageSize, true);
3509 }
3510
FetchMessage(const nsCString & messageIds,nsIMAPeFetchFields whatToFetch,const char * fetchModifier,uint32_t startByte,uint32_t numBytes,char * part)3511 void nsImapProtocol::FetchMessage(const nsCString& messageIds,
3512 nsIMAPeFetchFields whatToFetch,
3513 const char* fetchModifier, uint32_t startByte,
3514 uint32_t numBytes, char* part) {
3515 IncrementCommandTagNumber();
3516
3517 nsCString commandString;
3518 commandString = "%s UID fetch";
3519
3520 switch (whatToFetch) {
3521 case kEveryThingRFC822:
3522 m_flagChangeCount++;
3523 m_fetchingWholeMessage = true;
3524 if (m_trackingTime) AdjustChunkSize(); // we started another segment
3525 m_startTime = PR_Now(); // save start of download time
3526 m_trackingTime = true;
3527 MOZ_LOG(IMAP, LogLevel::Debug,
3528 ("FetchMessage everything: curFetchSize %u numBytes %u",
3529 m_curFetchSize, numBytes));
3530 if (numBytes > 0) m_curFetchSize = numBytes;
3531
3532 if (GetServerStateParser().ServerHasIMAP4Rev1Capability()) {
3533 if (GetServerStateParser().GetCapabilityFlag() & kHasXSenderCapability)
3534 commandString.AppendLiteral(" %s (XSENDER UID RFC822.SIZE BODY[]");
3535 else
3536 commandString.AppendLiteral(" %s (UID RFC822.SIZE BODY[]");
3537 } else {
3538 if (GetServerStateParser().GetCapabilityFlag() & kHasXSenderCapability)
3539 commandString.AppendLiteral(" %s (XSENDER UID RFC822.SIZE RFC822");
3540 else
3541 commandString.AppendLiteral(" %s (UID RFC822.SIZE RFC822");
3542 }
3543 if (numBytes > 0) {
3544 // if we are retrieving chunks
3545 char* byterangeString = PR_smprintf("<%ld.%ld>", startByte, numBytes);
3546 if (byterangeString) {
3547 commandString.Append(byterangeString);
3548 PR_Free(byterangeString);
3549 }
3550 }
3551 commandString.Append(')');
3552
3553 break;
3554
3555 case kEveryThingRFC822Peek: {
3556 MOZ_LOG(IMAP, LogLevel::Debug,
3557 ("FetchMessage peek: curFetchSize %u numBytes %u", m_curFetchSize,
3558 numBytes));
3559 if (numBytes > 0) m_curFetchSize = numBytes;
3560 const char* formatString = "";
3561 eIMAPCapabilityFlags server_capabilityFlags =
3562 GetServerStateParser().GetCapabilityFlag();
3563
3564 m_fetchingWholeMessage = true;
3565 if (server_capabilityFlags & kIMAP4rev1Capability) {
3566 // use body[].peek since rfc822.peek is not in IMAP4rev1
3567 if (server_capabilityFlags & kHasXSenderCapability)
3568 formatString = " %s (XSENDER UID RFC822.SIZE BODY.PEEK[]";
3569 else
3570 formatString = " %s (UID RFC822.SIZE BODY.PEEK[]";
3571 } else {
3572 if (server_capabilityFlags & kHasXSenderCapability)
3573 formatString = " %s (XSENDER UID RFC822.SIZE RFC822.peek";
3574 else
3575 formatString = " %s (UID RFC822.SIZE RFC822.peek";
3576 }
3577
3578 commandString.Append(formatString);
3579 if (numBytes > 0) {
3580 // if we are retrieving chunks
3581 char* byterangeString = PR_smprintf("<%ld.%ld>", startByte, numBytes);
3582 if (byterangeString) {
3583 commandString.Append(byterangeString);
3584 PR_Free(byterangeString);
3585 }
3586 }
3587 commandString.Append(')');
3588 } break;
3589 case kHeadersRFC822andUid:
3590 if (GetServerStateParser().ServerHasIMAP4Rev1Capability()) {
3591 eIMAPCapabilityFlags server_capabilityFlags =
3592 GetServerStateParser().GetCapabilityFlag();
3593 bool aolImapServer =
3594 ((server_capabilityFlags & kAOLImapCapability) != 0);
3595 bool downloadAllHeaders = false;
3596 // checks if we're filtering on "any header" or running a spam filter
3597 // requiring all headers
3598 GetShouldDownloadAllHeaders(&downloadAllHeaders);
3599
3600 if (!downloadAllHeaders) // if it's ok -- no filters on any header,
3601 // etc.
3602 {
3603 char* headersToDL = nullptr;
3604 char* what = nullptr;
3605 const char* dbHeaders =
3606 (gUseEnvelopeCmd) ? IMAP_DB_HEADERS : IMAP_ENV_AND_DB_HEADERS;
3607 nsCString arbitraryHeaders;
3608 GetArbitraryHeadersToDownload(arbitraryHeaders);
3609 for (uint32_t i = 0; i < mCustomDBHeaders.Length(); i++) {
3610 if (arbitraryHeaders.Find(mCustomDBHeaders[i],
3611 /* ignoreCase = */ true) == kNotFound) {
3612 if (!arbitraryHeaders.IsEmpty()) arbitraryHeaders.Append(' ');
3613 arbitraryHeaders.Append(mCustomDBHeaders[i]);
3614 }
3615 }
3616 for (uint32_t i = 0; i < mCustomHeaders.Length(); i++) {
3617 if (arbitraryHeaders.Find(mCustomHeaders[i],
3618 /* ignoreCase = */ true) == kNotFound) {
3619 if (!arbitraryHeaders.IsEmpty()) arbitraryHeaders.Append(' ');
3620 arbitraryHeaders.Append(mCustomHeaders[i]);
3621 }
3622 }
3623 if (arbitraryHeaders.IsEmpty())
3624 headersToDL = strdup(dbHeaders);
3625 else
3626 headersToDL =
3627 PR_smprintf("%s %s", dbHeaders, arbitraryHeaders.get());
3628
3629 if (gUseEnvelopeCmd)
3630 what = PR_smprintf(" ENVELOPE BODY.PEEK[HEADER.FIELDS (%s)])",
3631 headersToDL);
3632 else
3633 what = PR_smprintf(" BODY.PEEK[HEADER.FIELDS (%s)])", headersToDL);
3634 free(headersToDL);
3635 if (what) {
3636 commandString.AppendLiteral(" %s (UID ");
3637 if (m_isGmailServer)
3638 commandString.AppendLiteral("X-GM-MSGID X-GM-THRID X-GM-LABELS ");
3639 if (aolImapServer)
3640 commandString.AppendLiteral(" XAOL.SIZE");
3641 else
3642 commandString.AppendLiteral("RFC822.SIZE");
3643 commandString.AppendLiteral(" FLAGS");
3644 commandString.Append(what);
3645 PR_Free(what);
3646 } else {
3647 commandString.AppendLiteral(
3648 " %s (UID RFC822.SIZE BODY.PEEK[HEADER] FLAGS)");
3649 }
3650 } else
3651 commandString.AppendLiteral(
3652 " %s (UID RFC822.SIZE BODY.PEEK[HEADER] FLAGS)");
3653 } else
3654 commandString.AppendLiteral(
3655 " %s (UID RFC822.SIZE RFC822.HEADER FLAGS)");
3656 break;
3657 case kUid:
3658 commandString.AppendLiteral(" %s (UID)");
3659 break;
3660 case kFlags:
3661 GetServerStateParser().SetFetchingFlags(true);
3662 commandString.AppendLiteral(" %s (FLAGS)");
3663 break;
3664 case kRFC822Size:
3665 commandString.AppendLiteral(" %s (RFC822.SIZE)");
3666 break;
3667 case kBodyStart: {
3668 int32_t numBytesToFetch;
3669 m_runningUrl->GetNumBytesToFetch(&numBytesToFetch);
3670
3671 commandString.AppendLiteral(
3672 " %s (UID BODY.PEEK[HEADER.FIELDS (Content-Type "
3673 "Content-Transfer-Encoding)] BODY.PEEK[TEXT]<0.");
3674 commandString.AppendInt(numBytesToFetch);
3675 commandString.AppendLiteral(">)");
3676 } break;
3677 case kRFC822HeadersOnly:
3678 if (GetServerStateParser().ServerHasIMAP4Rev1Capability()) {
3679 if (part) {
3680 commandString.AppendLiteral(" %s (BODY[");
3681 char* what = PR_smprintf("%s.HEADER])", part);
3682 if (what) {
3683 commandString.Append(what);
3684 PR_Free(what);
3685 } else
3686 HandleMemoryFailure();
3687 } else {
3688 // headers for the top-level message
3689 commandString.AppendLiteral(" %s (BODY[HEADER])");
3690 }
3691 } else
3692 commandString.AppendLiteral(" %s (RFC822.HEADER)");
3693 break;
3694 case kMIMEPart:
3695 commandString.AppendLiteral(" %s (BODY.PEEK[%s]");
3696 if (numBytes > 0) {
3697 // if we are retrieving chunks
3698 char* byterangeString = PR_smprintf("<%ld.%ld>", startByte, numBytes);
3699 if (byterangeString) {
3700 commandString.Append(byterangeString);
3701 PR_Free(byterangeString);
3702 }
3703 }
3704 commandString.Append(')');
3705 break;
3706 case kMIMEHeader:
3707 commandString.AppendLiteral(" %s (BODY[%s.MIME])");
3708 break;
3709 }
3710
3711 if (fetchModifier) commandString.Append(fetchModifier);
3712
3713 commandString.Append(CRLF);
3714
3715 // since messageIds can be infinitely long, use a dynamic buffer rather than
3716 // the fixed one
3717 const char* commandTag = GetServerCommandTag();
3718 int protocolStringSize = commandString.Length() + messageIds.Length() +
3719 PL_strlen(commandTag) + 1 +
3720 (part ? PL_strlen(part) : 0);
3721 char* protocolString = (char*)PR_CALLOC(protocolStringSize);
3722
3723 if (protocolString) {
3724 char* cCommandStr = ToNewCString(commandString);
3725 if ((whatToFetch == kMIMEPart) || (whatToFetch == kMIMEHeader)) {
3726 PR_snprintf(protocolString, // string to create
3727 protocolStringSize, // max size
3728 cCommandStr, // format string
3729 commandTag, // command tag
3730 messageIds.get(), part);
3731 } else {
3732 PR_snprintf(protocolString, // string to create
3733 protocolStringSize, // max size
3734 cCommandStr, // format string
3735 commandTag, // command tag
3736 messageIds.get());
3737 }
3738
3739 nsresult rv = SendData(protocolString);
3740
3741 free(cCommandStr);
3742 if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail(protocolString);
3743 PR_Free(protocolString);
3744 GetServerStateParser().SetFetchingFlags(false);
3745 // Always clear this flag after every fetch.
3746 m_fetchingWholeMessage = false;
3747 if (GetServerStateParser().LastCommandSuccessful() && CheckNeeded())
3748 Check();
3749 } else
3750 HandleMemoryFailure();
3751 }
3752
FetchTryChunking(const nsCString & messageIds,nsIMAPeFetchFields whatToFetch,bool idIsUid,char * part,uint32_t downloadSize,bool tryChunking)3753 void nsImapProtocol::FetchTryChunking(const nsCString& messageIds,
3754 nsIMAPeFetchFields whatToFetch,
3755 bool idIsUid, char* part,
3756 uint32_t downloadSize, bool tryChunking) {
3757 GetServerStateParser().SetTotalDownloadSize(downloadSize);
3758 MOZ_LOG(IMAP, LogLevel::Debug,
3759 ("FetchTryChunking: curFetchSize %u", downloadSize));
3760 m_curFetchSize = downloadSize; // we'll change this if chunking.
3761 if (m_fetchByChunks && tryChunking &&
3762 GetServerStateParser().ServerHasIMAP4Rev1Capability() &&
3763 (downloadSize > (uint32_t)m_chunkThreshold)) {
3764 uint32_t startByte = 0;
3765 m_curFetchSize = m_chunkSize;
3766 GetServerStateParser().ClearLastFetchChunkReceived();
3767 while (!DeathSignalReceived() && !GetPseudoInterrupted() &&
3768 !GetServerStateParser().GetLastFetchChunkReceived() &&
3769 GetServerStateParser().ContinueParse()) {
3770 // This chunk is a fetch of m_chunkSize bytes. But m_chunkSize can be
3771 // changed inside FetchMessage(). Save the original value of m_chunkSize
3772 // to set the correct offset (startByte) for the next chunk.
3773 int32_t bytesFetched = m_chunkSize;
3774 FetchMessage(messageIds, whatToFetch, nullptr, startByte, bytesFetched,
3775 part);
3776 startByte += bytesFetched;
3777 }
3778
3779 // Only abort the stream if this is a normal message download
3780 // Otherwise, let the body shell abort the stream.
3781 if ((whatToFetch == kEveryThingRFC822) &&
3782 ((startByte > 0 && (startByte < downloadSize) &&
3783 (DeathSignalReceived() || GetPseudoInterrupted())) ||
3784 !GetServerStateParser().ContinueParse())) {
3785 AbortMessageDownLoad();
3786 PseudoInterrupt(false);
3787 }
3788 } else {
3789 // small message, or (we're not chunking and not doing bodystructure),
3790 // or the server is not rev1.
3791 // Just fetch the whole thing.
3792 FetchMessage(messageIds, whatToFetch, nullptr, 0, 0, part);
3793 }
3794 }
3795
PipelinedFetchMessageParts(nsCString & uid,const nsTArray<nsIMAPMessagePartID> & parts)3796 void nsImapProtocol::PipelinedFetchMessageParts(
3797 nsCString& uid, const nsTArray<nsIMAPMessagePartID>& parts) {
3798 // assumes no chunking
3799
3800 // build up a string to fetch
3801 nsCString stringToFetch;
3802 nsCString what;
3803
3804 uint32_t currentPartNum = 0;
3805 while ((parts.Length() > currentPartNum) && !DeathSignalReceived()) {
3806 nsIMAPMessagePartID currentPart = parts[currentPartNum];
3807 // Do things here depending on the type of message part
3808 // Append it to the fetch string
3809 if (currentPartNum > 0) stringToFetch += " ";
3810
3811 switch (currentPart.GetFields()) {
3812 case kMIMEHeader:
3813 what = "BODY.PEEK[";
3814 what += currentPart.GetPartNumberString();
3815 what += ".MIME]";
3816 stringToFetch += what;
3817 break;
3818 case kRFC822HeadersOnly:
3819 if (currentPart.GetPartNumberString()) {
3820 what = "BODY.PEEK[";
3821 what += currentPart.GetPartNumberString();
3822 what += ".HEADER]";
3823 stringToFetch += what;
3824 } else {
3825 // headers for the top-level message
3826 stringToFetch += "BODY.PEEK[HEADER]";
3827 }
3828 break;
3829 default:
3830 NS_ASSERTION(
3831 false,
3832 "we should only be pipelining MIME headers and Message headers");
3833 break;
3834 }
3835 currentPartNum++;
3836 }
3837
3838 // Run the single, pipelined fetch command
3839 if ((parts.Length() > 0) && !DeathSignalReceived() &&
3840 !GetPseudoInterrupted() && stringToFetch.get()) {
3841 IncrementCommandTagNumber();
3842
3843 char* commandString =
3844 PR_smprintf("%s UID fetch %s (%s)%s", GetServerCommandTag(), uid.get(),
3845 stringToFetch.get(), CRLF);
3846
3847 if (commandString) {
3848 nsresult rv = SendData(commandString);
3849 if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail(commandString);
3850 PR_Free(commandString);
3851 } else
3852 HandleMemoryFailure();
3853 }
3854 }
3855
PostLineDownLoadEvent(const char * line,uint32_t uidOfMessage)3856 void nsImapProtocol::PostLineDownLoadEvent(const char* line,
3857 uint32_t uidOfMessage) {
3858 if (!GetServerStateParser().GetDownloadingHeaders()) {
3859 uint32_t byteCount = PL_strlen(line);
3860 bool echoLineToMessageSink = false;
3861 // if we have a channel listener, then just spool the message
3862 // directly to the listener
3863 if (m_channelListener) {
3864 uint32_t count = 0;
3865 if (m_channelOutputStream) {
3866 nsresult rv = m_channelOutputStream->Write(line, byteCount, &count);
3867 NS_ASSERTION(count == byteCount,
3868 "IMAP channel pipe couldn't buffer entire write");
3869 if (NS_SUCCEEDED(rv)) {
3870 m_channelListener->OnDataAvailable(m_mockChannel,
3871 m_channelInputStream, 0, count);
3872 }
3873 // else some sort of explosion?
3874 }
3875 }
3876 if (m_runningUrl)
3877 m_runningUrl->GetStoreResultsOffline(&echoLineToMessageSink);
3878
3879 m_bytesToChannel += byteCount;
3880 if (m_imapMessageSink && line && echoLineToMessageSink &&
3881 !GetPseudoInterrupted())
3882 m_imapMessageSink->ParseAdoptedMsgLine(line, uidOfMessage, m_runningUrl);
3883 }
3884 // ***** We need to handle the pseudo interrupt here *****
3885 }
3886
3887 // Handle a line seen by the parser.
3888 // * The argument |lineCopy| must be nullptr or should contain the same string
3889 // as |line|. |lineCopy| will be modified.
3890 // * A line may be passed by parts, e.g., "part1 part2\r\n" may be passed as
3891 // HandleMessageDownLoadLine("part 1 ", 1);
3892 // HandleMessageDownLoadLine("part 2\r\n", 0);
3893 // However, it is assumed that a CRLF or a CRCRLF is never split (i.e., this
3894 // is ensured *before* invoking this method).
HandleMessageDownLoadLine(const char * line,bool isPartialLine,char * lineCopy)3895 void nsImapProtocol::HandleMessageDownLoadLine(const char* line,
3896 bool isPartialLine,
3897 char* lineCopy) {
3898 NS_ENSURE_TRUE_VOID(line);
3899 NS_ASSERTION(lineCopy == nullptr || !PL_strcmp(line, lineCopy),
3900 "line and lineCopy must contain the same string");
3901 const char* messageLine = line;
3902 uint32_t lineLength = strlen(messageLine);
3903 const char* cEndOfLine = messageLine + lineLength;
3904 char* localMessageLine = nullptr;
3905
3906 // If we obtain a partial line (due to fetching by chunks), we do not
3907 // add/modify the end-of-line terminator.
3908 if (!isPartialLine) {
3909 // Change this line to native line termination, duplicate if necessary.
3910 // Do not assume that the line really ends in CRLF
3911 // to start with, even though it is supposed to be RFC822
3912
3913 // normalize line endings to CRLF unless we are saving the message to disk
3914 bool canonicalLineEnding = true;
3915 nsCOMPtr<nsIMsgMessageUrl> msgUrl = do_QueryInterface(m_runningUrl);
3916
3917 if (m_imapAction == nsIImapUrl::nsImapSaveMessageToDisk && msgUrl)
3918 msgUrl->GetCanonicalLineEnding(&canonicalLineEnding);
3919
3920 NS_ASSERTION(MSG_LINEBREAK_LEN == 1 || (MSG_LINEBREAK_LEN == 2 &&
3921 !PL_strcmp(CRLF, MSG_LINEBREAK)),
3922 "violated assumptions on MSG_LINEBREAK");
3923 if (MSG_LINEBREAK_LEN == 1 && !canonicalLineEnding) {
3924 bool lineEndsWithCRorLF =
3925 lineLength >= 1 && (cEndOfLine[-1] == '\r' || cEndOfLine[-1] == '\n');
3926 char* endOfLine;
3927 if (lineCopy && lineEndsWithCRorLF) // true for most lines
3928 {
3929 endOfLine = lineCopy + lineLength;
3930 messageLine = lineCopy;
3931 } else {
3932 // leave enough room for one more char, MSG_LINEBREAK[0]
3933 localMessageLine = (char*)PR_MALLOC(lineLength + 2);
3934 if (!localMessageLine) // memory failure
3935 return;
3936 PL_strcpy(localMessageLine, line);
3937 endOfLine = localMessageLine + lineLength;
3938 messageLine = localMessageLine;
3939 }
3940
3941 if (lineLength >= 2 && endOfLine[-2] == '\r' && endOfLine[-1] == '\n') {
3942 if (lineLength >= 3 && endOfLine[-3] == '\r') // CRCRLF
3943 {
3944 endOfLine--;
3945 lineLength--;
3946 }
3947 /* CRLF -> CR or LF */
3948 endOfLine[-2] = MSG_LINEBREAK[0];
3949 endOfLine[-1] = '\0';
3950 lineLength--;
3951 } else if (lineLength >= 1 &&
3952 ((endOfLine[-1] == '\r') || (endOfLine[-1] == '\n'))) {
3953 /* CR -> LF or LF -> CR */
3954 endOfLine[-1] = MSG_LINEBREAK[0];
3955 } else // no eol characters at all
3956 {
3957 endOfLine[0] = MSG_LINEBREAK[0]; // CR or LF
3958 endOfLine[1] = '\0';
3959 lineLength++;
3960 }
3961 } else // enforce canonical CRLF linebreaks
3962 {
3963 if (lineLength == 0 || (lineLength == 1 && cEndOfLine[-1] == '\n')) {
3964 messageLine = CRLF;
3965 lineLength = 2;
3966 } else if (cEndOfLine[-1] != '\n' || cEndOfLine[-2] != '\r' ||
3967 (lineLength >= 3 && cEndOfLine[-3] == '\r')) {
3968 // The line does not end in CRLF (or it ends in CRCRLF).
3969 // Copy line and leave enough room for two more chars (CR and LF).
3970 localMessageLine = (char*)PR_MALLOC(lineLength + 3);
3971 if (!localMessageLine) // memory failure
3972 return;
3973 PL_strcpy(localMessageLine, line);
3974 char* endOfLine = localMessageLine + lineLength;
3975 messageLine = localMessageLine;
3976
3977 if (lineLength >= 3 && endOfLine[-1] == '\n' && endOfLine[-2] == '\r') {
3978 // CRCRLF -> CRLF
3979 endOfLine[-2] = '\n';
3980 endOfLine[-1] = '\0';
3981 lineLength--;
3982 } else if ((endOfLine[-1] == '\r') || (endOfLine[-1] == '\n')) {
3983 // LF -> CRLF or CR -> CRLF
3984 endOfLine[-1] = '\r';
3985 endOfLine[0] = '\n';
3986 endOfLine[1] = '\0';
3987 lineLength++;
3988 } else // no eol characters at all
3989 {
3990 endOfLine[0] = '\r';
3991 endOfLine[1] = '\n';
3992 endOfLine[2] = '\0';
3993 lineLength += 2;
3994 }
3995 }
3996 }
3997 }
3998 NS_ASSERTION(lineLength == PL_strlen(messageLine), "lineLength not accurate");
3999
4000 // check if sender obtained via XSENDER server extension matches "From:" field
4001 const char* xSenderInfo = GetServerStateParser().GetXSenderInfo();
4002 if (xSenderInfo && *xSenderInfo && !m_fromHeaderSeen) {
4003 if (!PL_strncmp("From: ", messageLine, 6)) {
4004 m_fromHeaderSeen = true;
4005 if (PL_strstr(messageLine, xSenderInfo) != NULL)
4006 // Adding a X-Mozilla-Status line here is not very elegant but it
4007 // works. Another X-Mozilla-Status line is added to the message when
4008 // downloading to a local folder; this new line will also contain the
4009 // 'authed' flag we are adding here. (If the message is again
4010 // uploaded to the server, this flag is lost.)
4011 // 0x0200 == nsMsgMessageFlags::SenderAuthed
4012 HandleMessageDownLoadLine("X-Mozilla-Status: 0200\r\n", false);
4013 GetServerStateParser().FreeXSenderInfo();
4014 }
4015 }
4016
4017 if (GetServerStateParser().GetDownloadingHeaders()) {
4018 if (!m_curHdrInfo)
4019 BeginMessageDownLoad(GetServerStateParser().SizeOfMostRecentMessage(),
4020 MESSAGE_RFC822);
4021 if (m_curHdrInfo)
4022 m_curHdrInfo->CacheLine(messageLine,
4023 GetServerStateParser().CurrentResponseUID());
4024 PR_Free(localMessageLine);
4025 return;
4026 }
4027 // if this line is for a different message, or the incoming line is too big
4028 if (((m_downloadLineCache->CurrentUID() !=
4029 GetServerStateParser().CurrentResponseUID()) &&
4030 !m_downloadLineCache->CacheEmpty()) ||
4031 (m_downloadLineCache->SpaceAvailable() < lineLength + 1))
4032 FlushDownloadCache();
4033
4034 // so now the cache is flushed, but this string might still be to big
4035 if (m_downloadLineCache->SpaceAvailable() < lineLength + 1)
4036 PostLineDownLoadEvent(messageLine,
4037 GetServerStateParser().CurrentResponseUID());
4038 else
4039 m_downloadLineCache->CacheLine(messageLine,
4040 GetServerStateParser().CurrentResponseUID());
4041
4042 PR_Free(localMessageLine);
4043 }
4044
FlushDownloadCache()4045 void nsImapProtocol::FlushDownloadCache() {
4046 if (!m_downloadLineCache->CacheEmpty()) {
4047 msg_line_info* downloadLine = m_downloadLineCache->GetCurrentLineInfo();
4048 PostLineDownLoadEvent(downloadLine->adoptedMessageLine,
4049 downloadLine->uidOfMessage);
4050 m_downloadLineCache->ResetCache();
4051 }
4052 }
4053
NormalMessageEndDownload()4054 void nsImapProtocol::NormalMessageEndDownload() {
4055 Log("STREAM", "CLOSE", "Normal Message End Download Stream");
4056
4057 if (m_trackingTime) AdjustChunkSize();
4058 if (m_imapMailFolderSink && m_curHdrInfo &&
4059 GetServerStateParser().GetDownloadingHeaders()) {
4060 m_curHdrInfo->SetMsgSize(GetServerStateParser().SizeOfMostRecentMessage());
4061 m_curHdrInfo->SetMsgUid(GetServerStateParser().CurrentResponseUID());
4062 m_hdrDownloadCache->FinishCurrentHdr();
4063 int32_t numHdrsCached;
4064 m_hdrDownloadCache->GetNumHeaders(&numHdrsCached);
4065 if (numHdrsCached == kNumHdrsToXfer) {
4066 m_imapMailFolderSink->ParseMsgHdrs(this, m_hdrDownloadCache);
4067 m_hdrDownloadCache->ResetAll();
4068 }
4069 }
4070 FlushDownloadCache();
4071
4072 if (!GetServerStateParser().GetDownloadingHeaders()) {
4073 int32_t updatedMessageSize = -1;
4074 if (m_fetchingWholeMessage) {
4075 updatedMessageSize = m_bytesToChannel;
4076 if (m_bytesToChannel !=
4077 GetServerStateParser().SizeOfMostRecentMessage()) {
4078 MOZ_LOG(IMAP, LogLevel::Debug,
4079 ("STREAM:CLOSE Server's RFC822.SIZE %u, actual size %u",
4080 GetServerStateParser().SizeOfMostRecentMessage(),
4081 m_bytesToChannel));
4082 }
4083 }
4084 // need to know if we're downloading for display or not. We'll use action ==
4085 // nsImapMsgFetch for now
4086 nsImapAction imapAction =
4087 nsIImapUrl::nsImapSelectFolder; // just set it to some legal value
4088 if (m_runningUrl) m_runningUrl->GetImapAction(&imapAction);
4089
4090 if (m_imapMessageSink)
4091 m_imapMessageSink->NormalEndMsgWriteStream(
4092 m_downloadLineCache->CurrentUID(),
4093 imapAction == nsIImapUrl::nsImapMsgFetch, m_runningUrl,
4094 updatedMessageSize);
4095
4096 if (m_runningUrl && m_imapMailFolderSink) {
4097 nsCOMPtr<nsISupports> copyState;
4098 m_runningUrl->GetCopyState(getter_AddRefs(copyState));
4099 if (copyState) // only need this notification during copy
4100 {
4101 nsCOMPtr<nsIMsgMailNewsUrl> mailUrl(do_QueryInterface(m_runningUrl));
4102 m_imapMailFolderSink->EndMessage(mailUrl,
4103 m_downloadLineCache->CurrentUID());
4104 }
4105 }
4106 }
4107 m_curHdrInfo = nullptr;
4108 }
4109
AbortMessageDownLoad()4110 void nsImapProtocol::AbortMessageDownLoad() {
4111 Log("STREAM", "CLOSE", "Abort Message Download Stream");
4112
4113 if (m_trackingTime) AdjustChunkSize();
4114 FlushDownloadCache();
4115 if (GetServerStateParser().GetDownloadingHeaders()) {
4116 if (m_imapMailFolderSink)
4117 m_imapMailFolderSink->AbortHeaderParseStream(this);
4118 } else if (m_imapMessageSink)
4119 m_imapMessageSink->AbortMsgWriteStream();
4120
4121 m_curHdrInfo = nullptr;
4122 }
4123
ProcessMailboxUpdate(bool handlePossibleUndo)4124 void nsImapProtocol::ProcessMailboxUpdate(bool handlePossibleUndo) {
4125 if (DeathSignalReceived()) return;
4126
4127 // Update quota information
4128 char* boxName;
4129 GetSelectedMailboxName(&boxName);
4130 GetQuotaDataIfSupported(boxName);
4131 PR_Free(boxName);
4132
4133 // fetch the flags and uids of all existing messages or new ones
4134 if (!DeathSignalReceived() && GetServerStateParser().NumberOfMessages()) {
4135 if (handlePossibleUndo) {
4136 // undo any delete flags we may have asked to
4137 nsCString undoIdsStr;
4138 nsAutoCString undoIds;
4139
4140 GetCurrentUrl()->GetListOfMessageIds(undoIdsStr);
4141 undoIds.Assign(undoIdsStr);
4142 if (!undoIds.IsEmpty()) {
4143 char firstChar = (char)undoIds.CharAt(0);
4144 undoIds.Cut(0, 1); // remove first character
4145 // if this string started with a '-', then this is an undo of a delete
4146 // if its a '+' its a redo
4147 if (firstChar == '-')
4148 Store(undoIds, "-FLAGS (\\Deleted)",
4149 true); // most servers will fail silently on a failure, deal
4150 // with it?
4151 else if (firstChar == '+')
4152 Store(undoIds, "+FLAGS (\\Deleted)",
4153 true); // most servers will fail silently on a failure, deal
4154 // with it?
4155 else
4156 NS_ASSERTION(false, "bogus undo Id's");
4157 }
4158 }
4159
4160 // make the parser record these flags
4161 nsCString fetchStr;
4162 int32_t added = 0, deleted = 0;
4163
4164 m_flagState->GetNumberOfMessages(&added);
4165 deleted = m_flagState->NumberOfDeletedMessages();
4166 bool flagStateEmpty = !added;
4167 bool useCS = UseCondStore();
4168
4169 // Figure out if we need to do a full sync (UID Fetch Flags 1:*),
4170 // a partial sync using CHANGEDSINCE, or a sync from the previous
4171 // highwater mark.
4172
4173 // If the folder doesn't know about the highest uid, or the flag state
4174 // is empty, and we're not using CondStore, we definitely need a full sync.
4175 //
4176 // Print to log items affecting needFullFolderSync:
4177 MOZ_LOG(IMAP_CS, LogLevel::Debug,
4178 ("Do full sync?: mFolderHighestUID=%" PRIu32 ", added=%" PRId32
4179 ", useCS=%s",
4180 mFolderHighestUID, added, useCS ? "true" : "false"));
4181 bool needFullFolderSync = !mFolderHighestUID || (flagStateEmpty && !useCS);
4182 bool needFolderSync = false;
4183
4184 if (!needFullFolderSync) {
4185 // Figure out if we need to do a non-highwater mark sync.
4186 // Set needFolderSync true when at least 1 of these 3 cases is true:
4187 // 1. Have no uids in flag array or all flag elements are marked deleted
4188 // AND not using CONDSTORE.
4189 // 2. Have no uids in flag array or all flag elements are marked deleted
4190 // AND using "just mark as deleted" and EXISTS response count differs from
4191 // stored message count for folder.
4192 // 3. Using CONDSTORE and highest MODSEQ response is not equal to stored
4193 // mod seq for folder.
4194
4195 // Print to log items affecting needFolderSync:
4196 // clang-format off
4197 MOZ_LOG(IMAP_CS, LogLevel::Debug,
4198 ("1. Do a sync?: added=%" PRId32 ", deleted=%" PRId32 ", useCS=%s",
4199 added, deleted, useCS ? "true" : "false"));
4200 MOZ_LOG(IMAP_CS, LogLevel::Debug,
4201 ("2. Do a sync?: ShowDeletedMsgs=%s, exists=%" PRId32
4202 ", mFolderTotalMsgCount=%" PRId32,
4203 GetShowDeletedMessages() ? "true" : "false",
4204 GetServerStateParser().NumberOfMessages(), mFolderTotalMsgCount));
4205 // clang-format on
4206 MOZ_LOG(IMAP_CS, LogLevel::Debug,
4207 ("3. Do a sync?: fHighestModSeq=%" PRIu64
4208 ", mFolderLastModSeq=%" PRIu64,
4209 GetServerStateParser().fHighestModSeq, mFolderLastModSeq));
4210
4211 needFolderSync =
4212 ((flagStateEmpty || added == deleted) &&
4213 (!useCS || (GetShowDeletedMessages() &&
4214 GetServerStateParser().NumberOfMessages() !=
4215 mFolderTotalMsgCount))) ||
4216 (useCS && GetServerStateParser().fHighestModSeq != mFolderLastModSeq);
4217 }
4218 MOZ_LOG(IMAP_CS, LogLevel::Debug,
4219 ("needFullFolderSync=%s, needFolderSync=%s",
4220 needFullFolderSync ? "true" : "false",
4221 needFolderSync ? "true" : "false"));
4222
4223 if (needFullFolderSync || needFolderSync) {
4224 nsCString idsToFetch("1:*");
4225 char fetchModifier[40] = "";
4226 if (!needFullFolderSync && !GetShowDeletedMessages() && useCS) {
4227 m_flagState->StartCapture();
4228 MOZ_LOG(IMAP_CS, LogLevel::Debug,
4229 ("Doing UID fetch 1:* (CHANGEDSINCE %" PRIu64 ")",
4230 mFolderLastModSeq));
4231 PR_snprintf(fetchModifier, sizeof(fetchModifier),
4232 " (CHANGEDSINCE %llu)", mFolderLastModSeq);
4233 } else
4234 m_flagState->SetPartialUIDFetch(false);
4235
4236 FetchMessage(idsToFetch, kFlags, fetchModifier);
4237 // lets see if we should expunge during a full sync of flags.
4238 if (GetServerStateParser().LastCommandSuccessful()) {
4239 // if we did a CHANGEDSINCE fetch, do a sanity check on the msg counts
4240 // to see if some other client may have done an expunge.
4241 if (m_flagState->GetPartialUIDFetch()) {
4242 uint32_t numExists = GetServerStateParser().NumberOfMessages();
4243 uint32_t numPrevExists = mFolderTotalMsgCount;
4244
4245 if (MOZ_LOG_TEST(IMAP_CS, LogLevel::Debug)) {
4246 int32_t addedByPartialFetch;
4247 m_flagState->GetNumberOfMessages(&addedByPartialFetch);
4248 MOZ_LOG(IMAP_CS, LogLevel::Debug,
4249 ("Sanity, deleted=%" PRId32 ", numPrevExists=%" PRIu32
4250 ", numExists=%" PRIu32,
4251 m_flagState->NumberOfDeletedMessages(), numPrevExists,
4252 numExists));
4253 // clang-format off
4254 MOZ_LOG(IMAP_CS, LogLevel::Debug,
4255 ("Sanity, addedByPartialFetch=%" PRId32, addedByPartialFetch));
4256 // clang-format on
4257 }
4258
4259 // Determine the number of new UIDs just fetched that are greater than
4260 // the saved highest UID for the folder. numToCheck will contain the
4261 // number of UIDs just fetched and, of course, not all are new.
4262 uint32_t numNewUIDs = 0;
4263 uint32_t numToCheck = m_flagState->GetNumAdded();
4264 bool flagChangeDetected = false;
4265 MOZ_LOG(IMAP_CS, LogLevel::Debug,
4266 ("numToCheck=%" PRIu32, numToCheck));
4267 if (numToCheck && mFolderHighestUID) {
4268 uint32_t uid;
4269 int32_t topIndex;
4270 m_flagState->GetNumberOfMessages(&topIndex);
4271 do {
4272 topIndex--;
4273 m_flagState->GetUidOfMessage(topIndex, &uid);
4274 if (uid && uid != nsMsgKey_None) {
4275 if (uid > mFolderHighestUID) {
4276 numNewUIDs++;
4277 MOZ_LOG(IMAP_CS, LogLevel::Debug,
4278 ("numNewUIDs=%" PRIu32 ", Added new UID=%" PRIu32,
4279 numNewUIDs, uid));
4280 numToCheck--;
4281 } else {
4282 // Just a flag change on an existing UID. No more new UIDs
4283 // will be found. This does not detect an expunged message.
4284 flagChangeDetected = true;
4285 MOZ_LOG(IMAP_CS, LogLevel::Debug,
4286 ("Not new uid=%" PRIu32, uid));
4287 break;
4288 }
4289 }
4290 } while (numToCheck);
4291 }
4292
4293 // Another client expunged at least one message if the number of new
4294 // UIDs is not equal to the observed change in the number of messages
4295 // existing in the folder.
4296 bool expungeHappened = numNewUIDs != (numExists - numPrevExists);
4297 if (expungeHappened) {
4298 // Sanity check failed - need full fetch to remove expunged msgs.
4299 MOZ_LOG(IMAP_CS, LogLevel::Debug,
4300 ("Other client expunged msgs, do full fetch to remove "
4301 "expunged msgs"));
4302 m_flagState->Reset();
4303 m_flagState->SetPartialUIDFetch(false);
4304 FetchMessage("1:*"_ns, kFlags);
4305 } else if (numNewUIDs == 0) {
4306 // Nothing has been expunged and no new UIDs, so if just a flag
4307 // change on existing message(s), avoid unneeded fetch of flags for
4308 // messages with UIDs at and above uid (see var uid above) when
4309 // "highwater mark" fetch occurs below.
4310 if (mFolderHighestUID && flagChangeDetected) {
4311 MOZ_LOG(IMAP_CS, LogLevel::Debug,
4312 ("Avoid unneeded fetches after just flag changes"));
4313 GetServerStateParser().ResetHighestRecordedUID();
4314 }
4315 }
4316 }
4317 int32_t numDeleted = m_flagState->NumberOfDeletedMessages();
4318 // Don't do expunge when we are lite selecting folder because we
4319 // could be doing undo.
4320 // Expunge if we're always expunging, or the number of deleted messages
4321 // is over the threshold, and we're either always respecting the
4322 // threshold, or we're expunging based on the delete model, and
4323 // the delete model is not the imap delete model.
4324 if (m_imapAction != nsIImapUrl::nsImapLiteSelectFolder &&
4325 (gExpungeOption == kAutoExpungeAlways ||
4326 (numDeleted >= gExpungeThreshold &&
4327 (gExpungeOption == kAutoExpungeOnThreshold ||
4328 (gExpungeOption == kAutoExpungeDeleteModel &&
4329 !GetShowDeletedMessages())))))
4330 Expunge();
4331 }
4332 } else {
4333 // Obtain the highest (highwater mark) UID seen since the last UIDVALIDITY
4334 // response occurred (associated with the most recent SELECT for the
4335 // folder).
4336 uint32_t highestRecordedUID = GetServerStateParser().HighestRecordedUID();
4337 // if we're using CONDSTORE, and the parser hasn't seen any UIDs, use
4338 // the highest UID previously seen and saved for the folder instead.
4339 if (useCS && !highestRecordedUID) highestRecordedUID = mFolderHighestUID;
4340 // clang-format off
4341 MOZ_LOG(IMAP_CS, LogLevel::Debug,
4342 ("Check for new messages above UID=%" PRIu32, highestRecordedUID));
4343 // clang-format on
4344 AppendUid(fetchStr, highestRecordedUID + 1);
4345 fetchStr.AppendLiteral(":*");
4346 FetchMessage(fetchStr, kFlags); // only new messages please
4347 }
4348 } else if (GetServerStateParser().LastCommandSuccessful()) {
4349 GetServerStateParser().ResetFlagInfo();
4350 // the flag state is empty, but not partial.
4351 m_flagState->SetPartialUIDFetch(false);
4352 }
4353
4354 if (GetServerStateParser().LastCommandSuccessful()) {
4355 nsImapAction imapAction;
4356 nsresult res = m_runningUrl->GetImapAction(&imapAction);
4357 if (NS_SUCCEEDED(res) && imapAction == nsIImapUrl::nsImapLiteSelectFolder)
4358 return;
4359 }
4360
4361 bool entered_waitForBodyIdsMonitor = false;
4362
4363 nsTArray<nsMsgKey> msgIdList;
4364
4365 RefPtr<nsImapMailboxSpec> new_spec =
4366 GetServerStateParser().CreateCurrentMailboxSpec();
4367 if (new_spec && GetServerStateParser().LastCommandSuccessful()) {
4368 nsImapAction imapAction;
4369 nsresult res = m_runningUrl->GetImapAction(&imapAction);
4370 if (NS_SUCCEEDED(res) && imapAction == nsIImapUrl::nsImapExpungeFolder)
4371 new_spec->mBoxFlags |= kJustExpunged;
4372 m_waitForBodyIdsMonitor.Enter();
4373 entered_waitForBodyIdsMonitor = true;
4374
4375 if (m_imapMailFolderSink) {
4376 bool more;
4377 m_imapMailFolderSink->UpdateImapMailboxInfo(this, new_spec);
4378 m_imapMailFolderSink->GetMsgHdrsToDownload(
4379 &more, &m_progressExpectedNumber, msgIdList);
4380 // Assert that either it's empty string OR it must be header string.
4381 MOZ_ASSERT((m_stringIndex == IMAP_EMPTY_STRING_INDEX) ||
4382 (m_stringIndex == IMAP_HEADERS_STRING_INDEX));
4383 m_progressCurrentNumber[m_stringIndex] = 0;
4384 m_runningUrl->SetMoreHeadersToDownload(more);
4385 // We're going to be re-running this url if there are more headers.
4386 if (more) m_runningUrl->SetRerunningUrl(true);
4387 }
4388 }
4389
4390 if (GetServerStateParser().LastCommandSuccessful()) {
4391 if (entered_waitForBodyIdsMonitor) m_waitForBodyIdsMonitor.Exit();
4392
4393 if (msgIdList.Length() > 0 && !DeathSignalReceived() &&
4394 GetServerStateParser().LastCommandSuccessful()) {
4395 FolderHeaderDump(msgIdList.Elements(), msgIdList.Length());
4396 }
4397 HeaderFetchCompleted();
4398 // this might be bogus, how are we going to do pane notification and stuff
4399 // when we fetch bodies without headers!
4400 } else if (entered_waitForBodyIdsMonitor) // need to exit this monitor if
4401 // death signal received
4402 m_waitForBodyIdsMonitor.Exit();
4403
4404 // wait for a list of bodies to fetch.
4405 if (GetServerStateParser().LastCommandSuccessful()) {
4406 nsTArray<nsMsgKey> msgIds;
4407 WaitForPotentialListOfBodysToFetch(msgIds);
4408 if (msgIds.Length() > 0 && GetServerStateParser().LastCommandSuccessful()) {
4409 // Tell the url that it should store the msg fetch results offline,
4410 // while we're dumping the messages, and then restore the setting.
4411 bool wasStoringOffline;
4412 m_runningUrl->GetStoreResultsOffline(&wasStoringOffline);
4413 m_runningUrl->SetStoreResultsOffline(true);
4414 // Assert that either it's empty string OR it must be message string.
4415 MOZ_ASSERT((m_stringIndex == IMAP_EMPTY_STRING_INDEX) ||
4416 (m_stringIndex == IMAP_MESSAGES_STRING_INDEX));
4417 m_progressCurrentNumber[m_stringIndex] = 0;
4418 m_progressExpectedNumber = msgIds.Length();
4419 FolderMsgDump(msgIds.Elements(), msgIds.Length(), kEveryThingRFC822Peek);
4420 m_runningUrl->SetStoreResultsOffline(wasStoringOffline);
4421 }
4422 }
4423 if (!GetServerStateParser().LastCommandSuccessful())
4424 GetServerStateParser().ResetFlagInfo();
4425 }
4426
FolderHeaderDump(uint32_t * msgUids,uint32_t msgCount)4427 void nsImapProtocol::FolderHeaderDump(uint32_t* msgUids, uint32_t msgCount) {
4428 FolderMsgDump(msgUids, msgCount, kHeadersRFC822andUid);
4429 }
4430
FolderMsgDump(uint32_t * msgUids,uint32_t msgCount,nsIMAPeFetchFields fields)4431 void nsImapProtocol::FolderMsgDump(uint32_t* msgUids, uint32_t msgCount,
4432 nsIMAPeFetchFields fields) {
4433 // lets worry about this progress stuff later.
4434 switch (fields) {
4435 case kHeadersRFC822andUid:
4436 SetProgressString(IMAP_HEADERS_STRING_INDEX);
4437 break;
4438 case kFlags:
4439 SetProgressString(IMAP_FLAGS_STRING_INDEX);
4440 break;
4441 default:
4442 SetProgressString(IMAP_MESSAGES_STRING_INDEX);
4443 break;
4444 }
4445
4446 FolderMsgDumpLoop(msgUids, msgCount, fields);
4447
4448 SetProgressString(IMAP_EMPTY_STRING_INDEX);
4449 }
4450
WaitForPotentialListOfBodysToFetch(nsTArray<nsMsgKey> & msgIdList)4451 void nsImapProtocol::WaitForPotentialListOfBodysToFetch(
4452 nsTArray<nsMsgKey>& msgIdList) {
4453 PRIntervalTime sleepTime = kImapSleepTime;
4454
4455 ReentrantMonitorAutoEnter fetchListMon(m_fetchBodyListMonitor);
4456 while (!m_fetchBodyListIsNew && !DeathSignalReceived())
4457 fetchListMon.Wait(sleepTime);
4458 m_fetchBodyListIsNew = false;
4459
4460 msgIdList = m_fetchBodyIdList.Clone();
4461 }
4462
4463 // libmsg uses this to notify a running imap url about message bodies it should
4464 // download. why not just have libmsg explicitly download the message bodies?
NotifyBodysToDownload(const nsTArray<nsMsgKey> & keys)4465 NS_IMETHODIMP nsImapProtocol::NotifyBodysToDownload(
4466 const nsTArray<nsMsgKey>& keys) {
4467 ReentrantMonitorAutoEnter fetchListMon(m_fetchBodyListMonitor);
4468 m_fetchBodyIdList = keys.Clone();
4469 m_fetchBodyListIsNew = true;
4470 fetchListMon.Notify();
4471 return NS_OK;
4472 }
4473
GetFlagsForUID(uint32_t uid,bool * foundIt,imapMessageFlagsType * resultFlags,char ** customFlags)4474 NS_IMETHODIMP nsImapProtocol::GetFlagsForUID(uint32_t uid, bool* foundIt,
4475 imapMessageFlagsType* resultFlags,
4476 char** customFlags) {
4477 int32_t i;
4478
4479 imapMessageFlagsType flags =
4480 m_flagState->GetMessageFlagsFromUID(uid, foundIt, &i);
4481 if (*foundIt) {
4482 *resultFlags = flags;
4483 if ((flags & kImapMsgCustomKeywordFlag) && customFlags)
4484 m_flagState->GetCustomFlags(uid, customFlags);
4485 }
4486 return NS_OK;
4487 }
4488
GetFlagAndUidState(nsIImapFlagAndUidState ** aFlagState)4489 NS_IMETHODIMP nsImapProtocol::GetFlagAndUidState(
4490 nsIImapFlagAndUidState** aFlagState) {
4491 NS_ENSURE_ARG_POINTER(aFlagState);
4492 NS_IF_ADDREF(*aFlagState = m_flagState);
4493 return NS_OK;
4494 }
4495
GetSupportedUserFlags(uint16_t * supportedFlags)4496 NS_IMETHODIMP nsImapProtocol::GetSupportedUserFlags(uint16_t* supportedFlags) {
4497 if (!supportedFlags) return NS_ERROR_NULL_POINTER;
4498
4499 *supportedFlags = m_flagState->GetSupportedUserFlags();
4500 return NS_OK;
4501 }
FolderMsgDumpLoop(uint32_t * msgUids,uint32_t msgCount,nsIMAPeFetchFields fields)4502 void nsImapProtocol::FolderMsgDumpLoop(uint32_t* msgUids, uint32_t msgCount,
4503 nsIMAPeFetchFields fields) {
4504 int32_t msgCountLeft = msgCount;
4505 uint32_t msgsDownloaded = 0;
4506 do {
4507 nsCString idString;
4508 uint32_t msgsToDownload = msgCountLeft;
4509 AllocateImapUidString(msgUids + msgsDownloaded, msgsToDownload, m_flagState,
4510 idString); // 20 * 200
4511 FetchMessage(idString, fields);
4512 msgsDownloaded += msgsToDownload;
4513 msgCountLeft -= msgsToDownload;
4514 } while (msgCountLeft > 0 && !DeathSignalReceived());
4515 }
4516
HeaderFetchCompleted()4517 void nsImapProtocol::HeaderFetchCompleted() {
4518 if (m_imapMailFolderSink)
4519 m_imapMailFolderSink->ParseMsgHdrs(this, m_hdrDownloadCache);
4520 m_hdrDownloadCache->ReleaseAll();
4521
4522 if (m_imapMailFolderSink) m_imapMailFolderSink->HeaderFetchCompleted(this);
4523 }
4524
4525 // Use the noop to tell the server we are still here, and therefore we are
4526 // willing to receive status updates. The recent or exists response from the
4527 // server could tell us that there is more mail waiting for us, but we need to
4528 // check the flags of the mail and the high water mark to make sure that we do
4529 // not tell the user that there is new mail when perhaps they have already read
4530 // it in another machine.
4531
PeriodicBiff()4532 void nsImapProtocol::PeriodicBiff() {
4533 nsMsgBiffState startingState = m_currentBiffState;
4534
4535 if (GetServerStateParser().GetIMAPstate() ==
4536 nsImapServerResponseParser::kFolderSelected) {
4537 Noop(); // check the latest number of messages
4538 int32_t numMessages = 0;
4539 m_flagState->GetNumberOfMessages(&numMessages);
4540 if (GetServerStateParser().NumberOfMessages() != numMessages) {
4541 uint32_t id = GetServerStateParser().HighestRecordedUID() + 1;
4542 nsCString fetchStr; // only update flags
4543 uint32_t added = 0, deleted = 0;
4544
4545 deleted = m_flagState->NumberOfDeletedMessages();
4546 added = numMessages;
4547 if (!added || (added == deleted)) // empty keys, get them all
4548 id = 1;
4549
4550 // sprintf(fetchStr, "%ld:%ld", id, id +
4551 // GetServerStateParser().NumberOfMessages() -
4552 // fFlagState->GetNumberOfMessages());
4553 AppendUid(fetchStr, id);
4554 fetchStr.AppendLiteral(":*");
4555 FetchMessage(fetchStr, kFlags);
4556 if (((uint32_t)m_flagState->GetHighestNonDeletedUID() >= id) &&
4557 m_flagState->IsLastMessageUnseen())
4558 m_currentBiffState = nsIMsgFolder::nsMsgBiffState_NewMail;
4559 else
4560 m_currentBiffState = nsIMsgFolder::nsMsgBiffState_NoMail;
4561 } else
4562 m_currentBiffState = nsIMsgFolder::nsMsgBiffState_NoMail;
4563 } else
4564 m_currentBiffState = nsIMsgFolder::nsMsgBiffState_Unknown;
4565
4566 if (startingState != m_currentBiffState)
4567 SendSetBiffIndicatorEvent(m_currentBiffState);
4568 }
4569
SendSetBiffIndicatorEvent(nsMsgBiffState newState)4570 void nsImapProtocol::SendSetBiffIndicatorEvent(nsMsgBiffState newState) {
4571 if (m_imapMailFolderSink)
4572 m_imapMailFolderSink->SetBiffStateAndUpdate(newState);
4573 }
4574
LogImapUrl(const char * logMsg,nsIImapUrl * imapUrl)4575 /* static */ void nsImapProtocol::LogImapUrl(const char* logMsg,
4576 nsIImapUrl* imapUrl) {
4577 if (MOZ_LOG_TEST(IMAP, LogLevel::Info)) {
4578 nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(imapUrl);
4579 if (mailnewsUrl) {
4580 nsAutoCString urlSpec, unescapedUrlSpec;
4581 nsresult rv = mailnewsUrl->GetSpec(urlSpec);
4582 if (NS_FAILED(rv)) return;
4583 MsgUnescapeString(urlSpec, 0, unescapedUrlSpec);
4584 MOZ_LOG(IMAP, LogLevel::Info, ("%s:%s", logMsg, unescapedUrlSpec.get()));
4585 }
4586 }
4587 }
4588
4589 // log info including current state...
Log(const char * logSubName,const char * extraInfo,const char * logData)4590 void nsImapProtocol::Log(const char* logSubName, const char* extraInfo,
4591 const char* logData) {
4592 if (MOZ_LOG_TEST(IMAP, LogLevel::Info)) {
4593 static const char nonAuthStateName[] = "NA";
4594 static const char authStateName[] = "A";
4595 static const char selectedStateName[] = "S";
4596 const nsCString& hostName =
4597 GetImapHostName(); // initialize to empty string
4598
4599 int32_t logDataLen = PL_strlen(logData); // PL_strlen checks for null
4600 nsCString logDataLines;
4601 const char* logDataToLog;
4602 int32_t lastLineEnd;
4603
4604 // nspr line length is 512, and we allow some space for the log preamble.
4605 const int kLogDataChunkSize = 400;
4606
4607 // break up buffers > 400 bytes on line boundaries.
4608 if (logDataLen > kLogDataChunkSize) {
4609 logDataLines.Assign(logData);
4610 lastLineEnd = logDataLines.RFindChar('\n', kLogDataChunkSize);
4611 // null terminate the last line
4612 if (lastLineEnd == kNotFound) lastLineEnd = kLogDataChunkSize - 1;
4613
4614 logDataLines.Insert('\0', lastLineEnd + 1);
4615 logDataToLog = logDataLines.get();
4616 } else {
4617 logDataToLog = logData;
4618 lastLineEnd = logDataLen;
4619 }
4620 switch (GetServerStateParser().GetIMAPstate()) {
4621 case nsImapServerResponseParser::kFolderSelected:
4622 if (extraInfo)
4623 MOZ_LOG(IMAP, LogLevel::Info,
4624 ("%p:%s:%s-%s:%s:%s: %.400s", this, hostName.get(),
4625 selectedStateName,
4626 GetServerStateParser().GetSelectedMailboxName(), logSubName,
4627 extraInfo, logDataToLog));
4628 else
4629 MOZ_LOG(IMAP, LogLevel::Info,
4630 ("%p:%s:%s-%s:%s: %.400s", this, hostName.get(),
4631 selectedStateName,
4632 GetServerStateParser().GetSelectedMailboxName(), logSubName,
4633 logDataToLog));
4634 break;
4635 case nsImapServerResponseParser::kNonAuthenticated:
4636 case nsImapServerResponseParser::kAuthenticated: {
4637 const char* stateName = (GetServerStateParser().GetIMAPstate() ==
4638 nsImapServerResponseParser::kNonAuthenticated)
4639 ? nonAuthStateName
4640 : authStateName;
4641 if (extraInfo)
4642 MOZ_LOG(IMAP, LogLevel::Info,
4643 ("%p:%s:%s:%s:%s: %.400s", this, hostName.get(), stateName,
4644 logSubName, extraInfo, logDataToLog));
4645 else
4646 MOZ_LOG(IMAP, LogLevel::Info,
4647 ("%p:%s:%s:%s: %.400s", this, hostName.get(), stateName,
4648 logSubName, logDataToLog));
4649 }
4650 }
4651
4652 // dump the rest of the string in < 400 byte chunks
4653 while (logDataLen > kLogDataChunkSize) {
4654 logDataLines.Cut(
4655 0,
4656 lastLineEnd + 2); // + 2 to account for the LF and the '\0' we added
4657 logDataLen = logDataLines.Length();
4658 lastLineEnd = (logDataLen > kLogDataChunkSize)
4659 ? logDataLines.RFindChar('\n', kLogDataChunkSize)
4660 : kNotFound;
4661 // null terminate the last line
4662 if (lastLineEnd == kNotFound) lastLineEnd = kLogDataChunkSize - 1;
4663 logDataLines.Insert('\0', lastLineEnd + 1);
4664 logDataToLog = logDataLines.get();
4665 MOZ_LOG(IMAP, LogLevel::Info, ("%.400s", logDataToLog));
4666 }
4667 }
4668 }
4669
4670 // In 4.5, this posted an event back to libmsg and blocked until it got a
4671 // response. We may still have to do this.It would be nice if we could preflight
4672 // this value, but we may not always know when we'll need it.
GetMessageSize(const nsACString & messageId)4673 uint32_t nsImapProtocol::GetMessageSize(const nsACString& messageId) {
4674 uint32_t size = 0;
4675 if (m_imapMessageSink)
4676 m_imapMessageSink->GetMessageSizeFromDB(PromiseFlatCString(messageId).get(),
4677 &size);
4678 if (DeathSignalReceived()) size = 0;
4679 return size;
4680 }
4681
4682 // message id string utility functions
HandlingMultipleMessages(const nsCString & messageIdString)4683 /* static */ bool nsImapProtocol::HandlingMultipleMessages(
4684 const nsCString& messageIdString) {
4685 return (MsgFindCharInSet(messageIdString, ",:") != kNotFound);
4686 }
4687
CountMessagesInIdString(const char * idString)4688 uint32_t nsImapProtocol::CountMessagesInIdString(const char* idString) {
4689 uint32_t numberOfMessages = 0;
4690 char* uidString = PL_strdup(idString);
4691
4692 if (uidString) {
4693 // This is in the form <id>,<id>, or <id1>:<id2>
4694 char curChar = *uidString;
4695 bool isRange = false;
4696 int32_t curToken;
4697 int32_t saveStartToken = 0;
4698
4699 for (char* curCharPtr = uidString; curChar && *curCharPtr;) {
4700 char* currentKeyToken = curCharPtr;
4701 curChar = *curCharPtr;
4702 while (curChar != ':' && curChar != ',' && curChar != '\0')
4703 curChar = *curCharPtr++;
4704 *(curCharPtr - 1) = '\0';
4705 curToken = atol(currentKeyToken);
4706 if (isRange) {
4707 while (saveStartToken < curToken) {
4708 numberOfMessages++;
4709 saveStartToken++;
4710 }
4711 }
4712
4713 numberOfMessages++;
4714 isRange = (curChar == ':');
4715 if (isRange) saveStartToken = curToken + 1;
4716 }
4717 PR_Free(uidString);
4718 }
4719 return numberOfMessages;
4720 }
4721
4722 // It would be really nice not to have to use this method nearly as much as we
4723 // did in 4.5 - we need to think about this some. Some of it may just go away in
4724 // the new world order
DeathSignalReceived()4725 bool nsImapProtocol::DeathSignalReceived() {
4726 // ignore mock channel status if we've been pseudo interrupted
4727 // ### need to make sure we clear pseudo interrupted status appropriately.
4728 if (!GetPseudoInterrupted() && m_mockChannel) {
4729 nsresult returnValue;
4730 m_mockChannel->GetStatus(&returnValue);
4731 if (NS_FAILED(returnValue)) return false;
4732 }
4733
4734 // Check the other way of cancelling.
4735 ReentrantMonitorAutoEnter threadDeathMon(m_threadDeathMonitor);
4736 return m_threadShouldDie;
4737 }
4738
ResetToAuthenticatedState()4739 NS_IMETHODIMP nsImapProtocol::ResetToAuthenticatedState() {
4740 GetServerStateParser().PreauthSetAuthenticatedState();
4741 return NS_OK;
4742 }
4743
GetSelectedMailboxName(char ** folderName)4744 NS_IMETHODIMP nsImapProtocol::GetSelectedMailboxName(char** folderName) {
4745 if (!folderName) return NS_ERROR_NULL_POINTER;
4746 if (GetServerStateParser().GetSelectedMailboxName())
4747 *folderName = PL_strdup((GetServerStateParser().GetSelectedMailboxName()));
4748 return NS_OK;
4749 }
4750
GetPseudoInterrupted()4751 bool nsImapProtocol::GetPseudoInterrupted() {
4752 ReentrantMonitorAutoEnter pseudoInterruptMon(m_pseudoInterruptMonitor);
4753 return m_pseudoInterrupted;
4754 }
4755
PseudoInterrupt(bool the_interrupt)4756 void nsImapProtocol::PseudoInterrupt(bool the_interrupt) {
4757 ReentrantMonitorAutoEnter pseudoInterruptMon(m_pseudoInterruptMonitor);
4758 m_pseudoInterrupted = the_interrupt;
4759 if (the_interrupt) Log("CONTROL", NULL, "PSEUDO-Interrupted");
4760 }
4761
SetActive(bool active)4762 void nsImapProtocol::SetActive(bool active) {
4763 ReentrantMonitorAutoEnter dataMemberMon(m_dataMemberMonitor);
4764 m_active = active;
4765 }
4766
GetActive()4767 bool nsImapProtocol::GetActive() {
4768 ReentrantMonitorAutoEnter dataMemberMon(m_dataMemberMonitor);
4769 return m_active;
4770 }
4771
GetShowAttachmentsInline()4772 bool nsImapProtocol::GetShowAttachmentsInline() {
4773 bool showAttachmentsInline = true;
4774 if (m_imapServerSink)
4775 m_imapServerSink->GetShowAttachmentsInline(&showAttachmentsInline);
4776 return showAttachmentsInline;
4777 }
4778
SetContentModified(IMAP_ContentModifiedType modified)4779 void nsImapProtocol::SetContentModified(IMAP_ContentModifiedType modified) {
4780 if (m_runningUrl && m_imapMessageSink)
4781 m_imapMessageSink->SetContentModified(m_runningUrl, modified);
4782 }
4783
GetShouldFetchAllParts()4784 bool nsImapProtocol::GetShouldFetchAllParts() {
4785 if (m_runningUrl && !DeathSignalReceived()) {
4786 nsImapContentModifiedType contentModified;
4787 if (NS_SUCCEEDED(m_runningUrl->GetContentModified(&contentModified)))
4788 return (contentModified == IMAP_CONTENT_FORCE_CONTENT_NOT_MODIFIED);
4789 }
4790 return true;
4791 }
4792
4793 // Adds a set of rights for a given user on a given mailbox on the current host.
4794 // if userName is NULL, it means "me," or MYRIGHTS.
AddFolderRightsForUser(const char * mailboxName,const char * userName,const char * rights)4795 void nsImapProtocol::AddFolderRightsForUser(const char* mailboxName,
4796 const char* userName,
4797 const char* rights) {
4798 if (!userName) userName = "";
4799 if (m_imapServerSink)
4800 m_imapServerSink->AddFolderRights(nsDependentCString(mailboxName),
4801 nsDependentCString(userName),
4802 nsDependentCString(rights));
4803 }
4804
SetCopyResponseUid(const char * msgIdString)4805 void nsImapProtocol::SetCopyResponseUid(const char* msgIdString) {
4806 if (m_imapMailFolderSink)
4807 m_imapMailFolderSink->SetCopyResponseUid(msgIdString, m_runningUrl);
4808 }
4809
CommitNamespacesForHostEvent()4810 void nsImapProtocol::CommitNamespacesForHostEvent() {
4811 if (m_imapServerSink) m_imapServerSink->CommitNamespaces();
4812 }
4813
4814 // notifies libmsg that we have new capability data for the current host
CommitCapability()4815 void nsImapProtocol::CommitCapability() {
4816 if (m_imapServerSink) {
4817 m_imapServerSink->SetCapability(GetServerStateParser().GetCapabilityFlag());
4818 }
4819 }
4820
4821 // rights is a single string of rights, as specified by RFC2086, the IMAP ACL
4822 // extension. Clears all rights for a given folder, for all users.
ClearAllFolderRights()4823 void nsImapProtocol::ClearAllFolderRights() {
4824 if (m_imapMailFolderSink) m_imapMailFolderSink->ClearFolderRights();
4825 }
4826
4827 // Reads a line from the socket.
4828 // Upon failure, the thread will be flagged for shutdown, and
4829 // m_connectionStatus will be set to a failing code.
4830 // Remember that some socket errors are deferred until the first read
4831 // attempt, so this function could be the first place we hear about
4832 // connection issues (e.g. bad certificates for SSL).
CreateNewLineFromSocket()4833 char* nsImapProtocol::CreateNewLineFromSocket() {
4834 bool needMoreData = false;
4835 char* newLine = nullptr;
4836 uint32_t numBytesInLine = 0;
4837 nsresult rv = NS_OK;
4838 // we hold a ref to the input stream in case we get cancelled from the
4839 // ui thread, which releases our ref to the input stream, and can
4840 // cause the pipe to get deleted before the monitor the read is
4841 // blocked on gets notified. When that happens, the imap thread
4842 // will stay blocked.
4843 nsCOMPtr<nsIInputStream> kungFuGrip = m_inputStream;
4844
4845 if (m_mockChannel) {
4846 nsImapMockChannel* imapChannel =
4847 static_cast<nsImapMockChannel*>(m_mockChannel.get());
4848
4849 mozilla::MonitorAutoLock lock(imapChannel->mSuspendedMonitor);
4850
4851 bool suspended = imapChannel->mSuspended;
4852 if (suspended)
4853 MOZ_LOG(IMAP, LogLevel::Debug,
4854 ("Waiting until [imapChannel=%p] is resumed.", imapChannel));
4855 while (imapChannel->mSuspended) {
4856 lock.Wait();
4857 }
4858 if (suspended)
4859 MOZ_LOG(
4860 IMAP, LogLevel::Debug,
4861 ("Done waiting, [imapChannel=%p] has been resumed.", imapChannel));
4862 }
4863
4864 do {
4865 newLine = m_inputStreamBuffer->ReadNextLine(m_inputStream, numBytesInLine,
4866 needMoreData, &rv);
4867 MOZ_LOG(IMAP, LogLevel::Debug,
4868 ("ReadNextLine [rv=0x%" PRIx32 " stream=%p nb=%u needmore=%u]",
4869 static_cast<uint32_t>(rv), m_inputStream.get(), numBytesInLine,
4870 needMoreData));
4871
4872 } while (!newLine && NS_SUCCEEDED(rv) &&
4873 !DeathSignalReceived()); // until we get the next line and haven't
4874 // been interrupted
4875
4876 kungFuGrip = nullptr;
4877
4878 if (NS_FAILED(rv)) {
4879 switch (rv) {
4880 case NS_ERROR_UNKNOWN_HOST:
4881 case NS_ERROR_UNKNOWN_PROXY_HOST:
4882 AlertUserEventUsingName("imapUnknownHostError");
4883 break;
4884 case NS_ERROR_CONNECTION_REFUSED:
4885 case NS_ERROR_PROXY_CONNECTION_REFUSED:
4886 AlertUserEventUsingName("imapConnectionRefusedError");
4887 break;
4888 case NS_ERROR_NET_TIMEOUT:
4889 case NS_ERROR_NET_RESET:
4890 case NS_BASE_STREAM_CLOSED:
4891 case NS_ERROR_NET_INTERRUPT:
4892 // we should retry on RESET, especially for SSL...
4893 if ((TestFlag(IMAP_RECEIVED_GREETING) || rv == NS_ERROR_NET_RESET) &&
4894 m_runningUrl && !m_retryUrlOnError) {
4895 bool rerunningUrl;
4896 nsImapAction imapAction;
4897 m_runningUrl->GetRerunningUrl(&rerunningUrl);
4898 m_runningUrl->GetImapAction(&imapAction);
4899 // don't rerun if we already were rerunning. And don't rerun
4900 // online move/copies that timeout.
4901 if (!rerunningUrl && (rv != NS_ERROR_NET_TIMEOUT ||
4902 (imapAction != nsIImapUrl::nsImapOnlineCopy &&
4903 imapAction != nsIImapUrl::nsImapOnlineMove))) {
4904 m_runningUrl->SetRerunningUrl(true);
4905 m_retryUrlOnError = true;
4906 break;
4907 }
4908 }
4909 if (rv == NS_ERROR_NET_TIMEOUT)
4910 AlertUserEventUsingName("imapNetTimeoutError");
4911 else
4912 AlertUserEventUsingName(TestFlag(IMAP_RECEIVED_GREETING)
4913 ? "imapServerDisconnected"
4914 : "imapServerDroppedConnection");
4915 break;
4916 default:
4917 // This is probably a TLS error. Usually TLS errors won't show up until
4918 // we do ReadNextLine() above. Since we're in the IMAP thread we can't
4919 // call NSSErrorsService::GetErrorClass() to determine if the error
4920 // should result in an non-fatal override dialog (usually certificate
4921 // issues) or if it's a fatal protocol error that the user must be
4922 // alerted to. Instead, we use some publicly-accessible macros and a
4923 // function to determine this.
4924 if (NS_ERROR_GET_MODULE(rv) == NS_ERROR_MODULE_SECURITY &&
4925 NS_ERROR_GET_SEVERITY(rv) == NS_ERROR_SEVERITY_ERROR) {
4926 // It's an error of class 21 (SSL/TLS/Security), e.g., overridable
4927 // SSL_ERROR_BAD_CERT_DOMAIN from security/nss/lib/ssl/sslerr.h
4928 // rv = 0x80000000 + 0x00450000 + 0x00150000 + 0x00002ff4 = 0x805A2ff4
4929 int32_t sec_error = -1 * NS_ERROR_GET_CODE(rv); // = 0xFFFFD00C
4930 if (!mozilla::psm::ErrorIsOverridable(sec_error))
4931 AlertUserEventUsingName("imapTlsError");
4932
4933 // Stash the socket transport's securityInfo on the URL so it will be
4934 // available in nsIUrlListener OnStopRunningUrl() callbacks to trigger
4935 // the override dialog or a security related error message.
4936 if (m_runningUrl) {
4937 nsCOMPtr<nsIMsgMailNewsUrl> mailNewsUrl =
4938 do_QueryInterface(m_runningUrl);
4939 nsCOMPtr<nsISupports> secInfo;
4940 if (mailNewsUrl && NS_SUCCEEDED(m_transport->GetSecurityInfo(
4941 getter_AddRefs(secInfo)))) {
4942 nsCOMPtr<nsITransportSecurityInfo> transportSecInfo =
4943 do_QueryInterface(secInfo);
4944 if (transportSecInfo) {
4945 mailNewsUrl->SetFailedSecInfo(transportSecInfo);
4946 }
4947 }
4948 }
4949 }
4950 break;
4951 }
4952
4953 nsAutoCString logMsg("clearing IMAP_CONNECTION_IS_OPEN - rv = ");
4954 logMsg.AppendInt(static_cast<uint32_t>(rv), 16);
4955 Log("CreateNewLineFromSocket", nullptr, logMsg.get());
4956 ClearFlag(IMAP_CONNECTION_IS_OPEN);
4957 TellThreadToDie();
4958 }
4959 Log("CreateNewLineFromSocket", nullptr, newLine);
4960 SetConnectionStatus(newLine && numBytesInLine
4961 ? NS_OK
4962 : rv); // set > 0 if string is not null or empty
4963 return newLine;
4964 }
4965
GetConnectionStatus()4966 nsresult nsImapProtocol::GetConnectionStatus() { return m_connectionStatus; }
4967
SetConnectionStatus(nsresult status)4968 void nsImapProtocol::SetConnectionStatus(nsresult status) {
4969 MOZ_LOG(
4970 IMAP, LogLevel::Debug,
4971 ("SetConnectionStatus(0x%" PRIx32 ")", static_cast<uint32_t>(status)));
4972 m_connectionStatus = status;
4973 }
4974
NotifyMessageFlags(imapMessageFlagsType flags,const nsACString & keywords,nsMsgKey key,uint64_t highestModSeq)4975 void nsImapProtocol::NotifyMessageFlags(imapMessageFlagsType flags,
4976 const nsACString& keywords,
4977 nsMsgKey key, uint64_t highestModSeq) {
4978 if (m_imapMessageSink) {
4979 // if we're selecting the folder, don't need to report the flags; we've
4980 // already fetched them.
4981 if (m_imapAction != nsIImapUrl::nsImapSelectFolder &&
4982 (m_imapAction != nsIImapUrl::nsImapMsgFetch ||
4983 (flags & ~kImapMsgRecentFlag) != kImapMsgSeenFlag))
4984 m_imapMessageSink->NotifyMessageFlags(flags, keywords, key,
4985 highestModSeq);
4986 }
4987 }
4988
NotifySearchHit(const char * hitLine)4989 void nsImapProtocol::NotifySearchHit(const char* hitLine) {
4990 nsresult rv;
4991 nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl =
4992 do_QueryInterface(m_runningUrl, &rv);
4993 if (m_imapMailFolderSink)
4994 m_imapMailFolderSink->NotifySearchHit(mailnewsUrl, hitLine);
4995 }
4996
SetMailboxDiscoveryStatus(EMailboxDiscoverStatus status)4997 void nsImapProtocol::SetMailboxDiscoveryStatus(EMailboxDiscoverStatus status) {
4998 ReentrantMonitorAutoEnter mon(m_dataMemberMonitor);
4999 m_discoveryStatus = status;
5000 }
5001
GetMailboxDiscoveryStatus()5002 EMailboxDiscoverStatus nsImapProtocol::GetMailboxDiscoveryStatus() {
5003 ReentrantMonitorAutoEnter mon(m_dataMemberMonitor);
5004 return m_discoveryStatus;
5005 }
5006
GetSubscribingNow()5007 bool nsImapProtocol::GetSubscribingNow() {
5008 // ***** code me *****
5009 return false; // ***** for now
5010 }
5011
DiscoverMailboxSpec(nsImapMailboxSpec * adoptedBoxSpec)5012 void nsImapProtocol::DiscoverMailboxSpec(nsImapMailboxSpec* adoptedBoxSpec) {
5013 nsImapNamespace* ns = nullptr;
5014
5015 NS_ASSERTION(m_hostSessionList, "fatal null host session list");
5016 if (!m_hostSessionList) return;
5017
5018 m_hostSessionList->GetDefaultNamespaceOfTypeForHost(GetImapServerKey(),
5019 kPersonalNamespace, ns);
5020 const char* nsPrefix = ns ? ns->GetPrefix() : 0;
5021
5022 if (m_specialXListMailboxes.Count() > 0) {
5023 nsCString strHashKey(adoptedBoxSpec->mAllocatedPathName);
5024 int32_t hashValue = m_specialXListMailboxes.Get(strHashKey);
5025 adoptedBoxSpec->mBoxFlags |= hashValue;
5026 }
5027
5028 switch (m_hierarchyNameState) {
5029 case kXListing:
5030 if (adoptedBoxSpec->mBoxFlags &
5031 (kImapXListTrash | kImapAllMail | kImapInbox | kImapSent | kImapSpam |
5032 kImapDrafts)) {
5033 nsCString mailboxName(adoptedBoxSpec->mAllocatedPathName);
5034 m_specialXListMailboxes.InsertOrUpdate(mailboxName,
5035 adoptedBoxSpec->mBoxFlags);
5036 // Remember hierarchy delimiter in case this is the first time we've
5037 // connected to the server and we need it to be correct for the
5038 // two-level XLIST we send (INBOX is guaranteed to be in the first
5039 // response).
5040 if (adoptedBoxSpec->mBoxFlags & kImapInbox)
5041 m_runningUrl->SetOnlineSubDirSeparator(
5042 adoptedBoxSpec->mHierarchySeparator);
5043 }
5044 break;
5045 case kListingForFolderFlags: {
5046 // store mailbox flags from LIST for use by LSUB
5047 nsCString mailboxName(adoptedBoxSpec->mAllocatedPathName);
5048 m_standardListMailboxes.InsertOrUpdate(mailboxName,
5049 adoptedBoxSpec->mBoxFlags);
5050 } break;
5051 case kListingForCreate:
5052 case kNoOperationInProgress:
5053 case kDiscoverTrashFolderInProgress:
5054 case kListingForInfoAndDiscovery: {
5055 // standard mailbox specs are stored in m_standardListMailboxes
5056 // because LSUB does necessarily return all mailbox flags.
5057 // count should be > 0 only when we are looking at response of LSUB
5058 if (m_standardListMailboxes.Count() > 0) {
5059 int32_t hashValue = 0;
5060 nsCString strHashKey(adoptedBoxSpec->mAllocatedPathName);
5061 if (m_standardListMailboxes.Get(strHashKey, &hashValue))
5062 adoptedBoxSpec->mBoxFlags |= hashValue;
5063 else
5064 // if mailbox is not in hash list, then it is subscribed but does not
5065 // exist, so we make sure it can't be selected
5066 adoptedBoxSpec->mBoxFlags |= kNoselect;
5067 }
5068 if (ns &&
5069 nsPrefix) // if no personal namespace, there can be no Trash folder
5070 {
5071 bool onlineTrashFolderExists = false;
5072 if (m_hostSessionList) {
5073 if (adoptedBoxSpec->mBoxFlags & (kImapTrash | kImapXListTrash)) {
5074 m_hostSessionList->SetOnlineTrashFolderExistsForHost(
5075 GetImapServerKey(), true);
5076 onlineTrashFolderExists = true;
5077 } else {
5078 m_hostSessionList->GetOnlineTrashFolderExistsForHost(
5079 GetImapServerKey(), onlineTrashFolderExists);
5080 }
5081 }
5082
5083 // Don't set the Trash flag if not using the Trash model
5084 if (GetDeleteIsMoveToTrash() && !onlineTrashFolderExists &&
5085 adoptedBoxSpec->mAllocatedPathName.Find(
5086 m_trashFolderPath, /* ignoreCase = */ true) != -1) {
5087 bool trashExists = false;
5088 if (StringBeginsWith(m_trashFolderPath, "INBOX/"_ns,
5089 nsCaseInsensitiveCStringComparator)) {
5090 nsAutoCString pathName(adoptedBoxSpec->mAllocatedPathName.get() +
5091 6);
5092 trashExists =
5093 StringBeginsWith(
5094 adoptedBoxSpec->mAllocatedPathName, m_trashFolderPath,
5095 nsCaseInsensitiveCStringComparator) && /* "INBOX/" */
5096 pathName.Equals(Substring(m_trashFolderPath, 6),
5097 nsCaseInsensitiveCStringComparator);
5098 } else
5099 trashExists = adoptedBoxSpec->mAllocatedPathName.Equals(
5100 m_trashFolderPath, nsCaseInsensitiveCStringComparator);
5101
5102 if (m_hostSessionList)
5103 m_hostSessionList->SetOnlineTrashFolderExistsForHost(
5104 GetImapServerKey(), trashExists);
5105
5106 if (trashExists) adoptedBoxSpec->mBoxFlags |= kImapTrash;
5107 }
5108 }
5109
5110 // Discover the folder (shuttle over to libmsg, yay)
5111 // Do this only if the folder name is not empty (i.e. the root)
5112 if (!adoptedBoxSpec->mAllocatedPathName.IsEmpty()) {
5113 if (m_hierarchyNameState == kListingForCreate)
5114 adoptedBoxSpec->mBoxFlags |= kNewlyCreatedFolder;
5115
5116 if (m_imapServerSink) {
5117 bool newFolder;
5118
5119 m_imapServerSink->PossibleImapMailbox(
5120 adoptedBoxSpec->mAllocatedPathName,
5121 adoptedBoxSpec->mHierarchySeparator, adoptedBoxSpec->mBoxFlags,
5122 &newFolder);
5123 // if it's a new folder to the server sink, setting discovery status
5124 // to eContinueNew will cause us to get the ACL for the new folder.
5125 if (newFolder) SetMailboxDiscoveryStatus(eContinueNew);
5126
5127 bool useSubscription = false;
5128
5129 if (m_hostSessionList)
5130 m_hostSessionList->GetHostIsUsingSubscription(GetImapServerKey(),
5131 useSubscription);
5132
5133 if ((GetMailboxDiscoveryStatus() != eContinue) &&
5134 (GetMailboxDiscoveryStatus() != eContinueNew) &&
5135 (GetMailboxDiscoveryStatus() != eListMyChildren)) {
5136 SetConnectionStatus(NS_ERROR_FAILURE);
5137 } else if (!adoptedBoxSpec->mAllocatedPathName.IsEmpty() &&
5138 (GetMailboxDiscoveryStatus() == eListMyChildren) &&
5139 (!useSubscription || GetSubscribingNow())) {
5140 NS_ASSERTION(false, "we should never get here anymore");
5141 SetMailboxDiscoveryStatus(eContinue);
5142 } else if (GetMailboxDiscoveryStatus() == eContinueNew) {
5143 if (m_hierarchyNameState == kListingForInfoAndDiscovery &&
5144 !adoptedBoxSpec->mAllocatedPathName.IsEmpty() &&
5145 !(adoptedBoxSpec->mBoxFlags & kNameSpace)) {
5146 // remember the info here also
5147 nsIMAPMailboxInfo* mb =
5148 new nsIMAPMailboxInfo(adoptedBoxSpec->mAllocatedPathName,
5149 adoptedBoxSpec->mHierarchySeparator);
5150 m_listedMailboxList.AppendElement(mb);
5151 }
5152 SetMailboxDiscoveryStatus(eContinue);
5153 }
5154 }
5155 }
5156 } break;
5157 case kDeleteSubFoldersInProgress: {
5158 NS_ASSERTION(m_deletableChildren, "Oops .. null m_deletableChildren");
5159 m_deletableChildren->AppendElement(
5160 ToNewCString(adoptedBoxSpec->mAllocatedPathName));
5161 } break;
5162 case kListingForInfoOnly: {
5163 // UpdateProgressWindowForUpgrade(adoptedBoxSpec->allocatedPathName);
5164 ProgressEventFunctionUsingNameWithString(
5165 "imapDiscoveringMailbox", adoptedBoxSpec->mAllocatedPathName.get());
5166 nsIMAPMailboxInfo* mb =
5167 new nsIMAPMailboxInfo(adoptedBoxSpec->mAllocatedPathName,
5168 adoptedBoxSpec->mHierarchySeparator);
5169 m_listedMailboxList.AppendElement(mb);
5170 } break;
5171 case kDiscoveringNamespacesOnly: {
5172 } break;
5173 default:
5174 NS_ASSERTION(false, "we aren't supposed to be here");
5175 break;
5176 }
5177 }
5178
AlertUserEventUsingName(const char * aMessageName)5179 void nsImapProtocol::AlertUserEventUsingName(const char* aMessageName) {
5180 if (m_imapServerSink) {
5181 bool suppressErrorMsg = false;
5182
5183 nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningUrl);
5184 if (mailnewsUrl) mailnewsUrl->GetSuppressErrorMsgs(&suppressErrorMsg);
5185
5186 if (!suppressErrorMsg)
5187 m_imapServerSink->FEAlertWithName(aMessageName, mailnewsUrl);
5188 }
5189 }
5190
AlertUserEvent(const char * message)5191 void nsImapProtocol::AlertUserEvent(const char* message) {
5192 if (m_imapServerSink) {
5193 bool suppressErrorMsg = false;
5194
5195 nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningUrl);
5196 if (mailnewsUrl) mailnewsUrl->GetSuppressErrorMsgs(&suppressErrorMsg);
5197
5198 if (!suppressErrorMsg)
5199 m_imapServerSink->FEAlert(NS_ConvertASCIItoUTF16(message), mailnewsUrl);
5200 }
5201 }
5202
AlertUserEventFromServer(const char * aServerEvent,bool aForIdle)5203 void nsImapProtocol::AlertUserEventFromServer(const char* aServerEvent,
5204 bool aForIdle) {
5205 if (aServerEvent) {
5206 // If called due to BAD/NO imap IDLE response, the server sink and running
5207 // url are typically null when IDLE command is sent. So use the stored
5208 // latest values for these so that the error alert notification occurs.
5209 if (aForIdle && !m_imapServerSink && !m_runningUrl &&
5210 m_imapServerSinkLatest) {
5211 nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl =
5212 do_QueryInterface(m_runningUrlLatest);
5213 m_imapServerSinkLatest->FEAlertFromServer(
5214 nsDependentCString(aServerEvent), mailnewsUrl);
5215 } else if (m_imapServerSink) {
5216 nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningUrl);
5217 m_imapServerSink->FEAlertFromServer(nsDependentCString(aServerEvent),
5218 mailnewsUrl);
5219 }
5220 }
5221 }
5222
ResetProgressInfo()5223 void nsImapProtocol::ResetProgressInfo() {
5224 m_lastProgressTime = 0;
5225 m_lastPercent = -1;
5226 m_lastProgressStringName.Truncate();
5227 }
5228
SetProgressString(uint32_t aStringIndex)5229 void nsImapProtocol::SetProgressString(uint32_t aStringIndex) {
5230 m_stringIndex = aStringIndex;
5231 MOZ_ASSERT(m_stringIndex <= IMAP_EMPTY_STRING_INDEX);
5232 switch (m_stringIndex) {
5233 case IMAP_HEADERS_STRING_INDEX:
5234 m_progressStringName = "imapReceivingMessageHeaders3";
5235 break;
5236 case IMAP_MESSAGES_STRING_INDEX:
5237 m_progressStringName = "imapFolderReceivingMessageOf3";
5238 break;
5239 case IMAP_FLAGS_STRING_INDEX:
5240 m_progressStringName = "imapReceivingMessageFlags3";
5241 break;
5242 case IMAP_EMPTY_STRING_INDEX:
5243 default:
5244 break;
5245 }
5246 }
5247
ShowProgress()5248 void nsImapProtocol::ShowProgress() {
5249 if (m_imapServerSink && (m_stringIndex != IMAP_EMPTY_STRING_INDEX)) {
5250 nsString progressString;
5251 const char* mailboxName = GetServerStateParser().GetSelectedMailboxName();
5252 nsString unicodeMailboxName;
5253 nsresult rv = CopyFolderNameToUTF16(nsDependentCString(mailboxName),
5254 unicodeMailboxName);
5255 NS_ENSURE_SUCCESS_VOID(rv);
5256
5257 int32_t progressCurrentNumber = ++m_progressCurrentNumber[m_stringIndex];
5258
5259 PercentProgressUpdateEvent(m_progressStringName, unicodeMailboxName,
5260 progressCurrentNumber, m_progressExpectedNumber);
5261 }
5262 }
5263
ProgressEventFunctionUsingName(const char * aMsgName)5264 void nsImapProtocol::ProgressEventFunctionUsingName(const char* aMsgName) {
5265 if (m_imapMailFolderSink && !m_lastProgressStringName.Equals(aMsgName)) {
5266 m_imapMailFolderSink->ProgressStatusString(this, aMsgName, nullptr);
5267 m_lastProgressStringName.Assign(aMsgName);
5268 // who's going to free this? Does ProgressStatusString complete
5269 // synchronously?
5270 }
5271 }
5272
ProgressEventFunctionUsingNameWithString(const char * aMsgName,const char * aExtraInfo)5273 void nsImapProtocol::ProgressEventFunctionUsingNameWithString(
5274 const char* aMsgName, const char* aExtraInfo) {
5275 if (m_imapMailFolderSink) {
5276 nsString unicodeStr;
5277 nsresult rv =
5278 CopyFolderNameToUTF16(nsDependentCString(aExtraInfo), unicodeStr);
5279 if (NS_SUCCEEDED(rv))
5280 m_imapMailFolderSink->ProgressStatusString(this, aMsgName,
5281 unicodeStr.get());
5282 }
5283 }
5284
PercentProgressUpdateEvent(nsACString const & fmtStringName,nsAString const & mailbox,int64_t currentProgress,int64_t maxProgress)5285 void nsImapProtocol::PercentProgressUpdateEvent(nsACString const& fmtStringName,
5286 nsAString const& mailbox,
5287 int64_t currentProgress,
5288 int64_t maxProgress) {
5289 int64_t nowMS = 0;
5290 int32_t percent = (100 * currentProgress) / maxProgress;
5291 if (percent == m_lastPercent)
5292 return; // hasn't changed, right? So just return. Do we need to clear this
5293 // anywhere?
5294
5295 if (percent < 100) // always need to do 100%
5296 {
5297 nowMS = PR_IntervalToMilliseconds(PR_IntervalNow());
5298 if (nowMS - m_lastProgressTime < 750) return;
5299 }
5300
5301 m_lastPercent = percent;
5302 m_lastProgressTime = nowMS;
5303
5304 // set our max progress on the running URL
5305 if (m_runningUrl) {
5306 nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl(do_QueryInterface(m_runningUrl));
5307 mailnewsUrl->SetMaxProgress(maxProgress);
5308 }
5309
5310 if (m_imapMailFolderSink) {
5311 m_imapMailFolderSink->PercentProgress(this, fmtStringName, mailbox,
5312 currentProgress, maxProgress);
5313 }
5314 }
5315
5316 // imap commands issued by the parser
Store(const nsCString & messageList,const char * messageData,bool idsAreUid)5317 void nsImapProtocol::Store(const nsCString& messageList,
5318 const char* messageData, bool idsAreUid) {
5319 // turn messageList back into key array and then back into a message id list,
5320 // but use the flag state to handle ranges correctly.
5321 nsCString messageIdList;
5322 nsTArray<nsMsgKey> msgKeys;
5323 if (idsAreUid) ParseUidString(messageList.get(), msgKeys);
5324
5325 int32_t msgCountLeft = msgKeys.Length();
5326 uint32_t msgsHandled = 0;
5327 do {
5328 nsCString idString;
5329
5330 uint32_t msgsToHandle = msgCountLeft;
5331 if (idsAreUid)
5332 AllocateImapUidString(msgKeys.Elements() + msgsHandled, msgsToHandle,
5333 m_flagState, idString); // 20 * 200
5334 else
5335 idString.Assign(messageList);
5336
5337 msgsHandled += msgsToHandle;
5338 msgCountLeft -= msgsToHandle;
5339
5340 IncrementCommandTagNumber();
5341 const char* formatString;
5342 if (idsAreUid)
5343 formatString = "%s uid store %s %s\015\012";
5344 else
5345 formatString = "%s store %s %s\015\012";
5346
5347 // we might need to close this mailbox after this
5348 m_closeNeededBeforeSelect =
5349 GetDeleteIsMoveToTrash() && (PL_strcasestr(messageData, "\\Deleted"));
5350
5351 const char* commandTag = GetServerCommandTag();
5352 int protocolStringSize = PL_strlen(formatString) + messageList.Length() +
5353 PL_strlen(messageData) + PL_strlen(commandTag) + 1;
5354 char* protocolString = (char*)PR_CALLOC(protocolStringSize);
5355
5356 if (protocolString) {
5357 PR_snprintf(protocolString, // string to create
5358 protocolStringSize, // max size
5359 formatString, // format string
5360 commandTag, // command tag
5361 idString.get(), messageData);
5362
5363 nsresult rv = SendData(protocolString);
5364 if (NS_SUCCEEDED(rv)) {
5365 m_flagChangeCount++;
5366 ParseIMAPandCheckForNewMail(protocolString);
5367 if (GetServerStateParser().LastCommandSuccessful() && CheckNeeded())
5368 Check();
5369 }
5370 PR_Free(protocolString);
5371 } else
5372 HandleMemoryFailure();
5373 } while (msgCountLeft > 0 && !DeathSignalReceived());
5374 }
5375
IssueUserDefinedMsgCommand(const char * command,const char * messageList)5376 void nsImapProtocol::IssueUserDefinedMsgCommand(const char* command,
5377 const char* messageList) {
5378 IncrementCommandTagNumber();
5379
5380 const char* formatString;
5381 formatString = "%s uid %s %s\015\012";
5382
5383 const char* commandTag = GetServerCommandTag();
5384 int protocolStringSize = PL_strlen(formatString) + PL_strlen(messageList) +
5385 PL_strlen(command) + PL_strlen(commandTag) + 1;
5386 char* protocolString = (char*)PR_CALLOC(protocolStringSize);
5387
5388 if (protocolString) {
5389 PR_snprintf(protocolString, // string to create
5390 protocolStringSize, // max size
5391 formatString, // format string
5392 commandTag, // command tag
5393 command, messageList);
5394
5395 nsresult rv = SendData(protocolString);
5396 if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail(protocolString);
5397 PR_Free(protocolString);
5398 } else
5399 HandleMemoryFailure();
5400 }
5401
UidExpunge(const nsCString & messageSet)5402 void nsImapProtocol::UidExpunge(const nsCString& messageSet) {
5403 IncrementCommandTagNumber();
5404 nsCString command(GetServerCommandTag());
5405 command.AppendLiteral(" uid expunge ");
5406 command.Append(messageSet);
5407 command.Append(CRLF);
5408 nsresult rv = SendData(command.get());
5409 if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
5410 }
5411
Expunge()5412 void nsImapProtocol::Expunge() {
5413 uint32_t aclFlags = 0;
5414 if (GetServerStateParser().ServerHasACLCapability() && m_imapMailFolderSink)
5415 m_imapMailFolderSink->GetAclFlags(&aclFlags);
5416
5417 if (aclFlags && !(aclFlags & IMAP_ACL_EXPUNGE_FLAG)) return;
5418 ProgressEventFunctionUsingName("imapStatusExpungingMailbox");
5419
5420 if (gCheckDeletedBeforeExpunge) {
5421 GetServerStateParser().ResetSearchResultSequence();
5422 Search("SEARCH DELETED", false, false);
5423 if (GetServerStateParser().LastCommandSuccessful()) {
5424 nsImapSearchResultIterator* search =
5425 GetServerStateParser().CreateSearchResultIterator();
5426 nsMsgKey key = search->GetNextMessageNumber();
5427 delete search;
5428 if (key == 0) return; // no deleted messages to expunge (bug 235004)
5429 }
5430 }
5431
5432 IncrementCommandTagNumber();
5433 nsAutoCString command(GetServerCommandTag());
5434 command.AppendLiteral(" expunge" CRLF);
5435
5436 nsresult rv = SendData(command.get());
5437 if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
5438 }
5439
HandleMemoryFailure()5440 void nsImapProtocol::HandleMemoryFailure() {
5441 PR_CEnterMonitor(this);
5442 // **** jefft fix me!!!!!! ******
5443 // m_imapThreadIsRunning = false;
5444 // SetConnectionStatus(-1);
5445 PR_CExitMonitor(this);
5446 }
5447
HandleCurrentUrlError()5448 void nsImapProtocol::HandleCurrentUrlError() {
5449 // This is to handle a move/copy failing, especially because the user
5450 // cancelled the password prompt.
5451 (void)m_runningUrl->GetImapAction(&m_imapAction);
5452 if (m_imapAction == nsIImapUrl::nsImapOfflineToOnlineMove ||
5453 m_imapAction == nsIImapUrl::nsImapAppendMsgFromFile ||
5454 m_imapAction == nsIImapUrl::nsImapAppendDraftFromFile) {
5455 if (m_imapMailFolderSink)
5456 m_imapMailFolderSink->OnlineCopyCompleted(
5457 this, ImapOnlineCopyStateType::kFailedCopy);
5458 }
5459 }
5460
StartTLS()5461 void nsImapProtocol::StartTLS() {
5462 IncrementCommandTagNumber();
5463 nsCString tag(GetServerCommandTag());
5464 nsCString command(tag);
5465
5466 command.AppendLiteral(" STARTTLS" CRLF);
5467 nsresult rv = SendData(command.get());
5468 bool ok = false;
5469 if (NS_SUCCEEDED(rv)) {
5470 nsCString expectOkResponse = tag + " OK "_ns;
5471 char* serverResponse = nullptr;
5472 do {
5473 // This reads and discards lines not starting with "<tag> OK " or
5474 // "<tag> BAD " and exits when when either are found. Otherwise, this
5475 // exits on timeout when all lines in the buffer are read causing
5476 // serverResponse to be set null. Usually just "<tag> OK " is present.
5477 serverResponse = CreateNewLineFromSocket();
5478 ok = serverResponse &&
5479 !PL_strncasecmp(serverResponse, expectOkResponse.get(),
5480 expectOkResponse.Length());
5481 if (!ok && serverResponse) {
5482 // Check for possible BAD response, e.g., server not STARTTLS capable.
5483 nsCString expectBadResponse = tag + " BAD "_ns;
5484 if (!PL_strncasecmp(serverResponse, expectBadResponse.get(),
5485 expectBadResponse.Length())) {
5486 PR_Free(serverResponse);
5487 break;
5488 }
5489 }
5490 PR_Free(serverResponse);
5491 } while (serverResponse && !ok);
5492 }
5493 // ok == false implies a "<tag> BAD " response or time out on socket read.
5494 // It could also be due to failure on SendData() above.
5495 GetServerStateParser().SetCommandFailed(!ok);
5496 }
5497
Capability()5498 void nsImapProtocol::Capability() {
5499 ProgressEventFunctionUsingName("imapStatusCheckCompat");
5500 IncrementCommandTagNumber();
5501 nsCString command(GetServerCommandTag());
5502
5503 command.AppendLiteral(" capability" CRLF);
5504
5505 nsresult rv = SendData(command.get());
5506 if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
5507 }
5508
ID()5509 void nsImapProtocol::ID() {
5510 if (!gAppName[0]) return;
5511 IncrementCommandTagNumber();
5512 nsCString command(GetServerCommandTag());
5513 command.AppendLiteral(" ID (\"name\" \"");
5514 command.Append(gAppName);
5515 command.AppendLiteral("\" \"version\" \"");
5516 command.Append(gAppVersion);
5517 command.AppendLiteral("\")" CRLF);
5518
5519 nsresult rv = SendData(command.get());
5520 if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
5521 }
5522
EnableUTF8Accept()5523 void nsImapProtocol::EnableUTF8Accept() {
5524 IncrementCommandTagNumber();
5525 nsCString command(GetServerCommandTag());
5526 command.AppendLiteral(" ENABLE UTF8=ACCEPT" CRLF);
5527
5528 nsresult rv = SendData(command.get());
5529 if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
5530 }
5531
EnableCondStore()5532 void nsImapProtocol::EnableCondStore() {
5533 IncrementCommandTagNumber();
5534 nsCString command(GetServerCommandTag());
5535
5536 command.AppendLiteral(" ENABLE CONDSTORE" CRLF);
5537
5538 nsresult rv = SendData(command.get());
5539 if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
5540 }
5541
StartCompressDeflate()5542 void nsImapProtocol::StartCompressDeflate() {
5543 // only issue a compression request if we haven't already
5544 if (!TestFlag(IMAP_ISSUED_COMPRESS_REQUEST)) {
5545 SetFlag(IMAP_ISSUED_COMPRESS_REQUEST);
5546 IncrementCommandTagNumber();
5547 nsCString command(GetServerCommandTag());
5548
5549 command.AppendLiteral(" COMPRESS DEFLATE" CRLF);
5550
5551 nsresult rv = SendData(command.get());
5552 if (NS_SUCCEEDED(rv)) {
5553 ParseIMAPandCheckForNewMail();
5554 if (GetServerStateParser().LastCommandSuccessful()) {
5555 rv = BeginCompressing();
5556 if (NS_FAILED(rv)) {
5557 Log("CompressDeflate", nullptr, "failed to enable compression");
5558 // we can't use this connection without compression any more, so die
5559 ClearFlag(IMAP_CONNECTION_IS_OPEN);
5560 TellThreadToDie();
5561 SetConnectionStatus(rv);
5562 return;
5563 }
5564 }
5565 }
5566 }
5567 }
5568
BeginCompressing()5569 nsresult nsImapProtocol::BeginCompressing() {
5570 // wrap the streams in compression layers that compress or decompress
5571 // all traffic.
5572 RefPtr<nsMsgCompressIStream> new_in = new nsMsgCompressIStream();
5573 if (!new_in) return NS_ERROR_OUT_OF_MEMORY;
5574
5575 nsresult rv = new_in->InitInputStream(m_inputStream);
5576 NS_ENSURE_SUCCESS(rv, rv);
5577
5578 m_inputStream = new_in;
5579
5580 RefPtr<nsMsgCompressOStream> new_out = new nsMsgCompressOStream();
5581 if (!new_out) return NS_ERROR_OUT_OF_MEMORY;
5582
5583 rv = new_out->InitOutputStream(m_outputStream);
5584 NS_ENSURE_SUCCESS(rv, rv);
5585
5586 m_outputStream = new_out;
5587 return rv;
5588 }
5589
Language()5590 void nsImapProtocol::Language() {
5591 // only issue the language request if we haven't done so already...
5592 if (!TestFlag(IMAP_ISSUED_LANGUAGE_REQUEST)) {
5593 SetFlag(IMAP_ISSUED_LANGUAGE_REQUEST);
5594 ProgressEventFunctionUsingName("imapStatusCheckCompat");
5595 IncrementCommandTagNumber();
5596 nsCString command(GetServerCommandTag());
5597
5598 // extract the desired language attribute from prefs
5599 nsresult rv = NS_OK;
5600
5601 // we need to parse out the first language out of this comma separated
5602 // list.... i.e if we have en,ja we only want to send en to the server.
5603 if (mAcceptLanguages.get()) {
5604 nsAutoCString extractedLanguage;
5605 LossyCopyUTF16toASCII(mAcceptLanguages, extractedLanguage);
5606 int32_t pos = extractedLanguage.FindChar(',');
5607 if (pos > 0) // we have a comma separated list of languages...
5608 extractedLanguage.SetLength(pos); // truncate everything after the
5609 // first comma (including the comma)
5610
5611 if (extractedLanguage.IsEmpty()) return;
5612
5613 command.AppendLiteral(" LANGUAGE ");
5614 command.Append(extractedLanguage);
5615 command.Append(CRLF);
5616
5617 rv = SendData(command.get());
5618 if (NS_SUCCEEDED(rv))
5619 ParseIMAPandCheckForNewMail(nullptr, true /* ignore bad or no result from the server for this command */);
5620 }
5621 }
5622 }
5623
EscapeUserNamePasswordString(const char * strToEscape,nsCString * resultStr)5624 void nsImapProtocol::EscapeUserNamePasswordString(const char* strToEscape,
5625 nsCString* resultStr) {
5626 if (strToEscape) {
5627 uint32_t i = 0;
5628 uint32_t escapeStrlen = strlen(strToEscape);
5629 for (i = 0; i < escapeStrlen; i++) {
5630 if (strToEscape[i] == '\\' || strToEscape[i] == '\"') {
5631 resultStr->Append('\\');
5632 }
5633 resultStr->Append(strToEscape[i]);
5634 }
5635 }
5636 }
5637
InitPrefAuthMethods(int32_t authMethodPrefValue,nsIMsgIncomingServer * aServer)5638 void nsImapProtocol::InitPrefAuthMethods(int32_t authMethodPrefValue,
5639 nsIMsgIncomingServer* aServer) {
5640 // for m_prefAuthMethods, using the same flags as server capabilities.
5641 switch (authMethodPrefValue) {
5642 case nsMsgAuthMethod::none:
5643 m_prefAuthMethods = kHasAuthNoneCapability;
5644 break;
5645 case nsMsgAuthMethod::old:
5646 m_prefAuthMethods = kHasAuthOldLoginCapability;
5647 break;
5648 case nsMsgAuthMethod::passwordCleartext:
5649 m_prefAuthMethods = kHasAuthOldLoginCapability | kHasAuthLoginCapability |
5650 kHasAuthPlainCapability;
5651 break;
5652 case nsMsgAuthMethod::passwordEncrypted:
5653 m_prefAuthMethods = kHasCRAMCapability;
5654 break;
5655 case nsMsgAuthMethod::NTLM:
5656 m_prefAuthMethods = kHasAuthNTLMCapability | kHasAuthMSNCapability;
5657 break;
5658 case nsMsgAuthMethod::GSSAPI:
5659 m_prefAuthMethods = kHasAuthGssApiCapability;
5660 break;
5661 case nsMsgAuthMethod::External:
5662 m_prefAuthMethods = kHasAuthExternalCapability;
5663 break;
5664 case nsMsgAuthMethod::secure:
5665 m_prefAuthMethods = kHasCRAMCapability | kHasAuthGssApiCapability |
5666 kHasAuthNTLMCapability | kHasAuthMSNCapability;
5667 break;
5668 case nsMsgAuthMethod::OAuth2:
5669 m_prefAuthMethods = kHasXOAuth2Capability;
5670 break;
5671 default:
5672 NS_ASSERTION(false, "IMAP: authMethod pref invalid");
5673 MOZ_LOG(IMAP, LogLevel::Error,
5674 ("IMAP: bad pref authMethod = %d", authMethodPrefValue));
5675 // fall to any
5676 [[fallthrough]];
5677 case nsMsgAuthMethod::anything:
5678 m_prefAuthMethods = kHasAuthOldLoginCapability | kHasAuthLoginCapability |
5679 kHasAuthPlainCapability | kHasCRAMCapability |
5680 kHasAuthGssApiCapability | kHasAuthNTLMCapability |
5681 kHasAuthMSNCapability | kHasAuthExternalCapability |
5682 kHasXOAuth2Capability;
5683 break;
5684 }
5685
5686 if (m_prefAuthMethods & kHasXOAuth2Capability) {
5687 mOAuth2Support = new mozilla::mailnews::OAuth2ThreadHelper(aServer);
5688 if (!mOAuth2Support || !mOAuth2Support->SupportsOAuth2()) {
5689 // Disable OAuth2 support if we don't have the prefs installed.
5690 m_prefAuthMethods &= ~kHasXOAuth2Capability;
5691 mOAuth2Support = nullptr;
5692 MOZ_LOG(IMAP, LogLevel::Warning,
5693 ("IMAP: no OAuth2 support for this server."));
5694 }
5695 }
5696 }
5697
5698 /**
5699 * Changes m_currentAuthMethod to pick the best remaining one
5700 * which is allowed by server and prefs and not marked failed.
5701 * The order of preference and trying of auth methods is encoded here.
5702 */
ChooseAuthMethod()5703 nsresult nsImapProtocol::ChooseAuthMethod() {
5704 eIMAPCapabilityFlags serverCaps = GetServerStateParser().GetCapabilityFlag();
5705 eIMAPCapabilityFlags availCaps =
5706 serverCaps & m_prefAuthMethods & ~m_failedAuthMethods;
5707
5708 MOZ_LOG(IMAP, LogLevel::Debug,
5709 ("IMAP auth: server caps 0x%" PRIx64 ", pref 0x%" PRIx64
5710 ", failed 0x%" PRIx64 ", avail caps 0x%" PRIx64,
5711 serverCaps, m_prefAuthMethods, m_failedAuthMethods, availCaps));
5712 // clang-format off
5713 MOZ_LOG(IMAP, LogLevel::Debug,
5714 ("(GSSAPI = 0x%" PRIx64 ", CRAM = 0x%" PRIx64 ", NTLM = 0x%" PRIx64
5715 ", MSN = 0x%" PRIx64 ", PLAIN = 0x%" PRIx64 ", LOGIN = 0x%" PRIx64
5716 ", old-style IMAP login = 0x%" PRIx64
5717 ", auth external IMAP login = 0x%" PRIx64 ", OAUTH2 = 0x%" PRIx64 ")",
5718 kHasAuthGssApiCapability, kHasCRAMCapability, kHasAuthNTLMCapability,
5719 kHasAuthMSNCapability, kHasAuthPlainCapability, kHasAuthLoginCapability,
5720 kHasAuthOldLoginCapability, kHasAuthExternalCapability,
5721 kHasXOAuth2Capability));
5722 // clang-format on
5723
5724 if (kHasAuthExternalCapability & availCaps)
5725 m_currentAuthMethod = kHasAuthExternalCapability;
5726 else if (kHasAuthGssApiCapability & availCaps)
5727 m_currentAuthMethod = kHasAuthGssApiCapability;
5728 else if (kHasCRAMCapability & availCaps)
5729 m_currentAuthMethod = kHasCRAMCapability;
5730 else if (kHasAuthNTLMCapability & availCaps)
5731 m_currentAuthMethod = kHasAuthNTLMCapability;
5732 else if (kHasAuthMSNCapability & availCaps)
5733 m_currentAuthMethod = kHasAuthMSNCapability;
5734 else if (kHasXOAuth2Capability & availCaps)
5735 m_currentAuthMethod = kHasXOAuth2Capability;
5736 else if (kHasAuthPlainCapability & availCaps)
5737 m_currentAuthMethod = kHasAuthPlainCapability;
5738 else if (kHasAuthLoginCapability & availCaps)
5739 m_currentAuthMethod = kHasAuthLoginCapability;
5740 else if (kHasAuthOldLoginCapability & availCaps)
5741 m_currentAuthMethod = kHasAuthOldLoginCapability;
5742 else {
5743 MOZ_LOG(IMAP, LogLevel::Debug, ("No remaining auth method"));
5744 m_currentAuthMethod = kCapabilityUndefined;
5745 return NS_ERROR_FAILURE;
5746 }
5747 MOZ_LOG(IMAP, LogLevel::Debug,
5748 ("Trying auth method 0x%" PRIx64, m_currentAuthMethod));
5749 return NS_OK;
5750 }
5751
MarkAuthMethodAsFailed(eIMAPCapabilityFlags failedAuthMethod)5752 void nsImapProtocol::MarkAuthMethodAsFailed(
5753 eIMAPCapabilityFlags failedAuthMethod) {
5754 MOZ_LOG(IMAP, LogLevel::Debug,
5755 ("Marking auth method 0x%" PRIx64 " failed", failedAuthMethod));
5756 m_failedAuthMethods |= failedAuthMethod;
5757 }
5758
5759 /**
5760 * Start over, trying all auth methods again
5761 */
ResetAuthMethods()5762 void nsImapProtocol::ResetAuthMethods() {
5763 MOZ_LOG(IMAP, LogLevel::Debug, ("resetting (failed) auth methods"));
5764 m_currentAuthMethod = kCapabilityUndefined;
5765 m_failedAuthMethods = 0;
5766 }
5767
SendDataParseIMAPandCheckForNewMail(const char * aData,const char * aCommand)5768 nsresult nsImapProtocol::SendDataParseIMAPandCheckForNewMail(
5769 const char* aData, const char* aCommand) {
5770 nsresult rv;
5771 bool isResend = false;
5772 while (true) {
5773 // Send authentication string (true: suppress logging the string).
5774 rv = SendData(aData, true);
5775 if (NS_FAILED(rv)) break;
5776 ParseIMAPandCheckForNewMail(aCommand);
5777 if (!GetServerStateParser().WaitingForMoreClientInput()) break;
5778
5779 // The server is asking for the authentication string again. So we send
5780 // the same string again although we know that it might be rejected again.
5781 // We do that to get a firm authentication failure instead of a resend
5782 // request. That keeps things in order before failing authentication and
5783 // trying another method if capable.
5784 if (isResend) {
5785 rv = NS_ERROR_FAILURE;
5786 break;
5787 }
5788 isResend = true;
5789 }
5790
5791 return rv;
5792 }
5793
ClientID()5794 nsresult nsImapProtocol::ClientID() {
5795 IncrementCommandTagNumber();
5796 nsCString command(GetServerCommandTag());
5797 command += " CLIENTID UUID ";
5798 command += m_clientId;
5799 command += CRLF;
5800 nsresult rv = SendDataParseIMAPandCheckForNewMail(command.get(), nullptr);
5801 NS_ENSURE_SUCCESS(rv, rv);
5802 if (!GetServerStateParser().LastCommandSuccessful()) {
5803 return NS_ERROR_FAILURE;
5804 }
5805 return NS_OK;
5806 }
5807
AuthLogin(const char * userName,const nsString & aPassword,eIMAPCapabilityFlag flag)5808 nsresult nsImapProtocol::AuthLogin(const char* userName,
5809 const nsString& aPassword,
5810 eIMAPCapabilityFlag flag) {
5811 ProgressEventFunctionUsingName("imapStatusSendingAuthLogin");
5812 IncrementCommandTagNumber();
5813
5814 char* currentCommand = nullptr;
5815 nsresult rv;
5816 NS_ConvertUTF16toUTF8 password(aPassword);
5817 MOZ_LOG(IMAP, LogLevel::Debug,
5818 ("IMAP: trying auth method 0x%" PRIx64, m_currentAuthMethod));
5819
5820 if (flag & kHasAuthExternalCapability) {
5821 char* base64UserName = PL_Base64Encode(userName, strlen(userName), nullptr);
5822 nsAutoCString command(GetServerCommandTag());
5823 command.AppendLiteral(" authenticate EXTERNAL ");
5824 command.Append(base64UserName);
5825 command.Append(CRLF);
5826 PR_Free(base64UserName);
5827 rv = SendData(command.get());
5828 ParseIMAPandCheckForNewMail();
5829 nsImapServerResponseParser& parser = GetServerStateParser();
5830 if (parser.LastCommandSuccessful()) return NS_OK;
5831 parser.SetCapabilityFlag(parser.GetCapabilityFlag() &
5832 ~kHasAuthExternalCapability);
5833 } else if (flag & kHasCRAMCapability) {
5834 NS_ENSURE_TRUE(m_imapServerSink, NS_ERROR_NULL_POINTER);
5835 MOZ_LOG(IMAP, LogLevel::Debug, ("MD5 auth"));
5836 // inform the server that we want to begin a CRAM authentication
5837 // procedure...
5838 nsAutoCString command(GetServerCommandTag());
5839 command.AppendLiteral(" authenticate CRAM-MD5" CRLF);
5840 rv = SendData(command.get());
5841 NS_ENSURE_SUCCESS(rv, rv);
5842 ParseIMAPandCheckForNewMail();
5843 if (GetServerStateParser().LastCommandSuccessful()) {
5844 char* digest = nullptr;
5845 char* cramDigest = GetServerStateParser().fAuthChallenge;
5846 char* decodedChallenge =
5847 PL_Base64Decode(cramDigest, strlen(cramDigest), nullptr);
5848 rv = m_imapServerSink->CramMD5Hash(decodedChallenge, password.get(),
5849 &digest);
5850 PR_Free(decodedChallenge);
5851 NS_ENSURE_SUCCESS(rv, rv);
5852 NS_ENSURE_TRUE(digest, NS_ERROR_NULL_POINTER);
5853 // The encoded digest is the hexadecimal representation of
5854 // DIGEST_LENGTH characters, so it will be twice that length.
5855 nsAutoCStringN<2 * DIGEST_LENGTH> encodedDigest;
5856
5857 for (uint32_t j = 0; j < DIGEST_LENGTH; j++) {
5858 char hexVal[3];
5859 PR_snprintf(hexVal, 3, "%.2x", 0x0ff & (unsigned short)(digest[j]));
5860 encodedDigest.Append(hexVal);
5861 }
5862
5863 PR_snprintf(m_dataOutputBuf, OUTPUT_BUFFER_SIZE, "%.255s %s", userName,
5864 encodedDigest.get());
5865 char* base64Str =
5866 PL_Base64Encode(m_dataOutputBuf, strlen(m_dataOutputBuf), nullptr);
5867 PR_snprintf(m_dataOutputBuf, OUTPUT_BUFFER_SIZE, "%s" CRLF, base64Str);
5868 PR_Free(base64Str);
5869 PR_Free(digest);
5870 rv = SendData(m_dataOutputBuf);
5871 NS_ENSURE_SUCCESS(rv, rv);
5872 ParseIMAPandCheckForNewMail(command.get());
5873 }
5874 } // if CRAM response was received
5875 else if (flag & kHasAuthGssApiCapability) {
5876 MOZ_LOG(IMAP, LogLevel::Debug, ("MD5 auth"));
5877
5878 // Only try GSSAPI once - if it fails, its going to be because we don't
5879 // have valid credentials
5880 // MarkAuthMethodAsFailed(kHasAuthGssApiCapability);
5881
5882 // We do step1 first, so we don't try GSSAPI against a server which
5883 // we can't get credentials for.
5884 nsAutoCString response;
5885
5886 nsAutoCString service("imap@");
5887 service.Append(m_realHostName);
5888 rv = DoGSSAPIStep1(service.get(), userName, response);
5889 NS_ENSURE_SUCCESS(rv, rv);
5890
5891 nsAutoCString command(GetServerCommandTag());
5892 command.AppendLiteral(" authenticate GSSAPI" CRLF);
5893 rv = SendData(command.get());
5894 NS_ENSURE_SUCCESS(rv, rv);
5895
5896 ParseIMAPandCheckForNewMail("AUTH GSSAPI");
5897 if (GetServerStateParser().LastCommandSuccessful()) {
5898 response += CRLF;
5899 rv = SendData(response.get());
5900 NS_ENSURE_SUCCESS(rv, rv);
5901 ParseIMAPandCheckForNewMail(command.get());
5902 nsresult gssrv = NS_OK;
5903
5904 while (GetServerStateParser().LastCommandSuccessful() &&
5905 NS_SUCCEEDED(gssrv) && gssrv != NS_SUCCESS_AUTH_FINISHED) {
5906 nsCString challengeStr(GetServerStateParser().fAuthChallenge);
5907 gssrv = DoGSSAPIStep2(challengeStr, response);
5908 if (NS_SUCCEEDED(gssrv)) {
5909 response += CRLF;
5910 rv = SendData(response.get());
5911 } else
5912 rv = SendData("*" CRLF);
5913
5914 NS_ENSURE_SUCCESS(rv, rv);
5915 ParseIMAPandCheckForNewMail(command.get());
5916 }
5917 // TODO: whether it worked or not is shown by LastCommandSuccessful(), not
5918 // gssrv, right?
5919 }
5920 } else if (flag & (kHasAuthNTLMCapability | kHasAuthMSNCapability)) {
5921 MOZ_LOG(IMAP, LogLevel::Debug, ("NTLM auth"));
5922 nsAutoCString command(GetServerCommandTag());
5923 command.Append((flag & kHasAuthNTLMCapability) ? " authenticate NTLM" CRLF
5924 : " authenticate MSN" CRLF);
5925 rv = SendData(command.get());
5926 ParseIMAPandCheckForNewMail(
5927 "AUTH NTLM"); // this just waits for ntlm step 1
5928 if (GetServerStateParser().LastCommandSuccessful()) {
5929 nsAutoCString cmd;
5930 rv = DoNtlmStep1(nsDependentCString(userName), aPassword, cmd);
5931 NS_ENSURE_SUCCESS(rv, rv);
5932 cmd += CRLF;
5933 rv = SendData(cmd.get());
5934 NS_ENSURE_SUCCESS(rv, rv);
5935 ParseIMAPandCheckForNewMail(command.get());
5936 if (GetServerStateParser().LastCommandSuccessful()) {
5937 nsCString challengeStr(GetServerStateParser().fAuthChallenge);
5938 nsCString response;
5939 rv = DoNtlmStep2(challengeStr, response);
5940 NS_ENSURE_SUCCESS(rv, rv);
5941 response += CRLF;
5942 rv = SendData(response.get());
5943 ParseIMAPandCheckForNewMail(command.get());
5944 }
5945 }
5946 } else if (flag & kHasAuthPlainCapability) {
5947 MOZ_LOG(IMAP, LogLevel::Debug, ("PLAIN auth"));
5948 PR_snprintf(m_dataOutputBuf, OUTPUT_BUFFER_SIZE,
5949 "%s authenticate PLAIN" CRLF, GetServerCommandTag());
5950 rv = SendData(m_dataOutputBuf);
5951 NS_ENSURE_SUCCESS(rv, rv);
5952 currentCommand = PL_strdup(
5953 m_dataOutputBuf); /* StrAllocCopy(currentCommand, GetOutputBuffer()); */
5954 ParseIMAPandCheckForNewMail();
5955 if (GetServerStateParser().LastCommandSuccessful()) {
5956 // RFC 4616
5957 char plain_string[513];
5958 memset(plain_string, 0, 513);
5959 PR_snprintf(&plain_string[1], 256, "%.255s", userName);
5960 uint32_t len = std::min<uint32_t>(PL_strlen(userName), 255u) +
5961 2; // We include two <NUL> characters.
5962 PR_snprintf(&plain_string[len], 256, "%.255s", password.get());
5963 len += std::min<uint32_t>(password.Length(), 255u);
5964 char* base64Str = PL_Base64Encode(plain_string, len, nullptr);
5965 PR_snprintf(m_dataOutputBuf, OUTPUT_BUFFER_SIZE, "%s" CRLF, base64Str);
5966 PR_Free(base64Str);
5967
5968 rv = SendDataParseIMAPandCheckForNewMail(m_dataOutputBuf, currentCommand);
5969 } // if the last command succeeded
5970 } // if auth plain capability
5971 else if (flag & kHasAuthLoginCapability) {
5972 MOZ_LOG(IMAP, LogLevel::Debug, ("LOGIN auth"));
5973 PR_snprintf(m_dataOutputBuf, OUTPUT_BUFFER_SIZE,
5974 "%s authenticate LOGIN" CRLF, GetServerCommandTag());
5975 rv = SendData(m_dataOutputBuf);
5976 NS_ENSURE_SUCCESS(rv, rv);
5977 currentCommand = PL_strdup(m_dataOutputBuf);
5978 ParseIMAPandCheckForNewMail();
5979
5980 if (GetServerStateParser().LastCommandSuccessful()) {
5981 char* base64Str = PL_Base64Encode(
5982 userName, std::min<uint32_t>(PL_strlen(userName), 255u), nullptr);
5983 PR_snprintf(m_dataOutputBuf, OUTPUT_BUFFER_SIZE, "%s" CRLF, base64Str);
5984 PR_Free(base64Str);
5985 rv = SendData(m_dataOutputBuf, true /* suppress logging */);
5986 if (NS_SUCCEEDED(rv)) {
5987 ParseIMAPandCheckForNewMail(currentCommand);
5988 if (GetServerStateParser().LastCommandSuccessful()) {
5989 base64Str = PL_Base64Encode(
5990 password.get(), std::min<uint32_t>(password.Length(), 255u),
5991 nullptr);
5992 PR_snprintf(m_dataOutputBuf, OUTPUT_BUFFER_SIZE, "%s" CRLF,
5993 base64Str);
5994 PR_Free(base64Str);
5995 rv = SendData(m_dataOutputBuf, true /* suppress logging */);
5996 if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail(currentCommand);
5997 } // if last command successful
5998 } // if last command successful
5999 } // if last command successful
6000 } // if has auth login capability
6001 else if (flag & kHasAuthOldLoginCapability) {
6002 MOZ_LOG(IMAP, LogLevel::Debug, ("old-style auth"));
6003 ProgressEventFunctionUsingName("imapStatusSendingLogin");
6004 IncrementCommandTagNumber();
6005 nsCString command(GetServerCommandTag());
6006 nsAutoCString escapedUserName;
6007 command.AppendLiteral(" login \"");
6008 EscapeUserNamePasswordString(userName, &escapedUserName);
6009 command.Append(escapedUserName);
6010 command.AppendLiteral("\" \"");
6011
6012 // if the password contains a \, login will fail
6013 // turn foo\bar into foo\\bar
6014 nsAutoCString correctedPassword;
6015 // We're assuming old style login doesn't want UTF-8
6016 EscapeUserNamePasswordString(NS_LossyConvertUTF16toASCII(aPassword).get(),
6017 &correctedPassword);
6018 command.Append(correctedPassword);
6019 command.AppendLiteral("\"" CRLF);
6020 rv = SendData(command.get(), true /* suppress logging */);
6021 NS_ENSURE_SUCCESS(rv, rv);
6022 ParseIMAPandCheckForNewMail();
6023 } else if (flag & kHasXOAuth2Capability) {
6024 MOZ_LOG(IMAP, LogLevel::Debug, ("XOAUTH2 auth"));
6025
6026 // Get the XOAuth2 base64 string.
6027 NS_ASSERTION(mOAuth2Support,
6028 "What are we doing here without OAuth2 helper?");
6029 if (!mOAuth2Support) return NS_ERROR_UNEXPECTED;
6030 nsAutoCString base64Str;
6031 mOAuth2Support->GetXOAuth2String(base64Str);
6032 mOAuth2Support = nullptr; // Its purpose has been served.
6033 if (base64Str.IsEmpty()) {
6034 MOZ_LOG(IMAP, LogLevel::Debug, ("OAuth2 failed"));
6035 return NS_ERROR_FAILURE;
6036 }
6037
6038 // Send the data on the network.
6039 nsAutoCString command(GetServerCommandTag());
6040 command += " AUTHENTICATE XOAUTH2 ";
6041 command += base64Str;
6042 command += CRLF;
6043
6044 rv = SendDataParseIMAPandCheckForNewMail(command.get(), nullptr);
6045 } else if (flag & kHasAuthNoneCapability) {
6046 // TODO What to do? "login <username>" like POP?
6047 return NS_ERROR_NOT_IMPLEMENTED;
6048 } else {
6049 MOZ_LOG(IMAP, LogLevel::Error, ("flags param has no auth scheme selected"));
6050 return NS_ERROR_ILLEGAL_VALUE;
6051 }
6052
6053 PR_Free(currentCommand);
6054 NS_ENSURE_SUCCESS(rv, rv);
6055 return GetServerStateParser().LastCommandSuccessful() ? NS_OK
6056 : NS_ERROR_FAILURE;
6057 }
6058
OnLSubFolders()6059 void nsImapProtocol::OnLSubFolders() {
6060 // **** use to find out whether Drafts, Sent, & Templates folder
6061 // exists or not even the user didn't subscribe to it
6062 char* mailboxName = OnCreateServerSourceFolderPathString();
6063 if (mailboxName) {
6064 ProgressEventFunctionUsingName("imapStatusLookingForMailbox");
6065 IncrementCommandTagNumber();
6066 PR_snprintf(m_dataOutputBuf, OUTPUT_BUFFER_SIZE, "%s list \"\" \"%s\"" CRLF,
6067 GetServerCommandTag(), mailboxName);
6068 nsresult rv = SendData(m_dataOutputBuf);
6069 if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
6070 PR_Free(mailboxName);
6071 } else {
6072 HandleMemoryFailure();
6073 }
6074 }
6075
OnAppendMsgFromFile()6076 void nsImapProtocol::OnAppendMsgFromFile() {
6077 nsCOMPtr<nsIFile> file;
6078 nsresult rv = NS_OK;
6079 rv = m_runningUrl->GetMsgFile(getter_AddRefs(file));
6080 if (NS_SUCCEEDED(rv) && file) {
6081 char* mailboxName = OnCreateServerSourceFolderPathString();
6082 if (mailboxName) {
6083 imapMessageFlagsType flagsToSet = 0;
6084 uint32_t msgFlags = 0;
6085 PRTime date = 0;
6086 nsCString keywords;
6087 if (m_imapMessageSink)
6088 m_imapMessageSink->GetCurMoveCopyMessageInfo(m_runningUrl, &date,
6089 keywords, &msgFlags);
6090
6091 if (msgFlags & nsMsgMessageFlags::Read) flagsToSet |= kImapMsgSeenFlag;
6092 if (msgFlags & nsMsgMessageFlags::MDNReportSent)
6093 flagsToSet |= kImapMsgMDNSentFlag;
6094 // convert msg flag label (0xE000000) to imap flag label (0x0E00)
6095 if (msgFlags & nsMsgMessageFlags::Labels)
6096 flagsToSet |= (msgFlags & nsMsgMessageFlags::Labels) >> 16;
6097 if (msgFlags & nsMsgMessageFlags::Marked)
6098 flagsToSet |= kImapMsgFlaggedFlag;
6099 if (msgFlags & nsMsgMessageFlags::Replied)
6100 flagsToSet |= kImapMsgAnsweredFlag;
6101 if (msgFlags & nsMsgMessageFlags::Forwarded)
6102 flagsToSet |= kImapMsgForwardedFlag;
6103
6104 // If the message copied was a draft, flag it as such
6105 nsImapAction imapAction;
6106 rv = m_runningUrl->GetImapAction(&imapAction);
6107 if (NS_SUCCEEDED(rv) &&
6108 (imapAction == nsIImapUrl::nsImapAppendDraftFromFile))
6109 flagsToSet |= kImapMsgDraftFlag;
6110 UploadMessageFromFile(file, mailboxName, date, flagsToSet, keywords);
6111 PR_Free(mailboxName);
6112 } else {
6113 HandleMemoryFailure();
6114 }
6115 }
6116 }
6117
UploadMessageFromFile(nsIFile * file,const char * mailboxName,PRTime date,imapMessageFlagsType flags,nsCString & keywords)6118 void nsImapProtocol::UploadMessageFromFile(nsIFile* file,
6119 const char* mailboxName, PRTime date,
6120 imapMessageFlagsType flags,
6121 nsCString& keywords) {
6122 if (!file || !mailboxName) return;
6123 IncrementCommandTagNumber();
6124
6125 int64_t fileSize = 0;
6126 int64_t totalSize;
6127 uint32_t readCount;
6128 char* dataBuffer = nullptr;
6129 nsCString command(GetServerCommandTag());
6130 nsCString escapedName;
6131 CreateEscapedMailboxName(mailboxName, escapedName);
6132 nsresult rv;
6133 bool eof = false;
6134 nsCString flagString;
6135
6136 nsCOMPtr<nsIInputStream> fileInputStream;
6137
6138 if (!escapedName.IsEmpty()) {
6139 command.AppendLiteral(" append \"");
6140 command.Append(escapedName);
6141 command.Append('"');
6142 if (flags || keywords.Length()) {
6143 command.AppendLiteral(" (");
6144
6145 if (flags) {
6146 SetupMessageFlagsString(flagString, flags,
6147 GetServerStateParser().SupportsUserFlags());
6148 command.Append(flagString);
6149 }
6150 if (keywords.Length()) {
6151 if (flags) command.Append(' ');
6152 command.Append(keywords);
6153 }
6154 command.Append(')');
6155 }
6156
6157 // date should never be 0, but just in case...
6158 if (date) {
6159 /* Use PR_FormatTimeUSEnglish() to format the date in US English format,
6160 then figure out what our local GMT offset is, and append it (since
6161 PR_FormatTimeUSEnglish() can't do that.) Generate four digit years as
6162 per RFC 1123 (superseding RFC 822.)
6163 */
6164 char szDateTime[64];
6165 char dateStr[100];
6166 PRExplodedTime exploded;
6167 PR_ExplodeTime(date, PR_LocalTimeParameters, &exploded);
6168 PR_FormatTimeUSEnglish(szDateTime, sizeof(szDateTime),
6169 "%d-%b-%Y %H:%M:%S", &exploded);
6170 PRExplodedTime now;
6171 PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &now);
6172 int gmtoffset =
6173 (now.tm_params.tp_gmt_offset + now.tm_params.tp_dst_offset) / 60;
6174 PR_snprintf(dateStr, sizeof(dateStr), " \"%s %c%02d%02d\"", szDateTime,
6175 (gmtoffset >= 0 ? '+' : '-'),
6176 ((gmtoffset >= 0 ? gmtoffset : -gmtoffset) / 60),
6177 ((gmtoffset >= 0 ? gmtoffset : -gmtoffset) % 60));
6178
6179 command.Append(dateStr);
6180 }
6181 if (m_allowUTF8Accept)
6182 command.AppendLiteral(" UTF8 (~{");
6183 else
6184 command.AppendLiteral(" {");
6185
6186 dataBuffer = (char*)PR_CALLOC(COPY_BUFFER_SIZE + 1);
6187 if (!dataBuffer) goto done;
6188 rv = file->GetFileSize(&fileSize);
6189 NS_ASSERTION(fileSize, "got empty file in UploadMessageFromFile");
6190 if (NS_FAILED(rv) || !fileSize) goto done;
6191 rv = NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream), file);
6192 if (NS_FAILED(rv) || !fileInputStream) goto done;
6193 command.AppendInt((int32_t)fileSize);
6194
6195 // Set useLiteralPlus to true if server has capability LITERAL+ and
6196 // LITERAL+ usage is enabled in the config editor,
6197 // i.e., "mail.imap.use_literal_plus" = true.
6198 bool useLiteralPlus =
6199 (GetServerStateParser().GetCapabilityFlag() & kLiteralPlusCapability) &&
6200 gUseLiteralPlus;
6201 if (useLiteralPlus)
6202 command.AppendLiteral("+}" CRLF);
6203 else
6204 command.AppendLiteral("}" CRLF);
6205
6206 rv = SendData(command.get());
6207 if (NS_FAILED(rv)) goto done;
6208
6209 if (!useLiteralPlus) {
6210 ParseIMAPandCheckForNewMail();
6211 if (!GetServerStateParser().LastCommandSuccessful()) goto done;
6212 }
6213
6214 totalSize = fileSize;
6215 readCount = 0;
6216 while (NS_SUCCEEDED(rv) && !eof && totalSize > 0) {
6217 rv = fileInputStream->Read(dataBuffer, COPY_BUFFER_SIZE, &readCount);
6218 if (NS_SUCCEEDED(rv) && !readCount) rv = NS_ERROR_FAILURE;
6219
6220 if (NS_SUCCEEDED(rv)) {
6221 NS_ASSERTION(readCount <= (uint32_t)totalSize,
6222 "got more bytes than there should be");
6223 dataBuffer[readCount] = 0;
6224 rv = SendData(dataBuffer);
6225 totalSize -= readCount;
6226 PercentProgressUpdateEvent(""_ns, u""_ns, fileSize - totalSize,
6227 fileSize);
6228 }
6229 }
6230 if (NS_SUCCEEDED(rv)) { // complete the append
6231 if (m_allowUTF8Accept)
6232 rv = SendData(")" CRLF);
6233 else
6234 rv = SendData(CRLF);
6235 ParseIMAPandCheckForNewMail(command.get());
6236
6237 nsImapAction imapAction;
6238 m_runningUrl->GetImapAction(&imapAction);
6239
6240 if (GetServerStateParser().LastCommandSuccessful() &&
6241 (imapAction == nsIImapUrl::nsImapAppendDraftFromFile ||
6242 imapAction == nsIImapUrl::nsImapAppendMsgFromFile)) {
6243 if (GetServerStateParser().GetCapabilityFlag() & kUidplusCapability) {
6244 nsMsgKey newKey = GetServerStateParser().CurrentResponseUID();
6245 if (m_imapMailFolderSink)
6246 m_imapMailFolderSink->SetAppendMsgUid(newKey, m_runningUrl);
6247
6248 // Courier imap server seems to have problems with recently
6249 // appended messages. Noop seems to clear its confusion.
6250 if (FolderIsSelected(mailboxName)) Noop();
6251
6252 nsCString oldMsgId;
6253 rv = m_runningUrl->GetListOfMessageIds(oldMsgId);
6254 if (NS_SUCCEEDED(rv) && !oldMsgId.IsEmpty()) {
6255 bool idsAreUids = true;
6256 m_runningUrl->MessageIdsAreUids(&idsAreUids);
6257 Store(oldMsgId, "+FLAGS (\\Deleted)", idsAreUids);
6258 UidExpunge(oldMsgId);
6259 }
6260 }
6261 // for non UIDPLUS servers this code used to check for
6262 // imapAction==nsIImapUrl::nsImapAppendMsgFromFile, which meant we'd get
6263 // into this code whenever sending a message, as well as when copying
6264 // messages to an imap folder from local folders or an other imap
6265 // server. This made sending a message slow when there was a large sent
6266 // folder. I don't believe this code worked anyway.
6267 // *** code me to search for the newly appended message
6268 else if (m_imapMailFolderSink &&
6269 imapAction == nsIImapUrl::nsImapAppendDraftFromFile) {
6270 // go to selected state
6271 nsCString messageId;
6272 rv = m_imapMailFolderSink->GetMessageId(m_runningUrl, messageId);
6273 if (NS_SUCCEEDED(rv) && !messageId.IsEmpty() &&
6274 GetServerStateParser().LastCommandSuccessful()) {
6275 // if the appended to folder isn't selected in the connection,
6276 // select it.
6277 if (!FolderIsSelected(mailboxName))
6278 SelectMailbox(mailboxName);
6279 else
6280 Noop(); // See if this makes SEARCH work on the newly appended
6281 // msg.
6282
6283 if (GetServerStateParser().LastCommandSuccessful()) {
6284 command = "SEARCH UNDELETED HEADER Message-ID ";
6285 command.Append(messageId);
6286
6287 // Clean up result sequence before issuing the cmd.
6288 GetServerStateParser().ResetSearchResultSequence();
6289
6290 Search(command.get(), true, false);
6291 if (GetServerStateParser().LastCommandSuccessful()) {
6292 nsMsgKey newkey = nsMsgKey_None;
6293 nsImapSearchResultIterator* searchResult =
6294 GetServerStateParser().CreateSearchResultIterator();
6295 newkey = searchResult->GetNextMessageNumber();
6296 delete searchResult;
6297 if (newkey != nsMsgKey_None)
6298 m_imapMailFolderSink->SetAppendMsgUid(newkey, m_runningUrl);
6299 }
6300 }
6301 }
6302 }
6303 }
6304 }
6305 }
6306 done:
6307 PR_Free(dataBuffer);
6308 if (fileInputStream) fileInputStream->Close();
6309 }
6310
6311 // caller must free using PR_Free
OnCreateServerSourceFolderPathString()6312 char* nsImapProtocol::OnCreateServerSourceFolderPathString() {
6313 char* sourceMailbox = nullptr;
6314 char hierarchyDelimiter = 0;
6315 char onlineDelimiter = 0;
6316 m_runningUrl->GetOnlineSubDirSeparator(&hierarchyDelimiter);
6317 if (m_imapMailFolderSink)
6318 m_imapMailFolderSink->GetOnlineDelimiter(&onlineDelimiter);
6319
6320 if (onlineDelimiter != kOnlineHierarchySeparatorUnknown &&
6321 onlineDelimiter != hierarchyDelimiter)
6322 m_runningUrl->SetOnlineSubDirSeparator(onlineDelimiter);
6323
6324 m_runningUrl->CreateServerSourceFolderPathString(&sourceMailbox);
6325
6326 return sourceMailbox;
6327 }
6328
6329 // caller must free using PR_Free, safe to call from ui thread
GetFolderPathString()6330 char* nsImapProtocol::GetFolderPathString() {
6331 char* sourceMailbox = nullptr;
6332 char onlineSubDirDelimiter = 0;
6333 char hierarchyDelimiter = 0;
6334 nsCOMPtr<nsIMsgFolder> msgFolder;
6335
6336 m_runningUrl->GetOnlineSubDirSeparator(&onlineSubDirDelimiter);
6337 nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningUrl);
6338 mailnewsUrl->GetFolder(getter_AddRefs(msgFolder));
6339 if (msgFolder) {
6340 nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(msgFolder);
6341 if (imapFolder) {
6342 imapFolder->GetHierarchyDelimiter(&hierarchyDelimiter);
6343 if (hierarchyDelimiter != kOnlineHierarchySeparatorUnknown &&
6344 onlineSubDirDelimiter != hierarchyDelimiter)
6345 m_runningUrl->SetOnlineSubDirSeparator(hierarchyDelimiter);
6346 }
6347 }
6348 m_runningUrl->CreateServerSourceFolderPathString(&sourceMailbox);
6349
6350 return sourceMailbox;
6351 }
6352
CreateServerSourceFolderPathString(char ** result)6353 nsresult nsImapProtocol::CreateServerSourceFolderPathString(char** result) {
6354 NS_ENSURE_ARG(result);
6355 *result = OnCreateServerSourceFolderPathString();
6356 return (*result) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
6357 }
6358
6359 // caller must free using PR_Free
OnCreateServerDestinationFolderPathString()6360 char* nsImapProtocol::OnCreateServerDestinationFolderPathString() {
6361 char* destinationMailbox = nullptr;
6362 char hierarchyDelimiter = 0;
6363 char onlineDelimiter = 0;
6364 m_runningUrl->GetOnlineSubDirSeparator(&hierarchyDelimiter);
6365 if (m_imapMailFolderSink)
6366 m_imapMailFolderSink->GetOnlineDelimiter(&onlineDelimiter);
6367 if (onlineDelimiter != kOnlineHierarchySeparatorUnknown &&
6368 onlineDelimiter != hierarchyDelimiter)
6369 m_runningUrl->SetOnlineSubDirSeparator(onlineDelimiter);
6370
6371 m_runningUrl->CreateServerDestinationFolderPathString(&destinationMailbox);
6372
6373 return destinationMailbox;
6374 }
6375
OnCreateFolder(const char * aSourceMailbox)6376 void nsImapProtocol::OnCreateFolder(const char* aSourceMailbox) {
6377 bool created = CreateMailboxRespectingSubscriptions(aSourceMailbox);
6378 if (created) {
6379 m_hierarchyNameState = kListingForCreate;
6380 nsCString mailboxWODelim(aSourceMailbox);
6381 RemoveHierarchyDelimiter(mailboxWODelim);
6382 List(mailboxWODelim.get(), false);
6383 m_hierarchyNameState = kNoOperationInProgress;
6384 } else
6385 FolderNotCreated(aSourceMailbox);
6386 }
6387
OnEnsureExistsFolder(const char * aSourceMailbox)6388 void nsImapProtocol::OnEnsureExistsFolder(const char* aSourceMailbox) {
6389 // We need to handle the following edge case where the destination server
6390 // wasn't authenticated when the folder name was encoded in
6391 // `EnsureFolderExists()'. In this case we always get MUTF-7. Here we are
6392 // authenticated and can rely on `m_allowUTF8Accept'. If the folder appears
6393 // to be MUTF-7 and we need UTF-8, we re-encode it. If it's not ASCII, it
6394 // must be already correct in UTF-8. And if it was ASCII to start with, it
6395 // doesn't matter that we MUTF-7 decode and UTF-8 re-encode.
6396
6397 // `aSourceMailbox' is a path with hierarchy delimiters possibly. To determine
6398 // if the edge case is in effect, we only want to check the leaf node for
6399 // ASCII and this is only necessary when `m_allowUTF8Accept' is true.
6400
6401 // `fullPath' is modified below if leaf re-encoding is necessary and it must
6402 // be defined here at top level so it stays in scope.
6403 nsAutoCString fullPath(aSourceMailbox);
6404
6405 if (m_allowUTF8Accept) {
6406 char onlineDirSeparator = kOnlineHierarchySeparatorUnknown;
6407 m_runningUrl->GetOnlineSubDirSeparator(&onlineDirSeparator);
6408
6409 int32_t leafStart = fullPath.RFindChar(onlineDirSeparator);
6410 nsAutoCString leafName;
6411 if (leafStart == kNotFound) {
6412 // This is a root level mailbox
6413 leafName = fullPath;
6414 fullPath.SetLength(0);
6415 } else {
6416 leafName = Substring(fullPath, leafStart + 1);
6417 fullPath.SetLength(leafStart + 1);
6418 }
6419
6420 if (NS_IsAscii(leafName.get())) {
6421 MOZ_LOG(IMAP, LogLevel::Debug,
6422 ("re-encode leaf of mailbox %s to UTF-8", aSourceMailbox));
6423 nsAutoString utf16LeafName;
6424 CopyMUTF7toUTF16(leafName, utf16LeafName);
6425
6426 // Convert UTF-16 to UTF-8 to create the folder.
6427 nsAutoCString utf8LeafName;
6428 CopyUTF16toUTF8(utf16LeafName, utf8LeafName);
6429 fullPath.Append(utf8LeafName);
6430 aSourceMailbox = fullPath.get();
6431 MOZ_LOG(IMAP, LogLevel::Debug,
6432 ("re-encoded leaf of mailbox %s to UTF-8", aSourceMailbox));
6433 }
6434 }
6435 List(aSourceMailbox, false); // how to tell if that succeeded?
6436
6437 // try converting aSourceMailbox to canonical format
6438 nsImapNamespace* nsForMailbox = nullptr;
6439 m_hostSessionList->GetNamespaceForMailboxForHost(
6440 GetImapServerKey(), aSourceMailbox, nsForMailbox);
6441 // NS_ASSERTION (nsForMailbox, "Oops .. null nsForMailbox");
6442
6443 nsCString name;
6444
6445 if (nsForMailbox)
6446 m_runningUrl->AllocateCanonicalPath(
6447 aSourceMailbox, nsForMailbox->GetDelimiter(), getter_Copies(name));
6448 else
6449 m_runningUrl->AllocateCanonicalPath(
6450 aSourceMailbox, kOnlineHierarchySeparatorUnknown, getter_Copies(name));
6451
6452 bool exists = false;
6453 if (m_imapServerSink) m_imapServerSink->FolderVerifiedOnline(name, &exists);
6454
6455 if (exists) {
6456 Subscribe(aSourceMailbox);
6457 } else {
6458 bool created = CreateMailboxRespectingSubscriptions(aSourceMailbox);
6459 if (created) {
6460 List(aSourceMailbox, false);
6461 }
6462 }
6463 if (!GetServerStateParser().LastCommandSuccessful())
6464 FolderNotCreated(aSourceMailbox);
6465 }
6466
OnSubscribe(const char * sourceMailbox)6467 void nsImapProtocol::OnSubscribe(const char* sourceMailbox) {
6468 Subscribe(sourceMailbox);
6469 }
6470
OnUnsubscribe(const char * sourceMailbox)6471 void nsImapProtocol::OnUnsubscribe(const char* sourceMailbox) {
6472 // When we try to auto-unsubscribe from \Noselect folders,
6473 // some servers report errors if we were already unsubscribed
6474 // from them.
6475 bool lastReportingErrors = GetServerStateParser().GetReportingErrors();
6476 GetServerStateParser().SetReportingErrors(false);
6477 Unsubscribe(sourceMailbox);
6478 GetServerStateParser().SetReportingErrors(lastReportingErrors);
6479 }
6480
RefreshACLForFolderIfNecessary(const char * mailboxName)6481 void nsImapProtocol::RefreshACLForFolderIfNecessary(const char* mailboxName) {
6482 if (GetServerStateParser().ServerHasACLCapability()) {
6483 if (!m_folderNeedsACLRefreshed && m_imapMailFolderSink)
6484 m_imapMailFolderSink->GetFolderNeedsACLListed(&m_folderNeedsACLRefreshed);
6485 if (m_folderNeedsACLRefreshed) {
6486 RefreshACLForFolder(mailboxName);
6487 m_folderNeedsACLRefreshed = false;
6488 }
6489 }
6490 }
6491
RefreshACLForFolder(const char * mailboxName)6492 void nsImapProtocol::RefreshACLForFolder(const char* mailboxName) {
6493 nsImapNamespace* ns = nullptr;
6494 m_hostSessionList->GetNamespaceForMailboxForHost(GetImapServerKey(),
6495 mailboxName, ns);
6496 if (ns) {
6497 switch (ns->GetType()) {
6498 case kPersonalNamespace:
6499 // It's a personal folder, most likely.
6500 // I find it hard to imagine a server that supports ACL that doesn't
6501 // support NAMESPACE, so most likely we KNOW that this is a personal,
6502 // rather than the default, namespace.
6503
6504 // First, clear what we have.
6505 ClearAllFolderRights();
6506 // Now, get the new one.
6507 GetMyRightsForFolder(mailboxName);
6508 if (m_imapMailFolderSink) {
6509 uint32_t aclFlags = 0;
6510 if (NS_SUCCEEDED(m_imapMailFolderSink->GetAclFlags(&aclFlags)) &&
6511 aclFlags & IMAP_ACL_ADMINISTER_FLAG)
6512 GetACLForFolder(mailboxName);
6513 }
6514
6515 // We're all done, refresh the icon/flags for this folder
6516 RefreshFolderACLView(mailboxName, ns);
6517 break;
6518 default:
6519 // We know it's a public folder or other user's folder.
6520 // We only want our own rights
6521
6522 // First, clear what we have
6523 ClearAllFolderRights();
6524 // Now, get the new one.
6525 GetMyRightsForFolder(mailboxName);
6526 // We're all done, refresh the icon/flags for this folder
6527 RefreshFolderACLView(mailboxName, ns);
6528 break;
6529 }
6530 } else {
6531 // no namespace, not even default... can this happen?
6532 NS_ASSERTION(false, "couldn't get namespace");
6533 }
6534 }
6535
RefreshFolderACLView(const char * mailboxName,nsImapNamespace * nsForMailbox)6536 void nsImapProtocol::RefreshFolderACLView(const char* mailboxName,
6537 nsImapNamespace* nsForMailbox) {
6538 nsCString canonicalMailboxName;
6539
6540 if (nsForMailbox)
6541 m_runningUrl->AllocateCanonicalPath(mailboxName,
6542 nsForMailbox->GetDelimiter(),
6543 getter_Copies(canonicalMailboxName));
6544 else
6545 m_runningUrl->AllocateCanonicalPath(mailboxName,
6546 kOnlineHierarchySeparatorUnknown,
6547 getter_Copies(canonicalMailboxName));
6548
6549 if (m_imapServerSink)
6550 m_imapServerSink->RefreshFolderRights(canonicalMailboxName);
6551 }
6552
GetACLForFolder(const char * mailboxName)6553 void nsImapProtocol::GetACLForFolder(const char* mailboxName) {
6554 IncrementCommandTagNumber();
6555
6556 nsCString command(GetServerCommandTag());
6557 nsCString escapedName;
6558 CreateEscapedMailboxName(mailboxName, escapedName);
6559 command.AppendLiteral(" getacl \"");
6560 command.Append(escapedName);
6561 command.AppendLiteral("\"" CRLF);
6562
6563 nsresult rv = SendData(command.get());
6564 if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
6565 }
6566
OnRefreshAllACLs()6567 void nsImapProtocol::OnRefreshAllACLs() {
6568 m_hierarchyNameState = kListingForInfoOnly;
6569 nsIMAPMailboxInfo* mb = NULL;
6570
6571 // This will fill in the list
6572 List("*", true);
6573
6574 int32_t total = m_listedMailboxList.Length(), count = 0;
6575 GetServerStateParser().SetReportingErrors(false);
6576 for (int32_t i = 0; i < total; i++) {
6577 mb = m_listedMailboxList.ElementAt(i);
6578 if (mb) // paranoia
6579 {
6580 char* onlineName = nullptr;
6581 m_runningUrl->AllocateServerPath(
6582 PromiseFlatCString(mb->GetMailboxName()).get(), mb->GetDelimiter(),
6583 &onlineName);
6584 if (onlineName) {
6585 RefreshACLForFolder(onlineName);
6586 free(onlineName);
6587 }
6588 PercentProgressUpdateEvent(""_ns, u""_ns, count, total);
6589 delete mb;
6590 count++;
6591 }
6592 }
6593 m_listedMailboxList.Clear();
6594
6595 PercentProgressUpdateEvent(""_ns, u""_ns, 100, 100);
6596 GetServerStateParser().SetReportingErrors(true);
6597 m_hierarchyNameState = kNoOperationInProgress;
6598 }
6599
6600 // any state commands
Logout(bool shuttingDown,bool waitForResponse)6601 void nsImapProtocol::Logout(bool shuttingDown /* = false */,
6602 bool waitForResponse /* = true */) {
6603 if (!shuttingDown) ProgressEventFunctionUsingName("imapStatusLoggingOut");
6604
6605 /******************************************************************
6606 * due to the undo functionality we cannot issule a close when logout; there
6607 * is no way to do an undo if the message has been permanently expunge
6608 * jt - 07/12/1999
6609
6610 bool closeNeeded = GetServerStateParser().GetIMAPstate() ==
6611 nsImapServerResponseParser::kFolderSelected;
6612
6613 if (closeNeeded && GetDeleteIsMoveToTrash())
6614 Close();
6615 ********************/
6616
6617 IncrementCommandTagNumber();
6618
6619 nsCString command(GetServerCommandTag());
6620
6621 command.AppendLiteral(" logout" CRLF);
6622
6623 nsresult rv = SendData(command.get());
6624 if (m_transport && shuttingDown)
6625 m_transport->SetTimeout(nsISocketTransport::TIMEOUT_READ_WRITE, 5);
6626 // the socket may be dead before we read the response, so drop it.
6627 if (NS_SUCCEEDED(rv) && waitForResponse) ParseIMAPandCheckForNewMail();
6628 }
6629
Noop()6630 void nsImapProtocol::Noop() {
6631 // ProgressUpdateEvent("noop...");
6632 IncrementCommandTagNumber();
6633 nsCString command(GetServerCommandTag());
6634
6635 command.AppendLiteral(" noop" CRLF);
6636
6637 nsresult rv = SendData(command.get());
6638 if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
6639 }
6640
XServerInfo()6641 void nsImapProtocol::XServerInfo() {
6642 ProgressEventFunctionUsingName("imapGettingServerInfo");
6643 IncrementCommandTagNumber();
6644 nsCString command(GetServerCommandTag());
6645
6646 command.AppendLiteral(
6647 " XSERVERINFO MANAGEACCOUNTURL MANAGELISTSURL MANAGEFILTERSURL" CRLF);
6648
6649 nsresult rv = SendData(command.get());
6650 if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
6651 }
6652
Netscape()6653 void nsImapProtocol::Netscape() {
6654 ProgressEventFunctionUsingName("imapGettingServerInfo");
6655 IncrementCommandTagNumber();
6656
6657 nsCString command(GetServerCommandTag());
6658
6659 command.AppendLiteral(" netscape" CRLF);
6660
6661 nsresult rv = SendData(command.get());
6662 if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
6663 }
6664
XMailboxInfo(const char * mailboxName)6665 void nsImapProtocol::XMailboxInfo(const char* mailboxName) {
6666 ProgressEventFunctionUsingName("imapGettingMailboxInfo");
6667 IncrementCommandTagNumber();
6668 nsCString command(GetServerCommandTag());
6669
6670 command.AppendLiteral(" XMAILBOXINFO \"");
6671 command.Append(mailboxName);
6672 command.AppendLiteral("\" MANAGEURL POSTURL" CRLF);
6673
6674 nsresult rv = SendData(command.get());
6675 if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
6676 }
6677
Namespace()6678 void nsImapProtocol::Namespace() {
6679 IncrementCommandTagNumber();
6680
6681 nsCString command(GetServerCommandTag());
6682 command.AppendLiteral(" namespace" CRLF);
6683
6684 nsresult rv = SendData(command.get());
6685 if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
6686 }
6687
MailboxData()6688 void nsImapProtocol::MailboxData() {
6689 IncrementCommandTagNumber();
6690
6691 nsCString command(GetServerCommandTag());
6692 command.AppendLiteral(" mailboxdata" CRLF);
6693
6694 nsresult rv = SendData(command.get());
6695 if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
6696 }
6697
GetMyRightsForFolder(const char * mailboxName)6698 void nsImapProtocol::GetMyRightsForFolder(const char* mailboxName) {
6699 IncrementCommandTagNumber();
6700
6701 nsCString command(GetServerCommandTag());
6702 nsCString escapedName;
6703 CreateEscapedMailboxName(mailboxName, escapedName);
6704
6705 if (MailboxIsNoSelectMailbox(escapedName.get()))
6706 return; // Don't issue myrights on Noselect folder
6707
6708 command.AppendLiteral(" myrights \"");
6709 command.Append(escapedName);
6710 command.AppendLiteral("\"" CRLF);
6711
6712 nsresult rv = SendData(command.get());
6713 if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
6714 }
6715
FolderIsSelected(const char * mailboxName)6716 bool nsImapProtocol::FolderIsSelected(const char* mailboxName) {
6717 return (GetServerStateParser().GetIMAPstate() ==
6718 nsImapServerResponseParser::kFolderSelected &&
6719 GetServerStateParser().GetSelectedMailboxName() &&
6720 PL_strcmp(GetServerStateParser().GetSelectedMailboxName(),
6721 mailboxName) == 0);
6722 }
6723
OnStatusForFolder(const char * mailboxName)6724 void nsImapProtocol::OnStatusForFolder(const char* mailboxName) {
6725 if (FolderIsSelected(mailboxName)) {
6726 int32_t prevNumMessages = GetServerStateParser().NumberOfMessages();
6727 Noop();
6728 // OnNewIdleMessages will cause the ui thread to update the folder
6729 if (m_imapMailFolderSink &&
6730 (GetServerStateParser().NumberOfRecentMessages() ||
6731 prevNumMessages != GetServerStateParser().NumberOfMessages()))
6732 m_imapMailFolderSink->OnNewIdleMessages();
6733 return;
6734 }
6735
6736 IncrementCommandTagNumber();
6737
6738 nsAutoCString command(GetServerCommandTag());
6739 nsCString escapedName;
6740 CreateEscapedMailboxName(mailboxName, escapedName);
6741
6742 command.AppendLiteral(" STATUS \"");
6743 command.Append(escapedName);
6744 command.AppendLiteral("\" (UIDNEXT MESSAGES UNSEEN RECENT)" CRLF);
6745
6746 nsresult rv = SendData(command.get());
6747 if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
6748
6749 if (GetServerStateParser().LastCommandSuccessful()) {
6750 RefPtr<nsImapMailboxSpec> new_spec =
6751 GetServerStateParser().CreateCurrentMailboxSpec(mailboxName);
6752 if (new_spec && m_imapMailFolderSink)
6753 m_imapMailFolderSink->UpdateImapMailboxStatus(this, new_spec);
6754 }
6755 }
6756
OnListFolder(const char * aSourceMailbox,bool aBool)6757 void nsImapProtocol::OnListFolder(const char* aSourceMailbox, bool aBool) {
6758 List(aSourceMailbox, aBool);
6759 }
6760
6761 // Returns true if the mailbox is a NoSelect mailbox.
6762 // If we don't know about it, returns false.
MailboxIsNoSelectMailbox(const char * mailboxName)6763 bool nsImapProtocol::MailboxIsNoSelectMailbox(const char* mailboxName) {
6764 bool rv = false;
6765
6766 nsImapNamespace* nsForMailbox = nullptr;
6767 m_hostSessionList->GetNamespaceForMailboxForHost(GetImapServerKey(),
6768 mailboxName, nsForMailbox);
6769 // NS_ASSERTION (nsForMailbox, "Oops .. null nsForMailbox");
6770
6771 nsCString name;
6772
6773 if (nsForMailbox)
6774 m_runningUrl->AllocateCanonicalPath(
6775 mailboxName, nsForMailbox->GetDelimiter(), getter_Copies(name));
6776 else
6777 m_runningUrl->AllocateCanonicalPath(
6778 mailboxName, kOnlineHierarchySeparatorUnknown, getter_Copies(name));
6779
6780 if (name.IsEmpty()) return false;
6781
6782 NS_ASSERTION(m_imapServerSink,
6783 "unexpected, no imap server sink, see bug #194335");
6784 if (m_imapServerSink) m_imapServerSink->FolderIsNoSelect(name, &rv);
6785 return rv;
6786 }
6787
SetFolderAdminUrl(const char * mailboxName)6788 nsresult nsImapProtocol::SetFolderAdminUrl(const char* mailboxName) {
6789 nsresult rv =
6790 NS_ERROR_NULL_POINTER; // if m_imapServerSink is null, rv will be this.
6791
6792 nsImapNamespace* nsForMailbox = nullptr;
6793 m_hostSessionList->GetNamespaceForMailboxForHost(GetImapServerKey(),
6794 mailboxName, nsForMailbox);
6795
6796 nsCString name;
6797
6798 if (nsForMailbox)
6799 m_runningUrl->AllocateCanonicalPath(
6800 mailboxName, nsForMailbox->GetDelimiter(), getter_Copies(name));
6801 else
6802 m_runningUrl->AllocateCanonicalPath(
6803 mailboxName, kOnlineHierarchySeparatorUnknown, getter_Copies(name));
6804
6805 if (m_imapServerSink)
6806 rv = m_imapServerSink->SetFolderAdminURL(
6807 name, nsDependentCString(GetServerStateParser().GetManageFolderUrl()));
6808 return rv;
6809 }
6810 // returns true is the delete succeeded (regardless of subscription changes)
DeleteMailboxRespectingSubscriptions(const char * mailboxName)6811 bool nsImapProtocol::DeleteMailboxRespectingSubscriptions(
6812 const char* mailboxName) {
6813 bool rv = true;
6814 if (!MailboxIsNoSelectMailbox(mailboxName)) {
6815 // Only try to delete it if it really exists
6816 DeleteMailbox(mailboxName);
6817 rv = GetServerStateParser().LastCommandSuccessful();
6818 }
6819
6820 // We can unsubscribe even if the mailbox doesn't exist.
6821 if (rv && m_autoUnsubscribe) // auto-unsubscribe is on
6822 {
6823 bool reportingErrors = GetServerStateParser().GetReportingErrors();
6824 GetServerStateParser().SetReportingErrors(false);
6825 Unsubscribe(mailboxName);
6826 GetServerStateParser().SetReportingErrors(reportingErrors);
6827 }
6828 return (rv);
6829 }
6830
6831 // returns true is the rename succeeded (regardless of subscription changes)
6832 // reallyRename tells us if we should really do the rename (true) or if we
6833 // should just move subscriptions (false)
RenameMailboxRespectingSubscriptions(const char * existingName,const char * newName,bool reallyRename)6834 bool nsImapProtocol::RenameMailboxRespectingSubscriptions(
6835 const char* existingName, const char* newName, bool reallyRename) {
6836 bool rv = true;
6837 if (reallyRename && !MailboxIsNoSelectMailbox(existingName)) {
6838 RenameMailbox(existingName, newName);
6839 rv = GetServerStateParser().LastCommandSuccessful();
6840 }
6841
6842 if (rv) {
6843 if (m_autoSubscribe) // if auto-subscribe is on
6844 {
6845 bool reportingErrors = GetServerStateParser().GetReportingErrors();
6846 GetServerStateParser().SetReportingErrors(false);
6847 Subscribe(newName);
6848 GetServerStateParser().SetReportingErrors(reportingErrors);
6849 }
6850 if (m_autoUnsubscribe) // if auto-unsubscribe is on
6851 {
6852 bool reportingErrors = GetServerStateParser().GetReportingErrors();
6853 GetServerStateParser().SetReportingErrors(false);
6854 Unsubscribe(existingName);
6855 GetServerStateParser().SetReportingErrors(reportingErrors);
6856 }
6857 }
6858 return (rv);
6859 }
6860
RenameHierarchyByHand(const char * oldParentMailboxName,const char * newParentMailboxName)6861 bool nsImapProtocol::RenameHierarchyByHand(const char* oldParentMailboxName,
6862 const char* newParentMailboxName) {
6863 bool renameSucceeded = true;
6864 char onlineDirSeparator = kOnlineHierarchySeparatorUnknown;
6865 m_deletableChildren = new nsTArray<char*>();
6866
6867 bool nonHierarchicalRename =
6868 ((GetServerStateParser().GetCapabilityFlag() & kNoHierarchyRename) ||
6869 MailboxIsNoSelectMailbox(oldParentMailboxName));
6870
6871 if (m_deletableChildren) {
6872 m_hierarchyNameState = kDeleteSubFoldersInProgress;
6873 nsImapNamespace* ns = nullptr;
6874 m_hostSessionList->GetNamespaceForMailboxForHost(GetImapServerKey(),
6875 oldParentMailboxName,
6876 ns); // for delimiter
6877 if (!ns) {
6878 if (!PL_strcasecmp(oldParentMailboxName, "INBOX"))
6879 m_hostSessionList->GetDefaultNamespaceOfTypeForHost(
6880 GetImapServerKey(), kPersonalNamespace, ns);
6881 }
6882 if (ns) {
6883 nsCString pattern(oldParentMailboxName);
6884 pattern += ns->GetDelimiter();
6885 pattern += "*";
6886 bool isUsingSubscription = false;
6887 m_hostSessionList->GetHostIsUsingSubscription(GetImapServerKey(),
6888 isUsingSubscription);
6889
6890 if (isUsingSubscription)
6891 Lsub(pattern.get(), false);
6892 else
6893 List(pattern.get(), false);
6894 }
6895 m_hierarchyNameState = kNoOperationInProgress;
6896
6897 if (GetServerStateParser().LastCommandSuccessful())
6898 renameSucceeded = // rename this, and move subscriptions
6899 RenameMailboxRespectingSubscriptions(oldParentMailboxName,
6900 newParentMailboxName, true);
6901
6902 size_t numberToDelete = m_deletableChildren->Length();
6903 size_t childIndex;
6904
6905 for (childIndex = 0; (childIndex < numberToDelete) && renameSucceeded;
6906 childIndex++) {
6907 char* currentName = m_deletableChildren->ElementAt(childIndex);
6908 if (!currentName) {
6909 renameSucceeded = false;
6910 break;
6911 }
6912 char* serverName = nullptr;
6913 m_runningUrl->AllocateServerPath(currentName, onlineDirSeparator,
6914 &serverName);
6915 PR_FREEIF(currentName);
6916 currentName = serverName;
6917
6918 // calculate the new name and do the rename
6919 nsCString newChildName(newParentMailboxName);
6920 newChildName += (currentName + PL_strlen(oldParentMailboxName));
6921 // Pass in 'nonHierarchicalRename' to determine if we should really
6922 // rename, or just move subscriptions.
6923 renameSucceeded = RenameMailboxRespectingSubscriptions(
6924 currentName, newChildName.get(), nonHierarchicalRename);
6925 PR_FREEIF(currentName);
6926 }
6927
6928 delete m_deletableChildren;
6929 m_deletableChildren = nullptr;
6930 }
6931
6932 return renameSucceeded;
6933 }
6934
DeleteSubFolders(const char * selectedMailbox,bool & aDeleteSelf)6935 bool nsImapProtocol::DeleteSubFolders(const char* selectedMailbox,
6936 bool& aDeleteSelf) {
6937 bool deleteSucceeded = true;
6938 m_deletableChildren = new nsTArray<char*>();
6939
6940 if (m_deletableChildren) {
6941 bool folderDeleted = false;
6942
6943 m_hierarchyNameState = kDeleteSubFoldersInProgress;
6944 nsCString pattern(selectedMailbox);
6945 char onlineDirSeparator = kOnlineHierarchySeparatorUnknown;
6946 m_runningUrl->GetOnlineSubDirSeparator(&onlineDirSeparator);
6947 pattern.Append(onlineDirSeparator);
6948 pattern.Append('*');
6949
6950 if (!pattern.IsEmpty()) {
6951 List(pattern.get(), false);
6952 }
6953 m_hierarchyNameState = kNoOperationInProgress;
6954
6955 // this should be a short list so perform a sequential search for the
6956 // longest name mailbox. Deleting the longest first will hopefully
6957 // prevent the server from having problems about deleting parents
6958 // ** jt - why? I don't understand this.
6959 size_t numberToDelete = m_deletableChildren->Length();
6960 size_t outerIndex, innerIndex;
6961
6962 // intelligently decide if myself(either plain format or following the
6963 // dir-separator) is in the sub-folder list
6964 bool folderInSubfolderList = false; // For Performance
6965 char* selectedMailboxDir = nullptr;
6966 {
6967 int32_t length = strlen(selectedMailbox);
6968 selectedMailboxDir = (char*)PR_MALLOC(length + 2);
6969 if (selectedMailboxDir) // only do the intelligent test if there is
6970 // enough memory
6971 {
6972 strcpy(selectedMailboxDir, selectedMailbox);
6973 selectedMailboxDir[length] = onlineDirSeparator;
6974 selectedMailboxDir[length + 1] = '\0';
6975 size_t i;
6976 for (i = 0; i < numberToDelete && !folderInSubfolderList; i++) {
6977 char* currentName = m_deletableChildren->ElementAt(i);
6978 if (!strcmp(currentName, selectedMailbox) ||
6979 !strcmp(currentName, selectedMailboxDir))
6980 folderInSubfolderList = true;
6981 }
6982 }
6983 }
6984
6985 deleteSucceeded = GetServerStateParser().LastCommandSuccessful();
6986 for (outerIndex = 0; (outerIndex < numberToDelete) && deleteSucceeded;
6987 outerIndex++) {
6988 char* longestName = nullptr;
6989 size_t longestIndex = 0; // fix bogus warning by initializing
6990 for (innerIndex = 0; innerIndex < m_deletableChildren->Length();
6991 innerIndex++) {
6992 char* currentName = m_deletableChildren->ElementAt(innerIndex);
6993 if (!longestName || strlen(longestName) < strlen(currentName)) {
6994 longestName = currentName;
6995 longestIndex = innerIndex;
6996 }
6997 }
6998 if (longestName) {
6999 char* serverName = nullptr;
7000
7001 m_deletableChildren->RemoveElementAt(longestIndex);
7002 m_runningUrl->AllocateServerPath(longestName, onlineDirSeparator,
7003 &serverName);
7004 PR_FREEIF(longestName);
7005 longestName = serverName;
7006 }
7007
7008 // some imap servers include the selectedMailbox in the list of
7009 // subfolders of the selectedMailbox. Check for this so we don't
7010 // delete the selectedMailbox (usually the trash and doing an
7011 // empty trash)
7012 // The Cyrus imap server ignores the "INBOX.Trash" constraining
7013 // string passed to the list command. Be defensive and make sure
7014 // we only delete children of the trash
7015 if (longestName && strcmp(selectedMailbox, longestName) &&
7016 !strncmp(selectedMailbox, longestName, strlen(selectedMailbox))) {
7017 if (selectedMailboxDir &&
7018 !strcmp(selectedMailboxDir, longestName)) // just myself
7019 {
7020 if (aDeleteSelf) {
7021 bool deleted = DeleteMailboxRespectingSubscriptions(longestName);
7022 if (deleted) FolderDeleted(longestName);
7023 folderDeleted = deleted;
7024 deleteSucceeded = deleted;
7025 }
7026 } else {
7027 if (m_imapServerSink)
7028 m_imapServerSink->ResetServerConnection(
7029 nsDependentCString(longestName));
7030 bool deleted = false;
7031 if (folderInSubfolderList) // for performance
7032 {
7033 nsTArray<char*>* pDeletableChildren = m_deletableChildren;
7034 m_deletableChildren = nullptr;
7035 bool folderDeleted = true;
7036 deleted = DeleteSubFolders(longestName, folderDeleted);
7037 // longestName may have subfolder list including itself
7038 if (!folderDeleted) {
7039 if (deleted)
7040 deleted = DeleteMailboxRespectingSubscriptions(longestName);
7041 if (deleted) FolderDeleted(longestName);
7042 }
7043 m_deletableChildren = pDeletableChildren;
7044 } else {
7045 deleted = DeleteMailboxRespectingSubscriptions(longestName);
7046 if (deleted) FolderDeleted(longestName);
7047 }
7048 deleteSucceeded = deleted;
7049 }
7050 }
7051 PR_FREEIF(longestName);
7052 }
7053
7054 aDeleteSelf = folderDeleted; // feedback if myself is deleted
7055 PR_Free(selectedMailboxDir);
7056
7057 delete m_deletableChildren;
7058 m_deletableChildren = nullptr;
7059 }
7060 return deleteSucceeded;
7061 }
7062
FolderDeleted(const char * mailboxName)7063 void nsImapProtocol::FolderDeleted(const char* mailboxName) {
7064 char onlineDelimiter = kOnlineHierarchySeparatorUnknown;
7065 nsCString orphanedMailboxName;
7066
7067 if (mailboxName) {
7068 m_runningUrl->AllocateCanonicalPath(mailboxName, onlineDelimiter,
7069 getter_Copies(orphanedMailboxName));
7070 if (m_imapServerSink)
7071 m_imapServerSink->OnlineFolderDelete(orphanedMailboxName);
7072 }
7073 }
7074
FolderNotCreated(const char * folderName)7075 void nsImapProtocol::FolderNotCreated(const char* folderName) {
7076 if (folderName && m_imapServerSink)
7077 m_imapServerSink->OnlineFolderCreateFailed(nsDependentCString(folderName));
7078 }
7079
FolderRenamed(const char * oldName,const char * newName)7080 void nsImapProtocol::FolderRenamed(const char* oldName, const char* newName) {
7081 char onlineDelimiter = kOnlineHierarchySeparatorUnknown;
7082
7083 if ((m_hierarchyNameState == kNoOperationInProgress) ||
7084 (m_hierarchyNameState == kListingForInfoAndDiscovery))
7085
7086 {
7087 nsCString canonicalOldName, canonicalNewName;
7088 m_runningUrl->AllocateCanonicalPath(oldName, onlineDelimiter,
7089 getter_Copies(canonicalOldName));
7090 m_runningUrl->AllocateCanonicalPath(newName, onlineDelimiter,
7091 getter_Copies(canonicalNewName));
7092 AutoProxyReleaseMsgWindow msgWindow;
7093 GetMsgWindow(getter_AddRefs(msgWindow));
7094 m_imapServerSink->OnlineFolderRename(msgWindow, canonicalOldName,
7095 canonicalNewName);
7096 }
7097 }
7098
OnDeleteFolder(const char * sourceMailbox)7099 void nsImapProtocol::OnDeleteFolder(const char* sourceMailbox) {
7100 // intelligently delete the folder
7101 bool folderDeleted = true;
7102 bool deleted = DeleteSubFolders(sourceMailbox, folderDeleted);
7103 if (!folderDeleted) {
7104 if (deleted) deleted = DeleteMailboxRespectingSubscriptions(sourceMailbox);
7105 if (deleted) FolderDeleted(sourceMailbox);
7106 }
7107 }
7108
RemoveMsgsAndExpunge()7109 void nsImapProtocol::RemoveMsgsAndExpunge() {
7110 uint32_t numberOfMessages = GetServerStateParser().NumberOfMessages();
7111 if (numberOfMessages) {
7112 // Remove all msgs and expunge the folder (ie, compact it).
7113 Store("1:*"_ns, "+FLAGS.SILENT (\\Deleted)",
7114 false); // use sequence #'s
7115 if (GetServerStateParser().LastCommandSuccessful()) Expunge();
7116 }
7117 }
7118
DeleteFolderAndMsgs(const char * sourceMailbox)7119 void nsImapProtocol::DeleteFolderAndMsgs(const char* sourceMailbox) {
7120 RemoveMsgsAndExpunge();
7121 if (GetServerStateParser().LastCommandSuccessful()) {
7122 // All msgs are deleted successfully - let's remove the folder itself.
7123 bool reportingErrors = GetServerStateParser().GetReportingErrors();
7124 GetServerStateParser().SetReportingErrors(false);
7125 OnDeleteFolder(sourceMailbox);
7126 GetServerStateParser().SetReportingErrors(reportingErrors);
7127 }
7128 }
7129
OnRenameFolder(const char * sourceMailbox)7130 void nsImapProtocol::OnRenameFolder(const char* sourceMailbox) {
7131 char* destinationMailbox = OnCreateServerDestinationFolderPathString();
7132
7133 if (destinationMailbox) {
7134 bool renamed = RenameHierarchyByHand(sourceMailbox, destinationMailbox);
7135 if (renamed) FolderRenamed(sourceMailbox, destinationMailbox);
7136
7137 // Cause a LIST and re-discovery when slash and/or ^ are escaped. Also
7138 // needed when folder renamed to non-ASCII UTF-8 when UTF8=ACCEPT in
7139 // effect.
7140 m_hierarchyNameState = kListingForCreate;
7141 nsCString mailboxWODelim(destinationMailbox);
7142 RemoveHierarchyDelimiter(mailboxWODelim);
7143 List(mailboxWODelim.get(), false);
7144 m_hierarchyNameState = kNoOperationInProgress;
7145
7146 PR_Free(destinationMailbox);
7147 } else
7148 HandleMemoryFailure();
7149 }
7150
OnMoveFolderHierarchy(const char * sourceMailbox)7151 void nsImapProtocol::OnMoveFolderHierarchy(const char* sourceMailbox) {
7152 char* destinationMailbox = OnCreateServerDestinationFolderPathString();
7153
7154 if (destinationMailbox) {
7155 nsCString newBoxName;
7156 newBoxName.Adopt(destinationMailbox);
7157
7158 char onlineDirSeparator = kOnlineHierarchySeparatorUnknown;
7159 m_runningUrl->GetOnlineSubDirSeparator(&onlineDirSeparator);
7160
7161 nsCString oldBoxName(sourceMailbox);
7162 int32_t leafStart = oldBoxName.RFindChar(onlineDirSeparator);
7163 nsCString leafName;
7164
7165 if (-1 == leafStart)
7166 leafName = oldBoxName; // this is a root level box
7167 else
7168 leafName = Substring(oldBoxName, leafStart + 1);
7169
7170 if (!newBoxName.IsEmpty()) newBoxName.Append(onlineDirSeparator);
7171 newBoxName.Append(leafName);
7172 bool renamed = RenameHierarchyByHand(sourceMailbox, newBoxName.get());
7173 if (renamed) FolderRenamed(sourceMailbox, newBoxName.get());
7174 } else
7175 HandleMemoryFailure();
7176 }
7177
7178 // This is called to do mailbox discovery if discovery not already complete
7179 // for the "host" (i.e., server or account). Discovery still only occurs if
7180 // the imap action is appropriate and if discovery is not in progress due to
7181 // a running "discoverallboxes" URL.
FindMailboxesIfNecessary()7182 void nsImapProtocol::FindMailboxesIfNecessary() {
7183 // biff should not discover mailboxes
7184 nsImapAction imapAction;
7185 (void)m_runningUrl->GetImapAction(&imapAction);
7186 if ((imapAction != nsIImapUrl::nsImapBiff) &&
7187 (imapAction != nsIImapUrl::nsImapVerifylogon) &&
7188 (imapAction != nsIImapUrl::nsImapDiscoverAllBoxesUrl) &&
7189 (imapAction != nsIImapUrl::nsImapUpgradeToSubscription) &&
7190 !GetSubscribingNow()) {
7191 // If discovery in progress, don't kick-off another discovery.
7192 bool discoveryInProgress = false;
7193 m_hostSessionList->GetDiscoveryForHostInProgress(GetImapServerKey(),
7194 discoveryInProgress);
7195 if (!discoveryInProgress) {
7196 m_hostSessionList->SetDiscoveryForHostInProgress(GetImapServerKey(),
7197 true);
7198 DiscoverMailboxList();
7199 }
7200 }
7201 }
7202
DiscoverAllAndSubscribedBoxes()7203 void nsImapProtocol::DiscoverAllAndSubscribedBoxes() {
7204 // used for subscribe pane
7205 // iterate through all namespaces
7206 uint32_t count = 0;
7207 m_hostSessionList->GetNumberOfNamespacesForHost(GetImapServerKey(), count);
7208
7209 for (uint32_t i = 0; i < count; i++) {
7210 nsImapNamespace* ns = nullptr;
7211 m_hostSessionList->GetNamespaceNumberForHost(GetImapServerKey(), i, ns);
7212 if (!ns) {
7213 continue;
7214 }
7215 if ((gHideOtherUsersFromList && (ns->GetType() != kOtherUsersNamespace)) ||
7216 !gHideOtherUsersFromList) {
7217 const char* prefix = ns->GetPrefix();
7218 if (prefix) {
7219 nsAutoCString inboxNameWithDelim("INBOX");
7220 inboxNameWithDelim.Append(ns->GetDelimiter());
7221
7222 // Only do it for non-empty namespace prefixes.
7223 if (!gHideUnusedNamespaces && *prefix &&
7224 PL_strcasecmp(prefix, inboxNameWithDelim.get())) {
7225 // Explicitly discover each Namespace, just so they're
7226 // there in the subscribe UI
7227 RefPtr<nsImapMailboxSpec> boxSpec = new nsImapMailboxSpec;
7228 boxSpec->mFolderSelected = false;
7229 boxSpec->mHostName.Assign(GetImapHostName());
7230 boxSpec->mConnection = this;
7231 boxSpec->mFlagState = nullptr;
7232 boxSpec->mDiscoveredFromLsub = true;
7233 boxSpec->mOnlineVerified = true;
7234 boxSpec->mBoxFlags = kNoselect;
7235 boxSpec->mHierarchySeparator = ns->GetDelimiter();
7236
7237 m_runningUrl->AllocateCanonicalPath(
7238 ns->GetPrefix(), ns->GetDelimiter(),
7239 getter_Copies(boxSpec->mAllocatedPathName));
7240 boxSpec->mNamespaceForFolder = ns;
7241 boxSpec->mBoxFlags |= kNameSpace;
7242
7243 switch (ns->GetType()) {
7244 case kPersonalNamespace:
7245 boxSpec->mBoxFlags |= kPersonalMailbox;
7246 break;
7247 case kPublicNamespace:
7248 boxSpec->mBoxFlags |= kPublicMailbox;
7249 break;
7250 case kOtherUsersNamespace:
7251 boxSpec->mBoxFlags |= kOtherUsersMailbox;
7252 break;
7253 default: // (kUnknownNamespace)
7254 break;
7255 }
7256
7257 DiscoverMailboxSpec(boxSpec);
7258 }
7259
7260 nsAutoCString allPattern(prefix);
7261 allPattern += '*';
7262
7263 if (!m_imapServerSink) return;
7264
7265 m_imapServerSink->SetServerDoingLsub(true);
7266 Lsub(allPattern.get(), true); // LSUB all the subscribed
7267
7268 m_imapServerSink->SetServerDoingLsub(false);
7269 List(allPattern.get(), true); // LIST all folders
7270 }
7271 }
7272 }
7273 }
7274
7275 // DiscoverMailboxList() is used to actually do the discovery of folders
7276 // for a host. This is used both when we initially start up (and re-sync)
7277 // and also when the user manually requests a re-sync, by collapsing and
7278 // expanding a host in the folder pane. This is not used for the subscribe
7279 // pane.
7280 // DiscoverMailboxList() also gets the ACLs for each newly discovered folder
DiscoverMailboxList()7281 void nsImapProtocol::DiscoverMailboxList() {
7282 bool usingSubscription = false;
7283
7284 m_hostSessionList->GetHostIsUsingSubscription(GetImapServerKey(),
7285 usingSubscription);
7286 // Pretend that the Trash folder doesn't exist, so we will rediscover it if we
7287 // need to.
7288 m_hostSessionList->SetOnlineTrashFolderExistsForHost(GetImapServerKey(),
7289 false);
7290
7291 // should we check a pref here, to be able to turn off XList?
7292 bool hasXLIST =
7293 GetServerStateParser().GetCapabilityFlag() & kHasXListCapability;
7294 if (hasXLIST && usingSubscription) {
7295 m_hierarchyNameState = kXListing;
7296 nsAutoCString pattern("%");
7297 List("%", true, true);
7298 // We list the first and second levels since special folders are unlikely
7299 // to be more than 2 levels deep.
7300 char separator = 0;
7301 m_runningUrl->GetOnlineSubDirSeparator(&separator);
7302 pattern.Append(separator);
7303 pattern += '%';
7304 List(pattern.get(), true, true);
7305 }
7306
7307 SetMailboxDiscoveryStatus(eContinue);
7308 if (GetServerStateParser().ServerHasACLCapability())
7309 m_hierarchyNameState = kListingForInfoAndDiscovery;
7310 else
7311 m_hierarchyNameState = kNoOperationInProgress;
7312
7313 // iterate through all namespaces and LSUB them.
7314 uint32_t count = 0;
7315 m_hostSessionList->GetNumberOfNamespacesForHost(GetImapServerKey(), count);
7316 for (uint32_t i = 0; i < count; i++) {
7317 nsImapNamespace* ns = nullptr;
7318 m_hostSessionList->GetNamespaceNumberForHost(GetImapServerKey(), i, ns);
7319 if (ns) {
7320 const char* prefix = ns->GetPrefix();
7321 if (prefix) {
7322 nsAutoCString inboxNameWithDelim("INBOX");
7323 inboxNameWithDelim.Append(ns->GetDelimiter());
7324
7325 // static bool gHideUnusedNamespaces = true;
7326 // mscott -> WARNING!!! i where are we going to get this
7327 // global variable for unused name spaces from???
7328 // dmb - we should get this from a per-host preference,
7329 // I'd say. But for now, just make it true.
7330 // Only do it for non-empty namespace prefixes, and for non-INBOX prefix
7331 if (!gHideUnusedNamespaces && *prefix &&
7332 PL_strcasecmp(prefix, inboxNameWithDelim.get())) {
7333 // Explicitly discover each Namespace, so that we can
7334 // create subfolders of them,
7335 RefPtr<nsImapMailboxSpec> boxSpec = new nsImapMailboxSpec;
7336 boxSpec->mFolderSelected = false;
7337 boxSpec->mHostName = GetImapHostName();
7338 boxSpec->mConnection = this;
7339 boxSpec->mFlagState = nullptr;
7340 boxSpec->mDiscoveredFromLsub = true;
7341 boxSpec->mOnlineVerified = true;
7342 boxSpec->mBoxFlags = kNoselect;
7343 boxSpec->mHierarchySeparator = ns->GetDelimiter();
7344 // Until |AllocateCanonicalPath()| gets updated:
7345 m_runningUrl->AllocateCanonicalPath(
7346 ns->GetPrefix(), ns->GetDelimiter(),
7347 getter_Copies(boxSpec->mAllocatedPathName));
7348 boxSpec->mNamespaceForFolder = ns;
7349 boxSpec->mBoxFlags |= kNameSpace;
7350
7351 switch (ns->GetType()) {
7352 case kPersonalNamespace:
7353 boxSpec->mBoxFlags |= kPersonalMailbox;
7354 break;
7355 case kPublicNamespace:
7356 boxSpec->mBoxFlags |= kPublicMailbox;
7357 break;
7358 case kOtherUsersNamespace:
7359 boxSpec->mBoxFlags |= kOtherUsersMailbox;
7360 break;
7361 default: // (kUnknownNamespace)
7362 break;
7363 }
7364
7365 DiscoverMailboxSpec(boxSpec);
7366 }
7367
7368 // now do the folders within this namespace
7369 nsCString pattern;
7370 nsCString pattern2;
7371 if (usingSubscription) {
7372 pattern.Append(prefix);
7373 pattern.Append('*');
7374 } else {
7375 pattern.Append(prefix);
7376 pattern.Append('%'); // mscott just need one percent right?
7377 // pattern = PR_smprintf("%s%%", prefix);
7378 char delimiter = ns->GetDelimiter();
7379 if (delimiter) {
7380 // delimiter might be NIL, in which case there's no hierarchy anyway
7381 pattern2 = prefix;
7382 pattern2 += "%";
7383 pattern2 += delimiter;
7384 pattern2 += "%";
7385 // pattern2 = PR_smprintf("%s%%%c%%", prefix, delimiter);
7386 }
7387 }
7388 // Note: It is important to make sure we are respecting the
7389 // server_sub_directory preference when calling List and Lsub (2nd arg =
7390 // true), otherwise we end up with performance issues or even crashes
7391 // when connecting to servers that expose the users entire home
7392 // directory (like UW-IMAP).
7393 if (usingSubscription) { // && !GetSubscribingNow()) should never get
7394 // here from subscribe pane
7395 if (GetServerStateParser().GetCapabilityFlag() &
7396 kHasListExtendedCapability)
7397 Lsub(pattern.get(), true); // do LIST (SUBSCRIBED)
7398 else {
7399 // store mailbox flags from LIST
7400 EMailboxHierarchyNameState currentState = m_hierarchyNameState;
7401 m_hierarchyNameState = kListingForFolderFlags;
7402 List(pattern.get(), true);
7403 m_hierarchyNameState = currentState;
7404 // then do LSUB using stored flags
7405 Lsub(pattern.get(), true);
7406 m_standardListMailboxes.Clear();
7407 }
7408 } else {
7409 List(pattern.get(), true, hasXLIST);
7410 List(pattern2.get(), true, hasXLIST);
7411 }
7412 }
7413 }
7414 }
7415
7416 // explicitly LIST the INBOX if (a) we're not using subscription, or (b) we
7417 // are using subscription and the user wants us to always show the INBOX.
7418 bool listInboxForHost = false;
7419 m_hostSessionList->GetShouldAlwaysListInboxForHost(GetImapServerKey(),
7420 listInboxForHost);
7421 if (!usingSubscription || listInboxForHost) List("INBOX", true);
7422
7423 m_hierarchyNameState = kNoOperationInProgress;
7424
7425 MailboxDiscoveryFinished();
7426
7427 // Get the ACLs for newly discovered folders
7428 if (GetServerStateParser().ServerHasACLCapability()) {
7429 int32_t total = m_listedMailboxList.Length(), cnt = 0;
7430 // Let's not turn this off here, since we don't turn it on after
7431 // GetServerStateParser().SetReportingErrors(false);
7432 if (total) {
7433 ProgressEventFunctionUsingName("imapGettingACLForFolder");
7434 nsIMAPMailboxInfo* mb = nullptr;
7435 do {
7436 if (m_listedMailboxList.Length() == 0) break;
7437
7438 mb = m_listedMailboxList[0]; // get top element
7439 m_listedMailboxList.RemoveElementAt(
7440 0); // XP_ListRemoveTopObject(fListedMailboxList);
7441 if (mb) {
7442 if (FolderNeedsACLInitialized(
7443 PromiseFlatCString(mb->GetMailboxName()).get())) {
7444 char* onlineName = nullptr;
7445 m_runningUrl->AllocateServerPath(
7446 PromiseFlatCString(mb->GetMailboxName()).get(),
7447 mb->GetDelimiter(), &onlineName);
7448 if (onlineName) {
7449 RefreshACLForFolder(onlineName);
7450 PR_Free(onlineName);
7451 }
7452 }
7453 PercentProgressUpdateEvent(""_ns, u""_ns, cnt, total);
7454 delete mb; // this is the last time we're using the list, so delete
7455 // the entries here
7456 cnt++;
7457 }
7458 } while (mb && !DeathSignalReceived());
7459 }
7460 }
7461 }
7462
FolderNeedsACLInitialized(const char * folderName)7463 bool nsImapProtocol::FolderNeedsACLInitialized(const char* folderName) {
7464 bool rv = false;
7465 m_imapServerSink->FolderNeedsACLInitialized(nsDependentCString(folderName),
7466 &rv);
7467 return rv;
7468 }
7469
MailboxDiscoveryFinished()7470 void nsImapProtocol::MailboxDiscoveryFinished() {
7471 if (!DeathSignalReceived() && !GetSubscribingNow() &&
7472 ((m_hierarchyNameState == kNoOperationInProgress) ||
7473 (m_hierarchyNameState == kListingForInfoAndDiscovery))) {
7474 nsImapNamespace* ns = nullptr;
7475 m_hostSessionList->GetDefaultNamespaceOfTypeForHost(GetImapServerKey(),
7476 kPersonalNamespace, ns);
7477 const char* personalDir = ns ? ns->GetPrefix() : 0;
7478
7479 bool trashFolderExists = false;
7480 bool usingSubscription = false;
7481 m_hostSessionList->GetOnlineTrashFolderExistsForHost(GetImapServerKey(),
7482 trashFolderExists);
7483 m_hostSessionList->GetHostIsUsingSubscription(GetImapServerKey(),
7484 usingSubscription);
7485 if (!trashFolderExists && GetDeleteIsMoveToTrash() && usingSubscription) {
7486 // maybe we're not subscribed to the Trash folder
7487 if (personalDir) {
7488 m_hierarchyNameState = kDiscoverTrashFolderInProgress;
7489 List(m_trashFolderPath.get(), true);
7490 m_hierarchyNameState = kNoOperationInProgress;
7491 }
7492 }
7493
7494 // There is no Trash folder (either LIST'd or LSUB'd), and we're using the
7495 // Delete-is-move-to-Trash model, and there is a personal namespace
7496 if (!trashFolderExists && GetDeleteIsMoveToTrash() && ns) {
7497 nsCString onlineTrashName;
7498 m_runningUrl->AllocateServerPath(m_trashFolderPath.get(),
7499 ns->GetDelimiter(),
7500 getter_Copies(onlineTrashName));
7501
7502 GetServerStateParser().SetReportingErrors(false);
7503 bool created =
7504 CreateMailboxRespectingSubscriptions(onlineTrashName.get());
7505 GetServerStateParser().SetReportingErrors(true);
7506
7507 // force discovery of new trash folder.
7508 if (created) {
7509 m_hierarchyNameState = kDiscoverTrashFolderInProgress;
7510 List(onlineTrashName.get(), false);
7511 m_hierarchyNameState = kNoOperationInProgress;
7512 } else
7513 m_hostSessionList->SetOnlineTrashFolderExistsForHost(GetImapServerKey(),
7514 true);
7515 } // if trash folder doesn't exist
7516 m_hostSessionList->SetHaveWeEverDiscoveredFoldersForHost(GetImapServerKey(),
7517 true);
7518 // notify front end that folder discovery is complete....
7519 if (m_imapServerSink) m_imapServerSink->DiscoveryDone();
7520
7521 // Clear the discovery in progress flag.
7522 m_hostSessionList->SetDiscoveryForHostInProgress(GetImapServerKey(), false);
7523 }
7524 }
7525
7526 // returns the mailboxName with the IMAP delimiter removed from the tail end
RemoveHierarchyDelimiter(nsCString & mailboxName)7527 void nsImapProtocol::RemoveHierarchyDelimiter(nsCString& mailboxName) {
7528 char onlineDelimiter[2] = {0, 0};
7529 if (m_imapMailFolderSink)
7530 m_imapMailFolderSink->GetOnlineDelimiter(&onlineDelimiter[0]);
7531 // take the hierarchy delimiter off the end, if any.
7532 if (onlineDelimiter[0]) mailboxName.Trim(onlineDelimiter, false, true);
7533 }
7534
7535 // returns true is the create succeeded (regardless of subscription changes)
CreateMailboxRespectingSubscriptions(const char * mailboxName)7536 bool nsImapProtocol::CreateMailboxRespectingSubscriptions(
7537 const char* mailboxName) {
7538 CreateMailbox(mailboxName);
7539 bool rv = GetServerStateParser().LastCommandSuccessful();
7540 if (rv && m_autoSubscribe) // auto-subscribe is on
7541 {
7542 // create succeeded - let's subscribe to it
7543 bool reportingErrors = GetServerStateParser().GetReportingErrors();
7544 GetServerStateParser().SetReportingErrors(false);
7545 nsCString mailboxWODelim(mailboxName);
7546 RemoveHierarchyDelimiter(mailboxWODelim);
7547 OnSubscribe(mailboxWODelim.get());
7548 GetServerStateParser().SetReportingErrors(reportingErrors);
7549 }
7550 return rv;
7551 }
7552
CreateMailbox(const char * mailboxName)7553 void nsImapProtocol::CreateMailbox(const char* mailboxName) {
7554 ProgressEventFunctionUsingName("imapStatusCreatingMailbox");
7555
7556 IncrementCommandTagNumber();
7557
7558 nsCString escapedName;
7559 CreateEscapedMailboxName(mailboxName, escapedName);
7560 nsCString command(GetServerCommandTag());
7561 command += " create \"";
7562 command += escapedName;
7563 command += "\"" CRLF;
7564
7565 nsresult rv = SendData(command.get());
7566 if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
7567 // If that failed, let's list the parent folder to see if
7568 // it allows inferiors, so we won't try to create sub-folders
7569 // of the parent folder again in the current session.
7570 if (GetServerStateParser().CommandFailed()) {
7571 // Figure out parent folder name.
7572 nsCString parentName(mailboxName);
7573 char hierarchyDelimiter;
7574 m_runningUrl->GetOnlineSubDirSeparator(&hierarchyDelimiter);
7575 int32_t leafPos = parentName.RFindChar(hierarchyDelimiter);
7576 if (leafPos > 0) {
7577 parentName.SetLength(leafPos);
7578 List(parentName.get(), false);
7579 // We still want the caller to know the create failed, so restore that.
7580 GetServerStateParser().SetCommandFailed(true);
7581 }
7582 }
7583 }
7584
DeleteMailbox(const char * mailboxName)7585 void nsImapProtocol::DeleteMailbox(const char* mailboxName) {
7586 // check if this connection currently has the folder to be deleted selected.
7587 // If so, we should close it because at least some UW servers don't like you
7588 // deleting a folder you have open.
7589 if (FolderIsSelected(mailboxName)) Close();
7590
7591 ProgressEventFunctionUsingNameWithString("imapStatusDeletingMailbox",
7592 mailboxName);
7593
7594 IncrementCommandTagNumber();
7595
7596 nsCString escapedName;
7597 CreateEscapedMailboxName(mailboxName, escapedName);
7598 nsCString command(GetServerCommandTag());
7599 command += " delete \"";
7600 command += escapedName;
7601 command += "\"" CRLF;
7602
7603 nsresult rv = SendData(command.get());
7604 if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
7605 }
7606
RenameMailbox(const char * existingName,const char * newName)7607 void nsImapProtocol::RenameMailbox(const char* existingName,
7608 const char* newName) {
7609 // just like DeleteMailbox; Some UW servers don't like it.
7610 if (FolderIsSelected(existingName)) Close();
7611
7612 ProgressEventFunctionUsingNameWithString("imapStatusRenamingMailbox",
7613 existingName);
7614
7615 IncrementCommandTagNumber();
7616
7617 nsCString escapedExistingName;
7618 nsCString escapedNewName;
7619 CreateEscapedMailboxName(existingName, escapedExistingName);
7620 CreateEscapedMailboxName(newName, escapedNewName);
7621 nsCString command(GetServerCommandTag());
7622 command += " rename \"";
7623 command += escapedExistingName;
7624 command += "\" \"";
7625 command += escapedNewName;
7626 command += "\"" CRLF;
7627
7628 nsresult rv = SendData(command.get());
7629 if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
7630 }
7631
GetListSubscribedIsBrokenOnServer()7632 bool nsImapProtocol::GetListSubscribedIsBrokenOnServer() {
7633 // This is a workaround for an issue with LIST(SUBSCRIBED) crashing older
7634 // versions of Zimbra
7635 if (GetServerStateParser().GetServerID().Find(
7636 "\"NAME\" \"Zimbra\"", /* ignoreCase = */ true) != kNotFound) {
7637 nsCString serverID(GetServerStateParser().GetServerID());
7638 int start = serverID.Find("\"VERSION\" \"", /* ignoreCase = */ true) + 11;
7639 int length = serverID.Find("\" ", true, start);
7640 const nsDependentCSubstring serverVersionSubstring =
7641 Substring(serverID, start, length);
7642 nsCString serverVersionStr(serverVersionSubstring);
7643 Version serverVersion(serverVersionStr.get());
7644 Version sevenTwoThree("7.2.3_");
7645 Version eightZeroZero("8.0.0_");
7646 Version eightZeroThree("8.0.3_");
7647 if ((serverVersion < sevenTwoThree) ||
7648 ((serverVersion >= eightZeroZero) && (serverVersion < eightZeroThree)))
7649 return true;
7650 }
7651 return false;
7652 }
7653
7654 // This identifies servers that require an extra imap SELECT to detect new
7655 // email in a mailbox. Servers requiring this are found by comparing their
7656 // ID string, returned with imap ID command, to strings entered in
7657 // mail.imap.force_select_detect. Only openwave servers used by
7658 // Charter/Spectrum ISP returning an ID containing the strings ""name" "Email
7659 // Mx"" and ""vendor" "Openwave Messaging"" are now known to have this issue.
7660 // The compared strings can be modified with the config editor if necessary
7661 // (e.g., a "version" substring could be added). Also, additional servers
7662 // having a different set of strings can be added if ever needed.
7663 // The mail.imap.force_select_detect uses semicolon delimiter between
7664 // servers and within a server substrings to compare are comma delimited.
7665 // This example force_select_detect value shows how two servers types
7666 // could be detected:
7667 // "name" "Email Mx","vendor" "Openwave Messaging";"vendor" "Yahoo! Inc.","name"
7668 // "Y!IMAP";
IsExtraSelectNeeded()7669 bool nsImapProtocol::IsExtraSelectNeeded() {
7670 bool retVal;
7671 for (uint32_t i = 0; i < gForceSelectServersArray.Length(); i++) {
7672 retVal = true;
7673 nsTArray<nsCString> forceSelectStringsArray;
7674 ParseString(gForceSelectServersArray[i], ',', forceSelectStringsArray);
7675 for (uint32_t j = 0; j < forceSelectStringsArray.Length(); j++) {
7676 // Each substring within the server string must be contained in ID string.
7677 // First un-escape any comma (%2c) or semicolon (%3b) within the
7678 // substring.
7679 nsAutoCString unescapedString;
7680 MsgUnescapeString(forceSelectStringsArray[j], 0, unescapedString);
7681 if (GetServerStateParser().GetServerID().Find(
7682 unescapedString, /* ignoreCase = */ true) == kNotFound) {
7683 retVal = false;
7684 break;
7685 }
7686 }
7687 // Matches found for all substrings for the server.
7688 if (retVal) return true;
7689 }
7690
7691 // If reached, no substrings match for any server.
7692 return false;
7693 }
7694
Lsub(const char * mailboxPattern,bool addDirectoryIfNecessary)7695 void nsImapProtocol::Lsub(const char* mailboxPattern,
7696 bool addDirectoryIfNecessary) {
7697 ProgressEventFunctionUsingName("imapStatusLookingForMailbox");
7698
7699 IncrementCommandTagNumber();
7700
7701 char* boxnameWithOnlineDirectory = nullptr;
7702 if (addDirectoryIfNecessary)
7703 m_runningUrl->AddOnlineDirectoryIfNecessary(mailboxPattern,
7704 &boxnameWithOnlineDirectory);
7705
7706 nsCString escapedPattern;
7707 CreateEscapedMailboxName(
7708 boxnameWithOnlineDirectory ? boxnameWithOnlineDirectory : mailboxPattern,
7709 escapedPattern);
7710
7711 nsCString command(GetServerCommandTag());
7712 eIMAPCapabilityFlags flag = GetServerStateParser().GetCapabilityFlag();
7713 bool useListSubscribed = (flag & kHasListExtendedCapability) &&
7714 !GetListSubscribedIsBrokenOnServer();
7715 if (useListSubscribed)
7716 command += " list (subscribed)";
7717 else
7718 command += " lsub";
7719 command += " \"\" \"";
7720 command += escapedPattern;
7721 if (useListSubscribed && (flag & kHasSpecialUseCapability))
7722 command += "\" return (special-use)" CRLF;
7723 else
7724 command += "\"" CRLF;
7725
7726 PR_Free(boxnameWithOnlineDirectory);
7727
7728 nsresult rv = SendData(command.get());
7729 if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail(command.get(), true);
7730 }
7731
List(const char * mailboxPattern,bool addDirectoryIfNecessary,bool useXLIST)7732 void nsImapProtocol::List(const char* mailboxPattern,
7733 bool addDirectoryIfNecessary, bool useXLIST) {
7734 ProgressEventFunctionUsingName("imapStatusLookingForMailbox");
7735
7736 IncrementCommandTagNumber();
7737
7738 char* boxnameWithOnlineDirectory = nullptr;
7739 if (addDirectoryIfNecessary)
7740 m_runningUrl->AddOnlineDirectoryIfNecessary(mailboxPattern,
7741 &boxnameWithOnlineDirectory);
7742
7743 nsCString escapedPattern;
7744 CreateEscapedMailboxName(
7745 boxnameWithOnlineDirectory ? boxnameWithOnlineDirectory : mailboxPattern,
7746 escapedPattern);
7747
7748 nsCString command(GetServerCommandTag());
7749 command += useXLIST ? " xlist \"\" \"" : " list \"\" \"";
7750 command += escapedPattern;
7751 command += "\"" CRLF;
7752
7753 PR_Free(boxnameWithOnlineDirectory);
7754
7755 nsresult rv = SendData(command.get());
7756 if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail(command.get(), true);
7757 }
7758
Subscribe(const char * mailboxName)7759 void nsImapProtocol::Subscribe(const char* mailboxName) {
7760 ProgressEventFunctionUsingNameWithString("imapStatusSubscribeToMailbox",
7761 mailboxName);
7762
7763 IncrementCommandTagNumber();
7764
7765 nsCString escapedName;
7766 CreateEscapedMailboxName(mailboxName, escapedName);
7767
7768 nsCString command(GetServerCommandTag());
7769 command += " subscribe \"";
7770 command += escapedName;
7771 command += "\"" CRLF;
7772
7773 nsresult rv = SendData(command.get());
7774 if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
7775 }
7776
Unsubscribe(const char * mailboxName)7777 void nsImapProtocol::Unsubscribe(const char* mailboxName) {
7778 ProgressEventFunctionUsingNameWithString("imapStatusUnsubscribeMailbox",
7779 mailboxName);
7780 IncrementCommandTagNumber();
7781
7782 nsCString escapedName;
7783 CreateEscapedMailboxName(mailboxName, escapedName);
7784
7785 nsCString command(GetServerCommandTag());
7786 command += " unsubscribe \"";
7787 command += escapedName;
7788 command += "\"" CRLF;
7789
7790 nsresult rv = SendData(command.get());
7791 if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
7792 }
7793
Idle()7794 void nsImapProtocol::Idle() {
7795 IncrementCommandTagNumber();
7796
7797 if (m_urlInProgress) return;
7798 nsAutoCString command(GetServerCommandTag());
7799 command += " IDLE" CRLF;
7800 nsresult rv = SendData(command.get());
7801 if (NS_SUCCEEDED(rv)) {
7802 // we'll just get back a continuation char at first.
7803 // + idling...
7804 ParseIMAPandCheckForNewMail();
7805 if (GetServerStateParser().LastCommandSuccessful()) {
7806 m_idle = true;
7807 // this will cause us to get notified of data or the socket getting
7808 // closed. That notification will occur on the socket transport thread -
7809 // we just need to poke a monitor so the imap thread will do a blocking
7810 // read and parse the data.
7811 nsCOMPtr<nsIAsyncInputStream> asyncInputStream =
7812 do_QueryInterface(m_inputStream);
7813 if (asyncInputStream) asyncInputStream->AsyncWait(this, 0, 0, nullptr);
7814 } else {
7815 m_idle = false;
7816 }
7817 }
7818 }
7819
7820 // until we can fix the hang on shutdown waiting for server
7821 // responses, we need to not wait for the server response
7822 // on shutdown.
EndIdle(bool waitForResponse)7823 void nsImapProtocol::EndIdle(bool waitForResponse /* = true */) {
7824 // clear the async wait - otherwise, we seem to have trouble doing a blocking
7825 // read
7826 nsCOMPtr<nsIAsyncInputStream> asyncInputStream =
7827 do_QueryInterface(m_inputStream);
7828 if (asyncInputStream) asyncInputStream->AsyncWait(nullptr, 0, 0, nullptr);
7829 nsresult rv = SendData("DONE" CRLF);
7830 // set a short timeout if we don't want to wait for a response
7831 if (m_transport && !waitForResponse)
7832 m_transport->SetTimeout(nsISocketTransport::TIMEOUT_READ_WRITE, 5);
7833 if (NS_SUCCEEDED(rv)) {
7834 m_idle = false;
7835 ParseIMAPandCheckForNewMail();
7836 }
7837 m_imapMailFolderSink = nullptr;
7838 }
7839
Search(const char * searchCriteria,bool useUID,bool notifyHit)7840 void nsImapProtocol::Search(const char* searchCriteria, bool useUID,
7841 bool notifyHit /* true */) {
7842 m_notifySearchHit = notifyHit;
7843 ProgressEventFunctionUsingName("imapStatusSearchMailbox");
7844 IncrementCommandTagNumber();
7845
7846 nsCString protocolString(GetServerCommandTag());
7847 // the searchCriteria string contains the 'search ....' string
7848 if (useUID) protocolString.AppendLiteral(" uid");
7849 protocolString.Append(' ');
7850 protocolString.Append(searchCriteria);
7851 // the search criteria can contain string literals, which means we
7852 // need to break up the protocol string by CRLF's, and after sending CRLF,
7853 // wait for the server to respond OK before sending more data
7854 nsresult rv;
7855 int32_t crlfIndex;
7856 while ((crlfIndex = protocolString.Find(CRLF)) != kNotFound &&
7857 !DeathSignalReceived()) {
7858 nsAutoCString tempProtocolString;
7859 tempProtocolString = StringHead(protocolString, crlfIndex + 2);
7860 rv = SendData(tempProtocolString.get());
7861 if (NS_FAILED(rv)) return;
7862 ParseIMAPandCheckForNewMail();
7863 protocolString.Cut(0, crlfIndex + 2);
7864 }
7865 protocolString.Append(CRLF);
7866
7867 rv = SendData(protocolString.get());
7868 if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
7869 }
7870
Copy(const char * messageList,const char * destinationMailbox,bool idsAreUid)7871 void nsImapProtocol::Copy(const char* messageList,
7872 const char* destinationMailbox, bool idsAreUid) {
7873 IncrementCommandTagNumber();
7874
7875 nsCString escapedDestination;
7876 CreateEscapedMailboxName(destinationMailbox, escapedDestination);
7877
7878 // turn messageList back into key array and then back into a message id list,
7879 // but use the flag state to handle ranges correctly.
7880 nsCString messageIdList;
7881 nsTArray<nsMsgKey> msgKeys;
7882 if (idsAreUid) ParseUidString(messageList, msgKeys);
7883
7884 int32_t msgCountLeft = msgKeys.Length();
7885 uint32_t msgsHandled = 0;
7886
7887 do {
7888 nsCString idString;
7889
7890 uint32_t msgsToHandle = msgCountLeft;
7891 if (idsAreUid)
7892 AllocateImapUidString(msgKeys.Elements() + msgsHandled, msgsToHandle,
7893 m_flagState, idString);
7894 else
7895 idString.Assign(messageList);
7896
7897 msgsHandled += msgsToHandle;
7898 msgCountLeft -= msgsToHandle;
7899
7900 IncrementCommandTagNumber();
7901 nsAutoCString protocolString(GetServerCommandTag());
7902 if (idsAreUid) protocolString.AppendLiteral(" uid");
7903 // If it's a MOVE operation on aol servers then use 'xaol-move' cmd.
7904 if ((m_imapAction == nsIImapUrl::nsImapOnlineMove) &&
7905 GetServerStateParser().ServerIsAOLServer())
7906 protocolString.AppendLiteral(" xaol-move ");
7907 else if ((m_imapAction == nsIImapUrl::nsImapOnlineMove) &&
7908 GetServerStateParser().GetCapabilityFlag() & kHasMoveCapability)
7909 protocolString.AppendLiteral(" move ");
7910 else
7911 protocolString.AppendLiteral(" copy ");
7912
7913 protocolString.Append(idString);
7914 protocolString.AppendLiteral(" \"");
7915 protocolString.Append(escapedDestination);
7916 protocolString.AppendLiteral("\"" CRLF);
7917
7918 nsresult rv = SendData(protocolString.get());
7919 if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail(protocolString.get());
7920 } while (msgCountLeft > 0 && !DeathSignalReceived());
7921 }
7922
NthLevelChildList(const char * onlineMailboxPrefix,int32_t depth)7923 void nsImapProtocol::NthLevelChildList(const char* onlineMailboxPrefix,
7924 int32_t depth) {
7925 NS_ASSERTION(depth >= 0, "Oops ... depth must be equal or greater than 0");
7926 if (depth < 0) return;
7927
7928 nsCString truncatedPrefix(onlineMailboxPrefix);
7929 char16_t slash = '/';
7930 if (truncatedPrefix.Last() == slash)
7931 truncatedPrefix.SetLength(truncatedPrefix.Length() - 1);
7932
7933 nsAutoCString pattern(truncatedPrefix);
7934 nsAutoCString suffix;
7935 int count = 0;
7936 char separator = 0;
7937 m_runningUrl->GetOnlineSubDirSeparator(&separator);
7938 suffix.Assign(separator);
7939 suffix += '%';
7940
7941 while (count < depth) {
7942 pattern += suffix;
7943 count++;
7944 List(pattern.get(), false);
7945 }
7946 }
7947
ProcessAuthenticatedStateURL()7948 void nsImapProtocol::ProcessAuthenticatedStateURL() {
7949 nsImapAction imapAction;
7950 char* sourceMailbox = nullptr;
7951 m_runningUrl->GetImapAction(&imapAction);
7952
7953 // switch off of the imap url action and take an appropriate action
7954 switch (imapAction) {
7955 case nsIImapUrl::nsImapLsubFolders:
7956 OnLSubFolders();
7957 break;
7958 case nsIImapUrl::nsImapAppendMsgFromFile:
7959 OnAppendMsgFromFile();
7960 break;
7961 case nsIImapUrl::nsImapDiscoverAllBoxesUrl:
7962 NS_ASSERTION(!GetSubscribingNow(),
7963 "Oops ... should not get here from subscribe UI");
7964 DiscoverMailboxList();
7965 break;
7966 case nsIImapUrl::nsImapDiscoverAllAndSubscribedBoxesUrl:
7967 DiscoverAllAndSubscribedBoxes();
7968 break;
7969 case nsIImapUrl::nsImapCreateFolder:
7970 sourceMailbox = OnCreateServerSourceFolderPathString();
7971 OnCreateFolder(sourceMailbox);
7972 break;
7973 case nsIImapUrl::nsImapEnsureExistsFolder:
7974 sourceMailbox = OnCreateServerSourceFolderPathString();
7975 OnEnsureExistsFolder(sourceMailbox);
7976 break;
7977 case nsIImapUrl::nsImapDiscoverChildrenUrl: {
7978 char* canonicalParent = nullptr;
7979 m_runningUrl->CreateServerSourceFolderPathString(&canonicalParent);
7980 if (canonicalParent) {
7981 NthLevelChildList(canonicalParent, 2);
7982 PR_Free(canonicalParent);
7983 }
7984 break;
7985 }
7986 case nsIImapUrl::nsImapSubscribe:
7987 sourceMailbox = OnCreateServerSourceFolderPathString();
7988 OnSubscribe(sourceMailbox); // used to be called subscribe
7989
7990 if (GetServerStateParser().LastCommandSuccessful()) {
7991 bool shouldList;
7992 // if url is an external click url, then we should list the folder
7993 // after subscribing to it, so we can select it.
7994 m_runningUrl->GetExternalLinkUrl(&shouldList);
7995 if (shouldList) OnListFolder(sourceMailbox, true);
7996 }
7997 break;
7998 case nsIImapUrl::nsImapUnsubscribe:
7999 sourceMailbox = OnCreateServerSourceFolderPathString();
8000 OnUnsubscribe(sourceMailbox);
8001 break;
8002 case nsIImapUrl::nsImapRefreshACL:
8003 sourceMailbox = OnCreateServerSourceFolderPathString();
8004 RefreshACLForFolder(sourceMailbox);
8005 break;
8006 case nsIImapUrl::nsImapRefreshAllACLs:
8007 OnRefreshAllACLs();
8008 break;
8009 case nsIImapUrl::nsImapListFolder:
8010 sourceMailbox = OnCreateServerSourceFolderPathString();
8011 OnListFolder(sourceMailbox, false);
8012 break;
8013 case nsIImapUrl::nsImapFolderStatus:
8014 sourceMailbox = OnCreateServerSourceFolderPathString();
8015 OnStatusForFolder(sourceMailbox);
8016 break;
8017 case nsIImapUrl::nsImapRefreshFolderUrls:
8018 sourceMailbox = OnCreateServerSourceFolderPathString();
8019 XMailboxInfo(sourceMailbox);
8020 if (GetServerStateParser().LastCommandSuccessful())
8021 SetFolderAdminUrl(sourceMailbox);
8022 break;
8023 case nsIImapUrl::nsImapDeleteFolder:
8024 sourceMailbox = OnCreateServerSourceFolderPathString();
8025 OnDeleteFolder(sourceMailbox);
8026 break;
8027 case nsIImapUrl::nsImapRenameFolder:
8028 sourceMailbox = OnCreateServerSourceFolderPathString();
8029 OnRenameFolder(sourceMailbox);
8030 break;
8031 case nsIImapUrl::nsImapMoveFolderHierarchy:
8032 sourceMailbox = OnCreateServerSourceFolderPathString();
8033 OnMoveFolderHierarchy(sourceMailbox);
8034 break;
8035 case nsIImapUrl::nsImapVerifylogon:
8036 break;
8037 default:
8038 break;
8039 }
8040 PR_Free(sourceMailbox);
8041 }
8042
ProcessAfterAuthenticated()8043 void nsImapProtocol::ProcessAfterAuthenticated() {
8044 // if we're a netscape server, and we haven't got the admin url, get it
8045 bool hasAdminUrl = true;
8046
8047 // If a capability response didn't occur during authentication, request
8048 // the capabilities again to ensure the full capability set is known.
8049 if (!m_capabilityResponseOccurred) Capability();
8050
8051 if (NS_SUCCEEDED(m_hostSessionList->GetHostHasAdminURL(GetImapServerKey(),
8052 hasAdminUrl)) &&
8053 !hasAdminUrl) {
8054 if (GetServerStateParser().ServerHasServerInfo()) {
8055 XServerInfo();
8056 if (GetServerStateParser().LastCommandSuccessful() && m_imapServerSink) {
8057 m_imapServerSink->SetMailServerUrls(
8058 GetServerStateParser().GetMailAccountUrl(),
8059 GetServerStateParser().GetManageListsUrl(),
8060 GetServerStateParser().GetManageFiltersUrl());
8061 // we've tried to ask for it, so don't try again this session.
8062 m_hostSessionList->SetHostHasAdminURL(GetImapServerKey(), true);
8063 }
8064 } else if (GetServerStateParser().ServerIsNetscape3xServer()) {
8065 Netscape();
8066 if (GetServerStateParser().LastCommandSuccessful() && m_imapServerSink)
8067 m_imapServerSink->SetMailServerUrls(
8068 GetServerStateParser().GetMailAccountUrl(), EmptyCString(),
8069 EmptyCString());
8070 }
8071 }
8072
8073 if (GetServerStateParser().ServerHasNamespaceCapability()) {
8074 bool nameSpacesOverridable = false;
8075 bool haveNameSpacesForHost = false;
8076 m_hostSessionList->GetNamespacesOverridableForHost(GetImapServerKey(),
8077 nameSpacesOverridable);
8078 m_hostSessionList->GetGotNamespacesForHost(GetImapServerKey(),
8079 haveNameSpacesForHost);
8080
8081 // mscott: VERIFY THIS CLAUSE!!!!!!!
8082 if (nameSpacesOverridable && !haveNameSpacesForHost) Namespace();
8083 }
8084
8085 // If the server supports compression, turn it on now.
8086 // Choosing this spot (after login has finished) because
8087 // many proxies (e.g. perdition, nginx) talk IMAP to the
8088 // client until login is finished, then hand off to the
8089 // backend. If we enable compression early the proxy
8090 // will be confused.
8091 if (UseCompressDeflate()) StartCompressDeflate();
8092
8093 if ((GetServerStateParser().GetCapabilityFlag() & kHasEnableCapability) &&
8094 UseCondStore())
8095 EnableCondStore();
8096
8097 bool haveIdResponse = false;
8098 if ((GetServerStateParser().GetCapabilityFlag() & kHasIDCapability) &&
8099 m_sendID) {
8100 ID();
8101 if (m_imapServerSink && !GetServerStateParser().GetServerID().IsEmpty()) {
8102 haveIdResponse = true;
8103 // Determine value for m_forceSelect based on config editor
8104 // entries and comparison to imap ID string returned by the server.
8105 m_imapServerSink->SetServerID(GetServerStateParser().GetServerID());
8106 switch (m_forceSelectValue.get()[0]) {
8107 // Yes: Set to always force even if imap server doesn't need it.
8108 case 'y':
8109 case 'Y':
8110 m_forceSelect = true;
8111 break;
8112
8113 // No: Set to never force a select for this imap server.
8114 case 'n':
8115 case 'N':
8116 m_forceSelect = false;
8117 break;
8118
8119 // Auto: Set to force only if imap server requires it.
8120 default:
8121 nsAutoCString statusString;
8122 m_forceSelect = IsExtraSelectNeeded();
8123 // Setting to "yes-auto" or "no-auto" avoids doing redundant calls to
8124 // IsExtraSelectNeeded() on subsequent ID() occurrences. It also
8125 // provides feedback to the user regarding the detection status.
8126 if (m_forceSelect) {
8127 // Set preference value to "yes-auto".
8128 statusString.AssignLiteral("yes-auto");
8129 } else {
8130 // Set preference value to "no-auto".
8131 statusString.AssignLiteral("no-auto");
8132 }
8133 m_imapServerSink->SetServerForceSelect(statusString);
8134 break;
8135 }
8136 }
8137 }
8138
8139 // If no ID capability or empty ID response, user may still want to
8140 // change "force select".
8141 if (!haveIdResponse) {
8142 switch (m_forceSelectValue.get()[0]) {
8143 case 'a': {
8144 // If default "auto", set to "no-auto" so visible in config editor
8145 // and set/keep m_forceSelect false.
8146 nsAutoCString statusString;
8147 statusString.AssignLiteral("no-auto");
8148 m_imapServerSink->SetServerForceSelect(statusString);
8149 m_forceSelect = false;
8150 } break;
8151 case 'y':
8152 case 'Y':
8153 m_forceSelect = true;
8154 break;
8155 default:
8156 m_forceSelect = false;
8157 }
8158 }
8159 bool utf8AcceptAllowed = m_allowUTF8Accept;
8160 m_allowUTF8Accept = false;
8161 if (utf8AcceptAllowed &&
8162 (GetServerStateParser().GetCapabilityFlag() & kHasEnableCapability)) {
8163 if (m_imapServerSink) {
8164 EnableUTF8Accept();
8165 m_allowUTF8Accept = GetServerStateParser().fUtf8AcceptEnabled;
8166 // m_allowUTF8Accept affects imap append handling. See
8167 // UploadMessageFromFile().
8168 m_imapServerSink->SetServerUtf8AcceptEnabled(m_allowUTF8Accept);
8169 GetServerStateParser().fUtf8AcceptEnabled = false;
8170 } else if (GetServerStateParser().GetCapabilityFlag() &
8171 kHasUTF8AcceptCapability)
8172 NS_WARNING("UTF8=ACCEPT not enabled due to null m_imapServerSink");
8173 }
8174 }
8175
SetupMessageFlagsString(nsCString & flagString,imapMessageFlagsType flags,uint16_t userFlags)8176 void nsImapProtocol::SetupMessageFlagsString(nsCString& flagString,
8177 imapMessageFlagsType flags,
8178 uint16_t userFlags) {
8179 if (flags & kImapMsgSeenFlag) flagString.AppendLiteral("\\Seen ");
8180 if (flags & kImapMsgAnsweredFlag) flagString.AppendLiteral("\\Answered ");
8181 if (flags & kImapMsgFlaggedFlag) flagString.AppendLiteral("\\Flagged ");
8182 if (flags & kImapMsgDeletedFlag) flagString.AppendLiteral("\\Deleted ");
8183 if (flags & kImapMsgDraftFlag) flagString.AppendLiteral("\\Draft ");
8184 if (flags & kImapMsgRecentFlag) flagString.AppendLiteral("\\Recent ");
8185 if ((flags & kImapMsgForwardedFlag) &&
8186 (userFlags & kImapMsgSupportForwardedFlag))
8187 flagString.AppendLiteral("$Forwarded "); // Not always available
8188 if ((flags & kImapMsgMDNSentFlag) && (userFlags & kImapMsgSupportMDNSentFlag))
8189 flagString.AppendLiteral("$MDNSent "); // Not always available
8190
8191 // eat the last space
8192 if (!flagString.IsEmpty()) flagString.SetLength(flagString.Length() - 1);
8193 }
8194
ProcessStoreFlags(const nsCString & messageIdsString,bool idsAreUids,imapMessageFlagsType flags,bool addFlags)8195 void nsImapProtocol::ProcessStoreFlags(const nsCString& messageIdsString,
8196 bool idsAreUids,
8197 imapMessageFlagsType flags,
8198 bool addFlags) {
8199 nsCString flagString;
8200
8201 uint16_t userFlags = GetServerStateParser().SupportsUserFlags();
8202 uint16_t settableFlags = GetServerStateParser().SettablePermanentFlags();
8203
8204 if (!addFlags && (flags & userFlags) && !(flags & settableFlags)) {
8205 if (m_runningUrl)
8206 m_runningUrl->SetExtraStatus(nsIImapUrl::ImapStatusFlagsNotSettable);
8207 return; // if cannot set any of the flags bail out
8208 }
8209
8210 if (addFlags)
8211 flagString = "+Flags (";
8212 else
8213 flagString = "-Flags (";
8214
8215 if (flags & kImapMsgSeenFlag && kImapMsgSeenFlag & settableFlags)
8216 flagString.AppendLiteral("\\Seen ");
8217 if (flags & kImapMsgAnsweredFlag && kImapMsgAnsweredFlag & settableFlags)
8218 flagString.AppendLiteral("\\Answered ");
8219 if (flags & kImapMsgFlaggedFlag && kImapMsgFlaggedFlag & settableFlags)
8220 flagString.AppendLiteral("\\Flagged ");
8221 if (flags & kImapMsgDeletedFlag && kImapMsgDeletedFlag & settableFlags)
8222 flagString.AppendLiteral("\\Deleted ");
8223 if (flags & kImapMsgDraftFlag && kImapMsgDraftFlag & settableFlags)
8224 flagString.AppendLiteral("\\Draft ");
8225 if (flags & kImapMsgForwardedFlag && kImapMsgSupportForwardedFlag & userFlags)
8226 flagString.AppendLiteral("$Forwarded "); // if supported
8227 if (flags & kImapMsgMDNSentFlag && kImapMsgSupportMDNSentFlag & userFlags)
8228 flagString.AppendLiteral("$MDNSent "); // if supported
8229
8230 if (flagString.Length() > 8) // if more than "+Flags ("
8231 {
8232 // replace the final space with ')'
8233 flagString.SetCharAt(')', flagString.Length() - 1);
8234
8235 Store(messageIdsString, flagString.get(), idsAreUids);
8236 if (m_runningUrl && idsAreUids) {
8237 nsCString messageIdString;
8238 m_runningUrl->GetListOfMessageIds(messageIdString);
8239 nsTArray<nsMsgKey> msgKeys;
8240 ParseUidString(messageIdString.get(), msgKeys);
8241
8242 int32_t msgCount = msgKeys.Length();
8243 for (int32_t i = 0; i < msgCount; i++) {
8244 bool found;
8245 imapMessageFlagsType resultFlags;
8246 // check if the flags were added/removed, and if the uid really exists.
8247 nsresult rv = GetFlagsForUID(msgKeys[i], &found, &resultFlags, nullptr);
8248 if (NS_FAILED(rv) || !found ||
8249 (addFlags && ((flags & resultFlags) != flags)) ||
8250 (!addFlags && ((flags & resultFlags) != 0))) {
8251 m_runningUrl->SetExtraStatus(nsIImapUrl::ImapStatusFlagChangeFailed);
8252 break;
8253 }
8254 }
8255 }
8256 }
8257 }
8258
Close(bool shuttingDown,bool waitForResponse)8259 void nsImapProtocol::Close(bool shuttingDown /* = false */,
8260 bool waitForResponse /* = true */) {
8261 IncrementCommandTagNumber();
8262
8263 nsCString command(GetServerCommandTag());
8264 command.AppendLiteral(" close" CRLF);
8265
8266 if (!shuttingDown) ProgressEventFunctionUsingName("imapStatusCloseMailbox");
8267
8268 GetServerStateParser().ResetFlagInfo();
8269
8270 nsresult rv = SendData(command.get());
8271 if (m_transport && shuttingDown)
8272 m_transport->SetTimeout(nsISocketTransport::TIMEOUT_READ_WRITE, 5);
8273
8274 if (NS_SUCCEEDED(rv) && waitForResponse) ParseIMAPandCheckForNewMail();
8275 }
8276
XAOL_Option(const char * option)8277 void nsImapProtocol::XAOL_Option(const char* option) {
8278 IncrementCommandTagNumber();
8279
8280 nsCString command(GetServerCommandTag());
8281 command.AppendLiteral(" XAOL-OPTION ");
8282 command.Append(option);
8283 command.Append(CRLF);
8284
8285 nsresult rv = SendData(command.get());
8286 if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
8287 }
8288
Check()8289 void nsImapProtocol::Check() {
8290 // ProgressUpdateEvent("Checking mailbox...");
8291
8292 IncrementCommandTagNumber();
8293
8294 nsCString command(GetServerCommandTag());
8295 command.AppendLiteral(" check" CRLF);
8296
8297 nsresult rv = SendData(command.get());
8298 if (NS_SUCCEEDED(rv)) {
8299 m_flagChangeCount = 0;
8300 m_lastCheckTime = PR_Now();
8301 ParseIMAPandCheckForNewMail();
8302 }
8303 }
8304
GetMsgWindow(nsIMsgWindow ** aMsgWindow)8305 nsresult nsImapProtocol::GetMsgWindow(nsIMsgWindow** aMsgWindow) {
8306 nsresult rv;
8307 *aMsgWindow = nullptr;
8308 nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl =
8309 do_QueryInterface(m_runningUrl, &rv);
8310 NS_ENSURE_SUCCESS(rv, rv);
8311 if (!m_imapProtocolSink) return NS_ERROR_FAILURE;
8312 return m_imapProtocolSink->GetUrlWindow(mailnewsUrl, aMsgWindow);
8313 }
8314
8315 /**
8316 * Get password from RAM, disk (password manager) or user (dialog)
8317 * @return NS_MSG_PASSWORD_PROMPT_CANCELLED
8318 * (which is NS_SUCCEEDED!) when user cancelled
8319 * NS_FAILED(rv) for other errors
8320 */
GetPassword(nsString & password,bool newPasswordRequested)8321 nsresult nsImapProtocol::GetPassword(nsString& password,
8322 bool newPasswordRequested) {
8323 // we are in the imap thread so *NEVER* try to extract the password with UI
8324 NS_ENSURE_TRUE(m_imapServerSink, NS_ERROR_NULL_POINTER);
8325 NS_ENSURE_TRUE(m_server, NS_ERROR_NULL_POINTER);
8326 nsresult rv;
8327
8328 // Get the password already stored in mem
8329 rv = m_imapServerSink->GetServerPassword(password);
8330 if (NS_FAILED(rv) || password.IsEmpty()) {
8331 AutoProxyReleaseMsgWindow msgWindow;
8332 GetMsgWindow(getter_AddRefs(msgWindow));
8333 NS_ENSURE_TRUE(msgWindow, NS_ERROR_NOT_AVAILABLE); // biff case
8334
8335 // Get the password from pw manager (harddisk) or user (dialog)
8336 m_passwordObtained = false;
8337 rv = m_imapServerSink->AsyncGetPassword(this, newPasswordRequested,
8338 password);
8339 if (password.IsEmpty()) {
8340 PRIntervalTime sleepTime = kImapSleepTime;
8341 m_passwordStatus = NS_OK;
8342 ReentrantMonitorAutoEnter mon(m_passwordReadyMonitor);
8343 while (!m_passwordObtained && !NS_FAILED(m_passwordStatus) &&
8344 m_passwordStatus != NS_MSG_PASSWORD_PROMPT_CANCELLED &&
8345 !DeathSignalReceived())
8346 mon.Wait(sleepTime);
8347 rv = m_passwordStatus;
8348 password = m_password;
8349 }
8350 }
8351 if (!password.IsEmpty()) m_lastPasswordSent = password;
8352 return rv;
8353 }
8354
OnPromptStartAsync(nsIMsgAsyncPromptCallback * aCallback)8355 NS_IMETHODIMP nsImapProtocol::OnPromptStartAsync(
8356 nsIMsgAsyncPromptCallback* aCallback) {
8357 bool result = false;
8358 OnPromptStart(&result);
8359 return aCallback->OnAuthResult(result);
8360 }
8361
8362 // This is called from the UI thread.
8363 NS_IMETHODIMP
OnPromptStart(bool * aResult)8364 nsImapProtocol::OnPromptStart(bool* aResult) {
8365 nsresult rv;
8366 nsCOMPtr<nsIImapIncomingServer> imapServer = do_QueryReferent(m_server, &rv);
8367 NS_ENSURE_SUCCESS(rv, rv);
8368 nsCOMPtr<nsIMsgWindow> msgWindow;
8369
8370 *aResult = false;
8371 GetMsgWindow(getter_AddRefs(msgWindow));
8372 nsString password = m_lastPasswordSent;
8373 rv = imapServer->PromptPassword(msgWindow, password);
8374 m_password = password;
8375 m_passwordStatus = rv;
8376 if (!m_password.IsEmpty()) *aResult = true;
8377
8378 // Notify the imap thread that we have a password.
8379 m_passwordObtained = true;
8380 ReentrantMonitorAutoEnter passwordMon(m_passwordReadyMonitor);
8381 passwordMon.Notify();
8382 return rv;
8383 }
8384
8385 NS_IMETHODIMP
OnPromptAuthAvailable()8386 nsImapProtocol::OnPromptAuthAvailable() {
8387 nsresult rv;
8388 nsCOMPtr<nsIMsgIncomingServer> imapServer = do_QueryReferent(m_server, &rv);
8389 NS_ENSURE_SUCCESS(rv, rv);
8390 m_passwordStatus = imapServer->GetPassword(m_password);
8391 // Notify the imap thread that we have a password.
8392 m_passwordObtained = true;
8393 ReentrantMonitorAutoEnter passwordMon(m_passwordReadyMonitor);
8394 passwordMon.Notify();
8395 return m_passwordStatus;
8396 }
8397
8398 NS_IMETHODIMP
OnPromptCanceled()8399 nsImapProtocol::OnPromptCanceled() {
8400 // A prompt was cancelled, so notify the imap thread.
8401 m_passwordStatus = NS_MSG_PASSWORD_PROMPT_CANCELLED;
8402 ReentrantMonitorAutoEnter passwordMon(m_passwordReadyMonitor);
8403 passwordMon.Notify();
8404 return NS_OK;
8405 }
8406
8407 // Called when capability response is parsed.
SetCapabilityResponseOccurred()8408 void nsImapProtocol::SetCapabilityResponseOccurred() {
8409 m_capabilityResponseOccurred = true;
8410 }
8411
TryToLogon()8412 bool nsImapProtocol::TryToLogon() {
8413 MOZ_LOG(IMAP, LogLevel::Debug, ("Try to log in"));
8414 NS_ENSURE_TRUE(m_imapServerSink, false);
8415 bool loginSucceeded = false;
8416 bool skipLoop = false;
8417 nsAutoString password;
8418 nsAutoCString userName;
8419
8420 // If remains false when authentication is complete it means that a
8421 // capability response didn't occur within the authentication response so
8422 // capabilities will be requested explicitly.
8423 m_capabilityResponseOccurred = false;
8424
8425 nsresult rv = ChooseAuthMethod();
8426 if (NS_FAILED(rv)) // all methods failed
8427 {
8428 // are there any matching login schemes at all?
8429 if (!(GetServerStateParser().GetCapabilityFlag() & m_prefAuthMethods)) {
8430 // Pref doesn't match server. Now, find an appropriate error msg.
8431
8432 // pref has plaintext pw & server claims to support encrypted pw
8433 if (m_prefAuthMethods ==
8434 (kHasAuthOldLoginCapability | kHasAuthLoginCapability |
8435 kHasAuthPlainCapability) &&
8436 GetServerStateParser().GetCapabilityFlag() & kHasCRAMCapability)
8437 // tell user to change to encrypted pw
8438 AlertUserEventUsingName("imapAuthChangePlainToEncrypt");
8439 // pref has encrypted pw & server claims to support plaintext pw
8440 else if (m_prefAuthMethods == kHasCRAMCapability &&
8441 GetServerStateParser().GetCapabilityFlag() &
8442 (kHasAuthOldLoginCapability | kHasAuthLoginCapability |
8443 kHasAuthPlainCapability)) {
8444 // have SSL
8445 if (m_socketType == nsMsgSocketType::SSL ||
8446 m_socketType == nsMsgSocketType::alwaysSTARTTLS)
8447 // tell user to change to plaintext pw
8448 AlertUserEventUsingName("imapAuthChangeEncryptToPlainSSL");
8449 else
8450 // tell user to change to plaintext pw, with big warning
8451 AlertUserEventUsingName("imapAuthChangeEncryptToPlainNoSSL");
8452 } else
8453 // just "change auth method"
8454 AlertUserEventUsingName("imapAuthMechNotSupported");
8455
8456 skipLoop = true;
8457 } else {
8458 // try to reset failed methods and try them again
8459 ResetAuthMethods();
8460 rv = ChooseAuthMethod();
8461 if (NS_FAILED(rv)) // all methods failed
8462 {
8463 MOZ_LOG(IMAP, LogLevel::Error,
8464 ("huch? there are auth methods, and we reset failed ones, but "
8465 "ChooseAuthMethod still fails."));
8466 return false;
8467 }
8468 }
8469 }
8470
8471 // Check the uri host for localhost indicators to see if we
8472 // should bypass the SSL check for clientid.
8473 // Unfortunately we cannot call IsOriginPotentiallyTrustworthy
8474 // here because it can only be called from the main thread.
8475 bool isLocalhostConnection = false;
8476 if (m_mockChannel) {
8477 nsCOMPtr<nsIURI> uri;
8478 m_mockChannel->GetURI(getter_AddRefs(uri));
8479 if (uri) {
8480 nsCString uriHost;
8481 uri->GetHost(uriHost);
8482 if (uriHost.Equals("127.0.0.1") || uriHost.Equals("::1") ||
8483 uriHost.Equals("localhost")) {
8484 isLocalhostConnection = true;
8485 }
8486 }
8487 }
8488
8489 // Whether our connection can be considered 'secure' and whether
8490 // we should allow the CLIENTID to be sent over this channel.
8491 bool isSecureConnection =
8492 (m_connectionType.EqualsLiteral("starttls") ||
8493 m_connectionType.EqualsLiteral("ssl") || isLocalhostConnection);
8494
8495 // Before running the ClientID command we check for clientid
8496 // support by checking the server capability flags for the
8497 // flag kHasClientIDCapability.
8498 // We check that the m_clientId string is not empty, and
8499 // we ensure the connection can be considered secure.
8500 if ((GetServerStateParser().GetCapabilityFlag() & kHasClientIDCapability) &&
8501 !m_clientId.IsEmpty() && isSecureConnection) {
8502 rv = ClientID();
8503 if (NS_FAILED(rv)) {
8504 MOZ_LOG(IMAP, LogLevel::Error,
8505 ("TryToLogon: Could not issue CLIENTID command"));
8506 skipLoop = true;
8507 }
8508 }
8509
8510 // Get username, either the stored one or from user
8511 rv = m_imapServerSink->GetLoginUsername(userName);
8512 if (NS_FAILED(rv) || userName.IsEmpty()) {
8513 // The user hit "Cancel" on the dialog box
8514 skipLoop = true;
8515 }
8516
8517 // clang-format off
8518 /*
8519 * Login can fail for various reasons:
8520 * 1. Server claims to support GSSAPI, but it really doesn't.
8521 * Or the client doesn't support GSSAPI, or is not logged in yet.
8522 * (GSSAPI is a mechanism without password in apps).
8523 * 2. Server claims to support CRAM-MD5, but it's broken and will fail despite correct password.
8524 * 2.1. Some servers say they support CRAM but are so badly broken that trying it causes
8525 * all subsequent login attempts to fail during this connection (bug 231303).
8526 * So we use CRAM/NTLM/MSN only if enabled in prefs.
8527 * Update: if it affects only some ISPs, we can maybe use the ISP DB
8528 * and disable CRAM specifically for these.
8529 * 3. Prefs are set to require auth methods which the server doesn't support
8530 * (per CAPS or we tried and they failed).
8531 * 4. User provided wrong password.
8532 * 5. We tried too often and the server shut us down, so even a correct attempt
8533 * will now (currently) fail.
8534 * The above problems may overlap, e.g. 3. with 1. and 2., and we can't differentiate
8535 * between 2. and 4., which is really unfortunate.
8536 */
8537 // clang-format on
8538
8539 bool newPasswordRequested = false;
8540 // remember the msgWindow before we start trying to logon, because if the
8541 // server drops the connection on errors, TellThreadToDie will null out the
8542 // protocolsink and we won't be able to get the msgWindow.
8543 AutoProxyReleaseMsgWindow msgWindow;
8544 GetMsgWindow(getter_AddRefs(msgWindow));
8545
8546 // This loops over 1) auth methods (only one per loop) and 2) password tries
8547 // (with UI)
8548 while (!loginSucceeded && !skipLoop && !DeathSignalReceived()) {
8549 // Get password
8550 if (m_currentAuthMethod !=
8551 kHasAuthGssApiCapability && // GSSAPI uses no pw in apps
8552 m_currentAuthMethod != kHasAuthExternalCapability &&
8553 m_currentAuthMethod != kHasXOAuth2Capability &&
8554 m_currentAuthMethod != kHasAuthNoneCapability) {
8555 rv = GetPassword(password, newPasswordRequested);
8556 newPasswordRequested = false;
8557 if (rv == NS_MSG_PASSWORD_PROMPT_CANCELLED || NS_FAILED(rv)) {
8558 MOZ_LOG(IMAP, LogLevel::Error,
8559 ("IMAP: password prompt failed or user canceled it"));
8560 break;
8561 }
8562 MOZ_LOG(IMAP, LogLevel::Debug, ("got new password"));
8563 }
8564
8565 bool lastReportingErrors = GetServerStateParser().GetReportingErrors();
8566 GetServerStateParser().SetReportingErrors(
8567 false); // turn off errors - we'll put up our own.
8568
8569 rv = AuthLogin(userName.get(), password, m_currentAuthMethod);
8570
8571 GetServerStateParser().SetReportingErrors(
8572 lastReportingErrors); // restore error reports
8573 loginSucceeded = NS_SUCCEEDED(rv);
8574
8575 if (!loginSucceeded) {
8576 MOZ_LOG(IMAP, LogLevel::Debug, ("authlogin failed"));
8577 MarkAuthMethodAsFailed(m_currentAuthMethod);
8578 rv = ChooseAuthMethod(); // change m_currentAuthMethod to try other one
8579 // next round
8580
8581 if (NS_FAILED(rv)) // all methods failed
8582 {
8583 if (m_prefAuthMethods == kHasAuthGssApiCapability) {
8584 // GSSAPI failed, and it's the only available method,
8585 // and it's password-less, so nothing left to do.
8586 AlertUserEventUsingName("imapAuthGssapiFailed");
8587 break;
8588 }
8589
8590 if (m_prefAuthMethods & kHasXOAuth2Capability) {
8591 // OAuth2 failed. Entering password does not help.
8592 AlertUserEventUsingName("imapOAuth2Error");
8593 break;
8594 }
8595
8596 // The reason that we failed might be a wrong password, so
8597 // ask user what to do
8598 MOZ_LOG(IMAP, LogLevel::Warning,
8599 ("IMAP: ask user what to do (after login failed): new "
8600 "passwort, retry, cancel"));
8601 if (!m_imapServerSink) break;
8602 // if there's no msg window, don't forget the password
8603 if (!msgWindow) break;
8604 int32_t buttonPressed = 1;
8605 rv = m_imapServerSink->PromptLoginFailed(msgWindow, &buttonPressed);
8606 if (NS_FAILED(rv)) break;
8607 if (buttonPressed == 2) // 'New password' button
8608 {
8609 MOZ_LOG(IMAP, LogLevel::Warning, ("new password button pressed."));
8610 // Forget the current password
8611 password.Truncate();
8612 m_hostSessionList->SetPasswordForHost(GetImapServerKey(),
8613 EmptyString());
8614 m_imapServerSink->ForgetPassword();
8615 m_password.Truncate();
8616 MOZ_LOG(IMAP, LogLevel::Warning, ("password reset (nulled)"));
8617 newPasswordRequested = true;
8618 // Will call GetPassword() in beginning of next loop
8619
8620 // Try all possible auth methods again with the new password.
8621 ResetAuthMethods();
8622 } else if (buttonPressed == 0) // Retry button
8623 {
8624 MOZ_LOG(IMAP, LogLevel::Warning, ("retry button pressed"));
8625 // Try all possible auth methods again
8626 ResetAuthMethods();
8627 } else if (buttonPressed == 1) // Cancel button
8628 {
8629 MOZ_LOG(IMAP, LogLevel::Warning, ("cancel button pressed"));
8630 break; // Abort quickly
8631 }
8632
8633 // TODO what is this for? When does it get set to != unknown again?
8634 m_currentBiffState = nsIMsgFolder::nsMsgBiffState_Unknown;
8635 SendSetBiffIndicatorEvent(m_currentBiffState);
8636 } // all methods failed
8637 } // login failed
8638 } // while
8639
8640 if (loginSucceeded) {
8641 MOZ_LOG(IMAP, LogLevel::Debug, ("login succeeded"));
8642 bool passwordAlreadyVerified;
8643 m_hostSessionList->SetPasswordForHost(GetImapServerKey(), password);
8644 rv = m_hostSessionList->GetPasswordVerifiedOnline(GetImapServerKey(),
8645 passwordAlreadyVerified);
8646 if (NS_SUCCEEDED(rv) && !passwordAlreadyVerified)
8647 m_hostSessionList->SetPasswordVerifiedOnline(GetImapServerKey());
8648 bool imapPasswordIsNew = !passwordAlreadyVerified;
8649 if (imapPasswordIsNew) {
8650 if (m_currentBiffState == nsIMsgFolder::nsMsgBiffState_Unknown) {
8651 m_currentBiffState = nsIMsgFolder::nsMsgBiffState_NoMail;
8652 SendSetBiffIndicatorEvent(m_currentBiffState);
8653 }
8654 m_imapServerSink->SetUserAuthenticated(true);
8655 }
8656
8657 nsImapAction imapAction;
8658 m_runningUrl->GetImapAction(&imapAction);
8659 // We don't want to do any more processing if we're just
8660 // verifying the ability to logon because it can leave us in
8661 // a half-constructed state.
8662 if (imapAction != nsIImapUrl::nsImapVerifylogon)
8663 ProcessAfterAuthenticated();
8664 } else // login failed
8665 {
8666 MOZ_LOG(IMAP, LogLevel::Error, ("login failed entirely"));
8667 m_currentBiffState = nsIMsgFolder::nsMsgBiffState_Unknown;
8668 SendSetBiffIndicatorEvent(m_currentBiffState);
8669 HandleCurrentUrlError();
8670 SetConnectionStatus(NS_ERROR_FAILURE); // stop netlib
8671 }
8672
8673 return loginSucceeded;
8674 }
8675
UpdateFolderQuotaData(nsImapQuotaAction aAction,nsCString & aQuotaRoot,uint64_t aUsed,uint64_t aMax)8676 void nsImapProtocol::UpdateFolderQuotaData(nsImapQuotaAction aAction,
8677 nsCString& aQuotaRoot,
8678 uint64_t aUsed, uint64_t aMax) {
8679 NS_ASSERTION(m_imapMailFolderSink, "m_imapMailFolderSink is null!");
8680
8681 m_imapMailFolderSink->SetFolderQuotaData(aAction, aQuotaRoot, aUsed, aMax);
8682 }
8683
GetQuotaDataIfSupported(const char * aBoxName)8684 void nsImapProtocol::GetQuotaDataIfSupported(const char* aBoxName) {
8685 // If server doesn't have quota support, don't do anything
8686 if (!(GetServerStateParser().GetCapabilityFlag() & kQuotaCapability)) return;
8687
8688 nsCString escapedName;
8689 CreateEscapedMailboxName(aBoxName, escapedName);
8690
8691 IncrementCommandTagNumber();
8692
8693 nsAutoCString quotacommand(GetServerCommandTag());
8694 quotacommand.AppendLiteral(" getquotaroot \"");
8695 quotacommand.Append(escapedName);
8696 quotacommand.AppendLiteral("\"" CRLF);
8697
8698 NS_ASSERTION(m_imapMailFolderSink, "m_imapMailFolderSink is null!");
8699 if (m_imapMailFolderSink)
8700 m_imapMailFolderSink->SetFolderQuotaCommandIssued(true);
8701
8702 nsresult quotarv = SendData(quotacommand.get());
8703 if (NS_SUCCEEDED(quotarv))
8704 ParseIMAPandCheckForNewMail(nullptr, true); // don't display errors.
8705 }
8706
GetDeleteIsMoveToTrash()8707 bool nsImapProtocol::GetDeleteIsMoveToTrash() {
8708 bool rv = false;
8709 NS_ASSERTION(m_hostSessionList, "fatal... null host session list");
8710 if (m_hostSessionList)
8711 m_hostSessionList->GetDeleteIsMoveToTrashForHost(GetImapServerKey(), rv);
8712 return rv;
8713 }
8714
GetShowDeletedMessages()8715 bool nsImapProtocol::GetShowDeletedMessages() {
8716 bool rv = false;
8717 if (m_hostSessionList)
8718 m_hostSessionList->GetShowDeletedMessagesForHost(GetImapServerKey(), rv);
8719 return rv;
8720 }
8721
CheckNeeded()8722 bool nsImapProtocol::CheckNeeded() {
8723 if (m_flagChangeCount >= kFlagChangesBeforeCheck) return true;
8724
8725 int32_t deltaInSeconds;
8726
8727 PRTime2Seconds(PR_Now() - m_lastCheckTime, &deltaInSeconds);
8728
8729 return (deltaInSeconds >= kMaxSecondsBeforeCheck);
8730 }
8731
UseCondStore()8732 bool nsImapProtocol::UseCondStore() {
8733 // Check that the server is capable of cond store, and the user
8734 // hasn't disabled the use of constore for this server.
8735 return m_useCondStore &&
8736 GetServerStateParser().GetCapabilityFlag() & kHasCondStoreCapability &&
8737 GetServerStateParser().fUseModSeq;
8738 }
8739
UseCompressDeflate()8740 bool nsImapProtocol::UseCompressDeflate() {
8741 // Check that the server is capable of compression, and the user
8742 // hasn't disabled the use of compression for this server.
8743 return m_useCompressDeflate && GetServerStateParser().GetCapabilityFlag() &
8744 kHasCompressDeflateCapability;
8745 }
8746
8747 //////////////////////////////////////////////////////////////////////////////////////////////
8748 // The following is the implementation of nsImapMockChannel and an intermediary
8749 // imap steam listener. The stream listener is used to make a clean binding
8750 // between the imap mock channel and the memory cache channel (if we are reading
8751 // from the cache)
8752 //////////////////////////////////////////////////////////////////////////////////////////////
8753
8754 // WARNING: the cache stream listener is intended to be accessed from the UI
8755 // thread! it will NOT create another proxy for the stream listener that gets
8756 // passed in...
8757 class nsImapCacheStreamListener : public nsIStreamListener {
8758 public:
8759 NS_DECL_ISUPPORTS
8760 NS_DECL_NSIREQUESTOBSERVER
8761 NS_DECL_NSISTREAMLISTENER
8762
8763 nsImapCacheStreamListener();
8764
8765 nsresult Init(nsIStreamListener* aStreamListener,
8766 nsIImapMockChannel* aMockChannelToUse);
8767
8768 protected:
8769 virtual ~nsImapCacheStreamListener();
8770 nsCOMPtr<nsIImapMockChannel> mChannelToUse;
8771 nsCOMPtr<nsIStreamListener> mListener;
8772 };
8773
8774 NS_IMPL_ADDREF(nsImapCacheStreamListener)
NS_IMPL_RELEASE(nsImapCacheStreamListener)8775 NS_IMPL_RELEASE(nsImapCacheStreamListener)
8776
8777 NS_INTERFACE_MAP_BEGIN(nsImapCacheStreamListener)
8778 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStreamListener)
8779 NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
8780 NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
8781 NS_INTERFACE_MAP_END
8782
8783 nsImapCacheStreamListener::nsImapCacheStreamListener() {}
8784
~nsImapCacheStreamListener()8785 nsImapCacheStreamListener::~nsImapCacheStreamListener() {}
8786
Init(nsIStreamListener * aStreamListener,nsIImapMockChannel * aMockChannelToUse)8787 nsresult nsImapCacheStreamListener::Init(
8788 nsIStreamListener* aStreamListener, nsIImapMockChannel* aMockChannelToUse) {
8789 NS_ENSURE_ARG(aStreamListener);
8790 NS_ENSURE_ARG(aMockChannelToUse);
8791
8792 mChannelToUse = aMockChannelToUse;
8793 mListener = aStreamListener;
8794
8795 return NS_OK;
8796 }
8797
8798 NS_IMETHODIMP
OnStartRequest(nsIRequest * request)8799 nsImapCacheStreamListener::OnStartRequest(nsIRequest* request) {
8800 if (!mChannelToUse) {
8801 NS_ERROR("OnStartRequest called after OnStopRequest");
8802 return NS_ERROR_NULL_POINTER;
8803 }
8804 nsCOMPtr<nsILoadGroup> loadGroup;
8805 mChannelToUse->GetLoadGroup(getter_AddRefs(loadGroup));
8806 if (loadGroup)
8807 loadGroup->AddRequest(mChannelToUse, nullptr /* context isupports */);
8808 return mListener->OnStartRequest(mChannelToUse);
8809 }
8810
8811 NS_IMETHODIMP
OnStopRequest(nsIRequest * request,nsresult aStatus)8812 nsImapCacheStreamListener::OnStopRequest(nsIRequest* request,
8813 nsresult aStatus) {
8814 if (!mListener) {
8815 NS_ERROR("OnStopRequest called twice");
8816 return NS_ERROR_NULL_POINTER;
8817 }
8818 nsresult rv = mListener->OnStopRequest(mChannelToUse, aStatus);
8819 nsCOMPtr<nsILoadGroup> loadGroup;
8820 mChannelToUse->GetLoadGroup(getter_AddRefs(loadGroup));
8821 if (loadGroup) loadGroup->RemoveRequest(mChannelToUse, nullptr, aStatus);
8822
8823 mListener = nullptr;
8824 mChannelToUse->Close();
8825 mChannelToUse = nullptr;
8826 return rv;
8827 }
8828
8829 NS_IMETHODIMP
OnDataAvailable(nsIRequest * request,nsIInputStream * aInStream,uint64_t aSourceOffset,uint32_t aCount)8830 nsImapCacheStreamListener::OnDataAvailable(nsIRequest* request,
8831 nsIInputStream* aInStream,
8832 uint64_t aSourceOffset,
8833 uint32_t aCount) {
8834 return mListener->OnDataAvailable(mChannelToUse, aInStream, aSourceOffset,
8835 aCount);
8836 }
8837
NS_IMPL_ISUPPORTS_INHERITED(nsImapMockChannel,nsHashPropertyBag,nsIImapMockChannel,nsIChannel,nsIRequest,nsICacheEntryOpenCallback,nsITransportEventSink,nsISupportsWeakReference)8838 NS_IMPL_ISUPPORTS_INHERITED(nsImapMockChannel, nsHashPropertyBag,
8839 nsIImapMockChannel, nsIChannel, nsIRequest,
8840 nsICacheEntryOpenCallback, nsITransportEventSink,
8841 nsISupportsWeakReference)
8842
8843 nsImapMockChannel::nsImapMockChannel()
8844 : mSuspendedMonitor("nsImapMockChannel"), mSuspended(false) {
8845 m_cancelStatus = NS_OK;
8846 mLoadFlags = 0;
8847 mChannelClosed = false;
8848 mReadingFromCache = false;
8849 mTryingToReadPart = false;
8850 mContentLength = mozilla::dom::InternalResponse::UNKNOWN_BODY_SIZE;
8851 mContentDisposition = nsIChannel::DISPOSITION_INLINE;
8852 }
8853
~nsImapMockChannel()8854 nsImapMockChannel::~nsImapMockChannel() {
8855 // if we're offline, we may not get to close the channel correctly.
8856 // we need to do this to send the url state change notification in
8857 // the case of mem and disk cache reads.
8858 MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread(),
8859 "should only access mock channel on ui thread");
8860 if (!mChannelClosed) Close();
8861 }
8862
NotifyStartEndReadFromCache(bool start)8863 nsresult nsImapMockChannel::NotifyStartEndReadFromCache(bool start) {
8864 nsresult rv = NS_OK;
8865 mReadingFromCache = start;
8866 nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(m_url, &rv);
8867 nsCOMPtr<nsIImapProtocol> imapProtocol = do_QueryReferent(mProtocol);
8868 if (imapUrl) {
8869 nsCOMPtr<nsIImapMailFolderSink> folderSink;
8870 rv = imapUrl->GetImapMailFolderSink(getter_AddRefs(folderSink));
8871 if (folderSink) {
8872 nsCOMPtr<nsIMsgMailNewsUrl> mailUrl = do_QueryInterface(m_url);
8873 rv = folderSink->SetUrlState(nullptr /* we don't know the protocol */,
8874 mailUrl, start, false, m_cancelStatus);
8875
8876 // Required for killing ImapProtocol thread
8877 if (NS_FAILED(m_cancelStatus) && imapProtocol)
8878 imapProtocol->TellThreadToDie(false);
8879 }
8880 }
8881 return rv;
8882 }
8883
Close()8884 NS_IMETHODIMP nsImapMockChannel::Close() {
8885 if (mReadingFromCache)
8886 NotifyStartEndReadFromCache(false);
8887 else {
8888 nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url);
8889 if (mailnewsUrl) {
8890 nsCOMPtr<nsICacheEntry> cacheEntry;
8891 mailnewsUrl->GetMemCacheEntry(getter_AddRefs(cacheEntry));
8892 if (cacheEntry) cacheEntry->MarkValid();
8893 // remove the channel from the load group
8894 nsCOMPtr<nsILoadGroup> loadGroup;
8895 GetLoadGroup(getter_AddRefs(loadGroup));
8896 // if the mock channel wasn't initialized with a load group then
8897 // use our load group (they may differ)
8898 if (!loadGroup) mailnewsUrl->GetLoadGroup(getter_AddRefs(loadGroup));
8899 if (loadGroup)
8900 loadGroup->RemoveRequest((nsIRequest*)this, nullptr, NS_OK);
8901 }
8902 }
8903
8904 m_channelListener = nullptr;
8905 mCacheRequest = nullptr;
8906 if (mTryingToReadPart) {
8907 // clear mem cache entry on imap part url in case it's holding
8908 // onto last reference in mem cache. Need to do this on ui thread
8909 nsresult rv;
8910 nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(m_url, &rv);
8911 if (imapUrl) {
8912 nsCOMPtr<nsIImapMailFolderSink> folderSink;
8913 rv = imapUrl->GetImapMailFolderSink(getter_AddRefs(folderSink));
8914 if (folderSink) {
8915 nsCOMPtr<nsIMsgMailNewsUrl> mailUrl = do_QueryInterface(m_url);
8916 rv = folderSink->ReleaseUrlCacheEntry(mailUrl);
8917 }
8918 }
8919 }
8920 mChannelClosed = true;
8921 return NS_OK;
8922 }
8923
GetProgressEventSink(nsIProgressEventSink ** aProgressEventSink)8924 NS_IMETHODIMP nsImapMockChannel::GetProgressEventSink(
8925 nsIProgressEventSink** aProgressEventSink) {
8926 NS_IF_ADDREF(*aProgressEventSink = mProgressEventSink);
8927 return NS_OK;
8928 }
8929
SetProgressEventSink(nsIProgressEventSink * aProgressEventSink)8930 NS_IMETHODIMP nsImapMockChannel::SetProgressEventSink(
8931 nsIProgressEventSink* aProgressEventSink) {
8932 mProgressEventSink = aProgressEventSink;
8933 return NS_OK;
8934 }
8935
GetChannelListener(nsIStreamListener ** aChannelListener)8936 NS_IMETHODIMP nsImapMockChannel::GetChannelListener(
8937 nsIStreamListener** aChannelListener) {
8938 NS_IF_ADDREF(*aChannelListener = m_channelListener);
8939 return NS_OK;
8940 }
8941
8942 // now implement our mock implementation of the channel interface...we forward
8943 // all calls to the real channel if we have one...otherwise we return something
8944 // bogus...
8945
SetLoadGroup(nsILoadGroup * aLoadGroup)8946 NS_IMETHODIMP nsImapMockChannel::SetLoadGroup(nsILoadGroup* aLoadGroup) {
8947 m_loadGroup = aLoadGroup;
8948 return NS_OK;
8949 }
8950
GetLoadGroup(nsILoadGroup ** aLoadGroup)8951 NS_IMETHODIMP nsImapMockChannel::GetLoadGroup(nsILoadGroup** aLoadGroup) {
8952 NS_IF_ADDREF(*aLoadGroup = m_loadGroup);
8953 return NS_OK;
8954 }
8955
GetTRRMode(nsIRequest::TRRMode * aTRRMode)8956 NS_IMETHODIMP nsImapMockChannel::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
8957 return GetTRRModeImpl(aTRRMode);
8958 }
8959
SetTRRMode(nsIRequest::TRRMode aTRRMode)8960 NS_IMETHODIMP nsImapMockChannel::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
8961 return SetTRRModeImpl(aTRRMode);
8962 }
8963
GetLoadInfo(nsILoadInfo ** aLoadInfo)8964 NS_IMETHODIMP nsImapMockChannel::GetLoadInfo(nsILoadInfo** aLoadInfo) {
8965 NS_IF_ADDREF(*aLoadInfo = m_loadInfo);
8966 return NS_OK;
8967 }
8968
SetLoadInfo(nsILoadInfo * aLoadInfo)8969 NS_IMETHODIMP nsImapMockChannel::SetLoadInfo(nsILoadInfo* aLoadInfo) {
8970 m_loadInfo = aLoadInfo;
8971 return NS_OK;
8972 }
8973
GetOriginalURI(nsIURI ** aURI)8974 NS_IMETHODIMP nsImapMockChannel::GetOriginalURI(nsIURI** aURI) {
8975 // IMap does not seem to have the notion of an original URI :-(
8976 // *aURI = m_originalUrl ? m_originalUrl : m_url;
8977 NS_IF_ADDREF(*aURI = m_url);
8978 return NS_OK;
8979 }
8980
SetOriginalURI(nsIURI * aURI)8981 NS_IMETHODIMP nsImapMockChannel::SetOriginalURI(nsIURI* aURI) {
8982 // IMap does not seem to have the notion of an original URI :-(
8983 // MOZ_ASSERT_UNREACHABLE("nsImapMockChannel::SetOriginalURI");
8984 // return NS_ERROR_NOT_IMPLEMENTED;
8985 return NS_OK; // ignore
8986 }
8987
GetURI(nsIURI ** aURI)8988 NS_IMETHODIMP nsImapMockChannel::GetURI(nsIURI** aURI) {
8989 NS_IF_ADDREF(*aURI = m_url);
8990 return NS_OK;
8991 }
8992
SetURI(nsIURI * aURI)8993 NS_IMETHODIMP nsImapMockChannel::SetURI(nsIURI* aURI) {
8994 m_url = aURI;
8995 #ifdef DEBUG_bienvenu
8996 if (!aURI) printf("Clearing URI\n");
8997 #endif
8998 if (m_url) {
8999 // if we don't have a progress event sink yet, get it from the url for
9000 // now...
9001 nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url);
9002 if (mailnewsUrl && !mProgressEventSink) {
9003 nsCOMPtr<nsIMsgStatusFeedback> statusFeedback;
9004 mailnewsUrl->GetStatusFeedback(getter_AddRefs(statusFeedback));
9005 mProgressEventSink = do_QueryInterface(statusFeedback);
9006 }
9007 // If this is a fetch URL and we can, get the message size from the message
9008 // header and set it to be the content length.
9009 // Note that for an attachment URL, this will set the content length to be
9010 // equal to the size of the entire message.
9011 nsCOMPtr<nsIImapUrl> imapUrl(do_QueryInterface(m_url));
9012 nsImapAction imapAction;
9013 imapUrl->GetImapAction(&imapAction);
9014 if (imapAction == nsIImapUrl::nsImapMsgFetch) {
9015 nsCOMPtr<nsIMsgMessageUrl> msgUrl(do_QueryInterface(m_url));
9016 if (msgUrl) {
9017 nsCOMPtr<nsIMsgDBHdr> msgHdr;
9018 // A failure to get a message header isn't an error
9019 msgUrl->GetMessageHeader(getter_AddRefs(msgHdr));
9020 if (msgHdr) {
9021 uint32_t messageSize;
9022 if (NS_SUCCEEDED(msgHdr->GetMessageSize(&messageSize)))
9023 SetContentLength(messageSize);
9024 }
9025 }
9026 }
9027 }
9028 return NS_OK;
9029 }
9030
Open(nsIInputStream ** _retval)9031 NS_IMETHODIMP nsImapMockChannel::Open(nsIInputStream** _retval) {
9032 nsCOMPtr<nsIStreamListener> listener;
9033 nsresult rv =
9034 nsContentSecurityManager::doContentSecurityCheck(this, listener);
9035 NS_ENSURE_SUCCESS(rv, rv);
9036 if (m_url) {
9037 nsCOMPtr<nsIImapUrl> imapUrl(do_QueryInterface(m_url, &rv));
9038 NS_ENSURE_SUCCESS(rv, rv);
9039 nsImapAction imapAction;
9040 imapUrl->GetImapAction(&imapAction);
9041 // If we're shutting down, and not running the kinds of urls we run at
9042 // shutdown, then this should fail because running urls during
9043 // shutdown will very likely fail and potentially hang.
9044 nsCOMPtr<nsIMsgAccountManager> accountMgr =
9045 do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
9046 NS_ENSURE_SUCCESS(rv, rv);
9047 bool shuttingDown = false;
9048 (void)accountMgr->GetShutdownInProgress(&shuttingDown);
9049 if (shuttingDown && imapAction != nsIImapUrl::nsImapExpungeFolder &&
9050 imapAction != nsIImapUrl::nsImapDeleteAllMsgs &&
9051 imapAction != nsIImapUrl::nsImapDeleteFolder)
9052 return NS_ERROR_FAILURE;
9053 }
9054 return NS_ImplementChannelOpen(this, _retval);
9055 }
9056
9057 NS_IMETHODIMP
OnCacheEntryAvailable(nsICacheEntry * entry,bool aNew,nsresult status)9058 nsImapMockChannel::OnCacheEntryAvailable(nsICacheEntry* entry, bool aNew,
9059 nsresult status) {
9060 if (MOZ_LOG_TEST(IMAPCache, LogLevel::Debug)) {
9061 MOZ_LOG(IMAPCache, LogLevel::Debug,
9062 ("OnCacheEntryAvailable(): Create/write new cache entry=%s",
9063 aNew ? "true" : "false"));
9064 MOZ_LOG(IMAPCache, LogLevel::Debug,
9065 ("OnCacheEntryAvailable(): Get part from entire message=%s",
9066 mTryingToReadPart ? "true" : "false"));
9067 nsAutoCString key;
9068 if (entry) entry->GetKey(key);
9069 MOZ_LOG(IMAPCache, LogLevel::Debug,
9070 ("OnCacheEntryAvailable(): Cache entry key = |%s|", key.get()));
9071 }
9072 nsresult rv = NS_OK;
9073
9074 // make sure we didn't close the channel before the async call back came in...
9075 // hmmm....if we had write access and we canceled this mock channel then I
9076 // wonder if we should be invalidating the cache entry before kicking out...
9077 if (mChannelClosed) {
9078 entry->AsyncDoom(nullptr);
9079 return NS_OK;
9080 }
9081
9082 if (!m_url) {
9083 // Something has gone terribly wrong.
9084 NS_WARNING("m_url is null in OnCacheEntryAvailable");
9085 return Cancel(NS_ERROR_UNEXPECTED);
9086 }
9087
9088 do {
9089 // For "normal" read/write access we always receive NS_OK here. aNew
9090 // indicates whether the cache entry is new and needs to be written, or not
9091 // new and can be read. If AsyncOpenURI() was called with access read-only,
9092 // status==NS_ERROR_CACHE_KEY_NOT_FOUND can be received here and we just
9093 // read the data directly.
9094 if (NS_FAILED(status)) {
9095 MOZ_LOG(IMAPCache, LogLevel::Debug,
9096 ("OnCacheEntryAvailable(): status parameter bad, preference "
9097 "browser.cache.memory not enabled?"));
9098 break;
9099 }
9100
9101 nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url, &rv);
9102 mailnewsUrl->SetMemCacheEntry(entry);
9103
9104 // For URLs not related to parts, the processing is easy:
9105 // aNew==true means that we need to write to the entry,
9106 // aNew==false means that we can read it.
9107 //
9108 // Parts processing is a little complicated, we distinguish two cases:
9109 // 1) The caller a) knows that the part is there or
9110 // b) it is not there and can also not be read from the
9111 // entire message.
9112 // In this case, the URL used as cache key addresses the part and
9113 // mTryingToReadPart==false.
9114 // The caller has already set up part extraction.
9115 // This case is no different to non-part processing.
9116 // 2) The caller wants to try to extract the part from the cache entry
9117 // of the entire message.
9118 // In this case, the URL used as cache key addresses the message and
9119 // mTryingToReadPart==true.
9120 if (mTryingToReadPart) {
9121 // clang-format off
9122 MOZ_LOG(IMAPCache, LogLevel::Debug,
9123 ("OnCacheEntryAvailable(): Trying to read part from entire message"));
9124 // clang-format on
9125 // We are here with the URI of the entire message which we know exists.
9126 MOZ_ASSERT(!aNew,
9127 "Logic error: Trying to read part from entire message which "
9128 "doesn't exist");
9129 if (!aNew) {
9130 // Check the meta data.
9131 nsCString annotation;
9132 rv = entry->GetMetaDataElement("ContentModified",
9133 getter_Copies(annotation));
9134 if (NS_FAILED(rv) || !annotation.EqualsLiteral("Not Modified")) {
9135 MOZ_LOG(IMAPCache, LogLevel::Debug,
9136 ("OnCacheEntryAvailable(): Entry is not complete and cannot "
9137 "be used"));
9138 // The cache entry is not marked "Not Modified", that means it doesn't
9139 // contain the entire message, so we can't use it.
9140 // Call OpenCacheEntry() a second time to get the part.
9141 rv = OpenCacheEntry();
9142 if (NS_SUCCEEDED(rv)) return rv;
9143
9144 // Something has gone wrong, fall back to reading from the imap
9145 // connection so the protocol doesn't hang.
9146 break;
9147 }
9148 }
9149 }
9150
9151 if (aNew) {
9152 MOZ_LOG(IMAPCache, LogLevel::Debug,
9153 ("OnCacheEntryAvailable(): Begin cache WRITE"));
9154 // If we are writing, then insert a "stream listener Tee" into the flow
9155 // to force data into the cache and to our current channel listener.
9156 nsCOMPtr<nsIStreamListenerTee> tee =
9157 do_CreateInstance(NS_STREAMLISTENERTEE_CONTRACTID, &rv);
9158 if (NS_SUCCEEDED(rv)) {
9159 nsCOMPtr<nsIOutputStream> out;
9160 // This will fail with the cache turned off, so we need to fall through
9161 // to ReadFromImapConnection instead of aborting with
9162 // NS_ENSURE_SUCCESS(rv,rv).
9163 rv = entry->OpenOutputStream(0, -1, getter_AddRefs(out));
9164 if (NS_SUCCEEDED(rv)) {
9165 rv = tee->Init(m_channelListener, out, nullptr);
9166 m_channelListener = tee;
9167 } else
9168 NS_WARNING(
9169 "IMAP Protocol failed to open output stream to Necko cache");
9170 }
9171 } else {
9172 if (MOZ_LOG_TEST(IMAPCache, LogLevel::Debug)) {
9173 int64_t size = 0;
9174 rv = entry->GetDataSize(&size);
9175 if (rv == NS_ERROR_IN_PROGRESS)
9176 MOZ_LOG(IMAPCache, LogLevel::Debug,
9177 ("OnCacheEntryAvailable(): Concurrent cache READ, no size "
9178 "available"));
9179 MOZ_LOG(IMAPCache, LogLevel::Debug,
9180 ("OnCacheEntryAvailable(): Begin cache READ, size=%" PRIi64 " ",
9181 size));
9182 }
9183 rv = ReadFromMemCache(entry);
9184 if (NS_SUCCEEDED(rv)) {
9185 NotifyStartEndReadFromCache(true);
9186 entry->MarkValid();
9187 return NS_OK; // Kick out if reading from the cache succeeded.
9188 }
9189 entry->AsyncDoom(nullptr); // Doom entry if we failed to read from cache.
9190 mailnewsUrl->SetMemCacheEntry(
9191 nullptr); // We aren't going to be reading from the cache.
9192 }
9193 } while (false);
9194
9195 // If reading from the cache failed or if we are writing into the cache,
9196 // default to ReadFromImapConnection.
9197 if (aNew)
9198 MOZ_LOG(IMAPCache, LogLevel::Debug,
9199 ("OnCacheEntryAvailable(): Cache WRITE start successful"));
9200 else
9201 MOZ_LOG(IMAPCache, LogLevel::Debug,
9202 ("OnCacheEntryAvailable: Cache READ failed"));
9203 return ReadFromImapConnection();
9204 }
9205
9206 NS_IMETHODIMP
OnCacheEntryCheck(nsICacheEntry * entry,uint32_t * aResult)9207 nsImapMockChannel::OnCacheEntryCheck(nsICacheEntry* entry, uint32_t* aResult) {
9208 *aResult = nsICacheEntryOpenCallback::ENTRY_WANTED;
9209
9210 // Check concurrent read: We can't read concurrently since we don't know
9211 // that the entry will ever be written successfully. It may be aborted
9212 // due to a size limitation. If reading concurrently, the following function
9213 // will return NS_ERROR_IN_PROGRESS. Then we tell the cache to wait until
9214 // the write is finished.
9215 int64_t size = 0;
9216 nsresult rv = entry->GetDataSize(&size);
9217 if (rv == NS_ERROR_IN_PROGRESS) {
9218 *aResult = nsICacheEntryOpenCallback::RECHECK_AFTER_WRITE_FINISHED;
9219 MOZ_LOG(IMAPCache, LogLevel::Debug,
9220 ("OnCacheEntryCheck(): Attempted cache write while reading, will "
9221 "try again"));
9222 }
9223 return NS_OK;
9224 }
9225
OpenCacheEntry()9226 nsresult nsImapMockChannel::OpenCacheEntry() {
9227 nsresult rv;
9228 // get the cache session from our imap service...
9229 nsCOMPtr<nsIImapService> imapService =
9230 do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
9231 NS_ENSURE_SUCCESS(rv, rv);
9232
9233 nsCOMPtr<nsICacheStorage> cacheStorage;
9234 rv = imapService->GetCacheStorage(getter_AddRefs(cacheStorage));
9235 NS_ENSURE_SUCCESS(rv, rv);
9236
9237 int32_t uidValidity = -1;
9238 uint32_t cacheAccess = nsICacheStorage::OPEN_NORMALLY;
9239
9240 nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(m_url, &rv);
9241 NS_ENSURE_SUCCESS(rv, rv);
9242
9243 bool storeResultsOffline;
9244 nsCOMPtr<nsIImapMailFolderSink> folderSink;
9245
9246 rv = imapUrl->GetImapMailFolderSink(getter_AddRefs(folderSink));
9247 if (folderSink) folderSink->GetUidValidity(&uidValidity);
9248 imapUrl->GetStoreResultsOffline(&storeResultsOffline);
9249 // If we're storing the message in the offline store, don't
9250 // write to the memory cache.
9251 if (storeResultsOffline) cacheAccess = nsICacheStorage::OPEN_READONLY;
9252
9253 // clang-format off
9254 MOZ_LOG(IMAPCache, LogLevel::Debug,
9255 ("OpenCacheEntry(): For URL = |%s|", m_url->GetSpecOrDefault().get()));
9256 // clang-format on
9257
9258 // Use the uid validity as part of the cache key, so that if the uid validity
9259 // changes, we won't re-use the wrong cache entries.
9260 nsAutoCString extension;
9261 extension.AppendInt(uidValidity, 16);
9262
9263 // Open a cache entry where the key is the potentially modified URL.
9264 nsAutoCString path;
9265 m_url->GetPathQueryRef(path);
9266
9267 // First we need to "normalise" the URL by extracting ?part= and &filename.
9268 // The path should only contain: ?part=x.y&filename=file.ext
9269 // These are seen in the wild:
9270 // /;section=2?part=1.2&filename=A01.JPG
9271 // ?section=2?part=1.2&filename=A01.JPG&type=image/jpeg&filename=A01.JPG
9272 // ?part=1.2&type=image/jpeg&filename=IMG_C0030.jpg
9273 // ?header=quotebody&part=1.2&filename=lijbmghmkilicioj.png
9274 nsCString partQuery = MsgExtractQueryPart(path, "?part=");
9275 if (partQuery.IsEmpty()) {
9276 partQuery = MsgExtractQueryPart(path, "&part=");
9277 if (!partQuery.IsEmpty()) {
9278 // ? indicates a part query, so set the first character to that.
9279 partQuery.SetCharAt('?', 0);
9280 }
9281 }
9282 nsCString filenameQuery = MsgExtractQueryPart(path, "&filename=");
9283 MOZ_LOG(IMAPCache, LogLevel::Debug,
9284 ("OpenCacheEntry: part = |%s|, filename = |%s|", partQuery.get(),
9285 filenameQuery.get()));
9286
9287 // Truncate path at either /; or ?
9288 MsgRemoveQueryPart(path);
9289
9290 nsCOMPtr<nsIURI> newUri;
9291 if (partQuery.IsEmpty()) {
9292 // Not looking for a part. That's the easy part.
9293 rv = NS_MutateURI(m_url).SetPathQueryRef(path).Finalize(newUri);
9294 NS_ENSURE_SUCCESS(rv, rv);
9295 MOZ_LOG(IMAPCache, LogLevel::Debug,
9296 ("OpenCacheEntry(): Call AsyncOpenURI() on entire message"));
9297 return cacheStorage->AsyncOpenURI(newUri, extension, cacheAccess, this);
9298 }
9299
9300 /**
9301 * Part processing (rest of this function).
9302 */
9303 if (mTryingToReadPart) {
9304 // If mTryingToReadPart is set, we are here for the second time.
9305 // We tried to read a part from the entire message but the meta data didn't
9306 // allow it. So we come back here.
9307 // Now request the part with its full URL.
9308 mTryingToReadPart = false;
9309
9310 // Note that part extraction was already set the first time.
9311 rv = NS_MutateURI(m_url)
9312 .SetPathQueryRef(path + partQuery + filenameQuery)
9313 .Finalize(newUri);
9314 NS_ENSURE_SUCCESS(rv, rv);
9315 MOZ_LOG(IMAPCache, LogLevel::Debug,
9316 ("OpenCacheEntry(): Call AsyncOpenURI() to write part (2nd try)"));
9317 return cacheStorage->AsyncOpenURI(newUri, extension, cacheAccess, this);
9318 }
9319
9320 // First time processing. Set up part extraction.
9321 SetupPartExtractorListener(imapUrl, m_channelListener);
9322
9323 // Check whether part is in the cache.
9324 bool exists = false;
9325 rv = NS_MutateURI(m_url)
9326 .SetPathQueryRef(path + partQuery + filenameQuery)
9327 .Finalize(newUri);
9328 NS_ENSURE_SUCCESS(rv, rv);
9329 rv = cacheStorage->Exists(newUri, extension, &exists);
9330 NS_ENSURE_SUCCESS(rv, rv);
9331 if (exists) {
9332 MOZ_LOG(IMAPCache, LogLevel::Debug,
9333 ("OpenCacheEntry(): Call AsyncOpenURI() to read part from its own "
9334 "cache"));
9335 return cacheStorage->AsyncOpenURI(newUri, extension, cacheAccess, this);
9336 }
9337
9338 // Let's see whether we have the entire message instead.
9339 rv = NS_MutateURI(m_url).SetPathQueryRef(path).Finalize(newUri);
9340 NS_ENSURE_SUCCESS(rv, rv);
9341 rv = cacheStorage->Exists(newUri, extension, &exists);
9342 NS_ENSURE_SUCCESS(rv, rv);
9343
9344 if (!exists) {
9345 // The entire message is not in the cache. Request the part.
9346 rv = NS_MutateURI(m_url)
9347 .SetPathQueryRef(path + partQuery + filenameQuery)
9348 .Finalize(newUri);
9349 NS_ENSURE_SUCCESS(rv, rv);
9350 MOZ_LOG(IMAPCache, LogLevel::Debug,
9351 ("OpenCacheEntry(): Call AsyncOpenURI() to write part (entire "
9352 "message cache doesn't exists)"));
9353 return cacheStorage->AsyncOpenURI(newUri, extension, cacheAccess, this);
9354 }
9355
9356 // This is where is gets complicated. The entire message is in the cache,
9357 // but we don't know whether it's suitable for use. Its meta data
9358 // might indicate that the message is incomplete.
9359 mTryingToReadPart = true;
9360 MOZ_LOG(IMAPCache, LogLevel::Debug,
9361 ("OpenCacheEntry(): Call AsyncOpenURI() to try to read part from "
9362 "entire message cache"));
9363 return cacheStorage->AsyncOpenURI(newUri, extension, cacheAccess, this);
9364 }
9365
ReadFromMemCache(nsICacheEntry * entry)9366 nsresult nsImapMockChannel::ReadFromMemCache(nsICacheEntry* entry) {
9367 NS_ENSURE_ARG(entry);
9368
9369 nsCString annotation;
9370 nsAutoCString entryKey;
9371 nsAutoCString contentType;
9372 nsresult rv = NS_OK;
9373 bool shouldUseCacheEntry = false;
9374
9375 entry->GetKey(entryKey);
9376 if (entryKey.FindChar('?') != kNotFound) {
9377 // Part processing: If we have a part, then we should use the cache entry.
9378 entry->GetMetaDataElement("contentType", getter_Copies(contentType));
9379 if (!contentType.IsEmpty()) SetContentType(contentType);
9380 shouldUseCacheEntry = true;
9381 MOZ_LOG(IMAPCache, LogLevel::Debug, ("ReadFromMemCache(): Reading a part"));
9382 } else {
9383 // Whole message processing: We should make sure the content isn't modified.
9384 rv =
9385 entry->GetMetaDataElement("ContentModified", getter_Copies(annotation));
9386 if (NS_SUCCEEDED(rv) && !annotation.IsEmpty())
9387 shouldUseCacheEntry = annotation.EqualsLiteral("Not Modified");
9388 MOZ_LOG(IMAPCache, LogLevel::Debug,
9389 ("ReadFromMemCache: Reading entire message, annotation: |%s|",
9390 annotation.get()));
9391
9392 // Compare cache entry size with message size.
9393 if (shouldUseCacheEntry) {
9394 int64_t entrySize;
9395
9396 rv = entry->GetDataSize(&entrySize);
9397 // We don't expect concurrent read here, so this call should always work.
9398 NS_ENSURE_SUCCESS(rv, rv);
9399
9400 nsCOMPtr<nsIMsgMessageUrl> msgUrl(do_QueryInterface(m_url));
9401 if (msgUrl) {
9402 nsCOMPtr<nsIMsgDBHdr> msgHdr;
9403 // A failure to get a message header isn't an error
9404 msgUrl->GetMessageHeader(getter_AddRefs(msgHdr));
9405 if (msgHdr) {
9406 uint32_t messageSize;
9407 if (NS_SUCCEEDED(msgHdr->GetMessageSize(&messageSize)) &&
9408 messageSize != entrySize) {
9409 // clang-format off
9410 MOZ_LOG(IMAP, LogLevel::Warning,
9411 ("ReadFromMemCache(): Size mismatch for %s: message %" PRIu32
9412 ", cache %" PRIi64,
9413 entryKey.get(), messageSize, entrySize));
9414 MOZ_LOG(IMAPCache, LogLevel::Debug,
9415 ("ReadFromMemCache(): Size mismatch for %s: message %" PRIu32
9416 ", cache %" PRIi64,
9417 entryKey.get(), messageSize, entrySize));
9418 // clang-format on
9419 shouldUseCacheEntry = false;
9420 }
9421 }
9422 }
9423 }
9424 }
9425
9426 /**
9427 * Common processing for full messages and message parts.
9428 */
9429 // clang-format off
9430 MOZ_LOG(IMAPCache, LogLevel::Debug,
9431 ("ReadFromMemCache(): End separate processing: shouldUseCacheEntry=%s",
9432 shouldUseCacheEntry ? "true" : "false"));
9433 // clang-format on
9434
9435 // Check header of full message or part.
9436 if (shouldUseCacheEntry) {
9437 nsCOMPtr<nsIInputStream> in;
9438 uint32_t readCount;
9439 rv = entry->OpenInputStream(0, getter_AddRefs(in));
9440 NS_ENSURE_SUCCESS(rv, rv);
9441 const int kFirstBlockSize = 100;
9442 char firstBlock[kFirstBlockSize + 1];
9443
9444 // Note: This will not work for a cache2 disk cache.
9445 // (see bug 1302422 comment #4)
9446 rv = in->Read(firstBlock, sizeof(firstBlock), &readCount);
9447 NS_ENSURE_SUCCESS(rv, rv);
9448 firstBlock[kFirstBlockSize] = '\0';
9449 int32_t findPos =
9450 MsgFindCharInSet(nsDependentCString(firstBlock), ":\n\r", 0);
9451 // Check that the first line is a header line, i.e., with a ':' in it
9452 // Or that it begins with "From " because some IMAP servers allow that,
9453 // even though it's technically invalid.
9454 shouldUseCacheEntry = ((findPos != -1 && firstBlock[findPos] == ':') ||
9455 !(strncmp(firstBlock, "From ", 5)));
9456 in->Close();
9457 }
9458
9459 MOZ_LOG(IMAPCache, LogLevel::Debug,
9460 ("ReadFromMemCache(): After header check: shouldUseCacheEntry=%s",
9461 shouldUseCacheEntry ? "true" : "false"));
9462
9463 if (shouldUseCacheEntry) {
9464 nsCOMPtr<nsIInputStream> in;
9465 rv = entry->OpenInputStream(0, getter_AddRefs(in));
9466 NS_ENSURE_SUCCESS(rv, rv);
9467 // if mem cache entry is broken or empty, return error.
9468 uint64_t bytesAvailable;
9469 rv = in->Available(&bytesAvailable);
9470 NS_ENSURE_SUCCESS(rv, rv);
9471 if (!bytesAvailable) return NS_ERROR_FAILURE;
9472
9473 nsCOMPtr<nsIInputStreamPump> pump;
9474 rv = NS_NewInputStreamPump(getter_AddRefs(pump), in.forget());
9475 NS_ENSURE_SUCCESS(rv, rv);
9476
9477 // if we are going to read from the cache, then create a mock stream
9478 // listener class and use it
9479 RefPtr<nsImapCacheStreamListener> cacheListener =
9480 new nsImapCacheStreamListener();
9481 cacheListener->Init(m_channelListener, this);
9482 rv = pump->AsyncRead(cacheListener);
9483
9484 if (NS_SUCCEEDED(rv)) // ONLY if we succeeded in actually starting the read
9485 // should we return
9486 {
9487 mCacheRequest = pump;
9488 nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(m_url);
9489 // if the msg is unread, we should mark it read on the server. This lets
9490 // the code running this url we're loading from the cache, if it cares.
9491 imapUrl->SetMsgLoadingFromCache(true);
9492
9493 // be sure to set the cache entry's security info status as our security
9494 // info status...
9495 nsCOMPtr<nsISupports> securityInfo;
9496 entry->GetSecurityInfo(getter_AddRefs(securityInfo));
9497 SetSecurityInfo(securityInfo);
9498 MOZ_LOG(IMAPCache, LogLevel::Debug,
9499 ("ReadFromMemCache(): Cache entry accepted"));
9500 return NS_OK;
9501 } // if AsyncRead succeeded.
9502 } // if content is not modified
9503 else {
9504 // Content is modified so return an error so we try to open it the
9505 // old fashioned way.
9506 MOZ_LOG(IMAPCache, LogLevel::Debug,
9507 ("ReadFromMemCache(): Cache entry rejected"));
9508 rv = NS_ERROR_FAILURE;
9509 }
9510 // clang-format off
9511 MOZ_LOG(IMAPCache, LogLevel::Debug,
9512 ("ReadFromMemCache(): Returning %" PRIx32, static_cast<uint32_t>(rv)));
9513 // clang-format on
9514 return rv;
9515 }
9516
9517 class nsReadFromImapConnectionFailure : public mozilla::Runnable {
9518 public:
nsReadFromImapConnectionFailure(nsImapMockChannel * aChannel)9519 explicit nsReadFromImapConnectionFailure(nsImapMockChannel* aChannel)
9520 : mozilla::Runnable("nsReadFromImapConnectionFailure"),
9521 mImapMockChannel(aChannel) {}
9522
Run()9523 NS_IMETHOD Run() {
9524 if (mImapMockChannel) {
9525 mImapMockChannel->RunOnStopRequestFailure();
9526 }
9527 return NS_OK;
9528 }
9529
9530 private:
9531 RefPtr<nsImapMockChannel> mImapMockChannel;
9532 };
9533
RunOnStopRequestFailure()9534 nsresult nsImapMockChannel::RunOnStopRequestFailure() {
9535 if (m_channelListener) {
9536 m_channelListener->OnStopRequest(this, NS_MSG_ERROR_MSG_NOT_OFFLINE);
9537 }
9538 return NS_OK;
9539 }
9540
9541 // the requested url isn't in any of our caches so create an imap connection
9542 // to process it.
ReadFromImapConnection()9543 nsresult nsImapMockChannel::ReadFromImapConnection() {
9544 nsresult rv = NS_OK;
9545 nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(m_url);
9546 nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url);
9547
9548 bool localOnly = false;
9549 imapUrl->GetLocalFetchOnly(&localOnly);
9550 if (localOnly) {
9551 // This will cause an OnStartRunningUrl, and the subsequent close
9552 // will then cause an OnStopRunningUrl with the cancel status.
9553 NotifyStartEndReadFromCache(true);
9554 Cancel(NS_MSG_ERROR_MSG_NOT_OFFLINE);
9555
9556 // Dispatch error notification, so ReadFromImapConnection() returns *before*
9557 // the error is sent to the listener's OnStopRequest(). This avoids
9558 // endless recursion where the caller relies on async execution.
9559 nsCOMPtr<nsIRunnable> event = new nsReadFromImapConnectionFailure(this);
9560 NS_DispatchToCurrentThread(event);
9561 return NS_MSG_ERROR_MSG_NOT_OFFLINE;
9562 }
9563
9564 nsCOMPtr<nsILoadGroup> loadGroup;
9565 GetLoadGroup(getter_AddRefs(loadGroup));
9566 if (!loadGroup) // if we don't have one, the url will snag one from the msg
9567 // window...
9568 mailnewsUrl->GetLoadGroup(getter_AddRefs(loadGroup));
9569
9570 // okay, add the mock channel to the load group..
9571 if (loadGroup)
9572 loadGroup->AddRequest((nsIRequest*)this, nullptr /* context isupports */);
9573
9574 // loading the url consists of asking the server to add the url to it's imap
9575 // event queue....
9576 nsCOMPtr<nsIMsgIncomingServer> server;
9577 rv = mailnewsUrl->GetServer(getter_AddRefs(server));
9578 NS_ENSURE_SUCCESS(rv, rv);
9579 nsCOMPtr<nsIImapIncomingServer> imapServer(do_QueryInterface(server, &rv));
9580 NS_ENSURE_SUCCESS(rv, rv);
9581
9582 // Assume AsyncRead is always called from the UI thread.....
9583 return imapServer->GetImapConnectionAndLoadUrl(imapUrl, m_channelListener);
9584 }
9585
9586 // for messages stored in our offline cache, we have special code to handle
9587 // that... If it's in the local cache, we return true and we can abort the
9588 // download because this method does the rest of the work.
ReadFromLocalCache()9589 bool nsImapMockChannel::ReadFromLocalCache() {
9590 nsresult rv = NS_OK;
9591
9592 nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(m_url);
9593 nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url, &rv);
9594
9595 bool useLocalCache = false;
9596 mailnewsUrl->GetMsgIsInLocalCache(&useLocalCache);
9597 if (!useLocalCache) {
9598 return false;
9599 }
9600
9601 nsAutoCString messageIdString;
9602
9603 SetupPartExtractorListener(imapUrl, m_channelListener);
9604
9605 imapUrl->GetListOfMessageIds(messageIdString);
9606 nsCOMPtr<nsIMsgFolder> folder;
9607 rv = mailnewsUrl->GetFolder(getter_AddRefs(folder));
9608 NS_ENSURE_SUCCESS(rv, false);
9609 if (!folder) {
9610 return false;
9611 }
9612 // we want to create a file channel and read the msg from there.
9613 nsCOMPtr<nsIInputStream> fileStream;
9614 nsMsgKey msgKey = strtoul(messageIdString.get(), nullptr, 10);
9615 uint32_t size;
9616 int64_t offset;
9617 rv = folder->GetOfflineFileStream(msgKey, &offset, &size,
9618 getter_AddRefs(fileStream));
9619 NS_ENSURE_SUCCESS(rv, false);
9620 // get the file channel from the folder, somehow (through the message or
9621 // folder sink?) We also need to set the transfer offset to the message
9622 // offset
9623 // dougt - This may break the ablity to "cancel" a read from offline
9624 // mail reading. fileChannel->SetLoadGroup(m_loadGroup);
9625 RefPtr<nsImapCacheStreamListener> cacheListener =
9626 new nsImapCacheStreamListener();
9627 cacheListener->Init(m_channelListener, this);
9628
9629 // create a stream pump that will async read the specified amount of
9630 // data.
9631 // XXX make size 64-bit int
9632 RefPtr<SlicedInputStream> slicedStream = new SlicedInputStream(
9633 fileStream.forget(), uint64_t(offset), uint64_t(size));
9634 nsCOMPtr<nsIInputStreamPump> pump;
9635 rv = NS_NewInputStreamPump(getter_AddRefs(pump), slicedStream.forget());
9636 NS_ENSURE_SUCCESS(rv, false);
9637 rv = pump->AsyncRead(cacheListener);
9638 NS_ENSURE_SUCCESS(rv, false);
9639
9640 // if the msg is unread, we should mark it read on the server. This lets
9641 // the code running this url know we're loading from the cache, if it cares.
9642 imapUrl->SetMsgLoadingFromCache(true);
9643 return true;
9644 }
9645
AsyncOpen(nsIStreamListener * aListener)9646 NS_IMETHODIMP nsImapMockChannel::AsyncOpen(nsIStreamListener* aListener) {
9647 nsCOMPtr<nsIStreamListener> listener = aListener;
9648 nsresult rv =
9649 nsContentSecurityManager::doContentSecurityCheck(this, listener);
9650 NS_ENSURE_SUCCESS(rv, rv);
9651
9652 int32_t port;
9653 if (!m_url) return NS_ERROR_NULL_POINTER;
9654 rv = m_url->GetPort(&port);
9655 if (NS_FAILED(rv)) return rv;
9656
9657 rv = NS_CheckPortSafety(port, "imap");
9658 if (NS_FAILED(rv)) return rv;
9659
9660 // set the stream listener and then load the url
9661 NS_ASSERTION(!m_channelListener, "shouldn't already have a listener");
9662 m_channelListener = listener;
9663 nsCOMPtr<nsIImapUrl> imapUrl(do_QueryInterface(m_url));
9664
9665 nsImapAction imapAction;
9666 imapUrl->GetImapAction(&imapAction);
9667
9668 bool externalLink = true;
9669 imapUrl->GetExternalLinkUrl(&externalLink);
9670
9671 if (externalLink) {
9672 // for security purposes, only allow imap urls originating from external
9673 // sources perform a limited set of actions. Currently the allowed set
9674 // includes: 1) folder selection 2) message fetch 3) message part fetch
9675
9676 if (!(imapAction == nsIImapUrl::nsImapSelectFolder ||
9677 imapAction == nsIImapUrl::nsImapMsgFetch ||
9678 imapAction == nsIImapUrl::nsImapOpenMimePart ||
9679 imapAction == nsIImapUrl::nsImapMsgFetchPeek))
9680 return NS_ERROR_FAILURE; // abort the running of this url....it failed a
9681 // security check
9682 }
9683
9684 if (ReadFromLocalCache()) {
9685 (void)NotifyStartEndReadFromCache(true);
9686 return NS_OK;
9687 }
9688
9689 // okay, it's not in the local cache, now check the memory cache...
9690 // but we can't download for offline use from the memory cache
9691 if (imapAction != nsIImapUrl::nsImapMsgDownloadForOffline) {
9692 rv = OpenCacheEntry();
9693 if (NS_SUCCEEDED(rv)) return rv;
9694 }
9695
9696 SetupPartExtractorListener(imapUrl, m_channelListener);
9697 // if for some reason open cache entry failed then just default to opening an
9698 // imap connection for the url
9699 return ReadFromImapConnection();
9700 }
9701
SetupPartExtractorListener(nsIImapUrl * aUrl,nsIStreamListener * aConsumer)9702 nsresult nsImapMockChannel::SetupPartExtractorListener(
9703 nsIImapUrl* aUrl, nsIStreamListener* aConsumer) {
9704 // if the url we are loading refers to a specific part then we need
9705 // libmime to extract that part from the message for us.
9706 bool refersToPart = false;
9707 aUrl->GetMimePartSelectorDetected(&refersToPart);
9708 if (refersToPart) {
9709 nsCOMPtr<nsIStreamConverterService> converter =
9710 do_GetService("@mozilla.org/streamConverters;1");
9711 if (converter && aConsumer) {
9712 nsCOMPtr<nsIStreamListener> newConsumer;
9713 converter->AsyncConvertData("message/rfc822", "*/*", aConsumer,
9714 static_cast<nsIChannel*>(this),
9715 getter_AddRefs(newConsumer));
9716 if (newConsumer) m_channelListener = newConsumer;
9717 }
9718 }
9719
9720 return NS_OK;
9721 }
9722
GetLoadFlags(nsLoadFlags * aLoadFlags)9723 NS_IMETHODIMP nsImapMockChannel::GetLoadFlags(nsLoadFlags* aLoadFlags) {
9724 //*aLoadFlags = nsIRequest::LOAD_NORMAL;
9725 *aLoadFlags = mLoadFlags;
9726 return NS_OK;
9727 }
9728
SetLoadFlags(nsLoadFlags aLoadFlags)9729 NS_IMETHODIMP nsImapMockChannel::SetLoadFlags(nsLoadFlags aLoadFlags) {
9730 mLoadFlags = aLoadFlags;
9731 return NS_OK; // don't fail when trying to set this
9732 }
9733
GetContentType(nsACString & aContentType)9734 NS_IMETHODIMP nsImapMockChannel::GetContentType(nsACString& aContentType) {
9735 if (mContentType.IsEmpty()) {
9736 nsImapAction imapAction = 0;
9737 if (m_url) {
9738 nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(m_url);
9739 if (imapUrl) {
9740 imapUrl->GetImapAction(&imapAction);
9741 }
9742 }
9743 if (imapAction == nsIImapUrl::nsImapSelectFolder)
9744 aContentType.AssignLiteral("x-application-imapfolder");
9745 else
9746 aContentType.AssignLiteral("message/rfc822");
9747 } else
9748 aContentType = mContentType;
9749 return NS_OK;
9750 }
9751
SetContentType(const nsACString & aContentType)9752 NS_IMETHODIMP nsImapMockChannel::SetContentType(
9753 const nsACString& aContentType) {
9754 nsAutoCString charset;
9755 nsresult rv =
9756 NS_ParseResponseContentType(aContentType, mContentType, charset);
9757 if (NS_FAILED(rv) || mContentType.IsEmpty())
9758 mContentType.AssignLiteral(UNKNOWN_CONTENT_TYPE);
9759 return rv;
9760 }
9761
GetContentCharset(nsACString & aContentCharset)9762 NS_IMETHODIMP nsImapMockChannel::GetContentCharset(
9763 nsACString& aContentCharset) {
9764 aContentCharset.Assign(mCharset);
9765 return NS_OK;
9766 }
9767
SetContentCharset(const nsACString & aContentCharset)9768 NS_IMETHODIMP nsImapMockChannel::SetContentCharset(
9769 const nsACString& aContentCharset) {
9770 mCharset.Assign(aContentCharset);
9771 return NS_OK;
9772 }
9773
9774 NS_IMETHODIMP
GetContentDisposition(uint32_t * aContentDisposition)9775 nsImapMockChannel::GetContentDisposition(uint32_t* aContentDisposition) {
9776 *aContentDisposition = mContentDisposition;
9777 return NS_OK;
9778 }
9779
9780 NS_IMETHODIMP
SetContentDisposition(uint32_t aContentDisposition)9781 nsImapMockChannel::SetContentDisposition(uint32_t aContentDisposition) {
9782 mContentDisposition = aContentDisposition;
9783 return NS_OK;
9784 }
9785
9786 NS_IMETHODIMP
GetContentDispositionFilename(nsAString & aContentDispositionFilename)9787 nsImapMockChannel::GetContentDispositionFilename(
9788 nsAString& aContentDispositionFilename) {
9789 return NS_ERROR_NOT_AVAILABLE;
9790 }
9791
9792 NS_IMETHODIMP
SetContentDispositionFilename(const nsAString & aContentDispositionFilename)9793 nsImapMockChannel::SetContentDispositionFilename(
9794 const nsAString& aContentDispositionFilename) {
9795 return NS_ERROR_NOT_AVAILABLE;
9796 }
9797
9798 NS_IMETHODIMP
GetContentDispositionHeader(nsACString & aContentDispositionHeader)9799 nsImapMockChannel::GetContentDispositionHeader(
9800 nsACString& aContentDispositionHeader) {
9801 return NS_ERROR_NOT_AVAILABLE;
9802 }
9803
GetContentLength(int64_t * aContentLength)9804 NS_IMETHODIMP nsImapMockChannel::GetContentLength(int64_t* aContentLength) {
9805 *aContentLength = mContentLength;
9806 return NS_OK;
9807 }
9808
9809 NS_IMETHODIMP
SetContentLength(int64_t aContentLength)9810 nsImapMockChannel::SetContentLength(int64_t aContentLength) {
9811 mContentLength = aContentLength;
9812 return NS_OK;
9813 }
9814
GetOwner(nsISupports ** aPrincipal)9815 NS_IMETHODIMP nsImapMockChannel::GetOwner(nsISupports** aPrincipal) {
9816 NS_IF_ADDREF(*aPrincipal = mOwner);
9817 return NS_OK;
9818 }
9819
SetOwner(nsISupports * aPrincipal)9820 NS_IMETHODIMP nsImapMockChannel::SetOwner(nsISupports* aPrincipal) {
9821 mOwner = aPrincipal;
9822 return NS_OK;
9823 }
9824
GetSecurityInfo(nsISupports ** aSecurityInfo)9825 NS_IMETHODIMP nsImapMockChannel::GetSecurityInfo(nsISupports** aSecurityInfo) {
9826 NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo);
9827 return NS_OK;
9828 }
9829
SetSecurityInfo(nsISupports * aSecurityInfo)9830 NS_IMETHODIMP nsImapMockChannel::SetSecurityInfo(nsISupports* aSecurityInfo) {
9831 mSecurityInfo = aSecurityInfo;
9832 return NS_OK;
9833 }
9834
9835 NS_IMETHODIMP
GetIsDocument(bool * aIsDocument)9836 nsImapMockChannel::GetIsDocument(bool* aIsDocument) {
9837 return NS_GetIsDocumentChannel(this, aIsDocument);
9838 }
9839
9840 ////////////////////////////////////////////////////////////////////////////////
9841 // From nsIRequest
9842 ////////////////////////////////////////////////////////////////////////////////
9843
GetName(nsACString & result)9844 NS_IMETHODIMP nsImapMockChannel::GetName(nsACString& result) {
9845 if (m_url) return m_url->GetSpec(result);
9846 result.Truncate();
9847 return NS_OK;
9848 }
9849
IsPending(bool * result)9850 NS_IMETHODIMP nsImapMockChannel::IsPending(bool* result) {
9851 *result = m_channelListener != nullptr;
9852 return NS_OK;
9853 }
9854
GetStatus(nsresult * status)9855 NS_IMETHODIMP nsImapMockChannel::GetStatus(nsresult* status) {
9856 *status = m_cancelStatus;
9857 return NS_OK;
9858 }
9859
SetImapProtocol(nsIImapProtocol * aProtocol)9860 NS_IMETHODIMP nsImapMockChannel::SetImapProtocol(nsIImapProtocol* aProtocol) {
9861 mProtocol = do_GetWeakReference(aProtocol);
9862 return NS_OK;
9863 }
9864
Cancel(nsresult status)9865 NS_IMETHODIMP nsImapMockChannel::Cancel(nsresult status) {
9866 MOZ_DIAGNOSTIC_ASSERT(
9867 NS_IsMainThread(),
9868 "nsImapMockChannel::Cancel should only be called from UI thread");
9869 m_cancelStatus = status;
9870 nsCOMPtr<nsIImapProtocol> imapProtocol = do_QueryReferent(mProtocol);
9871
9872 // if we aren't reading from the cache and we get canceled...doom our cache
9873 // entry...
9874 if (m_url) {
9875 nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url);
9876 MOZ_LOG(IMAPCache, LogLevel::Debug, ("Cancel(): Calling DoomCacheEntry()"));
9877 DoomCacheEntry(mailnewsUrl);
9878 }
9879
9880 // The associated ImapProtocol thread must be unblocked before being killed.
9881 // Otherwise, it will be deadlocked.
9882 ResumeAndNotifyOne();
9883
9884 // Required for killing ImapProtocol thread
9885 if (imapProtocol) imapProtocol->TellThreadToDie(false);
9886
9887 return NS_OK;
9888 }
9889
GetCanceled(bool * aCanceled)9890 NS_IMETHODIMP nsImapMockChannel::GetCanceled(bool* aCanceled) {
9891 nsresult status = NS_ERROR_FAILURE;
9892 GetStatus(&status);
9893 *aCanceled = NS_FAILED(status);
9894 return NS_OK;
9895 }
9896
9897 /**
9898 * Suspends the current request. This may have the effect of closing
9899 * any underlying transport (in order to free up resources), although
9900 * any open streams remain logically opened and will continue delivering
9901 * data when the transport is resumed.
9902 *
9903 * Calling cancel() on a suspended request must not send any
9904 * notifications (such as onstopRequest) until the request is resumed.
9905 *
9906 * NOTE: some implementations are unable to immediately suspend, and
9907 * may continue to deliver events already posted to an event queue. In
9908 * general, callers should be capable of handling events even after
9909 * suspending a request.
9910 */
Suspend()9911 NS_IMETHODIMP nsImapMockChannel::Suspend() {
9912 MOZ_LOG(IMAP, LogLevel::Debug, ("Suspending [this=%p].", this));
9913
9914 mozilla::MonitorAutoLock lock(mSuspendedMonitor);
9915 NS_ENSURE_TRUE(!mSuspended, NS_ERROR_NOT_AVAILABLE);
9916 mSuspended = true;
9917
9918 MOZ_LOG(IMAP, LogLevel::Debug, ("Suspended [this=%p].", this));
9919
9920 return NS_OK;
9921 }
9922
9923 /**
9924 * Resumes the current request. This may have the effect of re-opening
9925 * any underlying transport and will resume the delivery of data to
9926 * any open streams.
9927 */
Resume()9928 NS_IMETHODIMP nsImapMockChannel::Resume() {
9929 MOZ_LOG(IMAP, LogLevel::Debug, ("Resuming [this=%p].", this));
9930
9931 nsresult rv = ResumeAndNotifyOne();
9932
9933 MOZ_LOG(IMAP, LogLevel::Debug, ("Resumed [this=%p].", this));
9934
9935 return rv;
9936 }
9937
ResumeAndNotifyOne()9938 nsresult nsImapMockChannel::ResumeAndNotifyOne() {
9939 mozilla::MonitorAutoLock lock(mSuspendedMonitor);
9940 NS_ENSURE_TRUE(mSuspended, NS_ERROR_NOT_AVAILABLE);
9941 mSuspended = false;
9942 lock.Notify();
9943
9944 return NS_OK;
9945 }
9946
9947 NS_IMETHODIMP
GetNotificationCallbacks(nsIInterfaceRequestor ** aNotificationCallbacks)9948 nsImapMockChannel::GetNotificationCallbacks(
9949 nsIInterfaceRequestor** aNotificationCallbacks) {
9950 NS_IF_ADDREF(*aNotificationCallbacks = mCallbacks.get());
9951 return NS_OK;
9952 }
9953
9954 NS_IMETHODIMP
SetNotificationCallbacks(nsIInterfaceRequestor * aNotificationCallbacks)9955 nsImapMockChannel::SetNotificationCallbacks(
9956 nsIInterfaceRequestor* aNotificationCallbacks) {
9957 mCallbacks = aNotificationCallbacks;
9958 return NS_OK;
9959 }
9960
9961 NS_IMETHODIMP
OnTransportStatus(nsITransport * transport,nsresult status,int64_t progress,int64_t progressMax)9962 nsImapMockChannel::OnTransportStatus(nsITransport* transport, nsresult status,
9963 int64_t progress, int64_t progressMax) {
9964 if (NS_FAILED(m_cancelStatus) || (mLoadFlags & LOAD_BACKGROUND) || !m_url)
9965 return NS_OK;
9966
9967 // these transport events should not generate any status messages
9968 if (status == NS_NET_STATUS_RECEIVING_FROM ||
9969 status == NS_NET_STATUS_SENDING_TO)
9970 return NS_OK;
9971
9972 if (!mProgressEventSink) {
9973 NS_QueryNotificationCallbacks(mCallbacks, m_loadGroup, mProgressEventSink);
9974 if (!mProgressEventSink) return NS_OK;
9975 }
9976
9977 nsAutoCString host;
9978 m_url->GetHost(host);
9979
9980 nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url);
9981 if (mailnewsUrl) {
9982 nsCOMPtr<nsIMsgIncomingServer> server;
9983 mailnewsUrl->GetServer(getter_AddRefs(server));
9984 if (server) server->GetRealHostName(host);
9985 }
9986 mProgressEventSink->OnStatus(this, status, NS_ConvertUTF8toUTF16(host).get());
9987
9988 return NS_OK;
9989 }
9990
nsIMAPMailboxInfo(const nsACString & aName,char aDelimiter)9991 nsIMAPMailboxInfo::nsIMAPMailboxInfo(const nsACString& aName, char aDelimiter) {
9992 mMailboxName.Assign(aName);
9993 mDelimiter = aDelimiter;
9994 mChildrenListed = false;
9995 }
9996
~nsIMAPMailboxInfo()9997 nsIMAPMailboxInfo::~nsIMAPMailboxInfo() {}
9998
SetChildrenListed(bool childrenListed)9999 void nsIMAPMailboxInfo::SetChildrenListed(bool childrenListed) {
10000 mChildrenListed = childrenListed;
10001 }
10002
GetChildrenListed()10003 bool nsIMAPMailboxInfo::GetChildrenListed() { return mChildrenListed; }
10004
GetMailboxName()10005 const nsACString& nsIMAPMailboxInfo::GetMailboxName() { return mMailboxName; }
10006
GetDelimiter()10007 char nsIMAPMailboxInfo::GetDelimiter() { return mDelimiter; }
10008