import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo

This commit is contained in:
Roy Tam 2018-01-19 03:59:58 +08:00
commit dcd9973243
150858 changed files with 23884658 additions and 0 deletions

View file

@ -0,0 +1,330 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/net/ChildDNSService.h"
#include "nsIDNSListener.h"
#include "nsIIOService.h"
#include "nsIThread.h"
#include "nsThreadUtils.h"
#include "nsIXPConnect.h"
#include "nsIPrefService.h"
#include "nsIProtocolProxyService.h"
#include "nsNetCID.h"
#include "mozilla/net/NeckoChild.h"
#include "mozilla/net/DNSListenerProxy.h"
#include "nsServiceManagerUtils.h"
namespace mozilla {
namespace net {
//-----------------------------------------------------------------------------
// ChildDNSService
//-----------------------------------------------------------------------------
static ChildDNSService *gChildDNSService;
static const char kPrefNameDisablePrefetch[] = "network.dns.disablePrefetch";
ChildDNSService* ChildDNSService::GetSingleton()
{
MOZ_ASSERT(IsNeckoChild());
if (!gChildDNSService) {
gChildDNSService = new ChildDNSService();
}
NS_ADDREF(gChildDNSService);
return gChildDNSService;
}
NS_IMPL_ISUPPORTS(ChildDNSService,
nsIDNSService,
nsPIDNSService,
nsIObserver)
ChildDNSService::ChildDNSService()
: mFirstTime(true)
, mDisablePrefetch(false)
, mPendingRequestsLock("DNSPendingRequestsLock")
{
MOZ_ASSERT(IsNeckoChild());
}
ChildDNSService::~ChildDNSService()
{
}
void
ChildDNSService::GetDNSRecordHashKey(const nsACString &aHost,
uint32_t aFlags,
const nsACString &aNetworkInterface,
nsIDNSListener* aListener,
nsACString &aHashKey)
{
aHashKey.Assign(aHost);
aHashKey.AppendInt(aFlags);
if (!aNetworkInterface.IsEmpty()) {
aHashKey.Append(aNetworkInterface);
}
aHashKey.AppendPrintf("%p", aListener);
}
//-----------------------------------------------------------------------------
// ChildDNSService::nsIDNSService
//-----------------------------------------------------------------------------
NS_IMETHODIMP
ChildDNSService::AsyncResolve(const nsACString &hostname,
uint32_t flags,
nsIDNSListener *listener,
nsIEventTarget *target_,
nsICancelable **result)
{
return AsyncResolveExtended(hostname, flags, EmptyCString(), listener,
target_, result);
}
NS_IMETHODIMP
ChildDNSService::AsyncResolveExtended(const nsACString &hostname,
uint32_t flags,
const nsACString &aNetworkInterface,
nsIDNSListener *listener,
nsIEventTarget *target_,
nsICancelable **result)
{
NS_ENSURE_TRUE(gNeckoChild != nullptr, NS_ERROR_FAILURE);
if (mDisablePrefetch && (flags & RESOLVE_SPECULATE)) {
return NS_ERROR_DNS_LOOKUP_QUEUE_FULL;
}
// We need original flags for the pending requests hash.
uint32_t originalFlags = flags;
// Support apps being 'offline' even if parent is not: avoids DNS traffic by
// apps that have been told they are offline.
if (GetOffline()) {
flags |= RESOLVE_OFFLINE;
}
// We need original listener for the pending requests hash.
nsIDNSListener *originalListener = listener;
// make sure JS callers get notification on the main thread
nsCOMPtr<nsIEventTarget> target = target_;
nsCOMPtr<nsIXPConnectWrappedJS> wrappedListener = do_QueryInterface(listener);
if (wrappedListener && !target) {
nsCOMPtr<nsIThread> mainThread;
NS_GetMainThread(getter_AddRefs(mainThread));
target = do_QueryInterface(mainThread);
}
if (target) {
// Guarantee listener freed on main thread. Not sure we need this in child
// (or in parent in nsDNSService.cpp) but doesn't hurt.
listener = new DNSListenerProxy(listener, target);
}
RefPtr<DNSRequestChild> childReq =
new DNSRequestChild(nsCString(hostname), flags,
nsCString(aNetworkInterface),
listener, target);
{
MutexAutoLock lock(mPendingRequestsLock);
nsCString key;
GetDNSRecordHashKey(hostname, originalFlags, aNetworkInterface,
originalListener, key);
nsTArray<RefPtr<DNSRequestChild>> *hashEntry;
if (mPendingRequests.Get(key, &hashEntry)) {
hashEntry->AppendElement(childReq);
} else {
hashEntry = new nsTArray<RefPtr<DNSRequestChild>>();
hashEntry->AppendElement(childReq);
mPendingRequests.Put(key, hashEntry);
}
}
childReq->StartRequest();
childReq.forget(result);
return NS_OK;
}
NS_IMETHODIMP
ChildDNSService::CancelAsyncResolve(const nsACString &aHostname,
uint32_t aFlags,
nsIDNSListener *aListener,
nsresult aReason)
{
return CancelAsyncResolveExtended(aHostname, aFlags, EmptyCString(),
aListener, aReason);
}
NS_IMETHODIMP
ChildDNSService::CancelAsyncResolveExtended(const nsACString &aHostname,
uint32_t aFlags,
const nsACString &aNetworkInterface,
nsIDNSListener *aListener,
nsresult aReason)
{
if (mDisablePrefetch && (aFlags & RESOLVE_SPECULATE)) {
return NS_ERROR_DNS_LOOKUP_QUEUE_FULL;
}
MutexAutoLock lock(mPendingRequestsLock);
nsTArray<RefPtr<DNSRequestChild>> *hashEntry;
nsCString key;
GetDNSRecordHashKey(aHostname, aFlags, aNetworkInterface, aListener, key);
if (mPendingRequests.Get(key, &hashEntry)) {
// We cancel just one.
hashEntry->ElementAt(0)->Cancel(aReason);
}
return NS_OK;
}
NS_IMETHODIMP
ChildDNSService::Resolve(const nsACString &hostname,
uint32_t flags,
nsIDNSRecord **result)
{
// not planning to ever support this, since sync IPDL is evil.
return NS_ERROR_NOT_AVAILABLE;
}
NS_IMETHODIMP
ChildDNSService::GetDNSCacheEntries(nsTArray<mozilla::net::DNSCacheEntries> *args)
{
// Only used by networking dashboard, so may not ever need this in child.
// (and would provide a way to spy on what hosts other apps are connecting to,
// unless we start keeping per-app DNS caches).
return NS_ERROR_NOT_AVAILABLE;
}
NS_IMETHODIMP
ChildDNSService::GetMyHostName(nsACString &result)
{
// TODO: get value from parent during PNecko construction?
return NS_ERROR_NOT_AVAILABLE;
}
void
ChildDNSService::NotifyRequestDone(DNSRequestChild *aDnsRequest)
{
// We need the original flags and listener for the pending requests hash.
uint32_t originalFlags = aDnsRequest->mFlags & ~RESOLVE_OFFLINE;
nsCOMPtr<nsIDNSListener> originalListener = aDnsRequest->mListener;
nsCOMPtr<nsIDNSListenerProxy> wrapper = do_QueryInterface(originalListener);
if (wrapper) {
wrapper->GetOriginalListener(getter_AddRefs(originalListener));
if (NS_WARN_IF(!originalListener)) {
MOZ_ASSERT(originalListener);
return;
}
}
MutexAutoLock lock(mPendingRequestsLock);
nsCString key;
GetDNSRecordHashKey(aDnsRequest->mHost, originalFlags,
aDnsRequest->mNetworkInterface, originalListener, key);
nsTArray<RefPtr<DNSRequestChild>> *hashEntry;
if (mPendingRequests.Get(key, &hashEntry)) {
int idx;
if ((idx = hashEntry->IndexOf(aDnsRequest))) {
hashEntry->RemoveElementAt(idx);
if (hashEntry->IsEmpty()) {
mPendingRequests.Remove(key);
}
}
}
}
//-----------------------------------------------------------------------------
// ChildDNSService::nsPIDNSService
//-----------------------------------------------------------------------------
nsresult
ChildDNSService::Init()
{
// Disable prefetching either by explicit preference or if a manual proxy
// is configured
bool disablePrefetch = false;
int proxyType = nsIProtocolProxyService::PROXYCONFIG_DIRECT;
nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
if (prefs) {
prefs->GetIntPref("network.proxy.type", &proxyType);
prefs->GetBoolPref(kPrefNameDisablePrefetch, &disablePrefetch);
}
if (mFirstTime) {
mFirstTime = false;
if (prefs) {
prefs->AddObserver(kPrefNameDisablePrefetch, this, false);
// Monitor these to see if there is a change in proxy configuration
// If a manual proxy is in use, disable prefetch implicitly
prefs->AddObserver("network.proxy.type", this, false);
}
}
mDisablePrefetch = disablePrefetch ||
(proxyType == nsIProtocolProxyService::PROXYCONFIG_MANUAL);
return NS_OK;
}
nsresult
ChildDNSService::Shutdown()
{
return NS_OK;
}
NS_IMETHODIMP
ChildDNSService::GetPrefetchEnabled(bool *outVal)
{
*outVal = !mDisablePrefetch;
return NS_OK;
}
NS_IMETHODIMP
ChildDNSService::SetPrefetchEnabled(bool inVal)
{
mDisablePrefetch = !inVal;
return NS_OK;
}
bool
ChildDNSService::GetOffline() const
{
bool offline = false;
nsCOMPtr<nsIIOService> io = do_GetService(NS_IOSERVICE_CONTRACTID);
if (io) {
io->GetOffline(&offline);
}
return offline;
}
//-----------------------------------------------------------------------------
// ChildDNSService::nsIObserver
//-----------------------------------------------------------------------------
NS_IMETHODIMP
ChildDNSService::Observe(nsISupports *subject, const char *topic,
const char16_t *data)
{
// we are only getting called if a preference has changed.
NS_ASSERTION(strcmp(topic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) == 0,
"unexpected observe call");
// Reread prefs
Init();
return NS_OK;
}
} // namespace net
} // namespace mozilla

View file

@ -0,0 +1,60 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et tw=80 : */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_net_ChildDNSService_h
#define mozilla_net_ChildDNSService_h
#include "nsPIDNSService.h"
#include "nsIObserver.h"
#include "mozilla/Attributes.h"
#include "mozilla/Mutex.h"
#include "DNSRequestChild.h"
#include "nsHashKeys.h"
#include "nsClassHashtable.h"
namespace mozilla {
namespace net {
class ChildDNSService final
: public nsPIDNSService
, public nsIObserver
{
public:
// AsyncResolve (and CancelAsyncResolve) can be called off-main
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSPIDNSSERVICE
NS_DECL_NSIDNSSERVICE
NS_DECL_NSIOBSERVER
ChildDNSService();
static ChildDNSService* GetSingleton();
void NotifyRequestDone(DNSRequestChild *aDnsRequest);
bool GetOffline() const;
private:
virtual ~ChildDNSService();
void MOZ_ALWAYS_INLINE GetDNSRecordHashKey(const nsACString &aHost,
uint32_t aFlags,
const nsACString &aNetworkInterface,
nsIDNSListener* aListener,
nsACString &aHashKey);
bool mFirstTime;
bool mDisablePrefetch;
// We need to remember pending dns requests to be able to cancel them.
nsClassHashtable<nsCStringHashKey, nsTArray<RefPtr<DNSRequestChild>>> mPendingRequests;
Mutex mPendingRequestsLock;
};
} // namespace net
} // namespace mozilla
#endif // mozilla_net_ChildDNSService_h

368
netwerk/dns/DNS.cpp Normal file
View file

@ -0,0 +1,368 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/net/DNS.h"
#include "mozilla/Assertions.h"
#include "mozilla/mozalloc.h"
#include "mozilla/ArrayUtils.h"
#include <string.h>
#ifdef XP_WIN
#include "ws2tcpip.h"
#endif
namespace mozilla {
namespace net {
const char *inet_ntop_internal(int af, const void *src, char *dst, socklen_t size)
{
#ifdef XP_WIN
if (af == AF_INET) {
struct sockaddr_in s;
memset(&s, 0, sizeof(s));
s.sin_family = AF_INET;
memcpy(&s.sin_addr, src, sizeof(struct in_addr));
int result = getnameinfo((struct sockaddr *)&s, sizeof(struct sockaddr_in),
dst, size, nullptr, 0, NI_NUMERICHOST);
if (result == 0) {
return dst;
}
}
else if (af == AF_INET6) {
struct sockaddr_in6 s;
memset(&s, 0, sizeof(s));
s.sin6_family = AF_INET6;
memcpy(&s.sin6_addr, src, sizeof(struct in_addr6));
int result = getnameinfo((struct sockaddr *)&s, sizeof(struct sockaddr_in6),
dst, size, nullptr, 0, NI_NUMERICHOST);
if (result == 0) {
return dst;
}
}
return nullptr;
#else
return inet_ntop(af, src, dst, size);
#endif
}
// Copies the contents of a PRNetAddr to a NetAddr.
// Does not do a ptr safety check!
void PRNetAddrToNetAddr(const PRNetAddr *prAddr, NetAddr *addr)
{
if (prAddr->raw.family == PR_AF_INET) {
addr->inet.family = AF_INET;
addr->inet.port = prAddr->inet.port;
addr->inet.ip = prAddr->inet.ip;
}
else if (prAddr->raw.family == PR_AF_INET6) {
addr->inet6.family = AF_INET6;
addr->inet6.port = prAddr->ipv6.port;
addr->inet6.flowinfo = prAddr->ipv6.flowinfo;
memcpy(&addr->inet6.ip, &prAddr->ipv6.ip, sizeof(addr->inet6.ip.u8));
addr->inet6.scope_id = prAddr->ipv6.scope_id;
}
#if defined(XP_UNIX)
else if (prAddr->raw.family == PR_AF_LOCAL) {
addr->local.family = AF_LOCAL;
memcpy(addr->local.path, prAddr->local.path, sizeof(addr->local.path));
}
#endif
}
// Copies the contents of a NetAddr to a PRNetAddr.
// Does not do a ptr safety check!
void NetAddrToPRNetAddr(const NetAddr *addr, PRNetAddr *prAddr)
{
if (addr->raw.family == AF_INET) {
prAddr->inet.family = PR_AF_INET;
prAddr->inet.port = addr->inet.port;
prAddr->inet.ip = addr->inet.ip;
}
else if (addr->raw.family == AF_INET6) {
prAddr->ipv6.family = PR_AF_INET6;
prAddr->ipv6.port = addr->inet6.port;
prAddr->ipv6.flowinfo = addr->inet6.flowinfo;
memcpy(&prAddr->ipv6.ip, &addr->inet6.ip, sizeof(addr->inet6.ip.u8));
prAddr->ipv6.scope_id = addr->inet6.scope_id;
}
#if defined(XP_UNIX)
else if (addr->raw.family == AF_LOCAL) {
prAddr->local.family = PR_AF_LOCAL;
memcpy(prAddr->local.path, addr->local.path, sizeof(addr->local.path));
}
#elif defined(XP_WIN)
else if (addr->raw.family == AF_LOCAL) {
prAddr->local.family = PR_AF_LOCAL;
memcpy(prAddr->local.path, addr->local.path, sizeof(addr->local.path));
}
#endif
}
bool NetAddrToString(const NetAddr *addr, char *buf, uint32_t bufSize)
{
if (addr->raw.family == AF_INET) {
if (bufSize < INET_ADDRSTRLEN) {
return false;
}
struct in_addr nativeAddr = {};
nativeAddr.s_addr = addr->inet.ip;
return !!inet_ntop_internal(AF_INET, &nativeAddr, buf, bufSize);
}
else if (addr->raw.family == AF_INET6) {
if (bufSize < INET6_ADDRSTRLEN) {
return false;
}
struct in6_addr nativeAddr = {};
memcpy(&nativeAddr.s6_addr, &addr->inet6.ip, sizeof(addr->inet6.ip.u8));
return !!inet_ntop_internal(AF_INET6, &nativeAddr, buf, bufSize);
}
#if defined(XP_UNIX)
else if (addr->raw.family == AF_LOCAL) {
if (bufSize < sizeof(addr->local.path)) {
// Many callers don't bother checking our return value, so
// null-terminate just in case.
if (bufSize > 0) {
buf[0] = '\0';
}
return false;
}
// Usually, the size passed to memcpy should be the size of the
// destination. Here, we know that the source is no larger than the
// destination, so using the source's size is always safe, whereas
// using the destination's size may cause us to read off the end of the
// source.
memcpy(buf, addr->local.path, sizeof(addr->local.path));
return true;
}
#endif
return false;
}
bool IsLoopBackAddress(const NetAddr *addr)
{
if (addr->raw.family == AF_INET) {
return (addr->inet.ip == htonl(INADDR_LOOPBACK));
}
else if (addr->raw.family == AF_INET6) {
if (IPv6ADDR_IS_LOOPBACK(&addr->inet6.ip)) {
return true;
} else if (IPv6ADDR_IS_V4MAPPED(&addr->inet6.ip) &&
IPv6ADDR_V4MAPPED_TO_IPADDR(&addr->inet6.ip) == htonl(INADDR_LOOPBACK)) {
return true;
}
}
return false;
}
bool IsIPAddrAny(const NetAddr *addr)
{
if (addr->raw.family == AF_INET) {
if (addr->inet.ip == htonl(INADDR_ANY)) {
return true;
}
}
else if (addr->raw.family == AF_INET6) {
if (IPv6ADDR_IS_UNSPECIFIED(&addr->inet6.ip)) {
return true;
} else if (IPv6ADDR_IS_V4MAPPED(&addr->inet6.ip) &&
IPv6ADDR_V4MAPPED_TO_IPADDR(&addr->inet6.ip) == htonl(INADDR_ANY)) {
return true;
}
}
return false;
}
bool IsIPAddrV4Mapped(const NetAddr *addr)
{
if (addr->raw.family == AF_INET6) {
return IPv6ADDR_IS_V4MAPPED(&addr->inet6.ip);
}
return false;
}
bool IsIPAddrLocal(const NetAddr *addr)
{
MOZ_ASSERT(addr);
// IPv4 RFC1918 and Link Local Addresses.
if (addr->raw.family == AF_INET) {
uint32_t addr32 = ntohl(addr->inet.ip);
if (addr32 >> 24 == 0x0A || // 10/8 prefix (RFC 1918).
addr32 >> 20 == 0xAC1 || // 172.16/12 prefix (RFC 1918).
addr32 >> 16 == 0xC0A8 || // 192.168/16 prefix (RFC 1918).
addr32 >> 16 == 0xA9FE) { // 169.254/16 prefix (Link Local).
return true;
}
}
// IPv6 Unique and Link Local Addresses.
if (addr->raw.family == AF_INET6) {
uint16_t addr16 = ntohs(addr->inet6.ip.u16[0]);
if (addr16 >> 9 == 0xfc >> 1 || // fc00::/7 Unique Local Address.
addr16 >> 6 == 0xfe80 >> 6) { // fe80::/10 Link Local Address.
return true;
}
}
// Not an IPv4/6 local address.
return false;
}
nsresult
GetPort(const NetAddr *aAddr, uint16_t *aResult)
{
uint16_t port;
if (aAddr->raw.family == PR_AF_INET) {
port = aAddr->inet.port;
} else if (aAddr->raw.family == PR_AF_INET6) {
port = aAddr->inet6.port;
} else {
return NS_ERROR_NOT_INITIALIZED;
}
*aResult = ntohs(port);
return NS_OK;
}
bool
NetAddr::operator == (const NetAddr& other) const
{
if (this->raw.family != other.raw.family) {
return false;
} else if (this->raw.family == AF_INET) {
return (this->inet.port == other.inet.port) &&
(this->inet.ip == other.inet.ip);
} else if (this->raw.family == AF_INET6) {
return (this->inet6.port == other.inet6.port) &&
(this->inet6.flowinfo == other.inet6.flowinfo) &&
(memcmp(&this->inet6.ip, &other.inet6.ip,
sizeof(this->inet6.ip)) == 0) &&
(this->inet6.scope_id == other.inet6.scope_id);
#if defined(XP_UNIX)
} else if (this->raw.family == AF_LOCAL) {
return PL_strncmp(this->local.path, other.local.path,
ArrayLength(this->local.path));
#endif
}
return false;
}
bool
NetAddr::operator < (const NetAddr& other) const
{
if (this->raw.family != other.raw.family) {
return this->raw.family < other.raw.family;
} else if (this->raw.family == AF_INET) {
if (this->inet.ip == other.inet.ip) {
return this->inet.port < other.inet.port;
} else {
return this->inet.ip < other.inet.ip;
}
} else if (this->raw.family == AF_INET6) {
int cmpResult = memcmp(&this->inet6.ip, &other.inet6.ip,
sizeof(this->inet6.ip));
if (cmpResult) {
return cmpResult < 0;
} else if (this->inet6.port != other.inet6.port) {
return this->inet6.port < other.inet6.port;
} else {
return this->inet6.flowinfo < other.inet6.flowinfo;
}
}
return false;
}
NetAddrElement::NetAddrElement(const PRNetAddr *prNetAddr)
{
PRNetAddrToNetAddr(prNetAddr, &mAddress);
}
NetAddrElement::NetAddrElement(const NetAddrElement& netAddr)
{
mAddress = netAddr.mAddress;
}
NetAddrElement::~NetAddrElement() = default;
AddrInfo::AddrInfo(const char *host, const PRAddrInfo *prAddrInfo,
bool disableIPv4, bool filterNameCollision, const char *cname)
: mHostName(nullptr)
, mCanonicalName(nullptr)
, ttl(NO_TTL_DATA)
{
MOZ_ASSERT(prAddrInfo, "Cannot construct AddrInfo with a null prAddrInfo pointer!");
const uint32_t nameCollisionAddr = htonl(0x7f003535); // 127.0.53.53
Init(host, cname);
PRNetAddr tmpAddr;
void *iter = nullptr;
do {
iter = PR_EnumerateAddrInfo(iter, prAddrInfo, 0, &tmpAddr);
bool addIt = iter &&
(!disableIPv4 || tmpAddr.raw.family != PR_AF_INET) &&
(!filterNameCollision || tmpAddr.raw.family != PR_AF_INET || (tmpAddr.inet.ip != nameCollisionAddr));
if (addIt) {
auto *addrElement = new NetAddrElement(&tmpAddr);
mAddresses.insertBack(addrElement);
}
} while (iter);
}
AddrInfo::AddrInfo(const char *host, const char *cname)
: mHostName(nullptr)
, mCanonicalName(nullptr)
, ttl(NO_TTL_DATA)
{
Init(host, cname);
}
AddrInfo::~AddrInfo()
{
NetAddrElement *addrElement;
while ((addrElement = mAddresses.popLast())) {
delete addrElement;
}
free(mHostName);
free(mCanonicalName);
}
void
AddrInfo::Init(const char *host, const char *cname)
{
MOZ_ASSERT(host, "Cannot initialize AddrInfo with a null host pointer!");
ttl = NO_TTL_DATA;
size_t hostlen = strlen(host);
mHostName = static_cast<char*>(moz_xmalloc(hostlen + 1));
memcpy(mHostName, host, hostlen + 1);
if (cname) {
size_t cnameLen = strlen(cname);
mCanonicalName = static_cast<char*>(moz_xmalloc(cnameLen + 1));
memcpy(mCanonicalName, cname, cnameLen + 1);
}
else {
mCanonicalName = nullptr;
}
}
void
AddrInfo::AddAddress(NetAddrElement *address)
{
MOZ_ASSERT(address, "Cannot add the address to an uninitialized list");
mAddresses.insertBack(address);
}
size_t
AddrInfo::SizeOfIncludingThis(MallocSizeOf mallocSizeOf) const
{
size_t n = mallocSizeOf(this);
n += mallocSizeOf(mHostName);
n += mallocSizeOf(mCanonicalName);
n += mAddresses.sizeOfExcludingThis(mallocSizeOf);
return n;
}
} // namespace net
} // namespace mozilla

183
netwerk/dns/DNS.h Normal file
View file

@ -0,0 +1,183 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef DNS_h_
#define DNS_h_
#include "nscore.h"
#include "prio.h"
#include "prnetdb.h"
#include "plstr.h"
#include "mozilla/LinkedList.h"
#include "mozilla/MemoryReporting.h"
#if !defined(XP_WIN)
#include <arpa/inet.h>
#endif
#ifdef XP_WIN
#include "winsock2.h"
#endif
#ifndef AF_LOCAL
#define AF_LOCAL 1 // used for named pipe
#endif
#define IPv6ADDR_IS_LOOPBACK(a) \
(((a)->u32[0] == 0) && \
((a)->u32[1] == 0) && \
((a)->u32[2] == 0) && \
((a)->u8[12] == 0) && \
((a)->u8[13] == 0) && \
((a)->u8[14] == 0) && \
((a)->u8[15] == 0x1U))
#define IPv6ADDR_IS_V4MAPPED(a) \
(((a)->u32[0] == 0) && \
((a)->u32[1] == 0) && \
((a)->u8[8] == 0) && \
((a)->u8[9] == 0) && \
((a)->u8[10] == 0xff) && \
((a)->u8[11] == 0xff))
#define IPv6ADDR_V4MAPPED_TO_IPADDR(a) ((a)->u32[3])
#define IPv6ADDR_IS_UNSPECIFIED(a) \
(((a)->u32[0] == 0) && \
((a)->u32[1] == 0) && \
((a)->u32[2] == 0) && \
((a)->u32[3] == 0))
namespace mozilla {
namespace net {
// Required buffer size for text form of an IP address.
// Includes space for null termination. We make our own contants
// because we don't want higher-level code depending on things
// like INET6_ADDRSTRLEN and having to include the associated
// platform-specific headers.
#ifdef XP_WIN
// Windows requires longer buffers for some reason.
const int kIPv4CStrBufSize = 22;
const int kIPv6CStrBufSize = 65;
const int kNetAddrMaxCStrBufSize = kIPv6CStrBufSize;
#else
const int kIPv4CStrBufSize = 16;
const int kIPv6CStrBufSize = 46;
const int kLocalCStrBufSize = 108;
const int kNetAddrMaxCStrBufSize = kLocalCStrBufSize;
#endif
// This was all created at a time in which we were using NSPR for host
// resolution and we were propagating NSPR types like "PRAddrInfo" and
// "PRNetAddr" all over Gecko. This made it hard to use another host
// resolver -- we were locked into NSPR. The goal here is to get away
// from that. We'll translate what we get from NSPR or any other host
// resolution library into the types below and use them in Gecko.
union IPv6Addr {
uint8_t u8[16];
uint16_t u16[8];
uint32_t u32[4];
uint64_t u64[2];
};
// This struct is similar to operating system structs like "sockaddr", used for
// things like "connect" and "getsockname". When tempted to cast or do dumb
// copies of this struct to another struct, bear compiler-computed padding
// in mind. The size of this struct, and the layout of the data in it, may
// not be what you expect.
union NetAddr {
struct {
uint16_t family; /* address family (0x00ff maskable) */
char data[14]; /* raw address data */
} raw;
struct {
uint16_t family; /* address family (AF_INET) */
uint16_t port; /* port number */
uint32_t ip; /* The actual 32 bits of address */
} inet;
struct {
uint16_t family; /* address family (AF_INET6) */
uint16_t port; /* port number */
uint32_t flowinfo; /* routing information */
IPv6Addr ip; /* the actual 128 bits of address */
uint32_t scope_id; /* set of interfaces for a scope */
} inet6;
#if defined(XP_UNIX) || defined(XP_WIN)
struct { /* Unix domain socket or
Windows Named Pipes address */
uint16_t family; /* address family (AF_UNIX) */
char path[104]; /* null-terminated pathname */
} local;
#endif
// introduced to support nsTArray<NetAddr> comparisons and sorting
bool operator == (const NetAddr& other) const;
bool operator < (const NetAddr &other) const;
};
// This class wraps a NetAddr union to provide C++ linked list
// capabilities and other methods. It is created from a PRNetAddr,
// which is converted to a mozilla::dns::NetAddr.
class NetAddrElement : public LinkedListElement<NetAddrElement> {
public:
explicit NetAddrElement(const PRNetAddr *prNetAddr);
NetAddrElement(const NetAddrElement& netAddr);
~NetAddrElement();
NetAddr mAddress;
};
class AddrInfo {
public:
// Creates an AddrInfo object. It calls the AddrInfo(const char*, const char*)
// to initialize the host and the cname.
AddrInfo(const char *host, const PRAddrInfo *prAddrInfo, bool disableIPv4,
bool filterNameCollision, const char *cname);
// Creates a basic AddrInfo object (initialize only the host and the cname).
AddrInfo(const char *host, const char *cname);
~AddrInfo();
void AddAddress(NetAddrElement *address);
size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
char *mHostName;
char *mCanonicalName;
uint16_t ttl;
static const uint16_t NO_TTL_DATA = (uint16_t) -1;
LinkedList<NetAddrElement> mAddresses;
private:
void Init(const char *host, const char *cname);
};
// Copies the contents of a PRNetAddr to a NetAddr.
// Does not do a ptr safety check!
void PRNetAddrToNetAddr(const PRNetAddr *prAddr, NetAddr *addr);
// Copies the contents of a NetAddr to a PRNetAddr.
// Does not do a ptr safety check!
void NetAddrToPRNetAddr(const NetAddr *addr, PRNetAddr *prAddr);
bool NetAddrToString(const NetAddr *addr, char *buf, uint32_t bufSize);
bool IsLoopBackAddress(const NetAddr *addr);
bool IsIPAddrAny(const NetAddr *addr);
bool IsIPAddrV4Mapped(const NetAddr *addr);
bool IsIPAddrLocal(const NetAddr *addr);
nsresult GetPort(const NetAddr *aAddr, uint16_t *aResult);
} // namespace net
} // namespace mozilla
#endif // DNS_h_

View file

@ -0,0 +1,43 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim: set sw=4 ts=8 et tw=80 : */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/net/DNSListenerProxy.h"
#include "nsICancelable.h"
#include "nsIEventTarget.h"
namespace mozilla {
namespace net {
NS_IMPL_ISUPPORTS(DNSListenerProxy,
nsIDNSListener,
nsIDNSListenerProxy)
NS_IMETHODIMP
DNSListenerProxy::OnLookupComplete(nsICancelable* aRequest,
nsIDNSRecord* aRecord,
nsresult aStatus)
{
RefPtr<OnLookupCompleteRunnable> r =
new OnLookupCompleteRunnable(mListener, aRequest, aRecord, aStatus);
return mTargetThread->Dispatch(r, NS_DISPATCH_NORMAL);
}
NS_IMETHODIMP
DNSListenerProxy::OnLookupCompleteRunnable::Run()
{
mListener->OnLookupComplete(mRequest, mRecord, mStatus);
return NS_OK;
}
NS_IMETHODIMP
DNSListenerProxy::GetOriginalListener(nsIDNSListener **aOriginalListener)
{
NS_IF_ADDREF(*aOriginalListener = mListener);
return NS_OK;
}
} // namespace net
} // namespace mozilla

View file

@ -0,0 +1,72 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim: set sw=4 ts=8 et tw=80 : */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef DNSListenerProxy_h__
#define DNSListenerProxy_h__
#include "nsIDNSListener.h"
#include "nsIDNSRecord.h"
#include "nsProxyRelease.h"
#include "nsThreadUtils.h"
class nsIEventTarget;
class nsICancelable;
namespace mozilla {
namespace net {
class DNSListenerProxy final
: public nsIDNSListener
, public nsIDNSListenerProxy
{
public:
DNSListenerProxy(nsIDNSListener* aListener, nsIEventTarget* aTargetThread)
// Sometimes aListener is a main-thread only object like XPCWrappedJS, and
// sometimes it's a threadsafe object like nsSOCKSSocketInfo. Use a main-
// thread pointer holder, but disable strict enforcement of thread invariants.
// The AddRef implementation of XPCWrappedJS will assert if we go wrong here.
: mListener(new nsMainThreadPtrHolder<nsIDNSListener>(aListener, false))
, mTargetThread(aTargetThread)
{ }
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIDNSLISTENER
NS_DECL_NSIDNSLISTENERPROXY
class OnLookupCompleteRunnable : public Runnable
{
public:
OnLookupCompleteRunnable(const nsMainThreadPtrHandle<nsIDNSListener>& aListener,
nsICancelable* aRequest,
nsIDNSRecord* aRecord,
nsresult aStatus)
: mListener(aListener)
, mRequest(aRequest)
, mRecord(aRecord)
, mStatus(aStatus)
{ }
NS_DECL_NSIRUNNABLE
private:
nsMainThreadPtrHandle<nsIDNSListener> mListener;
nsCOMPtr<nsICancelable> mRequest;
nsCOMPtr<nsIDNSRecord> mRecord;
nsresult mStatus;
};
private:
~DNSListenerProxy() {}
nsMainThreadPtrHandle<nsIDNSListener> mListener;
nsCOMPtr<nsIEventTarget> mTargetThread;
};
} // namespace net
} // namespace mozilla
#endif // DNSListenerProxy_h__

View file

@ -0,0 +1,313 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et tw=80 : */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/net/ChildDNSService.h"
#include "mozilla/net/DNSRequestChild.h"
#include "mozilla/net/NeckoChild.h"
#include "mozilla/Unused.h"
#include "nsIDNSRecord.h"
#include "nsHostResolver.h"
#include "nsTArray.h"
#include "nsNetAddr.h"
#include "nsIThread.h"
#include "nsThreadUtils.h"
using namespace mozilla::ipc;
namespace mozilla {
namespace net {
//-----------------------------------------------------------------------------
// ChildDNSRecord:
// A simple class to provide nsIDNSRecord on the child
//-----------------------------------------------------------------------------
class ChildDNSRecord : public nsIDNSRecord
{
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIDNSRECORD
ChildDNSRecord(const DNSRecord& reply, uint16_t flags);
private:
virtual ~ChildDNSRecord();
nsCString mCanonicalName;
nsTArray<NetAddr> mAddresses;
uint32_t mCurrent; // addr iterator
uint32_t mLength; // number of addrs
uint16_t mFlags;
};
NS_IMPL_ISUPPORTS(ChildDNSRecord, nsIDNSRecord)
ChildDNSRecord::ChildDNSRecord(const DNSRecord& reply, uint16_t flags)
: mCurrent(0)
, mFlags(flags)
{
mCanonicalName = reply.canonicalName();
// A shame IPDL gives us no way to grab ownership of array: so copy it.
const nsTArray<NetAddr>& addrs = reply.addrs();
uint32_t i = 0;
mLength = addrs.Length();
for (; i < mLength; i++) {
mAddresses.AppendElement(addrs[i]);
}
}
ChildDNSRecord::~ChildDNSRecord()
{
}
//-----------------------------------------------------------------------------
// ChildDNSRecord::nsIDNSRecord
//-----------------------------------------------------------------------------
NS_IMETHODIMP
ChildDNSRecord::GetCanonicalName(nsACString &result)
{
if (!(mFlags & nsHostResolver::RES_CANON_NAME)) {
return NS_ERROR_NOT_AVAILABLE;
}
result = mCanonicalName;
return NS_OK;
}
NS_IMETHODIMP
ChildDNSRecord::GetNextAddr(uint16_t port, NetAddr *addr)
{
if (mCurrent >= mLength) {
return NS_ERROR_NOT_AVAILABLE;
}
memcpy(addr, &mAddresses[mCurrent++], sizeof(NetAddr));
// both Ipv4/6 use same bits for port, so safe to just use ipv4's field
addr->inet.port = htons(port);
return NS_OK;
}
NS_IMETHODIMP
ChildDNSRecord::GetAddresses(nsTArray<NetAddr> & aAddressArray)
{
aAddressArray = mAddresses;
return NS_OK;
}
// shamelessly copied from nsDNSRecord
NS_IMETHODIMP
ChildDNSRecord::GetScriptableNextAddr(uint16_t port, nsINetAddr **result)
{
NetAddr addr;
nsresult rv = GetNextAddr(port, &addr);
if (NS_FAILED(rv)) return rv;
NS_ADDREF(*result = new nsNetAddr(&addr));
return NS_OK;
}
// also copied from nsDNSRecord
NS_IMETHODIMP
ChildDNSRecord::GetNextAddrAsString(nsACString &result)
{
NetAddr addr;
nsresult rv = GetNextAddr(0, &addr);
if (NS_FAILED(rv)) {
return rv;
}
char buf[kIPv6CStrBufSize];
if (NetAddrToString(&addr, buf, sizeof(buf))) {
result.Assign(buf);
return NS_OK;
}
NS_ERROR("NetAddrToString failed unexpectedly");
return NS_ERROR_FAILURE; // conversion failed for some reason
}
NS_IMETHODIMP
ChildDNSRecord::HasMore(bool *result)
{
*result = mCurrent < mLength;
return NS_OK;
}
NS_IMETHODIMP
ChildDNSRecord::Rewind()
{
mCurrent = 0;
return NS_OK;
}
NS_IMETHODIMP
ChildDNSRecord::ReportUnusable(uint16_t aPort)
{
// "We thank you for your feedback" == >/dev/null
// TODO: we could send info back to parent.
return NS_OK;
}
//-----------------------------------------------------------------------------
// CancelDNSRequestEvent
//-----------------------------------------------------------------------------
class CancelDNSRequestEvent : public Runnable
{
public:
CancelDNSRequestEvent(DNSRequestChild* aDnsReq, nsresult aReason)
: mDnsRequest(aDnsReq)
, mReasonForCancel(aReason)
{}
NS_IMETHOD Run() override
{
if (mDnsRequest->mIPCOpen) {
// Send request to Parent process.
mDnsRequest->SendCancelDNSRequest(mDnsRequest->mHost, mDnsRequest->mFlags,
mDnsRequest->mNetworkInterface,
mReasonForCancel);
}
return NS_OK;
}
private:
RefPtr<DNSRequestChild> mDnsRequest;
nsresult mReasonForCancel;
};
//-----------------------------------------------------------------------------
// DNSRequestChild
//-----------------------------------------------------------------------------
DNSRequestChild::DNSRequestChild(const nsCString& aHost,
const uint32_t& aFlags,
const nsCString& aNetworkInterface,
nsIDNSListener *aListener,
nsIEventTarget *target)
: mListener(aListener)
, mTarget(target)
, mResultStatus(NS_OK)
, mHost(aHost)
, mFlags(aFlags)
, mNetworkInterface(aNetworkInterface)
, mIPCOpen(false)
{
}
void
DNSRequestChild::StartRequest()
{
// we can only do IPDL on the main thread
if (!NS_IsMainThread()) {
NS_DispatchToMainThread(
NewRunnableMethod(this, &DNSRequestChild::StartRequest));
return;
}
// Send request to Parent process.
gNeckoChild->SendPDNSRequestConstructor(this, mHost, mFlags,
mNetworkInterface);
mIPCOpen = true;
// IPDL holds a reference until IPDL channel gets destroyed
AddIPDLReference();
}
void
DNSRequestChild::CallOnLookupComplete()
{
MOZ_ASSERT(mListener);
mListener->OnLookupComplete(this, mResultRecord, mResultStatus);
}
bool
DNSRequestChild::RecvLookupCompleted(const DNSRequestResponse& reply)
{
mIPCOpen = false;
MOZ_ASSERT(mListener);
switch (reply.type()) {
case DNSRequestResponse::TDNSRecord: {
mResultRecord = new ChildDNSRecord(reply.get_DNSRecord(), mFlags);
break;
}
case DNSRequestResponse::Tnsresult: {
mResultStatus = reply.get_nsresult();
break;
}
default:
NS_NOTREACHED("unknown type");
return false;
}
MOZ_ASSERT(NS_IsMainThread());
bool targetIsMain = false;
if (!mTarget) {
targetIsMain = true;
} else {
mTarget->IsOnCurrentThread(&targetIsMain);
}
if (targetIsMain) {
CallOnLookupComplete();
} else {
nsCOMPtr<nsIRunnable> event =
NewRunnableMethod(this, &DNSRequestChild::CallOnLookupComplete);
mTarget->Dispatch(event, NS_DISPATCH_NORMAL);
}
Unused << Send__delete__(this);
return true;
}
void
DNSRequestChild::ReleaseIPDLReference()
{
// Request is done or destroyed. Remove it from the hash table.
RefPtr<ChildDNSService> dnsServiceChild =
dont_AddRef(ChildDNSService::GetSingleton());
dnsServiceChild->NotifyRequestDone(this);
Release();
}
void
DNSRequestChild::ActorDestroy(ActorDestroyReason why)
{
mIPCOpen = false;
}
//-----------------------------------------------------------------------------
// DNSRequestChild::nsISupports
//-----------------------------------------------------------------------------
NS_IMPL_ISUPPORTS(DNSRequestChild,
nsICancelable)
//-----------------------------------------------------------------------------
// DNSRequestChild::nsICancelable
//-----------------------------------------------------------------------------
NS_IMETHODIMP
DNSRequestChild::Cancel(nsresult reason)
{
if(mIPCOpen) {
// We can only do IPDL on the main thread
NS_DispatchToMainThread(
new CancelDNSRequestEvent(this, reason));
}
return NS_OK;
}
//------------------------------------------------------------------------------
} // namespace net
} // namespace mozilla

View file

@ -0,0 +1,61 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et tw=80 : */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_net_DNSRequestChild_h
#define mozilla_net_DNSRequestChild_h
#include "mozilla/net/PDNSRequestChild.h"
#include "nsICancelable.h"
#include "nsIDNSRecord.h"
#include "nsIDNSListener.h"
#include "nsIEventTarget.h"
namespace mozilla {
namespace net {
class DNSRequestChild final
: public PDNSRequestChild
, public nsICancelable
{
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSICANCELABLE
DNSRequestChild(const nsCString& aHost, const uint32_t& aFlags,
const nsCString& aNetworkInterface,
nsIDNSListener *aListener, nsIEventTarget *target);
void AddIPDLReference() {
AddRef();
}
void ReleaseIPDLReference();
// Sends IPDL request to parent
void StartRequest();
void CallOnLookupComplete();
protected:
friend class CancelDNSRequestEvent;
friend class ChildDNSService;
virtual ~DNSRequestChild() {}
virtual bool RecvLookupCompleted(const DNSRequestResponse& reply) override;
virtual void ActorDestroy(ActorDestroyReason why) override;
nsCOMPtr<nsIDNSListener> mListener;
nsCOMPtr<nsIEventTarget> mTarget;
nsCOMPtr<nsIDNSRecord> mResultRecord;
nsresult mResultStatus;
nsCString mHost;
uint16_t mFlags;
nsCString mNetworkInterface;
bool mIPCOpen;
};
} // namespace net
} // namespace mozilla
#endif // mozilla_net_DNSRequestChild_h

View file

@ -0,0 +1,132 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et tw=80 : */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/net/DNSRequestParent.h"
#include "nsIDNSService.h"
#include "nsNetCID.h"
#include "nsThreadUtils.h"
#include "nsIServiceManager.h"
#include "nsICancelable.h"
#include "nsIDNSRecord.h"
#include "nsHostResolver.h"
#include "mozilla/Unused.h"
using namespace mozilla::ipc;
namespace mozilla {
namespace net {
DNSRequestParent::DNSRequestParent()
: mFlags(0)
, mIPCClosed(false)
{
}
DNSRequestParent::~DNSRequestParent()
{
}
void
DNSRequestParent::DoAsyncResolve(const nsACString &hostname, uint32_t flags,
const nsACString &networkInterface)
{
nsresult rv;
mFlags = flags;
nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID, &rv);
if (NS_SUCCEEDED(rv)) {
nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
nsCOMPtr<nsICancelable> unused;
rv = dns->AsyncResolveExtended(hostname, flags, networkInterface, this,
mainThread, getter_AddRefs(unused));
}
if (NS_FAILED(rv) && !mIPCClosed) {
mIPCClosed = true;
Unused << SendLookupCompleted(DNSRequestResponse(rv));
}
}
bool
DNSRequestParent::RecvCancelDNSRequest(const nsCString& hostName,
const uint32_t& flags,
const nsCString& networkInterface,
const nsresult& reason)
{
nsresult rv;
nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID, &rv);
if (NS_SUCCEEDED(rv)) {
rv = dns->CancelAsyncResolveExtended(hostName, flags, networkInterface,
this, reason);
}
return true;
}
bool
DNSRequestParent::Recv__delete__()
{
mIPCClosed = true;
return true;
}
void
DNSRequestParent::ActorDestroy(ActorDestroyReason why)
{
// We may still have refcount>0 if DNS hasn't called our OnLookupComplete
// yet, but child process has crashed. We must not send any more msgs
// to child, or IPDL will kill chrome process, too.
mIPCClosed = true;
}
//-----------------------------------------------------------------------------
// DNSRequestParent::nsISupports
//-----------------------------------------------------------------------------
NS_IMPL_ISUPPORTS(DNSRequestParent,
nsIDNSListener)
//-----------------------------------------------------------------------------
// nsIDNSListener functions
//-----------------------------------------------------------------------------
NS_IMETHODIMP
DNSRequestParent::OnLookupComplete(nsICancelable *request,
nsIDNSRecord *rec,
nsresult status)
{
if (mIPCClosed) {
// nothing to do: child probably crashed
return NS_OK;
}
if (NS_SUCCEEDED(status)) {
MOZ_ASSERT(rec);
nsAutoCString cname;
if (mFlags & nsHostResolver::RES_CANON_NAME) {
rec->GetCanonicalName(cname);
}
// Get IP addresses for hostname (use port 80 as dummy value for NetAddr)
NetAddrArray array;
NetAddr addr;
while (NS_SUCCEEDED(rec->GetNextAddr(80, &addr))) {
array.AppendElement(addr);
}
Unused << SendLookupCompleted(DNSRequestResponse(DNSRecord(cname, array)));
} else {
Unused << SendLookupCompleted(DNSRequestResponse(status));
}
mIPCClosed = true;
return NS_OK;
}
} // namespace net
} // namespace mozilla

View file

@ -0,0 +1,50 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et tw=80 : */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_net_DNSRequestParent_h
#define mozilla_net_DNSRequestParent_h
#include "mozilla/net/PDNSRequestParent.h"
#include "nsIDNSService.h"
#include "nsIDNSListener.h"
namespace mozilla {
namespace net {
class DNSRequestParent
: public PDNSRequestParent
, public nsIDNSListener
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIDNSLISTENER
DNSRequestParent();
void DoAsyncResolve(const nsACString &hostname, uint32_t flags,
const nsACString &networkInterface);
// Pass args here rather than storing them in the parent; they are only
// needed if the request is to be canceled.
bool RecvCancelDNSRequest(const nsCString& hostName,
const uint32_t& flags,
const nsCString& networkInterface,
const nsresult& reason) override;
bool Recv__delete__() override;
protected:
virtual void ActorDestroy(ActorDestroyReason why) override;
private:
virtual ~DNSRequestParent();
uint32_t mFlags;
bool mIPCClosed; // true if IPDL channel has been closed (child crash)
};
} // namespace net
} // namespace mozilla
#endif // mozilla_net_DNSRequestParent_h

369
netwerk/dns/GetAddrInfo.cpp Normal file
View file

@ -0,0 +1,369 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "GetAddrInfo.h"
#include "mozilla/net/DNS.h"
#include "prnetdb.h"
#include "nsHostResolver.h"
#include "nsError.h"
#include "mozilla/Mutex.h"
#include "nsAutoPtr.h"
#include "mozilla/StaticPtr.h"
#include "MainThreadUtils.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/net/DNS.h"
#include <algorithm>
#include "prerror.h"
#include "mozilla/Logging.h"
#if DNSQUERY_AVAILABLE
// There is a bug in windns.h where the type of parameter ppQueryResultsSet for
// DnsQuery_A is dependent on UNICODE being set. It should *always* be
// PDNS_RECORDA, but if UNICODE is set it is PDNS_RECORDW. To get around this
// we make sure that UNICODE is unset.
#undef UNICODE
#include <ws2tcpip.h>
#undef GetAddrInfo
#include <windns.h>
#endif
namespace mozilla {
namespace net {
static LazyLogModule gGetAddrInfoLog("GetAddrInfo");
#define LOG(msg, ...) \
MOZ_LOG(gGetAddrInfoLog, LogLevel::Debug, ("[DNS]: " msg, ##__VA_ARGS__))
#define LOG_WARNING(msg, ...) \
MOZ_LOG(gGetAddrInfoLog, LogLevel::Warning, ("[DNS]: " msg, ##__VA_ARGS__))
#if DNSQUERY_AVAILABLE
////////////////////////////
// WINDOWS IMPLEMENTATION //
////////////////////////////
// Ensure consistency of PR_* and AF_* constants to allow for legacy usage of
// PR_* constants with this API.
static_assert(PR_AF_INET == AF_INET && PR_AF_INET6 == AF_INET6
&& PR_AF_UNSPEC == AF_UNSPEC, "PR_AF_* must match AF_*");
// We intentionally leak this mutex. This is because we can run into a
// situation where the worker threads are still running until the process
// is actually fully shut down, and at any time one of those worker
// threads can access gDnsapiInfoLock.
static OffTheBooksMutex* gDnsapiInfoLock = nullptr;
struct DnsapiInfo
{
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DnsapiInfo);
HMODULE mLibrary;
decltype(&DnsQuery_A) mDnsQueryFunc;
decltype(&DnsFree) mDnsFreeFunc;
private:
// This will either be called during shutdown of the GetAddrInfo module, or
// when a worker thread is done doing a lookup (ie: within
// _GetAddrInfo_Windows). Note that the lock must be held when this is
// called.
~DnsapiInfo()
{
if (gDnsapiInfoLock) {
gDnsapiInfoLock->AssertCurrentThreadOwns();
} else {
MOZ_ASSERT_UNREACHABLE("No mutex available during GetAddrInfo "
"shutdown.");
return;
}
LOG("Freeing Dnsapi.dll");
MOZ_ASSERT(mLibrary);
DebugOnly<BOOL> rv = FreeLibrary(mLibrary);
NS_WARNING_ASSERTION(rv, "Failed to free Dnsapi.dll.");
}
};
static StaticRefPtr<DnsapiInfo> gDnsapiInfo;
static MOZ_ALWAYS_INLINE nsresult
_GetAddrInfoInit_Windows()
{
// This is necessary to ensure strict thread safety because if two threads
// run this function at the same time they can potentially create two
// mutexes.
MOZ_ASSERT(NS_IsMainThread(),
"Do not initialize GetAddrInfo off main thread!");
if (!gDnsapiInfoLock) {
gDnsapiInfoLock = new OffTheBooksMutex("GetAddrInfo.cpp::gDnsapiInfoLock");
}
OffTheBooksMutexAutoLock lock(*gDnsapiInfoLock);
if (gDnsapiInfo) {
MOZ_ASSERT_UNREACHABLE("GetAddrInfo is being initialized multiple times!");
return NS_ERROR_ALREADY_INITIALIZED;
}
HMODULE library = LoadLibraryA("Dnsapi.dll");
if (NS_WARN_IF(!library)) {
return NS_ERROR_FAILURE;
}
FARPROC dnsQueryFunc = GetProcAddress(library, "DnsQuery_A");
FARPROC dnsFreeFunc = GetProcAddress(library, "DnsFree");
if (NS_WARN_IF(!dnsQueryFunc) || NS_WARN_IF(!dnsFreeFunc)) {
DebugOnly<BOOL> rv = FreeLibrary(library);
NS_WARNING_ASSERTION(rv, "Failed to free Dnsapi.dll.");
return NS_ERROR_FAILURE;
}
DnsapiInfo* info = new DnsapiInfo;
info->mLibrary = library;
info->mDnsQueryFunc = (decltype(info->mDnsQueryFunc)) dnsQueryFunc;
info->mDnsFreeFunc = (decltype(info->mDnsFreeFunc)) dnsFreeFunc;
gDnsapiInfo = info;
return NS_OK;
}
static MOZ_ALWAYS_INLINE nsresult
_GetAddrInfoShutdown_Windows()
{
OffTheBooksMutexAutoLock lock(*gDnsapiInfoLock);
if (NS_WARN_IF(!gDnsapiInfo) || NS_WARN_IF(!gDnsapiInfoLock)) {
MOZ_ASSERT_UNREACHABLE("GetAddrInfo not initialized!");
return NS_ERROR_NOT_INITIALIZED;
}
gDnsapiInfo = nullptr;
return NS_OK;
}
// If successful, returns in aResult a TTL value that is smaller or
// equal with the one already there. Gets the TTL value by calling
// to dnsapi->mDnsQueryFunc and iterating through the returned
// records to find the one with the smallest TTL value.
static MOZ_ALWAYS_INLINE nsresult
_GetMinTTLForRequestType_Windows(DnsapiInfo * dnsapi, const char* aHost,
uint16_t aRequestType, unsigned int* aResult)
{
MOZ_ASSERT(dnsapi);
MOZ_ASSERT(aHost);
MOZ_ASSERT(aResult);
PDNS_RECORDA dnsData = nullptr;
DNS_STATUS status = dnsapi->mDnsQueryFunc(
aHost,
aRequestType,
(DNS_QUERY_STANDARD | DNS_QUERY_NO_NETBT | DNS_QUERY_NO_HOSTS_FILE
| DNS_QUERY_NO_MULTICAST | DNS_QUERY_ACCEPT_TRUNCATED_RESPONSE
| DNS_QUERY_DONT_RESET_TTL_VALUES),
nullptr,
&dnsData,
nullptr);
if (status == DNS_INFO_NO_RECORDS || status == DNS_ERROR_RCODE_NAME_ERROR
|| !dnsData) {
LOG("No DNS records found for %s. status=%X. aRequestType = %X\n",
aHost, status, aRequestType);
return NS_ERROR_FAILURE;
} else if (status != NOERROR) {
LOG_WARNING("DnsQuery_A failed with status %X.\n", status);
return NS_ERROR_UNEXPECTED;
}
for (PDNS_RECORDA curRecord = dnsData; curRecord; curRecord = curRecord->pNext) {
// Only records in the answer section are important
if (curRecord->Flags.S.Section != DnsSectionAnswer) {
continue;
}
if (curRecord->wType == aRequestType) {
*aResult = std::min<unsigned int>(*aResult, curRecord->dwTtl);
} else {
LOG("Received unexpected record type %u in response for %s.\n",
curRecord->wType, aHost);
}
}
dnsapi->mDnsFreeFunc(dnsData, DNS_FREE_TYPE::DnsFreeRecordList);
return NS_OK;
}
static MOZ_ALWAYS_INLINE nsresult
_GetTTLData_Windows(const char* aHost, uint16_t* aResult, uint16_t aAddressFamily)
{
MOZ_ASSERT(aHost);
MOZ_ASSERT(aResult);
if (aAddressFamily != PR_AF_UNSPEC &&
aAddressFamily != PR_AF_INET &&
aAddressFamily != PR_AF_INET6) {
return NS_ERROR_UNEXPECTED;
}
RefPtr<DnsapiInfo> dnsapi = nullptr;
{
OffTheBooksMutexAutoLock lock(*gDnsapiInfoLock);
dnsapi = gDnsapiInfo;
}
if (!dnsapi) {
LOG_WARNING("GetAddrInfo has been shutdown or has not been initialized.");
return NS_ERROR_NOT_INITIALIZED;
}
// In order to avoid using ANY records which are not always implemented as a
// "Gimme what you have" request in hostname resolvers, we should send A
// and/or AAAA requests, based on the address family requested.
unsigned int ttl = -1;
if (aAddressFamily == PR_AF_UNSPEC || aAddressFamily == PR_AF_INET) {
_GetMinTTLForRequestType_Windows(dnsapi, aHost, DNS_TYPE_A, &ttl);
}
if (aAddressFamily == PR_AF_UNSPEC || aAddressFamily == PR_AF_INET6) {
_GetMinTTLForRequestType_Windows(dnsapi, aHost, DNS_TYPE_AAAA, &ttl);
}
{
// dnsapi's destructor is not thread-safe, so we release explicitly here
OffTheBooksMutexAutoLock lock(*gDnsapiInfoLock);
dnsapi = nullptr;
}
if (ttl == -1) {
LOG("No useable TTL found.");
return NS_ERROR_FAILURE;
}
*aResult = ttl;
return NS_OK;
}
#endif
////////////////////////////////////
// PORTABLE RUNTIME IMPLEMENTATION//
////////////////////////////////////
static MOZ_ALWAYS_INLINE nsresult
_GetAddrInfo_Portable(const char* aCanonHost, uint16_t aAddressFamily,
uint16_t aFlags, const char* aNetworkInterface,
AddrInfo** aAddrInfo)
{
MOZ_ASSERT(aCanonHost);
MOZ_ASSERT(aAddrInfo);
// We accept the same aFlags that nsHostResolver::ResolveHost accepts, but we
// need to translate the aFlags into a form that PR_GetAddrInfoByName
// accepts.
int prFlags = PR_AI_ADDRCONFIG;
if (!(aFlags & nsHostResolver::RES_CANON_NAME)) {
prFlags |= PR_AI_NOCANONNAME;
}
// We need to remove IPv4 records manually because PR_GetAddrInfoByName
// doesn't support PR_AF_INET6.
bool disableIPv4 = aAddressFamily == PR_AF_INET6;
if (disableIPv4) {
aAddressFamily = PR_AF_UNSPEC;
}
PRAddrInfo* prai = PR_GetAddrInfoByName(aCanonHost, aAddressFamily, prFlags);
if (!prai) {
return NS_ERROR_UNKNOWN_HOST;
}
const char* canonName = nullptr;
if (aFlags & nsHostResolver::RES_CANON_NAME) {
canonName = PR_GetCanonNameFromAddrInfo(prai);
}
bool filterNameCollision = !(aFlags & nsHostResolver::RES_ALLOW_NAME_COLLISION);
nsAutoPtr<AddrInfo> ai(new AddrInfo(aCanonHost, prai, disableIPv4,
filterNameCollision, canonName));
PR_FreeAddrInfo(prai);
if (ai->mAddresses.isEmpty()) {
return NS_ERROR_UNKNOWN_HOST;
}
*aAddrInfo = ai.forget();
return NS_OK;
}
//////////////////////////////////////
// COMMON/PLATFORM INDEPENDENT CODE //
//////////////////////////////////////
nsresult
GetAddrInfoInit() {
LOG("Initializing GetAddrInfo.\n");
#if DNSQUERY_AVAILABLE
return _GetAddrInfoInit_Windows();
#else
return NS_OK;
#endif
}
nsresult
GetAddrInfoShutdown() {
LOG("Shutting down GetAddrInfo.\n");
#if DNSQUERY_AVAILABLE
return _GetAddrInfoShutdown_Windows();
#else
return NS_OK;
#endif
}
nsresult
GetAddrInfo(const char* aHost, uint16_t aAddressFamily, uint16_t aFlags,
const char* aNetworkInterface, AddrInfo** aAddrInfo, bool aGetTtl)
{
if (NS_WARN_IF(!aHost) || NS_WARN_IF(!aAddrInfo)) {
return NS_ERROR_NULL_POINTER;
}
#if DNSQUERY_AVAILABLE
// The GetTTLData needs the canonical name to function properly
if (aGetTtl) {
aFlags |= nsHostResolver::RES_CANON_NAME;
}
#endif
*aAddrInfo = nullptr;
nsresult rv = _GetAddrInfo_Portable(aHost, aAddressFamily, aFlags,
aNetworkInterface, aAddrInfo);
#if DNSQUERY_AVAILABLE
if (aGetTtl && NS_SUCCEEDED(rv)) {
// Figure out the canonical name, or if that fails, just use the host name
// we have.
const char *name = nullptr;
if (*aAddrInfo != nullptr && (*aAddrInfo)->mCanonicalName) {
name = (*aAddrInfo)->mCanonicalName;
} else {
name = aHost;
}
LOG("Getting TTL for %s (cname = %s).", aHost, name);
uint16_t ttl = 0;
nsresult ttlRv = _GetTTLData_Windows(name, &ttl, aAddressFamily);
if (NS_SUCCEEDED(ttlRv)) {
(*aAddrInfo)->ttl = ttl;
LOG("Got TTL %u for %s (name = %s).", ttl, aHost, name);
} else {
LOG_WARNING("Could not get TTL for %s (cname = %s).", aHost, name);
}
}
#endif
return rv;
}
} // namespace net
} // namespace mozilla

67
netwerk/dns/GetAddrInfo.h Normal file
View file

@ -0,0 +1,67 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef netwerk_dns_GetAddrInfo_h
#define netwerk_dns_GetAddrInfo_h
#include "nsError.h"
#include "nscore.h"
#if defined(XP_WIN)
#define DNSQUERY_AVAILABLE 1
#define TTL_AVAILABLE 1
#else
#define DNSQUERY_AVAILABLE 0
#define TTL_AVAILABLE 0
#endif
namespace mozilla {
namespace net {
class AddrInfo;
/**
* Look up a host by name. Mostly equivalent to getaddrinfo(host, NULL, ...) of
* RFC 3493.
*
* @param aHost[in] Character string defining the host name of interest
* @param aAddressFamily[in] May be AF_INET, AF_INET6, or AF_UNSPEC.
* @param aFlags[in] May be either PR_AI_ADDRCONFIG or
* PR_AI_ADDRCONFIG | PR_AI_NOCANONNAME. Include PR_AI_NOCANONNAME to
* suppress the determination of the canonical name corresponding to
* hostname (PR_AI_NOCANONNAME will be ignored if the TTL is retrieved).
* @param aAddrInfo[out] Will point to the results of the host lookup, or be
* null if the lookup failed.
* @param aGetTtl[in] If true, and TTL_AVAILABLE is truthy, the TTL will be
* retrieved if DNS provides the answers..
*/
nsresult
GetAddrInfo(const char* aHost, uint16_t aAddressFamily, uint16_t aFlags,
const char* aNetworkInterface, AddrInfo** aAddrInfo, bool aGetTtl);
/**
* Initialize the GetAddrInfo module.
*
* GetAddrInfoShutdown() should be called for every time this function is
* called.
*/
nsresult
GetAddrInfoInit();
/**
* Shutdown the GetAddrInfo module.
*
* This function should be called for every time GetAddrInfoInit() is called.
* An assertion may throw (but is not guarenteed) if this function is called
* too many times.
*/
nsresult
GetAddrInfoShutdown();
} // namespace net
} // namespace mozilla
#endif // netwerk_dns_GetAddrInfo_h

23
netwerk/dns/PDNSParams.h Normal file
View file

@ -0,0 +1,23 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et tw=80 ft=c: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef PDNSParams_h
#define PDNSParams_h
#include "DNS.h"
#include "nsTArray.h"
namespace mozilla {
namespace net {
// Need to define typedef in .h file--can't seem to in ipdl.h file?
typedef nsTArray<NetAddr> NetAddrArray;
} // namespace net
} // namespace mozilla
#endif // PDNSParams_h

View file

@ -0,0 +1,36 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
include protocol PNecko;
include PDNSRequestParams;
include "mozilla/net/NeckoMessageUtils.h";
namespace mozilla {
namespace net {
async protocol PDNSRequest
{
manager PNecko;
parent:
// constructor in PNecko takes AsyncResolve args that initialize request
// Pass args here rather than storing them in the parent; they are only
// needed if the request is to be canceled.
async CancelDNSRequest(nsCString hostName, uint32_t flags,
nsCString networkInterface, nsresult reason);
async __delete__();
child:
async LookupCompleted(DNSRequestResponse reply);
};
} //namespace net
} //namespace mozilla

View file

@ -0,0 +1,31 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
using NetAddrArray from "mozilla/net/PDNSParams.h";
namespace mozilla {
namespace net {
//-----------------------------------------------------------------------------
// DNS IPDL structs
//-----------------------------------------------------------------------------
struct DNSRecord
{
nsCString canonicalName;
NetAddrArray addrs;
};
union DNSRequestResponse
{
DNSRecord;
nsresult; // if error
};
} // namespace ipc
} // namespace mozilla

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,779 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "MDNSResponderOperator.h"
#include "MDNSResponderReply.h"
#include "mozilla/EndianUtils.h"
#include "mozilla/Logging.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/Unused.h"
#include "nsComponentManagerUtils.h"
#include "nsCOMPtr.h"
#include "nsDebug.h"
#include "nsDNSServiceInfo.h"
#include "nsHashPropertyBag.h"
#include "nsIProperty.h"
#include "nsISimpleEnumerator.h"
#include "nsIVariant.h"
#include "nsServiceManagerUtils.h"
#include "nsNetAddr.h"
#include "nsNetCID.h"
#include "nsSocketTransportService2.h"
#include "nsThreadUtils.h"
#include "nsXPCOMCID.h"
#include "private/pprio.h"
#include "nsASocketHandler.h"
namespace mozilla {
namespace net {
static LazyLogModule gMDNSLog("MDNSResponderOperator");
#undef LOG_I
#define LOG_I(...) MOZ_LOG(mozilla::net::gMDNSLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
#undef LOG_E
#define LOG_E(...) MOZ_LOG(mozilla::net::gMDNSLog, mozilla::LogLevel::Error, (__VA_ARGS__))
class MDNSResponderOperator::ServiceWatcher final
: public nsASocketHandler
{
public:
NS_DECL_THREADSAFE_ISUPPORTS
// nsASocketHandler methods
virtual void OnSocketReady(PRFileDesc* fd, int16_t outFlags) override
{
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
MOZ_ASSERT(fd == mFD);
if (outFlags & (PR_POLL_ERR | PR_POLL_HUP | PR_POLL_NVAL)) {
LOG_E("error polling on listening socket (%p)", fd);
mCondition = NS_ERROR_UNEXPECTED;
}
if (!(outFlags & PR_POLL_READ)) {
return;
}
DNSServiceProcessResult(mService);
}
virtual void OnSocketDetached(PRFileDesc *fd) override
{
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
MOZ_ASSERT(mThread);
MOZ_ASSERT(fd == mFD);
if (!mFD) {
return;
}
// Bug 1175387: do not double close the handle here.
PR_ChangeFileDescNativeHandle(mFD, -1);
PR_Close(mFD);
mFD = nullptr;
mThread->Dispatch(NewRunnableMethod(this, &ServiceWatcher::Deallocate),
NS_DISPATCH_NORMAL);
}
virtual void IsLocal(bool *aIsLocal) override { *aIsLocal = true; }
virtual void KeepWhenOffline(bool *aKeepWhenOffline) override
{
*aKeepWhenOffline = true;
}
virtual uint64_t ByteCountSent() override { return 0; }
virtual uint64_t ByteCountReceived() override { return 0; }
explicit ServiceWatcher(DNSServiceRef aService,
MDNSResponderOperator* aOperator)
: mThread(nullptr)
, mSts(nullptr)
, mOperatorHolder(aOperator)
, mService(aService)
, mFD(nullptr)
, mAttached(false)
{
if (!gSocketTransportService)
{
nsCOMPtr<nsISocketTransportService> sts =
do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID);
}
}
nsresult Init()
{
MOZ_ASSERT(PR_GetCurrentThread() != gSocketThread);
mThread = NS_GetCurrentThread();
if (!mService) {
return NS_OK;
}
if (!gSocketTransportService) {
return NS_ERROR_FAILURE;
}
mSts = gSocketTransportService;
int osfd = DNSServiceRefSockFD(mService);
if (osfd == -1) {
return NS_ERROR_FAILURE;
}
mFD = PR_ImportFile(osfd);
return PostEvent(&ServiceWatcher::OnMsgAttach);
}
void Close()
{
MOZ_ASSERT(PR_GetCurrentThread() != gSocketThread);
if (!gSocketTransportService) {
Deallocate();
return;
}
PostEvent(&ServiceWatcher::OnMsgClose);
}
private:
~ServiceWatcher() = default;
void Deallocate()
{
if (mService) {
DNSServiceRefDeallocate(mService);
mService = nullptr;
}
mOperatorHolder = nullptr;
}
nsresult PostEvent(void(ServiceWatcher::*func)(void))
{
return gSocketTransportService->Dispatch(NewRunnableMethod(this, func),
NS_DISPATCH_NORMAL);
}
void OnMsgClose()
{
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
if (NS_FAILED(mCondition)) {
return;
}
// tear down socket. this signals the STS to detach our socket handler.
mCondition = NS_BINDING_ABORTED;
// if we are attached, then socket transport service will call our
// OnSocketDetached method automatically. Otherwise, we have to call it
// (and thus close the socket) manually.
if (!mAttached) {
OnSocketDetached(mFD);
}
}
void OnMsgAttach()
{
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
if (NS_FAILED(mCondition)) {
return;
}
mCondition = TryAttach();
// if we hit an error while trying to attach then bail...
if (NS_FAILED(mCondition)) {
NS_ASSERTION(!mAttached, "should not be attached already");
OnSocketDetached(mFD);
}
}
nsresult TryAttach()
{
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
nsresult rv;
if (!gSocketTransportService) {
return NS_ERROR_FAILURE;
}
//
// find out if it is going to be ok to attach another socket to the STS.
// if not then we have to wait for the STS to tell us that it is ok.
// the notification is asynchronous, which means that when we could be
// in a race to call AttachSocket once notified. for this reason, when
// we get notified, we just re-enter this function. as a result, we are
// sure to ask again before calling AttachSocket. in this way we deal
// with the race condition. though it isn't the most elegant solution,
// it is far simpler than trying to build a system that would guarantee
// FIFO ordering (which wouldn't even be that valuable IMO). see bug
// 194402 for more info.
//
if (!gSocketTransportService->CanAttachSocket()) {
nsCOMPtr<nsIRunnable> event =
NewRunnableMethod(this, &ServiceWatcher::OnMsgAttach);
nsresult rv = gSocketTransportService->NotifyWhenCanAttachSocket(event);
if (NS_FAILED(rv)) {
return rv;
}
}
//
// ok, we can now attach our socket to the STS for polling
//
rv = gSocketTransportService->AttachSocket(mFD, this);
if (NS_FAILED(rv)) {
return rv;
}
mAttached = true;
//
// now, configure our poll flags for listening...
//
mPollFlags = (PR_POLL_READ | PR_POLL_EXCEPT);
return NS_OK;
}
nsCOMPtr<nsIThread> mThread;
RefPtr<nsSocketTransportService> mSts;
RefPtr<MDNSResponderOperator> mOperatorHolder;
DNSServiceRef mService;
PRFileDesc* mFD;
bool mAttached;
};
NS_IMPL_ISUPPORTS(MDNSResponderOperator::ServiceWatcher, nsISupports)
MDNSResponderOperator::MDNSResponderOperator()
: mService(nullptr)
, mWatcher(nullptr)
, mThread(NS_GetCurrentThread())
, mIsCancelled(false)
{
}
MDNSResponderOperator::~MDNSResponderOperator()
{
Stop();
}
nsresult
MDNSResponderOperator::Start()
{
if (mIsCancelled) {
return NS_OK;
}
if (IsServing()) {
Stop();
}
return NS_OK;
}
nsresult
MDNSResponderOperator::Stop()
{
return ResetService(nullptr);
}
nsresult
MDNSResponderOperator::ResetService(DNSServiceRef aService)
{
nsresult rv;
if (aService != mService) {
if (mWatcher) {
mWatcher->Close();
mWatcher = nullptr;
}
if (aService) {
RefPtr<ServiceWatcher> watcher = new ServiceWatcher(aService, this);
if (NS_WARN_IF(NS_FAILED(rv = watcher->Init()))) {
return rv;
}
mWatcher = watcher;
}
mService = aService;
}
return NS_OK;
}
BrowseOperator::BrowseOperator(const nsACString& aServiceType,
nsIDNSServiceDiscoveryListener* aListener)
: MDNSResponderOperator()
, mServiceType(aServiceType)
, mListener(aListener)
{
}
nsresult
BrowseOperator::Start()
{
nsresult rv;
if (NS_WARN_IF(NS_FAILED(rv = MDNSResponderOperator::Start()))) {
return rv;
}
DNSServiceRef service = nullptr;
DNSServiceErrorType err = DNSServiceBrowse(&service,
0,
kDNSServiceInterfaceIndexAny,
mServiceType.get(),
nullptr,
&BrowseReplyRunnable::Reply,
this);
NS_WARNING_ASSERTION(kDNSServiceErr_NoError == err, "DNSServiceBrowse fail");
if (mListener) {
if (kDNSServiceErr_NoError == err) {
mListener->OnDiscoveryStarted(mServiceType);
} else {
mListener->OnStartDiscoveryFailed(mServiceType, err);
}
}
if (NS_WARN_IF(kDNSServiceErr_NoError != err)) {
return NS_ERROR_FAILURE;
}
return ResetService(service);
}
nsresult
BrowseOperator::Stop()
{
bool isServing = IsServing();
nsresult rv = MDNSResponderOperator::Stop();
if (isServing && mListener) {
if (NS_SUCCEEDED(rv)) {
mListener->OnDiscoveryStopped(mServiceType);
} else {
mListener->OnStopDiscoveryFailed(mServiceType,
static_cast<uint32_t>(rv));
}
}
return rv;
}
void
BrowseOperator::Reply(DNSServiceRef aSdRef,
DNSServiceFlags aFlags,
uint32_t aInterfaceIndex,
DNSServiceErrorType aErrorCode,
const nsACString& aServiceName,
const nsACString& aRegType,
const nsACString& aReplyDomain)
{
MOZ_ASSERT(GetThread() == NS_GetCurrentThread());
if (NS_WARN_IF(kDNSServiceErr_NoError != aErrorCode)) {
LOG_E("BrowseOperator::Reply (%d)", aErrorCode);
if (mListener) {
mListener->OnStartDiscoveryFailed(mServiceType, aErrorCode);
}
return;
}
if (!mListener) { return; }
nsCOMPtr<nsIDNSServiceInfo> info = new nsDNSServiceInfo();
if (NS_WARN_IF(!info)) { return; }
if (NS_WARN_IF(NS_FAILED(info->SetServiceName(aServiceName)))) { return; }
if (NS_WARN_IF(NS_FAILED(info->SetServiceType(aRegType)))) { return; }
if (NS_WARN_IF(NS_FAILED(info->SetDomainName(aReplyDomain)))) { return; }
if (aFlags & kDNSServiceFlagsAdd) {
mListener->OnServiceFound(info);
} else {
mListener->OnServiceLost(info);
}
}
RegisterOperator::RegisterOperator(nsIDNSServiceInfo* aServiceInfo,
nsIDNSRegistrationListener* aListener)
: MDNSResponderOperator()
, mServiceInfo(aServiceInfo)
, mListener(aListener)
{
}
nsresult
RegisterOperator::Start()
{
nsresult rv;
rv = MDNSResponderOperator::Start();
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
uint16_t port;
if (NS_WARN_IF(NS_FAILED(rv = mServiceInfo->GetPort(&port)))) {
return rv;
}
nsAutoCString type;
if (NS_WARN_IF(NS_FAILED(rv = mServiceInfo->GetServiceType(type)))) {
return rv;
}
TXTRecordRef txtRecord;
char buf[TXT_BUFFER_SIZE] = { 0 };
TXTRecordCreate(&txtRecord, TXT_BUFFER_SIZE, buf);
nsCOMPtr<nsIPropertyBag2> attributes;
if (NS_FAILED(rv = mServiceInfo->GetAttributes(getter_AddRefs(attributes)))) {
LOG_I("register: no attributes");
} else {
nsCOMPtr<nsISimpleEnumerator> enumerator;
if (NS_WARN_IF(NS_FAILED(rv =
attributes->GetEnumerator(getter_AddRefs(enumerator))))) {
return rv;
}
bool hasMoreElements;
while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMoreElements)) &&
hasMoreElements) {
nsCOMPtr<nsISupports> element;
MOZ_ALWAYS_SUCCEEDS(enumerator->GetNext(getter_AddRefs(element)));
nsCOMPtr<nsIProperty> property = do_QueryInterface(element);
MOZ_ASSERT(property);
nsAutoString name;
nsCOMPtr<nsIVariant> value;
MOZ_ALWAYS_SUCCEEDS(property->GetName(name));
MOZ_ALWAYS_SUCCEEDS(property->GetValue(getter_AddRefs(value)));
nsAutoCString str;
if (NS_WARN_IF(NS_FAILED(value->GetAsACString(str)))) {
continue;
}
TXTRecordSetValue(&txtRecord,
/* it's safe because key name is ASCII only. */
NS_LossyConvertUTF16toASCII(name).get(),
str.Length(),
str.get());
}
}
nsAutoCString host;
nsAutoCString name;
nsAutoCString domain;
DNSServiceRef service = nullptr;
DNSServiceErrorType err =
DNSServiceRegister(&service,
0,
0,
NS_SUCCEEDED(mServiceInfo->GetServiceName(name)) ?
name.get() : nullptr,
type.get(),
NS_SUCCEEDED(mServiceInfo->GetDomainName(domain)) ?
domain.get() : nullptr,
NS_SUCCEEDED(mServiceInfo->GetHost(host)) ?
host.get() : nullptr,
NativeEndian::swapToNetworkOrder(port),
TXTRecordGetLength(&txtRecord),
TXTRecordGetBytesPtr(&txtRecord),
&RegisterReplyRunnable::Reply,
this);
NS_WARNING_ASSERTION(kDNSServiceErr_NoError == err,
"DNSServiceRegister fail");
TXTRecordDeallocate(&txtRecord);
if (NS_WARN_IF(kDNSServiceErr_NoError != err)) {
if (mListener) {
mListener->OnRegistrationFailed(mServiceInfo, err);
}
return NS_ERROR_FAILURE;
}
return ResetService(service);
}
nsresult
RegisterOperator::Stop()
{
bool isServing = IsServing();
nsresult rv = MDNSResponderOperator::Stop();
if (isServing && mListener) {
if (NS_SUCCEEDED(rv)) {
mListener->OnServiceUnregistered(mServiceInfo);
} else {
mListener->OnUnregistrationFailed(mServiceInfo,
static_cast<uint32_t>(rv));
}
}
return rv;
}
void
RegisterOperator::Reply(DNSServiceRef aSdRef,
DNSServiceFlags aFlags,
DNSServiceErrorType aErrorCode,
const nsACString& aName,
const nsACString& aRegType,
const nsACString& aDomain)
{
MOZ_ASSERT(GetThread() == NS_GetCurrentThread());
if (kDNSServiceErr_NoError != aErrorCode) {
LOG_E("RegisterOperator::Reply (%d)", aErrorCode);
}
if (!mListener) { return; }
nsCOMPtr<nsIDNSServiceInfo> info = new nsDNSServiceInfo(mServiceInfo);
if (NS_WARN_IF(NS_FAILED(info->SetServiceName(aName)))) { return; }
if (NS_WARN_IF(NS_FAILED(info->SetServiceType(aRegType)))) { return; }
if (NS_WARN_IF(NS_FAILED(info->SetDomainName(aDomain)))) { return; }
if (kDNSServiceErr_NoError == aErrorCode) {
if (aFlags & kDNSServiceFlagsAdd) {
mListener->OnServiceRegistered(info);
} else {
// If a successfully-registered name later suffers a name conflict
// or similar problem and has to be deregistered, the callback will
// be invoked with the kDNSServiceFlagsAdd flag not set.
LOG_E("RegisterOperator::Reply: deregister");
if (NS_WARN_IF(NS_FAILED(Stop()))) {
return;
}
}
} else {
mListener->OnRegistrationFailed(info, aErrorCode);
}
}
ResolveOperator::ResolveOperator(nsIDNSServiceInfo* aServiceInfo,
nsIDNSServiceResolveListener* aListener)
: MDNSResponderOperator()
, mServiceInfo(aServiceInfo)
, mListener(aListener)
{
}
nsresult
ResolveOperator::Start()
{
nsresult rv;
if (NS_WARN_IF(NS_FAILED(rv = MDNSResponderOperator::Start()))) {
return rv;
}
nsAutoCString name;
mServiceInfo->GetServiceName(name);
nsAutoCString type;
mServiceInfo->GetServiceType(type);
nsAutoCString domain;
mServiceInfo->GetDomainName(domain);
LOG_I("Resolve: (%s), (%s), (%s)", name.get(), type.get(), domain.get());
DNSServiceRef service = nullptr;
DNSServiceErrorType err =
DNSServiceResolve(&service,
0,
kDNSServiceInterfaceIndexAny,
name.get(),
type.get(),
domain.get(),
(DNSServiceResolveReply)&ResolveReplyRunnable::Reply,
this);
if (NS_WARN_IF(kDNSServiceErr_NoError != err)) {
if (mListener) {
mListener->OnResolveFailed(mServiceInfo, err);
}
return NS_ERROR_FAILURE;
}
return ResetService(service);
}
void
ResolveOperator::Reply(DNSServiceRef aSdRef,
DNSServiceFlags aFlags,
uint32_t aInterfaceIndex,
DNSServiceErrorType aErrorCode,
const nsACString& aFullName,
const nsACString& aHostTarget,
uint16_t aPort,
uint16_t aTxtLen,
const unsigned char* aTxtRecord)
{
MOZ_ASSERT(GetThread() == NS_GetCurrentThread());
auto guard = MakeScopeExit([&] {
Unused << NS_WARN_IF(NS_FAILED(Stop()));
});
if (NS_WARN_IF(kDNSServiceErr_NoError != aErrorCode)) {
LOG_E("ResolveOperator::Reply (%d)", aErrorCode);
return;
}
// Resolve TXT record
int count = TXTRecordGetCount(aTxtLen, aTxtRecord);
LOG_I("resolve: txt count = %d, len = %d", count, aTxtLen);
nsCOMPtr<nsIWritablePropertyBag2> attributes = new nsHashPropertyBag();
if (NS_WARN_IF(!attributes)) {
return;
}
if (count) {
for (int i = 0; i < count; ++i) {
char key[TXT_BUFFER_SIZE] = { '\0' };
uint8_t vSize = 0;
const void* value = nullptr;
if (kDNSServiceErr_NoError !=
TXTRecordGetItemAtIndex(aTxtLen,
aTxtRecord,
i,
TXT_BUFFER_SIZE,
key,
&vSize,
&value)) {
break;
}
nsAutoCString str(reinterpret_cast<const char*>(value), vSize);
LOG_I("resolve TXT: (%d) %s=%s", vSize, key, str.get());
if (NS_WARN_IF(NS_FAILED(attributes->SetPropertyAsACString(
/* it's safe to convert because key name is ASCII only. */
NS_ConvertASCIItoUTF16(key),
str)))) {
break;
}
}
}
if (!mListener) { return; }
nsCOMPtr<nsIDNSServiceInfo> info = new nsDNSServiceInfo(mServiceInfo);
if (NS_WARN_IF(NS_FAILED(info->SetHost(aHostTarget)))) { return; }
if (NS_WARN_IF(NS_FAILED(info->SetPort(aPort)))) { return; }
if (NS_WARN_IF(NS_FAILED(info->SetAttributes(attributes)))) { return; }
if (kDNSServiceErr_NoError == aErrorCode) {
GetAddrInfor(info);
}
else {
mListener->OnResolveFailed(info, aErrorCode);
Unused << NS_WARN_IF(NS_FAILED(Stop()));
}
}
void
ResolveOperator::GetAddrInfor(nsIDNSServiceInfo* aServiceInfo)
{
RefPtr<GetAddrInfoOperator> getAddreOp = new GetAddrInfoOperator(aServiceInfo,
mListener);
Unused << NS_WARN_IF(NS_FAILED(getAddreOp->Start()));
}
GetAddrInfoOperator::GetAddrInfoOperator(nsIDNSServiceInfo* aServiceInfo,
nsIDNSServiceResolveListener* aListener)
: MDNSResponderOperator()
, mServiceInfo(aServiceInfo)
, mListener(aListener)
{
}
nsresult
GetAddrInfoOperator::Start()
{
nsresult rv;
if (NS_WARN_IF(NS_FAILED(rv = MDNSResponderOperator::Start()))) {
return rv;
}
nsAutoCString host;
mServiceInfo->GetHost(host);
LOG_I("GetAddrInfo: (%s)", host.get());
DNSServiceRef service = nullptr;
DNSServiceErrorType err =
DNSServiceGetAddrInfo(&service,
kDNSServiceFlagsForceMulticast,
kDNSServiceInterfaceIndexAny,
kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6,
host.get(),
(DNSServiceGetAddrInfoReply)&GetAddrInfoReplyRunnable::Reply,
this);
if (NS_WARN_IF(kDNSServiceErr_NoError != err)) {
if (mListener) {
mListener->OnResolveFailed(mServiceInfo, err);
}
return NS_ERROR_FAILURE;
}
return ResetService(service);
}
void
GetAddrInfoOperator::Reply(DNSServiceRef aSdRef,
DNSServiceFlags aFlags,
uint32_t aInterfaceIndex,
DNSServiceErrorType aErrorCode,
const nsACString& aHostName,
const NetAddr& aAddress,
uint32_t aTTL)
{
MOZ_ASSERT(GetThread() == NS_GetCurrentThread());
auto guard = MakeScopeExit([&] {
Unused << NS_WARN_IF(NS_FAILED(Stop()));
});
if (NS_WARN_IF(kDNSServiceErr_NoError != aErrorCode)) {
LOG_E("GetAddrInfoOperator::Reply (%d)", aErrorCode);
return;
}
if (!mListener) { return; }
NetAddr addr = aAddress;
nsCOMPtr<nsINetAddr> address = new nsNetAddr(&addr);
nsCString addressStr;
if (NS_WARN_IF(NS_FAILED(address->GetAddress(addressStr)))) { return; }
nsCOMPtr<nsIDNSServiceInfo> info = new nsDNSServiceInfo(mServiceInfo);
if (NS_WARN_IF(NS_FAILED(info->SetAddress(addressStr)))) { return; }
/**
* |kDNSServiceFlagsMoreComing| means this callback will be one or more
* callback events later, so this instance should be kept alive until all
* follow-up events are processed.
*/
if (aFlags & kDNSServiceFlagsMoreComing) {
guard.release();
}
if (kDNSServiceErr_NoError == aErrorCode) {
mListener->OnServiceResolved(info);
} else {
mListener->OnResolveFailed(info, aErrorCode);
}
}
} // namespace net
} // namespace mozilla

View file

@ -0,0 +1,152 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_netwerk_dns_mdns_libmdns_MDNSResponderOperator_h
#define mozilla_netwerk_dns_mdns_libmdns_MDNSResponderOperator_h
#include "dns_sd.h"
#include "mozilla/Atomics.h"
#include "mozilla/RefPtr.h"
#include "nsCOMPtr.h"
#include "nsIDNSServiceDiscovery.h"
#include "nsIThread.h"
#include "nsString.h"
namespace mozilla {
namespace net {
class MDNSResponderOperator
{
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MDNSResponderOperator)
public:
MDNSResponderOperator();
virtual nsresult Start();
virtual nsresult Stop();
void Cancel() { mIsCancelled = true; }
nsIThread* GetThread() const { return mThread; }
protected:
virtual ~MDNSResponderOperator();
bool IsServing() const { return mService; }
nsresult ResetService(DNSServiceRef aService);
private:
class ServiceWatcher;
DNSServiceRef mService;
RefPtr<ServiceWatcher> mWatcher;
nsCOMPtr<nsIThread> mThread; // remember caller thread for callback
Atomic<bool> mIsCancelled;
};
class BrowseOperator final : public MDNSResponderOperator
{
public:
BrowseOperator(const nsACString& aServiceType,
nsIDNSServiceDiscoveryListener* aListener);
nsresult Start() override;
nsresult Stop() override;
void Reply(DNSServiceRef aSdRef,
DNSServiceFlags aFlags,
uint32_t aInterfaceIndex,
DNSServiceErrorType aErrorCode,
const nsACString& aServiceName,
const nsACString& aRegType,
const nsACString& aReplyDomain);
private:
~BrowseOperator() = default;
nsCString mServiceType;
nsCOMPtr<nsIDNSServiceDiscoveryListener> mListener;
};
class RegisterOperator final : public MDNSResponderOperator
{
enum { TXT_BUFFER_SIZE = 256 };
public:
RegisterOperator(nsIDNSServiceInfo* aServiceInfo,
nsIDNSRegistrationListener* aListener);
nsresult Start() override;
nsresult Stop() override;
void Reply(DNSServiceRef aSdRef,
DNSServiceFlags aFlags,
DNSServiceErrorType aErrorCode,
const nsACString& aName,
const nsACString& aRegType,
const nsACString& aDomain);
private:
~RegisterOperator() = default;
nsCOMPtr<nsIDNSServiceInfo> mServiceInfo;
nsCOMPtr<nsIDNSRegistrationListener> mListener;
};
class ResolveOperator final : public MDNSResponderOperator
{
enum { TXT_BUFFER_SIZE = 256 };
public:
ResolveOperator(nsIDNSServiceInfo* aServiceInfo,
nsIDNSServiceResolveListener* aListener);
nsresult Start() override;
void Reply(DNSServiceRef aSdRef,
DNSServiceFlags aFlags,
uint32_t aInterfaceIndex,
DNSServiceErrorType aErrorCode,
const nsACString& aFullName,
const nsACString& aHostTarget,
uint16_t aPort,
uint16_t aTxtLen,
const unsigned char* aTxtRecord);
private:
~ResolveOperator() = default;
void GetAddrInfor(nsIDNSServiceInfo* aServiceInfo);
nsCOMPtr<nsIDNSServiceInfo> mServiceInfo;
nsCOMPtr<nsIDNSServiceResolveListener> mListener;
};
union NetAddr;
class GetAddrInfoOperator final : public MDNSResponderOperator
{
public:
GetAddrInfoOperator(nsIDNSServiceInfo* aServiceInfo,
nsIDNSServiceResolveListener* aListener);
nsresult Start() override;
void Reply(DNSServiceRef aSdRef,
DNSServiceFlags aFlags,
uint32_t aInterfaceIndex,
DNSServiceErrorType aErrorCode,
const nsACString& aHostName,
const NetAddr& aAddress,
uint32_t aTTL);
private:
~GetAddrInfoOperator() = default;
nsCOMPtr<nsIDNSServiceInfo> mServiceInfo;
nsCOMPtr<nsIDNSServiceResolveListener> mListener;
};
} // namespace net
} // namespace mozilla
#endif // mozilla_netwerk_dns_mdns_libmdns_MDNSResponderOperator_h

View file

@ -0,0 +1,302 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "MDNSResponderReply.h"
#include "mozilla/EndianUtils.h"
#include "private/pprio.h"
namespace mozilla {
namespace net {
BrowseReplyRunnable::BrowseReplyRunnable(DNSServiceRef aSdRef,
DNSServiceFlags aFlags,
uint32_t aInterfaceIndex,
DNSServiceErrorType aErrorCode,
const nsACString& aServiceName,
const nsACString& aRegType,
const nsACString& aReplyDomain,
BrowseOperator* aContext)
: mSdRef(aSdRef)
, mFlags(aFlags)
, mInterfaceIndex(aInterfaceIndex)
, mErrorCode(aErrorCode)
, mServiceName(aServiceName)
, mRegType(aRegType)
, mReplyDomain(aReplyDomain)
, mContext(aContext)
{
}
NS_IMETHODIMP
BrowseReplyRunnable::Run()
{
MOZ_ASSERT(mContext);
mContext->Reply(mSdRef,
mFlags,
mInterfaceIndex,
mErrorCode,
mServiceName,
mRegType,
mReplyDomain);
return NS_OK;
}
void
BrowseReplyRunnable::Reply(DNSServiceRef aSdRef,
DNSServiceFlags aFlags,
uint32_t aInterfaceIndex,
DNSServiceErrorType aErrorCode,
const char* aServiceName,
const char* aRegType,
const char* aReplyDomain,
void* aContext)
{
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
BrowseOperator* obj(reinterpret_cast<BrowseOperator*>(aContext));
if (!obj) {
return;
}
nsCOMPtr<nsIThread> thread(obj->GetThread());
if (!thread) {
return;
}
thread->Dispatch(new BrowseReplyRunnable(aSdRef,
aFlags,
aInterfaceIndex,
aErrorCode,
nsCString(aServiceName),
nsCString(aRegType),
nsCString(aReplyDomain),
obj),
NS_DISPATCH_NORMAL);
}
RegisterReplyRunnable::RegisterReplyRunnable(DNSServiceRef aSdRef,
DNSServiceFlags aFlags,
DNSServiceErrorType aErrorCode,
const nsACString& aName,
const nsACString& aRegType,
const nsACString& domain,
RegisterOperator* aContext)
: mSdRef(aSdRef)
, mFlags(aFlags)
, mErrorCode(aErrorCode)
, mName(aName)
, mRegType(aRegType)
, mDomain(domain)
, mContext(aContext)
{
}
NS_IMETHODIMP
RegisterReplyRunnable::Run()
{
MOZ_ASSERT(mContext);
mContext->Reply(mSdRef,
mFlags,
mErrorCode,
mName,
mRegType,
mDomain);
return NS_OK;
}
void
RegisterReplyRunnable::Reply(DNSServiceRef aSdRef,
DNSServiceFlags aFlags,
DNSServiceErrorType aErrorCode,
const char* aName,
const char* aRegType,
const char* domain,
void* aContext)
{
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
RegisterOperator* obj(reinterpret_cast<RegisterOperator*>(aContext));
if (!obj) {
return;
}
nsCOMPtr<nsIThread> thread(obj->GetThread());
if (!thread) {
return;
}
thread->Dispatch(new RegisterReplyRunnable(aSdRef,
aFlags,
aErrorCode,
nsCString(aName),
nsCString(aRegType),
nsCString(domain),
obj),
NS_DISPATCH_NORMAL);
}
ResolveReplyRunnable::ResolveReplyRunnable(DNSServiceRef aSdRef,
DNSServiceFlags aFlags,
uint32_t aInterfaceIndex,
DNSServiceErrorType aErrorCode,
const nsACString& aFullName,
const nsACString& aHostTarget,
uint16_t aPort,
uint16_t aTxtLen,
const unsigned char* aTxtRecord,
ResolveOperator* aContext)
: mSdRef(aSdRef)
, mFlags(aFlags)
, mInterfaceIndex(aInterfaceIndex)
, mErrorCode(aErrorCode)
, mFullname(aFullName)
, mHosttarget(aHostTarget)
, mPort(aPort)
, mTxtLen(aTxtLen)
, mTxtRecord(new unsigned char[aTxtLen])
, mContext(aContext)
{
if (mTxtRecord) {
memcpy(mTxtRecord.get(), aTxtRecord, aTxtLen);
}
}
ResolveReplyRunnable::~ResolveReplyRunnable()
{
}
NS_IMETHODIMP
ResolveReplyRunnable::Run()
{
MOZ_ASSERT(mContext);
mContext->Reply(mSdRef,
mFlags,
mInterfaceIndex,
mErrorCode,
mFullname,
mHosttarget,
mPort,
mTxtLen,
mTxtRecord.get());
return NS_OK;
}
void
ResolveReplyRunnable::Reply(DNSServiceRef aSdRef,
DNSServiceFlags aFlags,
uint32_t aInterfaceIndex,
DNSServiceErrorType aErrorCode,
const char* aFullName,
const char* aHostTarget,
uint16_t aPort,
uint16_t aTxtLen,
const unsigned char* aTxtRecord,
void* aContext)
{
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
ResolveOperator* obj(reinterpret_cast<ResolveOperator*>(aContext));
if (!obj) {
return;
}
nsCOMPtr<nsIThread> thread(obj->GetThread());
if (!thread) {
return;
}
thread->Dispatch(new ResolveReplyRunnable(aSdRef,
aFlags,
aInterfaceIndex,
aErrorCode,
nsCString(aFullName),
nsCString(aHostTarget),
NativeEndian::swapFromNetworkOrder(aPort),
aTxtLen,
aTxtRecord,
obj),
NS_DISPATCH_NORMAL);
}
GetAddrInfoReplyRunnable::GetAddrInfoReplyRunnable(DNSServiceRef aSdRef,
DNSServiceFlags aFlags,
uint32_t aInterfaceIndex,
DNSServiceErrorType aErrorCode,
const nsACString& aHostName,
const mozilla::net::NetAddr& aAddress,
uint32_t aTTL,
GetAddrInfoOperator* aContext)
: mSdRef(aSdRef)
, mFlags(aFlags)
, mInterfaceIndex(aInterfaceIndex)
, mErrorCode(aErrorCode)
, mHostName(aHostName)
, mAddress(aAddress)
, mTTL(aTTL)
, mContext(aContext)
{
}
GetAddrInfoReplyRunnable::~GetAddrInfoReplyRunnable()
{
}
NS_IMETHODIMP
GetAddrInfoReplyRunnable::Run()
{
MOZ_ASSERT(mContext);
mContext->Reply(mSdRef,
mFlags,
mInterfaceIndex,
mErrorCode,
mHostName,
mAddress,
mTTL);
return NS_OK;
}
void
GetAddrInfoReplyRunnable::Reply(DNSServiceRef aSdRef,
DNSServiceFlags aFlags,
uint32_t aInterfaceIndex,
DNSServiceErrorType aErrorCode,
const char* aHostName,
const struct sockaddr* aAddress,
uint32_t aTTL,
void* aContext)
{
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
GetAddrInfoOperator* obj(reinterpret_cast<GetAddrInfoOperator*>(aContext));
if (!obj) {
return;
}
nsCOMPtr<nsIThread> thread(obj->GetThread());
if (!thread) {
return;
}
NetAddr address;
address.raw.family = aAddress->sa_family;
static_assert(sizeof(address.raw.data) >= sizeof(aAddress->sa_data),
"size of sockaddr.sa_data is too big");
memcpy(&address.raw.data, aAddress->sa_data, sizeof(aAddress->sa_data));
thread->Dispatch(new GetAddrInfoReplyRunnable(aSdRef,
aFlags,
aInterfaceIndex,
aErrorCode,
nsCString(aHostName),
address,
aTTL,
obj),
NS_DISPATCH_NORMAL);
}
} // namespace net
} // namespace mozilla

View file

@ -0,0 +1,164 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_netwerk_dns_mdns_libmdns_MDNSResponderReply_h
#define mozilla_netwerk_dns_mdns_libmdns_MDNSResponderReply_h
#include "dns_sd.h"
#include "MDNSResponderOperator.h"
#include "mozilla/UniquePtr.h"
#include "nsIThread.h"
#include "mozilla/net/DNS.h"
#include "mozilla/RefPtr.h"
#include "nsThreadUtils.h"
namespace mozilla {
namespace net {
class BrowseReplyRunnable final : public Runnable
{
public:
BrowseReplyRunnable(DNSServiceRef aSdRef,
DNSServiceFlags aFlags,
uint32_t aInterfaceIndex,
DNSServiceErrorType aErrorCode,
const nsACString& aServiceName,
const nsACString& aRegType,
const nsACString& aReplyDomain,
BrowseOperator* aContext);
NS_IMETHOD Run() override;
static void Reply(DNSServiceRef aSdRef,
DNSServiceFlags aFlags,
uint32_t aInterfaceIndex,
DNSServiceErrorType aErrorCode,
const char* aServiceName,
const char* aRegType,
const char* aReplyDomain,
void* aContext);
private:
DNSServiceRef mSdRef;
DNSServiceFlags mFlags;
uint32_t mInterfaceIndex;
DNSServiceErrorType mErrorCode;
nsCString mServiceName;
nsCString mRegType;
nsCString mReplyDomain;
RefPtr<BrowseOperator> mContext;
};
class RegisterReplyRunnable final : public Runnable
{
public:
RegisterReplyRunnable(DNSServiceRef aSdRef,
DNSServiceFlags aFlags,
DNSServiceErrorType aErrorCode,
const nsACString& aName,
const nsACString& aRegType,
const nsACString& aDomain,
RegisterOperator* aContext);
NS_IMETHOD Run() override;
static void Reply(DNSServiceRef aSdRef,
DNSServiceFlags aFlags,
DNSServiceErrorType aErrorCode,
const char* aName,
const char* aRegType,
const char* aDomain,
void* aContext);
private:
DNSServiceRef mSdRef;
DNSServiceFlags mFlags;
DNSServiceErrorType mErrorCode;
nsCString mName;
nsCString mRegType;
nsCString mDomain;
RefPtr<RegisterOperator> mContext;
};
class ResolveReplyRunnable final : public Runnable
{
public:
ResolveReplyRunnable(DNSServiceRef aSdRef,
DNSServiceFlags aFlags,
uint32_t aInterfaceIndex,
DNSServiceErrorType aErrorCode,
const nsACString& aFullName,
const nsACString& aHostTarget,
uint16_t aPort,
uint16_t aTxtLen,
const unsigned char* aTxtRecord,
ResolveOperator* aContext);
~ResolveReplyRunnable();
NS_IMETHOD Run() override;
static void Reply(DNSServiceRef aSdRef,
DNSServiceFlags aFlags,
uint32_t aInterfaceIndex,
DNSServiceErrorType aErrorCode,
const char* aFullName,
const char* aHostTarget,
uint16_t aPort,
uint16_t aTxtLen,
const unsigned char* aTxtRecord,
void* aContext);
private:
DNSServiceRef mSdRef;
DNSServiceFlags mFlags;
uint32_t mInterfaceIndex;
DNSServiceErrorType mErrorCode;
nsCString mFullname;
nsCString mHosttarget;
uint16_t mPort;
uint16_t mTxtLen;
UniquePtr<unsigned char> mTxtRecord;
RefPtr<ResolveOperator> mContext;
};
class GetAddrInfoReplyRunnable final : public Runnable
{
public:
GetAddrInfoReplyRunnable(DNSServiceRef aSdRef,
DNSServiceFlags aFlags,
uint32_t aInterfaceIndex,
DNSServiceErrorType aErrorCode,
const nsACString& aHostName,
const mozilla::net::NetAddr& aAddress,
uint32_t aTTL,
GetAddrInfoOperator* aContext);
~GetAddrInfoReplyRunnable();
NS_IMETHOD Run() override;
static void Reply(DNSServiceRef aSdRef,
DNSServiceFlags aFlags,
uint32_t aInterfaceIndex,
DNSServiceErrorType aErrorCode,
const char* aHostName,
const struct sockaddr* aAddress,
uint32_t aTTL,
void* aContext);
private:
DNSServiceRef mSdRef;
DNSServiceFlags mFlags;
uint32_t mInterfaceIndex;
DNSServiceErrorType mErrorCode;
nsCString mHostName;
mozilla::net::NetAddr mAddress;
uint32_t mTTL;
RefPtr<GetAddrInfoOperator> mContext;
};
} // namespace net
} // namespace mozilla
#endif // mozilla_netwerk_dns_mdns_libmdns_MDNSResponderReply_h

View file

@ -0,0 +1,244 @@
// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
this.EXPORTED_SYMBOLS = ["MulticastDNS"];
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/Messaging.jsm");
Cu.import("resource://gre/modules/Services.jsm");
var log = Cu.import("resource://gre/modules/AndroidLog.jsm", {}).AndroidLog.d.bind(null, "MulticastDNS");
const FAILURE_INTERNAL_ERROR = -65537;
// Helper function for sending commands to Java.
function send(type, data, callback) {
let msg = {
type: type
};
for (let i in data) {
try {
msg[i] = data[i];
} catch (e) {
}
}
Messaging.sendRequestForResult(msg)
.then(result => callback(result, null),
err => callback(null, typeof err === "number" ? err : FAILURE_INTERNAL_ERROR));
}
// Receives service found/lost event from NsdManager
function ServiceManager() {
}
ServiceManager.prototype = {
listeners: {},
numListeners: 0,
registerEvent: function() {
log("registerEvent");
Messaging.addListener(this.onServiceFound.bind(this), "NsdManager:ServiceFound");
Messaging.addListener(this.onServiceLost.bind(this), "NsdManager:ServiceLost");
},
unregisterEvent: function() {
log("unregisterEvent");
Messaging.removeListener("NsdManager:ServiceFound");
Messaging.removeListener("NsdManager:ServiceLost");
},
addListener: function(aServiceType, aListener) {
log("addListener: " + aServiceType + ", " + aListener);
if (!this.listeners[aServiceType]) {
this.listeners[aServiceType] = [];
}
if (this.listeners[aServiceType].includes(aListener)) {
log("listener already exists");
return;
}
this.listeners[aServiceType].push(aListener);
++this.numListeners;
if (this.numListeners === 1) {
this.registerEvent();
}
log("listener added: " + this);
},
removeListener: function(aServiceType, aListener) {
log("removeListener: " + aServiceType + ", " + aListener);
if (!this.listeners[aServiceType]) {
log("listener doesn't exist");
return;
}
let index = this.listeners[aServiceType].indexOf(aListener);
if (index < 0) {
log("listener doesn't exist");
return;
}
this.listeners[aServiceType].splice(index, 1);
--this.numListeners;
if (this.numListeners === 0) {
this.unregisterEvent();
}
log("listener removed" + this);
},
onServiceFound: function(aServiceInfo) {
let listeners = this.listeners[aServiceInfo.serviceType];
if (listeners) {
for (let listener of listeners) {
listener.onServiceFound(aServiceInfo);
}
} else {
log("no listener");
}
return {};
},
onServiceLost: function(aServiceInfo) {
let listeners = this.listeners[aServiceInfo.serviceType];
if (listeners) {
for (let listener of listeners) {
listener.onServiceLost(aServiceInfo);
}
} else {
log("no listener");
}
return {};
}
};
// make an object from nsIPropertyBag2
function parsePropertyBag2(bag) {
if (!bag || !(bag instanceof Ci.nsIPropertyBag2)) {
throw new TypeError("Not a property bag");
}
let attributes = [];
let enumerator = bag.enumerator;
while (enumerator.hasMoreElements()) {
let name = enumerator.getNext().QueryInterface(Ci.nsIProperty).name;
let value = bag.getPropertyAsACString(name);
attributes.push({
"name": name,
"value": value
});
}
return attributes;
}
function MulticastDNS() {
this.serviceManager = new ServiceManager();
}
MulticastDNS.prototype = {
startDiscovery: function(aServiceType, aListener) {
this.serviceManager.addListener(aServiceType, aListener);
let serviceInfo = {
serviceType: aServiceType,
uniqueId: aListener.uuid
};
send("NsdManager:DiscoverServices", serviceInfo, (result, err) => {
if (err) {
log("onStartDiscoveryFailed: " + aServiceType + " (" + err + ")");
this.serviceManager.removeListener(aServiceType, aListener);
aListener.onStartDiscoveryFailed(aServiceType, err);
} else {
aListener.onDiscoveryStarted(result);
}
});
},
stopDiscovery: function(aServiceType, aListener) {
this.serviceManager.removeListener(aServiceType, aListener);
let serviceInfo = {
uniqueId: aListener.uuid
};
send("NsdManager:StopServiceDiscovery", serviceInfo, (result, err) => {
if (err) {
log("onStopDiscoveryFailed: " + aServiceType + " (" + err + ")");
aListener.onStopDiscoveryFailed(aServiceType, err);
} else {
aListener.onDiscoveryStopped(aServiceType);
}
});
},
registerService: function(aServiceInfo, aListener) {
let serviceInfo = {
port: aServiceInfo.port,
serviceType: aServiceInfo.serviceType,
uniqueId: aListener.uuid
};
try {
serviceInfo.host = aServiceInfo.host;
} catch(e) {
// host unspecified
}
try {
serviceInfo.serviceName = aServiceInfo.serviceName;
} catch(e) {
// serviceName unspecified
}
try {
serviceInfo.attributes = parsePropertyBag2(aServiceInfo.attributes);
} catch(e) {
// attributes unspecified
}
send("NsdManager:RegisterService", serviceInfo, (result, err) => {
if (err) {
log("onRegistrationFailed: (" + err + ")");
aListener.onRegistrationFailed(aServiceInfo, err);
} else {
aListener.onServiceRegistered(result);
}
});
},
unregisterService: function(aServiceInfo, aListener) {
let serviceInfo = {
uniqueId: aListener.uuid
};
send("NsdManager:UnregisterService", serviceInfo, (result, err) => {
if (err) {
log("onUnregistrationFailed: (" + err + ")");
aListener.onUnregistrationFailed(aServiceInfo, err);
} else {
aListener.onServiceUnregistered(aServiceInfo);
}
});
},
resolveService: function(aServiceInfo, aListener) {
send("NsdManager:ResolveService", aServiceInfo, (result, err) => {
if (err) {
log("onResolveFailed: (" + err + ")");
aListener.onResolveFailed(aServiceInfo, err);
} else {
aListener.onServiceResolved(result);
}
});
}
};

View file

@ -0,0 +1,297 @@
/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil; tab-width: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
/* jshint esnext: true, moz: true */
'use strict';
this.EXPORTED_SYMBOLS = ['DNSPacket'];
const { utils: Cu } = Components;
Cu.import('resource://gre/modules/Services.jsm');
Cu.import('resource://gre/modules/DataReader.jsm');
Cu.import('resource://gre/modules/DataWriter.jsm');
Cu.import('resource://gre/modules/DNSRecord.jsm');
Cu.import('resource://gre/modules/DNSResourceRecord.jsm');
const DEBUG = true;
function debug(msg) {
Services.console.logStringMessage('DNSPacket: ' + msg);
}
let DNS_PACKET_SECTION_TYPES = [
'QD', // Question
'AN', // Answer
'NS', // Authority
'AR' // Additional
];
/**
* DNS Packet Structure
* *************************************************
*
* Header
* ======
*
* 00 2-Bytes 15
* -------------------------------------------------
* |00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|
* -------------------------------------------------
* |<==================== ID =====================>|
* |QR|<== OP ===>|AA|TC|RD|RA|UN|AD|CD|<== RC ===>|
* |<================== QDCOUNT ==================>|
* |<================== ANCOUNT ==================>|
* |<================== NSCOUNT ==================>|
* |<================== ARCOUNT ==================>|
* -------------------------------------------------
*
* ID: 2-Bytes
* FLAGS: 2-Bytes
* - QR: 1-Bit
* - OP: 4-Bits
* - AA: 1-Bit
* - TC: 1-Bit
* - RD: 1-Bit
* - RA: 1-Bit
* - UN: 1-Bit
* - AD: 1-Bit
* - CD: 1-Bit
* - RC: 4-Bits
* QDCOUNT: 2-Bytes
* ANCOUNT: 2-Bytes
* NSCOUNT: 2-Bytes
* ARCOUNT: 2-Bytes
*
*
* Data
* ====
*
* 00 2-Bytes 15
* -------------------------------------------------
* |00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|
* -------------------------------------------------
* |<???=============== QD[...] ===============???>|
* |<???=============== AN[...] ===============???>|
* |<???=============== NS[...] ===============???>|
* |<???=============== AR[...] ===============???>|
* -------------------------------------------------
*
* QD: ??-Bytes
* AN: ??-Bytes
* NS: ??-Bytes
* AR: ??-Bytes
*
*
* Question Record
* ===============
*
* 00 2-Bytes 15
* -------------------------------------------------
* |00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|
* -------------------------------------------------
* |<???================ NAME =================???>|
* |<=================== TYPE ====================>|
* |<=================== CLASS ===================>|
* -------------------------------------------------
*
* NAME: ??-Bytes
* TYPE: 2-Bytes
* CLASS: 2-Bytes
*
*
* Resource Record
* ===============
*
* 00 4-Bytes 31
* -------------------------------------------------
* |00|02|04|06|08|10|12|14|16|18|20|22|24|26|28|30|
* -------------------------------------------------
* |<???================ NAME =================???>|
* |<======= TYPE ========>|<======= CLASS =======>|
* |<==================== TTL ====================>|
* |<====== DATALEN ======>|<???==== DATA =====???>|
* -------------------------------------------------
*
* NAME: ??-Bytes
* TYPE: 2-Bytes
* CLASS: 2-Bytes
* DATALEN: 2-Bytes
* DATA: ??-Bytes (Specified By DATALEN)
*/
class DNSPacket {
constructor() {
this._flags = _valueToFlags(0x0000);
this._records = {};
DNS_PACKET_SECTION_TYPES.forEach((sectionType) => {
this._records[sectionType] = [];
});
}
static parse(data) {
let reader = new DataReader(data);
if (reader.getValue(2) !== 0x0000) {
throw new Error('Packet must start with 0x0000');
}
let packet = new DNSPacket();
packet._flags = _valueToFlags(reader.getValue(2));
let recordCounts = {};
// Parse the record counts.
DNS_PACKET_SECTION_TYPES.forEach((sectionType) => {
recordCounts[sectionType] = reader.getValue(2);
});
// Parse the actual records.
DNS_PACKET_SECTION_TYPES.forEach((sectionType) => {
let recordCount = recordCounts[sectionType];
for (let i = 0; i < recordCount; i++) {
if (sectionType === 'QD') {
packet.addRecord(sectionType,
DNSRecord.parseFromPacketReader(reader));
}
else {
packet.addRecord(sectionType,
DNSResourceRecord.parseFromPacketReader(reader));
}
}
});
if (!reader.eof) {
DEBUG && debug('Did not complete parsing packet data');
}
return packet;
}
getFlag(flag) {
return this._flags[flag];
}
setFlag(flag, value) {
this._flags[flag] = value;
}
addRecord(sectionType, record) {
this._records[sectionType].push(record);
}
getRecords(sectionTypes, recordType) {
let records = [];
sectionTypes.forEach((sectionType) => {
records = records.concat(this._records[sectionType]);
});
if (!recordType) {
return records;
}
return records.filter(r => r.recordType === recordType);
}
serialize() {
let writer = new DataWriter();
// Write leading 0x0000 (2 bytes)
writer.putValue(0x0000, 2);
// Write `flags` (2 bytes)
writer.putValue(_flagsToValue(this._flags), 2);
// Write lengths of record sections (2 bytes each)
DNS_PACKET_SECTION_TYPES.forEach((sectionType) => {
writer.putValue(this._records[sectionType].length, 2);
});
// Write records
DNS_PACKET_SECTION_TYPES.forEach((sectionType) => {
this._records[sectionType].forEach((record) => {
writer.putBytes(record.serialize());
});
});
return writer.data;
}
toJSON() {
return JSON.stringify(this.toJSONObject());
}
toJSONObject() {
let result = {flags: this._flags};
DNS_PACKET_SECTION_TYPES.forEach((sectionType) => {
result[sectionType] = [];
let records = this._records[sectionType];
records.forEach((record) => {
result[sectionType].push(record.toJSONObject());
});
});
return result;
}
}
/**
* @private
*/
function _valueToFlags(value) {
return {
QR: (value & 0x8000) >> 15,
OP: (value & 0x7800) >> 11,
AA: (value & 0x0400) >> 10,
TC: (value & 0x0200) >> 9,
RD: (value & 0x0100) >> 8,
RA: (value & 0x0080) >> 7,
UN: (value & 0x0040) >> 6,
AD: (value & 0x0020) >> 5,
CD: (value & 0x0010) >> 4,
RC: (value & 0x000f) >> 0
};
}
/**
* @private
*/
function _flagsToValue(flags) {
let value = 0x0000;
value += flags.QR & 0x01;
value <<= 4;
value += flags.OP & 0x0f;
value <<= 1;
value += flags.AA & 0x01;
value <<= 1;
value += flags.TC & 0x01;
value <<= 1;
value += flags.RD & 0x01;
value <<= 1;
value += flags.RA & 0x01;
value <<= 1;
value += flags.UN & 0x01;
value <<= 1;
value += flags.AD & 0x01;
value <<= 1;
value += flags.CD & 0x01;
value <<= 4;
value += flags.RC & 0x0f;
return value;
}

View file

@ -0,0 +1,70 @@
/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil; tab-width: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
/* jshint esnext: true, moz: true */
'use strict';
this.EXPORTED_SYMBOLS = ['DNSRecord'];
const { utils: Cu } = Components;
Cu.import('resource://gre/modules/DataWriter.jsm');
Cu.import('resource://gre/modules/DNSTypes.jsm');
class DNSRecord {
constructor(properties = {}) {
this.name = properties.name || '';
this.recordType = properties.recordType || DNS_RECORD_TYPES.ANY;
this.classCode = properties.classCode || DNS_CLASS_CODES.IN;
this.cacheFlush = properties.cacheFlush || false;
}
static parseFromPacketReader(reader) {
let name = reader.getLabel();
let recordType = reader.getValue(2);
let classCode = reader.getValue(2);
let cacheFlush = (classCode & 0x8000) ? true : false;
classCode &= 0xff;
return new this({
name: name,
recordType: recordType,
classCode: classCode,
cacheFlush: cacheFlush
});
}
serialize() {
let writer = new DataWriter();
// Write `name` (ends with trailing 0x00 byte)
writer.putLabel(this.name);
// Write `recordType` (2 bytes)
writer.putValue(this.recordType, 2);
// Write `classCode` (2 bytes)
let classCode = this.classCode;
if (this.cacheFlush) {
classCode |= 0x8000;
}
writer.putValue(classCode, 2);
return writer.data;
}
toJSON() {
return JSON.stringify(this.toJSONObject());
}
toJSONObject() {
return {
name: this.name,
recordType: this.recordType,
classCode: this.classCode,
cacheFlush: this.cacheFlush
};
}
}

View file

@ -0,0 +1,221 @@
/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil; tab-width: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
/* jshint esnext: true, moz: true */
'use strict';
this.EXPORTED_SYMBOLS = ['DNSResourceRecord'];
const { utils: Cu } = Components;
Cu.import('resource://gre/modules/Services.jsm');
Cu.import('resource://gre/modules/DataReader.jsm');
Cu.import('resource://gre/modules/DataWriter.jsm');
Cu.import('resource://gre/modules/DNSRecord.jsm');
Cu.import('resource://gre/modules/DNSTypes.jsm');
function debug(msg) {
Services.console.logStringMessage('MulticastDNS: ' + msg);
}
const DNS_RESOURCE_RECORD_DEFAULT_TTL = 120; // 120 seconds
class DNSResourceRecord extends DNSRecord {
constructor(properties = {}) {
super(properties);
this.ttl = properties.ttl || DNS_RESOURCE_RECORD_DEFAULT_TTL;
this.data = properties.data || {};
}
static parseFromPacketReader(reader) {
let record = super.parseFromPacketReader(reader);
let ttl = reader.getValue(4);
let recordData = reader.getBytes(reader.getValue(2));
let packetData = reader.data;
let data;
switch (record.recordType) {
case DNS_RECORD_TYPES.A:
data = _parseA(recordData, packetData);
break;
case DNS_RECORD_TYPES.PTR:
data = _parsePTR(recordData, packetData);
break;
case DNS_RECORD_TYPES.TXT:
data = _parseTXT(recordData, packetData);
break;
case DNS_RECORD_TYPES.SRV:
data = _parseSRV(recordData, packetData);
break;
default:
data = null;
break;
}
record.ttl = ttl;
record.data = data;
return record;
}
serialize() {
let writer = new DataWriter(super.serialize());
// Write `ttl` (4 bytes)
writer.putValue(this.ttl, 4);
let data;
switch (this.recordType) {
case DNS_RECORD_TYPES.A:
data = _serializeA(this.data);
break;
case DNS_RECORD_TYPES.PTR:
data = _serializePTR(this.data);
break;
case DNS_RECORD_TYPES.TXT:
data = _serializeTXT(this.data);
break;
case DNS_RECORD_TYPES.SRV:
data = _serializeSRV(this.data);
break;
default:
data = new Uint8Array();
break;
}
// Write `data` length.
writer.putValue(data.length, 2);
// Write `data` (ends with trailing 0x00 byte)
writer.putBytes(data);
return writer.data;
}
toJSON() {
return JSON.stringify(this.toJSONObject());
}
toJSONObject() {
let result = super.toJSONObject();
result.ttl = this.ttl;
result.data = this.data;
return result;
}
}
/**
* @private
*/
function _parseA(recordData, packetData) {
let reader = new DataReader(recordData);
let parts = [];
for (let i = 0; i < 4; i++) {
parts.push(reader.getValue(1));
}
return parts.join('.');
}
/**
* @private
*/
function _parsePTR(recordData, packetData) {
let reader = new DataReader(recordData);
return reader.getLabel(packetData);
}
/**
* @private
*/
function _parseTXT(recordData, packetData) {
let reader = new DataReader(recordData);
let result = {};
let label = reader.getLabel(packetData);
if (label.length > 0) {
let parts = label.split('.');
parts.forEach((part) => {
let [name] = part.split('=', 1);
let value = part.substr(name.length + 1);
result[name] = value;
});
}
return result;
}
/**
* @private
*/
function _parseSRV(recordData, packetData) {
let reader = new DataReader(recordData);
let priority = reader.getValue(2);
let weight = reader.getValue(2);
let port = reader.getValue(2);
let target = reader.getLabel(packetData);
return { priority, weight, port, target };
}
/**
* @private
*/
function _serializeA(data) {
let writer = new DataWriter();
let parts = data.split('.');
for (let i = 0; i < 4; i++) {
writer.putValue(parseInt(parts[i], 10) || 0);
}
return writer.data;
}
/**
* @private
*/
function _serializePTR(data) {
let writer = new DataWriter();
writer.putLabel(data);
return writer.data;
}
/**
* @private
*/
function _serializeTXT(data) {
let writer = new DataWriter();
for (let name in data) {
writer.putLengthString(name + '=' + data[name]);
}
return writer.data;
}
/**
* @private
*/
function _serializeSRV(data) {
let writer = new DataWriter();
writer.putValue(data.priority || 0, 2);
writer.putValue(data.weight || 0, 2);
writer.putValue(data.port || 0, 2);
writer.putLabel(data.target);
return writer.data;
}

View file

@ -0,0 +1,100 @@
/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil; tab-width: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
/* jshint esnext: true, moz: true */
'use strict';
this.EXPORTED_SYMBOLS = [
'DNS_QUERY_RESPONSE_CODES',
'DNS_AUTHORITATIVE_ANSWER_CODES',
'DNS_CLASS_CODES',
'DNS_RECORD_TYPES'
];
let DNS_QUERY_RESPONSE_CODES = {
QUERY : 0, // RFC 1035 - Query
RESPONSE : 1 // RFC 1035 - Reponse
};
let DNS_AUTHORITATIVE_ANSWER_CODES = {
NO : 0, // RFC 1035 - Not Authoritative
YES : 1 // RFC 1035 - Is Authoritative
};
let DNS_CLASS_CODES = {
IN : 0x01, // RFC 1035 - Internet
CS : 0x02, // RFC 1035 - CSNET
CH : 0x03, // RFC 1035 - CHAOS
HS : 0x04, // RFC 1035 - Hesiod
NONE : 0xfe, // RFC 2136 - None
ANY : 0xff, // RFC 1035 - Any
};
let DNS_RECORD_TYPES = {
SIGZERO : 0, // RFC 2931
A : 1, // RFC 1035
NS : 2, // RFC 1035
MD : 3, // RFC 1035
MF : 4, // RFC 1035
CNAME : 5, // RFC 1035
SOA : 6, // RFC 1035
MB : 7, // RFC 1035
MG : 8, // RFC 1035
MR : 9, // RFC 1035
NULL : 10, // RFC 1035
WKS : 11, // RFC 1035
PTR : 12, // RFC 1035
HINFO : 13, // RFC 1035
MINFO : 14, // RFC 1035
MX : 15, // RFC 1035
TXT : 16, // RFC 1035
RP : 17, // RFC 1183
AFSDB : 18, // RFC 1183
X25 : 19, // RFC 1183
ISDN : 20, // RFC 1183
RT : 21, // RFC 1183
NSAP : 22, // RFC 1706
NSAP_PTR : 23, // RFC 1348
SIG : 24, // RFC 2535
KEY : 25, // RFC 2535
PX : 26, // RFC 2163
GPOS : 27, // RFC 1712
AAAA : 28, // RFC 1886
LOC : 29, // RFC 1876
NXT : 30, // RFC 2535
EID : 31, // RFC ????
NIMLOC : 32, // RFC ????
SRV : 33, // RFC 2052
ATMA : 34, // RFC ????
NAPTR : 35, // RFC 2168
KX : 36, // RFC 2230
CERT : 37, // RFC 2538
DNAME : 39, // RFC 2672
OPT : 41, // RFC 2671
APL : 42, // RFC 3123
DS : 43, // RFC 4034
SSHFP : 44, // RFC 4255
IPSECKEY : 45, // RFC 4025
RRSIG : 46, // RFC 4034
NSEC : 47, // RFC 4034
DNSKEY : 48, // RFC 4034
DHCID : 49, // RFC 4701
NSEC3 : 50, // RFC ????
NSEC3PARAM : 51, // RFC ????
HIP : 55, // RFC 5205
SPF : 99, // RFC 4408
UINFO : 100, // RFC ????
UID : 101, // RFC ????
GID : 102, // RFC ????
UNSPEC : 103, // RFC ????
TKEY : 249, // RFC 2930
TSIG : 250, // RFC 2931
IXFR : 251, // RFC 1995
AXFR : 252, // RFC 1035
MAILB : 253, // RFC 1035
MAILA : 254, // RFC 1035
ANY : 255, // RFC 1035
DLV : 32769 // RFC 4431
};

View file

@ -0,0 +1,133 @@
/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil; tab-width: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
/* jshint esnext: true, moz: true */
'use strict';
this.EXPORTED_SYMBOLS = ['DataReader'];
class DataReader {
// `data` is `Uint8Array`
constructor(data, startByte = 0) {
this._data = data;
this._cursor = startByte;
}
get buffer() {
return this._data.buffer;
}
get data() {
return this._data;
}
get eof() {
return this._cursor >= this._data.length;
}
getBytes(length = 1) {
if (!length) {
return new Uint8Array();
}
let end = this._cursor + length;
if (end > this._data.length) {
return new Uint8Array();
}
let uint8Array = new Uint8Array(this.buffer.slice(this._cursor, end));
this._cursor += length;
return uint8Array;
}
getString(length) {
let uint8Array = this.getBytes(length);
return _uint8ArrayToString(uint8Array);
}
getValue(length) {
let uint8Array = this.getBytes(length);
return _uint8ArrayToValue(uint8Array);
}
getLabel(decompressData) {
let parts = [];
let partLength;
while ((partLength = this.getValue(1))) {
// If a length has been specified instead of a pointer,
// read the string of the specified length.
if (partLength !== 0xc0) {
parts.push(this.getString(partLength));
continue;
}
// TODO: Handle case where we have a pointer to the label
parts.push(String.fromCharCode(0xc0) + this.getString(1));
break;
}
let label = parts.join('.');
return _decompressLabel(label, decompressData || this._data);
}
}
/**
* @private
*/
function _uint8ArrayToValue(uint8Array) {
let length = uint8Array.length;
if (length === 0) {
return null;
}
let value = 0;
for (let i = 0; i < length; i++) {
value = value << 8;
value += uint8Array[i];
}
return value;
}
/**
* @private
*/
function _uint8ArrayToString(uint8Array) {
let length = uint8Array.length;
if (length === 0) {
return '';
}
let results = [];
for (let i = 0; i < length; i += 1024) {
results.push(String.fromCharCode.apply(null, uint8Array.subarray(i, i + 1024)));
}
return results.join('');
}
/**
* @private
*/
function _decompressLabel(label, decompressData) {
let result = '';
for (let i = 0, length = label.length; i < length; i++) {
if (label.charCodeAt(i) !== 0xc0) {
result += label.charAt(i);
continue;
}
i++;
let reader = new DataReader(decompressData, label.charCodeAt(i));
result += _decompressLabel(reader.getLabel(), decompressData);
}
return result;
}

View file

@ -0,0 +1,98 @@
/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil; tab-width: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
/* jshint esnext: true, moz: true */
'use strict';
this.EXPORTED_SYMBOLS = ['DataWriter'];
class DataWriter {
constructor(data, maxBytes = 512) {
if (typeof data === 'number') {
maxBytes = data;
data = undefined;
}
this._buffer = new ArrayBuffer(maxBytes);
this._data = new Uint8Array(this._buffer);
this._cursor = 0;
if (data) {
this.putBytes(data);
}
}
get buffer() {
return this._buffer.slice(0, this._cursor);
}
get data() {
return new Uint8Array(this.buffer);
}
// `data` is `Uint8Array`
putBytes(data) {
if (this._cursor + data.length > this._data.length) {
throw new Error('DataWriter buffer is exceeded');
}
for (let i = 0, length = data.length; i < length; i++) {
this._data[this._cursor] = data[i];
this._cursor++;
}
}
putByte(byte) {
if (this._cursor + 1 > this._data.length) {
throw new Error('DataWriter buffer is exceeded');
}
this._data[this._cursor] = byte
this._cursor++;
}
putValue(value, length) {
length = length || 1;
if (length == 1) {
this.putByte(value);
} else {
this.putBytes(_valueToUint8Array(value, length));
}
}
putLabel(label) {
// Eliminate any trailing '.'s in the label (valid in text representation).
label = label.replace(/\.$/, '');
let parts = label.split('.');
parts.forEach((part) => {
this.putLengthString(part);
});
this.putValue(0);
}
putLengthString(string) {
if (string.length > 0xff) {
throw new Error("String too long.");
}
this.putValue(string.length);
for (let i = 0; i < string.length; i++) {
this.putValue(string.charCodeAt(i));
}
}
}
/**
* @private
*/
function _valueToUint8Array(value, length) {
let arrayBuffer = new ArrayBuffer(length);
let uint8Array = new Uint8Array(arrayBuffer);
for (let i = length - 1; i >= 0; i--) {
uint8Array[i] = value & 0xff;
value = value >> 8;
}
return uint8Array;
}

View file

@ -0,0 +1,875 @@
/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil; tab-width: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
/* jshint esnext: true, moz: true */
'use strict';
this.EXPORTED_SYMBOLS = ['MulticastDNS'];
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
Cu.import('resource://gre/modules/Services.jsm');
Cu.import('resource://gre/modules/Timer.jsm');
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
Cu.import('resource://gre/modules/DNSPacket.jsm');
Cu.import('resource://gre/modules/DNSRecord.jsm');
Cu.import('resource://gre/modules/DNSResourceRecord.jsm');
Cu.import('resource://gre/modules/DNSTypes.jsm');
const NS_NETWORK_LINK_TOPIC = 'network:link-status-changed';
let observerService = Cc["@mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService);
let networkInfoService = Cc['@mozilla.org/network-info-service;1']
.createInstance(Ci.nsINetworkInfoService);
const DEBUG = true;
const MDNS_MULTICAST_GROUP = '224.0.0.251';
const MDNS_PORT = 5353;
const DEFAULT_TTL = 120;
function debug(msg) {
dump('MulticastDNS: ' + msg + '\n');
}
function ServiceKey(svc) {
return "" + svc.serviceType.length + "/" + svc.serviceType + "|" +
svc.serviceName.length + "/" + svc.serviceName + "|" +
svc.port;
}
function TryGet(obj, name) {
try {
return obj[name];
} catch (err) {
return undefined;
}
}
function IsIpv4Address(addr) {
let parts = addr.split('.');
if (parts.length != 4) {
return false;
}
for (let part of parts) {
let partInt = Number.parseInt(part, 10);
if (partInt.toString() != part) {
return false;
}
if (partInt < 0 || partInt >= 256) {
return false;
}
}
return true;
}
class PublishedService {
constructor(attrs) {
this.serviceType = attrs.serviceType.replace(/\.$/, '');
this.serviceName = attrs.serviceName;
this.domainName = TryGet(attrs, 'domainName') || "local";
this.address = TryGet(attrs, 'address') || "0.0.0.0";
this.port = attrs.port;
this.serviceAttrs = _propertyBagToObject(TryGet(attrs, 'attributes') || {});
this.host = TryGet(attrs, 'host');
this.key = this.generateKey();
this.lastAdvertised = undefined;
this.advertiseTimer = undefined;
}
equals(svc) {
return (this.port == svc.port) &&
(this.serviceName == svc.serviceName) &&
(this.serviceType == svc.serviceType);
}
generateKey() {
return ServiceKey(this);
}
ptrMatch(name) {
return name == (this.serviceType + "." + this.domainName);
}
clearAdvertiseTimer() {
if (!this.advertiseTimer) {
return;
}
clearTimeout(this.advertiseTimer);
this.advertiseTimer = undefined;
}
}
class MulticastDNS {
constructor() {
this._listeners = new Map();
this._sockets = new Map();
this._services = new Map();
this._discovered = new Map();
this._querySocket = undefined;
this._broadcastReceiverSocket = undefined;
this._broadcastTimer = undefined;
this._networkLinkObserver = {
observe: (subject, topic, data) => {
DEBUG && debug(NS_NETWORK_LINK_TOPIC + '(' + data + '); Clearing list of previously discovered services');
this._discovered.clear();
}
};
}
_attachNetworkLinkObserver() {
if (this._networkLinkObserverTimeout) {
clearTimeout(this._networkLinkObserverTimeout);
}
if (!this._isNetworkLinkObserverAttached) {
DEBUG && debug('Attaching observer ' + NS_NETWORK_LINK_TOPIC);
observerService.addObserver(this._networkLinkObserver, NS_NETWORK_LINK_TOPIC, false);
this._isNetworkLinkObserverAttached = true;
}
}
_detachNetworkLinkObserver() {
if (this._isNetworkLinkObserverAttached) {
if (this._networkLinkObserverTimeout) {
clearTimeout(this._networkLinkObserverTimeout);
}
this._networkLinkObserverTimeout = setTimeout(() => {
DEBUG && debug('Detaching observer ' + NS_NETWORK_LINK_TOPIC);
observerService.removeObserver(this._networkLinkObserver, NS_NETWORK_LINK_TOPIC);
this._isNetworkLinkObserverAttached = false;
this._networkLinkObserverTimeout = null;
}, 5000);
}
}
startDiscovery(aServiceType, aListener) {
DEBUG && debug('startDiscovery("' + aServiceType + '")');
let { serviceType } = _parseServiceDomainName(aServiceType);
this._attachNetworkLinkObserver();
this._addServiceListener(serviceType, aListener);
try {
this._query(serviceType + '.local');
aListener.onDiscoveryStarted(serviceType);
} catch (e) {
DEBUG && debug('startDiscovery("' + serviceType + '") FAILED: ' + e);
this._removeServiceListener(serviceType, aListener);
aListener.onStartDiscoveryFailed(serviceType, Cr.NS_ERROR_FAILURE);
}
}
stopDiscovery(aServiceType, aListener) {
DEBUG && debug('stopDiscovery("' + aServiceType + '")');
let { serviceType } = _parseServiceDomainName(aServiceType);
this._detachNetworkLinkObserver();
this._removeServiceListener(serviceType, aListener);
aListener.onDiscoveryStopped(serviceType);
this._checkCloseSockets();
}
resolveService(aServiceInfo, aListener) {
DEBUG && debug('resolveService(): ' + aServiceInfo.serviceName);
// Address info is already resolved during discovery
setTimeout(() => aListener.onServiceResolved(aServiceInfo));
}
registerService(aServiceInfo, aListener) {
DEBUG && debug('registerService(): ' + aServiceInfo.serviceName);
// Initialize the broadcast receiver socket in case it
// hasn't already been started so we can listen for
// multicast queries/announcements on all interfaces.
this._getBroadcastReceiverSocket();
for (let name of ['port', 'serviceName', 'serviceType']) {
if (!TryGet(aServiceInfo, name)) {
aListener.onRegistrationFailed(aServiceInfo, Cr.NS_ERROR_FAILURE);
throw new Error('Invalid nsIDNSServiceInfo; Missing "' + name + '"');
}
}
let publishedService;
try {
publishedService = new PublishedService(aServiceInfo);
} catch (e) {
DEBUG && debug("Error constructing PublishedService: " + e + " - " + e.stack);
setTimeout(() => aListener.onRegistrationFailed(aServiceInfo, Cr.NS_ERROR_FAILURE));
return;
}
// Ensure such a service does not already exist.
if (this._services.get(publishedService.key)) {
setTimeout(() => aListener.onRegistrationFailed(aServiceInfo, Cr.NS_ERROR_FAILURE));
return;
}
// Make sure that the service addr is '0.0.0.0', or there is at least one
// socket open on the address the service is open on.
this._getSockets().then((sockets) => {
if (publishedService.address != '0.0.0.0' && !sockets.get(publishedService.address)) {
setTimeout(() => aListener.onRegistrationFailed(aServiceInfo, Cr.NS_ERROR_FAILURE));
return;
}
this._services.set(publishedService.key, publishedService);
// Service registered.. call onServiceRegistered on next tick.
setTimeout(() => aListener.onServiceRegistered(aServiceInfo));
// Set a timeout to start advertising the service too.
publishedService.advertiseTimer = setTimeout(() => {
this._advertiseService(publishedService.key, /* firstAdv = */ true);
});
});
}
unregisterService(aServiceInfo, aListener) {
DEBUG && debug('unregisterService(): ' + aServiceInfo.serviceName);
let serviceKey;
try {
serviceKey = ServiceKey(aServiceInfo);
} catch (e) {
setTimeout(() => aListener.onUnregistrationFailed(aServiceInfo, Cr.NS_ERROR_FAILURE));
return;
}
let publishedService = this._services.get(serviceKey);
if (!publishedService) {
setTimeout(() => aListener.onUnregistrationFailed(aServiceInfo, Cr.NS_ERROR_FAILURE));
return;
}
// Clear any advertise timeout for this published service.
publishedService.clearAdvertiseTimer();
// Delete the service from the service map.
if (!this._services.delete(serviceKey)) {
setTimeout(() => aListener.onUnregistrationFailed(aServiceInfo, Cr.NS_ERROR_FAILURE));
return;
}
// Check the broadcast timer again to rejig when it should run next.
this._checkStartBroadcastTimer();
// Check to see if sockets should be closed, and if so close them.
this._checkCloseSockets();
aListener.onServiceUnregistered(aServiceInfo);
}
_respondToQuery(serviceKey, message) {
let address = message.fromAddr.address;
let port = message.fromAddr.port;
DEBUG && debug('_respondToQuery(): key=' + serviceKey + ', fromAddr='
+ address + ":" + port);
let publishedService = this._services.get(serviceKey);
if (!publishedService) {
debug("_respondToQuery Could not find service (key=" + serviceKey + ")");
return;
}
DEBUG && debug('_respondToQuery(): key=' + serviceKey + ': SENDING RESPONSE');
this._advertiseServiceHelper(publishedService, {address,port});
}
_advertiseService(serviceKey, firstAdv) {
DEBUG && debug('_advertiseService(): key=' + serviceKey);
let publishedService = this._services.get(serviceKey);
if (!publishedService) {
debug("_advertiseService Could not find service to advertise (key=" + serviceKey + ")");
return;
}
publishedService.advertiseTimer = undefined;
this._advertiseServiceHelper(publishedService, null).then(() => {
// If first advertisement, re-advertise in 1 second.
// Otherwise, set the lastAdvertised time.
if (firstAdv) {
publishedService.advertiseTimer = setTimeout(() => {
this._advertiseService(serviceKey)
}, 1000);
} else {
publishedService.lastAdvertised = Date.now();
this._checkStartBroadcastTimer();
}
});
}
_advertiseServiceHelper(svc, target) {
if (!target) {
target = {address:MDNS_MULTICAST_GROUP, port:MDNS_PORT};
}
return this._getSockets().then((sockets) => {
sockets.forEach((socket, address) => {
if (svc.address == "0.0.0.0" || address == svc.address)
{
let packet = this._makeServicePacket(svc, [address]);
let data = packet.serialize();
try {
socket.send(target.address, target.port, data, data.length);
} catch (err) {
DEBUG && debug("Failed to send packet to "
+ target.address + ":" + target.port);
}
}
});
});
}
_cancelBroadcastTimer() {
if (!this._broadcastTimer) {
return;
}
clearTimeout(this._broadcastTimer);
this._broadcastTimer = undefined;
}
_checkStartBroadcastTimer() {
DEBUG && debug("_checkStartBroadcastTimer()");
// Cancel any existing broadcasting timer.
this._cancelBroadcastTimer();
let now = Date.now();
// Go through services and find services to broadcast.
let bcastServices = [];
let nextBcastWait = undefined;
for (let [serviceKey, publishedService] of this._services) {
// if lastAdvertised is undefined, service hasn't finished it's initial
// two broadcasts.
if (publishedService.lastAdvertised === undefined) {
continue;
}
// Otherwise, check lastAdvertised against now.
let msSinceAdv = now - publishedService.lastAdvertised;
// If msSinceAdv is more than 90% of the way to the TTL, advertise now.
if (msSinceAdv > (DEFAULT_TTL * 1000 * 0.9)) {
bcastServices.push(publishedService);
continue;
}
// Otherwise, calculate the next time to advertise for this service.
// We set that at 95% of the time to the TTL expiry.
let nextAdvWait = (DEFAULT_TTL * 1000 * 0.95) - msSinceAdv;
if (nextBcastWait === undefined || nextBcastWait > nextAdvWait) {
nextBcastWait = nextAdvWait;
}
}
// Schedule an immediate advertisement of all services to be advertised now.
for (let svc of bcastServices) {
svc.advertiseTimer = setTimeout(() => this._advertiseService(svc.key));
}
// Schedule next broadcast check for the next bcast time.
if (nextBcastWait !== undefined) {
DEBUG && debug("_checkStartBroadcastTimer(): Scheduling next check in " + nextBcastWait + "ms");
this._broadcastTimer = setTimeout(() => this._checkStartBroadcastTimer(), nextBcastWait);
}
}
_query(name) {
DEBUG && debug('query("' + name + '")');
let packet = new DNSPacket();
packet.setFlag('QR', DNS_QUERY_RESPONSE_CODES.QUERY);
// PTR Record
packet.addRecord('QD', new DNSRecord({
name: name,
recordType: DNS_RECORD_TYPES.PTR,
classCode: DNS_CLASS_CODES.IN,
cacheFlush: true
}));
let data = packet.serialize();
// Initialize the broadcast receiver socket in case it
// hasn't already been started so we can listen for
// multicast queries/announcements on all interfaces.
this._getBroadcastReceiverSocket();
this._getQuerySocket().then((querySocket) => {
DEBUG && debug('sending query on query socket ("' + name + '")');
querySocket.send(MDNS_MULTICAST_GROUP, MDNS_PORT, data, data.length);
});
// Automatically announce previously-discovered
// services that match and haven't expired yet.
setTimeout(() => {
DEBUG && debug('announcing previously discovered services ("' + name + '")');
let { serviceType } = _parseServiceDomainName(name);
this._clearExpiredDiscoveries();
this._discovered.forEach((discovery, key) => {
let serviceInfo = discovery.serviceInfo;
if (serviceInfo.serviceType !== serviceType) {
return;
}
let listeners = this._listeners.get(serviceInfo.serviceType) || [];
listeners.forEach((listener) => {
listener.onServiceFound(serviceInfo);
});
});
});
}
_clearExpiredDiscoveries() {
this._discovered.forEach((discovery, key) => {
if (discovery.expireTime < Date.now()) {
this._discovered.delete(key);
return;
}
});
}
_handleQueryPacket(packet, message) {
packet.getRecords(['QD']).forEach((record) => {
// Don't respond if the query's class code is not IN or ANY.
if (record.classCode !== DNS_CLASS_CODES.IN &&
record.classCode !== DNS_CLASS_CODES.ANY) {
return;
}
// Don't respond if the query's record type is not PTR or ANY.
if (record.recordType !== DNS_RECORD_TYPES.PTR &&
record.recordType !== DNS_RECORD_TYPES.ANY) {
return;
}
for (let [serviceKey, publishedService] of this._services) {
DEBUG && debug("_handleQueryPacket: " + packet.toJSON());
if (publishedService.ptrMatch(record.name)) {
this._respondToQuery(serviceKey, message);
}
}
});
}
_makeServicePacket(service, addresses) {
let packet = new DNSPacket();
packet.setFlag('QR', DNS_QUERY_RESPONSE_CODES.RESPONSE);
packet.setFlag('AA', DNS_AUTHORITATIVE_ANSWER_CODES.YES);
let host = service.host || _hostname;
// e.g.: foo-bar-service._http._tcp.local
let serviceDomainName = service.serviceName + '.' + service.serviceType + '.local';
// PTR Record
packet.addRecord('AN', new DNSResourceRecord({
name: service.serviceType + '.local', // e.g.: _http._tcp.local
recordType: DNS_RECORD_TYPES.PTR,
data: serviceDomainName
}));
// SRV Record
packet.addRecord('AR', new DNSResourceRecord({
name: serviceDomainName,
recordType: DNS_RECORD_TYPES.SRV,
classCode: DNS_CLASS_CODES.IN,
cacheFlush: true,
data: {
priority: 0,
weight: 0,
port: service.port,
target: host // e.g.: My-Android-Phone.local
}
}));
// A Records
for (let address of addresses) {
packet.addRecord('AR', new DNSResourceRecord({
name: host,
recordType: DNS_RECORD_TYPES.A,
data: address
}));
}
// TXT Record
packet.addRecord('AR', new DNSResourceRecord({
name: serviceDomainName,
recordType: DNS_RECORD_TYPES.TXT,
classCode: DNS_CLASS_CODES.IN,
cacheFlush: true,
data: service.serviceAttrs || {}
}));
return packet;
}
_handleResponsePacket(packet, message) {
let services = {};
let hosts = {};
let srvRecords = packet.getRecords(['AN', 'AR'], DNS_RECORD_TYPES.SRV);
let txtRecords = packet.getRecords(['AN', 'AR'], DNS_RECORD_TYPES.TXT);
let ptrRecords = packet.getRecords(['AN', 'AR'], DNS_RECORD_TYPES.PTR);
let aRecords = packet.getRecords(['AN', 'AR'], DNS_RECORD_TYPES.A);
srvRecords.forEach((record) => {
let data = record.data || {};
services[record.name] = {
host: data.target,
port: data.port,
ttl: record.ttl
};
});
txtRecords.forEach((record) => {
if (!services[record.name]) {
return;
}
services[record.name].attributes = record.data;
});
aRecords.forEach((record) => {
if (IsIpv4Address(record.data)) {
hosts[record.name] = record.data;
}
});
ptrRecords.forEach((record) => {
let name = record.data;
if (!services[name]) {
return;
}
let {host, port} = services[name];
if (!host || !port) {
return;
}
let { serviceName, serviceType, domainName } = _parseServiceDomainName(name);
if (!serviceName || !serviceType || !domainName) {
return;
}
let address = hosts[host];
if (!address) {
return;
}
let ttl = services[name].ttl || 0;
let serviceInfo = {
serviceName: serviceName,
serviceType: serviceType,
host: host,
address: address,
port: port,
domainName: domainName,
attributes: services[name].attributes || {}
};
this._onServiceFound(serviceInfo, ttl);
});
}
_onServiceFound(serviceInfo, ttl = 0) {
let expireTime = Date.now() + (ttl * 1000);
let key = serviceInfo.serviceName + '.' +
serviceInfo.serviceType + '.' +
serviceInfo.domainName + ' @' +
serviceInfo.address + ':' +
serviceInfo.port;
// If this service was already discovered, just update
// its expiration time and don't re-emit it.
if (this._discovered.has(key)) {
this._discovered.get(key).expireTime = expireTime;
return;
}
this._discovered.set(key, {
serviceInfo: serviceInfo,
expireTime: expireTime
});
let listeners = this._listeners.get(serviceInfo.serviceType) || [];
listeners.forEach((listener) => {
listener.onServiceFound(serviceInfo);
});
DEBUG && debug('_onServiceFound()' + serviceInfo.serviceName);
}
/**
* Gets a non-exclusive socket on 0.0.0.0:{random} to send
* multicast queries on all interfaces. This socket does
* not need to join a multicast group since it is still
* able to *send* multicast queries, but it does not need
* to *listen* for multicast queries/announcements since
* the `_broadcastReceiverSocket` is already handling them.
*/
_getQuerySocket() {
return new Promise((resolve, reject) => {
if (!this._querySocket) {
this._querySocket = _openSocket('0.0.0.0', 0, {
onPacketReceived: this._onPacketReceived.bind(this),
onStopListening: this._onStopListening.bind(this)
});
}
resolve(this._querySocket);
});
}
/**
* Gets a non-exclusive socket on 0.0.0.0:5353 to listen
* for multicast queries/announcements on all interfaces.
* Since this socket needs to listen for multicast queries
* and announcements, this socket joins the multicast
* group on *all* interfaces (0.0.0.0).
*/
_getBroadcastReceiverSocket() {
return new Promise((resolve, reject) => {
if (!this._broadcastReceiverSocket) {
this._broadcastReceiverSocket = _openSocket('0.0.0.0', MDNS_PORT, {
onPacketReceived: this._onPacketReceived.bind(this),
onStopListening: this._onStopListening.bind(this)
}, /* multicastInterface = */ '0.0.0.0');
}
resolve(this._broadcastReceiverSocket);
});
}
/**
* Gets a non-exclusive socket for each interface on
* {iface-ip}:5353 for sending query responses as
* well as for listening for unicast queries. These
* sockets do not need to join a multicast group
* since they are still able to *send* multicast
* query responses, but they do not need to *listen*
* for multicast queries since the `_querySocket` is
* already handling them.
*/
_getSockets() {
return new Promise((resolve) => {
if (this._sockets.size > 0) {
resolve(this._sockets);
return;
}
Promise.all([getAddresses(), getHostname()]).then(() => {
_addresses.forEach((address) => {
let socket = _openSocket(address, MDNS_PORT, null);
this._sockets.set(address, socket);
});
resolve(this._sockets);
});
});
}
_checkCloseSockets() {
// Nothing to do if no sockets to close.
if (this._sockets.size == 0)
return;
// Don't close sockets if discovery listeners are still present.
if (this._listeners.size > 0)
return;
// Don't close sockets if advertised services are present.
// Since we need to listen for service queries and respond to them.
if (this._services.size > 0)
return;
this._closeSockets();
}
_closeSockets() {
this._sockets.forEach(socket => socket.close());
this._sockets.clear();
}
_onPacketReceived(socket, message) {
let packet = DNSPacket.parse(message.rawData);
switch (packet.getFlag('QR')) {
case DNS_QUERY_RESPONSE_CODES.QUERY:
this._handleQueryPacket(packet, message);
break;
case DNS_QUERY_RESPONSE_CODES.RESPONSE:
this._handleResponsePacket(packet, message);
break;
default:
break;
}
}
_onStopListening(socket, status) {
DEBUG && debug('_onStopListening() ' + status);
}
_addServiceListener(serviceType, listener) {
let listeners = this._listeners.get(serviceType);
if (!listeners) {
listeners = [];
this._listeners.set(serviceType, listeners);
}
if (!listeners.find(l => l === listener)) {
listeners.push(listener);
}
}
_removeServiceListener(serviceType, listener) {
let listeners = this._listeners.get(serviceType);
if (!listeners) {
return;
}
let index = listeners.findIndex(l => l === listener);
if (index >= 0) {
listeners.splice(index, 1);
}
if (listeners.length === 0) {
this._listeners.delete(serviceType);
}
}
}
let _addresses;
/**
* @private
*/
function getAddresses() {
return new Promise((resolve, reject) => {
if (_addresses) {
resolve(_addresses);
return;
}
networkInfoService.listNetworkAddresses({
onListedNetworkAddresses(aAddressArray) {
_addresses = aAddressArray.filter((address) => {
return address.indexOf('%p2p') === -1 && // No WiFi Direct interfaces
address.indexOf(':') === -1 && // XXX: No IPv6 for now
address != "127.0.0.1" // No ipv4 loopback addresses.
});
DEBUG && debug('getAddresses(): ' + _addresses);
resolve(_addresses);
},
onListNetworkAddressesFailed() {
DEBUG && debug('getAddresses() FAILED!');
resolve([]);
}
});
});
}
let _hostname;
/**
* @private
*/
function getHostname() {
return new Promise((resolve) => {
if (_hostname) {
resolve(_hostname);
return;
}
networkInfoService.getHostname({
onGotHostname(aHostname) {
_hostname = aHostname.replace(/\s/g, '-') + '.local';
DEBUG && debug('getHostname(): ' + _hostname);
resolve(_hostname);
},
onGetHostnameFailed() {
DEBUG && debug('getHostname() FAILED');
resolve('localhost');
}
});
});
}
/**
* Parse fully qualified domain name to service name, instance name,
* and domain name. See https://tools.ietf.org/html/rfc6763#section-7.
*
* Example: 'foo-bar-service._http._tcp.local' -> {
* serviceName: 'foo-bar-service',
* serviceType: '_http._tcp',
* domainName: 'local'
* }
*
* @private
*/
function _parseServiceDomainName(serviceDomainName) {
let parts = serviceDomainName.split('.');
let index = Math.max(parts.lastIndexOf('_tcp'), parts.lastIndexOf('_udp'));
return {
serviceName: parts.splice(0, index - 1).join('.'),
serviceType: parts.splice(0, 2).join('.'),
domainName: parts.join('.')
};
}
/**
* @private
*/
function _propertyBagToObject(propBag) {
let result = {};
if (propBag.QueryInterface) {
propBag.QueryInterface(Ci.nsIPropertyBag2);
let propEnum = propBag.enumerator;
while (propEnum.hasMoreElements()) {
let prop = propEnum.getNext().QueryInterface(Ci.nsIProperty);
result[prop.name] = prop.value.toString();
}
} else {
for (let name in propBag) {
result[name] = propBag[name].toString();
}
}
return result;
}
/**
* @private
*/
function _openSocket(addr, port, handler, multicastInterface) {
let socket = Cc['@mozilla.org/network/udp-socket;1'].createInstance(Ci.nsIUDPSocket);
socket.init2(addr, port, Services.scriptSecurityManager.getSystemPrincipal(), true);
if (handler) {
socket.asyncListen({
onPacketReceived: handler.onPacketReceived,
onStopListening: handler.onStopListening
});
}
if (multicastInterface) {
socket.joinMulticast(MDNS_MULTICAST_GROUP, multicastInterface);
}
return socket;
}

View file

@ -0,0 +1,56 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa' or \
(CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk' and CONFIG['ANDROID_VERSION'] >= '16'):
UNIFIED_SOURCES += [
'MDNSResponderOperator.cpp',
'MDNSResponderReply.cpp',
'nsDNSServiceDiscovery.cpp',
]
LOCAL_INCLUDES += [
'/netwerk/base',
]
if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
LOCAL_INCLUDES += [
'%' + '%s/%s' % (CONFIG['ANDROID_SOURCE'], d) for d in [
'external/mdnsresponder/mDNSShared',
]
]
else:
EXTRA_COMPONENTS += [
'nsDNSServiceDiscovery.js',
'nsDNSServiceDiscovery.manifest',
]
EXTRA_JS_MODULES += [
'fallback/DataReader.jsm',
'fallback/DataWriter.jsm',
'fallback/DNSPacket.jsm',
'fallback/DNSRecord.jsm',
'fallback/DNSResourceRecord.jsm',
'fallback/DNSTypes.jsm',
'fallback/MulticastDNS.jsm',
]
if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
EXTRA_JS_MODULES += [
'MulticastDNSAndroid.jsm',
]
UNIFIED_SOURCES += [
'nsDNSServiceInfo.cpp',
'nsMulticastDNSModule.cpp',
]
include('/ipc/chromium/chromium-config.mozbuild')
FINAL_LIBRARY = 'xul'
if CONFIG['GNU_CXX']:
CXXFLAGS += ['-Wno-error=shadow']

View file

@ -0,0 +1,285 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsDNSServiceDiscovery.h"
#include "MDNSResponderOperator.h"
#include "nsICancelable.h"
#include "nsXULAppAPI.h"
#include "private/pprio.h"
#ifdef MOZ_WIDGET_GONK
#include <cutils/properties.h>
#endif // MOZ_WIDGET_GONK
namespace mozilla {
namespace net {
namespace {
inline void
StartService()
{
#ifdef MOZ_WIDGET_GONK
char value[PROPERTY_VALUE_MAX] = { '\0' };
property_get("init.svc.mdnsd", value, "");
if (strcmp(value, "running") == 0) {
return;
}
property_set("ctl.start", "mdnsd");
#endif // MOZ_WIDGET_GONK
}
inline void
StopService()
{
#ifdef MOZ_WIDGET_GONK
char value[PROPERTY_VALUE_MAX] = { '\0' };
property_get("init.svc.mdnsd", value, "");
if (strcmp(value, "stopped") == 0) {
return;
}
property_set("ctl.stop", "mdnsd");
#endif // MOZ_WIDGET_GONK
}
class ServiceCounter
{
public:
static bool IsServiceRunning()
{
return !!sUseCount;
}
private:
static uint32_t sUseCount;
protected:
ServiceCounter()
{
MOZ_ASSERT(NS_IsMainThread());
if (!sUseCount++) {
StartService();
}
}
virtual ~ServiceCounter()
{
MOZ_ASSERT(NS_IsMainThread());
if (!--sUseCount) {
StopService();
}
}
};
uint32_t ServiceCounter::sUseCount = 0;
class DiscoveryRequest final : public nsICancelable
, private ServiceCounter
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSICANCELABLE
explicit DiscoveryRequest(nsDNSServiceDiscovery* aService,
nsIDNSServiceDiscoveryListener* aListener);
private:
virtual ~DiscoveryRequest() { Cancel(NS_OK); }
RefPtr<nsDNSServiceDiscovery> mService;
nsIDNSServiceDiscoveryListener* mListener;
};
NS_IMPL_ISUPPORTS(DiscoveryRequest, nsICancelable)
DiscoveryRequest::DiscoveryRequest(nsDNSServiceDiscovery* aService,
nsIDNSServiceDiscoveryListener* aListener)
: mService(aService)
, mListener(aListener)
{
}
NS_IMETHODIMP
DiscoveryRequest::Cancel(nsresult aReason)
{
if (mService) {
mService->StopDiscovery(mListener);
}
mService = nullptr;
return NS_OK;
}
class RegisterRequest final : public nsICancelable
, private ServiceCounter
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSICANCELABLE
explicit RegisterRequest(nsDNSServiceDiscovery* aService,
nsIDNSRegistrationListener* aListener);
private:
virtual ~RegisterRequest() { Cancel(NS_OK); }
RefPtr<nsDNSServiceDiscovery> mService;
nsIDNSRegistrationListener* mListener;
};
NS_IMPL_ISUPPORTS(RegisterRequest, nsICancelable)
RegisterRequest::RegisterRequest(nsDNSServiceDiscovery* aService,
nsIDNSRegistrationListener* aListener)
: mService(aService)
, mListener(aListener)
{
}
NS_IMETHODIMP
RegisterRequest::Cancel(nsresult aReason)
{
if (mService) {
mService->UnregisterService(mListener);
}
mService = nullptr;
return NS_OK;
}
} // namespace anonymous
NS_IMPL_ISUPPORTS(nsDNSServiceDiscovery, nsIDNSServiceDiscovery)
nsDNSServiceDiscovery::~nsDNSServiceDiscovery()
{
#ifdef MOZ_WIDGET_GONK
StopService();
#endif
}
nsresult
nsDNSServiceDiscovery::Init()
{
if (!XRE_IsParentProcess()) {
MOZ_ASSERT(false, "nsDNSServiceDiscovery can only be used in parent process");
return NS_ERROR_FAILURE;
}
return NS_OK;
}
NS_IMETHODIMP
nsDNSServiceDiscovery::StartDiscovery(const nsACString& aServiceType,
nsIDNSServiceDiscoveryListener* aListener,
nsICancelable** aRetVal)
{
MOZ_ASSERT(aRetVal);
nsresult rv;
if (NS_WARN_IF(NS_FAILED(rv = StopDiscovery(aListener)))) {
return rv;
}
nsCOMPtr<nsICancelable> req = new DiscoveryRequest(this, aListener);
RefPtr<BrowseOperator> browserOp = new BrowseOperator(aServiceType,
aListener);
if (NS_WARN_IF(NS_FAILED(rv = browserOp->Start()))) {
return rv;
}
mDiscoveryMap.Put(aListener, browserOp);
req.forget(aRetVal);
return NS_OK;
}
NS_IMETHODIMP
nsDNSServiceDiscovery::StopDiscovery(nsIDNSServiceDiscoveryListener* aListener)
{
nsresult rv;
RefPtr<BrowseOperator> browserOp;
if (!mDiscoveryMap.Get(aListener, getter_AddRefs(browserOp))) {
return NS_OK;
}
browserOp->Cancel(); // cancel non-started operation
if (NS_WARN_IF(NS_FAILED(rv = browserOp->Stop()))) {
return rv;
}
mDiscoveryMap.Remove(aListener);
return NS_OK;
}
NS_IMETHODIMP
nsDNSServiceDiscovery::RegisterService(nsIDNSServiceInfo* aServiceInfo,
nsIDNSRegistrationListener* aListener,
nsICancelable** aRetVal)
{
MOZ_ASSERT(aRetVal);
nsresult rv;
if (NS_WARN_IF(NS_FAILED(rv = UnregisterService(aListener)))) {
return rv;
}
nsCOMPtr<nsICancelable> req = new RegisterRequest(this, aListener);
RefPtr<RegisterOperator> registerOp = new RegisterOperator(aServiceInfo,
aListener);
if (NS_WARN_IF(NS_FAILED(rv = registerOp->Start()))) {
return rv;
}
mRegisterMap.Put(aListener, registerOp);
req.forget(aRetVal);
return NS_OK;
}
NS_IMETHODIMP
nsDNSServiceDiscovery::UnregisterService(nsIDNSRegistrationListener* aListener)
{
nsresult rv;
RefPtr<RegisterOperator> registerOp;
if (!mRegisterMap.Get(aListener, getter_AddRefs(registerOp))) {
return NS_OK;
}
registerOp->Cancel(); // cancel non-started operation
if (NS_WARN_IF(NS_FAILED(rv = registerOp->Stop()))) {
return rv;
}
mRegisterMap.Remove(aListener);
return NS_OK;
}
NS_IMETHODIMP
nsDNSServiceDiscovery::ResolveService(nsIDNSServiceInfo* aServiceInfo,
nsIDNSServiceResolveListener* aListener)
{
if (!ServiceCounter::IsServiceRunning()) {
return NS_ERROR_FAILURE;
}
nsresult rv;
RefPtr<ResolveOperator> resolveOp = new ResolveOperator(aServiceInfo,
aListener);
if (NS_WARN_IF(NS_FAILED(rv = resolveOp->Start()))) {
return rv;
}
return NS_OK;
}
} // namespace net
} // namespace mozilla

View file

@ -0,0 +1,48 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_netwerk_dns_mdns_libmdns_nsDNSServiceDiscovery_h
#define mozilla_netwerk_dns_mdns_libmdns_nsDNSServiceDiscovery_h
#include "nsIDNSServiceDiscovery.h"
#include "nsCOMPtr.h"
#include "mozilla/RefPtr.h"
#include "nsRefPtrHashtable.h"
namespace mozilla {
namespace net {
class BrowseOperator;
class RegisterOperator;
class nsDNSServiceDiscovery final : public nsIDNSServiceDiscovery
{
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIDNSSERVICEDISCOVERY
explicit nsDNSServiceDiscovery() = default;
/*
** The mDNS service is started on demand. If no one uses, mDNS service will not
** start. Therefore, all operations before service started will fail
** and get error code |kDNSServiceErr_ServiceNotRunning| defined in dns_sd.h.
**/
nsresult Init();
nsresult StopDiscovery(nsIDNSServiceDiscoveryListener* aListener);
nsresult UnregisterService(nsIDNSRegistrationListener* aListener);
private:
virtual ~nsDNSServiceDiscovery();
nsRefPtrHashtable<nsISupportsHashKey, BrowseOperator> mDiscoveryMap;
nsRefPtrHashtable<nsISupportsHashKey, RegisterOperator> mRegisterMap;
};
} // namespace net
} // namespace mozilla
#endif // mozilla_netwerk_dns_mdns_libmdns_nsDNSServiceDiscovery_h

View file

@ -0,0 +1,201 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
Cu.import("resource://gre/modules/ExtensionUtils.jsm");
Cu.import('resource://gre/modules/Services.jsm');
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
let { PlatformInfo } = ExtensionUtils;
if (PlatformInfo.os == "android" && !Services.prefs.getBoolPref("network.mdns.use_js_fallback")) {
Cu.import("resource://gre/modules/MulticastDNSAndroid.jsm");
} else {
Cu.import("resource://gre/modules/MulticastDNS.jsm");
}
const DNSSERVICEDISCOVERY_CID = Components.ID("{f9346d98-f27a-4e89-b744-493843416480}");
const DNSSERVICEDISCOVERY_CONTRACT_ID = "@mozilla.org/toolkit/components/mdnsresponder/dns-sd;1";
const DNSSERVICEINFO_CONTRACT_ID = "@mozilla.org/toolkit/components/mdnsresponder/dns-info;1";
function log(aMsg) {
dump("-*- nsDNSServiceDiscovery.js : " + aMsg + "\n");
}
function generateUuid() {
var uuidGenerator = Components.classes["@mozilla.org/uuid-generator;1"].
getService(Ci.nsIUUIDGenerator);
return uuidGenerator.generateUUID().toString();
}
// Helper class to transform return objects to correct type.
function ListenerWrapper(aListener, aMdns) {
this.listener = aListener;
this.mdns = aMdns;
this.discoveryStarting = false;
this.stopDiscovery = false;
this.registrationStarting = false;
this.stopRegistration = false;
this.uuid = generateUuid();
}
ListenerWrapper.prototype = {
// Helper function for transforming an Object into nsIDNSServiceInfo.
makeServiceInfo: function (aServiceInfo) {
let serviceInfo = Cc[DNSSERVICEINFO_CONTRACT_ID].createInstance(Ci.nsIDNSServiceInfo);
for (let name of ['host', 'address', 'port', 'serviceName', 'serviceType']) {
try {
serviceInfo[name] = aServiceInfo[name];
} catch (e) {
// ignore exceptions
}
}
let attributes;
try {
attributes = _toPropertyBag2(aServiceInfo.attributes);
} catch (err) {
// Ignore unset attributes in object.
log("Caught unset attributes error: " + err + " - " + err.stack);
attributes = Cc['@mozilla.org/hash-property-bag;1']
.createInstance(Ci.nsIWritablePropertyBag2);
}
serviceInfo.attributes = attributes;
return serviceInfo;
},
/* transparent types */
onDiscoveryStarted: function(aServiceType) {
this.discoveryStarting = false;
this.listener.onDiscoveryStarted(aServiceType);
if (this.stopDiscovery) {
this.mdns.stopDiscovery(aServiceType, this);
}
},
onDiscoveryStopped: function(aServiceType) {
this.listener.onDiscoveryStopped(aServiceType);
},
onStartDiscoveryFailed: function(aServiceType, aErrorCode) {
log('onStartDiscoveryFailed: ' + aServiceType + ' (' + aErrorCode + ')');
this.discoveryStarting = false;
this.stopDiscovery = true;
this.listener.onStartDiscoveryFailed(aServiceType, aErrorCode);
},
onStopDiscoveryFailed: function(aServiceType, aErrorCode) {
log('onStopDiscoveryFailed: ' + aServiceType + ' (' + aErrorCode + ')');
this.listener.onStopDiscoveryFailed(aServiceType, aErrorCode);
},
/* transform types */
onServiceFound: function(aServiceInfo) {
this.listener.onServiceFound(this.makeServiceInfo(aServiceInfo));
},
onServiceLost: function(aServiceInfo) {
this.listener.onServiceLost(this.makeServiceInfo(aServiceInfo));
},
onServiceRegistered: function(aServiceInfo) {
this.registrationStarting = false;
this.listener.onServiceRegistered(this.makeServiceInfo(aServiceInfo));
if (this.stopRegistration) {
this.mdns.unregisterService(aServiceInfo, this);
}
},
onServiceUnregistered: function(aServiceInfo) {
this.listener.onServiceUnregistered(this.makeServiceInfo(aServiceInfo));
},
onServiceResolved: function(aServiceInfo) {
this.listener.onServiceResolved(this.makeServiceInfo(aServiceInfo));
},
onRegistrationFailed: function(aServiceInfo, aErrorCode) {
log('onRegistrationFailed: (' + aErrorCode + ')');
this.registrationStarting = false;
this.stopRegistration = true;
this.listener.onRegistrationFailed(this.makeServiceInfo(aServiceInfo), aErrorCode);
},
onUnregistrationFailed: function(aServiceInfo, aErrorCode) {
log('onUnregistrationFailed: (' + aErrorCode + ')');
this.listener.onUnregistrationFailed(this.makeServiceInfo(aServiceInfo), aErrorCode);
},
onResolveFailed: function(aServiceInfo, aErrorCode) {
log('onResolveFailed: (' + aErrorCode + ')');
this.listener.onResolveFailed(this.makeServiceInfo(aServiceInfo), aErrorCode);
}
};
function nsDNSServiceDiscovery() {
log("nsDNSServiceDiscovery");
this.mdns = new MulticastDNS();
}
nsDNSServiceDiscovery.prototype = {
classID: DNSSERVICEDISCOVERY_CID,
QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference, Ci.nsIDNSServiceDiscovery]),
startDiscovery: function(aServiceType, aListener) {
log("startDiscovery");
let listener = new ListenerWrapper(aListener, this.mdns);
listener.discoveryStarting = true;
this.mdns.startDiscovery(aServiceType, listener);
return {
QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]),
cancel: (function() {
if (this.discoveryStarting || this.stopDiscovery) {
this.stopDiscovery = true;
return;
}
this.mdns.stopDiscovery(aServiceType, listener);
}).bind(listener)
};
},
registerService: function(aServiceInfo, aListener) {
log("registerService");
let listener = new ListenerWrapper(aListener, this.mdns);
listener.registrationStarting = true;
this.mdns.registerService(aServiceInfo, listener);
return {
QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]),
cancel: (function() {
if (this.registrationStarting || this.stopRegistration) {
this.stopRegistration = true;
return;
}
this.mdns.unregisterService(aServiceInfo, listener);
}).bind(listener)
};
},
resolveService: function(aServiceInfo, aListener) {
log("resolveService");
this.mdns.resolveService(aServiceInfo, new ListenerWrapper(aListener));
}
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([nsDNSServiceDiscovery]);
function _toPropertyBag2(obj)
{
if (obj.QueryInterface) {
return obj.QueryInterface(Ci.nsIPropertyBag2);
}
let result = Cc['@mozilla.org/hash-property-bag;1']
.createInstance(Ci.nsIWritablePropertyBag2);
for (let name in obj) {
result.setPropertyAsAString(name, obj[name]);
}
return result;
}

View file

@ -0,0 +1,3 @@
# nsDNSServiceDiscovery.js
component {f9346d98-f27a-4e89-b744-493843416480} nsDNSServiceDiscovery.js
contract @mozilla.org/toolkit/components/mdnsresponder/dns-sd;1 {f9346d98-f27a-4e89-b744-493843416480}

View file

@ -0,0 +1,209 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsDNSServiceInfo.h"
#include "nsHashPropertyBag.h"
#include "nsIProperty.h"
#include "nsISimpleEnumerator.h"
#include "nsISupportsImpl.h"
#include "mozilla/Unused.h"
namespace mozilla {
namespace net {
NS_IMPL_ISUPPORTS(nsDNSServiceInfo, nsIDNSServiceInfo)
nsDNSServiceInfo::nsDNSServiceInfo(nsIDNSServiceInfo* aServiceInfo)
{
if (NS_WARN_IF(!aServiceInfo)) {
return;
}
nsAutoCString str;
uint16_t value;
if (NS_SUCCEEDED(aServiceInfo->GetHost(str))) {
Unused << NS_WARN_IF(NS_FAILED(SetHost(str)));
}
if (NS_SUCCEEDED(aServiceInfo->GetAddress(str))) {
Unused << NS_WARN_IF(NS_FAILED(SetAddress(str)));
}
if (NS_SUCCEEDED(aServiceInfo->GetPort(&value))) {
Unused << NS_WARN_IF(NS_FAILED(SetPort(value)));
}
if (NS_SUCCEEDED(aServiceInfo->GetServiceName(str))) {
Unused << NS_WARN_IF(NS_FAILED(SetServiceName(str)));
}
if (NS_SUCCEEDED(aServiceInfo->GetServiceType(str))) {
Unused << NS_WARN_IF(NS_FAILED(SetServiceType(str)));
}
if (NS_SUCCEEDED(aServiceInfo->GetDomainName(str))) {
Unused << NS_WARN_IF(NS_FAILED(SetDomainName(str)));
}
nsCOMPtr<nsIPropertyBag2> attributes; // deep copy
if (NS_SUCCEEDED(aServiceInfo->GetAttributes(getter_AddRefs(attributes)))) {
nsCOMPtr<nsISimpleEnumerator> enumerator;
if (NS_WARN_IF(NS_FAILED(attributes->GetEnumerator(getter_AddRefs(enumerator))))) {
return;
}
nsCOMPtr<nsIWritablePropertyBag2> newAttributes = new nsHashPropertyBag();
bool hasMoreElements;
while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMoreElements)) &&
hasMoreElements) {
nsCOMPtr<nsISupports> element;
Unused <<
NS_WARN_IF(NS_FAILED(enumerator->GetNext(getter_AddRefs(element))));
nsCOMPtr<nsIProperty> property = do_QueryInterface(element);
MOZ_ASSERT(property);
nsAutoString name;
nsCOMPtr<nsIVariant> value;
Unused << NS_WARN_IF(NS_FAILED(property->GetName(name)));
Unused << NS_WARN_IF(NS_FAILED(property->GetValue(getter_AddRefs(value))));
nsAutoCString valueStr;
Unused << NS_WARN_IF(NS_FAILED(value->GetAsACString(valueStr)));
Unused << NS_WARN_IF(NS_FAILED(newAttributes->SetPropertyAsACString(name, valueStr)));
}
Unused << NS_WARN_IF(NS_FAILED(SetAttributes(newAttributes)));
}
}
NS_IMETHODIMP
nsDNSServiceInfo::GetHost(nsACString& aHost)
{
if (!mIsHostSet) {
return NS_ERROR_NOT_INITIALIZED;
}
aHost = mHost;
return NS_OK;
}
NS_IMETHODIMP
nsDNSServiceInfo::SetHost(const nsACString& aHost)
{
mHost = aHost;
mIsHostSet = true;
return NS_OK;
}
NS_IMETHODIMP
nsDNSServiceInfo::GetAddress(nsACString& aAddress)
{
if (!mIsAddressSet) {
return NS_ERROR_NOT_INITIALIZED;
}
aAddress = mAddress;
return NS_OK;
}
NS_IMETHODIMP
nsDNSServiceInfo::SetAddress(const nsACString& aAddress)
{
mAddress = aAddress;
mIsAddressSet = true;
return NS_OK;
}
NS_IMETHODIMP
nsDNSServiceInfo::GetPort(uint16_t* aPort)
{
if (NS_WARN_IF(!aPort)) {
return NS_ERROR_INVALID_ARG;
}
if (!mIsPortSet) {
return NS_ERROR_NOT_INITIALIZED;
}
*aPort = mPort;
return NS_OK;
}
NS_IMETHODIMP
nsDNSServiceInfo::SetPort(uint16_t aPort)
{
mPort = aPort;
mIsPortSet = true;
return NS_OK;
}
NS_IMETHODIMP
nsDNSServiceInfo::GetServiceName(nsACString& aServiceName)
{
if (!mIsServiceNameSet) {
return NS_ERROR_NOT_INITIALIZED;
}
aServiceName = mServiceName;
return NS_OK;
}
NS_IMETHODIMP
nsDNSServiceInfo::SetServiceName(const nsACString& aServiceName)
{
mServiceName = aServiceName;
mIsServiceNameSet = true;
return NS_OK;
}
NS_IMETHODIMP
nsDNSServiceInfo::GetServiceType(nsACString& aServiceType)
{
if (!mIsServiceTypeSet) {
return NS_ERROR_NOT_INITIALIZED;
}
aServiceType = mServiceType;
return NS_OK;
}
NS_IMETHODIMP
nsDNSServiceInfo::SetServiceType(const nsACString& aServiceType)
{
mServiceType = aServiceType;
mIsServiceTypeSet = true;
return NS_OK;
}
NS_IMETHODIMP
nsDNSServiceInfo::GetDomainName(nsACString& aDomainName)
{
if (!mIsDomainNameSet) {
return NS_ERROR_NOT_INITIALIZED;
}
aDomainName = mDomainName;
return NS_OK;
}
NS_IMETHODIMP
nsDNSServiceInfo::SetDomainName(const nsACString& aDomainName)
{
mDomainName = aDomainName;
mIsDomainNameSet = true;
return NS_OK;
}
NS_IMETHODIMP
nsDNSServiceInfo::GetAttributes(nsIPropertyBag2** aAttributes)
{
if (!mIsAttributesSet) {
return NS_ERROR_NOT_INITIALIZED;
}
nsCOMPtr<nsIPropertyBag2> attributes(mAttributes);
attributes.forget(aAttributes);
return NS_OK;
}
NS_IMETHODIMP
nsDNSServiceInfo::SetAttributes(nsIPropertyBag2* aAttributes)
{
mAttributes = aAttributes;
mIsAttributesSet = aAttributes ? true : false;
return NS_OK;
}
} // namespace net
} // namespace mozilla

View file

@ -0,0 +1,50 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_netwerk_dns_mdns_libmdns_nsDNSServiceInfo_h
#define mozilla_netwerk_dns_mdns_libmdns_nsDNSServiceInfo_h
#include "nsCOMPtr.h"
#include "nsIDNSServiceDiscovery.h"
#include "nsIPropertyBag2.h"
#include "nsString.h"
namespace mozilla {
namespace net {
class nsDNSServiceInfo final : public nsIDNSServiceInfo
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIDNSSERVICEINFO
explicit nsDNSServiceInfo() = default;
explicit nsDNSServiceInfo(nsIDNSServiceInfo* aServiceInfo);
private:
virtual ~nsDNSServiceInfo() = default;
private:
nsCString mHost;
nsCString mAddress;
uint16_t mPort = 0;
nsCString mServiceName;
nsCString mServiceType;
nsCString mDomainName;
nsCOMPtr<nsIPropertyBag2> mAttributes;
bool mIsHostSet = false;
bool mIsAddressSet = false;
bool mIsPortSet = false;
bool mIsServiceNameSet = false;
bool mIsServiceTypeSet = false;
bool mIsDomainNameSet = false;
bool mIsAttributesSet = false;
};
} // namespace net
} // namespace mozilla
#endif // mozilla_netwerk_dns_mdns_libmdns_nsDNSServiceInfo_h

View file

@ -0,0 +1,61 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#if defined(MOZ_WIDGET_COCOA) || (defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 16)
#define ENABLE_DNS_SERVICE_DISCOVERY
#endif
#include "mozilla/ModuleUtils.h"
#ifdef ENABLE_DNS_SERVICE_DISCOVERY
#include "nsDNSServiceDiscovery.h"
#endif
#include "nsDNSServiceInfo.h"
#ifdef ENABLE_DNS_SERVICE_DISCOVERY
using mozilla::net::nsDNSServiceDiscovery;
#define DNSSERVICEDISCOVERY_CID \
{0x8df43d23, 0xd3f9, 0x4dd5, \
{ 0xb9, 0x65, 0xde, 0x2c, 0xa3, 0xf6, 0xa4, 0x2c }}
NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsDNSServiceDiscovery, Init)
NS_DEFINE_NAMED_CID(DNSSERVICEDISCOVERY_CID);
#endif // ENABLE_DNS_SERVICE_DISCOVERY
using mozilla::net::nsDNSServiceInfo;
#define DNSSERVICEINFO_CID \
{0x14a50f2b, 0x7ff6, 0x48a5, \
{ 0x88, 0xe3, 0x61, 0x5f, 0xd1, 0x11, 0xf5, 0xd3 }}
NS_GENERIC_FACTORY_CONSTRUCTOR(nsDNSServiceInfo)
NS_DEFINE_NAMED_CID(DNSSERVICEINFO_CID);
static const mozilla::Module::CIDEntry knsDNSServiceDiscoveryCIDs[] = {
#ifdef ENABLE_DNS_SERVICE_DISCOVERY
{ &kDNSSERVICEDISCOVERY_CID, false, nullptr, nsDNSServiceDiscoveryConstructor },
#endif
{ &kDNSSERVICEINFO_CID, false, nullptr, nsDNSServiceInfoConstructor },
{ nullptr }
};
static const mozilla::Module::ContractIDEntry knsDNSServiceDiscoveryContracts[] = {
#ifdef ENABLE_DNS_SERVICE_DISCOVERY
{ DNSSERVICEDISCOVERY_CONTRACT_ID, &kDNSSERVICEDISCOVERY_CID },
#endif
{ DNSSERVICEINFO_CONTRACT_ID, &kDNSSERVICEINFO_CID },
{ nullptr }
};
static const mozilla::Module::CategoryEntry knsDNSServiceDiscoveryCategories[] = {
{ nullptr }
};
static const mozilla::Module knsDNSServiceDiscoveryModule = {
mozilla::Module::kVersion,
knsDNSServiceDiscoveryCIDs,
knsDNSServiceDiscoveryContracts,
knsDNSServiceDiscoveryCategories
};
NSMODULE_DEFN(nsDNSServiceDiscoveryModule) = &knsDNSServiceDiscoveryModule;

View file

@ -0,0 +1,13 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
DIRS += ['libmdns']
XPIDL_SOURCES += [
'nsIDNSServiceDiscovery.idl',
]
XPIDL_MODULE = 'necko_mdns'

View file

@ -0,0 +1,219 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl"
interface nsICancelable;
interface nsIPropertyBag2;
/**
* Service information
*/
[scriptable, uuid(670ed0f9-2fa5-4544-bf1e-ea58ac179374)]
interface nsIDNSServiceInfo : nsISupports
{
/**
* The host name of the service. (E.g. "Android.local.")
* @throws NS_ERROR_NOT_INITIALIZED when getting unset value.
*/
attribute AUTF8String host;
/**
* The IP address of the service.
* @throws NS_ERROR_NOT_INITIALIZED when getting unset value.
*/
attribute AUTF8String address;
/**
* The port number of the service. (E.g. 80)
* @throws NS_ERROR_NOT_INITIALIZED when getting unset value.
*/
attribute unsigned short port;
/**
* The service name of the service for display. (E.g. "My TV")
* @throws NS_ERROR_NOT_INITIALIZED when getting unset value.
*/
attribute AUTF8String serviceName;
/**
* The type of the service. (E.g. "_http._tcp")
* @throws NS_ERROR_NOT_INITIALIZED when getting unset value.
*/
attribute AUTF8String serviceType;
/**
* The domain name of the service. (E.g. "local.")
* @throws NS_ERROR_NOT_INITIALIZED when getting unset value.
*/
attribute AUTF8String domainName;
/**
* The attributes of the service.
*/
attribute nsIPropertyBag2 attributes;
};
/**
* The callback interface for service discovery
*/
[scriptable, uuid(3025b7f2-97bb-435b-b43d-26731b3f5fc4)]
interface nsIDNSServiceDiscoveryListener : nsISupports
{
/**
* Callback when the discovery begins.
* @param aServiceType
* the service type of |startDiscovery|.
*/
void onDiscoveryStarted(in AUTF8String aServiceType);
/**
* Callback when the discovery ends.
* @param aServiceType
* the service type of |startDiscovery|.
*/
void onDiscoveryStopped(in AUTF8String aServiceType);
/**
* Callback when the a service is found.
* @param aServiceInfo
* the info about the found service, where |serviceName|, |aServiceType|, and |domainName| are set.
*/
void onServiceFound(in nsIDNSServiceInfo aServiceInfo);
/**
* Callback when the a service is lost.
* @param aServiceInfo
* the info about the lost service, where |serviceName|, |aServiceType|, and |domainName| are set.
*/
void onServiceLost(in nsIDNSServiceInfo aServiceInfo);
/**
* Callback when the discovery cannot start.
* @param aServiceType
* the service type of |startDiscovery|.
* @param aErrorCode
* the error code.
*/
void onStartDiscoveryFailed(in AUTF8String aServiceType, in long aErrorCode);
/**
* Callback when the discovery cannot stop.
* @param aServiceType
* the service type of |startDiscovery|.
* @param aErrorCode
* the error code.
*/
void onStopDiscoveryFailed(in AUTF8String aServiceType, in long aErrorCode);
};
/**
* The callback interface for service registration
*/
[scriptable, uuid(e165e4be-abf4-4963-a66d-ed3ca116e5e4)]
interface nsIDNSRegistrationListener : nsISupports
{
const long ERROR_SERVICE_NOT_RUNNING = -65563;
/**
* Callback when the service is registered successfully.
* @param aServiceInfo
* the info about the registered service,
* where |serviceName|, |aServiceType|, and |domainName| are set.
*/
void onServiceRegistered(in nsIDNSServiceInfo aServiceInfo);
/**
* Callback when the service is unregistered successfully.
* @param aServiceInfo
* the info about the unregistered service.
*/
void onServiceUnregistered(in nsIDNSServiceInfo aServiceInfo);
/**
* Callback when the service cannot be registered.
* @param aServiceInfo
* the info about the service to be registered.
* @param aErrorCode
* the error code.
*/
void onRegistrationFailed(in nsIDNSServiceInfo aServiceInfo, in long aErrorCode);
/**
* Callback when the service cannot be unregistered.
* @param aServiceInfo
* the info about the registered service.
* @param aErrorCode
* the error code.
*/
void onUnregistrationFailed(in nsIDNSServiceInfo aServiceInfo, in long aErrorCode);
};
/**
* The callback interface for service resolve
*/
[scriptable, uuid(24ee6408-648e-421d-accf-c6e5adeccf97)]
interface nsIDNSServiceResolveListener : nsISupports
{
/**
* Callback when the service is resolved successfully.
* @param aServiceInfo
* the info about the resolved service, where |host| and |port| are set.
*/
void onServiceResolved(in nsIDNSServiceInfo aServiceInfo);
/**
* Callback when the service cannot be resolved.
* @param aServiceInfo
* the info about the service to be resolved.
* @param aErrorCode
* the error code.
*/
void onResolveFailed(in nsIDNSServiceInfo aServiceInfo, in long aErrorCode);
};
/**
* The interface for DNS service discovery/registration/resolve
*/
[scriptable, uuid(6487899b-beb1-455a-ba65-e4fd465066d7)]
interface nsIDNSServiceDiscovery : nsISupports
{
/**
* Browse for instances of a service.
* @param aServiceType
* the service type to be discovered, E.g. "_http._tcp".
* @param aListener
* callback interface for discovery notifications.
* @return An object that can be used to cancel the service discovery.
*/
nsICancelable startDiscovery(in AUTF8String aServiceType, in nsIDNSServiceDiscoveryListener aListener);
/**
* Register a service that is discovered via |startDiscovery| and |resolveService| calls.
* @param aServiceInfo
* the service information to be registered.
* |port| and |aServiceType| are required attributes.
* @param aListener
* callback interface for registration notifications.
* @return An object that can be used to cancel the service registration.
*/
nsICancelable registerService(in nsIDNSServiceInfo aServiceInfo, in nsIDNSRegistrationListener aListener);
/**
* Resolve a service name discovered via |startDiscovery| to a target host name, port number.
* @param aServiceInfo
* the service information to be registered.
* |serviceName|, |aServiceType|, and |domainName| are required attributes as reported to the |onServiceFound| callback.
* @param aListener
* callback interface for registration notifications.
*/
void resolveService(in nsIDNSServiceInfo aServiceInfo, in nsIDNSServiceResolveListener aListener);
};
%{ C++
#define DNSSERVICEDISCOVERY_CONTRACT_ID \
"@mozilla.org/toolkit/components/mdnsresponder/dns-sd;1"
#define DNSSERVICEINFO_CONTRACT_ID \
"@mozilla.org/toolkit/components/mdnsresponder/dns-info;1"
%}

78
netwerk/dns/moz.build Normal file
View file

@ -0,0 +1,78 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
DIRS += [
'mdns',
]
XPIDL_SOURCES += [
'nsIDNSListener.idl',
'nsIDNSRecord.idl',
'nsIDNSService.idl',
'nsIEffectiveTLDService.idl',
'nsIIDNService.idl',
'nsPIDNSService.idl',
]
XPIDL_MODULE = 'necko_dns'
EXPORTS.mozilla.net += [
'ChildDNSService.h',
'DNS.h',
'DNSListenerProxy.h',
'DNSRequestChild.h',
'DNSRequestParent.h',
'PDNSParams.h',
]
SOURCES += [
'nsEffectiveTLDService.cpp', # Excluded from UNIFIED_SOURCES due to special build flags.
'nsHostResolver.cpp', # Redefines LOG
]
UNIFIED_SOURCES += [
'ChildDNSService.cpp',
'DNS.cpp',
'DNSListenerProxy.cpp',
'DNSRequestChild.cpp',
'DNSRequestParent.cpp',
'GetAddrInfo.cpp',
'nsDNSService2.cpp',
'nsIDNService.cpp',
'punycode.c',
]
IPDL_SOURCES = [
'PDNSRequest.ipdl',
'PDNSRequestParams.ipdlh',
]
include('/ipc/chromium/chromium-config.mozbuild')
FINAL_LIBRARY = 'xul'
GENERATED_FILES = [
'etld_data.inc',
]
etld_data = GENERATED_FILES['etld_data.inc']
etld_data.script = 'prepare_tlds.py'
etld_data.inputs = ['effective_tld_names.dat']
# need to include etld_data.inc
LOCAL_INCLUDES += [
'/netwerk/base',
]
if CONFIG['ENABLE_INTL_API']:
DEFINES['IDNA2008'] = True
USE_LIBS += ['icu']
else:
UNIFIED_SOURCES += [
'nameprep.c',
]
if CONFIG['GNU_CXX']:
CXXFLAGS += ['-Wno-error=shadow']

347
netwerk/dns/nameprep.c Normal file
View file

@ -0,0 +1,347 @@
/*
* Copyright (c) 2001,2002 Japan Network Information Center.
* All rights reserved.
*
* By using this file, you agree to the terms and conditions set forth bellow.
*
* LICENSE TERMS AND CONDITIONS
*
* The following License Terms and Conditions apply, unless a different
* license is obtained from Japan Network Information Center ("JPNIC"),
* a Japanese association, Kokusai-Kougyou-Kanda Bldg 6F, 2-3-4 Uchi-Kanda,
* Chiyoda-ku, Tokyo 101-0047, Japan.
*
* 1. Use, Modification and Redistribution (including distribution of any
* modified or derived work) in source and/or binary forms is permitted
* under this License Terms and Conditions.
*
* 2. Redistribution of source code must retain the copyright notices as they
* appear in each source code file, this License Terms and Conditions.
*
* 3. Redistribution in binary form must reproduce the Copyright Notice,
* this License Terms and Conditions, in the documentation and/or other
* materials provided with the distribution. For the purposes of binary
* distribution the "Copyright Notice" refers to the following language:
* "Copyright (c) 2000-2002 Japan Network Information Center. All rights reserved."
*
* 4. The name of JPNIC may not be used to endorse or promote products
* derived from this Software without specific prior written approval of
* JPNIC.
*
* 5. Disclaimer/Limitation of Liability: THIS SOFTWARE IS PROVIDED BY JPNIC
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JPNIC BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
*/
#include <stdlib.h>
#include <string.h>
#include "nsIDNKitInterface.h"
#define UCS_MAX 0x7fffffff
#define UNICODE_MAX 0x10ffff
/*
* Load NAMEPREP compiled tables.
*/
#include "nameprepdata.c"
/*
* Define mapping/checking functions for each version of the draft.
*/
#define VERSION id11
#include "nameprep_template.c"
#undef VERSION
typedef const char *(*nameprep_mapproc)(uint32_t v);
typedef int (*nameprep_checkproc)(uint32_t v);
typedef idn_biditype_t (*nameprep_biditypeproc)(uint32_t v);
static struct idn_nameprep {
char *version;
nameprep_mapproc map_proc;
nameprep_checkproc prohibited_proc;
nameprep_checkproc unassigned_proc;
nameprep_biditypeproc biditype_proc;
} nameprep_versions[] = {
#define MAKE_NAMEPREP_HANDLE(version, id) \
{ version, \
compose_sym2(nameprep_map_, id), \
compose_sym2(nameprep_prohibited_, id), \
compose_sym2(nameprep_unassigned_, id), \
compose_sym2(nameprep_biditype_, id), }
MAKE_NAMEPREP_HANDLE("nameprep-11", id11),
{ NULL, NULL, NULL, NULL, NULL },
};
static idn_result_t idn_nameprep_check(nameprep_checkproc proc,
const uint32_t *str,
const uint32_t **found);
idn_result_t
idn_nameprep_create(const char *version, idn_nameprep_t *handlep) {
idn_nameprep_t handle;
assert(handlep != NULL);
TRACE(("idn_nameprep_create(version=%-.50s)\n",
version == NULL ? "<NULL>" : version));
if (version == NULL)
version = IDN_NAMEPREP_CURRENT;
/*
* Lookup table for the specified version. Since the number of
* versions won't be large (I don't want see draft-23 or such :-),
* simple linear search is OK.
*/
for (handle = nameprep_versions; handle->version != NULL; handle++) {
if (strcmp(handle->version, version) == 0) {
*handlep = handle;
return (idn_success);
}
}
return (idn_notfound);
}
void
idn_nameprep_destroy(idn_nameprep_t handle) {
assert(handle != NULL);
TRACE(("idn_nameprep_destroy()\n"));
/* Nothing to do. */
}
idn_result_t
idn_nameprep_map(idn_nameprep_t handle, const uint32_t *from,
uint32_t *to, size_t tolen) {
assert(handle != NULL && from != NULL && to != NULL);
TRACE(("idn_nameprep_map(ctx=%s, from=\"%s\")\n",
handle->version, idn__debug_ucs4xstring(from, 50)));
while (*from != '\0') {
uint32_t v = *from;
const char *mapped;
if (v > UCS_MAX) {
/* This cannot happen, but just in case.. */
return (idn_invalid_codepoint);
} else if (v > UNICODE_MAX) {
/* No mapping is possible. */
mapped = NULL;
} else {
/* Try mapping. */
mapped = (*handle->map_proc)(v);
}
if (mapped == NULL) {
/* No mapping. Just copy verbatim. */
if (tolen < 1)
return (idn_buffer_overflow);
*to++ = v;
tolen--;
} else {
const unsigned char *mappeddata;
size_t mappedlen;
mappeddata = (const unsigned char *)mapped + 1;
mappedlen = *mapped;
if (tolen < (mappedlen + 3) / 4)
return (idn_buffer_overflow);
tolen -= (mappedlen + 3) / 4;
while (mappedlen >= 4) {
*to = *mappeddata++;
*to |= *mappeddata++ << 8;
*to |= *mappeddata++ << 16;
*to |= *mappeddata++ << 24;
mappedlen -= 4;
to++;
}
if (mappedlen > 0) {
*to = *mappeddata++;
*to |= (mappedlen >= 2) ?
*mappeddata++ << 8: 0;
*to |= (mappedlen >= 3) ?
*mappeddata++ << 16: 0;
to++;
}
}
from++;
}
if (tolen == 0)
return (idn_buffer_overflow);
*to = '\0';
return (idn_success);
}
idn_result_t
idn_nameprep_isprohibited(idn_nameprep_t handle, const uint32_t *str,
const uint32_t **found) {
assert(handle != NULL && str != NULL && found != NULL);
TRACE(("idn_nameprep_isprohibited(ctx=%s, str=\"%s\")\n",
handle->version, idn__debug_ucs4xstring(str, 50)));
return (idn_nameprep_check(handle->prohibited_proc, str, found));
}
idn_result_t
idn_nameprep_isunassigned(idn_nameprep_t handle, const uint32_t *str,
const uint32_t **found) {
assert(handle != NULL && str != NULL && found != NULL);
TRACE(("idn_nameprep_isunassigned(handle->version, str=\"%s\")\n",
handle->version, idn__debug_ucs4xstring(str, 50)));
return (idn_nameprep_check(handle->unassigned_proc, str, found));
}
static idn_result_t
idn_nameprep_check(nameprep_checkproc proc, const uint32_t *str,
const uint32_t **found) {
uint32_t v;
while (*str != '\0') {
v = *str;
if (v > UCS_MAX) {
/* This cannot happen, but just in case.. */
return (idn_invalid_codepoint);
} else if (v > UNICODE_MAX) {
/* It is invalid.. */
*found = str;
return (idn_success);
} else if ((*proc)(v)) {
*found = str;
return (idn_success);
}
str++;
}
*found = NULL;
return (idn_success);
}
idn_result_t
idn_nameprep_isvalidbidi(idn_nameprep_t handle, const uint32_t *str,
const uint32_t **found) {
uint32_t v;
idn_biditype_t first_char;
idn_biditype_t last_char;
int found_r_al;
assert(handle != NULL && str != NULL && found != NULL);
TRACE(("idn_nameprep_isvalidbidi(ctx=%s, str=\"%s\")\n",
handle->version, idn__debug_ucs4xstring(str, 50)));
if (*str == '\0') {
*found = NULL;
return (idn_success);
}
/*
* check first character's type and initialize variables.
*/
found_r_al = 0;
if (*str > UCS_MAX) {
/* This cannot happen, but just in case.. */
return (idn_invalid_codepoint);
} else if (*str > UNICODE_MAX) {
/* It is invalid.. */
*found = str;
return (idn_success);
}
first_char = last_char = (*(handle->biditype_proc))(*str);
if (first_char == idn_biditype_r_al) {
found_r_al = 1;
}
str++;
/*
* see whether string is valid or not.
*/
while (*str != '\0') {
v = *str;
if (v > UCS_MAX) {
/* This cannot happen, but just in case.. */
return (idn_invalid_codepoint);
} else if (v > UNICODE_MAX) {
/* It is invalid.. */
*found = str;
return (idn_success);
} else {
last_char = (*(handle->biditype_proc))(v);
if (found_r_al && last_char == idn_biditype_l) {
*found = str;
return (idn_success);
}
if (first_char != idn_biditype_r_al && last_char == idn_biditype_r_al) {
*found = str;
return (idn_success);
}
if (last_char == idn_biditype_r_al) {
found_r_al = 1;
}
}
str++;
}
if (found_r_al) {
if (last_char != idn_biditype_r_al) {
*found = str - 1;
return (idn_success);
}
}
*found = NULL;
return (idn_success);
}
idn_result_t
idn_nameprep_createproc(const char *parameter, void **handlep) {
return idn_nameprep_create(parameter, (idn_nameprep_t *)handlep);
}
void
idn_nameprep_destroyproc(void *handle) {
idn_nameprep_destroy((idn_nameprep_t)handle);
}
idn_result_t
idn_nameprep_mapproc(void *handle, const uint32_t *from,
uint32_t *to, size_t tolen) {
return idn_nameprep_map((idn_nameprep_t)handle, from, to, tolen);
}
idn_result_t
idn_nameprep_prohibitproc(void *handle, const uint32_t *str,
const uint32_t **found) {
return idn_nameprep_isprohibited((idn_nameprep_t)handle, str, found);
}
idn_result_t
idn_nameprep_unassignedproc(void *handle, const uint32_t *str,
const uint32_t **found) {
return idn_nameprep_isunassigned((idn_nameprep_t)handle, str, found);
}
idn_result_t
idn_nameprep_bidiproc(void *handle, const uint32_t *str,
const uint32_t **found) {
return idn_nameprep_isvalidbidi((idn_nameprep_t)handle, str, found);
}

View file

@ -0,0 +1,139 @@
/*
* Copyright (c) 2001 Japan Network Information Center. All rights reserved.
*
* By using this file, you agree to the terms and conditions set forth bellow.
*
* LICENSE TERMS AND CONDITIONS
*
* The following License Terms and Conditions apply, unless a different
* license is obtained from Japan Network Information Center ("JPNIC"),
* a Japanese association, Kokusai-Kougyou-Kanda Bldg 6F, 2-3-4 Uchi-Kanda,
* Chiyoda-ku, Tokyo 101-0047, Japan.
*
* 1. Use, Modification and Redistribution (including distribution of any
* modified or derived work) in source and/or binary forms is permitted
* under this License Terms and Conditions.
*
* 2. Redistribution of source code must retain the copyright notices as they
* appear in each source code file, this License Terms and Conditions.
*
* 3. Redistribution in binary form must reproduce the Copyright Notice,
* this License Terms and Conditions, in the documentation and/or other
* materials provided with the distribution. For the purposes of binary
* distribution the "Copyright Notice" refers to the following language:
* "Copyright (c) 2000-2002 Japan Network Information Center. All rights reserved."
*
* 4. The name of JPNIC may not be used to endorse or promote products
* derived from this Software without specific prior written approval of
* JPNIC.
*
* 5. Disclaimer/Limitation of Liability: THIS SOFTWARE IS PROVIDED BY JPNIC
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JPNIC BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
*/
/*
* Include this file once for each version of NAMEPREP.
* VERSION should be defined to appropriate value before inclusion.
*/
#ifndef NAMEPREP_TEMPLATE_INIT
#define NAMEPREP_TEMPLATE_INIT
/* Symbol composition. */
#define compose_sym2(a, b) compose_sym2X(a, b)
#define compose_sym2X(a, b) a ## b
#define compose_sym3(a, b, c) compose_sym3X(a, b, c)
#define compose_sym3X(a, b, c) a ## b ## c
/* The table is based on "Optimized Two-Stage Table" mentioned in
* Unicode 3.0 page 106, extended to handle 21bit data instead of 16 bit.
*/
/* Index calculation for multi-level index tables. */
#define IDX0(type, v) IDX_0(v, BITS1(type), BITS2(type))
#define IDX1(type, v) IDX_1(v, BITS1(type), BITS2(type))
#define IDX2(type, v) IDX_2(v, BITS1(type), BITS2(type))
#define IDX_0(v, bits1, bits2) ((v) >> ((bits1) + (bits2)))
#define IDX_1(v, bits1, bits2) (((v) >> (bits2)) & ((1 << (bits1)) - 1))
#define IDX_2(v, bits1, bits2) ((v) & ((1 << (bits2)) - 1))
#define BITS1(type) type ## _BITS_1
#define BITS2(type) type ## _BITS_2
#endif /* NAMEPREP_TEMPLATE_INIT */
static const char *
compose_sym2(nameprep_map_, VERSION) (uint32_t v) {
int idx0 = IDX0(MAP, v);
int idx1 = IDX1(MAP, v);
int idx2 = IDX2(MAP, v);
int offset;
#define IMAP compose_sym3(nameprep_, VERSION, _map_imap)
#define TABLE compose_sym3(nameprep_, VERSION, _map_table)
#define DATA compose_sym3(nameprep_, VERSION, _map_data)
offset = TABLE[IMAP[IMAP[idx0] + idx1]].tbl[idx2];
if (offset == 0)
return (NULL); /* no mapping */
return (const char *)(DATA + offset);
#undef IMAP
#undef TABLE
#undef DATA
}
static int
compose_sym2(nameprep_prohibited_, VERSION) (uint32_t v) {
int idx0 = IDX0(PROH, v);
int idx1 = IDX1(PROH, v);
int idx2 = IDX2(PROH, v);
const unsigned char *bm;
#define IMAP compose_sym3(nameprep_, VERSION, _prohibited_imap)
#define BITMAP compose_sym3(nameprep_, VERSION, _prohibited_bitmap)
bm = BITMAP[IMAP[IMAP[idx0] + idx1]].bm;
return (bm[idx2 / 8] & (1 << (idx2 % 8)));
#undef IMAP
#undef BITMAP
}
static int
compose_sym2(nameprep_unassigned_, VERSION) (uint32_t v) {
int idx0 = IDX0(UNAS, v);
int idx1 = IDX1(UNAS, v);
int idx2 = IDX2(UNAS, v);
const unsigned char *bm;
#define IMAP compose_sym3(nameprep_, VERSION, _unassigned_imap)
#define BITMAP compose_sym3(nameprep_, VERSION, _unassigned_bitmap)
bm = BITMAP[IMAP[IMAP[idx0] + idx1]].bm;
return (bm[idx2 / 8] & (1 << (idx2 % 8)));
#undef IMAP
#undef BITMAP
}
static idn_biditype_t
compose_sym2(nameprep_biditype_, VERSION) (uint32_t v) {
int idx0 = IDX0(BIDI, v);
int idx1 = IDX1(BIDI, v);
int idx2 = IDX2(BIDI, v);
int offset;
#define IMAP compose_sym3(nameprep_, VERSION, _bidi_imap)
#define TABLE compose_sym3(nameprep_, VERSION, _bidi_table)
#define DATA compose_sym3(nameprep_, VERSION, _bidi_data)
offset = TABLE[IMAP[IMAP[idx0] + idx1]].tbl[idx2];
return DATA[offset];
#undef IMAP
#undef TABLE
#undef DATA
}

2588
netwerk/dns/nameprepdata.c Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,72 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim: set sw=4 ts=8 et tw=80 : */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef nsDNSService2_h__
#define nsDNSService2_h__
#include "nsPIDNSService.h"
#include "nsIIDNService.h"
#include "nsIMemoryReporter.h"
#include "nsIObserver.h"
#include "nsHostResolver.h"
#include "nsAutoPtr.h"
#include "nsString.h"
#include "nsTHashtable.h"
#include "nsHashKeys.h"
#include "mozilla/Mutex.h"
#include "mozilla/Attributes.h"
class nsDNSService final : public nsPIDNSService
, public nsIObserver
, public nsIMemoryReporter
{
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSPIDNSSERVICE
NS_DECL_NSIDNSSERVICE
NS_DECL_NSIOBSERVER
NS_DECL_NSIMEMORYREPORTER
nsDNSService();
static nsIDNSService* GetXPCOMSingleton();
size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
bool GetOffline() const;
private:
~nsDNSService();
static nsDNSService* GetSingleton();
uint16_t GetAFForLookup(const nsACString &host, uint32_t flags);
nsresult PreprocessHostname(bool aLocalDomain,
const nsACString &aInput,
nsIIDNService *aIDN,
nsACString &aACE);
RefPtr<nsHostResolver> mResolver;
nsCOMPtr<nsIIDNService> mIDN;
// mLock protects access to mResolver and mIPv4OnlyDomains
mozilla::Mutex mLock;
// mIPv4OnlyDomains is a comma-separated list of domains for which only
// IPv4 DNS lookups are performed. This allows the user to disable IPv6 on
// a per-domain basis and work around broken DNS servers. See bug 68796.
nsAdoptingCString mIPv4OnlyDomains;
bool mDisableIPv6;
bool mDisablePrefetch;
bool mBlockDotOnion;
bool mFirstTime;
bool mNotifyResolution;
bool mOfflineLocalhost;
nsTHashtable<nsCStringHashKey> mLocalDomains;
};
#endif //nsDNSService2_h__

View file

@ -0,0 +1,368 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// This service reads a file of rules describing TLD-like domain names. For a
// complete description of the expected file format and parsing rules, see
// http://wiki.mozilla.org/Gecko:Effective_TLD_Service
#include "mozilla/ArrayUtils.h"
#include "mozilla/MemoryReporting.h"
#include "nsEffectiveTLDService.h"
#include "nsIIDNService.h"
#include "nsNetUtil.h"
#include "prnetdb.h"
#include "nsIURI.h"
#include "nsNetCID.h"
#include "nsServiceManagerUtils.h"
using namespace mozilla;
NS_IMPL_ISUPPORTS(nsEffectiveTLDService, nsIEffectiveTLDService,
nsIMemoryReporter)
// ----------------------------------------------------------------------
#define ETLD_STR_NUM_1(line) str##line
#define ETLD_STR_NUM(line) ETLD_STR_NUM_1(line)
#define ETLD_ENTRY_OFFSET(name) offsetof(struct etld_string_list, ETLD_STR_NUM(__LINE__))
const ETLDEntry ETLDEntry::entries[] = {
#define ETLD_ENTRY(name, ex, wild) { ETLD_ENTRY_OFFSET(name), ex, wild },
#include "etld_data.inc"
#undef ETLD_ENTRY
};
const union ETLDEntry::etld_strings ETLDEntry::strings = {
{
#define ETLD_ENTRY(name, ex, wild) name,
#include "etld_data.inc"
#undef ETLD_ENTRY
}
};
/* static */ const ETLDEntry*
ETLDEntry::GetEntry(const char* aDomain)
{
size_t i;
if (BinarySearchIf(entries, 0, ArrayLength(ETLDEntry::entries),
Cmp(aDomain), &i)) {
return &entries[i];
}
return nullptr;
}
// Dummy function to statically ensure that our indices don't overflow
// the storage provided for them.
void
ETLDEntry::FuncForStaticAsserts(void)
{
#define ETLD_ENTRY(name, ex, wild) \
static_assert(ETLD_ENTRY_OFFSET(name) < (1 << ETLD_ENTRY_N_INDEX_BITS), \
"invalid strtab index");
#include "etld_data.inc"
#undef ETLD_ENTRY
}
#undef ETLD_ENTRY_OFFSET
#undef ETLD_STR_NUM
#undef ETLD_STR_NUM1
// ----------------------------------------------------------------------
static nsEffectiveTLDService *gService = nullptr;
nsEffectiveTLDService::nsEffectiveTLDService()
{
}
nsresult
nsEffectiveTLDService::Init()
{
nsresult rv;
mIDNService = do_GetService(NS_IDNSERVICE_CONTRACTID, &rv);
if (NS_FAILED(rv)) return rv;
#ifdef DEBUG
// Sanity-check the eTLD entries.
for (uint32_t i = 0; i < ArrayLength(ETLDEntry::entries); i++) {
const char* domain = ETLDEntry::entries[i].GetEffectiveTLDName();
nsDependentCString name(domain);
nsAutoCString normalizedName(domain);
MOZ_ASSERT(NS_SUCCEEDED(NormalizeHostname(normalizedName)),
"normalization failure!");
MOZ_ASSERT(name.Equals(normalizedName), "domain not normalized!");
// Domains must be in sorted order for binary search to work.
if (i > 0) {
const char* domain0 = ETLDEntry::entries[i - 1].GetEffectiveTLDName();
MOZ_ASSERT(strcmp(domain0, domain) < 0, "domains not in sorted order!");
}
}
#endif
MOZ_ASSERT(!gService);
gService = this;
RegisterWeakMemoryReporter(this);
return NS_OK;
}
nsEffectiveTLDService::~nsEffectiveTLDService()
{
UnregisterWeakMemoryReporter(this);
gService = nullptr;
}
MOZ_DEFINE_MALLOC_SIZE_OF(EffectiveTLDServiceMallocSizeOf)
// The amount of heap memory measured here is tiny. It used to be bigger when
// nsEffectiveTLDService used a separate hash table instead of binary search.
// Nonetheless, we keep this code here in anticipation of bug 1083971 which will
// change ETLDEntries::entries to a heap-allocated array modifiable at runtime.
NS_IMETHODIMP
nsEffectiveTLDService::CollectReports(nsIHandleReportCallback* aHandleReport,
nsISupports* aData, bool aAnonymize)
{
MOZ_COLLECT_REPORT(
"explicit/network/effective-TLD-service", KIND_HEAP, UNITS_BYTES,
SizeOfIncludingThis(EffectiveTLDServiceMallocSizeOf),
"Memory used by the effective TLD service.");
return NS_OK;
}
size_t
nsEffectiveTLDService::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)
{
size_t n = aMallocSizeOf(this);
// Measurement of the following members may be added later if DMD finds it is
// worthwhile:
// - mIDNService
return n;
}
// External function for dealing with URI's correctly.
// Pulls out the host portion from an nsIURI, and calls through to
// GetPublicSuffixFromHost().
NS_IMETHODIMP
nsEffectiveTLDService::GetPublicSuffix(nsIURI *aURI,
nsACString &aPublicSuffix)
{
NS_ENSURE_ARG_POINTER(aURI);
nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(aURI);
NS_ENSURE_ARG_POINTER(innerURI);
nsAutoCString host;
nsresult rv = innerURI->GetAsciiHost(host);
if (NS_FAILED(rv)) return rv;
return GetBaseDomainInternal(host, 0, aPublicSuffix);
}
// External function for dealing with URI's correctly.
// Pulls out the host portion from an nsIURI, and calls through to
// GetBaseDomainFromHost().
NS_IMETHODIMP
nsEffectiveTLDService::GetBaseDomain(nsIURI *aURI,
uint32_t aAdditionalParts,
nsACString &aBaseDomain)
{
NS_ENSURE_ARG_POINTER(aURI);
NS_ENSURE_TRUE( ((int32_t)aAdditionalParts) >= 0, NS_ERROR_INVALID_ARG);
nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(aURI);
NS_ENSURE_ARG_POINTER(innerURI);
nsAutoCString host;
nsresult rv = innerURI->GetAsciiHost(host);
if (NS_FAILED(rv)) return rv;
return GetBaseDomainInternal(host, aAdditionalParts + 1, aBaseDomain);
}
// External function for dealing with a host string directly: finds the public
// suffix (e.g. co.uk) for the given hostname. See GetBaseDomainInternal().
NS_IMETHODIMP
nsEffectiveTLDService::GetPublicSuffixFromHost(const nsACString &aHostname,
nsACString &aPublicSuffix)
{
// Create a mutable copy of the hostname and normalize it to ACE.
// This will fail if the hostname includes invalid characters.
nsAutoCString normHostname(aHostname);
nsresult rv = NormalizeHostname(normHostname);
if (NS_FAILED(rv)) return rv;
return GetBaseDomainInternal(normHostname, 0, aPublicSuffix);
}
// External function for dealing with a host string directly: finds the base
// domain (e.g. www.co.uk) for the given hostname and number of subdomain parts
// requested. See GetBaseDomainInternal().
NS_IMETHODIMP
nsEffectiveTLDService::GetBaseDomainFromHost(const nsACString &aHostname,
uint32_t aAdditionalParts,
nsACString &aBaseDomain)
{
NS_ENSURE_TRUE( ((int32_t)aAdditionalParts) >= 0, NS_ERROR_INVALID_ARG);
// Create a mutable copy of the hostname and normalize it to ACE.
// This will fail if the hostname includes invalid characters.
nsAutoCString normHostname(aHostname);
nsresult rv = NormalizeHostname(normHostname);
if (NS_FAILED(rv)) return rv;
return GetBaseDomainInternal(normHostname, aAdditionalParts + 1, aBaseDomain);
}
NS_IMETHODIMP
nsEffectiveTLDService::GetNextSubDomain(const nsACString& aHostname,
nsACString& aBaseDomain)
{
// Create a mutable copy of the hostname and normalize it to ACE.
// This will fail if the hostname includes invalid characters.
nsAutoCString normHostname(aHostname);
nsresult rv = NormalizeHostname(normHostname);
NS_ENSURE_SUCCESS(rv, rv);
return GetBaseDomainInternal(normHostname, -1, aBaseDomain);
}
// Finds the base domain for a host, with requested number of additional parts.
// This will fail, generating an error, if the host is an IPv4/IPv6 address,
// if more subdomain parts are requested than are available, or if the hostname
// includes characters that are not valid in a URL. Normalization is performed
// on the host string and the result will be in UTF8.
nsresult
nsEffectiveTLDService::GetBaseDomainInternal(nsCString &aHostname,
int32_t aAdditionalParts,
nsACString &aBaseDomain)
{
if (aHostname.IsEmpty())
return NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS;
// chomp any trailing dot, and keep track of it for later
bool trailingDot = aHostname.Last() == '.';
if (trailingDot)
aHostname.Truncate(aHostname.Length() - 1);
// check the edge cases of the host being '.' or having a second trailing '.',
// since subsequent checks won't catch it.
if (aHostname.IsEmpty() || aHostname.Last() == '.')
return NS_ERROR_INVALID_ARG;
// Check if we're dealing with an IPv4/IPv6 hostname, and return
PRNetAddr addr;
PRStatus result = PR_StringToNetAddr(aHostname.get(), &addr);
if (result == PR_SUCCESS)
return NS_ERROR_HOST_IS_IP_ADDRESS;
// Walk up the domain tree, most specific to least specific,
// looking for matches at each level. Note that a given level may
// have multiple attributes (e.g. IsWild() and IsNormal()).
const char *prevDomain = nullptr;
const char *currDomain = aHostname.get();
const char *nextDot = strchr(currDomain, '.');
const char *end = currDomain + aHostname.Length();
// Default value of *eTLD is currDomain as set in the while loop below
const char *eTLD = nullptr;
while (true) {
// sanity check the string we're about to look up: it should not begin with
// a '.'; this would mean the hostname began with a '.' or had an
// embedded '..' sequence.
if (*currDomain == '.')
return NS_ERROR_INVALID_ARG;
// Perform the lookup.
const ETLDEntry* entry = ETLDEntry::GetEntry(currDomain);
if (entry) {
if (entry->IsWild() && prevDomain) {
// wildcard rules imply an eTLD one level inferior to the match.
eTLD = prevDomain;
break;
} else if (entry->IsNormal() || !nextDot) {
// specific match, or we've hit the top domain level
eTLD = currDomain;
break;
} else if (entry->IsException()) {
// exception rules imply an eTLD one level superior to the match.
eTLD = nextDot + 1;
break;
}
}
if (!nextDot) {
// we've hit the top domain level; use it by default.
eTLD = currDomain;
break;
}
prevDomain = currDomain;
currDomain = nextDot + 1;
nextDot = strchr(currDomain, '.');
}
const char *begin, *iter;
if (aAdditionalParts < 0) {
NS_ASSERTION(aAdditionalParts == -1,
"aAdditionalParts can't be negative and different from -1");
for (iter = aHostname.get(); iter != eTLD && *iter != '.'; iter++);
if (iter != eTLD) {
iter++;
}
if (iter != eTLD) {
aAdditionalParts = 0;
}
} else {
// count off the number of requested domains.
begin = aHostname.get();
iter = eTLD;
while (true) {
if (iter == begin)
break;
if (*(--iter) == '.' && aAdditionalParts-- == 0) {
++iter;
++aAdditionalParts;
break;
}
}
}
if (aAdditionalParts != 0)
return NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS;
aBaseDomain = Substring(iter, end);
// add on the trailing dot, if applicable
if (trailingDot)
aBaseDomain.Append('.');
return NS_OK;
}
// Normalizes the given hostname, component by component. ASCII/ACE
// components are lower-cased, and UTF-8 components are normalized per
// RFC 3454 and converted to ACE.
nsresult
nsEffectiveTLDService::NormalizeHostname(nsCString &aHostname)
{
if (!IsASCII(aHostname)) {
nsresult rv = mIDNService->ConvertUTF8toACE(aHostname, aHostname);
if (NS_FAILED(rv))
return rv;
}
ToLowerCase(aHostname);
return NS_OK;
}

View file

@ -0,0 +1,98 @@
//* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef EffectiveTLDService_h
#define EffectiveTLDService_h
#include "nsIEffectiveTLDService.h"
#include "nsIMemoryReporter.h"
#include "nsString.h"
#include "nsCOMPtr.h"
#include "mozilla/Attributes.h"
#include "mozilla/BinarySearch.h"
#include "mozilla/MemoryReporting.h"
class nsIIDNService;
// struct for static data generated from effective_tld_names.dat
struct ETLDEntry {
friend class nsEffectiveTLDService;
public:
bool IsNormal() const { return wild || !exception; }
bool IsException() const { return exception; }
bool IsWild() const { return wild; }
const char* GetEffectiveTLDName() const
{
return strings.strtab + strtab_index;
}
static const ETLDEntry* GetEntry(const char* aDomain);
static const size_t ETLD_ENTRY_N_INDEX_BITS = 30;
// These fields must be public to allow static construction.
uint32_t strtab_index : ETLD_ENTRY_N_INDEX_BITS;
uint32_t exception : 1;
uint32_t wild : 1;
private:
struct Cmp {
int operator()(const ETLDEntry aEntry) const
{
return strcmp(mName, aEntry.GetEffectiveTLDName());
}
explicit Cmp(const char* aName) : mName(aName) {}
const char* mName;
};
#define ETLD_STR_NUM_1(line) str##line
#define ETLD_STR_NUM(line) ETLD_STR_NUM_1(line)
struct etld_string_list {
#define ETLD_ENTRY(name, ex, wild) char ETLD_STR_NUM(__LINE__)[sizeof(name)];
#include "etld_data.inc"
#undef ETLD_ENTRY
};
// This static string table is all the eTLD domain names packed together.
static const union etld_strings {
struct etld_string_list list;
char strtab[1];
} strings;
// This is the static entries table. Each entry has an index into the string
// table. The entries are in sorted order so that binary search can be used.
static const ETLDEntry entries[];
void FuncForStaticAsserts(void);
#undef ETLD_STR_NUM
#undef ETLD_STR_NUM1
};
class nsEffectiveTLDService final
: public nsIEffectiveTLDService
, public nsIMemoryReporter
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIEFFECTIVETLDSERVICE
NS_DECL_NSIMEMORYREPORTER
nsEffectiveTLDService();
nsresult Init();
size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf);
private:
nsresult GetBaseDomainInternal(nsCString &aHostname, int32_t aAdditionalParts, nsACString &aBaseDomain);
nsresult NormalizeHostname(nsCString &aHostname);
~nsEffectiveTLDService();
nsCOMPtr<nsIIDNService> mIDNService;
};
#endif // EffectiveTLDService_h

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,372 @@
/* vim:set ts=4 sw=4 sts=4 et cin: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef nsHostResolver_h__
#define nsHostResolver_h__
#include "nscore.h"
#include "prclist.h"
#include "prnetdb.h"
#include "PLDHashTable.h"
#include "mozilla/CondVar.h"
#include "mozilla/Mutex.h"
#include "nsISupportsImpl.h"
#include "nsIDNSListener.h"
#include "nsIDNSService.h"
#include "nsString.h"
#include "nsTArray.h"
#include "GetAddrInfo.h"
#include "mozilla/net/DNS.h"
#include "mozilla/net/DashboardTypes.h"
#include "mozilla/TimeStamp.h"
class nsHostResolver;
class nsHostRecord;
class nsResolveHostCallback;
#define MAX_RESOLVER_THREADS_FOR_ANY_PRIORITY 3
#define MAX_RESOLVER_THREADS_FOR_HIGH_PRIORITY 5
#define MAX_NON_PRIORITY_REQUESTS 150
#define MAX_RESOLVER_THREADS (MAX_RESOLVER_THREADS_FOR_ANY_PRIORITY + \
MAX_RESOLVER_THREADS_FOR_HIGH_PRIORITY)
struct nsHostKey
{
const char *host;
uint16_t flags;
uint16_t af;
const char *netInterface;
};
/**
* nsHostRecord - ref counted object type stored in host resolver cache.
*/
class nsHostRecord : public PRCList, public nsHostKey
{
typedef mozilla::Mutex Mutex;
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsHostRecord)
/* instantiates a new host record */
static nsresult Create(const nsHostKey *key, nsHostRecord **record);
/* a fully resolved host record has either a non-null |addr_info| or |addr|
* field. if |addr_info| is null, it implies that the |host| is an IP
* address literal. in which case, |addr| contains the parsed address.
* otherwise, if |addr_info| is non-null, then it contains one or many
* IP addresses corresponding to the given host name. if both |addr_info|
* and |addr| are null, then the given host has not yet been fully resolved.
* |af| is the address family of the record we are querying for.
*/
/* the lock protects |addr_info| and |addr_info_gencnt| because they
* are mutable and accessed by the resolver worker thread and the
* nsDNSService2 class. |addr| doesn't change after it has been
* assigned a value. only the resolver worker thread modifies
* nsHostRecord (and only in nsHostResolver::OnLookupComplete);
* the other threads just read it. therefore the resolver worker
* thread doesn't need to lock when reading |addr_info|.
*/
Mutex addr_info_lock;
int addr_info_gencnt; /* generation count of |addr_info| */
mozilla::net::AddrInfo *addr_info;
mozilla::net::NetAddr *addr;
bool negative; /* True if this record is a cache of a failed lookup.
Negative cache entries are valid just like any other
(though never for more than 60 seconds), but a use
of that negative entry forces an asynchronous refresh. */
enum ExpirationStatus {
EXP_VALID,
EXP_GRACE,
EXP_EXPIRED,
};
ExpirationStatus CheckExpiration(const mozilla::TimeStamp& now) const;
// When the record began being valid. Used mainly for bookkeeping.
mozilla::TimeStamp mValidStart;
// When the record is no longer valid (it's time of expiration)
mozilla::TimeStamp mValidEnd;
// When the record enters its grace period. This must be before mValidEnd.
// If a record is in its grace period (and not expired), it will be used
// but a request to refresh it will be made.
mozilla::TimeStamp mGraceStart;
// Convenience function for setting the timestamps above (mValidStart,
// mValidEnd, and mGraceStart). valid and grace are durations in seconds.
void SetExpiration(const mozilla::TimeStamp& now, unsigned int valid,
unsigned int grace);
void CopyExpirationTimesAndFlagsFrom(const nsHostRecord *aFromHostRecord);
// Checks if the record is usable (not expired and has a value)
bool HasUsableResult(const mozilla::TimeStamp& now, uint16_t queryFlags = 0) const;
// hold addr_info_lock when calling the blacklist functions
bool Blacklisted(mozilla::net::NetAddr *query);
void ResetBlacklist();
void ReportUnusable(mozilla::net::NetAddr *addr);
size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
enum DnsPriority {
DNS_PRIORITY_LOW,
DNS_PRIORITY_MEDIUM,
DNS_PRIORITY_HIGH,
};
static DnsPriority GetPriority(uint16_t aFlags);
bool RemoveOrRefresh(); // Mark records currently being resolved as needed
// to resolve again.
private:
friend class nsHostResolver;
PRCList callbacks; /* list of callbacks */
bool resolving; /* true if this record is being resolved, which means
* that it is either on the pending queue or owned by
* one of the worker threads. */
bool onQueue; /* true if pending and on the queue (not yet given to getaddrinfo())*/
bool usingAnyThread; /* true if off queue and contributing to mActiveAnyThreadCount */
bool mDoomed; /* explicitly expired */
#if TTL_AVAILABLE
bool mGetTtl;
#endif
// The number of times ReportUnusable() has been called in the record's
// lifetime.
uint32_t mBlacklistedCount;
// when the results from this resolve is returned, it is not to be
// trusted, but instead a new resolve must be made!
bool mResolveAgain;
// a list of addresses associated with this record that have been reported
// as unusable. the list is kept as a set of strings to make it independent
// of gencnt.
nsTArray<nsCString> mBlacklistedItems;
explicit nsHostRecord(const nsHostKey *key); /* use Create() instead */
~nsHostRecord();
};
/**
* ResolveHost callback object. It's PRCList members are used by
* the nsHostResolver and should not be used by anything else.
*/
class NS_NO_VTABLE nsResolveHostCallback : public PRCList
{
public:
/**
* OnLookupComplete
*
* this function is called to complete a host lookup initiated by
* nsHostResolver::ResolveHost. it may be invoked recursively from
* ResolveHost or on an unspecified background thread.
*
* NOTE: it is the responsibility of the implementor of this method
* to handle the callback in a thread safe manner.
*
* @param resolver
* nsHostResolver object associated with this result
* @param record
* the host record containing the results of the lookup
* @param status
* if successful, |record| contains non-null results
*/
virtual void OnLookupComplete(nsHostResolver *resolver,
nsHostRecord *record,
nsresult status) = 0;
/**
* EqualsAsyncListener
*
* Determines if the listener argument matches the listener member var.
* For subclasses not implementing a member listener, should return false.
* For subclasses having a member listener, the function should check if
* they are the same. Used for cases where a pointer to an object
* implementing nsResolveHostCallback is unknown, but a pointer to
* the original listener is known.
*
* @param aListener
* nsIDNSListener object associated with the original request
*/
virtual bool EqualsAsyncListener(nsIDNSListener *aListener) = 0;
virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf) const = 0;
};
/**
* nsHostResolver - an asynchronous host name resolver.
*/
class nsHostResolver
{
typedef mozilla::CondVar CondVar;
typedef mozilla::Mutex Mutex;
public:
/**
* host resolver instances are reference counted.
*/
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsHostResolver)
/**
* creates an addref'd instance of a nsHostResolver object.
*/
static nsresult Create(uint32_t maxCacheEntries, // zero disables cache
uint32_t defaultCacheEntryLifetime, // seconds
uint32_t defaultGracePeriod, // seconds
nsHostResolver **resolver);
/**
* puts the resolver in the shutdown state, which will cause any pending
* callbacks to be detached. any future calls to ResolveHost will fail.
*/
void Shutdown();
/**
* resolve the given hostname asynchronously. the caller can synthesize
* a synchronous host lookup using a lock and a cvar. as noted above
* the callback will occur re-entrantly from an unspecified thread. the
* host lookup cannot be canceled (cancelation can be layered above this
* by having the callback implementation return without doing anything).
*/
nsresult ResolveHost(const char *hostname,
uint16_t flags,
uint16_t af,
const char *netInterface,
nsResolveHostCallback *callback);
/**
* removes the specified callback from the nsHostRecord for the given
* hostname, flags, and address family. these parameters should correspond
* to the parameters passed to ResolveHost. this function executes the
* callback if the callback is still pending with the given status.
*/
void DetachCallback(const char *hostname,
uint16_t flags,
uint16_t af,
const char *netInterface,
nsResolveHostCallback *callback,
nsresult status);
/**
* Cancels an async request associated with the hostname, flags,
* address family and listener. Cancels first callback found which matches
* these criteria. These parameters should correspond to the parameters
* passed to ResolveHost. If this is the last callback associated with the
* host record, it is removed from any request queues it might be on.
*/
void CancelAsyncRequest(const char *host,
uint16_t flags,
uint16_t af,
const char *netInterface,
nsIDNSListener *aListener,
nsresult status);
/**
* values for the flags parameter passed to ResolveHost and DetachCallback
* that may be bitwise OR'd together.
*
* NOTE: in this implementation, these flags correspond exactly in value
* to the flags defined on nsIDNSService.
*/
enum {
RES_BYPASS_CACHE = nsIDNSService::RESOLVE_BYPASS_CACHE,
RES_CANON_NAME = nsIDNSService::RESOLVE_CANONICAL_NAME,
RES_PRIORITY_MEDIUM = nsIDNSService::RESOLVE_PRIORITY_MEDIUM,
RES_PRIORITY_LOW = nsIDNSService::RESOLVE_PRIORITY_LOW,
RES_SPECULATE = nsIDNSService::RESOLVE_SPECULATE,
//RES_DISABLE_IPV6 = nsIDNSService::RESOLVE_DISABLE_IPV6, // Not used
RES_OFFLINE = nsIDNSService::RESOLVE_OFFLINE,
//RES_DISABLE_IPv4 = nsIDNSService::RESOLVE_DISABLE_IPV4, // Not Used
RES_ALLOW_NAME_COLLISION = nsIDNSService::RESOLVE_ALLOW_NAME_COLLISION
};
size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
/**
* Flush the DNS cache.
*/
void FlushCache();
private:
explicit nsHostResolver(uint32_t maxCacheEntries,
uint32_t defaultCacheEntryLifetime,
uint32_t defaultGracePeriod);
~nsHostResolver();
nsresult Init();
nsresult IssueLookup(nsHostRecord *);
bool GetHostToLookup(nsHostRecord **m);
enum LookupStatus {
LOOKUP_OK,
LOOKUP_RESOLVEAGAIN,
};
LookupStatus OnLookupComplete(nsHostRecord *, nsresult, mozilla::net::AddrInfo *);
void DeQueue(PRCList &aQ, nsHostRecord **aResult);
void ClearPendingQueue(PRCList *aPendingQueue);
nsresult ConditionallyCreateThread(nsHostRecord *rec);
/**
* Starts a new lookup in the background for entries that are in the grace
* period with a failed connect or all cached entries are negative.
*/
nsresult ConditionallyRefreshRecord(nsHostRecord *rec, const char *host);
static void MoveQueue(nsHostRecord *aRec, PRCList &aDestQ);
static void ThreadFunc(void *);
enum {
METHOD_HIT = 1,
METHOD_RENEWAL = 2,
METHOD_NEGATIVE_HIT = 3,
METHOD_LITERAL = 4,
METHOD_OVERFLOW = 5,
METHOD_NETWORK_FIRST = 6,
METHOD_NETWORK_SHARED = 7
};
uint32_t mMaxCacheEntries;
uint32_t mDefaultCacheLifetime; // granularity seconds
uint32_t mDefaultGracePeriod; // granularity seconds
mutable Mutex mLock; // mutable so SizeOfIncludingThis can be const
CondVar mIdleThreadCV;
PLDHashTable mDB;
PRCList mHighQ;
PRCList mMediumQ;
PRCList mLowQ;
PRCList mEvictionQ;
uint32_t mEvictionQSize;
PRTime mCreationTime;
PRIntervalTime mLongIdleTimeout;
PRIntervalTime mShortIdleTimeout;
mozilla::Atomic<bool> mShutdown;
mozilla::Atomic<uint32_t> mNumIdleThreads;
mozilla::Atomic<uint32_t> mThreadCount;
mozilla::Atomic<uint32_t> mActiveAnyThreadCount;
mozilla::Atomic<uint32_t> mPendingCount;
// Set the expiration time stamps appropriately.
void PrepareRecordExpiration(nsHostRecord* rec) const;
public:
/*
* Called by the networking dashboard via the DnsService2
*/
void GetDNSCacheEntries(nsTArray<mozilla::net::DNSCacheEntries> *);
};
#endif // nsHostResolver_h__

View file

@ -0,0 +1,195 @@
/*
* Copyright (c) 2000-2002 Japan Network Information Center. All rights reserved.
*
* By using this file, you agree to the terms and conditions set forth bellow.
*
* LICENSE TERMS AND CONDITIONS
*
* The following License Terms and Conditions apply, unless a different
* license is obtained from Japan Network Information Center ("JPNIC"),
* a Japanese association, Kokusai-Kougyou-Kanda Bldg 6F, 2-3-4 Uchi-Kanda,
* Chiyoda-ku, Tokyo 101-0047, Japan.
* 1. Use, Modification and Redistribution (including distribution of any
* modified or derived work) in source and/or binary forms is permitted
* under this License Terms and Conditions.
*
* 2. Redistribution of source code must retain the copyright notices as they
* appear in each source code file, this License Terms and Conditions.
*
* 3. Redistribution in binary form must reproduce the Copyright Notice,
* this License Terms and Conditions, in the documentation and/or other
* materials provided with the distribution. For the purposes of binary
* distribution the "Copyright Notice" refers to the following language:
* "Copyright (c) 2000-2002 Japan Network Information Center. All rights reserved."
*
* 4. The name of JPNIC may not be used to endorse or promote products
* derived from this Software without specific prior written approval of
* JPNIC.
*
* 5. Disclaimer/Limitation of Liability: THIS SOFTWARE IS PROVIDED BY JPNIC
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JPNIC BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
*/
#ifndef nsIDNKitWrapper_h__
#define nsIDNKitWrapper_h__
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
/*
* libidnkit result code.
*/
typedef enum {
idn_success,
idn_notfound,
idn_invalid_encoding,
idn_invalid_syntax,
idn_invalid_name,
idn_invalid_message,
idn_invalid_action,
idn_invalid_codepoint,
idn_invalid_length,
idn_buffer_overflow,
idn_noentry,
idn_nomemory,
idn_nofile,
idn_nomapping,
idn_context_required,
idn_prohibited,
idn_failure /* !!This must be the last one!! */
} idn_result_t;
/*
* BIDI type codes.
*/
typedef enum {
idn_biditype_r_al,
idn_biditype_l,
idn_biditype_others
} idn_biditype_t;
/*
* A Handle for nameprep operations.
*/
typedef struct idn_nameprep *idn_nameprep_t;
/*
* The latest version of nameprep.
*/
#define IDN_NAMEPREP_CURRENT "nameprep-11"
#undef assert
#define assert(a)
#define TRACE(a)
/* race.c */
idn_result_t race_decode_decompress(const char *from,
uint16_t *buf,
size_t buflen);
idn_result_t race_compress_encode(const uint16_t *p,
int compress_mode,
char *to, size_t tolen);
int get_compress_mode(uint16_t *p);
/* nameprep.c */
/*
* Create a handle for nameprep operations.
* The handle is stored in '*handlep', which is used other functions
* in this module.
* The version of the NAMEPREP specification can be specified with
* 'version' parameter. If 'version' is nullptr, the latest version
* is used.
*
* Returns:
* idn_success -- ok.
* idn_notfound -- specified version not found.
*/
idn_result_t
idn_nameprep_create(const char *version, idn_nameprep_t *handlep);
/*
* Close a handle, which was created by 'idn_nameprep_create'.
*/
void
idn_nameprep_destroy(idn_nameprep_t handle);
/*
* Perform character mapping on an UCS4 string specified by 'from', and
* store the result into 'to', whose length is specified by 'tolen'.
*
* Returns:
* idn_success -- ok.
* idn_buffer_overflow -- result buffer is too small.
*/
idn_result_t
idn_nameprep_map(idn_nameprep_t handle, const uint32_t *from,
uint32_t *to, size_t tolen);
/*
* Check if an UCS4 string 'str' contains any prohibited characters specified
* by the draft. If found, the pointer to the first such character is stored
* into '*found'. Otherwise '*found' will be nullptr.
*
* Returns:
* idn_success -- check has been done properly. (But this
* does not mean that no prohibited character
* was found. Check '*found' to see the
* result.)
*/
idn_result_t
idn_nameprep_isprohibited(idn_nameprep_t handle, const uint32_t *str,
const uint32_t **found);
/*
* Check if an UCS4 string 'str' contains any unassigned characters specified
* by the draft. If found, the pointer to the first such character is stored
* into '*found'. Otherwise '*found' will be nullptr.
*
* Returns:
* idn_success -- check has been done properly. (But this
* does not mean that no unassinged character
* was found. Check '*found' to see the
* result.)
*/
idn_result_t
idn_nameprep_isunassigned(idn_nameprep_t handle, const uint32_t *str,
const uint32_t **found);
/*
* Check if an UCS4 string 'str' is valid string specified by ``bidi check''
* of the draft. If it is not valid, the pointer to the first invalid
* character is stored into '*found'. Otherwise '*found' will be nullptr.
*
* Returns:
* idn_success -- check has been done properly. (But this
* does not mean that the string was valid.
* Check '*found' to see the result.)
*/
idn_result_t
idn_nameprep_isvalidbidi(idn_nameprep_t handle, const uint32_t *str,
const uint32_t **found);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* nsIDNKitWrapper_h__ */

View file

@ -0,0 +1,46 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl"
interface nsICancelable;
interface nsIDNSRecord;
/**
* nsIDNSListener
*/
[scriptable, function, uuid(27d49bfe-280c-49e0-bbaa-f6200c232c3d)]
interface nsIDNSListener : nsISupports
{
/**
* called when an asynchronous host lookup completes.
*
* @param aRequest
* the value returned from asyncResolve.
* @param aRecord
* the DNS record corresponding to the hostname that was resolved.
* this parameter is null if there was an error.
* @param aStatus
* if the lookup failed, this parameter gives the reason.
*/
void onLookupComplete(in nsICancelable aRequest,
in nsIDNSRecord aRecord,
in nsresult aStatus);
};
/**
* nsIDNSListenerProxy:
*
* Must be implemented by classes that wrap the original listener passed to
* nsIDNSService.AsyncResolve, so we have access to original listener for
* comparison purposes.
*/
[uuid(60eff0e4-6f7c-493c-add9-1cbea59063ad)]
interface nsIDNSListenerProxy : nsISupports
{
/*
* The original nsIDNSListener which requested hostname resolution.
*/
readonly attribute nsIDNSListener originalListener;
};

View file

@ -0,0 +1,100 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl"
%{ C++
namespace mozilla {
namespace net {
union NetAddr;
}
}
#include "nsTArrayForwardDeclare.h"
%}
native NetAddr(mozilla::net::NetAddr);
[ref] native nsNetAddrTArrayRef(nsTArray<mozilla::net::NetAddr>);
interface nsINetAddr;
/**
* nsIDNSRecord
*
* this interface represents the result of a DNS lookup. since a DNS
* query may return more than one resolved IP address, the record acts
* like an enumerator, allowing the caller to easily step through the
* list of IP addresses.
*/
[scriptable, uuid(f92228ae-c417-4188-a604-0830a95e7eb9)]
interface nsIDNSRecord : nsISupports
{
/**
* @return the canonical hostname for this record. this value is empty if
* the record was not fetched with the RESOLVE_CANONICAL_NAME flag.
*
* e.g., www.mozilla.org --> rheet.mozilla.org
*/
readonly attribute ACString canonicalName;
/**
* this function copies the value of the next IP address into the
* given NetAddr struct and increments the internal address iterator.
*
* @param aPort
* A port number to initialize the NetAddr with.
*
* @throws NS_ERROR_NOT_AVAILABLE if there is not another IP address in
* the record.
*/
[noscript] NetAddr getNextAddr(in uint16_t aPort);
/**
* this function copies the value of all working members of the RR
* set into the output array.
*
* @param aAddressArray
* The result set
*/
[noscript] void getAddresses(out nsNetAddrTArrayRef aAddressArray);
/**
* this function returns the value of the next IP address as a
* scriptable address and increments the internal address iterator.
*
* @param aPort
* A port number to initialize the nsINetAddr with.
*
* @throws NS_ERROR_NOT_AVAILABLE if there is not another IP address in
* the record.
*/
nsINetAddr getScriptableNextAddr(in uint16_t aPort);
/**
* this function returns the value of the next IP address as a
* string and increments the internal address iterator.
*
* @throws NS_ERROR_NOT_AVAILABLE if there is not another IP address in
* the record.
*/
ACString getNextAddrAsString();
/**
* this function returns true if there is another address in the record.
*/
boolean hasMore();
/**
* this function resets the internal address iterator to the first
* address in the record.
*/
void rewind();
/**
* This function indicates that the last address obtained via getNextAddr*()
* was not usuable and should be skipped in future uses of this
* record if other addresses are available.
*
* @param aPort is the port number associated with the failure, if any.
* It may be zero if not applicable.
*/
void reportUnusable(in uint16_t aPort);
};

View file

@ -0,0 +1,171 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl"
interface nsICancelable;
interface nsIEventTarget;
interface nsIDNSRecord;
interface nsIDNSListener;
%{C++
#include "nsTArrayForwardDeclare.h"
namespace mozilla { namespace net {
struct DNSCacheEntries;
} }
%}
[ptr] native EntriesArray(nsTArray<mozilla::net::DNSCacheEntries>);
/**
* nsIDNSService
*/
[scriptable, uuid(de5642c6-61fc-4fcf-9a47-03226b0d4e21)]
interface nsIDNSService : nsISupports
{
/**
* kicks off an asynchronous host lookup.
*
* @param aHostName
* the hostname or IP-address-literal to resolve.
* @param aFlags
* a bitwise OR of the RESOLVE_ prefixed constants defined below.
* @param aListener
* the listener to be notified when the result is available.
* @param aListenerTarget
* optional parameter (may be null). if non-null, this parameter
* specifies the nsIEventTarget of the thread on which the
* listener's onLookupComplete should be called. however, if this
* parameter is null, then onLookupComplete will be called on an
* unspecified thread (possibly recursively).
*
* @return An object that can be used to cancel the host lookup.
*/
nsICancelable asyncResolve(in AUTF8String aHostName,
in unsigned long aFlags,
in nsIDNSListener aListener,
in nsIEventTarget aListenerTarget);
/**
* Attempts to cancel a previously requested async DNS lookup
*
* @param aHostName
* the hostname or IP-address-literal to resolve.
* @param aFlags
* a bitwise OR of the RESOLVE_ prefixed constants defined below.
* @param aListener
* the original listener which was to be notified about the host lookup
* result - used to match request information to requestor.
* @param aReason
* nsresult reason for the cancellation
*
* @return An object that can be used to cancel the host lookup.
*/
void cancelAsyncResolve(in AUTF8String aHostName,
in unsigned long aFlags,
in nsIDNSListener aListener,
in nsresult aReason);
/**
* called to synchronously resolve a hostname. warning this method may
* block the calling thread for a long period of time. it is extremely
* unwise to call this function on the UI thread of an application.
*
* @param aHostName
* the hostname or IP-address-literal to resolve.
* @param aFlags
* a bitwise OR of the RESOLVE_ prefixed constants defined below.
*
* @return DNS record corresponding to the given hostname.
* @throws NS_ERROR_UNKNOWN_HOST if host could not be resolved.
*/
nsIDNSRecord resolve(in AUTF8String aHostName,
in unsigned long aFlags);
/**
* kicks off an asynchronous host lookup.
*
* This function is identical to asyncResolve except an additional
* parameter aNetwortInterface. If parameter aNetworkInterface is an empty
* string function will return the same result as asyncResolve.
* Setting aNetworkInterface value make only sense for gonk,because it
* an per networking interface query is possible.
*/
nsICancelable asyncResolveExtended(in AUTF8String aHostName,
in unsigned long aFlags,
in AUTF8String aNetworkInterface,
in nsIDNSListener aListener,
in nsIEventTarget aListenerTarget);
/**
* Attempts to cancel a previously requested async DNS lookup
* This is an extended versin with a additional parameter aNetworkInterface
*/
void cancelAsyncResolveExtended(in AUTF8String aHostName,
in unsigned long aFlags,
in AUTF8String aNetworkInterface,
in nsIDNSListener aListener,
in nsresult aReason);
/**
* The method takes a pointer to an nsTArray
* and fills it with cache entry data
* Called by the networking dashboard
*/
[noscript] void getDNSCacheEntries(in EntriesArray args);
/**
* @return the hostname of the operating system.
*/
readonly attribute AUTF8String myHostName;
/*************************************************************************
* Listed below are the various flags that may be OR'd together to form
* the aFlags parameter passed to asyncResolve() and resolve().
*/
/**
* if set, this flag suppresses the internal DNS lookup cache.
*/
const unsigned long RESOLVE_BYPASS_CACHE = (1 << 0);
/**
* if set, the canonical name of the specified host will be queried.
*/
const unsigned long RESOLVE_CANONICAL_NAME = (1 << 1);
/**
* if set, the query is given lower priority. Medium takes precedence
* if both are used.
*/
const unsigned long RESOLVE_PRIORITY_MEDIUM = (1 << 2);
const unsigned long RESOLVE_PRIORITY_LOW = (1 << 3);
/**
* if set, indicates request is speculative. Speculative requests
* return errors if prefetching is disabled by configuration.
*/
const unsigned long RESOLVE_SPECULATE = (1 << 4);
/**
* If set, only IPv4 addresses will be returned from resolve/asyncResolve.
*/
const unsigned long RESOLVE_DISABLE_IPV6 = (1 << 5);
/**
* If set, only literals and cached entries will be returned from resolve/
* asyncResolve.
*/
const unsigned long RESOLVE_OFFLINE = (1 << 6);
/**
* If set, only IPv6 addresses will be returned from resolve/asyncResolve.
*/
const unsigned long RESOLVE_DISABLE_IPV4 = (1 << 7);
/**
* If set, allow name collision results (127.0.53.53) which are normally filtered.
*/
const unsigned long RESOLVE_ALLOW_NAME_COLLISION = (1 << 8);
};

View file

@ -0,0 +1,959 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsIDNService.h"
#include "nsReadableUtils.h"
#include "nsCRT.h"
#include "nsUnicharUtils.h"
#include "nsUnicodeProperties.h"
#include "nsUnicodeScriptCodes.h"
#include "harfbuzz/hb.h"
#include "nsIServiceManager.h"
#include "nsIPrefService.h"
#include "nsIPrefBranch.h"
#include "nsIObserverService.h"
#include "nsISupportsPrimitives.h"
#include "punycode.h"
#ifdef IDNA2008
// Currently we use the non-transitional processing option -- see
// http://unicode.org/reports/tr46/
// To switch to transitional processing, change the value of this flag
// and kTransitionalProcessing in netwerk/test/unit/test_idna2008.js to true
// (revert bug 1218179).
const bool kIDNA2008_TransitionalProcessing = false;
#include "ICUUtils.h"
#endif
using namespace mozilla::unicode;
//-----------------------------------------------------------------------------
// RFC 1034 - 3.1. Name space specifications and terminology
static const uint32_t kMaxDNSNodeLen = 63;
// RFC 3490 - 5. ACE prefix
static const char kACEPrefix[] = "xn--";
#define kACEPrefixLen 4
//-----------------------------------------------------------------------------
#define NS_NET_PREF_IDNBLACKLIST "network.IDN.blacklist_chars"
#define NS_NET_PREF_SHOWPUNYCODE "network.IDN_show_punycode"
#define NS_NET_PREF_IDNWHITELIST "network.IDN.whitelist."
#define NS_NET_PREF_IDNUSEWHITELIST "network.IDN.use_whitelist"
#define NS_NET_PREF_IDNRESTRICTION "network.IDN.restriction_profile"
inline bool isOnlySafeChars(const nsAFlatString& in,
const nsAFlatString& blacklist)
{
return (blacklist.IsEmpty() ||
in.FindCharInSet(blacklist) == kNotFound);
}
//-----------------------------------------------------------------------------
// nsIDNService
//-----------------------------------------------------------------------------
/* Implementation file */
NS_IMPL_ISUPPORTS(nsIDNService,
nsIIDNService,
nsIObserver,
nsISupportsWeakReference)
nsresult nsIDNService::Init()
{
nsCOMPtr<nsIPrefService> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
if (prefs)
prefs->GetBranch(NS_NET_PREF_IDNWHITELIST, getter_AddRefs(mIDNWhitelistPrefBranch));
nsCOMPtr<nsIPrefBranch> prefInternal(do_QueryInterface(prefs));
if (prefInternal) {
prefInternal->AddObserver(NS_NET_PREF_IDNBLACKLIST, this, true);
prefInternal->AddObserver(NS_NET_PREF_SHOWPUNYCODE, this, true);
prefInternal->AddObserver(NS_NET_PREF_IDNRESTRICTION, this, true);
prefInternal->AddObserver(NS_NET_PREF_IDNUSEWHITELIST, this, true);
prefsChanged(prefInternal, nullptr);
}
return NS_OK;
}
NS_IMETHODIMP nsIDNService::Observe(nsISupports *aSubject,
const char *aTopic,
const char16_t *aData)
{
if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
nsCOMPtr<nsIPrefBranch> prefBranch( do_QueryInterface(aSubject) );
if (prefBranch)
prefsChanged(prefBranch, aData);
}
return NS_OK;
}
void nsIDNService::prefsChanged(nsIPrefBranch *prefBranch, const char16_t *pref)
{
if (!pref || NS_LITERAL_STRING(NS_NET_PREF_IDNBLACKLIST).Equals(pref)) {
nsCOMPtr<nsISupportsString> blacklist;
nsresult rv = prefBranch->GetComplexValue(NS_NET_PREF_IDNBLACKLIST,
NS_GET_IID(nsISupportsString),
getter_AddRefs(blacklist));
if (NS_SUCCEEDED(rv))
blacklist->ToString(getter_Copies(mIDNBlacklist));
else
mIDNBlacklist.Truncate();
}
if (!pref || NS_LITERAL_STRING(NS_NET_PREF_SHOWPUNYCODE).Equals(pref)) {
bool val;
if (NS_SUCCEEDED(prefBranch->GetBoolPref(NS_NET_PREF_SHOWPUNYCODE, &val)))
mShowPunycode = val;
}
if (!pref || NS_LITERAL_STRING(NS_NET_PREF_IDNUSEWHITELIST).Equals(pref)) {
bool val;
if (NS_SUCCEEDED(prefBranch->GetBoolPref(NS_NET_PREF_IDNUSEWHITELIST,
&val)))
mIDNUseWhitelist = val;
}
if (!pref || NS_LITERAL_STRING(NS_NET_PREF_IDNRESTRICTION).Equals(pref)) {
nsXPIDLCString profile;
if (NS_FAILED(prefBranch->GetCharPref(NS_NET_PREF_IDNRESTRICTION,
getter_Copies(profile)))) {
profile.Truncate();
}
if (profile.EqualsLiteral("moderate")) {
mRestrictionProfile = eModeratelyRestrictiveProfile;
} else if (profile.EqualsLiteral("high")) {
mRestrictionProfile = eHighlyRestrictiveProfile;
} else {
mRestrictionProfile = eASCIIOnlyProfile;
}
}
}
nsIDNService::nsIDNService()
: mShowPunycode(false)
, mIDNUseWhitelist(false)
{
#ifdef IDNA2008
uint32_t IDNAOptions = UIDNA_CHECK_BIDI | UIDNA_CHECK_CONTEXTJ;
if (!kIDNA2008_TransitionalProcessing) {
IDNAOptions |= UIDNA_NONTRANSITIONAL_TO_UNICODE;
}
UErrorCode errorCode = U_ZERO_ERROR;
mIDNA = uidna_openUTS46(IDNAOptions, &errorCode);
#else
if (idn_success != idn_nameprep_create(nullptr, &mNamePrepHandle))
mNamePrepHandle = nullptr;
mNormalizer = do_GetService(NS_UNICODE_NORMALIZER_CONTRACTID);
/* member initializers and constructor code */
#endif
}
nsIDNService::~nsIDNService()
{
#ifdef IDNA2008
uidna_close(mIDNA);
#else
idn_nameprep_destroy(mNamePrepHandle);
#endif
}
#ifdef IDNA2008
nsresult
nsIDNService::IDNA2008ToUnicode(const nsACString& input, nsAString& output)
{
NS_ConvertUTF8toUTF16 inputStr(input);
UIDNAInfo info = UIDNA_INFO_INITIALIZER;
UErrorCode errorCode = U_ZERO_ERROR;
int32_t inLen = inputStr.Length();
int32_t outMaxLen = kMaxDNSNodeLen + 1;
UChar outputBuffer[kMaxDNSNodeLen + 1];
int32_t outLen = uidna_labelToUnicode(mIDNA, (const UChar*)inputStr.get(),
inLen, outputBuffer, outMaxLen,
&info, &errorCode);
if (info.errors != 0) {
return NS_ERROR_MALFORMED_URI;
}
if (U_SUCCESS(errorCode)) {
ICUUtils::AssignUCharArrayToString(outputBuffer, outLen, output);
}
nsresult rv = ICUUtils::UErrorToNsResult(errorCode);
if (rv == NS_ERROR_FAILURE) {
rv = NS_ERROR_MALFORMED_URI;
}
return rv;
}
nsresult
nsIDNService::IDNA2008StringPrep(const nsAString& input,
nsAString& output,
stringPrepFlag flag)
{
UIDNAInfo info = UIDNA_INFO_INITIALIZER;
UErrorCode errorCode = U_ZERO_ERROR;
int32_t inLen = input.Length();
int32_t outMaxLen = kMaxDNSNodeLen + 1;
UChar outputBuffer[kMaxDNSNodeLen + 1];
int32_t outLen =
uidna_labelToUnicode(mIDNA, (const UChar*)PromiseFlatString(input).get(),
inLen, outputBuffer, outMaxLen, &info, &errorCode);
nsresult rv = ICUUtils::UErrorToNsResult(errorCode);
if (rv == NS_ERROR_FAILURE) {
rv = NS_ERROR_MALFORMED_URI;
}
NS_ENSURE_SUCCESS(rv, rv);
// Output the result of nameToUnicode even if there were errors
ICUUtils::AssignUCharArrayToString(outputBuffer, outLen, output);
if (flag == eStringPrepIgnoreErrors) {
return NS_OK;
}
if (info.errors != 0) {
if (flag == eStringPrepForDNS) {
output.Truncate();
}
rv = NS_ERROR_MALFORMED_URI;
}
return rv;
}
#endif
NS_IMETHODIMP nsIDNService::ConvertUTF8toACE(const nsACString & input, nsACString & ace)
{
return UTF8toACE(input, ace, eStringPrepForDNS);
}
nsresult nsIDNService::UTF8toACE(const nsACString & input, nsACString & ace,
stringPrepFlag flag)
{
nsresult rv;
NS_ConvertUTF8toUTF16 ustr(input);
// map ideographic period to ASCII period etc.
normalizeFullStops(ustr);
uint32_t len, offset;
len = 0;
offset = 0;
nsAutoCString encodedBuf;
nsAString::const_iterator start, end;
ustr.BeginReading(start);
ustr.EndReading(end);
ace.Truncate();
// encode nodes if non ASCII
while (start != end) {
len++;
if (*start++ == (char16_t)'.') {
rv = stringPrepAndACE(Substring(ustr, offset, len - 1), encodedBuf, flag);
NS_ENSURE_SUCCESS(rv, rv);
ace.Append(encodedBuf);
ace.Append('.');
offset += len;
len = 0;
}
}
// encode the last node if non ASCII
if (len) {
rv = stringPrepAndACE(Substring(ustr, offset, len), encodedBuf, flag);
NS_ENSURE_SUCCESS(rv, rv);
ace.Append(encodedBuf);
}
return NS_OK;
}
NS_IMETHODIMP nsIDNService::ConvertACEtoUTF8(const nsACString & input, nsACString & _retval)
{
return ACEtoUTF8(input, _retval, eStringPrepForDNS);
}
nsresult nsIDNService::ACEtoUTF8(const nsACString & input, nsACString & _retval,
stringPrepFlag flag)
{
// RFC 3490 - 4.2 ToUnicode
// ToUnicode never fails. If any step fails, then the original input
// sequence is returned immediately in that step.
uint32_t len = 0, offset = 0;
nsAutoCString decodedBuf;
nsACString::const_iterator start, end;
input.BeginReading(start);
input.EndReading(end);
_retval.Truncate();
// loop and decode nodes
while (start != end) {
len++;
if (*start++ == '.') {
if (NS_FAILED(decodeACE(Substring(input, offset, len - 1), decodedBuf,
flag))) {
_retval.Assign(input);
return NS_OK;
}
_retval.Append(decodedBuf);
_retval.Append('.');
offset += len;
len = 0;
}
}
// decode the last node
if (len) {
if (NS_FAILED(decodeACE(Substring(input, offset, len), decodedBuf,
flag)))
_retval.Assign(input);
else
_retval.Append(decodedBuf);
}
return NS_OK;
}
NS_IMETHODIMP nsIDNService::IsACE(const nsACString & input, bool *_retval)
{
const char *data = input.BeginReading();
uint32_t dataLen = input.Length();
// look for the ACE prefix in the input string. it may occur
// at the beginning of any segment in the domain name. for
// example: "www.xn--ENCODED.com"
const char *p = PL_strncasestr(data, kACEPrefix, dataLen);
*_retval = p && (p == data || *(p - 1) == '.');
return NS_OK;
}
NS_IMETHODIMP nsIDNService::Normalize(const nsACString & input,
nsACString & output)
{
// protect against bogus input
NS_ENSURE_TRUE(IsUTF8(input), NS_ERROR_UNEXPECTED);
NS_ConvertUTF8toUTF16 inUTF16(input);
normalizeFullStops(inUTF16);
// pass the domain name to stringprep label by label
nsAutoString outUTF16, outLabel;
uint32_t len = 0, offset = 0;
nsresult rv;
nsAString::const_iterator start, end;
inUTF16.BeginReading(start);
inUTF16.EndReading(end);
while (start != end) {
len++;
if (*start++ == char16_t('.')) {
rv = stringPrep(Substring(inUTF16, offset, len - 1), outLabel,
eStringPrepIgnoreErrors);
NS_ENSURE_SUCCESS(rv, rv);
outUTF16.Append(outLabel);
outUTF16.Append(char16_t('.'));
offset += len;
len = 0;
}
}
if (len) {
rv = stringPrep(Substring(inUTF16, offset, len), outLabel,
eStringPrepIgnoreErrors);
NS_ENSURE_SUCCESS(rv, rv);
outUTF16.Append(outLabel);
}
CopyUTF16toUTF8(outUTF16, output);
return NS_OK;
}
NS_IMETHODIMP nsIDNService::ConvertToDisplayIDN(const nsACString & input, bool * _isASCII, nsACString & _retval)
{
// If host is ACE, then convert to UTF-8 if the host is in the IDN whitelist.
// Else, if host is already UTF-8, then make sure it is normalized per IDN.
nsresult rv = NS_OK;
// Even if the hostname is not ASCII, individual labels may still be ACE, so
// test IsACE before testing IsASCII
bool isACE;
IsACE(input, &isACE);
if (IsASCII(input)) {
// first, canonicalize the host to lowercase, for whitelist lookup
_retval = input;
ToLowerCase(_retval);
if (isACE && !mShowPunycode) {
// ACEtoUTF8() can't fail, but might return the original ACE string
nsAutoCString temp(_retval);
// If the domain is in the whitelist, return the host in UTF-8.
// Otherwise convert from ACE to UTF8 only those labels which are
// considered safe for display
ACEtoUTF8(temp, _retval, isInWhitelist(temp) ?
eStringPrepIgnoreErrors : eStringPrepForUI);
*_isASCII = IsASCII(_retval);
} else {
*_isASCII = true;
}
} else {
// We have to normalize the hostname before testing against the domain
// whitelist (see bug 315411), and to ensure the entire string gets
// normalized.
//
// Normalization and the tests for safe display below, assume that the
// input is Unicode, so first convert any ACE labels to UTF8
if (isACE) {
nsAutoCString temp;
ACEtoUTF8(input, temp, eStringPrepIgnoreErrors);
rv = Normalize(temp, _retval);
} else {
rv = Normalize(input, _retval);
}
if (NS_FAILED(rv)) return rv;
if (mShowPunycode && NS_SUCCEEDED(UTF8toACE(_retval, _retval,
eStringPrepIgnoreErrors))) {
*_isASCII = true;
return NS_OK;
}
// normalization could result in an ASCII-only hostname. alternatively, if
// the host is converted to ACE by the normalizer, then the host may contain
// unsafe characters, so leave it ACE encoded. see bug 283016, bug 301694, and bug 309311.
*_isASCII = IsASCII(_retval);
if (!*_isASCII && !isInWhitelist(_retval)) {
// UTF8toACE with eStringPrepForUI may return a domain name where
// some labels are in UTF-8 and some are in ACE, depending on
// whether they are considered safe for display
rv = UTF8toACE(_retval, _retval, eStringPrepForUI);
*_isASCII = IsASCII(_retval);
return rv;
}
}
return NS_OK;
}
//-----------------------------------------------------------------------------
static nsresult utf16ToUcs4(const nsAString& in,
uint32_t *out,
uint32_t outBufLen,
uint32_t *outLen)
{
uint32_t i = 0;
nsAString::const_iterator start, end;
in.BeginReading(start);
in.EndReading(end);
while (start != end) {
char16_t curChar;
curChar= *start++;
if (start != end &&
NS_IS_HIGH_SURROGATE(curChar) &&
NS_IS_LOW_SURROGATE(*start)) {
out[i] = SURROGATE_TO_UCS4(curChar, *start);
++start;
}
else
out[i] = curChar;
i++;
if (i >= outBufLen)
return NS_ERROR_MALFORMED_URI;
}
out[i] = (uint32_t)'\0';
*outLen = i;
return NS_OK;
}
#ifndef IDNA2008
static void ucs4toUtf16(const uint32_t *in, nsAString& out)
{
while (*in) {
if (!IS_IN_BMP(*in)) {
out.Append((char16_t) H_SURROGATE(*in));
out.Append((char16_t) L_SURROGATE(*in));
}
else
out.Append((char16_t) *in);
in++;
}
}
#endif
static nsresult punycode(const nsAString& in, nsACString& out)
{
uint32_t ucs4Buf[kMaxDNSNodeLen + 1];
uint32_t ucs4Len = 0u;
nsresult rv = utf16ToUcs4(in, ucs4Buf, kMaxDNSNodeLen, &ucs4Len);
NS_ENSURE_SUCCESS(rv, rv);
// need maximum 20 bits to encode 16 bit Unicode character
// (include null terminator)
const uint32_t kEncodedBufSize = kMaxDNSNodeLen * 20 / 8 + 1 + 1;
char encodedBuf[kEncodedBufSize];
punycode_uint encodedLength = kEncodedBufSize;
enum punycode_status status = punycode_encode(ucs4Len,
ucs4Buf,
nullptr,
&encodedLength,
encodedBuf);
if (punycode_success != status ||
encodedLength >= kEncodedBufSize)
return NS_ERROR_MALFORMED_URI;
encodedBuf[encodedLength] = '\0';
out.Assign(nsDependentCString(kACEPrefix) + nsDependentCString(encodedBuf));
return rv;
}
// RFC 3454
//
// 1) Map -- For each character in the input, check if it has a mapping
// and, if so, replace it with its mapping. This is described in section 3.
//
// 2) Normalize -- Possibly normalize the result of step 1 using Unicode
// normalization. This is described in section 4.
//
// 3) Prohibit -- Check for any characters that are not allowed in the
// output. If any are found, return an error. This is described in section
// 5.
//
// 4) Check bidi -- Possibly check for right-to-left characters, and if any
// are found, make sure that the whole string satisfies the requirements
// for bidirectional strings. If the string does not satisfy the requirements
// for bidirectional strings, return an error. This is described in section 6.
//
// 5) Check unassigned code points -- If allowUnassigned is false, check for
// any unassigned Unicode points and if any are found return an error.
// This is described in section 7.
//
nsresult nsIDNService::stringPrep(const nsAString& in, nsAString& out,
stringPrepFlag flag)
{
#ifdef IDNA2008
return IDNA2008StringPrep(in, out, flag);
#else
if (!mNamePrepHandle || !mNormalizer)
return NS_ERROR_FAILURE;
uint32_t ucs4Buf[kMaxDNSNodeLen + 1];
uint32_t ucs4Len;
nsresult rv = utf16ToUcs4(in, ucs4Buf, kMaxDNSNodeLen, &ucs4Len);
NS_ENSURE_SUCCESS(rv, rv);
// map
idn_result_t idn_err;
uint32_t namePrepBuf[kMaxDNSNodeLen * 3]; // map up to three characters
idn_err = idn_nameprep_map(mNamePrepHandle, (const uint32_t *) ucs4Buf,
(uint32_t *) namePrepBuf, kMaxDNSNodeLen * 3);
NS_ENSURE_TRUE(idn_err == idn_success, NS_ERROR_MALFORMED_URI);
nsAutoString namePrepStr;
ucs4toUtf16(namePrepBuf, namePrepStr);
if (namePrepStr.Length() >= kMaxDNSNodeLen)
return NS_ERROR_MALFORMED_URI;
// normalize
nsAutoString normlizedStr;
rv = mNormalizer->NormalizeUnicodeNFKC(namePrepStr, normlizedStr);
if (normlizedStr.Length() >= kMaxDNSNodeLen)
return NS_ERROR_MALFORMED_URI;
// set the result string
out.Assign(normlizedStr);
if (flag == eStringPrepIgnoreErrors) {
return NS_OK;
}
// prohibit
const uint32_t *found = nullptr;
idn_err = idn_nameprep_isprohibited(mNamePrepHandle,
(const uint32_t *) ucs4Buf, &found);
if (idn_err != idn_success || found) {
rv = NS_ERROR_MALFORMED_URI;
} else {
// check bidi
idn_err = idn_nameprep_isvalidbidi(mNamePrepHandle,
(const uint32_t *) ucs4Buf, &found);
if (idn_err != idn_success || found) {
rv = NS_ERROR_MALFORMED_URI;
} else if (flag == eStringPrepForUI) {
// check unassigned code points
idn_err = idn_nameprep_isunassigned(mNamePrepHandle,
(const uint32_t *) ucs4Buf, &found);
if (idn_err != idn_success || found) {
rv = NS_ERROR_MALFORMED_URI;
}
}
}
if (flag == eStringPrepForDNS && NS_FAILED(rv)) {
out.Truncate();
}
return rv;
#endif
}
nsresult nsIDNService::stringPrepAndACE(const nsAString& in, nsACString& out,
stringPrepFlag flag)
{
nsresult rv = NS_OK;
out.Truncate();
if (in.Length() > kMaxDNSNodeLen) {
NS_WARNING("IDN node too large");
return NS_ERROR_MALFORMED_URI;
}
if (IsASCII(in)) {
LossyCopyUTF16toASCII(in, out);
return NS_OK;
}
nsAutoString strPrep;
rv = stringPrep(in, strPrep, flag);
if (flag == eStringPrepForDNS) {
NS_ENSURE_SUCCESS(rv, rv);
}
if (IsASCII(strPrep)) {
LossyCopyUTF16toASCII(strPrep, out);
return NS_OK;
}
if (flag == eStringPrepForUI && NS_SUCCEEDED(rv) && isLabelSafe(in)) {
CopyUTF16toUTF8(strPrep, out);
return NS_OK;
}
rv = punycode(strPrep, out);
// Check that the encoded output isn't larger than the maximum length
// of a DNS node per RFC 1034.
// This test isn't necessary in the code paths above where the input
// is ASCII (since the output will be the same length as the input) or
// where we convert to UTF-8 (since the output is only used for
// display in the UI and not passed to DNS and can legitimately be
// longer than the limit).
if (out.Length() > kMaxDNSNodeLen) {
NS_WARNING("IDN node too large");
return NS_ERROR_MALFORMED_URI;
}
return rv;
}
// RFC 3490
// 1) Whenever dots are used as label separators, the following characters
// MUST be recognized as dots: U+002E (full stop), U+3002 (ideographic full
// stop), U+FF0E (fullwidth full stop), U+FF61 (halfwidth ideographic full
// stop).
void nsIDNService::normalizeFullStops(nsAString& s)
{
nsAString::const_iterator start, end;
s.BeginReading(start);
s.EndReading(end);
int32_t index = 0;
while (start != end) {
switch (*start) {
case 0x3002:
case 0xFF0E:
case 0xFF61:
s.Replace(index, 1, NS_LITERAL_STRING("."));
break;
default:
break;
}
start++;
index++;
}
}
nsresult nsIDNService::decodeACE(const nsACString& in, nsACString& out,
stringPrepFlag flag)
{
bool isAce;
IsACE(in, &isAce);
if (!isAce) {
out.Assign(in);
return NS_OK;
}
nsAutoString utf16;
#ifdef IDNA2008
nsresult result = IDNA2008ToUnicode(in, utf16);
NS_ENSURE_SUCCESS(result, result);
#else
// RFC 3490 - 4.2 ToUnicode
// The ToUnicode output never contains more code points than its input.
punycode_uint output_length = in.Length() - kACEPrefixLen + 1;
auto *output = new punycode_uint[output_length];
NS_ENSURE_TRUE(output, NS_ERROR_OUT_OF_MEMORY);
enum punycode_status status = punycode_decode(in.Length() - kACEPrefixLen,
PromiseFlatCString(in).get() + kACEPrefixLen,
&output_length,
output,
nullptr);
if (status != punycode_success) {
delete [] output;
return NS_ERROR_MALFORMED_URI;
}
// UCS4 -> UTF8
output[output_length] = 0;
ucs4toUtf16(output, utf16);
delete [] output;
#endif
if (flag != eStringPrepForUI || isLabelSafe(utf16)) {
CopyUTF16toUTF8(utf16, out);
} else {
out.Assign(in);
return NS_OK;
}
// Validation: encode back to ACE and compare the strings
nsAutoCString ace;
nsresult rv = UTF8toACE(out, ace, flag);
NS_ENSURE_SUCCESS(rv, rv);
if (flag == eStringPrepForDNS &&
!ace.Equals(in, nsCaseInsensitiveCStringComparator())) {
return NS_ERROR_MALFORMED_URI;
}
return NS_OK;
}
bool nsIDNService::isInWhitelist(const nsACString &host)
{
if (mIDNUseWhitelist && mIDNWhitelistPrefBranch) {
nsAutoCString tld(host);
// make sure the host is ACE for lookup and check that there are no
// unassigned codepoints
if (!IsASCII(tld) && NS_FAILED(UTF8toACE(tld, tld, eStringPrepForDNS))) {
return false;
}
// truncate trailing dots first
tld.Trim(".");
int32_t pos = tld.RFind(".");
if (pos == kNotFound)
return false;
tld.Cut(0, pos + 1);
bool safe;
if (NS_SUCCEEDED(mIDNWhitelistPrefBranch->GetBoolPref(tld.get(), &safe)))
return safe;
}
return false;
}
bool nsIDNService::isLabelSafe(const nsAString &label)
{
if (!isOnlySafeChars(PromiseFlatString(label), mIDNBlacklist)) {
return false;
}
// We should never get here if the label is ASCII
NS_ASSERTION(!IsASCII(label), "ASCII label in IDN checking");
if (mRestrictionProfile == eASCIIOnlyProfile) {
return false;
}
nsAString::const_iterator current, end;
label.BeginReading(current);
label.EndReading(end);
Script lastScript = Script::INVALID;
uint32_t previousChar = 0;
uint32_t savedNumberingSystem = 0;
// Simplified/Traditional Chinese check temporarily disabled -- bug 857481
#if 0
HanVariantType savedHanVariant = HVT_NotHan;
#endif
int32_t savedScript = -1;
while (current != end) {
uint32_t ch = *current++;
if (NS_IS_HIGH_SURROGATE(ch) && current != end &&
NS_IS_LOW_SURROGATE(*current)) {
ch = SURROGATE_TO_UCS4(ch, *current++);
}
// Check for restricted characters; aspirational scripts are NOT permitted,
// in anticipation of the category being merged into Limited-Use scripts
// in the upcoming (Unicode 10.0-based) revision of UAX #31.
XidmodType xm = GetIdentifierModification(ch);
if (xm != XIDMOD_RECOMMENDED &&
xm != XIDMOD_INCLUSION) {
return false;
}
// Check for mixed script
Script script = GetScriptCode(ch);
if (script != Script::COMMON &&
script != Script::INHERITED &&
script != lastScript) {
if (illegalScriptCombo(script, savedScript)) {
return false;
}
lastScript = script;
}
// Check for mixed numbering systems
if (GetGeneralCategory(ch) ==
HB_UNICODE_GENERAL_CATEGORY_DECIMAL_NUMBER) {
uint32_t zeroCharacter = ch - GetNumericValue(ch);
if (savedNumberingSystem == 0) {
// If we encounter a decimal number, save the zero character from that
// numbering system.
savedNumberingSystem = zeroCharacter;
} else if (zeroCharacter != savedNumberingSystem) {
return false;
}
}
// Check for consecutive non-spacing marks
if (previousChar != 0 &&
previousChar == ch &&
GetGeneralCategory(ch) == HB_UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK) {
return false;
}
// Simplified/Traditional Chinese check temporarily disabled -- bug 857481
#if 0
// Check for both simplified-only and traditional-only Chinese characters
HanVariantType hanVariant = GetHanVariant(ch);
if (hanVariant == HVT_SimplifiedOnly || hanVariant == HVT_TraditionalOnly) {
if (savedHanVariant == HVT_NotHan) {
savedHanVariant = hanVariant;
} else if (hanVariant != savedHanVariant) {
return false;
}
}
#endif
previousChar = ch;
}
return true;
}
// Scripts that we care about in illegalScriptCombo
static const Script scriptTable[] = {
Script::BOPOMOFO, Script::CYRILLIC, Script::GREEK,
Script::HANGUL, Script::HAN, Script::HIRAGANA,
Script::KATAKANA, Script::LATIN };
#define BOPO 0
#define CYRL 1
#define GREK 2
#define HANG 3
#define HANI 4
#define HIRA 5
#define KATA 6
#define LATN 7
#define OTHR 8
#define JPAN 9 // Latin + Han + Hiragana + Katakana
#define CHNA 10 // Latin + Han + Bopomofo
#define KORE 11 // Latin + Han + Hangul
#define HNLT 12 // Latin + Han (could be any of the above combinations)
#define FAIL 13
static inline int32_t findScriptIndex(Script aScript)
{
int32_t tableLength = sizeof(scriptTable) / sizeof(int32_t);
for (int32_t index = 0; index < tableLength; ++index) {
if (aScript == scriptTable[index]) {
return index;
}
}
return OTHR;
}
static const int32_t scriptComboTable[13][9] = {
/* thisScript: BOPO CYRL GREK HANG HANI HIRA KATA LATN OTHR
* savedScript */
/* BOPO */ { BOPO, FAIL, FAIL, FAIL, CHNA, FAIL, FAIL, CHNA, FAIL },
/* CYRL */ { FAIL, CYRL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL },
/* GREK */ { FAIL, FAIL, GREK, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL },
/* HANG */ { FAIL, FAIL, FAIL, HANG, KORE, FAIL, FAIL, KORE, FAIL },
/* HANI */ { CHNA, FAIL, FAIL, KORE, HANI, JPAN, JPAN, HNLT, FAIL },
/* HIRA */ { FAIL, FAIL, FAIL, FAIL, JPAN, HIRA, JPAN, JPAN, FAIL },
/* KATA */ { FAIL, FAIL, FAIL, FAIL, JPAN, JPAN, KATA, JPAN, FAIL },
/* LATN */ { CHNA, FAIL, FAIL, KORE, HNLT, JPAN, JPAN, LATN, OTHR },
/* OTHR */ { FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, OTHR, FAIL },
/* JPAN */ { FAIL, FAIL, FAIL, FAIL, JPAN, JPAN, JPAN, JPAN, FAIL },
/* CHNA */ { CHNA, FAIL, FAIL, FAIL, CHNA, FAIL, FAIL, CHNA, FAIL },
/* KORE */ { FAIL, FAIL, FAIL, KORE, KORE, FAIL, FAIL, KORE, FAIL },
/* HNLT */ { CHNA, FAIL, FAIL, KORE, HNLT, JPAN, JPAN, HNLT, FAIL }
};
bool nsIDNService::illegalScriptCombo(Script script, int32_t& savedScript)
{
if (savedScript == -1) {
savedScript = findScriptIndex(script);
return false;
}
savedScript = scriptComboTable[savedScript] [findScriptIndex(script)];
/*
* Special case combinations that depend on which profile is in use
* In the Highly Restrictive profile Latin is not allowed with any
* other script
*
* In the Moderately Restrictive profile Latin mixed with any other
* single script is allowed.
*/
return ((savedScript == OTHR &&
mRestrictionProfile == eHighlyRestrictiveProfile) ||
savedScript == FAIL);
}
#undef BOPO
#undef CYRL
#undef GREK
#undef HANG
#undef HANI
#undef HIRA
#undef KATA
#undef LATN
#undef OTHR
#undef JPAN
#undef CHNA
#undef KORE
#undef HNLT
#undef FAIL

195
netwerk/dns/nsIDNService.h Normal file
View file

@ -0,0 +1,195 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef nsIDNService_h__
#define nsIDNService_h__
#include "nsIIDNService.h"
#include "nsCOMPtr.h"
#include "nsIObserver.h"
#include "nsUnicodeScriptCodes.h"
#include "nsWeakReference.h"
#ifdef IDNA2008
#include "unicode/uidna.h"
#else
#include "nsIUnicodeNormalizer.h"
#include "nsIDNKitInterface.h"
#endif
#include "nsString.h"
class nsIPrefBranch;
//-----------------------------------------------------------------------------
// nsIDNService
//-----------------------------------------------------------------------------
class nsIDNService final : public nsIIDNService,
public nsIObserver,
public nsSupportsWeakReference
{
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIIDNSERVICE
NS_DECL_NSIOBSERVER
nsIDNService();
nsresult Init();
protected:
virtual ~nsIDNService();
private:
enum stringPrepFlag {
eStringPrepForDNS,
eStringPrepForUI,
eStringPrepIgnoreErrors
};
/**
* Convert the following characters that must be recognized as label
* separators per RFC 3490 to ASCII full stop characters
*
* U+3002 (ideographic full stop)
* U+FF0E (fullwidth full stop)
* U+FF61 (halfwidth ideographic full stop)
*/
void normalizeFullStops(nsAString& s);
/**
* Convert and encode a DNS label in ACE/punycode.
* @param flag
* if eStringPrepIgnoreErrors, all non-ASCII labels are
* converted to punycode.
* if eStringPrepForUI, only labels that are considered safe
* for display are converted.
* @see isLabelSafe
* if eStringPrepForDNS and stringPrep finds an illegal
* character, returns NS_FAILURE and out is empty
*/
nsresult stringPrepAndACE(const nsAString& in, nsACString& out,
stringPrepFlag flag);
/**
* Convert a DNS label using the stringprep profile defined in RFC 3454
*/
nsresult stringPrep(const nsAString& in, nsAString& out, stringPrepFlag flag);
/**
* Decode an ACE-encoded DNS label to UTF-8
*
* @param flag
* if eStringPrepForUI and the label is not considered safe to
* display, the output is the same as the input
* @see isLabelSafe
*/
nsresult decodeACE(const nsACString& in, nsACString& out,
stringPrepFlag flag);
/**
* Convert complete domain names between UTF8 and ACE and vice versa
*
* @param flag is passed to decodeACE or stringPrepAndACE for each
* label individually, so the output may contain some labels in
* punycode and some in UTF-8
*/
nsresult UTF8toACE(const nsACString& input, nsACString& ace,
stringPrepFlag flag);
nsresult ACEtoUTF8(const nsACString& input, nsACString& _retval,
stringPrepFlag flag);
bool isInWhitelist(const nsACString &host);
void prefsChanged(nsIPrefBranch *prefBranch, const char16_t *pref);
/**
* Determine whether a label is considered safe to display to the user
* according to the algorithm defined in UTR 39 and the profile
* selected in mRestrictionProfile.
*
* For the ASCII-only profile, returns false for all labels containing
* non-ASCII characters.
*
* For the other profiles, returns false for labels containing any of
* the following:
*
* Characters in scripts other than the "recommended scripts" and
* "aspirational scripts" defined in
* http://www.unicode.org/reports/tr31/#Table_Recommended_Scripts
* and http://www.unicode.org/reports/tr31/#Aspirational_Use_Scripts
* This includes codepoints that are not defined as Unicode
* characters
*
* Illegal combinations of scripts (@see illegalScriptCombo)
*
* Numbers from more than one different numbering system
*
* Sequences of the same non-spacing mark
*
* Both simplified-only and traditional-only Chinese characters
* XXX this test was disabled by bug 857481
*/
bool isLabelSafe(const nsAString &label);
/**
* Determine whether a combination of scripts in a single label is
* permitted according to the algorithm defined in UTR 39 and the
* profile selected in mRestrictionProfile.
*
* For the "Highly restrictive" profile, all characters in each
* identifier must be from a single script, or from the combinations:
* Latin + Han + Hiragana + Katakana;
* Latin + Han + Bopomofo; or
* Latin + Han + Hangul
*
* For the "Moderately restrictive" profile, Latin is also allowed
* with other scripts except Cyrillic and Greek
*/
bool illegalScriptCombo(mozilla::unicode::Script script,
int32_t& savedScript);
#ifdef IDNA2008
/**
* Convert a DNS label from ASCII to Unicode using IDNA2008
*/
nsresult IDNA2008ToUnicode(const nsACString& input, nsAString& output);
/**
* Convert a DNS label to a normalized form conforming to IDNA2008
*/
nsresult IDNA2008StringPrep(const nsAString& input, nsAString& output,
stringPrepFlag flag);
UIDNA* mIDNA;
#else
idn_nameprep_t mNamePrepHandle;
nsCOMPtr<nsIUnicodeNormalizer> mNormalizer;
#endif
nsXPIDLString mIDNBlacklist;
/**
* Flag set by the pref network.IDN_show_punycode. When it is true,
* IDNs containing non-ASCII characters are always displayed to the
* user in punycode
*/
bool mShowPunycode;
/**
* Restriction-level Detection profiles defined in UTR 39
* http://www.unicode.org/reports/tr39/#Restriction_Level_Detection,
* and selected by the pref network.IDN.restriction_profile
*/
enum restrictionProfile {
eASCIIOnlyProfile,
eHighlyRestrictiveProfile,
eModeratelyRestrictiveProfile
};
restrictionProfile mRestrictionProfile;
nsCOMPtr<nsIPrefBranch> mIDNWhitelistPrefBranch;
bool mIDNUseWhitelist;
};
#endif // nsIDNService_h__

View file

@ -0,0 +1,125 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl"
interface nsIURI;
[scriptable, uuid(68067eb5-ad8d-43cb-a043-1cc85ebe06e7)]
interface nsIEffectiveTLDService : nsISupports
{
/**
* Returns the public suffix of a URI. A public suffix is the highest-level domain
* under which individual domains may be registered; it may therefore contain one
* or more dots. For example, the public suffix for "www.bbc.co.uk" is "co.uk",
* because the .uk TLD does not allow the registration of domains at the
* second level ("bbc.uk" is forbidden).
*
* The public suffix will be returned encoded in ASCII/ACE and will be normalized
* according to RFC 3454, i.e. the same encoding returned by nsIURI::GetAsciiHost().
* If consumers wish to compare the result of this method against the host from
* another nsIURI, the host should be obtained using nsIURI::GetAsciiHost().
* In the case of nested URIs, the innermost URI will be used.
*
* @param aURI The URI to be analyzed
*
* @returns the public suffix
*
* @throws NS_ERROR_UNEXPECTED
* or other error returned by nsIIDNService::normalize when
* the hostname contains characters disallowed in URIs
* @throws NS_ERROR_HOST_IS_IP_ADDRESS
* if the host is a numeric IPv4 or IPv6 address (as determined by
* the success of a call to PR_StringToNetAddr()).
*/
ACString getPublicSuffix(in nsIURI aURI);
/**
* Returns the base domain of a URI; that is, the public suffix with a given
* number of additional domain name parts. For example, the result of this method
* for "www.bbc.co.uk", depending on the value of aAdditionalParts parameter, will
* be:
*
* 0 (default) -> bbc.co.uk
* 1 -> www.bbc.co.uk
*
* Similarly, the public suffix for "www.developer.mozilla.org" is "org", and the base
* domain will be:
*
* 0 (default) -> mozilla.org
* 1 -> developer.mozilla.org
* 2 -> www.developer.mozilla.org
*
* The base domain will be returned encoded in ASCII/ACE and will be normalized
* according to RFC 3454, i.e. the same encoding returned by nsIURI::GetAsciiHost().
* If consumers wish to compare the result of this method against the host from
* another nsIURI, the host should be obtained using nsIURI::GetAsciiHost().
* In the case of nested URIs, the innermost URI will be used.
*
* @param aURI The URI to be analyzed
* @param aAdditionalParts Number of domain name parts to be
* returned in addition to the public suffix
*
* @returns the base domain (public suffix plus the requested number of additional parts)
*
* @throws NS_ERROR_UNEXPECTED
* or other error returned by nsIIDNService::normalize when
* the hostname contains characters disallowed in URIs
* @throws NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS
* when there are insufficient subdomain levels in the hostname to satisfy the
* requested aAdditionalParts value.
* @throws NS_ERROR_HOST_IS_IP_ADDRESS
* if aHost is a numeric IPv4 or IPv6 address (as determined by
* the success of a call to PR_StringToNetAddr()).
*
* @see getPublicSuffix()
*/
ACString getBaseDomain(in nsIURI aURI, [optional] in uint32_t aAdditionalParts);
/**
* NOTE: It is strongly recommended to use getPublicSuffix() above if a suitable
* nsIURI is available. Only use this method if this is not the case.
*
* Returns the public suffix of a host string. Otherwise identical to getPublicSuffix().
*
* @param aHost The host to be analyzed. Any additional parts (e.g. scheme,
* port, or path) will cause this method to throw. ASCII/ACE and
* UTF8 encodings are acceptable as input; normalization will
* be performed as specified in getBaseDomain().
*
* @see getPublicSuffix()
*/
ACString getPublicSuffixFromHost(in AUTF8String aHost);
/**
* NOTE: It is strongly recommended to use getBaseDomain() above if a suitable
* nsIURI is available. Only use this method if this is not the case.
*
* Returns the base domain of a host string. Otherwise identical to getBaseDomain().
*
* @param aHost The host to be analyzed. Any additional parts (e.g. scheme,
* port, or path) will cause this method to throw. ASCII/ACE and
* UTF8 encodings are acceptable as input; normalization will
* be performed as specified in getBaseDomain().
*
* @see getBaseDomain()
*/
ACString getBaseDomainFromHost(in AUTF8String aHost, [optional] in uint32_t aAdditionalParts);
/**
* Returns the parent sub-domain of a host string. If the host is a base
* domain, it will throw NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS.
*
* For example: "player.bbc.co.uk" would return "bbc.co.uk" and
* "bbc.co.uk" would throw NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS.
*
* @param aHost The host to be analyzed. Any additional parts (e.g. scheme,
* port, or path) will cause this method to throw. ASCII/ACE and
* UTF8 encodings are acceptable as input; normalization will
* be performed as specified in getBaseDomain().
*/
ACString getNextSubDomain(in AUTF8String aHost);
};

View file

@ -0,0 +1,58 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl"
/**
* nsIIDNService interface.
*
* IDN (Internationalized Domain Name) support. Provides facilities
* for manipulating IDN hostnames according to the specification set
* forth by the IETF.
*
* IDN effort:
* http://www.ietf.org/html.characters/idn-charter.html
* http://www.i-dns.net
*
* IDNA specification:
* http://search.ietf.org/internet-drafts/draft-ietf-idn-idna-06.txt
*/
[scriptable, uuid(a592a60e-3621-4f19-a318-2bf233cfad3e)]
interface nsIIDNService : nsISupports
{
/**
* Prepares the input hostname according to IDNA ToASCII operation,
* the input hostname is assumed to be UTF8-encoded.
*/
ACString convertUTF8toACE(in AUTF8String input);
/**
* This is the ToUnicode operation as specified in the IDNA proposal,
* with an additional step to encode the result in UTF-8.
* It takes an ACE-encoded hostname and performs ToUnicode to it, then
* encodes the resulting string into UTF8.
*/
AUTF8String convertACEtoUTF8(in ACString input);
/**
* Checks if the input string is ACE encoded or not.
*/
boolean isACE(in ACString input);
/**
* Performs the unicode normalization needed for hostnames in IDN,
* for callers that want early normalization.
*/
AUTF8String normalize(in AUTF8String input);
/**
* Normalizes and converts a host to UTF-8 if the host is in the IDN
* whitelist, otherwise converts it to ACE. This is useful for display
* purposes and to ensure an encoding consistent with nsIURI::GetHost().
* If the result is ASCII or ACE encoded, |isASCII| will be true.
*/
AUTF8String convertToDisplayIDN(in AUTF8String input, out boolean isASCII);
};

View file

@ -0,0 +1,34 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsIDNSService.idl"
/**
* This is a private interface used by the internals of the networking library.
* It will never be frozen. Do not use it in external code.
*/
[scriptable, uuid(24e598fd-7b1a-436c-9154-14d8b38df8a5)]
interface nsPIDNSService : nsIDNSService
{
/**
* called to initialize the DNS service.
*/
void init();
/**
* called to shutdown the DNS service. any pending asynchronous
* requests will be canceled, and the local cache of DNS records
* will be cleared. NOTE: the operating system may still have
* its own cache of DNS records, which would be unaffected by
* this method.
*/
void shutdown();
/**
* Whether or not DNS prefetching (aka RESOLVE_SPECULATE) is enabled
*/
attribute boolean prefetchEnabled;
};

121
netwerk/dns/prepare_tlds.py Normal file
View file

@ -0,0 +1,121 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
import codecs
import encodings.idna
import re
import sys
"""
Processes a file containing effective TLD data. See the following URL for a
description of effective TLDs and of the file format that this script
processes (although for the latter you're better off just reading this file's
short source code).
http://wiki.mozilla.org/Gecko:Effective_TLD_Service
"""
def getEffectiveTLDs(path):
file = codecs.open(path, "r", "UTF-8")
entries = []
domains = set()
for line in file:
# line always contains a line terminator unless the file is empty
if len(line) == 0:
raise StopIteration
line = line.rstrip()
# comment, empty, or superfluous line for explicitness purposes
if line.startswith("//") or "." not in line:
continue
line = re.split(r"[ \t\n]", line, 1)[0]
entry = EffectiveTLDEntry(line)
domain = entry.domain()
assert domain not in domains, \
"repeating domain %s makes no sense" % domain
domains.add(domain)
entries.append(entry)
# Sort the entries so we can use binary search on them.
entries.sort(key=EffectiveTLDEntry.domain)
return entries
def _normalizeHostname(domain):
"""
Normalizes the given domain, component by component. ASCII components are
lowercased, while non-ASCII components are processed using the ToASCII
algorithm.
"""
def convertLabel(label):
if _isASCII(label):
return label.lower()
return encodings.idna.ToASCII(label)
return ".".join(map(convertLabel, domain.split(".")))
def _isASCII(s):
"True if s consists entirely of ASCII characters, false otherwise."
for c in s:
if ord(c) > 127:
return False
return True
class EffectiveTLDEntry:
"""
Stores an entry in an effective-TLD name file.
"""
_exception = False
_wild = False
def __init__(self, line):
"""
Creates a TLD entry from a line of data, which must have been stripped of
the line ending.
"""
if line.startswith("!"):
self._exception = True
domain = line[1:]
elif line.startswith("*."):
self._wild = True
domain = line[2:]
else:
domain = line
self._domain = _normalizeHostname(domain)
def domain(self):
"The domain this represents."
return self._domain
def exception(self):
"True if this entry's domain denotes does not denote an effective TLD."
return self._exception
def wild(self):
"True if this entry represents a class of effective TLDs."
return self._wild
#################
# DO EVERYTHING #
#################
def main(output, effective_tld_filename):
"""
effective_tld_filename is the effective TLD file to parse.
A C++ array of { domain, exception, wild } entries representing the
eTLD file is then printed to output.
"""
def boolStr(b):
if b:
return "true"
return "false"
for etld in getEffectiveTLDs(effective_tld_filename):
exception = boolStr(etld.exception())
wild = boolStr(etld.wild())
output.write('ETLD_ENTRY("%s", %s, %s)\n' % (etld.domain(), exception, wild))
if __name__ == '__main__':
main(sys.stdout, sys.argv[1])

289
netwerk/dns/punycode.c Normal file
View file

@ -0,0 +1,289 @@
/*
punycode.c from RFC 3492
http://www.nicemice.net/idn/
Adam M. Costello
http://www.nicemice.net/amc/
This is ANSI C code (C89) implementing Punycode (RFC 3492).
C. Disclaimer and license
Regarding this entire document or any portion of it (including
the pseudocode and C code), the author makes no guarantees and
is not responsible for any damage resulting from its use. The
author grants irrevocable permission to anyone to use, modify,
and distribute it in any way that does not diminish the rights
of anyone else to use, modify, and distribute it, provided that
redistributed derivative works do not contain misleading author or
version information. Derivative works need not be licensed under
similar terms.
*/
#include "punycode.h"
/**********************************************************/
/* Implementation (would normally go in its own .c file): */
#include <string.h>
/*** Bootstring parameters for Punycode ***/
enum { base = 36, tmin = 1, tmax = 26, skew = 38, damp = 700,
initial_bias = 72, initial_n = 0x80, delimiter = 0x2D };
/* basic(cp) tests whether cp is a basic code point: */
#define basic(cp) ((punycode_uint)(cp) < 0x80)
/* delim(cp) tests whether cp is a delimiter: */
#define delim(cp) ((cp) == delimiter)
/* decode_digit(cp) returns the numeric value of a basic code */
/* point (for use in representing integers) in the range 0 to */
/* base-1, or base if cp is does not represent a value. */
static punycode_uint decode_digit(punycode_uint cp)
{
return cp - 48 < 10 ? cp - 22 : cp - 65 < 26 ? cp - 65 :
cp - 97 < 26 ? cp - 97 : base;
}
/* encode_digit(d,flag) returns the basic code point whose value */
/* (when used for representing integers) is d, which needs to be in */
/* the range 0 to base-1. The lowercase form is used unless flag is */
/* nonzero, in which case the uppercase form is used. The behavior */
/* is undefined if flag is nonzero and digit d has no uppercase form. */
static char encode_digit(punycode_uint d, int flag)
{
return d + 22 + 75 * (d < 26) - ((flag != 0) << 5);
/* 0..25 map to ASCII a..z or A..Z */
/* 26..35 map to ASCII 0..9 */
}
/* flagged(bcp) tests whether a basic code point is flagged */
/* (uppercase). The behavior is undefined if bcp is not a */
/* basic code point. */
#define flagged(bcp) ((punycode_uint)(bcp) - 65 < 26)
/* encode_basic(bcp,flag) forces a basic code point to lowercase */
/* if flag is zero, uppercase if flag is nonzero, and returns */
/* the resulting code point. The code point is unchanged if it */
/* is caseless. The behavior is undefined if bcp is not a basic */
/* code point. */
static char encode_basic(punycode_uint bcp, int flag)
{
bcp -= (bcp - 97 < 26) << 5;
return bcp + ((!flag && (bcp - 65 < 26)) << 5);
}
/*** Platform-specific constants ***/
/* maxint is the maximum value of a punycode_uint variable: */
static const punycode_uint maxint = (punycode_uint) -1;
/* Because maxint is unsigned, -1 becomes the maximum value. */
/*** Bias adaptation function ***/
static punycode_uint adapt(
punycode_uint delta, punycode_uint numpoints, int firsttime )
{
punycode_uint k;
delta = firsttime ? delta / damp : delta >> 1;
/* delta >> 1 is a faster way of doing delta / 2 */
delta += delta / numpoints;
for (k = 0; delta > ((base - tmin) * tmax) / 2; k += base) {
delta /= base - tmin;
}
return k + (base - tmin + 1) * delta / (delta + skew);
}
/*** Main encode function ***/
enum punycode_status punycode_encode(
punycode_uint input_length,
const punycode_uint input[],
const unsigned char case_flags[],
punycode_uint *output_length,
char output[] )
{
punycode_uint n, delta, h, b, out, max_out, bias, j, m, q, k, t;
/* Initialize the state: */
n = initial_n;
delta = out = 0;
max_out = *output_length;
bias = initial_bias;
/* Handle the basic code points: */
for (j = 0; j < input_length; ++j) {
if (basic(input[j])) {
if (max_out - out < 2) return punycode_big_output;
output[out++] =
case_flags ? encode_basic(input[j], case_flags[j]) : (char)input[j];
}
/* else if (input[j] < n) return punycode_bad_input; */
/* (not needed for Punycode with unsigned code points) */
}
h = b = out;
/* h is the number of code points that have been handled, b is the */
/* number of basic code points, and out is the number of characters */
/* that have been output. */
if (b > 0) output[out++] = delimiter;
/* Main encoding loop: */
while (h < input_length) {
/* All non-basic code points < n have been */
/* handled already. Find the next larger one: */
for (m = maxint, j = 0; j < input_length; ++j) {
/* if (basic(input[j])) continue; */
/* (not needed for Punycode) */
if (input[j] >= n && input[j] < m) m = input[j];
}
/* Increase delta enough to advance the decoder's */
/* <n,i> state to <m,0>, but guard against overflow: */
if (m - n > (maxint - delta) / (h + 1)) return punycode_overflow;
delta += (m - n) * (h + 1);
n = m;
for (j = 0; j < input_length; ++j) {
/* Punycode does not need to check whether input[j] is basic: */
if (input[j] < n /* || basic(input[j]) */ ) {
if (++delta == 0) return punycode_overflow;
}
if (input[j] == n) {
/* Represent delta as a generalized variable-length integer: */
for (q = delta, k = base; ; k += base) {
if (out >= max_out) return punycode_big_output;
t = k <= bias /* + tmin */ ? tmin : /* +tmin not needed */
k >= bias + tmax ? tmax : k - bias;
if (q < t) break;
output[out++] = encode_digit(t + (q - t) % (base - t), 0);
q = (q - t) / (base - t);
}
output[out++] = encode_digit(q, case_flags && case_flags[j]);
bias = adapt(delta, h + 1, h == b);
delta = 0;
++h;
}
}
++delta, ++n;
}
*output_length = out;
return punycode_success;
}
/*** Main decode function ***/
enum punycode_status punycode_decode(
punycode_uint input_length,
const char input[],
punycode_uint *output_length,
punycode_uint output[],
unsigned char case_flags[] )
{
punycode_uint n, out, i, max_out, bias,
b, j, in, oldi, w, k, digit, t;
if (!input_length) {
return punycode_bad_input;
}
/* Initialize the state: */
n = initial_n;
out = i = 0;
max_out = *output_length;
bias = initial_bias;
/* Handle the basic code points: Let b be the number of input code */
/* points before the last delimiter, or 0 if there is none, then */
/* copy the first b code points to the output. */
for (b = 0, j = input_length - 1 ; j > 0; --j) {
if (delim(input[j])) {
b = j;
break;
}
}
if (b > max_out) return punycode_big_output;
for (j = 0; j < b; ++j) {
if (case_flags) case_flags[out] = flagged(input[j]);
if (!basic(input[j])) return punycode_bad_input;
output[out++] = input[j];
}
/* Main decoding loop: Start just after the last delimiter if any */
/* basic code points were copied; start at the beginning otherwise. */
for (in = b > 0 ? b + 1 : 0; in < input_length; ++out) {
/* in is the index of the next character to be consumed, and */
/* out is the number of code points in the output array. */
/* Decode a generalized variable-length integer into delta, */
/* which gets added to i. The overflow checking is easier */
/* if we increase i as we go, then subtract off its starting */
/* value at the end to obtain delta. */
for (oldi = i, w = 1, k = base; ; k += base) {
if (in >= input_length) return punycode_bad_input;
digit = decode_digit(input[in++]);
if (digit >= base) return punycode_bad_input;
if (digit > (maxint - i) / w) return punycode_overflow;
i += digit * w;
t = k <= bias /* + tmin */ ? tmin : /* +tmin not needed */
k >= bias + tmax ? tmax : k - bias;
if (digit < t) break;
if (w > maxint / (base - t)) return punycode_overflow;
w *= (base - t);
}
bias = adapt(i - oldi, out + 1, oldi == 0);
/* i was supposed to wrap around from out+1 to 0, */
/* incrementing n each time, so we'll fix that now: */
if (i / (out + 1) > maxint - n) return punycode_overflow;
n += i / (out + 1);
i %= (out + 1);
/* Insert n at position i of the output: */
/* not needed for Punycode: */
/* if (decode_digit(n) <= base) return punycode_invalid_input; */
if (out >= max_out) return punycode_big_output;
if (case_flags) {
memmove(case_flags + i + 1, case_flags + i, out - i);
/* Case of last character determines uppercase flag: */
case_flags[i] = flagged(input[in - 1]);
}
memmove(output + i + 1, output + i, (out - i) * sizeof *output);
output[i++] = n;
}
*output_length = out;
return punycode_success;
}

108
netwerk/dns/punycode.h Normal file
View file

@ -0,0 +1,108 @@
/*
punycode.c from RFC 3492
http://www.nicemice.net/idn/
Adam M. Costello
http://www.nicemice.net/amc/
This is ANSI C code (C89) implementing Punycode (RFC 3492).
C. Disclaimer and license
Regarding this entire document or any portion of it (including
the pseudocode and C code), the author makes no guarantees and
is not responsible for any damage resulting from its use. The
author grants irrevocable permission to anyone to use, modify,
and distribute it in any way that does not diminish the rights
of anyone else to use, modify, and distribute it, provided that
redistributed derivative works do not contain misleading author or
version information. Derivative works need not be licensed under
similar terms.
*/
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
/************************************************************/
/* Public interface (would normally go in its own .h file): */
#include <limits.h>
enum punycode_status {
punycode_success,
punycode_bad_input, /* Input is invalid. */
punycode_big_output, /* Output would exceed the space provided. */
punycode_overflow /* Input needs wider integers to process. */
};
#if UINT_MAX >= (1 << 26) - 1
typedef unsigned int punycode_uint;
#else
typedef unsigned long punycode_uint;
#endif
enum punycode_status punycode_encode(
punycode_uint input_length,
const punycode_uint input[],
const unsigned char case_flags[],
punycode_uint *output_length,
char output[] );
/* punycode_encode() converts Unicode to Punycode. The input */
/* is represented as an array of Unicode code points (not code */
/* units; surrogate pairs are not allowed), and the output */
/* will be represented as an array of ASCII code points. The */
/* output string is *not* null-terminated; it will contain */
/* zeros if and only if the input contains zeros. (Of course */
/* the caller can leave room for a terminator and add one if */
/* needed.) The input_length is the number of code points in */
/* the input. The output_length is an in/out argument: the */
/* caller passes in the maximum number of code points that it */
/* can receive, and on successful return it will contain the */
/* number of code points actually output. The case_flags array */
/* holds input_length boolean values, where nonzero suggests that */
/* the corresponding Unicode character be forced to uppercase */
/* after being decoded (if possible), and zero suggests that */
/* it be forced to lowercase (if possible). ASCII code points */
/* are encoded literally, except that ASCII letters are forced */
/* to uppercase or lowercase according to the corresponding */
/* uppercase flags. If case_flags is a null pointer then ASCII */
/* letters are left as they are, and other code points are */
/* treated as if their uppercase flags were zero. The return */
/* value can be any of the punycode_status values defined above */
/* except punycode_bad_input; if not punycode_success, then */
/* output_size and output might contain garbage. */
enum punycode_status punycode_decode(
punycode_uint input_length,
const char input[],
punycode_uint *output_length,
punycode_uint output[],
unsigned char case_flags[] );
/* punycode_decode() converts Punycode to Unicode. The input is */
/* represented as an array of ASCII code points, and the output */
/* will be represented as an array of Unicode code points. The */
/* input_length is the number of code points in the input. The */
/* output_length is an in/out argument: the caller passes in */
/* the maximum number of code points that it can receive, and */
/* on successful return it will contain the actual number of */
/* code points output. The case_flags array needs room for at */
/* least output_length values, or it can be a null pointer if the */
/* case information is not needed. A nonzero flag suggests that */
/* the corresponding Unicode character be forced to uppercase */
/* by the caller (if possible), while zero suggests that it be */
/* forced to lowercase (if possible). ASCII code points are */
/* output already in the proper case, but their flags will be set */
/* appropriately so that applying the flags would be harmless. */
/* The return value can be any of the punycode_status values */
/* defined above; if not punycode_success, then output_length, */
/* output, and case_flags might contain garbage. On success, the */
/* decoder will never need to write an output_length greater than */
/* input_length, because of how the encoding is defined. */
#ifdef __cplusplus
}
#endif /* __cplusplus */