Back to index

lightning-sunbird  0.9+nobinonly
nsGnomeVFSProtocolHandler.cpp
Go to the documentation of this file.
00001 /* vim:set ts=2 sw=2 et cindent: */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is the Mozilla gnome-vfs extension.
00016  *
00017  * The Initial Developer of the Original Code is IBM Corporation.
00018  * Portions created by IBM Corporation are Copyright (C) 2004
00019  * IBM Corporation. All Rights Reserved.
00020  *
00021  * Contributor(s):
00022  *   Darin Fisher <darin@meer.net>
00023  *
00024  * Alternatively, the contents of this file may be used under the terms of
00025  * either the GNU General Public License Version 2 or later (the "GPL"), or
00026  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00027  * in which case the provisions of the GPL or the LGPL are applicable instead
00028  * of those above. If you wish to allow use of your version of this file only
00029  * under the terms of either the GPL or the LGPL, and not to allow others to
00030  * use your version of this file under the terms of the MPL, indicate your
00031  * decision by deleting the provisions above and replace them with the notice
00032  * and other provisions required by the GPL or the LGPL. If you do not delete
00033  * the provisions above, a recipient may use your version of this file under
00034  * the terms of any one of the MPL, the GPL or the LGPL.
00035  *
00036  * ***** END LICENSE BLOCK ***** */
00037 
00038 // GnomeVFS v2.2.2 is missing G_BEGIN_DECLS in gnome-vfs-module-callback.h
00039 extern "C" {
00040 #include <libgnomevfs/gnome-vfs.h>
00041 #include <libgnomevfs/gnome-vfs-standard-callbacks.h>
00042 #include <libgnomevfs/gnome-vfs-mime-utils.h>
00043 }
00044 
00045 #include "nsIGenericFactory.h"
00046 #include "nsIInterfaceRequestorUtils.h"
00047 #include "nsIPrefService.h"
00048 #include "nsIPrefBranch2.h"
00049 #include "nsIObserver.h"
00050 #include "nsEventQueueUtils.h"
00051 #include "nsProxyRelease.h"
00052 #include "nsIAuthPrompt.h"
00053 #include "nsIStringBundle.h"
00054 #include "nsIStandardURL.h"
00055 #include "nsIURL.h"
00056 #include "nsMimeTypes.h"
00057 #include "nsNetUtil.h"
00058 #include "nsAutoPtr.h"
00059 #include "nsError.h"
00060 #include "nsEscape.h"
00061 #include "nsCRT.h"
00062 #include "netCore.h"
00063 #include "prlog.h"
00064 #include "prtime.h"
00065 
00066 #define MOZ_GNOMEVFS_SCHEME              "moz-gnomevfs"
00067 #define MOZ_GNOMEVFS_SUPPORTED_PROTOCOLS "network.gnomevfs.supported-protocols"
00068 
00069 //-----------------------------------------------------------------------------
00070 
00071 // NSPR_LOG_MODULES=gnomevfs:5
00072 #ifdef PR_LOGGING
00073 static PRLogModuleInfo *sGnomeVFSLog;
00074 #define LOG(args) PR_LOG(sGnomeVFSLog, PR_LOG_DEBUG, args)
00075 #else
00076 #define LOG(args)
00077 #endif
00078 
00079 //-----------------------------------------------------------------------------
00080 
00081 static nsresult
00082 MapGnomeVFSResult(GnomeVFSResult result)
00083 {
00084   switch (result)
00085   {
00086     case GNOME_VFS_OK:                           return NS_OK;
00087     case GNOME_VFS_ERROR_NOT_FOUND:              return NS_ERROR_FILE_NOT_FOUND;
00088     case GNOME_VFS_ERROR_INTERNAL:               return NS_ERROR_UNEXPECTED;
00089     case GNOME_VFS_ERROR_BAD_PARAMETERS:         return NS_ERROR_INVALID_ARG;
00090     case GNOME_VFS_ERROR_NOT_SUPPORTED:          return NS_ERROR_NOT_AVAILABLE;
00091     case GNOME_VFS_ERROR_CORRUPTED_DATA:         return NS_ERROR_FILE_CORRUPTED;
00092     case GNOME_VFS_ERROR_TOO_BIG:                return NS_ERROR_FILE_TOO_BIG;
00093     case GNOME_VFS_ERROR_NO_SPACE:               return NS_ERROR_FILE_NO_DEVICE_SPACE;
00094     case GNOME_VFS_ERROR_READ_ONLY:
00095     case GNOME_VFS_ERROR_READ_ONLY_FILE_SYSTEM:  return NS_ERROR_FILE_READ_ONLY;
00096     case GNOME_VFS_ERROR_INVALID_URI:
00097     case GNOME_VFS_ERROR_INVALID_HOST_NAME:      return NS_ERROR_MALFORMED_URI;
00098     case GNOME_VFS_ERROR_ACCESS_DENIED:
00099     case GNOME_VFS_ERROR_NOT_PERMITTED:
00100     case GNOME_VFS_ERROR_LOGIN_FAILED:           return NS_ERROR_FILE_ACCESS_DENIED;
00101     case GNOME_VFS_ERROR_EOF:                    return NS_BASE_STREAM_CLOSED;
00102     case GNOME_VFS_ERROR_NOT_A_DIRECTORY:        return NS_ERROR_FILE_NOT_DIRECTORY;
00103     case GNOME_VFS_ERROR_IN_PROGRESS:            return NS_ERROR_IN_PROGRESS;
00104     case GNOME_VFS_ERROR_FILE_EXISTS:            return NS_ERROR_FILE_ALREADY_EXISTS;
00105     case GNOME_VFS_ERROR_IS_DIRECTORY:           return NS_ERROR_FILE_IS_DIRECTORY;
00106     case GNOME_VFS_ERROR_NO_MEMORY:              return NS_ERROR_OUT_OF_MEMORY;
00107     case GNOME_VFS_ERROR_HOST_NOT_FOUND:
00108     case GNOME_VFS_ERROR_HOST_HAS_NO_ADDRESS:    return NS_ERROR_UNKNOWN_HOST;
00109     case GNOME_VFS_ERROR_CANCELLED:
00110     case GNOME_VFS_ERROR_INTERRUPTED:            return NS_ERROR_ABORT;
00111     case GNOME_VFS_ERROR_DIRECTORY_NOT_EMPTY:    return NS_ERROR_FILE_DIR_NOT_EMPTY;
00112     case GNOME_VFS_ERROR_NAME_TOO_LONG:          return NS_ERROR_FILE_NAME_TOO_LONG;
00113     case GNOME_VFS_ERROR_SERVICE_NOT_AVAILABLE:  return NS_ERROR_UNKNOWN_PROTOCOL;
00114 
00115     /* No special mapping for these error codes...
00116 
00117     case GNOME_VFS_ERROR_GENERIC:
00118     case GNOME_VFS_ERROR_IO:
00119     case GNOME_VFS_ERROR_WRONG_FORMAT:
00120     case GNOME_VFS_ERROR_BAD_FILE:
00121     case GNOME_VFS_ERROR_NOT_OPEN:
00122     case GNOME_VFS_ERROR_INVALID_OPEN_MODE:
00123     case GNOME_VFS_ERROR_TOO_MANY_OPEN_FILES:
00124     case GNOME_VFS_ERROR_LOOP:
00125     case GNOME_VFS_ERROR_DIRECTORY_BUSY:
00126     case GNOME_VFS_ERROR_TOO_MANY_LINKS:
00127     case GNOME_VFS_ERROR_NOT_SAME_FILE_SYSTEM:
00128     case GNOME_VFS_ERROR_SERVICE_OBSOLETE:
00129     case GNOME_VFS_ERROR_PROTOCOL_ERROR:
00130     case GNOME_VFS_ERROR_NO_MASTER_BROWSER:
00131 
00132     */
00133 
00134     // Make GCC happy
00135     default:
00136       return NS_ERROR_FAILURE;
00137   }
00138 
00139   return NS_ERROR_FAILURE;
00140 }
00141 
00142 //-----------------------------------------------------------------------------
00143 
00144 static void
00145 ProxiedAuthCallback(gconstpointer in,
00146                     gsize         in_size,
00147                     gpointer      out,
00148                     gsize         out_size,
00149                     gpointer      callback_data)
00150 {
00151   GnomeVFSModuleCallbackAuthenticationIn *authIn =
00152       (GnomeVFSModuleCallbackAuthenticationIn *) in;
00153   GnomeVFSModuleCallbackAuthenticationOut *authOut =
00154       (GnomeVFSModuleCallbackAuthenticationOut *) out;
00155 
00156   LOG(("gnomevfs: ProxiedAuthCallback [uri=%s]\n", authIn->uri));
00157 
00158   // Without a channel, we have no way of getting a prompter.
00159   nsIChannel *channel = (nsIChannel *) callback_data;
00160   if (!channel)
00161     return;
00162 
00163   nsCOMPtr<nsIAuthPrompt> prompt;
00164   NS_QueryNotificationCallbacks(channel, prompt);
00165 
00166   // If no auth prompt, then give up.  We could failover to using the
00167   // WindowWatcher service, but that might defeat a consumer's purposeful
00168   // attempt to disable authentication (for whatever reason).
00169   if (!prompt)
00170     return;
00171 
00172   // Parse out the host and port...
00173   nsCOMPtr<nsIURI> uri;
00174   channel->GetURI(getter_AddRefs(uri));
00175   if (!uri)
00176     return;
00177 
00178 #ifdef DEBUG
00179   {
00180     //
00181     // Make sure authIn->uri is consistent with the channel's URI.
00182     //
00183     // XXX This check is probably not IDN safe, and it might incorrectly
00184     //     fire as a result of escaping differences.  It's unclear what
00185     //     kind of transforms GnomeVFS might have applied to the URI spec
00186     //     that we originally gave to it.  In spite of the likelihood of
00187     //     false hits, this check is probably still valuable.
00188     //
00189     nsCAutoString spec;
00190     uri->GetSpec(spec);
00191     int uriLen = strlen(authIn->uri);
00192     if (!StringHead(spec, uriLen).Equals(nsDependentCString(authIn->uri, uriLen)))
00193     {
00194       LOG(("gnomevfs: [spec=%s authIn->uri=%s]\n", spec.get(), authIn->uri));
00195       NS_ERROR("URI mismatch");
00196     }
00197   }
00198 #endif
00199 
00200   nsCAutoString scheme, hostPort;
00201   uri->GetScheme(scheme);
00202   uri->GetHostPort(hostPort);
00203 
00204   // It doesn't make sense for either of these strings to be empty.  What kind
00205   // of funky URI is this?
00206   if (scheme.IsEmpty() || hostPort.IsEmpty())
00207     return;
00208 
00209   // Construct the single signon key.  Altering the value of this key will
00210   // cause peoples remembered passwords to be forgotten.  Think carefully
00211   // before changing the way this key is constructed.
00212   nsAutoString key, dispHost, realm;
00213   AppendUTF8toUTF16(scheme + NS_LITERAL_CSTRING("://") + hostPort, dispHost);
00214   key = dispHost;
00215   if (authIn->realm)
00216   {
00217     // We assume the realm string is ASCII.  That might be a bogus assumption,
00218     // but we have no idea what encoding GnomeVFS is using, so for now we'll
00219     // limit ourselves to ISO-Latin-1.  XXX What is a better solution?
00220     realm.Append(PRUnichar('"'));
00221     realm.AppendWithConversion(authIn->realm);
00222     realm.Append(PRUnichar('"'));
00223     key += NS_LITERAL_STRING(" ") + realm;
00224   }
00225 
00226   // Construct the message string...
00227   //
00228   // We use Necko's string bundle here.  This code really should be encapsulated
00229   // behind some Necko API, after all this code is based closely on the code in
00230   // nsHttpChannel.cpp.
00231 
00232   nsCOMPtr<nsIStringBundleService> bundleSvc =
00233       do_GetService(NS_STRINGBUNDLE_CONTRACTID);
00234   if (!bundleSvc)
00235     return;
00236 
00237   nsCOMPtr<nsIStringBundle> bundle;
00238   bundleSvc->CreateBundle(NECKO_MSGS_URL, getter_AddRefs(bundle));
00239   if (!bundle)
00240     return;
00241 
00242   nsXPIDLString message;
00243   if (!realm.IsEmpty())
00244   {
00245     const PRUnichar *strings[] = { realm.get(), dispHost.get() };
00246     bundle->FormatStringFromName(NS_LITERAL_STRING("EnterUserPasswordForRealm").get(),
00247                                  strings, 2, getter_Copies(message));
00248   }
00249   else
00250   {
00251     const PRUnichar *strings[] = { dispHost.get() };
00252     bundle->FormatStringFromName(NS_LITERAL_STRING("EnterUserPasswordFor").get(),
00253                                  strings, 1, getter_Copies(message));
00254   }
00255   if (!message)
00256     return;
00257 
00258   // Prompt the user...
00259   nsresult rv;
00260   PRBool retval = PR_FALSE;
00261   PRUnichar *user = nsnull, *pass = nsnull;
00262 
00263   rv = prompt->PromptUsernameAndPassword(nsnull, message.get(),
00264                                          key.get(),
00265                                          nsIAuthPrompt::SAVE_PASSWORD_PERMANENTLY,
00266                                          &user, &pass, &retval);
00267   if (NS_FAILED(rv))
00268     return;
00269   if (!retval || !user || !pass)
00270     return;
00271 
00272   // XXX We need to convert the UTF-16 username and password from our dialog to
00273   // strings that GnomeVFS can understand.  It's unclear what encoding GnomeVFS
00274   // expects, so for now we assume 7-bit ASCII.  Hopefully, we can get a better
00275   // solution at some point.
00276 
00277   // One copy is never enough...
00278   authOut->username = g_strdup(NS_LossyConvertUTF16toASCII(user).get());
00279   authOut->password = g_strdup(NS_LossyConvertUTF16toASCII(pass).get());
00280 
00281   nsMemory::Free(user);
00282   nsMemory::Free(pass);
00283 }
00284 
00285 struct nsGnomeVFSAuthParams
00286 {
00287   gconstpointer in;
00288   gsize         in_size;
00289   gpointer      out;
00290   gsize         out_size;
00291   gpointer      callback_data;
00292 };
00293 
00294 PR_STATIC_CALLBACK(void *)
00295 AuthCallbackEventHandler(PLEvent *ev)
00296 {
00297   nsGnomeVFSAuthParams *params = (nsGnomeVFSAuthParams *) ev->owner;
00298   ProxiedAuthCallback(params->in, params->in_size,
00299                       params->out, params->out_size,
00300                       params->callback_data);
00301   return nsnull;
00302 }
00303 
00304 PR_STATIC_CALLBACK(void)
00305 AuthCallbackEventDestructor(PLEvent *ev)
00306 {
00307   // ignored
00308 }
00309 
00310 static void
00311 AuthCallback(gconstpointer in,
00312              gsize         in_size,
00313              gpointer      out,
00314              gsize         out_size,
00315              gpointer      callback_data)
00316 {
00317   // Need to proxy this callback over to the main thread.  This code is greatly
00318   // simplified by the fact that we are making a synchronous callback.  E.g., we
00319   // don't need to allocate the PLEvent on the heap.
00320 
00321   nsCOMPtr<nsIEventQueue> eventQ;
00322   NS_GetMainEventQ(getter_AddRefs(eventQ));
00323   if (!eventQ)
00324     return;
00325 
00326   nsGnomeVFSAuthParams params;
00327   params.in = in;
00328   params.in_size = in_size;
00329   params.out = out;
00330   params.out_size = out_size;
00331   params.callback_data = callback_data;
00332 
00333   PLEvent ev;
00334   PL_InitEvent(&ev, &params,
00335       AuthCallbackEventHandler,
00336       AuthCallbackEventDestructor);
00337 
00338   void *result;
00339   if (NS_FAILED(eventQ->PostSynchronousEvent(&ev, &result)))
00340     PL_DestroyEvent(&ev);
00341 }
00342 
00343 //-----------------------------------------------------------------------------
00344 
00345 static gint
00346 FileInfoComparator(gconstpointer a, gconstpointer b)
00347 {
00348   const GnomeVFSFileInfo *ia = (const GnomeVFSFileInfo *) a;
00349   const GnomeVFSFileInfo *ib = (const GnomeVFSFileInfo *) b;
00350 
00351   return strcasecmp(ia->name, ib->name);
00352 }
00353 
00354 //-----------------------------------------------------------------------------
00355 
00356 class nsGnomeVFSInputStream : public nsIInputStream
00357 {
00358   public:
00359     NS_DECL_ISUPPORTS
00360     NS_DECL_NSIINPUTSTREAM
00361 
00362     nsGnomeVFSInputStream(const nsCString &uriSpec)
00363       : mSpec(uriSpec)
00364       , mChannel(nsnull)
00365       , mHandle(nsnull)
00366       , mBytesRemaining(PR_UINT32_MAX)
00367       , mStatus(NS_OK)
00368       , mDirList(nsnull)
00369       , mDirListPtr(nsnull)
00370       , mDirBufCursor(0)
00371       , mDirOpen(PR_FALSE) {}
00372 
00373    ~nsGnomeVFSInputStream() { Close(); }
00374 
00375     void SetChannel(nsIChannel *channel)
00376     {
00377       // We need to hold an owning reference to our channel.  This is done
00378       // so we can access the channel's notification callbacks to acquire
00379       // a reference to a nsIAuthPrompt if we need to handle a GnomeVFS
00380       // authentication callback.
00381       //
00382       // However, the channel can only be accessed on the main thread, so
00383       // we have to be very careful with ownership.  Moreover, it doesn't
00384       // support threadsafe addref/release, so proxying is the answer.
00385       //
00386       // Also, it's important to note that this likely creates a reference
00387       // cycle since the channel likely owns this stream.  This reference
00388       // cycle is broken in our Close method.
00389 
00390       NS_ADDREF(mChannel = channel);
00391     }
00392 
00393   private:
00394     GnomeVFSResult DoOpen();
00395     GnomeVFSResult DoRead(char *aBuf, PRUint32 aCount, PRUint32 *aCountRead);
00396     nsresult       SetContentTypeOfChannel(const char *contentType);
00397 
00398   private:
00399     nsCString                mSpec;
00400     nsIChannel              *mChannel; // manually refcounted
00401     GnomeVFSHandle          *mHandle;
00402     PRUint32                 mBytesRemaining;
00403     nsresult                 mStatus;
00404     GList                   *mDirList;
00405     GList                   *mDirListPtr;
00406     nsCString                mDirBuf;
00407     PRUint32                 mDirBufCursor;
00408     PRPackedBool             mDirOpen;
00409 };
00410 
00411 GnomeVFSResult
00412 nsGnomeVFSInputStream::DoOpen()
00413 {
00414   GnomeVFSResult rv;
00415 
00416   NS_ASSERTION(mHandle == nsnull, "already open");
00417 
00418   // Push a callback handler on the stack for this thread, so we can intercept
00419   // authentication requests from GnomeVFS.  We'll use the channel to get a
00420   // nsIAuthPrompt instance.
00421 
00422   gnome_vfs_module_callback_push(GNOME_VFS_MODULE_CALLBACK_AUTHENTICATION,
00423                                  AuthCallback, mChannel, NULL);
00424 
00425   // Query the mime type first (this could return NULL). 
00426   //
00427   // XXX We need to do this up-front in order to determine how to open the URI.
00428   //     Unfortunately, the error code GNOME_VFS_ERROR_IS_DIRECTORY is not
00429   //     always returned by gnome_vfs_open when we pass it a URI to a directory!
00430   //     Otherwise, we could have used that as a way to failover to opening the
00431   //     URI as a directory.  Also, it would have been ideal if
00432   //     gnome_vfs_get_file_info_from_handle were actually implemented by the
00433   //     smb:// module, since that would have allowed us to potentially save a
00434   //     round trip to the server to discover the mime type of the document in
00435   //     the case where gnome_vfs_open would have been used.  (Oh well!  /me
00436   //     throws hands up in the air and moves on...)
00437 
00438   GnomeVFSFileInfo info = {0};
00439   rv = gnome_vfs_get_file_info(mSpec.get(), &info, GNOME_VFS_FILE_INFO_DEFAULT);
00440   if (rv == GNOME_VFS_OK && info.type == GNOME_VFS_FILE_TYPE_DIRECTORY)
00441   {
00442     rv = gnome_vfs_directory_list_load(&mDirList, mSpec.get(),
00443                                        GNOME_VFS_FILE_INFO_DEFAULT);
00444 
00445     LOG(("gnomevfs: gnome_vfs_directory_list_load returned %d (%s) [spec=\"%s\"]\n",
00446         rv, gnome_vfs_result_to_string(rv), mSpec.get()));
00447   }
00448   else
00449   {
00450     rv = gnome_vfs_open(&mHandle, mSpec.get(), GNOME_VFS_OPEN_READ);
00451 
00452     LOG(("gnomevfs: gnome_vfs_open returned %d (%s) [spec=\"%s\"]\n",
00453         rv, gnome_vfs_result_to_string(rv), mSpec.get()));
00454   }
00455 
00456   gnome_vfs_module_callback_pop(GNOME_VFS_MODULE_CALLBACK_AUTHENTICATION);
00457 
00458   if (rv == GNOME_VFS_OK)
00459   {
00460     if (mHandle)
00461     {
00462       // Here we set the content type of the channel to the value of the mime
00463       // type determined by GnomeVFS.  However, if GnomeVFS is telling us that
00464       // the document is binary, we'll ignore that and keep the channel's
00465       // content type unspecified.  That will enable our content type sniffing
00466       // algorithms.  This should provide more consistent mime type handling.
00467 
00468       if (info.mime_type && (strcmp(info.mime_type, APPLICATION_OCTET_STREAM) != 0))
00469         SetContentTypeOfChannel(info.mime_type);
00470 
00471       // XXX truncates size from 64-bit to 32-bit
00472       mBytesRemaining = (PRUint32) info.size;
00473 
00474       // Update the content length attribute on the channel.  We do this
00475       // synchronously without proxying.  This hack is not as bad as it looks!
00476       if (mBytesRemaining != PR_UINT32_MAX)
00477         mChannel->SetContentLength(mBytesRemaining);
00478     }
00479     else
00480     {
00481       mDirOpen = PR_TRUE;
00482 
00483       // Sort mDirList
00484       mDirList = g_list_sort(mDirList, FileInfoComparator);
00485       mDirListPtr = mDirList;
00486 
00487       // Write base URL (make sure it ends with a '/')
00488       mDirBuf.Append(NS_LITERAL_CSTRING("300: ") + mSpec);
00489       if (mSpec.Last() != '/')
00490         mDirBuf.Append('/');
00491       mDirBuf.Append('\n');
00492 
00493       // Write column names
00494       mDirBuf.Append("200: filename content-length last-modified file-type\n");
00495 
00496       // Write charset (assume UTF-8)
00497       // XXX is this correct?
00498       mDirBuf.Append("301: UTF-8\n");
00499 
00500       SetContentTypeOfChannel(APPLICATION_HTTP_INDEX_FORMAT);
00501     }
00502   }
00503 
00504   gnome_vfs_file_info_clear(&info);
00505   return rv;
00506 }
00507 
00508 GnomeVFSResult
00509 nsGnomeVFSInputStream::DoRead(char *aBuf, PRUint32 aCount, PRUint32 *aCountRead)
00510 {
00511   GnomeVFSResult rv;
00512 
00513   if (mHandle)
00514   {
00515     GnomeVFSFileSize bytesRead;
00516     rv = gnome_vfs_read(mHandle, aBuf, aCount, &bytesRead);
00517     if (rv == GNOME_VFS_OK)
00518     {
00519       *aCountRead = (PRUint32) bytesRead;
00520       mBytesRemaining -= *aCountRead;
00521     }
00522   }
00523   else if (mDirOpen)
00524   {
00525     rv = GNOME_VFS_OK;
00526 
00527     while (aCount && rv != GNOME_VFS_ERROR_EOF)
00528     {
00529       // Copy data out of our buffer
00530       PRUint32 bufLen = mDirBuf.Length() - mDirBufCursor;
00531       if (bufLen)
00532       {
00533         PRUint32 n = PR_MIN(bufLen, aCount);
00534         memcpy(aBuf, mDirBuf.get() + mDirBufCursor, n);
00535         *aCountRead += n;
00536         aBuf += n;
00537         aCount -= n;
00538         mDirBufCursor += n;
00539       }
00540 
00541       if (!mDirListPtr)    // Are we at the end of the directory list?
00542       {
00543         rv = GNOME_VFS_ERROR_EOF;
00544       }
00545       else if (aCount)     // Do we need more data?
00546       {
00547         GnomeVFSFileInfo *info = (GnomeVFSFileInfo *) mDirListPtr->data;
00548 
00549         // Prune '.' and '..' from directory listing.
00550         if (info->name[0] == '.' &&
00551                (info->name[1] == '\0' ||
00552                    (info->name[1] == '.' && info->name[2] == '\0')))
00553         {
00554           mDirListPtr = mDirListPtr->next;
00555           continue;
00556         }
00557 
00558         mDirBuf.Assign("201: ");
00559 
00560         // The "filename" field
00561         char *escName = nsEscape(info->name, url_Path);
00562         if (escName)
00563         {
00564           mDirBuf.Append(escName);
00565           mDirBuf.Append(' ');
00566           nsMemory::Free(escName);
00567         }
00568 
00569         // The "content-length" field
00570         // XXX truncates size from 64-bit to 32-bit
00571         mDirBuf.AppendInt(PRInt32(info->size));
00572         mDirBuf.Append(' ');
00573 
00574         // The "last-modified" field
00575         // 
00576         // NSPR promises: PRTime is compatible with time_t
00577         // we just need to convert from seconds to microseconds
00578         PRExplodedTime tm;
00579         PRTime pt = ((PRTime) info->mtime) * 1000000;
00580         PR_ExplodeTime(pt, PR_GMTParameters, &tm);
00581         {
00582           char buf[64];
00583           PR_FormatTimeUSEnglish(buf, sizeof(buf),
00584               "%a,%%20%d%%20%b%%20%Y%%20%H:%M:%S%%20GMT ", &tm);
00585           mDirBuf.Append(buf);
00586         }
00587 
00588         // The "file-type" field
00589         switch (info->type)
00590         {
00591           case GNOME_VFS_FILE_TYPE_REGULAR:
00592             mDirBuf.AppendLiteral("FILE ");
00593             break;
00594           case GNOME_VFS_FILE_TYPE_DIRECTORY:
00595             mDirBuf.AppendLiteral("DIRECTORY ");
00596             break;
00597           case GNOME_VFS_FILE_TYPE_SYMBOLIC_LINK:
00598             mDirBuf.AppendLiteral("SYMBOLIC-LINK ");
00599             break;
00600           default:
00601             break;
00602         }
00603 
00604         mDirBuf.Append('\n');
00605 
00606         mDirBufCursor = 0;
00607         mDirListPtr = mDirListPtr->next;
00608       }
00609     }
00610   }
00611   else
00612   {
00613     NS_NOTREACHED("reading from what?");
00614     rv = GNOME_VFS_ERROR_GENERIC;
00615   }
00616 
00617   return rv;
00618 }
00619 
00620 // This class is used to implement SetContentTypeOfChannel.
00621 class nsGnomeVFSSetContentTypeEvent : public PLEvent
00622 {
00623   public:
00624     nsGnomeVFSSetContentTypeEvent(nsIChannel *channel, const char *contentType)
00625       : mContentType(contentType)
00626     {
00627       // stash channel reference in owner field.  no AddRef here!  see note
00628       // in SetContentTypeOfchannel.
00629       PL_InitEvent(this, channel, EventHandler, EventDestructor);
00630     }
00631 
00632     PR_STATIC_CALLBACK(void *) EventHandler(PLEvent *ev)
00633     {
00634       nsGnomeVFSSetContentTypeEvent *self = (nsGnomeVFSSetContentTypeEvent *) ev;
00635       ((nsIChannel *) self->owner)->SetContentType(self->mContentType);
00636       return nsnull;
00637     }
00638 
00639     PR_STATIC_CALLBACK(void) EventDestructor(PLEvent *ev)
00640     {
00641       nsGnomeVFSSetContentTypeEvent *self = (nsGnomeVFSSetContentTypeEvent *) ev;
00642       delete self;
00643     }
00644 
00645   private: 
00646     nsCString mContentType;
00647 };
00648 
00649 nsresult
00650 nsGnomeVFSInputStream::SetContentTypeOfChannel(const char *contentType)
00651 {
00652   // We need to proxy this call over to the main thread.  We post an
00653   // asynchronous event in this case so that we don't delay reading data, and
00654   // we know that this is safe to do since the channel's reference will be
00655   // released asynchronously as well.  We trust the ordering of the main
00656   // thread's event queue to protect us against memory corruption.
00657 
00658   nsresult rv;
00659   nsCOMPtr<nsIEventQueue> eventQ;
00660   rv = NS_GetMainEventQ(getter_AddRefs(eventQ));
00661   if (NS_FAILED(rv))
00662     return rv;
00663 
00664   nsGnomeVFSSetContentTypeEvent *ev =
00665       new nsGnomeVFSSetContentTypeEvent(mChannel, contentType);
00666   if (!ev)
00667   {
00668     rv = NS_ERROR_OUT_OF_MEMORY;
00669   }
00670   else
00671   {
00672     rv = eventQ->PostEvent(ev);
00673     if (NS_FAILED(rv))
00674       PL_DestroyEvent(ev);
00675   }
00676   return rv;
00677 }
00678 
00679 NS_IMPL_THREADSAFE_ISUPPORTS1(nsGnomeVFSInputStream, nsIInputStream)
00680 
00681 NS_IMETHODIMP
00682 nsGnomeVFSInputStream::Close()
00683 {
00684   if (mHandle)
00685   {
00686     gnome_vfs_close(mHandle);
00687     mHandle = nsnull;
00688   }
00689 
00690   if (mDirList)
00691   {
00692     // Destroy the list of GnomeVFSFileInfo objects...
00693     g_list_foreach(mDirList, (GFunc) gnome_vfs_file_info_unref, nsnull);
00694     g_list_free(mDirList);
00695     mDirList = nsnull;
00696     mDirListPtr = nsnull;
00697   }
00698 
00699   if (mChannel)
00700   {
00701     nsresult rv;
00702 
00703     nsCOMPtr<nsIEventQueue> eventQ;
00704     rv = NS_GetMainEventQ(getter_AddRefs(eventQ));
00705     if (NS_SUCCEEDED(rv))
00706       rv = NS_ProxyRelease(eventQ, mChannel);
00707 
00708     NS_ASSERTION(NS_SUCCEEDED(rv), "leaking channel reference");
00709     mChannel = nsnull;
00710   }
00711 
00712   mSpec.Truncate(); // free memory
00713 
00714   // Prevent future reads from re-opening the handle.
00715   if (NS_SUCCEEDED(mStatus))
00716     mStatus = NS_BASE_STREAM_CLOSED;
00717 
00718   return NS_OK;
00719 }
00720 
00721 NS_IMETHODIMP
00722 nsGnomeVFSInputStream::Available(PRUint32 *aResult)
00723 {
00724   if (NS_FAILED(mStatus))
00725     return mStatus;
00726 
00727   *aResult = mBytesRemaining;
00728   return NS_OK;
00729 }
00730 
00731 NS_IMETHODIMP
00732 nsGnomeVFSInputStream::Read(char *aBuf,
00733                             PRUint32 aCount,
00734                             PRUint32 *aCountRead)
00735 {
00736   *aCountRead = 0;
00737 
00738   if (NS_FAILED(mStatus))
00739     return mStatus;
00740 
00741   GnomeVFSResult rv = GNOME_VFS_OK;
00742 
00743   // If this is our first-time through here, then open the URI.
00744   if (!mHandle && !mDirOpen)
00745     rv = DoOpen();
00746   
00747   if (rv == GNOME_VFS_OK)
00748     rv = DoRead(aBuf, aCount, aCountRead);
00749 
00750   if (rv != GNOME_VFS_OK)
00751   {
00752     // If we reach here, we hit some kind of error.  EOF is not an error.
00753     mStatus = MapGnomeVFSResult(rv);
00754     if (mStatus == NS_BASE_STREAM_CLOSED)
00755       return NS_OK;
00756 
00757     LOG(("gnomevfs: result %d [%s] mapped to 0x%x\n",
00758         rv, gnome_vfs_result_to_string(rv), mStatus));
00759   }
00760   return mStatus;
00761 }
00762 
00763 NS_IMETHODIMP
00764 nsGnomeVFSInputStream::ReadSegments(nsWriteSegmentFun aWriter,
00765                                     void *aClosure,
00766                                     PRUint32 aCount,
00767                                     PRUint32 *aResult)
00768 {
00769   // There is no way to implement this using GnomeVFS, but fortunately
00770   // that doesn't matter.  Because we are a blocking input stream, Necko
00771   // isn't going to call our ReadSegments method.
00772   NS_NOTREACHED("nsGnomeVFSInputStream::ReadSegments");
00773   return NS_ERROR_NOT_IMPLEMENTED;
00774 }
00775 
00776 NS_IMETHODIMP
00777 nsGnomeVFSInputStream::IsNonBlocking(PRBool *aResult)
00778 {
00779   *aResult = PR_FALSE;
00780   return NS_OK;
00781 }
00782 
00783 //-----------------------------------------------------------------------------
00784 
00785 class nsGnomeVFSProtocolHandler : public nsIProtocolHandler
00786                                 , public nsIObserver
00787 {
00788   public:
00789     NS_DECL_ISUPPORTS
00790     NS_DECL_NSIPROTOCOLHANDLER
00791     NS_DECL_NSIOBSERVER
00792 
00793     nsresult Init();
00794 
00795   private:
00796     void   InitSupportedProtocolsPref(nsIPrefBranch *prefs);
00797     PRBool IsSupportedProtocol(const nsCString &spec);
00798 
00799     nsXPIDLCString mSupportedProtocols;
00800 };
00801 
00802 NS_IMPL_ISUPPORTS2(nsGnomeVFSProtocolHandler, nsIProtocolHandler, nsIObserver)
00803 
00804 nsresult
00805 nsGnomeVFSProtocolHandler::Init()
00806 {
00807 #ifdef PR_LOGGING
00808   sGnomeVFSLog = PR_NewLogModule("gnomevfs");
00809 #endif
00810 
00811   if (!gnome_vfs_initialized())
00812   {
00813     if (!gnome_vfs_init())
00814     {
00815       NS_WARNING("gnome_vfs_init failed");
00816       return NS_ERROR_UNEXPECTED;
00817     }
00818   }
00819 
00820   nsCOMPtr<nsIPrefBranch2> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
00821   if (prefs)
00822   {
00823     InitSupportedProtocolsPref(prefs);
00824     prefs->AddObserver(MOZ_GNOMEVFS_SUPPORTED_PROTOCOLS, this, PR_FALSE);
00825   }
00826 
00827   return NS_OK;
00828 }
00829 
00830 void
00831 nsGnomeVFSProtocolHandler::InitSupportedProtocolsPref(nsIPrefBranch *prefs)
00832 {
00833   // read preferences
00834   nsresult rv = prefs->GetCharPref(MOZ_GNOMEVFS_SUPPORTED_PROTOCOLS,
00835                                    getter_Copies(mSupportedProtocols));
00836   if (NS_SUCCEEDED(rv))
00837     mSupportedProtocols.StripWhitespace();
00838   else
00839     mSupportedProtocols.Truncate();
00840 
00841   LOG(("gnomevfs: supported protocols \"%s\"\n", mSupportedProtocols.get()));
00842 }
00843 
00844 PRBool
00845 nsGnomeVFSProtocolHandler::IsSupportedProtocol(const nsCString &spec)
00846 {
00847   PRInt32 colon = spec.FindChar(':');
00848   if (colon == kNotFound)
00849     return PR_FALSE;
00850 
00851   // <scheme> + ':'
00852   const nsCSubstring &scheme = StringHead(spec, colon + 1);
00853 
00854   nsACString::const_iterator begin, end, iter;
00855   mSupportedProtocols.BeginReading(begin);
00856   mSupportedProtocols.EndReading(end);
00857 
00858   iter = begin;
00859   return CaseInsensitiveFindInReadable(scheme, iter, end) &&
00860       (iter == begin || *(--iter) == ',');
00861 }
00862 
00863 NS_IMETHODIMP
00864 nsGnomeVFSProtocolHandler::GetScheme(nsACString &aScheme)
00865 {
00866   aScheme.AssignLiteral(MOZ_GNOMEVFS_SCHEME);
00867   return NS_OK;
00868 }
00869 
00870 NS_IMETHODIMP
00871 nsGnomeVFSProtocolHandler::GetDefaultPort(PRInt32 *aDefaultPort)
00872 {
00873   *aDefaultPort = -1;
00874   return NS_OK;
00875 }
00876 
00877 NS_IMETHODIMP
00878 nsGnomeVFSProtocolHandler::GetProtocolFlags(PRUint32 *aProtocolFlags)
00879 {
00880   // Is this true of all GnomeVFS URI types?
00881   *aProtocolFlags = URI_STD;
00882   return NS_OK;
00883 }
00884 
00885 NS_IMETHODIMP
00886 nsGnomeVFSProtocolHandler::NewURI(const nsACString &aSpec,
00887                                   const char *aOriginCharset,
00888                                   nsIURI *aBaseURI,
00889                                   nsIURI **aResult)
00890 {
00891   const nsPromiseFlatCString &flatSpec = PromiseFlatCString(aSpec);
00892 
00893   LOG(("gnomevfs: NewURI [spec=%s]\n", flatSpec.get()));
00894 
00895   if (!aBaseURI)
00896   {
00897     //
00898     // XXX This check is used to limit the gnome-vfs protocols we support.  For
00899     //     security reasons, it is best that we limit the protocols we support to
00900     //     those with known characteristics.  We might want to lessen this
00901     //     restriction if it proves to be too heavy handed.  A black list of
00902     //     protocols we don't want to support might be better.  For example, we
00903     //     probably don't want to try to load "start-here:" inside the browser.
00904     //     There are others that fall into this category, which are best handled
00905     //     externally by Nautilus (or another app like it).
00906     //
00907     if (!IsSupportedProtocol(flatSpec))
00908       return NS_ERROR_UNKNOWN_PROTOCOL;
00909 
00910     // Verify that GnomeVFS supports this URI scheme.
00911     GnomeVFSURI *uri = gnome_vfs_uri_new(flatSpec.get());
00912     if (!uri)
00913       return NS_ERROR_UNKNOWN_PROTOCOL;
00914   }
00915 
00916   //
00917   // XXX Can we really assume that all gnome-vfs URIs can be parsed using
00918   //     nsStandardURL?  We probably really need to implement nsIURI/nsIURL
00919   //     in terms of the gnome_vfs_uri_XXX methods, but at least this works
00920   //     correctly for smb:// URLs ;-)
00921   //
00922   //     Also, it might not be possible to fully implement nsIURI/nsIURL in
00923   //     terms of GnomeVFSURI since some Necko methods have no GnomeVFS
00924   //     equivalent.
00925   //
00926   nsresult rv;
00927   nsCOMPtr<nsIStandardURL> url =
00928       do_CreateInstance(NS_STANDARDURL_CONTRACTID, &rv);
00929   if (NS_FAILED(rv))
00930     return rv;
00931 
00932   rv = url->Init(nsIStandardURL::URLTYPE_STANDARD, -1, flatSpec,
00933                  aOriginCharset, aBaseURI);
00934   if (NS_SUCCEEDED(rv))
00935     rv = CallQueryInterface(url, aResult);
00936 
00937   return rv;
00938 }
00939 
00940 NS_IMETHODIMP
00941 nsGnomeVFSProtocolHandler::NewChannel(nsIURI *aURI, nsIChannel **aResult)
00942 {
00943   NS_ENSURE_ARG_POINTER(aURI);
00944   nsresult rv;
00945 
00946   nsCAutoString spec;
00947   rv = aURI->GetSpec(spec);
00948   if (NS_FAILED(rv))
00949     return rv;
00950 
00951   nsRefPtr<nsGnomeVFSInputStream> stream = new nsGnomeVFSInputStream(spec);
00952   if (!stream)
00953   {
00954     rv = NS_ERROR_OUT_OF_MEMORY;
00955   }
00956   else
00957   {
00958     // start out assuming an unknown content-type.  we'll set the content-type
00959     // to something better once we open the URI.
00960     rv = NS_NewInputStreamChannel(aResult, aURI, stream,
00961                                   NS_LITERAL_CSTRING(UNKNOWN_CONTENT_TYPE));
00962     if (NS_SUCCEEDED(rv))
00963       stream->SetChannel(*aResult);
00964   }
00965   return rv;
00966 }
00967 
00968 NS_IMETHODIMP
00969 nsGnomeVFSProtocolHandler::AllowPort(PRInt32 aPort,
00970                                      const char *aScheme,
00971                                      PRBool *aResult)
00972 {
00973   // Don't override anything.
00974   *aResult = PR_FALSE; 
00975   return NS_OK;
00976 }
00977 
00978 NS_IMETHODIMP
00979 nsGnomeVFSProtocolHandler::Observe(nsISupports *aSubject,
00980                                    const char *aTopic,
00981                                    const PRUnichar *aData)
00982 {
00983   if (strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) == 0) {
00984     nsCOMPtr<nsIPrefBranch> prefs = do_QueryInterface(aSubject);
00985     InitSupportedProtocolsPref(prefs);
00986   }
00987   return NS_OK;
00988 }
00989 
00990 //-----------------------------------------------------------------------------
00991 
00992 #define NS_GNOMEVFSPROTOCOLHANDLER_CID               \
00993 { /* 9b6dc177-a2e4-49e1-9c98-0a8384de7f6c */         \
00994     0x9b6dc177,                                      \
00995     0xa2e4,                                          \
00996     0x49e1,                                          \
00997     {0x9c, 0x98, 0x0a, 0x83, 0x84, 0xde, 0x7f, 0x6c} \
00998 }
00999 
01000 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsGnomeVFSProtocolHandler, Init)
01001 
01002 static const nsModuleComponentInfo components[] =
01003 {
01004   { "nsGnomeVFSProtocolHandler",
01005     NS_GNOMEVFSPROTOCOLHANDLER_CID,
01006     NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX MOZ_GNOMEVFS_SCHEME,
01007     nsGnomeVFSProtocolHandlerConstructor
01008   }
01009 };
01010 
01011 NS_IMPL_NSGETMODULE(nsGnomeVFSModule, components)