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