1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim:expandtab:shiftwidth=2:tabstop=2:cin:
3 * ***** BEGIN LICENSE BLOCK *****
4 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 * The contents of this file are subject to the Mozilla Public License Version
7 * 1.1 (the "License"); you may not use this file except in compliance with
8 * the License. You may obtain a copy of the License at
9 * http://www.mozilla.org/MPL/
11 * Software distributed under the License is distributed on an "AS IS" basis,
12 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 * for the specific language governing rights and limitations under the
16 * The Original Code is the Mozilla browser.
18 * The Initial Developer of the Original Code is
19 * Netscape Communications, Inc.
20 * Portions created by the Initial Developer are Copyright (C) 1999
21 * the Initial Developer. All Rights Reserved.
24 * Scott MacGregor <mscott@netscape.com>
25 * Bill Law <law@netscape.com>
26 * Christian Biesinger <cbiesinger@web.de>
27 * Dan Mosedale <dmose@mozilla.org>
28 * Myk Melez <myk@mozilla.org>
30 * Alternatively, the contents of this file may be used under the terms of
31 * either of the GNU General Public License Version 2 or later (the "GPL"),
32 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
33 * in which case the provisions of the GPL or the LGPL are applicable instead
34 * of those above. If you wish to allow use of your version of this file only
35 * under the terms of either the GPL or the LGPL, and not to allow others to
36 * use your version of this file under the terms of the MPL, indicate your
37 * decision by deleting the provisions above and replace them with the notice
38 * and other provisions required by the GPL or the LGPL. If you do not delete
39 * the provisions above, a recipient may use your version of this file under
40 * the terms of any one of the MPL, the GPL or the LGPL.
42 * ***** END LICENSE BLOCK ***** */
44 #include "nsExternalHelperAppService.h"
45 #include "nsCExternalHandlerService.h"
49 #include "nsIFileURL.h"
50 #include "nsIChannel.h"
51 #include "nsIDirectoryService.h"
52 #include "nsAppDirectoryServiceDefs.h"
53 #include "nsICategoryManager.h"
54 #include "nsXPIDLString.h"
55 #include "nsUnicharUtils.h"
56 #include "nsIStringEnumerator.h"
58 #include "nsIStreamListener.h"
59 #include "nsIMIMEService.h"
60 #include "nsILoadGroup.h"
61 #include "nsIWebProgressListener.h"
62 #include "nsITransfer.h"
63 #include "nsReadableUtils.h"
64 #include "nsIRequest.h"
65 #include "nsDirectoryServiceDefs.h"
66 #include "nsIInterfaceRequestor.h"
67 #include "nsThreadUtils.h"
68 #include "nsAutoPtr.h"
69 #include "nsIMutableArray.h"
71 // used to access our datastore of user-configured helper applications
72 #include "nsIHandlerService.h"
73 #include "nsIMIMEInfo.h"
74 #include "nsIRefreshURI.h" // XXX needed to redirect according to Refresh: URI
75 #include "nsIDocumentLoader.h" // XXX needed to get orig. channel and assoc. refresh uri
76 #include "nsIHelperAppLauncherDialog.h"
77 #include "nsIContentDispatchChooser.h"
78 #include "nsNetUtil.h"
79 #include "nsIIOService.h"
81 #include "nsChannelProperties.h"
83 #include "nsMimeTypes.h"
84 // used for header disposition information.
85 #include "nsIHttpChannel.h"
86 #include "nsIEncodedChannel.h"
87 #include "nsIMultiPartChannel.h"
88 #include "nsIFileChannel.h"
89 #include "nsIObserverService.h" // so we can be a profile change observer
90 #include "nsIPropertyBag2.h" // for the 64-bit content length
93 #include "nsILocalFileMac.h"
94 #include "nsIInternetConfigService.h"
95 #include "nsIAppleFileDecoder.h"
97 #include "nsILocalFileOS2.h"
100 #include "nsIPluginHost.h" // XXX needed for ext->type mapping (bug 233289)
101 #include "nsEscape.h"
103 #include "nsIStringBundle.h" // XXX needed to localize error msgs
104 #include "nsIPrompt.h"
106 #include "nsITextToSubURI.h" // to unescape the filename
107 #include "nsIMIMEHeaderParam.h"
109 #include "nsIPrefService.h"
110 #include "nsIWindowWatcher.h"
112 #include "nsIDownloadHistory.h" // to mark downloads as visited
113 #include "nsDocShellCID.h"
115 #include "nsIDOMWindow.h"
116 #include "nsIDOMWindowInternal.h"
117 #include "nsIDocShell.h"
121 #include "nsLocalHandlerApp.h"
123 #include "nsIRandomGenerator.h"
124 #include "plbase64.h"
127 // Buffer file writes in 32kb chunks
128 #define BUFFERED_OUTPUT_SIZE (1024 * 32)
130 // Download Folder location constants
131 #define NS_PREF_DOWNLOAD_DIR "browser.download.dir"
132 #define NS_PREF_DOWNLOAD_FOLDERLIST "browser.download.folderList"
134 NS_FOLDER_VALUE_DESKTOP
= 0
135 , NS_FOLDER_VALUE_DOWNLOADS
= 1
136 , NS_FOLDER_VALUE_CUSTOM
= 2
140 PRLogModuleInfo
* nsExternalHelperAppService::mLog
= nsnull
;
143 // Using level 3 here because the OSHelperAppServices use a log level
144 // of PR_LOG_DEBUG (4), and we want less detailed output here
145 // Using 3 instead of PR_LOG_WARN because we don't output warnings
146 #define LOG(args) PR_LOG(mLog, 3, args)
147 #define LOG_ENABLED() PR_LOG_TEST(mLog, 3)
149 static const char NEVER_ASK_PREF_BRANCH
[] = "browser.helperApps.neverAsk.";
150 static const char NEVER_ASK_FOR_SAVE_TO_DISK_PREF
[] = "saveToDisk";
151 static const char NEVER_ASK_FOR_OPEN_FILE_PREF
[] = "openFile";
153 static NS_DEFINE_CID(kPluginManagerCID
, NS_PLUGINMANAGER_CID
);
156 * Contains a pointer to the helper app service, set in its constructor
158 nsExternalHelperAppService
* gExtProtSvc
;
160 // Helper functions for Content-Disposition headers
163 * Given a URI fragment, unescape it
164 * @param aFragment The string to unescape
165 * @param aURI The URI from which this fragment is taken. Only its character set
167 * @param aResult [out] Unescaped string.
169 static nsresult
UnescapeFragment(const nsACString
& aFragment
, nsIURI
* aURI
,
172 // First, we need a charset
173 nsCAutoString originCharset
;
174 nsresult rv
= aURI
->GetOriginCharset(originCharset
);
175 NS_ENSURE_SUCCESS(rv
, rv
);
177 // Now, we need the unescaper
178 nsCOMPtr
<nsITextToSubURI
> textToSubURI
= do_GetService(NS_ITEXTTOSUBURI_CONTRACTID
, &rv
);
179 NS_ENSURE_SUCCESS(rv
, rv
);
181 return textToSubURI
->UnEscapeURIForUI(originCharset
, aFragment
, aResult
);
185 * UTF-8 version of UnescapeFragment.
186 * @param aFragment The string to unescape
187 * @param aURI The URI from which this fragment is taken. Only its character set
189 * @param aResult [out] Unescaped string, UTF-8 encoded.
190 * @note It is safe to pass the same string for aFragment and aResult.
191 * @note When this function fails, aResult will not be modified.
193 static nsresult
UnescapeFragment(const nsACString
& aFragment
, nsIURI
* aURI
,
197 nsresult rv
= UnescapeFragment(aFragment
, aURI
, result
);
198 if (NS_SUCCEEDED(rv
))
199 CopyUTF16toUTF8(result
, aResult
);
203 /** Gets the content-disposition header from a channel, using nsIHttpChannel
204 * or nsIMultipartChannel if available
205 * @param aChannel The channel to extract the disposition header from
206 * @param aDisposition Reference to a string where the header is to be stored
208 static void ExtractDisposition(nsIChannel
* aChannel
, nsACString
& aDisposition
)
210 aDisposition
.Truncate();
211 // First see whether this is an http channel
212 nsCOMPtr
<nsIHttpChannel
> httpChannel(do_QueryInterface(aChannel
));
215 httpChannel
->GetResponseHeader(NS_LITERAL_CSTRING("content-disposition"), aDisposition
);
217 if (aDisposition
.IsEmpty())
219 nsCOMPtr
<nsIMultiPartChannel
> multipartChannel(do_QueryInterface(aChannel
));
220 if (multipartChannel
)
222 multipartChannel
->GetContentDisposition(aDisposition
);
228 /** Extracts the filename out of a content-disposition header
229 * @param aFilename [out] The filename. Can be empty on error.
230 * @param aDisposition Value of a Content-Disposition header
231 * @param aURI Optional. Will be used to get a fallback charset for the
232 * filename, if it is QI'able to nsIURL
233 * @param aMIMEHeaderParam Optional. Pointer to a nsIMIMEHeaderParam class, so
234 * that it doesn't need to be fetched by this function.
236 static void GetFilenameFromDisposition(nsAString
& aFilename
,
237 const nsACString
& aDisposition
,
238 nsIURI
* aURI
= nsnull
,
239 nsIMIMEHeaderParam
* aMIMEHeaderParam
= nsnull
)
241 aFilename
.Truncate();
242 nsCOMPtr
<nsIMIMEHeaderParam
> mimehdrpar(aMIMEHeaderParam
);
244 mimehdrpar
= do_GetService(NS_MIMEHEADERPARAM_CONTRACTID
);
249 nsCOMPtr
<nsIURL
> url
= do_QueryInterface(aURI
);
251 nsCAutoString fallbackCharset
;
253 url
->GetOriginCharset(fallbackCharset
);
254 // Get the value of 'filename' parameter
255 nsresult rv
= mimehdrpar
->GetParameter(aDisposition
, "filename", fallbackCharset
,
256 PR_TRUE
, nsnull
, aFilename
);
257 if (NS_FAILED(rv
) || aFilename
.IsEmpty())
258 // Try 'name' parameter, instead.
259 rv
= mimehdrpar
->GetParameter(aDisposition
, "name", fallbackCharset
, PR_TRUE
,
264 * Given a channel, returns the filename and extension the channel has.
265 * This uses the URL and other sources (nsIMultiPartChannel).
266 * Also gives back whether the channel requested external handling (i.e.
267 * whether Content-Disposition: attachment was sent)
268 * @param aChannel The channel to extract the filename/extension from
269 * @param aFileName [out] Reference to the string where the filename should be
270 * stored. Empty if it could not be retrieved.
271 * WARNING - this filename may contain characters which the OS does not
272 * allow as part of filenames!
273 * @param aExtension [out] Reference to the string where the extension should
274 * be stored. Empty if it could not be retrieved. Stored in UTF-8.
275 * @param aAllowURLExtension (optional) Get the extension from the URL if no
276 * Content-Disposition header is present. Default is true.
277 * @retval true The server sent Content-Disposition:attachment or equivalent
278 * @retval false Content-Disposition: inline or no content-disposition header
281 static PRBool
GetFilenameAndExtensionFromChannel(nsIChannel
* aChannel
,
283 nsCString
& aExtension
,
284 PRBool aAllowURLExtension
= PR_TRUE
)
286 aExtension
.Truncate();
288 * If the channel is an http or part of a multipart channel and we
289 * have a content disposition header set, then use the file name
290 * suggested there as the preferred file name to SUGGEST to the
291 * user. we shouldn't actually use that without their
292 * permission... otherwise just use our temp file
295 ExtractDisposition(aChannel
, disp
);
296 PRBool handleExternally
= PR_FALSE
;
297 nsCOMPtr
<nsIURI
> uri
;
299 aChannel
->GetURI(getter_AddRefs(uri
));
300 // content-disposition: has format:
301 // disposition-type < ; name=value >* < ; filename=value > < ; name=value >*
304 nsCOMPtr
<nsIMIMEHeaderParam
> mimehdrpar
= do_GetService(NS_MIMEHEADERPARAM_CONTRACTID
, &rv
);
308 nsCAutoString fallbackCharset
;
309 uri
->GetOriginCharset(fallbackCharset
);
310 // Get the disposition type
311 nsAutoString dispToken
;
312 rv
= mimehdrpar
->GetParameter(disp
, "", fallbackCharset
, PR_TRUE
,
314 // RFC 2183, section 2.8 says that an unknown disposition
315 // value should be treated as "attachment"
316 // XXXbz this code is duplicated in nsDocumentOpenInfo::DispatchContent.
317 // Factor it out! Maybe store it in the nsDocumentOpenInfo?
319 (!dispToken
.IsEmpty() &&
320 !dispToken
.LowerCaseEqualsLiteral("inline") &&
321 // Broken sites just send
322 // Content-Disposition: filename="file"
323 // without a disposition token... screen those out.
324 !dispToken
.EqualsIgnoreCase("filename", 8) &&
325 // Also in use is Content-Disposition: name="file"
326 !dispToken
.EqualsIgnoreCase("name", 4)))
328 // We have a content-disposition of "attachment" or unknown
329 handleExternally
= PR_TRUE
;
332 // We may not have a disposition type listed; some servers suck.
333 // But they could have listed a filename anyway.
334 GetFilenameFromDisposition(aFileName
, disp
, uri
, mimehdrpar
);
336 } // we had a disp header
338 // If the disposition header didn't work, try the filename from nsIURL
339 nsCOMPtr
<nsIURL
> url(do_QueryInterface(uri
));
340 if (url
&& aFileName
.IsEmpty())
342 if (aAllowURLExtension
) {
343 url
->GetFileExtension(aExtension
);
344 UnescapeFragment(aExtension
, url
, aExtension
);
346 // Windows ignores terminating dots. So we have to as well, so
347 // that our security checks do "the right thing"
348 // In case the aExtension consisted only of the dot, the code below will
349 // extract an aExtension from the filename
350 aExtension
.Trim(".", PR_FALSE
);
353 // try to extract the file name from the url and use that as a first pass as the
354 // leaf name of our temp file...
355 nsCAutoString leafName
;
356 url
->GetFileName(leafName
);
357 if (!leafName
.IsEmpty())
359 rv
= UnescapeFragment(leafName
, url
, aFileName
);
362 CopyUTF8toUTF16(leafName
, aFileName
); // use escaped name
367 // Extract Extension, if we have a filename; otherwise,
368 // truncate the string
369 if (aExtension
.IsEmpty()) {
370 if (!aFileName
.IsEmpty())
372 // Windows ignores terminating dots. So we have to as well, so
373 // that our security checks do "the right thing"
374 aFileName
.Trim(".", PR_FALSE
);
376 // XXX RFindCharInReadable!!
377 nsAutoString
fileNameStr(aFileName
);
378 PRInt32 idx
= fileNameStr
.RFindChar(PRUnichar('.'));
379 if (idx
!= kNotFound
)
380 CopyUTF16toUTF8(StringTail(fileNameStr
, fileNameStr
.Length() - idx
- 1), aExtension
);
385 return handleExternally
;
389 * Obtains the download directory to use. This tends to vary per platform, and
390 * needs to be consistent throughout our codepaths.
392 static nsresult
GetDownloadDirectory(nsIFile
**_directory
)
394 nsCOMPtr
<nsIFile
> dir
;
396 // On OS X, we first try to get the users download location, if it's set.
397 nsCOMPtr
<nsIPrefBranch
> prefs
=
398 do_GetService(NS_PREFSERVICE_CONTRACTID
);
399 NS_ENSURE_TRUE(prefs
, NS_ERROR_UNEXPECTED
);
401 PRInt32 folderValue
= -1;
402 (void) prefs
->GetIntPref(NS_PREF_DOWNLOAD_FOLDERLIST
, &folderValue
);
403 switch (folderValue
) {
404 case NS_FOLDER_VALUE_DESKTOP
:
405 (void) NS_GetSpecialDirectory(NS_OS_DESKTOP_DIR
, getter_AddRefs(dir
));
407 case NS_FOLDER_VALUE_CUSTOM
:
409 (void) prefs
->GetComplexValue(NS_PREF_DOWNLOAD_DIR
,
411 getter_AddRefs(dir
));
414 // We have the directory, and now we need to make sure it exists
415 PRBool dirExists
= PR_FALSE
;
416 (void) dir
->Exists(&dirExists
);
417 if (dirExists
) break;
419 nsresult rv
= dir
->Create(nsIFile::DIRECTORY_TYPE
, 0755);
426 case NS_FOLDER_VALUE_DOWNLOADS
:
427 // This is just the OS default location, so fall out
432 // If not, we default to the OS X default download location.
433 nsresult rv
= NS_GetSpecialDirectory(NS_OSX_DEFAULT_DOWNLOAD_DIR
,
434 getter_AddRefs(dir
));
435 NS_ENSURE_SUCCESS(rv
, rv
);
438 // On all other platforms, we default to the systems temporary directory.
439 nsresult rv
= NS_GetSpecialDirectory(NS_OS_TEMP_DIR
, getter_AddRefs(dir
));
440 NS_ENSURE_SUCCESS(rv
, rv
);
443 NS_ASSERTION(dir
, "Somehow we didn't get a download directory!");
444 dir
.forget(_directory
);
449 * Structure for storing extension->type mappings.
450 * @see defaultMimeEntries
452 struct nsDefaultMimeTypeEntry
{
453 const char* mMimeType
;
454 const char* mFileExtension
;
458 * Default extension->mimetype mappings. These are not overridable.
459 * If you add types here, make sure they are lowercase, or you'll regret it.
461 static nsDefaultMimeTypeEntry defaultMimeEntries
[] =
463 // The following are those extensions that we're asked about during startup,
464 // sorted by order used
465 { IMAGE_GIF
, "gif" },
467 { APPLICATION_RDF
, "rdf" },
469 { IMAGE_PNG
, "png" },
470 // -- end extensions used during startup
472 { IMAGE_JPG
, "jpeg" },
473 { IMAGE_JPG
, "jpg" },
474 { TEXT_HTML
, "html" },
475 { TEXT_HTML
, "htm" },
476 { APPLICATION_XPINSTALL
, "xpi" },
477 { "application/xhtml+xml", "xhtml" },
478 { "application/xhtml+xml", "xht" },
479 { TEXT_PLAIN
, "txt" }
483 * This is a small private struct used to help us initialize some
484 * default mime types.
486 struct nsExtraMimeTypeEntry
{
487 const char* mMimeType
;
488 const char* mFileExtensions
;
489 const char* mDescription
;
491 PRUint32 mMacCreator
;
495 #define MAC_TYPE(x) x
497 #define MAC_TYPE(x) 0
501 * This table lists all of the 'extra' content types that we can deduce from particular
502 * file extensions. These entries also ensure that we provide a good descriptive name
503 * when we encounter files with these content types and/or extensions. These can be
504 * overridden by user helper app prefs.
505 * If you add types here, make sure they are lowercase, or you'll regret it.
507 static nsExtraMimeTypeEntry extraMimeEntries
[] =
510 { APPLICATION_OCTET_STREAM
, "exe,com,bin,sav,bck,pcsi,dcx_axpexe,dcx_vaxexe,sfx_axpexe,sfx_vaxexe", "Binary File", 0, 0 },
511 #elif defined(XP_MACOSX) // don't define .bin on the mac...use internet config to look that up...
512 { APPLICATION_OCTET_STREAM
, "exe,com", "Binary File", 0, 0 },
514 { APPLICATION_OCTET_STREAM
, "exe,com,bin", "Binary File", 0, 0 },
516 { APPLICATION_GZIP2
, "gz", "gzip", 0, 0 },
517 { "application/x-arj", "arj", "ARJ file", 0,0 },
518 { APPLICATION_XPINSTALL
, "xpi", "XPInstall Install", MAC_TYPE('xpi*'), MAC_TYPE('MOSS') },
519 { APPLICATION_POSTSCRIPT
, "ps,eps,ai", "Postscript File", 0, 0 },
520 { APPLICATION_JAVASCRIPT
, "js", "Javascript Source File", MAC_TYPE('TEXT'), MAC_TYPE('ttxt') },
521 { IMAGE_ART
, "art", "ART Image", 0, 0 },
522 { IMAGE_BMP
, "bmp", "BMP Image", 0, 0 },
523 { IMAGE_GIF
, "gif", "GIF Image", 0,0 },
524 { IMAGE_ICO
, "ico,cur", "ICO Image", 0, 0 },
525 { IMAGE_JPG
, "jpeg,jpg,jfif,pjpeg,pjp", "JPEG Image", 0, 0 },
526 { IMAGE_PNG
, "png", "PNG Image", 0, 0 },
527 { IMAGE_TIFF
, "tiff,tif", "TIFF Image", 0, 0 },
528 { IMAGE_XBM
, "xbm", "XBM Image", 0, 0 },
529 { "image/svg+xml", "svg", "Scalable Vector Graphics", MAC_TYPE('svg '), MAC_TYPE('ttxt') },
530 { MESSAGE_RFC822
, "eml", "RFC-822 data", MAC_TYPE('TEXT'), MAC_TYPE('MOSS') },
531 { TEXT_PLAIN
, "txt,text", "Text File", MAC_TYPE('TEXT'), MAC_TYPE('ttxt') },
532 { TEXT_HTML
, "html,htm,shtml,ehtml", "HyperText Markup Language", MAC_TYPE('TEXT'), MAC_TYPE('MOSS') },
533 { "application/xhtml+xml", "xhtml,xht", "Extensible HyperText Markup Language", MAC_TYPE('TEXT'), MAC_TYPE('ttxt') },
534 { APPLICATION_RDF
, "rdf", "Resource Description Framework", MAC_TYPE('TEXT'), MAC_TYPE('ttxt') },
535 { TEXT_XUL
, "xul", "XML-Based User Interface Language", MAC_TYPE('TEXT'), MAC_TYPE('ttxt') },
536 { TEXT_XML
, "xml,xsl,xbl", "Extensible Markup Language", MAC_TYPE('TEXT'), MAC_TYPE('ttxt') },
537 { TEXT_CSS
, "css", "Style Sheet", MAC_TYPE('TEXT'), MAC_TYPE('ttxt') },
538 { "audio/ogg", "oga", "Ogg Audio", 0, 0 },
539 { "video/ogg", "ogv", "Ogg Video", 0, 0 },
540 { "audio/ogg", "ogg", "Ogg Audio", 0, 0 }
546 * File extensions for which decoding should be disabled.
547 * NOTE: These MUST be lower-case and ASCII.
549 static nsDefaultMimeTypeEntry nonDecodableExtensions
[] = {
550 { APPLICATION_GZIP
, "gz" },
551 { APPLICATION_GZIP
, "tgz" },
552 { APPLICATION_ZIP
, "zip" },
553 { APPLICATION_COMPRESS
, "z" },
554 { APPLICATION_GZIP
, "svgz" }
558 nsExternalHelperAppService
,
559 nsIExternalHelperAppService
,
560 nsPIExternalAppLauncher
,
561 nsIExternalProtocolService
,
564 nsISupportsWeakReference
)
566 nsExternalHelperAppService::nsExternalHelperAppService()
570 nsresult
nsExternalHelperAppService::Init()
572 // Add an observer for profile change
574 nsCOMPtr
<nsIObserverService
> obs
= do_GetService("@mozilla.org/observer-service;1", &rv
);
575 NS_ENSURE_SUCCESS(rv
, rv
);
579 mLog
= PR_NewLogModule("HelperAppService");
581 return NS_ERROR_OUT_OF_MEMORY
;
585 return obs
->AddObserver(this, "profile-before-change", PR_TRUE
);
588 nsExternalHelperAppService::~nsExternalHelperAppService()
590 gExtProtSvc
= nsnull
;
593 NS_IMETHODIMP
nsExternalHelperAppService::DoContent(const nsACString
& aMimeContentType
,
594 nsIRequest
*aRequest
,
595 nsIInterfaceRequestor
*aWindowContext
,
597 nsIStreamListener
** aStreamListener
)
599 nsAutoString fileName
;
600 nsCAutoString fileExtension
;
601 PRUint32 reason
= nsIHelperAppLauncherDialog::REASON_CANTHANDLE
;
604 // Get the file extension and name that we will need later
605 nsCOMPtr
<nsIChannel
> channel
= do_QueryInterface(aRequest
);
607 // Check if we have a POST request, in which case we don't want to use
608 // the url's extension
609 PRBool allowURLExt
= PR_TRUE
;
610 nsCOMPtr
<nsIHttpChannel
> httpChan
= do_QueryInterface(channel
);
612 nsCAutoString requestMethod
;
613 httpChan
->GetRequestMethod(requestMethod
);
614 allowURLExt
= !requestMethod
.Equals("POST");
617 nsCOMPtr
<nsIURI
> uri
;
618 channel
->GetURI(getter_AddRefs(uri
));
620 // Check if we had a query string - we don't want to check the URL
621 // extension if a query is present in the URI
622 // If we already know we don't want to check the URL extension, don't
623 // bother checking the query
624 if (uri
&& allowURLExt
) {
625 nsCOMPtr
<nsIURL
> url
= do_QueryInterface(uri
);
630 // We only care about the query for HTTP and HTTPS URLs
631 PRBool isHTTP
, isHTTPS
;
632 rv
= uri
->SchemeIs("http", &isHTTP
);
635 rv
= uri
->SchemeIs("https", &isHTTPS
);
639 if (isHTTP
|| isHTTPS
)
640 url
->GetQuery(query
);
642 // Only get the extension if the query is empty; if it isn't, then the
643 // extension likely belongs to a cgi script and isn't helpful
644 allowURLExt
= query
.IsEmpty();
647 // Extract name & extension
648 PRBool isAttachment
= GetFilenameAndExtensionFromChannel(channel
, fileName
,
651 LOG(("Found extension '%s' (filename is '%s', handling attachment: %i)",
652 fileExtension
.get(), NS_ConvertUTF16toUTF8(fileName
).get(),
655 reason
= nsIHelperAppLauncherDialog::REASON_SERVERREQUEST
;
658 LOG(("HelperAppService::DoContent: mime '%s', extension '%s'\n",
659 PromiseFlatCString(aMimeContentType
).get(), fileExtension
.get()));
661 // we get the mime service here even though we're the default implementation of it,
662 // so it's possible to override only the mime service and not need to reimplement the
663 // whole external helper app service itself
664 nsCOMPtr
<nsIMIMEService
> mimeSvc(do_GetService(NS_MIMESERVICE_CONTRACTID
));
665 NS_ENSURE_TRUE(mimeSvc
, NS_ERROR_FAILURE
);
667 // Try to find a mime object by looking at the mime type/extension
668 nsCOMPtr
<nsIMIMEInfo
> mimeInfo
;
669 if (aMimeContentType
.Equals(APPLICATION_GUESS_FROM_EXT
, nsCaseInsensitiveCStringComparator())) {
670 nsCAutoString mimeType
;
671 if (!fileExtension
.IsEmpty()) {
672 mimeSvc
->GetFromTypeAndExtension(EmptyCString(), fileExtension
, getter_AddRefs(mimeInfo
));
674 mimeInfo
->GetMIMEType(mimeType
);
676 LOG(("OS-Provided mime type '%s' for extension '%s'\n",
677 mimeType
.get(), fileExtension
.get()));
681 if (fileExtension
.IsEmpty() || mimeType
.IsEmpty()) {
682 // Extension lookup gave us no useful match
683 mimeSvc
->GetFromTypeAndExtension(NS_LITERAL_CSTRING(APPLICATION_OCTET_STREAM
), fileExtension
,
684 getter_AddRefs(mimeInfo
));
685 mimeType
.AssignLiteral(APPLICATION_OCTET_STREAM
);
688 channel
->SetContentType(mimeType
);
689 // Don't overwrite SERVERREQUEST
690 if (reason
== nsIHelperAppLauncherDialog::REASON_CANTHANDLE
)
691 reason
= nsIHelperAppLauncherDialog::REASON_TYPESNIFFED
;
694 mimeSvc
->GetFromTypeAndExtension(aMimeContentType
, fileExtension
,
695 getter_AddRefs(mimeInfo
));
697 LOG(("Type/Ext lookup found 0x%p\n", mimeInfo
.get()));
699 // No mimeinfo -> we can't continue. probably OOM.
701 return NS_ERROR_OUT_OF_MEMORY
;
703 *aStreamListener
= nsnull
;
704 // We want the mimeInfo's primary extension to pass it to
705 // nsExternalAppHandler
707 mimeInfo
->GetPrimaryExtension(buf
);
709 nsExternalAppHandler
* handler
= new nsExternalAppHandler(mimeInfo
,
716 return NS_ERROR_OUT_OF_MEMORY
;
717 NS_ADDREF(*aStreamListener
= handler
);
722 NS_IMETHODIMP
nsExternalHelperAppService::ApplyDecodingForExtension(const nsACString
& aExtension
,
723 const nsACString
& aEncodingType
,
724 PRBool
*aApplyDecoding
)
726 *aApplyDecoding
= PR_TRUE
;
728 for(i
= 0; i
< NS_ARRAY_LENGTH(nonDecodableExtensions
); ++i
) {
729 if (aExtension
.LowerCaseEqualsASCII(nonDecodableExtensions
[i
].mFileExtension
) &&
730 aEncodingType
.LowerCaseEqualsASCII(nonDecodableExtensions
[i
].mMimeType
)) {
731 *aApplyDecoding
= PR_FALSE
;
738 nsresult
nsExternalHelperAppService::GetFileTokenForPath(const PRUnichar
* aPlatformAppPath
,
741 nsDependentString
platformAppPath(aPlatformAppPath
);
742 // First, check if we have an absolute path
743 nsILocalFile
* localFile
= nsnull
;
744 nsresult rv
= NS_NewLocalFile(platformAppPath
, PR_TRUE
, &localFile
);
745 if (NS_SUCCEEDED(rv
)) {
748 if (NS_FAILED((*aFile
)->Exists(&exists
)) || !exists
) {
750 return NS_ERROR_FILE_NOT_FOUND
;
756 // Second, check if file exists in mozilla program directory
757 rv
= NS_GetSpecialDirectory(NS_XPCOM_CURRENT_PROCESS_DIR
, aFile
);
758 if (NS_SUCCEEDED(rv
)) {
759 rv
= (*aFile
)->Append(platformAppPath
);
760 if (NS_SUCCEEDED(rv
)) {
761 PRBool exists
= PR_FALSE
;
762 rv
= (*aFile
)->Exists(&exists
);
763 if (NS_SUCCEEDED(rv
) && exists
)
770 return NS_ERROR_NOT_AVAILABLE
;
773 //////////////////////////////////////////////////////////////////////////////////////////////////////
774 // begin external protocol service default implementation...
775 //////////////////////////////////////////////////////////////////////////////////////////////////////
776 NS_IMETHODIMP
nsExternalHelperAppService::ExternalProtocolHandlerExists(const char * aProtocolScheme
,
777 PRBool
* aHandlerExists
)
779 nsCOMPtr
<nsIHandlerInfo
> handlerInfo
;
780 nsresult rv
= GetProtocolHandlerInfo(nsDependentCString(aProtocolScheme
),
781 getter_AddRefs(handlerInfo
));
782 NS_ENSURE_SUCCESS(rv
, rv
);
784 // See if we have any known possible handler apps for this
785 nsCOMPtr
<nsIMutableArray
> possibleHandlers
;
786 handlerInfo
->GetPossibleApplicationHandlers(getter_AddRefs(possibleHandlers
));
789 possibleHandlers
->GetLength(&length
);
791 *aHandlerExists
= PR_TRUE
;
795 // if not, fall back on an os-based handler
796 return OSProtocolHandlerExists(aProtocolScheme
, aHandlerExists
);
799 NS_IMETHODIMP
nsExternalHelperAppService::IsExposedProtocol(const char * aProtocolScheme
, PRBool
* aResult
)
801 // by default, no protocol is exposed. i.e., by default all link clicks must
802 // go through the external protocol service. most applications override this
806 nsCOMPtr
<nsIPrefBranch
> prefs
= do_GetService(NS_PREFSERVICE_CONTRACTID
);
812 // check the per protocol setting first. it always takes precidence.
813 // if not set, then use the global setting.
816 name
= NS_LITERAL_CSTRING("network.protocol-handler.expose.")
817 + nsDependentCString(aProtocolScheme
);
818 rv
= prefs
->GetBoolPref(name
.get(), &val
);
819 if (NS_SUCCEEDED(rv
))
825 rv
= prefs
->GetBoolPref("network.protocol-handler.expose-all", &val
);
826 if (NS_SUCCEEDED(rv
) && val
)
833 NS_IMETHODIMP
nsExternalHelperAppService::LoadUrl(nsIURI
* aURL
)
835 return LoadURI(aURL
, nsnull
);
838 static const char kExternalProtocolPrefPrefix
[] = "network.protocol-handler.external.";
839 static const char kExternalProtocolDefaultPref
[] = "network.protocol-handler.external-default";
842 nsExternalHelperAppService::LoadURI(nsIURI
*aURI
,
843 nsIInterfaceRequestor
*aWindowContext
)
845 NS_ENSURE_ARG_POINTER(aURI
);
850 if (spec
.Find("%00") != -1)
851 return NS_ERROR_MALFORMED_URI
;
853 spec
.ReplaceSubstring("\"", "%22");
854 spec
.ReplaceSubstring("`", "%60");
856 nsCOMPtr
<nsIIOService
> ios(do_GetIOService());
857 nsCOMPtr
<nsIURI
> uri
;
858 nsresult rv
= ios
->NewURI(spec
, nsnull
, nsnull
, getter_AddRefs(uri
));
859 NS_ENSURE_SUCCESS(rv
, rv
);
861 nsCAutoString scheme
;
862 uri
->GetScheme(scheme
);
863 if (scheme
.IsEmpty())
864 return NS_OK
; // must have a scheme
866 nsCOMPtr
<nsIPrefBranch
> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID
));
868 return NS_OK
; // deny if we can't check prefs
870 // Deny load if the prefs say to do so
871 nsCAutoString
externalPref(kExternalProtocolPrefPrefix
);
872 externalPref
+= scheme
;
873 PRBool allowLoad
= PR_FALSE
;
874 rv
= prefs
->GetBoolPref(externalPref
.get(), &allowLoad
);
877 // no scheme-specific value, check the default
878 rv
= prefs
->GetBoolPref(kExternalProtocolDefaultPref
, &allowLoad
);
880 if (NS_FAILED(rv
) || !allowLoad
)
881 return NS_OK
; // explicitly denied or missing default pref
884 nsCOMPtr
<nsIHandlerInfo
> handler
;
885 rv
= GetProtocolHandlerInfo(scheme
, getter_AddRefs(handler
));
886 NS_ENSURE_SUCCESS(rv
, rv
);
888 nsHandlerInfoAction preferredAction
;
889 handler
->GetPreferredAction(&preferredAction
);
890 PRBool alwaysAsk
= PR_TRUE
;
891 handler
->GetAlwaysAskBeforeHandling(&alwaysAsk
);
893 // if we are not supposed to ask, and the preferred action is to use
894 // a helper app or the system default, we just launch the URI.
895 if (!alwaysAsk
&& (preferredAction
== nsIHandlerInfo::useHelperApp
||
896 preferredAction
== nsIHandlerInfo::useSystemDefault
))
897 return handler
->LaunchWithURI(uri
, aWindowContext
);
899 nsCOMPtr
<nsIContentDispatchChooser
> chooser
=
900 do_CreateInstance("@mozilla.org/content-dispatch-chooser;1", &rv
);
901 NS_ENSURE_SUCCESS(rv
, rv
);
903 return chooser
->Ask(handler
, aWindowContext
, uri
,
904 nsIContentDispatchChooser::REASON_CANNOT_HANDLE
);
907 NS_IMETHODIMP
nsExternalHelperAppService::GetApplicationDescription(const nsACString
& aScheme
, nsAString
& _retval
)
909 // this method should only be implemented by each OS specific implementation of this service.
910 return NS_ERROR_NOT_IMPLEMENTED
;
914 //////////////////////////////////////////////////////////////////////////////////////////////////////
915 // Methods related to deleting temporary files on exit
916 //////////////////////////////////////////////////////////////////////////////////////////////////////
918 NS_IMETHODIMP
nsExternalHelperAppService::DeleteTemporaryFileOnExit(nsIFile
* aTemporaryFile
)
921 PRBool isFile
= PR_FALSE
;
922 nsCOMPtr
<nsILocalFile
> localFile (do_QueryInterface(aTemporaryFile
, &rv
));
923 NS_ENSURE_SUCCESS(rv
, rv
);
925 // as a safety measure, make sure the nsIFile is really a file and not a directory object.
926 localFile
->IsFile(&isFile
);
927 if (!isFile
) return NS_OK
;
929 mTemporaryFilesList
.AppendObject(localFile
);
934 void nsExternalHelperAppService::FixFilePermissions(nsILocalFile
* aFile
)
936 // This space intentionally left blank
939 nsresult
nsExternalHelperAppService::ExpungeTemporaryFiles()
941 PRInt32 numEntries
= mTemporaryFilesList
.Count();
942 nsILocalFile
* localFile
;
943 for (PRInt32 index
= 0; index
< numEntries
; index
++)
945 localFile
= mTemporaryFilesList
[index
];
947 // First make the file writable, since the temp file is probably readonly.
948 localFile
->SetPermissions(0600);
949 localFile
->Remove(PR_FALSE
);
953 mTemporaryFilesList
.Clear();
958 static const char kExternalWarningPrefPrefix
[] =
959 "network.protocol-handler.warn-external.";
960 static const char kExternalWarningDefaultPref
[] =
961 "network.protocol-handler.warn-external-default";
964 nsExternalHelperAppService::GetProtocolHandlerInfo(const nsACString
&aScheme
,
965 nsIHandlerInfo
**aHandlerInfo
)
967 // XXX enterprise customers should be able to turn this support off with a
968 // single master pref (maybe use one of the "exposed" prefs here?)
971 nsresult rv
= GetProtocolHandlerInfoFromOS(aScheme
, &exists
, aHandlerInfo
);
973 // Either it knows nothing, or we ran out of memory
974 return NS_ERROR_FAILURE
;
977 nsCOMPtr
<nsIHandlerService
> handlerSvc
= do_GetService(NS_HANDLERSERVICE_CONTRACTID
);
979 PRBool hasHandler
= PR_FALSE
;
980 (void) handlerSvc
->Exists(*aHandlerInfo
, &hasHandler
);
982 rv
= handlerSvc
->FillHandlerInfo(*aHandlerInfo
, EmptyCString());
983 if (NS_SUCCEEDED(rv
))
988 return SetProtocolHandlerDefaults(*aHandlerInfo
, exists
);
992 nsExternalHelperAppService::GetProtocolHandlerInfoFromOS(const nsACString
&aScheme
,
994 nsIHandlerInfo
**aHandlerInfo
)
996 // intended to be implemented by the subclass
997 return NS_ERROR_NOT_IMPLEMENTED
;
1001 nsExternalHelperAppService::SetProtocolHandlerDefaults(nsIHandlerInfo
*aHandlerInfo
,
1002 PRBool aOSHandlerExists
)
1004 // this type isn't in our database, so we've only got an OS default handler,
1007 if (aOSHandlerExists
) {
1008 // we've got a default, so use it
1009 aHandlerInfo
->SetPreferredAction(nsIHandlerInfo::useSystemDefault
);
1011 // whether or not to ask the user depends on the warning preference
1012 nsCOMPtr
<nsIPrefBranch
> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID
));
1014 return NS_OK
; // deny if we can't check prefs
1016 nsCAutoString scheme
;
1017 aHandlerInfo
->GetType(scheme
);
1019 nsCAutoString
warningPref(kExternalWarningPrefPrefix
);
1020 warningPref
+= scheme
;
1021 PRBool warn
= PR_TRUE
;
1022 nsresult rv
= prefs
->GetBoolPref(warningPref
.get(), &warn
);
1023 if (NS_FAILED(rv
)) {
1024 // no scheme-specific value, check the default
1025 prefs
->GetBoolPref(kExternalWarningDefaultPref
, &warn
);
1027 aHandlerInfo
->SetAlwaysAskBeforeHandling(warn
);
1029 // If no OS default existed, we set the preferred action to alwaysAsk.
1030 // This really means not initialized (i.e. there's no available handler)
1031 // to all the code...
1032 aHandlerInfo
->SetPreferredAction(nsIHandlerInfo::alwaysAsk
);
1038 // XPCOM profile change observer
1040 nsExternalHelperAppService::Observe(nsISupports
*aSubject
, const char *aTopic
, const PRUnichar
*someData
)
1042 if (!strcmp(aTopic
, "profile-before-change")) {
1043 ExpungeTemporaryFiles();
1048 //////////////////////////////////////////////////////////////////////////////////////////////////////
1049 // begin external app handler implementation
1050 //////////////////////////////////////////////////////////////////////////////////////////////////////
1052 NS_IMPL_THREADSAFE_ADDREF(nsExternalAppHandler
)
1053 NS_IMPL_THREADSAFE_RELEASE(nsExternalAppHandler
)
1055 NS_INTERFACE_MAP_BEGIN(nsExternalAppHandler
)
1056 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, nsIStreamListener
)
1057 NS_INTERFACE_MAP_ENTRY(nsIStreamListener
)
1058 NS_INTERFACE_MAP_ENTRY(nsIRequestObserver
)
1059 NS_INTERFACE_MAP_ENTRY(nsIHelperAppLauncher
)
1060 NS_INTERFACE_MAP_ENTRY(nsICancelable
)
1061 NS_INTERFACE_MAP_ENTRY(nsITimerCallback
)
1062 NS_INTERFACE_MAP_END_THREADSAFE
1064 nsExternalAppHandler::nsExternalAppHandler(nsIMIMEInfo
* aMIMEInfo
,
1065 const nsCSubstring
& aTempFileExtension
,
1066 nsIInterfaceRequestor
* aWindowContext
,
1067 const nsAString
& aSuggestedFilename
,
1068 PRUint32 aReason
, PRBool aForceSave
)
1069 : mMimeInfo(aMIMEInfo
)
1070 , mWindowContext(aWindowContext
)
1071 , mWindowToClose(nsnull
)
1072 , mSuggestedFileName(aSuggestedFilename
)
1073 , mForceSave(aForceSave
)
1074 , mCanceled(PR_FALSE
)
1075 , mShouldCloseWindow(PR_FALSE
)
1076 , mReceivedDispositionInfo(PR_FALSE
)
1077 , mStopRequestIssued(PR_FALSE
)
1078 , mProgressListenerInitialized(PR_FALSE
)
1080 , mContentLength(-1)
1085 // make sure the extention includes the '.'
1086 if (!aTempFileExtension
.IsEmpty() && aTempFileExtension
.First() != '.')
1087 mTempFileExtension
= PRUnichar('.');
1088 AppendUTF8toUTF16(aTempFileExtension
, mTempFileExtension
);
1090 // replace platform specific path separator and illegal characters to avoid any confusion
1091 mSuggestedFileName
.ReplaceChar(FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS
, '_');
1092 mTempFileExtension
.ReplaceChar(FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS
, '_');
1094 // Make sure extension is correct.
1095 EnsureSuggestedFileName();
1097 gExtProtSvc
->AddRef();
1100 nsExternalAppHandler::~nsExternalAppHandler()
1102 // Not using NS_RELEASE, since we don't want to set gExtProtSvc to NULL
1103 gExtProtSvc
->Release();
1106 NS_IMETHODIMP
nsExternalAppHandler::SetWebProgressListener(nsIWebProgressListener2
* aWebProgressListener
)
1108 // this call back means we've successfully brought up the
1109 // progress window so set the appropriate flag, even though
1110 // aWebProgressListener might be null
1112 if (mReceivedDispositionInfo
)
1113 mProgressListenerInitialized
= PR_TRUE
;
1115 // Go ahead and register the progress listener....
1116 mWebProgressListener
= aWebProgressListener
;
1118 // while we were bringing up the progress dialog, we actually finished processing the
1119 // url. If that's the case then mStopRequestIssued will be true. We need to execute the
1120 // operation since we are actually done now.
1121 if (mStopRequestIssued
&& aWebProgressListener
)
1123 return ExecuteDesiredAction();
1129 NS_IMETHODIMP
nsExternalAppHandler::GetTargetFile(nsIFile
** aTarget
)
1131 if (mFinalFileDestination
)
1132 *aTarget
= mFinalFileDestination
;
1134 *aTarget
= mTempFile
;
1136 NS_IF_ADDREF(*aTarget
);
1140 NS_IMETHODIMP
nsExternalAppHandler::GetTargetFileIsExecutable(PRBool
*aExec
)
1142 // Use the real target if it's been set
1143 if (mFinalFileDestination
)
1144 return mFinalFileDestination
->IsExecutable(aExec
);
1146 // Otherwise, use the stored executable-ness of the temporary
1147 *aExec
= mTempFileIsExecutable
;
1151 NS_IMETHODIMP
nsExternalAppHandler::GetTimeDownloadStarted(PRTime
* aTime
)
1153 *aTime
= mTimeDownloadStarted
;
1157 NS_IMETHODIMP
nsExternalAppHandler::GetContentLength(PRInt64
*aContentLength
)
1159 *aContentLength
= mContentLength
;
1163 NS_IMETHODIMP
nsExternalAppHandler::CloseProgressWindow()
1165 // release extra state...
1166 mWebProgressListener
= nsnull
;
1170 void nsExternalAppHandler::RetargetLoadNotifications(nsIRequest
*request
)
1172 // we are going to run the downloading of the helper app in our own little docloader / load group context.
1173 // so go ahead and force the creation of a load group and doc loader for us to use...
1174 nsCOMPtr
<nsIChannel
> aChannel
= do_QueryInterface(request
);
1178 // we need to store off the original (pre redirect!) channel that initiated the load. We do
1179 // this so later on, we can pass any refresh urls associated with the original channel back to the
1180 // window context which started the whole process. More comments about that are listed below....
1181 // HACK ALERT: it's pretty bogus that we are getting the document channel from the doc loader.
1182 // ideally we should be able to just use mChannel (the channel we are extracting content from) or
1183 // the default load channel associated with the original load group. Unfortunately because
1184 // a redirect may have occurred, the doc loader is the only one with a ptr to the original channel
1185 // which is what we really want....
1187 // Note that we need to do this before removing aChannel from the loadgroup,
1188 // since that would mess with the original channel on the loader.
1189 nsCOMPtr
<nsIDocumentLoader
> origContextLoader
=
1190 do_GetInterface(mWindowContext
);
1191 if (origContextLoader
)
1192 origContextLoader
->GetDocumentChannel(getter_AddRefs(mOriginalChannel
));
1194 nsCOMPtr
<nsILoadGroup
> oldLoadGroup
;
1195 aChannel
->GetLoadGroup(getter_AddRefs(oldLoadGroup
));
1198 oldLoadGroup
->RemoveRequest(request
, nsnull
, NS_BINDING_RETARGETED
);
1200 aChannel
->SetLoadGroup(nsnull
);
1201 aChannel
->SetNotificationCallbacks(nsnull
);
1206 * Make mTempFileExtension contain an extension exactly when its previous value
1207 * is different from mSuggestedFileName's extension, so that it can be appended
1208 * to mSuggestedFileName and form a valid, useful leaf name.
1209 * This is required so that the (renamed) temporary file has the correct extension
1210 * after downloading to make sure the OS will launch the application corresponding
1211 * to the MIME type (which was used to calculate mTempFileExtension). This prevents
1212 * a cgi-script named foobar.exe that returns application/zip from being named
1213 * foobar.exe and executed as an executable file. It also blocks content that
1214 * a web site might provide with a content-disposition header indicating
1215 * filename="foobar.exe" from being downloaded to a file with extension .exe
1218 void nsExternalAppHandler::EnsureSuggestedFileName()
1220 // Make sure there is a mTempFileExtension (not "" or ".").
1221 // Remember that mTempFileExtension will always have the leading "."
1222 // (the check for empty is just to be safe).
1223 if (mTempFileExtension
.Length() > 1)
1225 // Get mSuggestedFileName's current extension.
1226 nsAutoString fileExt
;
1227 PRInt32 pos
= mSuggestedFileName
.RFindChar('.');
1228 if (pos
!= kNotFound
)
1229 mSuggestedFileName
.Right(fileExt
, mSuggestedFileName
.Length() - pos
);
1231 // Now, compare fileExt to mTempFileExtension.
1232 if (fileExt
.Equals(mTempFileExtension
, nsCaseInsensitiveStringComparator()))
1234 // Matches -> mTempFileExtension can be empty
1235 mTempFileExtension
.Truncate();
1240 nsresult
nsExternalAppHandler::SetUpTempFile(nsIChannel
* aChannel
)
1242 // First we need to try to get the destination directory for the temporary
1244 nsresult rv
= GetDownloadDirectory(getter_AddRefs(mTempFile
));
1245 NS_ENSURE_SUCCESS(rv
, rv
);
1247 // At this point, we do not have a filename for the temp file. For security
1248 // purposes, this cannot be predictable, so we must use a cryptographic
1249 // quality PRNG to generate one.
1250 // We will request raw random bytes, and transform that to a base64 string,
1251 // as all characters from the base64 set are acceptable for filenames. For
1252 // each three bytes of random data, we will get four bytes of ASCII. Request
1253 // a bit more, to be safe, and truncate to the length we want in the end.
1255 const PRUint32 wantedFileNameLength
= 8;
1256 const PRUint32 requiredBytesLength
=
1257 static_cast<PRUint32
>((wantedFileNameLength
+ 1) / 4 * 3);
1259 nsCOMPtr
<nsIRandomGenerator
> rg
=
1260 do_GetService("@mozilla.org/security/random-generator;1", &rv
);
1261 NS_ENSURE_SUCCESS(rv
, rv
);
1264 rv
= rg
->GenerateRandomBytes(requiredBytesLength
, &buffer
);
1265 NS_ENSURE_SUCCESS(rv
, rv
);
1267 char *b64
= PL_Base64Encode(reinterpret_cast<const char *>(buffer
),
1268 requiredBytesLength
, nsnull
);
1273 return NS_ERROR_OUT_OF_MEMORY
;
1275 NS_ASSERTION(strlen(b64
) >= wantedFileNameLength
,
1276 "not enough bytes produced for conversion!");
1278 nsCAutoString
tempLeafName(b64
, wantedFileNameLength
);
1282 // Base64 characters are alphanumeric (a-zA-Z0-9) and '+' and '/', so we need
1283 // to replace illegal characters -- notably '/'
1284 tempLeafName
.ReplaceChar(FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS
, '_');
1286 // now append our extension.
1288 mMimeInfo
->GetPrimaryExtension(ext
);
1289 if (!ext
.IsEmpty()) {
1290 ext
.ReplaceChar(FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS
, '_');
1291 if (ext
.First() != '.')
1292 tempLeafName
.Append('.');
1293 tempLeafName
.Append(ext
);
1297 // On windows, we need to temporarily create a dummy file with the correct
1298 // file extension to determine the executable-ness, so do this before adding
1299 // the extra .part extension.
1300 nsCOMPtr
<nsIFile
> dummyFile
;
1301 rv
= NS_GetSpecialDirectory(NS_OS_TEMP_DIR
, getter_AddRefs(dummyFile
));
1302 NS_ENSURE_SUCCESS(rv
, rv
);
1304 // Set the file name without .part
1305 rv
= dummyFile
->Append(NS_ConvertUTF8toUTF16(tempLeafName
));
1306 NS_ENSURE_SUCCESS(rv
, rv
);
1307 rv
= dummyFile
->CreateUnique(nsIFile::NORMAL_FILE_TYPE
, 0600);
1308 NS_ENSURE_SUCCESS(rv
, rv
);
1310 // Store executable-ness then delete
1311 dummyFile
->IsExecutable(&mTempFileIsExecutable
);
1312 dummyFile
->Remove(PR_FALSE
);
1315 // Add an additional .part to prevent the OS from running this file in the
1316 // default application.
1317 tempLeafName
.Append(NS_LITERAL_CSTRING(".part"));
1319 rv
= mTempFile
->Append(NS_ConvertUTF8toUTF16(tempLeafName
));
1320 // make this file unique!!!
1321 NS_ENSURE_SUCCESS(rv
, rv
);
1322 rv
= mTempFile
->CreateUnique(nsIFile::NORMAL_FILE_TYPE
, 0600);
1323 NS_ENSURE_SUCCESS(rv
, rv
);
1326 // On other platforms, the file permission bits are used, so we can just call
1328 mTempFile
->IsExecutable(&mTempFileIsExecutable
);
1332 // Now that the file exists set Mac type if the file has no extension
1333 // and we can determine a type.
1334 if (ext
.IsEmpty() && mMimeInfo
) {
1335 nsCOMPtr
<nsILocalFileMac
> macfile
= do_QueryInterface(mTempFile
);
1338 mMimeInfo
->GetMacType(&type
);
1339 macfile
->SetFileType(type
);
1344 nsCOMPtr
<nsIOutputStream
> outputStream
;
1345 rv
= NS_NewLocalFileOutputStream(getter_AddRefs(outputStream
), mTempFile
,
1346 PR_WRONLY
| PR_CREATE_FILE
, 0600);
1347 if (NS_FAILED(rv
)) {
1348 mTempFile
->Remove(PR_FALSE
);
1352 mOutStream
= NS_BufferOutputStream(outputStream
, BUFFERED_OUTPUT_SIZE
);
1355 nsCAutoString contentType
;
1356 mMimeInfo
->GetMIMEType(contentType
);
1357 if (contentType
.LowerCaseEqualsLiteral(APPLICATION_APPLEFILE
) ||
1358 contentType
.LowerCaseEqualsLiteral(MULTIPART_APPLEDOUBLE
))
1360 nsCOMPtr
<nsIAppleFileDecoder
> appleFileDecoder
= do_CreateInstance(NS_IAPPLEFILEDECODER_CONTRACTID
, &rv
);
1361 if (NS_SUCCEEDED(rv
))
1363 rv
= appleFileDecoder
->Initialize(mOutStream
, mTempFile
);
1364 if (NS_SUCCEEDED(rv
))
1365 mOutStream
= do_QueryInterface(appleFileDecoder
, &rv
);
1373 NS_IMETHODIMP
nsExternalAppHandler::OnStartRequest(nsIRequest
*request
, nsISupports
* aCtxt
)
1375 NS_PRECONDITION(request
, "OnStartRequest without request?");
1377 // Set mTimeDownloadStarted here as the download has already started and
1378 // we want to record the start time before showing the filepicker.
1379 mTimeDownloadStarted
= PR_Now();
1383 nsCOMPtr
<nsIChannel
> aChannel
= do_QueryInterface(request
);
1387 nsCOMPtr
<nsIFileChannel
> fileChan(do_QueryInterface(request
));
1388 mIsFileChannel
= fileChan
!= nsnull
;
1390 // Get content length
1391 nsCOMPtr
<nsIPropertyBag2
> props(do_QueryInterface(request
, &rv
));
1393 rv
= props
->GetPropertyAsInt64(NS_CHANNEL_PROP_CONTENT_LENGTH
,
1394 &mContentLength
.mValue
);
1396 // If that failed, ask the channel
1397 if (NS_FAILED(rv
) && aChannel
) {
1399 aChannel
->GetContentLength(&len
);
1400 mContentLength
= len
;
1403 // Determine whether a new window was opened specifically for this request
1405 PRBool tmp
= PR_FALSE
;
1406 props
->GetPropertyAsBool(NS_LITERAL_STRING("docshell.newWindowTarget"),
1408 mShouldCloseWindow
= tmp
;
1414 aChannel
->GetURI(getter_AddRefs(mSourceUrl
));
1417 rv
= SetUpTempFile(aChannel
);
1418 if (NS_FAILED(rv
)) {
1419 mCanceled
= PR_TRUE
;
1420 request
->Cancel(rv
);
1423 mTempFile
->GetPath(path
);
1424 SendStatusChange(kWriteError
, rv
, request
, path
);
1428 // Extract mime type for later use below.
1429 nsCAutoString MIMEType
;
1430 mMimeInfo
->GetMIMEType(MIMEType
);
1432 // retarget all load notifications to our docloader instead of the original window's docloader...
1433 RetargetLoadNotifications(request
);
1435 // Check to see if there is a refresh header on the original channel.
1436 if (mOriginalChannel
) {
1437 nsCOMPtr
<nsIHttpChannel
> httpChannel(do_QueryInterface(mOriginalChannel
));
1439 nsCAutoString refreshHeader
;
1440 httpChannel
->GetResponseHeader(NS_LITERAL_CSTRING("refresh"),
1442 if (!refreshHeader
.IsEmpty()) {
1443 mShouldCloseWindow
= PR_FALSE
;
1448 // Close the underlying DOMWindow if there is no refresh header
1449 // and it was opened specifically for the download
1452 nsCOMPtr
<nsIEncodedChannel
> encChannel
= do_QueryInterface( aChannel
);
1455 // Turn off content encoding conversions if needed
1456 PRBool applyConversion
= PR_TRUE
;
1458 nsCOMPtr
<nsIURL
> sourceURL(do_QueryInterface(mSourceUrl
));
1461 nsCAutoString extension
;
1462 sourceURL
->GetFileExtension(extension
);
1463 if (!extension
.IsEmpty())
1465 nsCOMPtr
<nsIUTF8StringEnumerator
> encEnum
;
1466 encChannel
->GetContentEncodings(getter_AddRefs(encEnum
));
1470 rv
= encEnum
->HasMore(&hasMore
);
1471 if (NS_SUCCEEDED(rv
) && hasMore
)
1473 nsCAutoString encType
;
1474 rv
= encEnum
->GetNext(encType
);
1475 if (NS_SUCCEEDED(rv
) && !encType
.IsEmpty())
1477 NS_ASSERTION(gExtProtSvc
, "Where did the service go?");
1478 gExtProtSvc
->ApplyDecodingForExtension(extension
, encType
,
1486 encChannel
->SetApplyConversion( applyConversion
);
1489 // now that the temp file is set up, find out if we need to invoke a dialog
1490 // asking the user what they want us to do with this content...
1492 // We can get here for three reasons: "can't handle", "sniffed type", or
1493 // "server sent content-disposition:attachment". In the first case we want
1494 // to honor the user's "always ask" pref; in the other two cases we want to
1495 // honor it only if the default action is "save". Opening attachments in
1496 // helper apps by default breaks some websites (especially if the attachment
1497 // is one part of a multipart document). Opening sniffed content in helper
1498 // apps by default introduces security holes that we'd rather not have.
1500 // So let's find out whether the user wants to be prompted. If he does not,
1501 // check mReason and the preferred action to see what we should do.
1503 PRBool alwaysAsk
= PR_TRUE
;
1504 mMimeInfo
->GetAlwaysAskBeforeHandling(&alwaysAsk
);
1507 // But we *don't* ask if this mimeInfo didn't come from
1508 // our user configuration datastore and the user has said
1509 // at some point in the distant past that they don't
1510 // want to be asked. The latter fact would have been
1511 // stored in pref strings back in the old days.
1512 NS_ASSERTION(gExtProtSvc
, "Service gone away!?");
1514 PRBool mimeTypeIsInDatastore
= PR_FALSE
;
1515 nsCOMPtr
<nsIHandlerService
> handlerSvc
= do_GetService(NS_HANDLERSERVICE_CONTRACTID
);
1517 handlerSvc
->Exists(mMimeInfo
, &mimeTypeIsInDatastore
);
1518 if (!handlerSvc
|| !mimeTypeIsInDatastore
)
1520 if (!GetNeverAskFlagFromPref(NEVER_ASK_FOR_SAVE_TO_DISK_PREF
, MIMEType
.get()))
1522 // Don't need to ask after all.
1523 alwaysAsk
= PR_FALSE
;
1524 // Make sure action matches pref (save to disk).
1525 mMimeInfo
->SetPreferredAction(nsIMIMEInfo::saveToDisk
);
1527 else if (!GetNeverAskFlagFromPref(NEVER_ASK_FOR_OPEN_FILE_PREF
, MIMEType
.get()))
1529 // Don't need to ask after all.
1530 alwaysAsk
= PR_FALSE
;
1535 PRInt32 action
= nsIMIMEInfo::saveToDisk
;
1536 mMimeInfo
->GetPreferredAction( &action
);
1538 // OK, now check why we're here
1539 if (!alwaysAsk
&& mReason
!= nsIHelperAppLauncherDialog::REASON_CANTHANDLE
) {
1540 // Force asking if we're not saving. See comment back when we fetched the
1541 // alwaysAsk boolean for details.
1542 alwaysAsk
= (action
!= nsIMIMEInfo::saveToDisk
);
1545 // if we were told that we _must_ save to disk without asking, all the stuff
1546 // before this is irrelevant; override it
1548 alwaysAsk
= PR_FALSE
;
1549 action
= nsIMIMEInfo::saveToDisk
;
1554 // do this first! make sure we don't try to take an action until the user tells us what they want to do
1556 mReceivedDispositionInfo
= PR_FALSE
;
1558 // invoke the dialog!!!!! use mWindowContext as the window context parameter for the dialog request
1559 mDialog
= do_CreateInstance( NS_IHELPERAPPLAUNCHERDLG_CONTRACTID
, &rv
);
1560 NS_ENSURE_SUCCESS(rv
, rv
);
1562 // this will create a reference cycle (the dialog holds a reference to us as
1563 // nsIHelperAppLauncher), which will be broken in Cancel or
1564 // CreateProgressListener.
1565 rv
= mDialog
->Show( this, mWindowContext
, mReason
);
1567 // what do we do if the dialog failed? I guess we should call Cancel and abort the load....
1571 mReceivedDispositionInfo
= PR_TRUE
; // no need to wait for a response from the user
1573 // We need to do the save/open immediately, then.
1575 /* We need to see whether the file we've got here could be
1576 * executable. If it could, we had better not try to open it!
1577 * We can skip this check, though, if we have a setting to open in a
1579 * This code mirrors the code in
1580 * nsExternalAppHandler::LaunchWithApplication so that what we
1581 * test here is as close as possible to what will really be
1582 * happening if we decide to execute
1584 nsCOMPtr
<nsIHandlerApp
> prefApp
;
1585 mMimeInfo
->GetPreferredApplicationHandler(getter_AddRefs(prefApp
));
1586 if (action
!= nsIMIMEInfo::useHelperApp
|| !prefApp
) {
1587 nsCOMPtr
<nsIFile
> fileToTest
;
1588 GetTargetFile(getter_AddRefs(fileToTest
));
1590 PRBool isExecutable
;
1591 rv
= fileToTest
->IsExecutable(&isExecutable
);
1592 if (NS_FAILED(rv
) || isExecutable
) { // checking NS_FAILED, because paranoia is good
1593 action
= nsIMIMEInfo::saveToDisk
;
1595 } else { // Paranoia is good here too, though this really should not happen
1596 NS_WARNING("GetDownloadInfo returned a null file after the temp file has been set up! ");
1597 action
= nsIMIMEInfo::saveToDisk
;
1602 if (action
== nsIMIMEInfo::useHelperApp
||
1603 action
== nsIMIMEInfo::useSystemDefault
)
1605 rv
= LaunchWithApplication(nsnull
, PR_FALSE
);
1607 else // Various unknown actions go here too
1609 rv
= SaveToDisk(nsnull
, PR_FALSE
);
1613 // Now let's add the download to history
1614 nsCOMPtr
<nsIDownloadHistory
> dh(do_GetService(NS_DOWNLOADHISTORY_CONTRACTID
));
1616 nsCOMPtr
<nsIURI
> referrer
;
1618 NS_GetReferrerFromChannel(aChannel
, getter_AddRefs(referrer
));
1619 dh
->AddDownload(mSourceUrl
, referrer
, mTimeDownloadStarted
);
1625 // Convert error info into proper message text and send OnStatusChange notification
1626 // to the web progress listener.
1627 void nsExternalAppHandler::SendStatusChange(ErrorType type
, nsresult rv
, nsIRequest
*aRequest
, const nsAFlatString
&path
)
1632 case NS_ERROR_OUT_OF_MEMORY
:
1634 msgId
.AssignLiteral("noMemory");
1637 case NS_ERROR_FILE_DISK_FULL
:
1638 case NS_ERROR_FILE_NO_DEVICE_SPACE
:
1639 // Out of space on target volume.
1640 msgId
.AssignLiteral("diskFull");
1643 case NS_ERROR_FILE_READ_ONLY
:
1644 // Attempt to write to read/only file.
1645 msgId
.AssignLiteral("readOnly");
1648 case NS_ERROR_FILE_ACCESS_DENIED
:
1649 if (type
== kWriteError
) {
1650 // Attempt to write without sufficient permissions.
1651 msgId
.AssignLiteral("accessError");
1655 msgId
.AssignLiteral("launchError");
1659 case NS_ERROR_FILE_NOT_FOUND
:
1660 case NS_ERROR_FILE_TARGET_DOES_NOT_EXIST
:
1661 case NS_ERROR_FILE_UNRECOGNIZED_PATH
:
1662 // Helper app not found, let's verify this happened on launch
1663 if (type
== kLaunchError
) {
1664 msgId
.AssignLiteral("helperAppNotFound");
1670 // Generic read/write/launch error message.
1674 msgId
.AssignLiteral("readError");
1677 msgId
.AssignLiteral("writeError");
1680 msgId
.AssignLiteral("launchError");
1685 PR_LOG(nsExternalHelperAppService::mLog
, PR_LOG_ERROR
,
1686 ("Error: %s, type=%i, listener=0x%p, rv=0x%08X\n",
1687 NS_LossyConvertUTF16toASCII(msgId
).get(), type
, mWebProgressListener
.get(), rv
));
1688 PR_LOG(nsExternalHelperAppService::mLog
, PR_LOG_ERROR
,
1689 (" path='%s'\n", NS_ConvertUTF16toUTF8(path
).get()));
1691 // Get properties file bundle and extract status string.
1692 nsCOMPtr
<nsIStringBundleService
> s
= do_GetService(NS_STRINGBUNDLE_CONTRACTID
);
1695 nsCOMPtr
<nsIStringBundle
> bundle
;
1696 if (NS_SUCCEEDED(s
->CreateBundle("chrome://global/locale/nsWebBrowserPersist.properties", getter_AddRefs(bundle
))))
1698 nsXPIDLString msgText
;
1699 const PRUnichar
*strings
[] = { path
.get() };
1700 if(NS_SUCCEEDED(bundle
->FormatStringFromName(msgId
.get(), strings
, 1, getter_Copies(msgText
))))
1702 if (mWebProgressListener
)
1704 // We have a listener, let it handle the error.
1705 mWebProgressListener
->OnStatusChange(nsnull
, (type
== kReadError
) ? aRequest
: nsnull
, rv
, msgText
);
1709 // We don't have a listener. Simply show the alert ourselves.
1710 nsCOMPtr
<nsIPrompt
> prompter(do_GetInterface(mWindowContext
));
1711 nsXPIDLString title
;
1712 bundle
->FormatStringFromName(NS_LITERAL_STRING("title").get(),
1715 getter_Copies(title
));
1718 prompter
->Alert(title
, msgText
);
1726 NS_IMETHODIMP
nsExternalAppHandler::OnDataAvailable(nsIRequest
*request
, nsISupports
* aCtxt
,
1727 nsIInputStream
* inStr
, PRUint32 sourceOffset
, PRUint32 count
)
1729 nsresult rv
= NS_OK
;
1730 // first, check to see if we've been canceled....
1731 if (mCanceled
) // then go cancel our underlying channel too
1732 return request
->Cancel(NS_BINDING_ABORTED
);
1734 // read the data out of the stream and write it to the temp file.
1735 if (mOutStream
&& count
> 0)
1737 PRUint32 numBytesRead
= 0;
1738 PRUint32 numBytesWritten
= 0;
1740 PRBool readError
= PR_TRUE
;
1741 while (NS_SUCCEEDED(rv
) && count
> 0) // while we still have bytes to copy...
1743 readError
= PR_TRUE
;
1744 rv
= inStr
->Read(mDataBuffer
, PR_MIN(count
, DATA_BUFFER_SIZE
- 1), &numBytesRead
);
1745 if (NS_SUCCEEDED(rv
))
1747 if (count
>= numBytesRead
)
1748 count
-= numBytesRead
; // subtract off the number of bytes we just read
1751 readError
= PR_FALSE
;
1752 // Write out the data until something goes wrong, or, it is
1753 // all written. We loop because for some errors (e.g., disk
1754 // full), we get NS_OK with some bytes written, then an error.
1755 // So, we want to write again in that case to get the actual
1757 const char *bufPtr
= mDataBuffer
; // Where to write from.
1758 while (NS_SUCCEEDED(rv
) && numBytesRead
)
1760 numBytesWritten
= 0;
1761 rv
= mOutStream
->Write(bufPtr
, numBytesRead
, &numBytesWritten
);
1762 if (NS_SUCCEEDED(rv
))
1764 numBytesRead
-= numBytesWritten
;
1765 bufPtr
+= numBytesWritten
;
1766 // Force an error if (for some reason) we get NS_OK but
1767 // no bytes written.
1768 if (!numBytesWritten
)
1770 rv
= NS_ERROR_FAILURE
;
1776 if (NS_SUCCEEDED(rv
))
1778 // Send progress notification.
1779 if (mWebProgressListener
)
1781 mWebProgressListener
->OnProgressChange64(nsnull
, request
, mProgress
, mContentLength
, mProgress
, mContentLength
);
1786 // An error occurred, notify listener.
1787 nsAutoString tempFilePath
;
1789 mTempFile
->GetPath(tempFilePath
);
1790 SendStatusChange(readError
? kReadError
: kWriteError
, rv
, request
, tempFilePath
);
1792 // Cancel the download.
1799 NS_IMETHODIMP
nsExternalAppHandler::OnStopRequest(nsIRequest
*request
, nsISupports
*aCtxt
,
1802 mStopRequestIssued
= PR_TRUE
;
1804 // Cancel if the request did not complete successfully.
1805 if (!mCanceled
&& NS_FAILED(aStatus
))
1807 // Send error notification.
1808 nsAutoString tempFilePath
;
1810 mTempFile
->GetPath(tempFilePath
);
1811 SendStatusChange( kReadError
, aStatus
, request
, tempFilePath
);
1816 // first, check to see if we've been canceled....
1820 // close the stream...
1823 mOutStream
->Close();
1824 mOutStream
= nsnull
;
1827 // Do what the user asked for
1828 ExecuteDesiredAction();
1830 // At this point, the channel should still own us. So releasing the reference
1831 // to us in the nsITransfer should be ok.
1832 // This nsITransfer object holds a reference to us (we are its observer), so
1833 // we need to release the reference to break a reference cycle (and therefore
1834 // to prevent leaking)
1835 mWebProgressListener
= nsnull
;
1840 nsresult
nsExternalAppHandler::ExecuteDesiredAction()
1842 nsresult rv
= NS_OK
;
1843 if (mProgressListenerInitialized
&& !mCanceled
)
1845 nsHandlerInfoAction action
= nsIMIMEInfo::saveToDisk
;
1846 mMimeInfo
->GetPreferredAction(&action
);
1847 if (action
== nsIMIMEInfo::useHelperApp
||
1848 action
== nsIMIMEInfo::useSystemDefault
)
1850 // Make sure the suggested name is unique since in this case we don't
1851 // have a file name that was guaranteed to be unique by going through
1852 // the File Save dialog
1853 rv
= mFinalFileDestination
->CreateUnique(nsIFile::NORMAL_FILE_TYPE
, 0600);
1854 if (NS_SUCCEEDED(rv
))
1856 // Source and dest dirs should be == so this should just do a rename
1857 rv
= MoveFile(mFinalFileDestination
);
1858 if (NS_SUCCEEDED(rv
))
1859 rv
= OpenWithApplication();
1862 else // Various unknown actions go here too
1864 // XXX Put progress dialog in barber-pole mode
1865 // and change text to say "Copying from:".
1866 rv
= MoveFile(mFinalFileDestination
);
1867 if (NS_SUCCEEDED(rv
) && action
== nsIMIMEInfo::saveToDisk
)
1869 nsCOMPtr
<nsILocalFile
> destfile(do_QueryInterface(mFinalFileDestination
));
1870 gExtProtSvc
->FixFilePermissions(destfile
);
1874 // Notify dialog that download is complete.
1875 // By waiting till this point, it ensures that the progress dialog doesn't indicate
1876 // success until we're really done.
1877 if(mWebProgressListener
)
1881 mWebProgressListener
->OnProgressChange64(nsnull
, nsnull
, mProgress
, mContentLength
, mProgress
, mContentLength
);
1883 mWebProgressListener
->OnStateChange(nsnull
, nsnull
,
1884 nsIWebProgressListener::STATE_STOP
|
1885 nsIWebProgressListener::STATE_IS_REQUEST
|
1886 nsIWebProgressListener::STATE_IS_NETWORK
, NS_OK
);
1893 NS_IMETHODIMP
nsExternalAppHandler::GetMIMEInfo(nsIMIMEInfo
** aMIMEInfo
)
1895 *aMIMEInfo
= mMimeInfo
;
1896 NS_ADDREF(*aMIMEInfo
);
1900 NS_IMETHODIMP
nsExternalAppHandler::GetSource(nsIURI
** aSourceURI
)
1902 NS_ENSURE_ARG(aSourceURI
);
1903 *aSourceURI
= mSourceUrl
;
1904 NS_IF_ADDREF(*aSourceURI
);
1908 NS_IMETHODIMP
nsExternalAppHandler::GetSuggestedFileName(nsAString
& aSuggestedFileName
)
1910 aSuggestedFileName
= mSuggestedFileName
;
1914 nsresult
nsExternalAppHandler::InitializeDownload(nsITransfer
* aTransfer
)
1918 nsCOMPtr
<nsIURI
> target
;
1919 rv
= NS_NewFileURI(getter_AddRefs(target
), mFinalFileDestination
);
1920 if (NS_FAILED(rv
)) return rv
;
1922 nsCOMPtr
<nsILocalFile
> lf(do_QueryInterface(mTempFile
));
1923 rv
= aTransfer
->Init(mSourceUrl
, target
, EmptyString(),
1924 mMimeInfo
, mTimeDownloadStarted
, lf
, this);
1925 if (NS_FAILED(rv
)) return rv
;
1930 nsresult
nsExternalAppHandler::CreateProgressListener()
1932 // we are back from the helper app dialog (where the user chooses to save or open), but we aren't
1933 // done processing the load. in this case, throw up a progress dialog so the user can see what's going on...
1934 // Also, release our reference to mDialog. We don't need it anymore, and we
1935 // need to break the reference cycle.
1939 nsCOMPtr
<nsITransfer
> tr
= do_CreateInstance(NS_TRANSFER_CONTRACTID
, &rv
);
1940 if (NS_SUCCEEDED(rv
))
1941 InitializeDownload(tr
);
1944 tr
->OnStateChange(nsnull
, mRequest
, nsIWebProgressListener::STATE_START
|
1945 nsIWebProgressListener::STATE_IS_REQUEST
|
1946 nsIWebProgressListener::STATE_IS_NETWORK
, NS_OK
);
1948 // note we might not have a listener here if the QI() failed, or if
1949 // there is no nsITransfer object, but we still call
1950 // SetWebProgressListener() to make sure our progress state is sane
1951 // NOTE: This will set up a reference cycle (this nsITransfer has us set up as
1952 // its observer). This cycle will be broken in Cancel, CloseProgressWindow or
1954 SetWebProgressListener(tr
);
1959 nsresult
nsExternalAppHandler::PromptForSaveToFile(nsILocalFile
** aNewFile
, const nsAFlatString
&aDefaultFile
, const nsAFlatString
&aFileExtension
)
1961 // invoke the dialog!!!!! use mWindowContext as the window context parameter for the dialog request
1962 // Convert to use file picker? No, then embeddors could not do any sort of
1963 // "AutoDownload" w/o showing a prompt
1964 nsresult rv
= NS_OK
;
1967 // Get helper app launcher dialog.
1968 mDialog
= do_CreateInstance( NS_IHELPERAPPLAUNCHERDLG_CONTRACTID
, &rv
);
1969 NS_ENSURE_SUCCESS(rv
, rv
);
1972 // we want to explicitly unescape aDefaultFile b4 passing into the dialog. we can't unescape
1973 // it because the dialog is implemented by a JS component which doesn't have a window so no unescape routine is defined...
1975 // Now, be sure to keep |this| alive, and the dialog
1976 // If we don't do this, users that close the helper app dialog while the file
1977 // picker is up would cause Cancel() to be called, and the dialog would be
1978 // released, which would release this object too, which would crash.
1980 nsRefPtr
<nsExternalAppHandler
> kungFuDeathGrip(this);
1981 nsCOMPtr
<nsIHelperAppLauncherDialog
> dlg(mDialog
);
1982 rv
= mDialog
->PromptForSaveToFile(this,
1985 aFileExtension
.get(),
1986 mForceSave
, aNewFile
);
1990 nsresult
nsExternalAppHandler::MoveFile(nsIFile
* aNewFileLocation
)
1992 nsresult rv
= NS_OK
;
1993 NS_ASSERTION(mStopRequestIssued
, "uhoh, how did we get here if we aren't done getting data?");
1995 nsCOMPtr
<nsILocalFile
> fileToUse
= do_QueryInterface(aNewFileLocation
);
1997 // if the on stop request was actually issued then it's now time to actually perform the file move....
1998 if (mStopRequestIssued
&& fileToUse
)
2000 // Unfortunately, MoveTo will fail if a file already exists at the user specified location....
2001 // but the user has told us, this is where they want the file! (when we threw up the save to file dialog,
2002 // it told them the file already exists and do they wish to over write it. So it should be okay to delete
2003 // fileToUse if it already exists.
2004 PRBool equalToTempFile
= PR_FALSE
;
2005 PRBool filetoUseAlreadyExists
= PR_FALSE
;
2006 fileToUse
->Equals(mTempFile
, &equalToTempFile
);
2007 fileToUse
->Exists(&filetoUseAlreadyExists
);
2008 if (filetoUseAlreadyExists
&& !equalToTempFile
)
2009 fileToUse
->Remove(PR_FALSE
);
2011 // extract the new leaf name from the file location
2012 nsAutoString fileName
;
2013 fileToUse
->GetLeafName(fileName
);
2014 nsCOMPtr
<nsIFile
> directoryLocation
;
2015 rv
= fileToUse
->GetParent(getter_AddRefs(directoryLocation
));
2016 if (directoryLocation
)
2018 rv
= mTempFile
->MoveTo(directoryLocation
, fileName
);
2022 // Send error notification.
2024 fileToUse
->GetPath(path
);
2025 SendStatusChange(kWriteError
, rv
, nsnull
, path
);
2026 Cancel(rv
); // Cancel (and clean up temp file).
2031 // tag the file with its source URI
2032 nsCOMPtr
<nsILocalFileOS2
> localFileOS2
= do_QueryInterface(fileToUse
);
2036 mSourceUrl
->GetSpec(url
);
2037 localFileOS2
->SetFileSource(url
);
2046 // SaveToDisk should only be called by the helper app dialog which allows
2047 // the user to say launch with application or save to disk. It doesn't actually
2048 // perform the save, it just prompts for the destination file name. The actual save
2049 // won't happen until we are done downloading the content and are sure we've
2050 // shown a progress dialog. This was done to simplify the
2051 // logic that was showing up in this method. Internal callers who actually want
2052 // to preform the save should call ::MoveFile
2054 NS_IMETHODIMP
nsExternalAppHandler::SaveToDisk(nsIFile
* aNewFileLocation
, PRBool aRememberThisPreference
)
2056 nsresult rv
= NS_OK
;
2060 mMimeInfo
->SetPreferredAction(nsIMIMEInfo::saveToDisk
);
2062 // The helper app dialog has told us what to do.
2063 mReceivedDispositionInfo
= PR_TRUE
;
2065 nsCOMPtr
<nsILocalFile
> fileToUse
= do_QueryInterface(aNewFileLocation
);
2068 nsAutoString leafName
;
2069 mTempFile
->GetLeafName(leafName
);
2070 if (mSuggestedFileName
.IsEmpty())
2071 rv
= PromptForSaveToFile(getter_AddRefs(fileToUse
), leafName
, mTempFileExtension
);
2074 nsAutoString fileExt
;
2075 PRInt32 pos
= mSuggestedFileName
.RFindChar('.');
2077 mSuggestedFileName
.Right(fileExt
, mSuggestedFileName
.Length() - pos
);
2078 if (fileExt
.IsEmpty())
2079 fileExt
= mTempFileExtension
;
2081 rv
= PromptForSaveToFile(getter_AddRefs(fileToUse
), mSuggestedFileName
, fileExt
);
2084 if (NS_FAILED(rv
) || !fileToUse
) {
2085 Cancel(NS_BINDING_ABORTED
);
2086 return NS_ERROR_FAILURE
;
2090 mFinalFileDestination
= do_QueryInterface(fileToUse
);
2092 // Move what we have in the final directory, but append .part
2093 // to it, to indicate that it's unfinished.
2094 // do not do that if we're already done
2095 if (mFinalFileDestination
&& !mStopRequestIssued
)
2097 nsCOMPtr
<nsIFile
> movedFile
;
2098 mFinalFileDestination
->Clone(getter_AddRefs(movedFile
));
2100 // Get the old leaf name and append .part to it
2102 mFinalFileDestination
->GetLeafName(name
);
2103 name
.AppendLiteral(".part");
2104 movedFile
->SetLeafName(name
);
2106 nsCOMPtr
<nsIFile
> dir
;
2107 movedFile
->GetParent(getter_AddRefs(dir
));
2109 mOutStream
->Close();
2111 rv
= mTempFile
->MoveTo(dir
, name
);
2112 if (NS_SUCCEEDED(rv
)) // if it failed, we just continue with $TEMP
2113 mTempFile
= movedFile
;
2115 nsCOMPtr
<nsIOutputStream
> outputStream
;
2116 rv
= NS_NewLocalFileOutputStream(getter_AddRefs(outputStream
), mTempFile
,
2117 PR_WRONLY
| PR_APPEND
, 0600);
2118 if (NS_FAILED(rv
)) { // (Re-)opening the output stream failed. bad luck.
2120 mTempFile
->GetPath(path
);
2121 SendStatusChange(kWriteError
, rv
, nsnull
, path
);
2126 mOutStream
= NS_BufferOutputStream(outputStream
, BUFFERED_OUTPUT_SIZE
);
2130 if (!mProgressListenerInitialized
)
2131 CreateProgressListener();
2133 // now that the user has chosen the file location to save to, it's okay to fire the refresh tag
2134 // if there is one. We don't want to do this before the save as dialog goes away because this dialog
2135 // is modal and we do bad things if you try to load a web page in the underlying window while a modal
2136 // dialog is still up.
2137 ProcessAnyRefreshTags();
2143 nsresult
nsExternalAppHandler::OpenWithApplication()
2145 nsresult rv
= NS_OK
;
2149 // we only should have gotten here if the on stop request had been fired already.
2151 NS_ASSERTION(mStopRequestIssued
, "uhoh, how did we get here if we aren't done getting data?");
2152 // if a stop request was already issued then proceed with launching the application.
2153 if (mStopRequestIssued
)
2155 PRBool deleteTempFileOnExit
;
2156 nsCOMPtr
<nsIPrefBranch
> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID
));
2157 if (!prefs
|| NS_FAILED(prefs
->GetBoolPref(
2158 "browser.helperApps.deleteTempFileOnExit", &deleteTempFileOnExit
))) {
2159 // No prefservice or no pref set; use default value
2160 #if !defined(XP_MACOSX)
2161 // Mac users have been very verbal about temp files being deleted on
2162 // app exit - they don't like it - but we'll continue to do this on
2163 // other platforms for now.
2164 deleteTempFileOnExit
= PR_TRUE
;
2166 deleteTempFileOnExit
= PR_FALSE
;
2170 // make the tmp file readonly so users won't edit it and lose the changes
2171 // only if we're going to delete the file
2172 if (deleteTempFileOnExit
)
2173 mFinalFileDestination
->SetPermissions(0400);
2175 rv
= mMimeInfo
->LaunchWithFile(mFinalFileDestination
);
2178 // Send error notification.
2180 mFinalFileDestination
->GetPath(path
);
2181 SendStatusChange(kLaunchError
, rv
, nsnull
, path
);
2182 Cancel(rv
); // Cancel, and clean up temp file.
2184 else if (deleteTempFileOnExit
) {
2185 NS_ASSERTION(gExtProtSvc
, "Service gone away!?");
2186 gExtProtSvc
->DeleteTemporaryFileOnExit(mFinalFileDestination
);
2193 // LaunchWithApplication should only be called by the helper app dialog which allows
2194 // the user to say launch with application or save to disk. It doesn't actually
2195 // perform launch with application. That won't happen until we are done downloading
2196 // the content and are sure we've showna progress dialog. This was done to simplify the
2197 // logic that was showing up in this method.
2198 NS_IMETHODIMP
nsExternalAppHandler::LaunchWithApplication(nsIFile
* aApplication
, PRBool aRememberThisPreference
)
2203 // user has chosen to launch using an application, fire any refresh tags now...
2204 ProcessAnyRefreshTags();
2206 mReceivedDispositionInfo
= PR_TRUE
;
2207 if (mMimeInfo
&& aApplication
) {
2208 PlatformLocalHandlerApp_t
*handlerApp
=
2209 new PlatformLocalHandlerApp_t(EmptyString(), aApplication
);
2210 mMimeInfo
->SetPreferredApplicationHandler(handlerApp
);
2213 // Now check if the file is local, in which case we won't bother with saving
2214 // it to a temporary directory and just launch it from where it is
2215 nsCOMPtr
<nsIFileURL
> fileUrl(do_QueryInterface(mSourceUrl
));
2216 if (fileUrl
&& mIsFileChannel
)
2218 Cancel(NS_BINDING_ABORTED
);
2219 nsCOMPtr
<nsIFile
> file
;
2220 nsresult rv
= fileUrl
->GetFile(getter_AddRefs(file
));
2222 if (NS_SUCCEEDED(rv
))
2224 rv
= mMimeInfo
->LaunchWithFile(file
);
2225 if (NS_SUCCEEDED(rv
))
2230 file
->GetPath(path
);
2231 // If we get here, an error happened
2232 SendStatusChange(kLaunchError
, rv
, nsnull
, path
);
2236 // Now that the user has elected to launch the downloaded file with a helper app, we're justified in
2237 // removing the 'salted' name. We'll rename to what was specified in mSuggestedFileName after the
2238 // download is done prior to launching the helper app. So that any existing file of that name won't
2239 // be overwritten we call CreateUnique() before calling MoveFile(). Also note that we use the same
2240 // directory as originally downloaded to so that MoveFile() just does an in place rename.
2242 nsCOMPtr
<nsIFile
> fileToUse
;
2243 (void) GetDownloadDirectory(getter_AddRefs(fileToUse
));
2245 if (mSuggestedFileName
.IsEmpty())
2247 // Keep using the leafname of the temp file, since we're just starting a helper
2248 mTempFile
->GetLeafName(mSuggestedFileName
);
2252 fileToUse
->Append(mSuggestedFileName
+ mTempFileExtension
);
2254 fileToUse
->Append(mSuggestedFileName
);
2257 // We'll make sure this results in a unique name later
2259 mFinalFileDestination
= do_QueryInterface(fileToUse
);
2261 // launch the progress window now that the user has picked the desired action.
2262 if (!mProgressListenerInitialized
)
2263 CreateProgressListener();
2268 NS_IMETHODIMP
nsExternalAppHandler::Cancel(nsresult aReason
)
2270 NS_ENSURE_ARG(NS_FAILED(aReason
));
2271 // XXX should not ignore the reason
2273 mCanceled
= PR_TRUE
;
2274 // Break our reference cycle with the helper app dialog (set up in
2277 // shutdown our stream to the temp file
2280 mOutStream
->Close();
2281 mOutStream
= nsnull
;
2284 // Clean up after ourselves and delete the temp file only if the user
2285 // canceled the helper app dialog (we didn't get the disposition info yet).
2286 // We leave the partial file for everything else because it could be useful
2287 // e.g., resume a download
2288 if (mTempFile
&& !mReceivedDispositionInfo
)
2290 mTempFile
->Remove(PR_FALSE
);
2294 // Release the listener, to break the reference cycle with it (we are the
2295 // observer of the listener).
2296 mWebProgressListener
= nsnull
;
2301 void nsExternalAppHandler::ProcessAnyRefreshTags()
2303 // one last thing, try to see if the original window context supports a refresh interface...
2304 // Sometimes, when you download content that requires an external handler, there is
2305 // a refresh header associated with the download. This refresh header points to a page
2306 // the content provider wants the user to see after they download the content. How do we
2307 // pass this refresh information back to the caller? For now, try to get the refresh URI
2308 // interface. If the window context where the request originated came from supports this
2309 // then we can force it to process the refresh information (if there is any) from this channel.
2310 if (mWindowContext
&& mOriginalChannel
)
2312 nsCOMPtr
<nsIRefreshURI
> refreshHandler (do_GetInterface(mWindowContext
));
2313 if (refreshHandler
) {
2314 refreshHandler
->SetupRefreshURI(mOriginalChannel
);
2316 mOriginalChannel
= nsnull
;
2320 PRBool
nsExternalAppHandler::GetNeverAskFlagFromPref(const char * prefName
, const char * aContentType
)
2322 // Search the obsolete pref strings.
2324 nsCOMPtr
<nsIPrefService
> prefs
= do_GetService(NS_PREFSERVICE_CONTRACTID
, &rv
);
2325 nsCOMPtr
<nsIPrefBranch
> prefBranch
;
2327 rv
= prefs
->GetBranch(NEVER_ASK_PREF_BRANCH
, getter_AddRefs(prefBranch
));
2328 if (NS_SUCCEEDED(rv
) && prefBranch
)
2330 nsXPIDLCString prefCString
;
2331 nsCAutoString prefValue
;
2332 rv
= prefBranch
->GetCharPref(prefName
, getter_Copies(prefCString
));
2333 if (NS_SUCCEEDED(rv
) && !prefCString
.IsEmpty())
2335 NS_UnescapeURL(prefCString
);
2336 nsACString::const_iterator start
, end
;
2337 prefCString
.BeginReading(start
);
2338 prefCString
.EndReading(end
);
2339 if (CaseInsensitiveFindInReadable(nsDependentCString(aContentType
), start
, end
))
2343 // Default is true, if not found in the pref string.
2347 nsresult
nsExternalAppHandler::MaybeCloseWindow()
2349 nsCOMPtr
<nsIDOMWindow
> window(do_GetInterface(mWindowContext
));
2350 nsCOMPtr
<nsIDOMWindowInternal
> internalWindow
= do_QueryInterface(window
);
2351 NS_ENSURE_STATE(internalWindow
);
2353 if (mShouldCloseWindow
) {
2354 // Reset the window context to the opener window so that the dependent
2355 // dialogs have a parent
2356 nsCOMPtr
<nsIDOMWindowInternal
> opener
;
2357 internalWindow
->GetOpener(getter_AddRefs(opener
));
2360 if (opener
&& NS_SUCCEEDED(opener
->GetClosed(&isClosed
)) && !isClosed
) {
2361 mWindowContext
= do_GetInterface(opener
);
2363 // Now close the old window. Do it on a timer so that we don't run
2364 // into issues trying to close the window before it has fully opened.
2365 NS_ASSERTION(!mTimer
, "mTimer was already initialized once!");
2366 mTimer
= do_CreateInstance("@mozilla.org/timer;1");
2368 return NS_ERROR_FAILURE
;
2371 mTimer
->InitWithCallback(this, 0, nsITimer::TYPE_ONE_SHOT
);
2372 mWindowToClose
= internalWindow
;
2380 nsExternalAppHandler::Notify(nsITimer
* timer
)
2382 NS_ASSERTION(mWindowToClose
, "No window to close after timer fired");
2384 mWindowToClose
->Close();
2385 mWindowToClose
= nsnull
;
2390 //////////////////////////////////////////////////////////////////////////////////////////////////////////////
2391 // The following section contains our nsIMIMEService implementation and related methods.
2393 //////////////////////////////////////////////////////////////////////////////////////////////////////////////
2395 // nsIMIMEService methods
2396 NS_IMETHODIMP
nsExternalHelperAppService::GetFromTypeAndExtension(const nsACString
& aMIMEType
, const nsACString
& aFileExt
, nsIMIMEInfo
**_retval
)
2398 NS_PRECONDITION(!aMIMEType
.IsEmpty() ||
2399 !aFileExt
.IsEmpty(),
2400 "Give me something to work with");
2401 LOG(("Getting mimeinfo from type '%s' ext '%s'\n",
2402 PromiseFlatCString(aMIMEType
).get(), PromiseFlatCString(aFileExt
).get()));
2406 // OK... we need a type. Get one.
2407 nsCAutoString
typeToUse(aMIMEType
);
2408 if (typeToUse
.IsEmpty()) {
2409 nsresult rv
= GetTypeFromExtension(aFileExt
, typeToUse
);
2411 return NS_ERROR_NOT_AVAILABLE
;
2414 // We promise to only send lower case mime types to the OS
2415 ToLowerCase(typeToUse
);
2417 // (1) Ask the OS for a mime info
2419 *_retval
= GetMIMEInfoFromOS(typeToUse
, aFileExt
, &found
).get();
2420 LOG(("OS gave back 0x%p - found: %i\n", *_retval
, found
));
2421 // If we got no mimeinfo, something went wrong. Probably lack of memory.
2423 return NS_ERROR_OUT_OF_MEMORY
;
2425 // (2) Now, let's see if we can find something in our datastore
2426 // This will not overwrite the OS information that interests us
2427 // (i.e. default application, default app. description)
2429 nsCOMPtr
<nsIHandlerService
> handlerSvc
= do_GetService(NS_HANDLERSERVICE_CONTRACTID
);
2431 PRBool hasHandler
= PR_FALSE
;
2432 (void) handlerSvc
->Exists(*_retval
, &hasHandler
);
2434 rv
= handlerSvc
->FillHandlerInfo(*_retval
, EmptyCString());
2435 LOG(("Data source: Via type: retval 0x%08x\n", rv
));
2437 rv
= NS_ERROR_NOT_AVAILABLE
;
2440 found
= found
|| NS_SUCCEEDED(rv
);
2442 if (!found
|| NS_FAILED(rv
)) {
2443 // No type match, try extension match
2444 if (!aFileExt
.IsEmpty()) {
2445 nsCAutoString overrideType
;
2446 rv
= handlerSvc
->GetTypeFromExtension(aFileExt
, overrideType
);
2447 if (NS_SUCCEEDED(rv
) && !overrideType
.IsEmpty()) {
2448 // We can't check handlerSvc->Exists() here, because we have a
2449 // overideType. That's ok, it just results in some console noise.
2450 // (If there's no handler for the override type, it throws)
2451 rv
= handlerSvc
->FillHandlerInfo(*_retval
, overrideType
);
2452 LOG(("Data source: Via ext: retval 0x%08x\n", rv
));
2453 found
= found
|| NS_SUCCEEDED(rv
);
2459 // (3) No match yet. Ask extras.
2461 rv
= NS_ERROR_FAILURE
;
2463 /* XXX Gross hack to wallpaper over the most common Win32
2464 * extension issues caused by the fix for bug 116938. See bug
2465 * 120327, comment 271 for why this is needed. Not even sure we
2466 * want to remove this once we have fixed all this stuff to work
2467 * right; any info we get from extras on this type is pretty much
2470 if (!typeToUse
.Equals(APPLICATION_OCTET_STREAM
, nsCaseInsensitiveCStringComparator()))
2472 rv
= FillMIMEInfoForMimeTypeFromExtras(typeToUse
, *_retval
);
2473 LOG(("Searched extras (by type), rv 0x%08X\n", rv
));
2474 // If that didn't work out, try file extension from extras
2475 if (NS_FAILED(rv
) && !aFileExt
.IsEmpty()) {
2476 rv
= FillMIMEInfoForExtensionFromExtras(aFileExt
, *_retval
);
2477 LOG(("Searched extras (by ext), rv 0x%08X\n", rv
));
2481 // Finally, check if we got a file extension and if yes, if it is an
2482 // extension on the mimeinfo, in which case we want it to be the primary one
2483 if (!aFileExt
.IsEmpty()) {
2484 PRBool matches
= PR_FALSE
;
2485 (*_retval
)->ExtensionExists(aFileExt
, &matches
);
2486 LOG(("Extension '%s' matches mime info: %i\n", PromiseFlatCString(aFileExt
).get(), matches
));
2488 (*_retval
)->SetPrimaryExtension(aFileExt
);
2492 if (LOG_ENABLED()) {
2494 (*_retval
)->GetMIMEType(type
);
2497 (*_retval
)->GetPrimaryExtension(ext
);
2498 LOG(("MIME Info Summary: Type '%s', Primary Ext '%s'\n", type
.get(), ext
.get()));
2505 NS_IMETHODIMP
nsExternalHelperAppService::GetTypeFromExtension(const nsACString
& aFileExt
, nsACString
& aContentType
)
2507 // OK. We want to try the following sources of mimetype information, in this order:
2508 // 1. defaultMimeEntries array
2509 // 2. User-set preferences (managed by the handler service)
2510 // 3. OS-provided information
2511 // 4. our "extras" array
2512 // 5. Information from plugins
2513 // 6. The "ext-to-type-mapping" category
2515 nsresult rv
= NS_OK
;
2516 // First of all, check our default entries
2517 for (size_t i
= 0; i
< NS_ARRAY_LENGTH(defaultMimeEntries
); i
++)
2519 if (aFileExt
.LowerCaseEqualsASCII(defaultMimeEntries
[i
].mFileExtension
)) {
2520 aContentType
= defaultMimeEntries
[i
].mMimeType
;
2525 // Check user-set prefs
2526 nsCOMPtr
<nsIHandlerService
> handlerSvc
= do_GetService(NS_HANDLERSERVICE_CONTRACTID
);
2528 rv
= handlerSvc
->GetTypeFromExtension(aFileExt
, aContentType
);
2529 if (NS_SUCCEEDED(rv
) && !aContentType
.IsEmpty())
2533 PRBool found
= PR_FALSE
;
2534 nsCOMPtr
<nsIMIMEInfo
> mi
= GetMIMEInfoFromOS(EmptyCString(), aFileExt
, &found
);
2536 return mi
->GetMIMEType(aContentType
);
2538 // Check extras array.
2539 found
= GetTypeFromExtras(aFileExt
, aContentType
);
2543 const nsCString
& flatExt
= PromiseFlatCString(aFileExt
);
2545 const char* mimeType
;
2546 nsCOMPtr
<nsIPluginHost
> pluginHost (do_GetService(kPluginManagerCID
, &rv
));
2547 if (NS_SUCCEEDED(rv
)) {
2548 if (NS_SUCCEEDED(pluginHost
->IsPluginEnabledForExtension(flatExt
.get(), mimeType
)))
2550 aContentType
= mimeType
;
2556 // Let's see if an extension added something
2557 nsCOMPtr
<nsICategoryManager
> catMan(do_GetService("@mozilla.org/categorymanager;1"));
2559 nsXPIDLCString type
;
2560 rv
= catMan
->GetCategoryEntry("ext-to-type-mapping", flatExt
.get(), getter_Copies(type
));
2561 aContentType
= type
;
2564 rv
= NS_ERROR_NOT_AVAILABLE
;
2570 NS_IMETHODIMP
nsExternalHelperAppService::GetPrimaryExtension(const nsACString
& aMIMEType
, const nsACString
& aFileExt
, nsACString
& _retval
)
2572 NS_ENSURE_ARG(!aMIMEType
.IsEmpty());
2574 nsCOMPtr
<nsIMIMEInfo
> mi
;
2575 nsresult rv
= GetFromTypeAndExtension(aMIMEType
, aFileExt
, getter_AddRefs(mi
));
2579 return mi
->GetPrimaryExtension(_retval
);
2582 NS_IMETHODIMP
nsExternalHelperAppService::GetTypeFromURI(nsIURI
*aURI
, nsACString
& aContentType
)
2584 nsresult rv
= NS_ERROR_NOT_AVAILABLE
;
2585 aContentType
.Truncate();
2587 // First look for a file to use. If we have one, we just use that.
2588 nsCOMPtr
<nsIFileURL
> fileUrl
= do_QueryInterface(aURI
);
2590 nsCOMPtr
<nsIFile
> file
;
2591 rv
= fileUrl
->GetFile(getter_AddRefs(file
));
2592 if (NS_SUCCEEDED(rv
)) {
2593 rv
= GetTypeFromFile(file
, aContentType
);
2594 if (NS_SUCCEEDED(rv
)) {
2595 // we got something!
2601 // Now try to get an nsIURL so we don't have to do our own parsing
2602 nsCOMPtr
<nsIURL
> url
= do_QueryInterface(aURI
);
2605 rv
= url
->GetFileExtension(ext
);
2609 return NS_ERROR_NOT_AVAILABLE
;
2611 UnescapeFragment(ext
, url
, ext
);
2613 return GetTypeFromExtension(ext
, aContentType
);
2616 // no url, let's give the raw spec a shot
2617 nsCAutoString specStr
;
2618 rv
= aURI
->GetSpec(specStr
);
2621 UnescapeFragment(specStr
, aURI
, specStr
);
2623 // find the file extension (if any)
2624 PRInt32 extLoc
= specStr
.RFindChar('.');
2625 PRInt32 specLength
= specStr
.Length();
2627 extLoc
!= specLength
- 1 &&
2628 // nothing over 20 chars long can be sanely considered an
2629 // extension.... Dat dere would be just data.
2630 specLength
- extLoc
< 20)
2632 return GetTypeFromExtension(Substring(specStr
, extLoc
+ 1), aContentType
);
2635 // We found no information; say so.
2636 return NS_ERROR_NOT_AVAILABLE
;
2639 NS_IMETHODIMP
nsExternalHelperAppService::GetTypeFromFile(nsIFile
* aFile
, nsACString
& aContentType
)
2642 nsCOMPtr
<nsIMIMEInfo
> info
;
2644 // Get the Extension
2645 nsAutoString fileName
;
2646 rv
= aFile
->GetLeafName(fileName
);
2647 if (NS_FAILED(rv
)) return rv
;
2649 nsCAutoString fileExt
;
2650 if (!fileName
.IsEmpty())
2652 PRInt32 len
= fileName
.Length();
2653 for (PRInt32 i
= len
; i
>= 0; i
--)
2655 if (fileName
[i
] == PRUnichar('.'))
2657 CopyUTF16toUTF8(fileName
.get() + i
+ 1, fileExt
);
2664 nsCOMPtr
<nsILocalFileMac
> macFile
;
2665 macFile
= do_QueryInterface( aFile
, &rv
);
2666 if (NS_SUCCEEDED( rv
) && fileExt
.IsEmpty())
2668 PRUint32 type
, creator
;
2669 macFile
->GetFileType( (OSType
*)&type
);
2670 macFile
->GetFileCreator( (OSType
*)&creator
);
2671 nsCOMPtr
<nsIInternetConfigService
> icService (do_GetService(NS_INTERNETCONFIGSERVICE_CONTRACTID
));
2674 rv
= icService
->GetMIMEInfoFromTypeCreator(type
, creator
, fileExt
.get(), getter_AddRefs(info
));
2675 if (NS_SUCCEEDED(rv
))
2676 return info
->GetMIMEType(aContentType
);
2681 // Windows, unix and mac when no type match occured.
2682 if (fileExt
.IsEmpty())
2683 return NS_ERROR_FAILURE
;
2684 return GetTypeFromExtension(fileExt
, aContentType
);
2687 nsresult
nsExternalHelperAppService::FillMIMEInfoForMimeTypeFromExtras(
2688 const nsACString
& aContentType
, nsIMIMEInfo
* aMIMEInfo
)
2690 NS_ENSURE_ARG( aMIMEInfo
);
2692 NS_ENSURE_ARG( !aContentType
.IsEmpty() );
2694 // Look for default entry with matching mime type.
2695 nsCAutoString
MIMEType(aContentType
);
2696 ToLowerCase(MIMEType
);
2697 PRInt32 numEntries
= NS_ARRAY_LENGTH(extraMimeEntries
);
2698 for (PRInt32 index
= 0; index
< numEntries
; index
++)
2700 if ( MIMEType
.Equals(extraMimeEntries
[index
].mMimeType
) )
2702 // This is the one. Set attributes appropriately.
2703 aMIMEInfo
->SetFileExtensions(nsDependentCString(extraMimeEntries
[index
].mFileExtensions
));
2704 aMIMEInfo
->SetDescription(NS_ConvertASCIItoUTF16(extraMimeEntries
[index
].mDescription
));
2705 aMIMEInfo
->SetMacType(extraMimeEntries
[index
].mMactype
);
2706 aMIMEInfo
->SetMacCreator(extraMimeEntries
[index
].mMacCreator
);
2712 return NS_ERROR_NOT_AVAILABLE
;
2715 nsresult
nsExternalHelperAppService::FillMIMEInfoForExtensionFromExtras(
2716 const nsACString
& aExtension
, nsIMIMEInfo
* aMIMEInfo
)
2719 PRBool found
= GetTypeFromExtras(aExtension
, type
);
2721 return NS_ERROR_NOT_AVAILABLE
;
2722 return FillMIMEInfoForMimeTypeFromExtras(type
, aMIMEInfo
);
2725 PRBool
nsExternalHelperAppService::GetTypeFromExtras(const nsACString
& aExtension
, nsACString
& aMIMEType
)
2727 NS_ASSERTION(!aExtension
.IsEmpty(), "Empty aExtension parameter!");
2729 // Look for default entry with matching extension.
2730 nsDependentCString::const_iterator start
, end
, iter
;
2731 PRInt32 numEntries
= NS_ARRAY_LENGTH(extraMimeEntries
);
2732 for (PRInt32 index
= 0; index
< numEntries
; index
++)
2734 nsDependentCString
extList(extraMimeEntries
[index
].mFileExtensions
);
2735 extList
.BeginReading(start
);
2736 extList
.EndReading(end
);
2738 while (start
!= end
)
2740 FindCharInReadable(',', iter
, end
);
2741 if (Substring(start
, iter
).Equals(aExtension
,
2742 nsCaseInsensitiveCStringComparator()))
2744 aMIMEType
= extraMimeEntries
[index
].mMimeType
;