tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

nsGIOProtocolHandler.cpp (33080B)


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