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