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