// Copyright 2008, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//    * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//    * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//    * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include "webkit/default_plugin/plugin_database_handler.h"

#include "base/file_util.h"
#include "base/path_service.h"
#include "base/string_util.h"
#include "base/time.h"
#include "third_party/libxml/include/libxml/parser.h"
#include "third_party/libxml/include/libxml/xpath.h"
#include "webkit/default_plugin/plugin_impl.h"
#include "webkit/default_plugin/plugin_main.h"

PluginDatabaseHandler::PluginDatabaseHandler(
    PluginInstallerImpl& plugin_installer_instance)
    : plugin_downloads_file_(INVALID_HANDLE_VALUE),
      plugin_installer_instance_(plugin_installer_instance),
      ignore_plugin_db_data_(false) {
}

PluginDatabaseHandler::~PluginDatabaseHandler() {
  if (plugin_downloads_file_ != INVALID_HANDLE_VALUE) {
    ::CloseHandle(plugin_downloads_file_);
    plugin_downloads_file_ = INVALID_HANDLE_VALUE;
  }
}

bool PluginDatabaseHandler::DownloadPluginsFileIfNeeded(
    const std::string& plugin_finder_url) {
  DCHECK(!plugin_finder_url.empty());
  // The time in days for which the plugins list is cached.
  // TODO(iyengar) Make this configurable.
  const int kPluginsListCacheTimeInDays = 3;

  plugin_finder_url_ = plugin_finder_url;

  PathService::Get(base::DIR_MODULE, &plugins_file_);
  plugins_file_ += L"\\chrome_plugins_file.xml";

  bool initiate_download = false;
  if (!file_util::PathExists(plugins_file_)) {
    initiate_download = true;
  } else {
    SYSTEMTIME creation_system_time = {0};
    if (!file_util::GetFileCreationLocalTime(plugins_file_,
                                             &creation_system_time)) {
      NOTREACHED();
      return false;
    }

    FILETIME creation_file_time = {0};
    ::SystemTimeToFileTime(&creation_system_time, &creation_file_time);

    FILETIME current_time = {0};
    ::GetSystemTimeAsFileTime(&current_time);

    Time file_time = Time::FromFileTime(creation_file_time);
    Time current_system_time = Time::FromFileTime(current_time);

    TimeDelta time_diff = file_time - current_system_time;
    if (time_diff.InDays() > kPluginsListCacheTimeInDays) {
      initiate_download = true;
    }
  }

  if (initiate_download) {
    DLOG(INFO) << "Initiating GetURLNotify on the plugin finder URL "
               << plugin_finder_url.c_str();

    plugin_installer_instance_.set_plugin_installer_state(
        PluginListDownloadInitiated);

    DCHECK(default_plugin::g_browser->geturlnotify);
    default_plugin::g_browser->geturlnotify(
        plugin_installer_instance_.instance(), plugin_finder_url.c_str(),
        NULL, NULL);
  } else {
    DLOG(INFO) << "Plugins file " << plugins_file_.c_str()
               << " already exists";
    plugin_downloads_file_ = ::CreateFile(plugins_file_.c_str(), GENERIC_READ,
                                          FILE_SHARE_READ, NULL, OPEN_EXISTING,
                                          FILE_ATTRIBUTE_NORMAL, NULL);
    if (plugin_downloads_file_ == INVALID_HANDLE_VALUE) {
      DLOG(INFO) << "Failed to open plugins file "
                 << plugins_file_.c_str() << " Error "
                 << ::GetLastError();
      NOTREACHED();
      return false;
    }
    // The URLNotify function contains all handling needed to parse the plugins
    // file and display the UI accordingly.
    plugin_installer_instance_.set_plugin_installer_state(
        PluginListDownloadInitiated);
    plugin_installer_instance_.URLNotify(plugin_finder_url.c_str(),
                                         NPRES_DONE);
  }
  return true;
}

int32 PluginDatabaseHandler::Write(NPStream* stream, int32 offset,
                                   int32 buffer_length, void* buffer) {
  if (ignore_plugin_db_data_) {
    return buffer_length;
  }

  if (plugin_downloads_file_ == INVALID_HANDLE_VALUE) {
    DLOG(INFO) << "About to create plugins file " << plugins_file_.c_str();
    plugin_downloads_file_ = CreateFile(plugins_file_.c_str(),
                                        GENERIC_READ | GENERIC_WRITE,
                                        FILE_SHARE_READ, NULL, CREATE_ALWAYS,
                                        FILE_ATTRIBUTE_NORMAL, NULL);
    if (plugin_downloads_file_ == INVALID_HANDLE_VALUE) {
      unsigned long error = ::GetLastError();
      if (error == ERROR_SHARING_VIOLATION) {
        // File may have been downloaded by another plugin instance on this
        // page.
        plugin_downloads_file_ = ::CreateFile(
            plugins_file_.c_str(), GENERIC_READ,
            FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
            FILE_ATTRIBUTE_NORMAL, NULL);
        if (plugin_downloads_file_ != INVALID_HANDLE_VALUE) {
          ignore_plugin_db_data_ = true;
          return buffer_length;
        }
      }

      DLOG(INFO) << "Failed to create plugins file "
                 << plugins_file_.c_str() << " Error "
                 << ::GetLastError();
      NOTREACHED();
      return 0;
    }
  }

  unsigned long bytes_written = 0;
  if (0 == lstrcmpiA(stream->url, plugin_finder_url_.c_str())) {
    DCHECK(plugin_downloads_file_ != INVALID_HANDLE_VALUE);

    WriteFile(plugin_downloads_file_, buffer, buffer_length, &bytes_written,
              NULL);
    DCHECK(buffer_length == bytes_written);
  }
  return bytes_written;
}


bool PluginDatabaseHandler::ParsePluginList() {
  if (plugin_downloads_file_ == INVALID_HANDLE_VALUE) {
    DLOG(WARNING) << "Invalid plugins file";
    NOTREACHED();
    return false;
  }

  bool parse_result = false;

  std::string plugins_file = WideToUTF8(plugins_file_.c_str());
  xmlDocPtr plugin_downloads_doc = xmlParseFile(plugins_file.c_str());
  if (plugin_downloads_doc == NULL) {
    DLOG(WARNING) << "Failed to parse plugins file " << plugins_file.c_str();
    return parse_result;
  }

  xmlXPathContextPtr context = NULL;
  xmlXPathObjectPtr plugins_result = NULL;

  do {
    context = xmlXPathNewContext(plugin_downloads_doc);
    if (context == NULL) {
      DLOG(WARNING) << "Failed to retrieve XPath context";
      NOTREACHED();
      parse_result = false;
      break;
    }

    plugins_result =
        xmlXPathEvalExpression(reinterpret_cast<const xmlChar*>("//plugin"),
                               context);
    if ((plugins_result == NULL) ||
         xmlXPathNodeSetIsEmpty(plugins_result->nodesetval)) {
      DLOG(WARNING) << "Failed to find XPath //plugin";
      NOTREACHED();
      parse_result = false;
      break;
    }

    xmlNodeSetPtr plugin_list = plugins_result->nodesetval;
    for (int plugin_index = 0; plugin_index < plugin_list->nodeNr;
         ++plugin_index) {
      PluginDetail plugin_detail;
      if (!ReadPluginInfo(plugin_list->nodeTab[plugin_index]->children,
                          &plugin_detail)) {
        DLOG(ERROR) << "Failed to read plugin details at index "
                    << plugin_index;
        break;
      }
      downloaded_plugins_list_.push_back(plugin_detail);
    }
    if (downloaded_plugins_list_.size())
      parse_result = true;
  } while (0);

  xmlXPathFreeContext(context);
  xmlXPathFreeObject(plugins_result);
  xmlFreeDoc(plugin_downloads_doc);
  xmlCleanupParser();
  DLOG(INFO) << "Parse plugins file result " << parse_result;
  return parse_result;
}

bool PluginDatabaseHandler::GetPluginDetailsForMimeType(
    const char* mime_type, const char* language,
    std::string* download_url, std::wstring* plugin_name) {
  if (!mime_type || !language || !download_url || !plugin_name) {
    NOTREACHED();
    return false;
  }

  PluginList::iterator plugin_index;
  for (plugin_index = downloaded_plugins_list_.begin();
       plugin_index != downloaded_plugins_list_.end();
       ++plugin_index) {
    const PluginDetail& current_plugin = *plugin_index;
    if ((0 == lstrcmpiA(mime_type, current_plugin.mime_type.c_str())) &&
        (0 == lstrcmpiA(language, current_plugin.language.c_str()))) {
      *download_url = current_plugin.download_url;
      *plugin_name = current_plugin.display_name;
      return true;
    }
  }
  return false;
}

void PluginDatabaseHandler::Close(bool delete_file) {
  if (plugin_downloads_file_ != INVALID_HANDLE_VALUE) {
    ::CloseHandle(plugin_downloads_file_);
    plugin_downloads_file_ = INVALID_HANDLE_VALUE;
    if (delete_file) {
      ::DeleteFile(plugins_file_.c_str());
      plugins_file_.clear();
    }
  }
}

bool PluginDatabaseHandler::ReadPluginInfo(_xmlNode* plugin_node,
                                           PluginDetail* plugin_detail) {
  if (!plugin_node) {
    NOTREACHED();
    return false;
  }

  _xmlNode* plugin_mime_type = plugin_node->next;
  if ((plugin_mime_type == NULL) ||
      (plugin_mime_type->next == NULL)) {
    DLOG(WARNING) << "Failed to find mime type node in file";
    NOTREACHED();
    return false;
  }

  _xmlNode* plugin_mime_type_val = plugin_mime_type->children;
  if (plugin_mime_type_val == NULL) {
    DLOG(WARNING) << "Invalid mime type";
    NOTREACHED();
    return false;
  }
  // Skip the first child of each node as it is the text element
  // for that node.
  _xmlNode* plugin_lang_node = plugin_mime_type->next->next;
  if ((plugin_lang_node == NULL) ||
      (plugin_lang_node->next == NULL)) {
    DLOG(WARNING) << "Failed to find plugin language node";
    NOTREACHED();
    return false;
  }
  _xmlNode* plugin_lang_val = plugin_lang_node->children;
  if (plugin_lang_val == NULL) {
    DLOG(WARNING) << "Invalid plugin language";
    NOTREACHED();
    return false;
  }
  _xmlNode* plugin_name_node = plugin_lang_node->next->next;
  if ((plugin_name_node == NULL) ||
      (plugin_name_node->next == NULL)) {
    DLOG(WARNING) << "Failed to find plugin name node";
    NOTREACHED();
    return false;
  }
  _xmlNode* plugin_name_val = plugin_name_node->children;
  if (plugin_name_val == NULL) {
    DLOG(WARNING) << "Invalid plugin name";
    NOTREACHED();
    return false;
  }
  _xmlNode* plugin_download_url_node = plugin_name_node->next->next;
  if (plugin_download_url_node == NULL) {
    DLOG(WARNING) << "Failed to find plugin URL node";
    NOTREACHED();
    return false;
  }
  _xmlNode* plugin_download_url_val = plugin_download_url_node->children;
  if (plugin_download_url_val == NULL) {
    DLOG(WARNING) << "Invalid plugin URL";
    NOTREACHED();
    return false;
  }

  plugin_detail->display_name =
      UTF8ToWide(reinterpret_cast<const char*>(plugin_name_val->content));
  plugin_detail->download_url =
      reinterpret_cast<const char*>(plugin_download_url_val->content);
  plugin_detail->mime_type =
      reinterpret_cast<const char*>(plugin_mime_type_val->content);
  plugin_detail->language =
      reinterpret_cast<const char*>(plugin_lang_val->content);

  return true;
}