mirror of
https://gitlab.isc.org/isc-projects/kea
synced 2025-08-30 05:27:55 +00:00
[master] Merge branch 'master' of ssh://git.kea.isc.org/git/kea
# Conflicts: # ChangeLog
This commit is contained in:
commit
995f389acf
@ -1,9 +1,15 @@
|
||||
1213. [func] tomek
|
||||
1214. [func] tomek
|
||||
Bison parser implemented for Control-agent. The code is able
|
||||
to syntactically parse input configuration, but the output
|
||||
is not used yet.
|
||||
(Trac #5076, git d99048aa5b90efa7812a75cdae98a0913470f5a6)
|
||||
|
||||
1213. [bug] fdupont
|
||||
Option string values containing comma can now be specified
|
||||
correctly by preceding comma with double backslashes (e.g.
|
||||
"foo\\,bar").
|
||||
(Trac #5105, git fa79ac2396aa94d7bac91bd12d3593ebaaa9386d)
|
||||
|
||||
1212. [doc] andreipavelQ
|
||||
Many spelling corrections.
|
||||
(Github #47, git a6a7ca1ced8c63c1e11ef4c572f09272340afdd7)
|
||||
|
@ -27,10 +27,10 @@
|
||||
# "renew-timer": 1000,
|
||||
# "rebind-timer": 2000,
|
||||
|
||||
# Defining a subnet. There are 3 DHCP options returned to the
|
||||
# clients connected to this subnet. The first two options are
|
||||
# identified by the name. The third option is identified by the
|
||||
# option code.
|
||||
// Defining a subnet. There are 3 DHCP options returned to the
|
||||
// clients connected to this subnet. The first and third options are
|
||||
// identified by the name. The third option is identified by the
|
||||
// option code.
|
||||
"subnet4": [
|
||||
{
|
||||
"pools": [ { "pool": "192.0.2.10 - 192.0.2.200" } ],
|
||||
@ -46,8 +46,22 @@
|
||||
"data": "192.0.2.1"
|
||||
},
|
||||
{
|
||||
"code": 15,
|
||||
"data": "example.org"
|
||||
// String options that have a comma in their values need to have
|
||||
// it escaped (i.e. each comma is predeced by two backslashes).
|
||||
// That's because commas are reserved for separating fields in
|
||||
// compound options. At the same time, we need to be conformant
|
||||
// with JSON spec, that does not allow "\,". Therefore the
|
||||
// slightly uncommon double backslashes notation is needed.
|
||||
"name": "boot-file-name",
|
||||
"data": "EST5EDT4\\,M3.2.0/02:00\\,M11.1.0/02:00"
|
||||
|
||||
// Legal JSON escapes are \ followed by "\/bfnrt character
|
||||
// or \u followed by 4 hexa-decimal numbers (currently Kea
|
||||
// supports only \u0000 to \u00ff code points).
|
||||
// CSV processing translates '\\' into '\' and '\,' into ','
|
||||
// only so for instance '\x' is translated into '\x'. But
|
||||
// as it works on a JSON string value each of these '\'
|
||||
// characters must be doubled on JSON input.
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -28,7 +28,7 @@
|
||||
"renew-timer": 1000,
|
||||
"rebind-timer": 2000,
|
||||
|
||||
# Defining a subnet. There are 2 DHCP options returned to the
|
||||
# Defining a subnet. There are 3 DHCP options returned to the
|
||||
# clients connected to this subnet. The first option is identified
|
||||
# by the name. The second option is identified by the code.
|
||||
# There are two address pools defined within this subnet. Pool
|
||||
@ -51,6 +51,24 @@
|
||||
{
|
||||
"code": 12,
|
||||
"data": "2001:db8:1:0:ff00::1"
|
||||
},
|
||||
{
|
||||
// String options that have a comma in their values need to have
|
||||
// it escaped (i.e. each comma is predeced by two backslashes).
|
||||
// That's because commas are reserved for separating fields in
|
||||
// compound options. At the same time, we need to be conformant
|
||||
// with JSON spec, that does not allow "\,". Therefore the
|
||||
// slightly uncommon double backslashes notation is needed.
|
||||
"name": "new-posix-timezone",
|
||||
"data": "EST5EDT4\\,M3.2.0/02:00\\,M11.1.0/02:00"
|
||||
|
||||
// Legal JSON escapes are \ followed by "\/bfnrt character
|
||||
// or \u followed by 4 hexa-decimal numbers (currently Kea
|
||||
// supports only \u0000 to \u00ff code points).
|
||||
// CSV processing translates '\\' into '\' and '\,' into ','
|
||||
// only so for instance '\x' is translated into '\x'. But
|
||||
// as it works on a JSON string value each of these '\'
|
||||
// characters must be doubled on JSON input.
|
||||
}
|
||||
],
|
||||
"pools": [
|
||||
@ -64,7 +82,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"pool": "2001:db8:1::500 - 2001:db8:2::1000"
|
||||
"pool": "2001:db8:1::500 - 2001:db8:1::1000"
|
||||
}
|
||||
],
|
||||
"pd-pools": [
|
||||
|
@ -1016,6 +1016,36 @@ temporarily override a list of interface names and listen on all interfaces.
|
||||
structures. "Type" designates the format of the data: the meanings of
|
||||
the various types is given in <xref linkend="dhcp-types"/>.
|
||||
</para>
|
||||
|
||||
<para>When a data field is a string, and that string contains the comma
|
||||
(,; U+002C) character, the comma must be escaped with a double reverse solidus
|
||||
character (\; U+005C). This double escape is required, because both the
|
||||
routine splitting CSV data into fields and JSON use the same escape
|
||||
character: a single escape (\,) would make the JSON invalid.
|
||||
For example, the string "foo,bar" would be represented as:
|
||||
<screen>
|
||||
"Dhcp4": {
|
||||
"subnet4": [
|
||||
{
|
||||
"pools": [
|
||||
{
|
||||
<userinput>"option-data": [
|
||||
{
|
||||
"name": "boot-file-name",
|
||||
"data": "foo\\,bar"
|
||||
}
|
||||
]</userinput>
|
||||
},
|
||||
...
|
||||
],
|
||||
...
|
||||
},
|
||||
...
|
||||
],
|
||||
...
|
||||
}
|
||||
</screen>
|
||||
</para>
|
||||
<para>
|
||||
Some options are designated as arrays, which means that more than one
|
||||
value is allowed in such an option. For example the option time-servers
|
||||
|
@ -1050,6 +1050,37 @@ temporarily override a list of interface names and listen on all interfaces.
|
||||
which was not assigned by IANA) are listed in
|
||||
<xref linkend="dhcp6-exp-options-list"/>.
|
||||
</para>
|
||||
<para>When a data field is a string, and that string contains
|
||||
the comma (,; U+002C) character, the comma must be escaped with a
|
||||
reverse solidus character (\; U+005C). This double escape is
|
||||
required, because both the routine splitting CSV data into fields
|
||||
and JSON use the same escape character: a single escape (\,) would
|
||||
make the JSON invalid. For example, the string
|
||||
"EST5EDT4,M3.2.0/02:00,M11.1.0/02:00" would be
|
||||
represented as:
|
||||
<screen>
|
||||
"Dhcp6": {
|
||||
"subnet6": [
|
||||
{
|
||||
"pools": [
|
||||
{
|
||||
<userinput>"option-data": [
|
||||
{
|
||||
"name": "new-posix-timezone",
|
||||
"data": "EST5EDT4\,M3.2.0/02:00\,M11.1.0/02:00"
|
||||
}
|
||||
]</userinput>
|
||||
},
|
||||
...
|
||||
],
|
||||
...
|
||||
},
|
||||
...
|
||||
],
|
||||
...
|
||||
}
|
||||
</screen>
|
||||
</para>
|
||||
<para>
|
||||
Some options are designated as arrays, which means that more than one
|
||||
value is allowed in such an option. For example the option dns-servers
|
||||
|
@ -526,7 +526,10 @@ OptionDataParser::createOption(ConstElementPtr option_data) {
|
||||
// separated values then we need to split this string into
|
||||
// individual values - each value will be used to initialize
|
||||
// one data field of an option.
|
||||
data_tokens = isc::util::str::tokens(data_param, ",");
|
||||
// It is the only usage of the escape option: this allows
|
||||
// to embed commas in individual values and to return
|
||||
// for instance a string value with embedded commas.
|
||||
data_tokens = isc::util::str::tokens(data_param, ",", true);
|
||||
|
||||
} else {
|
||||
// Otherwise, the option data is specified as a string of
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include <dhcp/option.h>
|
||||
#include <dhcp/option_custom.h>
|
||||
#include <dhcp/option_int.h>
|
||||
#include <dhcp/option_string.h>
|
||||
#include <dhcp/option6_addrlst.h>
|
||||
#include <dhcp/tests/iface_mgr_test_config.h>
|
||||
#include <dhcpsrv/cfgmgr.h>
|
||||
@ -1210,6 +1211,37 @@ TEST_F(ParseConfigTest, optionDataNoSubOpion) {
|
||||
ASSERT_EQ(0, opt->getOptions().size());
|
||||
}
|
||||
|
||||
// This tests option-data in CSV format and embedded commas.
|
||||
TEST_F(ParseConfigTest, commaCSVFormatOptionData) {
|
||||
|
||||
// Configuration string.
|
||||
std::string config =
|
||||
"{ \"option-data\": [ {"
|
||||
" \"csv-format\": true,"
|
||||
" \"code\": 41,"
|
||||
" \"data\": \"EST5EDT4\\\\,M3.2.0/02:00\\\\,M11.1.0/02:00\","
|
||||
" \"space\": \"dhcp6\""
|
||||
" } ]"
|
||||
"}";
|
||||
|
||||
// Verify that the configuration string parses.
|
||||
int rcode = parseConfiguration(config, true);
|
||||
ASSERT_EQ(0, rcode);
|
||||
|
||||
// Verify that the option can be retrieved.
|
||||
OptionPtr opt = getOptionPtr(DHCP6_OPTION_SPACE, 41);
|
||||
ASSERT_TRUE(opt);
|
||||
|
||||
// Get the option as an option string.
|
||||
OptionStringPtr opt_str = boost::dynamic_pointer_cast<OptionString>(opt);
|
||||
ASSERT_TRUE(opt_str);
|
||||
|
||||
|
||||
// Verify that the option data is correct.
|
||||
string val = "EST5EDT4,M3.2.0/02:00,M11.1.0/02:00";
|
||||
EXPECT_EQ(val, opt_str->getValue());
|
||||
}
|
||||
|
||||
/// The next set of tests check basic operation of the HooksLibrariesParser.
|
||||
//
|
||||
// Convenience function to set a configuration of zero or more hooks
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright (C) 2011-2016 Internet Systems Consortium, Inc. ("ISC")
|
||||
// Copyright (C) 2011-2017 Internet Systems Consortium, Inc. ("ISC")
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
@ -61,29 +61,68 @@ trim(const string& instring) {
|
||||
// another dependency on a Boost library.
|
||||
|
||||
vector<string>
|
||||
tokens(const std::string& text, const std::string& delim) {
|
||||
tokens(const std::string& text, const std::string& delim, bool escape) {
|
||||
vector<string> result;
|
||||
|
||||
// Search for the first non-delimiter character
|
||||
size_t start = text.find_first_not_of(delim);
|
||||
while (start != string::npos) {
|
||||
|
||||
// Non-delimiter found, look for next delimiter
|
||||
size_t end = text.find_first_of(delim, start);
|
||||
if (end != string::npos) {
|
||||
|
||||
// Delimiter found, so extract string & search for start of next
|
||||
// non-delimiter segment.
|
||||
result.push_back(text.substr(start, (end - start)));
|
||||
start = text.find_first_not_of(delim, end);
|
||||
|
||||
string token;
|
||||
bool in_token = false;
|
||||
bool escaped = false;
|
||||
for (auto c = text.cbegin(); c != text.cend(); ++c) {
|
||||
if (delim.find(*c) != string::npos) {
|
||||
// Current character is a delimiter
|
||||
if (!in_token) {
|
||||
// Two or more delimiters, eat them
|
||||
} else if (escaped) {
|
||||
// Escaped delimiter in a token: reset escaped and keep it
|
||||
escaped = false;
|
||||
token.push_back(*c);
|
||||
} else {
|
||||
// End of the current token: save it if not empty
|
||||
if (!token.empty()) {
|
||||
result.push_back(token);
|
||||
}
|
||||
// Reset state
|
||||
in_token = false;
|
||||
token.clear();
|
||||
}
|
||||
} else if (escape && (*c == '\\')) {
|
||||
// Current character is the escape character
|
||||
if (!in_token) {
|
||||
// The escape character is the first character of a new token
|
||||
in_token = true;
|
||||
}
|
||||
if (escaped) {
|
||||
// Escaped escape: reset escaped and keep one character
|
||||
escaped = false;
|
||||
token.push_back(*c);
|
||||
} else {
|
||||
// Remember to keep the next character
|
||||
escaped = true;
|
||||
}
|
||||
} else {
|
||||
|
||||
// End of string found, extract rest of string and flag to exit
|
||||
result.push_back(text.substr(start));
|
||||
start = string::npos;
|
||||
// Not a delimiter nor an escape
|
||||
if (!in_token) {
|
||||
// First character of a new token
|
||||
in_token = true;
|
||||
}
|
||||
if (escaped) {
|
||||
// Escaped common character: as escape was false
|
||||
escaped = false;
|
||||
token.push_back('\\');
|
||||
token.push_back(*c);
|
||||
} else {
|
||||
// The common case: keep it
|
||||
token.push_back(*c);
|
||||
}
|
||||
}
|
||||
}
|
||||
// End of input: close and save the current token if not empty
|
||||
if (escaped) {
|
||||
// Pending escape
|
||||
token.push_back('\\');
|
||||
}
|
||||
if (!token.empty()) {
|
||||
result.push_back(token);
|
||||
}
|
||||
|
||||
return (result);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright (C) 2011-2016 Internet Systems Consortium, Inc. ("ISC")
|
||||
// Copyright (C) 2011-2017 Internet Systems Consortium, Inc. ("ISC")
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
@ -69,6 +69,8 @@ std::string trim(const std::string& instring);
|
||||
/// invisible leading and trailing delimiter characters. Therefore both cases
|
||||
/// reduce to a set of contiguous delimiters, which are considered a single
|
||||
/// delimiter (so getting rid of the string).
|
||||
/// Optional escape allows to escape delimiter characters (and *only* them
|
||||
/// and the escape character itself) using backslash.
|
||||
///
|
||||
/// We could use Boost for this, but this (simple) function eliminates one
|
||||
/// dependency in the code.
|
||||
@ -76,10 +78,12 @@ std::string trim(const std::string& instring);
|
||||
/// \param text String to be split. Passed by value as the internal copy is
|
||||
/// altered during the processing.
|
||||
/// \param delim Delimiter characters
|
||||
/// \param escape Use backslash to escape delimiter characters
|
||||
///
|
||||
/// \return Vector of tokens.
|
||||
std::vector<std::string> tokens(const std::string& text,
|
||||
const std::string& delim = std::string(" \t\n"));
|
||||
const std::string& delim = std::string(" \t\n"),
|
||||
bool escape = false);
|
||||
|
||||
|
||||
/// \brief Uppercase Character
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright (C) 2011-2016 Internet Systems Consortium, Inc. ("ISC")
|
||||
// Copyright (C) 2011-2017 Internet Systems Consortium, Inc. ("ISC")
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
@ -143,6 +143,42 @@ TEST(StringUtilTest, Tokens) {
|
||||
EXPECT_EQ(string("gamma"), result[3]);
|
||||
EXPECT_EQ(string("delta"), result[4]);
|
||||
EXPECT_EQ(string("epsilon"), result[5]);
|
||||
|
||||
// Escaped delimiter
|
||||
result = isc::util::str::tokens("foo\\,bar", ",", true);
|
||||
EXPECT_EQ(1, result.size());
|
||||
EXPECT_EQ(string("foo,bar"), result[0]);
|
||||
|
||||
// Escaped escape
|
||||
result = isc::util::str::tokens("foo\\\\,bar", ",", true);
|
||||
ASSERT_EQ(2, result.size());
|
||||
EXPECT_EQ(string("foo\\"), result[0]);
|
||||
EXPECT_EQ(string("bar"), result[1]);
|
||||
|
||||
// Double escapes
|
||||
result = isc::util::str::tokens("foo\\\\\\\\,\\bar", ",", true);
|
||||
ASSERT_EQ(2, result.size());
|
||||
EXPECT_EQ(string("foo\\\\"), result[0]);
|
||||
EXPECT_EQ(string("\\bar"), result[1]);
|
||||
|
||||
// Escaped standard character
|
||||
result = isc::util::str::tokens("fo\\o,bar", ",", true);
|
||||
ASSERT_EQ(2, result.size());
|
||||
EXPECT_EQ(string("fo\\o"), result[0]);
|
||||
EXPECT_EQ(string("bar"), result[1]);
|
||||
|
||||
// Escape at the end
|
||||
result = isc::util::str::tokens("foo,bar\\", ",", true);
|
||||
ASSERT_EQ(2, result.size());
|
||||
EXPECT_EQ(string("foo"), result[0]);
|
||||
EXPECT_EQ(string("bar\\"), result[1]);
|
||||
|
||||
// Escape opening a token
|
||||
result = isc::util::str::tokens("foo,\\,,bar", ",", true);
|
||||
ASSERT_EQ(3, result.size());
|
||||
EXPECT_EQ(string("foo"), result[0]);
|
||||
EXPECT_EQ(string(","), result[1]);
|
||||
EXPECT_EQ(string("bar"), result[2]);
|
||||
}
|
||||
|
||||
// Changing case
|
||||
|
Loading…
x
Reference in New Issue
Block a user