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