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