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