1 // Copyright (C) 2011-2020 Internet Systems Consortium, Inc. ("ISC")
2 //
3 // This Source Code Form is subject to the terms of the Mozilla Public
4 // License, v. 2.0. If a copy of the MPL was not distributed with this
5 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 
7 // This is the common code for TXT and SPF tests.
8 
9 #include <config.h>
10 
11 #include <util/buffer.h>
12 #include <dns/exceptions.h>
13 #include <dns/rdataclass.h>
14 
15 #include <dns/tests/unittest_util.h>
16 #include <dns/tests/rdata_unittest.h>
17 
18 #include <util/unittests/wiredata.h>
19 
20 #include <gtest/gtest.h>
21 
22 #include <string>
23 #include <sstream>
24 #include <vector>
25 
26 using namespace std;
27 using namespace isc::dns;
28 using namespace isc::util;
29 using namespace isc::dns::rdata;
30 using isc::UnitTestUtil;
31 using isc::util::unittests::matchWireData;
32 
33 namespace {
34 
35 template<class T>
36 class RRTYPE : public RRType {
37 public:
38     RRTYPE();
39 };
40 
RRTYPE()41 template<> RRTYPE<generic::TXT>::RRTYPE() : RRType(RRType::TXT()) {}
RRTYPE()42 template<> RRTYPE<generic::SPF>::RRTYPE() : RRType(RRType::SPF()) {}
43 
44 const uint8_t wiredata_txt_like[] = {
45     sizeof("Test-String") - 1,
46     'T', 'e', 's', 't', '-', 'S', 't', 'r', 'i', 'n', 'g'
47 };
48 
49 const uint8_t wiredata_nulltxt[] = { 0 };
50 
51 template<class TXT_LIKE>
52 class Rdata_TXT_LIKE_Test : public RdataTest {
53 protected:
Rdata_TXT_LIKE_Test()54     Rdata_TXT_LIKE_Test() :
55         wiredata_longesttxt(256, 'a'),
56         rdata_txt_like("Test-String"),
57         rdata_txt_like_empty("\"\""),
58         rdata_txt_like_quoted("\"Test-String\"")
59     {
60         wiredata_longesttxt[0] = 255; // adjust length
61     }
62 
63 protected:
64     vector<uint8_t> wiredata_longesttxt;
65     const TXT_LIKE rdata_txt_like;
66     const TXT_LIKE rdata_txt_like_empty;
67     const TXT_LIKE rdata_txt_like_quoted;
68 };
69 
70 // The list of types we want to test.
71 typedef testing::Types<generic::TXT, generic::SPF> Implementations;
72 
73 #ifdef TYPED_TEST_SUITE
74 TYPED_TEST_SUITE(Rdata_TXT_LIKE_Test, Implementations);
75 #else
76 TYPED_TEST_CASE(Rdata_TXT_LIKE_Test, Implementations);
77 #endif
78 
TYPED_TEST(Rdata_TXT_LIKE_Test,createFromText)79 TYPED_TEST(Rdata_TXT_LIKE_Test, createFromText) {
80     // Below we check the behavior for the "from text" constructors, both
81     // from std::string and with MasterLexer.  The underlying implementation
82     // is the same, so both should work exactly same, but we confirm both
83     // cases.
84 
85     const std::string multi_line = "(\n \"Test-String\" )";
86     const std::string escaped_txt = "Test\\045Strin\\g";
87 
88     // test input for the lexer version
89     std::stringstream ss;
90     ss << "Test-String\n";
91     ss << "\"Test-String\"\n";   // explicitly surrounded by '"'s
92     ss << multi_line << "\n";   // multi-line text with ()
93     ss << escaped_txt << "\n";   // using the two types of escape with '\'
94     ss << "\"\"\n";              // empty string (note: still valid char-str)
95     ss << string(255, 'a') << "\n"; // Longest possible character-string.
96     ss << string(256, 'a') << "\n"; // char-string too long
97     ss << "\"Test-String\\\"\n";    // unbalanced quote
98     ss << "\"Test-String\\\"\"\n";
99     this->lexer.pushSource(ss);
100 
101     // commonly used Rdata to compare below, created from wire
102     ConstRdataPtr const rdata =
103         this->rdataFactoryFromFile(RRTYPE<TypeParam>(),
104                                    RRClass("IN"), "rdata_txt_fromWire1");
105 
106     // normal case is covered in toWireBuffer.  First check the std::string
107     // case, then with MasterLexer.  For the latter, we need to read and skip
108     // '\n'.  These apply to most of the other cases below.
109     EXPECT_EQ(0, this->rdata_txt_like.compare(*rdata));
110     EXPECT_EQ(0, TypeParam(this->lexer, NULL, MasterLoader::MANY_ERRORS,
111                            this->loader_cb).compare(*rdata));
112     EXPECT_EQ(MasterToken::END_OF_LINE, this->lexer.getNextToken().getType());
113 
114     // surrounding double-quotes shouldn't change the result.
115     EXPECT_EQ(0, this->rdata_txt_like_quoted.compare(*rdata));
116     EXPECT_EQ(0, TypeParam(this->lexer, NULL, MasterLoader::MANY_ERRORS,
117                            this->loader_cb).compare(*rdata));
118     EXPECT_EQ(MasterToken::END_OF_LINE, this->lexer.getNextToken().getType());
119 
120     // multi-line input with ()
121     EXPECT_EQ(0, TypeParam(multi_line).compare(*rdata));
122     EXPECT_EQ(0, TypeParam(this->lexer, NULL, MasterLoader::MANY_ERRORS,
123                            this->loader_cb).compare(*rdata));
124     EXPECT_EQ(MasterToken::END_OF_LINE, this->lexer.getNextToken().getType());
125 
126     // for the same data using escape
127     EXPECT_EQ(0, TypeParam(escaped_txt).compare(*rdata));
128     EXPECT_EQ(0, TypeParam(this->lexer, NULL, MasterLoader::MANY_ERRORS,
129                            this->loader_cb).compare(*rdata));
130     EXPECT_EQ(MasterToken::END_OF_LINE, this->lexer.getNextToken().getType());
131 
132     // Null character-string.
133     this->obuffer.clear();
134     TypeParam(string("\"\"")).toWire(this->obuffer);
135     matchWireData(wiredata_nulltxt, sizeof(wiredata_nulltxt),
136                   this->obuffer.getData(), this->obuffer.getLength());
137 
138     this->obuffer.clear();
139     TypeParam(this->lexer, NULL, MasterLoader::MANY_ERRORS, this->loader_cb).
140         toWire(this->obuffer);
141     matchWireData(wiredata_nulltxt, sizeof(wiredata_nulltxt),
142                   this->obuffer.getData(), this->obuffer.getLength());
143 
144     EXPECT_EQ(MasterToken::END_OF_LINE, this->lexer.getNextToken().getType());
145 
146     // Longest possible character-string.
147     this->obuffer.clear();
148     TypeParam(string(255, 'a')).toWire(this->obuffer);
149     matchWireData(&this->wiredata_longesttxt[0],
150                   this->wiredata_longesttxt.size(),
151                   this->obuffer.getData(), this->obuffer.getLength());
152 
153     this->obuffer.clear();
154     TypeParam(this->lexer, NULL, MasterLoader::MANY_ERRORS, this->loader_cb).
155         toWire(this->obuffer);
156     matchWireData(&this->wiredata_longesttxt[0],
157                   this->wiredata_longesttxt.size(),
158                   this->obuffer.getData(), this->obuffer.getLength());
159 
160     EXPECT_EQ(MasterToken::END_OF_LINE, this->lexer.getNextToken().getType());
161 
162     // Too long text for a valid character-string.
163     EXPECT_THROW(TypeParam(string(256, 'a')), CharStringTooLong);
164     EXPECT_THROW(TypeParam(this->lexer, NULL, MasterLoader::MANY_ERRORS,
165                            this->loader_cb), CharStringTooLong);
166     EXPECT_EQ(MasterToken::END_OF_LINE, this->lexer.getNextToken().getType());
167 
168     // The escape character makes the double quote a part of character-string,
169     // so this is invalid input and should be rejected.
170     EXPECT_THROW(TypeParam("\"Test-String\\\""), InvalidRdataText);
171     EXPECT_THROW(TypeParam(this->lexer, NULL, MasterLoader::MANY_ERRORS,
172                            this->loader_cb), MasterLexer::LexerError);
173     EXPECT_EQ(MasterToken::END_OF_LINE, this->lexer.getNextToken().getType());
174 }
175 
TYPED_TEST(Rdata_TXT_LIKE_Test,createMultiStringsFromText)176 TYPED_TEST(Rdata_TXT_LIKE_Test, createMultiStringsFromText) {
177     // Tests for "from text" variants construction with various forms of
178     // multi character-strings.
179 
180     std::vector<std::string > texts;
181     texts.push_back("\"Test-String\" \"Test-String\""); // most common form
182     texts.push_back("\"Test-String\"\"Test-String\"");  // no space between'em
183     texts.push_back("\"Test-String\" Test-String");  // no '"' for one
184     texts.push_back("\"Test-String\"Test-String"); // and no space either
185     texts.push_back("Test-String \"Test-String\""); // no '"' for the other
186     texts.push_back("Test-String\"Test-String\""); // and no space either
187 
188     std::stringstream ss;
189     for (std::vector<std::string >::const_iterator it = texts.begin();
190          it != texts.end(); ++it) {
191         ss << *it << "\n";
192     }
193     this->lexer.pushSource(ss);
194 
195     // The corresponding Rdata built from wire to compare in the checks below.
196     ConstRdataPtr const rdata =
197         this->rdataFactoryFromFile(RRTYPE<TypeParam>(),
198                                    RRClass("IN"), "rdata_txt_fromWire3.wire");
199 
200     // Confirm we can construct the Rdata from the test text, both from
201     // std::string and with lexer, and that matches the from-wire data.
202     for (std::vector<std::string >::const_iterator it = texts.begin();
203          it != texts.end(); ++it) {
204         SCOPED_TRACE(*it);
205         EXPECT_EQ(0, TypeParam(*it).compare(*rdata));
206 
207         EXPECT_EQ(0, TypeParam(this->lexer, NULL, MasterLoader::MANY_ERRORS,
208                                this->loader_cb).compare(*rdata));
209         EXPECT_EQ(MasterToken::END_OF_LINE,
210                   this->lexer.getNextToken().getType());
211     }
212 }
213 
TYPED_TEST(Rdata_TXT_LIKE_Test,createFromTextExtra)214 TYPED_TEST(Rdata_TXT_LIKE_Test, createFromTextExtra) {
215     // This is for the std::string version only: the input must end with EOF;
216     // an extra new-line will result in an exception.
217     EXPECT_THROW(TypeParam("\"Test-String\"\n"), InvalidRdataText);
218     // Same if there's a space before '\n'
219     EXPECT_THROW(TypeParam("\"Test-String\" \n"), InvalidRdataText);
220 }
221 
TYPED_TEST(Rdata_TXT_LIKE_Test,fromTextEmpty)222 TYPED_TEST(Rdata_TXT_LIKE_Test, fromTextEmpty) {
223     // If the input text doesn't contain any character-string, it should be
224     // rejected
225     EXPECT_THROW(TypeParam(""), InvalidRdataText);
226     EXPECT_THROW(TypeParam(" "), InvalidRdataText); // even with a space
227     EXPECT_THROW(TypeParam("(\n)"), InvalidRdataText); // or multi-line with ()
228 }
229 
230 void
makeLargest(vector<uint8_t> & data)231 makeLargest(vector<uint8_t>& data) {
232     uint8_t ch = 0;
233 
234     // create 255 sets of character-strings, each of which has the longest
235     // length (255bytes string + 1-byte length field)
236     for (int i = 0; i < 255; ++i, ++ch) {
237         data.push_back(255);
238         data.insert(data.end(), 255, ch);
239     }
240     // the last character-string should be 255 bytes (including the one-byte
241     // length field) in length so that the total length should be in the range
242     // of 16-bit integers.
243     data.push_back(254);
244     data.insert(data.end(), 254, ch);
245 
246     assert(data.size() == 65535);
247 }
248 
TYPED_TEST(Rdata_TXT_LIKE_Test,createFromWire)249 TYPED_TEST(Rdata_TXT_LIKE_Test, createFromWire) {
250     EXPECT_EQ(0, this->rdata_txt_like.compare(
251                   *this->rdataFactoryFromFile(RRTYPE<TypeParam>(),
252                                               RRClass("IN"),
253                                               "rdata_txt_fromWire1")));
254 
255     // Empty character string
256     EXPECT_EQ(0, this->rdata_txt_like_empty.compare(
257                   *this->rdataFactoryFromFile(RRTYPE<TypeParam>(),
258                                               RRClass("IN"),
259                                               "rdata_txt_fromWire2.wire")));
260 
261     // Multiple character strings
262     this->obuffer.clear();
263     this->rdataFactoryFromFile(RRTYPE<TypeParam>(), RRClass("IN"),
264                          "rdata_txt_fromWire3.wire")->toWire(this->obuffer);
265     // the result should be 'wiredata_txt' repeated twice
266     vector<uint8_t> expected_data(wiredata_txt_like, wiredata_txt_like +
267                                   sizeof(wiredata_txt_like));
268     expected_data.insert(expected_data.end(), wiredata_txt_like,
269                          wiredata_txt_like + sizeof(wiredata_txt_like));
270     matchWireData(&expected_data[0], expected_data.size(),
271                   this->obuffer.getData(), this->obuffer.getLength());
272 
273     // Largest length of data.  There's nothing special, but should be
274     // constructed safely, and the content should be identical to the original
275     // data.
276     vector<uint8_t> largest_txt_like_data;
277     makeLargest(largest_txt_like_data);
278     InputBuffer ibuffer(&largest_txt_like_data[0],
279                         largest_txt_like_data.size());
280     TypeParam largest_txt_like(ibuffer, largest_txt_like_data.size());
281     this->obuffer.clear();
282     largest_txt_like.toWire(this->obuffer);
283     matchWireData(&largest_txt_like_data[0], largest_txt_like_data.size(),
284                   this->obuffer.getData(), this->obuffer.getLength());
285 
286     // rdlen parameter is out of range.  This is a rare event because we'd
287     // normally call the constructor via a polymorphic wrapper, where the
288     // length is validated.  But this should be checked explicitly.
289     InputBuffer ibuffer2(&largest_txt_like_data[0],
290                          largest_txt_like_data.size());
291     EXPECT_THROW(TypeParam(ibuffer2, 65536), InvalidRdataLength);
292 
293     // RDATA is empty, which is invalid for TXT_LIKE.
294     EXPECT_THROW(this->rdataFactoryFromFile(RRTYPE<TypeParam>(), RRClass("IN"),
295                                       "rdata_txt_fromWire4.wire"),
296                  DNSMessageFORMERR);
297 
298     // character-string length is too large, which could cause overrun.
299     EXPECT_THROW(this->rdataFactoryFromFile(RRTYPE<TypeParam>(), RRClass("IN"),
300                                       "rdata_txt_fromWire5.wire"),
301                  DNSMessageFORMERR);
302 }
303 
TYPED_TEST(Rdata_TXT_LIKE_Test,createFromLexer)304 TYPED_TEST(Rdata_TXT_LIKE_Test, createFromLexer) {
305     EXPECT_EQ(0, this->rdata_txt_like.compare(
306         *test::createRdataUsingLexer(RRTYPE<TypeParam>(), RRClass::IN(),
307                                      "Test-String")));
308 }
309 
TYPED_TEST(Rdata_TXT_LIKE_Test,toWireBuffer)310 TYPED_TEST(Rdata_TXT_LIKE_Test, toWireBuffer) {
311     this->rdata_txt_like.toWire(this->obuffer);
312     matchWireData(wiredata_txt_like, sizeof(wiredata_txt_like),
313                   this->obuffer.getData(), this->obuffer.getLength());
314 }
315 
TYPED_TEST(Rdata_TXT_LIKE_Test,toWireRenderer)316 TYPED_TEST(Rdata_TXT_LIKE_Test, toWireRenderer) {
317     this->rdata_txt_like.toWire(this->renderer);
318     matchWireData(wiredata_txt_like, sizeof(wiredata_txt_like),
319                   this->renderer.getData(), this->renderer.getLength());
320 }
321 
TYPED_TEST(Rdata_TXT_LIKE_Test,toText)322 TYPED_TEST(Rdata_TXT_LIKE_Test, toText) {
323     EXPECT_EQ("\"Test-String\"", this->rdata_txt_like.toText());
324     EXPECT_EQ("\"\"", this->rdata_txt_like_empty.toText());
325     EXPECT_EQ("\"Test-String\"", this->rdata_txt_like_quoted.toText());
326 
327     // Check escape behavior
328     const TypeParam double_quotes("Test-String\"Test-String\"");
329     EXPECT_EQ("\"Test-String\" \"Test-String\"", double_quotes.toText());
330     const TypeParam semicolon("Test-String\\;Test-String");
331     EXPECT_EQ("\"Test-String\\;Test-String\"", semicolon.toText());
332     const TypeParam backslash("Test-String\\\\Test-String");
333     EXPECT_EQ("\"Test-String\\\\Test-String\"", backslash.toText());
334     const TypeParam before_x20("Test-String\\031Test-String");
335     EXPECT_EQ("\"Test-String\\031Test-String\"", before_x20.toText());
336     const TypeParam from_x20_to_x7e("\"Test-String ~ Test-String\"");
337     EXPECT_EQ("\"Test-String ~ Test-String\"", from_x20_to_x7e.toText());
338     const TypeParam from_x20_to_x7e_2("Test-String\\032\\126\\032Test-String");
339     EXPECT_EQ("\"Test-String ~ Test-String\"", from_x20_to_x7e_2.toText());
340     const TypeParam after_x7e("Test-String\\127Test-String");
341     EXPECT_EQ("\"Test-String\\127Test-String\"", after_x7e.toText());
342 }
343 
TYPED_TEST(Rdata_TXT_LIKE_Test,assignment)344 TYPED_TEST(Rdata_TXT_LIKE_Test, assignment) {
345     TypeParam rdata1("assignment1");
346     TypeParam rdata2("assignment2");
347     rdata1 = rdata2;
348     EXPECT_EQ(0, rdata2.compare(rdata1));
349 
350     // Check if the copied data is valid even after the original is deleted
351     TypeParam* rdata3 = new TypeParam(rdata1);
352     TypeParam rdata4("assignment3");
353     rdata4 = *rdata3;
354     delete rdata3;
355     EXPECT_EQ(0, rdata4.compare(rdata1));
356 
357     // Self assignment
358     rdata2 = *&rdata2;
359     EXPECT_EQ(0, rdata2.compare(rdata1));
360 }
361 
TYPED_TEST(Rdata_TXT_LIKE_Test,compare)362 TYPED_TEST(Rdata_TXT_LIKE_Test, compare) {
363     string const txt1("aaaaaaaa");
364     string const txt2("aaaaaaaaaa");
365     string const txt3("bbbbbbbb");
366     string const txt4(129, 'a');
367     string const txt5(128, 'b');
368 
369     EXPECT_EQ(TypeParam(txt1).compare(TypeParam(txt1)), 0);
370 
371     EXPECT_LT(TypeParam("\"\"").compare(TypeParam(txt1)), 0);
372     EXPECT_GT(TypeParam(txt1).compare(TypeParam("\"\"")), 0);
373 
374     EXPECT_LT(TypeParam(txt1).compare(TypeParam(txt2)), 0);
375     EXPECT_GT(TypeParam(txt2).compare(TypeParam(txt1)), 0);
376 
377     EXPECT_LT(TypeParam(txt1).compare(TypeParam(txt3)), 0);
378     EXPECT_GT(TypeParam(txt3).compare(TypeParam(txt1)), 0);
379 
380     // we're comparing the data raw, starting at the length octet, so a shorter
381     // string sorts before a longer one no matter the lexicopraphical order
382     EXPECT_LT(TypeParam(txt3).compare(TypeParam(txt2)), 0);
383     EXPECT_GT(TypeParam(txt2).compare(TypeParam(txt3)), 0);
384 
385     // to make sure the length octet compares unsigned
386     EXPECT_LT(TypeParam(txt1).compare(TypeParam(txt4)), 0);
387     EXPECT_GT(TypeParam(txt4).compare(TypeParam(txt1)), 0);
388 
389     EXPECT_LT(TypeParam(txt5).compare(TypeParam(txt4)), 0);
390     EXPECT_GT(TypeParam(txt4).compare(TypeParam(txt5)), 0);
391 
392     // comparison attempt between incompatible RR types should be rejected
393     EXPECT_THROW(TypeParam(txt1).compare(*this->rdata_nomatch),
394                  bad_cast);
395 }
396 
397 }
398