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