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