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