Backed out changeset 30bfb150da06
[wine-gecko.git] / uriloader / exthandler / nsExternalHelperAppService.cpp
blobb8c5726818cbf232fd52a3455a59d84c4bb9e7a6
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
14 * License.
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.
23 * Contributor(s):
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"
46 #include "nsIURI.h"
47 #include "nsIURL.h"
48 #include "nsIFile.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"
57 #include "nsMemory.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"
80 #include "nsNetCID.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
92 #ifdef XP_MACOSX
93 #include "nsILocalFileMac.h"
94 #include "nsIInternetConfigService.h"
95 #include "nsIAppleFileDecoder.h"
96 #elif defined(XP_OS2)
97 #include "nsILocalFileOS2.h"
98 #endif
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"
119 #include "nsCRT.h"
121 #include "nsLocalHandlerApp.h"
123 #include "nsIRandomGenerator.h"
124 #include "plbase64.h"
125 #include "prmem.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"
133 enum {
134 NS_FOLDER_VALUE_DESKTOP = 0
135 , NS_FOLDER_VALUE_DOWNLOADS = 1
136 , NS_FOLDER_VALUE_CUSTOM = 2
139 #ifdef PR_LOGGING
140 PRLogModuleInfo* nsExternalHelperAppService::mLog = nsnull;
141 #endif
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
166 * will be used.
167 * @param aResult [out] Unescaped string.
169 static nsresult UnescapeFragment(const nsACString& aFragment, nsIURI* aURI,
170 nsAString& aResult)
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
188 * will be used.
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,
194 nsACString& aResult)
196 nsAutoString result;
197 nsresult rv = UnescapeFragment(aFragment, aURI, result);
198 if (NS_SUCCEEDED(rv))
199 CopyUTF16toUTF8(result, aResult);
200 return rv;
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));
213 if (httpChannel)
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);
243 if (!mimehdrpar) {
244 mimehdrpar = do_GetService(NS_MIMEHEADERPARAM_CONTRACTID);
245 if (!mimehdrpar)
246 return;
249 nsCOMPtr<nsIURL> url = do_QueryInterface(aURI);
251 nsCAutoString fallbackCharset;
252 if (url)
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,
260 nsnull, aFilename);
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
279 * was sent.
281 static PRBool GetFilenameAndExtensionFromChannel(nsIChannel* aChannel,
282 nsString& aFileName,
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
294 nsCAutoString disp;
295 ExtractDisposition(aChannel, disp);
296 PRBool handleExternally = PR_FALSE;
297 nsCOMPtr<nsIURI> uri;
298 nsresult rv;
299 aChannel->GetURI(getter_AddRefs(uri));
300 // content-disposition: has format:
301 // disposition-type < ; name=value >* < ; filename=value > < ; name=value >*
302 if (!disp.IsEmpty())
304 nsCOMPtr<nsIMIMEHeaderParam> mimehdrpar = do_GetService(NS_MIMEHEADERPARAM_CONTRACTID, &rv);
305 if (NS_FAILED(rv))
306 return PR_FALSE;
308 nsCAutoString fallbackCharset;
309 uri->GetOriginCharset(fallbackCharset);
310 // Get the disposition type
311 nsAutoString dispToken;
312 rv = mimehdrpar->GetParameter(disp, "", fallbackCharset, PR_TRUE,
313 nsnull, dispToken);
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?
318 if (NS_FAILED(rv) ||
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);
360 if (NS_FAILED(rv))
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;
395 #ifdef XP_MACOSX
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));
406 break;
407 case NS_FOLDER_VALUE_CUSTOM:
409 (void) prefs->GetComplexValue(NS_PREF_DOWNLOAD_DIR,
410 NS_GET_IID(nsIFile),
411 getter_AddRefs(dir));
412 if (!dir) break;
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);
420 if (NS_FAILED(rv)) {
421 dir = nsnull;
422 break;
425 break;
426 case NS_FOLDER_VALUE_DOWNLOADS:
427 // This is just the OS default location, so fall out
428 break;
431 if (!dir) {
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);
437 #else
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);
441 #endif
443 NS_ASSERTION(dir, "Somehow we didn't get a download directory!");
444 dir.forget(_directory);
445 return NS_OK;
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" },
466 { TEXT_XML, "xml" },
467 { APPLICATION_RDF, "rdf" },
468 { TEXT_XUL, "xul" },
469 { IMAGE_PNG, "png" },
470 // -- end extensions used during startup
471 { TEXT_CSS, "css" },
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;
490 PRUint32 mMactype;
491 PRUint32 mMacCreator;
494 #ifdef XP_MACOSX
495 #define MAC_TYPE(x) x
496 #else
497 #define MAC_TYPE(x) 0
498 #endif
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 [] =
509 #if defined(VMS)
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 },
513 #else
514 { APPLICATION_OCTET_STREAM, "exe,com,bin", "Binary File", 0, 0 },
515 #endif
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 }
543 #undef MAC_TYPE
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" }
557 NS_IMPL_ISUPPORTS6(
558 nsExternalHelperAppService,
559 nsIExternalHelperAppService,
560 nsPIExternalAppLauncher,
561 nsIExternalProtocolService,
562 nsIMIMEService,
563 nsIObserver,
564 nsISupportsWeakReference)
566 nsExternalHelperAppService::nsExternalHelperAppService()
568 gExtProtSvc = this;
570 nsresult nsExternalHelperAppService::Init()
572 // Add an observer for profile change
573 nsresult rv = NS_OK;
574 nsCOMPtr<nsIObserverService> obs = do_GetService("@mozilla.org/observer-service;1", &rv);
575 NS_ENSURE_SUCCESS(rv, rv);
577 #ifdef PR_LOGGING
578 if (!mLog) {
579 mLog = PR_NewLogModule("HelperAppService");
580 if (!mLog)
581 return NS_ERROR_OUT_OF_MEMORY;
583 #endif
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,
596 PRBool aForceSave,
597 nsIStreamListener ** aStreamListener)
599 nsAutoString fileName;
600 nsCAutoString fileExtension;
601 PRUint32 reason = nsIHelperAppLauncherDialog::REASON_CANTHANDLE;
602 nsresult rv;
604 // Get the file extension and name that we will need later
605 nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
606 if (channel) {
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);
611 if (httpChan) {
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);
627 if (url) {
628 nsCAutoString query;
630 // We only care about the query for HTTP and HTTPS URLs
631 PRBool isHTTP, isHTTPS;
632 rv = uri->SchemeIs("http", &isHTTP);
633 if (NS_FAILED(rv))
634 isHTTP = PR_FALSE;
635 rv = uri->SchemeIs("https", &isHTTPS);
636 if (NS_FAILED(rv))
637 isHTTPS = PR_FALSE;
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,
649 fileExtension,
650 allowURLExt);
651 LOG(("Found extension '%s' (filename is '%s', handling attachment: %i)",
652 fileExtension.get(), NS_ConvertUTF16toUTF8(fileName).get(),
653 isAttachment));
654 if (isAttachment)
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));
673 if (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);
687 if (channel)
688 channel->SetContentType(mimeType);
689 // Don't overwrite SERVERREQUEST
690 if (reason == nsIHelperAppLauncherDialog::REASON_CANTHANDLE)
691 reason = nsIHelperAppLauncherDialog::REASON_TYPESNIFFED;
693 else {
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.
700 if (!mimeInfo)
701 return NS_ERROR_OUT_OF_MEMORY;
703 *aStreamListener = nsnull;
704 // We want the mimeInfo's primary extension to pass it to
705 // nsExternalAppHandler
706 nsCAutoString buf;
707 mimeInfo->GetPrimaryExtension(buf);
709 nsExternalAppHandler * handler = new nsExternalAppHandler(mimeInfo,
710 buf,
711 aWindowContext,
712 fileName,
713 reason,
714 aForceSave);
715 if (!handler)
716 return NS_ERROR_OUT_OF_MEMORY;
717 NS_ADDREF(*aStreamListener = handler);
719 return NS_OK;
722 NS_IMETHODIMP nsExternalHelperAppService::ApplyDecodingForExtension(const nsACString& aExtension,
723 const nsACString& aEncodingType,
724 PRBool *aApplyDecoding)
726 *aApplyDecoding = PR_TRUE;
727 PRUint32 i;
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;
732 break;
735 return NS_OK;
738 nsresult nsExternalHelperAppService::GetFileTokenForPath(const PRUnichar * aPlatformAppPath,
739 nsIFile ** aFile)
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)) {
746 *aFile = localFile;
747 PRBool exists;
748 if (NS_FAILED((*aFile)->Exists(&exists)) || !exists) {
749 NS_RELEASE(*aFile);
750 return NS_ERROR_FILE_NOT_FOUND;
752 return NS_OK;
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)
764 return NS_OK;
766 NS_RELEASE(*aFile);
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));
788 PRUint32 length;
789 possibleHandlers->GetLength(&length);
790 if (length) {
791 *aHandlerExists = PR_TRUE;
792 return NS_OK;
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
803 // default behavior.
804 *aResult = PR_FALSE;
806 nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
807 if (prefs)
809 PRBool val;
810 nsresult rv;
812 // check the per protocol setting first. it always takes precidence.
813 // if not set, then use the global setting.
815 nsCAutoString name;
816 name = NS_LITERAL_CSTRING("network.protocol-handler.expose.")
817 + nsDependentCString(aProtocolScheme);
818 rv = prefs->GetBoolPref(name.get(), &val);
819 if (NS_SUCCEEDED(rv))
821 *aResult = val;
823 else
825 rv = prefs->GetBoolPref("network.protocol-handler.expose-all", &val);
826 if (NS_SUCCEEDED(rv) && val)
827 *aResult = PR_TRUE;
830 return NS_OK;
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";
841 NS_IMETHODIMP
842 nsExternalHelperAppService::LoadURI(nsIURI *aURI,
843 nsIInterfaceRequestor *aWindowContext)
845 NS_ENSURE_ARG_POINTER(aURI);
847 nsCAutoString spec;
848 aURI->GetSpec(spec);
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));
867 if (!prefs)
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);
875 if (NS_FAILED(rv))
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)
920 nsresult rv = NS_OK;
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);
931 return NS_OK;
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];
946 if (localFile) {
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();
955 return NS_OK;
958 static const char kExternalWarningPrefPrefix[] =
959 "network.protocol-handler.warn-external.";
960 static const char kExternalWarningDefaultPref[] =
961 "network.protocol-handler.warn-external-default";
963 NS_IMETHODIMP
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?)
970 PRBool exists;
971 nsresult rv = GetProtocolHandlerInfoFromOS(aScheme, &exists, aHandlerInfo);
972 if (NS_FAILED(rv)) {
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);
978 if (handlerSvc) {
979 PRBool hasHandler = PR_FALSE;
980 (void) handlerSvc->Exists(*aHandlerInfo, &hasHandler);
981 if (hasHandler) {
982 rv = handlerSvc->FillHandlerInfo(*aHandlerInfo, EmptyCString());
983 if (NS_SUCCEEDED(rv))
984 return NS_OK;
988 return SetProtocolHandlerDefaults(*aHandlerInfo, exists);
991 NS_IMETHODIMP
992 nsExternalHelperAppService::GetProtocolHandlerInfoFromOS(const nsACString &aScheme,
993 PRBool *found,
994 nsIHandlerInfo **aHandlerInfo)
996 // intended to be implemented by the subclass
997 return NS_ERROR_NOT_IMPLEMENTED;
1000 NS_IMETHODIMP
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,
1005 // if one exists
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));
1013 if (!prefs)
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);
1028 } else {
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);
1035 return NS_OK;
1038 // XPCOM profile change observer
1039 NS_IMETHODIMP
1040 nsExternalHelperAppService::Observe(nsISupports *aSubject, const char *aTopic, const PRUnichar *someData )
1042 if (!strcmp(aTopic, "profile-before-change")) {
1043 ExpungeTemporaryFiles();
1045 return NS_OK;
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)
1079 , mReason(aReason)
1080 , mContentLength(-1)
1081 , mProgress(0)
1082 , mRequest(nsnull)
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();
1126 return NS_OK;
1129 NS_IMETHODIMP nsExternalAppHandler::GetTargetFile(nsIFile** aTarget)
1131 if (mFinalFileDestination)
1132 *aTarget = mFinalFileDestination;
1133 else
1134 *aTarget = mTempFile;
1136 NS_IF_ADDREF(*aTarget);
1137 return NS_OK;
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;
1148 return NS_OK;
1151 NS_IMETHODIMP nsExternalAppHandler::GetTimeDownloadStarted(PRTime* aTime)
1153 *aTime = mTimeDownloadStarted;
1154 return NS_OK;
1157 NS_IMETHODIMP nsExternalAppHandler::GetContentLength(PRInt64 *aContentLength)
1159 *aContentLength = mContentLength;
1160 return NS_OK;
1163 NS_IMETHODIMP nsExternalAppHandler::CloseProgressWindow()
1165 // release extra state...
1166 mWebProgressListener = nsnull;
1167 return NS_OK;
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);
1175 if (!aChannel)
1176 return;
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));
1197 if(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
1216 * and executed.
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
1243 // file.
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);
1263 PRUint8 *buffer;
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);
1269 NS_Free(buffer);
1270 buffer = nsnull;
1272 if (!b64)
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);
1279 PR_Free(b64);
1280 b64 = nsnull;
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.
1287 nsCAutoString ext;
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);
1296 #ifdef XP_WIN
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);
1313 #endif
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);
1325 #ifndef XP_WIN
1326 // On other platforms, the file permission bits are used, so we can just call
1327 // IsExecutable
1328 mTempFile->IsExecutable(&mTempFileIsExecutable);
1329 #endif
1331 #ifdef XP_MACOSX
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);
1336 if (macfile) {
1337 PRUint32 type;
1338 mMimeInfo->GetMacType(&type);
1339 macfile->SetFileType(type);
1342 #endif
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);
1349 return rv;
1352 mOutStream = NS_BufferOutputStream(outputStream, BUFFERED_OUTPUT_SIZE);
1354 #ifdef XP_MACOSX
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);
1368 #endif
1370 return 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();
1381 mRequest = request;
1383 nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request);
1385 nsresult rv;
1387 nsCOMPtr<nsIFileChannel> fileChan(do_QueryInterface(request));
1388 mIsFileChannel = fileChan != nsnull;
1390 // Get content length
1391 nsCOMPtr<nsIPropertyBag2> props(do_QueryInterface(request, &rv));
1392 if (props) {
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) {
1398 PRInt32 len;
1399 aChannel->GetContentLength(&len);
1400 mContentLength = len;
1403 // Determine whether a new window was opened specifically for this request
1404 if (props) {
1405 PRBool tmp = PR_FALSE;
1406 props->GetPropertyAsBool(NS_LITERAL_STRING("docshell.newWindowTarget"),
1407 &tmp);
1408 mShouldCloseWindow = tmp;
1411 // Now get the URI
1412 if (aChannel)
1414 aChannel->GetURI(getter_AddRefs(mSourceUrl));
1417 rv = SetUpTempFile(aChannel);
1418 if (NS_FAILED(rv)) {
1419 mCanceled = PR_TRUE;
1420 request->Cancel(rv);
1421 nsAutoString path;
1422 if (mTempFile)
1423 mTempFile->GetPath(path);
1424 SendStatusChange(kWriteError, rv, request, path);
1425 return NS_OK;
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));
1438 if (httpChannel) {
1439 nsCAutoString refreshHeader;
1440 httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("refresh"),
1441 refreshHeader);
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
1450 MaybeCloseWindow();
1452 nsCOMPtr<nsIEncodedChannel> encChannel = do_QueryInterface( aChannel );
1453 if (encChannel)
1455 // Turn off content encoding conversions if needed
1456 PRBool applyConversion = PR_TRUE;
1458 nsCOMPtr<nsIURL> sourceURL(do_QueryInterface(mSourceUrl));
1459 if (sourceURL)
1461 nsCAutoString extension;
1462 sourceURL->GetFileExtension(extension);
1463 if (!extension.IsEmpty())
1465 nsCOMPtr<nsIUTF8StringEnumerator> encEnum;
1466 encChannel->GetContentEncodings(getter_AddRefs(encEnum));
1467 if (encEnum)
1469 PRBool hasMore;
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,
1479 &applyConversion);
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);
1505 if (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);
1516 if (handlerSvc)
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
1547 if (mForceSave) {
1548 alwaysAsk = PR_FALSE;
1549 action = nsIMIMEInfo::saveToDisk;
1552 if (alwaysAsk)
1554 // do this first! make sure we don't try to take an action until the user tells us what they want to do
1555 // with it...
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....
1569 else
1571 mReceivedDispositionInfo = PR_TRUE; // no need to wait for a response from the user
1573 // We need to do the save/open immediately, then.
1574 #ifdef XP_WIN
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
1578 * helper app.
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));
1589 if (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;
1601 #endif
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));
1615 if (dh) {
1616 nsCOMPtr<nsIURI> referrer;
1617 if (aChannel)
1618 NS_GetReferrerFromChannel(aChannel, getter_AddRefs(referrer));
1619 dh->AddDownload(mSourceUrl, referrer, mTimeDownloadStarted);
1622 return NS_OK;
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)
1629 nsAutoString msgId;
1630 switch(rv)
1632 case NS_ERROR_OUT_OF_MEMORY:
1633 // No memory
1634 msgId.AssignLiteral("noMemory");
1635 break;
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");
1641 break;
1643 case NS_ERROR_FILE_READ_ONLY:
1644 // Attempt to write to read/only file.
1645 msgId.AssignLiteral("readOnly");
1646 break;
1648 case NS_ERROR_FILE_ACCESS_DENIED:
1649 if (type == kWriteError) {
1650 // Attempt to write without sufficient permissions.
1651 msgId.AssignLiteral("accessError");
1653 else
1655 msgId.AssignLiteral("launchError");
1657 break;
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");
1665 break;
1667 // fall through
1669 default:
1670 // Generic read/write/launch error message.
1671 switch(type)
1673 case kReadError:
1674 msgId.AssignLiteral("readError");
1675 break;
1676 case kWriteError:
1677 msgId.AssignLiteral("writeError");
1678 break;
1679 case kLaunchError:
1680 msgId.AssignLiteral("launchError");
1681 break;
1683 break;
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);
1693 if (s)
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);
1707 else
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(),
1713 strings,
1715 getter_Copies(title));
1716 if (prompter)
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;
1739 mProgress += count;
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
1749 else
1750 count = 0;
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
1756 // error code.
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);
1784 else
1786 // An error occurred, notify listener.
1787 nsAutoString tempFilePath;
1788 if (mTempFile)
1789 mTempFile->GetPath(tempFilePath);
1790 SendStatusChange(readError ? kReadError : kWriteError, rv, request, tempFilePath);
1792 // Cancel the download.
1793 Cancel(rv);
1796 return rv;
1799 NS_IMETHODIMP nsExternalAppHandler::OnStopRequest(nsIRequest *request, nsISupports *aCtxt,
1800 nsresult aStatus)
1802 mStopRequestIssued = PR_TRUE;
1803 mRequest = nsnull;
1804 // Cancel if the request did not complete successfully.
1805 if (!mCanceled && NS_FAILED(aStatus))
1807 // Send error notification.
1808 nsAutoString tempFilePath;
1809 if (mTempFile)
1810 mTempFile->GetPath(tempFilePath);
1811 SendStatusChange( kReadError, aStatus, request, tempFilePath );
1813 Cancel(aStatus);
1816 // first, check to see if we've been canceled....
1817 if (mCanceled)
1818 return NS_OK;
1820 // close the stream...
1821 if (mOutStream)
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;
1837 return NS_OK;
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)
1879 if (!mCanceled)
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);
1890 return rv;
1893 NS_IMETHODIMP nsExternalAppHandler::GetMIMEInfo(nsIMIMEInfo ** aMIMEInfo)
1895 *aMIMEInfo = mMimeInfo;
1896 NS_ADDREF(*aMIMEInfo);
1897 return NS_OK;
1900 NS_IMETHODIMP nsExternalAppHandler::GetSource(nsIURI ** aSourceURI)
1902 NS_ENSURE_ARG(aSourceURI);
1903 *aSourceURI = mSourceUrl;
1904 NS_IF_ADDREF(*aSourceURI);
1905 return NS_OK;
1908 NS_IMETHODIMP nsExternalAppHandler::GetSuggestedFileName(nsAString& aSuggestedFileName)
1910 aSuggestedFileName = mSuggestedFileName;
1911 return NS_OK;
1914 nsresult nsExternalAppHandler::InitializeDownload(nsITransfer* aTransfer)
1916 nsresult rv;
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;
1927 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.
1936 mDialog = nsnull;
1937 nsresult rv;
1939 nsCOMPtr<nsITransfer> tr = do_CreateInstance(NS_TRANSFER_CONTRACTID, &rv);
1940 if (NS_SUCCEEDED(rv))
1941 InitializeDownload(tr);
1943 if (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
1953 // OnStopRequest.
1954 SetWebProgressListener(tr);
1956 return rv;
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;
1965 if (!mDialog)
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.
1979 // See Bug 249143
1980 nsRefPtr<nsExternalAppHandler> kungFuDeathGrip(this);
1981 nsCOMPtr<nsIHelperAppLauncherDialog> dlg(mDialog);
1982 rv = mDialog->PromptForSaveToFile(this,
1983 mWindowContext,
1984 aDefaultFile.get(),
1985 aFileExtension.get(),
1986 mForceSave, aNewFile);
1987 return rv;
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);
2020 if (NS_FAILED(rv))
2022 // Send error notification.
2023 nsAutoString path;
2024 fileToUse->GetPath(path);
2025 SendStatusChange(kWriteError, rv, nsnull, path);
2026 Cancel(rv); // Cancel (and clean up temp file).
2028 #if defined(XP_OS2)
2029 else
2031 // tag the file with its source URI
2032 nsCOMPtr<nsILocalFileOS2> localFileOS2 = do_QueryInterface(fileToUse);
2033 if (localFileOS2)
2035 nsCAutoString url;
2036 mSourceUrl->GetSpec(url);
2037 localFileOS2->SetFileSource(url);
2040 #endif
2043 return rv;
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;
2057 if (mCanceled)
2058 return 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);
2066 if (!fileToUse)
2068 nsAutoString leafName;
2069 mTempFile->GetLeafName(leafName);
2070 if (mSuggestedFileName.IsEmpty())
2071 rv = PromptForSaveToFile(getter_AddRefs(fileToUse), leafName, mTempFileExtension);
2072 else
2074 nsAutoString fileExt;
2075 PRInt32 pos = mSuggestedFileName.RFindChar('.');
2076 if (pos >= 0)
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));
2099 if (movedFile) {
2100 // Get the old leaf name and append .part to it
2101 nsAutoString name;
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.
2119 nsAutoString path;
2120 mTempFile->GetPath(path);
2121 SendStatusChange(kWriteError, rv, nsnull, path);
2122 Cancel(rv);
2123 return NS_OK;
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();
2139 return NS_OK;
2143 nsresult nsExternalAppHandler::OpenWithApplication()
2145 nsresult rv = NS_OK;
2146 if (mCanceled)
2147 return 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;
2165 #else
2166 deleteTempFileOnExit = PR_FALSE;
2167 #endif
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);
2176 if (NS_FAILED(rv))
2178 // Send error notification.
2179 nsAutoString path;
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);
2190 return rv;
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)
2200 if (mCanceled)
2201 return NS_OK;
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))
2226 return NS_OK;
2228 nsAutoString path;
2229 if (file)
2230 file->GetPath(path);
2231 // If we get here, an error happened
2232 SendStatusChange(kLaunchError, rv, nsnull, path);
2233 return rv;
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);
2251 #ifdef XP_WIN
2252 fileToUse->Append(mSuggestedFileName + mTempFileExtension);
2253 #else
2254 fileToUse->Append(mSuggestedFileName);
2255 #endif
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();
2265 return NS_OK;
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
2275 // OnStartRequest)
2276 mDialog = nsnull;
2277 // shutdown our stream to the temp file
2278 if (mOutStream)
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);
2291 mTempFile = nsnull;
2294 // Release the listener, to break the reference cycle with it (we are the
2295 // observer of the listener).
2296 mWebProgressListener = nsnull;
2298 return NS_OK;
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.
2323 nsresult rv;
2324 nsCOMPtr<nsIPrefService> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
2325 nsCOMPtr<nsIPrefBranch> prefBranch;
2326 if (prefs)
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))
2340 return PR_FALSE;
2343 // Default is true, if not found in the pref string.
2344 return PR_TRUE;
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));
2359 PRBool isClosed;
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");
2367 if (!mTimer) {
2368 return NS_ERROR_FAILURE;
2371 mTimer->InitWithCallback(this, 0, nsITimer::TYPE_ONE_SHOT);
2372 mWindowToClose = internalWindow;
2376 return NS_OK;
2379 NS_IMETHODIMP
2380 nsExternalAppHandler::Notify(nsITimer* timer)
2382 NS_ASSERTION(mWindowToClose, "No window to close after timer fired");
2384 mWindowToClose->Close();
2385 mWindowToClose = nsnull;
2386 mTimer = nsnull;
2388 return NS_OK;
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()));
2404 *_retval = nsnull;
2406 // OK... we need a type. Get one.
2407 nsCAutoString typeToUse(aMIMEType);
2408 if (typeToUse.IsEmpty()) {
2409 nsresult rv = GetTypeFromExtension(aFileExt, typeToUse);
2410 if (NS_FAILED(rv))
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
2418 PRBool found;
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.
2422 if (!*_retval)
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)
2428 nsresult rv;
2429 nsCOMPtr<nsIHandlerService> handlerSvc = do_GetService(NS_HANDLERSERVICE_CONTRACTID);
2430 if (handlerSvc) {
2431 PRBool hasHandler = PR_FALSE;
2432 (void) handlerSvc->Exists(*_retval, &hasHandler);
2433 if (hasHandler) {
2434 rv = handlerSvc->FillHandlerInfo(*_retval, EmptyCString());
2435 LOG(("Data source: Via type: retval 0x%08x\n", rv));
2436 } else {
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.
2460 if (!found) {
2461 rv = NS_ERROR_FAILURE;
2462 #ifdef XP_WIN
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
2468 * useless....
2470 if (!typeToUse.Equals(APPLICATION_OCTET_STREAM, nsCaseInsensitiveCStringComparator()))
2471 #endif
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));
2487 if (matches)
2488 (*_retval)->SetPrimaryExtension(aFileExt);
2491 #ifdef PR_LOGGING
2492 if (LOG_ENABLED()) {
2493 nsCAutoString type;
2494 (*_retval)->GetMIMEType(type);
2496 nsCAutoString ext;
2497 (*_retval)->GetPrimaryExtension(ext);
2498 LOG(("MIME Info Summary: Type '%s', Primary Ext '%s'\n", type.get(), ext.get()));
2500 #endif
2502 return NS_OK;
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;
2521 return rv;
2525 // Check user-set prefs
2526 nsCOMPtr<nsIHandlerService> handlerSvc = do_GetService(NS_HANDLERSERVICE_CONTRACTID);
2527 if (handlerSvc)
2528 rv = handlerSvc->GetTypeFromExtension(aFileExt, aContentType);
2529 if (NS_SUCCEEDED(rv) && !aContentType.IsEmpty())
2530 return NS_OK;
2532 // Ask OS.
2533 PRBool found = PR_FALSE;
2534 nsCOMPtr<nsIMIMEInfo> mi = GetMIMEInfoFromOS(EmptyCString(), aFileExt, &found);
2535 if (mi && found)
2536 return mi->GetMIMEType(aContentType);
2538 // Check extras array.
2539 found = GetTypeFromExtras(aFileExt, aContentType);
2540 if (found)
2541 return NS_OK;
2543 const nsCString& flatExt = PromiseFlatCString(aFileExt);
2544 // Try the plugins
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;
2551 return NS_OK;
2555 rv = NS_OK;
2556 // Let's see if an extension added something
2557 nsCOMPtr<nsICategoryManager> catMan(do_GetService("@mozilla.org/categorymanager;1"));
2558 if (catMan) {
2559 nsXPIDLCString type;
2560 rv = catMan->GetCategoryEntry("ext-to-type-mapping", flatExt.get(), getter_Copies(type));
2561 aContentType = type;
2563 else {
2564 rv = NS_ERROR_NOT_AVAILABLE;
2567 return rv;
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));
2576 if (NS_FAILED(rv))
2577 return rv;
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);
2589 if (fileUrl) {
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!
2596 return rv;
2601 // Now try to get an nsIURL so we don't have to do our own parsing
2602 nsCOMPtr<nsIURL> url = do_QueryInterface(aURI);
2603 if (url) {
2604 nsCAutoString ext;
2605 rv = url->GetFileExtension(ext);
2606 if (NS_FAILED(rv))
2607 return rv;
2608 if (ext.IsEmpty())
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);
2619 if (NS_FAILED(rv))
2620 return rv;
2621 UnescapeFragment(specStr, aURI, specStr);
2623 // find the file extension (if any)
2624 PRInt32 extLoc = specStr.RFindChar('.');
2625 PRInt32 specLength = specStr.Length();
2626 if (-1 != extLoc &&
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)
2641 nsresult rv;
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);
2658 break;
2663 #ifdef XP_MACOSX
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));
2672 if (icService)
2674 rv = icService->GetMIMEInfoFromTypeCreator(type, creator, fileExt.get(), getter_AddRefs(info));
2675 if (NS_SUCCEEDED(rv))
2676 return info->GetMIMEType(aContentType);
2679 #endif
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);
2708 return NS_OK;
2712 return NS_ERROR_NOT_AVAILABLE;
2715 nsresult nsExternalHelperAppService::FillMIMEInfoForExtensionFromExtras(
2716 const nsACString& aExtension, nsIMIMEInfo * aMIMEInfo)
2718 nsCAutoString type;
2719 PRBool found = GetTypeFromExtras(aExtension, type);
2720 if (!found)
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);
2737 iter = start;
2738 while (start != end)
2740 FindCharInReadable(',', iter, end);
2741 if (Substring(start, iter).Equals(aExtension,
2742 nsCaseInsensitiveCStringComparator()))
2744 aMIMEType = extraMimeEntries[index].mMimeType;
2745 return PR_TRUE;
2747 if (iter != end) {
2748 ++iter;
2750 start = iter;
2754 return PR_FALSE;