/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: NPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Netscape Public License * Version 1.1 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://www.mozilla.org/NPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is mozilla.org code. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1998 * the Initial Developer. All Rights Reserved. * * Contributor(s): * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the NPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the NPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "msgCore.h" #include "nsReadableUtils.h" #include "nsMsgProtocol.h" #include "nsIMsgMailNewsUrl.h" #include "nsISocketTransportService.h" #include "nsXPIDLString.h" #include "nsSpecialSystemDirectory.h" #include "nsILoadGroup.h" #include "nsIIOService.h" #include "nsNetUtil.h" #include "nsIFileURL.h" #include "nsFileStream.h" #include "nsIFileTransportService.h" #include "nsIDNSService.h" #include "nsIMsgWindow.h" #include "nsIMsgStatusFeedback.h" #include "nsIWebProgressListener.h" #include "nsIPipe.h" #include "nsIPrompt.h" #include "prprf.h" #include "nsIStringBundle.h" #include "nsIProtocolProxyService.h" #include "nsIProxyInfo.h" static NS_DEFINE_CID(kSocketTransportServiceCID, NS_SOCKETTRANSPORTSERVICE_CID); static NS_DEFINE_CID(kIOServiceCID, NS_IOSERVICE_CID); static NS_DEFINE_CID(kFileTransportServiceCID, NS_FILETRANSPORTSERVICE_CID); static NS_DEFINE_CID(kStringBundleServiceCID, NS_STRINGBUNDLESERVICE_CID); NS_IMPL_THREADSAFE_ADDREF(nsMsgProtocol) NS_IMPL_THREADSAFE_RELEASE(nsMsgProtocol) NS_INTERFACE_MAP_BEGIN(nsMsgProtocol) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIChannel) NS_INTERFACE_MAP_ENTRY(nsIStreamListener) NS_INTERFACE_MAP_ENTRY(nsIRequestObserver) NS_INTERFACE_MAP_ENTRY(nsIChannel) NS_INTERFACE_MAP_ENTRY(nsIRequest) NS_INTERFACE_MAP_END_THREADSAFE static PRUnichar *GetStringByID(PRInt32 stringID); static PRUnichar *FormatStringWithHostNameByID(PRInt32 stringID, nsIMsgMailNewsUrl *msgUri); nsMsgProtocol::nsMsgProtocol(nsIURI * aURL) { NS_INIT_REFCNT(); m_flags = 0; m_startPosition = 0; m_readCount = 0; mLoadFlags = 0; m_socketIsOpen = PR_FALSE; m_tempMsgFileSpec = nsSpecialSystemDirectory(nsSpecialSystemDirectory::OS_TemporaryDirectory); m_tempMsgFileSpec += "tempMessage.eml"; mSuppressListenerNotifications = PR_FALSE; InitFromURI(aURL); } nsresult nsMsgProtocol::InitFromURI(nsIURI *aUrl) { m_url = aUrl; nsCOMPtr mailUrl = do_QueryInterface(aUrl); if (mailUrl) { mailUrl->GetLoadGroup(getter_AddRefs(m_loadGroup)); nsCOMPtr statusFeedback; mailUrl->GetStatusFeedback(getter_AddRefs(statusFeedback)); mProgressEventSink = do_QueryInterface(statusFeedback); } return NS_OK; } nsMsgProtocol::~nsMsgProtocol() {} nsresult nsMsgProtocol::OpenNetworkSocketWithInfo(const char * aHostName, PRInt32 aGetPort, const char *connectionType, nsIProxyInfo *aProxyInfo, nsIInterfaceRequestor* callbacks) { NS_ENSURE_ARG(aHostName); nsresult rv = NS_OK; nsCOMPtr socketService (do_GetService(kSocketTransportServiceCID)); NS_ENSURE_TRUE(socketService, NS_ERROR_FAILURE); // with socket connections we want to read as much data as arrives m_readCount = -1; m_startPosition = 0; rv = socketService->CreateTransportOfType(connectionType, aHostName, aGetPort, aProxyInfo, 0, 0, getter_AddRefs(m_transport)); if (NS_FAILED(rv)) return rv; m_transport->SetNotificationCallbacks(callbacks, PR_FALSE); m_socketIsOpen = PR_FALSE; return SetupTransportState(); } // open a connection on this url nsresult nsMsgProtocol::OpenNetworkSocket(nsIURI * aURL, const char *connectionType, nsIInterfaceRequestor* callbacks) { NS_ENSURE_ARG(aURL); nsCAutoString hostName; PRInt32 port = 0; aURL->GetPort(&port); aURL->GetAsciiHost(hostName); nsCOMPtr proxyInfo; nsCOMPtr pps = do_GetService("@mozilla.org/network/protocol-proxy-service;1"); NS_ASSERTION(pps, "Couldn't get the protocol proxy service!"); if (pps) { nsresult rv = NS_OK; // Yes, this is ugly. But necko needs to grap a protocol handler // to ask for flags, and smtp isn't registered as a handler, only // mailto. // Note that I cannot just clone, and call SetSpec, since Clone on // nsSmtpUrl calls nsStandardUrl's clone method, which fails // because smtp isn't a registered protocol. // So we cheat. Whilst creating a uri manually is valid here, // do _NOT_ copy this to use in your own code - bbaetz nsCOMPtr proxyUri = aURL; PRBool isSMTP = PR_FALSE; if (NS_SUCCEEDED(aURL->SchemeIs("smtp", &isSMTP)) && isSMTP) { nsCAutoString spec; rv = aURL->GetSpec(spec); if (NS_SUCCEEDED(rv)) { static NS_DEFINE_CID(kSTDURLCID, NS_STANDARDURL_CID); proxyUri = do_CreateInstance(kSTDURLCID, &rv); } if (NS_SUCCEEDED(rv)) rv = proxyUri->SetSpec(spec); if (NS_SUCCEEDED(rv)) rv = proxyUri->SetScheme(NS_LITERAL_CSTRING("mailto")); } if (NS_SUCCEEDED(rv)) rv = pps->ExamineForProxy(proxyUri, getter_AddRefs(proxyInfo)); NS_ASSERTION(NS_SUCCEEDED(rv), "Couldn't successfully call ExamineForProxy"); if (NS_FAILED(rv)) proxyInfo = nsnull; } return OpenNetworkSocketWithInfo(hostName.get(), port, connectionType, proxyInfo, callbacks); } nsresult nsMsgProtocol::GetFileFromURL(nsIURI * aURL, nsIFile **aResult) { NS_ENSURE_ARG_POINTER(aURL); NS_ENSURE_ARG_POINTER(aResult); // extract the file path from the uri... nsCAutoString urlSpec; aURL->GetPath(urlSpec); urlSpec.Insert(NS_LITERAL_CSTRING("file://"), 0); nsresult rv; // dougt - there should be an easier way! nsCOMPtr uri; if (NS_FAILED(rv = NS_NewURI(getter_AddRefs(uri), urlSpec.get()))) return rv; nsCOMPtr fileURL = do_QueryInterface(uri); if (!fileURL) return NS_ERROR_FAILURE; return fileURL->GetFile(aResult); // dougt } nsresult nsMsgProtocol::OpenFileSocket(nsIURI * aURL, PRUint32 aStartPosition, PRInt32 aReadCount) { // mscott - file needs to be encoded directly into aURL. I should be able to get // rid of this method completely. nsresult rv = NS_OK; m_startPosition = aStartPosition; m_readCount = aReadCount; nsCOMPtr file; rv = GetFileFromURL(aURL, getter_AddRefs(file)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr fts = do_GetService(kFileTransportServiceCID, &rv); if (NS_FAILED(rv)) return rv; //we are always using this file socket to read data from the mailbox. rv = fts->CreateTransport(file, PR_RDONLY, 0664, getter_AddRefs(m_transport)); m_socketIsOpen = PR_FALSE; return rv; } nsresult nsMsgProtocol::SetupTransportState() { nsresult rv = NS_OK; if (!m_socketIsOpen && m_transport) { rv = m_transport->OpenOutputStream(0, PRUint32(-1), 0, getter_AddRefs(m_outputStream)); NS_ASSERTION(NS_SUCCEEDED(rv), "unable to create an output stream"); // we want to open the stream } // if m_transport return rv; } nsresult nsMsgProtocol::CloseSocket() { nsresult rv = NS_OK; // release all of our socket state m_socketIsOpen = PR_FALSE; m_outputStream = nsnull; if (m_transport) m_transport->SetNotificationCallbacks(nsnull, PR_FALSE); // we need to call Cancel so that we remove the socket transport from the mActiveTransportList. see bug #30648 if (m_request) { rv = m_request->Cancel(NS_BINDING_ABORTED); } m_request = 0; m_transport = 0; return rv; } /* * Writes the data contained in dataBuffer into the current output stream. It also informs * the transport layer that this data is now available for transmission. * Returns a positive number for success, 0 for failure (not all the bytes were written to the * stream, etc). We need to make another pass through this file to install an error system (mscott) * * No logging is done in the base implementation, so aSuppressLogging is ignored. */ PRInt32 nsMsgProtocol::SendData(nsIURI * aURL, const char * dataBuffer, PRBool aSuppressLogging) { PRUint32 writeCount = 0; PRInt32 status = 0; // NS_PRECONDITION(m_outputStream, "oops....we don't have an output stream...how did that happen?"); if (dataBuffer && m_outputStream) { status = m_outputStream->Write(dataBuffer, PL_strlen(dataBuffer), &writeCount); } return status; } // Whenever data arrives from the connection, core netlib notifices the protocol by calling // OnDataAvailable. We then read and process the incoming data from the input stream. NS_IMETHODIMP nsMsgProtocol::OnDataAvailable(nsIRequest *request, nsISupports *ctxt, nsIInputStream *inStr, PRUint32 sourceOffset, PRUint32 count) { // right now, this really just means turn around and churn through the state machine nsCOMPtr uri = do_QueryInterface(ctxt); return ProcessProtocolState(uri, inStr, sourceOffset, count); } NS_IMETHODIMP nsMsgProtocol::OnStartRequest(nsIRequest *request, nsISupports *ctxt) { nsresult rv = NS_OK; nsCOMPtr aMsgUrl = do_QueryInterface(ctxt, &rv); if (NS_SUCCEEDED(rv) && aMsgUrl) { rv = aMsgUrl->SetUrlState(PR_TRUE, NS_OK); if (m_loadGroup) m_loadGroup->AddRequest(NS_STATIC_CAST(nsIRequest *, this), nsnull /* context isupports */); } // if we are set up as a channel, we should notify our channel listener that we are starting... // so pass in ourself as the channel and not the underlying socket or file channel the protocol // happens to be using if (!mSuppressListenerNotifications && m_channelListener) { if (!m_channelContext) m_channelContext = do_QueryInterface(ctxt); rv = m_channelListener->OnStartRequest(this, m_channelContext); } NS_ENSURE_SUCCESS(rv, rv); return rv; } // stop binding is a "notification" informing us that the stream associated with aURL is going away. NS_IMETHODIMP nsMsgProtocol::OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult aStatus) { nsresult rv = NS_OK; // if we are set up as a channel, we should notify our channel listener that we are starting... // so pass in ourself as the channel and not the underlying socket or file channel the protocol // happens to be using if (!mSuppressListenerNotifications && m_channelListener) rv = m_channelListener->OnStopRequest(this, m_channelContext, aStatus); nsCOMPtr msgUrl = do_QueryInterface(ctxt, &rv); if (NS_SUCCEEDED(rv) && msgUrl) { rv = msgUrl->SetUrlState(PR_FALSE, aStatus); if (m_loadGroup) m_loadGroup->RemoveRequest(NS_STATIC_CAST(nsIRequest *, this), nsnull, aStatus); // !NS_BINDING_ABORTED because we don't want to see an alert if the user // cancelled the operation. also, we'll get here because we call Cancel() // to force removal of the nsSocketTransport. see CloseSocket() // bugs #30775 and #30648 relate to this if (NS_FAILED(aStatus) && (aStatus != NS_BINDING_ABORTED)) { nsCOMPtr msgPrompt; GetPromptDialogFromUrl(msgUrl , getter_AddRefs(msgPrompt)); NS_ENSURE_TRUE(msgPrompt, NS_ERROR_FAILURE); PRInt32 errorID; switch (aStatus) { case NS_ERROR_UNKNOWN_HOST: errorID = UNKNOWN_HOST_ERROR; break; case NS_ERROR_CONNECTION_REFUSED: errorID = CONNECTION_REFUSED_ERROR; break; case NS_ERROR_NET_TIMEOUT: errorID = NET_TIMEOUT_ERROR; break; default: errorID = UNKNOWN_ERROR; break; } NS_ASSERTION(errorID != UNKNOWN_ERROR, "unknown error, but don't alert user."); if (errorID != UNKNOWN_ERROR) { PRUnichar *errorMsg = FormatStringWithHostNameByID(errorID, msgUrl); if (errorMsg == nsnull) { nsAutoString resultString(NS_LITERAL_STRING("[StringID ")); resultString.AppendInt(errorID, 10); resultString.Append(NS_LITERAL_STRING("?]")); errorMsg = ToNewUnicode(resultString); } rv = msgPrompt->Alert(nsnull, errorMsg); nsMemory::Free(errorMsg); } } // if we got an error code } // if we have a mailnews url. return rv; } nsresult nsMsgProtocol::GetPromptDialogFromUrl(nsIMsgMailNewsUrl * aMsgUrl, nsIPrompt ** aPromptDialog) { // get the nsIPrompt interface from the message window associated wit this url. nsCOMPtr msgWindow; aMsgUrl->GetMsgWindow(getter_AddRefs(msgWindow)); NS_ENSURE_TRUE(msgWindow, NS_ERROR_FAILURE); msgWindow->GetPromptDialog(aPromptDialog); NS_ENSURE_TRUE(*aPromptDialog, NS_ERROR_FAILURE); return NS_OK; } nsresult nsMsgProtocol::LoadUrl(nsIURI * aURL, nsISupports * aConsumer) { // okay now kick us off to the next state... // our first state is a process state so drive the state machine... nsresult rv = NS_OK; nsCOMPtr aMsgUrl = do_QueryInterface(aURL, &rv); if (NS_SUCCEEDED(rv) && aMsgUrl) { PRBool msgIsInLocalCache; aMsgUrl->GetMsgIsInLocalCache(&msgIsInLocalCache); rv = aMsgUrl->SetUrlState(PR_TRUE, NS_OK); // set the url as a url currently being run... // if the url is given a stream consumer then we should use it to forward calls to... if (!m_channelListener && aConsumer) // if we don't have a registered listener already { m_channelListener = do_QueryInterface(aConsumer); if (!m_channelContext) m_channelContext = do_QueryInterface(aURL); } if (!m_socketIsOpen) { nsCOMPtr urlSupports = do_QueryInterface(aURL); if (m_transport) { // put us in a state where we are always notified of incoming data rv = m_transport->AsyncRead(this, urlSupports, m_startPosition, m_readCount, 0, getter_AddRefs(m_request)); NS_ASSERTION(NS_SUCCEEDED(rv), "AsyncRead failed"); m_socketIsOpen = PR_TRUE; // mark the channel as open } } // if we got an event queue service else if (!msgIsInLocalCache) // the connection is already open so we should begin processing our new url... rv = ProcessProtocolState(aURL, nsnull, 0, 0); } return rv; } /////////////////////////////////////////////////////////////////////// // The rest of this file is mostly nsIChannel mumbo jumbo stuff /////////////////////////////////////////////////////////////////////// nsresult nsMsgProtocol::SetUrl(nsIURI * aURL) { m_url = dont_QueryInterface(aURL); return NS_OK; } NS_IMETHODIMP nsMsgProtocol::SetLoadGroup(nsILoadGroup * aLoadGroup) { m_loadGroup = dont_QueryInterface(aLoadGroup); return NS_OK; } NS_IMETHODIMP nsMsgProtocol::GetOriginalURI(nsIURI* *aURI) { *aURI = m_originalUrl ? m_originalUrl : m_url; NS_IF_ADDREF(*aURI); return NS_OK; } NS_IMETHODIMP nsMsgProtocol::SetOriginalURI(nsIURI* aURI) { m_originalUrl = aURI; return NS_OK; } NS_IMETHODIMP nsMsgProtocol::GetURI(nsIURI* *aURI) { *aURI = m_url; NS_IF_ADDREF(*aURI); return NS_OK; } NS_IMETHODIMP nsMsgProtocol::Open(nsIInputStream **_retval) { NS_NOTREACHED("Open"); return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsMsgProtocol::AsyncOpen(nsIStreamListener *listener, nsISupports *ctxt) { PRInt32 port; nsresult rv = m_url->GetPort(&port); if (NS_FAILED(rv)) return rv; nsCAutoString scheme; rv = m_url->GetScheme(scheme); if (NS_FAILED(rv)) return rv; rv = NS_CheckPortSafety(port, scheme.get()); if (NS_FAILED(rv)) return rv; // set the stream listener and then load the url m_channelContext = ctxt; m_channelListener = listener; return LoadUrl(m_url, nsnull); } NS_IMETHODIMP nsMsgProtocol::GetLoadFlags(nsLoadFlags *aLoadFlags) { *aLoadFlags = mLoadFlags; return NS_OK; } NS_IMETHODIMP nsMsgProtocol::SetLoadFlags(nsLoadFlags aLoadFlags) { mLoadFlags = aLoadFlags; return NS_OK; // don't fail when trying to set this } NS_IMETHODIMP nsMsgProtocol::GetContentType(nsACString &aContentType) { // as url dispatching matures, we'll be intelligent and actually start // opening the url before specifying the content type. This will allow // us to optimize the case where the message url actual refers to // a part in the message that has a content type that is not message/rfc822 if (m_ContentType.IsEmpty()) aContentType = NS_LITERAL_CSTRING("message/rfc822"); else aContentType = m_ContentType; return NS_OK; } NS_IMETHODIMP nsMsgProtocol::SetContentType(const nsACString &aContentType) { m_ContentType = aContentType; return NS_OK; } NS_IMETHODIMP nsMsgProtocol::GetContentCharset(nsACString &aContentCharset) { aContentCharset.Truncate(); return NS_OK; } NS_IMETHODIMP nsMsgProtocol::SetContentCharset(const nsACString &aContentCharset) { NS_NOTREACHED("nsMsgProtocol::SetContentCharset"); return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsMsgProtocol::GetContentLength(PRInt32 * aContentLength) { *aContentLength = -1; return NS_OK; } NS_IMETHODIMP nsMsgProtocol::GetSecurityInfo(nsISupports * *aSecurityInfo) { *aSecurityInfo = nsnull; return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsMsgProtocol::GetName(nsACString &aName) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsMsgProtocol::SetContentLength(PRInt32 aContentLength) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsMsgProtocol::GetOwner(nsISupports * *aPrincipal) { *aPrincipal = mOwner; NS_IF_ADDREF(*aPrincipal); return NS_OK; } NS_IMETHODIMP nsMsgProtocol::SetOwner(nsISupports * aPrincipal) { mOwner = aPrincipal; return NS_OK; } NS_IMETHODIMP nsMsgProtocol::GetLoadGroup(nsILoadGroup * *aLoadGroup) { *aLoadGroup = m_loadGroup; NS_IF_ADDREF(*aLoadGroup); return NS_OK; } NS_IMETHODIMP nsMsgProtocol::GetNotificationCallbacks(nsIInterfaceRequestor* *aNotificationCallbacks) { *aNotificationCallbacks = mCallbacks.get(); NS_IF_ADDREF(*aNotificationCallbacks); return NS_OK; } NS_IMETHODIMP nsMsgProtocol::SetNotificationCallbacks(nsIInterfaceRequestor* aNotificationCallbacks) { mCallbacks = aNotificationCallbacks; // Verify that the event sink is http if (mCallbacks) { nsCOMPtr progressSink; (void)mCallbacks->GetInterface(NS_GET_IID(nsIProgressEventSink), getter_AddRefs(progressSink)); // only replace our current progress event sink if we were given a new one.. if (progressSink) mProgressEventSink = progressSink; } return NS_OK; } //////////////////////////////////////////////////////////////////////////////// // From nsIRequest //////////////////////////////////////////////////////////////////////////////// NS_IMETHODIMP nsMsgProtocol::IsPending(PRBool *result) { *result = PR_TRUE; return NS_OK; } NS_IMETHODIMP nsMsgProtocol::GetStatus(nsresult *status) { if (m_request) return m_request->GetStatus(status); *status = NS_OK; return *status; } NS_IMETHODIMP nsMsgProtocol::Cancel(nsresult status) { NS_ASSERTION(m_request,"no channel"); if (!m_request) return NS_ERROR_FAILURE; return m_request->Cancel(status); } NS_IMETHODIMP nsMsgProtocol::Suspend() { NS_NOTREACHED("Suspend"); return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsMsgProtocol::Resume() { NS_NOTREACHED("Resume"); return NS_ERROR_NOT_IMPLEMENTED; } nsresult nsMsgProtocol::PostMessage(nsIURI* url, nsIFileSpec *fileSpec) { if (!url || !fileSpec) return NS_ERROR_NULL_POINTER; #define POST_DATA_BUFFER_SIZE 2048 // mscott -- this function should be re-written to use the file url code // so it can be asynch nsFileSpec afileSpec; fileSpec->GetFileSpec(&afileSpec); nsInputFileStream * fileStream = new nsInputFileStream(afileSpec, PR_RDONLY, 00700); if (fileStream && fileStream->is_open()) { PRInt32 amtInBuffer = 0; PRBool lastLineWasComplete = PR_TRUE; PRBool quoteLines = PR_TRUE; // it is always true but I'd like to // generalize this function and then it // might not be char buffer[POST_DATA_BUFFER_SIZE]; if (quoteLines /* || add_crlf_to_line_endings */) { char *line; char * b = buffer; PRInt32 bsize = POST_DATA_BUFFER_SIZE; amtInBuffer = 0; do { lastLineWasComplete = PR_TRUE; PRInt32 L = 0; if (fileStream->eof()) { line = nsnull; break; } if (!fileStream->readline(b, bsize-5)) lastLineWasComplete = PR_FALSE; line = b; L = PL_strlen(line); /* escape periods only if quote_lines_p is set */ if (quoteLines && lastLineWasComplete && line[0] == '.') { /* This line begins with "." so we need to quote it by adding another "." to the beginning of the line. */ PRInt32 i; line[L+1] = 0; for (i = L; i > 0; i--) line[i] = line[i-1]; L++; } if (!lastLineWasComplete || (L > 1 && line[L-2] == nsCRT::CR && line[L-1] == nsCRT::LF)) { /* already ok */ } else if(L > 0 /* && (line[L-1] == nsCRT::LF || line[L-1] == nsCRT::CR) */) { /* only add the crlf if required * we still need to do all the * if comparisons here to know * if the line was complete */ if(/* add_crlf_to_line_endings */ PR_TRUE) { /* Change newline to CRLF. */ line[L++] = nsCRT::CR; line[L++] = nsCRT::LF; line[L] = 0; } } else if (L == 0 && !fileStream->eof() /* && add_crlf_to_line_endings */) { // jt ** empty line; output CRLF line[L++] = nsCRT::CR; line[L++] = nsCRT::LF; line[L] = 0; } bsize -= L; b += L; amtInBuffer += L; // test hack by mscott. If our buffer is almost full, then // send it off & reset ourselves // to make more room. if (bsize < 100) // i chose 100 arbitrarily. { if (*buffer) SendData(url, buffer); buffer[0] = '\0'; b = buffer; // reset buffer bsize = POST_DATA_BUFFER_SIZE; } } while (line /* && bsize > 100 */); } SendData(url, buffer); delete fileStream; } return NS_OK; } ///////////////////////////////////////////////////////////////////// // nsMsgAsyncWriteProtocol subclass and related helper classes ///////////////////////////////////////////////////////////////////// class nsMsgProtocolStreamProvider : public nsIStreamProvider { public: NS_DECL_ISUPPORTS nsMsgProtocolStreamProvider() { NS_INIT_REFCNT(); } virtual ~nsMsgProtocolStreamProvider() {} void Init(nsMsgAsyncWriteProtocol *aProtInstance, nsIInputStream *aInputStream) { mMsgProtocol = aProtInstance; mInStream = aInputStream;} // // nsIRequestObserver implementation ... // NS_IMETHODIMP OnStartRequest(nsIRequest *chan, nsISupports *ctxt) { return NS_OK; } NS_IMETHODIMP OnStopRequest(nsIRequest *chan, nsISupports *ctxt, nsresult status) { return NS_OK; } // // nsIStreamProvider implementation ... // NS_IMETHODIMP OnDataWritable(nsIRequest *aChannel, nsISupports *aContext, nsIOutputStream *aOutStream, PRUint32 aOffset, PRUint32 aCount) { NS_ASSERTION(mInStream, "not initialized"); nsresult rv; PRUint32 avail; // Write whatever is available in the pipe. If the pipe is empty, then // return NS_BASE_STREAM_WOULD_BLOCK; we will resume the write when there // is more data. rv = mInStream->Available(&avail); if (NS_FAILED(rv)) return rv; if (avail == 0) { mMsgProtocol->mSuspendedWrite = PR_TRUE; return NS_BASE_STREAM_WOULD_BLOCK; } PRUint32 bytesWritten; rv = aOutStream->WriteFrom(mInStream, PR_MIN(avail, aCount), &bytesWritten); // if were full at the time, the input stream may be backed up and we need to read any remains from the last ODA call // before we'll get more ODA calls if (mMsgProtocol->mSuspendedRead) mMsgProtocol->UnblockPostReader(); mMsgProtocol->UpdateProgress(bytesWritten); return rv; } protected: nsMsgAsyncWriteProtocol * mMsgProtocol; nsCOMPtr mInStream; }; NS_IMPL_THREADSAFE_ISUPPORTS2(nsMsgProtocolStreamProvider, nsIStreamProvider, nsIRequestObserver) class nsMsgFilePostHelper : public nsIStreamListener { public: NS_DECL_ISUPPORTS NS_DECL_NSIREQUESTOBSERVER NS_DECL_NSISTREAMLISTENER nsMsgFilePostHelper() { NS_INIT_REFCNT(); mSuspendedPostFileRead = PR_FALSE;} nsresult Init(nsIOutputStream * aOutStream, nsMsgAsyncWriteProtocol * aProtInstance, nsIFile *aFileToPost); virtual ~nsMsgFilePostHelper() {} nsCOMPtr mPostFileRequest; PRBool mSuspendedPostFileRead; void CloseSocket() { mProtInstance = nsnull; } protected: nsCOMPtr mOutStream; nsMsgAsyncWriteProtocol * mProtInstance; }; NS_IMPL_THREADSAFE_ADDREF(nsMsgFilePostHelper) NS_IMPL_THREADSAFE_RELEASE(nsMsgFilePostHelper) NS_INTERFACE_MAP_BEGIN(nsMsgFilePostHelper) NS_INTERFACE_MAP_ENTRY(nsIStreamListener) NS_INTERFACE_MAP_ENTRY(nsIRequestObserver) NS_INTERFACE_MAP_END_THREADSAFE nsresult nsMsgFilePostHelper::Init(nsIOutputStream * aOutStream, nsMsgAsyncWriteProtocol * aProtInstance, nsIFile *aFileToPost) { nsresult rv = NS_OK; mOutStream = aOutStream; mProtInstance = aProtInstance; // mscott work out ref counting issue nsCOMPtr fts = do_GetService(kFileTransportServiceCID, &rv); if (NS_FAILED(rv)) return rv; nsCOMPtr transport; rv = fts->CreateTransport(aFileToPost, PR_RDONLY, 0664, getter_AddRefs(transport)); if (transport) { rv = transport->AsyncRead(this, nsnull, 0, PRUint32(-1), 0, getter_AddRefs(mPostFileRequest)); } return rv; } NS_IMETHODIMP nsMsgFilePostHelper::OnStartRequest(nsIRequest * aChannel, nsISupports *ctxt) { return NS_OK; } NS_IMETHODIMP nsMsgFilePostHelper::OnStopRequest(nsIRequest * aChannel, nsISupports *ctxt, nsresult aStatus) { if (!mProtInstance) return NS_OK; if (!mSuspendedPostFileRead) mProtInstance->PostDataFinished(); mSuspendedPostFileRead = PR_FALSE; mProtInstance->mFilePostHelper = nsnull; return NS_OK; } NS_IMETHODIMP nsMsgFilePostHelper::OnDataAvailable(nsIRequest * /* aChannel */, nsISupports *ctxt, nsIInputStream *inStr, PRUint32 sourceOffset, PRUint32 count) { if (!mProtInstance) return NS_OK; if (mSuspendedPostFileRead) { mProtInstance->UpdateSuspendedReadBytes(count, mProtInstance->mInsertPeriodRequired); return NS_OK; } mProtInstance->ProcessIncomingPostData(inStr, count); if (mProtInstance->mSuspendedWrite) { // if we got here then we had suspended the write 'cause we didn't have anymore // data to write (i.e. the pipe went empty). So resume the channel to kick // things off again. mProtInstance->mSuspendedWrite = PR_FALSE; mProtInstance->m_WriteRequest->Resume(); } return NS_OK; } NS_IMPL_ADDREF_INHERITED(nsMsgAsyncWriteProtocol, nsMsgProtocol) NS_IMPL_RELEASE_INHERITED(nsMsgAsyncWriteProtocol, nsMsgProtocol) NS_INTERFACE_MAP_BEGIN(nsMsgAsyncWriteProtocol) NS_INTERFACE_MAP_END_INHERITING(nsMsgProtocol) nsMsgAsyncWriteProtocol::nsMsgAsyncWriteProtocol(nsIURI * aURL) : nsMsgProtocol(aURL) { mSuspendedWrite = PR_FALSE; mSuspendedReadBytes = 0; mSuspendedRead = PR_FALSE; mInsertPeriodRequired = PR_FALSE; mGenerateProgressNotifications = PR_FALSE; mSuspendedReadBytesPostPeriod = 0; mFilePostHelper = nsnull; } nsMsgAsyncWriteProtocol::~nsMsgAsyncWriteProtocol() {} NS_IMETHODIMP nsMsgAsyncWriteProtocol::Cancel(nsresult status) { if (m_request) m_request->Cancel(status); if (m_WriteRequest) m_WriteRequest->Cancel(status); return NS_OK; } nsresult nsMsgAsyncWriteProtocol::PostMessage(nsIURI* url, nsIFileSpec *fileSpec) { // convert the file spec into a nsIFile.... nsFileSpec spec; fileSpec->GetFileSpec(&spec); nsCOMPtr file; NS_FileSpecToIFile(&spec, getter_AddRefs(file)); nsCOMPtr listener; NS_NEWXPCOM(listener, nsMsgFilePostHelper); if (!listener) return NS_ERROR_OUT_OF_MEMORY; // be sure to initialize some state before posting mSuspendedReadBytes = 0; mNumBytesPosted = 0; fileSpec->GetFileSize(&mFilePostSize); mSuspendedRead = PR_FALSE; mInsertPeriodRequired = PR_FALSE; mSuspendedReadBytesPostPeriod = 0; mGenerateProgressNotifications = PR_TRUE; mFilePostHelper = NS_STATIC_CAST(nsMsgFilePostHelper*,NS_STATIC_CAST(nsIStreamListener*, listener)); NS_STATIC_CAST(nsMsgFilePostHelper*,NS_STATIC_CAST(nsIStreamListener*, listener))->Init(m_outputStream, this, file); return NS_OK; } nsresult nsMsgAsyncWriteProtocol::SuspendPostFileRead() { #ifdef DEBUG_mscott printf("suspending post read during send\n"); #endif if (mFilePostHelper && !mFilePostHelper->mSuspendedPostFileRead) { // uhoh we need to pause reading in the file until we get unblocked... mFilePostHelper->mPostFileRequest->Suspend(); mFilePostHelper->mSuspendedPostFileRead = PR_TRUE; } return NS_OK; } nsresult nsMsgAsyncWriteProtocol::ResumePostFileRead() { #ifdef DEBUG_mscott printf("resuming post read during send\n"); #endif if (mFilePostHelper) { if (mFilePostHelper->mSuspendedPostFileRead) { mFilePostHelper->mPostFileRequest->Resume(); mFilePostHelper->mSuspendedPostFileRead = PR_FALSE; } } else // we must be done with the download so send the '.' { PostDataFinished(); } return NS_OK; } nsresult nsMsgAsyncWriteProtocol::UpdateSuspendedReadBytes(PRUint32 aNewBytes, PRBool aAddToPostPeriodByteCount) { // depending on our current state, we'll either add aNewBytes to mSuspendedReadBytes // or mSuspendedReadBytesAfterPeriod. mSuspendedRead = PR_TRUE; if (aAddToPostPeriodByteCount) mSuspendedReadBytesPostPeriod += aNewBytes; else mSuspendedReadBytes += aNewBytes; return NS_OK; } nsresult nsMsgAsyncWriteProtocol::PostDataFinished() { SendData(nsnull, CRLF "." CRLF); mGenerateProgressNotifications = PR_FALSE; mPostDataStream = nsnull; return NS_OK; } nsresult nsMsgAsyncWriteProtocol::ProcessIncomingPostData(nsIInputStream *inStr, PRUint32 count) { if (!m_socketIsOpen) return NS_OK; // kick out if the socket was canceled // We need to quote any '.' that occur at the beginning of a line. // but I don't want to waste time reading out the data into a buffer and searching // let's try to leverage nsIBufferedInputStream and see if we can "peek" into the // current contents for this particular case. nsCOMPtr bufferInputStr = do_QueryInterface(inStr); NS_ASSERTION(bufferInputStr, "i made a wrong assumption about the type of stream we are getting"); NS_ASSERTION(mSuspendedReadBytes == 0, "oops, I missed something"); if (!mPostDataStream) mPostDataStream = inStr; if (bufferInputStr) { PRUint32 amountWritten; while (count > 0) { PRBool found = PR_FALSE; PRUint32 offset = 0; bufferInputStr->Search("\012.", PR_TRUE, &found, &offset); // LF. if (!found || offset > count) { // push this data into the output stream m_outputStream->WriteFrom(inStr, count, &amountWritten); // store any remains which need read out at a later date if (count > amountWritten) // stream will block { UpdateSuspendedReadBytes(count - amountWritten, PR_FALSE); SuspendPostFileRead(); } break; } else { // count points to the LF in a LF followed by a '.' // go ahead and write up to offset.. m_outputStream->WriteFrom(inStr, offset + 1, &amountWritten); count -= amountWritten; if (offset+1 > amountWritten) { UpdateSuspendedReadBytes(offset+1 - amountWritten, PR_FALSE); mInsertPeriodRequired = PR_TRUE; UpdateSuspendedReadBytes(count, mInsertPeriodRequired); SuspendPostFileRead(); break; } // write out the extra '.' m_outputStream->Write(".", 1, &amountWritten); if (amountWritten != 1) { mInsertPeriodRequired = PR_TRUE; // once we do write out the '.', if we are now blocked we need to remember the remaining count that comes // after the '.' so we can perform processing on that once we become unblocked. UpdateSuspendedReadBytes(count, mInsertPeriodRequired); SuspendPostFileRead(); break; } } } // while count > 0 } return NS_OK; } nsresult nsMsgAsyncWriteProtocol::UnblockPostReader() { PRUint32 amountWritten = 0; if (!m_socketIsOpen) return NS_OK; // kick out if the socket was canceled if (mSuspendedRead) { // (1) attempt to write out any remaining read bytes we need in order to unblock the reader if (mSuspendedReadBytes > 0 && mPostDataStream) { PRUint32 avail = 0; mPostDataStream->Available(&avail); m_outputStream->WriteFrom(mPostDataStream, PR_MIN(avail, mSuspendedReadBytes), &amountWritten); // hmm sometimes my mSuspendedReadBytes is getting out of whack...so for now, reset it // if necessary. if (mSuspendedReadBytes > avail) mSuspendedReadBytes = avail; if (mSuspendedReadBytes > amountWritten) mSuspendedReadBytes -= amountWritten; else mSuspendedReadBytes = 0; } // (2) if we are now unblocked, and we need to insert a '.' then do so now... if (mInsertPeriodRequired && mSuspendedReadBytes == 0) { amountWritten = 0; m_outputStream->Write(".", 1, &amountWritten); if (amountWritten == 1) // if we succeeded then clear pending '.' flag mInsertPeriodRequired = PR_FALSE; } // (3) if we inserted a '.' and we still have bytes after the '.' which need processed before the stream is unblocked // then fake an ODA call to handle this now... if (!mInsertPeriodRequired && mSuspendedReadBytesPostPeriod > 0) { // these bytes actually need processed for extra '.''s..... PRUint32 postbytes = mSuspendedReadBytesPostPeriod; mSuspendedReadBytesPostPeriod = 0; ProcessIncomingPostData(mPostDataStream, postbytes); } // (4) determine if we are out of the suspended read state... if (mSuspendedReadBytes == 0 && !mInsertPeriodRequired && mSuspendedReadBytesPostPeriod == 0) { mSuspendedRead = PR_FALSE; ResumePostFileRead(); } } // if we are in the suspended read state return NS_OK; } nsresult nsMsgAsyncWriteProtocol::SetupTransportState() { nsresult rv = NS_OK; if (!m_outputStream && m_transport) { // first create a pipe which we'll use to write the data we want to send // into. rv = NS_NewPipe(getter_AddRefs(mInStream), getter_AddRefs(m_outputStream), 1024, // segmentSize 1024*8, // maxSize PR_TRUE, PR_TRUE); nsCOMPtr provider; NS_NEWXPCOM(provider, nsMsgProtocolStreamProvider); if (!provider) return NS_ERROR_OUT_OF_MEMORY; NS_STATIC_CAST(nsMsgProtocolStreamProvider*, NS_STATIC_CAST(nsIStreamProvider*, provider))->Init(this, mInStream); rv = m_transport->AsyncWrite(provider, nsnull, 0, 0, 0, getter_AddRefs(m_WriteRequest)); if (NS_FAILED(rv)) return rv; } // if m_transport return rv; } nsresult nsMsgAsyncWriteProtocol::CloseSocket() { nsresult rv = NS_OK; nsMsgProtocol::CloseSocket(); // we need to call Cancel so that we remove the socket transport from the mActiveTransportList. see bug #30648 if (m_WriteRequest) rv = m_WriteRequest->Cancel(NS_BINDING_ABORTED); if (mFilePostHelper) { mFilePostHelper->CloseSocket(); mFilePostHelper = nsnull; } m_WriteRequest = 0; return rv; } void nsMsgAsyncWriteProtocol::UpdateProgress(PRUint32 aNewBytes) { if (!mGenerateProgressNotifications) return; mNumBytesPosted += aNewBytes; if (mFilePostSize > 0) { nsCOMPtr mailUrl = do_QueryInterface(m_url); if (!mailUrl) return; nsCOMPtr statusFeedback; mailUrl->GetStatusFeedback(getter_AddRefs(statusFeedback)); if (!statusFeedback) return; nsCOMPtr webProgressListener (do_QueryInterface(statusFeedback)); if (!webProgressListener) return; webProgressListener->OnProgressChange(nsnull, m_WriteRequest, mNumBytesPosted, mFilePostSize, mNumBytesPosted, mFilePostSize); } return; } PRInt32 nsMsgAsyncWriteProtocol::SendData(nsIURI * aURL, const char * dataBuffer, PRBool aSuppressLogging) { PRUint32 len = strlen(dataBuffer); PRUint32 cnt; nsresult rv = m_outputStream->Write(dataBuffer, len, &cnt); if (NS_SUCCEEDED(rv) && len==cnt) { if (mSuspendedWrite) { // if we got here then we had suspended the write 'cause we didn't have anymore // data to write (i.e. the pipe went empty). So resume the channel to kick // things off again. mSuspendedWrite = PR_FALSE; m_WriteRequest->Resume(); } return NS_OK; } else return NS_ERROR_FAILURE; } #define MSGS_URL "chrome://messenger/locale/messenger.properties" PRUnichar *GetStringByID(PRInt32 stringID) { nsresult rv; nsCOMPtr sBundle = nsnull; nsCOMPtr sBundleService = do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv); if (NS_FAILED(rv) || (nsnull == sBundleService)) return nsnull; rv = sBundleService->CreateBundle(MSGS_URL, getter_AddRefs(sBundle)); if (NS_FAILED(rv)) return nsnull; PRUnichar *ptrv = nsnull; rv = sBundle->GetStringFromID(stringID, &ptrv); if (NS_FAILED(rv)) return nsnull; return (ptrv); } PRUnichar *FormatStringWithHostNameByID(PRInt32 stringID, nsIMsgMailNewsUrl *msgUri) { if (! msgUri) return nsnull; nsresult rv; nsCOMPtr sBundle = nsnull; nsCOMPtr sBundleService = do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv); if (NS_FAILED(rv) || (! sBundleService)) return nsnull; rv = sBundleService->CreateBundle(MSGS_URL, getter_AddRefs(sBundle)); if (NS_FAILED(rv)) return nsnull; PRUnichar *ptrv = nsnull; nsXPIDLCString hostName; nsCOMPtr server; rv = msgUri->GetServer(getter_AddRefs(server)); if (NS_SUCCEEDED(rv) && server) rv = server->GetRealHostName(getter_Copies(hostName)); nsAutoString hostStr; hostStr.AssignWithConversion(hostName.get()); const PRUnichar *params[] = { hostStr.get() }; rv = sBundle->FormatStringFromID(stringID, params, 1, &ptrv); if (NS_FAILED(rv)) return nsnull; return (ptrv); }