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 #include "msgCore.h"
7 #include "nsString.h"
8 #include "nsMemory.h"
9 #include "nsMsgProtocol.h"
10 #include "nsIMsgMailNewsUrl.h"
11 #include "nsIMsgMailSession.h"
12 #include "nsMsgBaseCID.h"
13 #include "nsIStreamTransportService.h"
14 #include "nsISocketTransportService.h"
15 #include "nsISocketTransport.h"
16 #include "nsILoadGroup.h"
17 #include "nsILoadInfo.h"
18 #include "nsIIOService.h"
19 #include "nsNetUtil.h"
20 #include "nsIFileURL.h"
21 #include "nsIMsgWindow.h"
22 #include "nsIMsgStatusFeedback.h"
23 #include "nsIWebProgressListener.h"
24 #include "nsIPipe.h"
25 #include "nsIPrompt.h"
26 #include "prprf.h"
27 #include "plbase64.h"
28 #include "nsIStringBundle.h"
29 #include "nsIProxyInfo.h"
30 #include "nsThreadUtils.h"
31 #include "nsIPrefBranch.h"
32 #include "nsIPrefService.h"
33 #include "nsDirectoryServiceDefs.h"
34 #include "nsMsgUtils.h"
35 #include "nsILineInputStream.h"
36 #include "nsIAsyncInputStream.h"
37 #include "nsIMsgIncomingServer.h"
38 #include "nsIInputStreamPump.h"
39 #include "nsICancelable.h"
40 #include "nsMimeTypes.h"
41 #include "mozilla/Services.h"
42 #include "mozilla/SlicedInputStream.h"
43 #include "nsContentSecurityManager.h"
44 #include "nsPrintfCString.h"
45 
46 #undef PostMessage  // avoid to collision with WinUser.h
47 
48 using namespace mozilla;
49 
50 NS_IMPL_ISUPPORTS_INHERITED(nsMsgProtocol, nsHashPropertyBag, nsIChannel,
51                             nsIStreamListener, nsIRequestObserver, nsIRequest,
52                             nsITransportEventSink)
53 
54 static char16_t* FormatStringWithHostNameByName(const char16_t* stringName,
55                                                 nsIMsgMailNewsUrl* msgUri);
56 
nsMsgProtocol(nsIURI * aURL)57 nsMsgProtocol::nsMsgProtocol(nsIURI* aURL) {
58   m_flags = 0;
59   m_readCount = 0;
60   mLoadFlags = 0;
61   m_socketIsOpen = false;
62   mContentLength = -1;
63   m_isChannel = false;
64   mContentDisposition = nsIChannel::DISPOSITION_INLINE;
65 
66   GetSpecialDirectoryWithFileName(NS_OS_TEMP_DIR, "tempMessage.eml",
67                                   getter_AddRefs(m_tempMsgFile));
68 
69   mSuppressListenerNotifications = false;
70   InitFromURI(aURL);
71 }
72 
InitFromURI(nsIURI * aUrl)73 nsresult nsMsgProtocol::InitFromURI(nsIURI* aUrl) {
74   m_url = aUrl;
75 
76   nsCOMPtr<nsIMsgMailNewsUrl> mailUrl = do_QueryInterface(aUrl);
77   if (mailUrl) {
78     mailUrl->GetLoadGroup(getter_AddRefs(m_loadGroup));
79     nsCOMPtr<nsIMsgStatusFeedback> statusFeedback;
80     mailUrl->GetStatusFeedback(getter_AddRefs(statusFeedback));
81     mProgressEventSink = do_QueryInterface(statusFeedback);
82   }
83 
84   // Reset channel data in case the object is reused and initialised again.
85   mCharset.Truncate();
86 
87   return NS_OK;
88 }
89 
~nsMsgProtocol()90 nsMsgProtocol::~nsMsgProtocol() {}
91 
92 static bool gGotTimeoutPref;
93 static int32_t gSocketTimeout = 60;
94 
GetQoSBits(uint8_t * aQoSBits)95 nsresult nsMsgProtocol::GetQoSBits(uint8_t* aQoSBits) {
96   NS_ENSURE_ARG_POINTER(aQoSBits);
97   const char* protocol = GetType();
98 
99   if (!protocol) return NS_ERROR_NOT_IMPLEMENTED;
100 
101   nsAutoCString prefName("mail.");
102   prefName.Append(protocol);
103   prefName.AppendLiteral(".qos");
104 
105   nsresult rv;
106   nsCOMPtr<nsIPrefBranch> prefBranch =
107       do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
108   NS_ENSURE_SUCCESS(rv, rv);
109 
110   int32_t val;
111   rv = prefBranch->GetIntPref(prefName.get(), &val);
112   NS_ENSURE_SUCCESS(rv, rv);
113   *aQoSBits = (uint8_t)clamped(val, 0, 0xff);
114   return NS_OK;
115 }
116 
OpenNetworkSocketWithInfo(const char * aHostName,int32_t aGetPort,const char * connectionType,nsIProxyInfo * aProxyInfo,nsIInterfaceRequestor * callbacks)117 nsresult nsMsgProtocol::OpenNetworkSocketWithInfo(
118     const char* aHostName, int32_t aGetPort, const char* connectionType,
119     nsIProxyInfo* aProxyInfo, nsIInterfaceRequestor* callbacks) {
120   NS_ENSURE_ARG(aHostName);
121 
122   nsresult rv = NS_OK;
123   nsCOMPtr<nsISocketTransportService> socketService(
124       do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID));
125   NS_ENSURE_TRUE(socketService, NS_ERROR_FAILURE);
126 
127   // with socket connections we want to read as much data as arrives
128   m_readCount = -1;
129 
130   nsCOMPtr<nsISocketTransport> strans;
131   AutoTArray<nsCString, 1> connectionTypeArray;
132   if (connectionType) connectionTypeArray.AppendElement(connectionType);
133   rv = socketService->CreateTransport(
134       connectionTypeArray, nsDependentCString(aHostName), aGetPort, aProxyInfo,
135       nullptr, getter_AddRefs(strans));
136   if (NS_FAILED(rv)) return rv;
137 
138   strans->SetSecurityCallbacks(callbacks);
139 
140   // creates cyclic reference!
141   nsCOMPtr<nsIThread> currentThread(do_GetCurrentThread());
142   strans->SetEventSink(this, currentThread);
143 
144   m_socketIsOpen = false;
145   m_transport = strans;
146 
147   if (!gGotTimeoutPref) {
148     nsCOMPtr<nsIPrefBranch> prefBranch =
149         do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
150     if (prefBranch) {
151       prefBranch->GetIntPref("mailnews.tcptimeout", &gSocketTimeout);
152       gGotTimeoutPref = true;
153     }
154   }
155   strans->SetTimeout(nsISocketTransport::TIMEOUT_CONNECT, gSocketTimeout + 60);
156   strans->SetTimeout(nsISocketTransport::TIMEOUT_READ_WRITE, gSocketTimeout);
157 
158   uint8_t qos;
159   rv = GetQoSBits(&qos);
160   if (NS_SUCCEEDED(rv)) strans->SetQoSBits(qos);
161 
162   return SetupTransportState();
163 }
164 
GetFileFromURL(nsIURI * aURL,nsIFile ** aResult)165 nsresult nsMsgProtocol::GetFileFromURL(nsIURI* aURL, nsIFile** aResult) {
166   NS_ENSURE_ARG_POINTER(aURL);
167   NS_ENSURE_ARG_POINTER(aResult);
168   // extract the file path from the uri...
169   nsAutoCString urlSpec;
170   aURL->GetPathQueryRef(urlSpec);
171   urlSpec.InsertLiteral("file://", 0);
172   nsresult rv;
173 
174   // dougt - there should be an easier way!
175   nsCOMPtr<nsIURI> uri;
176   if (NS_FAILED(rv = NS_NewURI(getter_AddRefs(uri), urlSpec.get()))) return rv;
177 
178   nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(uri);
179   if (!fileURL) return NS_ERROR_FAILURE;
180 
181   return fileURL->GetFile(aResult);
182   // dougt
183 }
184 
OpenFileSocket(nsIURI * aURL,uint64_t aStartPosition,int64_t aReadCount)185 nsresult nsMsgProtocol::OpenFileSocket(nsIURI* aURL, uint64_t aStartPosition,
186                                        int64_t aReadCount) {
187   // mscott - file needs to be encoded directly into aURL. I should be able to
188   // get rid of this method completely.
189 
190   nsresult rv = NS_OK;
191   m_readCount = aReadCount;
192   nsCOMPtr<nsIFile> file;
193 
194   rv = GetFileFromURL(aURL, getter_AddRefs(file));
195   NS_ENSURE_SUCCESS(rv, rv);
196 
197   nsCOMPtr<nsIInputStream> stream;
198   rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), file);
199   if (NS_FAILED(rv)) return rv;
200 
201   // create input stream transport
202   nsCOMPtr<nsIStreamTransportService> sts =
203       do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
204   if (NS_FAILED(rv)) return rv;
205 
206   // This can be called with aReadCount == -1 which means "read as much as we
207   // can". We pass this on as UINT64_MAX, which is in fact uint64_t(-1).
208   RefPtr<SlicedInputStream> slicedStream = new SlicedInputStream(
209       stream.forget(), aStartPosition,
210       aReadCount == -1 ? UINT64_MAX : uint64_t(aReadCount));
211   rv = sts->CreateInputTransport(slicedStream, true,
212                                  getter_AddRefs(m_transport));
213 
214   m_socketIsOpen = false;
215   return rv;
216 }
217 
GetTopmostMsgWindow(nsIMsgWindow ** aWindow)218 nsresult nsMsgProtocol::GetTopmostMsgWindow(nsIMsgWindow** aWindow) {
219   nsresult rv;
220   nsCOMPtr<nsIMsgMailSession> mailSession(
221       do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv));
222   NS_ENSURE_SUCCESS(rv, rv);
223   return mailSession->GetTopmostMsgWindow(aWindow);
224 }
225 
SetupTransportState()226 nsresult nsMsgProtocol::SetupTransportState() {
227   if (!m_socketIsOpen && m_transport) {
228     nsresult rv;
229 
230     // open buffered, blocking output stream
231     rv = m_transport->OpenOutputStream(nsITransport::OPEN_BLOCKING, 0, 0,
232                                        getter_AddRefs(m_outputStream));
233     if (NS_FAILED(rv)) return rv;
234     // we want to open the stream
235   }  // if m_transport
236 
237   return NS_OK;
238 }
239 
CloseSocket()240 nsresult nsMsgProtocol::CloseSocket() {
241   nsresult rv = NS_OK;
242   // release all of our socket state
243   m_socketIsOpen = false;
244   m_outputStream = nullptr;
245   if (m_transport) {
246     nsCOMPtr<nsISocketTransport> strans = do_QueryInterface(m_transport);
247     if (strans) {
248       strans->SetEventSink(nullptr, nullptr);  // break cyclic reference!
249     }
250   }
251   // we need to call Cancel so that we remove the socket transport from the
252   // mActiveTransportList.  see bug #30648
253   if (m_request) {
254     rv = m_request->Cancel(NS_BINDING_ABORTED);
255   }
256   m_request = nullptr;
257   if (m_transport) {
258     m_transport->Close(NS_BINDING_ABORTED);
259     m_transport = nullptr;
260   }
261 
262   return rv;
263 }
264 
265 /*
266  * Writes the data contained in dataBuffer into the current output stream. It
267  * also informs the transport layer that this data is now available for
268  * transmission. Returns a positive number for success, 0 for failure (not all
269  * the bytes were written to the stream, etc). We need to make another pass
270  * through this file to install an error system (mscott)
271  *
272  * No logging is done in the base implementation, so aSuppressLogging is
273  * ignored.
274  */
275 
SendData(const char * dataBuffer,bool aSuppressLogging)276 nsresult nsMsgProtocol::SendData(const char* dataBuffer,
277                                  bool aSuppressLogging) {
278   uint32_t writeCount = 0;
279 
280   if (dataBuffer && m_outputStream)
281     return m_outputStream->Write(dataBuffer, PL_strlen(dataBuffer),
282                                  &writeCount);
283   // TODO make sure all the bytes in PL_strlen(dataBuffer) were written
284   else
285     return NS_ERROR_INVALID_ARG;
286 }
287 
288 // Whenever data arrives from the connection, core netlib notifices the protocol
289 // by calling OnDataAvailable. We then read and process the incoming data from
290 // the input stream.
OnDataAvailable(nsIRequest * request,nsIInputStream * inStr,uint64_t sourceOffset,uint32_t count)291 NS_IMETHODIMP nsMsgProtocol::OnDataAvailable(nsIRequest* request,
292                                              nsIInputStream* inStr,
293                                              uint64_t sourceOffset,
294                                              uint32_t count) {
295   // right now, this really just means turn around and churn through the state
296   // machine
297   nsCOMPtr<nsIURI> uri;
298   GetURI(getter_AddRefs(uri));
299 
300   return ProcessProtocolState(uri, inStr, sourceOffset, count);
301 }
302 
OnStartRequest(nsIRequest * request)303 NS_IMETHODIMP nsMsgProtocol::OnStartRequest(nsIRequest* request) {
304   nsresult rv = NS_OK;
305   nsCOMPtr<nsIURI> uri;
306   GetURI(getter_AddRefs(uri));
307 
308   if (uri) {
309     nsCOMPtr<nsIMsgMailNewsUrl> aMsgUrl = do_QueryInterface(uri);
310     rv = aMsgUrl->SetUrlState(true, NS_OK);
311     if (m_loadGroup)
312       m_loadGroup->AddRequest(static_cast<nsIRequest*>(this),
313                               nullptr /* context isupports */);
314   }
315 
316   // if we are set up as a channel, we should notify our channel listener that
317   // we are starting... so pass in ourself as the channel and not the underlying
318   // socket or file channel the protocol happens to be using
319   if (!mSuppressListenerNotifications && m_channelListener) {
320     m_isChannel = true;
321     rv = m_channelListener->OnStartRequest(this);
322   }
323 
324   nsCOMPtr<nsISocketTransport> strans = do_QueryInterface(m_transport);
325 
326   if (strans)
327     strans->SetTimeout(nsISocketTransport::TIMEOUT_READ_WRITE, gSocketTimeout);
328 
329   NS_ENSURE_SUCCESS(rv, rv);
330   return rv;
331 }
332 
ShowAlertMessage(nsIMsgMailNewsUrl * aMsgUrl,nsresult aStatus)333 void nsMsgProtocol::ShowAlertMessage(nsIMsgMailNewsUrl* aMsgUrl,
334                                      nsresult aStatus) {
335   const char16_t* errorString = nullptr;
336   switch (aStatus) {
337     case NS_ERROR_UNKNOWN_HOST:
338     case NS_ERROR_UNKNOWN_PROXY_HOST:
339       errorString = u"unknownHostError";
340       break;
341     case NS_ERROR_CONNECTION_REFUSED:
342     case NS_ERROR_PROXY_CONNECTION_REFUSED:
343       errorString = u"connectionRefusedError";
344       break;
345     case NS_ERROR_NET_TIMEOUT:
346       errorString = u"netTimeoutError";
347       break;
348     case NS_ERROR_NET_RESET:
349       errorString = u"netResetError";
350       break;
351     case NS_ERROR_NET_INTERRUPT:
352       errorString = u"netInterruptError";
353       break;
354     case NS_ERROR_OFFLINE:
355       // Don't alert when offline as that is already displayed in the UI.
356       return;
357     default:
358       nsPrintfCString msg(
359           "Unexpected status passed to ShowAlertMessage: %" PRIx32,
360           static_cast<uint32_t>(aStatus));
361       NS_WARNING(msg.get());
362       return;
363   }
364 
365   nsString errorMsg;
366   errorMsg.Adopt(FormatStringWithHostNameByName(errorString, aMsgUrl));
367   if (errorMsg.IsEmpty()) {
368     errorMsg.AssignLiteral(u"[StringID ");
369     errorMsg.Append(errorString);
370     errorMsg.AppendLiteral(u"?]");
371   }
372 
373   nsCOMPtr<nsIMsgMailSession> mailSession =
374       do_GetService(NS_MSGMAILSESSION_CONTRACTID);
375   if (mailSession) mailSession->AlertUser(errorMsg, aMsgUrl);
376 }
377 
378 // stop binding is a "notification" informing us that the stream associated with
379 // aURL is going away.
OnStopRequest(nsIRequest * request,nsresult aStatus)380 NS_IMETHODIMP nsMsgProtocol::OnStopRequest(nsIRequest* request,
381                                            nsresult aStatus) {
382   nsresult rv = NS_OK;
383 
384   // if we are set up as a channel, we should notify our channel listener that
385   // we are starting... so pass in ourself as the channel and not the underlying
386   // socket or file channel the protocol happens to be using
387   if (!mSuppressListenerNotifications && m_channelListener)
388     rv = m_channelListener->OnStopRequest(this, aStatus);
389 
390   nsCOMPtr<nsIURI> uri;
391   GetURI(getter_AddRefs(uri));
392 
393   if (uri) {
394     nsCOMPtr<nsIMsgMailNewsUrl> msgUrl = do_QueryInterface(uri);
395     rv = msgUrl->SetUrlState(false, aStatus);  // Always returns NS_OK.
396     if (m_loadGroup)
397       m_loadGroup->RemoveRequest(static_cast<nsIRequest*>(this), nullptr,
398                                  aStatus);
399 
400     // !m_isChannel because if we're set up as a channel, then the remove
401     // request above will handle alerting the user, so we don't need to.
402     //
403     // !NS_BINDING_ABORTED because we don't want to see an alert if the user
404     // cancelled the operation.  also, we'll get here because we call Cancel()
405     // to force removal of the nsSocketTransport.  see CloseSocket()
406     // bugs #30775 and #30648 relate to this
407     if (!m_isChannel && NS_FAILED(aStatus) && (aStatus != NS_BINDING_ABORTED))
408       ShowAlertMessage(msgUrl, aStatus);
409   }  // if we have a mailnews url.
410 
411   // Drop notification callbacks to prevent cycles.
412   mCallbacks = nullptr;
413   mProgressEventSink = nullptr;
414   // Call CloseSocket(), in case we got here because the server dropped the
415   // connection while reading, and we never get a chance to get back into
416   // the protocol state machine via OnDataAvailable.
417   if (m_socketIsOpen) CloseSocket();
418 
419   return rv;
420 }
421 
GetPromptDialogFromUrl(nsIMsgMailNewsUrl * aMsgUrl,nsIPrompt ** aPromptDialog)422 nsresult nsMsgProtocol::GetPromptDialogFromUrl(nsIMsgMailNewsUrl* aMsgUrl,
423                                                nsIPrompt** aPromptDialog) {
424   // get the nsIPrompt interface from the message window associated wit this
425   // url.
426   nsCOMPtr<nsIMsgWindow> msgWindow;
427   aMsgUrl->GetMsgWindow(getter_AddRefs(msgWindow));
428   NS_ENSURE_TRUE(msgWindow, NS_ERROR_FAILURE);
429 
430   msgWindow->GetPromptDialog(aPromptDialog);
431 
432   NS_ENSURE_TRUE(*aPromptDialog, NS_ERROR_FAILURE);
433 
434   return NS_OK;
435 }
436 
LoadUrl(nsIURI * aURL,nsISupports * aConsumer)437 nsresult nsMsgProtocol::LoadUrl(nsIURI* aURL, nsISupports* aConsumer) {
438   // nsMsgProtocol implements nsIChannel, and all channels are required to
439   // have non-null loadInfo. So if it's still unset, we've not been correctly
440   // initialised.
441   MOZ_ASSERT(m_loadInfo);
442 
443   // okay now kick us off to the next state...
444   // our first state is a process state so drive the state machine...
445   nsresult rv = NS_OK;
446   nsCOMPtr<nsIMsgMailNewsUrl> aMsgUrl = do_QueryInterface(aURL, &rv);
447 
448   if (NS_SUCCEEDED(rv) && aMsgUrl) {
449     bool msgIsInLocalCache;
450     aMsgUrl->GetMsgIsInLocalCache(&msgIsInLocalCache);
451 
452     // Set the url as a url currently being run...
453     rv = aMsgUrl->SetUrlState(true, NS_OK);
454 
455     // if the url is given a stream consumer then we should use it to forward
456     // calls to...
457     if (!m_channelListener &&
458         aConsumer)  // if we don't have a registered listener already
459     {
460       m_channelListener = do_QueryInterface(aConsumer);
461       m_isChannel = true;
462     }
463 
464     if (!m_socketIsOpen) {
465       if (m_transport) {
466         // open buffered, asynchronous input stream
467         nsCOMPtr<nsIInputStream> stream;
468         rv = m_transport->OpenInputStream(0, 0, 0, getter_AddRefs(stream));
469         if (NS_FAILED(rv)) return rv;
470 
471         // m_readCount can be -1 which means "read as much as we can".
472         // We pass this on as UINT64_MAX, which is in fact uint64_t(-1).
473         // We don't clone m_inputStream here, we simply give up ownership
474         // since otherwise the original would never be closed.
475         RefPtr<SlicedInputStream> slicedStream = new SlicedInputStream(
476             stream.forget(), 0,
477             m_readCount == -1 ? UINT64_MAX : uint64_t(m_readCount));
478         nsCOMPtr<nsIInputStreamPump> pump;
479         rv = NS_NewInputStreamPump(getter_AddRefs(pump), slicedStream.forget());
480         if (NS_FAILED(rv)) return rv;
481 
482         m_request = pump;  // keep a reference to the pump so we can cancel it
483 
484         // Put us in a state where we are always notified of incoming data.
485         // OnDataAvailable() will be called when that happens, which will
486         // pass that data into ProcessProtocolState().
487         rv = pump->AsyncRead(this);
488         NS_ASSERTION(NS_SUCCEEDED(rv), "AsyncRead failed");
489         m_socketIsOpen = true;  // mark the channel as open
490       }
491     } else if (!msgIsInLocalCache) {
492       // The connection is already open so we should begin processing our url.
493       rv = ProcessProtocolState(aURL, nullptr, 0, 0);
494     }
495   }
496 
497   return rv;
498 }
499 
500 ///////////////////////////////////////////////////////////////////////
501 // The rest of this file is mostly nsIChannel mumbo jumbo stuff
502 ///////////////////////////////////////////////////////////////////////
503 
SetUrl(nsIURI * aURL)504 nsresult nsMsgProtocol::SetUrl(nsIURI* aURL) {
505   m_url = aURL;
506   return NS_OK;
507 }
508 
SetLoadGroup(nsILoadGroup * aLoadGroup)509 NS_IMETHODIMP nsMsgProtocol::SetLoadGroup(nsILoadGroup* aLoadGroup) {
510   m_loadGroup = aLoadGroup;
511   return NS_OK;
512 }
513 
GetTRRMode(nsIRequest::TRRMode * aTRRMode)514 NS_IMETHODIMP nsMsgProtocol::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
515   return GetTRRModeImpl(aTRRMode);
516 }
517 
SetTRRMode(nsIRequest::TRRMode aTRRMode)518 NS_IMETHODIMP nsMsgProtocol::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
519   return SetTRRModeImpl(aTRRMode);
520 }
521 
GetOriginalURI(nsIURI ** aURI)522 NS_IMETHODIMP nsMsgProtocol::GetOriginalURI(nsIURI** aURI) {
523   NS_IF_ADDREF(*aURI = m_originalUrl ? m_originalUrl : m_url);
524   return NS_OK;
525 }
526 
SetOriginalURI(nsIURI * aURI)527 NS_IMETHODIMP nsMsgProtocol::SetOriginalURI(nsIURI* aURI) {
528   m_originalUrl = aURI;
529   return NS_OK;
530 }
531 
GetURI(nsIURI ** aURI)532 NS_IMETHODIMP nsMsgProtocol::GetURI(nsIURI** aURI) {
533   NS_IF_ADDREF(*aURI = m_url);
534   return NS_OK;
535 }
536 
Open(nsIInputStream ** _retval)537 NS_IMETHODIMP nsMsgProtocol::Open(nsIInputStream** _retval) {
538   nsCOMPtr<nsIStreamListener> listener;
539   nsresult rv =
540       nsContentSecurityManager::doContentSecurityCheck(this, listener);
541   NS_ENSURE_SUCCESS(rv, rv);
542   return NS_ImplementChannelOpen(this, _retval);
543 }
544 
AsyncOpen(nsIStreamListener * aListener)545 NS_IMETHODIMP nsMsgProtocol::AsyncOpen(nsIStreamListener* aListener) {
546   nsCOMPtr<nsIStreamListener> listener = aListener;
547   nsresult rv =
548       nsContentSecurityManager::doContentSecurityCheck(this, listener);
549   NS_ENSURE_SUCCESS(rv, rv);
550 
551   int32_t port;
552   rv = m_url->GetPort(&port);
553   if (NS_FAILED(rv)) return rv;
554 
555   nsAutoCString scheme;
556   rv = m_url->GetScheme(scheme);
557   if (NS_FAILED(rv)) return rv;
558 
559   rv = NS_CheckPortSafety(port, scheme.get());
560   if (NS_FAILED(rv)) return rv;
561 
562   // set the stream listener and then load the url
563   m_isChannel = true;
564 
565   m_channelListener = listener;
566   return LoadUrl(m_url, nullptr);
567 }
568 
GetLoadFlags(nsLoadFlags * aLoadFlags)569 NS_IMETHODIMP nsMsgProtocol::GetLoadFlags(nsLoadFlags* aLoadFlags) {
570   *aLoadFlags = mLoadFlags;
571   return NS_OK;
572 }
573 
SetLoadFlags(nsLoadFlags aLoadFlags)574 NS_IMETHODIMP nsMsgProtocol::SetLoadFlags(nsLoadFlags aLoadFlags) {
575   mLoadFlags = aLoadFlags;
576   return NS_OK;  // don't fail when trying to set this
577 }
578 
GetContentType(nsACString & aContentType)579 NS_IMETHODIMP nsMsgProtocol::GetContentType(nsACString& aContentType) {
580   // as url dispatching matures, we'll be intelligent and actually start
581   // opening the url before specifying the content type. This will allow
582   // us to optimize the case where the message url actual refers to
583   // a part in the message that has a content type that is not message/rfc822
584 
585   if (mContentType.IsEmpty())
586     aContentType.AssignLiteral("message/rfc822");
587   else
588     aContentType = mContentType;
589   return NS_OK;
590 }
591 
SetContentType(const nsACString & aContentType)592 NS_IMETHODIMP nsMsgProtocol::SetContentType(const nsACString& aContentType) {
593   nsAutoCString charset;
594   nsresult rv =
595       NS_ParseResponseContentType(aContentType, mContentType, charset);
596   if (NS_FAILED(rv) || mContentType.IsEmpty())
597     mContentType.AssignLiteral(UNKNOWN_CONTENT_TYPE);
598   return rv;
599 }
600 
GetContentCharset(nsACString & aContentCharset)601 NS_IMETHODIMP nsMsgProtocol::GetContentCharset(nsACString& aContentCharset) {
602   aContentCharset.Assign(mCharset);
603   return NS_OK;
604 }
605 
SetContentCharset(const nsACString & aContentCharset)606 NS_IMETHODIMP nsMsgProtocol::SetContentCharset(
607     const nsACString& aContentCharset) {
608   mCharset.Assign(aContentCharset);
609   return NS_OK;
610 }
611 
612 NS_IMETHODIMP
GetContentDisposition(uint32_t * aContentDisposition)613 nsMsgProtocol::GetContentDisposition(uint32_t* aContentDisposition) {
614   *aContentDisposition = mContentDisposition;
615   return NS_OK;
616 }
617 
618 NS_IMETHODIMP
SetContentDisposition(uint32_t aContentDisposition)619 nsMsgProtocol::SetContentDisposition(uint32_t aContentDisposition) {
620   mContentDisposition = aContentDisposition;
621   return NS_OK;
622 }
623 
624 NS_IMETHODIMP
GetContentDispositionFilename(nsAString & aContentDispositionFilename)625 nsMsgProtocol::GetContentDispositionFilename(
626     nsAString& aContentDispositionFilename) {
627   return NS_ERROR_NOT_AVAILABLE;
628 }
629 
630 NS_IMETHODIMP
SetContentDispositionFilename(const nsAString & aContentDispositionFilename)631 nsMsgProtocol::SetContentDispositionFilename(
632     const nsAString& aContentDispositionFilename) {
633   return NS_ERROR_NOT_AVAILABLE;
634 }
635 
636 NS_IMETHODIMP
GetContentDispositionHeader(nsACString & aContentDispositionHeader)637 nsMsgProtocol::GetContentDispositionHeader(
638     nsACString& aContentDispositionHeader) {
639   return NS_ERROR_NOT_AVAILABLE;
640 }
641 
GetContentLength(int64_t * aContentLength)642 NS_IMETHODIMP nsMsgProtocol::GetContentLength(int64_t* aContentLength) {
643   *aContentLength = mContentLength;
644   return NS_OK;
645 }
646 
SetContentLength(int64_t aContentLength)647 NS_IMETHODIMP nsMsgProtocol::SetContentLength(int64_t aContentLength) {
648   mContentLength = aContentLength;
649   return NS_OK;
650 }
651 
GetSecurityInfo(nsISupports ** secInfo)652 NS_IMETHODIMP nsMsgProtocol::GetSecurityInfo(nsISupports** secInfo) {
653   if (m_transport) {
654     nsCOMPtr<nsISocketTransport> strans = do_QueryInterface(m_transport);
655     if (strans) {
656       return strans->GetSecurityInfo(secInfo);
657     }
658   }
659   *secInfo = nullptr;
660   return NS_OK;
661 }
662 
GetName(nsACString & result)663 NS_IMETHODIMP nsMsgProtocol::GetName(nsACString& result) {
664   if (m_url) return m_url->GetSpec(result);
665   result.Truncate();
666   return NS_OK;
667 }
668 
GetOwner(nsISupports ** aPrincipal)669 NS_IMETHODIMP nsMsgProtocol::GetOwner(nsISupports** aPrincipal) {
670   NS_IF_ADDREF(*aPrincipal = mOwner);
671   return NS_OK;
672 }
673 
SetOwner(nsISupports * aPrincipal)674 NS_IMETHODIMP nsMsgProtocol::SetOwner(nsISupports* aPrincipal) {
675   mOwner = aPrincipal;
676   return NS_OK;
677 }
678 
GetLoadGroup(nsILoadGroup ** aLoadGroup)679 NS_IMETHODIMP nsMsgProtocol::GetLoadGroup(nsILoadGroup** aLoadGroup) {
680   NS_IF_ADDREF(*aLoadGroup = m_loadGroup);
681   return NS_OK;
682 }
683 
GetLoadInfo(nsILoadInfo ** aLoadInfo)684 NS_IMETHODIMP nsMsgProtocol::GetLoadInfo(nsILoadInfo** aLoadInfo) {
685   NS_IF_ADDREF(*aLoadInfo = m_loadInfo);
686   return NS_OK;
687 }
688 
SetLoadInfo(nsILoadInfo * aLoadInfo)689 NS_IMETHODIMP nsMsgProtocol::SetLoadInfo(nsILoadInfo* aLoadInfo) {
690   m_loadInfo = aLoadInfo;
691   return NS_OK;
692 }
693 
694 NS_IMETHODIMP
GetNotificationCallbacks(nsIInterfaceRequestor ** aNotificationCallbacks)695 nsMsgProtocol::GetNotificationCallbacks(
696     nsIInterfaceRequestor** aNotificationCallbacks) {
697   NS_IF_ADDREF(*aNotificationCallbacks = mCallbacks.get());
698   return NS_OK;
699 }
700 
701 NS_IMETHODIMP
SetNotificationCallbacks(nsIInterfaceRequestor * aNotificationCallbacks)702 nsMsgProtocol::SetNotificationCallbacks(
703     nsIInterfaceRequestor* aNotificationCallbacks) {
704   mCallbacks = aNotificationCallbacks;
705   return NS_OK;
706 }
707 
708 NS_IMETHODIMP
OnTransportStatus(nsITransport * transport,nsresult status,int64_t progress,int64_t progressMax)709 nsMsgProtocol::OnTransportStatus(nsITransport* transport, nsresult status,
710                                  int64_t progress, int64_t progressMax) {
711   if ((mLoadFlags & LOAD_BACKGROUND) || !m_url) return NS_OK;
712 
713   // these transport events should not generate any status messages
714   if (status == NS_NET_STATUS_RECEIVING_FROM ||
715       status == NS_NET_STATUS_SENDING_TO)
716     return NS_OK;
717 
718   if (!mProgressEventSink) {
719     NS_QueryNotificationCallbacks(mCallbacks, m_loadGroup, mProgressEventSink);
720     if (!mProgressEventSink) return NS_OK;
721   }
722 
723   nsAutoCString host;
724   m_url->GetHost(host);
725 
726   nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url);
727   if (mailnewsUrl) {
728     nsCOMPtr<nsIMsgIncomingServer> server;
729     mailnewsUrl->GetServer(getter_AddRefs(server));
730     if (server) server->GetRealHostName(host);
731   }
732   mProgressEventSink->OnStatus(this, status, NS_ConvertUTF8toUTF16(host).get());
733 
734   return NS_OK;
735 }
736 
737 NS_IMETHODIMP
GetIsDocument(bool * aIsDocument)738 nsMsgProtocol::GetIsDocument(bool* aIsDocument) {
739   return NS_GetIsDocumentChannel(this, aIsDocument);
740 }
741 
742 ////////////////////////////////////////////////////////////////////////////////
743 // From nsIRequest
744 ////////////////////////////////////////////////////////////////////////////////
745 
IsPending(bool * result)746 NS_IMETHODIMP nsMsgProtocol::IsPending(bool* result) {
747   *result = m_channelListener != nullptr;
748   return NS_OK;
749 }
750 
GetStatus(nsresult * status)751 NS_IMETHODIMP nsMsgProtocol::GetStatus(nsresult* status) {
752   if (m_request) return m_request->GetStatus(status);
753 
754   *status = NS_OK;
755   return *status;
756 }
757 
Cancel(nsresult status)758 NS_IMETHODIMP nsMsgProtocol::Cancel(nsresult status) {
759   if (m_proxyRequest) m_proxyRequest->Cancel(status);
760 
761   if (m_request) return m_request->Cancel(status);
762 
763   NS_WARNING("no request to cancel");
764   return NS_ERROR_NOT_AVAILABLE;
765 }
766 
GetCanceled(bool * aCanceled)767 NS_IMETHODIMP nsMsgProtocol::GetCanceled(bool* aCanceled) {
768   nsresult status = NS_ERROR_FAILURE;
769   GetStatus(&status);
770   *aCanceled = NS_FAILED(status);
771   return NS_OK;
772 }
773 
Suspend()774 NS_IMETHODIMP nsMsgProtocol::Suspend() {
775   if (m_request) return m_request->Suspend();
776 
777   NS_WARNING("no request to suspend");
778   return NS_ERROR_NOT_AVAILABLE;
779 }
780 
Resume()781 NS_IMETHODIMP nsMsgProtocol::Resume() {
782   if (m_request) return m_request->Resume();
783 
784   NS_WARNING("no request to resume");
785   return NS_ERROR_NOT_AVAILABLE;
786 }
787 
PostMessage(nsIURI * url,nsIFile * postFile)788 nsresult nsMsgProtocol::PostMessage(nsIURI* url, nsIFile* postFile) {
789   if (!url || !postFile) return NS_ERROR_NULL_POINTER;
790 
791 #define POST_DATA_BUFFER_SIZE 2048
792 
793   // mscott -- this function should be re-written to use the file url code
794   // so it can be asynch
795   nsCOMPtr<nsIInputStream> inputStream;
796   nsresult rv =
797       NS_NewLocalFileInputStream(getter_AddRefs(inputStream), postFile);
798   NS_ENSURE_SUCCESS(rv, rv);
799   nsCOMPtr<nsILineInputStream> lineInputStream(
800       do_QueryInterface(inputStream, &rv));
801   NS_ENSURE_SUCCESS(rv, rv);
802 
803   bool more = true;
804   nsCString line;
805   nsCString outputBuffer;
806 
807   do {
808     lineInputStream->ReadLine(line, &more);
809 
810     /* escape starting periods
811      */
812     if (line.CharAt(0) == '.') line.Insert('.', 0);
813     line.AppendLiteral(CRLF);
814     outputBuffer.Append(line);
815     // test hack by mscott. If our buffer is almost full, then
816     // send it off & reset ourselves
817     // to make more room.
818     if (outputBuffer.Length() > POST_DATA_BUFFER_SIZE || !more) {
819       rv = SendData(outputBuffer.get());
820       NS_ENSURE_SUCCESS(rv, rv);
821       // does this keep the buffer around? That would be best.
822       // Maybe SetLength(0) instead?
823       outputBuffer.Truncate();
824     }
825   } while (more);
826 
827   return NS_OK;
828 }
829 
DoGSSAPIStep1(const char * service,const char * username,nsCString & response)830 nsresult nsMsgProtocol::DoGSSAPIStep1(const char* service, const char* username,
831                                       nsCString& response) {
832   nsresult rv;
833 #ifdef DEBUG_BenB
834   printf("GSSAPI step 1 for service %s, username %s\n", service, username);
835 #endif
836 
837   // if this fails, then it means that we cannot do GSSAPI SASL.
838   m_authModule = nsIAuthModule::CreateInstance("sasl-gssapi");
839 
840   m_authModule->Init(service, nsIAuthModule::REQ_DEFAULT, nullptr,
841                      NS_ConvertUTF8toUTF16(username).get(), nullptr);
842 
843   void* outBuf;
844   uint32_t outBufLen;
845   rv = m_authModule->GetNextToken((void*)nullptr, 0, &outBuf, &outBufLen);
846   if (NS_SUCCEEDED(rv) && outBuf) {
847     char* base64Str = PL_Base64Encode((char*)outBuf, outBufLen, nullptr);
848     if (base64Str)
849       response.Adopt(base64Str);
850     else
851       rv = NS_ERROR_OUT_OF_MEMORY;
852     free(outBuf);
853   }
854 
855 #ifdef DEBUG_BenB
856   printf("GSSAPI step 1 succeeded\n");
857 #endif
858   return rv;
859 }
860 
DoGSSAPIStep2(nsCString & commandResponse,nsCString & response)861 nsresult nsMsgProtocol::DoGSSAPIStep2(nsCString& commandResponse,
862                                       nsCString& response) {
863 #ifdef DEBUG_BenB
864   printf("GSSAPI step 2\n");
865 #endif
866   nsresult rv;
867   void *inBuf, *outBuf;
868   uint32_t inBufLen, outBufLen;
869   uint32_t len = commandResponse.Length();
870 
871   // Cyrus SASL may send us zero length tokens (grrrr)
872   if (len > 0) {
873     // decode into the input secbuffer
874     inBufLen = (len * 3) / 4;  // sufficient size (see plbase64.h)
875     inBuf = moz_xmalloc(inBufLen);
876     if (!inBuf) return NS_ERROR_OUT_OF_MEMORY;
877 
878     // strip off any padding (see bug 230351)
879     const char* challenge = commandResponse.get();
880     while (challenge[len - 1] == '=') len--;
881 
882     // We need to know the exact length of the decoded string to give to
883     // the GSSAPI libraries. But NSPR's base64 routine doesn't seem capable
884     // of telling us that. So, we figure it out for ourselves.
885 
886     // For every 4 characters, add 3 to the destination
887     // If there are 3 remaining, add 2
888     // If there are 2 remaining, add 1
889     // 1 remaining is an error
890     inBufLen =
891         (len / 4) * 3 + ((len % 4 == 3) ? 2 : 0) + ((len % 4 == 2) ? 1 : 0);
892 
893     rv = (PL_Base64Decode(challenge, len, (char*)inBuf))
894              ? m_authModule->GetNextToken(inBuf, inBufLen, &outBuf, &outBufLen)
895              : NS_ERROR_FAILURE;
896 
897     free(inBuf);
898   } else {
899     rv = m_authModule->GetNextToken(NULL, 0, &outBuf, &outBufLen);
900   }
901   if (NS_SUCCEEDED(rv)) {
902     // And in return, we may need to send Cyrus zero length tokens back
903     if (outBuf) {
904       char* base64Str = PL_Base64Encode((char*)outBuf, outBufLen, nullptr);
905       if (base64Str)
906         response.Adopt(base64Str);
907       else
908         rv = NS_ERROR_OUT_OF_MEMORY;
909     } else
910       response.Adopt((char*)moz_xmemdup("", 1));
911   }
912 
913 #ifdef DEBUG_BenB
914   printf(NS_SUCCEEDED(rv) ? "GSSAPI step 2 succeeded\n"
915                           : "GSSAPI step 2 failed\n");
916 #endif
917   return rv;
918 }
919 
DoNtlmStep1(const nsACString & username,const nsAString & password,nsCString & response)920 nsresult nsMsgProtocol::DoNtlmStep1(const nsACString& username,
921                                     const nsAString& password,
922                                     nsCString& response) {
923   nsresult rv;
924 
925   m_authModule = nsIAuthModule::CreateInstance("ntlm");
926 
927   m_authModule->Init(nullptr, 0, nullptr, NS_ConvertUTF8toUTF16(username).get(),
928                      PromiseFlatString(password).get());
929 
930   void* outBuf;
931   uint32_t outBufLen;
932   rv = m_authModule->GetNextToken((void*)nullptr, 0, &outBuf, &outBufLen);
933   if (NS_SUCCEEDED(rv) && outBuf) {
934     char* base64Str = PL_Base64Encode((char*)outBuf, outBufLen, nullptr);
935     if (base64Str)
936       response.Adopt(base64Str);
937     else
938       rv = NS_ERROR_OUT_OF_MEMORY;
939     free(outBuf);
940   }
941 
942   return rv;
943 }
944 
DoNtlmStep2(nsCString & commandResponse,nsCString & response)945 nsresult nsMsgProtocol::DoNtlmStep2(nsCString& commandResponse,
946                                     nsCString& response) {
947   nsresult rv;
948   void *inBuf, *outBuf;
949   uint32_t inBufLen, outBufLen;
950   uint32_t len = commandResponse.Length();
951 
952   // decode into the input secbuffer
953   inBufLen = (len * 3) / 4;  // sufficient size (see plbase64.h)
954   inBuf = moz_xmalloc(inBufLen);
955   if (!inBuf) return NS_ERROR_OUT_OF_MEMORY;
956 
957   // strip off any padding (see bug 230351)
958   const char* challenge = commandResponse.get();
959   while (challenge[len - 1] == '=') len--;
960 
961   rv = (PL_Base64Decode(challenge, len, (char*)inBuf))
962            ? m_authModule->GetNextToken(inBuf, inBufLen, &outBuf, &outBufLen)
963            : NS_ERROR_FAILURE;
964 
965   free(inBuf);
966   if (NS_SUCCEEDED(rv) && outBuf) {
967     char* base64Str = PL_Base64Encode((char*)outBuf, outBufLen, nullptr);
968     if (base64Str)
969       response.Adopt(base64Str);
970     else
971       rv = NS_ERROR_OUT_OF_MEMORY;
972   }
973 
974   if (NS_FAILED(rv)) response = "*";
975 
976   return rv;
977 }
978 
979 /////////////////////////////////////////////////////////////////////
980 // nsMsgAsyncWriteProtocol subclass and related helper classes
981 /////////////////////////////////////////////////////////////////////
982 
983 class nsMsgProtocolStreamProvider : public nsIOutputStreamCallback {
984  public:
985   // XXX this probably doesn't need to be threadsafe
986   NS_DECL_THREADSAFE_ISUPPORTS
987 
nsMsgProtocolStreamProvider()988   nsMsgProtocolStreamProvider() {}
989 
Init(nsMsgAsyncWriteProtocol * aProtInstance,nsIInputStream * aInputStream)990   void Init(nsMsgAsyncWriteProtocol* aProtInstance,
991             nsIInputStream* aInputStream) {
992     mMsgProtocol =
993         do_GetWeakReference(static_cast<nsIStreamListener*>(aProtInstance));
994     mInStream = aInputStream;
995   }
996 
997   //
998   // nsIOutputStreamCallback implementation ...
999   //
OnOutputStreamReady(nsIAsyncOutputStream * aOutStream)1000   NS_IMETHODIMP OnOutputStreamReady(nsIAsyncOutputStream* aOutStream) override {
1001     NS_ASSERTION(mInStream, "not initialized");
1002 
1003     nsresult rv;
1004     uint64_t avail;
1005 
1006     // Write whatever is available in the pipe. If the pipe is empty, then
1007     // return NS_BASE_STREAM_WOULD_BLOCK; we will resume the write when there
1008     // is more data.
1009 
1010     rv = mInStream->Available(&avail);
1011     if (NS_FAILED(rv)) return rv;
1012 
1013     nsMsgAsyncWriteProtocol* protInst = nullptr;
1014     nsCOMPtr<nsIStreamListener> callback = do_QueryReferent(mMsgProtocol);
1015     if (!callback) return NS_ERROR_FAILURE;
1016     protInst = static_cast<nsMsgAsyncWriteProtocol*>(callback.get());
1017 
1018     if (avail == 0 && !protInst->mAsyncBuffer.Length()) {
1019       // ok, stop writing...
1020       protInst->mSuspendedWrite = true;
1021       return NS_OK;
1022     }
1023     protInst->mSuspendedWrite = false;
1024 
1025     uint32_t bytesWritten;
1026 
1027     if (avail) {
1028       rv = aOutStream->WriteFrom(mInStream,
1029                                  std::min(avail, uint64_t(FILE_IO_BUFFER_SIZE)),
1030                                  &bytesWritten);
1031       // if were full at the time, the input stream may be backed up and we need
1032       // to read any remains from the last ODA call before we'll get more ODA
1033       // calls
1034       if (protInst->mSuspendedRead) protInst->UnblockPostReader();
1035     } else {
1036       rv = aOutStream->Write(protInst->mAsyncBuffer.get(),
1037                              protInst->mAsyncBuffer.Length(), &bytesWritten);
1038       protInst->mAsyncBuffer.Cut(0, bytesWritten);
1039     }
1040 
1041     protInst->UpdateProgress(bytesWritten);
1042 
1043     // try to write again...
1044     if (NS_SUCCEEDED(rv))
1045       rv = aOutStream->AsyncWait(this, 0, 0, protInst->mProviderThread);
1046 
1047     NS_ASSERTION(NS_SUCCEEDED(rv) || rv == NS_BINDING_ABORTED,
1048                  "unexpected error writing stream");
1049     return NS_OK;
1050   }
1051 
1052  protected:
~nsMsgProtocolStreamProvider()1053   virtual ~nsMsgProtocolStreamProvider() {}
1054 
1055   nsWeakPtr mMsgProtocol;
1056   nsCOMPtr<nsIInputStream> mInStream;
1057 };
1058 
1059 NS_IMPL_ISUPPORTS(nsMsgProtocolStreamProvider, nsIOutputStreamCallback)
1060 
1061 class nsMsgFilePostHelper : public nsIStreamListener {
1062  public:
1063   NS_DECL_THREADSAFE_ISUPPORTS
1064   NS_DECL_NSIREQUESTOBSERVER
1065   NS_DECL_NSISTREAMLISTENER
1066 
nsMsgFilePostHelper()1067   nsMsgFilePostHelper() { mSuspendedPostFileRead = false; }
1068   nsresult Init(nsIOutputStream* aOutStream,
1069                 nsMsgAsyncWriteProtocol* aProtInstance, nsIFile* aFileToPost);
1070   nsCOMPtr<nsIRequest> mPostFileRequest;
1071   bool mSuspendedPostFileRead;
CloseSocket()1072   void CloseSocket() { mProtInstance = nullptr; }
1073 
1074  protected:
~nsMsgFilePostHelper()1075   virtual ~nsMsgFilePostHelper() {}
1076   nsCOMPtr<nsIOutputStream> mOutStream;
1077   nsWeakPtr mProtInstance;
1078 };
1079 
NS_IMPL_ISUPPORTS(nsMsgFilePostHelper,nsIStreamListener,nsIRequestObserver)1080 NS_IMPL_ISUPPORTS(nsMsgFilePostHelper, nsIStreamListener, nsIRequestObserver)
1081 
1082 nsresult nsMsgFilePostHelper::Init(nsIOutputStream* aOutStream,
1083                                    nsMsgAsyncWriteProtocol* aProtInstance,
1084                                    nsIFile* aFileToPost) {
1085   nsresult rv = NS_OK;
1086   mOutStream = aOutStream;
1087   mProtInstance =
1088       do_GetWeakReference(static_cast<nsIStreamListener*>(aProtInstance));
1089 
1090   nsCOMPtr<nsIInputStream> stream;
1091   rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), aFileToPost);
1092   if (NS_FAILED(rv)) return rv;
1093 
1094   nsCOMPtr<nsIInputStreamPump> pump;
1095   rv = NS_NewInputStreamPump(getter_AddRefs(pump), stream.forget());
1096   if (NS_FAILED(rv)) return rv;
1097 
1098   rv = pump->AsyncRead(this);
1099   if (NS_FAILED(rv)) return rv;
1100 
1101   mPostFileRequest = pump;
1102   return NS_OK;
1103 }
1104 
OnStartRequest(nsIRequest * aChannel)1105 NS_IMETHODIMP nsMsgFilePostHelper::OnStartRequest(nsIRequest* aChannel) {
1106   return NS_OK;
1107 }
1108 
OnStopRequest(nsIRequest * aChannel,nsresult aStatus)1109 NS_IMETHODIMP nsMsgFilePostHelper::OnStopRequest(nsIRequest* aChannel,
1110                                                  nsresult aStatus) {
1111   nsMsgAsyncWriteProtocol* protInst = nullptr;
1112   nsCOMPtr<nsIStreamListener> callback = do_QueryReferent(mProtInstance);
1113   if (!callback) return NS_OK;
1114   protInst = static_cast<nsMsgAsyncWriteProtocol*>(callback.get());
1115 
1116   if (!mSuspendedPostFileRead) protInst->PostDataFinished();
1117 
1118   mSuspendedPostFileRead = false;
1119   protInst->mFilePostHelper = nullptr;
1120   return NS_OK;
1121 }
1122 
OnDataAvailable(nsIRequest *,nsIInputStream * inStr,uint64_t sourceOffset,uint32_t count)1123 NS_IMETHODIMP nsMsgFilePostHelper::OnDataAvailable(nsIRequest* /* aChannel */,
1124                                                    nsIInputStream* inStr,
1125                                                    uint64_t sourceOffset,
1126                                                    uint32_t count) {
1127   nsMsgAsyncWriteProtocol* protInst = nullptr;
1128   nsCOMPtr<nsIStreamListener> callback = do_QueryReferent(mProtInstance);
1129   if (!callback) return NS_OK;
1130 
1131   protInst = static_cast<nsMsgAsyncWriteProtocol*>(callback.get());
1132 
1133   if (mSuspendedPostFileRead) {
1134     protInst->UpdateSuspendedReadBytes(count, protInst->mInsertPeriodRequired);
1135     return NS_OK;
1136   }
1137 
1138   protInst->ProcessIncomingPostData(inStr, count);
1139 
1140   if (protInst->mSuspendedWrite) {
1141     // if we got here then we had suspended the write 'cause we didn't have
1142     // anymore data to write (i.e. the pipe went empty). So resume the channel
1143     // to kick things off again.
1144     protInst->mSuspendedWrite = false;
1145     protInst->mAsyncOutStream->AsyncWait(protInst->mProvider, 0, 0,
1146                                          protInst->mProviderThread);
1147   }
1148 
1149   return NS_OK;
1150 }
1151 
NS_IMPL_ADDREF_INHERITED(nsMsgAsyncWriteProtocol,nsMsgProtocol)1152 NS_IMPL_ADDREF_INHERITED(nsMsgAsyncWriteProtocol, nsMsgProtocol)
1153 NS_IMPL_RELEASE_INHERITED(nsMsgAsyncWriteProtocol, nsMsgProtocol)
1154 
1155 NS_INTERFACE_MAP_BEGIN(nsMsgAsyncWriteProtocol)
1156   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
1157 NS_INTERFACE_MAP_END_INHERITING(nsMsgProtocol)
1158 
1159 nsMsgAsyncWriteProtocol::nsMsgAsyncWriteProtocol(nsIURI* aURL)
1160     : nsMsgProtocol(aURL) {
1161   mSuspendedWrite = false;
1162   mSuspendedReadBytes = 0;
1163   mSuspendedRead = false;
1164   mInsertPeriodRequired = false;
1165   mGenerateProgressNotifications = false;
1166   mSuspendedReadBytesPostPeriod = 0;
1167   mFilePostHelper = nullptr;
1168 }
1169 
~nsMsgAsyncWriteProtocol()1170 nsMsgAsyncWriteProtocol::~nsMsgAsyncWriteProtocol() {}
1171 
Cancel(nsresult status)1172 NS_IMETHODIMP nsMsgAsyncWriteProtocol::Cancel(nsresult status) {
1173   mGenerateProgressNotifications = false;
1174 
1175   if (m_proxyRequest) {
1176     m_proxyRequest->Cancel(status);
1177   }
1178 
1179   if (m_request) m_request->Cancel(status);
1180 
1181   if (mAsyncOutStream) mAsyncOutStream->CloseWithStatus(status);
1182 
1183   return NS_OK;
1184 }
1185 
PostMessage(nsIURI * url,nsIFile * file)1186 nsresult nsMsgAsyncWriteProtocol::PostMessage(nsIURI* url, nsIFile* file) {
1187   nsCOMPtr<nsIStreamListener> listener = new nsMsgFilePostHelper();
1188 
1189   if (!listener) return NS_ERROR_OUT_OF_MEMORY;
1190 
1191   // be sure to initialize some state before posting
1192   mSuspendedReadBytes = 0;
1193   mNumBytesPosted = 0;
1194   file->GetFileSize(&mFilePostSize);
1195   mSuspendedRead = false;
1196   mInsertPeriodRequired = false;
1197   mSuspendedReadBytesPostPeriod = 0;
1198   mGenerateProgressNotifications = true;
1199 
1200   mFilePostHelper = static_cast<nsMsgFilePostHelper*>(
1201       static_cast<nsIStreamListener*>(listener));
1202 
1203   static_cast<nsMsgFilePostHelper*>(static_cast<nsIStreamListener*>(listener))
1204       ->Init(m_outputStream, this, file);
1205 
1206   return NS_OK;
1207 }
1208 
SuspendPostFileRead()1209 nsresult nsMsgAsyncWriteProtocol::SuspendPostFileRead() {
1210   if (mFilePostHelper && !mFilePostHelper->mSuspendedPostFileRead) {
1211     // uhoh we need to pause reading in the file until we get unblocked...
1212     mFilePostHelper->mPostFileRequest->Suspend();
1213     mFilePostHelper->mSuspendedPostFileRead = true;
1214   }
1215 
1216   return NS_OK;
1217 }
1218 
ResumePostFileRead()1219 nsresult nsMsgAsyncWriteProtocol::ResumePostFileRead() {
1220   if (mFilePostHelper) {
1221     if (mFilePostHelper->mSuspendedPostFileRead) {
1222       mFilePostHelper->mPostFileRequest->Resume();
1223       mFilePostHelper->mSuspendedPostFileRead = false;
1224     }
1225   } else  // we must be done with the download so send the '.'
1226   {
1227     PostDataFinished();
1228   }
1229 
1230   return NS_OK;
1231 }
1232 
UpdateSuspendedReadBytes(uint32_t aNewBytes,bool aAddToPostPeriodByteCount)1233 nsresult nsMsgAsyncWriteProtocol::UpdateSuspendedReadBytes(
1234     uint32_t aNewBytes, bool aAddToPostPeriodByteCount) {
1235   // depending on our current state, we'll either add aNewBytes to
1236   // mSuspendedReadBytes or mSuspendedReadBytesAfterPeriod.
1237 
1238   mSuspendedRead = true;
1239   if (aAddToPostPeriodByteCount)
1240     mSuspendedReadBytesPostPeriod += aNewBytes;
1241   else
1242     mSuspendedReadBytes += aNewBytes;
1243 
1244   return NS_OK;
1245 }
1246 
PostDataFinished()1247 nsresult nsMsgAsyncWriteProtocol::PostDataFinished() {
1248   nsresult rv = SendData("." CRLF);
1249   if (NS_FAILED(rv)) return rv;
1250   mGenerateProgressNotifications = false;
1251   mPostDataStream = nullptr;
1252   return NS_OK;
1253 }
1254 
ProcessIncomingPostData(nsIInputStream * inStr,uint32_t count)1255 nsresult nsMsgAsyncWriteProtocol::ProcessIncomingPostData(nsIInputStream* inStr,
1256                                                           uint32_t count) {
1257   if (!m_socketIsOpen) return NS_OK;  // kick out if the socket was canceled
1258 
1259   // We need to quote any '.' that occur at the beginning of a line.
1260   // but I don't want to waste time reading out the data into a buffer and
1261   // searching let's try to leverage nsIBufferedInputStream and see if we can
1262   // "peek" into the current contents for this particular case.
1263 
1264   nsCOMPtr<nsISearchableInputStream> bufferInputStr = do_QueryInterface(inStr);
1265   NS_ASSERTION(
1266       bufferInputStr,
1267       "i made a wrong assumption about the type of stream we are getting");
1268   NS_ASSERTION(mSuspendedReadBytes == 0, "oops, I missed something");
1269 
1270   if (!mPostDataStream) mPostDataStream = inStr;
1271 
1272   if (bufferInputStr) {
1273     uint32_t amountWritten;
1274 
1275     while (count > 0) {
1276       bool found = false;
1277       uint32_t offset = 0;
1278       bufferInputStr->Search("\012.", true, &found, &offset);  // LF.
1279 
1280       if (!found || offset > count) {
1281         // push this data into the output stream
1282         m_outputStream->WriteFrom(inStr, count, &amountWritten);
1283         // store any remains which need read out at a later date
1284         if (count > amountWritten)  // stream will block
1285         {
1286           UpdateSuspendedReadBytes(count - amountWritten, false);
1287           SuspendPostFileRead();
1288         }
1289         break;
1290       } else {
1291         // count points to the LF in a LF followed by a '.'
1292         // go ahead and write up to offset..
1293         m_outputStream->WriteFrom(inStr, offset + 1, &amountWritten);
1294         count -= amountWritten;
1295         if (offset + 1 > amountWritten) {
1296           UpdateSuspendedReadBytes(offset + 1 - amountWritten, false);
1297           mInsertPeriodRequired = true;
1298           UpdateSuspendedReadBytes(count, mInsertPeriodRequired);
1299           SuspendPostFileRead();
1300           break;
1301         }
1302 
1303         // write out the extra '.'
1304         m_outputStream->Write(".", 1, &amountWritten);
1305         if (amountWritten != 1) {
1306           mInsertPeriodRequired = true;
1307           // once we do write out the '.',  if we are now blocked we need to
1308           // remember the remaining count that comes after the '.' so we can
1309           // perform processing on that once we become unblocked.
1310           UpdateSuspendedReadBytes(count, mInsertPeriodRequired);
1311           SuspendPostFileRead();
1312           break;
1313         }
1314       }
1315     }  // while count > 0
1316   }
1317 
1318   return NS_OK;
1319 }
UnblockPostReader()1320 nsresult nsMsgAsyncWriteProtocol::UnblockPostReader() {
1321   uint32_t amountWritten = 0;
1322 
1323   if (!m_socketIsOpen) return NS_OK;  // kick out if the socket was canceled
1324 
1325   if (mSuspendedRead) {
1326     // (1) attempt to write out any remaining read bytes we need in order to
1327     // unblock the reader
1328     if (mSuspendedReadBytes > 0 && mPostDataStream) {
1329       uint64_t avail = 0;
1330       mPostDataStream->Available(&avail);
1331 
1332       m_outputStream->WriteFrom(mPostDataStream,
1333                                 std::min(avail, uint64_t(mSuspendedReadBytes)),
1334                                 &amountWritten);
1335       // hmm sometimes my mSuspendedReadBytes is getting out of whack...so for
1336       // now, reset it if necessary.
1337       if (mSuspendedReadBytes > avail) mSuspendedReadBytes = avail;
1338 
1339       if (mSuspendedReadBytes > amountWritten)
1340         mSuspendedReadBytes -= amountWritten;
1341       else
1342         mSuspendedReadBytes = 0;
1343     }
1344 
1345     // (2) if we are now unblocked, and we need to insert a '.' then do so
1346     // now...
1347     if (mInsertPeriodRequired && mSuspendedReadBytes == 0) {
1348       amountWritten = 0;
1349       m_outputStream->Write(".", 1, &amountWritten);
1350       if (amountWritten == 1)  // if we succeeded then clear pending '.' flag
1351         mInsertPeriodRequired = false;
1352     }
1353 
1354     // (3) if we inserted a '.' and we still have bytes after the '.' which need
1355     // processed before the stream is unblocked then fake an ODA call to handle
1356     // this now...
1357     if (!mInsertPeriodRequired && mSuspendedReadBytesPostPeriod > 0) {
1358       // these bytes actually need processed for extra '.''s.....
1359       uint32_t postbytes = mSuspendedReadBytesPostPeriod;
1360       mSuspendedReadBytesPostPeriod = 0;
1361       ProcessIncomingPostData(mPostDataStream, postbytes);
1362     }
1363 
1364     // (4) determine if we are out of the suspended read state...
1365     if (mSuspendedReadBytes == 0 && !mInsertPeriodRequired &&
1366         mSuspendedReadBytesPostPeriod == 0) {
1367       mSuspendedRead = false;
1368       ResumePostFileRead();
1369     }
1370 
1371   }  // if we are in the suspended read state
1372 
1373   return NS_OK;
1374 }
1375 
SetupTransportState()1376 nsresult nsMsgAsyncWriteProtocol::SetupTransportState() {
1377   nsresult rv = NS_OK;
1378 
1379   if (!m_outputStream && m_transport) {
1380     // first create a pipe which we'll use to write the data we want to send
1381     // into.
1382     nsCOMPtr<nsIPipe> pipe = do_CreateInstance("@mozilla.org/pipe;1");
1383     rv = pipe->Init(true, true, 1024, 8);
1384     NS_ENSURE_SUCCESS(rv, rv);
1385 
1386     nsIAsyncInputStream* inputStream = nullptr;
1387     // This always succeeds because the pipe is initialized above.
1388     MOZ_ALWAYS_SUCCEEDS(pipe->GetInputStream(&inputStream));
1389     mInStream = dont_AddRef(static_cast<nsIInputStream*>(inputStream));
1390 
1391     nsIAsyncOutputStream* outputStream = nullptr;
1392     // This always succeeds because the pipe is initialized above.
1393     MOZ_ALWAYS_SUCCEEDS(pipe->GetOutputStream(&outputStream));
1394     m_outputStream = dont_AddRef(static_cast<nsIOutputStream*>(outputStream));
1395 
1396     mProviderThread = do_GetCurrentThread();
1397 
1398     nsMsgProtocolStreamProvider* provider = new nsMsgProtocolStreamProvider();
1399 
1400     if (!provider) return NS_ERROR_OUT_OF_MEMORY;
1401 
1402     provider->Init(this, mInStream);
1403     mProvider = provider;  // ADDREF
1404 
1405     nsCOMPtr<nsIOutputStream> stream;
1406     rv = m_transport->OpenOutputStream(0, 0, 0, getter_AddRefs(stream));
1407     if (NS_FAILED(rv)) return rv;
1408 
1409     mAsyncOutStream = do_QueryInterface(stream, &rv);
1410     if (NS_FAILED(rv)) return rv;
1411 
1412     // wait for the output stream to become writable
1413     rv = mAsyncOutStream->AsyncWait(mProvider, 0, 0, mProviderThread);
1414   }  // if m_transport
1415 
1416   return rv;
1417 }
1418 
CloseSocket()1419 nsresult nsMsgAsyncWriteProtocol::CloseSocket() {
1420   nsresult rv = NS_OK;
1421   if (mAsyncOutStream) mAsyncOutStream->CloseWithStatus(NS_BINDING_ABORTED);
1422 
1423   nsMsgProtocol::CloseSocket();
1424 
1425   if (mFilePostHelper) {
1426     mFilePostHelper->CloseSocket();
1427     mFilePostHelper = nullptr;
1428   }
1429 
1430   mAsyncOutStream = nullptr;
1431   mProvider = nullptr;
1432   mProviderThread = nullptr;
1433   mAsyncBuffer.Truncate();
1434   return rv;
1435 }
1436 
UpdateProgress(uint32_t aNewBytes)1437 void nsMsgAsyncWriteProtocol::UpdateProgress(uint32_t aNewBytes) {
1438   if (!mGenerateProgressNotifications) return;
1439 
1440   mNumBytesPosted += aNewBytes;
1441   if (mFilePostSize > 0) {
1442     nsCOMPtr<nsIMsgMailNewsUrl> mailUrl = do_QueryInterface(m_url);
1443     if (!mailUrl) return;
1444 
1445     nsCOMPtr<nsIMsgStatusFeedback> statusFeedback;
1446     mailUrl->GetStatusFeedback(getter_AddRefs(statusFeedback));
1447     if (!statusFeedback) return;
1448 
1449     nsCOMPtr<nsIWebProgressListener> webProgressListener(
1450         do_QueryInterface(statusFeedback));
1451     if (!webProgressListener) return;
1452 
1453     // XXX not sure if m_request is correct here
1454     webProgressListener->OnProgressChange(nullptr, m_request, mNumBytesPosted,
1455                                           static_cast<uint32_t>(mFilePostSize),
1456                                           mNumBytesPosted, mFilePostSize);
1457   }
1458 
1459   return;
1460 }
1461 
SendData(const char * dataBuffer,bool aSuppressLogging)1462 nsresult nsMsgAsyncWriteProtocol::SendData(const char* dataBuffer,
1463                                            bool aSuppressLogging) {
1464   this->mAsyncBuffer.Append(dataBuffer);
1465   if (!mAsyncOutStream) return NS_ERROR_FAILURE;
1466   return mAsyncOutStream->AsyncWait(mProvider, 0, 0, mProviderThread);
1467 }
1468 
FormatStringWithHostNameByName(const char16_t * stringName,nsIMsgMailNewsUrl * msgUri)1469 char16_t* FormatStringWithHostNameByName(const char16_t* stringName,
1470                                          nsIMsgMailNewsUrl* msgUri) {
1471   if (!msgUri) return nullptr;
1472 
1473   nsresult rv;
1474 
1475   nsCOMPtr<nsIStringBundleService> sBundleService =
1476       mozilla::services::GetStringBundleService();
1477   NS_ENSURE_TRUE(sBundleService, nullptr);
1478 
1479   nsCOMPtr<nsIStringBundle> sBundle;
1480   rv = sBundleService->CreateBundle(MSGS_URL, getter_AddRefs(sBundle));
1481   NS_ENSURE_SUCCESS(rv, nullptr);
1482 
1483   nsCOMPtr<nsIMsgIncomingServer> server;
1484   rv = msgUri->GetServer(getter_AddRefs(server));
1485   NS_ENSURE_SUCCESS(rv, nullptr);
1486 
1487   nsCString hostName;
1488   rv = server->GetRealHostName(hostName);
1489   NS_ENSURE_SUCCESS(rv, nullptr);
1490 
1491   AutoTArray<nsString, 1> params;
1492   CopyASCIItoUTF16(hostName, *params.AppendElement());
1493   nsAutoString str;
1494   rv = sBundle->FormatStringFromName(NS_ConvertUTF16toUTF8(stringName).get(),
1495                                      params, str);
1496   NS_ENSURE_SUCCESS(rv, nullptr);
1497 
1498   return ToNewUnicode(str);
1499 }
1500 
1501 // vim: ts=2 sw=2
1502