uriloader/exthandler/nsExternalHelperAppService.cpp

branch
TOR_BUG_3246
changeset 5
4ab42b5ab56c
equal deleted inserted replaced
-1:000000000000 0:c7bb62ecd7db
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim:expandtab:shiftwidth=2:tabstop=2:cin:
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #ifdef MOZ_LOGGING
8 #define FORCE_PR_LOG
9 #endif
10
11 #include "base/basictypes.h"
12
13 /* This must occur *after* base/basictypes.h to avoid typedefs conflicts. */
14 #include "mozilla/ArrayUtils.h"
15 #include "mozilla/Base64.h"
16
17 #include "mozilla/dom/ContentChild.h"
18 #include "mozilla/dom/TabChild.h"
19 #include "nsXULAppAPI.h"
20
21 #include "nsExternalHelperAppService.h"
22 #include "nsCExternalHandlerService.h"
23 #include "nsIURI.h"
24 #include "nsIURL.h"
25 #include "nsIFile.h"
26 #include "nsIFileURL.h"
27 #include "nsIChannel.h"
28 #include "nsIDirectoryService.h"
29 #include "nsAppDirectoryServiceDefs.h"
30 #include "nsICategoryManager.h"
31 #include "nsDependentSubstring.h"
32 #include "nsXPIDLString.h"
33 #include "nsUnicharUtils.h"
34 #include "nsIStringEnumerator.h"
35 #include "nsMemory.h"
36 #include "nsIStreamListener.h"
37 #include "nsIMIMEService.h"
38 #include "nsILoadGroup.h"
39 #include "nsIWebProgressListener.h"
40 #include "nsITransfer.h"
41 #include "nsReadableUtils.h"
42 #include "nsIRequest.h"
43 #include "nsDirectoryServiceDefs.h"
44 #include "nsIInterfaceRequestor.h"
45 #include "nsThreadUtils.h"
46 #include "nsAutoPtr.h"
47 #include "nsIMutableArray.h"
48
49 // used to access our datastore of user-configured helper applications
50 #include "nsIHandlerService.h"
51 #include "nsIMIMEInfo.h"
52 #include "nsIRefreshURI.h" // XXX needed to redirect according to Refresh: URI
53 #include "nsIDocumentLoader.h" // XXX needed to get orig. channel and assoc. refresh uri
54 #include "nsIHelperAppLauncherDialog.h"
55 #include "nsIContentDispatchChooser.h"
56 #include "nsNetUtil.h"
57 #include "nsIIOService.h"
58 #include "nsNetCID.h"
59 #include "nsChannelProperties.h"
60
61 #include "nsMimeTypes.h"
62 // used for header disposition information.
63 #include "nsIHttpChannel.h"
64 #include "nsIHttpChannelInternal.h"
65 #include "nsIEncodedChannel.h"
66 #include "nsIMultiPartChannel.h"
67 #include "nsIFileChannel.h"
68 #include "nsIObserverService.h" // so we can be a profile change observer
69 #include "nsIPropertyBag2.h" // for the 64-bit content length
70
71 #ifdef XP_MACOSX
72 #include "nsILocalFileMac.h"
73 #endif
74
75 #include "nsIPluginHost.h" // XXX needed for ext->type mapping (bug 233289)
76 #include "nsPluginHost.h"
77 #include "nsEscape.h"
78
79 #include "nsIStringBundle.h" // XXX needed to localize error msgs
80 #include "nsIPrompt.h"
81
82 #include "nsITextToSubURI.h" // to unescape the filename
83 #include "nsIMIMEHeaderParam.h"
84
85 #include "nsIWindowWatcher.h"
86
87 #include "nsIDownloadHistory.h" // to mark downloads as visited
88 #include "nsDocShellCID.h"
89
90 #include "nsCRT.h"
91 #include "nsLocalHandlerApp.h"
92
93 #include "nsIRandomGenerator.h"
94
95 #include "ContentChild.h"
96 #include "nsXULAppAPI.h"
97 #include "nsPIDOMWindow.h"
98 #include "nsIDocShellTreeOwner.h"
99 #include "nsIDocShellTreeItem.h"
100 #include "ExternalHelperAppChild.h"
101
102 #ifdef XP_WIN
103 #include "nsWindowsHelpers.h"
104 #endif
105
106 #ifdef MOZ_WIDGET_ANDROID
107 #include "AndroidBridge.h"
108 #endif
109
110 #include "mozilla/Preferences.h"
111 #include "mozilla/ipc/URIUtils.h"
112
113 #ifdef MOZ_WIDGET_GONK
114 #include "nsDeviceStorage.h"
115 #endif
116
117 #ifdef NECKO_PROTOCOL_rtsp
118 #include "nsIScriptSecurityManager.h"
119 #include "nsIMessageManager.h"
120 #endif
121
122 using namespace mozilla;
123 using namespace mozilla::ipc;
124
125 // Download Folder location constants
126 #define NS_PREF_DOWNLOAD_DIR "browser.download.dir"
127 #define NS_PREF_DOWNLOAD_FOLDERLIST "browser.download.folderList"
128 enum {
129 NS_FOLDER_VALUE_DESKTOP = 0
130 , NS_FOLDER_VALUE_DOWNLOADS = 1
131 , NS_FOLDER_VALUE_CUSTOM = 2
132 };
133
134 #ifdef PR_LOGGING
135 PRLogModuleInfo* nsExternalHelperAppService::mLog = nullptr;
136 #endif
137
138 // Using level 3 here because the OSHelperAppServices use a log level
139 // of PR_LOG_DEBUG (4), and we want less detailed output here
140 // Using 3 instead of PR_LOG_WARN because we don't output warnings
141 #undef LOG
142 #define LOG(args) PR_LOG(nsExternalHelperAppService::mLog, 3, args)
143 #define LOG_ENABLED() PR_LOG_TEST(nsExternalHelperAppService::mLog, 3)
144
145 static const char NEVER_ASK_FOR_SAVE_TO_DISK_PREF[] =
146 "browser.helperApps.neverAsk.saveToDisk";
147 static const char NEVER_ASK_FOR_OPEN_FILE_PREF[] =
148 "browser.helperApps.neverAsk.openFile";
149
150 // Helper functions for Content-Disposition headers
151
152 /**
153 * Given a URI fragment, unescape it
154 * @param aFragment The string to unescape
155 * @param aURI The URI from which this fragment is taken. Only its character set
156 * will be used.
157 * @param aResult [out] Unescaped string.
158 */
159 static nsresult UnescapeFragment(const nsACString& aFragment, nsIURI* aURI,
160 nsAString& aResult)
161 {
162 // First, we need a charset
163 nsAutoCString originCharset;
164 nsresult rv = aURI->GetOriginCharset(originCharset);
165 NS_ENSURE_SUCCESS(rv, rv);
166
167 // Now, we need the unescaper
168 nsCOMPtr<nsITextToSubURI> textToSubURI = do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv);
169 NS_ENSURE_SUCCESS(rv, rv);
170
171 return textToSubURI->UnEscapeURIForUI(originCharset, aFragment, aResult);
172 }
173
174 /**
175 * UTF-8 version of UnescapeFragment.
176 * @param aFragment The string to unescape
177 * @param aURI The URI from which this fragment is taken. Only its character set
178 * will be used.
179 * @param aResult [out] Unescaped string, UTF-8 encoded.
180 * @note It is safe to pass the same string for aFragment and aResult.
181 * @note When this function fails, aResult will not be modified.
182 */
183 static nsresult UnescapeFragment(const nsACString& aFragment, nsIURI* aURI,
184 nsACString& aResult)
185 {
186 nsAutoString result;
187 nsresult rv = UnescapeFragment(aFragment, aURI, result);
188 if (NS_SUCCEEDED(rv))
189 CopyUTF16toUTF8(result, aResult);
190 return rv;
191 }
192
193 /**
194 * Given a channel, returns the filename and extension the channel has.
195 * This uses the URL and other sources (nsIMultiPartChannel).
196 * Also gives back whether the channel requested external handling (i.e.
197 * whether Content-Disposition: attachment was sent)
198 * @param aChannel The channel to extract the filename/extension from
199 * @param aFileName [out] Reference to the string where the filename should be
200 * stored. Empty if it could not be retrieved.
201 * WARNING - this filename may contain characters which the OS does not
202 * allow as part of filenames!
203 * @param aExtension [out] Reference to the string where the extension should
204 * be stored. Empty if it could not be retrieved. Stored in UTF-8.
205 * @param aAllowURLExtension (optional) Get the extension from the URL if no
206 * Content-Disposition header is present. Default is true.
207 * @retval true The server sent Content-Disposition:attachment or equivalent
208 * @retval false Content-Disposition: inline or no content-disposition header
209 * was sent.
210 */
211 static bool GetFilenameAndExtensionFromChannel(nsIChannel* aChannel,
212 nsString& aFileName,
213 nsCString& aExtension,
214 bool aAllowURLExtension = true)
215 {
216 aExtension.Truncate();
217 /*
218 * If the channel is an http or part of a multipart channel and we
219 * have a content disposition header set, then use the file name
220 * suggested there as the preferred file name to SUGGEST to the
221 * user. we shouldn't actually use that without their
222 * permission... otherwise just use our temp file
223 */
224 bool handleExternally = false;
225 uint32_t disp;
226 nsresult rv = aChannel->GetContentDisposition(&disp);
227 if (NS_SUCCEEDED(rv))
228 {
229 aChannel->GetContentDispositionFilename(aFileName);
230 if (disp == nsIChannel::DISPOSITION_ATTACHMENT)
231 handleExternally = true;
232 }
233
234 // If the disposition header didn't work, try the filename from nsIURL
235 nsCOMPtr<nsIURI> uri;
236 aChannel->GetURI(getter_AddRefs(uri));
237 nsCOMPtr<nsIURL> url(do_QueryInterface(uri));
238 if (url && aFileName.IsEmpty())
239 {
240 if (aAllowURLExtension) {
241 url->GetFileExtension(aExtension);
242 UnescapeFragment(aExtension, url, aExtension);
243
244 // Windows ignores terminating dots. So we have to as well, so
245 // that our security checks do "the right thing"
246 // In case the aExtension consisted only of the dot, the code below will
247 // extract an aExtension from the filename
248 aExtension.Trim(".", false);
249 }
250
251 // try to extract the file name from the url and use that as a first pass as the
252 // leaf name of our temp file...
253 nsAutoCString leafName;
254 url->GetFileName(leafName);
255 if (!leafName.IsEmpty())
256 {
257 rv = UnescapeFragment(leafName, url, aFileName);
258 if (NS_FAILED(rv))
259 {
260 CopyUTF8toUTF16(leafName, aFileName); // use escaped name
261 }
262 }
263 }
264
265 // Extract Extension, if we have a filename; otherwise,
266 // truncate the string
267 if (aExtension.IsEmpty()) {
268 if (!aFileName.IsEmpty())
269 {
270 // Windows ignores terminating dots. So we have to as well, so
271 // that our security checks do "the right thing"
272 aFileName.Trim(".", false);
273
274 // XXX RFindCharInReadable!!
275 nsAutoString fileNameStr(aFileName);
276 int32_t idx = fileNameStr.RFindChar(char16_t('.'));
277 if (idx != kNotFound)
278 CopyUTF16toUTF8(StringTail(fileNameStr, fileNameStr.Length() - idx - 1), aExtension);
279 }
280 }
281
282
283 return handleExternally;
284 }
285
286 /**
287 * Obtains the directory to use. This tends to vary per platform, and
288 * needs to be consistent throughout our codepaths. For platforms where
289 * helper apps use the downloads directory, this should be kept in
290 * sync with nsDownloadManager.cpp
291 *
292 * Optionally skip availability of the directory and storage.
293 */
294 static nsresult GetDownloadDirectory(nsIFile **_directory,
295 bool aSkipChecks = false)
296 {
297 nsCOMPtr<nsIFile> dir;
298 #ifdef XP_MACOSX
299 // On OS X, we first try to get the users download location, if it's set.
300 switch (Preferences::GetInt(NS_PREF_DOWNLOAD_FOLDERLIST, -1)) {
301 case NS_FOLDER_VALUE_DESKTOP:
302 (void) NS_GetSpecialDirectory(NS_OS_DESKTOP_DIR, getter_AddRefs(dir));
303 break;
304 case NS_FOLDER_VALUE_CUSTOM:
305 {
306 Preferences::GetComplex(NS_PREF_DOWNLOAD_DIR,
307 NS_GET_IID(nsIFile),
308 getter_AddRefs(dir));
309 if (!dir) break;
310
311 // If we're not checking for availability we're done.
312 if (aSkipChecks) {
313 dir.forget(_directory);
314 return NS_OK;
315 }
316
317 // We have the directory, and now we need to make sure it exists
318 bool dirExists = false;
319 (void) dir->Exists(&dirExists);
320 if (dirExists) break;
321
322 nsresult rv = dir->Create(nsIFile::DIRECTORY_TYPE, 0755);
323 if (NS_FAILED(rv)) {
324 dir = nullptr;
325 break;
326 }
327 }
328 break;
329 case NS_FOLDER_VALUE_DOWNLOADS:
330 // This is just the OS default location, so fall out
331 break;
332 }
333
334 if (!dir) {
335 // If not, we default to the OS X default download location.
336 nsresult rv = NS_GetSpecialDirectory(NS_OSX_DEFAULT_DOWNLOAD_DIR,
337 getter_AddRefs(dir));
338 NS_ENSURE_SUCCESS(rv, rv);
339 }
340 #elif defined(MOZ_WIDGET_GONK)
341 // On Gonk, store the files on the sdcard in the downloads directory.
342 // We need to check with the volume manager which storage point is
343 // available.
344
345 // Pick the default storage in case multiple (internal and external) ones
346 // are available.
347 nsString storageName;
348 nsDOMDeviceStorage::GetDefaultStorageName(NS_LITERAL_STRING("sdcard"),
349 storageName);
350
351 DeviceStorageFile dsf(NS_LITERAL_STRING("sdcard"),
352 storageName,
353 NS_LITERAL_STRING("downloads"));
354 NS_ENSURE_TRUE(dsf.mFile, NS_ERROR_FILE_ACCESS_DENIED);
355
356 // If we're not checking for availability we're done.
357 if (aSkipChecks) {
358 dsf.mFile.forget(_directory);
359 return NS_OK;
360 }
361
362 // Check device storage status before continuing.
363 nsString storageStatus;
364 dsf.GetStatus(storageStatus);
365
366 // If we get an "unavailable" status, it means the sd card is not present.
367 // We'll also catch internal errors by looking for an empty string and assume
368 // the SD card isn't present when this occurs.
369 if (storageStatus.EqualsLiteral("unavailable") ||
370 storageStatus.IsEmpty()) {
371 return NS_ERROR_FILE_NOT_FOUND;
372 }
373
374 // If we get a status other than 'available' here it means the card is busy
375 // because it's mounted via USB or it is being formatted.
376 if (!storageStatus.EqualsLiteral("available")) {
377 return NS_ERROR_FILE_ACCESS_DENIED;
378 }
379
380 bool alreadyThere;
381 nsresult rv = dsf.mFile->Exists(&alreadyThere);
382 NS_ENSURE_SUCCESS(rv, rv);
383 if (!alreadyThere) {
384 rv = dsf.mFile->Create(nsIFile::DIRECTORY_TYPE, 0770);
385 NS_ENSURE_SUCCESS(rv, rv);
386 }
387 dir = dsf.mFile;
388 #elif defined(ANDROID)
389 // On mobile devices, we are avoiding exposing users to the file
390 // system, and don't save downloads to temp directories
391
392 // On Android we only return something if we have and SD-card
393 char* downloadDir = getenv("DOWNLOADS_DIRECTORY");
394 nsresult rv;
395 if (downloadDir) {
396 nsCOMPtr<nsIFile> ldir;
397 rv = NS_NewNativeLocalFile(nsDependentCString(downloadDir),
398 true, getter_AddRefs(ldir));
399 NS_ENSURE_SUCCESS(rv, rv);
400 dir = do_QueryInterface(ldir);
401
402 // If we're not checking for availability we're done.
403 if (aSkipChecks) {
404 dir.forget(_directory);
405 return NS_OK;
406 }
407 }
408 else {
409 return NS_ERROR_FAILURE;
410 }
411 #elif defined(XP_WIN)
412 // On metro we want to be able to search opened files and the temp directory
413 // is exlcuded in searches.
414 nsresult rv;
415 if (IsRunningInWindowsMetro()) {
416 rv = NS_GetSpecialDirectory(NS_WIN_DEFAULT_DOWNLOAD_DIR, getter_AddRefs(dir));
417 } else {
418 rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(dir));
419 }
420 NS_ENSURE_SUCCESS(rv, rv);
421 #else
422 // On all other platforms, we default to the systems temporary directory.
423 nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(dir));
424 NS_ENSURE_SUCCESS(rv, rv);
425 #endif
426
427 NS_ASSERTION(dir, "Somehow we didn't get a download directory!");
428 dir.forget(_directory);
429 return NS_OK;
430 }
431
432 /**
433 * Structure for storing extension->type mappings.
434 * @see defaultMimeEntries
435 */
436 struct nsDefaultMimeTypeEntry {
437 const char* mMimeType;
438 const char* mFileExtension;
439 };
440
441 /**
442 * Default extension->mimetype mappings. These are not overridable.
443 * If you add types here, make sure they are lowercase, or you'll regret it.
444 */
445 static nsDefaultMimeTypeEntry defaultMimeEntries [] =
446 {
447 // The following are those extensions that we're asked about during startup,
448 // sorted by order used
449 { IMAGE_GIF, "gif" },
450 { TEXT_XML, "xml" },
451 { APPLICATION_RDF, "rdf" },
452 { TEXT_XUL, "xul" },
453 { IMAGE_PNG, "png" },
454 // -- end extensions used during startup
455 { TEXT_CSS, "css" },
456 { IMAGE_JPEG, "jpeg" },
457 { IMAGE_JPEG, "jpg" },
458 { IMAGE_SVG_XML, "svg" },
459 { TEXT_HTML, "html" },
460 { TEXT_HTML, "htm" },
461 { APPLICATION_XPINSTALL, "xpi" },
462 { "application/xhtml+xml", "xhtml" },
463 { "application/xhtml+xml", "xht" },
464 { TEXT_PLAIN, "txt" },
465 { VIDEO_OGG, "ogv" },
466 { VIDEO_OGG, "ogg" },
467 { APPLICATION_OGG, "ogg" },
468 { AUDIO_OGG, "oga" },
469 #ifdef MOZ_OPUS
470 { AUDIO_OGG, "opus" },
471 #endif
472 #ifdef MOZ_WEBM
473 { VIDEO_WEBM, "webm" },
474 { AUDIO_WEBM, "webm" },
475 #endif
476 #if defined(MOZ_GSTREAMER) || defined(MOZ_WMF)
477 { VIDEO_MP4, "mp4" },
478 { AUDIO_MP4, "m4a" },
479 { AUDIO_MP3, "mp3" },
480 #endif
481 #ifdef MOZ_RAW
482 { VIDEO_RAW, "yuv" }
483 #endif
484 };
485
486 /**
487 * This is a small private struct used to help us initialize some
488 * default mime types.
489 */
490 struct nsExtraMimeTypeEntry {
491 const char* mMimeType;
492 const char* mFileExtensions;
493 const char* mDescription;
494 };
495
496 #ifdef XP_MACOSX
497 #define MAC_TYPE(x) x
498 #else
499 #define MAC_TYPE(x) 0
500 #endif
501
502 /**
503 * This table lists all of the 'extra' content types that we can deduce from particular
504 * file extensions. These entries also ensure that we provide a good descriptive name
505 * when we encounter files with these content types and/or extensions. These can be
506 * overridden by user helper app prefs.
507 * If you add types here, make sure they are lowercase, or you'll regret it.
508 */
509 static nsExtraMimeTypeEntry extraMimeEntries [] =
510 {
511 #if defined(VMS)
512 { APPLICATION_OCTET_STREAM, "exe,com,bin,sav,bck,pcsi,dcx_axpexe,dcx_vaxexe,sfx_axpexe,sfx_vaxexe", "Binary File" },
513 #elif defined(XP_MACOSX) // don't define .bin on the mac...use internet config to look that up...
514 { APPLICATION_OCTET_STREAM, "exe,com", "Binary File" },
515 #else
516 { APPLICATION_OCTET_STREAM, "exe,com,bin", "Binary File" },
517 #endif
518 { APPLICATION_GZIP2, "gz", "gzip" },
519 { "application/x-arj", "arj", "ARJ file" },
520 { "application/rtf", "rtf", "Rich Text Format File" },
521 { APPLICATION_XPINSTALL, "xpi", "XPInstall Install" },
522 { APPLICATION_PDF, "pdf", "Portable Document Format" },
523 { APPLICATION_POSTSCRIPT, "ps,eps,ai", "Postscript File" },
524 { APPLICATION_XJAVASCRIPT, "js", "Javascript Source File" },
525 { APPLICATION_XJAVASCRIPT, "jsm", "Javascript Module Source File" },
526 #ifdef MOZ_WIDGET_ANDROID
527 { "application/vnd.android.package-archive", "apk", "Android Package" },
528 #endif
529 { IMAGE_ART, "art", "ART Image" },
530 { IMAGE_BMP, "bmp", "BMP Image" },
531 { IMAGE_GIF, "gif", "GIF Image" },
532 { IMAGE_ICO, "ico,cur", "ICO Image" },
533 { IMAGE_JPEG, "jpeg,jpg,jfif,pjpeg,pjp", "JPEG Image" },
534 { IMAGE_PNG, "png", "PNG Image" },
535 { IMAGE_TIFF, "tiff,tif", "TIFF Image" },
536 { IMAGE_XBM, "xbm", "XBM Image" },
537 { IMAGE_SVG_XML, "svg", "Scalable Vector Graphics" },
538 { MESSAGE_RFC822, "eml", "RFC-822 data" },
539 { TEXT_PLAIN, "txt,text", "Text File" },
540 { TEXT_HTML, "html,htm,shtml,ehtml", "HyperText Markup Language" },
541 { "application/xhtml+xml", "xhtml,xht", "Extensible HyperText Markup Language" },
542 { APPLICATION_MATHML_XML, "mml", "Mathematical Markup Language" },
543 { APPLICATION_RDF, "rdf", "Resource Description Framework" },
544 { TEXT_XUL, "xul", "XML-Based User Interface Language" },
545 { TEXT_XML, "xml,xsl,xbl", "Extensible Markup Language" },
546 { TEXT_CSS, "css", "Style Sheet" },
547 { TEXT_VCARD, "vcf,vcard", "Contact Information" },
548 { VIDEO_OGG, "ogv", "Ogg Video" },
549 { VIDEO_OGG, "ogg", "Ogg Video" },
550 { APPLICATION_OGG, "ogg", "Ogg Video"},
551 { AUDIO_OGG, "oga", "Ogg Audio" },
552 { AUDIO_OGG, "opus", "Opus Audio" },
553 #ifdef MOZ_WIDGET_GONK
554 { AUDIO_AMR, "amr", "Adaptive Multi-Rate Audio" },
555 #endif
556 { VIDEO_WEBM, "webm", "Web Media Video" },
557 { AUDIO_WEBM, "webm", "Web Media Audio" },
558 { AUDIO_MP3, "mp3", "MPEG Audio" },
559 { VIDEO_MP4, "mp4", "MPEG-4 Video" },
560 { AUDIO_MP4, "m4a", "MPEG-4 Audio" },
561 { VIDEO_RAW, "yuv", "Raw YUV Video" },
562 { AUDIO_WAV, "wav", "Waveform Audio" },
563 { VIDEO_3GPP, "3gpp,3gp", "3GPP Video" },
564 { AUDIO_MIDI, "mid", "Standard MIDI Audio" }
565 };
566
567 #undef MAC_TYPE
568
569 /**
570 * File extensions for which decoding should be disabled.
571 * NOTE: These MUST be lower-case and ASCII.
572 */
573 static nsDefaultMimeTypeEntry nonDecodableExtensions [] = {
574 { APPLICATION_GZIP, "gz" },
575 { APPLICATION_GZIP, "tgz" },
576 { APPLICATION_ZIP, "zip" },
577 { APPLICATION_COMPRESS, "z" },
578 { APPLICATION_GZIP, "svgz" }
579 };
580
581 NS_IMPL_ISUPPORTS(
582 nsExternalHelperAppService,
583 nsIExternalHelperAppService,
584 nsPIExternalAppLauncher,
585 nsIExternalProtocolService,
586 nsIMIMEService,
587 nsIObserver,
588 nsISupportsWeakReference)
589
590 nsExternalHelperAppService::nsExternalHelperAppService()
591 {
592 }
593 nsresult nsExternalHelperAppService::Init()
594 {
595 // Add an observer for profile change
596 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
597 if (!obs)
598 return NS_ERROR_FAILURE;
599
600 #ifdef PR_LOGGING
601 if (!mLog) {
602 mLog = PR_NewLogModule("HelperAppService");
603 if (!mLog)
604 return NS_ERROR_OUT_OF_MEMORY;
605 }
606 #endif
607
608 nsresult rv = obs->AddObserver(this, "profile-before-change", true);
609 NS_ENSURE_SUCCESS(rv, rv);
610 return obs->AddObserver(this, "last-pb-context-exited", true);
611 }
612
613 nsExternalHelperAppService::~nsExternalHelperAppService()
614 {
615 }
616
617 #ifdef NECKO_PROTOCOL_rtsp
618 namespace {
619 /**
620 * A stack helper to clear the currently pending exception in a JS context.
621 */
622 class AutoClearPendingException {
623 public:
624 AutoClearPendingException(JSContext* aCx) :
625 mCx(aCx) {
626 }
627 ~AutoClearPendingException() {
628 JS_ClearPendingException(mCx);
629 }
630 private:
631 JSContext *mCx;
632 };
633 } // anonymous namespace
634
635 /**
636 * This function sends a message. This 'content-handler' message is handled in
637 * b2g/chrome/content/shell.js where it starts an activity request that will
638 * open the video app.
639 */
640 void nsExternalHelperAppService::LaunchVideoAppForRtsp(nsIURI* aURI)
641 {
642 bool rv;
643
644 // Get a system principal.
645 nsCOMPtr<nsIScriptSecurityManager> securityManager =
646 do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID);
647 NS_ENSURE_TRUE_VOID(securityManager);
648
649 nsCOMPtr<nsIPrincipal> principal;
650 securityManager->GetSystemPrincipal(getter_AddRefs(principal));
651 NS_ENSURE_TRUE_VOID(principal);
652
653 // Construct the message in jsVal format.
654 AutoSafeJSContext cx;
655 AutoClearPendingException helper(cx);
656 JS::Rooted<JSObject*> msgObj(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr()));
657 NS_ENSURE_TRUE_VOID(msgObj);
658 JS::Rooted<JS::Value> jsVal(cx);
659
660 // Set the "type" property of the message. This is a fake MIME type.
661 {
662 NS_NAMED_LITERAL_CSTRING(mimeType, "video/rtsp");
663 JSString *typeStr = JS_NewStringCopyN(cx, mimeType.get(), mimeType.Length());
664 NS_ENSURE_TRUE_VOID(typeStr);
665 jsVal.setString(typeStr);
666 rv = JS_SetProperty(cx, msgObj, "type", jsVal);
667 NS_ENSURE_TRUE_VOID(rv);
668 }
669 // Set the "url" and "title" properties of the message.
670 // They are the same in the case of RTSP streaming.
671 {
672 nsAutoCString spec;
673 aURI->GetSpec(spec);
674 JSString *urlStr = JS_NewStringCopyN(cx, spec.get(), spec.Length());
675 NS_ENSURE_TRUE_VOID(urlStr);
676 jsVal.setString(urlStr);
677 rv = JS_SetProperty(cx, msgObj, "url", jsVal);
678 NS_ENSURE_TRUE_VOID(rv);
679 rv = JS_SetProperty(cx, msgObj, "title", jsVal);
680 }
681 jsVal.setObject(*msgObj);
682
683 // Send the message.
684 nsCOMPtr<nsIMessageSender> cpmm =
685 do_GetService("@mozilla.org/childprocessmessagemanager;1");
686 NS_ENSURE_TRUE_VOID(cpmm);
687 cpmm->SendAsyncMessage(NS_LITERAL_STRING("content-handler"),
688 jsVal, JS::NullHandleValue, principal, cx, 2);
689 }
690 #endif
691
692 NS_IMETHODIMP nsExternalHelperAppService::DoContent(const nsACString& aMimeContentType,
693 nsIRequest *aRequest,
694 nsIInterfaceRequestor *aWindowContext,
695 bool aForceSave,
696 nsIStreamListener ** aStreamListener)
697 {
698 nsAutoString fileName;
699 nsAutoCString fileExtension;
700 uint32_t reason = nsIHelperAppLauncherDialog::REASON_CANTHANDLE;
701 uint32_t contentDisposition = -1;
702
703 nsresult rv;
704
705 // Get the file extension and name that we will need later
706 nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
707 nsCOMPtr<nsIURI> uri;
708 int64_t contentLength = -1;
709 if (channel) {
710 channel->GetURI(getter_AddRefs(uri));
711 channel->GetContentLength(&contentLength);
712 channel->GetContentDisposition(&contentDisposition);
713 channel->GetContentDispositionFilename(fileName);
714 }
715
716 if (XRE_GetProcessType() == GeckoProcessType_Content) {
717 nsCOMPtr<nsIDOMWindow> window = do_GetInterface(aWindowContext);
718 NS_ENSURE_STATE(window);
719
720 // We need to get a hold of a ContentChild so that we can begin forwarding
721 // this data to the parent. In the HTTP case, this is unfortunate, since
722 // we're actually passing data from parent->child->parent wastefully, but
723 // the Right Fix will eventually be to short-circuit those channels on the
724 // parent side based on some sort of subscription concept.
725 using mozilla::dom::ContentChild;
726 using mozilla::dom::ExternalHelperAppChild;
727 ContentChild *child = ContentChild::GetSingleton();
728 if (!child)
729 return NS_ERROR_FAILURE;
730
731 nsCString disp;
732 if (channel) {
733 channel->GetContentDispositionHeader(disp);
734 }
735
736 nsCOMPtr<nsIURI> referrer;
737 rv = NS_GetReferrerFromChannel(channel, getter_AddRefs(referrer));
738
739 OptionalURIParams uriParams, referrerParams;
740 SerializeURI(uri, uriParams);
741 SerializeURI(referrer, referrerParams);
742
743 // Now we build a protocol for forwarding our data to the parent. The
744 // protocol will act as a listener on the child-side and create a "real"
745 // helperAppService listener on the parent-side, via another call to
746 // DoContent.
747 mozilla::dom::PExternalHelperAppChild *pc =
748 child->SendPExternalHelperAppConstructor(uriParams,
749 nsCString(aMimeContentType),
750 disp, contentDisposition,
751 fileName, aForceSave,
752 contentLength, referrerParams,
753 mozilla::dom::TabChild::GetFrom(window));
754 ExternalHelperAppChild *childListener = static_cast<ExternalHelperAppChild *>(pc);
755
756 NS_ADDREF(*aStreamListener = childListener);
757
758 nsRefPtr<nsExternalAppHandler> handler =
759 new nsExternalAppHandler(nullptr, EmptyCString(), aWindowContext, this,
760 fileName,
761 reason, aForceSave);
762 if (!handler)
763 return NS_ERROR_OUT_OF_MEMORY;
764
765 childListener->SetHandler(handler);
766 return NS_OK;
767 }
768
769 if (channel) {
770 // Check if we have a POST request, in which case we don't want to use
771 // the url's extension
772 bool allowURLExt = true;
773 nsCOMPtr<nsIHttpChannel> httpChan = do_QueryInterface(channel);
774 if (httpChan) {
775 nsAutoCString requestMethod;
776 httpChan->GetRequestMethod(requestMethod);
777 allowURLExt = !requestMethod.Equals("POST");
778 }
779
780 // Check if we had a query string - we don't want to check the URL
781 // extension if a query is present in the URI
782 // If we already know we don't want to check the URL extension, don't
783 // bother checking the query
784 if (uri && allowURLExt) {
785 nsCOMPtr<nsIURL> url = do_QueryInterface(uri);
786
787 if (url) {
788 nsAutoCString query;
789
790 // We only care about the query for HTTP and HTTPS URLs
791 bool isHTTP, isHTTPS;
792 rv = uri->SchemeIs("http", &isHTTP);
793 if (NS_FAILED(rv))
794 isHTTP = false;
795 rv = uri->SchemeIs("https", &isHTTPS);
796 if (NS_FAILED(rv))
797 isHTTPS = false;
798
799 if (isHTTP || isHTTPS)
800 url->GetQuery(query);
801
802 // Only get the extension if the query is empty; if it isn't, then the
803 // extension likely belongs to a cgi script and isn't helpful
804 allowURLExt = query.IsEmpty();
805 }
806 }
807 // Extract name & extension
808 bool isAttachment = GetFilenameAndExtensionFromChannel(channel, fileName,
809 fileExtension,
810 allowURLExt);
811 LOG(("Found extension '%s' (filename is '%s', handling attachment: %i)",
812 fileExtension.get(), NS_ConvertUTF16toUTF8(fileName).get(),
813 isAttachment));
814 if (isAttachment)
815 reason = nsIHelperAppLauncherDialog::REASON_SERVERREQUEST;
816 }
817
818 LOG(("HelperAppService::DoContent: mime '%s', extension '%s'\n",
819 PromiseFlatCString(aMimeContentType).get(), fileExtension.get()));
820
821 // we get the mime service here even though we're the default implementation of it,
822 // so it's possible to override only the mime service and not need to reimplement the
823 // whole external helper app service itself
824 nsCOMPtr<nsIMIMEService> mimeSvc(do_GetService(NS_MIMESERVICE_CONTRACTID));
825 NS_ENSURE_TRUE(mimeSvc, NS_ERROR_FAILURE);
826
827 // Try to find a mime object by looking at the mime type/extension
828 nsCOMPtr<nsIMIMEInfo> mimeInfo;
829 if (aMimeContentType.Equals(APPLICATION_GUESS_FROM_EXT, nsCaseInsensitiveCStringComparator())) {
830 nsAutoCString mimeType;
831 if (!fileExtension.IsEmpty()) {
832 mimeSvc->GetFromTypeAndExtension(EmptyCString(), fileExtension, getter_AddRefs(mimeInfo));
833 if (mimeInfo) {
834 mimeInfo->GetMIMEType(mimeType);
835
836 LOG(("OS-Provided mime type '%s' for extension '%s'\n",
837 mimeType.get(), fileExtension.get()));
838 }
839 }
840
841 if (fileExtension.IsEmpty() || mimeType.IsEmpty()) {
842 // Extension lookup gave us no useful match
843 mimeSvc->GetFromTypeAndExtension(NS_LITERAL_CSTRING(APPLICATION_OCTET_STREAM), fileExtension,
844 getter_AddRefs(mimeInfo));
845 mimeType.AssignLiteral(APPLICATION_OCTET_STREAM);
846 }
847 if (channel)
848 channel->SetContentType(mimeType);
849 // Don't overwrite SERVERREQUEST
850 if (reason == nsIHelperAppLauncherDialog::REASON_CANTHANDLE)
851 reason = nsIHelperAppLauncherDialog::REASON_TYPESNIFFED;
852 }
853 else {
854 mimeSvc->GetFromTypeAndExtension(aMimeContentType, fileExtension,
855 getter_AddRefs(mimeInfo));
856 }
857 LOG(("Type/Ext lookup found 0x%p\n", mimeInfo.get()));
858
859 // No mimeinfo -> we can't continue. probably OOM.
860 if (!mimeInfo)
861 return NS_ERROR_OUT_OF_MEMORY;
862
863 *aStreamListener = nullptr;
864 // We want the mimeInfo's primary extension to pass it to
865 // nsExternalAppHandler
866 nsAutoCString buf;
867 mimeInfo->GetPrimaryExtension(buf);
868
869 nsExternalAppHandler * handler = new nsExternalAppHandler(mimeInfo,
870 buf,
871 aWindowContext,
872 this,
873 fileName,
874 reason,
875 aForceSave);
876 if (!handler)
877 return NS_ERROR_OUT_OF_MEMORY;
878 NS_ADDREF(*aStreamListener = handler);
879
880 return NS_OK;
881 }
882
883 NS_IMETHODIMP nsExternalHelperAppService::ApplyDecodingForExtension(const nsACString& aExtension,
884 const nsACString& aEncodingType,
885 bool *aApplyDecoding)
886 {
887 *aApplyDecoding = true;
888 uint32_t i;
889 for(i = 0; i < ArrayLength(nonDecodableExtensions); ++i) {
890 if (aExtension.LowerCaseEqualsASCII(nonDecodableExtensions[i].mFileExtension) &&
891 aEncodingType.LowerCaseEqualsASCII(nonDecodableExtensions[i].mMimeType)) {
892 *aApplyDecoding = false;
893 break;
894 }
895 }
896 return NS_OK;
897 }
898
899 nsresult nsExternalHelperAppService::GetFileTokenForPath(const char16_t * aPlatformAppPath,
900 nsIFile ** aFile)
901 {
902 nsDependentString platformAppPath(aPlatformAppPath);
903 // First, check if we have an absolute path
904 nsIFile* localFile = nullptr;
905 nsresult rv = NS_NewLocalFile(platformAppPath, true, &localFile);
906 if (NS_SUCCEEDED(rv)) {
907 *aFile = localFile;
908 bool exists;
909 if (NS_FAILED((*aFile)->Exists(&exists)) || !exists) {
910 NS_RELEASE(*aFile);
911 return NS_ERROR_FILE_NOT_FOUND;
912 }
913 return NS_OK;
914 }
915
916
917 // Second, check if file exists in mozilla program directory
918 rv = NS_GetSpecialDirectory(NS_XPCOM_CURRENT_PROCESS_DIR, aFile);
919 if (NS_SUCCEEDED(rv)) {
920 rv = (*aFile)->Append(platformAppPath);
921 if (NS_SUCCEEDED(rv)) {
922 bool exists = false;
923 rv = (*aFile)->Exists(&exists);
924 if (NS_SUCCEEDED(rv) && exists)
925 return NS_OK;
926 }
927 NS_RELEASE(*aFile);
928 }
929
930
931 return NS_ERROR_NOT_AVAILABLE;
932 }
933
934 //////////////////////////////////////////////////////////////////////////////////////////////////////
935 // begin external protocol service default implementation...
936 //////////////////////////////////////////////////////////////////////////////////////////////////////
937 NS_IMETHODIMP nsExternalHelperAppService::ExternalProtocolHandlerExists(const char * aProtocolScheme,
938 bool * aHandlerExists)
939 {
940 nsCOMPtr<nsIHandlerInfo> handlerInfo;
941 nsresult rv = GetProtocolHandlerInfo(nsDependentCString(aProtocolScheme),
942 getter_AddRefs(handlerInfo));
943 NS_ENSURE_SUCCESS(rv, rv);
944
945 // See if we have any known possible handler apps for this
946 nsCOMPtr<nsIMutableArray> possibleHandlers;
947 handlerInfo->GetPossibleApplicationHandlers(getter_AddRefs(possibleHandlers));
948
949 uint32_t length;
950 possibleHandlers->GetLength(&length);
951 if (length) {
952 *aHandlerExists = true;
953 return NS_OK;
954 }
955
956 // if not, fall back on an os-based handler
957 return OSProtocolHandlerExists(aProtocolScheme, aHandlerExists);
958 }
959
960 NS_IMETHODIMP nsExternalHelperAppService::IsExposedProtocol(const char * aProtocolScheme, bool * aResult)
961 {
962 // check the per protocol setting first. it always takes precedence.
963 // if not set, then use the global setting.
964
965 nsAutoCString prefName("network.protocol-handler.expose.");
966 prefName += aProtocolScheme;
967 bool val;
968 if (NS_SUCCEEDED(Preferences::GetBool(prefName.get(), &val))) {
969 *aResult = val;
970 return NS_OK;
971 }
972
973 // by default, no protocol is exposed. i.e., by default all link clicks must
974 // go through the external protocol service. most applications override this
975 // default behavior.
976 *aResult =
977 Preferences::GetBool("network.protocol-handler.expose-all", false);
978
979 return NS_OK;
980 }
981
982 NS_IMETHODIMP nsExternalHelperAppService::LoadUrl(nsIURI * aURL)
983 {
984 return LoadURI(aURL, nullptr);
985 }
986
987 static const char kExternalProtocolPrefPrefix[] = "network.protocol-handler.external.";
988 static const char kExternalProtocolDefaultPref[] = "network.protocol-handler.external-default";
989
990 NS_IMETHODIMP
991 nsExternalHelperAppService::LoadURI(nsIURI *aURI,
992 nsIInterfaceRequestor *aWindowContext)
993 {
994 NS_ENSURE_ARG_POINTER(aURI);
995
996 if (XRE_GetProcessType() == GeckoProcessType_Content) {
997 URIParams uri;
998 SerializeURI(aURI, uri);
999
1000 mozilla::dom::ContentChild::GetSingleton()->SendLoadURIExternal(uri);
1001 return NS_OK;
1002 }
1003
1004 nsAutoCString spec;
1005 aURI->GetSpec(spec);
1006
1007 if (spec.Find("%00") != -1)
1008 return NS_ERROR_MALFORMED_URI;
1009
1010 spec.ReplaceSubstring("\"", "%22");
1011 spec.ReplaceSubstring("`", "%60");
1012
1013 nsCOMPtr<nsIIOService> ios(do_GetIOService());
1014 nsCOMPtr<nsIURI> uri;
1015 nsresult rv = ios->NewURI(spec, nullptr, nullptr, getter_AddRefs(uri));
1016 NS_ENSURE_SUCCESS(rv, rv);
1017
1018 nsAutoCString scheme;
1019 uri->GetScheme(scheme);
1020 if (scheme.IsEmpty())
1021 return NS_OK; // must have a scheme
1022
1023 // Deny load if the prefs say to do so
1024 nsAutoCString externalPref(kExternalProtocolPrefPrefix);
1025 externalPref += scheme;
1026 bool allowLoad = false;
1027 if (NS_FAILED(Preferences::GetBool(externalPref.get(), &allowLoad))) {
1028 // no scheme-specific value, check the default
1029 if (NS_FAILED(Preferences::GetBool(kExternalProtocolDefaultPref,
1030 &allowLoad))) {
1031 return NS_OK; // missing default pref
1032 }
1033 }
1034
1035 if (!allowLoad) {
1036 return NS_OK; // explicitly denied
1037 }
1038
1039 #ifdef NECKO_PROTOCOL_rtsp
1040 // Handle rtsp protocol.
1041 {
1042 bool isRTSP = false;
1043 rv = aURI->SchemeIs("rtsp", &isRTSP);
1044 if (NS_SUCCEEDED(rv) && isRTSP) {
1045 LaunchVideoAppForRtsp(aURI);
1046 return NS_OK;
1047 }
1048 }
1049 #endif
1050
1051 nsCOMPtr<nsIHandlerInfo> handler;
1052 rv = GetProtocolHandlerInfo(scheme, getter_AddRefs(handler));
1053 NS_ENSURE_SUCCESS(rv, rv);
1054
1055 nsHandlerInfoAction preferredAction;
1056 handler->GetPreferredAction(&preferredAction);
1057 bool alwaysAsk = true;
1058 handler->GetAlwaysAskBeforeHandling(&alwaysAsk);
1059
1060 // if we are not supposed to ask, and the preferred action is to use
1061 // a helper app or the system default, we just launch the URI.
1062 if (!alwaysAsk && (preferredAction == nsIHandlerInfo::useHelperApp ||
1063 preferredAction == nsIHandlerInfo::useSystemDefault))
1064 return handler->LaunchWithURI(uri, aWindowContext);
1065
1066 nsCOMPtr<nsIContentDispatchChooser> chooser =
1067 do_CreateInstance("@mozilla.org/content-dispatch-chooser;1", &rv);
1068 NS_ENSURE_SUCCESS(rv, rv);
1069
1070 return chooser->Ask(handler, aWindowContext, uri,
1071 nsIContentDispatchChooser::REASON_CANNOT_HANDLE);
1072 }
1073
1074 NS_IMETHODIMP nsExternalHelperAppService::GetApplicationDescription(const nsACString& aScheme, nsAString& _retval)
1075 {
1076 // this method should only be implemented by each OS specific implementation of this service.
1077 return NS_ERROR_NOT_IMPLEMENTED;
1078 }
1079
1080
1081 //////////////////////////////////////////////////////////////////////////////////////////////////////
1082 // Methods related to deleting temporary files on exit
1083 //////////////////////////////////////////////////////////////////////////////////////////////////////
1084
1085 /* static */
1086 nsresult
1087 nsExternalHelperAppService::DeleteTemporaryFileHelper(nsIFile * aTemporaryFile,
1088 nsCOMArray<nsIFile> &aFileList)
1089 {
1090 bool isFile = false;
1091
1092 // as a safety measure, make sure the nsIFile is really a file and not a directory object.
1093 aTemporaryFile->IsFile(&isFile);
1094 if (!isFile) return NS_OK;
1095
1096 aFileList.AppendObject(aTemporaryFile);
1097
1098 return NS_OK;
1099 }
1100
1101 NS_IMETHODIMP
1102 nsExternalHelperAppService::DeleteTemporaryFileOnExit(nsIFile* aTemporaryFile)
1103 {
1104 return DeleteTemporaryFileHelper(aTemporaryFile, mTemporaryFilesList);
1105 }
1106
1107 NS_IMETHODIMP
1108 nsExternalHelperAppService::DeleteTemporaryPrivateFileWhenPossible(nsIFile* aTemporaryFile)
1109 {
1110 return DeleteTemporaryFileHelper(aTemporaryFile, mTemporaryPrivateFilesList);
1111 }
1112
1113 void nsExternalHelperAppService::ExpungeTemporaryFilesHelper(nsCOMArray<nsIFile> &fileList)
1114 {
1115 int32_t numEntries = fileList.Count();
1116 nsIFile* localFile;
1117 for (int32_t index = 0; index < numEntries; index++)
1118 {
1119 localFile = fileList[index];
1120 if (localFile) {
1121 // First make the file writable, since the temp file is probably readonly.
1122 localFile->SetPermissions(0600);
1123 localFile->Remove(false);
1124 }
1125 }
1126
1127 fileList.Clear();
1128 }
1129
1130 void nsExternalHelperAppService::ExpungeTemporaryFiles()
1131 {
1132 ExpungeTemporaryFilesHelper(mTemporaryFilesList);
1133 }
1134
1135 void nsExternalHelperAppService::ExpungeTemporaryPrivateFiles()
1136 {
1137 ExpungeTemporaryFilesHelper(mTemporaryPrivateFilesList);
1138 }
1139
1140 static const char kExternalWarningPrefPrefix[] =
1141 "network.protocol-handler.warn-external.";
1142 static const char kExternalWarningDefaultPref[] =
1143 "network.protocol-handler.warn-external-default";
1144
1145 NS_IMETHODIMP
1146 nsExternalHelperAppService::GetProtocolHandlerInfo(const nsACString &aScheme,
1147 nsIHandlerInfo **aHandlerInfo)
1148 {
1149 // XXX enterprise customers should be able to turn this support off with a
1150 // single master pref (maybe use one of the "exposed" prefs here?)
1151
1152 bool exists;
1153 nsresult rv = GetProtocolHandlerInfoFromOS(aScheme, &exists, aHandlerInfo);
1154 if (NS_FAILED(rv)) {
1155 // Either it knows nothing, or we ran out of memory
1156 return NS_ERROR_FAILURE;
1157 }
1158
1159 nsCOMPtr<nsIHandlerService> handlerSvc = do_GetService(NS_HANDLERSERVICE_CONTRACTID);
1160 if (handlerSvc) {
1161 bool hasHandler = false;
1162 (void) handlerSvc->Exists(*aHandlerInfo, &hasHandler);
1163 if (hasHandler) {
1164 rv = handlerSvc->FillHandlerInfo(*aHandlerInfo, EmptyCString());
1165 if (NS_SUCCEEDED(rv))
1166 return NS_OK;
1167 }
1168 }
1169
1170 return SetProtocolHandlerDefaults(*aHandlerInfo, exists);
1171 }
1172
1173 NS_IMETHODIMP
1174 nsExternalHelperAppService::GetProtocolHandlerInfoFromOS(const nsACString &aScheme,
1175 bool *found,
1176 nsIHandlerInfo **aHandlerInfo)
1177 {
1178 // intended to be implemented by the subclass
1179 return NS_ERROR_NOT_IMPLEMENTED;
1180 }
1181
1182 NS_IMETHODIMP
1183 nsExternalHelperAppService::SetProtocolHandlerDefaults(nsIHandlerInfo *aHandlerInfo,
1184 bool aOSHandlerExists)
1185 {
1186 // this type isn't in our database, so we've only got an OS default handler,
1187 // if one exists
1188
1189 if (aOSHandlerExists) {
1190 // we've got a default, so use it
1191 aHandlerInfo->SetPreferredAction(nsIHandlerInfo::useSystemDefault);
1192
1193 // whether or not to ask the user depends on the warning preference
1194 nsAutoCString scheme;
1195 aHandlerInfo->GetType(scheme);
1196
1197 nsAutoCString warningPref(kExternalWarningPrefPrefix);
1198 warningPref += scheme;
1199 bool warn;
1200 if (NS_FAILED(Preferences::GetBool(warningPref.get(), &warn))) {
1201 // no scheme-specific value, check the default
1202 warn = Preferences::GetBool(kExternalWarningDefaultPref, true);
1203 }
1204 aHandlerInfo->SetAlwaysAskBeforeHandling(warn);
1205 } else {
1206 // If no OS default existed, we set the preferred action to alwaysAsk.
1207 // This really means not initialized (i.e. there's no available handler)
1208 // to all the code...
1209 aHandlerInfo->SetPreferredAction(nsIHandlerInfo::alwaysAsk);
1210 }
1211
1212 return NS_OK;
1213 }
1214
1215 // XPCOM profile change observer
1216 NS_IMETHODIMP
1217 nsExternalHelperAppService::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *someData )
1218 {
1219 if (!strcmp(aTopic, "profile-before-change")) {
1220 ExpungeTemporaryFiles();
1221 } else if (!strcmp(aTopic, "last-pb-context-exited")) {
1222 ExpungeTemporaryPrivateFiles();
1223 }
1224 return NS_OK;
1225 }
1226
1227 //////////////////////////////////////////////////////////////////////////////////////////////////////
1228 // begin external app handler implementation
1229 //////////////////////////////////////////////////////////////////////////////////////////////////////
1230
1231 NS_IMPL_ADDREF(nsExternalAppHandler)
1232 NS_IMPL_RELEASE(nsExternalAppHandler)
1233
1234 NS_INTERFACE_MAP_BEGIN(nsExternalAppHandler)
1235 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStreamListener)
1236 NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
1237 NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
1238 NS_INTERFACE_MAP_ENTRY(nsIHelperAppLauncher)
1239 NS_INTERFACE_MAP_ENTRY(nsICancelable)
1240 NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
1241 NS_INTERFACE_MAP_ENTRY(nsIBackgroundFileSaverObserver)
1242 NS_INTERFACE_MAP_END_THREADSAFE
1243
1244 nsExternalAppHandler::nsExternalAppHandler(nsIMIMEInfo * aMIMEInfo,
1245 const nsCSubstring& aTempFileExtension,
1246 nsIInterfaceRequestor* aWindowContext,
1247 nsExternalHelperAppService *aExtProtSvc,
1248 const nsAString& aSuggestedFilename,
1249 uint32_t aReason, bool aForceSave)
1250 : mMimeInfo(aMIMEInfo)
1251 , mWindowContext(aWindowContext)
1252 , mWindowToClose(nullptr)
1253 , mSuggestedFileName(aSuggestedFilename)
1254 , mForceSave(aForceSave)
1255 , mCanceled(false)
1256 , mShouldCloseWindow(false)
1257 , mStopRequestIssued(false)
1258 , mReason(aReason)
1259 , mContentLength(-1)
1260 , mProgress(0)
1261 , mSaver(nullptr)
1262 , mDialogProgressListener(nullptr)
1263 , mTransfer(nullptr)
1264 , mRequest(nullptr)
1265 , mExtProtSvc(aExtProtSvc)
1266 {
1267
1268 // make sure the extention includes the '.'
1269 if (!aTempFileExtension.IsEmpty() && aTempFileExtension.First() != '.')
1270 mTempFileExtension = char16_t('.');
1271 AppendUTF8toUTF16(aTempFileExtension, mTempFileExtension);
1272
1273 // replace platform specific path separator and illegal characters to avoid any confusion
1274 mSuggestedFileName.ReplaceChar(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS, '_');
1275 mTempFileExtension.ReplaceChar(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS, '_');
1276
1277 // Remove unsafe bidi characters which might have spoofing implications (bug 511521).
1278 const char16_t unsafeBidiCharacters[] = {
1279 char16_t(0x061c), // Arabic Letter Mark
1280 char16_t(0x200e), // Left-to-Right Mark
1281 char16_t(0x200f), // Right-to-Left Mark
1282 char16_t(0x202a), // Left-to-Right Embedding
1283 char16_t(0x202b), // Right-to-Left Embedding
1284 char16_t(0x202c), // Pop Directional Formatting
1285 char16_t(0x202d), // Left-to-Right Override
1286 char16_t(0x202e), // Right-to-Left Override
1287 char16_t(0x2066), // Left-to-Right Isolate
1288 char16_t(0x2067), // Right-to-Left Isolate
1289 char16_t(0x2068), // First Strong Isolate
1290 char16_t(0x2069), // Pop Directional Isolate
1291 char16_t(0)
1292 };
1293 mSuggestedFileName.ReplaceChar(unsafeBidiCharacters, '_');
1294 mTempFileExtension.ReplaceChar(unsafeBidiCharacters, '_');
1295
1296 // Make sure extension is correct.
1297 EnsureSuggestedFileName();
1298
1299 mBufferSize = Preferences::GetUint("network.buffer.cache.size", 4096);
1300 }
1301
1302 nsExternalAppHandler::~nsExternalAppHandler()
1303 {
1304 MOZ_ASSERT(!mSaver, "Saver should hold a reference to us until deleted");
1305 }
1306
1307 void
1308 nsExternalAppHandler::DidDivertRequest(nsIRequest *request)
1309 {
1310 MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Content, "in child process");
1311 // Remove our request from the child loadGroup
1312 RetargetLoadNotifications(request);
1313 MaybeCloseWindow();
1314 }
1315
1316 NS_IMETHODIMP nsExternalAppHandler::SetWebProgressListener(nsIWebProgressListener2 * aWebProgressListener)
1317 {
1318 // This is always called by nsHelperDlg.js. Go ahead and register the
1319 // progress listener. At this point, we don't have mTransfer.
1320 mDialogProgressListener = aWebProgressListener;
1321 return NS_OK;
1322 }
1323
1324 NS_IMETHODIMP nsExternalAppHandler::GetTargetFile(nsIFile** aTarget)
1325 {
1326 if (mFinalFileDestination)
1327 *aTarget = mFinalFileDestination;
1328 else
1329 *aTarget = mTempFile;
1330
1331 NS_IF_ADDREF(*aTarget);
1332 return NS_OK;
1333 }
1334
1335 NS_IMETHODIMP nsExternalAppHandler::GetTargetFileIsExecutable(bool *aExec)
1336 {
1337 // Use the real target if it's been set
1338 if (mFinalFileDestination)
1339 return mFinalFileDestination->IsExecutable(aExec);
1340
1341 // Otherwise, use the stored executable-ness of the temporary
1342 *aExec = mTempFileIsExecutable;
1343 return NS_OK;
1344 }
1345
1346 NS_IMETHODIMP nsExternalAppHandler::GetTimeDownloadStarted(PRTime* aTime)
1347 {
1348 *aTime = mTimeDownloadStarted;
1349 return NS_OK;
1350 }
1351
1352 NS_IMETHODIMP nsExternalAppHandler::GetContentLength(int64_t *aContentLength)
1353 {
1354 *aContentLength = mContentLength;
1355 return NS_OK;
1356 }
1357
1358 void nsExternalAppHandler::RetargetLoadNotifications(nsIRequest *request)
1359 {
1360 // we are going to run the downloading of the helper app in our own little docloader / load group context.
1361 // so go ahead and force the creation of a load group and doc loader for us to use...
1362 nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request);
1363 if (!aChannel)
1364 return;
1365
1366 // we need to store off the original (pre redirect!) channel that initiated the load. We do
1367 // this so later on, we can pass any refresh urls associated with the original channel back to the
1368 // window context which started the whole process. More comments about that are listed below....
1369 // HACK ALERT: it's pretty bogus that we are getting the document channel from the doc loader.
1370 // ideally we should be able to just use mChannel (the channel we are extracting content from) or
1371 // the default load channel associated with the original load group. Unfortunately because
1372 // a redirect may have occurred, the doc loader is the only one with a ptr to the original channel
1373 // which is what we really want....
1374
1375 // Note that we need to do this before removing aChannel from the loadgroup,
1376 // since that would mess with the original channel on the loader.
1377 nsCOMPtr<nsIDocumentLoader> origContextLoader =
1378 do_GetInterface(mWindowContext);
1379 if (origContextLoader)
1380 origContextLoader->GetDocumentChannel(getter_AddRefs(mOriginalChannel));
1381
1382 bool isPrivate = NS_UsePrivateBrowsing(aChannel);
1383
1384 nsCOMPtr<nsILoadGroup> oldLoadGroup;
1385 aChannel->GetLoadGroup(getter_AddRefs(oldLoadGroup));
1386
1387 if(oldLoadGroup)
1388 oldLoadGroup->RemoveRequest(request, nullptr, NS_BINDING_RETARGETED);
1389
1390 aChannel->SetLoadGroup(nullptr);
1391 aChannel->SetNotificationCallbacks(nullptr);
1392
1393 nsCOMPtr<nsIPrivateBrowsingChannel> pbChannel = do_QueryInterface(aChannel);
1394 if (pbChannel) {
1395 pbChannel->SetPrivate(isPrivate);
1396 }
1397 }
1398
1399 /**
1400 * Make mTempFileExtension contain an extension exactly when its previous value
1401 * is different from mSuggestedFileName's extension, so that it can be appended
1402 * to mSuggestedFileName and form a valid, useful leaf name.
1403 * This is required so that the (renamed) temporary file has the correct extension
1404 * after downloading to make sure the OS will launch the application corresponding
1405 * to the MIME type (which was used to calculate mTempFileExtension). This prevents
1406 * a cgi-script named foobar.exe that returns application/zip from being named
1407 * foobar.exe and executed as an executable file. It also blocks content that
1408 * a web site might provide with a content-disposition header indicating
1409 * filename="foobar.exe" from being downloaded to a file with extension .exe
1410 * and executed.
1411 */
1412 void nsExternalAppHandler::EnsureSuggestedFileName()
1413 {
1414 // Make sure there is a mTempFileExtension (not "" or ".").
1415 // Remember that mTempFileExtension will always have the leading "."
1416 // (the check for empty is just to be safe).
1417 if (mTempFileExtension.Length() > 1)
1418 {
1419 // Get mSuggestedFileName's current extension.
1420 nsAutoString fileExt;
1421 int32_t pos = mSuggestedFileName.RFindChar('.');
1422 if (pos != kNotFound)
1423 mSuggestedFileName.Right(fileExt, mSuggestedFileName.Length() - pos);
1424
1425 // Now, compare fileExt to mTempFileExtension.
1426 if (fileExt.Equals(mTempFileExtension, nsCaseInsensitiveStringComparator()))
1427 {
1428 // Matches -> mTempFileExtension can be empty
1429 mTempFileExtension.Truncate();
1430 }
1431 }
1432 }
1433
1434 nsresult nsExternalAppHandler::SetUpTempFile(nsIChannel * aChannel)
1435 {
1436 // First we need to try to get the destination directory for the temporary
1437 // file.
1438 nsresult rv = GetDownloadDirectory(getter_AddRefs(mTempFile));
1439 NS_ENSURE_SUCCESS(rv, rv);
1440
1441 // At this point, we do not have a filename for the temp file. For security
1442 // purposes, this cannot be predictable, so we must use a cryptographic
1443 // quality PRNG to generate one.
1444 // We will request raw random bytes, and transform that to a base64 string,
1445 // as all characters from the base64 set are acceptable for filenames. For
1446 // each three bytes of random data, we will get four bytes of ASCII. Request
1447 // a bit more, to be safe, and truncate to the length we want in the end.
1448
1449 const uint32_t wantedFileNameLength = 8;
1450 const uint32_t requiredBytesLength =
1451 static_cast<uint32_t>((wantedFileNameLength + 1) / 4 * 3);
1452
1453 nsCOMPtr<nsIRandomGenerator> rg =
1454 do_GetService("@mozilla.org/security/random-generator;1", &rv);
1455 NS_ENSURE_SUCCESS(rv, rv);
1456
1457 uint8_t *buffer;
1458 rv = rg->GenerateRandomBytes(requiredBytesLength, &buffer);
1459 NS_ENSURE_SUCCESS(rv, rv);
1460
1461 nsAutoCString tempLeafName;
1462 nsDependentCSubstring randomData(reinterpret_cast<const char*>(buffer), requiredBytesLength);
1463 rv = Base64Encode(randomData, tempLeafName);
1464 NS_Free(buffer);
1465 buffer = nullptr;
1466 NS_ENSURE_SUCCESS(rv, rv);
1467
1468 tempLeafName.Truncate(wantedFileNameLength);
1469
1470 // Base64 characters are alphanumeric (a-zA-Z0-9) and '+' and '/', so we need
1471 // to replace illegal characters -- notably '/'
1472 tempLeafName.ReplaceChar(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS, '_');
1473
1474 // now append our extension.
1475 nsAutoCString ext;
1476 mMimeInfo->GetPrimaryExtension(ext);
1477 if (!ext.IsEmpty()) {
1478 ext.ReplaceChar(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS, '_');
1479 if (ext.First() != '.')
1480 tempLeafName.Append('.');
1481 tempLeafName.Append(ext);
1482 }
1483
1484 // We need to temporarily create a dummy file with the correct
1485 // file extension to determine the executable-ness, so do this before adding
1486 // the extra .part extension.
1487 nsCOMPtr<nsIFile> dummyFile;
1488 rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(dummyFile));
1489 NS_ENSURE_SUCCESS(rv, rv);
1490
1491 // Set the file name without .part
1492 rv = dummyFile->Append(NS_ConvertUTF8toUTF16(tempLeafName));
1493 NS_ENSURE_SUCCESS(rv, rv);
1494 rv = dummyFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
1495 NS_ENSURE_SUCCESS(rv, rv);
1496
1497 // Store executable-ness then delete
1498 dummyFile->IsExecutable(&mTempFileIsExecutable);
1499 dummyFile->Remove(false);
1500
1501 // Add an additional .part to prevent the OS from running this file in the
1502 // default application.
1503 tempLeafName.Append(NS_LITERAL_CSTRING(".part"));
1504
1505 rv = mTempFile->Append(NS_ConvertUTF8toUTF16(tempLeafName));
1506 // make this file unique!!!
1507 NS_ENSURE_SUCCESS(rv, rv);
1508 rv = mTempFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0644);
1509 NS_ENSURE_SUCCESS(rv, rv);
1510
1511 // Now save the temp leaf name, minus the ".part" bit, so we can use it later.
1512 // This is a bit broken in the case when createUnique actually had to append
1513 // some numbers, because then we now have a filename like foo.bar-1.part and
1514 // we'll end up with foo.bar-1.bar as our final filename if we end up using
1515 // this. But the other options are all bad too.... Ideally we'd have a way
1516 // to tell createUnique to put its unique marker before the extension that
1517 // comes before ".part" or something.
1518 rv = mTempFile->GetLeafName(mTempLeafName);
1519 NS_ENSURE_SUCCESS(rv, rv);
1520
1521 NS_ENSURE_TRUE(StringEndsWith(mTempLeafName, NS_LITERAL_STRING(".part")),
1522 NS_ERROR_UNEXPECTED);
1523
1524 // Strip off the ".part" from mTempLeafName
1525 mTempLeafName.Truncate(mTempLeafName.Length() - ArrayLength(".part") + 1);
1526
1527 MOZ_ASSERT(!mSaver, "Output file initialization called more than once!");
1528 mSaver = do_CreateInstance(NS_BACKGROUNDFILESAVERSTREAMLISTENER_CONTRACTID,
1529 &rv);
1530 NS_ENSURE_SUCCESS(rv, rv);
1531
1532 rv = mSaver->SetObserver(this);
1533 if (NS_FAILED(rv)) {
1534 mSaver = nullptr;
1535 return rv;
1536 }
1537
1538 rv = mSaver->EnableSha256();
1539 NS_ENSURE_SUCCESS(rv, rv);
1540
1541 rv = mSaver->EnableSignatureInfo();
1542 NS_ENSURE_SUCCESS(rv, rv);
1543 LOG(("Enabled hashing and signature verification"));
1544
1545 rv = mSaver->SetTarget(mTempFile, false);
1546 NS_ENSURE_SUCCESS(rv, rv);
1547
1548 return rv;
1549 }
1550
1551 NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest *request, nsISupports * aCtxt)
1552 {
1553 NS_PRECONDITION(request, "OnStartRequest without request?");
1554
1555 // Set mTimeDownloadStarted here as the download has already started and
1556 // we want to record the start time before showing the filepicker.
1557 mTimeDownloadStarted = PR_Now();
1558
1559 mRequest = request;
1560
1561 nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request);
1562
1563 nsresult rv;
1564
1565 nsCOMPtr<nsIFileChannel> fileChan(do_QueryInterface(request));
1566 mIsFileChannel = fileChan != nullptr;
1567
1568 // Get content length
1569 if (aChannel) {
1570 aChannel->GetContentLength(&mContentLength);
1571 }
1572
1573 nsCOMPtr<nsIPropertyBag2> props(do_QueryInterface(request, &rv));
1574 // Determine whether a new window was opened specifically for this request
1575 if (props) {
1576 bool tmp = false;
1577 props->GetPropertyAsBool(NS_LITERAL_STRING("docshell.newWindowTarget"),
1578 &tmp);
1579 mShouldCloseWindow = tmp;
1580 }
1581
1582 // Now get the URI
1583 if (aChannel)
1584 {
1585 aChannel->GetURI(getter_AddRefs(mSourceUrl));
1586 }
1587
1588 // retarget all load notifications to our docloader instead of the original window's docloader...
1589 RetargetLoadNotifications(request);
1590
1591 // Check to see if there is a refresh header on the original channel.
1592 if (mOriginalChannel) {
1593 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mOriginalChannel));
1594 if (httpChannel) {
1595 nsAutoCString refreshHeader;
1596 httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("refresh"),
1597 refreshHeader);
1598 if (!refreshHeader.IsEmpty()) {
1599 mShouldCloseWindow = false;
1600 }
1601 }
1602 }
1603
1604 // Close the underlying DOMWindow if there is no refresh header
1605 // and it was opened specifically for the download
1606 MaybeCloseWindow();
1607
1608 // In an IPC setting, we're allowing the child process, here, to make
1609 // decisions about decoding the channel (e.g. decompression). It will
1610 // still forward the decoded (uncompressed) data back to the parent.
1611 // Con: Uncompressed data means more IPC overhead.
1612 // Pros: ExternalHelperAppParent doesn't need to implement nsIEncodedChannel.
1613 // Parent process doesn't need to expect CPU time on decompression.
1614 nsCOMPtr<nsIEncodedChannel> encChannel = do_QueryInterface( aChannel );
1615 if (encChannel)
1616 {
1617 // Turn off content encoding conversions if needed
1618 bool applyConversion = true;
1619
1620 nsCOMPtr<nsIURL> sourceURL(do_QueryInterface(mSourceUrl));
1621 if (sourceURL)
1622 {
1623 nsAutoCString extension;
1624 sourceURL->GetFileExtension(extension);
1625 if (!extension.IsEmpty())
1626 {
1627 nsCOMPtr<nsIUTF8StringEnumerator> encEnum;
1628 encChannel->GetContentEncodings(getter_AddRefs(encEnum));
1629 if (encEnum)
1630 {
1631 bool hasMore;
1632 rv = encEnum->HasMore(&hasMore);
1633 if (NS_SUCCEEDED(rv) && hasMore)
1634 {
1635 nsAutoCString encType;
1636 rv = encEnum->GetNext(encType);
1637 if (NS_SUCCEEDED(rv) && !encType.IsEmpty())
1638 {
1639 mExtProtSvc->ApplyDecodingForExtension(extension, encType,
1640 &applyConversion);
1641 }
1642 }
1643 }
1644 }
1645 }
1646
1647 encChannel->SetApplyConversion( applyConversion );
1648 }
1649
1650 // At this point, the child process has done everything it can usefully do
1651 // for OnStartRequest.
1652 if (XRE_GetProcessType() == GeckoProcessType_Content)
1653 return NS_OK;
1654
1655 rv = SetUpTempFile(aChannel);
1656 if (NS_FAILED(rv)) {
1657 nsresult transferError = rv;
1658
1659 rv = CreateFailedTransfer(aChannel && NS_UsePrivateBrowsing(aChannel));
1660 #ifdef PR_LOGGING
1661 if (NS_FAILED(rv)) {
1662 LOG(("Failed to create transfer to report failure."
1663 "Will fallback to prompter!"));
1664 }
1665 #endif
1666
1667 mCanceled = true;
1668 request->Cancel(transferError);
1669
1670 nsAutoString path;
1671 if (mTempFile)
1672 mTempFile->GetPath(path);
1673
1674 SendStatusChange(kWriteError, transferError, request, path);
1675
1676 return NS_OK;
1677 }
1678
1679 // Inform channel it is open on behalf of a download to prevent caching.
1680 nsCOMPtr<nsIHttpChannelInternal> httpInternal = do_QueryInterface(aChannel);
1681 if (httpInternal) {
1682 httpInternal->SetChannelIsForDownload(true);
1683 }
1684
1685 // now that the temp file is set up, find out if we need to invoke a dialog
1686 // asking the user what they want us to do with this content...
1687
1688 // We can get here for three reasons: "can't handle", "sniffed type", or
1689 // "server sent content-disposition:attachment". In the first case we want
1690 // to honor the user's "always ask" pref; in the other two cases we want to
1691 // honor it only if the default action is "save". Opening attachments in
1692 // helper apps by default breaks some websites (especially if the attachment
1693 // is one part of a multipart document). Opening sniffed content in helper
1694 // apps by default introduces security holes that we'd rather not have.
1695
1696 // So let's find out whether the user wants to be prompted. If he does not,
1697 // check mReason and the preferred action to see what we should do.
1698
1699 bool alwaysAsk = true;
1700 mMimeInfo->GetAlwaysAskBeforeHandling(&alwaysAsk);
1701 if (alwaysAsk)
1702 {
1703 // But we *don't* ask if this mimeInfo didn't come from
1704 // our user configuration datastore and the user has said
1705 // at some point in the distant past that they don't
1706 // want to be asked. The latter fact would have been
1707 // stored in pref strings back in the old days.
1708
1709 bool mimeTypeIsInDatastore = false;
1710 nsCOMPtr<nsIHandlerService> handlerSvc = do_GetService(NS_HANDLERSERVICE_CONTRACTID);
1711 if (handlerSvc)
1712 handlerSvc->Exists(mMimeInfo, &mimeTypeIsInDatastore);
1713 if (!handlerSvc || !mimeTypeIsInDatastore)
1714 {
1715 nsAutoCString MIMEType;
1716 mMimeInfo->GetMIMEType(MIMEType);
1717
1718 if (!GetNeverAskFlagFromPref(NEVER_ASK_FOR_SAVE_TO_DISK_PREF, MIMEType.get()))
1719 {
1720 // Don't need to ask after all.
1721 alwaysAsk = false;
1722 // Make sure action matches pref (save to disk).
1723 mMimeInfo->SetPreferredAction(nsIMIMEInfo::saveToDisk);
1724 }
1725 else if (!GetNeverAskFlagFromPref(NEVER_ASK_FOR_OPEN_FILE_PREF, MIMEType.get()))
1726 {
1727 // Don't need to ask after all.
1728 alwaysAsk = false;
1729 }
1730 }
1731 }
1732
1733 int32_t action = nsIMIMEInfo::saveToDisk;
1734 mMimeInfo->GetPreferredAction( &action );
1735
1736 // OK, now check why we're here
1737 if (!alwaysAsk && mReason != nsIHelperAppLauncherDialog::REASON_CANTHANDLE) {
1738 // Force asking if we're not saving. See comment back when we fetched the
1739 // alwaysAsk boolean for details.
1740 alwaysAsk = (action != nsIMIMEInfo::saveToDisk);
1741 }
1742
1743 // if we were told that we _must_ save to disk without asking, all the stuff
1744 // before this is irrelevant; override it
1745 if (mForceSave) {
1746 alwaysAsk = false;
1747 action = nsIMIMEInfo::saveToDisk;
1748 }
1749
1750 if (alwaysAsk)
1751 {
1752 // invoke the dialog!!!!! use mWindowContext as the window context parameter for the dialog request
1753 mDialog = do_CreateInstance( NS_HELPERAPPLAUNCHERDLG_CONTRACTID, &rv );
1754 NS_ENSURE_SUCCESS(rv, rv);
1755
1756 // this will create a reference cycle (the dialog holds a reference to us as
1757 // nsIHelperAppLauncher), which will be broken in Cancel or CreateTransfer.
1758 rv = mDialog->Show( this, mWindowContext, mReason );
1759
1760 // what do we do if the dialog failed? I guess we should call Cancel and abort the load....
1761 }
1762 else
1763 {
1764
1765 // We need to do the save/open immediately, then.
1766 #ifdef XP_WIN
1767 /* We need to see whether the file we've got here could be
1768 * executable. If it could, we had better not try to open it!
1769 * We can skip this check, though, if we have a setting to open in a
1770 * helper app.
1771 * This code mirrors the code in
1772 * nsExternalAppHandler::LaunchWithApplication so that what we
1773 * test here is as close as possible to what will really be
1774 * happening if we decide to execute
1775 */
1776 nsCOMPtr<nsIHandlerApp> prefApp;
1777 mMimeInfo->GetPreferredApplicationHandler(getter_AddRefs(prefApp));
1778 if (action != nsIMIMEInfo::useHelperApp || !prefApp) {
1779 nsCOMPtr<nsIFile> fileToTest;
1780 GetTargetFile(getter_AddRefs(fileToTest));
1781 if (fileToTest) {
1782 bool isExecutable;
1783 rv = fileToTest->IsExecutable(&isExecutable);
1784 if (NS_FAILED(rv) || isExecutable) { // checking NS_FAILED, because paranoia is good
1785 action = nsIMIMEInfo::saveToDisk;
1786 }
1787 } else { // Paranoia is good here too, though this really should not happen
1788 NS_WARNING("GetDownloadInfo returned a null file after the temp file has been set up! ");
1789 action = nsIMIMEInfo::saveToDisk;
1790 }
1791 }
1792
1793 #endif
1794 if (action == nsIMIMEInfo::useHelperApp ||
1795 action == nsIMIMEInfo::useSystemDefault)
1796 {
1797 rv = LaunchWithApplication(nullptr, false);
1798 }
1799 else // Various unknown actions go here too
1800 {
1801 rv = SaveToDisk(nullptr, false);
1802 }
1803 }
1804
1805 return NS_OK;
1806 }
1807
1808 // Convert error info into proper message text and send OnStatusChange
1809 // notification to the dialog progress listener or nsITransfer implementation.
1810 void nsExternalAppHandler::SendStatusChange(ErrorType type, nsresult rv, nsIRequest *aRequest, const nsAFlatString &path)
1811 {
1812 nsAutoString msgId;
1813 switch(rv)
1814 {
1815 case NS_ERROR_OUT_OF_MEMORY:
1816 // No memory
1817 msgId.AssignLiteral("noMemory");
1818 break;
1819
1820 case NS_ERROR_FILE_DISK_FULL:
1821 case NS_ERROR_FILE_NO_DEVICE_SPACE:
1822 // Out of space on target volume.
1823 msgId.AssignLiteral("diskFull");
1824 break;
1825
1826 case NS_ERROR_FILE_READ_ONLY:
1827 // Attempt to write to read/only file.
1828 msgId.AssignLiteral("readOnly");
1829 break;
1830
1831 case NS_ERROR_FILE_ACCESS_DENIED:
1832 if (type == kWriteError) {
1833 // Attempt to write without sufficient permissions.
1834 #if defined(ANDROID)
1835 // On Android (and Gonk), this means the SD card is present but
1836 // unavailable (read-only).
1837 msgId.AssignLiteral("SDAccessErrorCardReadOnly");
1838 #else
1839 msgId.AssignLiteral("accessError");
1840 #endif
1841 }
1842 else
1843 {
1844 msgId.AssignLiteral("launchError");
1845 }
1846 break;
1847
1848 case NS_ERROR_FILE_NOT_FOUND:
1849 case NS_ERROR_FILE_TARGET_DOES_NOT_EXIST:
1850 case NS_ERROR_FILE_UNRECOGNIZED_PATH:
1851 // Helper app not found, let's verify this happened on launch
1852 if (type == kLaunchError) {
1853 msgId.AssignLiteral("helperAppNotFound");
1854 break;
1855 }
1856 #if defined(ANDROID)
1857 else if (type == kWriteError) {
1858 // On Android (and Gonk), this means the SD card is missing (not in
1859 // SD slot).
1860 msgId.AssignLiteral("SDAccessErrorCardMissing");
1861 break;
1862 }
1863 #endif
1864 // fall through
1865
1866 default:
1867 // Generic read/write/launch error message.
1868 switch(type)
1869 {
1870 case kReadError:
1871 msgId.AssignLiteral("readError");
1872 break;
1873 case kWriteError:
1874 msgId.AssignLiteral("writeError");
1875 break;
1876 case kLaunchError:
1877 msgId.AssignLiteral("launchError");
1878 break;
1879 }
1880 break;
1881 }
1882 PR_LOG(nsExternalHelperAppService::mLog, PR_LOG_ERROR,
1883 ("Error: %s, type=%i, listener=0x%p, transfer=0x%p, rv=0x%08X\n",
1884 NS_LossyConvertUTF16toASCII(msgId).get(), type, mDialogProgressListener.get(), mTransfer.get(), rv));
1885 PR_LOG(nsExternalHelperAppService::mLog, PR_LOG_ERROR,
1886 (" path='%s'\n", NS_ConvertUTF16toUTF8(path).get()));
1887
1888 // Get properties file bundle and extract status string.
1889 nsCOMPtr<nsIStringBundleService> stringService =
1890 mozilla::services::GetStringBundleService();
1891 if (stringService)
1892 {
1893 nsCOMPtr<nsIStringBundle> bundle;
1894 if (NS_SUCCEEDED(stringService->CreateBundle("chrome://global/locale/nsWebBrowserPersist.properties", getter_AddRefs(bundle))))
1895 {
1896 nsXPIDLString msgText;
1897 const char16_t *strings[] = { path.get() };
1898 if(NS_SUCCEEDED(bundle->FormatStringFromName(msgId.get(), strings, 1, getter_Copies(msgText))))
1899 {
1900 if (mDialogProgressListener)
1901 {
1902 // We have a listener, let it handle the error.
1903 mDialogProgressListener->OnStatusChange(nullptr, (type == kReadError) ? aRequest : nullptr, rv, msgText);
1904 } else if (mTransfer) {
1905 mTransfer->OnStatusChange(nullptr, (type == kReadError) ? aRequest : nullptr, rv, msgText);
1906 }
1907 else
1908 if (XRE_GetProcessType() == GeckoProcessType_Default) {
1909 // We don't have a listener. Simply show the alert ourselves.
1910 nsresult qiRv;
1911 nsCOMPtr<nsIPrompt> prompter(do_GetInterface(mWindowContext, &qiRv));
1912 nsXPIDLString title;
1913 bundle->FormatStringFromName(MOZ_UTF16("title"),
1914 strings,
1915 1,
1916 getter_Copies(title));
1917
1918 PR_LOG(nsExternalHelperAppService::mLog, PR_LOG_DEBUG,
1919 ("mWindowContext=0x%p, prompter=0x%p, qi rv=0x%08X, title='%s', msg='%s'",
1920 mWindowContext.get(),
1921 prompter.get(),
1922 qiRv,
1923 NS_ConvertUTF16toUTF8(title).get(),
1924 NS_ConvertUTF16toUTF8(msgText).get()));
1925
1926 // If we didn't have a prompter we will try and get a window
1927 // instead, get it's docshell and use it to alert the user.
1928 if (!prompter)
1929 {
1930 nsCOMPtr<nsPIDOMWindow> window(do_GetInterface(mWindowContext));
1931 if (!window || !window->GetDocShell())
1932 {
1933 return;
1934 }
1935
1936 prompter = do_GetInterface(window->GetDocShell(), &qiRv);
1937
1938 PR_LOG(nsExternalHelperAppService::mLog, PR_LOG_DEBUG,
1939 ("No prompter from mWindowContext, using DocShell, " \
1940 "window=0x%p, docShell=0x%p, " \
1941 "prompter=0x%p, qi rv=0x%08X",
1942 window.get(),
1943 window->GetDocShell(),
1944 prompter.get(),
1945 qiRv));
1946
1947 // If we still don't have a prompter, there's nothing else we
1948 // can do so just return.
1949 if (!prompter)
1950 {
1951 PR_LOG(nsExternalHelperAppService::mLog, PR_LOG_ERROR,
1952 ("No prompter from DocShell, no way to alert user"));
1953 return;
1954 }
1955 }
1956
1957 // We should always have a prompter at this point.
1958 prompter->Alert(title, msgText);
1959 }
1960 }
1961 }
1962 }
1963 }
1964
1965 NS_IMETHODIMP
1966 nsExternalAppHandler::OnDataAvailable(nsIRequest *request, nsISupports * aCtxt,
1967 nsIInputStream * inStr,
1968 uint64_t sourceOffset, uint32_t count)
1969 {
1970 nsresult rv = NS_OK;
1971 // first, check to see if we've been canceled....
1972 if (mCanceled || !mSaver) // then go cancel our underlying channel too
1973 return request->Cancel(NS_BINDING_ABORTED);
1974
1975 // read the data out of the stream and write it to the temp file.
1976 if (count > 0)
1977 {
1978 mProgress += count;
1979
1980 nsCOMPtr<nsIStreamListener> saver = do_QueryInterface(mSaver);
1981 rv = saver->OnDataAvailable(request, aCtxt, inStr, sourceOffset, count);
1982 if (NS_SUCCEEDED(rv))
1983 {
1984 // Send progress notification.
1985 if (mTransfer) {
1986 mTransfer->OnProgressChange64(nullptr, request, mProgress,
1987 mContentLength, mProgress,
1988 mContentLength);
1989 }
1990 }
1991 else
1992 {
1993 // An error occurred, notify listener.
1994 nsAutoString tempFilePath;
1995 if (mTempFile)
1996 mTempFile->GetPath(tempFilePath);
1997 SendStatusChange(kReadError, rv, request, tempFilePath);
1998
1999 // Cancel the download.
2000 Cancel(rv);
2001 }
2002 }
2003 return rv;
2004 }
2005
2006 NS_IMETHODIMP nsExternalAppHandler::OnStopRequest(nsIRequest *request, nsISupports *aCtxt,
2007 nsresult aStatus)
2008 {
2009 LOG(("nsExternalAppHandler::OnStopRequest\n"
2010 " mCanceled=%d, mTransfer=0x%p, aStatus=0x%08X\n",
2011 mCanceled, mTransfer.get(), aStatus));
2012
2013 mStopRequestIssued = true;
2014
2015 // Cancel if the request did not complete successfully.
2016 if (!mCanceled && NS_FAILED(aStatus))
2017 {
2018 // Send error notification.
2019 nsAutoString tempFilePath;
2020 if (mTempFile)
2021 mTempFile->GetPath(tempFilePath);
2022 SendStatusChange( kReadError, aStatus, request, tempFilePath );
2023
2024 Cancel(aStatus);
2025 }
2026
2027 // first, check to see if we've been canceled....
2028 if (mCanceled || !mSaver)
2029 return NS_OK;
2030
2031 return mSaver->Finish(NS_OK);
2032 }
2033
2034 NS_IMETHODIMP
2035 nsExternalAppHandler::OnTargetChange(nsIBackgroundFileSaver *aSaver,
2036 nsIFile *aTarget)
2037 {
2038 return NS_OK;
2039 }
2040
2041 NS_IMETHODIMP
2042 nsExternalAppHandler::OnSaveComplete(nsIBackgroundFileSaver *aSaver,
2043 nsresult aStatus)
2044 {
2045 LOG(("nsExternalAppHandler::OnSaveComplete\n"
2046 " aSaver=0x%p, aStatus=0x%08X, mCanceled=%d, mTransfer=0x%p\n",
2047 aSaver, aStatus, mCanceled, mTransfer.get()));
2048
2049 if (!mCanceled) {
2050 // Save the hash
2051 (void)mSaver->GetSha256Hash(mHash);
2052 (void)mSaver->GetSignatureInfo(getter_AddRefs(mSignatureInfo));
2053 // Free the reference that the saver keeps on us, even if we couldn't get
2054 // the hash.
2055 mSaver = nullptr;
2056
2057 if (NS_FAILED(aStatus)) {
2058 nsAutoString path;
2059 mTempFile->GetPath(path);
2060
2061 // It may happen when e10s is enabled that there will be no transfer
2062 // object available to communicate status as expected by the system.
2063 // Let's try and create a temporary transfer object to take care of this
2064 // for us, we'll fall back to using the prompt service if we absolutely
2065 // have to.
2066 if (!mTransfer) {
2067 nsCOMPtr<nsIChannel> channel = do_QueryInterface(mRequest);
2068 // We don't care if this fails.
2069 CreateFailedTransfer(channel && NS_UsePrivateBrowsing(channel));
2070 }
2071
2072 SendStatusChange(kWriteError, aStatus, nullptr, path);
2073 if (!mCanceled)
2074 Cancel(aStatus);
2075 return NS_OK;
2076 }
2077 }
2078
2079 // Notify the transfer object that we are done if the user has chosen an
2080 // action. If the user hasn't chosen an action, the progress listener
2081 // (nsITransfer) will be notified in CreateTransfer.
2082 if (mTransfer) {
2083 NotifyTransfer(aStatus);
2084 }
2085
2086 return NS_OK;
2087 }
2088
2089 void nsExternalAppHandler::NotifyTransfer(nsresult aStatus)
2090 {
2091 MOZ_ASSERT(NS_IsMainThread(), "Must notify on main thread");
2092 MOZ_ASSERT(mTransfer, "We must have an nsITransfer");
2093
2094 LOG(("Notifying progress listener"));
2095
2096 if (NS_SUCCEEDED(aStatus)) {
2097 (void)mTransfer->SetSha256Hash(mHash);
2098 (void)mTransfer->SetSignatureInfo(mSignatureInfo);
2099 (void)mTransfer->OnProgressChange64(nullptr, nullptr, mProgress,
2100 mContentLength, mProgress, mContentLength);
2101 }
2102
2103 (void)mTransfer->OnStateChange(nullptr, nullptr,
2104 nsIWebProgressListener::STATE_STOP |
2105 nsIWebProgressListener::STATE_IS_REQUEST |
2106 nsIWebProgressListener::STATE_IS_NETWORK, aStatus);
2107
2108 // This nsITransfer object holds a reference to us (we are its observer), so
2109 // we need to release the reference to break a reference cycle (and therefore
2110 // to prevent leaking). We do this even if the previous calls failed.
2111 mTransfer = nullptr;
2112 }
2113
2114 NS_IMETHODIMP nsExternalAppHandler::GetMIMEInfo(nsIMIMEInfo ** aMIMEInfo)
2115 {
2116 *aMIMEInfo = mMimeInfo;
2117 NS_ADDREF(*aMIMEInfo);
2118 return NS_OK;
2119 }
2120
2121 NS_IMETHODIMP nsExternalAppHandler::GetSource(nsIURI ** aSourceURI)
2122 {
2123 NS_ENSURE_ARG(aSourceURI);
2124 *aSourceURI = mSourceUrl;
2125 NS_IF_ADDREF(*aSourceURI);
2126 return NS_OK;
2127 }
2128
2129 NS_IMETHODIMP nsExternalAppHandler::GetSuggestedFileName(nsAString& aSuggestedFileName)
2130 {
2131 aSuggestedFileName = mSuggestedFileName;
2132 return NS_OK;
2133 }
2134
2135 nsresult nsExternalAppHandler::CreateTransfer()
2136 {
2137 LOG(("nsExternalAppHandler::CreateTransfer"));
2138
2139 MOZ_ASSERT(NS_IsMainThread(), "Must create transfer on main thread");
2140 // We are back from the helper app dialog (where the user chooses to save or
2141 // open), but we aren't done processing the load. in this case, throw up a
2142 // progress dialog so the user can see what's going on.
2143 // Also, release our reference to mDialog. We don't need it anymore, and we
2144 // need to break the reference cycle.
2145 mDialog = nullptr;
2146 if (!mDialogProgressListener) {
2147 NS_WARNING("The dialog should nullify the dialog progress listener");
2148 }
2149 nsresult rv;
2150
2151 // We must be able to create an nsITransfer object. If not, it doesn't matter
2152 // much that we can't launch the helper application or save to disk. Work on
2153 // a local copy rather than mTransfer until we know we succeeded, to make it
2154 // clearer that this function is re-entrant.
2155 nsCOMPtr<nsITransfer> transfer = do_CreateInstance(
2156 NS_TRANSFER_CONTRACTID, &rv);
2157 NS_ENSURE_SUCCESS(rv, rv);
2158
2159 // Initialize the download
2160 nsCOMPtr<nsIURI> target;
2161 rv = NS_NewFileURI(getter_AddRefs(target), mFinalFileDestination);
2162 NS_ENSURE_SUCCESS(rv, rv);
2163
2164 nsCOMPtr<nsIChannel> channel = do_QueryInterface(mRequest);
2165
2166 rv = transfer->Init(mSourceUrl, target, EmptyString(),
2167 mMimeInfo, mTimeDownloadStarted, mTempFile, this,
2168 channel && NS_UsePrivateBrowsing(channel));
2169 NS_ENSURE_SUCCESS(rv, rv);
2170
2171 // Now let's add the download to history
2172 nsCOMPtr<nsIDownloadHistory> dh(do_GetService(NS_DOWNLOADHISTORY_CONTRACTID));
2173 if (dh) {
2174 nsCOMPtr<nsIURI> referrer;
2175 nsCOMPtr<nsIChannel> channel = do_QueryInterface(mRequest);
2176 if (channel) {
2177 NS_GetReferrerFromChannel(channel, getter_AddRefs(referrer));
2178 }
2179
2180 if (channel && !NS_UsePrivateBrowsing(channel)) {
2181 dh->AddDownload(mSourceUrl, referrer, mTimeDownloadStarted, target);
2182 }
2183 }
2184
2185 // If we were cancelled since creating the transfer, just return. It is
2186 // always ok to return NS_OK if we are cancelled. Callers of this function
2187 // must call Cancel if CreateTransfer fails, but there's no need to cancel
2188 // twice.
2189 if (mCanceled) {
2190 return NS_OK;
2191 }
2192 rv = transfer->OnStateChange(nullptr, mRequest,
2193 nsIWebProgressListener::STATE_START |
2194 nsIWebProgressListener::STATE_IS_REQUEST |
2195 nsIWebProgressListener::STATE_IS_NETWORK, NS_OK);
2196 NS_ENSURE_SUCCESS(rv, rv);
2197
2198 if (mCanceled) {
2199 return NS_OK;
2200 }
2201
2202 mRequest = nullptr;
2203 // Finally, save the transfer to mTransfer.
2204 mTransfer = transfer;
2205 transfer = nullptr;
2206
2207 // While we were bringing up the progress dialog, we actually finished
2208 // processing the url. If that's the case then mStopRequestIssued will be
2209 // true and OnSaveComplete has been called.
2210 if (mStopRequestIssued && !mSaver && mTransfer) {
2211 NotifyTransfer(NS_OK);
2212 }
2213
2214 return rv;
2215 }
2216
2217 nsresult nsExternalAppHandler::CreateFailedTransfer(bool aIsPrivateBrowsing)
2218 {
2219 nsresult rv;
2220 nsCOMPtr<nsITransfer> transfer =
2221 do_CreateInstance(NS_TRANSFER_CONTRACTID, &rv);
2222 NS_ENSURE_SUCCESS(rv, rv);
2223
2224 // If we don't have a download directory we're kinda screwed but it's OK
2225 // we'll still report the error via the prompter.
2226 nsCOMPtr<nsIFile> pseudoFile;
2227 rv = GetDownloadDirectory(getter_AddRefs(pseudoFile), true);
2228 NS_ENSURE_SUCCESS(rv, rv);
2229
2230 // Append the default suggested filename. If the user restarts the transfer
2231 // we will re-trigger a filename check anyway to ensure that it is unique.
2232 rv = pseudoFile->Append(mSuggestedFileName);
2233 NS_ENSURE_SUCCESS(rv, rv);
2234
2235 nsCOMPtr<nsIURI> pseudoTarget;
2236 rv = NS_NewFileURI(getter_AddRefs(pseudoTarget), pseudoFile);
2237 NS_ENSURE_SUCCESS(rv, rv);
2238
2239 rv = transfer->Init(mSourceUrl, pseudoTarget, EmptyString(),
2240 mMimeInfo, mTimeDownloadStarted, nullptr, this,
2241 aIsPrivateBrowsing);
2242 NS_ENSURE_SUCCESS(rv, rv);
2243
2244 // Our failed transfer is ready.
2245 mTransfer = transfer.forget();
2246
2247 return NS_OK;
2248 }
2249
2250 nsresult nsExternalAppHandler::SaveDestinationAvailable(nsIFile * aFile)
2251 {
2252 if (aFile)
2253 ContinueSave(aFile);
2254 else
2255 Cancel(NS_BINDING_ABORTED);
2256
2257 return NS_OK;
2258 }
2259
2260 void nsExternalAppHandler::RequestSaveDestination(const nsAFlatString &aDefaultFile, const nsAFlatString &aFileExtension)
2261 {
2262 // invoke the dialog!!!!! use mWindowContext as the window context parameter for the dialog request
2263 // Convert to use file picker? No, then embeddors could not do any sort of
2264 // "AutoDownload" w/o showing a prompt
2265 nsresult rv = NS_OK;
2266 if (!mDialog)
2267 {
2268 // Get helper app launcher dialog.
2269 mDialog = do_CreateInstance( NS_HELPERAPPLAUNCHERDLG_CONTRACTID, &rv );
2270 if (rv != NS_OK) {
2271 Cancel(NS_BINDING_ABORTED);
2272 return;
2273 }
2274 }
2275
2276 // we want to explicitly unescape aDefaultFile b4 passing into the dialog. we can't unescape
2277 // it because the dialog is implemented by a JS component which doesn't have a window so no unescape routine is defined...
2278
2279 // Now, be sure to keep |this| alive, and the dialog
2280 // If we don't do this, users that close the helper app dialog while the file
2281 // picker is up would cause Cancel() to be called, and the dialog would be
2282 // released, which would release this object too, which would crash.
2283 // See Bug 249143
2284 nsIFile* fileToUse;
2285 nsRefPtr<nsExternalAppHandler> kungFuDeathGrip(this);
2286 nsCOMPtr<nsIHelperAppLauncherDialog> dlg(mDialog);
2287 rv = mDialog->PromptForSaveToFile(this,
2288 mWindowContext,
2289 aDefaultFile.get(),
2290 aFileExtension.get(),
2291 mForceSave, &fileToUse);
2292
2293 if (rv == NS_ERROR_NOT_AVAILABLE) {
2294 // we need to use the async version -> nsIHelperAppLauncherDialog.promptForSaveToFileAsync.
2295 rv = mDialog->PromptForSaveToFileAsync(this,
2296 mWindowContext,
2297 aDefaultFile.get(),
2298 aFileExtension.get(),
2299 mForceSave);
2300 } else {
2301 SaveDestinationAvailable(rv == NS_OK ? fileToUse : nullptr);
2302 }
2303 }
2304
2305 // SaveToDisk should only be called by the helper app dialog which allows
2306 // the user to say launch with application or save to disk. It doesn't actually
2307 // perform the save, it just prompts for the destination file name.
2308 NS_IMETHODIMP nsExternalAppHandler::SaveToDisk(nsIFile * aNewFileLocation, bool aRememberThisPreference)
2309 {
2310 if (mCanceled)
2311 return NS_OK;
2312
2313 mMimeInfo->SetPreferredAction(nsIMIMEInfo::saveToDisk);
2314
2315 if (!aNewFileLocation) {
2316 if (mSuggestedFileName.IsEmpty())
2317 RequestSaveDestination(mTempLeafName, mTempFileExtension);
2318 else
2319 {
2320 nsAutoString fileExt;
2321 int32_t pos = mSuggestedFileName.RFindChar('.');
2322 if (pos >= 0)
2323 mSuggestedFileName.Right(fileExt, mSuggestedFileName.Length() - pos);
2324 if (fileExt.IsEmpty())
2325 fileExt = mTempFileExtension;
2326
2327 RequestSaveDestination(mSuggestedFileName, fileExt);
2328 }
2329 } else {
2330 ContinueSave(aNewFileLocation);
2331 }
2332
2333 return NS_OK;
2334 }
2335 nsresult nsExternalAppHandler::ContinueSave(nsIFile * aNewFileLocation)
2336 {
2337 if (mCanceled)
2338 return NS_OK;
2339
2340 NS_PRECONDITION(aNewFileLocation, "Must be called with a non-null file");
2341
2342 nsresult rv = NS_OK;
2343 nsCOMPtr<nsIFile> fileToUse = do_QueryInterface(aNewFileLocation);
2344 mFinalFileDestination = do_QueryInterface(fileToUse);
2345
2346 // Move what we have in the final directory, but append .part
2347 // to it, to indicate that it's unfinished. Do not call SetTarget on the
2348 // saver if we are done (Finish has been called) but OnSaverComplete has not
2349 // been called.
2350 if (mFinalFileDestination && mSaver && !mStopRequestIssued)
2351 {
2352 nsCOMPtr<nsIFile> movedFile;
2353 mFinalFileDestination->Clone(getter_AddRefs(movedFile));
2354 if (movedFile) {
2355 // Get the old leaf name and append .part to it
2356 nsAutoString name;
2357 mFinalFileDestination->GetLeafName(name);
2358 name.AppendLiteral(".part");
2359 movedFile->SetLeafName(name);
2360
2361 rv = mSaver->SetTarget(movedFile, true);
2362 if (NS_FAILED(rv)) {
2363 nsAutoString path;
2364 mTempFile->GetPath(path);
2365 SendStatusChange(kWriteError, rv, nullptr, path);
2366 Cancel(rv);
2367 return NS_OK;
2368 }
2369
2370 mTempFile = movedFile;
2371 }
2372 }
2373
2374 // The helper app dialog has told us what to do and we have a final file
2375 // destination.
2376 rv = CreateTransfer();
2377 // If we fail to create the transfer, Cancel.
2378 if (NS_FAILED(rv)) {
2379 Cancel(rv);
2380 return rv;
2381 }
2382
2383 // now that the user has chosen the file location to save to, it's okay to fire the refresh tag
2384 // if there is one. We don't want to do this before the save as dialog goes away because this dialog
2385 // is modal and we do bad things if you try to load a web page in the underlying window while a modal
2386 // dialog is still up.
2387 ProcessAnyRefreshTags();
2388
2389 return NS_OK;
2390 }
2391
2392
2393 // LaunchWithApplication should only be called by the helper app dialog which
2394 // allows the user to say launch with application or save to disk. It doesn't
2395 // actually perform launch with application.
2396 NS_IMETHODIMP nsExternalAppHandler::LaunchWithApplication(nsIFile * aApplication, bool aRememberThisPreference)
2397 {
2398 if (mCanceled)
2399 return NS_OK;
2400
2401 // user has chosen to launch using an application, fire any refresh tags now...
2402 ProcessAnyRefreshTags();
2403
2404 if (mMimeInfo && aApplication) {
2405 PlatformLocalHandlerApp_t *handlerApp =
2406 new PlatformLocalHandlerApp_t(EmptyString(), aApplication);
2407 mMimeInfo->SetPreferredApplicationHandler(handlerApp);
2408 }
2409
2410 // Now check if the file is local, in which case we won't bother with saving
2411 // it to a temporary directory and just launch it from where it is
2412 nsCOMPtr<nsIFileURL> fileUrl(do_QueryInterface(mSourceUrl));
2413 if (fileUrl && mIsFileChannel)
2414 {
2415 Cancel(NS_BINDING_ABORTED);
2416 nsCOMPtr<nsIFile> file;
2417 nsresult rv = fileUrl->GetFile(getter_AddRefs(file));
2418
2419 if (NS_SUCCEEDED(rv))
2420 {
2421 rv = mMimeInfo->LaunchWithFile(file);
2422 if (NS_SUCCEEDED(rv))
2423 return NS_OK;
2424 }
2425 nsAutoString path;
2426 if (file)
2427 file->GetPath(path);
2428 // If we get here, an error happened
2429 SendStatusChange(kLaunchError, rv, nullptr, path);
2430 return rv;
2431 }
2432
2433 // Now that the user has elected to launch the downloaded file with a helper
2434 // app, we're justified in removing the 'salted' name. We'll rename to what
2435 // was specified in mSuggestedFileName after the download is done prior to
2436 // launching the helper app. So that any existing file of that name won't be
2437 // overwritten we call CreateUnique(). Also note that we use the same
2438 // directory as originally downloaded so nsDownload can rename in place
2439 // later.
2440 nsCOMPtr<nsIFile> fileToUse;
2441 (void) GetDownloadDirectory(getter_AddRefs(fileToUse));
2442
2443 if (mSuggestedFileName.IsEmpty())
2444 {
2445 // Keep using the leafname of the temp file, since we're just starting a helper
2446 mSuggestedFileName = mTempLeafName;
2447 }
2448
2449 #ifdef XP_WIN
2450 fileToUse->Append(mSuggestedFileName + mTempFileExtension);
2451 #else
2452 fileToUse->Append(mSuggestedFileName);
2453 #endif
2454
2455 nsresult rv = fileToUse->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0644);
2456 if(NS_SUCCEEDED(rv))
2457 {
2458 mFinalFileDestination = do_QueryInterface(fileToUse);
2459 // launch the progress window now that the user has picked the desired action.
2460 rv = CreateTransfer();
2461 if (NS_FAILED(rv)) {
2462 Cancel(rv);
2463 }
2464 }
2465 else
2466 {
2467 // Cancel the download and report an error. We do not want to end up in
2468 // a state where it appears that we have a normal download that is
2469 // pointing to a file that we did not actually create.
2470 nsAutoString path;
2471 mTempFile->GetPath(path);
2472 SendStatusChange(kWriteError, rv, nullptr, path);
2473 Cancel(rv);
2474 }
2475 return rv;
2476 }
2477
2478 NS_IMETHODIMP nsExternalAppHandler::Cancel(nsresult aReason)
2479 {
2480 NS_ENSURE_ARG(NS_FAILED(aReason));
2481
2482 if (mCanceled) {
2483 return NS_OK;
2484 }
2485 mCanceled = true;
2486
2487 if (mSaver) {
2488 // We are still writing to the target file. Give the saver a chance to
2489 // close the target file, then notify the transfer object if necessary in
2490 // the OnSaveComplete callback.
2491 mSaver->Finish(aReason);
2492 mSaver = nullptr;
2493 } else {
2494 if (mStopRequestIssued && mTempFile) {
2495 // This branch can only happen when the user cancels the helper app dialog
2496 // when the request has completed. The temp file has to be removed here,
2497 // because mSaver has been released at that time with the temp file left.
2498 (void)mTempFile->Remove(false);
2499 }
2500
2501 // Notify the transfer object that the download has been canceled, if the
2502 // user has already chosen an action and we didn't notify already.
2503 if (mTransfer) {
2504 NotifyTransfer(aReason);
2505 }
2506 }
2507
2508 // Break our reference cycle with the helper app dialog (set up in
2509 // OnStartRequest)
2510 mDialog = nullptr;
2511
2512 mRequest = nullptr;
2513
2514 // Release the listener, to break the reference cycle with it (we are the
2515 // observer of the listener).
2516 mDialogProgressListener = nullptr;
2517
2518 return NS_OK;
2519 }
2520
2521 void nsExternalAppHandler::ProcessAnyRefreshTags()
2522 {
2523 // one last thing, try to see if the original window context supports a refresh interface...
2524 // Sometimes, when you download content that requires an external handler, there is
2525 // a refresh header associated with the download. This refresh header points to a page
2526 // the content provider wants the user to see after they download the content. How do we
2527 // pass this refresh information back to the caller? For now, try to get the refresh URI
2528 // interface. If the window context where the request originated came from supports this
2529 // then we can force it to process the refresh information (if there is any) from this channel.
2530 if (mWindowContext && mOriginalChannel)
2531 {
2532 nsCOMPtr<nsIRefreshURI> refreshHandler (do_GetInterface(mWindowContext));
2533 if (refreshHandler) {
2534 refreshHandler->SetupRefreshURI(mOriginalChannel);
2535 }
2536 mOriginalChannel = nullptr;
2537 }
2538 }
2539
2540 bool nsExternalAppHandler::GetNeverAskFlagFromPref(const char * prefName, const char * aContentType)
2541 {
2542 // Search the obsolete pref strings.
2543 nsAdoptingCString prefCString = Preferences::GetCString(prefName);
2544 if (prefCString.IsEmpty()) {
2545 // Default is true, if not found in the pref string.
2546 return true;
2547 }
2548
2549 NS_UnescapeURL(prefCString);
2550 nsACString::const_iterator start, end;
2551 prefCString.BeginReading(start);
2552 prefCString.EndReading(end);
2553 return !CaseInsensitiveFindInReadable(nsDependentCString(aContentType),
2554 start, end);
2555 }
2556
2557 nsresult nsExternalAppHandler::MaybeCloseWindow()
2558 {
2559 nsCOMPtr<nsIDOMWindow> window = do_GetInterface(mWindowContext);
2560 NS_ENSURE_STATE(window);
2561
2562 if (mShouldCloseWindow) {
2563 // Reset the window context to the opener window so that the dependent
2564 // dialogs have a parent
2565 nsCOMPtr<nsIDOMWindow> opener;
2566 window->GetOpener(getter_AddRefs(opener));
2567
2568 bool isClosed;
2569 if (opener && NS_SUCCEEDED(opener->GetClosed(&isClosed)) && !isClosed) {
2570 mWindowContext = do_GetInterface(opener);
2571
2572 // Now close the old window. Do it on a timer so that we don't run
2573 // into issues trying to close the window before it has fully opened.
2574 NS_ASSERTION(!mTimer, "mTimer was already initialized once!");
2575 mTimer = do_CreateInstance("@mozilla.org/timer;1");
2576 if (!mTimer) {
2577 return NS_ERROR_FAILURE;
2578 }
2579
2580 mTimer->InitWithCallback(this, 0, nsITimer::TYPE_ONE_SHOT);
2581 mWindowToClose = window;
2582 }
2583 }
2584
2585 return NS_OK;
2586 }
2587
2588 NS_IMETHODIMP
2589 nsExternalAppHandler::Notify(nsITimer* timer)
2590 {
2591 NS_ASSERTION(mWindowToClose, "No window to close after timer fired");
2592
2593 mWindowToClose->Close();
2594 mWindowToClose = nullptr;
2595 mTimer = nullptr;
2596
2597 return NS_OK;
2598 }
2599 //////////////////////////////////////////////////////////////////////////////////////////////////////////////
2600 // The following section contains our nsIMIMEService implementation and related methods.
2601 //
2602 //////////////////////////////////////////////////////////////////////////////////////////////////////////////
2603
2604 // nsIMIMEService methods
2605 NS_IMETHODIMP nsExternalHelperAppService::GetFromTypeAndExtension(const nsACString& aMIMEType, const nsACString& aFileExt, nsIMIMEInfo **_retval)
2606 {
2607 NS_PRECONDITION(!aMIMEType.IsEmpty() ||
2608 !aFileExt.IsEmpty(),
2609 "Give me something to work with");
2610 LOG(("Getting mimeinfo from type '%s' ext '%s'\n",
2611 PromiseFlatCString(aMIMEType).get(), PromiseFlatCString(aFileExt).get()));
2612
2613 *_retval = nullptr;
2614
2615 // OK... we need a type. Get one.
2616 nsAutoCString typeToUse(aMIMEType);
2617 if (typeToUse.IsEmpty()) {
2618 nsresult rv = GetTypeFromExtension(aFileExt, typeToUse);
2619 if (NS_FAILED(rv))
2620 return NS_ERROR_NOT_AVAILABLE;
2621 }
2622
2623 // We promise to only send lower case mime types to the OS
2624 ToLowerCase(typeToUse);
2625
2626 // (1) Ask the OS for a mime info
2627 bool found;
2628 *_retval = GetMIMEInfoFromOS(typeToUse, aFileExt, &found).take();
2629 LOG(("OS gave back 0x%p - found: %i\n", *_retval, found));
2630 // If we got no mimeinfo, something went wrong. Probably lack of memory.
2631 if (!*_retval)
2632 return NS_ERROR_OUT_OF_MEMORY;
2633
2634 // (2) Now, let's see if we can find something in our datastore
2635 // This will not overwrite the OS information that interests us
2636 // (i.e. default application, default app. description)
2637 nsresult rv;
2638 nsCOMPtr<nsIHandlerService> handlerSvc = do_GetService(NS_HANDLERSERVICE_CONTRACTID);
2639 if (handlerSvc) {
2640 bool hasHandler = false;
2641 (void) handlerSvc->Exists(*_retval, &hasHandler);
2642 if (hasHandler) {
2643 rv = handlerSvc->FillHandlerInfo(*_retval, EmptyCString());
2644 LOG(("Data source: Via type: retval 0x%08x\n", rv));
2645 } else {
2646 rv = NS_ERROR_NOT_AVAILABLE;
2647 }
2648
2649 found = found || NS_SUCCEEDED(rv);
2650
2651 if (!found || NS_FAILED(rv)) {
2652 // No type match, try extension match
2653 if (!aFileExt.IsEmpty()) {
2654 nsAutoCString overrideType;
2655 rv = handlerSvc->GetTypeFromExtension(aFileExt, overrideType);
2656 if (NS_SUCCEEDED(rv) && !overrideType.IsEmpty()) {
2657 // We can't check handlerSvc->Exists() here, because we have a
2658 // overideType. That's ok, it just results in some console noise.
2659 // (If there's no handler for the override type, it throws)
2660 rv = handlerSvc->FillHandlerInfo(*_retval, overrideType);
2661 LOG(("Data source: Via ext: retval 0x%08x\n", rv));
2662 found = found || NS_SUCCEEDED(rv);
2663 }
2664 }
2665 }
2666 }
2667
2668 // (3) No match yet. Ask extras.
2669 if (!found) {
2670 rv = NS_ERROR_FAILURE;
2671 #ifdef XP_WIN
2672 /* XXX Gross hack to wallpaper over the most common Win32
2673 * extension issues caused by the fix for bug 116938. See bug
2674 * 120327, comment 271 for why this is needed. Not even sure we
2675 * want to remove this once we have fixed all this stuff to work
2676 * right; any info we get from extras on this type is pretty much
2677 * useless....
2678 */
2679 if (!typeToUse.Equals(APPLICATION_OCTET_STREAM, nsCaseInsensitiveCStringComparator()))
2680 #endif
2681 rv = FillMIMEInfoForMimeTypeFromExtras(typeToUse, *_retval);
2682 LOG(("Searched extras (by type), rv 0x%08X\n", rv));
2683 // If that didn't work out, try file extension from extras
2684 if (NS_FAILED(rv) && !aFileExt.IsEmpty()) {
2685 rv = FillMIMEInfoForExtensionFromExtras(aFileExt, *_retval);
2686 LOG(("Searched extras (by ext), rv 0x%08X\n", rv));
2687 }
2688 // If that still didn't work, set the file description to "ext File"
2689 if (NS_FAILED(rv) && !aFileExt.IsEmpty()) {
2690 // XXXzpao This should probably be localized
2691 nsAutoCString desc(aFileExt);
2692 desc.Append(" File");
2693 (*_retval)->SetDescription(NS_ConvertASCIItoUTF16(desc));
2694 LOG(("Falling back to 'File' file description\n"));
2695 }
2696 }
2697
2698 // Finally, check if we got a file extension and if yes, if it is an
2699 // extension on the mimeinfo, in which case we want it to be the primary one
2700 if (!aFileExt.IsEmpty()) {
2701 bool matches = false;
2702 (*_retval)->ExtensionExists(aFileExt, &matches);
2703 LOG(("Extension '%s' matches mime info: %i\n", PromiseFlatCString(aFileExt).get(), matches));
2704 if (matches)
2705 (*_retval)->SetPrimaryExtension(aFileExt);
2706 }
2707
2708 #ifdef PR_LOGGING
2709 if (LOG_ENABLED()) {
2710 nsAutoCString type;
2711 (*_retval)->GetMIMEType(type);
2712
2713 nsAutoCString ext;
2714 (*_retval)->GetPrimaryExtension(ext);
2715 LOG(("MIME Info Summary: Type '%s', Primary Ext '%s'\n", type.get(), ext.get()));
2716 }
2717 #endif
2718
2719 return NS_OK;
2720 }
2721
2722 NS_IMETHODIMP nsExternalHelperAppService::GetTypeFromExtension(const nsACString& aFileExt, nsACString& aContentType)
2723 {
2724 // OK. We want to try the following sources of mimetype information, in this order:
2725 // 1. defaultMimeEntries array
2726 // 2. User-set preferences (managed by the handler service)
2727 // 3. OS-provided information
2728 // 4. our "extras" array
2729 // 5. Information from plugins
2730 // 6. The "ext-to-type-mapping" category
2731
2732 // Early return if called with an empty extension parameter
2733 if (aFileExt.IsEmpty())
2734 return NS_ERROR_NOT_AVAILABLE;
2735
2736 nsresult rv = NS_OK;
2737 // First of all, check our default entries
2738 for (size_t i = 0; i < ArrayLength(defaultMimeEntries); i++)
2739 {
2740 if (aFileExt.LowerCaseEqualsASCII(defaultMimeEntries[i].mFileExtension)) {
2741 aContentType = defaultMimeEntries[i].mMimeType;
2742 return rv;
2743 }
2744 }
2745
2746 // Check user-set prefs
2747 nsCOMPtr<nsIHandlerService> handlerSvc = do_GetService(NS_HANDLERSERVICE_CONTRACTID);
2748 if (handlerSvc)
2749 rv = handlerSvc->GetTypeFromExtension(aFileExt, aContentType);
2750 if (NS_SUCCEEDED(rv) && !aContentType.IsEmpty())
2751 return NS_OK;
2752
2753 // Ask OS.
2754 bool found = false;
2755 nsCOMPtr<nsIMIMEInfo> mi = GetMIMEInfoFromOS(EmptyCString(), aFileExt, &found);
2756 if (mi && found)
2757 return mi->GetMIMEType(aContentType);
2758
2759 // Check extras array.
2760 found = GetTypeFromExtras(aFileExt, aContentType);
2761 if (found)
2762 return NS_OK;
2763
2764 const nsCString& flatExt = PromiseFlatCString(aFileExt);
2765 // Try the plugins
2766 const char* mimeType;
2767 nsCOMPtr<nsIPluginHost> pluginHostCOM(do_GetService(MOZ_PLUGIN_HOST_CONTRACTID, &rv));
2768 nsPluginHost* pluginHost = static_cast<nsPluginHost*>(pluginHostCOM.get());
2769 if (NS_SUCCEEDED(rv)) {
2770 if (NS_SUCCEEDED(pluginHost->IsPluginEnabledForExtension(flatExt.get(), mimeType))) {
2771 aContentType = mimeType;
2772 return NS_OK;
2773 }
2774 }
2775
2776 rv = NS_OK;
2777 // Let's see if an extension added something
2778 nsCOMPtr<nsICategoryManager> catMan(do_GetService("@mozilla.org/categorymanager;1"));
2779 if (catMan) {
2780 // The extension in the category entry is always stored as lowercase
2781 nsAutoCString lowercaseFileExt(aFileExt);
2782 ToLowerCase(lowercaseFileExt);
2783 // Read the MIME type from the category entry, if available
2784 nsXPIDLCString type;
2785 rv = catMan->GetCategoryEntry("ext-to-type-mapping", lowercaseFileExt.get(),
2786 getter_Copies(type));
2787 aContentType = type;
2788 }
2789 else {
2790 rv = NS_ERROR_NOT_AVAILABLE;
2791 }
2792
2793 return rv;
2794 }
2795
2796 NS_IMETHODIMP nsExternalHelperAppService::GetPrimaryExtension(const nsACString& aMIMEType, const nsACString& aFileExt, nsACString& _retval)
2797 {
2798 NS_ENSURE_ARG(!aMIMEType.IsEmpty());
2799
2800 nsCOMPtr<nsIMIMEInfo> mi;
2801 nsresult rv = GetFromTypeAndExtension(aMIMEType, aFileExt, getter_AddRefs(mi));
2802 if (NS_FAILED(rv))
2803 return rv;
2804
2805 return mi->GetPrimaryExtension(_retval);
2806 }
2807
2808 NS_IMETHODIMP nsExternalHelperAppService::GetTypeFromURI(nsIURI *aURI, nsACString& aContentType)
2809 {
2810 NS_ENSURE_ARG_POINTER(aURI);
2811 nsresult rv = NS_ERROR_NOT_AVAILABLE;
2812 aContentType.Truncate();
2813
2814 // First look for a file to use. If we have one, we just use that.
2815 nsCOMPtr<nsIFileURL> fileUrl = do_QueryInterface(aURI);
2816 if (fileUrl) {
2817 nsCOMPtr<nsIFile> file;
2818 rv = fileUrl->GetFile(getter_AddRefs(file));
2819 if (NS_SUCCEEDED(rv)) {
2820 rv = GetTypeFromFile(file, aContentType);
2821 if (NS_SUCCEEDED(rv)) {
2822 // we got something!
2823 return rv;
2824 }
2825 }
2826 }
2827
2828 // Now try to get an nsIURL so we don't have to do our own parsing
2829 nsCOMPtr<nsIURL> url = do_QueryInterface(aURI);
2830 if (url) {
2831 nsAutoCString ext;
2832 rv = url->GetFileExtension(ext);
2833 if (NS_FAILED(rv))
2834 return rv;
2835 if (ext.IsEmpty())
2836 return NS_ERROR_NOT_AVAILABLE;
2837
2838 UnescapeFragment(ext, url, ext);
2839
2840 return GetTypeFromExtension(ext, aContentType);
2841 }
2842
2843 // no url, let's give the raw spec a shot
2844 nsAutoCString specStr;
2845 rv = aURI->GetSpec(specStr);
2846 if (NS_FAILED(rv))
2847 return rv;
2848 UnescapeFragment(specStr, aURI, specStr);
2849
2850 // find the file extension (if any)
2851 int32_t extLoc = specStr.RFindChar('.');
2852 int32_t specLength = specStr.Length();
2853 if (-1 != extLoc &&
2854 extLoc != specLength - 1 &&
2855 // nothing over 20 chars long can be sanely considered an
2856 // extension.... Dat dere would be just data.
2857 specLength - extLoc < 20)
2858 {
2859 return GetTypeFromExtension(Substring(specStr, extLoc + 1), aContentType);
2860 }
2861
2862 // We found no information; say so.
2863 return NS_ERROR_NOT_AVAILABLE;
2864 }
2865
2866 NS_IMETHODIMP nsExternalHelperAppService::GetTypeFromFile(nsIFile* aFile, nsACString& aContentType)
2867 {
2868 NS_ENSURE_ARG_POINTER(aFile);
2869 nsresult rv;
2870 nsCOMPtr<nsIMIMEInfo> info;
2871
2872 // Get the Extension
2873 nsAutoString fileName;
2874 rv = aFile->GetLeafName(fileName);
2875 if (NS_FAILED(rv)) return rv;
2876
2877 nsAutoCString fileExt;
2878 if (!fileName.IsEmpty())
2879 {
2880 int32_t len = fileName.Length();
2881 for (int32_t i = len; i >= 0; i--)
2882 {
2883 if (fileName[i] == char16_t('.'))
2884 {
2885 CopyUTF16toUTF8(fileName.get() + i + 1, fileExt);
2886 break;
2887 }
2888 }
2889 }
2890
2891 if (fileExt.IsEmpty())
2892 return NS_ERROR_FAILURE;
2893
2894 return GetTypeFromExtension(fileExt, aContentType);
2895 }
2896
2897 nsresult nsExternalHelperAppService::FillMIMEInfoForMimeTypeFromExtras(
2898 const nsACString& aContentType, nsIMIMEInfo * aMIMEInfo)
2899 {
2900 NS_ENSURE_ARG( aMIMEInfo );
2901
2902 NS_ENSURE_ARG( !aContentType.IsEmpty() );
2903
2904 // Look for default entry with matching mime type.
2905 nsAutoCString MIMEType(aContentType);
2906 ToLowerCase(MIMEType);
2907 int32_t numEntries = ArrayLength(extraMimeEntries);
2908 for (int32_t index = 0; index < numEntries; index++)
2909 {
2910 if ( MIMEType.Equals(extraMimeEntries[index].mMimeType) )
2911 {
2912 // This is the one. Set attributes appropriately.
2913 aMIMEInfo->SetFileExtensions(nsDependentCString(extraMimeEntries[index].mFileExtensions));
2914 aMIMEInfo->SetDescription(NS_ConvertASCIItoUTF16(extraMimeEntries[index].mDescription));
2915 return NS_OK;
2916 }
2917 }
2918
2919 return NS_ERROR_NOT_AVAILABLE;
2920 }
2921
2922 nsresult nsExternalHelperAppService::FillMIMEInfoForExtensionFromExtras(
2923 const nsACString& aExtension, nsIMIMEInfo * aMIMEInfo)
2924 {
2925 nsAutoCString type;
2926 bool found = GetTypeFromExtras(aExtension, type);
2927 if (!found)
2928 return NS_ERROR_NOT_AVAILABLE;
2929 return FillMIMEInfoForMimeTypeFromExtras(type, aMIMEInfo);
2930 }
2931
2932 bool nsExternalHelperAppService::GetTypeFromExtras(const nsACString& aExtension, nsACString& aMIMEType)
2933 {
2934 NS_ASSERTION(!aExtension.IsEmpty(), "Empty aExtension parameter!");
2935
2936 // Look for default entry with matching extension.
2937 nsDependentCString::const_iterator start, end, iter;
2938 int32_t numEntries = ArrayLength(extraMimeEntries);
2939 for (int32_t index = 0; index < numEntries; index++)
2940 {
2941 nsDependentCString extList(extraMimeEntries[index].mFileExtensions);
2942 extList.BeginReading(start);
2943 extList.EndReading(end);
2944 iter = start;
2945 while (start != end)
2946 {
2947 FindCharInReadable(',', iter, end);
2948 if (Substring(start, iter).Equals(aExtension,
2949 nsCaseInsensitiveCStringComparator()))
2950 {
2951 aMIMEType = extraMimeEntries[index].mMimeType;
2952 return true;
2953 }
2954 if (iter != end) {
2955 ++iter;
2956 }
2957 start = iter;
2958 }
2959 }
2960
2961 return false;
2962 }

mercurial