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: * michael@0: * This Original Code has been modified by IBM Corporation. michael@0: * Modifications made by IBM described herein are michael@0: * Copyright (c) International Business Machines michael@0: * Corporation, 2000 michael@0: * michael@0: * Modifications to Mozilla code or documentation michael@0: * identified per MPL Section 3.3 michael@0: * michael@0: * Date Modified by Description of modification michael@0: * 03/27/2000 IBM Corp. Added PR_CALLBACK for Optlink michael@0: * use in OS2 michael@0: */ michael@0: michael@0: #include "mozilla/net/NeckoChild.h" michael@0: #include "mozilla/net/FTPChannelChild.h" michael@0: using namespace mozilla; michael@0: using namespace mozilla::net; michael@0: michael@0: #include "nsFtpProtocolHandler.h" michael@0: #include "nsFTPChannel.h" michael@0: #include "nsIStandardURL.h" michael@0: #include "prlog.h" michael@0: #include "nsIPrefService.h" michael@0: #include "nsIPrefBranch.h" michael@0: #include "nsIObserverService.h" michael@0: #include "nsEscape.h" michael@0: #include "nsAlgorithm.h" michael@0: #include "nsICacheSession.h" michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: #if defined(PR_LOGGING) michael@0: // michael@0: // Log module for FTP Protocol logging... michael@0: // michael@0: // To enable logging (see prlog.h for full details): michael@0: // michael@0: // set NSPR_LOG_MODULES=nsFtp:5 michael@0: // set NSPR_LOG_FILE=nspr.log michael@0: // michael@0: // this enables PR_LOG_DEBUG level information and places all output in michael@0: // the file nspr.log michael@0: // michael@0: PRLogModuleInfo* gFTPLog = nullptr; michael@0: #endif michael@0: #undef LOG michael@0: #define LOG(args) PR_LOG(gFTPLog, PR_LOG_DEBUG, args) michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: #define IDLE_TIMEOUT_PREF "network.ftp.idleConnectionTimeout" michael@0: #define IDLE_CONNECTION_LIMIT 8 /* TODO pref me */ michael@0: michael@0: #define QOS_DATA_PREF "network.ftp.data.qos" michael@0: #define QOS_CONTROL_PREF "network.ftp.control.qos" michael@0: michael@0: nsFtpProtocolHandler *gFtpHandler = nullptr; michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: nsFtpProtocolHandler::nsFtpProtocolHandler() michael@0: : mIdleTimeout(-1) michael@0: , mSessionId(0) michael@0: , mControlQoSBits(0x00) michael@0: , mDataQoSBits(0x00) michael@0: { michael@0: #if defined(PR_LOGGING) michael@0: if (!gFTPLog) michael@0: gFTPLog = PR_NewLogModule("nsFtp"); michael@0: #endif michael@0: LOG(("FTP:creating handler @%x\n", this)); michael@0: michael@0: gFtpHandler = this; michael@0: } michael@0: michael@0: nsFtpProtocolHandler::~nsFtpProtocolHandler() michael@0: { michael@0: LOG(("FTP:destroying handler @%x\n", this)); michael@0: michael@0: NS_ASSERTION(mRootConnectionList.Length() == 0, "why wasn't Observe called?"); michael@0: michael@0: gFtpHandler = nullptr; michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(nsFtpProtocolHandler, michael@0: nsIProtocolHandler, michael@0: nsIProxiedProtocolHandler, michael@0: nsIObserver, michael@0: nsISupportsWeakReference) michael@0: michael@0: nsresult michael@0: nsFtpProtocolHandler::Init() michael@0: { michael@0: if (IsNeckoChild()) michael@0: NeckoChild::InitNeckoChild(); michael@0: michael@0: if (mIdleTimeout == -1) { michael@0: nsresult rv; michael@0: nsCOMPtr branch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: rv = branch->GetIntPref(IDLE_TIMEOUT_PREF, &mIdleTimeout); michael@0: if (NS_FAILED(rv)) michael@0: mIdleTimeout = 5*60; // 5 minute default michael@0: michael@0: rv = branch->AddObserver(IDLE_TIMEOUT_PREF, this, true); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: int32_t val; michael@0: rv = branch->GetIntPref(QOS_DATA_PREF, &val); michael@0: if (NS_SUCCEEDED(rv)) michael@0: mDataQoSBits = (uint8_t) clamped(val, 0, 0xff); michael@0: michael@0: rv = branch->AddObserver(QOS_DATA_PREF, this, true); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: rv = branch->GetIntPref(QOS_CONTROL_PREF, &val); michael@0: if (NS_SUCCEEDED(rv)) michael@0: mControlQoSBits = (uint8_t) clamped(val, 0, 0xff); michael@0: michael@0: rv = branch->AddObserver(QOS_CONTROL_PREF, this, true); michael@0: if (NS_FAILED(rv)) return rv; michael@0: } michael@0: michael@0: nsCOMPtr observerService = michael@0: mozilla::services::GetObserverService(); michael@0: if (observerService) { michael@0: observerService->AddObserver(this, michael@0: "network:offline-about-to-go-offline", michael@0: true); michael@0: michael@0: observerService->AddObserver(this, michael@0: "net:clear-active-logins", michael@0: true); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsIProtocolHandler methods: michael@0: michael@0: NS_IMETHODIMP michael@0: nsFtpProtocolHandler::GetScheme(nsACString &result) michael@0: { michael@0: result.AssignLiteral("ftp"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsFtpProtocolHandler::GetDefaultPort(int32_t *result) michael@0: { michael@0: *result = 21; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsFtpProtocolHandler::GetProtocolFlags(uint32_t *result) michael@0: { michael@0: *result = URI_STD | ALLOWS_PROXY | ALLOWS_PROXY_HTTP | michael@0: URI_LOADABLE_BY_ANYONE; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsFtpProtocolHandler::NewURI(const nsACString &aSpec, michael@0: const char *aCharset, michael@0: nsIURI *aBaseURI, michael@0: nsIURI **result) michael@0: { michael@0: nsAutoCString spec(aSpec); michael@0: spec.Trim(" \t\n\r"); // Match NS_IsAsciiWhitespace instead of HTML5 michael@0: michael@0: char *fwdPtr = spec.BeginWriting(); michael@0: michael@0: // now unescape it... %xx reduced inline to resulting character michael@0: michael@0: int32_t len = NS_UnescapeURL(fwdPtr); michael@0: michael@0: // NS_UnescapeURL() modified spec's buffer, truncate to ensure michael@0: // spec knows its new length. michael@0: spec.Truncate(len); michael@0: michael@0: // return an error if we find a NUL, CR, or LF in the path michael@0: if (spec.FindCharInSet(CRLF) >= 0 || spec.FindChar('\0') >= 0) michael@0: return NS_ERROR_MALFORMED_URI; michael@0: michael@0: nsresult rv; michael@0: nsCOMPtr url = do_CreateInstance(NS_STANDARDURL_CONTRACTID, &rv); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: rv = url->Init(nsIStandardURL::URLTYPE_AUTHORITY, 21, aSpec, aCharset, aBaseURI); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: return CallQueryInterface(url, result); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsFtpProtocolHandler::NewChannel(nsIURI* url, nsIChannel* *result) michael@0: { michael@0: return NewProxiedChannel(url, nullptr, 0, nullptr, result); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsFtpProtocolHandler::NewProxiedChannel(nsIURI* uri, nsIProxyInfo* proxyInfo, michael@0: uint32_t proxyResolveFlags, michael@0: nsIURI *proxyURI, michael@0: nsIChannel* *result) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(uri); michael@0: nsRefPtr channel; michael@0: if (IsNeckoChild()) michael@0: channel = new FTPChannelChild(uri); michael@0: else michael@0: channel = new nsFtpChannel(uri, proxyInfo); michael@0: michael@0: nsresult rv = channel->Init(); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: channel.forget(result); michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsFtpProtocolHandler::AllowPort(int32_t port, const char *scheme, bool *_retval) michael@0: { michael@0: *_retval = (port == 21 || port == 22); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // connection cache methods michael@0: michael@0: void michael@0: nsFtpProtocolHandler::Timeout(nsITimer *aTimer, void *aClosure) michael@0: { michael@0: LOG(("FTP:timeout reached for %p\n", aClosure)); michael@0: michael@0: bool found = gFtpHandler->mRootConnectionList.RemoveElement(aClosure); michael@0: if (!found) { michael@0: NS_ERROR("timerStruct not found"); michael@0: return; michael@0: } michael@0: michael@0: timerStruct* s = (timerStruct*)aClosure; michael@0: delete s; michael@0: } michael@0: michael@0: nsresult michael@0: nsFtpProtocolHandler::RemoveConnection(nsIURI *aKey, nsFtpControlConnection* *_retval) michael@0: { michael@0: NS_ASSERTION(_retval, "null pointer"); michael@0: NS_ASSERTION(aKey, "null pointer"); michael@0: michael@0: *_retval = nullptr; michael@0: michael@0: nsAutoCString spec; michael@0: aKey->GetPrePath(spec); michael@0: michael@0: LOG(("FTP:removing connection for %s\n", spec.get())); michael@0: michael@0: timerStruct* ts = nullptr; michael@0: uint32_t i; michael@0: bool found = false; michael@0: michael@0: for (i=0;ikey) == 0) { michael@0: found = true; michael@0: mRootConnectionList.RemoveElementAt(i); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: if (!found) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // swap connection ownership michael@0: *_retval = ts->conn; michael@0: ts->conn = nullptr; michael@0: delete ts; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsFtpProtocolHandler::InsertConnection(nsIURI *aKey, nsFtpControlConnection *aConn) michael@0: { michael@0: NS_ASSERTION(aConn, "null pointer"); michael@0: NS_ASSERTION(aKey, "null pointer"); michael@0: michael@0: if (aConn->mSessionId != mSessionId) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsAutoCString spec; michael@0: aKey->GetPrePath(spec); michael@0: michael@0: LOG(("FTP:inserting connection for %s\n", spec.get())); michael@0: michael@0: nsresult rv; michael@0: nsCOMPtr timer = do_CreateInstance("@mozilla.org/timer;1", &rv); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: timerStruct* ts = new timerStruct(); michael@0: if (!ts) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: rv = timer->InitWithFuncCallback(nsFtpProtocolHandler::Timeout, michael@0: ts, michael@0: mIdleTimeout*1000, michael@0: nsITimer::TYPE_REPEATING_SLACK); michael@0: if (NS_FAILED(rv)) { michael@0: delete ts; michael@0: return rv; michael@0: } michael@0: michael@0: ts->key = ToNewCString(spec); michael@0: if (!ts->key) { michael@0: delete ts; michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: NS_ADDREF(aConn); michael@0: ts->conn = aConn; michael@0: ts->timer = timer; michael@0: michael@0: // michael@0: // limit number of idle connections. if limit is reached, then prune michael@0: // eldest connection with matching key. if none matching, then prune michael@0: // eldest connection. michael@0: // michael@0: if (mRootConnectionList.Length() == IDLE_CONNECTION_LIMIT) { michael@0: uint32_t i; michael@0: for (i=0;ikey, ts->key) == 0) { michael@0: mRootConnectionList.RemoveElementAt(i); michael@0: delete candidate; michael@0: break; michael@0: } michael@0: } michael@0: if (mRootConnectionList.Length() == IDLE_CONNECTION_LIMIT) { michael@0: timerStruct *eldest = mRootConnectionList[0]; michael@0: mRootConnectionList.RemoveElementAt(0); michael@0: delete eldest; michael@0: } michael@0: } michael@0: michael@0: mRootConnectionList.AppendElement(ts); michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsIObserver michael@0: michael@0: NS_IMETHODIMP michael@0: nsFtpProtocolHandler::Observe(nsISupports *aSubject, michael@0: const char *aTopic, michael@0: const char16_t *aData) michael@0: { michael@0: LOG(("FTP:observing [%s]\n", aTopic)); michael@0: michael@0: if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) { michael@0: nsCOMPtr branch = do_QueryInterface(aSubject); michael@0: if (!branch) { michael@0: NS_ERROR("no prefbranch"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: int32_t val; michael@0: nsresult rv = branch->GetIntPref(IDLE_TIMEOUT_PREF, &val); michael@0: if (NS_SUCCEEDED(rv)) michael@0: mIdleTimeout = val; michael@0: michael@0: rv = branch->GetIntPref(QOS_DATA_PREF, &val); michael@0: if (NS_SUCCEEDED(rv)) michael@0: mDataQoSBits = (uint8_t) clamped(val, 0, 0xff); michael@0: michael@0: rv = branch->GetIntPref(QOS_CONTROL_PREF, &val); michael@0: if (NS_SUCCEEDED(rv)) michael@0: mControlQoSBits = (uint8_t) clamped(val, 0, 0xff); michael@0: } else if (!strcmp(aTopic, "network:offline-about-to-go-offline")) { michael@0: ClearAllConnections(); michael@0: } else if (!strcmp(aTopic, "net:clear-active-logins")) { michael@0: ClearAllConnections(); michael@0: mSessionId++; michael@0: } else { michael@0: NS_NOTREACHED("unexpected topic"); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsFtpProtocolHandler::ClearAllConnections() michael@0: { michael@0: uint32_t i; michael@0: for (i=0;i