1 : /* vim:set ts=2 sw=2 et cindent: */
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 the Mozilla gnome-vfs extension.
16 : *
17 : * The Initial Developer of the Original Code is IBM Corporation.
18 : * Portions created by IBM Corporation are Copyright (C) 2004
19 : * IBM Corporation. All Rights Reserved.
20 : *
21 : * Contributor(s):
22 : * Darin Fisher <darin@meer.net>
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 : // GnomeVFS v2.2.2 is missing G_BEGIN_DECLS in gnome-vfs-module-callback.h
39 : extern "C" {
40 : #include <libgnomevfs/gnome-vfs.h>
41 : #include <libgnomevfs/gnome-vfs-standard-callbacks.h>
42 : #include <libgnomevfs/gnome-vfs-mime-utils.h>
43 : }
44 :
45 : #include "nsServiceManagerUtils.h"
46 : #include "nsComponentManagerUtils.h"
47 : #include "mozilla/ModuleUtils.h"
48 : #include "nsIInterfaceRequestorUtils.h"
49 : #include "nsIPrefService.h"
50 : #include "nsIPrefBranch.h"
51 : #include "nsIObserver.h"
52 : #include "nsThreadUtils.h"
53 : #include "nsProxyRelease.h"
54 : #include "nsIAuthPrompt.h"
55 : #include "nsIStringBundle.h"
56 : #include "nsIStandardURL.h"
57 : #include "nsIURL.h"
58 : #include "nsMimeTypes.h"
59 : #include "nsNetUtil.h"
60 : #include "nsINetUtil.h"
61 : #include "nsAutoPtr.h"
62 : #include "nsError.h"
63 : #include "prlog.h"
64 : #include "prtime.h"
65 : #include "prprf.h"
66 : #include "plstr.h"
67 :
68 : #define MOZ_GNOMEVFS_SCHEME "moz-gnomevfs"
69 : #define MOZ_GNOMEVFS_SUPPORTED_PROTOCOLS "network.gnomevfs.supported-protocols"
70 :
71 : //-----------------------------------------------------------------------------
72 :
73 : // NSPR_LOG_MODULES=gnomevfs:5
74 : #ifdef PR_LOGGING
75 : static PRLogModuleInfo *sGnomeVFSLog;
76 : #define LOG(args) PR_LOG(sGnomeVFSLog, PR_LOG_DEBUG, args)
77 : #else
78 : #define LOG(args)
79 : #endif
80 :
81 : //-----------------------------------------------------------------------------
82 :
83 : static nsresult
84 0 : MapGnomeVFSResult(GnomeVFSResult result)
85 : {
86 0 : switch (result)
87 : {
88 0 : case GNOME_VFS_OK: return NS_OK;
89 0 : case GNOME_VFS_ERROR_NOT_FOUND: return NS_ERROR_FILE_NOT_FOUND;
90 0 : case GNOME_VFS_ERROR_INTERNAL: return NS_ERROR_UNEXPECTED;
91 0 : case GNOME_VFS_ERROR_BAD_PARAMETERS: return NS_ERROR_INVALID_ARG;
92 0 : case GNOME_VFS_ERROR_NOT_SUPPORTED: return NS_ERROR_NOT_AVAILABLE;
93 0 : case GNOME_VFS_ERROR_CORRUPTED_DATA: return NS_ERROR_FILE_CORRUPTED;
94 0 : case GNOME_VFS_ERROR_TOO_BIG: return NS_ERROR_FILE_TOO_BIG;
95 0 : case GNOME_VFS_ERROR_NO_SPACE: return NS_ERROR_FILE_NO_DEVICE_SPACE;
96 : case GNOME_VFS_ERROR_READ_ONLY:
97 0 : case GNOME_VFS_ERROR_READ_ONLY_FILE_SYSTEM: return NS_ERROR_FILE_READ_ONLY;
98 : case GNOME_VFS_ERROR_INVALID_URI:
99 0 : case GNOME_VFS_ERROR_INVALID_HOST_NAME: return NS_ERROR_MALFORMED_URI;
100 : case GNOME_VFS_ERROR_ACCESS_DENIED:
101 : case GNOME_VFS_ERROR_NOT_PERMITTED:
102 0 : case GNOME_VFS_ERROR_LOGIN_FAILED: return NS_ERROR_FILE_ACCESS_DENIED;
103 0 : case GNOME_VFS_ERROR_EOF: return NS_BASE_STREAM_CLOSED;
104 0 : case GNOME_VFS_ERROR_NOT_A_DIRECTORY: return NS_ERROR_FILE_NOT_DIRECTORY;
105 0 : case GNOME_VFS_ERROR_IN_PROGRESS: return NS_ERROR_IN_PROGRESS;
106 0 : case GNOME_VFS_ERROR_FILE_EXISTS: return NS_ERROR_FILE_ALREADY_EXISTS;
107 0 : case GNOME_VFS_ERROR_IS_DIRECTORY: return NS_ERROR_FILE_IS_DIRECTORY;
108 0 : case GNOME_VFS_ERROR_NO_MEMORY: return NS_ERROR_OUT_OF_MEMORY;
109 : case GNOME_VFS_ERROR_HOST_NOT_FOUND:
110 0 : case GNOME_VFS_ERROR_HOST_HAS_NO_ADDRESS: return NS_ERROR_UNKNOWN_HOST;
111 : case GNOME_VFS_ERROR_CANCELLED:
112 0 : case GNOME_VFS_ERROR_INTERRUPTED: return NS_ERROR_ABORT;
113 0 : case GNOME_VFS_ERROR_DIRECTORY_NOT_EMPTY: return NS_ERROR_FILE_DIR_NOT_EMPTY;
114 0 : case GNOME_VFS_ERROR_NAME_TOO_LONG: return NS_ERROR_FILE_NAME_TOO_LONG;
115 0 : case GNOME_VFS_ERROR_SERVICE_NOT_AVAILABLE: return NS_ERROR_UNKNOWN_PROTOCOL;
116 :
117 : /* No special mapping for these error codes...
118 :
119 : case GNOME_VFS_ERROR_GENERIC:
120 : case GNOME_VFS_ERROR_IO:
121 : case GNOME_VFS_ERROR_WRONG_FORMAT:
122 : case GNOME_VFS_ERROR_BAD_FILE:
123 : case GNOME_VFS_ERROR_NOT_OPEN:
124 : case GNOME_VFS_ERROR_INVALID_OPEN_MODE:
125 : case GNOME_VFS_ERROR_TOO_MANY_OPEN_FILES:
126 : case GNOME_VFS_ERROR_LOOP:
127 : case GNOME_VFS_ERROR_DIRECTORY_BUSY:
128 : case GNOME_VFS_ERROR_TOO_MANY_LINKS:
129 : case GNOME_VFS_ERROR_NOT_SAME_FILE_SYSTEM:
130 : case GNOME_VFS_ERROR_SERVICE_OBSOLETE:
131 : case GNOME_VFS_ERROR_PROTOCOL_ERROR:
132 : case GNOME_VFS_ERROR_NO_MASTER_BROWSER:
133 :
134 : */
135 :
136 : // Make GCC happy
137 : default:
138 0 : return NS_ERROR_FAILURE;
139 : }
140 :
141 : return NS_ERROR_FAILURE;
142 : }
143 :
144 : //-----------------------------------------------------------------------------
145 :
146 : static void
147 0 : ProxiedAuthCallback(gconstpointer in,
148 : gsize in_size,
149 : gpointer out,
150 : gsize out_size,
151 : gpointer callback_data)
152 : {
153 : GnomeVFSModuleCallbackAuthenticationIn *authIn =
154 0 : (GnomeVFSModuleCallbackAuthenticationIn *) in;
155 : GnomeVFSModuleCallbackAuthenticationOut *authOut =
156 0 : (GnomeVFSModuleCallbackAuthenticationOut *) out;
157 :
158 0 : LOG(("gnomevfs: ProxiedAuthCallback [uri=%s]\n", authIn->uri));
159 :
160 : // Without a channel, we have no way of getting a prompter.
161 0 : nsIChannel *channel = (nsIChannel *) callback_data;
162 0 : if (!channel)
163 0 : return;
164 :
165 0 : nsCOMPtr<nsIAuthPrompt> prompt;
166 0 : NS_QueryNotificationCallbacks(channel, prompt);
167 :
168 : // If no auth prompt, then give up. We could failover to using the
169 : // WindowWatcher service, but that might defeat a consumer's purposeful
170 : // attempt to disable authentication (for whatever reason).
171 0 : if (!prompt)
172 : return;
173 :
174 : // Parse out the host and port...
175 0 : nsCOMPtr<nsIURI> uri;
176 0 : channel->GetURI(getter_AddRefs(uri));
177 0 : if (!uri)
178 : return;
179 :
180 : #ifdef DEBUG
181 : {
182 : //
183 : // Make sure authIn->uri is consistent with the channel's URI.
184 : //
185 : // XXX This check is probably not IDN safe, and it might incorrectly
186 : // fire as a result of escaping differences. It's unclear what
187 : // kind of transforms GnomeVFS might have applied to the URI spec
188 : // that we originally gave to it. In spite of the likelihood of
189 : // false hits, this check is probably still valuable.
190 : //
191 0 : nsCAutoString spec;
192 0 : uri->GetSpec(spec);
193 0 : int uriLen = strlen(authIn->uri);
194 0 : if (!StringHead(spec, uriLen).Equals(nsDependentCString(authIn->uri, uriLen)))
195 : {
196 0 : LOG(("gnomevfs: [spec=%s authIn->uri=%s]\n", spec.get(), authIn->uri));
197 0 : NS_ERROR("URI mismatch");
198 : }
199 : }
200 : #endif
201 :
202 0 : nsCAutoString scheme, hostPort;
203 0 : uri->GetScheme(scheme);
204 0 : uri->GetHostPort(hostPort);
205 :
206 : // It doesn't make sense for either of these strings to be empty. What kind
207 : // of funky URI is this?
208 0 : if (scheme.IsEmpty() || hostPort.IsEmpty())
209 : return;
210 :
211 : // Construct the single signon key. Altering the value of this key will
212 : // cause people's remembered passwords to be forgotten. Think carefully
213 : // before changing the way this key is constructed.
214 0 : nsAutoString key, realm;
215 :
216 0 : NS_ConvertUTF8toUTF16 dispHost(scheme);
217 0 : dispHost.Append(NS_LITERAL_STRING("://"));
218 0 : dispHost.Append(NS_ConvertUTF8toUTF16(hostPort));
219 :
220 0 : key = dispHost;
221 0 : if (authIn->realm)
222 : {
223 : // We assume the realm string is ASCII. That might be a bogus assumption,
224 : // but we have no idea what encoding GnomeVFS is using, so for now we'll
225 : // limit ourselves to ISO-Latin-1. XXX What is a better solution?
226 0 : realm.Append('"');
227 0 : realm.Append(NS_ConvertASCIItoUTF16(authIn->realm));
228 0 : realm.Append('"');
229 0 : key.Append(' ');
230 0 : key.Append(realm);
231 : }
232 :
233 : // Construct the message string...
234 : //
235 : // We use Necko's string bundle here. This code really should be encapsulated
236 : // behind some Necko API, after all this code is based closely on the code in
237 : // nsHttpChannel.cpp.
238 :
239 : nsCOMPtr<nsIStringBundleService> bundleSvc =
240 0 : do_GetService(NS_STRINGBUNDLE_CONTRACTID);
241 0 : if (!bundleSvc)
242 : return;
243 :
244 0 : nsCOMPtr<nsIStringBundle> bundle;
245 0 : bundleSvc->CreateBundle("chrome://global/locale/commonDialogs.properties",
246 0 : getter_AddRefs(bundle));
247 0 : if (!bundle)
248 : return;
249 :
250 0 : nsString message;
251 0 : if (!realm.IsEmpty())
252 : {
253 0 : const PRUnichar *strings[] = { realm.get(), dispHost.get() };
254 0 : bundle->FormatStringFromName(NS_LITERAL_STRING("EnterUserPasswordForRealm").get(),
255 0 : strings, 2, getter_Copies(message));
256 : }
257 : else
258 : {
259 0 : const PRUnichar *strings[] = { dispHost.get() };
260 0 : bundle->FormatStringFromName(NS_LITERAL_STRING("EnterUserPasswordFor").get(),
261 0 : strings, 1, getter_Copies(message));
262 : }
263 0 : if (message.IsEmpty())
264 : return;
265 :
266 : // Prompt the user...
267 : nsresult rv;
268 0 : bool retval = false;
269 0 : PRUnichar *user = nsnull, *pass = nsnull;
270 :
271 0 : rv = prompt->PromptUsernameAndPassword(nsnull, message.get(),
272 : key.get(),
273 : nsIAuthPrompt::SAVE_PASSWORD_PERMANENTLY,
274 0 : &user, &pass, &retval);
275 0 : if (NS_FAILED(rv))
276 : return;
277 0 : if (!retval || !user || !pass)
278 : return;
279 :
280 : // XXX We need to convert the UTF-16 username and password from our dialog to
281 : // strings that GnomeVFS can understand. It's unclear what encoding GnomeVFS
282 : // expects, so for now we assume 7-bit ASCII. Hopefully, we can get a better
283 : // solution at some point.
284 :
285 : // One copy is never enough...
286 0 : authOut->username = g_strdup(NS_LossyConvertUTF16toASCII(user).get());
287 0 : authOut->password = g_strdup(NS_LossyConvertUTF16toASCII(pass).get());
288 :
289 0 : nsMemory::Free(user);
290 0 : nsMemory::Free(pass);
291 : }
292 :
293 : struct nsGnomeVFSAuthCallbackEvent : public nsRunnable
294 0 : {
295 : gconstpointer in;
296 : gsize in_size;
297 : gpointer out;
298 : gsize out_size;
299 : gpointer callback_data;
300 :
301 0 : NS_IMETHOD Run() {
302 0 : ProxiedAuthCallback(in, in_size, out, out_size, callback_data);
303 0 : return NS_OK;
304 : }
305 : };
306 :
307 : static void
308 0 : AuthCallback(gconstpointer in,
309 : gsize in_size,
310 : gpointer out,
311 : gsize out_size,
312 : gpointer callback_data)
313 : {
314 : // Need to proxy this callback over to the main thread. Synchronous dispatch
315 : // is required in order to provide data to the GnomeVFS callback.
316 :
317 0 : nsRefPtr<nsGnomeVFSAuthCallbackEvent> ev = new nsGnomeVFSAuthCallbackEvent();
318 0 : if (!ev)
319 : return; // OOM
320 :
321 0 : ev->in = in;
322 0 : ev->in_size = in_size;
323 0 : ev->out = out;
324 0 : ev->out_size = out_size;
325 0 : ev->callback_data = callback_data;
326 :
327 0 : NS_DispatchToMainThread(ev, NS_DISPATCH_SYNC);
328 : }
329 :
330 : //-----------------------------------------------------------------------------
331 :
332 : static gint
333 0 : FileInfoComparator(gconstpointer a, gconstpointer b)
334 : {
335 0 : const GnomeVFSFileInfo *ia = (const GnomeVFSFileInfo *) a;
336 0 : const GnomeVFSFileInfo *ib = (const GnomeVFSFileInfo *) b;
337 :
338 0 : return strcasecmp(ia->name, ib->name);
339 : }
340 :
341 : //-----------------------------------------------------------------------------
342 :
343 : class nsGnomeVFSInputStream : public nsIInputStream
344 : {
345 : public:
346 : NS_DECL_ISUPPORTS
347 : NS_DECL_NSIINPUTSTREAM
348 :
349 0 : nsGnomeVFSInputStream(const nsCString &uriSpec)
350 : : mSpec(uriSpec)
351 : , mChannel(nsnull)
352 : , mHandle(nsnull)
353 : , mBytesRemaining(PR_UINT32_MAX)
354 : , mStatus(NS_OK)
355 : , mDirList(nsnull)
356 : , mDirListPtr(nsnull)
357 : , mDirBufCursor(0)
358 0 : , mDirOpen(false) {}
359 :
360 0 : ~nsGnomeVFSInputStream() { Close(); }
361 :
362 0 : void SetChannel(nsIChannel *channel)
363 : {
364 : // We need to hold an owning reference to our channel. This is done
365 : // so we can access the channel's notification callbacks to acquire
366 : // a reference to a nsIAuthPrompt if we need to handle a GnomeVFS
367 : // authentication callback.
368 : //
369 : // However, the channel can only be accessed on the main thread, so
370 : // we have to be very careful with ownership. Moreover, it doesn't
371 : // support threadsafe addref/release, so proxying is the answer.
372 : //
373 : // Also, it's important to note that this likely creates a reference
374 : // cycle since the channel likely owns this stream. This reference
375 : // cycle is broken in our Close method.
376 :
377 0 : NS_ADDREF(mChannel = channel);
378 0 : }
379 :
380 : private:
381 : GnomeVFSResult DoOpen();
382 : GnomeVFSResult DoRead(char *aBuf, PRUint32 aCount, PRUint32 *aCountRead);
383 : nsresult SetContentTypeOfChannel(const char *contentType);
384 :
385 : private:
386 : nsCString mSpec;
387 : nsIChannel *mChannel; // manually refcounted
388 : GnomeVFSHandle *mHandle;
389 : PRUint32 mBytesRemaining;
390 : nsresult mStatus;
391 : GList *mDirList;
392 : GList *mDirListPtr;
393 : nsCString mDirBuf;
394 : PRUint32 mDirBufCursor;
395 : bool mDirOpen;
396 : };
397 :
398 : GnomeVFSResult
399 0 : nsGnomeVFSInputStream::DoOpen()
400 : {
401 : GnomeVFSResult rv;
402 :
403 0 : NS_ASSERTION(mHandle == nsnull, "already open");
404 :
405 : // Push a callback handler on the stack for this thread, so we can intercept
406 : // authentication requests from GnomeVFS. We'll use the channel to get a
407 : // nsIAuthPrompt instance.
408 :
409 : gnome_vfs_module_callback_push(GNOME_VFS_MODULE_CALLBACK_AUTHENTICATION,
410 0 : AuthCallback, mChannel, NULL);
411 :
412 : // Query the mime type first (this could return NULL).
413 : //
414 : // XXX We need to do this up-front in order to determine how to open the URI.
415 : // Unfortunately, the error code GNOME_VFS_ERROR_IS_DIRECTORY is not
416 : // always returned by gnome_vfs_open when we pass it a URI to a directory!
417 : // Otherwise, we could have used that as a way to failover to opening the
418 : // URI as a directory. Also, it would have been ideal if
419 : // gnome_vfs_get_file_info_from_handle were actually implemented by the
420 : // smb:// module, since that would have allowed us to potentially save a
421 : // round trip to the server to discover the mime type of the document in
422 : // the case where gnome_vfs_open would have been used. (Oh well! /me
423 : // throws hands up in the air and moves on...)
424 :
425 0 : GnomeVFSFileInfo info = {0};
426 : rv = gnome_vfs_get_file_info(mSpec.get(), &info, GnomeVFSFileInfoOptions(
427 : GNOME_VFS_FILE_INFO_DEFAULT |
428 0 : GNOME_VFS_FILE_INFO_FOLLOW_LINKS));
429 0 : if (rv == GNOME_VFS_OK)
430 : {
431 0 : if (info.type == GNOME_VFS_FILE_TYPE_DIRECTORY)
432 : {
433 : rv = gnome_vfs_directory_list_load(&mDirList, mSpec.get(),
434 0 : GNOME_VFS_FILE_INFO_DEFAULT);
435 :
436 0 : LOG(("gnomevfs: gnome_vfs_directory_list_load returned %d (%s) [spec=\"%s\"]\n",
437 : rv, gnome_vfs_result_to_string(rv), mSpec.get()));
438 : }
439 : else
440 : {
441 0 : rv = gnome_vfs_open(&mHandle, mSpec.get(), GNOME_VFS_OPEN_READ);
442 :
443 0 : LOG(("gnomevfs: gnome_vfs_open returned %d (%s) [spec=\"%s\"]\n",
444 : rv, gnome_vfs_result_to_string(rv), mSpec.get()));
445 : }
446 : }
447 :
448 0 : gnome_vfs_module_callback_pop(GNOME_VFS_MODULE_CALLBACK_AUTHENTICATION);
449 :
450 0 : if (rv == GNOME_VFS_OK)
451 : {
452 0 : if (mHandle)
453 : {
454 : // Here we set the content type of the channel to the value of the mime
455 : // type determined by GnomeVFS. However, if GnomeVFS is telling us that
456 : // the document is binary, we'll ignore that and keep the channel's
457 : // content type unspecified. That will enable our content type sniffing
458 : // algorithms. This should provide more consistent mime type handling.
459 :
460 0 : if (info.mime_type && (strcmp(info.mime_type, APPLICATION_OCTET_STREAM) != 0))
461 0 : SetContentTypeOfChannel(info.mime_type);
462 :
463 : // XXX truncates size from 64-bit to 32-bit
464 0 : mBytesRemaining = (PRUint32) info.size;
465 :
466 : // Update the content length attribute on the channel. We do this
467 : // synchronously without proxying. This hack is not as bad as it looks!
468 0 : if (mBytesRemaining != PR_UINT32_MAX)
469 0 : mChannel->SetContentLength(mBytesRemaining);
470 : }
471 : else
472 : {
473 0 : mDirOpen = true;
474 :
475 : // Sort mDirList
476 0 : mDirList = g_list_sort(mDirList, FileInfoComparator);
477 0 : mDirListPtr = mDirList;
478 :
479 : // Write base URL (make sure it ends with a '/')
480 0 : mDirBuf.Append("300: ");
481 0 : mDirBuf.Append(mSpec);
482 0 : if (mSpec.get()[mSpec.Length() - 1] != '/')
483 0 : mDirBuf.Append('/');
484 0 : mDirBuf.Append('\n');
485 :
486 : // Write column names
487 0 : mDirBuf.Append("200: filename content-length last-modified file-type\n");
488 :
489 : // Write charset (assume UTF-8)
490 : // XXX is this correct?
491 0 : mDirBuf.Append("301: UTF-8\n");
492 :
493 0 : SetContentTypeOfChannel(APPLICATION_HTTP_INDEX_FORMAT);
494 : }
495 : }
496 :
497 0 : gnome_vfs_file_info_clear(&info);
498 0 : return rv;
499 : }
500 :
501 : GnomeVFSResult
502 0 : nsGnomeVFSInputStream::DoRead(char *aBuf, PRUint32 aCount, PRUint32 *aCountRead)
503 : {
504 : GnomeVFSResult rv;
505 :
506 0 : if (mHandle)
507 : {
508 : GnomeVFSFileSize bytesRead;
509 0 : rv = gnome_vfs_read(mHandle, aBuf, aCount, &bytesRead);
510 0 : if (rv == GNOME_VFS_OK)
511 : {
512 0 : *aCountRead = (PRUint32) bytesRead;
513 0 : mBytesRemaining -= *aCountRead;
514 : }
515 : }
516 0 : else if (mDirOpen)
517 : {
518 0 : rv = GNOME_VFS_OK;
519 :
520 0 : while (aCount && rv != GNOME_VFS_ERROR_EOF)
521 : {
522 : // Copy data out of our buffer
523 0 : PRUint32 bufLen = mDirBuf.Length() - mDirBufCursor;
524 0 : if (bufLen)
525 : {
526 0 : PRUint32 n = NS_MIN(bufLen, aCount);
527 0 : memcpy(aBuf, mDirBuf.get() + mDirBufCursor, n);
528 0 : *aCountRead += n;
529 0 : aBuf += n;
530 0 : aCount -= n;
531 0 : mDirBufCursor += n;
532 : }
533 :
534 0 : if (!mDirListPtr) // Are we at the end of the directory list?
535 : {
536 0 : rv = GNOME_VFS_ERROR_EOF;
537 : }
538 0 : else if (aCount) // Do we need more data?
539 : {
540 0 : GnomeVFSFileInfo *info = (GnomeVFSFileInfo *) mDirListPtr->data;
541 :
542 : // Prune '.' and '..' from directory listing.
543 0 : if (info->name[0] == '.' &&
544 0 : (info->name[1] == '\0' ||
545 0 : (info->name[1] == '.' && info->name[2] == '\0')))
546 : {
547 0 : mDirListPtr = mDirListPtr->next;
548 0 : continue;
549 : }
550 :
551 0 : mDirBuf.Assign("201: ");
552 :
553 : // The "filename" field
554 0 : nsCString escName;
555 0 : nsCOMPtr<nsINetUtil> nu = do_GetService(NS_NETUTIL_CONTRACTID);
556 0 : if (nu) {
557 0 : nu->EscapeString(nsDependentCString(info->name),
558 0 : nsINetUtil::ESCAPE_URL_PATH, escName);
559 :
560 0 : mDirBuf.Append(escName);
561 0 : mDirBuf.Append(' ');
562 : }
563 :
564 : // The "content-length" field
565 : // XXX truncates size from 64-bit to 32-bit
566 0 : mDirBuf.AppendInt(PRInt32(info->size));
567 0 : mDirBuf.Append(' ');
568 :
569 : // The "last-modified" field
570 : //
571 : // NSPR promises: PRTime is compatible with time_t
572 : // we just need to convert from seconds to microseconds
573 : PRExplodedTime tm;
574 0 : PRTime pt = ((PRTime) info->mtime) * 1000000;
575 0 : PR_ExplodeTime(pt, PR_GMTParameters, &tm);
576 : {
577 : char buf[64];
578 : PR_FormatTimeUSEnglish(buf, sizeof(buf),
579 0 : "%a,%%20%d%%20%b%%20%Y%%20%H:%M:%S%%20GMT ", &tm);
580 0 : mDirBuf.Append(buf);
581 : }
582 :
583 : // The "file-type" field
584 0 : switch (info->type)
585 : {
586 : case GNOME_VFS_FILE_TYPE_REGULAR:
587 0 : mDirBuf.Append("FILE ");
588 0 : break;
589 : case GNOME_VFS_FILE_TYPE_DIRECTORY:
590 0 : mDirBuf.Append("DIRECTORY ");
591 0 : break;
592 : case GNOME_VFS_FILE_TYPE_SYMBOLIC_LINK:
593 0 : mDirBuf.Append("SYMBOLIC-LINK ");
594 0 : break;
595 : default:
596 0 : break;
597 : }
598 :
599 0 : mDirBuf.Append('\n');
600 :
601 0 : mDirBufCursor = 0;
602 0 : mDirListPtr = mDirListPtr->next;
603 : }
604 : }
605 : }
606 : else
607 : {
608 0 : NS_NOTREACHED("reading from what?");
609 0 : rv = GNOME_VFS_ERROR_GENERIC;
610 : }
611 :
612 0 : return rv;
613 : }
614 :
615 : // This class is used to implement SetContentTypeOfChannel.
616 : class nsGnomeVFSSetContentTypeEvent : public nsRunnable
617 0 : {
618 : public:
619 0 : nsGnomeVFSSetContentTypeEvent(nsIChannel *channel, const char *contentType)
620 0 : : mChannel(channel), mContentType(contentType)
621 : {
622 : // stash channel reference in mChannel. no AddRef here! see note
623 : // in SetContentTypeOfchannel.
624 0 : }
625 :
626 0 : NS_IMETHOD Run()
627 : {
628 0 : mChannel->SetContentType(mContentType);
629 0 : return NS_OK;
630 : }
631 :
632 : private:
633 : nsIChannel *mChannel;
634 : nsCString mContentType;
635 : };
636 :
637 : nsresult
638 0 : nsGnomeVFSInputStream::SetContentTypeOfChannel(const char *contentType)
639 : {
640 : // We need to proxy this call over to the main thread. We post an
641 : // asynchronous event in this case so that we don't delay reading data, and
642 : // we know that this is safe to do since the channel's reference will be
643 : // released asynchronously as well. We trust the ordering of the main
644 : // thread's event queue to protect us against memory corruption.
645 :
646 : nsresult rv;
647 : nsCOMPtr<nsIRunnable> ev =
648 0 : new nsGnomeVFSSetContentTypeEvent(mChannel, contentType);
649 0 : if (!ev)
650 : {
651 0 : rv = NS_ERROR_OUT_OF_MEMORY;
652 : }
653 : else
654 : {
655 0 : rv = NS_DispatchToMainThread(ev);
656 : }
657 0 : return rv;
658 : }
659 :
660 0 : NS_IMPL_THREADSAFE_ISUPPORTS1(nsGnomeVFSInputStream, nsIInputStream)
661 :
662 : NS_IMETHODIMP
663 0 : nsGnomeVFSInputStream::Close()
664 : {
665 0 : if (mHandle)
666 : {
667 0 : gnome_vfs_close(mHandle);
668 0 : mHandle = nsnull;
669 : }
670 :
671 0 : if (mDirList)
672 : {
673 : // Destroy the list of GnomeVFSFileInfo objects...
674 0 : g_list_foreach(mDirList, (GFunc) gnome_vfs_file_info_unref, nsnull);
675 0 : g_list_free(mDirList);
676 0 : mDirList = nsnull;
677 0 : mDirListPtr = nsnull;
678 : }
679 :
680 0 : if (mChannel)
681 : {
682 0 : nsresult rv = NS_OK;
683 :
684 0 : nsCOMPtr<nsIThread> thread = do_GetMainThread();
685 0 : if (thread)
686 0 : rv = NS_ProxyRelease(thread, mChannel);
687 :
688 0 : NS_ASSERTION(thread && NS_SUCCEEDED(rv), "leaking channel reference");
689 0 : mChannel = nsnull;
690 : }
691 :
692 0 : mSpec.Truncate(); // free memory
693 :
694 : // Prevent future reads from re-opening the handle.
695 0 : if (NS_SUCCEEDED(mStatus))
696 0 : mStatus = NS_BASE_STREAM_CLOSED;
697 :
698 0 : return NS_OK;
699 : }
700 :
701 : NS_IMETHODIMP
702 0 : nsGnomeVFSInputStream::Available(PRUint32 *aResult)
703 : {
704 0 : if (NS_FAILED(mStatus))
705 0 : return mStatus;
706 :
707 0 : *aResult = mBytesRemaining;
708 0 : return NS_OK;
709 : }
710 :
711 : NS_IMETHODIMP
712 0 : nsGnomeVFSInputStream::Read(char *aBuf,
713 : PRUint32 aCount,
714 : PRUint32 *aCountRead)
715 : {
716 0 : *aCountRead = 0;
717 :
718 0 : if (mStatus == NS_BASE_STREAM_CLOSED)
719 0 : return NS_OK;
720 0 : if (NS_FAILED(mStatus))
721 0 : return mStatus;
722 :
723 0 : GnomeVFSResult rv = GNOME_VFS_OK;
724 :
725 : // If this is our first-time through here, then open the URI.
726 0 : if (!mHandle && !mDirOpen)
727 0 : rv = DoOpen();
728 :
729 0 : if (rv == GNOME_VFS_OK)
730 0 : rv = DoRead(aBuf, aCount, aCountRead);
731 :
732 0 : if (rv != GNOME_VFS_OK)
733 : {
734 : // If we reach here, we hit some kind of error. EOF is not an error.
735 0 : mStatus = MapGnomeVFSResult(rv);
736 0 : if (mStatus == NS_BASE_STREAM_CLOSED)
737 0 : return NS_OK;
738 :
739 0 : LOG(("gnomevfs: result %d [%s] mapped to 0x%x\n",
740 : rv, gnome_vfs_result_to_string(rv), mStatus));
741 : }
742 0 : return mStatus;
743 : }
744 :
745 : NS_IMETHODIMP
746 0 : nsGnomeVFSInputStream::ReadSegments(nsWriteSegmentFun aWriter,
747 : void *aClosure,
748 : PRUint32 aCount,
749 : PRUint32 *aResult)
750 : {
751 : // There is no way to implement this using GnomeVFS, but fortunately
752 : // that doesn't matter. Because we are a blocking input stream, Necko
753 : // isn't going to call our ReadSegments method.
754 0 : NS_NOTREACHED("nsGnomeVFSInputStream::ReadSegments");
755 0 : return NS_ERROR_NOT_IMPLEMENTED;
756 : }
757 :
758 : NS_IMETHODIMP
759 0 : nsGnomeVFSInputStream::IsNonBlocking(bool *aResult)
760 : {
761 0 : *aResult = false;
762 0 : return NS_OK;
763 : }
764 :
765 : //-----------------------------------------------------------------------------
766 :
767 : class nsGnomeVFSProtocolHandler : public nsIProtocolHandler
768 : , public nsIObserver
769 26 : {
770 : public:
771 : NS_DECL_ISUPPORTS
772 : NS_DECL_NSIPROTOCOLHANDLER
773 : NS_DECL_NSIOBSERVER
774 :
775 : nsresult Init();
776 :
777 : private:
778 : void InitSupportedProtocolsPref(nsIPrefBranch *prefs);
779 : bool IsSupportedProtocol(const nsCString &spec);
780 :
781 : nsCString mSupportedProtocols;
782 : };
783 :
784 1240 : NS_IMPL_ISUPPORTS2(nsGnomeVFSProtocolHandler, nsIProtocolHandler, nsIObserver)
785 :
786 : nsresult
787 13 : nsGnomeVFSProtocolHandler::Init()
788 : {
789 : #ifdef PR_LOGGING
790 13 : sGnomeVFSLog = PR_NewLogModule("gnomevfs");
791 : #endif
792 :
793 13 : if (!gnome_vfs_initialized())
794 : {
795 12 : if (!gnome_vfs_init())
796 : {
797 0 : NS_WARNING("gnome_vfs_init failed");
798 0 : return NS_ERROR_UNEXPECTED;
799 : }
800 : }
801 :
802 26 : nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
803 13 : if (prefs)
804 : {
805 13 : InitSupportedProtocolsPref(prefs);
806 13 : prefs->AddObserver(MOZ_GNOMEVFS_SUPPORTED_PROTOCOLS, this, false);
807 : }
808 :
809 13 : return NS_OK;
810 : }
811 :
812 : void
813 13 : nsGnomeVFSProtocolHandler::InitSupportedProtocolsPref(nsIPrefBranch *prefs)
814 : {
815 : // read preferences
816 : nsresult rv = prefs->GetCharPref(MOZ_GNOMEVFS_SUPPORTED_PROTOCOLS,
817 13 : getter_Copies(mSupportedProtocols));
818 13 : if (NS_SUCCEEDED(rv)) {
819 0 : mSupportedProtocols.StripWhitespace();
820 0 : ToLowerCase(mSupportedProtocols);
821 : }
822 : else
823 13 : mSupportedProtocols.Assign("smb:,sftp:"); // use defaults
824 :
825 13 : LOG(("gnomevfs: supported protocols \"%s\"\n", mSupportedProtocols.get()));
826 13 : }
827 :
828 : bool
829 209 : nsGnomeVFSProtocolHandler::IsSupportedProtocol(const nsCString &aSpec)
830 : {
831 209 : const char *specString = aSpec.get();
832 209 : const char *colon = strchr(specString, ':');
833 209 : if (!colon)
834 0 : return false;
835 :
836 209 : PRUint32 length = colon - specString + 1;
837 :
838 : // <scheme> + ':'
839 418 : nsCString scheme(specString, length);
840 :
841 209 : char *found = PL_strcasestr(mSupportedProtocols.get(), scheme.get());
842 209 : if (!found)
843 209 : return false;
844 :
845 0 : if (found[length] != ',' && found[length] != '\0')
846 0 : return false;
847 :
848 0 : return true;
849 : }
850 :
851 : NS_IMETHODIMP
852 0 : nsGnomeVFSProtocolHandler::GetScheme(nsACString &aScheme)
853 : {
854 0 : aScheme.Assign(MOZ_GNOMEVFS_SCHEME);
855 0 : return NS_OK;
856 : }
857 :
858 : NS_IMETHODIMP
859 0 : nsGnomeVFSProtocolHandler::GetDefaultPort(PRInt32 *aDefaultPort)
860 : {
861 0 : *aDefaultPort = -1;
862 0 : return NS_OK;
863 : }
864 :
865 : NS_IMETHODIMP
866 0 : nsGnomeVFSProtocolHandler::GetProtocolFlags(PRUint32 *aProtocolFlags)
867 : {
868 : // Is URI_STD true of all GnomeVFS URI types?
869 0 : *aProtocolFlags = URI_STD | URI_DANGEROUS_TO_LOAD;
870 0 : return NS_OK;
871 : }
872 :
873 : NS_IMETHODIMP
874 209 : nsGnomeVFSProtocolHandler::NewURI(const nsACString &aSpec,
875 : const char *aOriginCharset,
876 : nsIURI *aBaseURI,
877 : nsIURI **aResult)
878 : {
879 418 : const nsCString flatSpec(aSpec);
880 209 : LOG(("gnomevfs: NewURI [spec=%s]\n", flatSpec.get()));
881 :
882 209 : if (!aBaseURI)
883 : {
884 : //
885 : // XXX This check is used to limit the gnome-vfs protocols we support. For
886 : // security reasons, it is best that we limit the protocols we support to
887 : // those with known characteristics. We might want to lessen this
888 : // restriction if it proves to be too heavy handed. A black list of
889 : // protocols we don't want to support might be better. For example, we
890 : // probably don't want to try to load "start-here:" inside the browser.
891 : // There are others that fall into this category, which are best handled
892 : // externally by Nautilus (or another app like it).
893 : //
894 209 : if (!IsSupportedProtocol(flatSpec))
895 209 : return NS_ERROR_UNKNOWN_PROTOCOL;
896 :
897 : // Verify that GnomeVFS supports this URI scheme.
898 0 : GnomeVFSURI *uri = gnome_vfs_uri_new(flatSpec.get());
899 0 : if (!uri)
900 0 : return NS_ERROR_UNKNOWN_PROTOCOL;
901 : }
902 :
903 : //
904 : // XXX Can we really assume that all gnome-vfs URIs can be parsed using
905 : // nsStandardURL? We probably really need to implement nsIURI/nsIURL
906 : // in terms of the gnome_vfs_uri_XXX methods, but at least this works
907 : // correctly for smb:// URLs ;-)
908 : //
909 : // Also, it might not be possible to fully implement nsIURI/nsIURL in
910 : // terms of GnomeVFSURI since some Necko methods have no GnomeVFS
911 : // equivalent.
912 : //
913 : nsresult rv;
914 : nsCOMPtr<nsIStandardURL> url =
915 0 : do_CreateInstance(NS_STANDARDURL_CONTRACTID, &rv);
916 0 : if (NS_FAILED(rv))
917 0 : return rv;
918 :
919 0 : rv = url->Init(nsIStandardURL::URLTYPE_STANDARD, -1, flatSpec,
920 0 : aOriginCharset, aBaseURI);
921 0 : if (NS_SUCCEEDED(rv))
922 0 : rv = CallQueryInterface(url, aResult);
923 :
924 0 : return rv;
925 : }
926 :
927 : NS_IMETHODIMP
928 0 : nsGnomeVFSProtocolHandler::NewChannel(nsIURI *aURI, nsIChannel **aResult)
929 : {
930 0 : NS_ENSURE_ARG_POINTER(aURI);
931 : nsresult rv;
932 :
933 0 : nsCAutoString spec;
934 0 : rv = aURI->GetSpec(spec);
935 0 : if (NS_FAILED(rv))
936 0 : return rv;
937 :
938 0 : nsRefPtr<nsGnomeVFSInputStream> stream = new nsGnomeVFSInputStream(spec);
939 0 : if (!stream)
940 : {
941 0 : rv = NS_ERROR_OUT_OF_MEMORY;
942 : }
943 : else
944 : {
945 : // start out assuming an unknown content-type. we'll set the content-type
946 : // to something better once we open the URI.
947 : rv = NS_NewInputStreamChannel(aResult, aURI, stream,
948 0 : NS_LITERAL_CSTRING(UNKNOWN_CONTENT_TYPE));
949 0 : if (NS_SUCCEEDED(rv))
950 0 : stream->SetChannel(*aResult);
951 : }
952 0 : return rv;
953 : }
954 :
955 : NS_IMETHODIMP
956 0 : nsGnomeVFSProtocolHandler::AllowPort(PRInt32 aPort,
957 : const char *aScheme,
958 : bool *aResult)
959 : {
960 : // Don't override anything.
961 0 : *aResult = false;
962 0 : return NS_OK;
963 : }
964 :
965 : NS_IMETHODIMP
966 0 : nsGnomeVFSProtocolHandler::Observe(nsISupports *aSubject,
967 : const char *aTopic,
968 : const PRUnichar *aData)
969 : {
970 0 : if (strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) == 0) {
971 0 : nsCOMPtr<nsIPrefBranch> prefs = do_QueryInterface(aSubject);
972 0 : InitSupportedProtocolsPref(prefs);
973 : }
974 0 : return NS_OK;
975 : }
976 :
977 : //-----------------------------------------------------------------------------
978 :
979 : #define NS_GNOMEVFSPROTOCOLHANDLER_CID \
980 : { /* 9b6dc177-a2e4-49e1-9c98-0a8384de7f6c */ \
981 : 0x9b6dc177, \
982 : 0xa2e4, \
983 : 0x49e1, \
984 : {0x9c, 0x98, 0x0a, 0x83, 0x84, 0xde, 0x7f, 0x6c} \
985 : }
986 :
987 26 : NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsGnomeVFSProtocolHandler, Init)
988 : NS_DEFINE_NAMED_CID(NS_GNOMEVFSPROTOCOLHANDLER_CID);
989 :
990 : static const mozilla::Module::CIDEntry kVFSCIDs[] = {
991 : { &kNS_GNOMEVFSPROTOCOLHANDLER_CID, false, NULL, nsGnomeVFSProtocolHandlerConstructor },
992 : { NULL }
993 : };
994 :
995 : static const mozilla::Module::ContractIDEntry kVFSContracts[] = {
996 : { NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX MOZ_GNOMEVFS_SCHEME, &kNS_GNOMEVFSPROTOCOLHANDLER_CID },
997 : { NULL }
998 : };
999 :
1000 : static const mozilla::Module kVFSModule = {
1001 : mozilla::Module::kVersion,
1002 : kVFSCIDs,
1003 : kVFSContracts
1004 : };
1005 :
1006 : NSMODULE_DEFN(nsGnomeVFSModule) = &kVFSModule;
|