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