diff options
Diffstat (limited to '')
-rw-r--r-- | uriloader/exthandler/unix/nsGNOMERegistry.cpp | 101 | ||||
-rw-r--r-- | uriloader/exthandler/unix/nsGNOMERegistry.h | 28 | ||||
-rw-r--r-- | uriloader/exthandler/unix/nsMIMEInfoUnix.cpp | 84 | ||||
-rw-r--r-- | uriloader/exthandler/unix/nsMIMEInfoUnix.h | 30 | ||||
-rw-r--r-- | uriloader/exthandler/unix/nsOSHelperAppService.cpp | 1498 | ||||
-rw-r--r-- | uriloader/exthandler/unix/nsOSHelperAppService.h | 122 |
6 files changed, 1863 insertions, 0 deletions
diff --git a/uriloader/exthandler/unix/nsGNOMERegistry.cpp b/uriloader/exthandler/unix/nsGNOMERegistry.cpp new file mode 100644 index 0000000000..dad4adbfd7 --- /dev/null +++ b/uriloader/exthandler/unix/nsGNOMERegistry.cpp @@ -0,0 +1,101 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsGNOMERegistry.h" +#include "nsString.h" +#include "nsMIMEInfoUnix.h" +#include "nsIGIOService.h" + +/* static */ +bool nsGNOMERegistry::HandlerExists(const char* aProtocolScheme) { + nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID); + if (!giovfs) { + return false; + } + + nsCOMPtr<nsIHandlerApp> app; + return NS_SUCCEEDED(giovfs->GetAppForURIScheme( + nsDependentCString(aProtocolScheme), getter_AddRefs(app))); +} + +// XXX Check HandlerExists() before calling LoadURL. + +/* static */ +nsresult nsGNOMERegistry::LoadURL(nsIURI* aURL) { + nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID); + if (!giovfs) { + return NS_ERROR_FAILURE; + } + + return giovfs->ShowURI(aURL); +} + +/* static */ +void nsGNOMERegistry::GetAppDescForScheme(const nsACString& aScheme, + nsAString& aDesc) { + nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID); + if (!giovfs) return; + + nsCOMPtr<nsIHandlerApp> app; + if (NS_FAILED(giovfs->GetAppForURIScheme(aScheme, getter_AddRefs(app)))) + return; + + app->GetName(aDesc); +} + +/* static */ +already_AddRefed<nsMIMEInfoBase> nsGNOMERegistry::GetFromExtension( + const nsACString& aFileExt) { + nsAutoCString mimeType; + nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID); + if (!giovfs) { + return nullptr; + } + + // Get the MIME type from the extension, then call GetFromType to + // fill in the MIMEInfo. + if (NS_FAILED(giovfs->GetMimeTypeFromExtension(aFileExt, mimeType)) || + mimeType.EqualsLiteral("application/octet-stream")) { + return nullptr; + } + + RefPtr<nsMIMEInfoBase> mi = GetFromType(mimeType); + if (mi) { + mi->AppendExtension(aFileExt); + } + + return mi.forget(); +} + +/* static */ +already_AddRefed<nsMIMEInfoBase> nsGNOMERegistry::GetFromType( + const nsACString& aMIMEType) { + RefPtr<nsMIMEInfoUnix> mimeInfo = new nsMIMEInfoUnix(aMIMEType); + NS_ENSURE_TRUE(mimeInfo, nullptr); + + nsAutoString name; + nsAutoCString description; + + nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID); + if (!giovfs) { + return nullptr; + } + + nsCOMPtr<nsIHandlerApp> handlerApp; + if (NS_FAILED( + giovfs->GetAppForMimeType(aMIMEType, getter_AddRefs(handlerApp))) || + !handlerApp) { + return nullptr; + } + handlerApp->GetName(name); + giovfs->GetDescriptionForMimeType(aMIMEType, description); + + mimeInfo->SetDefaultDescription(name); + + mimeInfo->SetPreferredAction(nsIMIMEInfo::saveToDisk); + mimeInfo->SetDescription(NS_ConvertUTF8toUTF16(description)); + + return mimeInfo.forget(); +} diff --git a/uriloader/exthandler/unix/nsGNOMERegistry.h b/uriloader/exthandler/unix/nsGNOMERegistry.h new file mode 100644 index 0000000000..ea626c5b50 --- /dev/null +++ b/uriloader/exthandler/unix/nsGNOMERegistry.h @@ -0,0 +1,28 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsGNOMERegistry_h +#define nsGNOMERegistry_h + +#include "nsIURI.h" +#include "nsCOMPtr.h" + +class nsMIMEInfoBase; + +class nsGNOMERegistry { + public: + static bool HandlerExists(const char* aProtocolScheme); + + static nsresult LoadURL(nsIURI* aURL); + + static void GetAppDescForScheme(const nsACString& aScheme, nsAString& aDesc); + + static already_AddRefed<nsMIMEInfoBase> GetFromExtension( + const nsACString& aFileExt); + + static already_AddRefed<nsMIMEInfoBase> GetFromType( + const nsACString& aMIMEType); +}; + +#endif // nsGNOMERegistry_h diff --git a/uriloader/exthandler/unix/nsMIMEInfoUnix.cpp b/uriloader/exthandler/unix/nsMIMEInfoUnix.cpp new file mode 100644 index 0000000000..330c441159 --- /dev/null +++ b/uriloader/exthandler/unix/nsMIMEInfoUnix.cpp @@ -0,0 +1,84 @@ +/* -*- Mode: C++; tab-width: 3; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsMIMEInfoUnix.h" +#include "nsGNOMERegistry.h" +#include "nsIGIOService.h" +#include "nsNetCID.h" +#include "nsIIOService.h" +#ifdef MOZ_ENABLE_DBUS +# include "nsDBusHandlerApp.h" +#endif + +nsresult nsMIMEInfoUnix::LoadUriInternal(nsIURI* aURI) { + return nsGNOMERegistry::LoadURL(aURI); +} + +NS_IMETHODIMP +nsMIMEInfoUnix::GetHasDefaultHandler(bool* _retval) { + // if a default app is set, it means the application has been set from + // either /etc/mailcap or ${HOME}/.mailcap, in which case we don't want to + // give the GNOME answer. + if (GetDefaultApplication()) { + return nsMIMEInfoImpl::GetHasDefaultHandler(_retval); + } + + *_retval = false; + + if (mClass == eProtocolInfo) { + *_retval = nsGNOMERegistry::HandlerExists(mSchemeOrType.get()); + } else { + RefPtr<nsMIMEInfoBase> mimeInfo = + nsGNOMERegistry::GetFromType(mSchemeOrType); + if (!mimeInfo) { + nsAutoCString ext; + nsresult rv = GetPrimaryExtension(ext); + if (NS_SUCCEEDED(rv)) { + mimeInfo = nsGNOMERegistry::GetFromExtension(ext); + } + } + if (mimeInfo) *_retval = true; + } + + if (*_retval) return NS_OK; + + return NS_OK; +} + +nsresult nsMIMEInfoUnix::LaunchDefaultWithFile(nsIFile* aFile) { + // if a default app is set, it means the application has been set from + // either /etc/mailcap or ${HOME}/.mailcap, in which case we don't want to + // give the GNOME answer. + if (GetDefaultApplication()) { + return nsMIMEInfoImpl::LaunchDefaultWithFile(aFile); + } + + nsAutoCString nativePath; + aFile->GetNativePath(nativePath); + + nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID); + if (!giovfs) { + return NS_ERROR_FAILURE; + } + + // nsGIOMimeApp->Launch wants a URI string instead of local file + nsresult rv; + nsCOMPtr<nsIIOService> ioservice = + do_GetService(NS_IOSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIURI> uri; + rv = ioservice->NewFileURI(aFile, getter_AddRefs(uri)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIHandlerApp> app; + if (NS_FAILED( + giovfs->GetAppForMimeType(mSchemeOrType, getter_AddRefs(app))) || + !app) { + return NS_ERROR_FILE_NOT_FOUND; + } + + return app->LaunchWithURI(uri, nullptr); +} diff --git a/uriloader/exthandler/unix/nsMIMEInfoUnix.h b/uriloader/exthandler/unix/nsMIMEInfoUnix.h new file mode 100644 index 0000000000..2e32be4915 --- /dev/null +++ b/uriloader/exthandler/unix/nsMIMEInfoUnix.h @@ -0,0 +1,30 @@ +/* -*- Mode: C++; tab-width: 3; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsMIMEInfoUnix_h_ +#define nsMIMEInfoUnix_h_ + +#include "nsMIMEInfoImpl.h" + +class nsMIMEInfoUnix : public nsMIMEInfoImpl { + public: + explicit nsMIMEInfoUnix(const char* aMIMEType = "") + : nsMIMEInfoImpl(aMIMEType) {} + explicit nsMIMEInfoUnix(const nsACString& aMIMEType) + : nsMIMEInfoImpl(aMIMEType) {} + nsMIMEInfoUnix(const nsACString& aType, HandlerClass aClass) + : nsMIMEInfoImpl(aType, aClass) {} + static bool HandlerExists(const char* aProtocolScheme); + + protected: + NS_IMETHOD GetHasDefaultHandler(bool* _retval) override; + + virtual nsresult LoadUriInternal(nsIURI* aURI) override; + + virtual nsresult LaunchDefaultWithFile(nsIFile* aFile) override; +}; + +#endif // nsMIMEInfoUnix_h_ diff --git a/uriloader/exthandler/unix/nsOSHelperAppService.cpp b/uriloader/exthandler/unix/nsOSHelperAppService.cpp new file mode 100644 index 0000000000..7f6eaa46f2 --- /dev/null +++ b/uriloader/exthandler/unix/nsOSHelperAppService.cpp @@ -0,0 +1,1498 @@ +/* -*- Mode: C++; tab-width: 3; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <sys/types.h> +#include <sys/stat.h> + +#include "nsOSHelperAppService.h" +#include "nsMIMEInfoUnix.h" +#ifdef MOZ_WIDGET_GTK +# include "nsGNOMERegistry.h" +# ifdef MOZ_BUILD_APP_IS_BROWSER +# include "nsIToolkitShellService.h" +# include "nsIGNOMEShellService.h" +# endif +#endif +#include "nsISupports.h" +#include "nsString.h" +#include "nsReadableUtils.h" +#include "nsUnicharUtils.h" +#include "nsIFileStreams.h" +#include "nsILineInputStream.h" +#include "nsIFile.h" +#include "nsIProcess.h" +#include "nsNetCID.h" +#include "nsXPCOM.h" +#include "nsComponentManagerUtils.h" +#include "nsCRT.h" +#include "nsDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" +#include "nsXULAppAPI.h" +#include "ContentHandlerService.h" +#include "prenv.h" // for PR_GetEnv() +#include "mozilla/EnumeratedRange.h" +#include "mozilla/Preferences.h" +#include "mozilla/ClearOnShutdown.h" +#include "nsMimeTypes.h" +#include <mutex> + +using namespace mozilla; + +#define LOG(...) \ + MOZ_LOG(nsOSHelperAppService::sLog, mozilla::LogLevel::Debug, (__VA_ARGS__)) +#define LOG_ENABLED() \ + MOZ_LOG_TEST(nsOSHelperAppService::sLog, mozilla::LogLevel::Debug) + +static nsresult FindSemicolon(nsAString::const_iterator& aSemicolon_iter, + const nsAString::const_iterator& aEnd_iter); +static nsresult ParseMIMEType(const nsAString::const_iterator& aStart_iter, + nsAString::const_iterator& aMajorTypeStart, + nsAString::const_iterator& aMajorTypeEnd, + nsAString::const_iterator& aMinorTypeStart, + nsAString::const_iterator& aMinorTypeEnd, + const nsAString::const_iterator& aEnd_iter); + +inline bool IsNetscapeFormat(const nsACString& aBuffer); + +nsOSHelperAppService::~nsOSHelperAppService() {} + +/* + * Take a command with all the mailcap escapes in it and unescape it + * Ideally this needs the mime type, mime type options, and location of the + * temporary file, but this last can't be got from here + */ +// static +nsresult nsOSHelperAppService::UnescapeCommand(const nsAString& aEscapedCommand, + const nsAString& aMajorType, + const nsAString& aMinorType, + nsACString& aUnEscapedCommand) { + LOG("-- UnescapeCommand"); + LOG("Command to escape: '%s'\n", + NS_LossyConvertUTF16toASCII(aEscapedCommand).get()); + // XXX This function will need to get the mime type and various stuff like + // that being passed in to work properly + + LOG( + ("UnescapeCommand really needs some work -- it should actually do some " + "unescaping\n")); + + CopyUTF16toUTF8(aEscapedCommand, aUnEscapedCommand); + LOG("Escaped command: '%s'\n", PromiseFlatCString(aUnEscapedCommand).get()); + return NS_OK; +} + +/* Put aSemicolon_iter at the first non-escaped semicolon after + * aStart_iter but before aEnd_iter + */ + +static nsresult FindSemicolon(nsAString::const_iterator& aSemicolon_iter, + const nsAString::const_iterator& aEnd_iter) { + bool semicolonFound = false; + while (aSemicolon_iter != aEnd_iter && !semicolonFound) { + switch (*aSemicolon_iter) { + case '\\': + aSemicolon_iter.advance(2); + break; + case ';': + semicolonFound = true; + break; + default: + ++aSemicolon_iter; + break; + } + } + return NS_OK; +} + +static nsresult ParseMIMEType(const nsAString::const_iterator& aStart_iter, + nsAString::const_iterator& aMajorTypeStart, + nsAString::const_iterator& aMajorTypeEnd, + nsAString::const_iterator& aMinorTypeStart, + nsAString::const_iterator& aMinorTypeEnd, + const nsAString::const_iterator& aEnd_iter) { + nsAString::const_iterator iter(aStart_iter); + + // skip leading whitespace + while (iter != aEnd_iter && nsCRT::IsAsciiSpace(*iter)) { + ++iter; + } + + if (iter == aEnd_iter) { + return NS_ERROR_INVALID_ARG; + } + + aMajorTypeStart = iter; + + // find major/minor separator ('/') + while (iter != aEnd_iter && *iter != '/') { + ++iter; + } + + if (iter == aEnd_iter) { + return NS_ERROR_INVALID_ARG; + } + + aMajorTypeEnd = iter; + + // skip '/' + ++iter; + + if (iter == aEnd_iter) { + return NS_ERROR_INVALID_ARG; + } + + aMinorTypeStart = iter; + + // find end of minor type, delimited by whitespace or ';' + while (iter != aEnd_iter && !nsCRT::IsAsciiSpace(*iter) && *iter != ';') { + ++iter; + } + + aMinorTypeEnd = iter; + + return NS_OK; +} + +// TODO: We should consider not only caching the file location but maybe the +// file contents too? +enum class FileKind { + PrivateMimeTypes = 0, + GlobalMimeTypes, + PrivateMailCap, + GlobalMailCap, + + Count, +}; + +struct FileLocationCache { + struct Entry { + bool mIsCached = false; + nsresult mResult = NS_OK; + nsString mLocation; + + void Clear() { *this = {}; } + }; + + EnumeratedArray<FileKind, FileKind::Count, Entry> mEntries; + + static const char* PrefFor(FileKind aKind) { + switch (aKind) { + case FileKind::GlobalMimeTypes: + return "helpers.global_mime_types_file"; + case FileKind::PrivateMimeTypes: + return "helpers.private_mime_types_file"; + case FileKind::GlobalMailCap: + return "helpers.global_mailcap_file"; + case FileKind::PrivateMailCap: + return "helpers.private_mailcap_file"; + case FileKind::Count: + break; + } + MOZ_ASSERT_UNREACHABLE("Unknown file kind"); + return ""; + } + + void Clear() { + for (auto& entry : mEntries) { + entry.Clear(); + } + } + + static void PrefChangeCallback(const char*, void*) { Get().Clear(); } + + static FileLocationCache& Get() { + static FileLocationCache sCache; + static std::once_flag flag; + std::call_once(flag, [&] { + for (auto kind : + MakeEnumeratedRange(FileKind::PrivateMimeTypes, FileKind::Count)) { + Preferences::RegisterCallback(PrefChangeCallback, + nsDependentCString(PrefFor(kind))); + } + RunOnShutdown([] { + sCache.Clear(); + for (auto kind : + MakeEnumeratedRange(FileKind::PrivateMimeTypes, FileKind::Count)) { + Preferences::UnregisterCallback(PrefChangeCallback, + nsDependentCString(PrefFor(kind))); + } + }); + }); + return sCache; + } + + Entry& EntryFor(FileKind aKind) { return mEntries[aKind]; } +}; + +// The lookup order is: +// 1) user pref +// 2) env var (if any) +// 3) pref +static nsresult DoGetFileLocation(FileKind aKind, nsAString& aFileLocation) { + LOG("-- GetFileLocation(%d)\n", int(aKind)); + + aFileLocation.Truncate(); + + const char* envVar = [&]() -> const char* { + switch (aKind) { + case FileKind::PrivateMailCap: + return "PERSONAL_MAILCAP"; + case FileKind::GlobalMailCap: + return "MAILCAP"; + default: + return nullptr; + } + }(); + + const char* prefName = FileLocationCache::PrefFor(aKind); + + if (envVar) { + // If we have an env var we should check whether the pref is a user pref. If + // we do not, we don't care. + if (Preferences::HasUserValue(prefName) && + NS_SUCCEEDED(Preferences::GetString(prefName, aFileLocation))) { + return NS_OK; + } + + char* envValue = PR_GetEnv(envVar); + if (envValue && *envValue) { + // the pref is in the system charset and it's a filepath... The + // natural way to do the charset conversion is by just initing + // an nsIFile with the native path and asking it for the Unicode + // version. + nsresult rv; + nsCOMPtr<nsIFile> file(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = file->InitWithNativePath(nsDependentCString(envValue)); + NS_ENSURE_SUCCESS(rv, rv); + + return file->GetPath(aFileLocation); + } + } + + return Preferences::GetString(prefName, aFileLocation); +} + +static nsresult GetFileLocation(FileKind aKind, nsAString& aFileLocation) { + MOZ_ASSERT(NS_IsMainThread()); + auto& entry = FileLocationCache::Get().EntryFor(aKind); + if (!entry.mIsCached) { + entry.mIsCached = true; + entry.mResult = DoGetFileLocation(aKind, entry.mLocation); + } + aFileLocation = entry.mLocation; + return entry.mResult; +} + +/* Get the mime.types file names from prefs and look up info in them + based on extension */ +// static +nsresult nsOSHelperAppService::LookUpTypeAndDescription( + const nsAString& aFileExtension, nsAString& aMajorType, + nsAString& aMinorType, nsAString& aDescription, bool aUserData) { + LOG("-- LookUpTypeAndDescription for extension '%s'\n", + NS_LossyConvertUTF16toASCII(aFileExtension).get()); + nsAutoString mimeFileName; + + auto kind = + aUserData ? FileKind::PrivateMimeTypes : FileKind::GlobalMimeTypes; + nsresult rv = GetFileLocation(kind, mimeFileName); + if (NS_SUCCEEDED(rv) && !mimeFileName.IsEmpty()) { + rv = GetTypeAndDescriptionFromMimetypesFile( + mimeFileName, aFileExtension, aMajorType, aMinorType, aDescription); + } else { + rv = NS_ERROR_NOT_AVAILABLE; + } + + return rv; +} + +inline bool IsNetscapeFormat(const nsACString& aBuffer) { + return StringBeginsWith( + aBuffer, + nsLiteralCString( + "#--Netscape Communications Corporation MIME Information")) || + StringBeginsWith(aBuffer, "#--MCOM MIME Information"_ns); +} + +/* + * Create a file stream and line input stream for the filename. + * Leaves the first line of the file in aBuffer and sets the format to + * true for netscape files and false for normail ones + */ +// static +nsresult nsOSHelperAppService::CreateInputStream( + const nsAString& aFilename, nsIFileInputStream** aFileInputStream, + nsILineInputStream** aLineInputStream, nsACString& aBuffer, + bool* aNetscapeFormat, bool* aMore) { + LOG("-- CreateInputStream"); + nsresult rv = NS_OK; + + nsCOMPtr<nsIFile> file(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) return rv; + rv = file->InitWithPath(aFilename); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsIFileInputStream> fileStream( + do_CreateInstance(NS_LOCALFILEINPUTSTREAM_CONTRACTID, &rv)); + if (NS_FAILED(rv)) return rv; + rv = fileStream->Init(file, -1, -1, false); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsILineInputStream> lineStream(do_QueryInterface(fileStream, &rv)); + + if (NS_FAILED(rv)) { + LOG("Interface trouble in stream land!"); + return rv; + } + + rv = lineStream->ReadLine(aBuffer, aMore); + if (NS_FAILED(rv)) { + fileStream->Close(); + return rv; + } + + *aNetscapeFormat = IsNetscapeFormat(aBuffer); + + *aFileInputStream = fileStream; + NS_ADDREF(*aFileInputStream); + *aLineInputStream = lineStream; + NS_ADDREF(*aLineInputStream); + + return NS_OK; +} + +/* Open the file, read the first line, decide what type of file it is, + then get info based on extension */ +// static +nsresult nsOSHelperAppService::GetTypeAndDescriptionFromMimetypesFile( + const nsAString& aFilename, const nsAString& aFileExtension, + nsAString& aMajorType, nsAString& aMinorType, nsAString& aDescription) { + LOG("-- GetTypeAndDescriptionFromMimetypesFile\n"); + LOG("Getting type and description from types file '%s'\n", + NS_LossyConvertUTF16toASCII(aFilename).get()); + LOG("Using extension '%s'\n", + NS_LossyConvertUTF16toASCII(aFileExtension).get()); + nsCOMPtr<nsIFileInputStream> mimeFile; + nsCOMPtr<nsILineInputStream> mimeTypes; + bool netscapeFormat; + nsAutoString buf; + nsAutoCString cBuf; + bool more = false; + nsresult rv = CreateInputStream(aFilename, getter_AddRefs(mimeFile), + getter_AddRefs(mimeTypes), cBuf, + &netscapeFormat, &more); + + if (NS_FAILED(rv)) { + return rv; + } + + nsAutoString extensions; + nsAutoStringN<101> entry; + nsAString::const_iterator majorTypeStart, majorTypeEnd, minorTypeStart, + minorTypeEnd, descriptionStart, descriptionEnd; + + do { + CopyASCIItoUTF16(cBuf, buf); + // read through, building up an entry. If we finish an entry, check for + // a match and return out of the loop if we match + + // skip comments and empty lines + if (!buf.IsEmpty() && buf.First() != '#') { + entry.Append(buf); + if (entry.Last() == '\\') { + entry.Truncate(entry.Length() - 1); + entry.Append(char16_t( + ' ')); // in case there is no trailing whitespace on this line + } else { // we have a full entry + LOG("Current entry: '%s'\n", NS_LossyConvertUTF16toASCII(entry).get()); + if (netscapeFormat) { + rv = ParseNetscapeMIMETypesEntry( + entry, majorTypeStart, majorTypeEnd, minorTypeStart, minorTypeEnd, + extensions, descriptionStart, descriptionEnd); + if (NS_FAILED(rv)) { + // We sometimes get things like RealPlayer appending + // "normal" entries to "Netscape" .mime.types files. Try + // to handle that. Bug 106381. + LOG("Bogus entry; trying 'normal' mode\n"); + rv = ParseNormalMIMETypesEntry( + entry, majorTypeStart, majorTypeEnd, minorTypeStart, + minorTypeEnd, extensions, descriptionStart, descriptionEnd); + } + } else { + rv = ParseNormalMIMETypesEntry( + entry, majorTypeStart, majorTypeEnd, minorTypeStart, minorTypeEnd, + extensions, descriptionStart, descriptionEnd); + if (NS_FAILED(rv)) { + // We sometimes get things like StarOffice prepending + // "normal" entries to "Netscape" .mime.types files. Try + // to handle that. Bug 136670. + LOG("Bogus entry; trying 'Netscape' mode\n"); + rv = ParseNetscapeMIMETypesEntry( + entry, majorTypeStart, majorTypeEnd, minorTypeStart, + minorTypeEnd, extensions, descriptionStart, descriptionEnd); + } + } + + if (NS_SUCCEEDED(rv)) { // entry parses + nsAString::const_iterator start, end; + extensions.BeginReading(start); + extensions.EndReading(end); + nsAString::const_iterator iter(start); + + while (start != end) { + FindCharInReadable(',', iter, end); + if (Substring(start, iter) + .Equals(aFileExtension, + nsCaseInsensitiveStringComparator)) { + // it's a match. Assign the type and description and run + aMajorType.Assign(Substring(majorTypeStart, majorTypeEnd)); + aMinorType.Assign(Substring(minorTypeStart, minorTypeEnd)); + aDescription.Assign(Substring(descriptionStart, descriptionEnd)); + mimeFile->Close(); + return NS_OK; + } + if (iter != end) { + ++iter; + } + start = iter; + } + } else { + LOG("Failed to parse entry: %s\n", + NS_LossyConvertUTF16toASCII(entry).get()); + } + // truncate the entry for the next iteration + entry.Truncate(); + } + } + if (!more) { + rv = NS_ERROR_NOT_AVAILABLE; + break; + } + // read the next line + rv = mimeTypes->ReadLine(cBuf, &more); + } while (NS_SUCCEEDED(rv)); + + mimeFile->Close(); + return rv; +} + +/* Get the mime.types file names from prefs and look up info in them + based on mimetype */ +// static +nsresult nsOSHelperAppService::LookUpExtensionsAndDescription( + const nsAString& aMajorType, const nsAString& aMinorType, + nsAString& aFileExtensions, nsAString& aDescription) { + LOG("-- LookUpExtensionsAndDescription for type '%s/%s'\n", + NS_LossyConvertUTF16toASCII(aMajorType).get(), + NS_LossyConvertUTF16toASCII(aMinorType).get()); + nsAutoString mimeFileName; + + nsresult rv = GetFileLocation(FileKind::PrivateMimeTypes, mimeFileName); + if (NS_SUCCEEDED(rv) && !mimeFileName.IsEmpty()) { + rv = GetExtensionsAndDescriptionFromMimetypesFile( + mimeFileName, aMajorType, aMinorType, aFileExtensions, aDescription); + } else { + rv = NS_ERROR_NOT_AVAILABLE; + } + if (NS_FAILED(rv) || aFileExtensions.IsEmpty()) { + rv = GetFileLocation(FileKind::GlobalMimeTypes, mimeFileName); + if (NS_SUCCEEDED(rv) && !mimeFileName.IsEmpty()) { + rv = GetExtensionsAndDescriptionFromMimetypesFile( + mimeFileName, aMajorType, aMinorType, aFileExtensions, aDescription); + } else { + rv = NS_ERROR_NOT_AVAILABLE; + } + } + return rv; +} + +/* Open the file, read the first line, decide what type of file it is, + then get info based on extension */ +// static +nsresult nsOSHelperAppService::GetExtensionsAndDescriptionFromMimetypesFile( + const nsAString& aFilename, const nsAString& aMajorType, + const nsAString& aMinorType, nsAString& aFileExtensions, + nsAString& aDescription) { + LOG("-- GetExtensionsAndDescriptionFromMimetypesFile\n"); + LOG("Getting extensions and description from types file '%s'\n", + NS_LossyConvertUTF16toASCII(aFilename).get()); + LOG("Using type '%s/%s'\n", NS_LossyConvertUTF16toASCII(aMajorType).get(), + NS_LossyConvertUTF16toASCII(aMinorType).get()); + nsCOMPtr<nsIFileInputStream> mimeFile; + nsCOMPtr<nsILineInputStream> mimeTypes; + bool netscapeFormat; + nsAutoCString cBuf; + nsAutoString buf; + bool more = false; + nsresult rv = CreateInputStream(aFilename, getter_AddRefs(mimeFile), + getter_AddRefs(mimeTypes), cBuf, + &netscapeFormat, &more); + if (NS_FAILED(rv)) { + return rv; + } + + nsAutoString extensions; + nsAutoStringN<101> entry; + nsAString::const_iterator majorTypeStart, majorTypeEnd, minorTypeStart, + minorTypeEnd, descriptionStart, descriptionEnd; + + do { + CopyASCIItoUTF16(cBuf, buf); + // read through, building up an entry. If we finish an entry, check for + // a match and return out of the loop if we match + + // skip comments and empty lines + if (!buf.IsEmpty() && buf.First() != '#') { + entry.Append(buf); + if (entry.Last() == '\\') { + entry.Truncate(entry.Length() - 1); + entry.Append(char16_t( + ' ')); // in case there is no trailing whitespace on this line + } else { // we have a full entry + LOG("Current entry: '%s'\n", NS_LossyConvertUTF16toASCII(entry).get()); + if (netscapeFormat) { + rv = ParseNetscapeMIMETypesEntry( + entry, majorTypeStart, majorTypeEnd, minorTypeStart, minorTypeEnd, + extensions, descriptionStart, descriptionEnd); + + if (NS_FAILED(rv)) { + // We sometimes get things like RealPlayer appending + // "normal" entries to "Netscape" .mime.types files. Try + // to handle that. Bug 106381. + LOG("Bogus entry; trying 'normal' mode\n"); + rv = ParseNormalMIMETypesEntry( + entry, majorTypeStart, majorTypeEnd, minorTypeStart, + minorTypeEnd, extensions, descriptionStart, descriptionEnd); + } + } else { + rv = ParseNormalMIMETypesEntry( + entry, majorTypeStart, majorTypeEnd, minorTypeStart, minorTypeEnd, + extensions, descriptionStart, descriptionEnd); + + if (NS_FAILED(rv)) { + // We sometimes get things like StarOffice prepending + // "normal" entries to "Netscape" .mime.types files. Try + // to handle that. Bug 136670. + LOG("Bogus entry; trying 'Netscape' mode\n"); + rv = ParseNetscapeMIMETypesEntry( + entry, majorTypeStart, majorTypeEnd, minorTypeStart, + minorTypeEnd, extensions, descriptionStart, descriptionEnd); + } + } + + if (NS_SUCCEEDED(rv) && + Substring(majorTypeStart, majorTypeEnd) + .Equals(aMajorType, nsCaseInsensitiveStringComparator) && + Substring(minorTypeStart, minorTypeEnd) + .Equals(aMinorType, nsCaseInsensitiveStringComparator)) { + // it's a match + aFileExtensions.Assign(extensions); + aDescription.Assign(Substring(descriptionStart, descriptionEnd)); + mimeFile->Close(); + return NS_OK; + } + if (NS_FAILED(rv)) { + LOG("Failed to parse entry: %s\n", + NS_LossyConvertUTF16toASCII(entry).get()); + } + + entry.Truncate(); + } + } + if (!more) { + rv = NS_ERROR_NOT_AVAILABLE; + break; + } + // read the next line + rv = mimeTypes->ReadLine(cBuf, &more); + } while (NS_SUCCEEDED(rv)); + + mimeFile->Close(); + return rv; +} + +/* + * This parses a Netscape format mime.types entry. There are two + * possible formats: + * + * type=foo/bar; options exts="baz" description="Some type" + * + * and + * + * type=foo/bar; options description="Some type" exts="baz" + */ +// static +nsresult nsOSHelperAppService::ParseNetscapeMIMETypesEntry( + const nsAString& aEntry, nsAString::const_iterator& aMajorTypeStart, + nsAString::const_iterator& aMajorTypeEnd, + nsAString::const_iterator& aMinorTypeStart, + nsAString::const_iterator& aMinorTypeEnd, nsAString& aExtensions, + nsAString::const_iterator& aDescriptionStart, + nsAString::const_iterator& aDescriptionEnd) { + LOG("-- ParseNetscapeMIMETypesEntry\n"); + NS_ASSERTION(!aEntry.IsEmpty(), + "Empty Netscape MIME types entry being parsed."); + + nsAString::const_iterator start_iter, end_iter, match_start, match_end; + + aEntry.BeginReading(start_iter); + aEntry.EndReading(end_iter); + + // skip trailing whitespace + do { + --end_iter; + } while (end_iter != start_iter && nsCRT::IsAsciiSpace(*end_iter)); + // if we're pointing to a quote, don't advance -- we don't want to + // include the quote.... + if (*end_iter != '"') ++end_iter; + match_start = start_iter; + match_end = end_iter; + + // Get the major and minor types + // First the major type + if (!FindInReadable(u"type="_ns, match_start, match_end)) { + return NS_ERROR_FAILURE; + } + + match_start = match_end; + + while (match_end != end_iter && *match_end != '/') { + ++match_end; + } + if (match_end == end_iter) { + return NS_ERROR_FAILURE; + } + + aMajorTypeStart = match_start; + aMajorTypeEnd = match_end; + + // now the minor type + if (++match_end == end_iter) { + return NS_ERROR_FAILURE; + } + + match_start = match_end; + + while (match_end != end_iter && !nsCRT::IsAsciiSpace(*match_end) && + *match_end != ';') { + ++match_end; + } + if (match_end == end_iter) { + return NS_ERROR_FAILURE; + } + + aMinorTypeStart = match_start; + aMinorTypeEnd = match_end; + + // ignore everything up to the end of the mime type from here on + start_iter = match_end; + + // get the extensions + match_start = match_end; + match_end = end_iter; + if (FindInReadable(u"exts="_ns, match_start, match_end)) { + nsAString::const_iterator extStart, extEnd; + + if (match_end == end_iter || + (*match_end == '"' && ++match_end == end_iter)) { + return NS_ERROR_FAILURE; + } + + extStart = match_end; + match_start = extStart; + match_end = end_iter; + if (FindInReadable(u"desc=\""_ns, match_start, match_end)) { + // exts= before desc=, so we have to find the actual end of the extensions + extEnd = match_start; + if (extEnd == extStart) { + return NS_ERROR_FAILURE; + } + + do { + --extEnd; + } while (extEnd != extStart && nsCRT::IsAsciiSpace(*extEnd)); + + if (extEnd != extStart && *extEnd == '"') { + --extEnd; + } + } else { + // desc= before exts=, so we can use end_iter as the end of the extensions + extEnd = end_iter; + } + aExtensions = Substring(extStart, extEnd); + } else { + // no extensions + aExtensions.Truncate(); + } + + // get the description + match_start = start_iter; + match_end = end_iter; + if (FindInReadable(u"desc=\""_ns, match_start, match_end)) { + aDescriptionStart = match_end; + match_start = aDescriptionStart; + match_end = end_iter; + if (FindInReadable(u"exts="_ns, match_start, match_end)) { + // exts= after desc=, so have to find actual end of description + aDescriptionEnd = match_start; + if (aDescriptionEnd == aDescriptionStart) { + return NS_ERROR_FAILURE; + } + + do { + --aDescriptionEnd; + } while (aDescriptionEnd != aDescriptionStart && + nsCRT::IsAsciiSpace(*aDescriptionEnd)); + } else { + // desc= after exts=, so use end_iter for the description end + aDescriptionEnd = end_iter; + } + } else { + // no description + aDescriptionStart = start_iter; + aDescriptionEnd = start_iter; + } + + return NS_OK; +} + +/* + * This parses a normal format mime.types entry. The format is: + * + * major/minor ext1 ext2 ext3 + */ +// static +nsresult nsOSHelperAppService::ParseNormalMIMETypesEntry( + const nsAString& aEntry, nsAString::const_iterator& aMajorTypeStart, + nsAString::const_iterator& aMajorTypeEnd, + nsAString::const_iterator& aMinorTypeStart, + nsAString::const_iterator& aMinorTypeEnd, nsAString& aExtensions, + nsAString::const_iterator& aDescriptionStart, + nsAString::const_iterator& aDescriptionEnd) { + LOG("-- ParseNormalMIMETypesEntry\n"); + NS_ASSERTION(!aEntry.IsEmpty(), + "Empty Normal MIME types entry being parsed."); + + nsAString::const_iterator start_iter, end_iter, iter; + + aEntry.BeginReading(start_iter); + aEntry.EndReading(end_iter); + + // no description + aDescriptionStart = start_iter; + aDescriptionEnd = start_iter; + + // skip leading whitespace + while (start_iter != end_iter && nsCRT::IsAsciiSpace(*start_iter)) { + ++start_iter; + } + if (start_iter == end_iter) { + return NS_ERROR_FAILURE; + } + // skip trailing whitespace + do { + --end_iter; + } while (end_iter != start_iter && nsCRT::IsAsciiSpace(*end_iter)); + + ++end_iter; // point to first whitespace char (or to end of string) + iter = start_iter; + + // get the major type + if (!FindCharInReadable('/', iter, end_iter)) return NS_ERROR_FAILURE; + + nsAString::const_iterator equals_sign_iter(start_iter); + if (FindCharInReadable('=', equals_sign_iter, iter)) + return NS_ERROR_FAILURE; // see bug 136670 + + aMajorTypeStart = start_iter; + aMajorTypeEnd = iter; + + // get the minor type + if (++iter == end_iter) { + return NS_ERROR_FAILURE; + } + start_iter = iter; + + while (iter != end_iter && !nsCRT::IsAsciiSpace(*iter)) { + ++iter; + } + aMinorTypeStart = start_iter; + aMinorTypeEnd = iter; + + // get the extensions + aExtensions.Truncate(); + while (iter != end_iter) { + while (iter != end_iter && nsCRT::IsAsciiSpace(*iter)) { + ++iter; + } + + start_iter = iter; + while (iter != end_iter && !nsCRT::IsAsciiSpace(*iter)) { + ++iter; + } + aExtensions.Append(Substring(start_iter, iter)); + if (iter != end_iter) { // not the last extension + aExtensions.Append(char16_t(',')); + } + } + + return NS_OK; +} + +// static +nsresult nsOSHelperAppService::LookUpHandlerAndDescription( + const nsAString& aMajorType, const nsAString& aMinorType, + nsAString& aHandler, nsAString& aDescription, nsAString& aMozillaFlags) { + // The mailcap lookup is two-pass to handle the case of mailcap files + // that have something like: + // + // text/*; emacs %s + // text/rtf; soffice %s + // + // in that order. We want to pick up "soffice" for text/rtf in such cases + nsresult rv = DoLookUpHandlerAndDescription( + aMajorType, aMinorType, aHandler, aDescription, aMozillaFlags, true); + if (NS_FAILED(rv)) { + rv = DoLookUpHandlerAndDescription(aMajorType, aMinorType, aHandler, + aDescription, aMozillaFlags, false); + } + + // maybe we have an entry for "aMajorType/*"? + if (NS_FAILED(rv)) { + rv = DoLookUpHandlerAndDescription(aMajorType, u"*"_ns, aHandler, + aDescription, aMozillaFlags, true); + } + + if (NS_FAILED(rv)) { + rv = DoLookUpHandlerAndDescription(aMajorType, u"*"_ns, aHandler, + aDescription, aMozillaFlags, false); + } + + return rv; +} + +// static +nsresult nsOSHelperAppService::DoLookUpHandlerAndDescription( + const nsAString& aMajorType, const nsAString& aMinorType, + nsAString& aHandler, nsAString& aDescription, nsAString& aMozillaFlags, + bool aUserData) { + LOG("-- LookUpHandlerAndDescription for type '%s/%s'\n", + NS_LossyConvertUTF16toASCII(aMajorType).get(), + NS_LossyConvertUTF16toASCII(aMinorType).get()); + nsAutoString mailcapFileName; + + const auto kind = + aUserData ? FileKind::PrivateMailCap : FileKind::GlobalMailCap; + nsresult rv = GetFileLocation(kind, mailcapFileName); + if (NS_FAILED(rv) || mailcapFileName.IsEmpty()) { + return NS_ERROR_NOT_AVAILABLE; + } + return GetHandlerAndDescriptionFromMailcapFile(mailcapFileName, aMajorType, + aMinorType, aHandler, + aDescription, aMozillaFlags); +} + +// static +nsresult nsOSHelperAppService::GetHandlerAndDescriptionFromMailcapFile( + const nsAString& aFilename, const nsAString& aMajorType, + const nsAString& aMinorType, nsAString& aHandler, nsAString& aDescription, + nsAString& aMozillaFlags) { + LOG("-- GetHandlerAndDescriptionFromMailcapFile\n"); + LOG("Getting handler and description from mailcap file '%s'\n", + NS_LossyConvertUTF16toASCII(aFilename).get()); + LOG("Using type '%s/%s'\n", NS_LossyConvertUTF16toASCII(aMajorType).get(), + NS_LossyConvertUTF16toASCII(aMinorType).get()); + + nsresult rv = NS_OK; + bool more = false; + + nsCOMPtr<nsIFile> file(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) return rv; + rv = file->InitWithPath(aFilename); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsIFileInputStream> mailcapFile( + do_CreateInstance(NS_LOCALFILEINPUTSTREAM_CONTRACTID, &rv)); + if (NS_FAILED(rv)) return rv; + rv = mailcapFile->Init(file, -1, -1, false); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsILineInputStream> mailcap(do_QueryInterface(mailcapFile, &rv)); + + if (NS_FAILED(rv)) { + LOG("Interface trouble in stream land!"); + return rv; + } + + nsAutoStringN<129> entry; + nsAutoStringN<81> buffer; + nsAutoCStringN<81> cBuffer; + rv = mailcap->ReadLine(cBuffer, &more); + if (NS_FAILED(rv)) { + mailcapFile->Close(); + return rv; + } + + do { // return on end-of-file in the loop + + CopyASCIItoUTF16(cBuffer, buffer); + if (!buffer.IsEmpty() && buffer.First() != '#') { + entry.Append(buffer); + if (entry.Last() == '\\') { // entry continues on next line + entry.Truncate(entry.Length() - 1); + entry.Append(char16_t( + ' ')); // in case there is no trailing whitespace on this line + } else { // we have a full entry in entry. Check it for the type + LOG("Current entry: '%s'\n", NS_LossyConvertUTF16toASCII(entry).get()); + + nsAString::const_iterator semicolon_iter, start_iter, end_iter, + majorTypeStart, majorTypeEnd, minorTypeStart, minorTypeEnd; + entry.BeginReading(start_iter); + entry.EndReading(end_iter); + semicolon_iter = start_iter; + FindSemicolon(semicolon_iter, end_iter); + if (semicolon_iter != + end_iter) { // we have something resembling a valid entry + rv = ParseMIMEType(start_iter, majorTypeStart, majorTypeEnd, + minorTypeStart, minorTypeEnd, semicolon_iter); + if (NS_SUCCEEDED(rv) && + Substring(majorTypeStart, majorTypeEnd) + .Equals(aMajorType, nsCaseInsensitiveStringComparator) && + Substring(minorTypeStart, minorTypeEnd) + .Equals(aMinorType, nsCaseInsensitiveStringComparator)) { + // we have a match + bool match = true; + ++semicolon_iter; // point at the first char past the semicolon + start_iter = semicolon_iter; // handler string starts here + FindSemicolon(semicolon_iter, end_iter); + while (start_iter != semicolon_iter && + nsCRT::IsAsciiSpace(*start_iter)) { + ++start_iter; + } + + LOG("The real handler is: '%s'\n", + NS_LossyConvertUTF16toASCII( + Substring(start_iter, semicolon_iter)) + .get()); + + // XXX ugly hack. Just grab the executable name + nsAString::const_iterator end_handler_iter = semicolon_iter; + nsAString::const_iterator end_executable_iter = start_iter; + while (end_executable_iter != end_handler_iter && + !nsCRT::IsAsciiSpace(*end_executable_iter)) { + ++end_executable_iter; + } + // XXX End ugly hack + + aHandler = Substring(start_iter, end_executable_iter); + + nsAString::const_iterator start_option_iter, end_optionname_iter, + equal_sign_iter; + bool equalSignFound; + while (match && semicolon_iter != end_iter && + ++semicolon_iter != + end_iter) { // there are options left and we still match + start_option_iter = semicolon_iter; + // skip over leading whitespace + while (start_option_iter != end_iter && + nsCRT::IsAsciiSpace(*start_option_iter)) { + ++start_option_iter; + } + if (start_option_iter == end_iter) { // nothing actually here + break; + } + semicolon_iter = start_option_iter; + FindSemicolon(semicolon_iter, end_iter); + equal_sign_iter = start_option_iter; + equalSignFound = false; + while (equal_sign_iter != semicolon_iter && !equalSignFound) { + switch (*equal_sign_iter) { + case '\\': + equal_sign_iter.advance(2); + break; + case '=': + equalSignFound = true; + break; + default: + ++equal_sign_iter; + break; + } + } + end_optionname_iter = start_option_iter; + // find end of option name + while (end_optionname_iter != equal_sign_iter && + !nsCRT::IsAsciiSpace(*end_optionname_iter)) { + ++end_optionname_iter; + } + nsDependentSubstring optionName(start_option_iter, + end_optionname_iter); + if (equalSignFound) { + // This is an option that has a name and value + if (optionName.EqualsLiteral("description")) { + aDescription = Substring(++equal_sign_iter, semicolon_iter); + } else if (optionName.EqualsLiteral("x-mozilla-flags")) { + aMozillaFlags = Substring(++equal_sign_iter, semicolon_iter); + } else if (optionName.EqualsLiteral("test")) { + nsAutoCString testCommand; + rv = UnescapeCommand( + Substring(++equal_sign_iter, semicolon_iter), aMajorType, + aMinorType, testCommand); + if (NS_FAILED(rv)) continue; + nsCOMPtr<nsIProcess> process = + do_CreateInstance(NS_PROCESS_CONTRACTID, &rv); + if (NS_FAILED(rv)) continue; + nsCOMPtr<nsIFile> file( + do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) continue; + rv = file->InitWithNativePath("/bin/sh"_ns); + if (NS_FAILED(rv)) continue; + rv = process->Init(file); + if (NS_FAILED(rv)) continue; + const char* args[] = {"-c", testCommand.get()}; + LOG("Running Test: %s\n", testCommand.get()); + rv = process->Run(true, args, 2); + if (NS_FAILED(rv)) continue; + int32_t exitValue; + rv = process->GetExitValue(&exitValue); + if (NS_FAILED(rv)) continue; + LOG("Exit code: %d\n", exitValue); + if (exitValue) { + match = false; + } + } + } else { + // This is an option that just has a name but no value (eg + // "copiousoutput") + if (optionName.EqualsLiteral("needsterminal")) { + match = false; + } + } + } + + if (match) { // we did not fail any test clauses; all is good + // get out of here + mailcapFile->Close(); + return NS_OK; + } + // pretend that this match never happened + aDescription.Truncate(); + aMozillaFlags.Truncate(); + aHandler.Truncate(); + } + } + // zero out the entry for the next cycle + entry.Truncate(); + } + } + if (!more) { + rv = NS_ERROR_NOT_AVAILABLE; + break; + } + rv = mailcap->ReadLine(cBuffer, &more); + } while (NS_SUCCEEDED(rv)); + mailcapFile->Close(); + return rv; +} + +nsresult nsOSHelperAppService::OSProtocolHandlerExists( + const char* aProtocolScheme, bool* aHandlerExists) { + nsresult rv = NS_OK; + + if (!XRE_IsContentProcess()) { +#ifdef MOZ_WIDGET_GTK + // Check the GNOME registry for a protocol handler + *aHandlerExists = nsGNOMERegistry::HandlerExists(aProtocolScheme); +#else + *aHandlerExists = false; +#endif + } else { + *aHandlerExists = false; + nsCOMPtr<nsIHandlerService> handlerSvc = + do_GetService(NS_HANDLERSERVICE_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv) && handlerSvc) { + rv = handlerSvc->ExistsForProtocolOS(nsCString(aProtocolScheme), + aHandlerExists); + } + } + + return rv; +} + +NS_IMETHODIMP nsOSHelperAppService::GetApplicationDescription( + const nsACString& aScheme, nsAString& _retval) { +#ifdef MOZ_WIDGET_GTK + nsGNOMERegistry::GetAppDescForScheme(aScheme, _retval); + return _retval.IsEmpty() ? NS_ERROR_NOT_AVAILABLE : NS_OK; +#else + return NS_ERROR_NOT_AVAILABLE; +#endif +} + +NS_IMETHODIMP nsOSHelperAppService::IsCurrentAppOSDefaultForProtocol( + const nsACString& aScheme, bool* _retval) { + *_retval = false; +#if defined(MOZ_BUILD_APP_IS_BROWSER) && defined(MOZ_WIDGET_GTK) + if (nsCOMPtr<nsIGNOMEShellService> shell = + do_GetService(NS_TOOLKITSHELLSERVICE_CONTRACTID)) { + return shell->IsDefaultForScheme(aScheme, _retval); + } +#endif + return NS_OK; +} + +nsresult nsOSHelperAppService::GetFileTokenForPath( + const char16_t* platformAppPath, nsIFile** aFile) { + LOG("-- nsOSHelperAppService::GetFileTokenForPath: '%s'\n", + NS_LossyConvertUTF16toASCII(platformAppPath).get()); + if (!*platformAppPath) { // empty filename--return error + NS_WARNING("Empty filename passed in."); + return NS_ERROR_INVALID_ARG; + } + + // first check if the base class implementation finds anything + nsresult rv = + nsExternalHelperAppService::GetFileTokenForPath(platformAppPath, aFile); + if (NS_SUCCEEDED(rv)) return rv; + // If the reason for failure was that the file doesn't exist, return too + // (because it means the path was absolute, and so that we shouldn't search in + // the path) + if (rv == NS_ERROR_FILE_NOT_FOUND) return rv; + + // If we get here, we really should have a relative path. + NS_ASSERTION(*platformAppPath != char16_t('/'), "Unexpected absolute path"); + + nsCOMPtr<nsIFile> localFile(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID)); + + if (!localFile) return NS_ERROR_NOT_INITIALIZED; + + bool exists = false; + // ugly hack. Walk the PATH variable... + char* unixpath = PR_GetEnv("PATH"); + nsAutoCString path(unixpath); + + const char* start_iter = path.BeginReading(start_iter); + const char* colon_iter = start_iter; + const char* end_iter = path.EndReading(end_iter); + + while (start_iter != end_iter && !exists) { + while (colon_iter != end_iter && *colon_iter != ':') { + ++colon_iter; + } + localFile->InitWithNativePath(Substring(start_iter, colon_iter)); + rv = localFile->AppendRelativePath(nsDependentString(platformAppPath)); + // Failing AppendRelativePath is a bad thing - it should basically always + // succeed given a relative path. Show a warning if it does fail. + // To prevent infinite loops when it does fail, return at this point. + NS_ENSURE_SUCCESS(rv, rv); + localFile->Exists(&exists); + if (!exists) { + if (colon_iter == end_iter) { + break; + } + ++colon_iter; + start_iter = colon_iter; + } + } + + if (exists) { + rv = NS_OK; + } else { + rv = NS_ERROR_NOT_AVAILABLE; + } + + *aFile = localFile; + NS_IF_ADDREF(*aFile); + + return rv; +} + +already_AddRefed<nsMIMEInfoBase> nsOSHelperAppService::GetFromExtension( + const nsCString& aFileExt) { + // if the extension is empty, return immediately + if (aFileExt.IsEmpty()) { + return nullptr; + } + + LOG("Here we do an extension lookup for '%s'\n", aFileExt.get()); + + nsAutoString majorType, minorType, mime_types_description, + mailcap_description, handler, mozillaFlags; + + nsresult rv = + LookUpTypeAndDescription(NS_ConvertUTF8toUTF16(aFileExt), majorType, + minorType, mime_types_description, true); + + if (NS_FAILED(rv) || majorType.IsEmpty()) { +#ifdef MOZ_WIDGET_GTK + LOG("Looking in GNOME registry\n"); + RefPtr<nsMIMEInfoBase> gnomeInfo = + nsGNOMERegistry::GetFromExtension(aFileExt); + if (gnomeInfo) { + LOG("Got MIMEInfo from GNOME registry\n"); + return gnomeInfo.forget(); + } +#endif + + rv = LookUpTypeAndDescription(NS_ConvertUTF8toUTF16(aFileExt), majorType, + minorType, mime_types_description, false); + } + + if (NS_FAILED(rv)) { + return nullptr; + } + + NS_LossyConvertUTF16toASCII asciiMajorType(majorType); + NS_LossyConvertUTF16toASCII asciiMinorType(minorType); + + LOG("Type/Description results: majorType='%s', minorType='%s', " + "description='%s'\n", + asciiMajorType.get(), asciiMinorType.get(), + NS_LossyConvertUTF16toASCII(mime_types_description).get()); + + if (majorType.IsEmpty() && minorType.IsEmpty()) { + // we didn't get a type mapping, so we can't do anything useful + return nullptr; + } + + nsAutoCString mimeType(asciiMajorType + "/"_ns + asciiMinorType); + RefPtr<nsMIMEInfoUnix> mimeInfo = new nsMIMEInfoUnix(mimeType); + + mimeInfo->AppendExtension(aFileExt); + rv = LookUpHandlerAndDescription(majorType, minorType, handler, + mailcap_description, mozillaFlags); + LOG("Handler/Description results: handler='%s', description='%s', " + "mozillaFlags='%s'\n", + NS_LossyConvertUTF16toASCII(handler).get(), + NS_LossyConvertUTF16toASCII(mailcap_description).get(), + NS_LossyConvertUTF16toASCII(mozillaFlags).get()); + mailcap_description.Trim(" \t\""); + mozillaFlags.Trim(" \t"); + if (!mime_types_description.IsEmpty()) { + mimeInfo->SetDescription(mime_types_description); + } else { + mimeInfo->SetDescription(mailcap_description); + } + + if (NS_SUCCEEDED(rv) && handler.IsEmpty()) { + rv = NS_ERROR_NOT_AVAILABLE; + } + + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIFile> handlerFile; + rv = GetFileTokenForPath(handler.get(), getter_AddRefs(handlerFile)); + + if (NS_SUCCEEDED(rv)) { + mimeInfo->SetDefaultApplication(handlerFile); + mimeInfo->SetPreferredAction(nsIMIMEInfo::saveToDisk); + mimeInfo->SetDefaultDescription(handler); + } + } + + if (NS_FAILED(rv)) { + mimeInfo->SetPreferredAction(nsIMIMEInfo::saveToDisk); + } + + return mimeInfo.forget(); +} + +already_AddRefed<nsMIMEInfoBase> nsOSHelperAppService::GetFromType( + const nsCString& aMIMEType) { + // if the type is empty, return immediately + if (aMIMEType.IsEmpty()) { + return nullptr; + } + + LOG("Here we do a mimetype lookup for '%s'\n", aMIMEType.get()); + + // extract the major and minor types + NS_ConvertASCIItoUTF16 mimeType(aMIMEType); + nsAString::const_iterator start_iter, end_iter, majorTypeStart, majorTypeEnd, + minorTypeStart, minorTypeEnd; + + mimeType.BeginReading(start_iter); + mimeType.EndReading(end_iter); + + // XXX FIXME: add typeOptions parsing in here + nsresult rv = ParseMIMEType(start_iter, majorTypeStart, majorTypeEnd, + minorTypeStart, minorTypeEnd, end_iter); + + if (NS_FAILED(rv)) { + return nullptr; + } + + nsDependentSubstring majorType(majorTypeStart, majorTypeEnd); + nsDependentSubstring minorType(minorTypeStart, minorTypeEnd); + + // First check the user's private mailcap file + nsAutoString mailcap_description, handler, mozillaFlags; + DoLookUpHandlerAndDescription(majorType, minorType, handler, + mailcap_description, mozillaFlags, true); + + LOG("Private Handler/Description results: handler='%s', description='%s'\n", + NS_LossyConvertUTF16toASCII(handler).get(), + NS_LossyConvertUTF16toASCII(mailcap_description).get()); + + // Now look up our extensions + nsAutoString extensions, mime_types_description; + LookUpExtensionsAndDescription(majorType, minorType, extensions, + mime_types_description); + +#ifdef MOZ_WIDGET_GTK + if (handler.IsEmpty()) { + RefPtr<nsMIMEInfoBase> gnomeInfo = nsGNOMERegistry::GetFromType(aMIMEType); + if (gnomeInfo) { + LOG("Got MIMEInfo from GNOME registry without extensions; setting them " + "to %s\n", + NS_LossyConvertUTF16toASCII(extensions).get()); + + NS_ASSERTION(!gnomeInfo->HasExtensions(), "How'd that happen?"); + gnomeInfo->SetFileExtensions(NS_ConvertUTF16toUTF8(extensions)); + return gnomeInfo.forget(); + } + } +#endif + + if (handler.IsEmpty()) { + DoLookUpHandlerAndDescription(majorType, minorType, handler, + mailcap_description, mozillaFlags, false); + } + + if (handler.IsEmpty()) { + DoLookUpHandlerAndDescription(majorType, u"*"_ns, handler, + mailcap_description, mozillaFlags, true); + } + + if (handler.IsEmpty()) { + DoLookUpHandlerAndDescription(majorType, u"*"_ns, handler, + mailcap_description, mozillaFlags, false); + } + + LOG("Handler/Description results: handler='%s', description='%s', " + "mozillaFlags='%s'\n", + NS_LossyConvertUTF16toASCII(handler).get(), + NS_LossyConvertUTF16toASCII(mailcap_description).get(), + NS_LossyConvertUTF16toASCII(mozillaFlags).get()); + + mailcap_description.Trim(" \t\""); + mozillaFlags.Trim(" \t"); + + if (handler.IsEmpty() && extensions.IsEmpty() && + mailcap_description.IsEmpty() && mime_types_description.IsEmpty()) { + // No real useful info + return nullptr; + } + + RefPtr<nsMIMEInfoUnix> mimeInfo = new nsMIMEInfoUnix(aMIMEType); + + mimeInfo->SetFileExtensions(NS_ConvertUTF16toUTF8(extensions)); + if (!mime_types_description.IsEmpty()) { + mimeInfo->SetDescription(mime_types_description); + } else { + mimeInfo->SetDescription(mailcap_description); + } + + rv = NS_ERROR_NOT_AVAILABLE; + nsCOMPtr<nsIFile> handlerFile; + if (!handler.IsEmpty()) { + rv = GetFileTokenForPath(handler.get(), getter_AddRefs(handlerFile)); + } + + if (NS_SUCCEEDED(rv)) { + mimeInfo->SetDefaultApplication(handlerFile); + mimeInfo->SetPreferredAction(nsIMIMEInfo::saveToDisk); + mimeInfo->SetDefaultDescription(handler); + } else { + mimeInfo->SetPreferredAction(nsIMIMEInfo::saveToDisk); + } + + return mimeInfo.forget(); +} + +nsresult nsOSHelperAppService::GetMIMEInfoFromOS(const nsACString& aType, + const nsACString& aFileExt, + bool* aFound, + nsIMIMEInfo** aMIMEInfo) { + *aFound = true; + RefPtr<nsMIMEInfoBase> retval; + // Fallback to lookup by extension when generic 'application/octet-stream' + // content type is received. + if (!aType.EqualsLiteral(APPLICATION_OCTET_STREAM)) { + retval = GetFromType(PromiseFlatCString(aType)); + } + bool hasDefault = false; + if (retval) retval->GetHasDefaultHandler(&hasDefault); + if (!retval || !hasDefault) { + RefPtr<nsMIMEInfoBase> miByExt = + GetFromExtension(PromiseFlatCString(aFileExt)); + // If we had no extension match, but a type match, use that + if (!miByExt && retval) { + retval.forget(aMIMEInfo); + return NS_OK; + } + // If we had an extension match but no type match, set the mimetype and use + // it + if (!retval && miByExt) { + if (!aType.IsEmpty()) miByExt->SetMIMEType(aType); + miByExt.swap(retval); + + retval.forget(aMIMEInfo); + return NS_OK; + } + // If we got nothing, make a new mimeinfo + if (!retval) { + *aFound = false; + retval = new nsMIMEInfoUnix(aType); + if (retval) { + if (!aFileExt.IsEmpty()) retval->AppendExtension(aFileExt); + } + + retval.forget(aMIMEInfo); + return NS_OK; + } + + // Copy the attributes of retval (mimeinfo from type) onto miByExt, to + // return it + // but reset to just collected mDefaultAppDescription (from ext) + nsAutoString byExtDefault; + miByExt->GetDefaultDescription(byExtDefault); + retval->SetDefaultDescription(byExtDefault); + retval->CopyBasicDataTo(miByExt); + + miByExt.swap(retval); + } + retval.forget(aMIMEInfo); + return NS_OK; +} + +NS_IMETHODIMP +nsOSHelperAppService::GetProtocolHandlerInfoFromOS(const nsACString& aScheme, + bool* found, + nsIHandlerInfo** _retval) { + NS_ASSERTION(!aScheme.IsEmpty(), "No scheme was specified!"); + + nsresult rv = + OSProtocolHandlerExists(nsPromiseFlatCString(aScheme).get(), found); + if (NS_FAILED(rv)) return rv; + + nsMIMEInfoUnix* handlerInfo = + new nsMIMEInfoUnix(aScheme, nsMIMEInfoBase::eProtocolInfo); + NS_ENSURE_TRUE(handlerInfo, NS_ERROR_OUT_OF_MEMORY); + NS_ADDREF(*_retval = handlerInfo); + + if (!*found) { + // Code that calls this requires an object regardless if the OS has + // something for us, so we return the empty object. + return NS_OK; + } + + nsAutoString desc; + GetApplicationDescription(aScheme, desc); + handlerInfo->SetDefaultDescription(desc); + + return NS_OK; +} diff --git a/uriloader/exthandler/unix/nsOSHelperAppService.h b/uriloader/exthandler/unix/nsOSHelperAppService.h new file mode 100644 index 0000000000..25feeac359 --- /dev/null +++ b/uriloader/exthandler/unix/nsOSHelperAppService.h @@ -0,0 +1,122 @@ +/* -*- Mode: C++; tab-width: 3; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsOSHelperAppService_h__ +#define nsOSHelperAppService_h__ + +// The OS helper app service is a subclass of nsExternalHelperAppService and is +// implemented on each platform. It contains platform specific code for finding +// helper applications for a given mime type in addition to launching those +// applications. + +#include "nsExternalHelperAppService.h" +#include "nsCExternalHandlerService.h" +#include "nsCOMPtr.h" + +class nsIFileInputStream; +class nsILineInputStream; +class nsMIMEInfoBase; + +class nsOSHelperAppService : public nsExternalHelperAppService { + public: + virtual ~nsOSHelperAppService(); + + // method overrides for mime.types and mime.info look up steps + NS_IMETHOD GetMIMEInfoFromOS(const nsACString& aMimeType, + const nsACString& aFileExt, bool* aFound, + nsIMIMEInfo** aMIMEInfo) override; + NS_IMETHOD GetProtocolHandlerInfoFromOS(const nsACString& aScheme, + bool* found, + nsIHandlerInfo** _retval) override; + + // override nsIExternalProtocolService methods + nsresult OSProtocolHandlerExists(const char* aProtocolScheme, + bool* aHandlerExists) override; + NS_IMETHOD GetApplicationDescription(const nsACString& aScheme, + nsAString& _retval) override; + NS_IMETHOD IsCurrentAppOSDefaultForProtocol(const nsACString& aScheme, + bool* _retval) override; + + // GetFileTokenForPath must be implemented by each platform. + // platformAppPath --> a platform specific path to an application that we got + // out of the rdf data source. This can be a mac file + // spec, a unix path or a windows path depending on the + // platform + // aFile --> an nsIFile representation of that platform application path. + virtual nsresult GetFileTokenForPath(const char16_t* platformAppPath, + nsIFile** aFile) override; + + protected: + already_AddRefed<nsMIMEInfoBase> GetFromType(const nsCString& aMimeType); + already_AddRefed<nsMIMEInfoBase> GetFromExtension(const nsCString& aFileExt); + + private: + // Helper methods which have to access static members + static nsresult UnescapeCommand(const nsAString& aEscapedCommand, + const nsAString& aMajorType, + const nsAString& aMinorType, + nsACString& aUnEscapedCommand); + static nsresult LookUpTypeAndDescription(const nsAString& aFileExtension, + nsAString& aMajorType, + nsAString& aMinorType, + nsAString& aDescription, + bool aUserData); + static nsresult CreateInputStream(const nsAString& aFilename, + nsIFileInputStream** aFileInputStream, + nsILineInputStream** aLineInputStream, + nsACString& aBuffer, bool* aNetscapeFormat, + bool* aMore); + + static nsresult GetTypeAndDescriptionFromMimetypesFile( + const nsAString& aFilename, const nsAString& aFileExtension, + nsAString& aMajorType, nsAString& aMinorType, nsAString& aDescription); + + static nsresult LookUpExtensionsAndDescription(const nsAString& aMajorType, + const nsAString& aMinorType, + nsAString& aFileExtensions, + nsAString& aDescription); + + static nsresult GetExtensionsAndDescriptionFromMimetypesFile( + const nsAString& aFilename, const nsAString& aMajorType, + const nsAString& aMinorType, nsAString& aFileExtensions, + nsAString& aDescription); + + static nsresult ParseNetscapeMIMETypesEntry( + const nsAString& aEntry, nsAString::const_iterator& aMajorTypeStart, + nsAString::const_iterator& aMajorTypeEnd, + nsAString::const_iterator& aMinorTypeStart, + nsAString::const_iterator& aMinorTypeEnd, nsAString& aExtensions, + nsAString::const_iterator& aDescriptionStart, + nsAString::const_iterator& aDescriptionEnd); + + static nsresult ParseNormalMIMETypesEntry( + const nsAString& aEntry, nsAString::const_iterator& aMajorTypeStart, + nsAString::const_iterator& aMajorTypeEnd, + nsAString::const_iterator& aMinorTypeStart, + nsAString::const_iterator& aMinorTypeEnd, nsAString& aExtensions, + nsAString::const_iterator& aDescriptionStart, + nsAString::const_iterator& aDescriptionEnd); + + static nsresult LookUpHandlerAndDescription(const nsAString& aMajorType, + const nsAString& aMinorType, + nsAString& aHandler, + nsAString& aDescription, + nsAString& aMozillaFlags); + + static nsresult DoLookUpHandlerAndDescription(const nsAString& aMajorType, + const nsAString& aMinorType, + nsAString& aHandler, + nsAString& aDescription, + nsAString& aMozillaFlags, + bool aUserData); + + static nsresult GetHandlerAndDescriptionFromMailcapFile( + const nsAString& aFilename, const nsAString& aMajorType, + const nsAString& aMinorType, nsAString& aHandler, nsAString& aDescription, + nsAString& aMozillaFlags); +}; + +#endif // nsOSHelperAppService_h__ |