Bug 1890689 apply drift correction to input rate instead of output rate r=pehrsons
[gecko.git] / uriloader / exthandler / unix / nsOSHelperAppService.cpp
blobfcc2e9f5aba3f76c0c3fea8349d388dc4a1da173
1 /* -*- Mode: C++; tab-width: 3; indent-tabs-mode: nil; c-basic-offset: 2 -*-
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/. */
7 #include <sys/types.h>
8 #include <sys/stat.h>
10 #include "nsOSHelperAppService.h"
11 #include "nsMIMEInfoUnix.h"
12 #ifdef MOZ_WIDGET_GTK
13 # include "nsGNOMERegistry.h"
14 # ifdef MOZ_BUILD_APP_IS_BROWSER
15 # include "nsIToolkitShellService.h"
16 # include "nsIGNOMEShellService.h"
17 # endif
18 #endif
19 #include "nsISupports.h"
20 #include "nsString.h"
21 #include "nsReadableUtils.h"
22 #include "nsUnicharUtils.h"
23 #include "nsIFileStreams.h"
24 #include "nsILineInputStream.h"
25 #include "nsIFile.h"
26 #include "nsIProcess.h"
27 #include "nsNetCID.h"
28 #include "nsXPCOM.h"
29 #include "nsComponentManagerUtils.h"
30 #include "nsCRT.h"
31 #include "nsDirectoryServiceDefs.h"
32 #include "nsDirectoryServiceUtils.h"
33 #include "nsXULAppAPI.h"
34 #include "ContentHandlerService.h"
35 #include "prenv.h" // for PR_GetEnv()
36 #include "mozilla/EnumeratedRange.h"
37 #include "mozilla/Preferences.h"
38 #include "mozilla/ClearOnShutdown.h"
39 #include "nsMimeTypes.h"
40 #include <mutex>
42 using namespace mozilla;
44 #define LOG(...) \
45 MOZ_LOG(nsOSHelperAppService::sLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
46 #define LOG_ENABLED() \
47 MOZ_LOG_TEST(nsOSHelperAppService::sLog, mozilla::LogLevel::Debug)
49 static nsresult FindSemicolon(nsAString::const_iterator& aSemicolon_iter,
50 const nsAString::const_iterator& aEnd_iter);
51 static nsresult ParseMIMEType(const nsAString::const_iterator& aStart_iter,
52 nsAString::const_iterator& aMajorTypeStart,
53 nsAString::const_iterator& aMajorTypeEnd,
54 nsAString::const_iterator& aMinorTypeStart,
55 nsAString::const_iterator& aMinorTypeEnd,
56 const nsAString::const_iterator& aEnd_iter);
58 inline bool IsNetscapeFormat(const nsACString& aBuffer);
60 nsOSHelperAppService::~nsOSHelperAppService() {}
63 * Take a command with all the mailcap escapes in it and unescape it
64 * Ideally this needs the mime type, mime type options, and location of the
65 * temporary file, but this last can't be got from here
67 // static
68 nsresult nsOSHelperAppService::UnescapeCommand(const nsAString& aEscapedCommand,
69 const nsAString& aMajorType,
70 const nsAString& aMinorType,
71 nsACString& aUnEscapedCommand) {
72 LOG("-- UnescapeCommand");
73 LOG("Command to escape: '%s'\n",
74 NS_LossyConvertUTF16toASCII(aEscapedCommand).get());
75 // XXX This function will need to get the mime type and various stuff like
76 // that being passed in to work properly
78 LOG(
79 ("UnescapeCommand really needs some work -- it should actually do some "
80 "unescaping\n"));
82 CopyUTF16toUTF8(aEscapedCommand, aUnEscapedCommand);
83 LOG("Escaped command: '%s'\n", PromiseFlatCString(aUnEscapedCommand).get());
84 return NS_OK;
87 /* Put aSemicolon_iter at the first non-escaped semicolon after
88 * aStart_iter but before aEnd_iter
91 static nsresult FindSemicolon(nsAString::const_iterator& aSemicolon_iter,
92 const nsAString::const_iterator& aEnd_iter) {
93 bool semicolonFound = false;
94 while (aSemicolon_iter != aEnd_iter && !semicolonFound) {
95 switch (*aSemicolon_iter) {
96 case '\\':
97 aSemicolon_iter.advance(2);
98 break;
99 case ';':
100 semicolonFound = true;
101 break;
102 default:
103 ++aSemicolon_iter;
104 break;
107 return NS_OK;
110 static nsresult ParseMIMEType(const nsAString::const_iterator& aStart_iter,
111 nsAString::const_iterator& aMajorTypeStart,
112 nsAString::const_iterator& aMajorTypeEnd,
113 nsAString::const_iterator& aMinorTypeStart,
114 nsAString::const_iterator& aMinorTypeEnd,
115 const nsAString::const_iterator& aEnd_iter) {
116 nsAString::const_iterator iter(aStart_iter);
118 // skip leading whitespace
119 while (iter != aEnd_iter && nsCRT::IsAsciiSpace(*iter)) {
120 ++iter;
123 if (iter == aEnd_iter) {
124 return NS_ERROR_INVALID_ARG;
127 aMajorTypeStart = iter;
129 // find major/minor separator ('/')
130 while (iter != aEnd_iter && *iter != '/') {
131 ++iter;
134 if (iter == aEnd_iter) {
135 return NS_ERROR_INVALID_ARG;
138 aMajorTypeEnd = iter;
140 // skip '/'
141 ++iter;
143 if (iter == aEnd_iter) {
144 return NS_ERROR_INVALID_ARG;
147 aMinorTypeStart = iter;
149 // find end of minor type, delimited by whitespace or ';'
150 while (iter != aEnd_iter && !nsCRT::IsAsciiSpace(*iter) && *iter != ';') {
151 ++iter;
154 aMinorTypeEnd = iter;
156 return NS_OK;
159 // TODO: We should consider not only caching the file location but maybe the
160 // file contents too?
161 enum class FileKind {
162 PrivateMimeTypes = 0,
163 GlobalMimeTypes,
164 PrivateMailCap,
165 GlobalMailCap,
167 Count,
170 struct FileLocationCache {
171 struct Entry {
172 bool mIsCached = false;
173 nsresult mResult = NS_OK;
174 nsString mLocation;
176 void Clear() { *this = {}; }
179 EnumeratedArray<FileKind, Entry, size_t(FileKind::Count)> mEntries;
181 static const char* PrefFor(FileKind aKind) {
182 switch (aKind) {
183 case FileKind::GlobalMimeTypes:
184 return "helpers.global_mime_types_file";
185 case FileKind::PrivateMimeTypes:
186 return "helpers.private_mime_types_file";
187 case FileKind::GlobalMailCap:
188 return "helpers.global_mailcap_file";
189 case FileKind::PrivateMailCap:
190 return "helpers.private_mailcap_file";
191 case FileKind::Count:
192 break;
194 MOZ_ASSERT_UNREACHABLE("Unknown file kind");
195 return "";
198 void Clear() {
199 for (auto& entry : mEntries) {
200 entry.Clear();
204 static void PrefChangeCallback(const char*, void*) { Get().Clear(); }
206 static FileLocationCache& Get() {
207 static FileLocationCache sCache;
208 static std::once_flag flag;
209 std::call_once(flag, [&] {
210 for (auto kind :
211 MakeEnumeratedRange(FileKind::PrivateMimeTypes, FileKind::Count)) {
212 Preferences::RegisterCallback(PrefChangeCallback,
213 nsDependentCString(PrefFor(kind)));
215 RunOnShutdown([] {
216 sCache.Clear();
217 for (auto kind :
218 MakeEnumeratedRange(FileKind::PrivateMimeTypes, FileKind::Count)) {
219 Preferences::UnregisterCallback(PrefChangeCallback,
220 nsDependentCString(PrefFor(kind)));
224 return sCache;
227 Entry& EntryFor(FileKind aKind) { return mEntries[aKind]; }
230 // The lookup order is:
231 // 1) user pref
232 // 2) env var (if any)
233 // 3) pref
234 static nsresult DoGetFileLocation(FileKind aKind, nsAString& aFileLocation) {
235 LOG("-- GetFileLocation(%d)\n", int(aKind));
237 aFileLocation.Truncate();
239 const char* envVar = [&]() -> const char* {
240 switch (aKind) {
241 case FileKind::PrivateMailCap:
242 return "PERSONAL_MAILCAP";
243 case FileKind::GlobalMailCap:
244 return "MAILCAP";
245 default:
246 return nullptr;
248 }();
250 const char* prefName = FileLocationCache::PrefFor(aKind);
252 if (envVar) {
253 // If we have an env var we should check whether the pref is a user pref. If
254 // we do not, we don't care.
255 if (Preferences::HasUserValue(prefName) &&
256 NS_SUCCEEDED(Preferences::GetString(prefName, aFileLocation))) {
257 return NS_OK;
260 char* envValue = PR_GetEnv(envVar);
261 if (envValue && *envValue) {
262 // the pref is in the system charset and it's a filepath... The
263 // natural way to do the charset conversion is by just initing
264 // an nsIFile with the native path and asking it for the Unicode
265 // version.
266 nsresult rv;
267 nsCOMPtr<nsIFile> file(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
268 NS_ENSURE_SUCCESS(rv, rv);
270 rv = file->InitWithNativePath(nsDependentCString(envValue));
271 NS_ENSURE_SUCCESS(rv, rv);
273 return file->GetPath(aFileLocation);
277 return Preferences::GetString(prefName, aFileLocation);
280 static nsresult GetFileLocation(FileKind aKind, nsAString& aFileLocation) {
281 MOZ_ASSERT(NS_IsMainThread());
282 auto& entry = FileLocationCache::Get().EntryFor(aKind);
283 if (!entry.mIsCached) {
284 entry.mIsCached = true;
285 entry.mResult = DoGetFileLocation(aKind, entry.mLocation);
287 aFileLocation = entry.mLocation;
288 return entry.mResult;
291 /* Get the mime.types file names from prefs and look up info in them
292 based on extension */
293 // static
294 nsresult nsOSHelperAppService::LookUpTypeAndDescription(
295 const nsAString& aFileExtension, nsAString& aMajorType,
296 nsAString& aMinorType, nsAString& aDescription, bool aUserData) {
297 LOG("-- LookUpTypeAndDescription for extension '%s'\n",
298 NS_LossyConvertUTF16toASCII(aFileExtension).get());
299 nsAutoString mimeFileName;
301 auto kind =
302 aUserData ? FileKind::PrivateMimeTypes : FileKind::GlobalMimeTypes;
303 nsresult rv = GetFileLocation(kind, mimeFileName);
304 if (NS_SUCCEEDED(rv) && !mimeFileName.IsEmpty()) {
305 rv = GetTypeAndDescriptionFromMimetypesFile(
306 mimeFileName, aFileExtension, aMajorType, aMinorType, aDescription);
307 } else {
308 rv = NS_ERROR_NOT_AVAILABLE;
311 return rv;
314 inline bool IsNetscapeFormat(const nsACString& aBuffer) {
315 return StringBeginsWith(
316 aBuffer,
317 nsLiteralCString(
318 "#--Netscape Communications Corporation MIME Information")) ||
319 StringBeginsWith(aBuffer, "#--MCOM MIME Information"_ns);
323 * Create a file stream and line input stream for the filename.
324 * Leaves the first line of the file in aBuffer and sets the format to
325 * true for netscape files and false for normail ones
327 // static
328 nsresult nsOSHelperAppService::CreateInputStream(
329 const nsAString& aFilename, nsIFileInputStream** aFileInputStream,
330 nsILineInputStream** aLineInputStream, nsACString& aBuffer,
331 bool* aNetscapeFormat, bool* aMore) {
332 LOG("-- CreateInputStream");
333 nsresult rv = NS_OK;
335 nsCOMPtr<nsIFile> file(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
336 if (NS_FAILED(rv)) return rv;
337 rv = file->InitWithPath(aFilename);
338 if (NS_FAILED(rv)) return rv;
340 nsCOMPtr<nsIFileInputStream> fileStream(
341 do_CreateInstance(NS_LOCALFILEINPUTSTREAM_CONTRACTID, &rv));
342 if (NS_FAILED(rv)) return rv;
343 rv = fileStream->Init(file, -1, -1, false);
344 if (NS_FAILED(rv)) return rv;
346 nsCOMPtr<nsILineInputStream> lineStream(do_QueryInterface(fileStream, &rv));
348 if (NS_FAILED(rv)) {
349 LOG("Interface trouble in stream land!");
350 return rv;
353 rv = lineStream->ReadLine(aBuffer, aMore);
354 if (NS_FAILED(rv)) {
355 fileStream->Close();
356 return rv;
359 *aNetscapeFormat = IsNetscapeFormat(aBuffer);
361 *aFileInputStream = fileStream;
362 NS_ADDREF(*aFileInputStream);
363 *aLineInputStream = lineStream;
364 NS_ADDREF(*aLineInputStream);
366 return NS_OK;
369 /* Open the file, read the first line, decide what type of file it is,
370 then get info based on extension */
371 // static
372 nsresult nsOSHelperAppService::GetTypeAndDescriptionFromMimetypesFile(
373 const nsAString& aFilename, const nsAString& aFileExtension,
374 nsAString& aMajorType, nsAString& aMinorType, nsAString& aDescription) {
375 LOG("-- GetTypeAndDescriptionFromMimetypesFile\n");
376 LOG("Getting type and description from types file '%s'\n",
377 NS_LossyConvertUTF16toASCII(aFilename).get());
378 LOG("Using extension '%s'\n",
379 NS_LossyConvertUTF16toASCII(aFileExtension).get());
380 nsCOMPtr<nsIFileInputStream> mimeFile;
381 nsCOMPtr<nsILineInputStream> mimeTypes;
382 bool netscapeFormat;
383 nsAutoString buf;
384 nsAutoCString cBuf;
385 bool more = false;
386 nsresult rv = CreateInputStream(aFilename, getter_AddRefs(mimeFile),
387 getter_AddRefs(mimeTypes), cBuf,
388 &netscapeFormat, &more);
390 if (NS_FAILED(rv)) {
391 return rv;
394 nsAutoString extensions;
395 nsAutoStringN<101> entry;
396 nsAString::const_iterator majorTypeStart, majorTypeEnd, minorTypeStart,
397 minorTypeEnd, descriptionStart, descriptionEnd;
399 do {
400 CopyASCIItoUTF16(cBuf, buf);
401 // read through, building up an entry. If we finish an entry, check for
402 // a match and return out of the loop if we match
404 // skip comments and empty lines
405 if (!buf.IsEmpty() && buf.First() != '#') {
406 entry.Append(buf);
407 if (entry.Last() == '\\') {
408 entry.Truncate(entry.Length() - 1);
409 entry.Append(char16_t(
410 ' ')); // in case there is no trailing whitespace on this line
411 } else { // we have a full entry
412 LOG("Current entry: '%s'\n", NS_LossyConvertUTF16toASCII(entry).get());
413 if (netscapeFormat) {
414 rv = ParseNetscapeMIMETypesEntry(
415 entry, majorTypeStart, majorTypeEnd, minorTypeStart, minorTypeEnd,
416 extensions, descriptionStart, descriptionEnd);
417 if (NS_FAILED(rv)) {
418 // We sometimes get things like RealPlayer appending
419 // "normal" entries to "Netscape" .mime.types files. Try
420 // to handle that. Bug 106381.
421 LOG("Bogus entry; trying 'normal' mode\n");
422 rv = ParseNormalMIMETypesEntry(
423 entry, majorTypeStart, majorTypeEnd, minorTypeStart,
424 minorTypeEnd, extensions, descriptionStart, descriptionEnd);
426 } else {
427 rv = ParseNormalMIMETypesEntry(
428 entry, majorTypeStart, majorTypeEnd, minorTypeStart, minorTypeEnd,
429 extensions, descriptionStart, descriptionEnd);
430 if (NS_FAILED(rv)) {
431 // We sometimes get things like StarOffice prepending
432 // "normal" entries to "Netscape" .mime.types files. Try
433 // to handle that. Bug 136670.
434 LOG("Bogus entry; trying 'Netscape' mode\n");
435 rv = ParseNetscapeMIMETypesEntry(
436 entry, majorTypeStart, majorTypeEnd, minorTypeStart,
437 minorTypeEnd, extensions, descriptionStart, descriptionEnd);
441 if (NS_SUCCEEDED(rv)) { // entry parses
442 nsAString::const_iterator start, end;
443 extensions.BeginReading(start);
444 extensions.EndReading(end);
445 nsAString::const_iterator iter(start);
447 while (start != end) {
448 FindCharInReadable(',', iter, end);
449 if (Substring(start, iter)
450 .Equals(aFileExtension,
451 nsCaseInsensitiveStringComparator)) {
452 // it's a match. Assign the type and description and run
453 aMajorType.Assign(Substring(majorTypeStart, majorTypeEnd));
454 aMinorType.Assign(Substring(minorTypeStart, minorTypeEnd));
455 aDescription.Assign(Substring(descriptionStart, descriptionEnd));
456 mimeFile->Close();
457 return NS_OK;
459 if (iter != end) {
460 ++iter;
462 start = iter;
464 } else {
465 LOG("Failed to parse entry: %s\n",
466 NS_LossyConvertUTF16toASCII(entry).get());
468 // truncate the entry for the next iteration
469 entry.Truncate();
472 if (!more) {
473 rv = NS_ERROR_NOT_AVAILABLE;
474 break;
476 // read the next line
477 rv = mimeTypes->ReadLine(cBuf, &more);
478 } while (NS_SUCCEEDED(rv));
480 mimeFile->Close();
481 return rv;
484 /* Get the mime.types file names from prefs and look up info in them
485 based on mimetype */
486 // static
487 nsresult nsOSHelperAppService::LookUpExtensionsAndDescription(
488 const nsAString& aMajorType, const nsAString& aMinorType,
489 nsAString& aFileExtensions, nsAString& aDescription) {
490 LOG("-- LookUpExtensionsAndDescription for type '%s/%s'\n",
491 NS_LossyConvertUTF16toASCII(aMajorType).get(),
492 NS_LossyConvertUTF16toASCII(aMinorType).get());
493 nsAutoString mimeFileName;
495 nsresult rv = GetFileLocation(FileKind::PrivateMimeTypes, mimeFileName);
496 if (NS_SUCCEEDED(rv) && !mimeFileName.IsEmpty()) {
497 rv = GetExtensionsAndDescriptionFromMimetypesFile(
498 mimeFileName, aMajorType, aMinorType, aFileExtensions, aDescription);
499 } else {
500 rv = NS_ERROR_NOT_AVAILABLE;
502 if (NS_FAILED(rv) || aFileExtensions.IsEmpty()) {
503 rv = GetFileLocation(FileKind::GlobalMimeTypes, mimeFileName);
504 if (NS_SUCCEEDED(rv) && !mimeFileName.IsEmpty()) {
505 rv = GetExtensionsAndDescriptionFromMimetypesFile(
506 mimeFileName, aMajorType, aMinorType, aFileExtensions, aDescription);
507 } else {
508 rv = NS_ERROR_NOT_AVAILABLE;
511 return rv;
514 /* Open the file, read the first line, decide what type of file it is,
515 then get info based on extension */
516 // static
517 nsresult nsOSHelperAppService::GetExtensionsAndDescriptionFromMimetypesFile(
518 const nsAString& aFilename, const nsAString& aMajorType,
519 const nsAString& aMinorType, nsAString& aFileExtensions,
520 nsAString& aDescription) {
521 LOG("-- GetExtensionsAndDescriptionFromMimetypesFile\n");
522 LOG("Getting extensions and description from types file '%s'\n",
523 NS_LossyConvertUTF16toASCII(aFilename).get());
524 LOG("Using type '%s/%s'\n", NS_LossyConvertUTF16toASCII(aMajorType).get(),
525 NS_LossyConvertUTF16toASCII(aMinorType).get());
526 nsCOMPtr<nsIFileInputStream> mimeFile;
527 nsCOMPtr<nsILineInputStream> mimeTypes;
528 bool netscapeFormat;
529 nsAutoCString cBuf;
530 nsAutoString buf;
531 bool more = false;
532 nsresult rv = CreateInputStream(aFilename, getter_AddRefs(mimeFile),
533 getter_AddRefs(mimeTypes), cBuf,
534 &netscapeFormat, &more);
535 if (NS_FAILED(rv)) {
536 return rv;
539 nsAutoString extensions;
540 nsAutoStringN<101> entry;
541 nsAString::const_iterator majorTypeStart, majorTypeEnd, minorTypeStart,
542 minorTypeEnd, descriptionStart, descriptionEnd;
544 do {
545 CopyASCIItoUTF16(cBuf, buf);
546 // read through, building up an entry. If we finish an entry, check for
547 // a match and return out of the loop if we match
549 // skip comments and empty lines
550 if (!buf.IsEmpty() && buf.First() != '#') {
551 entry.Append(buf);
552 if (entry.Last() == '\\') {
553 entry.Truncate(entry.Length() - 1);
554 entry.Append(char16_t(
555 ' ')); // in case there is no trailing whitespace on this line
556 } else { // we have a full entry
557 LOG("Current entry: '%s'\n", NS_LossyConvertUTF16toASCII(entry).get());
558 if (netscapeFormat) {
559 rv = ParseNetscapeMIMETypesEntry(
560 entry, majorTypeStart, majorTypeEnd, minorTypeStart, minorTypeEnd,
561 extensions, descriptionStart, descriptionEnd);
563 if (NS_FAILED(rv)) {
564 // We sometimes get things like RealPlayer appending
565 // "normal" entries to "Netscape" .mime.types files. Try
566 // to handle that. Bug 106381.
567 LOG("Bogus entry; trying 'normal' mode\n");
568 rv = ParseNormalMIMETypesEntry(
569 entry, majorTypeStart, majorTypeEnd, minorTypeStart,
570 minorTypeEnd, extensions, descriptionStart, descriptionEnd);
572 } else {
573 rv = ParseNormalMIMETypesEntry(
574 entry, majorTypeStart, majorTypeEnd, minorTypeStart, minorTypeEnd,
575 extensions, descriptionStart, descriptionEnd);
577 if (NS_FAILED(rv)) {
578 // We sometimes get things like StarOffice prepending
579 // "normal" entries to "Netscape" .mime.types files. Try
580 // to handle that. Bug 136670.
581 LOG("Bogus entry; trying 'Netscape' mode\n");
582 rv = ParseNetscapeMIMETypesEntry(
583 entry, majorTypeStart, majorTypeEnd, minorTypeStart,
584 minorTypeEnd, extensions, descriptionStart, descriptionEnd);
588 if (NS_SUCCEEDED(rv) &&
589 Substring(majorTypeStart, majorTypeEnd)
590 .Equals(aMajorType, nsCaseInsensitiveStringComparator) &&
591 Substring(minorTypeStart, minorTypeEnd)
592 .Equals(aMinorType, nsCaseInsensitiveStringComparator)) {
593 // it's a match
594 aFileExtensions.Assign(extensions);
595 aDescription.Assign(Substring(descriptionStart, descriptionEnd));
596 mimeFile->Close();
597 return NS_OK;
599 if (NS_FAILED(rv)) {
600 LOG("Failed to parse entry: %s\n",
601 NS_LossyConvertUTF16toASCII(entry).get());
604 entry.Truncate();
607 if (!more) {
608 rv = NS_ERROR_NOT_AVAILABLE;
609 break;
611 // read the next line
612 rv = mimeTypes->ReadLine(cBuf, &more);
613 } while (NS_SUCCEEDED(rv));
615 mimeFile->Close();
616 return rv;
620 * This parses a Netscape format mime.types entry. There are two
621 * possible formats:
623 * type=foo/bar; options exts="baz" description="Some type"
625 * and
627 * type=foo/bar; options description="Some type" exts="baz"
629 // static
630 nsresult nsOSHelperAppService::ParseNetscapeMIMETypesEntry(
631 const nsAString& aEntry, nsAString::const_iterator& aMajorTypeStart,
632 nsAString::const_iterator& aMajorTypeEnd,
633 nsAString::const_iterator& aMinorTypeStart,
634 nsAString::const_iterator& aMinorTypeEnd, nsAString& aExtensions,
635 nsAString::const_iterator& aDescriptionStart,
636 nsAString::const_iterator& aDescriptionEnd) {
637 LOG("-- ParseNetscapeMIMETypesEntry\n");
638 NS_ASSERTION(!aEntry.IsEmpty(),
639 "Empty Netscape MIME types entry being parsed.");
641 nsAString::const_iterator start_iter, end_iter, match_start, match_end;
643 aEntry.BeginReading(start_iter);
644 aEntry.EndReading(end_iter);
646 // skip trailing whitespace
647 do {
648 --end_iter;
649 } while (end_iter != start_iter && nsCRT::IsAsciiSpace(*end_iter));
650 // if we're pointing to a quote, don't advance -- we don't want to
651 // include the quote....
652 if (*end_iter != '"') ++end_iter;
653 match_start = start_iter;
654 match_end = end_iter;
656 // Get the major and minor types
657 // First the major type
658 if (!FindInReadable(u"type="_ns, match_start, match_end)) {
659 return NS_ERROR_FAILURE;
662 match_start = match_end;
664 while (match_end != end_iter && *match_end != '/') {
665 ++match_end;
667 if (match_end == end_iter) {
668 return NS_ERROR_FAILURE;
671 aMajorTypeStart = match_start;
672 aMajorTypeEnd = match_end;
674 // now the minor type
675 if (++match_end == end_iter) {
676 return NS_ERROR_FAILURE;
679 match_start = match_end;
681 while (match_end != end_iter && !nsCRT::IsAsciiSpace(*match_end) &&
682 *match_end != ';') {
683 ++match_end;
685 if (match_end == end_iter) {
686 return NS_ERROR_FAILURE;
689 aMinorTypeStart = match_start;
690 aMinorTypeEnd = match_end;
692 // ignore everything up to the end of the mime type from here on
693 start_iter = match_end;
695 // get the extensions
696 match_start = match_end;
697 match_end = end_iter;
698 if (FindInReadable(u"exts="_ns, match_start, match_end)) {
699 nsAString::const_iterator extStart, extEnd;
701 if (match_end == end_iter ||
702 (*match_end == '"' && ++match_end == end_iter)) {
703 return NS_ERROR_FAILURE;
706 extStart = match_end;
707 match_start = extStart;
708 match_end = end_iter;
709 if (FindInReadable(u"desc=\""_ns, match_start, match_end)) {
710 // exts= before desc=, so we have to find the actual end of the extensions
711 extEnd = match_start;
712 if (extEnd == extStart) {
713 return NS_ERROR_FAILURE;
716 do {
717 --extEnd;
718 } while (extEnd != extStart && nsCRT::IsAsciiSpace(*extEnd));
720 if (extEnd != extStart && *extEnd == '"') {
721 --extEnd;
723 } else {
724 // desc= before exts=, so we can use end_iter as the end of the extensions
725 extEnd = end_iter;
727 aExtensions = Substring(extStart, extEnd);
728 } else {
729 // no extensions
730 aExtensions.Truncate();
733 // get the description
734 match_start = start_iter;
735 match_end = end_iter;
736 if (FindInReadable(u"desc=\""_ns, match_start, match_end)) {
737 aDescriptionStart = match_end;
738 match_start = aDescriptionStart;
739 match_end = end_iter;
740 if (FindInReadable(u"exts="_ns, match_start, match_end)) {
741 // exts= after desc=, so have to find actual end of description
742 aDescriptionEnd = match_start;
743 if (aDescriptionEnd == aDescriptionStart) {
744 return NS_ERROR_FAILURE;
747 do {
748 --aDescriptionEnd;
749 } while (aDescriptionEnd != aDescriptionStart &&
750 nsCRT::IsAsciiSpace(*aDescriptionEnd));
751 } else {
752 // desc= after exts=, so use end_iter for the description end
753 aDescriptionEnd = end_iter;
755 } else {
756 // no description
757 aDescriptionStart = start_iter;
758 aDescriptionEnd = start_iter;
761 return NS_OK;
765 * This parses a normal format mime.types entry. The format is:
767 * major/minor ext1 ext2 ext3
769 // static
770 nsresult nsOSHelperAppService::ParseNormalMIMETypesEntry(
771 const nsAString& aEntry, nsAString::const_iterator& aMajorTypeStart,
772 nsAString::const_iterator& aMajorTypeEnd,
773 nsAString::const_iterator& aMinorTypeStart,
774 nsAString::const_iterator& aMinorTypeEnd, nsAString& aExtensions,
775 nsAString::const_iterator& aDescriptionStart,
776 nsAString::const_iterator& aDescriptionEnd) {
777 LOG("-- ParseNormalMIMETypesEntry\n");
778 NS_ASSERTION(!aEntry.IsEmpty(),
779 "Empty Normal MIME types entry being parsed.");
781 nsAString::const_iterator start_iter, end_iter, iter;
783 aEntry.BeginReading(start_iter);
784 aEntry.EndReading(end_iter);
786 // no description
787 aDescriptionStart = start_iter;
788 aDescriptionEnd = start_iter;
790 // skip leading whitespace
791 while (start_iter != end_iter && nsCRT::IsAsciiSpace(*start_iter)) {
792 ++start_iter;
794 if (start_iter == end_iter) {
795 return NS_ERROR_FAILURE;
797 // skip trailing whitespace
798 do {
799 --end_iter;
800 } while (end_iter != start_iter && nsCRT::IsAsciiSpace(*end_iter));
802 ++end_iter; // point to first whitespace char (or to end of string)
803 iter = start_iter;
805 // get the major type
806 if (!FindCharInReadable('/', iter, end_iter)) return NS_ERROR_FAILURE;
808 nsAString::const_iterator equals_sign_iter(start_iter);
809 if (FindCharInReadable('=', equals_sign_iter, iter))
810 return NS_ERROR_FAILURE; // see bug 136670
812 aMajorTypeStart = start_iter;
813 aMajorTypeEnd = iter;
815 // get the minor type
816 if (++iter == end_iter) {
817 return NS_ERROR_FAILURE;
819 start_iter = iter;
821 while (iter != end_iter && !nsCRT::IsAsciiSpace(*iter)) {
822 ++iter;
824 aMinorTypeStart = start_iter;
825 aMinorTypeEnd = iter;
827 // get the extensions
828 aExtensions.Truncate();
829 while (iter != end_iter) {
830 while (iter != end_iter && nsCRT::IsAsciiSpace(*iter)) {
831 ++iter;
834 start_iter = iter;
835 while (iter != end_iter && !nsCRT::IsAsciiSpace(*iter)) {
836 ++iter;
838 aExtensions.Append(Substring(start_iter, iter));
839 if (iter != end_iter) { // not the last extension
840 aExtensions.Append(char16_t(','));
844 return NS_OK;
847 // static
848 nsresult nsOSHelperAppService::LookUpHandlerAndDescription(
849 const nsAString& aMajorType, const nsAString& aMinorType,
850 nsAString& aHandler, nsAString& aDescription, nsAString& aMozillaFlags) {
851 // The mailcap lookup is two-pass to handle the case of mailcap files
852 // that have something like:
854 // text/*; emacs %s
855 // text/rtf; soffice %s
857 // in that order. We want to pick up "soffice" for text/rtf in such cases
858 nsresult rv = DoLookUpHandlerAndDescription(
859 aMajorType, aMinorType, aHandler, aDescription, aMozillaFlags, true);
860 if (NS_FAILED(rv)) {
861 rv = DoLookUpHandlerAndDescription(aMajorType, aMinorType, aHandler,
862 aDescription, aMozillaFlags, false);
865 // maybe we have an entry for "aMajorType/*"?
866 if (NS_FAILED(rv)) {
867 rv = DoLookUpHandlerAndDescription(aMajorType, u"*"_ns, aHandler,
868 aDescription, aMozillaFlags, true);
871 if (NS_FAILED(rv)) {
872 rv = DoLookUpHandlerAndDescription(aMajorType, u"*"_ns, aHandler,
873 aDescription, aMozillaFlags, false);
876 return rv;
879 // static
880 nsresult nsOSHelperAppService::DoLookUpHandlerAndDescription(
881 const nsAString& aMajorType, const nsAString& aMinorType,
882 nsAString& aHandler, nsAString& aDescription, nsAString& aMozillaFlags,
883 bool aUserData) {
884 LOG("-- LookUpHandlerAndDescription for type '%s/%s'\n",
885 NS_LossyConvertUTF16toASCII(aMajorType).get(),
886 NS_LossyConvertUTF16toASCII(aMinorType).get());
887 nsAutoString mailcapFileName;
889 const auto kind =
890 aUserData ? FileKind::PrivateMailCap : FileKind::GlobalMailCap;
891 nsresult rv = GetFileLocation(kind, mailcapFileName);
892 if (NS_FAILED(rv) || mailcapFileName.IsEmpty()) {
893 return NS_ERROR_NOT_AVAILABLE;
895 return GetHandlerAndDescriptionFromMailcapFile(mailcapFileName, aMajorType,
896 aMinorType, aHandler,
897 aDescription, aMozillaFlags);
900 // static
901 nsresult nsOSHelperAppService::GetHandlerAndDescriptionFromMailcapFile(
902 const nsAString& aFilename, const nsAString& aMajorType,
903 const nsAString& aMinorType, nsAString& aHandler, nsAString& aDescription,
904 nsAString& aMozillaFlags) {
905 LOG("-- GetHandlerAndDescriptionFromMailcapFile\n");
906 LOG("Getting handler and description from mailcap file '%s'\n",
907 NS_LossyConvertUTF16toASCII(aFilename).get());
908 LOG("Using type '%s/%s'\n", NS_LossyConvertUTF16toASCII(aMajorType).get(),
909 NS_LossyConvertUTF16toASCII(aMinorType).get());
911 nsresult rv = NS_OK;
912 bool more = false;
914 nsCOMPtr<nsIFile> file(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
915 if (NS_FAILED(rv)) return rv;
916 rv = file->InitWithPath(aFilename);
917 if (NS_FAILED(rv)) return rv;
919 nsCOMPtr<nsIFileInputStream> mailcapFile(
920 do_CreateInstance(NS_LOCALFILEINPUTSTREAM_CONTRACTID, &rv));
921 if (NS_FAILED(rv)) return rv;
922 rv = mailcapFile->Init(file, -1, -1, false);
923 if (NS_FAILED(rv)) return rv;
925 nsCOMPtr<nsILineInputStream> mailcap(do_QueryInterface(mailcapFile, &rv));
927 if (NS_FAILED(rv)) {
928 LOG("Interface trouble in stream land!");
929 return rv;
932 nsAutoStringN<129> entry;
933 nsAutoStringN<81> buffer;
934 nsAutoCStringN<81> cBuffer;
935 rv = mailcap->ReadLine(cBuffer, &more);
936 if (NS_FAILED(rv)) {
937 mailcapFile->Close();
938 return rv;
941 do { // return on end-of-file in the loop
943 CopyASCIItoUTF16(cBuffer, buffer);
944 if (!buffer.IsEmpty() && buffer.First() != '#') {
945 entry.Append(buffer);
946 if (entry.Last() == '\\') { // entry continues on next line
947 entry.Truncate(entry.Length() - 1);
948 entry.Append(char16_t(
949 ' ')); // in case there is no trailing whitespace on this line
950 } else { // we have a full entry in entry. Check it for the type
951 LOG("Current entry: '%s'\n", NS_LossyConvertUTF16toASCII(entry).get());
953 nsAString::const_iterator semicolon_iter, start_iter, end_iter,
954 majorTypeStart, majorTypeEnd, minorTypeStart, minorTypeEnd;
955 entry.BeginReading(start_iter);
956 entry.EndReading(end_iter);
957 semicolon_iter = start_iter;
958 FindSemicolon(semicolon_iter, end_iter);
959 if (semicolon_iter !=
960 end_iter) { // we have something resembling a valid entry
961 rv = ParseMIMEType(start_iter, majorTypeStart, majorTypeEnd,
962 minorTypeStart, minorTypeEnd, semicolon_iter);
963 if (NS_SUCCEEDED(rv) &&
964 Substring(majorTypeStart, majorTypeEnd)
965 .Equals(aMajorType, nsCaseInsensitiveStringComparator) &&
966 Substring(minorTypeStart, minorTypeEnd)
967 .Equals(aMinorType, nsCaseInsensitiveStringComparator)) {
968 // we have a match
969 bool match = true;
970 ++semicolon_iter; // point at the first char past the semicolon
971 start_iter = semicolon_iter; // handler string starts here
972 FindSemicolon(semicolon_iter, end_iter);
973 while (start_iter != semicolon_iter &&
974 nsCRT::IsAsciiSpace(*start_iter)) {
975 ++start_iter;
978 LOG("The real handler is: '%s'\n",
979 NS_LossyConvertUTF16toASCII(
980 Substring(start_iter, semicolon_iter))
981 .get());
983 // XXX ugly hack. Just grab the executable name
984 nsAString::const_iterator end_handler_iter = semicolon_iter;
985 nsAString::const_iterator end_executable_iter = start_iter;
986 while (end_executable_iter != end_handler_iter &&
987 !nsCRT::IsAsciiSpace(*end_executable_iter)) {
988 ++end_executable_iter;
990 // XXX End ugly hack
992 aHandler = Substring(start_iter, end_executable_iter);
994 nsAString::const_iterator start_option_iter, end_optionname_iter,
995 equal_sign_iter;
996 bool equalSignFound;
997 while (match && semicolon_iter != end_iter &&
998 ++semicolon_iter !=
999 end_iter) { // there are options left and we still match
1000 start_option_iter = semicolon_iter;
1001 // skip over leading whitespace
1002 while (start_option_iter != end_iter &&
1003 nsCRT::IsAsciiSpace(*start_option_iter)) {
1004 ++start_option_iter;
1006 if (start_option_iter == end_iter) { // nothing actually here
1007 break;
1009 semicolon_iter = start_option_iter;
1010 FindSemicolon(semicolon_iter, end_iter);
1011 equal_sign_iter = start_option_iter;
1012 equalSignFound = false;
1013 while (equal_sign_iter != semicolon_iter && !equalSignFound) {
1014 switch (*equal_sign_iter) {
1015 case '\\':
1016 equal_sign_iter.advance(2);
1017 break;
1018 case '=':
1019 equalSignFound = true;
1020 break;
1021 default:
1022 ++equal_sign_iter;
1023 break;
1026 end_optionname_iter = start_option_iter;
1027 // find end of option name
1028 while (end_optionname_iter != equal_sign_iter &&
1029 !nsCRT::IsAsciiSpace(*end_optionname_iter)) {
1030 ++end_optionname_iter;
1032 nsDependentSubstring optionName(start_option_iter,
1033 end_optionname_iter);
1034 if (equalSignFound) {
1035 // This is an option that has a name and value
1036 if (optionName.EqualsLiteral("description")) {
1037 aDescription = Substring(++equal_sign_iter, semicolon_iter);
1038 } else if (optionName.EqualsLiteral("x-mozilla-flags")) {
1039 aMozillaFlags = Substring(++equal_sign_iter, semicolon_iter);
1040 } else if (optionName.EqualsLiteral("test")) {
1041 nsAutoCString testCommand;
1042 rv = UnescapeCommand(
1043 Substring(++equal_sign_iter, semicolon_iter), aMajorType,
1044 aMinorType, testCommand);
1045 if (NS_FAILED(rv)) continue;
1046 nsCOMPtr<nsIProcess> process =
1047 do_CreateInstance(NS_PROCESS_CONTRACTID, &rv);
1048 if (NS_FAILED(rv)) continue;
1049 nsCOMPtr<nsIFile> file(
1050 do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
1051 if (NS_FAILED(rv)) continue;
1052 rv = file->InitWithNativePath("/bin/sh"_ns);
1053 if (NS_FAILED(rv)) continue;
1054 rv = process->Init(file);
1055 if (NS_FAILED(rv)) continue;
1056 const char* args[] = {"-c", testCommand.get()};
1057 LOG("Running Test: %s\n", testCommand.get());
1058 rv = process->Run(true, args, 2);
1059 if (NS_FAILED(rv)) continue;
1060 int32_t exitValue;
1061 rv = process->GetExitValue(&exitValue);
1062 if (NS_FAILED(rv)) continue;
1063 LOG("Exit code: %d\n", exitValue);
1064 if (exitValue) {
1065 match = false;
1068 } else {
1069 // This is an option that just has a name but no value (eg
1070 // "copiousoutput")
1071 if (optionName.EqualsLiteral("needsterminal")) {
1072 match = false;
1077 if (match) { // we did not fail any test clauses; all is good
1078 // get out of here
1079 mailcapFile->Close();
1080 return NS_OK;
1082 // pretend that this match never happened
1083 aDescription.Truncate();
1084 aMozillaFlags.Truncate();
1085 aHandler.Truncate();
1088 // zero out the entry for the next cycle
1089 entry.Truncate();
1092 if (!more) {
1093 rv = NS_ERROR_NOT_AVAILABLE;
1094 break;
1096 rv = mailcap->ReadLine(cBuffer, &more);
1097 } while (NS_SUCCEEDED(rv));
1098 mailcapFile->Close();
1099 return rv;
1102 nsresult nsOSHelperAppService::OSProtocolHandlerExists(
1103 const char* aProtocolScheme, bool* aHandlerExists) {
1104 nsresult rv = NS_OK;
1106 if (!XRE_IsContentProcess()) {
1107 #ifdef MOZ_WIDGET_GTK
1108 // Check the GNOME registry for a protocol handler
1109 *aHandlerExists = nsGNOMERegistry::HandlerExists(aProtocolScheme);
1110 #else
1111 *aHandlerExists = false;
1112 #endif
1113 } else {
1114 *aHandlerExists = false;
1115 nsCOMPtr<nsIHandlerService> handlerSvc =
1116 do_GetService(NS_HANDLERSERVICE_CONTRACTID, &rv);
1117 if (NS_SUCCEEDED(rv) && handlerSvc) {
1118 rv = handlerSvc->ExistsForProtocolOS(nsCString(aProtocolScheme),
1119 aHandlerExists);
1123 return rv;
1126 NS_IMETHODIMP nsOSHelperAppService::GetApplicationDescription(
1127 const nsACString& aScheme, nsAString& _retval) {
1128 #ifdef MOZ_WIDGET_GTK
1129 nsGNOMERegistry::GetAppDescForScheme(aScheme, _retval);
1130 return _retval.IsEmpty() ? NS_ERROR_NOT_AVAILABLE : NS_OK;
1131 #else
1132 return NS_ERROR_NOT_AVAILABLE;
1133 #endif
1136 NS_IMETHODIMP nsOSHelperAppService::IsCurrentAppOSDefaultForProtocol(
1137 const nsACString& aScheme, bool* _retval) {
1138 *_retval = false;
1139 #if defined(MOZ_BUILD_APP_IS_BROWSER) && defined(MOZ_WIDGET_GTK)
1140 if (nsCOMPtr<nsIGNOMEShellService> shell =
1141 do_GetService(NS_TOOLKITSHELLSERVICE_CONTRACTID)) {
1142 return shell->IsDefaultForScheme(aScheme, _retval);
1144 #endif
1145 return NS_OK;
1148 nsresult nsOSHelperAppService::GetFileTokenForPath(
1149 const char16_t* platformAppPath, nsIFile** aFile) {
1150 LOG("-- nsOSHelperAppService::GetFileTokenForPath: '%s'\n",
1151 NS_LossyConvertUTF16toASCII(platformAppPath).get());
1152 if (!*platformAppPath) { // empty filename--return error
1153 NS_WARNING("Empty filename passed in.");
1154 return NS_ERROR_INVALID_ARG;
1157 // first check if the base class implementation finds anything
1158 nsresult rv =
1159 nsExternalHelperAppService::GetFileTokenForPath(platformAppPath, aFile);
1160 if (NS_SUCCEEDED(rv)) return rv;
1161 // If the reason for failure was that the file doesn't exist, return too
1162 // (because it means the path was absolute, and so that we shouldn't search in
1163 // the path)
1164 if (rv == NS_ERROR_FILE_NOT_FOUND) return rv;
1166 // If we get here, we really should have a relative path.
1167 NS_ASSERTION(*platformAppPath != char16_t('/'), "Unexpected absolute path");
1169 nsCOMPtr<nsIFile> localFile(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID));
1171 if (!localFile) return NS_ERROR_NOT_INITIALIZED;
1173 bool exists = false;
1174 // ugly hack. Walk the PATH variable...
1175 char* unixpath = PR_GetEnv("PATH");
1176 nsAutoCString path(unixpath);
1178 const char* start_iter = path.BeginReading(start_iter);
1179 const char* colon_iter = start_iter;
1180 const char* end_iter = path.EndReading(end_iter);
1182 while (start_iter != end_iter && !exists) {
1183 while (colon_iter != end_iter && *colon_iter != ':') {
1184 ++colon_iter;
1186 localFile->InitWithNativePath(Substring(start_iter, colon_iter));
1187 rv = localFile->AppendRelativePath(nsDependentString(platformAppPath));
1188 // Failing AppendRelativePath is a bad thing - it should basically always
1189 // succeed given a relative path. Show a warning if it does fail.
1190 // To prevent infinite loops when it does fail, return at this point.
1191 NS_ENSURE_SUCCESS(rv, rv);
1192 localFile->Exists(&exists);
1193 if (!exists) {
1194 if (colon_iter == end_iter) {
1195 break;
1197 ++colon_iter;
1198 start_iter = colon_iter;
1202 if (exists) {
1203 rv = NS_OK;
1204 } else {
1205 rv = NS_ERROR_NOT_AVAILABLE;
1208 *aFile = localFile;
1209 NS_IF_ADDREF(*aFile);
1211 return rv;
1214 already_AddRefed<nsMIMEInfoBase> nsOSHelperAppService::GetFromExtension(
1215 const nsCString& aFileExt) {
1216 // if the extension is empty, return immediately
1217 if (aFileExt.IsEmpty()) {
1218 return nullptr;
1221 LOG("Here we do an extension lookup for '%s'\n", aFileExt.get());
1223 nsAutoString majorType, minorType, mime_types_description,
1224 mailcap_description, handler, mozillaFlags;
1226 nsresult rv =
1227 LookUpTypeAndDescription(NS_ConvertUTF8toUTF16(aFileExt), majorType,
1228 minorType, mime_types_description, true);
1230 if (NS_FAILED(rv) || majorType.IsEmpty()) {
1231 #ifdef MOZ_WIDGET_GTK
1232 LOG("Looking in GNOME registry\n");
1233 RefPtr<nsMIMEInfoBase> gnomeInfo =
1234 nsGNOMERegistry::GetFromExtension(aFileExt);
1235 if (gnomeInfo) {
1236 LOG("Got MIMEInfo from GNOME registry\n");
1237 return gnomeInfo.forget();
1239 #endif
1241 rv = LookUpTypeAndDescription(NS_ConvertUTF8toUTF16(aFileExt), majorType,
1242 minorType, mime_types_description, false);
1245 if (NS_FAILED(rv)) {
1246 return nullptr;
1249 NS_LossyConvertUTF16toASCII asciiMajorType(majorType);
1250 NS_LossyConvertUTF16toASCII asciiMinorType(minorType);
1252 LOG("Type/Description results: majorType='%s', minorType='%s', "
1253 "description='%s'\n",
1254 asciiMajorType.get(), asciiMinorType.get(),
1255 NS_LossyConvertUTF16toASCII(mime_types_description).get());
1257 if (majorType.IsEmpty() && minorType.IsEmpty()) {
1258 // we didn't get a type mapping, so we can't do anything useful
1259 return nullptr;
1262 nsAutoCString mimeType(asciiMajorType + "/"_ns + asciiMinorType);
1263 RefPtr<nsMIMEInfoUnix> mimeInfo = new nsMIMEInfoUnix(mimeType);
1265 mimeInfo->AppendExtension(aFileExt);
1266 rv = LookUpHandlerAndDescription(majorType, minorType, handler,
1267 mailcap_description, mozillaFlags);
1268 LOG("Handler/Description results: handler='%s', description='%s', "
1269 "mozillaFlags='%s'\n",
1270 NS_LossyConvertUTF16toASCII(handler).get(),
1271 NS_LossyConvertUTF16toASCII(mailcap_description).get(),
1272 NS_LossyConvertUTF16toASCII(mozillaFlags).get());
1273 mailcap_description.Trim(" \t\"");
1274 mozillaFlags.Trim(" \t");
1275 if (!mime_types_description.IsEmpty()) {
1276 mimeInfo->SetDescription(mime_types_description);
1277 } else {
1278 mimeInfo->SetDescription(mailcap_description);
1281 if (NS_SUCCEEDED(rv) && handler.IsEmpty()) {
1282 rv = NS_ERROR_NOT_AVAILABLE;
1285 if (NS_SUCCEEDED(rv)) {
1286 nsCOMPtr<nsIFile> handlerFile;
1287 rv = GetFileTokenForPath(handler.get(), getter_AddRefs(handlerFile));
1289 if (NS_SUCCEEDED(rv)) {
1290 mimeInfo->SetDefaultApplication(handlerFile);
1291 mimeInfo->SetPreferredAction(nsIMIMEInfo::saveToDisk);
1292 mimeInfo->SetDefaultDescription(handler);
1296 if (NS_FAILED(rv)) {
1297 mimeInfo->SetPreferredAction(nsIMIMEInfo::saveToDisk);
1300 return mimeInfo.forget();
1303 already_AddRefed<nsMIMEInfoBase> nsOSHelperAppService::GetFromType(
1304 const nsCString& aMIMEType) {
1305 // if the type is empty, return immediately
1306 if (aMIMEType.IsEmpty()) {
1307 return nullptr;
1310 LOG("Here we do a mimetype lookup for '%s'\n", aMIMEType.get());
1312 // extract the major and minor types
1313 NS_ConvertASCIItoUTF16 mimeType(aMIMEType);
1314 nsAString::const_iterator start_iter, end_iter, majorTypeStart, majorTypeEnd,
1315 minorTypeStart, minorTypeEnd;
1317 mimeType.BeginReading(start_iter);
1318 mimeType.EndReading(end_iter);
1320 // XXX FIXME: add typeOptions parsing in here
1321 nsresult rv = ParseMIMEType(start_iter, majorTypeStart, majorTypeEnd,
1322 minorTypeStart, minorTypeEnd, end_iter);
1324 if (NS_FAILED(rv)) {
1325 return nullptr;
1328 nsDependentSubstring majorType(majorTypeStart, majorTypeEnd);
1329 nsDependentSubstring minorType(minorTypeStart, minorTypeEnd);
1331 // First check the user's private mailcap file
1332 nsAutoString mailcap_description, handler, mozillaFlags;
1333 DoLookUpHandlerAndDescription(majorType, minorType, handler,
1334 mailcap_description, mozillaFlags, true);
1336 LOG("Private Handler/Description results: handler='%s', description='%s'\n",
1337 NS_LossyConvertUTF16toASCII(handler).get(),
1338 NS_LossyConvertUTF16toASCII(mailcap_description).get());
1340 // Now look up our extensions
1341 nsAutoString extensions, mime_types_description;
1342 LookUpExtensionsAndDescription(majorType, minorType, extensions,
1343 mime_types_description);
1345 #ifdef MOZ_WIDGET_GTK
1346 if (handler.IsEmpty()) {
1347 RefPtr<nsMIMEInfoBase> gnomeInfo = nsGNOMERegistry::GetFromType(aMIMEType);
1348 if (gnomeInfo) {
1349 LOG("Got MIMEInfo from GNOME registry without extensions; setting them "
1350 "to %s\n",
1351 NS_LossyConvertUTF16toASCII(extensions).get());
1353 NS_ASSERTION(!gnomeInfo->HasExtensions(), "How'd that happen?");
1354 gnomeInfo->SetFileExtensions(NS_ConvertUTF16toUTF8(extensions));
1355 return gnomeInfo.forget();
1358 #endif
1360 if (handler.IsEmpty()) {
1361 DoLookUpHandlerAndDescription(majorType, minorType, handler,
1362 mailcap_description, mozillaFlags, false);
1365 if (handler.IsEmpty()) {
1366 DoLookUpHandlerAndDescription(majorType, u"*"_ns, handler,
1367 mailcap_description, mozillaFlags, true);
1370 if (handler.IsEmpty()) {
1371 DoLookUpHandlerAndDescription(majorType, u"*"_ns, handler,
1372 mailcap_description, mozillaFlags, false);
1375 LOG("Handler/Description results: handler='%s', description='%s', "
1376 "mozillaFlags='%s'\n",
1377 NS_LossyConvertUTF16toASCII(handler).get(),
1378 NS_LossyConvertUTF16toASCII(mailcap_description).get(),
1379 NS_LossyConvertUTF16toASCII(mozillaFlags).get());
1381 mailcap_description.Trim(" \t\"");
1382 mozillaFlags.Trim(" \t");
1384 if (handler.IsEmpty() && extensions.IsEmpty() &&
1385 mailcap_description.IsEmpty() && mime_types_description.IsEmpty()) {
1386 // No real useful info
1387 return nullptr;
1390 RefPtr<nsMIMEInfoUnix> mimeInfo = new nsMIMEInfoUnix(aMIMEType);
1392 mimeInfo->SetFileExtensions(NS_ConvertUTF16toUTF8(extensions));
1393 if (!mime_types_description.IsEmpty()) {
1394 mimeInfo->SetDescription(mime_types_description);
1395 } else {
1396 mimeInfo->SetDescription(mailcap_description);
1399 rv = NS_ERROR_NOT_AVAILABLE;
1400 nsCOMPtr<nsIFile> handlerFile;
1401 if (!handler.IsEmpty()) {
1402 rv = GetFileTokenForPath(handler.get(), getter_AddRefs(handlerFile));
1405 if (NS_SUCCEEDED(rv)) {
1406 mimeInfo->SetDefaultApplication(handlerFile);
1407 mimeInfo->SetPreferredAction(nsIMIMEInfo::saveToDisk);
1408 mimeInfo->SetDefaultDescription(handler);
1409 } else {
1410 mimeInfo->SetPreferredAction(nsIMIMEInfo::saveToDisk);
1413 return mimeInfo.forget();
1416 nsresult nsOSHelperAppService::GetMIMEInfoFromOS(const nsACString& aType,
1417 const nsACString& aFileExt,
1418 bool* aFound,
1419 nsIMIMEInfo** aMIMEInfo) {
1420 *aFound = true;
1421 RefPtr<nsMIMEInfoBase> retval;
1422 // Fallback to lookup by extension when generic 'application/octet-stream'
1423 // content type is received.
1424 if (!aType.EqualsLiteral(APPLICATION_OCTET_STREAM)) {
1425 retval = GetFromType(PromiseFlatCString(aType));
1427 bool hasDefault = false;
1428 if (retval) retval->GetHasDefaultHandler(&hasDefault);
1429 if (!retval || !hasDefault) {
1430 RefPtr<nsMIMEInfoBase> miByExt =
1431 GetFromExtension(PromiseFlatCString(aFileExt));
1432 // If we had no extension match, but a type match, use that
1433 if (!miByExt && retval) {
1434 retval.forget(aMIMEInfo);
1435 return NS_OK;
1437 // If we had an extension match but no type match, set the mimetype and use
1438 // it
1439 if (!retval && miByExt) {
1440 if (!aType.IsEmpty()) miByExt->SetMIMEType(aType);
1441 miByExt.swap(retval);
1443 retval.forget(aMIMEInfo);
1444 return NS_OK;
1446 // If we got nothing, make a new mimeinfo
1447 if (!retval) {
1448 *aFound = false;
1449 retval = new nsMIMEInfoUnix(aType);
1450 if (retval) {
1451 if (!aFileExt.IsEmpty()) retval->AppendExtension(aFileExt);
1454 retval.forget(aMIMEInfo);
1455 return NS_OK;
1458 // Copy the attributes of retval (mimeinfo from type) onto miByExt, to
1459 // return it
1460 // but reset to just collected mDefaultAppDescription (from ext)
1461 nsAutoString byExtDefault;
1462 miByExt->GetDefaultDescription(byExtDefault);
1463 retval->SetDefaultDescription(byExtDefault);
1464 retval->CopyBasicDataTo(miByExt);
1466 miByExt.swap(retval);
1468 retval.forget(aMIMEInfo);
1469 return NS_OK;
1472 NS_IMETHODIMP
1473 nsOSHelperAppService::GetProtocolHandlerInfoFromOS(const nsACString& aScheme,
1474 bool* found,
1475 nsIHandlerInfo** _retval) {
1476 NS_ASSERTION(!aScheme.IsEmpty(), "No scheme was specified!");
1478 nsresult rv =
1479 OSProtocolHandlerExists(nsPromiseFlatCString(aScheme).get(), found);
1480 if (NS_FAILED(rv)) return rv;
1482 nsMIMEInfoUnix* handlerInfo =
1483 new nsMIMEInfoUnix(aScheme, nsMIMEInfoBase::eProtocolInfo);
1484 NS_ENSURE_TRUE(handlerInfo, NS_ERROR_OUT_OF_MEMORY);
1485 NS_ADDREF(*_retval = handlerInfo);
1487 if (!*found) {
1488 // Code that calls this requires an object regardless if the OS has
1489 // something for us, so we return the empty object.
1490 return NS_OK;
1493 nsAutoString desc;
1494 GetApplicationDescription(aScheme, desc);
1495 handlerInfo->SetDefaultDescription(desc);
1497 return NS_OK;