1 //* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 // This service reads a file of rules describing TLD-like domain names. For a
7 // complete description of the expected file format and parsing rules, see
8 // http://wiki.mozilla.org/Gecko:Effective_TLD_Service
10 #include "mozilla/Util.h"
12 #include "nsEffectiveTLDService.h"
13 #include "nsIIDNService.h"
14 #include "nsNetUtil.h"
18 using namespace mozilla
;
20 NS_IMPL_ISUPPORTS1(nsEffectiveTLDService
, nsIEffectiveTLDService
)
22 // ----------------------------------------------------------------------
24 #define ETLD_STR_NUM_1(line) str##line
25 #define ETLD_STR_NUM(line) ETLD_STR_NUM_1(line)
26 #define ETLD_ENTRY_OFFSET(name) offsetof(struct etld_string_list, ETLD_STR_NUM(__LINE__))
28 const ETLDEntry
nsDomainEntry::entries
[] = {
29 #define ETLD_ENTRY(name, ex, wild) { ETLD_ENTRY_OFFSET(name), ex, wild },
30 #include "etld_data.inc"
34 const union nsDomainEntry::etld_strings
nsDomainEntry::strings
= {
36 #define ETLD_ENTRY(name, ex, wild) name,
37 #include "etld_data.inc"
42 // Dummy function to statically ensure that our indices don't overflow
43 // the storage provided for them.
45 nsDomainEntry::FuncForStaticAsserts(void)
47 #define ETLD_ENTRY(name, ex, wild) \
48 MOZ_STATIC_ASSERT(ETLD_ENTRY_OFFSET(name) < (1 << ETLD_ENTRY_N_INDEX_BITS), \
49 "invalid strtab index");
50 #include "etld_data.inc"
54 #undef ETLD_ENTRY_OFFSET
58 // ----------------------------------------------------------------------
61 nsEffectiveTLDService::Init()
63 const ETLDEntry
*entries
= nsDomainEntry::entries
;
65 // We'll probably have to rehash at least once, since nsTHashtable doesn't
66 // use a perfect hash, but at least we'll save a few rehashes along the way.
67 // Next optimization here is to precompute the hash using something like
68 // gperf, but one step at a time. :-)
69 mHash
.Init(ArrayLength(nsDomainEntry::entries
));
72 mIDNService
= do_GetService(NS_IDNSERVICE_CONTRACTID
, &rv
);
73 if (NS_FAILED(rv
)) return rv
;
75 // Initialize eTLD hash from static array
76 for (uint32_t i
= 0; i
< ArrayLength(nsDomainEntry::entries
); i
++) {
77 const char *domain
= nsDomainEntry::GetEffectiveTLDName(entries
[i
].strtab_index
);
79 nsDependentCString
name(domain
);
80 nsAutoCString
normalizedName(domain
);
81 NS_ASSERTION(NS_SUCCEEDED(NormalizeHostname(normalizedName
)),
82 "normalization failure!");
83 NS_ASSERTION(name
.Equals(normalizedName
), "domain not normalized!");
85 nsDomainEntry
*entry
= mHash
.PutEntry(domain
);
86 NS_ENSURE_TRUE(entry
, NS_ERROR_OUT_OF_MEMORY
);
87 entry
->SetData(&entries
[i
]);
92 // External function for dealing with URI's correctly.
93 // Pulls out the host portion from an nsIURI, and calls through to
94 // GetPublicSuffixFromHost().
96 nsEffectiveTLDService::GetPublicSuffix(nsIURI
*aURI
,
97 nsACString
&aPublicSuffix
)
99 NS_ENSURE_ARG_POINTER(aURI
);
101 nsCOMPtr
<nsIURI
> innerURI
= NS_GetInnermostURI(aURI
);
102 NS_ENSURE_ARG_POINTER(innerURI
);
105 nsresult rv
= innerURI
->GetAsciiHost(host
);
106 if (NS_FAILED(rv
)) return rv
;
108 return GetBaseDomainInternal(host
, 0, aPublicSuffix
);
111 // External function for dealing with URI's correctly.
112 // Pulls out the host portion from an nsIURI, and calls through to
113 // GetBaseDomainFromHost().
115 nsEffectiveTLDService::GetBaseDomain(nsIURI
*aURI
,
116 uint32_t aAdditionalParts
,
117 nsACString
&aBaseDomain
)
119 NS_ENSURE_ARG_POINTER(aURI
);
121 nsCOMPtr
<nsIURI
> innerURI
= NS_GetInnermostURI(aURI
);
122 NS_ENSURE_ARG_POINTER(innerURI
);
125 nsresult rv
= innerURI
->GetAsciiHost(host
);
126 if (NS_FAILED(rv
)) return rv
;
128 return GetBaseDomainInternal(host
, aAdditionalParts
+ 1, aBaseDomain
);
131 // External function for dealing with a host string directly: finds the public
132 // suffix (e.g. co.uk) for the given hostname. See GetBaseDomainInternal().
134 nsEffectiveTLDService::GetPublicSuffixFromHost(const nsACString
&aHostname
,
135 nsACString
&aPublicSuffix
)
137 // Create a mutable copy of the hostname and normalize it to ACE.
138 // This will fail if the hostname includes invalid characters.
139 nsAutoCString
normHostname(aHostname
);
140 nsresult rv
= NormalizeHostname(normHostname
);
141 if (NS_FAILED(rv
)) return rv
;
143 return GetBaseDomainInternal(normHostname
, 0, aPublicSuffix
);
146 // External function for dealing with a host string directly: finds the base
147 // domain (e.g. www.co.uk) for the given hostname and number of subdomain parts
148 // requested. See GetBaseDomainInternal().
150 nsEffectiveTLDService::GetBaseDomainFromHost(const nsACString
&aHostname
,
151 uint32_t aAdditionalParts
,
152 nsACString
&aBaseDomain
)
154 // Create a mutable copy of the hostname and normalize it to ACE.
155 // This will fail if the hostname includes invalid characters.
156 nsAutoCString
normHostname(aHostname
);
157 nsresult rv
= NormalizeHostname(normHostname
);
158 if (NS_FAILED(rv
)) return rv
;
160 return GetBaseDomainInternal(normHostname
, aAdditionalParts
+ 1, aBaseDomain
);
163 // Finds the base domain for a host, with requested number of additional parts.
164 // This will fail, generating an error, if the host is an IPv4/IPv6 address,
165 // if more subdomain parts are requested than are available, or if the hostname
166 // includes characters that are not valid in a URL. Normalization is performed
167 // on the host string and the result will be in UTF8.
169 nsEffectiveTLDService::GetBaseDomainInternal(nsCString
&aHostname
,
170 uint32_t aAdditionalParts
,
171 nsACString
&aBaseDomain
)
173 if (aHostname
.IsEmpty())
174 return NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS
;
176 // chomp any trailing dot, and keep track of it for later
177 bool trailingDot
= aHostname
.Last() == '.';
179 aHostname
.Truncate(aHostname
.Length() - 1);
181 // check the edge cases of the host being '.' or having a second trailing '.',
182 // since subsequent checks won't catch it.
183 if (aHostname
.IsEmpty() || aHostname
.Last() == '.')
184 return NS_ERROR_INVALID_ARG
;
186 // Check if we're dealing with an IPv4/IPv6 hostname, and return
188 PRStatus result
= PR_StringToNetAddr(aHostname
.get(), &addr
);
189 if (result
== PR_SUCCESS
)
190 return NS_ERROR_HOST_IS_IP_ADDRESS
;
192 // Walk up the domain tree, most specific to least specific,
193 // looking for matches at each level. Note that a given level may
194 // have multiple attributes (e.g. IsWild() and IsNormal()).
195 const char *prevDomain
= nullptr;
196 const char *currDomain
= aHostname
.get();
197 const char *nextDot
= strchr(currDomain
, '.');
198 const char *end
= currDomain
+ aHostname
.Length();
199 const char *eTLD
= currDomain
;
201 // sanity check the string we're about to look up: it should not begin with
202 // a '.'; this would mean the hostname began with a '.' or had an
203 // embedded '..' sequence.
204 if (*currDomain
== '.')
205 return NS_ERROR_INVALID_ARG
;
207 // perform the hash lookup.
208 nsDomainEntry
*entry
= mHash
.GetEntry(currDomain
);
210 if (entry
->IsWild() && prevDomain
) {
211 // wildcard rules imply an eTLD one level inferior to the match.
215 } else if (entry
->IsNormal() || !nextDot
) {
216 // specific match, or we've hit the top domain level
220 } else if (entry
->IsException()) {
221 // exception rules imply an eTLD one level superior to the match.
228 // we've hit the top domain level; use it by default.
233 prevDomain
= currDomain
;
234 currDomain
= nextDot
+ 1;
235 nextDot
= strchr(currDomain
, '.');
238 // count off the number of requested domains.
239 const char *begin
= aHostname
.get();
240 const char *iter
= eTLD
;
245 if (*(--iter
) == '.' && aAdditionalParts
-- == 0) {
252 if (aAdditionalParts
!= 0)
253 return NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS
;
255 aBaseDomain
= Substring(iter
, end
);
256 // add on the trailing dot, if applicable
258 aBaseDomain
.Append('.');
263 // Normalizes the given hostname, component by component. ASCII/ACE
264 // components are lower-cased, and UTF-8 components are normalized per
265 // RFC 3454 and converted to ACE.
267 nsEffectiveTLDService::NormalizeHostname(nsCString
&aHostname
)
269 if (!IsASCII(aHostname
)) {
270 nsresult rv
= mIDNService
->ConvertUTF8toACE(aHostname
, aHostname
);
275 ToLowerCase(aHostname
);