// Copyright (c) 2011 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 "chrome/browser/chromeos/plugin_selection_policy.h"

#include <algorithm>
#include <iostream>
#include <map>
#include <sstream>
#include <string>
#include <vector>

#include "base/auto_reset.h"
#include "base/file_path.h"
#include "base/file_util.h"
#include "base/logging.h"
#include "base/string_util.h"
#include "content/browser/browser_thread.h"
#include "googleurl/src/gurl.h"

#if !defined(OS_CHROMEOS)
#error This file is meant to be compiled on ChromeOS only.
#endif

using std::vector;
using std::string;
using std::pair;
using std::map;

namespace chromeos {

static const char kPluginSelectionPolicyFile[] =
    "/usr/share/chromeos-assets/flash/plugin_policy";

PluginSelectionPolicy::PluginSelectionPolicy()
    : init_from_file_finished_(false) {
}

void PluginSelectionPolicy::StartInit() {
  // Initialize the policy on the FILE thread, since it reads from a
  // policy file.
  BrowserThread::PostTask(
      BrowserThread::FILE, FROM_HERE,
      NewRunnableMethod(this, &chromeos::PluginSelectionPolicy::Init));
}

bool PluginSelectionPolicy::Init() {
  return InitFromFile(FilePath(kPluginSelectionPolicyFile));
}

bool PluginSelectionPolicy::InitFromFile(const FilePath& policy_file) {
  // This must always be called from the FILE thread.
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));

  string data;
  // This should be a really small file, so we're OK with just
  // slurping it.
  if (!file_util::ReadFileToString(policy_file, &data)) {
    LOG(ERROR) << "Unable to read plugin policy file \""
               << policy_file.value() << "\".";
    init_from_file_finished_ = true;
    return false;
  }

  std::istringstream input_stream(data);
  string line;
  map<string, Policy> policies;
  Policy policy;
  string last_plugin;

  while (std::getline(input_stream, line)) {
    // Strip comments.
    string::size_type pos = line.find("#");
    if (pos != string::npos) {
      line = line.substr(0, pos);
    }
    TrimWhitespaceASCII(line, TRIM_ALL, &line);
    if (line.find("allow") == 0) {
      // Has to be preceeded by a "plugin" statement.
      if (last_plugin.empty()) {
        LOG(ERROR) << "Plugin policy file error: 'allow' out of context.";
        init_from_file_finished_ = true;
        return false;
      }
      line = line.substr(5);
      TrimWhitespaceASCII(line, TRIM_ALL, &line);
      line = StringToLowerASCII(line);
      policy.push_back(make_pair(true, line));
    }
    if (line.find("deny") == 0) {
      // Has to be preceeded by a "plugin" statement.
      if (last_plugin.empty()) {
        LOG(ERROR) << "Plugin policy file error: 'deny' out of context.";
        init_from_file_finished_ = true;
        return false;
      }
      line = line.substr(4);
      TrimWhitespaceASCII(line, TRIM_ALL, &line);
      line = StringToLowerASCII(line);
      policy.push_back(make_pair(false, line));
    }
    if (line.find("plugin") == 0) {
      line = line.substr(6);
      TrimWhitespaceASCII(line, TRIM_ALL, &line);
      if (!policy.empty() && !last_plugin.empty())
        policies.insert(make_pair(last_plugin, policy));
      last_plugin = line;
      policy.clear();
    }
  }

  if (!last_plugin.empty())
    policies.insert(make_pair(last_plugin, policy));

  policies_.swap(policies);
  init_from_file_finished_ = true;
  return true;
}

int PluginSelectionPolicy::FindFirstAllowed(
    const GURL& url,
    const std::vector<webkit::npapi::WebPluginInfo>& info) {
  for (std::vector<webkit::npapi::WebPluginInfo>::size_type i = 0;
       i < info.size(); ++i) {
    if (IsAllowed(url, info[i].path))
      return i;
  }
  return -1;
}

bool PluginSelectionPolicy::IsAllowed(const GURL& url,
                                      const FilePath& path) {
  // This must always be called from the FILE thread, to be sure
  // initialization doesn't happen at the same time.
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));

  // Make sure that we notice if this starts being called before
  // initialization is complete.  Right now it is guaranteed only by
  // the startup order and the fact that InitFromFile runs on the FILE
  // thread too.
  DCHECK(init_from_file_finished_)
      << "Tried to check policy before policy is initialized.";

  string name = path.BaseName().value();

  PolicyMap::iterator policy_iter = policies_.find(name);
  if (policy_iter != policies_.end()) {
    Policy& policy(policy_iter->second);

    // We deny by default. (equivalent to "deny" at the top of the section)
    bool allow = false;

    for (Policy::iterator iter = policy.begin(); iter != policy.end(); ++iter) {
      bool policy_allow = iter->first;
      string& policy_domain = iter->second;
      if (policy_domain.empty() || url.DomainIs(policy_domain.c_str(),
                                                policy_domain.size())) {
        allow = policy_allow;
      }
    }
    return allow;
  }

  // If it's not in the policy file, then we assume it's OK to allow
  // it.
  return true;
}

}  // namespace chromeos