// 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/content_settings_pattern_parser.h"

#include "base/strings/string_util.h"
#include "chrome/common/url_constants.h"
#include "extensions/common/constants.h"
#include "net/base/net_util.h"
#include "url/gurl.h"
#include "url/url_canon.h"

namespace {

const char* kUrlPathSeparator = "/";
const char* kUrlPortSeparator = ":";

class Component {
 public:
  Component() : start(0), len(0) {}
  Component(size_t s, size_t l) : start(s), len(l) {}

  bool IsNonEmpty() {
    return len > 0;
  }

  size_t start;
  size_t len;
};

}  // namespace

namespace content_settings {

const char* PatternParser::kDomainWildcard = "[*.]";

const size_t PatternParser::kDomainWildcardLength = 4;

const char* PatternParser::kSchemeWildcard = "*";

const char* PatternParser::kHostWildcard = "*";

const char* PatternParser::kPortWildcard = "*";

const char* PatternParser::kPathWildcard = "*";

// static
void PatternParser::Parse(const std::string& pattern_spec,
                          ContentSettingsPattern::BuilderInterface* builder) {
  if (pattern_spec == "*") {
    builder->WithSchemeWildcard();
    builder->WithDomainWildcard();
    builder->WithPortWildcard();
    return;
  }

  // Initialize components for the individual patterns parts to empty
  // sub-strings.
  Component scheme_component;
  Component host_component;
  Component port_component;
  Component path_component;

  size_t start = 0;
  size_t current_pos = 0;

  if (pattern_spec.empty())
    return;

  // Test if a scheme pattern is in the spec.
  current_pos = pattern_spec.find(
      std::string(url::kStandardSchemeSeparator), start);
  if (current_pos != std::string::npos) {
    scheme_component = Component(start, current_pos);
    start = current_pos + strlen(url::kStandardSchemeSeparator);
    current_pos = start;
  } else {
    current_pos = start;
  }

  if (start >= pattern_spec.size())
    return;  // Bad pattern spec.

  // Jump to the end of domain wildcards or an IPv6 addresses. IPv6 addresses
  // contain ':'. So first move to the end of an IPv6 address befor searching
  // for the ':' that separates the port form the host.
  if (pattern_spec[current_pos] == '[')
    current_pos = pattern_spec.find("]", start);

  if (current_pos == std::string::npos)
    return;  // Bad pattern spec.

  current_pos = pattern_spec.find(std::string(kUrlPortSeparator), current_pos);
  if (current_pos == std::string::npos) {
    // No port spec found
    current_pos = pattern_spec.find(std::string(kUrlPathSeparator), start);
    if (current_pos == std::string::npos) {
      current_pos = pattern_spec.size();
      host_component = Component(start, current_pos - start);
    } else {
      // Pattern has a path spec.
      host_component = Component(start, current_pos - start);
    }
    start = current_pos;
  } else {
    // Port spec found.
    host_component = Component(start, current_pos - start);
    start = current_pos + 1;
    if (start < pattern_spec.size()) {
      current_pos = pattern_spec.find(std::string(kUrlPathSeparator), start);
      if (current_pos == std::string::npos) {
        current_pos = pattern_spec.size();
      }
      port_component = Component(start, current_pos - start);
      start = current_pos;
    }
  }

  current_pos = pattern_spec.size();
  if (start < current_pos) {
    // Pattern has a path spec.
    path_component = Component(start, current_pos - start);
  }

  // Set pattern parts.
  std::string scheme;
  if (scheme_component.IsNonEmpty()) {
    scheme = pattern_spec.substr(scheme_component.start, scheme_component.len);
    if (scheme == kSchemeWildcard) {
      builder->WithSchemeWildcard();
    } else {
      builder->WithScheme(scheme);
    }
  } else {
    builder->WithSchemeWildcard();
  }

  if (host_component.IsNonEmpty()) {
    std::string host = pattern_spec.substr(host_component.start,
                                           host_component.len);
    if (host == kHostWildcard) {
      builder->WithDomainWildcard();
    } else if (StartsWithASCII(host, kDomainWildcard, true)) {
      host = host.substr(kDomainWildcardLength);
      builder->WithDomainWildcard();
      builder->WithHost(host);
    } else {
      // If the host contains a wildcard symbol then it is invalid.
      if (host.find(kHostWildcard) != std::string::npos) {
        builder->Invalid();
        return;
      }
      builder->WithHost(host);
    }
  }

  if (port_component.IsNonEmpty()) {
    const std::string port = pattern_spec.substr(port_component.start,
                                                 port_component.len);
    if (port == kPortWildcard) {
      builder->WithPortWildcard();
    } else {
      // Check if the port string represents a valid port.
      for (size_t i = 0; i < port.size(); ++i) {
        if (!IsAsciiDigit(port[i])) {
          builder->Invalid();
          return;
        }
      }
      // TODO(markusheintz): Check port range.
      builder->WithPort(port);
    }
  } else {
    if (scheme != std::string(extensions::kExtensionScheme) &&
        scheme != std::string(url::kFileScheme))
      builder->WithPortWildcard();
  }

  if (path_component.IsNonEmpty()) {
    const std::string path = pattern_spec.substr(path_component.start,
                                                 path_component.len);
    if (path.substr(1) == kPathWildcard)
      builder->WithPathWildcard();
    else
      builder->WithPath(path);
  }
}

// static
std::string PatternParser::ToString(
    const ContentSettingsPattern::PatternParts& parts) {
  // Return the most compact form to support legacy code and legacy pattern
  // strings.
  if (parts.is_scheme_wildcard &&
      parts.has_domain_wildcard &&
      parts.host.empty() &&
      parts.is_port_wildcard)
    return "*";

  std::string str;
  if (!parts.is_scheme_wildcard)
    str += parts.scheme + url::kStandardSchemeSeparator;

  if (parts.scheme == url::kFileScheme) {
    if (parts.is_path_wildcard)
      return str + kUrlPathSeparator + kPathWildcard;
    else
      return str + parts.path;
  }

  if (parts.has_domain_wildcard) {
    if (parts.host.empty())
      str += kHostWildcard;
    else
      str += kDomainWildcard;
  }
  str += parts.host;

  if (parts.scheme == std::string(extensions::kExtensionScheme)) {
    str += parts.path.empty() ? std::string(kUrlPathSeparator) : parts.path;
    return str;
  }

  if (!parts.is_port_wildcard) {
    str += std::string(kUrlPortSeparator) + parts.port;
  }

  return str;
}

}  // namespace content_settings