1 /* vim:set ts=4 sw=4 sts=4 et cin: */
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 #include "nsDNSService2.h"
7 #include "nsIDNSRecord.h"
8 #include "nsIDNSListener.h"
9 #include "nsICancelable.h"
10 #include "nsIPrefService.h"
11 #include "nsIPrefBranch.h"
12 #include "nsIServiceManager.h"
13 #include "nsProxyRelease.h"
14 #include "nsReadableUtils.h"
16 #include "nsAutoPtr.h"
19 #include "nsDNSPrefetch.h"
20 #include "nsThreadUtils.h"
21 #include "nsIProtocolProxyService.h"
27 #include "nsIOService.h"
28 #include "nsCharSeparatedTokenizer.h"
30 #include "mozilla/Attributes.h"
32 using namespace mozilla
;
34 static const char kPrefDnsCacheEntries
[] = "network.dnsCacheEntries";
35 static const char kPrefDnsCacheExpiration
[] = "network.dnsCacheExpiration";
36 static const char kPrefDnsCacheGrace
[] = "network.dnsCacheExpirationGracePeriod";
37 static const char kPrefEnableIDN
[] = "network.enableIDN";
38 static const char kPrefIPv4OnlyDomains
[] = "network.dns.ipv4OnlyDomains";
39 static const char kPrefDisableIPv6
[] = "network.dns.disableIPv6";
40 static const char kPrefDisablePrefetch
[] = "network.dns.disablePrefetch";
41 static const char kPrefDnsLocalDomains
[] = "network.dns.localDomains";
43 //-----------------------------------------------------------------------------
45 class nsDNSRecord
: public nsIDNSRecord
51 nsDNSRecord(nsHostRecord
*hostRecord
)
52 : mHostRecord(hostRecord
)
59 virtual ~nsDNSRecord() {}
61 nsRefPtr
<nsHostRecord
> mHostRecord
;
62 void *mIter
; // enum ptr for PR_EnumerateAddrInfo
63 void *mLastIter
; // previous enum ptr, for use in
64 // getting addrinfo in ReportUnusable
65 int mIterGenCnt
; // the generation count of
66 // mHostRecord->addr_info when we
71 NS_IMPL_THREADSAFE_ISUPPORTS1(nsDNSRecord
, nsIDNSRecord
)
74 nsDNSRecord::GetCanonicalName(nsACString
&result
)
76 // this method should only be called if we have a CNAME
77 NS_ENSURE_TRUE(mHostRecord
->flags
& nsHostResolver::RES_CANON_NAME
,
78 NS_ERROR_NOT_AVAILABLE
);
80 // if the record is for an IP address literal, then the canonical
81 // host name is the IP address literal.
84 MutexAutoLock
lock(mHostRecord
->addr_info_lock
);
85 if (mHostRecord
->addr_info
)
86 cname
= PR_GetCanonNameFromAddrInfo(mHostRecord
->addr_info
);
88 cname
= mHostRecord
->host
;
95 nsDNSRecord::GetNextAddr(uint16_t port
, PRNetAddr
*addr
)
97 // not a programming error to poke the DNS record when it has no more
98 // entries. just fail without any debug warnings. this enables consumers
99 // to enumerate the DNS record without calling HasMore.
101 return NS_ERROR_NOT_AVAILABLE
;
103 mHostRecord
->addr_info_lock
.Lock();
104 bool startedFresh
= !mIter
;
106 if (mHostRecord
->addr_info
) {
108 mIterGenCnt
= mHostRecord
->addr_info_gencnt
;
109 else if (mIterGenCnt
!= mHostRecord
->addr_info_gencnt
) {
110 // mHostRecord->addr_info has changed, so mIter is invalid.
111 // Restart the iteration. Alternatively, we could just fail.
113 mIterGenCnt
= mHostRecord
->addr_info_gencnt
;
119 mIter
= PR_EnumerateAddrInfo(mIter
, mHostRecord
->addr_info
,
122 while (mIter
&& mHostRecord
->Blacklisted(addr
));
124 if (startedFresh
&& !mIter
) {
125 // if everything was blacklisted we want to reset the blacklist (and
126 // likely relearn it) and return the first address. That is better
128 mHostRecord
->ResetBlacklist();
130 mIter
= PR_EnumerateAddrInfo(nullptr, mHostRecord
->addr_info
,
134 mHostRecord
->addr_info_lock
.Unlock();
137 return NS_ERROR_NOT_AVAILABLE
;
141 mHostRecord
->addr_info_lock
.Unlock();
142 if (!mHostRecord
->addr
) {
143 // Both mHostRecord->addr_info and mHostRecord->addr are null.
144 // This can happen if mHostRecord->addr_info expired and the
145 // attempt to reresolve it failed.
146 return NS_ERROR_NOT_AVAILABLE
;
148 memcpy(addr
, mHostRecord
->addr
, sizeof(PRNetAddr
));
150 port
= PR_htons(port
);
151 if (addr
->raw
.family
== PR_AF_INET
)
152 addr
->inet
.port
= port
;
154 addr
->ipv6
.port
= port
;
155 mDone
= true; // no iterations
162 nsDNSRecord::GetNextAddrAsString(nsACString
&result
)
165 nsresult rv
= GetNextAddr(0, &addr
);
166 if (NS_FAILED(rv
)) return rv
;
169 if (PR_NetAddrToString(&addr
, buf
, sizeof(buf
)) == PR_SUCCESS
) {
173 NS_ERROR("PR_NetAddrToString failed unexpectedly");
174 return NS_ERROR_FAILURE
; // conversion failed for some reason
178 nsDNSRecord::HasMore(bool *result
)
183 // unfortunately, NSPR does not provide a way for us to determine if
184 // there is another address other than to simply get the next address.
185 void *iterCopy
= mIter
;
186 void *iterLastCopy
= mLastIter
;
188 *result
= NS_SUCCEEDED(GetNextAddr(0, &addr
));
189 mIter
= iterCopy
; // backup iterator
190 mLastIter
= iterLastCopy
; // backup iterator
197 nsDNSRecord::Rewind()
207 nsDNSRecord::ReportUnusable(uint16_t aPort
)
209 // right now we don't use the port in the blacklist
211 mHostRecord
->addr_info_lock
.Lock();
213 // Check that we are using a real addr_info (as opposed to a single
214 // constant address), and that the generation count is valid. Otherwise,
215 // ignore the report.
217 if (mHostRecord
->addr_info
&&
218 mIterGenCnt
== mHostRecord
->addr_info_gencnt
) {
220 void *id
= PR_EnumerateAddrInfo(mLastIter
, mHostRecord
->addr_info
,
223 mHostRecord
->ReportUnusable(&addr
);
226 mHostRecord
->addr_info_lock
.Unlock();
230 //-----------------------------------------------------------------------------
232 class nsDNSAsyncRequest MOZ_FINAL
: public nsResolveHostCallback
233 , public nsICancelable
237 NS_DECL_NSICANCELABLE
239 nsDNSAsyncRequest(nsHostResolver
*res
,
240 const nsACString
&host
,
241 nsIDNSListener
*listener
,
246 , mListener(listener
)
249 ~nsDNSAsyncRequest() {}
251 void OnLookupComplete(nsHostResolver
*, nsHostRecord
*, nsresult
);
252 // Returns TRUE if the DNS listener arg is the same as the member listener
253 // Used in Cancellations to remove DNS requests associated with a
254 // particular hostname and nsIDNSListener
255 bool EqualsAsyncListener(nsIDNSListener
*aListener
);
257 nsRefPtr
<nsHostResolver
> mResolver
;
258 nsCString mHost
; // hostname we're resolving
259 nsCOMPtr
<nsIDNSListener
> mListener
;
265 nsDNSAsyncRequest::OnLookupComplete(nsHostResolver
*resolver
,
266 nsHostRecord
*hostRecord
,
269 // need to have an owning ref when we issue the callback to enable
270 // the caller to be able to addref/release multiple times without
271 // destroying the record prematurely.
272 nsCOMPtr
<nsIDNSRecord
> rec
;
273 if (NS_SUCCEEDED(status
)) {
274 NS_ASSERTION(hostRecord
, "no host record");
275 rec
= new nsDNSRecord(hostRecord
);
277 status
= NS_ERROR_OUT_OF_MEMORY
;
280 mListener
->OnLookupComplete(this, rec
, status
);
283 // release the reference to ourselves that was added before we were
284 // handed off to the host resolver.
289 nsDNSAsyncRequest::EqualsAsyncListener(nsIDNSListener
*aListener
)
291 return (aListener
== mListener
);
294 NS_IMPL_THREADSAFE_ISUPPORTS1(nsDNSAsyncRequest
, nsICancelable
)
297 nsDNSAsyncRequest::Cancel(nsresult reason
)
299 NS_ENSURE_ARG(NS_FAILED(reason
));
300 mResolver
->DetachCallback(mHost
.get(), mFlags
, mAF
, this, reason
);
304 //-----------------------------------------------------------------------------
306 class nsDNSSyncRequest
: public nsResolveHostCallback
309 nsDNSSyncRequest(PRMonitor
*mon
)
313 virtual ~nsDNSSyncRequest() {}
315 void OnLookupComplete(nsHostResolver
*, nsHostRecord
*, nsresult
);
316 bool EqualsAsyncListener(nsIDNSListener
*aListener
);
320 nsRefPtr
<nsHostRecord
> mHostRecord
;
327 nsDNSSyncRequest::OnLookupComplete(nsHostResolver
*resolver
,
328 nsHostRecord
*hostRecord
,
331 // store results, and wake up nsDNSService::Resolve to process results.
332 PR_EnterMonitor(mMonitor
);
335 mHostRecord
= hostRecord
;
337 PR_ExitMonitor(mMonitor
);
341 nsDNSSyncRequest::EqualsAsyncListener(nsIDNSListener
*aListener
)
343 // Sync request: no listener to compare
347 //-----------------------------------------------------------------------------
349 nsDNSService::nsDNSService()
350 : mLock("nsDNSServer.mLock")
356 nsDNSService::~nsDNSService()
360 NS_IMPL_THREADSAFE_ISUPPORTS3(nsDNSService
, nsIDNSService
, nsPIDNSService
,
368 NS_ENSURE_TRUE(!mResolver
, NS_ERROR_ALREADY_INITIALIZED
);
371 uint32_t maxCacheEntries
= 400;
372 uint32_t maxCacheLifetime
= 2; // minutes
373 uint32_t lifetimeGracePeriod
= 1;
374 bool enableIDN
= true;
375 bool disableIPv6
= false;
376 bool disablePrefetch
= false;
377 int proxyType
= nsIProtocolProxyService::PROXYCONFIG_DIRECT
;
379 nsAdoptingCString ipv4OnlyDomains
;
380 nsAdoptingCString localDomains
;
383 nsCOMPtr
<nsIPrefBranch
> prefs
= do_GetService(NS_PREFSERVICE_CONTRACTID
);
386 if (NS_SUCCEEDED(prefs
->GetIntPref(kPrefDnsCacheEntries
, &val
)))
387 maxCacheEntries
= (uint32_t) val
;
388 if (NS_SUCCEEDED(prefs
->GetIntPref(kPrefDnsCacheExpiration
, &val
)))
389 maxCacheLifetime
= val
/ 60; // convert from seconds to minutes
390 if (NS_SUCCEEDED(prefs
->GetIntPref(kPrefDnsCacheGrace
, &val
)))
391 lifetimeGracePeriod
= val
/ 60; // convert from seconds to minutes
393 // ASSUMPTION: pref branch does not modify out params on failure
394 prefs
->GetBoolPref(kPrefEnableIDN
, &enableIDN
);
395 prefs
->GetBoolPref(kPrefDisableIPv6
, &disableIPv6
);
396 prefs
->GetCharPref(kPrefIPv4OnlyDomains
, getter_Copies(ipv4OnlyDomains
));
397 prefs
->GetCharPref(kPrefDnsLocalDomains
, getter_Copies(localDomains
));
398 prefs
->GetBoolPref(kPrefDisablePrefetch
, &disablePrefetch
);
400 // If a manual proxy is in use, disable prefetch implicitly
401 prefs
->GetIntPref("network.proxy.type", &proxyType
);
407 mLocalDomains
.Init();
409 // register as prefs observer
411 prefs
->AddObserver(kPrefDnsCacheEntries
, this, false);
412 prefs
->AddObserver(kPrefDnsCacheExpiration
, this, false);
413 prefs
->AddObserver(kPrefDnsCacheGrace
, this, false);
414 prefs
->AddObserver(kPrefEnableIDN
, this, false);
415 prefs
->AddObserver(kPrefIPv4OnlyDomains
, this, false);
416 prefs
->AddObserver(kPrefDnsLocalDomains
, this, false);
417 prefs
->AddObserver(kPrefDisableIPv6
, this, false);
418 prefs
->AddObserver(kPrefDisablePrefetch
, this, false);
420 // Monitor these to see if there is a change in proxy configuration
421 // If a manual proxy is in use, disable prefetch implicitly
422 prefs
->AddObserver("network.proxy.type", this, false);
426 // we have to null out mIDN since we might be getting re-initialized
427 // as a result of a pref change.
428 nsCOMPtr
<nsIIDNService
> idn
;
430 idn
= do_GetService(NS_IDNSERVICE_CONTRACTID
);
432 nsDNSPrefetch::Initialize(this);
434 // Don't initialize the resolver if we're in offline mode.
435 // Later on, the IO service will reinitialize us when going online.
436 if (gIOService
->IsOffline() && !gIOService
->IsComingOnline())
439 nsRefPtr
<nsHostResolver
> res
;
440 nsresult rv
= nsHostResolver::Create(maxCacheEntries
,
443 getter_AddRefs(res
));
444 if (NS_SUCCEEDED(rv
)) {
445 // now, set all of our member variables while holding the lock
446 MutexAutoLock
lock(mLock
);
449 mIPv4OnlyDomains
= ipv4OnlyDomains
; // exchanges buffer ownership
450 mDisableIPv6
= disableIPv6
;
452 // Disable prefetching either by explicit preference or if a manual proxy is configured
453 mDisablePrefetch
= disablePrefetch
|| (proxyType
== nsIProtocolProxyService::PROXYCONFIG_MANUAL
);
455 mLocalDomains
.Clear();
457 nsAdoptingString domains
;
458 domains
.AssignASCII(nsDependentCString(localDomains
).get());
459 nsCharSeparatedTokenizer
tokenizer(domains
, ',',
460 nsCharSeparatedTokenizerTemplate
<>::SEPARATOR_OPTIONAL
);
462 while (tokenizer
.hasMoreTokens()) {
463 const nsSubstring
& domain
= tokenizer
.nextToken();
464 mLocalDomains
.PutEntry(nsDependentCString(NS_ConvertUTF16toUTF8(domain
).get()));
472 nsDNSService::Shutdown()
474 nsRefPtr
<nsHostResolver
> res
;
476 MutexAutoLock
lock(mLock
);
486 nsDNSService::GetOffline(bool *offline
)
493 nsDNSService::SetOffline(bool offline
)
500 nsDNSService::GetPrefetchEnabled(bool *outVal
)
502 *outVal
= !mDisablePrefetch
;
507 nsDNSService::SetPrefetchEnabled(bool inVal
)
509 mDisablePrefetch
= !inVal
;
515 class DNSListenerProxy MOZ_FINAL
: public nsIDNSListener
518 DNSListenerProxy(nsIDNSListener
* aListener
, nsIEventTarget
* aTargetThread
)
519 : mListener(aListener
)
520 , mTargetThread(aTargetThread
)
525 nsCOMPtr
<nsIThread
> mainThread(do_GetMainThread());
526 NS_ProxyRelease(mainThread
, mListener
);
530 NS_DECL_NSIDNSLISTENER
532 class OnLookupCompleteRunnable
: public nsRunnable
535 OnLookupCompleteRunnable(nsIDNSListener
* aListener
,
536 nsICancelable
* aRequest
,
537 nsIDNSRecord
* aRecord
,
539 : mListener(aListener
)
545 ~OnLookupCompleteRunnable()
547 nsCOMPtr
<nsIThread
> mainThread(do_GetMainThread());
548 NS_ProxyRelease(mainThread
, mListener
);
554 nsCOMPtr
<nsIDNSListener
> mListener
;
555 nsCOMPtr
<nsICancelable
> mRequest
;
556 nsCOMPtr
<nsIDNSRecord
> mRecord
;
561 nsCOMPtr
<nsIDNSListener
> mListener
;
562 nsCOMPtr
<nsIEventTarget
> mTargetThread
;
565 NS_IMPL_THREADSAFE_ISUPPORTS1(DNSListenerProxy
, nsIDNSListener
)
568 DNSListenerProxy::OnLookupComplete(nsICancelable
* aRequest
,
569 nsIDNSRecord
* aRecord
,
572 nsRefPtr
<OnLookupCompleteRunnable
> r
=
573 new OnLookupCompleteRunnable(mListener
, aRequest
, aRecord
, aStatus
);
574 return mTargetThread
->Dispatch(r
, NS_DISPATCH_NORMAL
);
578 DNSListenerProxy::OnLookupCompleteRunnable::Run()
580 mListener
->OnLookupComplete(mRequest
, mRecord
, mStatus
);
584 } // anonymous namespace
587 nsDNSService::AsyncResolve(const nsACString
&hostname
,
589 nsIDNSListener
*listener
,
590 nsIEventTarget
*target
,
591 nsICancelable
**result
)
593 // grab reference to global host resolver and IDN service. beware
594 // simultaneous shutdown!!
595 nsRefPtr
<nsHostResolver
> res
;
596 nsCOMPtr
<nsIIDNService
> idn
;
597 bool localDomain
= false;
599 MutexAutoLock
lock(mLock
);
601 if (mDisablePrefetch
&& (flags
& RESOLVE_SPECULATE
))
602 return NS_ERROR_DNS_LOOKUP_QUEUE_FULL
;
606 localDomain
= mLocalDomains
.GetEntry(hostname
);
609 return NS_ERROR_OFFLINE
;
612 flags
|= RESOLVE_OFFLINE
;
614 const nsACString
*hostPtr
= &hostname
;
617 hostPtr
= &(NS_LITERAL_CSTRING("localhost"));
621 nsAutoCString hostACE
;
622 if (idn
&& !IsASCII(*hostPtr
)) {
623 if (NS_SUCCEEDED(idn
->ConvertUTF8toACE(*hostPtr
, hostACE
)))
628 listener
= new DNSListenerProxy(listener
, target
);
631 uint16_t af
= GetAFForLookup(*hostPtr
, flags
);
633 nsDNSAsyncRequest
*req
=
634 new nsDNSAsyncRequest(res
, *hostPtr
, listener
, flags
, af
);
636 return NS_ERROR_OUT_OF_MEMORY
;
637 NS_ADDREF(*result
= req
);
639 // addref for resolver; will be released when OnLookupComplete is called.
641 rv
= res
->ResolveHost(req
->mHost
.get(), flags
, af
, req
);
650 nsDNSService::CancelAsyncResolve(const nsACString
&aHostname
,
652 nsIDNSListener
*aListener
,
655 // grab reference to global host resolver and IDN service. beware
656 // simultaneous shutdown!!
657 nsRefPtr
<nsHostResolver
> res
;
658 nsCOMPtr
<nsIIDNService
> idn
;
660 MutexAutoLock
lock(mLock
);
662 if (mDisablePrefetch
&& (aFlags
& RESOLVE_SPECULATE
))
663 return NS_ERROR_DNS_LOOKUP_QUEUE_FULL
;
669 return NS_ERROR_OFFLINE
;
671 nsCString
hostname(aHostname
);
673 nsAutoCString hostACE
;
674 if (idn
&& !IsASCII(aHostname
)) {
675 if (NS_SUCCEEDED(idn
->ConvertUTF8toACE(aHostname
, hostACE
)))
679 uint16_t af
= GetAFForLookup(hostname
, aFlags
);
681 res
->CancelAsyncRequest(hostname
.get(), aFlags
, af
, aListener
, aReason
);
686 nsDNSService::Resolve(const nsACString
&hostname
,
688 nsIDNSRecord
**result
)
690 NS_WARNING("Do not use synchronous DNS resolution! This API may be removed soon.");
692 // We will not allow this to be called on the main thread. This is transitional
693 // and a bit of a test for removing the synchronous API entirely.
694 if (NS_IsMainThread()) {
695 NS_ERROR("Synchronous DNS resolve failing - not allowed on the main thread!");
696 return NS_ERROR_FAILURE
;
699 // grab reference to global host resolver and IDN service. beware
700 // simultaneous shutdown!!
701 nsRefPtr
<nsHostResolver
> res
;
702 nsCOMPtr
<nsIIDNService
> idn
;
703 bool localDomain
= false;
705 MutexAutoLock
lock(mLock
);
708 localDomain
= mLocalDomains
.GetEntry(hostname
);
710 NS_ENSURE_TRUE(res
, NS_ERROR_OFFLINE
);
713 flags
|= RESOLVE_OFFLINE
;
715 const nsACString
*hostPtr
= &hostname
;
718 hostPtr
= &(NS_LITERAL_CSTRING("localhost"));
722 nsAutoCString hostACE
;
723 if (idn
&& !IsASCII(*hostPtr
)) {
724 if (NS_SUCCEEDED(idn
->ConvertUTF8toACE(*hostPtr
, hostACE
)))
729 // sync resolve: since the host resolver only works asynchronously, we need
730 // to use a mutex and a condvar to wait for the result. however, since the
731 // result may be in the resolvers cache, we might get called back recursively
732 // on the same thread. so, our mutex needs to be re-entrant. in other words,
733 // we need to use a monitor! ;-)
736 PRMonitor
*mon
= PR_NewMonitor();
738 return NS_ERROR_OUT_OF_MEMORY
;
740 PR_EnterMonitor(mon
);
741 nsDNSSyncRequest
syncReq(mon
);
743 uint16_t af
= GetAFForLookup(*hostPtr
, flags
);
745 rv
= res
->ResolveHost(PromiseFlatCString(*hostPtr
).get(), flags
, af
, &syncReq
);
746 if (NS_SUCCEEDED(rv
)) {
748 while (!syncReq
.mDone
)
749 PR_Wait(mon
, PR_INTERVAL_NO_TIMEOUT
);
751 if (NS_FAILED(syncReq
.mStatus
))
752 rv
= syncReq
.mStatus
;
754 NS_ASSERTION(syncReq
.mHostRecord
, "no host record");
755 nsDNSRecord
*rec
= new nsDNSRecord(syncReq
.mHostRecord
);
757 rv
= NS_ERROR_OUT_OF_MEMORY
;
759 NS_ADDREF(*result
= rec
);
764 PR_DestroyMonitor(mon
);
769 nsDNSService::GetMyHostName(nsACString
&result
)
772 if (PR_GetSystemInfo(PR_SI_HOSTNAME
, name
, sizeof(name
)) == PR_SUCCESS
) {
776 return NS_ERROR_FAILURE
;
780 nsDNSService::Observe(nsISupports
*subject
, const char *topic
, const PRUnichar
*data
)
782 // we are only getting called if a preference has changed.
783 NS_ASSERTION(strcmp(topic
, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID
) == 0,
784 "unexpected observe call");
787 // Shutdown and this function are both only called on the UI thread, so we don't
788 // have to worry about mResolver being cleared out from under us.
790 // NOTE Shutting down and reinitializing the service like this is obviously
791 // suboptimal if Observe gets called several times in a row, but we don't
792 // expect that to be the case.
803 nsDNSService::GetAFForLookup(const nsACString
&host
, uint32_t flags
)
805 if (mDisableIPv6
|| (flags
& RESOLVE_DISABLE_IPV6
))
808 MutexAutoLock
lock(mLock
);
810 uint16_t af
= PR_AF_UNSPEC
;
812 if (!mIPv4OnlyDomains
.IsEmpty()) {
813 const char *domain
, *domainEnd
, *end
;
814 uint32_t hostLen
, domainLen
;
816 // see if host is in one of the IPv4-only domains
817 domain
= mIPv4OnlyDomains
.BeginReading();
818 domainEnd
= mIPv4OnlyDomains
.EndReading();
820 nsACString::const_iterator hostStart
;
821 host
.BeginReading(hostStart
);
822 hostLen
= host
.Length();
825 // skip any whitespace
826 while (*domain
== ' ' || *domain
== '\t')
829 // find end of this domain in the string
830 end
= strchr(domain
, ',');
834 // to see if the hostname is in the domain, check if the domain
835 // matches the end of the hostname.
836 domainLen
= end
- domain
;
837 if (domainLen
&& hostLen
>= domainLen
) {
838 const char *hostTail
= hostStart
.get() + hostLen
- domainLen
;
839 if (PL_strncasecmp(domain
, hostTail
, domainLen
) == 0) {
840 // now, make sure either that the hostname is a direct match or
841 // that the hostname begins with a dot.
842 if (hostLen
== domainLen
||
843 *hostTail
== '.' || *(hostTail
- 1) == '.') {