RDAP: Display truncation notice for large domain result sets

The ICAAN Operational Profile dictates that a notice be added to the RDAP search results response when there are more objects than the server's chosen result set size. This CL handles the fixes for domain searches.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=135407203
This commit is contained in:
mountford 2016-10-06 14:43:46 -07:00 committed by Ben McIlwain
parent 5a4926323e
commit 179bd22531
6 changed files with 729 additions and 83 deletions

View file

@ -17,6 +17,7 @@ package google.registry.rdap;
import static google.registry.model.EppResourceUtils.loadByForeignKey;
import static google.registry.model.index.ForeignKeyIndex.loadAndGetKey;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.rdap.RdapIcannStandardInformation.TRUNCATION_NOTICES;
import static google.registry.request.Action.Method.GET;
import static google.registry.request.Action.Method.HEAD;
import static google.registry.util.DateTimeUtils.END_OF_TIME;
@ -63,8 +64,7 @@ public class RdapDomainSearchAction extends RdapActionBase {
public static final String PATH = "/rdap/domains";
public static final int CHUNK_SIZE_SCALING_FACTOR = 5;
public static final int MAX_CHUNK_FETCHES = 20;
public static final int RESULT_SET_SIZE_SCALING_FACTOR = 30;
@Inject Clock clock;
@Inject @Parameter("name") Optional<String> nameParam;
@ -98,7 +98,7 @@ public class RdapDomainSearchAction extends RdapActionBase {
throw new BadRequestException(
"You must specify either name=XXXX, nsLdhName=YYYY or nsIp=ZZZZ");
}
ImmutableList<ImmutableMap<String, Object>> results;
RdapSearchResults results;
if (nameParam.isPresent()) {
// syntax: /rdap/domains?name=exam*.com
String asciiName;
@ -121,86 +121,65 @@ public class RdapDomainSearchAction extends RdapActionBase {
// syntax: /rdap/domains?nsIp=1.2.3.4
results = searchByNameserverIp(nsIpParam.get(), now);
}
if (results.isEmpty()) {
if (results.jsonList().isEmpty()) {
throw new NotFoundException("No domains found");
}
ImmutableMap.Builder<String, Object> builder = new ImmutableMap.Builder<>();
builder.put("domainSearchResults", results);
builder.put("domainSearchResults", results.jsonList());
RdapJsonFormatter.addTopLevelEntries(
builder,
BoilerplateType.DOMAIN,
ImmutableList.<ImmutableMap<String, Object>>of(),
results.isTruncated()
? TRUNCATION_NOTICES : ImmutableList.<ImmutableMap<String, Object>>of(),
ImmutableList.<ImmutableMap<String, Object>>of(),
rdapLinkBase);
return builder.build();
}
/** Searches for domains by domain name, returning a JSON array of domain info maps. */
private ImmutableList<ImmutableMap<String, Object>>
searchByDomainName(final RdapSearchPattern partialStringQuery, final DateTime now) {
private RdapSearchResults searchByDomainName(
final RdapSearchPattern partialStringQuery, final DateTime now) {
// Handle queries without a wildcard -- just load by foreign key.
if (!partialStringQuery.getHasWildcard()) {
DomainResource domainResource =
loadByForeignKey(DomainResource.class, partialStringQuery.getInitialString(), now);
if (domainResource == null) {
return ImmutableList.of();
}
return makeSearchResults(ImmutableList.of(domainResource), now);
return makeSearchResults(
(domainResource == null) ? ImmutableList.of() : ImmutableList.of(domainResource),
false,
now);
// Handle queries with a wildcard.
} else {
// We can't query for undeleted domains as part of the query itself; that would require an
// inequality query on deletion time, and we are already using inequality queries on
// fullyQualifiedDomainName. So we need another way to limit the result set to the desired
// number of undeleted domains, which we do as follows. We query a batch of domains up to five
// times the size of the result set size limit (a factor picked out of thin air), and weed out
// all deleted domains. If we still have space in the result set (because there were an
// incredibly large number of deleted domains), we go back and query some more domains to try
// and find more results. We try this 20 times (meaning we search for 100 times as many
// domains as the result set size limit), then give up and return a result set that is smaller
// than the limit. Ugly? You bet!
// TODO(b/31546493): Add metrics to figure out how well this algorithm works.
// fullyQualifiedDomainName. So we instead pick an arbitrary limit of
// RESULT_SET_SIZE_SCALING_FACTOR times the result set size limit, fetch up to that many, and
// weed out all deleted domains. If there still isn't a full result set's worth of domains, we
// give up and return just the ones we found.
// TODO(b/31546493): Add metrics to figure out how well this.
List<DomainResource> domainList = new ArrayList<>();
String previousChunkEndString = null;
for (int numChunkFetches = 0;
(numChunkFetches < MAX_CHUNK_FETCHES) && (domainList.size() < rdapResultSetMaxSize);
numChunkFetches++) {
// Construct the query.
Query<DomainResource> query = ofy().load()
.type(DomainResource.class)
.filter("fullyQualifiedDomainName <", partialStringQuery.getNextInitialString());
if (previousChunkEndString == null) {
query = query.filter(
"fullyQualifiedDomainName >=", partialStringQuery.getInitialString());
} else {
query = query.filter("fullyQualifiedDomainName >", previousChunkEndString);
}
.filter("fullyQualifiedDomainName <", partialStringQuery.getNextInitialString())
.filter("fullyQualifiedDomainName >=", partialStringQuery.getInitialString());
if (partialStringQuery.getSuffix() != null) {
query = query.filter("tld", partialStringQuery.getSuffix());
}
// Perform the query and weed out deleted domains.
previousChunkEndString = null;
int numDomainsInChunk = 0;
// TODO(mountford): Investigate fetching by foreign key instead of the domain itself.
for (DomainResource domain :
query.limit(rdapResultSetMaxSize * CHUNK_SIZE_SCALING_FACTOR)) {
previousChunkEndString = domain.getFullyQualifiedDomainName();
numDomainsInChunk++;
query.limit(RESULT_SET_SIZE_SCALING_FACTOR * rdapResultSetMaxSize)) {
if (EppResourceUtils.isActive(domain, now)) {
domainList.add(domain);
if (domainList.size() >= rdapResultSetMaxSize) {
break;
return makeSearchResults(ImmutableList.copyOf(domainList), true, now);
}
domainList.add(domain);
}
}
}
if (numDomainsInChunk < rdapResultSetMaxSize * CHUNK_SIZE_SCALING_FACTOR) {
break;
}
}
return makeSearchResults(domainList, now);
return makeSearchResults(ImmutableList.copyOf(domainList), false, now);
}
}
/** Searches for domains by nameserver name, returning a JSON array of domain info maps. */
private ImmutableList<ImmutableMap<String, Object>> searchByNameserverLdhName(
private RdapSearchResults searchByNameserverLdhName(
final RdapSearchPattern partialStringQuery, final DateTime now) {
Iterable<Key<HostResource>> hostKeys = getNameserverRefsByLdhName(partialStringQuery, now);
if (Iterables.isEmpty(hostKeys)) {
@ -210,8 +189,8 @@ public class RdapDomainSearchAction extends RdapActionBase {
}
/** Assembles a list of {@link HostResource} keys by name. */
private Iterable<Key<HostResource>>
getNameserverRefsByLdhName(final RdapSearchPattern partialStringQuery, final DateTime now) {
private Iterable<Key<HostResource>> getNameserverRefsByLdhName(
final RdapSearchPattern partialStringQuery, final DateTime now) {
// Handle queries without a wildcard; just load the host by foreign key in the usual way.
if (!partialStringQuery.getHasWildcard()) {
Key<HostResource> hostKey = loadAndGetKey(
@ -255,8 +234,8 @@ public class RdapDomainSearchAction extends RdapActionBase {
}
/** Searches for domains by nameserver address, returning a JSON array of domain info maps. */
private ImmutableList<ImmutableMap<String, Object>>
searchByNameserverIp(final InetAddress inetAddress, final DateTime now) {
private RdapSearchResults searchByNameserverIp(
final InetAddress inetAddress, final DateTime now) {
// In theory, we could filter on the deletion time being in the future. But we can't do that in
// the query on nameserver name (because we're already using an inequality query), and it seems
// dangerous and confusing to filter on deletion time differently between the two queries.
@ -274,36 +253,44 @@ public class RdapDomainSearchAction extends RdapActionBase {
}
/**
* Locates all domains which are linked to a set of host keys. This method is called by
* {@link #searchByNameserverLdhName} and {@link #searchByNameserverIp} after they assemble the
* relevant host keys.
* Locates all domains which are linked to a set of host keys.
*
* <p>This method is called by {@link #searchByNameserverLdhName} and
* {@link #searchByNameserverIp} after they assemble the relevant host keys.
*/
private ImmutableList<ImmutableMap<String, Object>>
searchByNameserverRefs(final Iterable<Key<HostResource>> hostKeys, final DateTime now) {
private RdapSearchResults searchByNameserverRefs(
final Iterable<Key<HostResource>> hostKeys, final DateTime now) {
// We must break the query up into chunks, because the in operator is limited to 30 subqueries.
// Since it is possible for the same domain to show up more than once in our result list (if
// we do a wildcard nameserver search that returns multiple nameservers used by the same
// domain), we must create a set of resulting {@link DomainResource} objects. But we use a
// LinkedHashSet to preserve the order in which we found the domains.
LinkedHashSet<DomainResource> domainResources = new LinkedHashSet<>();
LinkedHashSet<DomainResource> domains = new LinkedHashSet<>();
for (List<Key<HostResource>> chunk : Iterables.partition(hostKeys, 30)) {
domainResources.addAll(
ofy().load()
for (DomainResource domain : ofy().load()
.type(DomainResource.class)
.filter("nameservers.linked in", chunk)
.filter("deletionTime >", now)
.limit(rdapResultSetMaxSize - domainResources.size())
.list());
if (domainResources.size() >= rdapResultSetMaxSize) {
break;
.limit(rdapResultSetMaxSize + 1)) {
if (!domains.contains(domain)) {
if (domains.size() >= rdapResultSetMaxSize) {
return makeSearchResults(ImmutableList.copyOf(domains), true, now);
}
domains.add(domain);
}
}
return makeSearchResults(ImmutableList.copyOf(domainResources), now);
}
return makeSearchResults(ImmutableList.copyOf(domains), false, now);
}
/** Output JSON for a list of domains. */
private ImmutableList<ImmutableMap<String, Object>> makeSearchResults(
List<DomainResource> domains, DateTime now) {
/**
* Output JSON for a list of domains.
*
* <p>The isTruncated parameter should be true if the search found more results than are in the
* list, meaning that the truncation notice should be added.
*/
private RdapSearchResults makeSearchResults(
ImmutableList<DomainResource> domains, boolean isTruncated, DateTime now) {
OutputDataType outputDataType =
(domains.size() > 1) ? OutputDataType.SUMMARY : OutputDataType.FULL;
ImmutableList.Builder<ImmutableMap<String, Object>> jsonBuilder = new ImmutableList.Builder<>();
@ -312,6 +299,6 @@ public class RdapDomainSearchAction extends RdapActionBase {
RdapJsonFormatter.makeRdapJsonForDomain(
domain, false, rdapLinkBase, rdapWhoisServer, now, outputDataType));
}
return jsonBuilder.build();
return RdapSearchResults.create(jsonBuilder.build(), isTruncated);
}
}

View file

@ -20,7 +20,7 @@ import com.google.common.collect.ImmutableMap;
/**
* This file contains boilerplate required by the ICANN RDAP Profile.
*
* @see "https://whois.icann.org/sites/default/files/files/gtld-rdap-operational-profile-draft-03dec15-en.pdf"
* @see "https://www.icann.org/resources/pages/rdap-operational-profile-2016-07-26-en"
*/
public class RdapIcannStandardInformation {
@ -72,7 +72,7 @@ public class RdapIcannStandardInformation {
static final ImmutableList<ImmutableMap<String, Object>> nameserverAndEntityBoilerplateRemarks =
ImmutableList.of(CONFORMANCE_REMARK);
/** Required by ICANN RDAP Profile section 1.1.18. */
/** Required by ICANN RDAP Profile section 1.4.8 / 1.4.9, as interpreted. */
static final ImmutableMap<String, Object> SUMMARY_DATA_REMARK =
ImmutableMap.<String, Object> of(
"title",
@ -82,4 +82,20 @@ public class RdapIcannStandardInformation {
"Summary data only. For complete data, send a specific query for the object."),
"type",
"object truncated due to unexplainable reasons");
/** Required by ICANN RDAP Profile section 1.4.8 / 1.4.9, as interpreted. */
static final ImmutableMap<String, Object> TRUNCATED_RESULT_SET_NOTICE =
ImmutableMap.<String, Object> of(
"title",
"Search Policy",
"description",
ImmutableList.of("Search results per query are limited."),
"type",
"result set truncated due to unexplainable reasons");
/** Truncation notice as a singleton list, for easy use. */
static final ImmutableList<ImmutableMap<String, Object>> TRUNCATION_NOTICES =
ImmutableList.of(TRUNCATED_RESULT_SET_NOTICE);
}

View file

@ -0,0 +1,44 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.rdap;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
/**
* Holds domain, nameserver and entity search results.
*
* <p>We need to know not only the list of things we found, but also whether the result set was
* truncated to the limit. If it is, we must add the ICANN-mandated notice to that effect.
*/
@AutoValue
abstract class RdapSearchResults {
static RdapSearchResults create(ImmutableList<ImmutableMap<String, Object>> jsonList) {
return create(jsonList, false);
}
static RdapSearchResults create(
ImmutableList<ImmutableMap<String, Object>> jsonList, boolean isTruncated) {
return new AutoValue_RdapSearchResults(jsonList, isTruncated);
}
/** List of JSON result object representations. */
abstract ImmutableList<ImmutableMap<String, Object>> jsonList();
/** True if the result set was truncated to the maximum size limit. */
abstract boolean isTruncated();
}

View file

@ -552,6 +552,31 @@ public class RdapDomainSearchActionTest {
persistResources(domainsBuilder.build());
}
private Object readMultiDomainFile(
String fileName,
String domainName1,
String domainHandle1,
String domainName2,
String domainHandle2,
String domainName3,
String domainHandle3,
String domainName4,
String domainHandle4) {
return JSONValue.parse(loadFileWithSubstitutions(
this.getClass(),
fileName,
new ImmutableMap.Builder<String, String>()
.put("DOMAINNAME1", domainName1)
.put("DOMAINHANDLE1", domainHandle1)
.put("DOMAINNAME2", domainName2)
.put("DOMAINHANDLE2", domainHandle2)
.put("DOMAINNAME3", domainName3)
.put("DOMAINHANDLE3", domainHandle3)
.put("DOMAINNAME4", domainName4)
.put("DOMAINHANDLE4", domainHandle4)
.build()));
}
private void checkNumberOfDomainsInResult(Object obj, int expected) {
assertThat(obj).isInstanceOf(Map.class);
@ -577,7 +602,7 @@ public class RdapDomainSearchActionTest {
public void testDomainMatch_manyDeletedDomains_partialResultSetDueToInsufficientDomains()
throws Exception {
// There are not enough domains to fill a full result set.
createManyDomainsAndHosts(3, 100, 2);
createManyDomainsAndHosts(3, 20, 2);
Object obj = generateActualJson(RequestType.NAME, "domain*.lol");
assertThat(response.getStatus()).isEqualTo(200);
checkNumberOfDomainsInResult(obj, 3);
@ -589,12 +614,82 @@ public class RdapDomainSearchActionTest {
// This is not exactly desired behavior, but expected: There are enough domains to fill a full
// result set, but there are so many deleted domains that we run out of patience before we work
// our way through all of them.
createManyDomainsAndHosts(4, 150, 2);
createManyDomainsAndHosts(4, 50, 2);
Object obj = generateActualJson(RequestType.NAME, "domain*.lol");
assertThat(response.getStatus()).isEqualTo(200);
checkNumberOfDomainsInResult(obj, 3);
}
@Test
public void testDomainMatch_nontruncatedResultsSet() throws Exception {
createManyDomainsAndHosts(4, 1, 2);
assertThat(generateActualJson(RequestType.NAME, "domain*.lol"))
.isEqualTo(readMultiDomainFile(
"rdap_nontruncated_domains.json",
"domain1.lol",
"41-LOL",
"domain2.lol",
"42-LOL",
"domain3.lol",
"43-LOL",
"domain4.lol",
"44-LOL"));
assertThat(response.getStatus()).isEqualTo(200);
}
@Test
public void testDomainMatch_truncatedResultsSet() throws Exception {
createManyDomainsAndHosts(5, 1, 2);
assertThat(generateActualJson(RequestType.NAME, "domain*.lol"))
.isEqualTo(readMultiDomainFile(
"rdap_truncated_domains.json",
"domain1.lol",
"41-LOL",
"domain2.lol",
"42-LOL",
"domain3.lol",
"43-LOL",
"domain4.lol",
"44-LOL"));
assertThat(response.getStatus()).isEqualTo(200);
}
@Test
public void testDomainMatch_reallyTruncatedResultsSet() throws Exception {
// Don't use 10 or more domains for this test, because domain10.lol will come before
// domain2.lol, and you'll get the wrong domains in the result set.
createManyDomainsAndHosts(9, 1, 2);
assertThat(generateActualJson(RequestType.NAME, "domain*.lol"))
.isEqualTo(readMultiDomainFile(
"rdap_truncated_domains.json",
"domain1.lol",
"41-LOL",
"domain2.lol",
"42-LOL",
"domain3.lol",
"43-LOL",
"domain4.lol",
"44-LOL"));
assertThat(response.getStatus()).isEqualTo(200);
}
@Test
public void testDomainMatch_truncatedResultsAfterMultipleChunks() throws Exception {
createManyDomainsAndHosts(5, 6, 2);
assertThat(generateActualJson(RequestType.NAME, "domain*.lol"))
.isEqualTo(readMultiDomainFile(
"rdap_truncated_domains.json",
"domain12.lol",
"4C-LOL",
"domain18.lol",
"52-LOL",
"domain24.lol",
"58-LOL",
"domain30.lol",
"5E-LOL"));
assertThat(response.getStatus()).isEqualTo(200);
}
@Test
public void testNameserverMatch_foundMultiple() throws Exception {
assertThat(generateActualJson(RequestType.NS_LDH_NAME, "ns1.cat.lol"))
@ -766,6 +861,77 @@ public class RdapDomainSearchActionTest {
checkNumberOfDomainsInResult(obj, 3);
}
@Test
public void testNameserverMatch_nontruncatedResultsSet() throws Exception {
createManyDomainsAndHosts(4, 1, 2);
assertThat(generateActualJson(RequestType.NS_LDH_NAME, "ns1.domain1.lol"))
.isEqualTo(readMultiDomainFile(
"rdap_nontruncated_domains.json",
"domain1.lol",
"41-LOL",
"domain2.lol",
"42-LOL",
"domain3.lol",
"43-LOL",
"domain4.lol",
"44-LOL"));
assertThat(response.getStatus()).isEqualTo(200);
}
@Test
public void testNameserverMatch_truncatedResultsSet() throws Exception {
createManyDomainsAndHosts(5, 1, 2);
assertThat(generateActualJson(RequestType.NS_LDH_NAME, "ns1.domain1.lol"))
.isEqualTo(readMultiDomainFile(
"rdap_truncated_domains.json",
"domain1.lol",
"41-LOL",
"domain2.lol",
"42-LOL",
"domain3.lol",
"43-LOL",
"domain4.lol",
"44-LOL"));
assertThat(response.getStatus()).isEqualTo(200);
}
@Test
public void testNameserverMatch_reallyTruncatedResultsSet() throws Exception {
createManyDomainsAndHosts(9, 1, 2);
assertThat(generateActualJson(RequestType.NS_LDH_NAME, "ns1.domain1.lol"))
.isEqualTo(readMultiDomainFile(
"rdap_truncated_domains.json",
"domain1.lol",
"41-LOL",
"domain2.lol",
"42-LOL",
"domain3.lol",
"43-LOL",
"domain4.lol",
"44-LOL"));
assertThat(response.getStatus()).isEqualTo(200);
}
@Test
public void testNameserverMatch_duplicatesNotTruncated() throws Exception {
// 60 nameservers for each of 4 domains; these should translate into 2 30-nameserver domain
// fetches, which should _not_ trigger the truncation warning because all the domains will be
// duplicates.
createManyDomainsAndHosts(4, 1, 60);
assertThat(generateActualJson(RequestType.NS_LDH_NAME, "ns*.domain1.lol"))
.isEqualTo(readMultiDomainFile(
"rdap_nontruncated_domains.json",
"domain1.lol",
"B5-LOL",
"domain2.lol",
"B6-LOL",
"domain3.lol",
"B7-LOL",
"domain4.lol",
"B8-LOL"));
assertThat(response.getStatus()).isEqualTo(200);
}
@Test
public void testAddressMatchV4Address_foundMultiple() throws Exception {
assertThat(generateActualJson(RequestType.NS_IP, "1.2.3.4"))
@ -821,4 +987,55 @@ public class RdapDomainSearchActionTest {
.isEqualTo(generateExpectedJson("No domains found", null, null, "rdap_error_404.json"));
assertThat(response.getStatus()).isEqualTo(404);
}
@Test
public void testAddressMatch_nontruncatedResultsSet() throws Exception {
createManyDomainsAndHosts(4, 1, 2);
assertThat(generateActualJson(RequestType.NS_IP, "5.5.5.1"))
.isEqualTo(readMultiDomainFile(
"rdap_nontruncated_domains.json",
"domain1.lol",
"41-LOL",
"domain2.lol",
"42-LOL",
"domain3.lol",
"43-LOL",
"domain4.lol",
"44-LOL"));
assertThat(response.getStatus()).isEqualTo(200);
}
@Test
public void testAddressMatch_truncatedResultsSet() throws Exception {
createManyDomainsAndHosts(5, 1, 2);
assertThat(generateActualJson(RequestType.NS_IP, "5.5.5.1"))
.isEqualTo(readMultiDomainFile(
"rdap_truncated_domains.json",
"domain1.lol",
"41-LOL",
"domain2.lol",
"42-LOL",
"domain3.lol",
"43-LOL",
"domain4.lol",
"44-LOL"));
assertThat(response.getStatus()).isEqualTo(200);
}
@Test
public void testAddressMatch_reallyTruncatedResultsSet() throws Exception {
createManyDomainsAndHosts(9, 1, 2);
assertThat(generateActualJson(RequestType.NS_IP, "5.5.5.1"))
.isEqualTo(readMultiDomainFile(
"rdap_truncated_domains.json",
"domain1.lol",
"41-LOL",
"domain2.lol",
"42-LOL",
"domain3.lol",
"43-LOL",
"domain4.lol",
"44-LOL"));
assertThat(response.getStatus()).isEqualTo(200);
}
}

View file

@ -0,0 +1,187 @@
{
"domainSearchResults": [
{
"status": [
"client delete prohibited",
"client renew prohibited",
"client transfer prohibited",
"server update prohibited"
],
"handle": "%DOMAINHANDLE1%",
"links": [
{
"href": "https://example.com/rdap/domain/%DOMAINNAME1%",
"type": "application/rdap+json",
"rel": "self",
"value": "https://example.com/rdap/domain/%DOMAINNAME1%"
}
],
"ldhName": "%DOMAINNAME1%",
"objectClassName": "domain",
"remarks": [
{
"title": "Incomplete Data",
"description": [
"Summary data only. For complete data, send a specific query for the object."
],
"type": "object truncated due to unexplainable reasons"
}
]
},
{
"status": [
"client delete prohibited",
"client renew prohibited",
"client transfer prohibited",
"server update prohibited"
],
"handle": "%DOMAINHANDLE2%",
"links": [
{
"href": "https://example.com/rdap/domain/%DOMAINNAME2%",
"type": "application/rdap+json",
"rel": "self",
"value": "https://example.com/rdap/domain/%DOMAINNAME2%"
}
],
"ldhName": "%DOMAINNAME2%",
"objectClassName": "domain",
"remarks": [
{
"title": "Incomplete Data",
"description": [
"Summary data only. For complete data, send a specific query for the object."
],
"type": "object truncated due to unexplainable reasons"
}
]
},
{
"status": [
"client delete prohibited",
"client renew prohibited",
"client transfer prohibited",
"server update prohibited"
],
"handle": "%DOMAINHANDLE3%",
"links": [
{
"href": "https://example.com/rdap/domain/%DOMAINNAME3%",
"type": "application/rdap+json",
"rel": "self",
"value": "https://example.com/rdap/domain/%DOMAINNAME3%"
}
],
"ldhName": "%DOMAINNAME3%",
"objectClassName": "domain",
"remarks": [
{
"title": "Incomplete Data",
"description": [
"Summary data only. For complete data, send a specific query for the object."
],
"type": "object truncated due to unexplainable reasons"
}
]
},
{
"status": [
"client delete prohibited",
"client renew prohibited",
"client transfer prohibited",
"server update prohibited"
],
"handle": "%DOMAINHANDLE4%",
"links": [
{
"href": "https://example.com/rdap/domain/%DOMAINNAME4%",
"type": "application/rdap+json",
"rel": "self",
"value": "https://example.com/rdap/domain/%DOMAINNAME4%"
}
],
"ldhName": "%DOMAINNAME4%",
"objectClassName": "domain",
"remarks": [
{
"title": "Incomplete Data",
"description": [
"Summary data only. For complete data, send a specific query for the object."
],
"type": "object truncated due to unexplainable reasons"
}
]
}
],
"rdapConformance": [
"rdap_level_0"
],
"notices" :
[
{
"title" : "RDAP Terms of Service",
"description" :
[
"By querying our Domain Database, you are agreeing to comply with these terms so please read them carefully.",
"Any information provided is 'as is' without any guarantee of accuracy.",
"Please do not misuse the Domain Database. It is intended solely for query-based access.",
"Don't use the Domain Database to allow, enable, or otherwise support the transmission of mass unsolicited, commercial advertising or solicitations.",
"Don't access our Domain Database through the use of high volume, automated electronic processes that send queries or data to the systems of Charleston Road Registry or any ICANN-accredited registrar.",
"You may only use the information contained in the Domain Database for lawful purposes.",
"Do not compile, repackage, disseminate, or otherwise use the information contained in the Domain Database in its entirety, or in any substantial portion, without our prior written permission.",
"We may retain certain details about queries to our Domain Database for the purposes of detecting and preventing misuse.",
"We reserve the right to restrict or deny your access to the database if we suspect that you have failed to comply with these terms.",
"We reserve the right to modify this agreement at any time."
],
"links" :
[
{
"value" : "https://example.com/rdap/help/tos",
"rel" : "alternate",
"href" : "https://www.registry.google/about/rdap/tos.html",
"type" : "text/html"
}
]
}
],
"remarks" :
[
{
"description" :
[
"This response conforms to the RDAP Operational Profile for gTLD Registries and Registrars version 1.0"
]
},
{
"title" : "EPP Status Codes",
"description" :
[
"For more information on domain status codes, please visit https://icann.org/epp"
],
"links" :
[
{
"value" : "https://icann.org/epp",
"rel" : "alternate",
"href" : "https://icann.org/epp",
"type" : "text/html"
}
]
},
{
"description" :
[
"URL of the ICANN Whois Inaccuracy Complaint Form: https://www.icann.org/wicf"
],
"links" :
[
{
"value" : "https://www.icann.org/wicf",
"rel" : "alternate",
"href" : "https://www.icann.org/wicf",
"type" : "text/html"
}
]
}
]
}

View file

@ -0,0 +1,195 @@
{
"domainSearchResults": [
{
"status": [
"client delete prohibited",
"client renew prohibited",
"client transfer prohibited",
"server update prohibited"
],
"handle": "%DOMAINHANDLE1%",
"links": [
{
"href": "https://example.com/rdap/domain/%DOMAINNAME1%",
"type": "application/rdap+json",
"rel": "self",
"value": "https://example.com/rdap/domain/%DOMAINNAME1%"
}
],
"ldhName": "%DOMAINNAME1%",
"objectClassName": "domain",
"remarks": [
{
"title": "Incomplete Data",
"description": [
"Summary data only. For complete data, send a specific query for the object."
],
"type": "object truncated due to unexplainable reasons"
}
]
},
{
"status": [
"client delete prohibited",
"client renew prohibited",
"client transfer prohibited",
"server update prohibited"
],
"handle": "%DOMAINHANDLE2%",
"links": [
{
"href": "https://example.com/rdap/domain/%DOMAINNAME2%",
"type": "application/rdap+json",
"rel": "self",
"value": "https://example.com/rdap/domain/%DOMAINNAME2%"
}
],
"ldhName": "%DOMAINNAME2%",
"objectClassName": "domain",
"remarks": [
{
"title": "Incomplete Data",
"description": [
"Summary data only. For complete data, send a specific query for the object."
],
"type": "object truncated due to unexplainable reasons"
}
]
},
{
"status": [
"client delete prohibited",
"client renew prohibited",
"client transfer prohibited",
"server update prohibited"
],
"handle": "%DOMAINHANDLE3%",
"links": [
{
"href": "https://example.com/rdap/domain/%DOMAINNAME3%",
"type": "application/rdap+json",
"rel": "self",
"value": "https://example.com/rdap/domain/%DOMAINNAME3%"
}
],
"ldhName": "%DOMAINNAME3%",
"objectClassName": "domain",
"remarks": [
{
"title": "Incomplete Data",
"description": [
"Summary data only. For complete data, send a specific query for the object."
],
"type": "object truncated due to unexplainable reasons"
}
]
},
{
"status": [
"client delete prohibited",
"client renew prohibited",
"client transfer prohibited",
"server update prohibited"
],
"handle": "%DOMAINHANDLE4%",
"links": [
{
"href": "https://example.com/rdap/domain/%DOMAINNAME4%",
"type": "application/rdap+json",
"rel": "self",
"value": "https://example.com/rdap/domain/%DOMAINNAME4%"
}
],
"ldhName": "%DOMAINNAME4%",
"objectClassName": "domain",
"remarks": [
{
"title": "Incomplete Data",
"description": [
"Summary data only. For complete data, send a specific query for the object."
],
"type": "object truncated due to unexplainable reasons"
}
]
}
],
"rdapConformance": [
"rdap_level_0"
],
"notices" :
[
{
"title" : "Search Policy",
"type" : "result set truncated due to unexplainable reasons",
"description" :
[
"Search results per query are limited."
]
},
{
"title" : "RDAP Terms of Service",
"description" :
[
"By querying our Domain Database, you are agreeing to comply with these terms so please read them carefully.",
"Any information provided is 'as is' without any guarantee of accuracy.",
"Please do not misuse the Domain Database. It is intended solely for query-based access.",
"Don't use the Domain Database to allow, enable, or otherwise support the transmission of mass unsolicited, commercial advertising or solicitations.",
"Don't access our Domain Database through the use of high volume, automated electronic processes that send queries or data to the systems of Charleston Road Registry or any ICANN-accredited registrar.",
"You may only use the information contained in the Domain Database for lawful purposes.",
"Do not compile, repackage, disseminate, or otherwise use the information contained in the Domain Database in its entirety, or in any substantial portion, without our prior written permission.",
"We may retain certain details about queries to our Domain Database for the purposes of detecting and preventing misuse.",
"We reserve the right to restrict or deny your access to the database if we suspect that you have failed to comply with these terms.",
"We reserve the right to modify this agreement at any time."
],
"links" :
[
{
"value" : "https://example.com/rdap/help/tos",
"rel" : "alternate",
"href" : "https://www.registry.google/about/rdap/tos.html",
"type" : "text/html"
}
]
}
],
"remarks" :
[
{
"description" :
[
"This response conforms to the RDAP Operational Profile for gTLD Registries and Registrars version 1.0"
]
},
{
"title" : "EPP Status Codes",
"description" :
[
"For more information on domain status codes, please visit https://icann.org/epp"
],
"links" :
[
{
"value" : "https://icann.org/epp",
"rel" : "alternate",
"href" : "https://icann.org/epp",
"type" : "text/html"
}
]
},
{
"description" :
[
"URL of the ICANN Whois Inaccuracy Complaint Form: https://www.icann.org/wicf"
],
"links" :
[
{
"value" : "https://www.icann.org/wicf",
"rel" : "alternate",
"href" : "https://www.icann.org/wicf",
"type" : "text/html"
}
]
}
]
}