blob: a8c72cbb977bc64aa4261e1f75dd1d0b1edc1063 [file] [log] [blame]
/*
* \copyright Copyright 2013 Google Inc. All Rights Reserved.
* \license @{
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @}
*/
// Implementation of RFC 6570 based on (open source implementation) at
// java/com/google/api/client/http/UriTemplate.java
// The URI Template spec is at http://tools.ietf.org/html/rfc6570
// Templates up to level 3 are supported.
#include "net/third_party/uri_template/uri_template.h"
#include <set>
#include <string>
#include <vector>
#include "base/strings/string_split.h"
#include "net/base/escape.h"
using std::string;
namespace uri_template {
namespace {
// The UriTemplateConfig is used to represent variable sections and to construct
// the expanded url.
struct UriTemplateConfig {
public:
UriTemplateConfig(const char* prefix,
const char* joiner,
bool requires_variable_assignment,
bool allow_reserved_expansion,
bool no_variable_assignment_if_empty = false)
: prefix_(prefix),
joiner_(joiner),
requires_variable_assignment_(requires_variable_assignment),
no_variable_assignment_if_empty_(no_variable_assignment_if_empty),
allow_reserved_expansion_(allow_reserved_expansion) {}
void AppendValue(const string& variable,
const string& value,
bool use_prefix,
string* target) const {
string joiner = use_prefix ? prefix_ : joiner_;
if (requires_variable_assignment_) {
if (value.empty() && no_variable_assignment_if_empty_) {
target->append(joiner + EscapedValue(variable));
} else {
target->append(joiner + EscapedValue(variable) + "=" +
EscapedValue(value));
}
} else {
target->append(joiner + EscapedValue(value));
}
}
private:
string EscapedValue(const string& value) const {
string escaped;
if (allow_reserved_expansion_) {
// Reserved expansion passes through reserved and pct-encoded characters.
escaped = net::EscapeExternalHandlerValue(value);
} else {
escaped = net::EscapeAllExceptUnreserved(value);
}
return escaped;
}
const char* prefix_;
const char* joiner_;
bool requires_variable_assignment_;
bool no_variable_assignment_if_empty_;
bool allow_reserved_expansion_;
};
// variable is an in-out argument. On input it is the content between the
// '{}' in the source. On result the control parameters are stripped off
// leaving just the comma-separated variable name(s) that we should try to
// resolve.
UriTemplateConfig MakeConfig(string* variable) {
switch (*variable->data()) {
// Reserved expansion.
case '+':
*variable = variable->substr(1);
return UriTemplateConfig("", ",", false, true);
// Fragment expansion.
case '#':
*variable = variable->substr(1);
return UriTemplateConfig("#", ",", false, true);
// Label with dot-prefix.
case '.':
*variable = variable->substr(1);
return UriTemplateConfig(".", ".", false, false);
// Path segment expansion.
case '/':
*variable = variable->substr(1);
return UriTemplateConfig("/", "/", false, false);
// Path segment parameter expansion.
case ';':
*variable = variable->substr(1);
return UriTemplateConfig(";", ";", true, false, true);
// Form-style query expansion.
case '?':
*variable = variable->substr(1);
return UriTemplateConfig("?", "&", true, false);
// Form-style query continuation.
case '&':
*variable = variable->substr(1);
return UriTemplateConfig("&", "&", true, false);
// Simple expansion.
default:
return UriTemplateConfig("", ",", false, false);
}
}
void ProcessVariableSection(
string* variable_section,
const std::unordered_map<string, string>& parameters,
string* target,
std::set<string>* vars_found) {
// Note that this function will modify the variable_section string to remove
// the decorators, leaving just comma-separated variable name(s).
UriTemplateConfig config = MakeConfig(variable_section);
std::vector<string> variables = base::SplitString(
*variable_section, ",", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
bool first_var = true;
for (const string& variable : variables) {
auto found = parameters.find(variable);
if (found != parameters.end()) {
config.AppendValue(variable, found->second, first_var, target);
first_var = false;
if (vars_found) {
vars_found->insert(variable);
}
}
}
}
} // namespace
bool Expand(const string& path_uri,
const std::unordered_map<string, string>& parameters,
string* target,
std::set<string>* vars_found) {
size_t cur = 0;
size_t length = path_uri.length();
while (cur < length) {
size_t open = path_uri.find('{', cur);
size_t close = path_uri.find('}', cur);
if (open == string::npos) {
if (close == string::npos) {
// No more variables to process.
target->append(path_uri.substr(cur).data(), path_uri.length() - cur);
return true;
} else {
// Template was malformed. Unexpected closing brace.
target->clear();
return false;
}
}
target->append(path_uri, cur, open - cur);
size_t next_open = path_uri.find('{', open + 1);
if (close == string::npos || close < open || next_open < close) {
// Template was malformed.
target->clear();
return false;
}
string variable_section(path_uri, open + 1, close - open - 1);
cur = close + 1;
ProcessVariableSection(&variable_section, parameters, target, vars_found);
}
return true;
}
} // namespace uri_template