extensions/gio/nsGIOProtocolHandler.cpp

Wed, 31 Dec 2014 13:27:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 13:27:57 +0100
branch
TOR_BUG_3246
changeset 6
8bccb770b82d
permissions
-rw-r--r--

Ignore runtime configuration files generated during quality assurance.

     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 /*
     7  * This code is based on original Mozilla gnome-vfs extension. It implements
     8  * input stream provided by GVFS/GIO.
     9 */
    10 #include "mozilla/ModuleUtils.h"
    11 #include "nsIPrefService.h"
    12 #include "nsIPrefBranch.h"
    13 #include "nsIObserver.h"
    14 #include "nsThreadUtils.h"
    15 #include "nsProxyRelease.h"
    16 #include "nsIStringBundle.h"
    17 #include "nsIStandardURL.h"
    18 #include "nsMimeTypes.h"
    19 #include "nsNetUtil.h"
    20 #include "mozilla/Monitor.h"
    21 #include <gio/gio.h>
    22 #include <algorithm>
    24 #define MOZ_GIO_SCHEME              "moz-gio"
    25 #define MOZ_GIO_SUPPORTED_PROTOCOLS "network.gio.supported-protocols"
    27 //-----------------------------------------------------------------------------
    29 // NSPR_LOG_MODULES=gio:5
    30 #ifdef PR_LOGGING
    31 static PRLogModuleInfo *sGIOLog;
    32 #define LOG(args) PR_LOG(sGIOLog, PR_LOG_DEBUG, args)
    33 #else
    34 #define LOG(args)
    35 #endif
    38 //-----------------------------------------------------------------------------
    39 static nsresult
    40 MapGIOResult(gint code) 
    41 {
    42   switch (code)
    43   {
    44      case G_IO_ERROR_NOT_FOUND:                  return NS_ERROR_FILE_NOT_FOUND; // shows error
    45      case G_IO_ERROR_INVALID_ARGUMENT:           return NS_ERROR_INVALID_ARG;
    46      case G_IO_ERROR_NOT_SUPPORTED:              return NS_ERROR_NOT_AVAILABLE;
    47      case G_IO_ERROR_NO_SPACE:                   return NS_ERROR_FILE_NO_DEVICE_SPACE;
    48      case G_IO_ERROR_READ_ONLY:                  return NS_ERROR_FILE_READ_ONLY;
    49      case G_IO_ERROR_PERMISSION_DENIED:          return NS_ERROR_FILE_ACCESS_DENIED; // wrong password/login
    50      case G_IO_ERROR_CLOSED:                     return NS_BASE_STREAM_CLOSED; // was EOF
    51      case G_IO_ERROR_NOT_DIRECTORY:              return NS_ERROR_FILE_NOT_DIRECTORY;
    52      case G_IO_ERROR_PENDING:                    return NS_ERROR_IN_PROGRESS;
    53      case G_IO_ERROR_EXISTS:                     return NS_ERROR_FILE_ALREADY_EXISTS;
    54      case G_IO_ERROR_IS_DIRECTORY:               return NS_ERROR_FILE_IS_DIRECTORY;
    55      case G_IO_ERROR_NOT_MOUNTED:                return NS_ERROR_NOT_CONNECTED; // shows error
    56      case G_IO_ERROR_HOST_NOT_FOUND:             return NS_ERROR_UNKNOWN_HOST; // shows error
    57      case G_IO_ERROR_CANCELLED:                  return NS_ERROR_ABORT;
    58      case G_IO_ERROR_NOT_EMPTY:                  return NS_ERROR_FILE_DIR_NOT_EMPTY;
    59      case G_IO_ERROR_FILENAME_TOO_LONG:          return NS_ERROR_FILE_NAME_TOO_LONG;
    60      case G_IO_ERROR_INVALID_FILENAME:           return NS_ERROR_FILE_INVALID_PATH;
    61      case G_IO_ERROR_TIMED_OUT:                  return NS_ERROR_NET_TIMEOUT; // shows error
    62      case G_IO_ERROR_WOULD_BLOCK:                return NS_BASE_STREAM_WOULD_BLOCK;
    63      case G_IO_ERROR_FAILED_HANDLED:             return NS_ERROR_ABORT; // Cancel on login dialog
    65 /* unhandled:
    66   G_IO_ERROR_NOT_REGULAR_FILE,
    67   G_IO_ERROR_NOT_SYMBOLIC_LINK,
    68   G_IO_ERROR_NOT_MOUNTABLE_FILE,
    69   G_IO_ERROR_TOO_MANY_LINKS,
    70   G_IO_ERROR_ALREADY_MOUNTED,
    71   G_IO_ERROR_CANT_CREATE_BACKUP,
    72   G_IO_ERROR_WRONG_ETAG,
    73   G_IO_ERROR_WOULD_RECURSE,
    74   G_IO_ERROR_BUSY,
    75   G_IO_ERROR_WOULD_MERGE,
    76   G_IO_ERROR_TOO_MANY_OPEN_FILES
    77 */
    78     // Make GCC happy
    79     default:
    80       return NS_ERROR_FAILURE;
    81   }
    83   return NS_ERROR_FAILURE;
    84 }
    86 static nsresult
    87 MapGIOResult(GError *result)
    88 {
    89   if (!result)
    90     return NS_OK;
    91   else 
    92     return MapGIOResult(result->code);
    93 }
    94 /** Return values for mount operation.
    95  * These enums are used as mount operation return values.
    96  */
    97 typedef enum {
    98   MOUNT_OPERATION_IN_PROGRESS, /** \enum operation in progress */
    99   MOUNT_OPERATION_SUCCESS,     /** \enum operation successful */
   100   MOUNT_OPERATION_FAILED       /** \enum operation not successful */
   101 } MountOperationResult;
   102 //-----------------------------------------------------------------------------
   103 /**
   104  * Sort function compares according to file type (directory/file)
   105  * and alphabethical order
   106  * @param a pointer to GFileInfo object to compare
   107  * @param b pointer to GFileInfo object to compare
   108  * @return -1 when first object should be before the second, 0 when equal,
   109  * +1 when second object should be before the first
   110  */
   111 static gint
   112 FileInfoComparator(gconstpointer a, gconstpointer b)
   113 {
   114   GFileInfo *ia = ( GFileInfo *) a;
   115   GFileInfo *ib = ( GFileInfo *) b;
   116   if (g_file_info_get_file_type(ia) == G_FILE_TYPE_DIRECTORY
   117       && g_file_info_get_file_type(ib) != G_FILE_TYPE_DIRECTORY)
   118     return -1;
   119   if (g_file_info_get_file_type(ib) == G_FILE_TYPE_DIRECTORY
   120       && g_file_info_get_file_type(ia) != G_FILE_TYPE_DIRECTORY)
   121     return 1;
   123   return strcasecmp(g_file_info_get_name(ia), g_file_info_get_name(ib));
   124 }
   126 /* Declaration of mount callback functions */
   127 static void mount_enclosing_volume_finished (GObject *source_object,
   128                                              GAsyncResult *res,
   129                                              gpointer user_data);
   130 static void mount_operation_ask_password (GMountOperation   *mount_op,
   131                                           const char        *message,
   132                                           const char        *default_user,
   133                                           const char        *default_domain,
   134                                           GAskPasswordFlags flags,
   135                                           gpointer          user_data);
   136 //-----------------------------------------------------------------------------
   138 class nsGIOInputStream MOZ_FINAL : public nsIInputStream
   139 {
   140   public:
   141     NS_DECL_THREADSAFE_ISUPPORTS
   142     NS_DECL_NSIINPUTSTREAM
   144     nsGIOInputStream(const nsCString &uriSpec)
   145       : mSpec(uriSpec)
   146       , mChannel(nullptr)
   147       , mHandle(nullptr)
   148       , mStream(nullptr)
   149       , mBytesRemaining(UINT64_MAX)
   150       , mStatus(NS_OK)
   151       , mDirList(nullptr)
   152       , mDirListPtr(nullptr)
   153       , mDirBufCursor(0)
   154       , mDirOpen(false)
   155       , mMonitorMountInProgress("GIOInputStream::MountFinished") { }
   157    ~nsGIOInputStream() { Close(); }
   159     void SetChannel(nsIChannel *channel)
   160     {
   161       // We need to hold an owning reference to our channel.  This is done
   162       // so we can access the channel's notification callbacks to acquire
   163       // a reference to a nsIAuthPrompt if we need to handle an interactive
   164       // mount operation.
   165       //
   166       // However, the channel can only be accessed on the main thread, so
   167       // we have to be very careful with ownership.  Moreover, it doesn't
   168       // support threadsafe addref/release, so proxying is the answer.
   169       //
   170       // Also, it's important to note that this likely creates a reference
   171       // cycle since the channel likely owns this stream.  This reference
   172       // cycle is broken in our Close method.
   174       NS_ADDREF(mChannel = channel);
   175     }
   176     void           SetMountResult(MountOperationResult result, gint error_code);
   177   private:
   178     nsresult       DoOpen();
   179     nsresult       DoRead(char *aBuf, uint32_t aCount, uint32_t *aCountRead);
   180     nsresult       SetContentTypeOfChannel(const char *contentType);
   181     nsresult       MountVolume();
   182     nsresult       DoOpenDirectory();
   183     nsresult       DoOpenFile(GFileInfo *info);        
   184     nsCString             mSpec;
   185     nsIChannel           *mChannel; // manually refcounted
   186     GFile                *mHandle;
   187     GFileInputStream     *mStream;
   188     uint64_t              mBytesRemaining;
   189     nsresult              mStatus;
   190     GList                *mDirList;
   191     GList                *mDirListPtr;
   192     nsCString             mDirBuf;
   193     uint32_t              mDirBufCursor;
   194     bool                  mDirOpen;
   195     MountOperationResult  mMountRes;
   196     mozilla::Monitor      mMonitorMountInProgress;
   197     gint                  mMountErrorCode;
   198 };
   199 /**
   200  * Set result of mount operation and notify monitor waiting for results.
   201  * This method is called in main thread as long as it is used only 
   202  * in mount_enclosing_volume_finished function.
   203  * @param result Result of mount operation
   204  */ 
   205 void
   206 nsGIOInputStream::SetMountResult(MountOperationResult result, gint error_code)
   207 {
   208   mozilla::MonitorAutoLock mon(mMonitorMountInProgress);
   209   mMountRes = result;
   210   mMountErrorCode = error_code;
   211   mon.Notify();
   212 }
   214 /**
   215  * Start mount operation and wait in loop until it is finished. This method is 
   216  * called from thread which is trying to read from location.
   217  */
   218 nsresult
   219 nsGIOInputStream::MountVolume() {
   220   GMountOperation* mount_op = g_mount_operation_new();
   221   g_signal_connect (mount_op, "ask-password",
   222                     G_CALLBACK (mount_operation_ask_password), mChannel);
   223   mMountRes = MOUNT_OPERATION_IN_PROGRESS;
   224   /* g_file_mount_enclosing_volume uses a dbus request to mount the volume.
   225      Callback mount_enclosing_volume_finished is called in main thread 
   226      (not this thread on which this method is called). */
   227   g_file_mount_enclosing_volume(mHandle,
   228                                 G_MOUNT_MOUNT_NONE,
   229                                 mount_op,
   230                                 nullptr,
   231                                 mount_enclosing_volume_finished,
   232                                 this);
   233   mozilla::MonitorAutoLock mon(mMonitorMountInProgress);
   234   /* Waiting for finish of mount operation thread */  
   235   while (mMountRes == MOUNT_OPERATION_IN_PROGRESS)
   236     mon.Wait();
   238   g_object_unref(mount_op);
   240   if (mMountRes == MOUNT_OPERATION_FAILED) {
   241     return MapGIOResult(mMountErrorCode);
   242   } else {
   243     return NS_OK;
   244   }
   245 }
   247 /**
   248  * Create list of infos about objects in opened directory
   249  * Return: NS_OK when list obtained, otherwise error code according
   250  * to failed operation.
   251  */
   252 nsresult
   253 nsGIOInputStream::DoOpenDirectory()
   254 {
   255   GError *error = nullptr;
   257   GFileEnumerator *f_enum = g_file_enumerate_children(mHandle,
   258                                                       "standard::*,time::*",
   259                                                       G_FILE_QUERY_INFO_NONE,
   260                                                       nullptr,
   261                                                       &error);
   262   if (!f_enum) {
   263     nsresult rv = MapGIOResult(error);
   264     g_warning("Cannot read from directory: %s", error->message);
   265     g_error_free(error);
   266     return rv;
   267   }
   268   // fill list of file infos
   269   GFileInfo *info = g_file_enumerator_next_file(f_enum, nullptr, &error);
   270   while (info) {
   271     mDirList = g_list_append(mDirList, info);
   272     info = g_file_enumerator_next_file(f_enum, nullptr, &error);
   273   }
   274   g_object_unref(f_enum);
   275   if (error) {
   276     g_warning("Error reading directory content: %s", error->message);
   277     nsresult rv = MapGIOResult(error);
   278     g_error_free(error);
   279     return rv;
   280   }
   281   mDirOpen = true;
   283   // Sort list of file infos by using FileInfoComparator function
   284   mDirList = g_list_sort(mDirList, FileInfoComparator);
   285   mDirListPtr = mDirList;
   287   // Write base URL (make sure it ends with a '/')
   288   mDirBuf.Append("300: ");
   289   mDirBuf.Append(mSpec);
   290   if (mSpec.get()[mSpec.Length() - 1] != '/')
   291     mDirBuf.Append('/');
   292   mDirBuf.Append('\n');
   294   // Write column names
   295   mDirBuf.Append("200: filename content-length last-modified file-type\n");
   297   // Write charset (assume UTF-8)
   298   // XXX is this correct?
   299   mDirBuf.Append("301: UTF-8\n");
   300   SetContentTypeOfChannel(APPLICATION_HTTP_INDEX_FORMAT);
   301   return NS_OK;
   302 }
   304 /**
   305  * Create file stream and set mime type for channel
   306  * @param info file info used to determine mime type
   307  * @return NS_OK when file stream created successfuly, error code otherwise
   308  */
   309 nsresult
   310 nsGIOInputStream::DoOpenFile(GFileInfo *info)
   311 {
   312   GError *error = nullptr;
   314   mStream = g_file_read(mHandle, nullptr, &error);
   315   if (!mStream) {
   316     nsresult rv = MapGIOResult(error);
   317     g_warning("Cannot read from file: %s", error->message);
   318     g_error_free(error);
   319     return rv;
   320   }
   322   const char * content_type = g_file_info_get_content_type(info);
   323   if (content_type) {
   324     char *mime_type = g_content_type_get_mime_type(content_type);
   325     if (mime_type) {
   326       if (strcmp(mime_type, APPLICATION_OCTET_STREAM) != 0) {
   327         SetContentTypeOfChannel(mime_type);
   328       }
   329       g_free(mime_type);
   330     }
   331   } else {
   332     g_warning("Missing content type.");
   333   }
   335   mBytesRemaining = g_file_info_get_size(info);
   336   // Update the content length attribute on the channel.  We do this
   337   // synchronously without proxying.  This hack is not as bad as it looks!
   338   mChannel->SetContentLength(mBytesRemaining);
   340   return NS_OK;
   341 }
   343 /**
   344  * Start file open operation, mount volume when needed and according to file type
   345  * create file output stream or read directory content.
   346  * @return NS_OK when file or directory opened successfully, error code otherwise
   347  */
   348 nsresult
   349 nsGIOInputStream::DoOpen()
   350 {
   351   nsresult rv;
   352   GError *error = nullptr;
   354   NS_ASSERTION(mHandle == nullptr, "already open");
   356   mHandle = g_file_new_for_uri( mSpec.get() );
   358   GFileInfo *info = g_file_query_info(mHandle,
   359                                       "standard::*",
   360                                       G_FILE_QUERY_INFO_NONE,
   361                                       nullptr,
   362                                       &error);
   364   if (error) {
   365     if (error->domain == G_IO_ERROR && error->code == G_IO_ERROR_NOT_MOUNTED) {
   366       // location is not yet mounted, try to mount
   367       g_error_free(error);
   368       if (NS_IsMainThread()) 
   369         return NS_ERROR_NOT_CONNECTED;
   370       error = nullptr;
   371       rv = MountVolume();
   372       if (rv != NS_OK) {
   373         return rv;
   374       }
   375       // get info again
   376       info = g_file_query_info(mHandle,
   377                                "standard::*",
   378                                G_FILE_QUERY_INFO_NONE,
   379                                nullptr,
   380                                &error);
   381       // second try to get file info from remote files after media mount
   382       if (!info) {
   383         g_warning("Unable to get file info: %s", error->message);
   384         rv = MapGIOResult(error);
   385         g_error_free(error);
   386         return rv;
   387       }
   388     } else {
   389       g_warning("Unable to get file info: %s", error->message);
   390       rv = MapGIOResult(error);
   391       g_error_free(error);
   392       return rv;
   393     }
   394   }
   395   // Get file type to handle directories and file differently
   396   GFileType f_type = g_file_info_get_file_type(info);
   397   if (f_type == G_FILE_TYPE_DIRECTORY) {
   398     // directory
   399     rv = DoOpenDirectory();
   400   } else if (f_type != G_FILE_TYPE_UNKNOWN) {
   401     // file
   402     rv = DoOpenFile(info);
   403   } else {
   404     g_warning("Unable to get file type.");
   405     rv = NS_ERROR_FILE_NOT_FOUND;
   406   }
   407   if (info)
   408     g_object_unref(info);
   409   return rv;
   410 }
   412 /**
   413  * Read content of file or create file list from directory
   414  * @param aBuf read destination buffer
   415  * @param aCount length of destination buffer
   416  * @param aCountRead number of read characters
   417  * @return NS_OK when read successfully, NS_BASE_STREAM_CLOSED when end of file,
   418  *         error code otherwise
   419  */
   420 nsresult
   421 nsGIOInputStream::DoRead(char *aBuf, uint32_t aCount, uint32_t *aCountRead)
   422 {
   423   nsresult rv = NS_ERROR_NOT_AVAILABLE;
   424   if (mStream) {
   425     // file read
   426     GError *error = nullptr;    
   427     uint32_t bytes_read = g_input_stream_read(G_INPUT_STREAM(mStream),
   428                                               aBuf,
   429                                               aCount,
   430                                               nullptr,
   431                                               &error);
   432     if (error) {
   433       rv = MapGIOResult(error);
   434       *aCountRead = 0;
   435       g_warning("Cannot read from file: %s", error->message);
   436       g_error_free(error);
   437       return rv;
   438     }
   439     *aCountRead = bytes_read;
   440     mBytesRemaining -= *aCountRead;
   441     return NS_OK;
   442   }
   443   else if (mDirOpen) {
   444     // directory read
   445     while (aCount && rv != NS_BASE_STREAM_CLOSED)
   446     {
   447       // Copy data out of our buffer
   448       uint32_t bufLen = mDirBuf.Length() - mDirBufCursor;
   449       if (bufLen)
   450       {
   451         uint32_t n = std::min(bufLen, aCount);
   452         memcpy(aBuf, mDirBuf.get() + mDirBufCursor, n);
   453         *aCountRead += n;
   454         aBuf += n;
   455         aCount -= n;
   456         mDirBufCursor += n;
   457       }
   459       if (!mDirListPtr)    // Are we at the end of the directory list?
   460       {
   461         rv = NS_BASE_STREAM_CLOSED;
   462       }
   463       else if (aCount)     // Do we need more data?
   464       {
   465         GFileInfo *info = (GFileInfo *) mDirListPtr->data;
   467         // Prune '.' and '..' from directory listing.
   468         const char * fname = g_file_info_get_name(info);
   469         if (fname && fname[0] == '.' && 
   470             (fname[1] == '\0' || (fname[1] == '.' && fname[2] == '\0')))
   471         {
   472           mDirListPtr = mDirListPtr->next;
   473           continue;
   474         }
   476         mDirBuf.Assign("201: ");
   478         // The "filename" field
   479         nsCString escName;
   480         nsCOMPtr<nsINetUtil> nu = do_GetService(NS_NETUTIL_CONTRACTID);
   481         if (nu && fname) {
   482           nu->EscapeString(nsDependentCString(fname),
   483                            nsINetUtil::ESCAPE_URL_PATH, escName);
   485           mDirBuf.Append(escName);
   486           mDirBuf.Append(' ');
   487         }
   489         // The "content-length" field
   490         // XXX truncates size from 64-bit to 32-bit
   491         mDirBuf.AppendInt(int32_t(g_file_info_get_size(info)));
   492         mDirBuf.Append(' ');
   494         // The "last-modified" field
   495         //
   496         // NSPR promises: PRTime is compatible with time_t
   497         // we just need to convert from seconds to microseconds
   498         GTimeVal gtime;
   499         g_file_info_get_modification_time(info, &gtime);
   501         PRExplodedTime tm;
   502         PRTime pt = ((PRTime) gtime.tv_sec) * 1000000;
   503         PR_ExplodeTime(pt, PR_GMTParameters, &tm);
   504         {
   505           char buf[64];
   506           PR_FormatTimeUSEnglish(buf, sizeof(buf),
   507               "%a,%%20%d%%20%b%%20%Y%%20%H:%M:%S%%20GMT ", &tm);
   508           mDirBuf.Append(buf);
   509         }
   511         // The "file-type" field
   512         switch (g_file_info_get_file_type(info))
   513         {
   514           case G_FILE_TYPE_REGULAR:
   515             mDirBuf.Append("FILE ");
   516             break;
   517           case G_FILE_TYPE_DIRECTORY:
   518             mDirBuf.Append("DIRECTORY ");
   519             break;
   520           case G_FILE_TYPE_SYMBOLIC_LINK:
   521             mDirBuf.Append("SYMBOLIC-LINK ");
   522             break;
   523           default:
   524             break;
   525         }
   526         mDirBuf.Append('\n');
   528         mDirBufCursor = 0;
   529         mDirListPtr = mDirListPtr->next;
   530       }
   531     }
   532   }
   533   return rv;
   534 }
   536 /**
   537  * This class is used to implement SetContentTypeOfChannel.
   538  */
   539 class nsGIOSetContentTypeEvent : public nsRunnable
   540 {
   541   public:
   542     nsGIOSetContentTypeEvent(nsIChannel *channel, const char *contentType)
   543       : mChannel(channel), mContentType(contentType)
   544     {
   545       // stash channel reference in mChannel.  no AddRef here!  see note
   546       // in SetContentTypeOfchannel.
   547     }
   549     NS_IMETHOD Run()
   550     {
   551       mChannel->SetContentType(mContentType);
   552       return NS_OK;
   553     }
   555   private:
   556     nsIChannel *mChannel;
   557     nsCString   mContentType;
   558 };
   560 nsresult
   561 nsGIOInputStream::SetContentTypeOfChannel(const char *contentType)
   562 {
   563   // We need to proxy this call over to the main thread.  We post an
   564   // asynchronous event in this case so that we don't delay reading data, and
   565   // we know that this is safe to do since the channel's reference will be
   566   // released asynchronously as well.  We trust the ordering of the main
   567   // thread's event queue to protect us against memory corruption.
   569   nsresult rv;
   570   nsCOMPtr<nsIRunnable> ev =
   571       new nsGIOSetContentTypeEvent(mChannel, contentType);
   572   if (!ev)
   573   {
   574     rv = NS_ERROR_OUT_OF_MEMORY;
   575   }
   576   else
   577   {
   578     rv = NS_DispatchToMainThread(ev);
   579   }
   580   return rv;
   581 }
   583 NS_IMPL_ISUPPORTS(nsGIOInputStream, nsIInputStream)
   585 /**
   586  * Free all used memory and close stream.
   587  */
   588 NS_IMETHODIMP
   589 nsGIOInputStream::Close()
   590 {
   591   if (mStream)
   592   {
   593     g_object_unref(mStream);
   594     mStream = nullptr;
   595   }
   597   if (mHandle)
   598   {
   599     g_object_unref(mHandle);
   600     mHandle = nullptr;
   601   }
   603   if (mDirList)
   604   {
   605     // Destroy the list of GIOFileInfo objects...
   606     g_list_foreach(mDirList, (GFunc) g_object_unref, nullptr);
   607     g_list_free(mDirList);
   608     mDirList = nullptr;
   609     mDirListPtr = nullptr;
   610   }
   612   if (mChannel)
   613   {
   614     nsresult rv = NS_OK;
   616     nsCOMPtr<nsIThread> thread = do_GetMainThread();
   617     if (thread)
   618       rv = NS_ProxyRelease(thread, mChannel);
   620     NS_ASSERTION(thread && NS_SUCCEEDED(rv), "leaking channel reference");
   621     mChannel = nullptr;
   622     (void) rv;
   623   }
   625   mSpec.Truncate(); // free memory
   627   // Prevent future reads from re-opening the handle.
   628   if (NS_SUCCEEDED(mStatus))
   629     mStatus = NS_BASE_STREAM_CLOSED;
   631   return NS_OK;
   632 }
   634 /**
   635  * Return number of remaining bytes available on input
   636  * @param aResult remaining bytes
   637  */
   638 NS_IMETHODIMP
   639 nsGIOInputStream::Available(uint64_t *aResult)
   640 {
   641   if (NS_FAILED(mStatus))
   642     return mStatus;
   644   *aResult = mBytesRemaining;
   646   return NS_OK;
   647 }
   649 /**
   650  * Trying to read from stream. When location is not available it tries to mount it.
   651  * @param aBuf buffer to put read data
   652  * @param aCount length of aBuf
   653  * @param aCountRead number of bytes actually read
   654  */
   655 NS_IMETHODIMP
   656 nsGIOInputStream::Read(char     *aBuf,
   657                        uint32_t  aCount,
   658                        uint32_t *aCountRead)
   659 {
   660   *aCountRead = 0;
   661   // Check if file is already opened, otherwise open it
   662   if (!mStream && !mDirOpen && mStatus == NS_OK) {
   663     mStatus = DoOpen();
   664     if (NS_FAILED(mStatus)) {
   665       return mStatus;
   666     }
   667   }
   669   mStatus = DoRead(aBuf, aCount, aCountRead);
   670   // Check if all data has been read
   671   if (mStatus == NS_BASE_STREAM_CLOSED)
   672     return NS_OK;
   674   // Check whenever any error appears while reading
   675   return mStatus;
   676 }
   678 NS_IMETHODIMP
   679 nsGIOInputStream::ReadSegments(nsWriteSegmentFun aWriter,
   680                                void             *aClosure,
   681                                uint32_t          aCount,
   682                                uint32_t         *aResult)
   683 {
   684   // There is no way to implement this using GnomeVFS, but fortunately
   685   // that doesn't matter.  Because we are a blocking input stream, Necko
   686   // isn't going to call our ReadSegments method.
   687   NS_NOTREACHED("nsGIOInputStream::ReadSegments");
   688   return NS_ERROR_NOT_IMPLEMENTED;
   689 }
   691 NS_IMETHODIMP
   692 nsGIOInputStream::IsNonBlocking(bool *aResult)
   693 {
   694   *aResult = false;
   695   return NS_OK;
   696 }
   698 //-----------------------------------------------------------------------------
   700 /**
   701  * Called when finishing mount operation. Result of operation is set in 
   702  * nsGIOInputStream. This function is called in main thread as an async request 
   703  * typically from dbus.
   704  * @param source_object GFile object which requested the mount
   705  * @param res result object
   706  * @param user_data pointer to nsGIOInputStream
   707  */
   708 static void
   709 mount_enclosing_volume_finished (GObject *source_object,
   710                                  GAsyncResult *res,
   711                                  gpointer user_data)
   712 {
   713   GError *error = nullptr;
   715   nsGIOInputStream* istream = static_cast<nsGIOInputStream*>(user_data);
   717   g_file_mount_enclosing_volume_finish(G_FILE (source_object), res, &error);
   719   if (error) {
   720     g_warning("Mount failed: %s %d", error->message, error->code);
   721     istream->SetMountResult(MOUNT_OPERATION_FAILED, error->code);
   722     g_error_free(error);
   723   } else {
   724     istream->SetMountResult(MOUNT_OPERATION_SUCCESS, 0);
   725   }
   726 }
   728 /**
   729  * This function is called when username or password are requested from user.
   730  * This function is called in main thread as async request from dbus.
   731  * @param mount_op mount operation
   732  * @param message message to show to user
   733  * @param default_user preffered user
   734  * @param default_domain domain name
   735  * @param flags what type of information is required
   736  * @param user_data nsIChannel
   737  */
   738 static void
   739 mount_operation_ask_password (GMountOperation   *mount_op,
   740                               const char        *message,
   741                               const char        *default_user,
   742                               const char        *default_domain,
   743                               GAskPasswordFlags flags,
   744                               gpointer          user_data)
   745 {
   746   nsIChannel *channel = (nsIChannel *) user_data;
   747   if (!channel) {
   748     g_mount_operation_reply(mount_op, G_MOUNT_OPERATION_ABORTED);
   749     return;
   750   }
   751   // We can't handle request for domain
   752   if (flags & G_ASK_PASSWORD_NEED_DOMAIN) {
   753     g_mount_operation_reply(mount_op, G_MOUNT_OPERATION_ABORTED);
   754     return;
   755   }
   757   nsCOMPtr<nsIAuthPrompt> prompt;
   758   NS_QueryNotificationCallbacks(channel, prompt);
   760   // If no auth prompt, then give up.  We could failover to using the
   761   // WindowWatcher service, but that might defeat a consumer's purposeful
   762   // attempt to disable authentication (for whatever reason).
   763   if (!prompt) {
   764     g_mount_operation_reply(mount_op, G_MOUNT_OPERATION_ABORTED);
   765     return;
   766   }
   767   // Parse out the host and port...
   768   nsCOMPtr<nsIURI> uri;
   769   channel->GetURI(getter_AddRefs(uri));
   770   if (!uri) {
   771     g_mount_operation_reply(mount_op, G_MOUNT_OPERATION_ABORTED);
   772     return;
   773   }
   775   nsAutoCString scheme, hostPort;
   776   uri->GetScheme(scheme);
   777   uri->GetHostPort(hostPort);
   779   // It doesn't make sense for either of these strings to be empty.  What kind
   780   // of funky URI is this?
   781   if (scheme.IsEmpty() || hostPort.IsEmpty()) {
   782     g_mount_operation_reply(mount_op, G_MOUNT_OPERATION_ABORTED);
   783     return;
   784   }
   785   // Construct the single signon key.  Altering the value of this key will
   786   // cause people's remembered passwords to be forgotten.  Think carefully
   787   // before changing the way this key is constructed.
   788   nsAutoString key, realm;
   790   NS_ConvertUTF8toUTF16 dispHost(scheme);
   791   dispHost.Append(NS_LITERAL_STRING("://"));
   792   dispHost.Append(NS_ConvertUTF8toUTF16(hostPort));
   794   key = dispHost;
   795   if (*default_domain != '\0')
   796   {
   797     // We assume the realm string is ASCII.  That might be a bogus assumption,
   798     // but we have no idea what encoding GnomeVFS is using, so for now we'll
   799     // limit ourselves to ISO-Latin-1.  XXX What is a better solution?
   800     realm.Append('"');
   801     realm.Append(NS_ConvertASCIItoUTF16(default_domain));
   802     realm.Append('"');
   803     key.Append(' ');
   804     key.Append(realm);
   805   }
   806   // Construct the message string...
   807   //
   808   // We use Necko's string bundle here.  This code really should be encapsulated
   809   // behind some Necko API, after all this code is based closely on the code in
   810   // nsHttpChannel.cpp.
   811   nsCOMPtr<nsIStringBundleService> bundleSvc =
   812       do_GetService(NS_STRINGBUNDLE_CONTRACTID);
   813   if (!bundleSvc) {
   814     g_mount_operation_reply(mount_op, G_MOUNT_OPERATION_ABORTED);
   815     return;
   816   }
   817   nsCOMPtr<nsIStringBundle> bundle;
   818   bundleSvc->CreateBundle("chrome://global/locale/commonDialogs.properties",
   819                           getter_AddRefs(bundle));
   820   if (!bundle) {
   821     g_mount_operation_reply(mount_op, G_MOUNT_OPERATION_ABORTED);
   822     return;
   823   }
   824   nsAutoString nsmessage;
   826   if (flags & G_ASK_PASSWORD_NEED_PASSWORD) {
   827     if (flags & G_ASK_PASSWORD_NEED_USERNAME) {
   828       if (!realm.IsEmpty()) {
   829         const char16_t *strings[] = { realm.get(), dispHost.get() };
   830         bundle->FormatStringFromName(MOZ_UTF16("EnterLoginForRealm"),
   831                                      strings, 2, getter_Copies(nsmessage));
   832       } else {
   833         const char16_t *strings[] = { dispHost.get() };
   834         bundle->FormatStringFromName(MOZ_UTF16("EnterUserPasswordFor"),
   835                                      strings, 1, getter_Copies(nsmessage));
   836       }
   837     } else {
   838       NS_ConvertUTF8toUTF16 userName(default_user);
   839       const char16_t *strings[] = { userName.get(), dispHost.get() };
   840       bundle->FormatStringFromName(MOZ_UTF16("EnterPasswordFor"),
   841                                    strings, 2, getter_Copies(nsmessage));
   842     }
   843   } else {
   844     g_warning("Unknown mount operation request (flags: %x)", flags);
   845   }
   847   if (nsmessage.IsEmpty()) {
   848     g_mount_operation_reply(mount_op, G_MOUNT_OPERATION_ABORTED);
   849     return;
   850   }
   851   // Prompt the user...
   852   nsresult rv;
   853   bool retval = false;
   854   char16_t *user = nullptr, *pass = nullptr;
   855   if (default_user) {
   856     // user will be freed by PromptUsernameAndPassword
   857     user = ToNewUnicode(NS_ConvertUTF8toUTF16(default_user));
   858   }
   859   if (flags & G_ASK_PASSWORD_NEED_USERNAME) {
   860     rv = prompt->PromptUsernameAndPassword(nullptr, nsmessage.get(),
   861                                            key.get(),
   862                                            nsIAuthPrompt::SAVE_PASSWORD_PERMANENTLY,
   863                                            &user, &pass, &retval);
   864   } else {
   865     rv = prompt->PromptPassword(nullptr, nsmessage.get(),
   866                                 key.get(),
   867                                 nsIAuthPrompt::SAVE_PASSWORD_PERMANENTLY,
   868                                 &pass, &retval);
   869   }
   870   if (NS_FAILED(rv) || !retval) {  //  was || user == '\0' || pass == '\0'
   871     g_mount_operation_reply(mount_op, G_MOUNT_OPERATION_ABORTED);
   872     return;
   873   }
   874   /* GIO should accept UTF8 */
   875   g_mount_operation_set_username(mount_op, NS_ConvertUTF16toUTF8(user).get());
   876   g_mount_operation_set_password(mount_op, NS_ConvertUTF16toUTF8(pass).get());
   877   nsMemory::Free(user);
   878   nsMemory::Free(pass);
   879   g_mount_operation_reply(mount_op, G_MOUNT_OPERATION_HANDLED);
   880 }
   882 //-----------------------------------------------------------------------------
   884 class nsGIOProtocolHandler MOZ_FINAL : public nsIProtocolHandler
   885                                      , public nsIObserver
   886 {
   887   public:
   888     NS_DECL_ISUPPORTS
   889     NS_DECL_NSIPROTOCOLHANDLER
   890     NS_DECL_NSIOBSERVER
   892     nsresult Init();
   894   private:
   895     void InitSupportedProtocolsPref(nsIPrefBranch *prefs);
   896     bool IsSupportedProtocol(const nsCString &spec);
   898     nsCString mSupportedProtocols;
   899 };
   901 NS_IMPL_ISUPPORTS(nsGIOProtocolHandler, nsIProtocolHandler, nsIObserver)
   903 nsresult
   904 nsGIOProtocolHandler::Init()
   905 {
   906 #ifdef PR_LOGGING
   907   sGIOLog = PR_NewLogModule("gio");
   908 #endif
   910   nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
   911   if (prefs)
   912   {
   913     InitSupportedProtocolsPref(prefs);
   914     prefs->AddObserver(MOZ_GIO_SUPPORTED_PROTOCOLS, this, false);
   915   }
   917   return NS_OK;
   918 }
   920 void
   921 nsGIOProtocolHandler::InitSupportedProtocolsPref(nsIPrefBranch *prefs)
   922 {
   923   // Get user preferences to determine which protocol is supported.
   924   // Gvfs/GIO has a set of supported protocols like obex, network, archive,
   925   // computer, dav, cdda, gphoto2, trash, etc. Some of these seems to be
   926   // irrelevant to process by browser. By default accept only smb and sftp
   927   // protocols so far.
   928   nsresult rv = prefs->GetCharPref(MOZ_GIO_SUPPORTED_PROTOCOLS,
   929                                    getter_Copies(mSupportedProtocols));
   930   if (NS_SUCCEEDED(rv)) {
   931     mSupportedProtocols.StripWhitespace();
   932     ToLowerCase(mSupportedProtocols);
   933   }
   934   else
   935     mSupportedProtocols.Assign("smb:,sftp:"); // use defaults
   937   LOG(("gio: supported protocols \"%s\"\n", mSupportedProtocols.get()));
   938 }
   940 bool
   941 nsGIOProtocolHandler::IsSupportedProtocol(const nsCString &aSpec)
   942 {
   943   const char *specString = aSpec.get();
   944   const char *colon = strchr(specString, ':');
   945   if (!colon)
   946     return false;
   948   uint32_t length = colon - specString + 1;
   950   // <scheme> + ':'
   951   nsCString scheme(specString, length);
   953   char *found = PL_strcasestr(mSupportedProtocols.get(), scheme.get());
   954   if (!found)
   955     return false;
   957   if (found[length] != ',' && found[length] != '\0')
   958     return false;
   960   return true;
   961 }
   963 NS_IMETHODIMP
   964 nsGIOProtocolHandler::GetScheme(nsACString &aScheme)
   965 {
   966   aScheme.Assign(MOZ_GIO_SCHEME);
   967   return NS_OK;
   968 }
   970 NS_IMETHODIMP
   971 nsGIOProtocolHandler::GetDefaultPort(int32_t *aDefaultPort)
   972 {
   973   *aDefaultPort = -1;
   974   return NS_OK;
   975 }
   977 NS_IMETHODIMP
   978 nsGIOProtocolHandler::GetProtocolFlags(uint32_t *aProtocolFlags)
   979 {
   980   // Is URI_STD true of all GnomeVFS URI types?
   981   *aProtocolFlags = URI_STD | URI_DANGEROUS_TO_LOAD;
   982   return NS_OK;
   983 }
   985 NS_IMETHODIMP
   986 nsGIOProtocolHandler::NewURI(const nsACString &aSpec,
   987                              const char *aOriginCharset,
   988                              nsIURI *aBaseURI,
   989                              nsIURI **aResult)
   990 {
   991   const nsCString flatSpec(aSpec);
   992   LOG(("gio: NewURI [spec=%s]\n", flatSpec.get()));
   994   if (!aBaseURI)
   995   {
   996     // XXX Is it good to support all GIO protocols?
   997     if (!IsSupportedProtocol(flatSpec))
   998       return NS_ERROR_UNKNOWN_PROTOCOL;
  1000     int32_t colon_location = flatSpec.FindChar(':');
  1001     if (colon_location <= 0)
  1002       return NS_ERROR_UNKNOWN_PROTOCOL;
  1004     // Verify that GIO supports this URI scheme.
  1005     bool uri_scheme_supported = false;
  1007     GVfs *gvfs = g_vfs_get_default();
  1009     if (!gvfs) {
  1010       g_warning("Cannot get GVfs object.");
  1011       return NS_ERROR_UNKNOWN_PROTOCOL;
  1014     const gchar* const * uri_schemes = g_vfs_get_supported_uri_schemes(gvfs);
  1016     while (*uri_schemes != nullptr) {
  1017       // While flatSpec ends with ':' the uri_scheme does not. Therefore do not
  1018       // compare last character.
  1019       if (StringHead(flatSpec, colon_location).Equals(*uri_schemes)) {
  1020         uri_scheme_supported = true;
  1021         break;
  1023       uri_schemes++;
  1026     if (!uri_scheme_supported) {
  1027       return NS_ERROR_UNKNOWN_PROTOCOL;
  1031   nsresult rv;
  1032   nsCOMPtr<nsIStandardURL> url =
  1033       do_CreateInstance(NS_STANDARDURL_CONTRACTID, &rv);
  1034   if (NS_FAILED(rv))
  1035     return rv;
  1037   rv = url->Init(nsIStandardURL::URLTYPE_STANDARD, -1, flatSpec,
  1038                  aOriginCharset, aBaseURI);
  1039   if (NS_SUCCEEDED(rv))
  1040     rv = CallQueryInterface(url, aResult);
  1041   return rv;
  1045 NS_IMETHODIMP
  1046 nsGIOProtocolHandler::NewChannel(nsIURI *aURI, nsIChannel **aResult)
  1048   NS_ENSURE_ARG_POINTER(aURI);
  1049   nsresult rv;
  1051   nsAutoCString spec;
  1052   rv = aURI->GetSpec(spec);
  1053   if (NS_FAILED(rv))
  1054     return rv;
  1056   nsRefPtr<nsGIOInputStream> stream = new nsGIOInputStream(spec);
  1057   if (!stream)
  1059     rv = NS_ERROR_OUT_OF_MEMORY;
  1061   else
  1063     // start out assuming an unknown content-type.  we'll set the content-type
  1064     // to something better once we open the URI.
  1065     rv = NS_NewInputStreamChannel(aResult,
  1066                                   aURI,
  1067                                   stream,
  1068                                   NS_LITERAL_CSTRING(UNKNOWN_CONTENT_TYPE));
  1069     if (NS_SUCCEEDED(rv))
  1070       stream->SetChannel(*aResult);
  1072   return rv;
  1075 NS_IMETHODIMP
  1076 nsGIOProtocolHandler::AllowPort(int32_t aPort,
  1077                                 const char *aScheme,
  1078                                 bool *aResult)
  1080   // Don't override anything.
  1081   *aResult = false;
  1082   return NS_OK;
  1085 NS_IMETHODIMP
  1086 nsGIOProtocolHandler::Observe(nsISupports *aSubject,
  1087                               const char *aTopic,
  1088                               const char16_t *aData)
  1090   if (strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) == 0) {
  1091     nsCOMPtr<nsIPrefBranch> prefs = do_QueryInterface(aSubject);
  1092     InitSupportedProtocolsPref(prefs);
  1094   return NS_OK;
  1097 //-----------------------------------------------------------------------------
  1099 #define NS_GIOPROTOCOLHANDLER_CID                    \
  1100 { /* ee706783-3af8-4d19-9e84-e2ebfe213480 */         \
  1101     0xee706783,                                      \
  1102     0x3af8,                                          \
  1103     0x4d19,                                          \
  1104     {0x9e, 0x84, 0xe2, 0xeb, 0xfe, 0x21, 0x34, 0x80} \
  1107 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsGIOProtocolHandler, Init)
  1108 NS_DEFINE_NAMED_CID(NS_GIOPROTOCOLHANDLER_CID);
  1110 static const mozilla::Module::CIDEntry kVFSCIDs[] = {
  1111   { &kNS_GIOPROTOCOLHANDLER_CID, false, nullptr, nsGIOProtocolHandlerConstructor },
  1112   { nullptr }
  1113 };
  1115 static const mozilla::Module::ContractIDEntry kVFSContracts[] = {
  1116   { NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX MOZ_GIO_SCHEME, &kNS_GIOPROTOCOLHANDLER_CID },
  1117   { nullptr }
  1118 };
  1120 static const mozilla::Module kVFSModule = {
  1121   mozilla::Module::kVersion,
  1122   kVFSCIDs,
  1123   kVFSContracts
  1124 };
  1126 NSMODULE_DEFN(nsGIOModule) = &kVFSModule;

mercurial