libtld 2.0.16
A library to determine the Top-Level Domain name of any Internet URI.
tld.cpp
Go to the documentation of this file.
1/* TLD library -- TLD, domain name, and sub-domain extraction
2 * Copyright (c) 2011-2025 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
32// self
33//
34#include "libtld/tld.h"
35#include "libtld/tld_data.h"
36#include "libtld/tld_file.h"
37
38
39// C++
40//
41#include <sstream>
42
43
44// C
45//
46#if defined(MO_DARWIN)
47#include <malloc/malloc.h>
48#endif
49#if !defined(MO_DARWIN) && !defined(MO_FREEBSD)
50#include <malloc.h>
51#endif
52#include <stdlib.h>
53#include <limits.h>
54#include <string.h>
55#include <ctype.h>
56
57#ifdef WIN32
58#define strncasecmp _strnicmp
59#endif
60
61
62
63#ifdef __cplusplus
64extern "C" {
65#endif
66
67
340static struct tld_file * g_tld_file = nullptr;
341
342
343
344
345namespace
346{
347
348
349
360void tags_to_info(const struct tld_description *tld, struct tld_info *info)
361{
362 tld_tag const * tag;
363 uint32_t l;
364 char const * str;
365 for(uint32_t idx(0); idx < tld->f_tags_count; ++idx)
366 {
367 tag = tld_file_tag(g_tld_file, tld->f_tags + idx * 2);
368 if(tag == nullptr)
369 {
370 continue;
371 }
372
373 str = tld_file_string(g_tld_file, tag->f_tag_name, &l);
374 if(str == nullptr)
375 {
376 continue;
377 }
378 if(l == 8
379 && memcmp(str, "category", l) == 0)
380 {
381 str = tld_file_string(g_tld_file, tag->f_tag_value, &l);
382 if(str != nullptr)
383 {
384 info->f_category = tld_word_to_category(str, l);
385 }
386 }
387 else if(l == 7
388 && memcmp(str, "country", l) == 0)
389 {
390 str = tld_file_string(g_tld_file, tag->f_tag_value, &l);
391 if(str != nullptr
392 && l < sizeof(info->f_country))
393 {
394 memcpy(info->f_country, str, l);
395 info->f_country[l] = '\0'; // the tld_clear_info() already does that -- double safe
396 }
397 }
398 }
399}
400
401
411bool is_hex(int c)
412{
413 return (c >= '0' && c <= '9')
414 || (c >= 'A' && c <= 'F')
415 || (c >= 'a' && c <= 'f');
416}
417
418
428int h2d(int c)
429{
430 if(c >= 'a')
431 {
432 return c - 'a' + 10;
433 }
434 if(c >= 'A')
435 {
436 return c - 'A' + 10;
437 }
438 return c - '0';
439}
440
441
442
443} // no name namespace
444
445
446
461{
462 if(g_tld_file == nullptr)
463 {
464 return tld_load_tlds(nullptr, 1);
465 }
466
467 return TLD_RESULT_SUCCESS;
468}
469
470
499static int cmp(const char *a, int l, const char *b, int n)
500{
501 /* if `a == "*"` then we have a bug in our algorithm
502 if(a[0] == '*'
503 && a[1] == '\0')
504 {
505 return 0;
506 }
507 */
508
509 /* n represents the maximum number of characters to check in b */
510 while(l > 0 && n > 0)
511 {
512 if(*a < *b)
513 {
514 return -1;
515 }
516 if(*a > *b)
517 {
518 return 1;
519 }
520 ++a;
521 ++b;
522 --l;
523 --n;
524 }
525 if(l == 0)
526 {
527 if(n > 0)
528 {
529 /* in this case n > 0 so b is larger */
530 return -1;
531 }
532 return 0;
533 }
534 /* in this case l > 0 so a is larger */
535 return 1;
536}
537
538
569static int search(int i, int j, char const * domain, int n)
570{
571 int auto_match = -1, p, r;
572 uint32_t l;
573 struct tld_description const * tld;
574 char const * name;
575 enum tld_result result;
576
578 if(result != TLD_RESULT_SUCCESS)
579 {
580 return -1;
581 }
582
583#ifdef _DEBUG
584 if(static_cast<uint32_t>(i) > static_cast<uint32_t>(j))
585 {
586 // LCOV_EXCL_START
587 std::cerr
588 << "error: i ("
589 << i
590 << ") is larger than j ("
591 << j
592 << ") which is not expected in search()."
593 << std::endl;
594 std::terminate();
595 // LCOV_EXCL_STOP
596 }
597#endif
598
599 if(i < j)
600 {
601#ifdef _DEBUG
602 if(static_cast<uint32_t>(i) >= g_tld_file->f_descriptions_count
603 || static_cast<uint32_t>(j) > g_tld_file->f_descriptions_count) // can be equal to max. (actually it should always be on first call)
604 {
605 // LCOV_EXCL_START
606 std::cerr
607 << "error: i ("
608 << i
609 << ") or j ("
610 << j
611 << ") is too large, max is "
612 << g_tld_file->f_descriptions_count
613 << '.'
614 << std::endl;
615 std::terminate();
616 // LCOV_EXCL_STOP
617 }
618#endif
619
620 /* the "*" breaks the binary search, we have to handle it specially */
621 tld = tld_file_description(g_tld_file, i);
622 if(tld == nullptr)
623 {
624 return -1; // LCOV_EXCL_LINE -- see above (already checked)
625 }
626 name = tld_file_string(g_tld_file, tld->f_tld, &l);
627 if(name == nullptr)
628 {
629 return -1; // LCOV_EXCL_LINE -- see above (already checked)
630 }
631 if(l == 1 && name[0] == '*')
632 {
633 auto_match = i;
634 ++i;
635 }
636
637 while(i < j)
638 {
639 p = (j - i) / 2 + i;
640 tld = tld_file_description(g_tld_file, p);
641 if(tld == nullptr)
642 {
643 return -1;
644 }
645 name = tld_file_string(g_tld_file, tld->f_tld, &l);
646 if(name == nullptr)
647 {
648 return -1;
649 }
650#if 0
651std::cerr << "--- name offset: " << tld->f_tld << " --- ptr: " << reinterpret_cast<void const *>(name) << ", cmp(\"" << std::string(name, l) << "\", \"" << std::string(domain, n) << "\") == " << r << "\n";
652#endif
653#ifdef _DEBUG
654 if(l == 1 && name[0] == '*')
655 {
656 // LCOV_EXCL_START
657 std::cerr
658 << "fatal error: found an asterisk within an array of sub-domains at "
659 << p
660 << std::endl;
661 std::terminate();
662 // LCOV_EXCL_STOP
663 }
664#endif
665 r = cmp(name, l, domain, n);
666#if 0
667std::cerr << "--- name offset: " << tld->f_tld << " --- cmp(\"" << std::string(name, l) << "\", \"" << std::string(domain, n) << "\") == " << r << "\n";
668#endif
669 if(r < 0)
670 {
671 /* eliminate the first half */
672 i = p + 1;
673 }
674 else if(r > 0)
675 {
676 /* eliminate the second half */
677 j = p;
678 }
679 else
680 {
681 /* match */
682 return p;
683 }
684 }
685 }
686
687 return auto_match;
688}
689
690
705void tld_clear_info(struct tld_info *info)
706{
709 memset(info->f_country, 0, sizeof(info->f_country));
710 info->f_tld = (const char *) 0;
711 info->f_offset = -1;
712 info->f_tld_index = -1;
713}
714
715
744enum tld_result tld_load_tlds(char const * filename, int fallback)
745{
746 enum tld_file_error err;
747
748 tld_file_free(&g_tld_file);
749
750 if(filename == nullptr)
751 {
752 // first try a user updated version of the file
753 //
754 err = tld_file_load("/var/lib/libtld/tlds.tld", &g_tld_file);
755 if(err == TLD_FILE_ERROR_NONE)
756 {
757 return TLD_RESULT_SUCCESS;
758 }
759 // else -- ignore any other error
760
761 // second try the default installed version of the file
762 //
763 filename = "/usr/share/libtld/tlds.tld";
764 }
765 // else -- only try with the user defined version
766
767 err = tld_file_load(filename, &g_tld_file);
768 if(err == TLD_FILE_ERROR_NONE)
769 {
770 return TLD_RESULT_SUCCESS;
771 }
772
773 if(fallback != 0)
774 {
775 // use the descriptions from tld_data.c as fallback
776 //
777 std::stringstream in;
778 in.write(reinterpret_cast<char const *>(tld_static_tlds), tld_get_static_tlds_buffer_size());
779 err = tld_file_load_stream(&g_tld_file, in);
780 if(err == TLD_FILE_ERROR_NONE)
781 {
782 return TLD_RESULT_SUCCESS;
783 }
784 }
785
786 return err == TLD_FILE_ERROR_CANNOT_OPEN_FILE
789}
790
791
809const struct tld_file * tld_get_tlds()
810{
811 return g_tld_file;
812}
813
814
829{
830 tld_file_free(&g_tld_file);
831}
832
833
888enum tld_result tld_next_tld(struct tld_enumeration_state * state, struct tld_info * info)
889{
890 if(state == nullptr
891 || info == nullptr)
892 {
893 return TLD_RESULT_NULL;
894 }
895
896 tld_clear_info(info);
897
899 if(loaded != TLD_RESULT_SUCCESS)
900 {
901 return loaded;
902 }
903
904 if(g_tld_file->f_header->f_tld_max_level > std::size(state->f_offset))
905 {
906 return TLD_RESULT_NO_TLD;
907 }
908
909 if(state->f_depth == 0
910 && state->f_offset[0] == 0)
911 {
912 // set offset for the very first domain name
913 //
914 state->f_offset[0] = g_tld_file->f_header->f_tld_start_offset;
915 }
916
917 // did we reach the end?
918 //
919 if(state->f_offset[0] >= g_tld_file->f_header->f_tld_end_offset)
920 {
922 }
923
924 const struct tld_description * tld(nullptr);
925
926 char * domain(state->f_domain + sizeof(state->f_domain));
927 --domain;
928 *domain = '\0';
929 for(int d(0); d <= state->f_depth; ++d)
930 {
931 tld = tld_file_description(g_tld_file, state->f_offset[d]);
932 //tld = g_tld_file->f_descriptions + state->f_offset[d];
933 uint32_t length;
934 char const * name = tld_file_string(g_tld_file, tld->f_tld, &length);
935 if(name == nullptr)
936 {
937 return TLD_RESULT_BAD_URI;
938 }
939 while(length > 0)
940 {
941 char c('\0');
942 --length;
943 if(length >= 2
944 && name[length - 2] == '%'
945 && is_hex(name[length - 1])
946 && is_hex(name[length - 0]))
947 {
948 // convert back to a byte
949 //
950 c = h2d(name[length - 1]) * 16 + h2d(name[length - 0]);
951 length -= 2;
952 }
953 else
954 {
955 c = name[length];
956 }
957 --domain;
958 if(domain < state->f_domain)
959 {
960 return TLD_RESULT_BAD_URI;
961 }
962 *domain = c;
963 }
964
965 // the period is not saved in this case
966 //
967 --domain;
968 if(domain < state->f_domain)
969 {
970 return TLD_RESULT_BAD_URI;
971 }
972 *domain = '.';
973 }
974
975 info->f_tld = state->f_domain;
976 info->f_offset = domain - state->f_domain;
977 info->f_tld_index = state->f_offset[state->f_depth];
978 info->f_status = static_cast<tld_status>(tld->f_status);
979 tags_to_info(tld, info);
980
981 // compute the next position now
982 //
983 if(tld->f_start_offset != 65535)
984 {
985 ++state->f_depth;
986 state->f_offset[state->f_depth] = tld->f_start_offset;
987 }
988 else
989 {
990 ++state->f_offset[state->f_depth];
991 while(state->f_depth > 0)
992 {
993 const struct tld_description * parent = g_tld_file->f_descriptions + state->f_offset[state->f_depth - 1];
994 if(state->f_offset[state->f_depth] < parent->f_end_offset)
995 {
996 break;
997 }
998 --state->f_depth;
999 ++state->f_offset[state->f_depth];
1000 }
1001 }
1002
1003 return info->f_status == TLD_STATUS_VALID
1006}
1007
1008
1113enum tld_result tld(char const * const uri, struct tld_info * info)
1114{
1115 char const * end = uri;
1116 struct tld_description const * tld;
1117 int level = 0, max_level, start_level, i, r, p, offset;
1118 enum tld_result result;
1119
1120 /* set defaults in the info structure */
1121 tld_clear_info(info);
1122
1123 if(uri == nullptr || uri[0] == '\0')
1124 {
1125 return TLD_RESULT_NULL;
1126 }
1127
1128 /* before we can go further, we want to load the TLDs file */
1129 result = tld_load_tlds_if_not_loaded();
1130 if(result != TLD_RESULT_SUCCESS)
1131 {
1132 return result;
1133 }
1134
1135 max_level = g_tld_file->f_header->f_tld_max_level;
1136 std::vector<const char *> level_ptr(max_level);
1137 for(char const * previous_start(end); *end != '\0'; ++end)
1138 {
1139 if(*end == '.')
1140 {
1141 if(end - previous_start > LIBTLD_MAXIMUM_LABEL_LENGTH)
1142 {
1143 return TLD_RESULT_BAD_URI;
1144 }
1145 if(level >= max_level)
1146 {
1147 /* At this point the maximum number of levels in the
1148 * TLDs is 7
1149 */
1150 for(i = 1; i < max_level; ++i)
1151 {
1152 level_ptr[i - 1] = level_ptr[i];
1153 }
1154 level_ptr[max_level - 1] = end;
1155 }
1156 else
1157 {
1158 level_ptr[level] = end;
1159 ++level;
1160 }
1161 if(level >= 2 && level_ptr[level - 2] + 1 == level_ptr[level - 1])
1162 {
1163 /* two periods one after another */
1164 return TLD_RESULT_BAD_URI;
1165 }
1166 previous_start = end + 1;
1167 }
1168 }
1169 if(end - uri > LIBTLD_MAXIMUM_URI_LENGTH)
1170 {
1171 return TLD_RESULT_BAD_URI;
1172 }
1173 /* if level is not at least 1 then there are no periods */
1174 if(level == 0)
1175 {
1176 /* no TLD */
1177 return TLD_RESULT_NO_TLD;
1178 }
1179
1180 start_level = level;
1181 --level;
1182 r = search(g_tld_file->f_header->f_tld_start_offset,
1183 g_tld_file->f_header->f_tld_end_offset,
1184 level_ptr[level] + 1, (int) (end - level_ptr[level] - 1));
1185 if(r == -1)
1186 {
1187 /* unknown */
1188 return TLD_RESULT_NOT_FOUND;
1189 }
1190
1191 /* check for the next level if there is one */
1192 for(p = r; level > 0; --level, p = r)
1193 {
1194 tld = tld_file_description(g_tld_file, r);
1195 if(tld == nullptr)
1196 {
1197 return TLD_RESULT_NOT_FOUND;
1198 }
1199 if(tld->f_start_offset == USHRT_MAX)
1200 {
1201 break;
1202 }
1203 r = search(tld->f_start_offset, tld->f_end_offset,
1204 level_ptr[level - 1] + 1,
1205 static_cast<int>(level_ptr[level] - level_ptr[level - 1] - 1));
1206 if(r == -1)
1207 {
1208 /* we are done, return the previous level */
1209 break;
1210 }
1211 }
1212 offset = (int) (level_ptr[level] - uri);
1213
1214 /* if there are exceptions we may need to search those now if level is 0 */
1215 if(level == 0)
1216 {
1217 tld = tld_file_description(g_tld_file, p);
1218 if(tld == nullptr)
1219 {
1220 return TLD_RESULT_NOT_FOUND;
1221 }
1222 r = search(tld->f_start_offset,
1223 tld->f_end_offset,
1224 uri,
1225 static_cast<int>(level_ptr[0] - uri));
1226 if(r != -1)
1227 {
1228 p = r;
1229 offset = 0;
1230 }
1231 }
1232
1233 tld = tld_file_description(g_tld_file, p);
1234 if(tld == nullptr)
1235 {
1236 return TLD_RESULT_NOT_FOUND;
1237 }
1238 info->f_status = static_cast<tld_status>(tld->f_status);
1239 info->f_tld_index = p;
1240 switch(info->f_status)
1241 {
1242 case TLD_STATUS_VALID:
1243 result = TLD_RESULT_SUCCESS;
1244 break;
1245
1247 /* return the actual TLD and not the exception
1248 * i.e. "nacion.ar" is valid and the TLD is just ".ar"
1249 * even though top level ".ar" is forbidden by default
1250 */
1251 p = tld->f_exception_apply_to;
1252 tld = tld_file_description(g_tld_file, p);
1253 if(tld == nullptr)
1254 {
1255 return TLD_RESULT_NOT_FOUND;
1256 }
1257 level = start_level - tld->f_exception_level;
1258 offset = static_cast<int>(level_ptr[level] - uri);
1259 info->f_status = TLD_STATUS_VALID;
1260 result = TLD_RESULT_SUCCESS;
1261 break;
1262
1263 default:
1264 result = TLD_RESULT_INVALID;
1265 break;
1266
1267 }
1268
1269 tags_to_info(tld, info);
1270
1271 info->f_tld = level_ptr[level];
1272 info->f_offset = offset;
1273
1274 return result;
1275}
1276
1277
1319enum tld_result tld_check_uri(const char * uri, struct tld_info * info, const char * protocols, int flags)
1320{
1321 const char *p, *q, *username, *password, *host, *port, *n, *a, *query_string;
1322 char domain[256];
1323 int protocol_length, length, valid, c, i, j, anchor;
1324 enum tld_result result;
1325
1326 /* set defaults in the info structure */
1327 tld_clear_info(info);
1328
1329 if(uri == nullptr || uri[0] == '\0')
1330 {
1331 return TLD_RESULT_NULL;
1332 }
1333
1334 /* check the protocol: [0-9A-Za-z_]+ */
1335 for(p = uri; *uri != '\0' && *uri != ':'; ++uri)
1336 {
1337 if((*uri < 'a' || *uri > 'z')
1338 && (*uri < 'A' || *uri > 'Z')
1339 && (*uri < '0' || *uri > '9')
1340 && *uri != '_')
1341 {
1342 return TLD_RESULT_BAD_URI;
1343 }
1344 }
1345 valid = 0;
1346 protocol_length = (int) (uri - p);
1347 c = tolower(*p);
1348 for(q = protocols; *q != '\0';)
1349 {
1350 if(q[0] == '*' && (q[1] == '\0' || q[1] == ','))
1351 {
1352 valid = 1;
1353 break;
1354 }
1355 if(tolower(*q) == c)
1356 {
1357 if(strncasecmp(p, q, protocol_length) == 0
1358 && (q[protocol_length] == '\0' || q[protocol_length] == ','))
1359 {
1360 valid = 1;
1361 break;
1362 }
1363 }
1364 /* move to the next protocol */
1365 for(; *q != '\0' && *q != ','; ++q);
1366 for(; *q == ','; ++q);
1367 }
1368 if(valid == 0)
1369 {
1370 return TLD_RESULT_BAD_URI;
1371 }
1372 if(uri[1] != '/' || uri[2] != '/')
1373 {
1374 return TLD_RESULT_BAD_URI;
1375 }
1376 uri += 3; /* skip the '://' */
1377
1378 /* extract the complete domain name with sub-domains, etc. */
1379 username = nullptr;
1380 host = uri;
1381 for(; *uri != '/' && *uri != '\0'; ++uri)
1382 {
1383 if((unsigned char) *uri < ' ')
1384 {
1385 /* forbid control characters in domain name */
1386 return TLD_RESULT_BAD_URI;
1387 }
1388 if(*uri == '@')
1389 {
1390 if(username != nullptr)
1391 {
1392 /* two '@' signs is not possible */
1393 return TLD_RESULT_BAD_URI;
1394 }
1395 username = host;
1396 host = uri + 1;
1397 }
1398 else if((*uri & 0x80) != 0)
1399 {
1400 if((flags & VALID_URI_ASCII_ONLY) != 0)
1401 {
1402 /* only ASCII allowed by caller */
1403 return TLD_RESULT_BAD_URI;
1404 }
1405 }
1406 else if(*uri == ' ' || *uri == '+')
1407 {
1408 /* spaces not allowed in domain name */
1409 return TLD_RESULT_BAD_URI;
1410 }
1411 else if(*uri == '%')
1412 {
1413 /* the next two characters must be hex digits
1414 * note that the first digit must be at least 2 because
1415 * we do not allow control characters
1416 */
1417 if(((uri[1] < '2' || uri[1] > '9')
1418 && (uri[1] < 'a' || uri[1] > 'f')
1419 && (uri[1] < 'A' || uri[1] > 'F'))
1420 || ((uri[2] < '0' || uri[2] > '9')
1421 && (uri[2] < 'a' || uri[2] > 'f')
1422 && (uri[2] < 'A' || uri[2] > 'F')))
1423 {
1424 return TLD_RESULT_BAD_URI;
1425 }
1426 if(uri[1] == '2' && uri[2] == '0')
1427 {
1428 /* spaces not allowed in domain name */
1429 return TLD_RESULT_BAD_URI;
1430 }
1431 if(uri[1] >= '8' && (flags & VALID_URI_ASCII_ONLY) != 0)
1432 {
1433 /* only ASCII allowed by caller */
1434 return TLD_RESULT_BAD_URI;
1435 }
1436 /* skip the two digits right away */
1437 uri += 2;
1438 }
1439 }
1440 if(username != nullptr)
1441 {
1442 password = username;
1443 for(; *password != '@' && *password != ':'; ++password);
1444 if(*password == ':')
1445 {
1446 if((host - 1) - (password + 1) <= 0)
1447 {
1448 /* empty password are not acceptable */
1449 return TLD_RESULT_BAD_URI;
1450 }
1451 }
1452 if(password - username - 1 <= 0)
1453 {
1454 /* username cannot be empty */
1455 return TLD_RESULT_BAD_URI;
1456 }
1457 }
1458 for(port = host; *port != ':' && port < uri; ++port);
1459 if(*port == ':')
1460 {
1461 // we have a port, at this time it must be digits [0-9]+
1462 // (this is incorrect, a port could be a name such as "https";
1463 // also my current numeric test is invalid, it should make sure
1464 // it's in range: 0 to 65,535)
1465 //
1466 for(n = port + 1; *n >= '0' && *n <= '9'; ++n);
1467 if(n != uri || n == port + 1)
1468 {
1469 /* port is empty or includes invalid characters */
1470 return TLD_RESULT_BAD_URI;
1471 }
1472 }
1473
1474 // check the path, query string, and anchor
1475 //
1476 query_string = nullptr;
1477 anchor = 0;
1478 for(a = uri; *a != '\0'; ++a)
1479 {
1480 if((unsigned char) *a < ' ')
1481 {
1482 // no control characters allowed
1483 //
1484 return TLD_RESULT_BAD_URI;
1485 }
1486 else if(*a == '+' || *a == ' ') // old space encoding is '+' (instead of %20)
1487 {
1488 if((flags & VALID_URI_NO_SPACES) != 0)
1489 {
1490 // spaces not allowed by caller
1491 //
1492 return TLD_RESULT_BAD_URI;
1493 }
1494 }
1495 else if(*a == '?')
1496 {
1497 if(anchor == 0)
1498 {
1499 if(query_string != nullptr)
1500 {
1501 // ? cannot be used multiple times
1502 //
1503 return TLD_RESULT_BAD_URI;
1504 }
1505
1506 query_string = a + 1;
1507 }
1508 }
1509 else if(*a == '&' && anchor == 0)
1510 {
1511 if(query_string == nullptr)
1512 {
1513 // '&' must be encoded if used before '?'
1514 //
1515 return TLD_RESULT_BAD_URI;
1516 }
1517
1518 // the query_string pointer is used to verify that the variable
1519 // name is not empty
1520 //
1521 query_string = a + 1;
1522 }
1523 else if(*a == '=')
1524 {
1525 if(query_string != nullptr && a - query_string == 0)
1526 {
1527 // a query string variable name cannot be empty
1528 return TLD_RESULT_BAD_URI;
1529 }
1530 }
1531 else if(*a == '#')
1532 {
1533 query_string = nullptr;
1534 anchor = 1;
1535 }
1536 else if(*a == '%')
1537 {
1538 /* the next two digits must be hex
1539 * note that the first digit must be at least 2 because
1540 * we do not allow control characters
1541 */
1542 if(((a[1] < '2' || a[1] > '9')
1543 && (a[1] < 'a' || a[1] > 'f')
1544 && (a[1] < 'A' || a[1] > 'F'))
1545 || ((a[2] < '0' || a[2] > '9')
1546 && (a[2] < 'a' || a[2] > 'f')
1547 && (a[2] < 'A' || a[2] > 'F')))
1548 {
1549 return TLD_RESULT_BAD_URI;
1550 }
1551 if(a[1] == '2' && a[2] == '0' && (flags & VALID_URI_NO_SPACES) != 0)
1552 {
1553 /* spaces not allowed by caller */
1554 return TLD_RESULT_BAD_URI;
1555 }
1556 if(a[1] >= '8' && (flags & VALID_URI_ASCII_ONLY) != 0)
1557 {
1558 /* only ASCII allowed by caller */
1559 return TLD_RESULT_BAD_URI;
1560 }
1561 /* skip the two digits right away */
1562 a += 2;
1563 }
1564 else if((*a & 0x80) != 0)
1565 {
1566 if((flags & VALID_URI_ASCII_ONLY) != 0)
1567 {
1568 /* only ASCII allowed by caller */
1569 return TLD_RESULT_BAD_URI;
1570 }
1571 }
1572 }
1573
1574 /* check the domain */
1575
1589 length = (int) (port - host);
1590 if(length >= (int) (sizeof(domain) / sizeof(domain[0])))
1591 {
1592 /* sub-domains + domain + TLD is more than 255 characters?!
1593 * note that the host main include many %XX characters but
1594 * we ignore the fact here at this time; we could move this
1595 * test in the for() loop below though.
1596 */
1597 return TLD_RESULT_BAD_URI;
1598 }
1599 if(length == 0)
1600 {
1601 // although we could return TLD_RESULT_NULL it would not be
1602 // valid here because "http:///blah.com" is invalid, not nullptr
1603 //
1604 return TLD_RESULT_BAD_URI;
1605 }
1606 for(i = 0, j = 0; i < length; ++i, ++j)
1607 {
1608 if(host[i] == '%')
1609 {
1610 domain[j] = (char) (h2d(host[i + 1]) * 16 + h2d(host[i + 2]));
1611 i += 2; // skip the 2 digits
1612 }
1613 else
1614 {
1615 domain[j] = host[i];
1616 }
1617 /* TODO: check that characters are acceptable in a domain name (done above, right?) */
1618 }
1619 domain[j] = '\0';
1620 result = tld(domain, info);
1621 if(info->f_tld != nullptr)
1622 {
1623 if(info->f_offset == 0)
1624 {
1625 // if there is only a TLD, then it's invalid
1626 //
1627 return TLD_RESULT_BAD_URI;
1628 }
1629
1630 // define the TLD inside the source string which "unfortunately"
1631 // is not null terminated by '\0'; also fix the offset since in
1632 // the complete URI the TLD is a bit further away
1633 //
1634 // note that `p` is the position at the start of the protocol
1635 // (at the start of 'uri' at the start)
1636 //
1637 info->f_tld = host + info->f_offset;
1638 info->f_offset = (int) (info->f_tld - p);
1639 }
1640 return result;
1641}
1642
1643
1654const char *tld_version()
1655{
1656 return LIBTLD_VERSION;
1657}
1658
1659
1671{
1672 // The RIFF format saves the file size except the first 8 bytes in the
1673 // second uint32_t
1674 //
1675 // WARNING: the following fails if you are running on a big endian
1676 // computer (the size will be swapped and the + 8 make it
1677 // even harder to understand what happened...)
1678 //
1679 return reinterpret_cast<uint32_t const *>(tld_static_tlds)[1] + 8;
1680}
1681
1682
1683int tld_tag_count(struct tld_info *info)
1684{
1685 const struct tld_description *tld;
1686
1687 if(info == nullptr
1688 || info->f_tld_index < 0)
1689 {
1690 return -1;
1691 }
1692
1693 tld = tld_file_description(g_tld_file, info->f_tld_index);
1694 if(tld == nullptr)
1695 {
1696 return -1;
1697 }
1698
1699 return tld->f_tags_count;
1700}
1701
1702
1703enum tld_result tld_get_tag(struct tld_info *info, int tag_idx, struct tld_tag_definition *tag)
1704{
1705 const struct tld_description *tld;
1706 const tld_tag *file_tag;
1707 enum tld_result result;
1708 uint32_t l;
1709
1710 if(tag == nullptr)
1711 {
1712 return TLD_RESULT_NULL;
1713 }
1714 tag->f_name = nullptr;
1715 tag->f_name_length = 0;
1716 tag->f_value = nullptr;
1717 tag->f_value_length = 0;
1718
1719 if(info == nullptr)
1720 {
1721 return TLD_RESULT_NULL;
1722 }
1723
1724 if(info->f_tld_index < 0)
1725 {
1726 return TLD_RESULT_INVALID;
1727 }
1728
1729 result = tld_load_tlds_if_not_loaded();
1730 if(result != TLD_RESULT_SUCCESS)
1731 {
1732 return result;
1733 }
1734
1735 tld = tld_file_description(g_tld_file, info->f_tld_index);
1736 if(tld == nullptr)
1737 {
1738 return TLD_RESULT_NOT_FOUND;
1739 }
1740
1741 file_tag = tld_file_tag(g_tld_file, tld->f_tags + tag_idx * 2);
1742 if(file_tag == nullptr)
1743 {
1744 return TLD_RESULT_NOT_FOUND;
1745 }
1746
1747 tag->f_name = tld_file_string(g_tld_file, file_tag->f_tag_name, &l);
1748 tag->f_name_length = l;
1749
1750 tag->f_value = tld_file_string(g_tld_file, file_tag->f_tag_value, &l);
1751 tag->f_value_length = l;
1752
1753 if(tag->f_name == nullptr
1754 || tag->f_value == nullptr)
1755 {
1756 return TLD_RESULT_NOT_FOUND;
1757 }
1758
1759 return TLD_RESULT_SUCCESS;
1760}
1761
1762
1763
2219#ifdef __cplusplus
2220}
2221#endif
2222
2223// vim: ts=4 sw=4 et
[internal] The description of one TLD.
Definition tld_file.h:117
uint16_t f_end_offset
The last offset of a list of TLDs.
Definition tld_file.h:123
Set of information returned by the tld() function.
Definition tld.h:105
enum tld_category f_category
The category of the TLD.
Definition tld.h:106
enum tld_status f_status
The status of the TLD.
Definition tld.h:107
int f_offset
The offset to the TLD in the URI string you supplied.
Definition tld.h:110
char f_country[48]
The country where this TLD is used.
Definition tld.h:108
const char * f_tld
Pointer to the TLD in the URI string you supplied.
Definition tld.h:109
const char * tld_version()
Return the version of the library.
Definition tld.cpp:1654
static struct tld_file * g_tld_file
The TLD file currently loaded or NULL.
Definition tld.cpp:340
enum tld_result tld(char const *const uri, struct tld_info *info)
Get information about the TLD for the specified URI.
Definition tld.cpp:1113
static enum tld_result tld_load_tlds_if_not_loaded()
Load the TLDs if not yet loaded.
Definition tld.cpp:460
static int cmp(const char *a, int l, const char *b, int n)
Compare two strings, one of which is limited by length.
Definition tld.cpp:499
void tld_clear_info(struct tld_info *info)
Clear the info structure.
Definition tld.cpp:705
const struct tld_file * tld_get_tlds()
Return a pointer to the current list of TLDs.
Definition tld.cpp:809
static int search(int i, int j, char const *domain, int n)
Search for the specified domain.
Definition tld.cpp:569
enum tld_result tld_next_tld(struct tld_enumeration_state *state, struct tld_info *info)
Read the next TLD and return its info.
Definition tld.cpp:888
enum tld_result tld_check_uri(const char *uri, struct tld_info *info, const char *protocols, int flags)
Check that a URI is valid.
Definition tld.cpp:1319
uint32_t tld_get_static_tlds_buffer_size()
Get the size of the TLDs static buffer.
Definition tld.cpp:1670
void tld_free_tlds()
Clear the allocated TLD file.
Definition tld.cpp:828
enum tld_result tld_load_tlds(char const *filename, int fallback)
Load a TLDs file as the file to be used by the tld() function.
Definition tld.cpp:744
The public header of the libtld library.
#define VALID_URI_NO_SPACES
Whether to check that the URI do not include any spaces.
Definition tld.h:130
#define LIBTLD_VERSION
The version of the library as a string.
Definition tld.h:51
@ TLD_CATEGORY_UNDEFINED
The TLD was not found.
Definition tld.h:69
#define VALID_URI_ASCII_ONLY
Whether to check that the URI only includes ASCII.
Definition tld.h:129
LIBTLD_EXPORT enum tld_category tld_word_to_category(const char *word, int n)
This is for backward compatibility.
tld_result
Definition tld.h:95
@ TLD_RESULT_SUCCESS
Success! The TLD of the specified URI is valid.
Definition tld.h:96
@ TLD_RESULT_NO_TLD
The input URI has no TLD defined.
Definition tld.h:99
@ TLD_RESULT_INVALID
The TLD was found, but it is marked as invalid.
Definition tld.h:97
@ TLD_RESULT_BAD_URI
The URI includes characters that are not accepted by the function.
Definition tld.h:100
@ TLD_RESULT_NOT_FOUND
The URI has a TLD that could not be determined.
Definition tld.h:101
@ TLD_RESULT_NULL
The input URI is empty.
Definition tld.h:98
tld_status
Definition tld.h:73
@ TLD_STATUS_EXCEPTION
Special status to indicate an exception which is not directly a TLD.
Definition tld.h:91
@ TLD_STATUS_UNDEFINED
Special status to indicate we did not find the TLD.
Definition tld.h:81
@ TLD_STATUS_VALID
The TLD is currently valid.
Definition tld.h:74
Declaration of the static TLDs file.
Declaration of the TLD file structures.

This document is part of the Snap! Websites Project.

Copyright by Made to Order Software Corp.