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, >ime);
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