1 /*
2  * Hunt - A data validation for DLang based on hunt library.
3  *
4  * Copyright (C) 2015-2019, HuntLabs
5  *
6  * Website: https://www.huntlabs.net
7  *
8  * Licensed under the Apache-2.0 License.
9  *
10  */
11 module hunt.validation.util.DomainNameUtil;
12 
13 import std.algorithm.searching;
14 import std.regex;
15 import std.string;
16 
17 class DomainNameUtil {
18 
19     /**
20      * This is the maximum length of a domain name. But be aware that each label (parts separated by a dot) of the
21      * domain name must be at most 63 characters long. This is verified by {@link IDN#toASCII(string)}.
22      */
23     private static const int MAX_DOMAIN_PART_LENGTH = 255;
24 
25     private static const string DOMAIN_CHARS_WITHOUT_DASH = "[a-z\u0080-\uFFFF0-9!#$%&'*+/=?^_`{|}~]";
26     private static const string DOMAIN_LABEL = "(" ~ DOMAIN_CHARS_WITHOUT_DASH ~ "-*)*" ~ DOMAIN_CHARS_WITHOUT_DASH ~ "+";
27     private static const string DOMAIN = DOMAIN_LABEL ~ "+(\\." ~ DOMAIN_LABEL ~ "+)*";
28 
29     private static const string IP_DOMAIN = "[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}";
30     //IP v6 regex taken from http://stackoverflow.com/questions/53497/regular-expression-that-matches-valid-ipv6-addresses
31     private static const string IP_V6_DOMAIN = "(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))";
32 
33     /**
34      * Regular expression for the domain part of an URL
35      * <p>
36      * A host string must be a domain string, an IPv4 address string, or "[", followed by an IPv6 address string,
37      * followed by "]".
38      */
39     private static const string DOMAIN_PATTERN = 
40             DOMAIN ~ "|\\[" ~ IP_V6_DOMAIN ~ "\\]";
41 
42     /**
43      * Regular expression for the domain part of an email address (everything after '@')
44      */
45     private static const string EMAIL_DOMAIN_PATTERN = 
46             DOMAIN ~ "|\\[" ~ IP_DOMAIN ~ "\\]|" ~ "\\[IPv6:" ~ IP_V6_DOMAIN ~ "\\]";
47 
48     private this() {
49     }
50 
51     /**
52      * Checks the validity of the domain name used in an email. To be valid it should be either a valid host name, or an
53      * IP address wrapped in [].
54      *
55      * @param domain domain to check for validity
56      * @return {@code true} if the provided string is a valid domain, {@code false} otherwise
57      */
58     public static bool isValidEmailDomainAddress(string domain) {
59         return isValidDomainAddress( domain, EMAIL_DOMAIN_PATTERN );
60     }
61 
62     /**
63      * Checks validity of a domain name.
64      *
65      * @param domain the domain to check for validity
66      * @return {@code true} if the provided string is a valid domain, {@code false} otherwise
67      */
68     public static bool isValidDomainAddress(string domain) {
69         return isValidDomainAddress( domain, DOMAIN_PATTERN );
70     }
71 
72     private static bool isValidDomainAddress(string domain, string pattern) {
73         // if we have a trailing dot the domain part we have an invalid email address.
74         // the regular expression match would take care of this, but IDN.toASCII drops the trailing '.'
75         if ( domain.endsWith( "." ) ) {
76             return false;
77         }
78 
79         auto matcher = matchAll( domain , regex(pattern));
80         if ( matcher.empty() ) {
81             return false;
82         }
83 
84         
85 
86         if ( domain.length > MAX_DOMAIN_PART_LENGTH ) {
87             return false;
88         }
89 
90         return true;
91     }
92 
93 }