// Copyright (c) 2012 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/common/extensions/manifest.h"

#include "base/basictypes.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/string_split.h"
#include "base/stringprintf.h"
#include "base/utf_string_conversions.h"
#include "chrome/common/extensions/extension_manifest_constants.h"
#include "chrome/common/extensions/features/base_feature_provider.h"
#include "extensions/common/error_utils.h"

namespace errors = extension_manifest_errors;
namespace keys = extension_manifest_keys;

namespace extensions {

Manifest::Manifest(Extension::Location location,
                   scoped_ptr<DictionaryValue> value)
    : location_(location),
      value_(value.Pass()),
      type_(Extension::TYPE_UNKNOWN) {
  if (value_->HasKey(keys::kTheme)) {
    type_ = Extension::TYPE_THEME;
  } else if (value_->HasKey(keys::kApp)) {
    if (value_->Get(keys::kWebURLs, NULL) ||
        value_->Get(keys::kLaunchWebURL, NULL)) {
      type_ = Extension::TYPE_HOSTED_APP;
    } else if (value_->Get(keys::kPlatformAppBackground, NULL)) {
      type_ = Extension::TYPE_PLATFORM_APP;
    } else {
      type_ = Extension::TYPE_LEGACY_PACKAGED_APP;
    }
  } else {
    type_ = Extension::TYPE_EXTENSION;
  }
  CHECK_NE(type_, Extension::TYPE_UNKNOWN);
}

Manifest::~Manifest() {
}

void Manifest::ValidateManifest(
    std::string* error,
    Extension::InstallWarningVector* warnings) const {
  *error = "";
  if (type_ == Extension::TYPE_PLATFORM_APP && GetManifestVersion() < 2) {
    *error = errors::kPlatformAppNeedsManifestVersion2;
    return;
  }

  // Check every feature to see if its in the manifest. Note that this means
  // we will ignore keys that are not features; we do this for forward
  // compatibility.
  // TODO(aa): Consider having an error here in the case of strict error
  // checking to let developers know when they screw up.

  std::set<std::string> feature_names =
      BaseFeatureProvider::GetManifestFeatures()->GetAllFeatureNames();
  for (std::set<std::string>::iterator feature_name = feature_names.begin();
       feature_name != feature_names.end(); ++feature_name) {
    // Use Get instead of HasKey because the former uses path expansion.
    if (!value_->Get(*feature_name, NULL))
      continue;

    Feature* feature =
        BaseFeatureProvider::GetManifestFeatures()->GetFeature(*feature_name);
    Feature::Availability result = feature->IsAvailableToManifest(
        extension_id_, type_, Feature::ConvertLocation(location_),
        GetManifestVersion());
    if (!result.is_available())
      warnings->push_back(Extension::InstallWarning(
          Extension::InstallWarning::FORMAT_TEXT, result.message()));
  }

  // Also generate warnings for keys that are not features.
  for (DictionaryValue::key_iterator key = value_->begin_keys();
      key != value_->end_keys(); ++key) {
    if (!BaseFeatureProvider::GetManifestFeatures()->GetFeature(*key)) {
      warnings->push_back(Extension::InstallWarning(
          Extension::InstallWarning::FORMAT_TEXT,
          base::StringPrintf("Unrecognized manifest key '%s'.",
                             (*key).c_str())));
    }
  }
}

bool Manifest::HasKey(const std::string& key) const {
  return CanAccessKey(key) && value_->HasKey(key);
}

bool Manifest::HasPath(const std::string& path) const {
  Value* ignored = NULL;
  return CanAccessPath(path) && value_->Get(path, &ignored);
}

bool Manifest::Get(
    const std::string& path, Value** out_value) const {
  return CanAccessPath(path) && value_->Get(path, out_value);
}

bool Manifest::GetBoolean(
    const std::string& path, bool* out_value) const {
  return CanAccessPath(path) && value_->GetBoolean(path, out_value);
}

bool Manifest::GetInteger(
    const std::string& path, int* out_value) const {
  return CanAccessPath(path) && value_->GetInteger(path, out_value);
}

bool Manifest::GetString(
    const std::string& path, std::string* out_value) const {
  return CanAccessPath(path) && value_->GetString(path, out_value);
}

bool Manifest::GetString(
    const std::string& path, string16* out_value) const {
  return CanAccessPath(path) && value_->GetString(path, out_value);
}

bool Manifest::GetDictionary(
    const std::string& path, DictionaryValue** out_value) const {
  return CanAccessPath(path) && value_->GetDictionary(path, out_value);
}

bool Manifest::GetList(
    const std::string& path, ListValue** out_value) const {
  return CanAccessPath(path) && value_->GetList(path, out_value);
}

Manifest* Manifest::DeepCopy() const {
  Manifest* manifest = new Manifest(
      location_, scoped_ptr<DictionaryValue>(value_->DeepCopy()));
  manifest->set_extension_id(extension_id_);
  return manifest;
}

bool Manifest::Equals(const Manifest* other) const {
  return other && value_->Equals(other->value());
}

int Manifest::GetManifestVersion() const {
  // Platform apps were launched after manifest version 2 was the preferred
  // version, so they default to that.
  int manifest_version = type_ == Extension::TYPE_PLATFORM_APP ? 2 : 1;
  value_->GetInteger(keys::kManifestVersion, &manifest_version);
  return manifest_version;
}

bool Manifest::CanAccessPath(const std::string& path) const {
  std::vector<std::string> components;
  base::SplitString(path, '.', &components);
  std::string key;
  for (size_t i = 0; i < components.size(); ++i) {
    key += components[i];
    if (!CanAccessKey(key))
      return false;
    key += '.';
  }
  return true;
}

bool Manifest::CanAccessKey(const std::string& key) const {
  Feature* feature =
      BaseFeatureProvider::GetManifestFeatures()->GetFeature(key);
  if (!feature)
    return true;

  return feature->IsAvailableToManifest(
      extension_id_, type_, Feature::ConvertLocation(location_),
      GetManifestVersion()).is_available();
}

}  // namespace extensions