// Copyright (c) 2010 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "webkit/glue/plugins/plugin_lib.h" #include #if defined(OS_OPENBSD) #include #else #include #include #endif #include #include #include #include "base/eintr_wrapper.h" #include "base/file_util.h" #include "base/string_split.h" #include "base/string_util.h" #include "base/sys_string_conversions.h" #include "base/utf_string_conversions.h" #include "webkit/glue/plugins/plugin_list.h" // These headers must be included in this order to make the declaration gods // happy. #include "base/third_party/nspr/prcpucfg_linux.h" namespace { using NPAPI::PluginList; // Copied from nsplugindefs.h instead of including the file since it has a bunch // of dependencies. enum nsPluginVariable { nsPluginVariable_NameString = 1, nsPluginVariable_DescriptionString = 2 }; // Read the ELF header and return true if it is usable on // the current architecture (e.g. 32-bit ELF on 32-bit build). // Returns false on other errors as well. bool ELFMatchesCurrentArchitecture(const FilePath& filename) { // First make sure we can open the file and it is in fact, a regular file. struct stat stat_buf; // Open with O_NONBLOCK so we don't block on pipes. int fd = open(filename.value().c_str(), O_RDONLY|O_NONBLOCK); if (fd < 0) return false; bool ret = (fstat(fd, &stat_buf) >= 0 && S_ISREG(stat_buf.st_mode)); if (HANDLE_EINTR(close(fd)) < 0) return false; if (!ret) return false; const size_t kELFBufferSize = 5; char buffer[kELFBufferSize]; if (!file_util::ReadFile(filename, buffer, kELFBufferSize)) return false; if (buffer[0] != ELFMAG0 || buffer[1] != ELFMAG1 || buffer[2] != ELFMAG2 || buffer[3] != ELFMAG3) { // Not an ELF file, perhaps? return false; } int elf_class = buffer[EI_CLASS]; #if defined(ARCH_CPU_32_BITS) if (elf_class == ELFCLASS32) return true; #elif defined(ARCH_CPU_64_BITS) if (elf_class == ELFCLASS64) return true; #endif return false; } // This structure matches enough of nspluginwrapper's NPW_PluginInfo // for us to extract the real plugin path. struct __attribute__((packed)) NSPluginWrapperInfo { char ident[32]; // NSPluginWrapper magic identifier (includes version). char path[PATH_MAX]; // Path to wrapped plugin. }; // Test a plugin for whether it's been wrapped by NSPluginWrapper, and // if so attempt to unwrap it. Pass in an opened plugin handle; on // success, |dl| and |unwrapped_path| will be filled in with the newly // opened plugin. On failure, params are left unmodified. void UnwrapNSPluginWrapper(void **dl, FilePath* unwrapped_path) { NSPluginWrapperInfo* info = reinterpret_cast(dlsym(*dl, "NPW_Plugin")); if (!info) return; // Not a NSPW plugin. // Here we could check the NSPW ident field for the versioning // information, but the path field is available in all versions // anyway. // Grab the path to the wrapped plugin. Just in case the structure // format changes, protect against the path not being null-terminated. char* path_end = static_cast(memchr(info->path, '\0', sizeof(info->path))); if (!path_end) path_end = info->path + sizeof(info->path); FilePath path = FilePath(std::string(info->path, path_end - info->path)); if (!ELFMatchesCurrentArchitecture(path)) { LOG(WARNING) << path.value() << " is nspluginwrapper wrapping a " << "plugin for a different architecture; it will " << "work better if you instead use a native plugin."; return; } void* newdl = base::LoadNativeLibrary(path); if (!newdl) { // We couldn't load the unwrapped plugin for some reason, despite // being able to load the wrapped one. Just use the wrapped one. LOG_IF(ERROR, PluginList::DebugPluginLoading()) << "Could not use unwrapped nspluginwrapper plugin " << unwrapped_path->value() << ", using the wrapped one."; return; } // Unload the wrapped plugin, and use the wrapped plugin instead. LOG_IF(ERROR, PluginList::DebugPluginLoading()) << "Using unwrapped version " << unwrapped_path->value() << " of nspluginwrapper-wrapped plugin."; base::UnloadNativeLibrary(*dl); *dl = newdl; *unwrapped_path = path; } } // anonymous namespace namespace NPAPI { bool PluginLib::ReadWebPluginInfo(const FilePath& filename, WebPluginInfo* info) { // The file to reference is: // http://mxr.mozilla.org/firefox/source/modules/plugin/base/src/nsPluginsDirUnix.cpp // Skip files that aren't appropriate for our architecture. if (!ELFMatchesCurrentArchitecture(filename)) { LOG_IF(ERROR, PluginList::DebugPluginLoading()) << "Skipping plugin " << filename.value() << " because it doesn't match the current architecture."; return false; } void* dl = base::LoadNativeLibrary(filename); if (!dl) { LOG_IF(ERROR, PluginList::DebugPluginLoading()) << "While reading plugin info, unable to load library " << filename.value() << ", skipping."; return false; } info->path = filename; info->enabled = true; // Attempt to swap in the wrapped plugin if this is nspluginwrapper. UnwrapNSPluginWrapper(&dl, &info->path); // See comments in plugin_lib_mac regarding this symbol. typedef const char* (*NP_GetMimeDescriptionType)(); NP_GetMimeDescriptionType NP_GetMIMEDescription = reinterpret_cast( dlsym(dl, "NP_GetMIMEDescription")); const char* mime_description = NULL; if (NP_GetMIMEDescription) mime_description = NP_GetMIMEDescription(); if (mime_description) ParseMIMEDescription(mime_description, &info->mime_types); // The plugin name and description live behind NP_GetValue calls. typedef NPError (*NP_GetValueType)(void* unused, nsPluginVariable variable, void* value_out); NP_GetValueType NP_GetValue = reinterpret_cast(dlsym(dl, "NP_GetValue")); if (NP_GetValue) { const char* name = NULL; NP_GetValue(NULL, nsPluginVariable_NameString, &name); if (name) info->name = UTF8ToUTF16(name); const char* description = NULL; NP_GetValue(NULL, nsPluginVariable_DescriptionString, &description); if (description) info->desc = UTF8ToUTF16(description); LOG_IF(ERROR, PluginList::DebugPluginLoading()) << "Got info for plugin " << filename.value() << " Name = \"" << UTF16ToUTF8(info->name) << "\", Description = \"" << UTF16ToUTF8(info->desc) << "\"."; } else { LOG_IF(ERROR, PluginList::DebugPluginLoading()) << "Plugin " << filename.value() << " has no GetValue() and probably won't work."; } // Intentionally not unloading the plugin here, it can lead to crashes. return true; } // static void PluginLib::ParseMIMEDescription( const std::string& description, std::vector* mime_types) { // We parse the description here into WebPluginMimeType structures. // Naively from the NPAPI docs you'd think you could use // string-splitting, but the Firefox parser turns out to do something // different: find the first colon, then the second, then a semi. // // See ParsePluginMimeDescription near // http://mxr.mozilla.org/firefox/source/modules/plugin/base/src/nsPluginsDirUtils.h#53 std::string::size_type ofs = 0; for (;;) { WebPluginMimeType mime_type; std::string::size_type end = description.find(':', ofs); if (end == std::string::npos) break; mime_type.mime_type = description.substr(ofs, end - ofs); ofs = end + 1; end = description.find(':', ofs); if (end == std::string::npos) break; const std::string extensions = description.substr(ofs, end - ofs); base::SplitString(extensions, ',', &mime_type.file_extensions); ofs = end + 1; end = description.find(';', ofs); // It's ok for end to run off the string here. If there's no // trailing semicolon we consume the remainder of the string. if (end != std::string::npos) { mime_type.description = UTF8ToUTF16(description.substr(ofs, end - ofs)); } else { mime_type.description = UTF8ToUTF16(description.substr(ofs)); } mime_types->push_back(mime_type); if (end == std::string::npos) break; ofs = end + 1; } } } // namespace NPAPI