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