michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsIOService.h" michael@0: #include "nsFtpControlConnection.h" michael@0: #include "nsFtpProtocolHandler.h" michael@0: #include "prlog.h" michael@0: #include "nsIInputStream.h" michael@0: #include "nsISocketTransportService.h" michael@0: #include "nsISocketTransport.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsIOutputStream.h" michael@0: #include "nsNetCID.h" michael@0: #include michael@0: michael@0: #if defined(PR_LOGGING) michael@0: extern PRLogModuleInfo* gFTPLog; michael@0: #endif michael@0: #define LOG(args) PR_LOG(gFTPLog, PR_LOG_DEBUG, args) michael@0: #define LOG_ALWAYS(args) PR_LOG(gFTPLog, PR_LOG_ALWAYS, args) michael@0: michael@0: // michael@0: // nsFtpControlConnection implementation ... michael@0: // michael@0: michael@0: NS_IMPL_ISUPPORTS(nsFtpControlConnection, nsIInputStreamCallback) michael@0: michael@0: NS_IMETHODIMP michael@0: nsFtpControlConnection::OnInputStreamReady(nsIAsyncInputStream *stream) michael@0: { michael@0: char data[4096]; michael@0: michael@0: // Consume data whether we have a listener or not. michael@0: uint64_t avail64; michael@0: uint32_t avail = 0; michael@0: nsresult rv = stream->Available(&avail64); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: avail = (uint32_t)std::min(avail64, (uint64_t)sizeof(data)); michael@0: michael@0: uint32_t n; michael@0: rv = stream->Read(data, avail, &n); michael@0: if (NS_SUCCEEDED(rv)) michael@0: avail = n; michael@0: } michael@0: michael@0: // It's important that we null out mListener before calling one of its michael@0: // methods as it may call WaitData, which would queue up another read. michael@0: michael@0: nsRefPtr listener; michael@0: listener.swap(mListener); michael@0: michael@0: if (!listener) michael@0: return NS_OK; michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: listener->OnControlError(rv); michael@0: } else { michael@0: listener->OnControlDataAvailable(data, avail); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsFtpControlConnection::nsFtpControlConnection(const nsCSubstring& host, michael@0: uint32_t port) michael@0: : mServerType(0), mSessionId(gFtpHandler->GetSessionId()) michael@0: , mUseUTF8(false), mHost(host), mPort(port) michael@0: { michael@0: LOG_ALWAYS(("FTP:CC created @%p", this)); michael@0: } michael@0: michael@0: nsFtpControlConnection::~nsFtpControlConnection() michael@0: { michael@0: LOG_ALWAYS(("FTP:CC destroyed @%p", this)); michael@0: } michael@0: michael@0: bool michael@0: nsFtpControlConnection::IsAlive() michael@0: { michael@0: if (!mSocket) michael@0: return false; michael@0: michael@0: bool isAlive = false; michael@0: mSocket->IsAlive(&isAlive); michael@0: return isAlive; michael@0: } michael@0: nsresult michael@0: nsFtpControlConnection::Connect(nsIProxyInfo* proxyInfo, michael@0: nsITransportEventSink* eventSink) michael@0: { michael@0: if (mSocket) michael@0: return NS_OK; michael@0: michael@0: // build our own michael@0: nsresult rv; michael@0: nsCOMPtr sts = michael@0: do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: rv = sts->CreateTransport(nullptr, 0, mHost, mPort, proxyInfo, michael@0: getter_AddRefs(mSocket)); // the command transport michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: mSocket->SetQoSBits(gFtpHandler->GetControlQoSBits()); michael@0: michael@0: // proxy transport events back to current thread michael@0: if (eventSink) michael@0: mSocket->SetEventSink(eventSink, NS_GetCurrentThread()); michael@0: michael@0: // open buffered, blocking output stream to socket. so long as commands michael@0: // do not exceed 1024 bytes in length, the writing thread (the main thread) michael@0: // will not block. this should be OK. michael@0: rv = mSocket->OpenOutputStream(nsITransport::OPEN_BLOCKING, 1024, 1, michael@0: getter_AddRefs(mSocketOutput)); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: // open buffered, non-blocking/asynchronous input stream to socket. michael@0: nsCOMPtr inStream; michael@0: rv = mSocket->OpenInputStream(0, michael@0: nsIOService::gDefaultSegmentSize, michael@0: nsIOService::gDefaultSegmentCount, michael@0: getter_AddRefs(inStream)); michael@0: if (NS_SUCCEEDED(rv)) michael@0: mSocketInput = do_QueryInterface(inStream); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsFtpControlConnection::WaitData(nsFtpControlConnectionListener *listener) michael@0: { michael@0: LOG(("FTP:(%p) wait data [listener=%p]\n", this, listener)); michael@0: michael@0: // If listener is null, then simply disconnect the listener. Otherwise, michael@0: // ensure that we are listening. michael@0: if (!listener) { michael@0: mListener = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_ENSURE_STATE(mSocketInput); michael@0: michael@0: mListener = listener; michael@0: return mSocketInput->AsyncWait(this, 0, 0, NS_GetCurrentThread()); michael@0: } michael@0: michael@0: nsresult michael@0: nsFtpControlConnection::Disconnect(nsresult status) michael@0: { michael@0: if (!mSocket) michael@0: return NS_OK; // already disconnected michael@0: michael@0: LOG_ALWAYS(("FTP:(%p) CC disconnecting (%x)", this, status)); michael@0: michael@0: if (NS_FAILED(status)) { michael@0: // break cyclic reference! michael@0: mSocket->Close(status); michael@0: mSocket = 0; michael@0: mSocketInput->AsyncWait(nullptr, 0, 0, nullptr); // clear any observer michael@0: mSocketInput = nullptr; michael@0: mSocketOutput = nullptr; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsFtpControlConnection::Write(const nsCSubstring& command) michael@0: { michael@0: NS_ENSURE_STATE(mSocketOutput); michael@0: michael@0: uint32_t len = command.Length(); michael@0: uint32_t cnt; michael@0: nsresult rv = mSocketOutput->Write(command.Data(), len, &cnt); michael@0: michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: if (len != cnt) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: return NS_OK; michael@0: }