1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 : * ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is mozilla.org code.
16 : *
17 : * The Initial Developer of the Original Code is
18 : * Netscape Communications Corporation.
19 : * Portions created by the Initial Developer are Copyright (C) 1998
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : *
24 : * Alternatively, the contents of this file may be used under the terms of
25 : * either the GNU General Public License Version 2 or later (the "GPL"), or
26 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27 : * in which case the provisions of the GPL or the LGPL are applicable instead
28 : * of those above. If you wish to allow use of your version of this file only
29 : * under the terms of either the GPL or the LGPL, and not to allow others to
30 : * use your version of this file under the terms of the MPL, indicate your
31 : * decision by deleting the provisions above and replace them with the notice
32 : * and other provisions required by the GPL or the LGPL. If you do not delete
33 : * the provisions above, a recipient may use your version of this file under
34 : * the terms of any one of the MPL, the GPL or the LGPL.
35 : *
36 : * ***** END LICENSE BLOCK *****
37 : *
38 : *
39 : * This Original Code has been modified by IBM Corporation.
40 : * Modifications made by IBM described herein are
41 : * Copyright (c) International Business Machines
42 : * Corporation, 2000
43 : *
44 : * Modifications to Mozilla code or documentation
45 : * identified per MPL Section 3.3
46 : *
47 : * Date Modified by Description of modification
48 : * 03/27/2000 IBM Corp. Added PR_CALLBACK for Optlink
49 : * use in OS2
50 : */
51 :
52 : #include "mozilla/net/NeckoChild.h"
53 : #include "mozilla/net/FTPChannelChild.h"
54 : using namespace mozilla;
55 : using namespace mozilla::net;
56 :
57 : #include "nsFtpProtocolHandler.h"
58 : #include "nsFTPChannel.h"
59 : #include "nsIURL.h"
60 : #include "nsIStandardURL.h"
61 : #include "nsCRT.h"
62 : #include "nsIComponentManager.h"
63 : #include "nsIInterfaceRequestor.h"
64 : #include "nsIInterfaceRequestorUtils.h"
65 : #include "nsIProgressEventSink.h"
66 : #include "prlog.h"
67 : #include "nsNetUtil.h"
68 : #include "nsIPrefService.h"
69 : #include "nsIPrefBranch.h"
70 : #include "nsIObserverService.h"
71 : #include "nsEscape.h"
72 : #include "nsAlgorithm.h"
73 :
74 : //-----------------------------------------------------------------------------
75 :
76 : #if defined(PR_LOGGING)
77 : //
78 : // Log module for FTP Protocol logging...
79 : //
80 : // To enable logging (see prlog.h for full details):
81 : //
82 : // set NSPR_LOG_MODULES=nsFtp:5
83 : // set NSPR_LOG_FILE=nspr.log
84 : //
85 : // this enables PR_LOG_DEBUG level information and places all output in
86 : // the file nspr.log
87 : //
88 : PRLogModuleInfo* gFTPLog = nsnull;
89 : #endif
90 : #undef LOG
91 : #define LOG(args) PR_LOG(gFTPLog, PR_LOG_DEBUG, args)
92 :
93 : //-----------------------------------------------------------------------------
94 :
95 : #define IDLE_TIMEOUT_PREF "network.ftp.idleConnectionTimeout"
96 : #define IDLE_CONNECTION_LIMIT 8 /* TODO pref me */
97 :
98 : #define QOS_DATA_PREF "network.ftp.data.qos"
99 : #define QOS_CONTROL_PREF "network.ftp.control.qos"
100 :
101 : nsFtpProtocolHandler *gFtpHandler = nsnull;
102 :
103 : //-----------------------------------------------------------------------------
104 :
105 18 : nsFtpProtocolHandler::nsFtpProtocolHandler()
106 : : mIdleTimeout(-1)
107 : , mSessionId(0)
108 : , mControlQoSBits(0x00)
109 18 : , mDataQoSBits(0x00)
110 : {
111 : #if defined(PR_LOGGING)
112 18 : if (!gFTPLog)
113 18 : gFTPLog = PR_NewLogModule("nsFtp");
114 : #endif
115 18 : LOG(("FTP:creating handler @%x\n", this));
116 :
117 18 : gFtpHandler = this;
118 18 : }
119 :
120 54 : nsFtpProtocolHandler::~nsFtpProtocolHandler()
121 : {
122 18 : LOG(("FTP:destroying handler @%x\n", this));
123 :
124 18 : NS_ASSERTION(mRootConnectionList.Length() == 0, "why wasn't Observe called?");
125 :
126 18 : gFtpHandler = nsnull;
127 72 : }
128 :
129 3428 : NS_IMPL_THREADSAFE_ISUPPORTS4(nsFtpProtocolHandler,
130 : nsIProtocolHandler,
131 : nsIProxiedProtocolHandler,
132 : nsIObserver,
133 : nsISupportsWeakReference)
134 :
135 : nsresult
136 18 : nsFtpProtocolHandler::Init()
137 : {
138 18 : if (IsNeckoChild())
139 0 : NeckoChild::InitNeckoChild();
140 :
141 18 : if (mIdleTimeout == -1) {
142 : nsresult rv;
143 36 : nsCOMPtr<nsIPrefBranch> branch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
144 18 : if (NS_FAILED(rv)) return rv;
145 :
146 18 : rv = branch->GetIntPref(IDLE_TIMEOUT_PREF, &mIdleTimeout);
147 18 : if (NS_FAILED(rv))
148 0 : mIdleTimeout = 5*60; // 5 minute default
149 :
150 18 : rv = branch->AddObserver(IDLE_TIMEOUT_PREF, this, true);
151 18 : if (NS_FAILED(rv)) return rv;
152 :
153 : PRInt32 val;
154 18 : rv = branch->GetIntPref(QOS_DATA_PREF, &val);
155 18 : if (NS_SUCCEEDED(rv))
156 18 : mDataQoSBits = (PRUint8) clamped(val, 0, 0xff);
157 :
158 18 : rv = branch->AddObserver(QOS_DATA_PREF, this, true);
159 18 : if (NS_FAILED(rv)) return rv;
160 :
161 18 : rv = branch->GetIntPref(QOS_CONTROL_PREF, &val);
162 18 : if (NS_SUCCEEDED(rv))
163 18 : mControlQoSBits = (PRUint8) clamped(val, 0, 0xff);
164 :
165 18 : rv = branch->AddObserver(QOS_CONTROL_PREF, this, true);
166 18 : if (NS_FAILED(rv)) return rv;
167 : }
168 :
169 : nsCOMPtr<nsIObserverService> observerService =
170 36 : mozilla::services::GetObserverService();
171 18 : if (observerService) {
172 18 : observerService->AddObserver(this,
173 : "network:offline-about-to-go-offline",
174 18 : true);
175 :
176 18 : observerService->AddObserver(this,
177 : "net:clear-active-logins",
178 18 : true);
179 : }
180 :
181 18 : return NS_OK;
182 : }
183 :
184 :
185 : //-----------------------------------------------------------------------------
186 : // nsIProtocolHandler methods:
187 :
188 : NS_IMETHODIMP
189 0 : nsFtpProtocolHandler::GetScheme(nsACString &result)
190 : {
191 0 : result.AssignLiteral("ftp");
192 0 : return NS_OK;
193 : }
194 :
195 : NS_IMETHODIMP
196 19 : nsFtpProtocolHandler::GetDefaultPort(PRInt32 *result)
197 : {
198 19 : *result = 21;
199 19 : return NS_OK;
200 : }
201 :
202 : NS_IMETHODIMP
203 38 : nsFtpProtocolHandler::GetProtocolFlags(PRUint32 *result)
204 : {
205 : *result = URI_STD | ALLOWS_PROXY | ALLOWS_PROXY_HTTP |
206 38 : URI_LOADABLE_BY_ANYONE;
207 38 : return NS_OK;
208 : }
209 :
210 : NS_IMETHODIMP
211 285 : nsFtpProtocolHandler::NewURI(const nsACString &aSpec,
212 : const char *aCharset,
213 : nsIURI *aBaseURI,
214 : nsIURI **result)
215 : {
216 570 : nsCAutoString spec(aSpec);
217 285 : spec.Trim(" \t\n\r"); // Match NS_IsAsciiWhitespace instead of HTML5
218 :
219 285 : char *fwdPtr = spec.BeginWriting();
220 :
221 : // now unescape it... %xx reduced inline to resulting character
222 :
223 285 : PRInt32 len = NS_UnescapeURL(fwdPtr);
224 :
225 : // NS_UnescapeURL() modified spec's buffer, truncate to ensure
226 : // spec knows its new length.
227 285 : spec.Truncate(len);
228 :
229 : // return an error if we find a NUL, CR, or LF in the path
230 285 : if (spec.FindCharInSet(CRLF) >= 0 || spec.FindChar('\0') >= 0)
231 0 : return NS_ERROR_MALFORMED_URI;
232 :
233 : nsresult rv;
234 570 : nsCOMPtr<nsIStandardURL> url = do_CreateInstance(NS_STANDARDURL_CONTRACTID, &rv);
235 285 : if (NS_FAILED(rv)) return rv;
236 :
237 285 : rv = url->Init(nsIStandardURL::URLTYPE_AUTHORITY, 21, aSpec, aCharset, aBaseURI);
238 285 : if (NS_FAILED(rv)) return rv;
239 :
240 285 : return CallQueryInterface(url, result);
241 : }
242 :
243 : NS_IMETHODIMP
244 19 : nsFtpProtocolHandler::NewChannel(nsIURI* url, nsIChannel* *result)
245 : {
246 19 : return NewProxiedChannel(url, nsnull, result);
247 : }
248 :
249 : NS_IMETHODIMP
250 19 : nsFtpProtocolHandler::NewProxiedChannel(nsIURI* uri, nsIProxyInfo* proxyInfo,
251 : nsIChannel* *result)
252 : {
253 19 : NS_ENSURE_ARG_POINTER(uri);
254 38 : nsRefPtr<nsBaseChannel> channel;
255 19 : if (IsNeckoChild())
256 0 : channel = new FTPChannelChild(uri);
257 : else
258 19 : channel = new nsFtpChannel(uri, proxyInfo);
259 :
260 19 : nsresult rv = channel->Init();
261 19 : if (NS_FAILED(rv)) {
262 0 : return rv;
263 : }
264 :
265 19 : channel.forget(result);
266 19 : return rv;
267 : }
268 :
269 : NS_IMETHODIMP
270 0 : nsFtpProtocolHandler::AllowPort(PRInt32 port, const char *scheme, bool *_retval)
271 : {
272 0 : *_retval = (port == 21 || port == 22);
273 0 : return NS_OK;
274 : }
275 :
276 : // connection cache methods
277 :
278 : void
279 0 : nsFtpProtocolHandler::Timeout(nsITimer *aTimer, void *aClosure)
280 : {
281 0 : LOG(("FTP:timeout reached for %p\n", aClosure));
282 :
283 0 : bool found = gFtpHandler->mRootConnectionList.RemoveElement(aClosure);
284 0 : if (!found) {
285 0 : NS_ERROR("timerStruct not found");
286 0 : return;
287 : }
288 :
289 0 : timerStruct* s = (timerStruct*)aClosure;
290 0 : delete s;
291 : }
292 :
293 : nsresult
294 0 : nsFtpProtocolHandler::RemoveConnection(nsIURI *aKey, nsFtpControlConnection* *_retval)
295 : {
296 0 : NS_ASSERTION(_retval, "null pointer");
297 0 : NS_ASSERTION(aKey, "null pointer");
298 :
299 0 : *_retval = nsnull;
300 :
301 0 : nsCAutoString spec;
302 0 : aKey->GetPrePath(spec);
303 :
304 0 : LOG(("FTP:removing connection for %s\n", spec.get()));
305 :
306 0 : timerStruct* ts = nsnull;
307 : PRUint32 i;
308 0 : bool found = false;
309 :
310 0 : for (i=0;i<mRootConnectionList.Length();++i) {
311 0 : ts = mRootConnectionList[i];
312 0 : if (strcmp(spec.get(), ts->key) == 0) {
313 0 : found = true;
314 0 : mRootConnectionList.RemoveElementAt(i);
315 0 : break;
316 : }
317 : }
318 :
319 0 : if (!found)
320 0 : return NS_ERROR_FAILURE;
321 :
322 : // swap connection ownership
323 0 : *_retval = ts->conn;
324 0 : ts->conn = nsnull;
325 0 : delete ts;
326 :
327 0 : return NS_OK;
328 : }
329 :
330 : nsresult
331 0 : nsFtpProtocolHandler::InsertConnection(nsIURI *aKey, nsFtpControlConnection *aConn)
332 : {
333 0 : NS_ASSERTION(aConn, "null pointer");
334 0 : NS_ASSERTION(aKey, "null pointer");
335 :
336 0 : if (aConn->mSessionId != mSessionId)
337 0 : return NS_ERROR_FAILURE;
338 :
339 0 : nsCAutoString spec;
340 0 : aKey->GetPrePath(spec);
341 :
342 0 : LOG(("FTP:inserting connection for %s\n", spec.get()));
343 :
344 : nsresult rv;
345 0 : nsCOMPtr<nsITimer> timer = do_CreateInstance("@mozilla.org/timer;1", &rv);
346 0 : if (NS_FAILED(rv)) return rv;
347 :
348 0 : timerStruct* ts = new timerStruct();
349 0 : if (!ts)
350 0 : return NS_ERROR_OUT_OF_MEMORY;
351 :
352 0 : rv = timer->InitWithFuncCallback(nsFtpProtocolHandler::Timeout,
353 : ts,
354 : mIdleTimeout*1000,
355 0 : nsITimer::TYPE_REPEATING_SLACK);
356 0 : if (NS_FAILED(rv)) {
357 0 : delete ts;
358 0 : return rv;
359 : }
360 :
361 0 : ts->key = ToNewCString(spec);
362 0 : if (!ts->key) {
363 0 : delete ts;
364 0 : return NS_ERROR_OUT_OF_MEMORY;
365 : }
366 :
367 0 : NS_ADDREF(aConn);
368 0 : ts->conn = aConn;
369 0 : ts->timer = timer;
370 :
371 : //
372 : // limit number of idle connections. if limit is reached, then prune
373 : // eldest connection with matching key. if none matching, then prune
374 : // eldest connection.
375 : //
376 0 : if (mRootConnectionList.Length() == IDLE_CONNECTION_LIMIT) {
377 : PRUint32 i;
378 0 : for (i=0;i<mRootConnectionList.Length();++i) {
379 0 : timerStruct *candidate = mRootConnectionList[i];
380 0 : if (strcmp(candidate->key, ts->key) == 0) {
381 0 : mRootConnectionList.RemoveElementAt(i);
382 0 : delete candidate;
383 0 : break;
384 : }
385 : }
386 0 : if (mRootConnectionList.Length() == IDLE_CONNECTION_LIMIT) {
387 0 : timerStruct *eldest = mRootConnectionList[0];
388 0 : mRootConnectionList.RemoveElementAt(0);
389 0 : delete eldest;
390 : }
391 : }
392 :
393 0 : mRootConnectionList.AppendElement(ts);
394 0 : return NS_OK;
395 : }
396 :
397 : //-----------------------------------------------------------------------------
398 : // nsIObserver
399 :
400 : NS_IMETHODIMP
401 18 : nsFtpProtocolHandler::Observe(nsISupports *aSubject,
402 : const char *aTopic,
403 : const PRUnichar *aData)
404 : {
405 18 : LOG(("FTP:observing [%s]\n", aTopic));
406 :
407 18 : if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
408 0 : nsCOMPtr<nsIPrefBranch> branch = do_QueryInterface(aSubject);
409 0 : if (!branch) {
410 0 : NS_ERROR("no prefbranch");
411 0 : return NS_ERROR_UNEXPECTED;
412 : }
413 : PRInt32 val;
414 0 : nsresult rv = branch->GetIntPref(IDLE_TIMEOUT_PREF, &val);
415 0 : if (NS_SUCCEEDED(rv))
416 0 : mIdleTimeout = val;
417 :
418 0 : rv = branch->GetIntPref(QOS_DATA_PREF, &val);
419 0 : if (NS_SUCCEEDED(rv))
420 0 : mDataQoSBits = (PRUint8) clamped(val, 0, 0xff);
421 :
422 0 : rv = branch->GetIntPref(QOS_CONTROL_PREF, &val);
423 0 : if (NS_SUCCEEDED(rv))
424 0 : mControlQoSBits = (PRUint8) clamped(val, 0, 0xff);
425 18 : } else if (!strcmp(aTopic, "network:offline-about-to-go-offline")) {
426 18 : ClearAllConnections();
427 0 : } else if (!strcmp(aTopic, "net:clear-active-logins")) {
428 0 : ClearAllConnections();
429 0 : mSessionId++;
430 : } else {
431 0 : NS_NOTREACHED("unexpected topic");
432 : }
433 :
434 18 : return NS_OK;
435 : }
436 :
437 : void
438 18 : nsFtpProtocolHandler::ClearAllConnections()
439 : {
440 : PRUint32 i;
441 18 : for (i=0;i<mRootConnectionList.Length();++i)
442 0 : delete mRootConnectionList[i];
443 18 : mRootConnectionList.Clear();
444 18 : }
|