Source code

Revision control

Other Tools

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
#include "mozilla/ResultExtensions.h"
13
14
#include "mozilla/dom/ContentChild.h"
15
#include "mozilla/dom/BrowserChild.h"
16
#include "nsXULAppAPI.h"
17
18
#include "nsExternalHelperAppService.h"
19
#include "nsCExternalHandlerService.h"
20
#include "nsIURI.h"
21
#include "nsIURL.h"
22
#include "nsIFile.h"
23
#include "nsIFileURL.h"
24
#include "nsIChannel.h"
25
#include "nsIDirectoryService.h"
26
#include "nsAppDirectoryServiceDefs.h"
27
#include "nsICategoryManager.h"
28
#include "nsDependentSubstring.h"
29
#include "nsString.h"
30
#include "nsUnicharUtils.h"
31
#include "nsIStringEnumerator.h"
32
#include "nsMemory.h"
33
#include "nsIStreamListener.h"
34
#include "nsIMIMEService.h"
35
#include "nsILoadGroup.h"
36
#include "nsIWebProgressListener.h"
37
#include "nsITransfer.h"
38
#include "nsReadableUtils.h"
39
#include "nsIRequest.h"
40
#include "nsDirectoryServiceDefs.h"
41
#include "nsIInterfaceRequestor.h"
42
#include "nsThreadUtils.h"
43
#include "nsAutoPtr.h"
44
#include "nsIMutableArray.h"
45
#include "nsIRedirectHistoryEntry.h"
46
#include "nsOSHelperAppService.h"
47
#include "nsOSHelperAppServiceChild.h"
48
49
// used to access our datastore of user-configured helper applications
50
#include "nsIHandlerService.h"
51
#include "nsIMIMEInfo.h"
52
#include "nsIRefreshURI.h" // XXX needed to redirect according to Refresh: URI
53
#include "nsIDocumentLoader.h" // XXX needed to get orig. channel and assoc. refresh uri
54
#include "nsIHelperAppLauncherDialog.h"
55
#include "nsIContentDispatchChooser.h"
56
#include "nsNetUtil.h"
57
#include "nsIPrivateBrowsingChannel.h"
58
#include "nsIIOService.h"
59
#include "nsNetCID.h"
60
61
#include "nsDSURIContentListener.h"
62
#include "nsMimeTypes.h"
63
// used for header disposition information.
64
#include "nsIHttpChannel.h"
65
#include "nsIHttpChannelInternal.h"
66
#include "nsIEncodedChannel.h"
67
#include "nsIMultiPartChannel.h"
68
#include "nsIFileChannel.h"
69
#include "nsIObserverService.h" // so we can be a profile change observer
70
#include "nsIPropertyBag2.h" // for the 64-bit content length
71
72
#ifdef XP_MACOSX
73
# include "nsILocalFileMac.h"
74
#endif
75
76
#include "nsIPluginHost.h" // XXX needed for ext->type mapping (bug 233289)
77
#include "nsPluginHost.h"
78
#include "nsEscape.h"
79
80
#include "nsIStringBundle.h" // XXX needed to localize error msgs
81
#include "nsIPrompt.h"
82
83
#include "nsITextToSubURI.h" // to unescape the filename
84
#include "nsIMIMEHeaderParam.h"
85
86
#include "nsIWindowWatcher.h"
87
88
#include "nsDocShellCID.h"
89
90
#include "nsCRT.h"
91
#include "nsLocalHandlerApp.h"
92
93
#include "nsIRandomGenerator.h"
94
95
#include "ContentChild.h"
96
#include "nsXULAppAPI.h"
97
#include "nsPIDOMWindow.h"
98
#include "nsIDocShellTreeOwner.h"
99
#include "nsIDocShellTreeItem.h"
100
#include "ExternalHelperAppChild.h"
101
102
#ifdef XP_WIN
103
# include "nsWindowsHelpers.h"
104
#endif
105
106
#include "mozilla/ClearOnShutdown.h"
107
#include "mozilla/Preferences.h"
108
#include "mozilla/ipc/URIUtils.h"
109
110
using namespace mozilla;
111
using namespace mozilla::ipc;
112
113
// Download Folder location constants
114
#define NS_PREF_DOWNLOAD_DIR "browser.download.dir"
115
#define NS_PREF_DOWNLOAD_FOLDERLIST "browser.download.folderList"
116
enum {
117
NS_FOLDER_VALUE_DESKTOP = 0,
118
NS_FOLDER_VALUE_DOWNLOADS = 1,
119
NS_FOLDER_VALUE_CUSTOM = 2
120
};
121
122
LazyLogModule nsExternalHelperAppService::mLog("HelperAppService");
123
124
// Using level 3 here because the OSHelperAppServices use a log level
125
// of LogLevel::Debug (4), and we want less detailed output here
126
// Using 3 instead of LogLevel::Warning because we don't output warnings
127
#undef LOG
128
#define LOG(args) \
129
MOZ_LOG(nsExternalHelperAppService::mLog, mozilla::LogLevel::Info, args)
130
#define LOG_ENABLED() \
131
MOZ_LOG_TEST(nsExternalHelperAppService::mLog, mozilla::LogLevel::Info)
132
133
static const char NEVER_ASK_FOR_SAVE_TO_DISK_PREF[] =
134
"browser.helperApps.neverAsk.saveToDisk";
135
static const char NEVER_ASK_FOR_OPEN_FILE_PREF[] =
136
"browser.helperApps.neverAsk.openFile";
137
138
// Helper functions for Content-Disposition headers
139
140
/**
141
* Given a URI fragment, unescape it
142
* @param aFragment The string to unescape
143
* @param aURI The URI from which this fragment is taken. Only its character set
144
* will be used.
145
* @param aResult [out] Unescaped string.
146
*/
147
static nsresult UnescapeFragment(const nsACString& aFragment, nsIURI* aURI,
148
nsAString& aResult) {
149
// We need the unescaper
150
nsresult rv;
151
nsCOMPtr<nsITextToSubURI> textToSubURI =
152
do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv);
153
NS_ENSURE_SUCCESS(rv, rv);
154
155
return textToSubURI->UnEscapeURIForUI(NS_LITERAL_CSTRING("UTF-8"), aFragment,
156
aResult);
157
}
158
159
/**
160
* UTF-8 version of UnescapeFragment.
161
* @param aFragment The string to unescape
162
* @param aURI The URI from which this fragment is taken. Only its character set
163
* will be used.
164
* @param aResult [out] Unescaped string, UTF-8 encoded.
165
* @note It is safe to pass the same string for aFragment and aResult.
166
* @note When this function fails, aResult will not be modified.
167
*/
168
static nsresult UnescapeFragment(const nsACString& aFragment, nsIURI* aURI,
169
nsACString& aResult) {
170
nsAutoString result;
171
nsresult rv = UnescapeFragment(aFragment, aURI, result);
172
if (NS_SUCCEEDED(rv)) CopyUTF16toUTF8(result, aResult);
173
return rv;
174
}
175
176
/**
177
* Given a channel, returns the filename and extension the channel has.
178
* This uses the URL and other sources (nsIMultiPartChannel).
179
* Also gives back whether the channel requested external handling (i.e.
180
* whether Content-Disposition: attachment was sent)
181
* @param aChannel The channel to extract the filename/extension from
182
* @param aFileName [out] Reference to the string where the filename should be
183
* stored. Empty if it could not be retrieved.
184
* WARNING - this filename may contain characters which the OS does not
185
* allow as part of filenames!
186
* @param aExtension [out] Reference to the string where the extension should
187
* be stored. Empty if it could not be retrieved. Stored in UTF-8.
188
* @param aAllowURLExtension (optional) Get the extension from the URL if no
189
* Content-Disposition header is present. Default is true.
190
* @retval true The server sent Content-Disposition:attachment or equivalent
191
* @retval false Content-Disposition: inline or no content-disposition header
192
* was sent.
193
*/
194
static bool GetFilenameAndExtensionFromChannel(nsIChannel* aChannel,
195
nsString& aFileName,
196
nsCString& aExtension,
197
bool aAllowURLExtension = true) {
198
aExtension.Truncate();
199
/*
200
* If the channel is an http or part of a multipart channel and we
201
* have a content disposition header set, then use the file name
202
* suggested there as the preferred file name to SUGGEST to the
203
* user. we shouldn't actually use that without their
204
* permission... otherwise just use our temp file
205
*/
206
bool handleExternally = false;
207
uint32_t disp;
208
nsresult rv = aChannel->GetContentDisposition(&disp);
209
if (NS_SUCCEEDED(rv)) {
210
aChannel->GetContentDispositionFilename(aFileName);
211
if (disp == nsIChannel::DISPOSITION_ATTACHMENT) handleExternally = true;
212
}
213
214
// If the disposition header didn't work, try the filename from nsIURL
215
nsCOMPtr<nsIURI> uri;
216
aChannel->GetURI(getter_AddRefs(uri));
217
nsCOMPtr<nsIURL> url(do_QueryInterface(uri));
218
if (url && aFileName.IsEmpty()) {
219
if (aAllowURLExtension) {
220
url->GetFileExtension(aExtension);
221
UnescapeFragment(aExtension, url, aExtension);
222
223
// Windows ignores terminating dots. So we have to as well, so
224
// that our security checks do "the right thing"
225
// In case the aExtension consisted only of the dot, the code below will
226
// extract an aExtension from the filename
227
aExtension.Trim(".", false);
228
}
229
230
// try to extract the file name from the url and use that as a first pass as
231
// the leaf name of our temp file...
232
nsAutoCString leafName;
233
url->GetFileName(leafName);
234
if (!leafName.IsEmpty()) {
235
rv = UnescapeFragment(leafName, url, aFileName);
236
if (NS_FAILED(rv)) {
237
CopyUTF8toUTF16(leafName, aFileName); // use escaped name
238
}
239
}
240
}
241
242
// Extract Extension, if we have a filename; otherwise,
243
// truncate the string
244
if (aExtension.IsEmpty()) {
245
if (!aFileName.IsEmpty()) {
246
// Windows ignores terminating dots. So we have to as well, so
247
// that our security checks do "the right thing"
248
aFileName.Trim(".", false);
249
250
// XXX RFindCharInReadable!!
251
nsAutoString fileNameStr(aFileName);
252
int32_t idx = fileNameStr.RFindChar(char16_t('.'));
253
if (idx != kNotFound)
254
CopyUTF16toUTF8(StringTail(fileNameStr, fileNameStr.Length() - idx - 1),
255
aExtension);
256
}
257
}
258
259
return handleExternally;
260
}
261
262
/**
263
* Obtains the directory to use. This tends to vary per platform, and
264
* needs to be consistent throughout our codepaths. For platforms where
265
* helper apps use the downloads directory, this should be kept in
266
* sync with DownloadIntegration.jsm.
267
*
268
* Optionally skip availability of the directory and storage.
269
*/
270
static nsresult GetDownloadDirectory(nsIFile** _directory,
271
bool aSkipChecks = false) {
272
nsCOMPtr<nsIFile> dir;
273
#ifdef XP_MACOSX
274
// On OS X, we first try to get the users download location, if it's set.
275
switch (Preferences::GetInt(NS_PREF_DOWNLOAD_FOLDERLIST, -1)) {
276
case NS_FOLDER_VALUE_DESKTOP:
277
(void)NS_GetSpecialDirectory(NS_OS_DESKTOP_DIR, getter_AddRefs(dir));
278
break;
279
case NS_FOLDER_VALUE_CUSTOM: {
280
Preferences::GetComplex(NS_PREF_DOWNLOAD_DIR, NS_GET_IID(nsIFile),
281
getter_AddRefs(dir));
282
if (!dir) break;
283
284
// If we're not checking for availability we're done.
285
if (aSkipChecks) {
286
dir.forget(_directory);
287
return NS_OK;
288
}
289
290
// We have the directory, and now we need to make sure it exists
291
bool dirExists = false;
292
(void)dir->Exists(&dirExists);
293
if (dirExists) break;
294
295
nsresult rv = dir->Create(nsIFile::DIRECTORY_TYPE, 0755);
296
if (NS_FAILED(rv)) {
297
dir = nullptr;
298
break;
299
}
300
} break;
301
case NS_FOLDER_VALUE_DOWNLOADS:
302
// This is just the OS default location, so fall out
303
break;
304
}
305
306
if (!dir) {
307
// If not, we default to the OS X default download location.
308
nsresult rv = NS_GetSpecialDirectory(NS_OSX_DEFAULT_DOWNLOAD_DIR,
309
getter_AddRefs(dir));
310
NS_ENSURE_SUCCESS(rv, rv);
311
}
312
#elif defined(ANDROID)
313
return NS_ERROR_FAILURE;
314
#else
315
// On all other platforms, we default to the systems temporary directory.
316
nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(dir));
317
NS_ENSURE_SUCCESS(rv, rv);
318
319
# if defined(XP_UNIX)
320
// Ensuring that only the current user can read the file names we end up
321
// creating. Note that Creating directories with specified permission only
322
// supported on Unix platform right now. That's why above if exists.
323
324
uint32_t permissions;
325
rv = dir->GetPermissions(&permissions);
326
NS_ENSURE_SUCCESS(rv, rv);
327
328
if (permissions != PR_IRWXU) {
329
const char* userName = PR_GetEnv("USERNAME");
330
if (!userName || !*userName) {
331
userName = PR_GetEnv("USER");
332
}
333
if (!userName || !*userName) {
334
userName = PR_GetEnv("LOGNAME");
335
}
336
if (!userName || !*userName) {
337
userName = "mozillaUser";
338
}
339
340
nsAutoString userDir;
341
userDir.AssignLiteral("mozilla_");
342
userDir.AppendASCII(userName);
343
userDir.ReplaceChar(FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS, '_');
344
345
int counter = 0;
346
bool pathExists;
347
nsCOMPtr<nsIFile> finalPath;
348
349
while (true) {
350
nsAutoString countedUserDir(userDir);
351
countedUserDir.AppendInt(counter, 10);
352
dir->Clone(getter_AddRefs(finalPath));
353
finalPath->Append(countedUserDir);
354
355
rv = finalPath->Exists(&pathExists);
356
NS_ENSURE_SUCCESS(rv, rv);
357
358
if (pathExists) {
359
// If this path has the right permissions, use it.
360
rv = finalPath->GetPermissions(&permissions);
361
NS_ENSURE_SUCCESS(rv, rv);
362
363
// Ensuring the path is writable by the current user.
364
bool isWritable;
365
rv = finalPath->IsWritable(&isWritable);
366
NS_ENSURE_SUCCESS(rv, rv);
367
368
if (permissions == PR_IRWXU && isWritable) {
369
dir = finalPath;
370
break;
371
}
372
}
373
374
rv = finalPath->Create(nsIFile::DIRECTORY_TYPE, PR_IRWXU);
375
if (NS_SUCCEEDED(rv)) {
376
dir = finalPath;
377
break;
378
} else if (rv != NS_ERROR_FILE_ALREADY_EXISTS) {
379
// Unexpected error.
380
return rv;
381
}
382
383
counter++;
384
}
385
}
386
387
# endif
388
#endif
389
390
NS_ASSERTION(dir, "Somehow we didn't get a download directory!");
391
dir.forget(_directory);
392
return NS_OK;
393
}
394
395
/**
396
* Structure for storing extension->type mappings.
397
* @see defaultMimeEntries
398
*/
399
struct nsDefaultMimeTypeEntry {
400
const char* mMimeType;
401
const char* mFileExtension;
402
};
403
404
/**
405
* Default extension->mimetype mappings. These are not overridable.
406
* If you add types here, make sure they are lowercase, or you'll regret it.
407
*/
408
static const nsDefaultMimeTypeEntry defaultMimeEntries[] = {
409
// The following are those extensions that we're asked about during startup,
410
// sorted by order used
411
{IMAGE_GIF, "gif"},
412
{TEXT_XML, "xml"},
413
{APPLICATION_RDF, "rdf"},
414
{TEXT_XUL, "xul"},
415
{IMAGE_PNG, "png"},
416
// -- end extensions used during startup
417
{TEXT_CSS, "css"},
418
{IMAGE_JPEG, "jpeg"},
419
{IMAGE_JPEG, "jpg"},
420
{IMAGE_SVG_XML, "svg"},
421
{TEXT_HTML, "html"},
422
{TEXT_HTML, "htm"},
423
{APPLICATION_XPINSTALL, "xpi"},
424
{"application/xhtml+xml", "xhtml"},
425
{"application/xhtml+xml", "xht"},
426
{TEXT_PLAIN, "txt"},
427
{APPLICATION_JSON, "json"},
428
{APPLICATION_XJAVASCRIPT, "js"},
429
{APPLICATION_XJAVASCRIPT, "jsm"},
430
{VIDEO_OGG, "ogv"},
431
{VIDEO_OGG, "ogg"},
432
{APPLICATION_OGG, "ogg"},
433
{AUDIO_OGG, "oga"},
434
{AUDIO_OGG, "opus"},
435
{APPLICATION_PDF, "pdf"},
436
{VIDEO_WEBM, "webm"},
437
{AUDIO_WEBM, "webm"},
438
{IMAGE_ICO, "ico"},
439
{TEXT_PLAIN, "properties"},
440
{TEXT_PLAIN, "locale"},
441
{TEXT_PLAIN, "ftl"},
442
#if defined(MOZ_WMF)
443
{VIDEO_MP4, "mp4"},
444
{AUDIO_MP4, "m4a"},
445
{AUDIO_MP3, "mp3"},
446
#endif
447
#ifdef MOZ_RAW
448
{VIDEO_RAW, "yuv"}
449
#endif
450
};
451
452
/**
453
* This is a small private struct used to help us initialize some
454
* default mime types.
455
*/
456
struct nsExtraMimeTypeEntry {
457
const char* mMimeType;
458
const char* mFileExtensions;
459
const char* mDescription;
460
};
461
462
#ifdef XP_MACOSX
463
# define MAC_TYPE(x) x
464
#else
465
# define MAC_TYPE(x) 0
466
#endif
467
468
/**
469
* This table lists all of the 'extra' content types that we can deduce from
470
* particular file extensions. These entries also ensure that we provide a good
471
* descriptive name when we encounter files with these content types and/or
472
* extensions. These can be overridden by user helper app prefs. If you add
473
* types here, make sure they are lowercase, or you'll regret it.
474
*/
475
static const nsExtraMimeTypeEntry extraMimeEntries[] = {
476
#if defined(XP_MACOSX) // don't define .bin on the mac...use internet config to
477
// look that up...
478
{APPLICATION_OCTET_STREAM, "exe,com", "Binary File"},
479
#else
480
{APPLICATION_OCTET_STREAM, "exe,com,bin", "Binary File"},
481
#endif
482
{APPLICATION_GZIP2, "gz", "gzip"},
483
{"application/x-arj", "arj", "ARJ file"},
484
{"application/rtf", "rtf", "Rich Text Format File"},
485
{APPLICATION_XPINSTALL, "xpi", "XPInstall Install"},
486
{APPLICATION_PDF, "pdf", "Portable Document Format"},
487
{APPLICATION_POSTSCRIPT, "ps,eps,ai", "Postscript File"},
488
{APPLICATION_XJAVASCRIPT, "js", "Javascript Source File"},
489
{APPLICATION_XJAVASCRIPT, "jsm", "Javascript Module Source File"},
490
#ifdef MOZ_WIDGET_ANDROID
491
{"application/vnd.android.package-archive", "apk", "Android Package"},
492
#endif
493
{IMAGE_ART, "art", "ART Image"},
494
{IMAGE_BMP, "bmp", "BMP Image"},
495
{IMAGE_GIF, "gif", "GIF Image"},
496
{IMAGE_ICO, "ico,cur", "ICO Image"},
497
{IMAGE_JPEG, "jpeg,jpg,jfif,pjpeg,pjp", "JPEG Image"},
498
{IMAGE_PNG, "png", "PNG Image"},
499
{IMAGE_APNG, "apng", "APNG Image"},
500
{IMAGE_TIFF, "tiff,tif", "TIFF Image"},
501
{IMAGE_XBM, "xbm", "XBM Image"},
502
{IMAGE_SVG_XML, "svg", "Scalable Vector Graphics"},
503
{IMAGE_WEBP, "webp", "WebP Image"},
504
{MESSAGE_RFC822, "eml", "RFC-822 data"},
505
{TEXT_PLAIN, "txt,text", "Text File"},
506
{APPLICATION_JSON, "json", "JavaScript Object Notation"},
507
{TEXT_VTT, "vtt", "Web Video Text Tracks"},
508
{TEXT_CACHE_MANIFEST, "appcache", "Application Cache Manifest"},
509
{TEXT_HTML, "html,htm,shtml,ehtml", "HyperText Markup Language"},
510
{"application/xhtml+xml", "xhtml,xht",
511
"Extensible HyperText Markup Language"},
512
{APPLICATION_MATHML_XML, "mml", "Mathematical Markup Language"},
513
{APPLICATION_RDF, "rdf", "Resource Description Framework"},
514
{TEXT_XUL, "xul", "XML-Based User Interface Language"},
515
{TEXT_XML, "xml,xsl,xbl", "Extensible Markup Language"},
516
{TEXT_CSS, "css", "Style Sheet"},
517
{TEXT_VCARD, "vcf,vcard", "Contact Information"},
518
{VIDEO_OGG, "ogv", "Ogg Video"},
519
{VIDEO_OGG, "ogg", "Ogg Video"},
520
{APPLICATION_OGG, "ogg", "Ogg Video"},
521
{AUDIO_OGG, "oga", "Ogg Audio"},
522
{AUDIO_OGG, "opus", "Opus Audio"},
523
{VIDEO_WEBM, "webm", "Web Media Video"},
524
{AUDIO_WEBM, "webm", "Web Media Audio"},
525
{AUDIO_MP3, "mp3", "MPEG Audio"},
526
{VIDEO_MP4, "mp4", "MPEG-4 Video"},
527
{AUDIO_MP4, "m4a", "MPEG-4 Audio"},
528
{VIDEO_RAW, "yuv", "Raw YUV Video"},
529
{AUDIO_WAV, "wav", "Waveform Audio"},
530
{VIDEO_3GPP, "3gpp,3gp", "3GPP Video"},
531
{VIDEO_3GPP2, "3g2", "3GPP2 Video"},
532
{AUDIO_MIDI, "mid", "Standard MIDI Audio"},
533
{APPLICATION_WASM, "wasm", "WebAssembly Module"}};
534
535
#undef MAC_TYPE
536
537
/**
538
* File extensions for which decoding should be disabled.
539
* NOTE: These MUST be lower-case and ASCII.
540
*/
541
static const nsDefaultMimeTypeEntry nonDecodableExtensions[] = {
542
{APPLICATION_GZIP, "gz"},
543
{APPLICATION_GZIP, "tgz"},
544
{APPLICATION_ZIP, "zip"},
545
{APPLICATION_COMPRESS, "z"},
546
{APPLICATION_GZIP, "svgz"}};
547
548
static StaticRefPtr<nsExternalHelperAppService> sExtHelperAppSvcSingleton;
549
550
/**
551
* On Mac child processes, return an nsOSHelperAppServiceChild for remoting
552
* OS calls to the parent process. On all other platforms use
553
* nsOSHelperAppService.
554
*/
555
/* static */
556
already_AddRefed<nsExternalHelperAppService>
557
nsExternalHelperAppService::GetSingleton() {
558
if (!sExtHelperAppSvcSingleton) {
559
#ifdef XP_MACOSX
560
if (XRE_IsParentProcess()) {
561
sExtHelperAppSvcSingleton = new nsOSHelperAppService();
562
} else {
563
sExtHelperAppSvcSingleton = new nsOSHelperAppServiceChild();
564
}
565
#else
566
sExtHelperAppSvcSingleton = new nsOSHelperAppService();
567
#endif /* XP_MACOSX */
568
ClearOnShutdown(&sExtHelperAppSvcSingleton);
569
}
570
571
return do_AddRef(sExtHelperAppSvcSingleton);
572
}
573
574
NS_IMPL_ISUPPORTS(nsExternalHelperAppService, nsIExternalHelperAppService,
575
nsPIExternalAppLauncher, nsIExternalProtocolService,
576
nsIMIMEService, nsIObserver, nsISupportsWeakReference)
577
578
nsExternalHelperAppService::nsExternalHelperAppService() {}
579
nsresult nsExternalHelperAppService::Init() {
580
// Add an observer for profile change
581
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
582
if (!obs) return NS_ERROR_FAILURE;
583
584
nsresult rv = obs->AddObserver(this, "profile-before-change", true);
585
NS_ENSURE_SUCCESS(rv, rv);
586
return obs->AddObserver(this, "last-pb-context-exited", true);
587
}
588
589
nsExternalHelperAppService::~nsExternalHelperAppService() {}
590
591
nsresult nsExternalHelperAppService::DoContentContentProcessHelper(
592
const nsACString& aMimeContentType, nsIRequest* aRequest,
593
nsIInterfaceRequestor* aContentContext, bool aForceSave,
594
nsIInterfaceRequestor* aWindowContext,
595
nsIStreamListener** aStreamListener) {
596
nsCOMPtr<nsPIDOMWindowOuter> window = do_GetInterface(aContentContext);
597
NS_ENSURE_STATE(window);
598
599
// We need to get a hold of a ContentChild so that we can begin forwarding
600
// this data to the parent. In the HTTP case, this is unfortunate, since
601
// we're actually passing data from parent->child->parent wastefully, but
602
// the Right Fix will eventually be to short-circuit those channels on the
603
// parent side based on some sort of subscription concept.
604
using mozilla::dom::ContentChild;
605
using mozilla::dom::ExternalHelperAppChild;
606
ContentChild* child = ContentChild::GetSingleton();
607
if (!child) {
608
return NS_ERROR_FAILURE;
609
}
610
611
nsCString disp;
612
nsCOMPtr<nsIURI> uri;
613
int64_t contentLength = -1;
614
bool wasFileChannel = false;
615
uint32_t contentDisposition = -1;
616
nsAutoString fileName;
617
nsCOMPtr<nsILoadInfo> loadInfo;
618
619
nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
620
if (channel) {
621
channel->GetURI(getter_AddRefs(uri));
622
channel->GetContentLength(&contentLength);
623
channel->GetContentDisposition(&contentDisposition);
624
channel->GetContentDispositionFilename(fileName);
625
channel->GetContentDispositionHeader(disp);
626
loadInfo = channel->LoadInfo();
627
628
nsCOMPtr<nsIFileChannel> fileChan(do_QueryInterface(aRequest));
629
wasFileChannel = fileChan != nullptr;
630
}
631
632
nsCOMPtr<nsIURI> referrer;
633
NS_GetReferrerFromChannel(channel, getter_AddRefs(referrer));
634
635
Maybe<URIParams> uriParams, referrerParams;
636
SerializeURI(uri, uriParams);
637
SerializeURI(referrer, referrerParams);
638
639
Maybe<mozilla::net::LoadInfoArgs> loadInfoArgs;
640
MOZ_ALWAYS_SUCCEEDS(LoadInfoToLoadInfoArgs(loadInfo, &loadInfoArgs));
641
642
// Now we build a protocol for forwarding our data to the parent. The
643
// protocol will act as a listener on the child-side and create a "real"
644
// helperAppService listener on the parent-side, via another call to
645
// DoContent.
646
RefPtr<ExternalHelperAppChild> childListener = new ExternalHelperAppChild();
647
MOZ_ALWAYS_TRUE(child->SendPExternalHelperAppConstructor(
648
childListener, uriParams, loadInfoArgs, nsCString(aMimeContentType), disp,
649
contentDisposition, fileName, aForceSave, contentLength, wasFileChannel,
650
referrerParams, mozilla::dom::BrowserChild::GetFrom(window)));
651
652
NS_ADDREF(*aStreamListener = childListener);
653
654
uint32_t reason = nsIHelperAppLauncherDialog::REASON_CANTHANDLE;
655
656
RefPtr<nsExternalAppHandler> handler = new nsExternalAppHandler(
657
nullptr, EmptyCString(), aContentContext, aWindowContext, this, fileName,
658
reason, aForceSave);
659
if (!handler) {
660
return NS_ERROR_OUT_OF_MEMORY;
661
}
662
663
childListener->SetHandler(handler);
664
return NS_OK;
665
}
666
667
NS_IMETHODIMP nsExternalHelperAppService::DoContent(
668
const nsACString& aMimeContentType, nsIRequest* aRequest,
669
nsIInterfaceRequestor* aContentContext, bool aForceSave,
670
nsIInterfaceRequestor* aWindowContext,
671
nsIStreamListener** aStreamListener) {
672
if (XRE_IsContentProcess()) {
673
return DoContentContentProcessHelper(aMimeContentType, aRequest,
674
aContentContext, aForceSave,
675
aWindowContext, aStreamListener);
676
}
677
678
nsAutoString fileName;
679
nsAutoCString fileExtension;
680
uint32_t reason = nsIHelperAppLauncherDialog::REASON_CANTHANDLE;
681
uint32_t contentDisposition = -1;
682
683
// Get the file extension and name that we will need later
684
nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
685
nsCOMPtr<nsIURI> uri;
686
int64_t contentLength = -1;
687
if (channel) {
688
channel->GetURI(getter_AddRefs(uri));
689
channel->GetContentLength(&contentLength);
690
channel->GetContentDisposition(&contentDisposition);
691
channel->GetContentDispositionFilename(fileName);
692
693
// Check if we have a POST request, in which case we don't want to use
694
// the url's extension
695
bool allowURLExt = true;
696
nsCOMPtr<nsIHttpChannel> httpChan = do_QueryInterface(channel);
697
if (httpChan) {
698
nsAutoCString requestMethod;
699
Unused << httpChan->GetRequestMethod(requestMethod);
700
allowURLExt = !requestMethod.EqualsLiteral("POST");
701
}
702
703
// Check if we had a query string - we don't want to check the URL
704
// extension if a query is present in the URI
705
// If we already know we don't want to check the URL extension, don't
706
// bother checking the query
707
if (uri && allowURLExt) {
708
nsCOMPtr<nsIURL> url = do_QueryInterface(uri);
709
710
if (url) {
711
nsAutoCString query;
712
713
// We only care about the query for HTTP and HTTPS URLs
714
if (uri->SchemeIs("http") || uri->SchemeIs("https")) {
715
url->GetQuery(query);
716
}
717
718
// Only get the extension if the query is empty; if it isn't, then the
719
// extension likely belongs to a cgi script and isn't helpful
720
allowURLExt = query.IsEmpty();
721
}
722
}
723
// Extract name & extension
724
bool isAttachment = GetFilenameAndExtensionFromChannel(
725
channel, fileName, fileExtension, allowURLExt);
726
LOG(("Found extension '%s' (filename is '%s', handling attachment: %i)",
727
fileExtension.get(), NS_ConvertUTF16toUTF8(fileName).get(),
728
isAttachment));
729
if (isAttachment) {
730
reason = nsIHelperAppLauncherDialog::REASON_SERVERREQUEST;
731
}
732
}
733
734
LOG(("HelperAppService::DoContent: mime '%s', extension '%s'\n",
735
PromiseFlatCString(aMimeContentType).get(), fileExtension.get()));
736
737
// We get the mime service here even though we're the default implementation
738
// of it, so it's possible to override only the mime service and not need to
739
// reimplement the whole external helper app service itself.
740
nsCOMPtr<nsIMIMEService> mimeSvc(do_GetService(NS_MIMESERVICE_CONTRACTID));
741
NS_ENSURE_TRUE(mimeSvc, NS_ERROR_FAILURE);
742
743
// Try to find a mime object by looking at the mime type/extension
744
nsCOMPtr<nsIMIMEInfo> mimeInfo;
745
if (aMimeContentType.Equals(APPLICATION_GUESS_FROM_EXT,
746
nsCaseInsensitiveCStringComparator())) {
747
nsAutoCString mimeType;
748
if (!fileExtension.IsEmpty()) {
749
mimeSvc->GetFromTypeAndExtension(EmptyCString(), fileExtension,
750
getter_AddRefs(mimeInfo));
751
if (mimeInfo) {
752
mimeInfo->GetMIMEType(mimeType);
753
754
LOG(("OS-Provided mime type '%s' for extension '%s'\n", mimeType.get(),
755
fileExtension.get()));
756
}
757
}
758
759
if (fileExtension.IsEmpty() || mimeType.IsEmpty()) {
760
// Extension lookup gave us no useful match
761
mimeSvc->GetFromTypeAndExtension(
762
NS_LITERAL_CSTRING(APPLICATION_OCTET_STREAM), fileExtension,
763
getter_AddRefs(mimeInfo));
764
mimeType.AssignLiteral(APPLICATION_OCTET_STREAM);
765
}
766
767
if (channel) {
768
channel->SetContentType(mimeType);
769
}
770
771
// Don't overwrite SERVERREQUEST
772
if (reason == nsIHelperAppLauncherDialog::REASON_CANTHANDLE) {
773
reason = nsIHelperAppLauncherDialog::REASON_TYPESNIFFED;
774
}
775
} else {
776
mimeSvc->GetFromTypeAndExtension(aMimeContentType, fileExtension,
777
getter_AddRefs(mimeInfo));
778
}
779
LOG(("Type/Ext lookup found 0x%p\n", mimeInfo.get()));
780
781
// No mimeinfo -> we can't continue. probably OOM.
782
if (!mimeInfo) {
783
return NS_ERROR_OUT_OF_MEMORY;
784
}
785
786
*aStreamListener = nullptr;
787
// We want the mimeInfo's primary extension to pass it to
788
// nsExternalAppHandler
789
nsAutoCString buf;
790
mimeInfo->GetPrimaryExtension(buf);
791
792
// NB: ExternalHelperAppParent depends on this listener always being an
793
// nsExternalAppHandler. If this changes, make sure to update that code.
794
nsExternalAppHandler* handler =
795
new nsExternalAppHandler(mimeInfo, buf, aContentContext, aWindowContext,
796
this, fileName, reason, aForceSave);
797
if (!handler) {
798
return NS_ERROR_OUT_OF_MEMORY;
799
}
800
801
NS_ADDREF(*aStreamListener = handler);
802
return NS_OK;
803
}
804
805
NS_IMETHODIMP nsExternalHelperAppService::ApplyDecodingForExtension(
806
const nsACString& aExtension, const nsACString& aEncodingType,
807
bool* aApplyDecoding) {
808
*aApplyDecoding = true;
809
uint32_t i;
810
for (i = 0; i < ArrayLength(nonDecodableExtensions); ++i) {
811
if (aExtension.LowerCaseEqualsASCII(
812
nonDecodableExtensions[i].mFileExtension) &&
813
aEncodingType.LowerCaseEqualsASCII(
814
nonDecodableExtensions[i].mMimeType)) {
815
*aApplyDecoding = false;
816
break;
817
}
818
}
819
return NS_OK;
820
}
821
822
nsresult nsExternalHelperAppService::GetFileTokenForPath(
823
const char16_t* aPlatformAppPath, nsIFile** aFile) {
824
nsDependentString platformAppPath(aPlatformAppPath);
825
// First, check if we have an absolute path
826
nsIFile* localFile = nullptr;
827
nsresult rv = NS_NewLocalFile(platformAppPath, true, &localFile);
828
if (NS_SUCCEEDED(rv)) {
829
*aFile = localFile;
830
bool exists;
831
if (NS_FAILED((*aFile)->Exists(&exists)) || !exists) {
832
NS_RELEASE(*aFile);
833
return NS_ERROR_FILE_NOT_FOUND;
834
}
835
return NS_OK;
836
}
837
838
// Second, check if file exists in mozilla program directory
839
rv = NS_GetSpecialDirectory(NS_XPCOM_CURRENT_PROCESS_DIR, aFile);
840
if (NS_SUCCEEDED(rv)) {
841
rv = (*aFile)->Append(platformAppPath);
842
if (NS_SUCCEEDED(rv)) {
843
bool exists = false;
844
rv = (*aFile)->Exists(&exists);
845
if (NS_SUCCEEDED(rv) && exists) return NS_OK;
846
}
847
NS_RELEASE(*aFile);
848
}
849
850
return NS_ERROR_NOT_AVAILABLE;
851
}
852
853
//////////////////////////////////////////////////////////////////////////////////////////////////////
854
// begin external protocol service default implementation...
855
//////////////////////////////////////////////////////////////////////////////////////////////////////
856
NS_IMETHODIMP nsExternalHelperAppService::ExternalProtocolHandlerExists(
857
const char* aProtocolScheme, bool* aHandlerExists) {
858
nsCOMPtr<nsIHandlerInfo> handlerInfo;
859
nsresult rv = GetProtocolHandlerInfo(nsDependentCString(aProtocolScheme),
860
getter_AddRefs(handlerInfo));
861
if (NS_SUCCEEDED(rv)) {
862
// See if we have any known possible handler apps for this
863
nsCOMPtr<nsIMutableArray> possibleHandlers;
864
handlerInfo->GetPossibleApplicationHandlers(
865
getter_AddRefs(possibleHandlers));
866
867
uint32_t length;
868
possibleHandlers->GetLength(&length);
869
if (length) {
870
*aHandlerExists = true;
871
return NS_OK;
872
}
873
}
874
875
// if not, fall back on an os-based handler
876
return OSProtocolHandlerExists(aProtocolScheme, aHandlerExists);
877
}
878
879
NS_IMETHODIMP nsExternalHelperAppService::IsExposedProtocol(
880
const char* aProtocolScheme, bool* aResult) {
881
// check the per protocol setting first. it always takes precedence.
882
// if not set, then use the global setting.
883
884
nsAutoCString prefName("network.protocol-handler.expose.");
885
prefName += aProtocolScheme;
886
bool val;
887
if (NS_SUCCEEDED(Preferences::GetBool(prefName.get(), &val))) {
888
*aResult = val;
889
return NS_OK;
890
}
891
892
// by default, no protocol is exposed. i.e., by default all link clicks must
893
// go through the external protocol service. most applications override this
894
// default behavior.
895
*aResult = Preferences::GetBool("network.protocol-handler.expose-all", false);
896
897
return NS_OK;
898
}
899
900
static const char kExternalProtocolPrefPrefix[] =
901
"network.protocol-handler.external.";
902
static const char kExternalProtocolDefaultPref[] =
903
"network.protocol-handler.external-default";
904
905
NS_IMETHODIMP
906
nsExternalHelperAppService::LoadURI(nsIURI* aURI,
907
nsIInterfaceRequestor* aWindowContext) {
908
NS_ENSURE_ARG_POINTER(aURI);
909
910
if (XRE_IsContentProcess()) {
911
URIParams uri;
912
SerializeURI(aURI, uri);
913
914
nsCOMPtr<nsIBrowserChild> browserChild(do_GetInterface(aWindowContext));
915
mozilla::dom::ContentChild::GetSingleton()->SendLoadURIExternal(
916
uri, static_cast<dom::BrowserChild*>(browserChild.get()));
917
return NS_OK;
918
}
919
920
nsAutoCString spec;
921
aURI->GetSpec(spec);
922
923
if (spec.Find("%00") != -1) return NS_ERROR_MALFORMED_URI;
924
925
spec.ReplaceSubstring("\"", "%22");
926
spec.ReplaceSubstring("`", "%60");
927
928
nsCOMPtr<nsIIOService> ios(do_GetIOService());
929
nsCOMPtr<nsIURI> uri;
930
nsresult rv = ios->NewURI(spec, nullptr, nullptr, getter_AddRefs(uri));
931
NS_ENSURE_SUCCESS(rv, rv);
932
933
nsAutoCString scheme;
934
uri->GetScheme(scheme);
935
if (scheme.IsEmpty()) return NS_OK; // must have a scheme
936
937
// Deny load if the prefs say to do so
938
nsAutoCString externalPref(kExternalProtocolPrefPrefix);
939
externalPref += scheme;
940
bool allowLoad = false;
941
if (NS_FAILED(Preferences::GetBool(externalPref.get(), &allowLoad))) {
942
// no scheme-specific value, check the default
943
if (NS_FAILED(
944
Preferences::GetBool(kExternalProtocolDefaultPref, &allowLoad))) {
945
return NS_OK; // missing default pref
946
}
947
}
948
949
if (!allowLoad) {
950
return NS_OK; // explicitly denied
951
}
952
953
nsCOMPtr<nsIHandlerInfo> handler;
954
rv = GetProtocolHandlerInfo(scheme, getter_AddRefs(handler));
955
NS_ENSURE_SUCCESS(rv, rv);
956
957
nsHandlerInfoAction preferredAction;
958
handler->GetPreferredAction(&preferredAction);
959
bool alwaysAsk = true;
960
handler->GetAlwaysAskBeforeHandling(&alwaysAsk);
961
962
// if we are not supposed to ask, and the preferred action is to use
963
// a helper app or the system default, we just launch the URI.
964
if (!alwaysAsk && (preferredAction == nsIHandlerInfo::useHelperApp ||
965
preferredAction == nsIHandlerInfo::useSystemDefault)) {
966
rv = handler->LaunchWithURI(uri, aWindowContext);
967
// We are not supposed to ask, but when file not found the user most likely
968
// uninstalled the application which handles the uri so we will continue
969
// by application chooser dialog.
970
if (rv != NS_ERROR_FILE_NOT_FOUND) {
971
return rv;
972
}
973
}
974
975
nsCOMPtr<nsIContentDispatchChooser> chooser =
976
do_CreateInstance("@mozilla.org/content-dispatch-chooser;1", &rv);
977
NS_ENSURE_SUCCESS(rv, rv);
978
979
return chooser->Ask(handler, aWindowContext, uri,
980
nsIContentDispatchChooser::REASON_CANNOT_HANDLE);
981
}
982
983
NS_IMETHODIMP nsExternalHelperAppService::GetApplicationDescription(
984
const nsACString& aScheme, nsAString& _retval) {
985
// this method should only be implemented by each OS specific implementation
986
// of this service.
987
return NS_ERROR_NOT_IMPLEMENTED;
988
}
989
990
//////////////////////////////////////////////////////////////////////////////////////////////////////
991
// Methods related to deleting temporary files on exit
992
//////////////////////////////////////////////////////////////////////////////////////////////////////
993
994
/* static */
995
nsresult nsExternalHelperAppService::DeleteTemporaryFileHelper(
996
nsIFile* aTemporaryFile, nsCOMArray<nsIFile>& aFileList) {
997
bool isFile = false;
998
999
// as a safety measure, make sure the nsIFile is really a file and not a
1000
// directory object.
1001
aTemporaryFile->IsFile(&isFile);
1002
if (!isFile) return NS_OK;
1003
1004
aFileList.AppendObject(aTemporaryFile);
1005
1006
return NS_OK;
1007
}
1008
1009
NS_IMETHODIMP
1010
nsExternalHelperAppService::DeleteTemporaryFileOnExit(nsIFile* aTemporaryFile) {
1011
return DeleteTemporaryFileHelper(aTemporaryFile, mTemporaryFilesList);
1012
}
1013
1014
NS_IMETHODIMP
1015
nsExternalHelperAppService::DeleteTemporaryPrivateFileWhenPossible(
1016
nsIFile* aTemporaryFile) {
1017
return DeleteTemporaryFileHelper(aTemporaryFile, mTemporaryPrivateFilesList);
1018
}
1019
1020
void nsExternalHelperAppService::ExpungeTemporaryFilesHelper(
1021
nsCOMArray<nsIFile>& fileList) {
1022
int32_t numEntries = fileList.Count();
1023
nsIFile* localFile;
1024
for (int32_t index = 0; index < numEntries; index++) {
1025
localFile = fileList[index];
1026
if (localFile) {
1027
// First make the file writable, since the temp file is probably readonly.
1028
localFile->SetPermissions(0600);
1029
localFile->Remove(false);
1030
}
1031
}
1032
1033
fileList.Clear();
1034
}
1035
1036
void nsExternalHelperAppService::ExpungeTemporaryFiles() {
1037
ExpungeTemporaryFilesHelper(mTemporaryFilesList);
1038
}
1039
1040
void nsExternalHelperAppService::ExpungeTemporaryPrivateFiles() {
1041
ExpungeTemporaryFilesHelper(mTemporaryPrivateFilesList);
1042
}
1043
1044
static const char kExternalWarningPrefPrefix[] =
1045
"network.protocol-handler.warn-external.";
1046
static const char kExternalWarningDefaultPref[] =
1047
"network.protocol-handler.warn-external-default";
1048
1049
NS_IMETHODIMP
1050
nsExternalHelperAppService::GetProtocolHandlerInfo(
1051
const nsACString& aScheme, nsIHandlerInfo** aHandlerInfo) {
1052
// XXX enterprise customers should be able to turn this support off with a
1053
// single master pref (maybe use one of the "exposed" prefs here?)
1054
1055
bool exists;
1056
nsresult rv = GetProtocolHandlerInfoFromOS(aScheme, &exists, aHandlerInfo);
1057
if (NS_FAILED(rv)) {
1058
// Either it knows nothing, or we ran out of memory
1059
return NS_ERROR_FAILURE;
1060
}
1061
1062
nsCOMPtr<nsIHandlerService> handlerSvc =
1063
do_GetService(NS_HANDLERSERVICE_CONTRACTID);
1064
if (handlerSvc) {
1065
bool hasHandler = false;
1066
(void)handlerSvc->Exists(*aHandlerInfo, &hasHandler);
1067
if (hasHandler) {
1068
rv = handlerSvc->FillHandlerInfo(*aHandlerInfo, EmptyCString());
1069
if (NS_SUCCEEDED(rv)) return NS_OK;
1070
}
1071
}
1072
1073
return SetProtocolHandlerDefaults(*aHandlerInfo, exists);
1074
}
1075
1076
NS_IMETHODIMP
1077
nsExternalHelperAppService::GetProtocolHandlerInfoFromOS(
1078
const nsACString& aScheme, bool* found, nsIHandlerInfo** aHandlerInfo) {
1079
// intended to be implemented by the subclass
1080
return NS_ERROR_NOT_IMPLEMENTED;
1081
}
1082
1083
NS_IMETHODIMP
1084
nsExternalHelperAppService::SetProtocolHandlerDefaults(
1085
nsIHandlerInfo* aHandlerInfo, bool aOSHandlerExists) {
1086
// this type isn't in our database, so we've only got an OS default handler,
1087
// if one exists
1088
1089
if (aOSHandlerExists) {
1090
// we've got a default, so use it
1091
aHandlerInfo->SetPreferredAction(nsIHandlerInfo::useSystemDefault);
1092
1093
// whether or not to ask the user depends on the warning preference
1094
nsAutoCString scheme;
1095
aHandlerInfo->GetType(scheme);
1096
1097
nsAutoCString warningPref(kExternalWarningPrefPrefix);
1098
warningPref += scheme;
1099
bool warn;
1100
if (NS_FAILED(Preferences::GetBool(warningPref.get(), &warn))) {
1101
// no scheme-specific value, check the default
1102
warn = Preferences::GetBool(kExternalWarningDefaultPref, true);
1103
}
1104
aHandlerInfo->SetAlwaysAskBeforeHandling(warn);
1105
} else {
1106
// If no OS default existed, we set the preferred action to alwaysAsk.
1107
// This really means not initialized (i.e. there's no available handler)
1108
// to all the code...
1109
aHandlerInfo->SetPreferredAction(nsIHandlerInfo::alwaysAsk);
1110
}
1111
1112
return NS_OK;
1113
}
1114
1115
// XPCOM profile change observer
1116
NS_IMETHODIMP
1117
nsExternalHelperAppService::Observe(nsISupports* aSubject, const char* aTopic,
1118
const char16_t* someData) {
1119
if (!strcmp(aTopic, "profile-before-change")) {
1120
ExpungeTemporaryFiles();
1121
} else if (!strcmp(aTopic, "last-pb-context-exited")) {
1122
ExpungeTemporaryPrivateFiles();
1123
}
1124
return NS_OK;
1125
}
1126
1127
//////////////////////////////////////////////////////////////////////////////////////////////////////
1128
// begin external app handler implementation
1129
//////////////////////////////////////////////////////////////////////////////////////////////////////
1130
1131
NS_IMPL_ADDREF(nsExternalAppHandler)
1132
NS_IMPL_RELEASE(nsExternalAppHandler)
1133
1134
NS_INTERFACE_MAP_BEGIN(nsExternalAppHandler)
1135
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStreamListener)
1136
NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
1137
NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
1138
NS_INTERFACE_MAP_ENTRY(nsIHelperAppLauncher)
1139
NS_INTERFACE_MAP_ENTRY(nsICancelable)
1140
NS_INTERFACE_MAP_ENTRY(nsIBackgroundFileSaverObserver)
1141
NS_INTERFACE_MAP_ENTRY(nsINamed)
1142
NS_INTERFACE_MAP_END
1143
1144
nsExternalAppHandler::nsExternalAppHandler(
1145
nsIMIMEInfo* aMIMEInfo, const nsACString& aTempFileExtension,
1146
nsIInterfaceRequestor* aContentContext,
1147
nsIInterfaceRequestor* aWindowContext,
1148
nsExternalHelperAppService* aExtProtSvc,
1149
const nsAString& aSuggestedFilename, uint32_t aReason, bool aForceSave)
1150
: mMimeInfo(aMIMEInfo),
1151
mContentContext(aContentContext),
1152
mWindowContext(aWindowContext),
1153
mSuggestedFileName(aSuggestedFilename),
1154
mForceSave(aForceSave),
1155
mCanceled(false),
1156
mStopRequestIssued(false),
1157
mIsFileChannel(false),
1158
mReason(aReason),
1159
mTempFileIsExecutable(false),
1160
mTimeDownloadStarted(0),
1161
mContentLength(-1),
1162
mProgress(0),
1163
mSaver(nullptr),
1164
mDialogProgressListener(nullptr),
1165
mTransfer(nullptr),
1166
mRequest(nullptr),
1167
mExtProtSvc(aExtProtSvc) {
1168
// make sure the extention includes the '.'
1169
if (!aTempFileExtension.IsEmpty() && aTempFileExtension.First() != '.')
1170
mTempFileExtension = char16_t('.');
1171
AppendUTF8toUTF16(aTempFileExtension, mTempFileExtension);
1172
1173
// replace platform specific path separator and illegal characters to avoid
1174
// any confusion
1175
mSuggestedFileName.ReplaceChar(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS,
1176
'_');
1177
mTempFileExtension.ReplaceChar(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS,
1178
'_');
1179
1180
// Remove unsafe bidi characters which might have spoofing implications (bug
1181
// 511521).
1182
const char16_t unsafeBidiCharacters[] = {
1183
char16_t(0x061c), // Arabic Letter Mark
1184
char16_t(0x200e), // Left-to-Right Mark
1185
char16_t(0x200f), // Right-to-Left Mark
1186
char16_t(0x202a), // Left-to-Right Embedding
1187
char16_t(0x202b), // Right-to-Left Embedding
1188
char16_t(0x202c), // Pop Directional Formatting
1189
char16_t(0x202d), // Left-to-Right Override
1190
char16_t(0x202e), // Right-to-Left Override
1191
char16_t(0x2066), // Left-to-Right Isolate
1192
char16_t(0x2067), // Right-to-Left Isolate
1193
char16_t(0x2068), // First Strong Isolate
1194
char16_t(0x2069), // Pop Directional Isolate
1195
char16_t(0)};
1196
mSuggestedFileName.ReplaceChar(unsafeBidiCharacters, '_');
1197
mTempFileExtension.ReplaceChar(unsafeBidiCharacters, '_');
1198
1199
// Make sure extension is correct.
1200
EnsureSuggestedFileName();
1201
1202
mBufferSize = Preferences::GetUint("network.buffer.cache.size", 4096);
1203
}
1204
1205
nsExternalAppHandler::~nsExternalAppHandler() {
1206
MOZ_ASSERT(!mSaver, "Saver should hold a reference to us until deleted");
1207
}
1208
1209
void nsExternalAppHandler::DidDivertRequest(nsIRequest* request) {
1210
MOZ_ASSERT(XRE_IsContentProcess(), "in child process");
1211
// Remove our request from the child loadGroup
1212
RetargetLoadNotifications(request);
1213
}
1214
1215
NS_IMETHODIMP nsExternalAppHandler::SetWebProgressListener(
1216
nsIWebProgressListener2* aWebProgressListener) {
1217
// This is always called by nsHelperDlg.js. Go ahead and register the
1218
// progress listener. At this point, we don't have mTransfer.
1219
mDialogProgressListener = aWebProgressListener;
1220
return NS_OK;
1221
}
1222
1223
NS_IMETHODIMP nsExternalAppHandler::GetTargetFile(nsIFile** aTarget) {
1224
if (mFinalFileDestination)
1225
*aTarget = mFinalFileDestination;
1226
else
1227
*aTarget = mTempFile;
1228
1229
NS_IF_ADDREF(*aTarget);
1230
return NS_OK;
1231
}
1232
1233
NS_IMETHODIMP nsExternalAppHandler::GetTargetFileIsExecutable(bool* aExec) {
1234
// Use the real target if it's been set
1235
if (mFinalFileDestination) return mFinalFileDestination->IsExecutable(aExec);
1236
1237
// Otherwise, use the stored executable-ness of the temporary
1238
*aExec = mTempFileIsExecutable;
1239
return NS_OK;
1240
}
1241
1242
NS_IMETHODIMP nsExternalAppHandler::GetTimeDownloadStarted(PRTime* aTime) {
1243
*aTime = mTimeDownloadStarted;
1244
return NS_OK;
1245
}
1246
1247
NS_IMETHODIMP nsExternalAppHandler::GetContentLength(int64_t* aContentLength) {
1248
*aContentLength = mContentLength;
1249
return NS_OK;
1250
}
1251
1252
void nsExternalAppHandler::RetargetLoadNotifications(nsIRequest* request) {
1253
// we are going to run the downloading of the helper app in our own little
1254
// docloader / load group context. so go ahead and force the creation of a
1255
// load group and doc loader for us to use...
1256
nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request);
1257
if (!aChannel) return;
1258
1259
// we need to store off the original (pre redirect!) channel that initiated
1260
// the load. We do this so later on, we can pass any refresh urls associated
1261
// with the original channel back to the window context which started the
1262
// whole process. More comments about that are listed below.... HACK ALERT:
1263
// it's pretty bogus that we are getting the document channel from the doc
1264
// loader. ideally we should be able to just use mChannel (the channel we are
1265
// extracting content from) or the default load channel associated with the
1266
// original load group. Unfortunately because a redirect may have occurred,
1267
// the doc loader is the only one with a ptr to the original channel which is
1268
// what we really want....
1269
1270
// Note that we need to do this before removing aChannel from the loadgroup,
1271
// since that would mess with the original channel on the loader.
1272
nsCOMPtr<nsIDocumentLoader> origContextLoader =
1273
do_GetInterface(mContentContext);
1274
if (origContextLoader) {
1275
origContextLoader->GetDocumentChannel(getter_AddRefs(mOriginalChannel));
1276
}
1277
1278
bool isPrivate = NS_UsePrivateBrowsing(aChannel);
1279
1280
nsCOMPtr<nsILoadGroup> oldLoadGroup;
1281
aChannel->GetLoadGroup(getter_AddRefs(oldLoadGroup));
1282
1283
if (oldLoadGroup) {
1284
oldLoadGroup->RemoveRequest(request, nullptr, NS_BINDING_RETARGETED);
1285
}
1286
1287
aChannel->SetLoadGroup(nullptr);
1288
aChannel->SetNotificationCallbacks(nullptr);
1289
1290
nsCOMPtr<nsIPrivateBrowsingChannel> pbChannel = do_QueryInterface(aChannel);
1291
if (pbChannel) {
1292
pbChannel->SetPrivate(isPrivate);
1293
}
1294
}
1295
1296
/**
1297
* Make mTempFileExtension contain an extension exactly when its previous value
1298
* is different from mSuggestedFileName's extension, so that it can be appended
1299
* to mSuggestedFileName and form a valid, useful leaf name.
1300
* This is required so that the (renamed) temporary file has the correct
1301
* extension after downloading to make sure the OS will launch the application
1302
* corresponding to the MIME type (which was used to calculate
1303
* mTempFileExtension). This prevents a cgi-script named foobar.exe that
1304
* returns application/zip from being named foobar.exe and executed as an
1305
* executable file. It also blocks content that a web site might provide with a
1306
* content-disposition header indicating filename="foobar.exe" from being
1307
* downloaded to a file with extension .exe and executed.
1308
*/
1309
void nsExternalAppHandler::EnsureSuggestedFileName() {
1310
// Make sure there is a mTempFileExtension (not "" or ".").
1311
// Remember that mTempFileExtension will always have the leading "."
1312
// (the check for empty is just to be safe).
1313
if (mTempFileExtension.Length() > 1) {
1314
// Get mSuggestedFileName's current extension.
1315
nsAutoString fileExt;
1316
int32_t pos = mSuggestedFileName.RFindChar('.');
1317
if (pos != kNotFound)
1318
mSuggestedFileName.Right(fileExt, mSuggestedFileName.Length() - pos);
1319
1320
// Now, compare fileExt to mTempFileExtension.
1321
if (fileExt.Equals(mTempFileExtension,
1322
nsCaseInsensitiveStringComparator())) {
1323
// Matches -> mTempFileExtension can be empty
1324
mTempFileExtension.Truncate();
1325
}
1326
}
1327
}
1328
1329
nsresult nsExternalAppHandler::SetUpTempFile(nsIChannel* aChannel) {
1330
// First we need to try to get the destination directory for the temporary
1331
// file.
1332
nsresult rv = GetDownloadDirectory(getter_AddRefs(mTempFile));
1333
NS_ENSURE_SUCCESS(rv, rv);
1334
1335
// At this point, we do not have a filename for the temp file. For security
1336
// purposes, this cannot be predictable, so we must use a cryptographic
1337
// quality PRNG to generate one.
1338
// We will request raw random bytes, and transform that to a base64 string,
1339
// as all characters from the base64 set are acceptable for filenames. For
1340
// each three bytes of random data, we will get four bytes of ASCII. Request
1341
// a bit more, to be safe, and truncate to the length we want in the end.
1342
1343
const uint32_t wantedFileNameLength = 8;
1344
const uint32_t requiredBytesLength =
1345
static_cast<uint32_t>((wantedFileNameLength + 1) / 4 * 3);
1346
1347
nsCOMPtr<nsIRandomGenerator> rg =
1348
do_GetService("@mozilla.org/security/random-generator;1", &rv);
1349
NS_ENSURE_SUCCESS(rv, rv);
1350
1351
uint8_t* buffer;
1352
rv = rg->GenerateRandomBytes(requiredBytesLength, &buffer);
1353
NS_ENSURE_SUCCESS(rv, rv);
1354
1355
nsAutoCString tempLeafName;
1356
nsDependentCSubstring randomData(reinterpret_cast<const char*>(buffer),
1357
requiredBytesLength);
1358
rv = Base64Encode(randomData, tempLeafName);
1359
free(buffer);
1360
buffer = nullptr;
1361
NS_ENSURE_SUCCESS(rv, rv);
1362
1363
tempLeafName.Truncate(wantedFileNameLength);
1364
1365
// Base64 characters are alphanumeric (a-zA-Z0-9) and '+' and '/', so we need
1366
// to replace illegal characters -- notably '/'
1367
tempLeafName.ReplaceChar(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS, '_');
1368
1369
// now append our extension.
1370
nsAutoCString ext;
1371
mMimeInfo->GetPrimaryExtension(ext);
1372
if (!ext.IsEmpty()) {
1373
ext.ReplaceChar(KNOWN_PATH_SEPARATORS FILE_ILLEGAL_CHARACTERS, '_');
1374
if (ext.First() != '.') tempLeafName.Append('.');
1375
tempLeafName.Append(ext);
1376
}
1377
1378
// We need to temporarily create a dummy file with the correct
1379
// file extension to determine the executable-ness, so do this before adding
1380
// the extra .part extension.
1381
nsCOMPtr<nsIFile> dummyFile;
1382
rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(dummyFile));
1383
NS_ENSURE_SUCCESS(rv, rv);
1384
1385
// Set the file name without .part
1386
rv = dummyFile->Append(NS_ConvertUTF8toUTF16(tempLeafName));
1387
NS_ENSURE_SUCCESS(rv, rv);
1388
rv = dummyFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
1389
NS_ENSURE_SUCCESS(rv, rv);
1390
1391
// Store executable-ness then delete
1392
dummyFile->IsExecutable(&mTempFileIsExecutable);
1393
dummyFile->Remove(false);
1394
1395
// Add an additional .part to prevent the OS from running this file in the
1396
// default application.
1397
tempLeafName.AppendLiteral(".part");
1398
1399
rv = mTempFile->Append(NS_ConvertUTF8toUTF16(tempLeafName));
1400
// make this file unique!!!
1401
NS_ENSURE_SUCCESS(rv, rv);
1402
rv = mTempFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
1403
NS_ENSURE_SUCCESS(rv, rv);
1404
1405
// Now save the temp leaf name, minus the ".part" bit, so we can use it later.
1406
// This is a bit broken in the case when createUnique actually had to append
1407
// some numbers, because then we now have a filename like foo.bar-1.part and
1408
// we'll end up with foo.bar-1.bar as our final filename if we end up using
1409
// this. But the other options are all bad too.... Ideally we'd have a way
1410
// to tell createUnique to put its unique marker before the extension that
1411
// comes before ".part" or something.
1412
rv = mTempFile->GetLeafName(mTempLeafName);
1413
NS_ENSURE_SUCCESS(rv, rv);
1414
1415
NS_ENSURE_TRUE(StringEndsWith(mTempLeafName, NS_LITERAL_STRING(".part")),
1416
NS_ERROR_UNEXPECTED);
1417
1418
// Strip off the ".part" from mTempLeafName
1419
mTempLeafName.Truncate(mTempLeafName.Length() - ArrayLength(".part") + 1);
1420
1421
MOZ_ASSERT(!mSaver, "Output file initialization called more than once!");
1422
mSaver =
1423
do_CreateInstance(NS_BACKGROUNDFILESAVERSTREAMLISTENER_CONTRACTID, &rv);
1424
NS_ENSURE_SUCCESS(rv, rv);
1425
1426
rv = mSaver->SetObserver(this);
1427
if (NS_FAILED(rv)) {
1428
mSaver = nullptr;
1429
return rv;
1430
}
1431
1432
rv = mSaver->EnableSha256();
1433
NS_ENSURE_SUCCESS(rv, rv);
1434
1435
rv = mSaver->EnableSignatureInfo();
1436
NS_ENSURE_SUCCESS(rv, rv);
1437
LOG(("Enabled hashing and signature verification"));
1438
1439
rv = mSaver->SetTarget(mTempFile, false);
1440
NS_ENSURE_SUCCESS(rv, rv);
1441
1442
return rv;
1443
}
1444
1445
void nsExternalAppHandler::MaybeApplyDecodingForExtension(
1446
nsIRequest* aRequest) {
1447
MOZ_ASSERT(aRequest);
1448
1449
nsCOMPtr<nsIEncodedChannel> encChannel = do_QueryInterface(aRequest);
1450
if (!encChannel) {
1451
return;
1452
}
1453
1454
// Turn off content encoding conversions if needed
1455
bool applyConversion = true;
1456
1457
// First, check to see if conversion is already disabled. If so, we
1458
// have nothing to do here.
1459
encChannel->GetApplyConversion(&applyConversion);
1460
if (!applyConversion) {
1461
return;
1462
}
1463
1464
nsCOMPtr<nsIURL> sourceURL(do_QueryInterface(mSourceUrl));
1465
if (sourceURL) {
1466
nsAutoCString extension;
1467
sourceURL->GetFileExtension(extension);
1468
if (!extension.IsEmpty()) {
1469
nsCOMPtr<nsIUTF8StringEnumerator> encEnum;
1470
encChannel->GetContentEncodings(getter_AddRefs(encEnum));
1471
if (encEnum) {
1472
bool hasMore;
1473
nsresult rv = encEnum->HasMore(&hasMore);
1474
if (NS_SUCCEEDED(rv) && hasMore) {
1475
nsAutoCString encType;
1476
rv = encEnum->GetNext(encType);
1477
if (NS_SUCCEEDED(rv) && !encType.IsEmpty()) {
1478
MOZ_ASSERT(mExtProtSvc);
1479
mExtProtSvc->ApplyDecodingForExtension(extension, encType,
1480
&applyConversion);
1481
}
1482
}
1483
}
1484
}
1485
}
1486
1487
encChannel->SetApplyConversion(applyConversion);
1488
}
1489
1490
NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest* request) {
1491
MOZ_ASSERT(request, "OnStartRequest without request?");
1492
1493
// Set mTimeDownloadStarted here as the download has already started and
1494
// we want to record the start time before showing the filepicker.
1495
mTimeDownloadStarted = PR_Now();
1496
1497
mRequest = request;
1498
1499
nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request);
1500
1501
nsresult rv;
1502
1503
nsCOMPtr<nsIFileChannel> fileChan(do_QueryInterface(request));
1504
mIsFileChannel = fileChan != nullptr;
1505
if (!mIsFileChannel) {
1506
// It's possible that this request came from the child process and the
1507
// file channel actually lives there. If this returns true, then our
1508
// mSourceUrl will be an nsIFileURL anyway.
1509
nsCOMPtr<dom::nsIExternalHelperAppParent> parent(
1510
do_QueryInterface(request));
1511
mIsFileChannel = parent && parent->WasFileChannel();
1512
}
1513
1514
// Get content length
1515
if (aChannel) {
1516
aChannel->GetContentLength(&mContentLength);
1517
}
1518
1519
mMaybeCloseWindowHelper = new MaybeCloseWindowHelper(mContentContext);
1520
1521
nsCOMPtr<nsIPropertyBag2> props(do_QueryInterface(request, &rv));
1522
// Determine whether a new window was opened specifically for this request
1523
if (props) {
1524
bool tmp = false;
1525
props->GetPropertyAsBool(NS_LITERAL_STRING("docshell.newWindowTarget"),
1526
&tmp);
1527
mMaybeCloseWindowHelper->SetShouldCloseWindow(tmp);
1528
}
1529
1530
// Now get the URI
1531
if (aChannel) {
1532
aChannel->GetURI(getter_AddRefs(mSourceUrl));
1533
}
1534
1535
// retarget all load notifications to our docloader instead of the original
1536
// window's docloader...
1537
RetargetLoadNotifications(request);
1538
1539
// Check to see if there is a refresh header on the original channel.
1540
if (mOriginalChannel) {
1541
nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mOriginalChannel));
1542
if (httpChannel) {
1543
nsAutoCString refreshHeader;
1544
Unused << httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("refresh"),
1545
refreshHeader);
1546
if (!refreshHeader.IsEmpty()) {
1547
mMaybeCloseWindowHelper->SetShouldCloseWindow(false);
1548
}
1549
}
1550
}
1551
1552
// Close the underlying DOMWindow if there is no refresh header
1553
// and it was opened specifically for the download
1554
mContentContext = mMaybeCloseWindowHelper->MaybeCloseWindow();
1555
1556
// In an IPC setting, we're allowing the child process, here, to make
1557
// decisions about decoding the channel (e.g. decompression). It will
1558
// still forward the decoded (uncompressed) data back to the parent.
1559
// Con: Uncompressed data means more IPC overhead.
1560
// Pros: ExternalHelperAppParent doesn't need to implement nsIEncodedChannel.
1561
// Parent process doesn't need to expect CPU time on decompression.
1562
MaybeApplyDecodingForExtension(aChannel);
1563
1564
// At this point, the child process has done everything it can usefully do
1565
// for OnStartRequest.
1566
if (XRE_IsContentProcess()) {
1567
return NS_OK;
1568
}
1569
1570
rv = SetUpTempFile(aChannel);
1571
if (NS_FAILED(rv)) {
1572
nsresult transferError = rv;
1573
1574
rv = CreateFailedTransfer(aChannel && NS_UsePrivateBrowsing(aChannel));
1575
if (NS_FAILED(rv)) {
1576
LOG(
1577
("Failed to create transfer to report failure."
1578
"Will fallback to prompter!"));
1579
}
1580
1581
mCanceled = true;
1582
request->Cancel(transferError);
1583
1584
nsAutoString path;
1585
if (mTempFile) mTempFile->GetPath(path);
1586
1587
SendStatusChange(kWriteError, transferError, request, path);
1588
1589
return NS_OK;
1590
}
1591
1592
// Inform channel it is open on behalf of a download to throttle it during
1593
// page loads and prevent its caching.
1594
nsCOMPtr<nsIHttpChannelInternal> httpInternal = do_QueryInterface(aChannel);
1595
if (httpInternal) {
1596
rv = httpInternal->SetChannelIsForDownload(true);
1597
MOZ_ASSERT(NS_SUCCEEDED(rv));
1598
}
1599
1600
// now that the temp file is set up, find out if we need to invoke a dialog
1601
// asking the user what they want us to do with this content...
1602
1603
// We can get here for three reasons: "can't handle", "sniffed type", or
1604
// "server sent content-disposition:attachment". In the first case we want
1605
// to honor the user's "always ask" pref; in the other two cases we want to
1606
// honor it only if the default action is "save". Opening attachments in
1607
// helper apps by default breaks some websites (especially if the attachment
1608
// is one part of a multipart document). Opening sniffed content in helper
1609
// apps by default introduces security holes that we'd rather not have.
1610
1611
// So let's find out whether the user wants to be prompted. If he does not,
1612
// check mReason and the preferred action to see what we should do.
1613
1614
bool alwaysAsk = true;
1615
mMimeInfo->GetAlwaysAskBeforeHandling(&alwaysAsk);
1616
if (alwaysAsk) {
1617
// But we *don't* ask if this mimeInfo didn't come from
1618
// our user configuration datastore and the user has said
1619
// at some point in the distant past that they don't
1620
// want to be asked. The latter fact would have been
1621
// stored in pref strings back in the old days.
1622
1623
bool mimeTypeIsInDatastore = false;
1624
nsCOMPtr<nsIHandlerService> handlerSvc =
1625
do_GetService(NS_HANDLERSERVICE_CONTRACTID);
1626
if (handlerSvc) {
1627
handlerSvc->Exists(mMimeInfo, &mimeTypeIsInDatastore);
1628
}
1629
if (!handlerSvc || !mimeTypeIsInDatastore) {
1630
nsAutoCString MIMEType;
1631
mMimeInfo->GetMIMEType(MIMEType);
1632
if (!GetNeverAskFlagFromPref(NEVER_ASK_FOR_SAVE_TO_DISK_PREF,
1633
MIMEType.get())) {
1634
// Don't need to ask after all.
1635
alwaysAsk = false;
1636
// Make sure action matches pref (save to disk).
1637
mMimeInfo->SetPreferredAction(nsIMIMEInfo::saveToDisk);
1638
} else if (!GetNeverAskFlagFromPref(NEVER_ASK_FOR_OPEN_FILE_PREF,
1639
MIMEType.get())) {
1640
// Don't need to ask after all.
1641
alwaysAsk = false;
1642
}
1643
}
1644
}
1645
1646
int32_t action = nsIMIMEInfo::saveToDisk;
1647
mMimeInfo->GetPreferredAction(&action);
1648
1649
// OK, now check why we're here
1650
if (!alwaysAsk && mReason != nsIHelperAppLauncherDialog::REASON_CANTHANDLE) {
1651
// Force asking if we're not saving. See comment back when we fetched the
1652
// alwaysAsk boolean for details.
1653
alwaysAsk = (action != nsIMIMEInfo::saveToDisk);
1654
}
1655
1656
// if we were told that we _must_ save to disk without asking, all the stuff
1657
// before this is irrelevant; override it
1658
if (mForceSave) {
1659
alwaysAsk = false;
1660
action = nsIMIMEInfo::saveToDisk;
1661
}
1662
1663
if (alwaysAsk) {
1664
// Display the dialog
1665
mDialog = do_CreateInstance(NS_HELPERAPPLAUNCHERDLG_CONTRACTID, &rv);
1666
NS_ENSURE_SUCCESS(rv, rv);
1667
1668
// this will create a reference cycle (the dialog holds a reference to us as
1669
// nsIHelperAppLauncher), which will be broken in Cancel or CreateTransfer.
1670
rv = mDialog->Show(this, GetDialogParent(), mReason);
1671
1672
// what do we do if the dialog failed? I guess we should call Cancel and
1673
// abort the load....
1674
} else {
1675
// We need to do the save/open immediately, then.
1676
#ifdef XP_WIN
1677
/* We need to see whether the file we've got here could be
1678
* executable. If it could, we had better not try to open it!
1679
* We can skip this check, though, if we have a setting to open in a
1680
* helper app.
1681
* This code mirrors the code in
1682
* nsExternalAppHandler::LaunchWithApplication so that what we
1683
* test here is as close as possible to what will really be
1684
* happening if we decide to execute
1685
*/
1686
nsCOMPtr<nsIHandlerApp> prefApp;
1687
mMimeInfo->GetPreferredApplicationHandler(getter_AddRefs(prefApp));
1688
if (action != nsIMIMEInfo::useHelperApp || !prefApp) {
1689
nsCOMPtr<nsIFile> fileToTest;
1690
GetTargetFile(getter_AddRefs(fileToTest));
1691
if (fileToTest) {
1692
bool isExecutable;
1693
rv = fileToTest->IsExecutable(&isExecutable);
1694
if (NS_FAILED(rv) ||
1695
isExecutable) { // checking NS_FAILED, because paranoia is good
1696
action = nsIMIMEInfo::saveToDisk;
1697
}
1698
} else { // Paranoia is good here too, though this really should not
1699
// happen
1700
NS_WARNING(
1701
"GetDownloadInfo returned a null file after the temp file has been "
1702
"set up! ");
1703
action = nsIMIMEInfo::saveToDisk;
1704
}
1705
}
1706
1707
#endif
1708
if (action == nsIMIMEInfo::useHelperApp ||
1709
action == nsIMIMEInfo::useSystemDefault) {
1710
rv = LaunchWithApplication(nullptr, false);
1711
} else {
1712
rv = SaveToDisk(nullptr, false);
1713
}
1714
}
1715
1716
return NS_OK;
1717
}
1718
1719
// Convert error info into proper message text and send OnStatusChange
1720
// notification to the dialog progress listener or nsITransfer implementation.
1721
void nsExternalAppHandler::SendStatusChange(ErrorType type, nsresult rv,
1722
nsIRequest* aRequest,
1723
const nsString& path) {
1724
const char* msgId = nullptr;
1725
switch (rv) {
1726
case NS_ERROR_OUT_OF_MEMORY:
1727
// No memory
1728
msgId = "noMemory";
1729
break;
1730
1731
case NS_ERROR_FILE_DISK_FULL:
1732
case NS_ERROR_FILE_NO_DEVICE_SPACE:
1733
// Out of space on target volume.
1734
msgId = "diskFull";
1735
break;
1736
1737
case NS_ERROR_FILE_READ_ONLY:
1738
// Attempt to write to read/only file.
1739
msgId = "readOnly";
1740
break;
1741
1742
case NS_ERROR_FILE_ACCESS_DENIED:
1743
if (type == kWriteError) {
1744
// Attempt to write without sufficient permissions.
1745
#if defined(ANDROID)
1746
// On Android this means the SD card is present but
1747
// unavailable (read-only).
1748
msgId = "SDAccessErrorCardReadOnly";
1749
#else
1750
msgId = "accessError";
1751
#endif
1752
} else {
1753
msgId = "launchError";
1754
}
1755
break;
1756
1757
case NS_ERROR_FILE_NOT_FOUND:
1758
case NS_ERROR_FILE_TARGET_DOES_NOT_EXIST:
1759
case NS_ERROR_FILE_UNRECOGNIZED_PATH:
1760
// Helper app not found, let's verify this happened on launch
1761
if (type == kLaunchError) {
1762
msgId = "helperAppNotFound";
1763
break;
1764
}
1765
#if defined(ANDROID)
1766
else if (type == kWriteError) {
1767
// On Android this means the SD card is missing (not in
1768
// SD slot).
1769
msgId = "SDAccessErrorCardMissing";
1770
break;
1771
}
1772
#endif
1773
MOZ_FALLTHROUGH;
1774
1775
default:
1776
// Generic read/write/launch error message.
1777
switch (type) {
1778
case kReadError:
1779
msgId = "readError";
1780
break;
1781
case kWriteError:
1782
msgId = "writeError";
1783
break;
1784
case kLaunchError:
1785
msgId = "launchError";
1786
break;
1787
}
1788
break;
1789
}
1790
1791
MOZ_LOG(
1792
nsExternalHelperAppService::mLog, LogLevel::Error,
1793
("Error: %s, type=%i, listener=0x%p, transfer=0x%p, rv=0x%08" PRIX32 "\n",
1794
msgId, type, mDialogProgressListener.get(), mTransfer.get(),
1795
static_cast<uint32_t>(rv)));
1796
1797
MOZ_LOG(nsExternalHelperAppService::mLog, LogLevel::Error,
1798
(" path='%s'\n", NS_ConvertUTF16toUTF8(path).get()));
1799
1800
// Get properties file bundle and extract status string.
1801
nsCOMPtr<nsIStringBundleService> stringService =
1802
mozilla::services::GetStringBundleService();
1803
if (stringService) {
1804
nsCOMPtr<nsIStringBundle> bundle;
1805
if (NS_SUCCEEDED(stringService->CreateBundle(
1807
getter_AddRefs(bundle)))) {
1808
nsAutoString msgText;
1809
AutoTArray<nsString, 1> strings = {path};
1810
if (NS_SUCCEEDED(bundle->FormatStringFromName(msgId, strings, msgText))) {
1811
if (mDialogProgressListener) {
1812
// We have a listener, let it handle the error.
1813
mDialogProgressListener->OnStatusChange(
1814
nullptr, (type == kReadError) ? aRequest : nullptr, rv,
1815
msgText.get());
1816
} else if (mTransfer) {
1817
mTransfer->OnStatusChange(nullptr,
1818
(type == kReadError) ? aRequest : nullptr,
1819
rv, msgText.get());
1820
} else if (XRE_IsParentProcess()) {
1821
// We don't have a listener. Simply show the alert ourselves.
1822
nsresult qiRv;
1823
nsCOMPtr<nsIPrompt> prompter(
1824
do_GetInterface(GetDialogParent(), &qiRv));
1825
nsAutoString title;
1826
bundle->FormatStringFromName("title", strings, title);
1827
1828
MOZ_LOG(
1829
nsExternalHelperAppService::mLog, LogLevel::Debug,
1830
("mContentContext=0x%p, prompter=0x%p, qi rv=0x%08" PRIX32
1831
", title='%s', msg='%s'",
1832
mContentContext.get(), prompter.get(),
1833
static_cast<uint32_t>(qiRv), NS_ConvertUTF16toUTF8(title).get(),
1834
NS_ConvertUTF16toUTF8(msgText).get()));
1835
1836
// If we didn't have a prompter we will try and get a window
1837
// instead, get it's docshell and use it to alert the user.
1838
if (!prompter) {
1839
nsCOMPtr<nsPIDOMWindowOuter> window(
1840
do_GetInterface(GetDialogParent()));
1841
if (!window || !window->GetDocShell()) {
1842
return;
1843
}
1844
1845
prompter = do_GetInterface(window->GetDocShell(), &qiRv);
1846
1847
MOZ_LOG(nsExternalHelperAppService::mLog, LogLevel::Debug,
1848
("No prompter from mContentContext, using DocShell, "
1849
"window=0x%p, docShell=0x%p, "
1850
"prompter=0x%p, qi rv=0x%08" PRIX32,
1851
window.get(), window->GetDocShell(), prompter.get(),
1852
static_cast<uint32_t>(qiRv)));
1853
1854
// If we still don't have a prompter, there's nothing else we
1855
// can do so just return.
1856
if (!prompter) {
1857
MOZ_LOG(nsExternalHelperAppService::mLog, LogLevel::Error,
1858
("No prompter from DocShell, no way to alert user"));
1859
return;
1860
}
1861
}
1862
1863
// We should always have a prompter at this point.
1864
prompter->Alert(title.get(), msgText.get());
1865
}
1866
}
1867
}
1868
}
1869
}
1870
1871
NS_IMETHODIMP
1872
nsExternalAppHandler::OnDataAvailable(nsIRequest* request,
1873
nsIInputStream* inStr,
1874
uint64_t sourceOffset, uint32_t count) {
1875
nsresult rv = NS_OK;
1876
// first, check to see if we've been canceled....
1877
if (mCanceled || !mSaver) {
1878
// then go cancel our underlying channel too
1879
return request->Cancel(NS_BINDING_ABORTED);
1880
}
1881
1882
// read the data out of the stream and write it to the temp file.