2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-09-01 14:35:29 +00:00

[master] Merge branch 'master' of ssh://git.kea.isc.org/git/kea

# Conflicts:
#	ChangeLog
This commit is contained in:
Tomek Mrugalski
2017-02-10 19:46:53 +01:00
10 changed files with 246 additions and 33 deletions

View File

@@ -1,9 +1,15 @@
1213. [func] tomek 1214. [func] tomek
Bison parser implemented for Control-agent. The code is able Bison parser implemented for Control-agent. The code is able
to syntactically parse input configuration, but the output to syntactically parse input configuration, but the output
is not used yet. is not used yet.
(Trac #5076, git d99048aa5b90efa7812a75cdae98a0913470f5a6) (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 1212. [doc] andreipavelQ
Many spelling corrections. Many spelling corrections.
(Github #47, git a6a7ca1ced8c63c1e11ef4c572f09272340afdd7) (Github #47, git a6a7ca1ced8c63c1e11ef4c572f09272340afdd7)

View File

@@ -27,10 +27,10 @@
# "renew-timer": 1000, # "renew-timer": 1000,
# "rebind-timer": 2000, # "rebind-timer": 2000,
# Defining a subnet. There are 3 DHCP options returned to the // Defining a subnet. There are 3 DHCP options returned to the
# clients connected to this subnet. The first two options are // clients connected to this subnet. The first and third options are
# identified by the name. The third option is identified by the // identified by the name. The third option is identified by the
# option code. // option code.
"subnet4": [ "subnet4": [
{ {
"pools": [ { "pool": "192.0.2.10 - 192.0.2.200" } ], "pools": [ { "pool": "192.0.2.10 - 192.0.2.200" } ],
@@ -46,8 +46,22 @@
"data": "192.0.2.1" "data": "192.0.2.1"
}, },
{ {
"code": 15, // String options that have a comma in their values need to have
"data": "example.org" // 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.
} }
] ]
} }

View File

@@ -28,7 +28,7 @@
"renew-timer": 1000, "renew-timer": 1000,
"rebind-timer": 2000, "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 # clients connected to this subnet. The first option is identified
# by the name. The second option is identified by the code. # by the name. The second option is identified by the code.
# There are two address pools defined within this subnet. Pool # There are two address pools defined within this subnet. Pool
@@ -51,6 +51,24 @@
{ {
"code": 12, "code": 12,
"data": "2001:db8:1:0:ff00::1" "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": [ "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": [ "pd-pools": [

View File

@@ -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 structures. "Type" designates the format of the data: the meanings of
the various types is given in <xref linkend="dhcp-types"/>. the various types is given in <xref linkend="dhcp-types"/>.
</para> </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 &quot;foo,bar&quot; would be represented as:
<screen>
"Dhcp4": {
"subnet4": [
{
"pools": [
{
<userinput>"option-data": [
{
"name": "boot-file-name",
"data": "foo\\,bar"
}
]</userinput>
},
...
],
...
},
...
],
...
}
</screen>
</para>
<para> <para>
Some options are designated as arrays, which means that more than one 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 value is allowed in such an option. For example the option time-servers

View File

@@ -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 which was not assigned by IANA) are listed in
<xref linkend="dhcp6-exp-options-list"/>. <xref linkend="dhcp6-exp-options-list"/>.
</para> </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
&quot;EST5EDT4,M3.2.0/02:00,M11.1.0/02:00&quot; 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> <para>
Some options are designated as arrays, which means that more than one 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 value is allowed in such an option. For example the option dns-servers

View File

@@ -526,7 +526,10 @@ OptionDataParser::createOption(ConstElementPtr option_data) {
// separated values then we need to split this string into // separated values then we need to split this string into
// individual values - each value will be used to initialize // individual values - each value will be used to initialize
// one data field of an option. // 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 { } else {
// Otherwise, the option data is specified as a string of // Otherwise, the option data is specified as a string of

View File

@@ -11,6 +11,7 @@
#include <dhcp/option.h> #include <dhcp/option.h>
#include <dhcp/option_custom.h> #include <dhcp/option_custom.h>
#include <dhcp/option_int.h> #include <dhcp/option_int.h>
#include <dhcp/option_string.h>
#include <dhcp/option6_addrlst.h> #include <dhcp/option6_addrlst.h>
#include <dhcp/tests/iface_mgr_test_config.h> #include <dhcp/tests/iface_mgr_test_config.h>
#include <dhcpsrv/cfgmgr.h> #include <dhcpsrv/cfgmgr.h>
@@ -1210,6 +1211,37 @@ TEST_F(ParseConfigTest, optionDataNoSubOpion) {
ASSERT_EQ(0, opt->getOptions().size()); 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. /// The next set of tests check basic operation of the HooksLibrariesParser.
// //
// Convenience function to set a configuration of zero or more hooks // Convenience function to set a configuration of zero or more hooks

View File

@@ -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 // 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 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -61,28 +61,67 @@ trim(const string& instring) {
// another dependency on a Boost library. // another dependency on a Boost library.
vector<string> 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; vector<string> result;
string token;
// Search for the first non-delimiter character bool in_token = false;
size_t start = text.find_first_not_of(delim); bool escaped = false;
while (start != string::npos) { for (auto c = text.cbegin(); c != text.cend(); ++c) {
if (delim.find(*c) != string::npos) {
// Non-delimiter found, look for next delimiter // Current character is a delimiter
size_t end = text.find_first_of(delim, start); if (!in_token) {
if (end != string::npos) { // Two or more delimiters, eat them
} else if (escaped) {
// Delimiter found, so extract string & search for start of next // Escaped delimiter in a token: reset escaped and keep it
// non-delimiter segment. escaped = false;
result.push_back(text.substr(start, (end - start))); token.push_back(*c);
start = text.find_first_not_of(delim, end);
} else { } else {
// End of the current token: save it if not empty
// End of string found, extract rest of string and flag to exit if (!token.empty()) {
result.push_back(text.substr(start)); result.push_back(token);
start = string::npos;
} }
// 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 {
// 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); return (result);

View File

@@ -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 // 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 // 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 /// invisible leading and trailing delimiter characters. Therefore both cases
/// reduce to a set of contiguous delimiters, which are considered a single /// reduce to a set of contiguous delimiters, which are considered a single
/// delimiter (so getting rid of the string). /// 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 /// We could use Boost for this, but this (simple) function eliminates one
/// dependency in the code. /// 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 /// \param text String to be split. Passed by value as the internal copy is
/// altered during the processing. /// altered during the processing.
/// \param delim Delimiter characters /// \param delim Delimiter characters
/// \param escape Use backslash to escape delimiter characters
/// ///
/// \return Vector of tokens. /// \return Vector of tokens.
std::vector<std::string> tokens(const std::string& text, 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 /// \brief Uppercase Character

View File

@@ -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 // 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 // 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("gamma"), result[3]);
EXPECT_EQ(string("delta"), result[4]); EXPECT_EQ(string("delta"), result[4]);
EXPECT_EQ(string("epsilon"), result[5]); 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 // Changing case