1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/extensions/gnomevfs/nsGnomeVFSProtocolHandler.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,980 @@ 1.4 +/* vim:set ts=2 sw=2 et cindent: */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +// GnomeVFS v2.2.2 is missing G_BEGIN_DECLS in gnome-vfs-module-callback.h 1.10 +extern "C" { 1.11 +#include <libgnomevfs/gnome-vfs.h> 1.12 +#include <libgnomevfs/gnome-vfs-standard-callbacks.h> 1.13 +#include <libgnomevfs/gnome-vfs-mime-utils.h> 1.14 +} 1.15 + 1.16 +#include <algorithm> 1.17 + 1.18 +#include "nsServiceManagerUtils.h" 1.19 +#include "nsComponentManagerUtils.h" 1.20 +#include "mozilla/ModuleUtils.h" 1.21 +#include "nsIInterfaceRequestorUtils.h" 1.22 +#include "nsIPrefService.h" 1.23 +#include "nsIPrefBranch.h" 1.24 +#include "nsIObserver.h" 1.25 +#include "nsThreadUtils.h" 1.26 +#include "nsProxyRelease.h" 1.27 +#include "nsIAuthPrompt.h" 1.28 +#include "nsIStringBundle.h" 1.29 +#include "nsIStandardURL.h" 1.30 +#include "nsIURL.h" 1.31 +#include "nsMimeTypes.h" 1.32 +#include "nsNetUtil.h" 1.33 +#include "nsINetUtil.h" 1.34 +#include "nsAutoPtr.h" 1.35 +#include "nsError.h" 1.36 +#include "prlog.h" 1.37 +#include "prtime.h" 1.38 +#include "prprf.h" 1.39 +#include "plstr.h" 1.40 +#include "mozilla/Attributes.h" 1.41 + 1.42 +#define MOZ_GNOMEVFS_SCHEME "moz-gnomevfs" 1.43 +#define MOZ_GNOMEVFS_SUPPORTED_PROTOCOLS "network.gnomevfs.supported-protocols" 1.44 + 1.45 +//----------------------------------------------------------------------------- 1.46 + 1.47 +// NSPR_LOG_MODULES=gnomevfs:5 1.48 +#ifdef PR_LOGGING 1.49 +static PRLogModuleInfo *sGnomeVFSLog; 1.50 +#define LOG(args) PR_LOG(sGnomeVFSLog, PR_LOG_DEBUG, args) 1.51 +#else 1.52 +#define LOG(args) 1.53 +#endif 1.54 + 1.55 +//----------------------------------------------------------------------------- 1.56 + 1.57 +static nsresult 1.58 +MapGnomeVFSResult(GnomeVFSResult result) 1.59 +{ 1.60 + switch (result) 1.61 + { 1.62 + case GNOME_VFS_OK: return NS_OK; 1.63 + case GNOME_VFS_ERROR_NOT_FOUND: return NS_ERROR_FILE_NOT_FOUND; 1.64 + case GNOME_VFS_ERROR_INTERNAL: return NS_ERROR_UNEXPECTED; 1.65 + case GNOME_VFS_ERROR_BAD_PARAMETERS: return NS_ERROR_INVALID_ARG; 1.66 + case GNOME_VFS_ERROR_NOT_SUPPORTED: return NS_ERROR_NOT_AVAILABLE; 1.67 + case GNOME_VFS_ERROR_CORRUPTED_DATA: return NS_ERROR_FILE_CORRUPTED; 1.68 + case GNOME_VFS_ERROR_TOO_BIG: return NS_ERROR_FILE_TOO_BIG; 1.69 + case GNOME_VFS_ERROR_NO_SPACE: return NS_ERROR_FILE_NO_DEVICE_SPACE; 1.70 + case GNOME_VFS_ERROR_READ_ONLY: 1.71 + case GNOME_VFS_ERROR_READ_ONLY_FILE_SYSTEM: return NS_ERROR_FILE_READ_ONLY; 1.72 + case GNOME_VFS_ERROR_INVALID_URI: 1.73 + case GNOME_VFS_ERROR_INVALID_HOST_NAME: return NS_ERROR_MALFORMED_URI; 1.74 + case GNOME_VFS_ERROR_ACCESS_DENIED: 1.75 + case GNOME_VFS_ERROR_NOT_PERMITTED: 1.76 + case GNOME_VFS_ERROR_LOGIN_FAILED: return NS_ERROR_FILE_ACCESS_DENIED; 1.77 + case GNOME_VFS_ERROR_EOF: return NS_BASE_STREAM_CLOSED; 1.78 + case GNOME_VFS_ERROR_NOT_A_DIRECTORY: return NS_ERROR_FILE_NOT_DIRECTORY; 1.79 + case GNOME_VFS_ERROR_IN_PROGRESS: return NS_ERROR_IN_PROGRESS; 1.80 + case GNOME_VFS_ERROR_FILE_EXISTS: return NS_ERROR_FILE_ALREADY_EXISTS; 1.81 + case GNOME_VFS_ERROR_IS_DIRECTORY: return NS_ERROR_FILE_IS_DIRECTORY; 1.82 + case GNOME_VFS_ERROR_NO_MEMORY: return NS_ERROR_OUT_OF_MEMORY; 1.83 + case GNOME_VFS_ERROR_HOST_NOT_FOUND: 1.84 + case GNOME_VFS_ERROR_HOST_HAS_NO_ADDRESS: return NS_ERROR_UNKNOWN_HOST; 1.85 + case GNOME_VFS_ERROR_CANCELLED: 1.86 + case GNOME_VFS_ERROR_INTERRUPTED: return NS_ERROR_ABORT; 1.87 + case GNOME_VFS_ERROR_DIRECTORY_NOT_EMPTY: return NS_ERROR_FILE_DIR_NOT_EMPTY; 1.88 + case GNOME_VFS_ERROR_NAME_TOO_LONG: return NS_ERROR_FILE_NAME_TOO_LONG; 1.89 + case GNOME_VFS_ERROR_SERVICE_NOT_AVAILABLE: return NS_ERROR_UNKNOWN_PROTOCOL; 1.90 + 1.91 + /* No special mapping for these error codes... 1.92 + 1.93 + case GNOME_VFS_ERROR_GENERIC: 1.94 + case GNOME_VFS_ERROR_IO: 1.95 + case GNOME_VFS_ERROR_WRONG_FORMAT: 1.96 + case GNOME_VFS_ERROR_BAD_FILE: 1.97 + case GNOME_VFS_ERROR_NOT_OPEN: 1.98 + case GNOME_VFS_ERROR_INVALID_OPEN_MODE: 1.99 + case GNOME_VFS_ERROR_TOO_MANY_OPEN_FILES: 1.100 + case GNOME_VFS_ERROR_LOOP: 1.101 + case GNOME_VFS_ERROR_DIRECTORY_BUSY: 1.102 + case GNOME_VFS_ERROR_TOO_MANY_LINKS: 1.103 + case GNOME_VFS_ERROR_NOT_SAME_FILE_SYSTEM: 1.104 + case GNOME_VFS_ERROR_SERVICE_OBSOLETE: 1.105 + case GNOME_VFS_ERROR_PROTOCOL_ERROR: 1.106 + case GNOME_VFS_ERROR_NO_MASTER_BROWSER: 1.107 + 1.108 + */ 1.109 + 1.110 + // Make GCC happy 1.111 + default: 1.112 + return NS_ERROR_FAILURE; 1.113 + } 1.114 + 1.115 + return NS_ERROR_FAILURE; 1.116 +} 1.117 + 1.118 +//----------------------------------------------------------------------------- 1.119 + 1.120 +static void 1.121 +ProxiedAuthCallback(gconstpointer in, 1.122 + gsize in_size, 1.123 + gpointer out, 1.124 + gsize out_size, 1.125 + gpointer callback_data) 1.126 +{ 1.127 + GnomeVFSModuleCallbackAuthenticationIn *authIn = 1.128 + (GnomeVFSModuleCallbackAuthenticationIn *) in; 1.129 + GnomeVFSModuleCallbackAuthenticationOut *authOut = 1.130 + (GnomeVFSModuleCallbackAuthenticationOut *) out; 1.131 + 1.132 + LOG(("gnomevfs: ProxiedAuthCallback [uri=%s]\n", authIn->uri)); 1.133 + 1.134 + // Without a channel, we have no way of getting a prompter. 1.135 + nsIChannel *channel = (nsIChannel *) callback_data; 1.136 + if (!channel) 1.137 + return; 1.138 + 1.139 + nsCOMPtr<nsIAuthPrompt> prompt; 1.140 + NS_QueryNotificationCallbacks(channel, prompt); 1.141 + 1.142 + // If no auth prompt, then give up. We could failover to using the 1.143 + // WindowWatcher service, but that might defeat a consumer's purposeful 1.144 + // attempt to disable authentication (for whatever reason). 1.145 + if (!prompt) 1.146 + return; 1.147 + 1.148 + // Parse out the host and port... 1.149 + nsCOMPtr<nsIURI> uri; 1.150 + channel->GetURI(getter_AddRefs(uri)); 1.151 + if (!uri) 1.152 + return; 1.153 + 1.154 +#ifdef DEBUG 1.155 + { 1.156 + // 1.157 + // Make sure authIn->uri is consistent with the channel's URI. 1.158 + // 1.159 + // XXX This check is probably not IDN safe, and it might incorrectly 1.160 + // fire as a result of escaping differences. It's unclear what 1.161 + // kind of transforms GnomeVFS might have applied to the URI spec 1.162 + // that we originally gave to it. In spite of the likelihood of 1.163 + // false hits, this check is probably still valuable. 1.164 + // 1.165 + nsAutoCString spec; 1.166 + uri->GetSpec(spec); 1.167 + int uriLen = strlen(authIn->uri); 1.168 + if (!StringHead(spec, uriLen).Equals(nsDependentCString(authIn->uri, uriLen))) 1.169 + { 1.170 + LOG(("gnomevfs: [spec=%s authIn->uri=%s]\n", spec.get(), authIn->uri)); 1.171 + NS_ERROR("URI mismatch"); 1.172 + } 1.173 + } 1.174 +#endif 1.175 + 1.176 + nsAutoCString scheme, hostPort; 1.177 + uri->GetScheme(scheme); 1.178 + uri->GetHostPort(hostPort); 1.179 + 1.180 + // It doesn't make sense for either of these strings to be empty. What kind 1.181 + // of funky URI is this? 1.182 + if (scheme.IsEmpty() || hostPort.IsEmpty()) 1.183 + return; 1.184 + 1.185 + // Construct the single signon key. Altering the value of this key will 1.186 + // cause people's remembered passwords to be forgotten. Think carefully 1.187 + // before changing the way this key is constructed. 1.188 + nsAutoString key, realm; 1.189 + 1.190 + NS_ConvertUTF8toUTF16 dispHost(scheme); 1.191 + dispHost.Append(NS_LITERAL_STRING("://")); 1.192 + dispHost.Append(NS_ConvertUTF8toUTF16(hostPort)); 1.193 + 1.194 + key = dispHost; 1.195 + if (authIn->realm) 1.196 + { 1.197 + // We assume the realm string is ASCII. That might be a bogus assumption, 1.198 + // but we have no idea what encoding GnomeVFS is using, so for now we'll 1.199 + // limit ourselves to ISO-Latin-1. XXX What is a better solution? 1.200 + realm.Append('"'); 1.201 + realm.Append(NS_ConvertASCIItoUTF16(authIn->realm)); 1.202 + realm.Append('"'); 1.203 + key.Append(' '); 1.204 + key.Append(realm); 1.205 + } 1.206 + 1.207 + // Construct the message string... 1.208 + // 1.209 + // We use Necko's string bundle here. This code really should be encapsulated 1.210 + // behind some Necko API, after all this code is based closely on the code in 1.211 + // nsHttpChannel.cpp. 1.212 + 1.213 + nsCOMPtr<nsIStringBundleService> bundleSvc = 1.214 + do_GetService(NS_STRINGBUNDLE_CONTRACTID); 1.215 + if (!bundleSvc) 1.216 + return; 1.217 + 1.218 + nsCOMPtr<nsIStringBundle> bundle; 1.219 + bundleSvc->CreateBundle("chrome://global/locale/commonDialogs.properties", 1.220 + getter_AddRefs(bundle)); 1.221 + if (!bundle) 1.222 + return; 1.223 + 1.224 + nsString message; 1.225 + if (!realm.IsEmpty()) 1.226 + { 1.227 + const char16_t *strings[] = { realm.get(), dispHost.get() }; 1.228 + bundle->FormatStringFromName(MOZ_UTF16("EnterUserPasswordForRealm"), 1.229 + strings, 2, getter_Copies(message)); 1.230 + } 1.231 + else 1.232 + { 1.233 + const char16_t *strings[] = { dispHost.get() }; 1.234 + bundle->FormatStringFromName(MOZ_UTF16("EnterUserPasswordFor"), 1.235 + strings, 1, getter_Copies(message)); 1.236 + } 1.237 + if (message.IsEmpty()) 1.238 + return; 1.239 + 1.240 + // Prompt the user... 1.241 + nsresult rv; 1.242 + bool retval = false; 1.243 + char16_t *user = nullptr, *pass = nullptr; 1.244 + 1.245 + rv = prompt->PromptUsernameAndPassword(nullptr, message.get(), 1.246 + key.get(), 1.247 + nsIAuthPrompt::SAVE_PASSWORD_PERMANENTLY, 1.248 + &user, &pass, &retval); 1.249 + if (NS_FAILED(rv)) 1.250 + return; 1.251 + if (!retval || !user || !pass) 1.252 + return; 1.253 + 1.254 + // XXX We need to convert the UTF-16 username and password from our dialog to 1.255 + // strings that GnomeVFS can understand. It's unclear what encoding GnomeVFS 1.256 + // expects, so for now we assume 7-bit ASCII. Hopefully, we can get a better 1.257 + // solution at some point. 1.258 + 1.259 + // One copy is never enough... 1.260 + authOut->username = g_strdup(NS_LossyConvertUTF16toASCII(user).get()); 1.261 + authOut->password = g_strdup(NS_LossyConvertUTF16toASCII(pass).get()); 1.262 + 1.263 + nsMemory::Free(user); 1.264 + nsMemory::Free(pass); 1.265 +} 1.266 + 1.267 +struct nsGnomeVFSAuthCallbackEvent : public nsRunnable 1.268 +{ 1.269 + gconstpointer in; 1.270 + gsize in_size; 1.271 + gpointer out; 1.272 + gsize out_size; 1.273 + gpointer callback_data; 1.274 + 1.275 + NS_IMETHOD Run() { 1.276 + ProxiedAuthCallback(in, in_size, out, out_size, callback_data); 1.277 + return NS_OK; 1.278 + } 1.279 +}; 1.280 + 1.281 +static void 1.282 +AuthCallback(gconstpointer in, 1.283 + gsize in_size, 1.284 + gpointer out, 1.285 + gsize out_size, 1.286 + gpointer callback_data) 1.287 +{ 1.288 + // Need to proxy this callback over to the main thread. Synchronous dispatch 1.289 + // is required in order to provide data to the GnomeVFS callback. 1.290 + 1.291 + nsRefPtr<nsGnomeVFSAuthCallbackEvent> ev = new nsGnomeVFSAuthCallbackEvent(); 1.292 + if (!ev) 1.293 + return; // OOM 1.294 + 1.295 + ev->in = in; 1.296 + ev->in_size = in_size; 1.297 + ev->out = out; 1.298 + ev->out_size = out_size; 1.299 + ev->callback_data = callback_data; 1.300 + 1.301 + NS_DispatchToMainThread(ev, NS_DISPATCH_SYNC); 1.302 +} 1.303 + 1.304 +//----------------------------------------------------------------------------- 1.305 + 1.306 +static gint 1.307 +FileInfoComparator(gconstpointer a, gconstpointer b) 1.308 +{ 1.309 + const GnomeVFSFileInfo *ia = (const GnomeVFSFileInfo *) a; 1.310 + const GnomeVFSFileInfo *ib = (const GnomeVFSFileInfo *) b; 1.311 + 1.312 + return strcasecmp(ia->name, ib->name); 1.313 +} 1.314 + 1.315 +//----------------------------------------------------------------------------- 1.316 + 1.317 +class nsGnomeVFSInputStream MOZ_FINAL : public nsIInputStream 1.318 +{ 1.319 + public: 1.320 + NS_DECL_THREADSAFE_ISUPPORTS 1.321 + NS_DECL_NSIINPUTSTREAM 1.322 + 1.323 + nsGnomeVFSInputStream(const nsCString &uriSpec) 1.324 + : mSpec(uriSpec) 1.325 + , mChannel(nullptr) 1.326 + , mHandle(nullptr) 1.327 + , mBytesRemaining(UINT64_MAX) 1.328 + , mStatus(NS_OK) 1.329 + , mDirList(nullptr) 1.330 + , mDirListPtr(nullptr) 1.331 + , mDirBufCursor(0) 1.332 + , mDirOpen(false) {} 1.333 + 1.334 + ~nsGnomeVFSInputStream() { Close(); } 1.335 + 1.336 + void SetChannel(nsIChannel *channel) 1.337 + { 1.338 + // We need to hold an owning reference to our channel. This is done 1.339 + // so we can access the channel's notification callbacks to acquire 1.340 + // a reference to a nsIAuthPrompt if we need to handle a GnomeVFS 1.341 + // authentication callback. 1.342 + // 1.343 + // However, the channel can only be accessed on the main thread, so 1.344 + // we have to be very careful with ownership. Moreover, it doesn't 1.345 + // support threadsafe addref/release, so proxying is the answer. 1.346 + // 1.347 + // Also, it's important to note that this likely creates a reference 1.348 + // cycle since the channel likely owns this stream. This reference 1.349 + // cycle is broken in our Close method. 1.350 + 1.351 + NS_ADDREF(mChannel = channel); 1.352 + } 1.353 + 1.354 + private: 1.355 + GnomeVFSResult DoOpen(); 1.356 + GnomeVFSResult DoRead(char *aBuf, uint32_t aCount, uint32_t *aCountRead); 1.357 + nsresult SetContentTypeOfChannel(const char *contentType); 1.358 + 1.359 + private: 1.360 + nsCString mSpec; 1.361 + nsIChannel *mChannel; // manually refcounted 1.362 + GnomeVFSHandle *mHandle; 1.363 + uint64_t mBytesRemaining; 1.364 + nsresult mStatus; 1.365 + GList *mDirList; 1.366 + GList *mDirListPtr; 1.367 + nsCString mDirBuf; 1.368 + uint32_t mDirBufCursor; 1.369 + bool mDirOpen; 1.370 +}; 1.371 + 1.372 +GnomeVFSResult 1.373 +nsGnomeVFSInputStream::DoOpen() 1.374 +{ 1.375 + GnomeVFSResult rv; 1.376 + 1.377 + NS_ASSERTION(mHandle == nullptr, "already open"); 1.378 + 1.379 + // Push a callback handler on the stack for this thread, so we can intercept 1.380 + // authentication requests from GnomeVFS. We'll use the channel to get a 1.381 + // nsIAuthPrompt instance. 1.382 + 1.383 + gnome_vfs_module_callback_push(GNOME_VFS_MODULE_CALLBACK_AUTHENTICATION, 1.384 + AuthCallback, mChannel, nullptr); 1.385 + 1.386 + // Query the mime type first (this could return nullptr). 1.387 + // 1.388 + // XXX We need to do this up-front in order to determine how to open the URI. 1.389 + // Unfortunately, the error code GNOME_VFS_ERROR_IS_DIRECTORY is not 1.390 + // always returned by gnome_vfs_open when we pass it a URI to a directory! 1.391 + // Otherwise, we could have used that as a way to failover to opening the 1.392 + // URI as a directory. Also, it would have been ideal if 1.393 + // gnome_vfs_get_file_info_from_handle were actually implemented by the 1.394 + // smb:// module, since that would have allowed us to potentially save a 1.395 + // round trip to the server to discover the mime type of the document in 1.396 + // the case where gnome_vfs_open would have been used. (Oh well! /me 1.397 + // throws hands up in the air and moves on...) 1.398 + 1.399 + GnomeVFSFileInfo info = {0}; 1.400 + rv = gnome_vfs_get_file_info(mSpec.get(), &info, GnomeVFSFileInfoOptions( 1.401 + GNOME_VFS_FILE_INFO_DEFAULT | 1.402 + GNOME_VFS_FILE_INFO_FOLLOW_LINKS)); 1.403 + if (rv == GNOME_VFS_OK) 1.404 + { 1.405 + if (info.type == GNOME_VFS_FILE_TYPE_DIRECTORY) 1.406 + { 1.407 + rv = gnome_vfs_directory_list_load(&mDirList, mSpec.get(), 1.408 + GNOME_VFS_FILE_INFO_DEFAULT); 1.409 + 1.410 + LOG(("gnomevfs: gnome_vfs_directory_list_load returned %d (%s) [spec=\"%s\"]\n", 1.411 + rv, gnome_vfs_result_to_string(rv), mSpec.get())); 1.412 + } 1.413 + else 1.414 + { 1.415 + rv = gnome_vfs_open(&mHandle, mSpec.get(), GNOME_VFS_OPEN_READ); 1.416 + 1.417 + LOG(("gnomevfs: gnome_vfs_open returned %d (%s) [spec=\"%s\"]\n", 1.418 + rv, gnome_vfs_result_to_string(rv), mSpec.get())); 1.419 + } 1.420 + } 1.421 + 1.422 + gnome_vfs_module_callback_pop(GNOME_VFS_MODULE_CALLBACK_AUTHENTICATION); 1.423 + 1.424 + if (rv == GNOME_VFS_OK) 1.425 + { 1.426 + if (mHandle) 1.427 + { 1.428 + // Here we set the content type of the channel to the value of the mime 1.429 + // type determined by GnomeVFS. However, if GnomeVFS is telling us that 1.430 + // the document is binary, we'll ignore that and keep the channel's 1.431 + // content type unspecified. That will enable our content type sniffing 1.432 + // algorithms. This should provide more consistent mime type handling. 1.433 + 1.434 + if (info.mime_type && (strcmp(info.mime_type, APPLICATION_OCTET_STREAM) != 0)) 1.435 + SetContentTypeOfChannel(info.mime_type); 1.436 + 1.437 + mBytesRemaining = info.size; 1.438 + 1.439 + // Update the content length attribute on the channel. We do this 1.440 + // synchronously without proxying. This hack is not as bad as it looks! 1.441 + if (mBytesRemaining > INT64_MAX) { 1.442 + mChannel->SetContentLength(-1); 1.443 + } else { 1.444 + mChannel->SetContentLength(mBytesRemaining); 1.445 + } 1.446 + } 1.447 + else 1.448 + { 1.449 + mDirOpen = true; 1.450 + 1.451 + // Sort mDirList 1.452 + mDirList = g_list_sort(mDirList, FileInfoComparator); 1.453 + mDirListPtr = mDirList; 1.454 + 1.455 + // Write base URL (make sure it ends with a '/') 1.456 + mDirBuf.Append("300: "); 1.457 + mDirBuf.Append(mSpec); 1.458 + if (mSpec.get()[mSpec.Length() - 1] != '/') 1.459 + mDirBuf.Append('/'); 1.460 + mDirBuf.Append('\n'); 1.461 + 1.462 + // Write column names 1.463 + mDirBuf.Append("200: filename content-length last-modified file-type\n"); 1.464 + 1.465 + // Write charset (assume UTF-8) 1.466 + // XXX is this correct? 1.467 + mDirBuf.Append("301: UTF-8\n"); 1.468 + 1.469 + SetContentTypeOfChannel(APPLICATION_HTTP_INDEX_FORMAT); 1.470 + } 1.471 + } 1.472 + 1.473 + gnome_vfs_file_info_clear(&info); 1.474 + return rv; 1.475 +} 1.476 + 1.477 +GnomeVFSResult 1.478 +nsGnomeVFSInputStream::DoRead(char *aBuf, uint32_t aCount, uint32_t *aCountRead) 1.479 +{ 1.480 + GnomeVFSResult rv; 1.481 + 1.482 + if (mHandle) 1.483 + { 1.484 + GnomeVFSFileSize bytesRead; 1.485 + rv = gnome_vfs_read(mHandle, aBuf, aCount, &bytesRead); 1.486 + if (rv == GNOME_VFS_OK) 1.487 + { 1.488 + // aCount is 32-bit, so aCountRead is under 32-bit value. 1.489 + *aCountRead = (uint32_t) bytesRead; 1.490 + mBytesRemaining -= *aCountRead; 1.491 + } 1.492 + } 1.493 + else if (mDirOpen) 1.494 + { 1.495 + rv = GNOME_VFS_OK; 1.496 + 1.497 + while (aCount && rv != GNOME_VFS_ERROR_EOF) 1.498 + { 1.499 + // Copy data out of our buffer 1.500 + uint32_t bufLen = mDirBuf.Length() - mDirBufCursor; 1.501 + if (bufLen) 1.502 + { 1.503 + uint32_t n = std::min(bufLen, aCount); 1.504 + memcpy(aBuf, mDirBuf.get() + mDirBufCursor, n); 1.505 + *aCountRead += n; 1.506 + aBuf += n; 1.507 + aCount -= n; 1.508 + mDirBufCursor += n; 1.509 + } 1.510 + 1.511 + if (!mDirListPtr) // Are we at the end of the directory list? 1.512 + { 1.513 + rv = GNOME_VFS_ERROR_EOF; 1.514 + } 1.515 + else if (aCount) // Do we need more data? 1.516 + { 1.517 + GnomeVFSFileInfo *info = (GnomeVFSFileInfo *) mDirListPtr->data; 1.518 + 1.519 + // Prune '.' and '..' from directory listing. 1.520 + if (info->name[0] == '.' && 1.521 + (info->name[1] == '\0' || 1.522 + (info->name[1] == '.' && info->name[2] == '\0'))) 1.523 + { 1.524 + mDirListPtr = mDirListPtr->next; 1.525 + continue; 1.526 + } 1.527 + 1.528 + mDirBuf.Assign("201: "); 1.529 + 1.530 + // The "filename" field 1.531 + nsCString escName; 1.532 + nsCOMPtr<nsINetUtil> nu = do_GetService(NS_NETUTIL_CONTRACTID); 1.533 + if (nu) { 1.534 + nu->EscapeString(nsDependentCString(info->name), 1.535 + nsINetUtil::ESCAPE_URL_PATH, escName); 1.536 + 1.537 + mDirBuf.Append(escName); 1.538 + mDirBuf.Append(' '); 1.539 + } 1.540 + 1.541 + // The "content-length" field 1.542 + // XXX truncates size from 64-bit to 32-bit 1.543 + mDirBuf.AppendInt(int32_t(info->size)); 1.544 + mDirBuf.Append(' '); 1.545 + 1.546 + // The "last-modified" field 1.547 + // 1.548 + // NSPR promises: PRTime is compatible with time_t 1.549 + // we just need to convert from seconds to microseconds 1.550 + PRExplodedTime tm; 1.551 + PRTime pt = ((PRTime) info->mtime) * 1000000; 1.552 + PR_ExplodeTime(pt, PR_GMTParameters, &tm); 1.553 + { 1.554 + char buf[64]; 1.555 + PR_FormatTimeUSEnglish(buf, sizeof(buf), 1.556 + "%a,%%20%d%%20%b%%20%Y%%20%H:%M:%S%%20GMT ", &tm); 1.557 + mDirBuf.Append(buf); 1.558 + } 1.559 + 1.560 + // The "file-type" field 1.561 + switch (info->type) 1.562 + { 1.563 + case GNOME_VFS_FILE_TYPE_REGULAR: 1.564 + mDirBuf.Append("FILE "); 1.565 + break; 1.566 + case GNOME_VFS_FILE_TYPE_DIRECTORY: 1.567 + mDirBuf.Append("DIRECTORY "); 1.568 + break; 1.569 + case GNOME_VFS_FILE_TYPE_SYMBOLIC_LINK: 1.570 + mDirBuf.Append("SYMBOLIC-LINK "); 1.571 + break; 1.572 + default: 1.573 + break; 1.574 + } 1.575 + 1.576 + mDirBuf.Append('\n'); 1.577 + 1.578 + mDirBufCursor = 0; 1.579 + mDirListPtr = mDirListPtr->next; 1.580 + } 1.581 + } 1.582 + } 1.583 + else 1.584 + { 1.585 + NS_NOTREACHED("reading from what?"); 1.586 + rv = GNOME_VFS_ERROR_GENERIC; 1.587 + } 1.588 + 1.589 + return rv; 1.590 +} 1.591 + 1.592 +// This class is used to implement SetContentTypeOfChannel. 1.593 +class nsGnomeVFSSetContentTypeEvent : public nsRunnable 1.594 +{ 1.595 + public: 1.596 + nsGnomeVFSSetContentTypeEvent(nsIChannel *channel, const char *contentType) 1.597 + : mChannel(channel), mContentType(contentType) 1.598 + { 1.599 + // stash channel reference in mChannel. no AddRef here! see note 1.600 + // in SetContentTypeOfchannel. 1.601 + } 1.602 + 1.603 + NS_IMETHOD Run() 1.604 + { 1.605 + mChannel->SetContentType(mContentType); 1.606 + return NS_OK; 1.607 + } 1.608 + 1.609 + private: 1.610 + nsIChannel *mChannel; 1.611 + nsCString mContentType; 1.612 +}; 1.613 + 1.614 +nsresult 1.615 +nsGnomeVFSInputStream::SetContentTypeOfChannel(const char *contentType) 1.616 +{ 1.617 + // We need to proxy this call over to the main thread. We post an 1.618 + // asynchronous event in this case so that we don't delay reading data, and 1.619 + // we know that this is safe to do since the channel's reference will be 1.620 + // released asynchronously as well. We trust the ordering of the main 1.621 + // thread's event queue to protect us against memory corruption. 1.622 + 1.623 + nsresult rv; 1.624 + nsCOMPtr<nsIRunnable> ev = 1.625 + new nsGnomeVFSSetContentTypeEvent(mChannel, contentType); 1.626 + if (!ev) 1.627 + { 1.628 + rv = NS_ERROR_OUT_OF_MEMORY; 1.629 + } 1.630 + else 1.631 + { 1.632 + rv = NS_DispatchToMainThread(ev); 1.633 + } 1.634 + return rv; 1.635 +} 1.636 + 1.637 +NS_IMPL_ISUPPORTS(nsGnomeVFSInputStream, nsIInputStream) 1.638 + 1.639 +NS_IMETHODIMP 1.640 +nsGnomeVFSInputStream::Close() 1.641 +{ 1.642 + if (mHandle) 1.643 + { 1.644 + gnome_vfs_close(mHandle); 1.645 + mHandle = nullptr; 1.646 + } 1.647 + 1.648 + if (mDirList) 1.649 + { 1.650 + // Destroy the list of GnomeVFSFileInfo objects... 1.651 + g_list_foreach(mDirList, (GFunc) gnome_vfs_file_info_unref, nullptr); 1.652 + g_list_free(mDirList); 1.653 + mDirList = nullptr; 1.654 + mDirListPtr = nullptr; 1.655 + } 1.656 + 1.657 + if (mChannel) 1.658 + { 1.659 + nsresult rv = NS_OK; 1.660 + 1.661 + nsCOMPtr<nsIThread> thread = do_GetMainThread(); 1.662 + if (thread) 1.663 + rv = NS_ProxyRelease(thread, mChannel); 1.664 + 1.665 + NS_ASSERTION(thread && NS_SUCCEEDED(rv), "leaking channel reference"); 1.666 + mChannel = nullptr; 1.667 + } 1.668 + 1.669 + mSpec.Truncate(); // free memory 1.670 + 1.671 + // Prevent future reads from re-opening the handle. 1.672 + if (NS_SUCCEEDED(mStatus)) 1.673 + mStatus = NS_BASE_STREAM_CLOSED; 1.674 + 1.675 + return NS_OK; 1.676 +} 1.677 + 1.678 +NS_IMETHODIMP 1.679 +nsGnomeVFSInputStream::Available(uint64_t *aResult) 1.680 +{ 1.681 + if (NS_FAILED(mStatus)) 1.682 + return mStatus; 1.683 + 1.684 + *aResult = mBytesRemaining; 1.685 + return NS_OK; 1.686 +} 1.687 + 1.688 +NS_IMETHODIMP 1.689 +nsGnomeVFSInputStream::Read(char *aBuf, 1.690 + uint32_t aCount, 1.691 + uint32_t *aCountRead) 1.692 +{ 1.693 + *aCountRead = 0; 1.694 + 1.695 + if (mStatus == NS_BASE_STREAM_CLOSED) 1.696 + return NS_OK; 1.697 + if (NS_FAILED(mStatus)) 1.698 + return mStatus; 1.699 + 1.700 + GnomeVFSResult rv = GNOME_VFS_OK; 1.701 + 1.702 + // If this is our first-time through here, then open the URI. 1.703 + if (!mHandle && !mDirOpen) 1.704 + rv = DoOpen(); 1.705 + 1.706 + if (rv == GNOME_VFS_OK) 1.707 + rv = DoRead(aBuf, aCount, aCountRead); 1.708 + 1.709 + if (rv != GNOME_VFS_OK) 1.710 + { 1.711 + // If we reach here, we hit some kind of error. EOF is not an error. 1.712 + mStatus = MapGnomeVFSResult(rv); 1.713 + if (mStatus == NS_BASE_STREAM_CLOSED) 1.714 + return NS_OK; 1.715 + 1.716 + LOG(("gnomevfs: result %d [%s] mapped to 0x%x\n", 1.717 + rv, gnome_vfs_result_to_string(rv), mStatus)); 1.718 + } 1.719 + return mStatus; 1.720 +} 1.721 + 1.722 +NS_IMETHODIMP 1.723 +nsGnomeVFSInputStream::ReadSegments(nsWriteSegmentFun aWriter, 1.724 + void *aClosure, 1.725 + uint32_t aCount, 1.726 + uint32_t *aResult) 1.727 +{ 1.728 + // There is no way to implement this using GnomeVFS, but fortunately 1.729 + // that doesn't matter. Because we are a blocking input stream, Necko 1.730 + // isn't going to call our ReadSegments method. 1.731 + NS_NOTREACHED("nsGnomeVFSInputStream::ReadSegments"); 1.732 + return NS_ERROR_NOT_IMPLEMENTED; 1.733 +} 1.734 + 1.735 +NS_IMETHODIMP 1.736 +nsGnomeVFSInputStream::IsNonBlocking(bool *aResult) 1.737 +{ 1.738 + *aResult = false; 1.739 + return NS_OK; 1.740 +} 1.741 + 1.742 +//----------------------------------------------------------------------------- 1.743 + 1.744 +class nsGnomeVFSProtocolHandler MOZ_FINAL : public nsIProtocolHandler 1.745 + , public nsIObserver 1.746 +{ 1.747 + public: 1.748 + NS_DECL_ISUPPORTS 1.749 + NS_DECL_NSIPROTOCOLHANDLER 1.750 + NS_DECL_NSIOBSERVER 1.751 + 1.752 + nsresult Init(); 1.753 + 1.754 + private: 1.755 + void InitSupportedProtocolsPref(nsIPrefBranch *prefs); 1.756 + bool IsSupportedProtocol(const nsCString &spec); 1.757 + 1.758 + nsCString mSupportedProtocols; 1.759 +}; 1.760 + 1.761 +NS_IMPL_ISUPPORTS(nsGnomeVFSProtocolHandler, nsIProtocolHandler, nsIObserver) 1.762 + 1.763 +nsresult 1.764 +nsGnomeVFSProtocolHandler::Init() 1.765 +{ 1.766 +#ifdef PR_LOGGING 1.767 + sGnomeVFSLog = PR_NewLogModule("gnomevfs"); 1.768 +#endif 1.769 + 1.770 + if (!gnome_vfs_initialized()) 1.771 + { 1.772 + if (!gnome_vfs_init()) 1.773 + { 1.774 + NS_WARNING("gnome_vfs_init failed"); 1.775 + return NS_ERROR_UNEXPECTED; 1.776 + } 1.777 + } 1.778 + 1.779 + nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); 1.780 + if (prefs) 1.781 + { 1.782 + InitSupportedProtocolsPref(prefs); 1.783 + prefs->AddObserver(MOZ_GNOMEVFS_SUPPORTED_PROTOCOLS, this, false); 1.784 + } 1.785 + 1.786 + return NS_OK; 1.787 +} 1.788 + 1.789 +void 1.790 +nsGnomeVFSProtocolHandler::InitSupportedProtocolsPref(nsIPrefBranch *prefs) 1.791 +{ 1.792 + // read preferences 1.793 + nsresult rv = prefs->GetCharPref(MOZ_GNOMEVFS_SUPPORTED_PROTOCOLS, 1.794 + getter_Copies(mSupportedProtocols)); 1.795 + if (NS_SUCCEEDED(rv)) { 1.796 + mSupportedProtocols.StripWhitespace(); 1.797 + ToLowerCase(mSupportedProtocols); 1.798 + } 1.799 + else 1.800 + mSupportedProtocols.Assign("smb:,sftp:"); // use defaults 1.801 + 1.802 + LOG(("gnomevfs: supported protocols \"%s\"\n", mSupportedProtocols.get())); 1.803 +} 1.804 + 1.805 +bool 1.806 +nsGnomeVFSProtocolHandler::IsSupportedProtocol(const nsCString &aSpec) 1.807 +{ 1.808 + const char *specString = aSpec.get(); 1.809 + const char *colon = strchr(specString, ':'); 1.810 + if (!colon) 1.811 + return false; 1.812 + 1.813 + uint32_t length = colon - specString + 1; 1.814 + 1.815 + // <scheme> + ':' 1.816 + nsCString scheme(specString, length); 1.817 + 1.818 + char *found = PL_strcasestr(mSupportedProtocols.get(), scheme.get()); 1.819 + if (!found) 1.820 + return false; 1.821 + 1.822 + if (found[length] != ',' && found[length] != '\0') 1.823 + return false; 1.824 + 1.825 + return true; 1.826 +} 1.827 + 1.828 +NS_IMETHODIMP 1.829 +nsGnomeVFSProtocolHandler::GetScheme(nsACString &aScheme) 1.830 +{ 1.831 + aScheme.Assign(MOZ_GNOMEVFS_SCHEME); 1.832 + return NS_OK; 1.833 +} 1.834 + 1.835 +NS_IMETHODIMP 1.836 +nsGnomeVFSProtocolHandler::GetDefaultPort(int32_t *aDefaultPort) 1.837 +{ 1.838 + *aDefaultPort = -1; 1.839 + return NS_OK; 1.840 +} 1.841 + 1.842 +NS_IMETHODIMP 1.843 +nsGnomeVFSProtocolHandler::GetProtocolFlags(uint32_t *aProtocolFlags) 1.844 +{ 1.845 + // Is URI_STD true of all GnomeVFS URI types? 1.846 + *aProtocolFlags = URI_STD | URI_DANGEROUS_TO_LOAD; 1.847 + return NS_OK; 1.848 +} 1.849 + 1.850 +NS_IMETHODIMP 1.851 +nsGnomeVFSProtocolHandler::NewURI(const nsACString &aSpec, 1.852 + const char *aOriginCharset, 1.853 + nsIURI *aBaseURI, 1.854 + nsIURI **aResult) 1.855 +{ 1.856 + const nsCString flatSpec(aSpec); 1.857 + LOG(("gnomevfs: NewURI [spec=%s]\n", flatSpec.get())); 1.858 + 1.859 + if (!aBaseURI) 1.860 + { 1.861 + // 1.862 + // XXX This check is used to limit the gnome-vfs protocols we support. For 1.863 + // security reasons, it is best that we limit the protocols we support to 1.864 + // those with known characteristics. We might want to lessen this 1.865 + // restriction if it proves to be too heavy handed. A black list of 1.866 + // protocols we don't want to support might be better. For example, we 1.867 + // probably don't want to try to load "start-here:" inside the browser. 1.868 + // There are others that fall into this category, which are best handled 1.869 + // externally by Nautilus (or another app like it). 1.870 + // 1.871 + if (!IsSupportedProtocol(flatSpec)) 1.872 + return NS_ERROR_UNKNOWN_PROTOCOL; 1.873 + 1.874 + // Verify that GnomeVFS supports this URI scheme. 1.875 + GnomeVFSURI *uri = gnome_vfs_uri_new(flatSpec.get()); 1.876 + if (!uri) 1.877 + return NS_ERROR_UNKNOWN_PROTOCOL; 1.878 + } 1.879 + 1.880 + // 1.881 + // XXX Can we really assume that all gnome-vfs URIs can be parsed using 1.882 + // nsStandardURL? We probably really need to implement nsIURI/nsIURL 1.883 + // in terms of the gnome_vfs_uri_XXX methods, but at least this works 1.884 + // correctly for smb:// URLs ;-) 1.885 + // 1.886 + // Also, it might not be possible to fully implement nsIURI/nsIURL in 1.887 + // terms of GnomeVFSURI since some Necko methods have no GnomeVFS 1.888 + // equivalent. 1.889 + // 1.890 + nsresult rv; 1.891 + nsCOMPtr<nsIStandardURL> url = 1.892 + do_CreateInstance(NS_STANDARDURL_CONTRACTID, &rv); 1.893 + if (NS_FAILED(rv)) 1.894 + return rv; 1.895 + 1.896 + rv = url->Init(nsIStandardURL::URLTYPE_STANDARD, -1, flatSpec, 1.897 + aOriginCharset, aBaseURI); 1.898 + if (NS_SUCCEEDED(rv)) 1.899 + rv = CallQueryInterface(url, aResult); 1.900 + 1.901 + return rv; 1.902 +} 1.903 + 1.904 +NS_IMETHODIMP 1.905 +nsGnomeVFSProtocolHandler::NewChannel(nsIURI *aURI, nsIChannel **aResult) 1.906 +{ 1.907 + NS_ENSURE_ARG_POINTER(aURI); 1.908 + nsresult rv; 1.909 + 1.910 + nsAutoCString spec; 1.911 + rv = aURI->GetSpec(spec); 1.912 + if (NS_FAILED(rv)) 1.913 + return rv; 1.914 + 1.915 + nsRefPtr<nsGnomeVFSInputStream> stream = new nsGnomeVFSInputStream(spec); 1.916 + if (!stream) 1.917 + { 1.918 + rv = NS_ERROR_OUT_OF_MEMORY; 1.919 + } 1.920 + else 1.921 + { 1.922 + // start out assuming an unknown content-type. we'll set the content-type 1.923 + // to something better once we open the URI. 1.924 + rv = NS_NewInputStreamChannel(aResult, aURI, stream, 1.925 + NS_LITERAL_CSTRING(UNKNOWN_CONTENT_TYPE)); 1.926 + if (NS_SUCCEEDED(rv)) 1.927 + stream->SetChannel(*aResult); 1.928 + } 1.929 + return rv; 1.930 +} 1.931 + 1.932 +NS_IMETHODIMP 1.933 +nsGnomeVFSProtocolHandler::AllowPort(int32_t aPort, 1.934 + const char *aScheme, 1.935 + bool *aResult) 1.936 +{ 1.937 + // Don't override anything. 1.938 + *aResult = false; 1.939 + return NS_OK; 1.940 +} 1.941 + 1.942 +NS_IMETHODIMP 1.943 +nsGnomeVFSProtocolHandler::Observe(nsISupports *aSubject, 1.944 + const char *aTopic, 1.945 + const char16_t *aData) 1.946 +{ 1.947 + if (strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) == 0) { 1.948 + nsCOMPtr<nsIPrefBranch> prefs = do_QueryInterface(aSubject); 1.949 + InitSupportedProtocolsPref(prefs); 1.950 + } 1.951 + return NS_OK; 1.952 +} 1.953 + 1.954 +//----------------------------------------------------------------------------- 1.955 + 1.956 +#define NS_GNOMEVFSPROTOCOLHANDLER_CID \ 1.957 +{ /* 9b6dc177-a2e4-49e1-9c98-0a8384de7f6c */ \ 1.958 + 0x9b6dc177, \ 1.959 + 0xa2e4, \ 1.960 + 0x49e1, \ 1.961 + {0x9c, 0x98, 0x0a, 0x83, 0x84, 0xde, 0x7f, 0x6c} \ 1.962 +} 1.963 + 1.964 +NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsGnomeVFSProtocolHandler, Init) 1.965 +NS_DEFINE_NAMED_CID(NS_GNOMEVFSPROTOCOLHANDLER_CID); 1.966 + 1.967 +static const mozilla::Module::CIDEntry kVFSCIDs[] = { 1.968 + { &kNS_GNOMEVFSPROTOCOLHANDLER_CID, false, nullptr, nsGnomeVFSProtocolHandlerConstructor }, 1.969 + { nullptr } 1.970 +}; 1.971 + 1.972 +static const mozilla::Module::ContractIDEntry kVFSContracts[] = { 1.973 + { NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX MOZ_GNOMEVFS_SCHEME, &kNS_GNOMEVFSPROTOCOLHANDLER_CID }, 1.974 + { nullptr } 1.975 +}; 1.976 + 1.977 +static const mozilla::Module kVFSModule = { 1.978 + mozilla::Module::kVersion, 1.979 + kVFSCIDs, 1.980 + kVFSContracts 1.981 +}; 1.982 + 1.983 +NSMODULE_DEFN(nsGnomeVFSModule) = &kVFSModule;