// Copyright 2014 The Chromium OS 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 <brillo/url_utils.h>

#include <algorithm>

namespace {
// Given a URL string, determine where the query string starts and ends.
// URLs have schema, domain and path (along with possible user name, password
// and port number which are of no interest for us here) which could optionally
// have a query string that is separated from the path by '?'. Finally, the URL
// could also have a '#'-separated URL fragment which is usually used by the
// browser as a bookmark element. So, for example:
//    http://server.com/path/to/object?k=v&foo=bar#fragment
// Here:
//    http://server.com/path/to/object - is the URL of the object,
//    ?k=v&foo=bar                     - URL query string
//    #fragment                        - URL fragment string
// If |exclude_fragment| is true, the function returns the start character and
// the length of the query string alone. If it is false, the query string length
// will include both the query string and the fragment.
bool GetQueryStringPos(const std::string& url,
                       bool exclude_fragment,
                       size_t* query_pos,
                       size_t* query_len) {
  size_t query_start = url.find_first_of("?#");
  if (query_start == std::string::npos) {
    *query_pos = url.size();
    if (query_len)
      *query_len = 0;
    return false;
  }

  *query_pos = query_start;
  if (query_len) {
    size_t query_end = url.size();

    if (exclude_fragment) {
      if (url[query_start] == '?') {
        size_t pos_fragment = url.find('#', query_start);
        if (pos_fragment != std::string::npos)
          query_end = pos_fragment;
      } else {
        query_end = query_start;
      }
    }
    *query_len = query_end - query_start;
  }
  return true;
}
}  // anonymous namespace

namespace brillo {

std::string url::TrimOffQueryString(std::string* url) {
  size_t query_pos;
  if (!GetQueryStringPos(*url, false, &query_pos, nullptr))
    return std::string();
  std::string query_string = url->substr(query_pos);
  url->resize(query_pos);
  return query_string;
}

std::string url::Combine(const std::string& url, const std::string& subpath) {
  return CombineMultiple(url, {subpath});
}

std::string url::CombineMultiple(const std::string& url,
                                 const std::vector<std::string>& parts) {
  std::string result = url;
  if (!parts.empty()) {
    std::string query_string = TrimOffQueryString(&result);
    for (const auto& part : parts) {
      if (!part.empty()) {
        if (!result.empty() && result.back() != '/')
          result += '/';
        size_t non_slash_pos = part.find_first_not_of('/');
        if (non_slash_pos != std::string::npos)
          result += part.substr(non_slash_pos);
      }
    }
    result += query_string;
  }
  return result;
}

std::string url::GetQueryString(const std::string& url, bool remove_fragment) {
  std::string query_string;
  size_t query_pos, query_len;
  if (GetQueryStringPos(url, remove_fragment, &query_pos, &query_len)) {
    query_string = url.substr(query_pos, query_len);
  }
  return query_string;
}

data_encoding::WebParamList url::GetQueryStringParameters(
    const std::string& url) {
  // Extract the query string and remove the leading '?'.
  std::string query_string = GetQueryString(url, true);
  if (!query_string.empty() && query_string.front() == '?')
    query_string.erase(query_string.begin());
  return data_encoding::WebParamsDecode(query_string);
}

std::string url::GetQueryStringValue(const std::string& url,
                                     const std::string& name) {
  return GetQueryStringValue(GetQueryStringParameters(url), name);
}

std::string url::GetQueryStringValue(const data_encoding::WebParamList& params,
                                     const std::string& name) {
  for (const auto& pair : params) {
    if (name.compare(pair.first) == 0)
      return pair.second;
  }
  return std::string();
}

std::string url::RemoveQueryString(const std::string& url,
                                   bool remove_fragment_too) {
  size_t query_pos, query_len;
  if (!GetQueryStringPos(url, !remove_fragment_too, &query_pos, &query_len))
    return url;
  std::string result = url.substr(0, query_pos);
  size_t fragment_pos = query_pos + query_len;
  if (fragment_pos < url.size()) {
    result += url.substr(fragment_pos);
  }
  return result;
}

std::string url::AppendQueryParam(const std::string& url,
                                  const std::string& name,
                                  const std::string& value) {
  return AppendQueryParams(url, {{name, value}});
}

std::string url::AppendQueryParams(const std::string& url,
                                   const data_encoding::WebParamList& params) {
  if (params.empty())
    return url;
  size_t query_pos, query_len;
  GetQueryStringPos(url, true, &query_pos, &query_len);
  size_t fragment_pos = query_pos + query_len;
  std::string result = url.substr(0, fragment_pos);
  if (query_len == 0) {
    result += '?';
  } else if (query_len > 1) {
    result += '&';
  }
  result += data_encoding::WebParamsEncode(params);
  if (fragment_pos < url.size()) {
    result += url.substr(fragment_pos);
  }
  return result;
}

bool url::HasQueryString(const std::string& url) {
  size_t query_pos, query_len;
  GetQueryStringPos(url, true, &query_pos, &query_len);
  return (query_len > 0);
}

}  // namespace brillo