Coverage Report

Created: 2018-09-25 14:53

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