// 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 {

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

  const APIPermissionInfo* permission_info =
      PermissionsInfo::GetInstance()->GetByName(permission_str);
  if (permission_info) {
    scoped_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;
    }

    if (!permission->FromValue(permission_value)) {
      if (error) {
        *error = ErrorUtils::FormatErrorMessageUTF16(
            errors::kInvalidPermission, permission_info->name());
        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,
                           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::IntToString(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);
  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,
    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::IntToString(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;
}

void APIPermissionSet::AddImpliedPermissions() {
  // The fileSystem.write and fileSystem.directory permissions imply
  // fileSystem.writeDirectory.
  // TODO(sammc): Remove this. See http://crbug.com/284849.
  if (ContainsKey(map(), APIPermission::kFileSystemWrite) &&
      ContainsKey(map(), APIPermission::kFileSystemDirectory)) {
    insert(APIPermission::kFileSystemWriteDirectory);
  }
}

}  // namespace extensions