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