Line data Source code
1 : /* TLD library -- TLD, domain name, and sub-domain extraction
2 : * Copyright (c) 2011-2022 Made to Order Software Corp. All Rights Reserved
3 : *
4 : * Permission is hereby granted, free of charge, to any person obtaining a
5 : * copy of this software and associated documentation files (the
6 : * "Software"), to deal in the Software without restriction, including
7 : * without limitation the rights to use, copy, modify, merge, publish,
8 : * distribute, sublicense, and/or sell copies of the Software, and to
9 : * permit persons to whom the Software is furnished to do so, subject to
10 : * the following conditions:
11 : *
12 : * The above copyright notice and this permission notice shall be included
13 : * in all copies or substantial portions of the Software.
14 : *
15 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16 : * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 : * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18 : * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19 : * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20 : * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21 : * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 : */
23 :
24 : /** \file
25 : * \brief Implementation of the TLD parser library.
26 : *
27 : * This file includes all the functions available in the C library
28 : * of libtld that pertain to the parsing of URIs and extraction of
29 : * TLDs.
30 : */
31 :
32 : // self
33 : //
34 : #include "libtld/tld_compiler.h"
35 : #include "libtld/tld_file.h"
36 :
37 :
38 : // C++ lib
39 : //
40 : #include <algorithm>
41 : #include <fstream>
42 : #include <iostream>
43 : #include <sstream>
44 : #include <iomanip>
45 :
46 :
47 : // C lib
48 : //
49 : #include <dirent.h>
50 : #include <string.h>
51 : #include <sys/stat.h>
52 :
53 :
54 :
55 :
56 :
57 7346 : tld_string::tld_string(string_id_t id, std::string const & s)
58 : : f_id(id)
59 7346 : , f_string(s)
60 : {
61 7346 : }
62 :
63 :
64 54282 : string_id_t tld_string::get_id() const
65 : {
66 54282 : return f_id;
67 : }
68 :
69 :
70 96133884 : std::string const & tld_string::get_string() const
71 : {
72 96133884 : return f_string;
73 : }
74 :
75 :
76 79690674 : std::string::size_type tld_string::length() const
77 : {
78 79690674 : return f_string.length();
79 : }
80 :
81 :
82 1720 : void tld_string::set_found_in(string_id_t id)
83 : {
84 1720 : f_found_in = id;
85 1720 : }
86 :
87 :
88 88687408 : string_id_t tld_string::get_found_in() const
89 : {
90 88687408 : return f_found_in;
91 : }
92 :
93 :
94 :
95 :
96 :
97 :
98 :
99 :
100 :
101 :
102 61628 : string_id_t tld_string_manager::add_string(std::string const & s)
103 : {
104 61628 : string_id_t id(find_string(s));
105 :
106 61628 : if(id == STRING_ID_NULL)
107 : {
108 7346 : id = ++f_next_id;
109 14692 : tld_string::pointer_t str(std::make_shared<tld_string>(id, s));
110 7346 : f_strings_by_string[s] = str;
111 7346 : f_strings_by_id[id] = str;
112 :
113 7346 : f_total_length += s.length();
114 7346 : if(s.length() > f_max_length)
115 : {
116 7 : f_max_length = s.length();
117 : }
118 : }
119 :
120 61628 : return id;
121 : }
122 :
123 :
124 61628 : string_id_t tld_string_manager::find_string(std::string const & s)
125 : {
126 61628 : auto it(f_strings_by_string.find(s));
127 61628 : if(it == f_strings_by_string.end())
128 : {
129 7346 : return STRING_ID_NULL;
130 : }
131 :
132 54282 : return it->second->get_id();
133 : }
134 :
135 :
136 357945 : std::string tld_string_manager::get_string(string_id_t id) const
137 : {
138 357945 : auto it(f_strings_by_id.find(id));
139 357945 : if(it == f_strings_by_id.end())
140 : {
141 0 : return std::string();
142 : }
143 357945 : return it->second->get_string();
144 : }
145 :
146 :
147 0 : string_id_t tld_string_manager::get_next_string_id() const
148 : {
149 0 : return f_next_id;
150 : }
151 :
152 :
153 2 : std::size_t tld_string_manager::size() const
154 : {
155 2 : return f_strings_by_id.size();
156 : }
157 :
158 :
159 1 : std::size_t tld_string_manager::max_length() const
160 : {
161 1 : return f_max_length;
162 : }
163 :
164 :
165 1 : std::size_t tld_string_manager::total_length() const
166 : {
167 1 : return f_total_length;
168 : }
169 :
170 :
171 1 : std::string const & tld_string_manager::compressed_strings() const
172 : {
173 1 : return f_merged_strings;
174 : }
175 :
176 :
177 2 : std::size_t tld_string_manager::compressed_length() const
178 : {
179 2 : return f_merged_strings.length();
180 : }
181 :
182 :
183 32238730 : std::string::size_type tld_string_manager::end_start_match(std::string const & s1, std::string const & s2)
184 : {
185 32238730 : char const *c1(s1.c_str() + s1.length());
186 32238730 : char const *c2(s2.c_str());
187 209038933 : for(std::string::size_type max(std::min(s1.length(), s2.length()) - 1);
188 209038933 : max > 0;
189 : --max)
190 : {
191 178169055 : if(strncmp(c1 - max, c2, max) == 0)
192 : {
193 1368852 : return max;
194 : }
195 : }
196 30869878 : return 0;
197 : }
198 :
199 :
200 1 : void tld_string_manager::merge_strings()
201 : {
202 : // we want to save all the strings as P-strings (a.k.a. "Pascal" strings)
203 : // with the size of the string inside our table; as a result, this means
204 : // all our strings can be merged in one superstring (i.e. no '\0' at all)
205 : //
206 : // (i.e. the implementation of the tld library makes use of a length in
207 : // various places, so having the length pre-computed allows us to avoid
208 : // an strlen() call each time we need it)
209 :
210 : // first we check for strings fully included in another string; those
211 : // do not need any special handling so we eliminate them first
212 : //
213 : //std::cout << "info: find included strings" << std::endl;
214 7312 : for(auto & s1 : f_strings_by_id)
215 : {
216 46790097 : for(auto & s2 : f_strings_by_id)
217 : {
218 93568872 : if(s1.first != s2.first
219 46777846 : && s2.second->get_found_in() == STRING_ID_NULL
220 39844512 : && s1.second->length() > s2.second->length()
221 62427172 : && s1.second->get_string().find(s2.second->get_string()) != std::string::npos)
222 : {
223 1650 : s2.second->set_found_in(s1.first);
224 1650 : ++f_included_count;
225 1650 : f_included_length += s2.second->length();
226 1650 : break;
227 : }
228 : }
229 : }
230 :
231 : // at this time I implemented a simplified superstring implementation;
232 : // I just look for the longest merge between two strings and use that
233 : // then move on to the next string; it's probably 50% correct already
234 : //
235 : // note: at the time I tested this one, I saved just under 2Kb so I
236 : // don't want to sweat it too much either, that said, with all the
237 : // compression, we save 2/3rd of the space (at the moment, a little
238 : // under 50Kb final instead of over 150Kb without any compression)
239 : //
240 : //std::cout << "info: find mergeable strings" << std::endl;
241 36 : while(merge_two_strings()); // TODO: This is dead slow...
242 :
243 : // now we have all the strings merged (or not if not possible)
244 : // create one big resulting string of the result
245 : //
246 : //std::cout << "info: generate final super-string" << std::endl;
247 7347 : for(auto s : f_strings_by_id)
248 : {
249 7346 : if(s.second->get_found_in() == STRING_ID_NULL)
250 : {
251 5626 : f_merged_strings += s.second->get_string();
252 : }
253 : }
254 : //std::cout << "final super-string: ["
255 : // << f_merged_strings
256 : // << "] length="
257 : // << f_merged_strings.length()
258 : // << std::endl;
259 1 : }
260 :
261 :
262 36 : bool tld_string_manager::merge_two_strings()
263 : {
264 36 : string_id_t id1(STRING_ID_NULL);
265 36 : string_id_t id2(STRING_ID_NULL);
266 36 : std::string::size_type best(0);
267 263862 : for(auto & s1 : f_strings_by_id)
268 : {
269 588312 : if(s1.second->get_found_in() == STRING_ID_NULL
270 730818 : && f_strings_reviewed.find(s1.first) == f_strings_reviewed.end())
271 : {
272 41649782 : for(auto & s2 : f_strings_by_id)
273 : {
274 83288172 : if(s1.first != s2.first
275 41644086 : && s2.second->get_found_in() == STRING_ID_NULL)
276 : {
277 32238730 : std::string const & str1(s1.second->get_string());
278 32238730 : std::string const & str2(s2.second->get_string());
279 32238730 : std::string::size_type const d(end_start_match(str1, str2));
280 :
281 32238730 : if(d > best)
282 : {
283 100 : best = d;
284 100 : id1 = s1.first;
285 100 : id2 = s2.first;
286 : }
287 : }
288 : }
289 5696 : f_strings_reviewed.insert(s1.first);
290 : }
291 : }
292 :
293 36 : if(best > 0)
294 : {
295 35 : std::string const & str1(f_strings_by_id[id1]->get_string());
296 35 : std::string const & str2(f_strings_by_id[id2]->get_string());
297 :
298 70 : std::string const merged(str1 + str2.substr(best));
299 : #if 0
300 : std::cout << "\n"
301 : << "Found " << best
302 : << ": [" << str1
303 : << "] vs [" << str2
304 : << "] -> [" << merged
305 : << "]" << std::endl;
306 : #endif
307 :
308 35 : string_id_t merged_id(add_string(merged));
309 :
310 35 : f_strings_by_id[id1]->set_found_in(merged_id);
311 35 : f_strings_by_id[id2]->set_found_in(merged_id);
312 :
313 35 : ++f_merged_count;
314 35 : f_merged_length += best;
315 35 : return true;
316 : }
317 :
318 : // no merge happened
319 : //
320 1 : return false;
321 : }
322 :
323 :
324 1 : std::size_t tld_string_manager::included_count() const
325 : {
326 1 : return f_included_count;
327 : }
328 :
329 :
330 1 : std::size_t tld_string_manager::included_length() const
331 : {
332 1 : return f_included_length;
333 : }
334 :
335 :
336 1 : std::size_t tld_string_manager::merged_count() const
337 : {
338 1 : return f_merged_count;
339 : }
340 :
341 :
342 1 : std::size_t tld_string_manager::merged_length() const
343 : {
344 1 : return f_merged_length;
345 : }
346 :
347 :
348 7311 : std::size_t tld_string_manager::get_string_offset(std::string const & s) const
349 : {
350 7311 : return f_merged_strings.find(s);
351 : }
352 :
353 :
354 7311 : std::size_t tld_string_manager::get_string_offset(string_id_t id) const
355 : {
356 7311 : auto it(f_strings_by_id.find(id));
357 7311 : if(it == f_strings_by_id.end())
358 : {
359 0 : return std::string::npos;
360 : }
361 :
362 7311 : return get_string_offset(it->second->get_string());
363 : }
364 :
365 :
366 :
367 :
368 :
369 :
370 :
371 :
372 10464 : void tld_tag_manager::add(tags_t const & tags)
373 : {
374 : // transform the tags in an array as we will save in the output
375 : //
376 10910 : tags_table_t const table(tags_to_table(tags));
377 :
378 : // if another description has the exact same tags, do not duplicate
379 : //
380 2345509 : for(auto const & it : f_tags)
381 : {
382 2345063 : if(it == table)
383 : {
384 10018 : return;
385 : }
386 : }
387 :
388 : // save the result in the vector if not found
389 : //
390 446 : f_tags.push_back(table);
391 : }
392 :
393 :
394 1 : void tld_tag_manager::merge()
395 : {
396 2 : std::set<int> processed_tags;
397 2 : std::set<int> processed_intermediates;
398 2 : std::set<int> unhandled_tags;
399 2 : std::set<int> unhandled_intermediates;
400 2 : tags_vector_t intermediate_tags;
401 :
402 447 : for(auto t1(f_tags.begin()); t1 != f_tags.end(); ++t1)
403 : {
404 446 : processed_tags.insert(std::distance(f_tags.begin(), t1));
405 :
406 446 : auto best_match(f_tags.end());
407 446 : auto best_intermediate_match(intermediate_tags.end());
408 446 : std::size_t best(0);
409 446 : std::size_t best_swapped(0);
410 :
411 : // check against other unmerged tags
412 : //
413 199362 : for(auto t2(f_tags.begin()); t2 != f_tags.end(); ++t2)
414 : {
415 198916 : if(processed_tags.find(std::distance(f_tags.begin(), t2)) != processed_tags.end())
416 : {
417 : // this was already used up, ignore
418 99862 : continue;
419 : }
420 :
421 99054 : std::size_t const d1(end_start_match(*t1, *t2));
422 99054 : std::size_t const d2(end_start_match(*t2, *t1));
423 99054 : if(d2 > d1)
424 : {
425 1 : if(d2 > best_swapped)
426 : {
427 1 : best_swapped = d2;
428 1 : best_match = t2;
429 : }
430 : }
431 : else
432 : {
433 99053 : if(d1 > best)
434 : {
435 0 : best = d1;
436 0 : best_match = t2;
437 : }
438 : }
439 : }
440 :
441 : // check against already merged tags
442 : //
443 882 : for(auto ti(intermediate_tags.begin()); ti != intermediate_tags.end(); ++ti)
444 : {
445 436 : if(processed_intermediates.find(std::distance(intermediate_tags.begin(), ti)) != processed_intermediates.end())
446 : {
447 : // TBD: I may just want to remove those used up intermediates
448 : // and I think I don't need this test at all
449 0 : continue;
450 : }
451 :
452 436 : std::size_t const d1(end_start_match(*t1, *ti));
453 436 : std::size_t const d2(end_start_match(*ti, *t1));
454 436 : if(d2 > d1)
455 : {
456 0 : if(d2 > best_swapped)
457 : {
458 0 : best_swapped = d2;
459 0 : best_intermediate_match = ti;
460 : }
461 : }
462 : else
463 : {
464 436 : if(d1 > best)
465 : {
466 0 : best = d1;
467 0 : best_intermediate_match = ti;
468 : }
469 : }
470 : }
471 :
472 446 : if(best_intermediate_match != intermediate_tags.end())
473 : {
474 0 : if(best_swapped > best)
475 : {
476 0 : tags_table_t merged(*best_intermediate_match);
477 0 : merged.insert(
478 0 : merged.end()
479 0 : , t1->begin() + best_swapped
480 0 : , t1->end());
481 0 : intermediate_tags.push_back(merged);
482 : }
483 : else
484 : {
485 0 : tags_table_t merged(*t1);
486 0 : merged.insert(
487 0 : merged.end()
488 0 : , best_intermediate_match->begin() + best
489 0 : , best_intermediate_match->end());
490 0 : intermediate_tags.push_back(merged);
491 : }
492 :
493 0 : processed_intermediates.insert(std::distance(intermediate_tags.begin(), best_intermediate_match));
494 : }
495 446 : else if(best_match != f_tags.end())
496 : {
497 : // we found a best match meaning that we can merged t1 & t2 a bit
498 : //
499 1 : if(best_swapped > best)
500 : {
501 2 : tags_table_t merged(*best_match);
502 3 : merged.insert(
503 2 : merged.end()
504 2 : , t1->begin() + best_swapped
505 5 : , t1->end());
506 1 : intermediate_tags.push_back(merged);
507 : }
508 : else
509 : {
510 0 : tags_table_t merged(*t1);
511 0 : merged.insert(
512 0 : merged.end()
513 0 : , best_match->begin() + best
514 0 : , best_match->end());
515 0 : intermediate_tags.push_back(merged);
516 : }
517 :
518 1 : processed_tags.insert(std::distance(f_tags.begin(), best_match));
519 : }
520 : else
521 : {
522 : // no merging possible, keep item as is for final
523 : //
524 445 : unhandled_tags.insert(std::distance(f_tags.begin(), t1));
525 : }
526 : }
527 :
528 : // repeat with the intermediate (which is unlikely to generate much
529 : // more merging, but we never know...)
530 : //
531 1 : bool repeat(false);
532 1 : do
533 : {
534 1 : repeat = false;
535 :
536 2 : for(std::size_t i1(0); i1 < intermediate_tags.size(); ++i1)
537 : {
538 1 : if(processed_intermediates.find(i1) != processed_intermediates.end())
539 : {
540 0 : continue;
541 : }
542 :
543 1 : processed_intermediates.insert(i1);
544 :
545 1 : std::size_t best_intermediate_match(static_cast<std::size_t>(-1));
546 1 : std::size_t best(0);
547 1 : std::size_t best_swapped(0);
548 :
549 : // check against other unmerged tags
550 : //
551 2 : for(std::size_t i2(0); i2 < intermediate_tags.size(); ++i2)
552 : {
553 1 : if(processed_intermediates.find(i2) != processed_intermediates.end())
554 : {
555 : // this was already used up, ignore
556 1 : continue;
557 : }
558 :
559 0 : std::size_t const d1(end_start_match(intermediate_tags[i1], intermediate_tags[i2]));
560 0 : std::size_t const d2(end_start_match(intermediate_tags[i2], intermediate_tags[i1]));
561 0 : if(d2 > d1
562 0 : && d2 > best_swapped)
563 : {
564 0 : best_swapped = d2;
565 0 : best_intermediate_match = i2;
566 : }
567 0 : else if(d1 > best)
568 : {
569 0 : best = d1;
570 0 : best_intermediate_match = i2;
571 : }
572 : }
573 :
574 1 : if(best_intermediate_match != static_cast<std::size_t>(-1))
575 : {
576 0 : repeat = true;
577 :
578 0 : if(best_swapped > best)
579 : {
580 0 : tags_table_t merged(intermediate_tags[best_intermediate_match]);
581 0 : merged.insert(
582 0 : merged.end()
583 0 : , intermediate_tags[i1].begin() + best_swapped
584 0 : , intermediate_tags[i1].end());
585 0 : intermediate_tags.push_back(merged);
586 : }
587 : else
588 : {
589 0 : tags_table_t merged(intermediate_tags[i1]);
590 0 : merged.insert(
591 0 : merged.end()
592 0 : , intermediate_tags[best_intermediate_match].begin() + best
593 0 : , intermediate_tags[best_intermediate_match].end());
594 0 : intermediate_tags.push_back(merged);
595 : }
596 : }
597 : else
598 : {
599 : // no merging possible, keep item as is for now
600 : //
601 1 : unhandled_intermediates.insert(i1);
602 : }
603 : }
604 : }
605 : while(repeat);
606 :
607 : // once done merging, we end up with a set of tables which we can
608 : // merge all together and any tag table can then be found in this
609 : // final super-table
610 : //
611 446 : for(auto const & idx : unhandled_tags)
612 : {
613 890 : f_merged_tags.insert(
614 890 : f_merged_tags.end()
615 445 : , f_tags[idx].begin()
616 2225 : , f_tags[idx].end());
617 : }
618 :
619 2 : for(auto const & idx : unhandled_intermediates)
620 : {
621 2 : f_merged_tags.insert(
622 2 : f_merged_tags.end()
623 1 : , intermediate_tags[idx].begin()
624 5 : , intermediate_tags[idx].end());
625 : }
626 1 : }
627 :
628 :
629 2 : tld_tag_manager::tags_table_t const & tld_tag_manager::merged_tags() const
630 : {
631 2 : return f_merged_tags;
632 : }
633 :
634 :
635 0 : std::size_t tld_tag_manager::merged_size() const
636 : {
637 0 : return f_merged_tags.size();
638 : }
639 :
640 :
641 10464 : std::size_t tld_tag_manager::get_tag_offset(tags_t const & tags) const
642 : {
643 20928 : tags_table_t const table(tags_to_table(tags));
644 10464 : auto it(std::search(
645 : f_merged_tags.begin(), f_merged_tags.end(),
646 10464 : table.begin(), table.end()));
647 10464 : if(it == f_merged_tags.end())
648 : {
649 0 : throw std::logic_error("tags not found in the list of merged tags.");
650 : }
651 :
652 20928 : return std::distance(f_merged_tags.begin(), it);
653 : }
654 :
655 :
656 20928 : tld_tag_manager::tags_table_t tld_tag_manager::tags_to_table(tags_t const & tags) const
657 : {
658 20928 : tld_tag_manager::tags_table_t table;
659 59822 : for(auto const & t : tags)
660 : {
661 38894 : table.push_back(t.first);
662 38894 : table.push_back(t.second);
663 : }
664 20928 : return table;
665 : }
666 :
667 :
668 198980 : std::size_t tld_tag_manager::end_start_match(tags_table_t const & tag1, tags_table_t const & tag2)
669 : {
670 804797 : for(std::string::size_type max(std::min(tag1.size(), tag2.size()) - 1);
671 804797 : max > 0;
672 : --max)
673 : {
674 605818 : if(std::equal(tag1.end() - max, tag1.end(), tag2.begin()))
675 : {
676 1 : return max;
677 : }
678 : }
679 :
680 198979 : return 0;
681 : }
682 :
683 :
684 :
685 :
686 :
687 :
688 :
689 :
690 :
691 :
692 :
693 :
694 :
695 :
696 :
697 10464 : tld_definition::tld_definition(tld_string_manager & strings)
698 10464 : : f_strings(strings)
699 : {
700 10464 : }
701 :
702 :
703 22693 : bool tld_definition::add_segment(
704 : std::string const & segment
705 : , std::string & errmsg)
706 : {
707 22693 : if((f_set & SET_TLD) != 0)
708 : {
709 0 : errmsg = "the TLD cannot be edited anymore (cannot add \""
710 0 : + segment
711 0 : + "\" to \""
712 0 : + get_name()
713 0 : + "\").";
714 0 : return false;
715 : }
716 : // f_set |= SET_TLD; -- reset_set_flags() sets this one
717 :
718 22693 : if(segment.empty())
719 : {
720 0 : errmsg = "a TLD segment cannot be an empty string.";
721 0 : return false;
722 : }
723 :
724 45386 : if(segment.front() == '-'
725 22693 : || segment.back() == '-')
726 : {
727 0 : errmsg = "a TLD segment (\""
728 0 : + segment
729 0 : + "\") cannot start or end with a dash ('-').";
730 0 : return false;
731 : }
732 :
733 45386 : std::string normalized_segment;
734 123842 : for(auto const & c : segment)
735 : {
736 101149 : switch(c)
737 : {
738 85 : case '*':
739 85 : if(segment.length() != 1)
740 : {
741 0 : errmsg = "a TLD segment (\""
742 0 : + segment
743 0 : + "\") cannot include an asterisk character ('*')."
744 : " However, the whole segment may be \"*\".";
745 0 : return false;
746 : }
747 : [[fallthrough]];
748 : case '-':
749 : case '0':
750 : case '1':
751 : case '2':
752 : case '3':
753 : case '4':
754 : case '5':
755 : case '6':
756 : case '7':
757 : case '8':
758 : case '9':
759 1768 : normalized_segment += c;
760 1768 : break;
761 :
762 99381 : default:
763 99381 : if((c >= 'a' && c <= 'z')
764 2854 : || (c >= 'A' && c <= 'Z'))
765 : {
766 96527 : normalized_segment += c;
767 : }
768 2854 : else if(static_cast<unsigned char>(c) < 0x80)
769 : {
770 0 : if(static_cast<unsigned char>(c) < 0x20)
771 : {
772 0 : errmsg = "this TLD segment: \""
773 0 : + segment
774 0 : + "\" includes control character: '^"
775 0 : + static_cast<char>(c + '@')
776 0 : + "'.";
777 : }
778 0 : else if(c == 0x7F)
779 : {
780 0 : errmsg = "this TLD segment: \""
781 0 : + segment
782 0 : + "\" includes the delete character (0x7F).";
783 : }
784 0 : else if(static_cast<unsigned char>(c) >= 0x80 && static_cast<unsigned char>(c) < 0xA0)
785 : {
786 0 : errmsg = "this TLD segment: \""
787 0 : + segment
788 0 : + "\" includes graphic control character: '@"
789 0 : + static_cast<char>(c - '@')
790 0 : + "'.";
791 : }
792 : else
793 : {
794 0 : errmsg = "this TLD segment: \""
795 0 : + segment
796 0 : + "\" includes unsupported character: '"
797 0 : + c
798 0 : + "'.";
799 : }
800 0 : return false;
801 : }
802 : else
803 : {
804 : // transform anything else in a %XX notation which is what
805 : // is expected in a TLD reaching a server
806 : //
807 5708 : std::stringstream ss;
808 2854 : ss << '%'
809 2854 : << std::hex
810 : << std::setw(2)
811 2854 : << std::setfill('0')
812 2854 : << static_cast<int>(static_cast<unsigned char>(c));
813 2854 : normalized_segment += ss.str();
814 : }
815 99381 : break;
816 :
817 : }
818 : }
819 :
820 22693 : f_tld.push_back(f_strings.add_string(normalized_segment));
821 :
822 22693 : return true;
823 : }
824 :
825 :
826 156959 : tld_definition::segments_t const & tld_definition::get_segments() const
827 : {
828 156959 : return f_tld;
829 : }
830 :
831 :
832 : /** \brief The domain name with periods separating each segment.
833 : *
834 : * This function rebuilds the full domain name. The idea is to have a way
835 : * to write error messages about various errors including the domain name.
836 : *
837 : * \return The name of the domain with each segment separated by periods.
838 : */
839 102057 : std::string tld_definition::get_name() const
840 : {
841 102057 : std::string name;
842 :
843 319053 : for(auto const & segment : f_tld)
844 : {
845 433992 : std::string const s(f_strings.get_string(segment));
846 216996 : if(s.empty())
847 : {
848 0 : throw std::logic_error("a segment string is not defined");
849 : }
850 216996 : name += '.';
851 216996 : name += s;
852 : }
853 :
854 102057 : return name;
855 : }
856 :
857 :
858 : /** \brief Get the full TLD as a reversed domain name.
859 : *
860 : * This function re-assembles the domain segments in a full name in reverse
861 : * order. This is used to properly sort sub-domain names (still part of the
862 : * TLD) by their parent domain name.
863 : *
864 : * We use '!' as the separate instead of the '.' because some domain names
865 : * have a dash in their as the order still needs to be correct and '.' > '-'
866 : * when we need the opposite, but '!' < '-'.
867 : *
868 : * \return The concatenated domain name in reverse order with '!' as separators.
869 : */
870 10464 : std::string tld_definition::get_inverted_name() const
871 : {
872 10464 : std::string name;
873 :
874 33157 : for(auto it(f_tld.rbegin()); it != f_tld.rend(); ++it)
875 : {
876 45386 : std::string const s(f_strings.get_string(*it));
877 22693 : if(s.empty())
878 : {
879 0 : throw std::logic_error("a segment string is not defined");
880 : }
881 22693 : name += '!';
882 22693 : name += s;
883 : }
884 :
885 10464 : return name;
886 : }
887 :
888 :
889 0 : std::string tld_definition::get_parent_name() const
890 : {
891 0 : std::string name;
892 :
893 0 : bool skip_first(true);
894 0 : for(auto const & segment : f_tld)
895 : {
896 0 : std::string const s(f_strings.get_string(segment));
897 0 : if(s.empty())
898 : {
899 0 : throw std::logic_error("a segment string is not defined");
900 : }
901 0 : if(skip_first)
902 : {
903 0 : skip_first = false;
904 : }
905 : else
906 : {
907 0 : name += '.';
908 0 : name += s;
909 : }
910 : }
911 :
912 0 : return name;
913 : }
914 :
915 :
916 10464 : std::string tld_definition::get_parent_inverted_name() const
917 : {
918 10464 : std::string name;
919 :
920 22693 : for(std::size_t idx(f_tld.size() - 1); idx > 0; --idx)
921 : {
922 24458 : std::string const s(f_strings.get_string(f_tld[idx]));
923 12229 : if(s.empty())
924 : {
925 0 : throw std::logic_error("a segment string is not defined");
926 : }
927 12229 : name += '!';
928 12229 : name += s;
929 : }
930 :
931 10464 : return name;
932 : }
933 :
934 :
935 10464 : void tld_definition::set_index(int idx)
936 : {
937 10464 : f_index = idx;
938 10464 : }
939 :
940 :
941 109505781 : int tld_definition::get_index() const
942 : {
943 109505781 : return f_index;
944 : }
945 :
946 :
947 547 : bool tld_definition::set_status(tld_status status)
948 : {
949 547 : if((f_set & SET_STATUS) != 0)
950 : {
951 0 : return false;
952 : }
953 547 : f_set |= SET_STATUS;
954 :
955 547 : f_status = status;
956 :
957 547 : return true;
958 : }
959 :
960 :
961 31371 : tld_status tld_definition::get_status() const
962 : {
963 31371 : return f_status;
964 : }
965 :
966 :
967 21 : bool tld_definition::set_apply_to(std::string const & apply_to)
968 : {
969 21 : if((f_set & SET_APPLY_TO) != 0)
970 : {
971 0 : return false;
972 : }
973 21 : f_set |= SET_APPLY_TO;
974 :
975 21 : if(!apply_to.empty())
976 : {
977 21 : if(apply_to[0] == '.')
978 : {
979 : // remove the introductory period if present
980 : //
981 11 : f_apply_to = apply_to.substr(1);
982 11 : return true;
983 : }
984 : }
985 10 : f_apply_to = apply_to;
986 :
987 10 : return true;
988 : }
989 :
990 :
991 41898 : std::string tld_definition::get_apply_to() const
992 : {
993 41898 : return f_apply_to;
994 : }
995 :
996 :
997 19449 : void tld_definition::add_tag(
998 : std::string const & tag_name
999 : , std::string const & value
1000 : , std::string & errmsg)
1001 : {
1002 19449 : if(tag_name.empty())
1003 : {
1004 0 : errmsg = "tag name cannot be empty.";
1005 0 : return;
1006 : }
1007 :
1008 19449 : f_tags[f_strings.add_string(tag_name)] = f_strings.add_string(value);
1009 : }
1010 :
1011 :
1012 62784 : tags_t const & tld_definition::get_tags() const
1013 : {
1014 62784 : return f_tags;
1015 : }
1016 :
1017 :
1018 10464 : void tld_definition::reset_set_flags()
1019 : {
1020 10464 : f_set = SET_TLD;
1021 10464 : }
1022 :
1023 :
1024 568 : void tld_definition::set_named_parameter(
1025 : std::string const & name
1026 : , std::string const & value
1027 : , std::string & errmsg)
1028 : {
1029 568 : if(!name.empty())
1030 : {
1031 568 : switch(name[0])
1032 : {
1033 21 : case 'a':
1034 21 : if(name == "apply_to")
1035 : {
1036 21 : if(!set_apply_to(value))
1037 : {
1038 0 : errmsg = "\"apply_to\" defined a second time (\"" + value + "\").";
1039 : }
1040 21 : return;
1041 : }
1042 0 : break;
1043 :
1044 547 : case 's':
1045 547 : if(name == "status")
1046 : {
1047 547 : if(!value.empty())
1048 : {
1049 547 : tld_status status(TLD_STATUS_UNDEFINED);
1050 547 : switch(value[0])
1051 : {
1052 186 : case 'd':
1053 186 : if(value == "deprecated")
1054 : {
1055 186 : status = TLD_STATUS_DEPRECATED;
1056 : }
1057 186 : break;
1058 :
1059 22 : case 'e':
1060 22 : if(value == "example")
1061 : {
1062 1 : status = TLD_STATUS_EXAMPLE;
1063 : }
1064 21 : else if(value == "exception")
1065 : {
1066 21 : status = TLD_STATUS_EXCEPTION;
1067 : }
1068 22 : break;
1069 :
1070 8 : case 'i':
1071 8 : if(value == "infrastructure")
1072 : {
1073 8 : status = TLD_STATUS_INFRASTRUCTURE;
1074 : }
1075 8 : break;
1076 :
1077 12 : case 'p':
1078 12 : if(value == "proposed")
1079 : {
1080 12 : status = TLD_STATUS_PROPOSED;
1081 : }
1082 12 : break;
1083 :
1084 16 : case 'r':
1085 16 : if(value == "reserved")
1086 : {
1087 16 : status = TLD_STATUS_RESERVED;
1088 : }
1089 16 : break;
1090 :
1091 0 : case 'v':
1092 0 : if(value == "valid")
1093 : {
1094 0 : status = TLD_STATUS_VALID;
1095 : }
1096 0 : break;
1097 :
1098 303 : case 'u':
1099 303 : if(value == "unused")
1100 : {
1101 303 : status = TLD_STATUS_UNUSED;
1102 : }
1103 303 : break;
1104 :
1105 : }
1106 547 : if(status != TLD_STATUS_UNDEFINED)
1107 : {
1108 547 : if(!set_status(status))
1109 : {
1110 0 : errmsg = "\"status\" defined a second time (\"" + value + "\").";
1111 : }
1112 547 : return;
1113 : }
1114 : }
1115 0 : errmsg = "unknown \"status\": \"" + value + "\".";
1116 0 : return;
1117 0 : }
1118 0 : break;
1119 :
1120 : }
1121 : }
1122 :
1123 0 : errmsg = "unknown variable name \"" + name + "\".";
1124 : }
1125 :
1126 :
1127 8842 : void tld_definition::set_start_offset(uint16_t start)
1128 : {
1129 8842 : if(f_start_offset == USHRT_MAX)
1130 : {
1131 795 : f_start_offset = start;
1132 : }
1133 8842 : }
1134 :
1135 :
1136 8842 : void tld_definition::set_end_offset(uint16_t end)
1137 : {
1138 8842 : f_end_offset = end;
1139 8842 : }
1140 :
1141 :
1142 32982 : uint16_t tld_definition::get_start_offset() const
1143 : {
1144 32982 : return f_start_offset;
1145 : }
1146 :
1147 :
1148 12054 : uint16_t tld_definition::get_end_offset() const
1149 : {
1150 12054 : return f_end_offset;
1151 : }
1152 :
1153 :
1154 :
1155 :
1156 :
1157 :
1158 :
1159 :
1160 :
1161 :
1162 :
1163 :
1164 :
1165 76577 : tld_compiler::token::token(
1166 : std::string const & filename
1167 : , int line
1168 : , token_t tok
1169 76577 : , std::string const & value)
1170 : : f_filename(filename)
1171 : , f_line(line)
1172 : , f_token(tok)
1173 76577 : , f_value(value)
1174 : {
1175 76577 : }
1176 :
1177 :
1178 0 : std::string const & tld_compiler::token::get_filename() const
1179 : {
1180 0 : return f_filename;
1181 : }
1182 :
1183 :
1184 8 : tld_string_manager & tld_compiler::get_string_manager()
1185 : {
1186 8 : return f_strings;
1187 : }
1188 :
1189 :
1190 :
1191 0 : int tld_compiler::token::get_line() const
1192 : {
1193 0 : return f_line;
1194 : }
1195 :
1196 :
1197 96141 : tld_compiler::token_t tld_compiler::token::get_token() const
1198 : {
1199 96141 : return f_token;
1200 : }
1201 :
1202 :
1203 29550 : std::string const & tld_compiler::token::get_value() const
1204 : {
1205 29550 : return f_value;
1206 : }
1207 :
1208 :
1209 :
1210 :
1211 :
1212 :
1213 :
1214 :
1215 :
1216 1 : void tld_compiler::set_input_folder(std::string const & path)
1217 : {
1218 1 : f_input_folder = path;
1219 1 : }
1220 :
1221 :
1222 0 : std::string const & tld_compiler::get_input_folder() const
1223 : {
1224 0 : return f_input_folder;
1225 : }
1226 :
1227 :
1228 1 : void tld_compiler::set_output(std::string const & output)
1229 : {
1230 1 : f_output = output;
1231 1 : }
1232 :
1233 :
1234 0 : std::string const & tld_compiler::get_output() const
1235 : {
1236 0 : return f_output;
1237 : }
1238 :
1239 :
1240 1 : void tld_compiler::set_c_file(std::string const & filename)
1241 : {
1242 1 : f_c_file = filename;
1243 1 : }
1244 :
1245 :
1246 0 : std::string const & tld_compiler::get_c_file() const
1247 : {
1248 0 : return f_c_file;
1249 : }
1250 :
1251 :
1252 1 : bool tld_compiler::compile()
1253 : {
1254 1 : find_files(f_input_folder);
1255 1 : if(get_errno() != 0)
1256 : {
1257 0 : return false;
1258 : }
1259 :
1260 1 : process_input_files();
1261 1 : if(get_errno() != 0)
1262 : {
1263 0 : return false;
1264 : }
1265 :
1266 1 : define_default_category();
1267 1 : if(get_errno() != 0)
1268 : {
1269 0 : return false;
1270 : }
1271 :
1272 : // the merge feature is going to add merged strings to the table which
1273 : // are not going to be found in the description tables, so here we want
1274 : // to save the total number of strings prior to the merge process
1275 : //
1276 1 : f_strings_count = static_cast<string_id_t>(f_strings.size());
1277 :
1278 1 : f_strings.merge_strings();
1279 :
1280 1 : compress_tags();
1281 :
1282 1 : find_max_level();
1283 :
1284 2 : std::stringstream out;
1285 1 : output_tlds(out);
1286 1 : if(get_errno() != 0)
1287 : {
1288 0 : return false;
1289 : }
1290 :
1291 1 : save_to_file(out.str()); // save to tlds.tld (RIFF/TLDS format)
1292 1 : if(get_errno() != 0)
1293 : {
1294 0 : return false;
1295 : }
1296 :
1297 1 : save_to_c_file(out.str());
1298 1 : if(get_errno() != 0)
1299 : {
1300 0 : return false;
1301 : }
1302 :
1303 1 : return true;
1304 : }
1305 :
1306 :
1307 15292 : int tld_compiler::get_errno() const
1308 : {
1309 15292 : return f_errno;
1310 : }
1311 :
1312 :
1313 0 : std::string const & tld_compiler::get_errmsg() const
1314 : {
1315 0 : return f_errmsg;
1316 : }
1317 :
1318 :
1319 0 : int tld_compiler::get_line() const
1320 : {
1321 0 : return f_line;
1322 : }
1323 :
1324 :
1325 0 : std::string const & tld_compiler::get_filename() const
1326 : {
1327 0 : return f_filename;
1328 : }
1329 :
1330 :
1331 4 : void tld_compiler::find_files(std::string const & path)
1332 : {
1333 4 : DIR * d = opendir(path.c_str());
1334 4 : if(d == nullptr)
1335 : {
1336 0 : f_errno = errno;
1337 0 : f_errmsg = "could not open directory \"" + path + "\".";
1338 0 : return;
1339 : }
1340 : // TODO: add `d` to a smart pointer
1341 :
1342 : for(;;)
1343 : {
1344 282 : struct dirent *e(readdir(d));
1345 282 : if(e == nullptr)
1346 : {
1347 4 : break;
1348 : }
1349 :
1350 556 : std::string name(e->d_name);
1351 278 : switch(e->d_type )
1352 : {
1353 11 : case DT_DIR:
1354 11 : if(strcmp(e->d_name, ".") != 0
1355 7 : && strcmp(e->d_name, "..") != 0)
1356 : {
1357 3 : find_files(path + '/' + name);
1358 3 : if(get_errno() != 0)
1359 : {
1360 0 : break;
1361 : }
1362 : }
1363 11 : break;
1364 :
1365 267 : case DT_REG:
1366 : case DT_LNK:
1367 534 : if(name.length() > 4
1368 267 : && strcmp(name.c_str() + name.length() - 4, ".ini") == 0)
1369 : {
1370 : // collect .ini files
1371 : //
1372 266 : f_input_files.push_back(path + '/' + name);
1373 : }
1374 267 : break;
1375 :
1376 0 : default:
1377 : // ignore other file types
1378 0 : break;
1379 :
1380 : }
1381 278 : }
1382 :
1383 4 : closedir(d);
1384 : }
1385 :
1386 :
1387 1 : void tld_compiler::process_input_files()
1388 : {
1389 267 : for(auto const & filename : f_input_files)
1390 : {
1391 266 : process_file(filename);
1392 266 : if(get_errno() != 0)
1393 : {
1394 0 : return;
1395 : }
1396 : }
1397 : }
1398 :
1399 :
1400 266 : void tld_compiler::process_file(std::string const & filename)
1401 : {
1402 266 : f_global_variables.clear();
1403 266 : f_global_tags.clear();
1404 266 : f_current_tld.clear();
1405 :
1406 266 : struct stat s;
1407 266 : int r(stat(filename.c_str(), &s));
1408 266 : if(r != 0)
1409 : {
1410 0 : f_errno = errno;
1411 0 : f_errmsg = "could not get statistics about \"" + filename + "\".";
1412 0 : return;
1413 : }
1414 266 : f_data.resize(s.st_size);
1415 :
1416 : {
1417 532 : std::ifstream in(filename);
1418 266 : in.read(reinterpret_cast<char *>(f_data.data()), f_data.size());
1419 266 : if(static_cast<size_t>(in.tellg()) != f_data.size())
1420 : {
1421 0 : f_errno = errno;
1422 0 : f_errmsg = "could not read file \"" + filename + "\" in full.";
1423 0 : return;
1424 : }
1425 : }
1426 :
1427 266 : f_pos = 0;
1428 266 : f_line = 1;
1429 266 : f_filename = filename;
1430 : for(;;)
1431 : {
1432 29768 : read_line();
1433 15017 : if(get_errno() != 0)
1434 : {
1435 0 : return;
1436 : }
1437 15017 : if(f_tokens.empty())
1438 : {
1439 1232 : continue;
1440 : }
1441 27570 : if(f_tokens.size() == 1
1442 13785 : && f_tokens[0].get_token() == TOKEN_EOF)
1443 : {
1444 266 : break;
1445 : }
1446 13519 : parse_line();
1447 : }
1448 : }
1449 :
1450 :
1451 1179 : bool tld_compiler::get_backslash(char32_t & c)
1452 : {
1453 1179 : c = getc();
1454 1179 : if(c == CHAR_ERR)
1455 : {
1456 0 : return false;
1457 : }
1458 :
1459 1179 : int count(0);
1460 1179 : switch(c)
1461 : {
1462 0 : case CHAR_EOF:
1463 0 : c = '\\';
1464 0 : return true;
1465 :
1466 0 : case '\\':
1467 : case '\'':
1468 : case '"':
1469 : case ';':
1470 : case '#':
1471 : case '=':
1472 : case ':':
1473 0 : return true;
1474 :
1475 : // TODO: support octal
1476 : //
1477 0 : case '0': // null
1478 0 : c = 0x0;
1479 0 : return true;
1480 :
1481 0 : case 'a': // bell
1482 0 : c = 0x07;
1483 0 : return true;
1484 :
1485 0 : case 'b': // backspace
1486 0 : c = 0x08;
1487 0 : return true;
1488 :
1489 0 : case 't': // tab
1490 0 : c = 0x09;
1491 0 : return true;
1492 :
1493 0 : case 'f': // form feed
1494 0 : c = 0x0C;
1495 0 : return true;
1496 :
1497 0 : case 'r': // carriage return
1498 0 : c = 0x0D;
1499 0 : return true;
1500 :
1501 0 : case 'n': // line feed
1502 0 : c = 0x0A;
1503 0 : return true;
1504 :
1505 233 : case 'x':
1506 : case 'X':
1507 233 : count = 2;
1508 233 : break;
1509 :
1510 946 : case 'u':
1511 946 : count = 4;
1512 946 : break;
1513 :
1514 0 : case 'U':
1515 0 : count = 6; // in C/C++ this is 8
1516 0 : break;
1517 :
1518 : }
1519 :
1520 1179 : c = 0;
1521 5423 : for(int i(0); i < count; ++i)
1522 : {
1523 4250 : char32_t d(getc());
1524 4250 : if(d == CHAR_ERR)
1525 : {
1526 0 : f_errno = EINVAL;
1527 0 : f_errmsg = "unexpected error while reading escape Unicode character.";
1528 0 : return false;
1529 : }
1530 4250 : if(d == CHAR_EOF)
1531 : {
1532 0 : break;
1533 : }
1534 4250 : c <<= 4;
1535 4250 : if(d >= 'a' && d <= 'f')
1536 : {
1537 0 : c |= d - 'a' + 10;
1538 : }
1539 4250 : else if(d >= 'A' && d <= 'F')
1540 : {
1541 1043 : c |= d - 'A' + 10;
1542 : }
1543 3207 : else if(d >= '0' && d <= '9')
1544 : {
1545 3201 : c |= d - '0';
1546 : }
1547 : else
1548 : {
1549 6 : if(i == 0)
1550 : {
1551 0 : f_errno = EINVAL;
1552 0 : f_errmsg = "a Unicode character must include at least one hexdecimal digit.";
1553 0 : return false;
1554 : }
1555 :
1556 : // premature end is okay by us
1557 : //
1558 6 : c >>= 4; // cancel the shift
1559 6 : ungetc(d);
1560 6 : break;
1561 : }
1562 : }
1563 :
1564 1179 : return true;
1565 : }
1566 :
1567 :
1568 15017 : void tld_compiler::read_line()
1569 : {
1570 15017 : f_tokens.clear();
1571 :
1572 : for(;;)
1573 : {
1574 91922 : char32_t c(getc());
1575 91922 : switch(c)
1576 : {
1577 0 : case CHAR_ERR:
1578 0 : return;
1579 :
1580 266 : case CHAR_EOF:
1581 266 : if(f_tokens.empty())
1582 : {
1583 798 : f_tokens.emplace_back(
1584 : f_filename
1585 : , f_line
1586 : , TOKEN_EOF
1587 1064 : , std::string());
1588 : }
1589 266 : return;
1590 :
1591 0 : case '\r':
1592 0 : c = getc();
1593 0 : if(c == CHAR_ERR)
1594 : {
1595 0 : return;
1596 : }
1597 0 : if(c != '\n')
1598 : {
1599 0 : ungetc(c);
1600 : }
1601 0 : ++f_line;
1602 0 : return;
1603 :
1604 14654 : case '\n':
1605 14654 : ++f_line;
1606 14654 : return;
1607 :
1608 0 : case ';':
1609 : // "end of line" delimiter
1610 : // additional values etc. can appear after a semicolon
1611 0 : return;
1612 :
1613 3055 : case '=':
1614 6110 : f_tokens.emplace_back(
1615 : f_filename
1616 : , f_line
1617 : , TOKEN_EQUAL
1618 9165 : , "=");
1619 3055 : break;
1620 :
1621 22704 : case '.':
1622 45408 : f_tokens.emplace_back(
1623 : f_filename
1624 : , f_line
1625 : , TOKEN_DOT
1626 68112 : , ".");
1627 22704 : break;
1628 :
1629 85 : case '*':
1630 170 : f_tokens.emplace_back(
1631 : f_filename
1632 : , f_line
1633 : , TOKEN_WILD_CARD
1634 255 : , "*");
1635 85 : break;
1636 :
1637 0 : case '?':
1638 0 : f_tokens.emplace_back(
1639 : f_filename
1640 : , f_line
1641 : , TOKEN_EXCEPTION
1642 0 : , "?");
1643 0 : break;
1644 :
1645 10464 : case '[':
1646 20928 : f_tokens.emplace_back(
1647 : f_filename
1648 : , f_line
1649 : , TOKEN_OPEN_SQUARE_BRACKET
1650 31392 : , "[");
1651 10464 : break;
1652 :
1653 10464 : case ']':
1654 20928 : f_tokens.emplace_back(
1655 : f_filename
1656 : , f_line
1657 : , TOKEN_CLOSE_SQUARE_BRACKET
1658 31392 : , "]");
1659 10464 : break;
1660 :
1661 4147 : case '#':
1662 : for(;;)
1663 : {
1664 8197 : c = getc();
1665 4147 : switch(c)
1666 : {
1667 0 : case CHAR_ERR:
1668 : case CHAR_EOF:
1669 0 : return;
1670 :
1671 0 : case L'\r':
1672 0 : c = getc();
1673 0 : if(c != L'\n')
1674 : {
1675 0 : ungetc(c);
1676 : }
1677 0 : ++f_line;
1678 0 : return;
1679 :
1680 97 : case L'\n':
1681 97 : ++f_line;
1682 97 : return;
1683 :
1684 : }
1685 : }
1686 : break;
1687 :
1688 637 : case '"':
1689 : case '\'':
1690 : {
1691 637 : int start_line(f_line);
1692 637 : char32_t quote(c);
1693 :
1694 1274 : std::string value;
1695 : for(;;)
1696 : {
1697 23427 : c = getc();
1698 12032 : if(c == CHAR_ERR)
1699 : {
1700 0 : return;
1701 : }
1702 12032 : if(c == CHAR_EOF)
1703 : {
1704 0 : f_errno = EINVAL;
1705 0 : f_errmsg = "missing closing quote (";
1706 0 : f_errmsg += static_cast<char>(quote);
1707 0 : f_errmsg += ") for string.";
1708 0 : return;
1709 : }
1710 12032 : if(c == quote)
1711 : {
1712 637 : break;
1713 : }
1714 11395 : if(c == '\\')
1715 : {
1716 9 : if(!get_backslash(c))
1717 : {
1718 0 : return;
1719 : }
1720 : }
1721 11395 : if(!append_wc(value, c))
1722 : {
1723 0 : return;
1724 : }
1725 : }
1726 :
1727 1274 : f_tokens.emplace_back(
1728 : f_filename
1729 : , start_line
1730 : , TOKEN_STRING
1731 2548 : , value);
1732 : }
1733 637 : break;
1734 :
1735 38 : case '0':
1736 : case '1':
1737 : case '2':
1738 : case '3':
1739 : case '4':
1740 : case '5':
1741 : case '6':
1742 : case '7':
1743 : case '8':
1744 : case '9':
1745 : {
1746 76 : std::string value;
1747 38 : value += static_cast<char>(c);
1748 :
1749 : for(;;)
1750 : {
1751 76 : c = getc();
1752 57 : if(c == CHAR_ERR)
1753 : {
1754 0 : return;
1755 : }
1756 57 : if(c < '0' || c > '9')
1757 : {
1758 : break;
1759 : }
1760 19 : value += static_cast<char>(c);
1761 : }
1762 38 : ungetc(c);
1763 :
1764 76 : f_tokens.emplace_back(
1765 : f_filename
1766 : , f_line
1767 : , TOKEN_NUMBER
1768 152 : , value);
1769 : }
1770 38 : break;
1771 :
1772 29458 : default:
1773 29458 : if(is_space(c))
1774 : {
1775 : // ignore spaces
1776 594 : break;
1777 : }
1778 :
1779 28864 : if((c >= 'A' && c <= 'Z')
1780 28864 : || (c >= 'a' && c <= 'z')
1781 1119 : || c == '_')
1782 : {
1783 : // identifier
1784 : //
1785 27745 : std::string value;
1786 27745 : value += static_cast<char>(c);
1787 : for(;;)
1788 : {
1789 240223 : c = getc();
1790 133984 : if(c == CHAR_ERR)
1791 : {
1792 0 : return;
1793 : }
1794 133984 : if((c < 'A' || c > 'Z')
1795 133984 : && (c < 'a' || c > 'z')
1796 30830 : && (c < '0' || c > '9')
1797 30255 : && c != '_'
1798 30232 : && c != '/')
1799 : {
1800 27745 : break;
1801 : }
1802 106239 : value += static_cast<char>(c);
1803 : }
1804 27745 : if(!is_space(c))
1805 : {
1806 27745 : ungetc(c);
1807 : }
1808 :
1809 55490 : f_tokens.emplace_back(
1810 : f_filename
1811 : , f_line
1812 : , TOKEN_IDENTIFIER
1813 83235 : , value);
1814 27745 : break;
1815 1119 : }
1816 :
1817 : // invalid character (mainly controls)
1818 : //
1819 1119 : if(c < 0x20 // controls
1820 1119 : || (c >= 0x7F && c <= 0x9F)) // delete & graphic controls
1821 : {
1822 0 : f_errno = EINVAL;
1823 0 : f_errmsg = "unexpected character found '";
1824 0 : if(c < 0x20)
1825 : {
1826 0 : f_errmsg += '^';
1827 0 : f_errmsg += static_cast<char>(c + '@');
1828 : }
1829 0 : else if(c == 0x7F)
1830 : {
1831 0 : f_errmsg += "<DEL>";
1832 : }
1833 : else
1834 : {
1835 0 : f_errmsg += '@';
1836 0 : f_errmsg += static_cast<char>(c - '@');
1837 : }
1838 0 : f_errmsg += "'.";
1839 0 : return;
1840 : }
1841 :
1842 : {
1843 : // anything else represents a "word"
1844 : //
1845 2238 : std::string value;
1846 : for(;;)
1847 : {
1848 12209 : if(c == '\\')
1849 : {
1850 1170 : if(!get_backslash(c))
1851 : {
1852 0 : return;
1853 : }
1854 : }
1855 6664 : if(!append_wc(value, c))
1856 : {
1857 0 : return;
1858 : }
1859 :
1860 6664 : c = getc();
1861 6664 : if(c == CHAR_ERR)
1862 : {
1863 0 : return;
1864 : }
1865 13328 : if(c == CHAR_EOF
1866 6664 : || is_space(c))
1867 : {
1868 0 : break;
1869 : }
1870 6664 : if(c == '.'
1871 5741 : || c == '['
1872 5741 : || c == '='
1873 5741 : || c == ']')
1874 : {
1875 1119 : ungetc(c);
1876 1119 : break;
1877 : }
1878 : }
1879 :
1880 2238 : f_tokens.emplace_back(
1881 : f_filename
1882 : , f_line
1883 : , TOKEN_WORD
1884 4476 : , value);
1885 : }
1886 1119 : break;
1887 :
1888 : }
1889 76905 : }
1890 : }
1891 :
1892 :
1893 :
1894 63867 : bool tld_compiler::is_space(char32_t wc) const
1895 : {
1896 63867 : if(wc == '\r'
1897 63867 : || wc == '\n')
1898 : {
1899 2418 : return false;
1900 : }
1901 :
1902 61449 : return iswspace(wc);
1903 : }
1904 :
1905 :
1906 254235 : char32_t tld_compiler::getc()
1907 : {
1908 254235 : if(f_ungetc_pos > 0)
1909 : {
1910 28908 : --f_ungetc_pos;
1911 28908 : return f_ungetc[f_ungetc_pos];
1912 : }
1913 :
1914 225327 : if(f_pos >= f_data.size())
1915 : {
1916 266 : return CHAR_EOF;
1917 : }
1918 :
1919 225061 : int c(f_data[f_pos]);
1920 225061 : ++f_pos;
1921 :
1922 225061 : if(c < 0x80)
1923 : {
1924 225061 : return static_cast<char32_t>(c);
1925 : }
1926 :
1927 0 : char32_t wc(L'\0');
1928 0 : int cnt(0);
1929 0 : if(c >= 0xF0)
1930 : {
1931 0 : if(c >= 0xF8)
1932 : {
1933 0 : return CHAR_ERR;
1934 : }
1935 0 : wc = c & 0x07;
1936 0 : cnt = 3;
1937 : }
1938 0 : else if(c >= 0xE0)
1939 : {
1940 0 : wc = c & 0x0F;
1941 0 : cnt = 2;
1942 : }
1943 0 : else if(c >= 0xC0)
1944 : {
1945 0 : wc = c & 0x1F;
1946 0 : cnt = 1;
1947 : }
1948 : else
1949 : {
1950 0 : return CHAR_ERR;
1951 : }
1952 :
1953 0 : for(; cnt > 0; --cnt)
1954 : {
1955 0 : c = f_data[f_pos];
1956 0 : if(c == '\0')
1957 : {
1958 0 : return CHAR_ERR;
1959 : }
1960 0 : if(c < 0x80 || c > 0xBF)
1961 : {
1962 0 : return CHAR_ERR;
1963 : }
1964 0 : wc = (wc << 6) | (c & 0x3F);
1965 : }
1966 :
1967 0 : return wc;
1968 : }
1969 :
1970 :
1971 28908 : void tld_compiler::ungetc(char32_t c)
1972 : {
1973 28908 : if(c == CHAR_EOF
1974 28908 : || c == CHAR_ERR)
1975 : {
1976 0 : return;
1977 : }
1978 :
1979 28908 : if(f_ungetc_pos >= std::size(f_ungetc))
1980 : {
1981 0 : throw std::logic_error("f_ungetc buffer is full");
1982 : }
1983 :
1984 28908 : f_ungetc[f_ungetc_pos] = c;
1985 28908 : ++f_ungetc_pos;
1986 : }
1987 :
1988 :
1989 18059 : bool tld_compiler::append_wc(std::string & value, char32_t wc)
1990 : {
1991 18059 : if(wc < 0x80)
1992 : {
1993 16880 : value += static_cast<char>(wc);
1994 : }
1995 1179 : else if(wc < 0x800)
1996 : {
1997 665 : value += static_cast<char>(((wc >> 6) & 0x1F) | 0xC0);
1998 665 : value += static_cast<char>(((wc >> 0) & 0x3F) | 0x80);
1999 : }
2000 514 : else if(wc < 0x10000)
2001 : {
2002 514 : if(wc >= 0xD800 && wc <= 0xDFFF)
2003 : {
2004 : // you can't directly use a surrogate
2005 : //
2006 : // TODO: convert to hex number instead of base 10
2007 : //
2008 0 : f_errno = EINVAL;
2009 0 : f_errmsg = "trying to encode a surrogate Unicode code \""
2010 0 : + std::to_string(static_cast<std::uint32_t>(wc))
2011 0 : + "\" (base 10).";
2012 0 : return false;
2013 : }
2014 :
2015 514 : value += static_cast<char>(((wc >> 12) & 0x0F) | 0xE0);
2016 514 : value += static_cast<char>(((wc >> 6) & 0x3F) | 0x80);
2017 514 : value += static_cast<char>(((wc >> 0) & 0x3F) | 0x80);
2018 : }
2019 0 : else if(wc < 0x110000)
2020 : {
2021 0 : value += static_cast<char>(((wc >> 18) & 0x07) | 0xF0);
2022 0 : value += static_cast<char>(((wc >> 12) & 0x3F) | 0x80);
2023 0 : value += static_cast<char>(((wc >> 6) & 0x3F) | 0x80);
2024 0 : value += static_cast<char>(((wc >> 0) & 0x3F) | 0x80);
2025 : }
2026 0 : else if(wc != CHAR_EOF)
2027 : {
2028 : // TODO: convert to hex number instead of base 10
2029 : //
2030 0 : f_errno = EINVAL;
2031 0 : f_errmsg = "trying to encode invalid Unicode character \""
2032 0 : + std::to_string(static_cast<std::uint32_t>(wc))
2033 0 : + "\" (base 10).";
2034 0 : return false;
2035 : }
2036 :
2037 18059 : return true;
2038 : }
2039 :
2040 :
2041 13519 : void tld_compiler::parse_line()
2042 : {
2043 13519 : switch(f_tokens[0].get_token())
2044 : {
2045 10464 : case TOKEN_OPEN_SQUARE_BRACKET:
2046 : // defining a new TLD
2047 : //
2048 10464 : parse_tld();
2049 10464 : break;
2050 :
2051 3055 : case TOKEN_IDENTIFIER:
2052 3055 : parse_variable();
2053 3055 : break;
2054 :
2055 0 : default:
2056 0 : f_errno = EINVAL;
2057 0 : f_errmsg = "invalid line, not recognized as a TLD definition nor a variable definition";
2058 : //print_tokens();
2059 0 : break;
2060 :
2061 : }
2062 13519 : }
2063 :
2064 :
2065 3055 : void tld_compiler::parse_variable()
2066 : {
2067 6110 : if(f_tokens.size() < 2
2068 3055 : || f_tokens[1].get_token() != TOKEN_EQUAL)
2069 : {
2070 0 : f_errno = EINVAL;
2071 0 : f_errmsg = "a variable name must be followed by an equal sign";
2072 0 : return;
2073 : }
2074 :
2075 3055 : std::string const & name(f_tokens[0].get_value());
2076 3055 : std::string::size_type const pos(name.find('/'));
2077 3055 : bool const is_tag(pos != std::string::npos);
2078 3055 : if(is_tag)
2079 : {
2080 2487 : if(name.substr(0, pos) != "tag")
2081 : {
2082 0 : f_errno = EINVAL;
2083 0 : f_errmsg = "variable name \""
2084 0 : + name
2085 0 : + "\" does not start with \"tag/...\".";
2086 0 : return;
2087 : }
2088 2487 : std::string::size_type const more(name.find('/', pos + 1));
2089 2487 : if(more != std::string::npos)
2090 : {
2091 0 : f_errno = EINVAL;
2092 0 : f_errmsg = "variable name \""
2093 0 : + name
2094 0 : + "\" cannot include more than one slash (/).";
2095 0 : return;
2096 : }
2097 : }
2098 :
2099 6110 : std::string value;
2100 3055 : if(f_tokens.size() > 3UL)
2101 : {
2102 : // we do not allow mixing words & strings in the value, so make
2103 : // sure that if we have more than 3 tokens, none at index
2104 : // 2+ are strings
2105 : //
2106 34 : for(std::size_t idx(2); idx < f_tokens.size(); ++idx)
2107 : {
2108 23 : if(f_tokens[idx].get_token() == TOKEN_STRING)
2109 : {
2110 0 : f_errno = EINVAL;
2111 0 : f_errmsg = "a variable value cannot mix words and a string";
2112 0 : return;
2113 : }
2114 23 : if(idx != 2)
2115 : {
2116 12 : value += ' ';
2117 : }
2118 23 : value = f_tokens[idx].get_value();
2119 : }
2120 : }
2121 3044 : else if(f_tokens.size() == 3)
2122 : {
2123 3044 : value = f_tokens[2].get_value();
2124 : }
2125 :
2126 3055 : if(is_tag)
2127 : {
2128 4974 : std::string const tag_name(name.substr(pos + 1));
2129 2487 : if(f_current_tld.empty())
2130 : {
2131 281 : f_global_tags[tag_name] = value;
2132 : }
2133 : else
2134 : {
2135 2206 : f_definitions[f_current_tld]->add_tag(tag_name, value, f_errmsg);
2136 2206 : if(!f_errmsg.empty())
2137 : {
2138 0 : f_errno = EINVAL;
2139 0 : return;
2140 : }
2141 : }
2142 : }
2143 : else
2144 : {
2145 568 : if(f_current_tld.empty())
2146 : {
2147 0 : if(f_global_variables.find(name) != f_global_variables.end())
2148 : {
2149 0 : f_errno = EINVAL;
2150 0 : f_errmsg = "\"" + name + "\" global variable defined more than once.";
2151 0 : return;
2152 : }
2153 :
2154 : // name != "apply_to" -- I don't think that would be useful as a global
2155 0 : if(pos != std::string::npos // any tag
2156 0 : && name != "status")
2157 : {
2158 0 : f_errno = EINVAL;
2159 0 : f_errmsg = "variable with name \"" + name + "\" is not supported. Missing \"tag/\"?";
2160 0 : return;
2161 : }
2162 :
2163 0 : f_global_variables[name] = value;
2164 : }
2165 : else
2166 : {
2167 568 : f_definitions[f_current_tld]->set_named_parameter(name, value, f_errmsg);
2168 568 : if(!f_errmsg.empty())
2169 : {
2170 0 : f_errno = EINVAL;
2171 0 : return;
2172 : }
2173 : }
2174 : }
2175 : }
2176 :
2177 :
2178 10464 : void tld_compiler::parse_tld()
2179 : {
2180 10464 : std::size_t const max(f_tokens.size() - 1);
2181 10464 : if(max < 2
2182 10464 : || f_tokens[max].get_token() != TOKEN_CLOSE_SQUARE_BRACKET)
2183 : {
2184 0 : f_errno = EINVAL;
2185 0 : f_errmsg = "a TLD must end with a closing square bracket (]) and not be empty";
2186 : //print_tokens();
2187 0 : return;
2188 : }
2189 :
2190 10464 : std::size_t idx(1);
2191 :
2192 10464 : bool is_exception(false);
2193 10464 : if(f_tokens[idx].get_token() == TOKEN_EXCEPTION)
2194 : {
2195 0 : is_exception = true;
2196 0 : ++idx;
2197 :
2198 0 : if(idx >= max)
2199 : {
2200 0 : f_errno = EINVAL;
2201 0 : f_errmsg = "a TLD cannot just be an exception (?), a name is required";
2202 0 : return;
2203 : }
2204 : }
2205 :
2206 : // the very first dot is optional now
2207 : //
2208 10464 : if(f_tokens[idx].get_token() == TOKEN_DOT)
2209 : {
2210 10464 : ++idx;
2211 :
2212 10464 : if(idx >= max)
2213 : {
2214 0 : f_errno = EINVAL;
2215 0 : f_errmsg = "a TLD cannot just be a dot (?), a name is required";
2216 0 : return;
2217 : }
2218 : }
2219 :
2220 20928 : tld_definition::pointer_t tld(std::make_shared<tld_definition>(f_strings));
2221 :
2222 : // a TLD always starts with a dot, but we do not force the user to enter it
2223 : //
2224 : // TODO: keep the name separated (since we already cut it at the dots)
2225 : //
2226 : for(;;)
2227 : {
2228 22693 : switch(f_tokens[idx].get_token())
2229 : {
2230 0 : case TOKEN_DOT:
2231 0 : f_errno = EINVAL;
2232 0 : f_errmsg = "a TLD cannot include two dots (.) in a raw.";
2233 0 : return;
2234 :
2235 85 : case TOKEN_WILD_CARD:
2236 85 : if(!tld->add_segment("*", f_errmsg))
2237 : {
2238 0 : f_errno = EINVAL;
2239 0 : return;
2240 : }
2241 85 : ++idx;
2242 85 : break;
2243 :
2244 22608 : case TOKEN_IDENTIFIER:
2245 : case TOKEN_WORD:
2246 : case TOKEN_NUMBER:
2247 : {
2248 45216 : std::string segment(f_tokens[idx].get_value());
2249 22608 : bool found_dot(false);
2250 22608 : ++idx;
2251 48536 : while(idx < max && !found_dot)
2252 : {
2253 12964 : switch(f_tokens[idx].get_token())
2254 : {
2255 820 : case TOKEN_IDENTIFIER:
2256 : case TOKEN_WORD:
2257 : case TOKEN_NUMBER:
2258 820 : segment += f_tokens[idx].get_value();
2259 820 : ++idx;
2260 820 : break;
2261 :
2262 12144 : case TOKEN_DOT:
2263 12144 : found_dot = true;
2264 12144 : break;
2265 :
2266 0 : default:
2267 0 : f_errno = EINVAL;
2268 0 : f_errmsg = "unexpected token in a TLD (strings and special characters are not allowed).";
2269 0 : return;
2270 :
2271 : }
2272 : }
2273 22608 : if(!tld->add_segment(segment, f_errmsg))
2274 : {
2275 0 : f_errno = EINVAL;
2276 0 : return;
2277 22608 : }
2278 : }
2279 22608 : break;
2280 :
2281 0 : default:
2282 0 : f_errno = EINVAL;
2283 0 : f_errmsg = "unexpected token in a TLD (strings and special characters are not allowed.)";
2284 0 : return;
2285 :
2286 : }
2287 :
2288 22693 : if(idx >= max)
2289 : {
2290 10464 : break;
2291 : }
2292 :
2293 12229 : if(f_tokens[idx].get_token() != TOKEN_DOT)
2294 : {
2295 0 : f_errno = EINVAL;
2296 0 : f_errmsg = "expected a dot (.) between TLD names";
2297 0 : return;
2298 : }
2299 12229 : ++idx;
2300 :
2301 12229 : if(idx >= max)
2302 : {
2303 : // allow ending names with a period (optional)
2304 : //
2305 0 : break;
2306 : }
2307 12229 : }
2308 :
2309 : // use the '!' (0x21) for sorting, because '.' (0x2E) is after '-' (0x2D)
2310 : // and there is no '!' allowed in domain names (so far)
2311 : //
2312 : // the get_inverted_name() takes care of that
2313 : //
2314 10464 : f_current_tld = tld->get_inverted_name();
2315 :
2316 10464 : if(f_definitions.find(f_current_tld) != f_definitions.end())
2317 : {
2318 0 : f_errno = EINVAL;
2319 0 : f_errmsg = "TLD name \""
2320 0 : + tld->get_name()
2321 0 : + "\" defined twice.";
2322 0 : return;
2323 : }
2324 :
2325 10464 : f_definitions[f_current_tld] = tld;
2326 :
2327 : // add the globals to this definition
2328 : //
2329 10464 : for(auto const & g : f_global_variables)
2330 : {
2331 0 : f_definitions[f_current_tld]->set_named_parameter(g.first, g.second, f_errmsg);
2332 0 : if(!f_errmsg.empty())
2333 : {
2334 : // this should not happen since the globals are defined in a map
2335 : //
2336 0 : f_errno = EINVAL;
2337 0 : return;
2338 : }
2339 : }
2340 :
2341 21188 : for(auto const & g : f_global_tags)
2342 : {
2343 10724 : f_definitions[f_current_tld]->add_tag(g.first, g.second, f_errmsg);
2344 10724 : if(!f_errmsg.empty())
2345 : {
2346 : // this should not happen since the globals are defined in a map
2347 : //
2348 0 : f_errno = EINVAL;
2349 0 : return;
2350 : }
2351 : }
2352 :
2353 10464 : f_definitions[f_current_tld]->reset_set_flags();
2354 : }
2355 :
2356 :
2357 0 : void tld_compiler::print_tokens()
2358 : {
2359 0 : for(auto const & t : f_tokens)
2360 : {
2361 : std::cerr
2362 0 : << t.get_filename()
2363 0 : << ":"
2364 : << t.get_line()
2365 0 : << ": "
2366 0 : << static_cast<int>(t.get_token())
2367 : << " = \""
2368 0 : << t.get_value()
2369 0 : << "\"\n";
2370 :
2371 : //std::string const & get_filename() const;
2372 : //int get_line() const;
2373 : //token_t get_token() const;
2374 : //std::string const & get_value() const;
2375 : }
2376 0 : }
2377 :
2378 :
2379 1 : void tld_compiler::define_default_category()
2380 : {
2381 1 : string_id_t const category_id(f_strings.add_string("category"));
2382 1 : string_id_t const country_id(f_strings.add_string("country"));
2383 :
2384 10465 : for(auto const & d : f_definitions)
2385 : {
2386 10464 : tags_t const & tags(d.second->get_tags());
2387 10464 : auto it(tags.find(category_id));
2388 10464 : if(it == tags.end())
2389 : {
2390 : // there is no category yet, let's determine that now
2391 : //
2392 6519 : if(tags.find(country_id) != tags.end())
2393 : {
2394 6519 : d.second->add_tag("category", "country", f_errmsg);
2395 6519 : if(!f_errmsg.empty())
2396 : {
2397 0 : f_errno = EINVAL;
2398 0 : return;
2399 : }
2400 : }
2401 : else
2402 : {
2403 0 : f_errmsg = "domain \""
2404 0 : + d.second->get_name()
2405 0 : + "\" has no category and we had no way to determine a default category.";
2406 0 : f_errno = EINVAL;
2407 0 : return;
2408 : }
2409 : }
2410 : }
2411 : }
2412 :
2413 :
2414 1 : void tld_compiler::compress_tags()
2415 : {
2416 10465 : for(auto const & d : f_definitions)
2417 : {
2418 10464 : f_tags.add(d.second->get_tags());
2419 : }
2420 :
2421 1 : f_tags.merge();
2422 1 : }
2423 :
2424 :
2425 10464 : uint16_t tld_compiler::find_definition(std::string name) const
2426 : {
2427 10464 : if(!name.empty())
2428 : {
2429 21 : if(name[0] != '.')
2430 : {
2431 21 : name = '.' + name;
2432 : }
2433 91593 : for(auto const & it : f_definitions)
2434 : {
2435 91593 : if(it.second->get_name() == name)
2436 : {
2437 21 : return it.second->get_index();
2438 : }
2439 : }
2440 : }
2441 :
2442 10443 : return USHRT_MAX;
2443 : }
2444 :
2445 :
2446 : /** \brief Determine the longest TLD in terms of levels.
2447 : *
2448 : * This function searches all the definitions checking for the longest
2449 : * number of segments (which is the number of periods in the TLD including
2450 : * the starting period, so in ".com", we have a level of 1).
2451 : */
2452 1 : void tld_compiler::find_max_level()
2453 : {
2454 1 : f_tld_max_level = 0;
2455 :
2456 1 : auto it(std::max_element(
2457 : f_definitions.begin()
2458 : , f_definitions.end()
2459 10463 : , [](auto const & a, auto const & b)
2460 : {
2461 10463 : return a.second->get_segments().size()
2462 10463 : < b.second->get_segments().size();
2463 10464 : }));
2464 1 : if(it == f_definitions.end())
2465 : {
2466 0 : f_errno = EINVAL;
2467 0 : f_errmsg = "error: could not find a definition with a larger level.";
2468 0 : return;
2469 : }
2470 :
2471 1 : f_tld_max_level = it->second->get_segments().size();
2472 : }
2473 :
2474 :
2475 1 : void tld_compiler::output_tlds(std::ostream & out)
2476 : {
2477 : #pragma GCC diagnostic push
2478 : #pragma GCC diagnostic ignored "-Wpedantic"
2479 1 : tld_header header =
2480 : {
2481 : .f_version_major = 1,
2482 : .f_version_minor = 0,
2483 : .f_pad0 = 0,
2484 1 : .f_tld_max_level = f_tld_max_level,
2485 : .f_tld_start_offset = USHRT_MAX,
2486 : .f_tld_end_offset = USHRT_MAX,
2487 1 : .f_created_on = f_created_on,
2488 2 : };
2489 : #pragma GCC diagnostic pop
2490 :
2491 : // define the "offsets" (indices) of all the items
2492 : //
2493 : // the index will be used for the `apply_to` below to properly
2494 : // determine the exception
2495 : //
2496 1 : int i(0);
2497 6 : for(uint8_t level(f_tld_max_level); level > 0; --level)
2498 : {
2499 52325 : for(auto const & d : f_definitions)
2500 : {
2501 52320 : if(d.second->get_segments().size() == level)
2502 : {
2503 10464 : d.second->set_index(i);
2504 10464 : ++i;
2505 : }
2506 : }
2507 : }
2508 :
2509 : // now we create the TLD table with the largest levels first,
2510 : // as we do so we save the index of the start and stop
2511 : // points of each level in the previous level (hence the
2512 : // need for a level 0 entry)
2513 : //
2514 : // we create the table in memory; we need the level 0 offsets in
2515 : // the header before we can start saving the results in the output
2516 : // file...
2517 : //
2518 2 : std::vector<tld_description> descriptions;
2519 1 : i = 0;
2520 6 : for(uint8_t level(header.f_tld_max_level); level > 0; --level)
2521 : {
2522 52325 : for(auto const & d : f_definitions)
2523 : {
2524 52320 : if(d.second->get_segments().size() == level)
2525 : {
2526 : #pragma GCC diagnostic push
2527 : #pragma GCC diagnostic ignored "-Wpedantic"
2528 10464 : tld_description description =
2529 : {
2530 : // make sure it's set to exception if we have an "apply to"
2531 : // (probably not required since we can check whether we do
2532 : // have an apply to)
2533 : //
2534 20928 : .f_status = static_cast<uint8_t>(d.second->get_apply_to().empty()
2535 10443 : ? d.second->get_status()
2536 : : TLD_STATUS_EXCEPTION),
2537 : .f_exception_level = level,
2538 20928 : .f_exception_apply_to = find_definition(d.second->get_apply_to()),
2539 10464 : .f_start_offset = d.second->get_start_offset(),
2540 10464 : .f_end_offset = d.second->get_end_offset(),
2541 10464 : .f_tld = static_cast<uint16_t>(d.second->get_segments()[0]),
2542 10464 : .f_tags = static_cast<uint16_t>(f_tags.get_tag_offset(d.second->get_tags())),
2543 10464 : .f_tags_count = static_cast<uint16_t>(d.second->get_tags().size()),
2544 104619 : };
2545 : #pragma GCC diagnostic pop
2546 :
2547 20928 : std::string const parent_name(d.second->get_parent_inverted_name());
2548 10464 : if(parent_name.empty())
2549 : {
2550 1622 : if(f_tld_start_offset == USHRT_MAX)
2551 : {
2552 1 : f_tld_start_offset = i;
2553 : }
2554 1622 : f_tld_end_offset = i + 1;
2555 : }
2556 : else
2557 : {
2558 8842 : auto it(f_definitions.find(parent_name));
2559 8842 : if(it == f_definitions.end())
2560 : {
2561 0 : f_errno = EINVAL;
2562 0 : f_errmsg = "parent domain \""
2563 0 : + parent_name
2564 0 : + "\" not found.";
2565 0 : return;
2566 : }
2567 8842 : it->second->set_start_offset(i);
2568 8842 : it->second->set_end_offset(i + 1);
2569 : }
2570 :
2571 10464 : descriptions.push_back(description);
2572 :
2573 10464 : ++i;
2574 : }
2575 : }
2576 : }
2577 :
2578 1 : header.f_tld_start_offset = f_tld_start_offset;
2579 1 : header.f_tld_end_offset = f_tld_end_offset;
2580 :
2581 1 : tld_hunk header_hunk;
2582 1 : header_hunk.f_name = TLD_HEADER;
2583 1 : header_hunk.f_size = sizeof(tld_header);
2584 :
2585 1 : tld_hunk descriptions_hunk;
2586 1 : descriptions_hunk.f_name = TLD_DESCRIPTIONS;
2587 1 : descriptions_hunk.f_size = sizeof(tld_description) * f_definitions.size();
2588 :
2589 1 : tld_hunk tags_hunk;
2590 1 : tags_hunk.f_name = TLD_TAGS;
2591 1 : tags_hunk.f_size = f_tags.merged_tags().size() * sizeof(uint32_t); // NOT sizeof(tld_tags) because the merged vector is not one to one equivalent
2592 :
2593 1 : tld_hunk string_offsets_hunk;
2594 1 : string_offsets_hunk.f_name = TLD_STRING_OFFSETS;
2595 1 : string_offsets_hunk.f_size = static_cast<std::size_t>(f_strings_count) * sizeof(tld_string_offset);
2596 :
2597 1 : tld_hunk string_lengths_hunk;
2598 1 : string_lengths_hunk.f_name = TLD_STRING_LENGTHS;
2599 1 : string_lengths_hunk.f_size = static_cast<std::size_t>(f_strings_count) * sizeof(tld_string_length);
2600 :
2601 1 : tld_hunk strings_hunk;
2602 1 : strings_hunk.f_name = TLD_STRINGS;
2603 1 : strings_hunk.f_size = f_strings.compressed_length();
2604 :
2605 1 : tld_magic magic;
2606 1 : magic.f_riff = TLD_MAGIC;
2607 1 : magic.f_size = sizeof(magic.f_type)
2608 1 : + sizeof(tld_hunk) + header_hunk.f_size
2609 1 : + sizeof(tld_hunk) + descriptions_hunk.f_size
2610 1 : + sizeof(tld_hunk) + tags_hunk.f_size
2611 1 : + sizeof(tld_hunk) + string_offsets_hunk.f_size
2612 1 : + sizeof(tld_hunk) + string_lengths_hunk.f_size
2613 1 : + sizeof(tld_hunk) + strings_hunk.f_size;
2614 1 : magic.f_type = TLD_TLDS;
2615 :
2616 1 : out.write(reinterpret_cast<char const *>(&magic), sizeof(magic));
2617 :
2618 : // header
2619 : //
2620 1 : out.write(reinterpret_cast<char const *>(&header_hunk), sizeof(header_hunk));
2621 1 : out.write(reinterpret_cast<char const *>(&header), sizeof(header));
2622 :
2623 : // descriptions
2624 : //
2625 1 : out.write(reinterpret_cast<char const *>(&descriptions_hunk), sizeof(descriptions_hunk));
2626 1 : out.write(reinterpret_cast<char const *>(descriptions.data()), descriptions.size() * sizeof(tld_description));
2627 :
2628 : // tags
2629 : //
2630 1 : out.write(reinterpret_cast<char const *>(&tags_hunk), sizeof(tags_hunk));
2631 1 : out.write(reinterpret_cast<char const *>(f_tags.merged_tags().data()), tags_hunk.f_size);
2632 :
2633 : // strings: offsets
2634 : //
2635 1 : out.write(reinterpret_cast<char const *>(&string_offsets_hunk), sizeof(string_offsets_hunk));
2636 7312 : for(string_id_t idx(1); idx <= f_strings_count; ++idx)
2637 : {
2638 : #pragma GCC diagnostic push
2639 : #pragma GCC diagnostic ignored "-Wpedantic"
2640 7311 : tld_string_offset offset =
2641 : {
2642 7311 : .f_string_offset = static_cast<uint32_t>(f_strings.get_string_offset(idx)),
2643 7311 : };
2644 : #pragma GCC diagnostic pop
2645 7311 : out.write(reinterpret_cast<char const *>(&offset), sizeof(offset));
2646 : }
2647 :
2648 : // strings: lengths
2649 : //
2650 1 : out.write(reinterpret_cast<char const *>(&string_lengths_hunk), sizeof(string_lengths_hunk));
2651 7312 : for(string_id_t idx(1); idx <= f_strings_count; ++idx)
2652 : {
2653 : #pragma GCC diagnostic push
2654 : #pragma GCC diagnostic ignored "-Wpedantic"
2655 7311 : tld_string_length length =
2656 : {
2657 14622 : .f_string_length = static_cast<uint16_t>(f_strings.get_string(idx).length()),
2658 14622 : };
2659 : #pragma GCC diagnostic pop
2660 7311 : out.write(reinterpret_cast<char const *>(&length), sizeof(length));
2661 : }
2662 :
2663 : // strings: actual strings
2664 : //
2665 1 : out.write(reinterpret_cast<char const *>(&strings_hunk), sizeof(strings_hunk));
2666 1 : out.write(f_strings.compressed_strings().c_str(), strings_hunk.f_size);
2667 : }
2668 :
2669 :
2670 1 : void tld_compiler::save_to_file(std::string const & buffer)
2671 : {
2672 2 : std::ofstream out;
2673 1 : out.open(f_output);
2674 1 : if(!out)
2675 : {
2676 0 : f_errno = errno;
2677 0 : f_errmsg = "error: could not open output file \""
2678 0 : + f_output
2679 0 : + "\", errno: "
2680 0 : + std::to_string(f_errno)
2681 0 : + ", "
2682 0 : + strerror(f_errno)
2683 0 : + ".";
2684 0 : return;
2685 : }
2686 :
2687 1 : out.write(buffer.c_str(), buffer.length());
2688 : }
2689 :
2690 :
2691 1 : void tld_compiler::output_header(std::ostream & out)
2692 : {
2693 1 : time_t const now(time(nullptr));
2694 1 : struct tm t;
2695 1 : localtime_r(&now, &t);
2696 1 : char year[16];
2697 1 : strftime(year, sizeof(year), "%Y", &t);
2698 :
2699 2 : std::string basename;
2700 1 : std::string::size_type const pos(f_c_file.rfind('/'));
2701 1 : if(pos == std::string::npos)
2702 : {
2703 0 : basename = f_c_file;
2704 : }
2705 : else
2706 : {
2707 1 : basename = f_c_file.substr(pos + 1);
2708 : }
2709 :
2710 : out << "/* *** AUTO-GENERATED *** DO NOT EDIT ***\n"
2711 : " *\n"
2712 : " * This list of TLDs was auto-generated using the tldc compiler.\n"
2713 : " * Fix the tld_compiler.cpp or the .ini files used as input instead\n"
2714 : " * of this file.\n"
2715 : " *\n"
2716 : " * Copyright (c) 2011-" << year << " Made to Order Software Corp. All Rights Reserved.\n"
2717 : " *\n"
2718 : " * Permission is hereby granted, free of charge, to any person obtaining a\n"
2719 : " * copy of this software and associated documentation files (the\n"
2720 : " * \"Software\"), to deal in the Software without restriction, including\n"
2721 : " * without limitation the rights to use, copy, modify, merge, publish,\n"
2722 : " * distribute, sublicense, and/or sell copies of the Software, and to\n"
2723 : " * permit persons to whom the Software is furnished to do so, subject to\n"
2724 : " * the following conditions:\n"
2725 : " *\n"
2726 : " * The above copyright notice and this permission notice shall be included\n"
2727 : " * in all copies or substantial portions of the Software.\n"
2728 : " *\n"
2729 : " * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n"
2730 : " * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n"
2731 : " * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n"
2732 : " * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\n"
2733 : " * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\n"
2734 : " * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n"
2735 : " * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
2736 : " */\n"
2737 : "\n"
2738 : "/** \\file\n"
2739 1 : " * \\brief GENERATED FILE -- the " << basename << " file is generated -- DO NOT EDIT\n"
2740 : " *\n"
2741 : " * This file is generated using the tldc tool and the conf/tlds/... files.\n"
2742 : " * It is strongly advised that you do not edit this file directly except to\n"
2743 : " * test before editing the source of the tldc tool and tld_compiler.cpp file.\n"
2744 : " *\n"
2745 : " * The file includes information about all the TLDs as defined in the\n"
2746 : " * .ini files. It is used by the tld() function to determine whether\n"
2747 : " * a string with a domain name matches a valid TLD. It includes all the\n"
2748 : " * currently assigned TLDs (all countries plus international or common TLDs.)\n"
2749 : " *\n"
2750 : " * In this new implementation, the C version to compile is actually the\n"
2751 : " * RIFF/TLDS binary. We load it with the tld_file_load() function as if it\n"
2752 : " * were on disk. This way we have exactly the same code to load the\n"
2753 : " * compiled-in and the TLDs from files.\n"
2754 : " */\n"
2755 1 : "#include <stdint.h>\n";
2756 1 : }
2757 :
2758 :
2759 1 : void tld_compiler::save_to_c_file(std::string const & buffer)
2760 : {
2761 : // user requested that file?
2762 : //
2763 1 : if(f_c_file.empty())
2764 : {
2765 0 : return;
2766 : }
2767 :
2768 2 : std::ofstream out;
2769 1 : out.open(f_c_file);
2770 1 : if(!out)
2771 : {
2772 0 : f_errno = errno;
2773 0 : f_errmsg = "error: could not open C-file output file \""
2774 0 : + f_output
2775 0 : + "\", errno: "
2776 0 : + std::to_string(f_errno)
2777 0 : + ", "
2778 0 : + strerror(f_errno)
2779 0 : + ".";
2780 0 : return;
2781 : }
2782 :
2783 1 : output_header(out);
2784 :
2785 1 : out << "uint8_t const tld_static_tlds[] = {\n"
2786 1 : << std::hex
2787 1 : << std::setfill('0');
2788 :
2789 15776 : for(std::uint32_t idx(0); idx + 16 < buffer.length(); idx += 16)
2790 : {
2791 15775 : out << " ";
2792 268175 : for(std::uint32_t o(0); o < 16; ++o)
2793 : {
2794 : out << " 0x"
2795 252400 : << std::setw(2)
2796 252400 : << static_cast<int>(static_cast<uint8_t>(buffer[idx + o]))
2797 252400 : << ",";
2798 : }
2799 15775 : out << "\n";
2800 : }
2801 1 : std::uint32_t const leftover(buffer.length() % 16);
2802 1 : std::uint32_t const offset(buffer.length() - leftover);
2803 1 : if(leftover > 0)
2804 : {
2805 1 : out << " ";
2806 11 : for(std::uint32_t o(0); o < leftover; ++o)
2807 : {
2808 : out << " 0x"
2809 10 : << std::setw(2)
2810 10 : << static_cast<int>(static_cast<uint8_t>(buffer[offset + o]))
2811 10 : << ",";
2812 : }
2813 1 : out << "\n";
2814 : }
2815 1 : out << "};\n";
2816 : }
2817 :
2818 :
2819 2 : void tld_compiler::output_to_json(std::ostream & out, bool verbose) const
2820 : {
2821 2 : out << "{\n";
2822 2 : out << "\"version\":\"" << TLD_FILE_VERSION_MAJOR
2823 2 : << '.' << TLD_FILE_VERSION_MINOR << "\",\n";
2824 2 : out << "\"created-on\":" << f_created_on << ",\n";
2825 2 : out << "\"max-level\":" << static_cast<int>(f_tld_max_level) << ",\n";
2826 2 : out << "\"tld-start-offset\":" << f_tld_start_offset << ",\n";
2827 2 : out << "\"tld-end-offset\":" << f_tld_end_offset << ",\n";
2828 2 : out << "\"descriptions\":[\n";
2829 20930 : for(std::size_t idx(0); idx < f_definitions.size(); ++idx)
2830 : {
2831 20928 : auto it(std::find_if(
2832 : f_definitions.begin()
2833 : , f_definitions.end()
2834 109505760 : , [idx](auto const & d)
2835 109505760 : {
2836 219011520 : return d.second->get_index() == static_cast<int>(idx);
2837 109526688 : }));
2838 20928 : if(it == f_definitions.end())
2839 : {
2840 0 : std::cerr << "error: could not find definition at index "
2841 0 : << idx
2842 0 : << "\n";
2843 0 : return;
2844 : }
2845 : //out << "\"index\":\"" << it->second->get_index() << "\"";
2846 :
2847 20928 : out << (idx == 0 ? "" : ",\n");
2848 :
2849 20928 : out << "{";
2850 :
2851 20928 : if(verbose)
2852 : {
2853 10464 : out << "\"index\":" << std::setw(5) << idx << ",";
2854 : }
2855 :
2856 20928 : out << "\"tld\":\"" << f_strings.get_string(it->second->get_segments()[0]) << "\"";
2857 :
2858 20928 : out << ",\"status\":\"" << tld_status_to_string(it->second->get_status()) << "\"";
2859 :
2860 20928 : if(!it->second->get_apply_to().empty())
2861 : {
2862 42 : out << ",\"apply-to\":\"" << it->second->get_apply_to() << "\"";
2863 : }
2864 :
2865 20928 : if(it->second->get_start_offset() != USHRT_MAX)
2866 : {
2867 1590 : out << ",\"start-offset\":" << it->second->get_start_offset();
2868 1590 : out << ",\"end-offset\":" << it->second->get_end_offset();
2869 : }
2870 :
2871 59822 : for(auto const & t : it->second->get_tags())
2872 : {
2873 77788 : out << ",\"" << f_strings.get_string(t.first)
2874 77788 : << "\":\"" << f_strings.get_string(t.second)
2875 116682 : << "\"";
2876 : }
2877 :
2878 20928 : if(verbose)
2879 : {
2880 10464 : out << ",\"full-tld\":\"" << it->second->get_name() << "\"";
2881 : }
2882 :
2883 20928 : out << "}";
2884 : }
2885 2 : out << "]}\n";
2886 717 : }
2887 :
2888 :
2889 : // vim: ts=4 sw=4 et
|