// Copyright 2013 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 "extensions/common/permissions/api_permission_set.h"

#include "base/logging.h"
#include "base/stl_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/values.h"
#include "extensions/common/error_utils.h"
#include "extensions/common/manifest_constants.h"
#include "extensions/common/permissions/permissions_info.h"

namespace extensions {

namespace errors = manifest_errors;

namespace {

// Helper object that is implicitly constructible from both a PermissionID and
// from an APIPermission::ID.
struct PermissionIDCompareHelper {
  PermissionIDCompareHelper(const PermissionID& id) : id(id.id()) {}
  PermissionIDCompareHelper(const APIPermission::ID id) : id(id) {}

  APIPermission::ID id;
};

bool CreateAPIPermission(
    const std::string& permission_str,
    const base::Value* permission_value,
    APIPermissionSet::ParseSource source,
    APIPermissionSet* api_permissions,
    base::string16* error,
    std::vector<std::string>* unhandled_permissions) {

  const APIPermissionInfo* permission_info =
      PermissionsInfo::GetInstance()->GetByName(permission_str);
  if (permission_info) {
    std::unique_ptr<APIPermission> permission(
        permission_info->CreateAPIPermission());
    if (source != APIPermissionSet::kAllowInternalPermissions &&
        permission_info->is_internal()) {
      // An internal permission specified in permissions list is an error.
      if (error) {
        *error = ErrorUtils::FormatErrorMessageUTF16(
            errors::kPermissionNotAllowedInManifest, permission_str);
      }
      return false;
    }

    std::string error_details;
    if (!permission->FromValue(permission_value, &error_details,
                               unhandled_permissions)) {
      if (error) {
        if (error_details.empty()) {
          *error = ErrorUtils::FormatErrorMessageUTF16(
              errors::kInvalidPermission,
              permission_info->name());
        } else {
          *error = ErrorUtils::FormatErrorMessageUTF16(
              errors::kInvalidPermissionWithDetail,
              permission_info->name(),
              error_details);
        }
        return false;
      }
      LOG(WARNING) << "Parse permission failed.";
    } else {
      api_permissions->insert(permission.release());
    }
    return true;
  }

  if (unhandled_permissions)
    unhandled_permissions->push_back(permission_str);
  else
    LOG(WARNING) << "Unknown permission[" << permission_str << "].";

  return true;
}

bool ParseChildPermissions(const std::string& base_name,
                           const base::Value* permission_value,
                           APIPermissionSet::ParseSource source,
                           APIPermissionSet* api_permissions,
                           base::string16* error,
                           std::vector<std::string>* unhandled_permissions) {
  if (permission_value) {
    const base::ListValue* permissions;
    if (!permission_value->GetAsList(&permissions)) {
      if (error) {
        *error = ErrorUtils::FormatErrorMessageUTF16(
            errors::kInvalidPermission, base_name);
        return false;
      }
      LOG(WARNING) << "Permission value is not a list.";
      // Failed to parse, but since error is NULL, failures are not fatal so
      // return true here anyway.
      return true;
    }

    for (size_t i = 0; i < permissions->GetSize(); ++i) {
      std::string permission_str;
      if (!permissions->GetString(i, &permission_str)) {
        // permission should be a string
        if (error) {
          *error = ErrorUtils::FormatErrorMessageUTF16(
              errors::kInvalidPermission,
              base_name + '.' + base::SizeTToString(i));
          return false;
        }
        LOG(WARNING) << "Permission is not a string.";
        continue;
      }

      if (!CreateAPIPermission(
              base_name + '.' + permission_str, NULL, source,
              api_permissions, error, unhandled_permissions))
        return false;
    }
  }

  return CreateAPIPermission(base_name, NULL, source,
                             api_permissions, error, NULL);
}

}  // namespace

void APIPermissionSet::insert(APIPermission::ID id) {
  const APIPermissionInfo* permission_info =
      PermissionsInfo::GetInstance()->GetByID(id);
  DCHECK(permission_info);
  insert(permission_info->CreateAPIPermission());
}

void APIPermissionSet::insert(APIPermission* permission) {
  BaseSetOperators<APIPermissionSet>::insert(permission);
}

// static
bool APIPermissionSet::ParseFromJSON(
    const base::ListValue* permissions,
    APIPermissionSet::ParseSource source,
    APIPermissionSet* api_permissions,
    base::string16* error,
    std::vector<std::string>* unhandled_permissions) {
  for (size_t i = 0; i < permissions->GetSize(); ++i) {
    std::string permission_str;
    const base::Value* permission_value = NULL;
    if (!permissions->GetString(i, &permission_str)) {
      const base::DictionaryValue* dict = NULL;
      // permission should be a string or a single key dict.
      if (!permissions->GetDictionary(i, &dict) || dict->size() != 1) {
        if (error) {
          *error = ErrorUtils::FormatErrorMessageUTF16(
              errors::kInvalidPermission, base::SizeTToString(i));
          return false;
        }
        LOG(WARNING) << "Permission is not a string or single key dict.";
        continue;
      }
      base::DictionaryValue::Iterator it(*dict);
      permission_str = it.key();
      permission_value = &it.value();
    }

    // Check if this permission is a special case where its value should
    // be treated as a list of child permissions.
    if (PermissionsInfo::GetInstance()->HasChildPermissions(permission_str)) {
      if (!ParseChildPermissions(permission_str, permission_value, source,
                                 api_permissions, error, unhandled_permissions))
        return false;
      continue;
    }

    if (!CreateAPIPermission(permission_str, permission_value, source,
                             api_permissions, error, unhandled_permissions))
      return false;
  }
  return true;
}

PermissionID::PermissionID(APIPermission::ID id)
    : std::pair<APIPermission::ID, base::string16>(id, base::string16()) {
}

PermissionID::PermissionID(APIPermission::ID id,
                           const base::string16& parameter)
    : std::pair<APIPermission::ID, base::string16>(id, parameter) {
}

PermissionID::~PermissionID() {
}

PermissionIDSet::PermissionIDSet() {
}

PermissionIDSet::PermissionIDSet(const PermissionIDSet& other) = default;

PermissionIDSet::~PermissionIDSet() {
}

void PermissionIDSet::insert(APIPermission::ID permission_id) {
  insert(permission_id, base::string16());
}

void PermissionIDSet::insert(APIPermission::ID permission_id,
                             const base::string16& permission_detail) {
  permissions_.insert(PermissionID(permission_id, permission_detail));
}

void PermissionIDSet::InsertAll(const PermissionIDSet& permission_set) {
  for (const auto& permission : permission_set.permissions_) {
    permissions_.insert(permission);
  }
}

void PermissionIDSet::erase(APIPermission::ID permission_id) {
  auto lower_bound = permissions_.lower_bound(PermissionID(permission_id));
  auto upper_bound = lower_bound;
  while (upper_bound != permissions_.end() &&
         upper_bound->id() == permission_id) {
    ++upper_bound;
  }
  permissions_.erase(lower_bound, upper_bound);
}

std::vector<base::string16> PermissionIDSet::GetAllPermissionParameters()
    const {
  std::vector<base::string16> params;
  for (const auto& permission : permissions_) {
    params.push_back(permission.parameter());
  }
  return params;
}

bool PermissionIDSet::ContainsID(APIPermission::ID permission_id) const {
  auto it = permissions_.lower_bound(PermissionID(permission_id));
  return it != permissions_.end() && it->id() == permission_id;
}

bool PermissionIDSet::ContainsAllIDs(
    const std::set<APIPermission::ID>& permission_ids) const {
  return std::includes(permissions_.begin(), permissions_.end(),
                       permission_ids.begin(), permission_ids.end(),
                       [] (const PermissionIDCompareHelper& lhs,
                           const PermissionIDCompareHelper& rhs) {
                         return lhs.id < rhs.id;
                       });
}

bool PermissionIDSet::ContainsAnyID(
    const std::set<APIPermission::ID>& permission_ids) const {
  for (APIPermission::ID id : permission_ids) {
    if (ContainsID(id))
      return true;
  }
  return false;
}

PermissionIDSet PermissionIDSet::GetAllPermissionsWithID(
    APIPermission::ID permission_id) const {
  PermissionIDSet subset;
  auto it = permissions_.lower_bound(PermissionID(permission_id));
  while (it != permissions_.end() && it->id() == permission_id) {
    subset.permissions_.insert(*it);
    ++it;
  }
  return subset;
}

PermissionIDSet PermissionIDSet::GetAllPermissionsWithIDs(
    const std::set<APIPermission::ID>& permission_ids) const {
  PermissionIDSet subset;
  for (const auto& permission : permissions_) {
    if (base::ContainsKey(permission_ids, permission.id())) {
      subset.permissions_.insert(permission);
    }
  }
  return subset;
}

bool PermissionIDSet::Includes(const PermissionIDSet& subset) const {
  return base::STLIncludes(permissions_, subset.permissions_);
}

bool PermissionIDSet::Equals(const PermissionIDSet& set) const {
  return permissions_ == set.permissions_;
}

// static
PermissionIDSet PermissionIDSet::Difference(const PermissionIDSet& set_1,
                                            const PermissionIDSet& set_2) {
  return PermissionIDSet(base::STLSetDifference<std::set<PermissionID>>(
      set_1.permissions_, set_2.permissions_));
}

size_t PermissionIDSet::size() const {
  return permissions_.size();
}

bool PermissionIDSet::empty() const {
  return permissions_.empty();
}

PermissionIDSet::PermissionIDSet(const std::set<PermissionID>& permissions)
    : permissions_(permissions) {
}

}  // namespace extensions
