mirror of
https://github.com/google/nomulus.git
synced 2025-05-28 11:10:57 +02:00
mv com/google/domain/registry google/registry
This change renames directories in preparation for the great package rename. The repository is now in a broken state because the code itself hasn't been updated. However this should ensure that git correctly preserves history for each file.
This commit is contained in:
parent
a41677aea1
commit
5012893c1d
2396 changed files with 0 additions and 0 deletions
30
java/google/registry/rdap/BUILD
Normal file
30
java/google/registry/rdap/BUILD
Normal file
|
@ -0,0 +1,30 @@
|
|||
package(
|
||||
default_visibility = ["//java/com/google/domain/registry:registry_project"],
|
||||
)
|
||||
|
||||
|
||||
java_library(
|
||||
name = "rdap",
|
||||
srcs = glob(["*.java"]),
|
||||
deps = [
|
||||
"//java/com/google/common/annotations",
|
||||
"//java/com/google/common/base",
|
||||
"//java/com/google/common/collect",
|
||||
"//java/com/google/common/html",
|
||||
"//java/com/google/common/io",
|
||||
"//java/com/google/common/net",
|
||||
"//java/com/google/common/primitives",
|
||||
"//java/com/google/domain/registry/config",
|
||||
"//java/com/google/domain/registry/model",
|
||||
"//java/com/google/domain/registry/request",
|
||||
"//java/com/google/domain/registry/util",
|
||||
"//third_party/java/auto:auto_value",
|
||||
"//third_party/java/dagger",
|
||||
"//third_party/java/joda_time",
|
||||
"//third_party/java/json_simple",
|
||||
"//third_party/java/jsr305_annotations",
|
||||
"//third_party/java/jsr330_inject",
|
||||
"//third_party/java/objectify:objectify-v4_1",
|
||||
"//third_party/java/servlet/servlet_api",
|
||||
],
|
||||
)
|
191
java/google/registry/rdap/RdapActionBase.java
Normal file
191
java/google/registry/rdap/RdapActionBase.java
Normal file
|
@ -0,0 +1,191 @@
|
|||
// 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 com.google.domain.registry.rdap;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.net.HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN;
|
||||
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
|
||||
import static com.google.domain.registry.model.registry.Registries.findTldForName;
|
||||
import static com.google.domain.registry.model.registry.Registries.getTlds;
|
||||
import static com.google.domain.registry.util.DateTimeUtils.END_OF_TIME;
|
||||
import static com.google.domain.registry.util.DomainNameUtils.canonicalizeDomainName;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_OK;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.net.InternetDomainName;
|
||||
import com.google.common.net.MediaType;
|
||||
import com.google.domain.registry.config.ConfigModule.Config;
|
||||
import com.google.domain.registry.model.EppResource;
|
||||
import com.google.domain.registry.request.Action;
|
||||
import com.google.domain.registry.request.HttpException;
|
||||
import com.google.domain.registry.request.HttpException.BadRequestException;
|
||||
import com.google.domain.registry.request.HttpException.NotFoundException;
|
||||
import com.google.domain.registry.request.RequestMethod;
|
||||
import com.google.domain.registry.request.RequestPath;
|
||||
import com.google.domain.registry.request.Response;
|
||||
import com.google.domain.registry.util.FormattingLogger;
|
||||
|
||||
import com.googlecode.objectify.cmd.Query;
|
||||
|
||||
import org.json.simple.JSONValue;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* Base RDAP (new WHOIS) action for single-item domain, nameserver and entity requests.
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc7482">
|
||||
* RFC 7482: Registration Data Access Protocol (RDAP) Query Format</a>
|
||||
*/
|
||||
public abstract class RdapActionBase implements Runnable {
|
||||
|
||||
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
|
||||
|
||||
/**
|
||||
* Pattern for checking LDH names, which must officially contains only alphanumeric plus dots and
|
||||
* hyphens. In this case, allow the wildcard asterisk as well.
|
||||
*/
|
||||
static final Pattern LDH_PATTERN = Pattern.compile("[-.a-zA-Z0-9*]+");
|
||||
|
||||
private static final MediaType RESPONSE_MEDIA_TYPE = MediaType.create("application", "rdap+json");
|
||||
|
||||
@Inject Response response;
|
||||
@Inject @RequestMethod Action.Method requestMethod;
|
||||
@Inject @RequestPath String requestPath;
|
||||
@Inject @Config("rdapLinkBase") String rdapLinkBase;
|
||||
@Inject @Config("rdapWhoisServer") String rdapWhoisServer;
|
||||
|
||||
/** Returns a string like "domain name" or "nameserver", used for error strings. */
|
||||
abstract String getHumanReadableObjectTypeName();
|
||||
|
||||
/** Returns the servlet action path; used to extract the search string from the incoming path. */
|
||||
abstract String getActionPath();
|
||||
|
||||
/**
|
||||
* Does the actual search and returns an RDAP JSON object.
|
||||
*
|
||||
* @param pathSearchString the search string in the URL path
|
||||
* @param isHeadRequest whether the returned map will actually be used. HTTP HEAD requests don't
|
||||
* actually return anything. However, we usually still want to go through the process of
|
||||
* building a map, to make sure that the request would return a 500 status if it were
|
||||
* invoked using GET. So this field should usually be ignored, unless there's some
|
||||
* expensive task required to create the map which will never result in a request failure.
|
||||
* @param linkBase the base URL for RDAP link structures
|
||||
* @return A map (probably containing nested maps and lists) with the final JSON response data.
|
||||
*/
|
||||
abstract ImmutableMap<String, Object> getJsonObjectForResource(
|
||||
String pathSearchString, boolean isHeadRequest, String linkBase) throws HttpException;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
// Extract what we're searching for from the request path. Some RDAP commands use trailing
|
||||
// data in the path itself (e.g. /rdap/domain/mydomain.com), and some use the query string
|
||||
// (e.g. /rdap/domains?name=mydomain); the query parameters are extracted by the subclasses
|
||||
// directly as needed.
|
||||
response.setHeader(ACCESS_CONTROL_ALLOW_ORIGIN, "*");
|
||||
URI uri = new URI(requestPath);
|
||||
String pathProper = uri.getPath();
|
||||
checkArgument(
|
||||
pathProper.startsWith(getActionPath()),
|
||||
"%s doesn't start with %s", pathProper, getActionPath());
|
||||
ImmutableMap<String, Object> rdapJson =
|
||||
getJsonObjectForResource(
|
||||
pathProper.substring(getActionPath().length()),
|
||||
requestMethod == Action.Method.HEAD,
|
||||
rdapLinkBase);
|
||||
response.setStatus(SC_OK);
|
||||
if (requestMethod != Action.Method.HEAD) {
|
||||
response.setPayload(JSONValue.toJSONString(rdapJson));
|
||||
}
|
||||
response.setContentType(MediaType.create("application", "rdap+json"));
|
||||
} catch (HttpException e) {
|
||||
setError(e.getResponseCode(), e.getResponseCodeString(), e.getMessage());
|
||||
} catch (URISyntaxException | IllegalArgumentException e) {
|
||||
setError(SC_BAD_REQUEST, "Bad Request", "Not a valid " + getHumanReadableObjectTypeName());
|
||||
} catch (RuntimeException e) {
|
||||
setError(SC_INTERNAL_SERVER_ERROR, "Internal Server Error", "An error was encountered");
|
||||
logger.severe(e, "Exception encountered while processing RDAP command");
|
||||
}
|
||||
}
|
||||
|
||||
void setError(int status, String title, String description) {
|
||||
response.setStatus(status);
|
||||
try {
|
||||
if (requestMethod != Action.Method.HEAD) {
|
||||
response.setPayload(
|
||||
JSONValue.toJSONString(RdapJsonFormatter.makeError(status, title, description)));
|
||||
}
|
||||
response.setContentType(RESPONSE_MEDIA_TYPE);
|
||||
} catch (Exception ex) {
|
||||
if (requestMethod != Action.Method.HEAD) {
|
||||
response.setPayload("");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void validateDomainName(String name) {
|
||||
try {
|
||||
Optional<InternetDomainName> tld = findTldForName(InternetDomainName.from(name));
|
||||
if (!tld.isPresent() || !getTlds().contains(tld.get().toString())) {
|
||||
throw new NotFoundException(name + " not found");
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new BadRequestException(
|
||||
name + " is not a valid " + getHumanReadableObjectTypeName());
|
||||
}
|
||||
}
|
||||
|
||||
String canonicalizeName(String name) {
|
||||
name = canonicalizeDomainName(name);
|
||||
if (name.endsWith(".")) {
|
||||
name = name.substring(0, name.length() - 1);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles prefix searches in cases where there are no pending deletes. In such cases, it is
|
||||
* sufficient to check whether {@code deletionTime} is equal to {@code END_OF_TIME}, because any
|
||||
* other value means it has already been deleted. This allows us to use an equality query for the
|
||||
* deletion time.
|
||||
*
|
||||
* @param clazz the type of resource to be queried
|
||||
* @param filterField the database field of interest
|
||||
* @param partialStringQuery the details of the search string
|
||||
* @param resultSetMaxSize the maximum number of results to return
|
||||
* @return the results of the query
|
||||
*/
|
||||
static <T extends EppResource> Query<T> queryUndeleted(
|
||||
Class<T> clazz,
|
||||
String filterField,
|
||||
RdapSearchPattern partialStringQuery,
|
||||
int resultSetMaxSize) {
|
||||
checkArgument(partialStringQuery.getHasWildcard(), "search string doesn't have wildcard");
|
||||
return ofy().load()
|
||||
.type(clazz)
|
||||
.filter(filterField + " >=", partialStringQuery.getInitialString())
|
||||
.filter(filterField + " <", partialStringQuery.getNextInitialString())
|
||||
.filter("deletionTime", END_OF_TIME)
|
||||
.limit(resultSetMaxSize);
|
||||
}
|
||||
}
|
55
java/google/registry/rdap/RdapAutnumAction.java
Normal file
55
java/google/registry/rdap/RdapAutnumAction.java
Normal file
|
@ -0,0 +1,55 @@
|
|||
// 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 com.google.domain.registry.rdap;
|
||||
|
||||
import static com.google.domain.registry.request.Action.Method.GET;
|
||||
import static com.google.domain.registry.request.Action.Method.HEAD;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.domain.registry.request.Action;
|
||||
import com.google.domain.registry.request.HttpException;
|
||||
import com.google.domain.registry.request.HttpException.NotImplementedException;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* RDAP (new WHOIS) action for RDAP autonomous system number reuqests.
|
||||
*
|
||||
* <p>This feature is not implemented because it's only necessary for <i>address</i> registries like
|
||||
* ARIN, not domain registries.
|
||||
*/
|
||||
@Action(path = RdapAutnumAction.PATH, method = {GET, HEAD}, isPrefix = true)
|
||||
public class RdapAutnumAction extends RdapActionBase {
|
||||
|
||||
public static final String PATH = "/rdap/autnum";
|
||||
|
||||
@Inject RdapAutnumAction() {}
|
||||
|
||||
@Override
|
||||
public String getHumanReadableObjectTypeName() {
|
||||
return "autnum";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getActionPath() {
|
||||
return PATH;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutableMap<String, Object> getJsonObjectForResource(
|
||||
String pathSearchString, boolean isHeadRequest, String linkBase) throws HttpException {
|
||||
throw new NotImplementedException("Domain Name Registry information only");
|
||||
}
|
||||
}
|
65
java/google/registry/rdap/RdapDomainAction.java
Normal file
65
java/google/registry/rdap/RdapDomainAction.java
Normal file
|
@ -0,0 +1,65 @@
|
|||
// 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 com.google.domain.registry.rdap;
|
||||
|
||||
import static com.google.domain.registry.model.EppResourceUtils.loadByUniqueId;
|
||||
import static com.google.domain.registry.request.Action.Method.GET;
|
||||
import static com.google.domain.registry.request.Action.Method.HEAD;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.domain.registry.model.domain.DomainResource;
|
||||
import com.google.domain.registry.request.Action;
|
||||
import com.google.domain.registry.request.HttpException;
|
||||
import com.google.domain.registry.request.HttpException.NotFoundException;
|
||||
import com.google.domain.registry.util.Clock;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* RDAP (new WHOIS) action for domain requests.
|
||||
*/
|
||||
@Action(path = RdapDomainAction.PATH, method = {GET, HEAD}, isPrefix = true)
|
||||
public class RdapDomainAction extends RdapActionBase {
|
||||
|
||||
public static final String PATH = "/rdap/domain/";
|
||||
|
||||
@Inject Clock clock;
|
||||
@Inject RdapDomainAction() {}
|
||||
|
||||
@Override
|
||||
public String getHumanReadableObjectTypeName() {
|
||||
return "domain name";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getActionPath() {
|
||||
return PATH;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutableMap<String, Object> getJsonObjectForResource(
|
||||
String pathSearchString, boolean isHeadRequest, String linkBase) throws HttpException {
|
||||
pathSearchString = canonicalizeName(pathSearchString);
|
||||
validateDomainName(pathSearchString);
|
||||
// The query string is not used; the RDAP syntax is /rdap/domain/mydomain.com.
|
||||
DomainResource domainResource =
|
||||
loadByUniqueId(DomainResource.class, pathSearchString, clock.nowUtc());
|
||||
if (domainResource == null) {
|
||||
throw new NotFoundException(pathSearchString + " not found");
|
||||
}
|
||||
return RdapJsonFormatter.makeRdapJsonForDomain(
|
||||
domainResource, true, rdapLinkBase, rdapWhoisServer);
|
||||
}
|
||||
}
|
275
java/google/registry/rdap/RdapDomainSearchAction.java
Normal file
275
java/google/registry/rdap/RdapDomainSearchAction.java
Normal file
|
@ -0,0 +1,275 @@
|
|||
// 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 com.google.domain.registry.rdap;
|
||||
|
||||
import static com.google.domain.registry.model.EppResourceUtils.loadByUniqueId;
|
||||
import static com.google.domain.registry.model.index.ForeignKeyIndex.loadAndGetReference;
|
||||
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
|
||||
import static com.google.domain.registry.request.Action.Method.GET;
|
||||
import static com.google.domain.registry.request.Action.Method.HEAD;
|
||||
import static com.google.domain.registry.util.DateTimeUtils.END_OF_TIME;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSortedSet;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.primitives.Booleans;
|
||||
import com.google.domain.registry.model.domain.DomainResource;
|
||||
import com.google.domain.registry.model.host.HostResource;
|
||||
import com.google.domain.registry.rdap.RdapJsonFormatter.BoilerplateType;
|
||||
import com.google.domain.registry.request.Action;
|
||||
import com.google.domain.registry.request.HttpException;
|
||||
import com.google.domain.registry.request.HttpException.BadRequestException;
|
||||
import com.google.domain.registry.request.HttpException.NotFoundException;
|
||||
import com.google.domain.registry.request.Parameter;
|
||||
import com.google.domain.registry.util.Clock;
|
||||
import com.google.domain.registry.util.Idn;
|
||||
|
||||
import com.googlecode.objectify.Key;
|
||||
import com.googlecode.objectify.Ref;
|
||||
import com.googlecode.objectify.cmd.Query;
|
||||
import com.googlecode.objectify.cmd.QueryKeys;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* RDAP (new WHOIS) action for domain search requests.
|
||||
*
|
||||
* <p>All commands and responses conform to the RDAP spec as defined in RFCs 7480 through 7485.
|
||||
*
|
||||
* @see <a href="http://tools.ietf.org/html/rfc7482">
|
||||
* RFC 7482: Registration Data Access Protocol (RDAP) Query Format</a>
|
||||
* @see <a href="http://tools.ietf.org/html/rfc7483">
|
||||
* RFC 7483: JSON Responses for the Registration Data Access Protocol (RDAP)</a>
|
||||
*/
|
||||
@Action(path = RdapDomainSearchAction.PATH, method = {GET, HEAD}, isPrefix = true)
|
||||
public class RdapDomainSearchAction extends RdapActionBase {
|
||||
|
||||
public static final String PATH = "/rdap/domains";
|
||||
|
||||
@Inject Clock clock;
|
||||
@Inject @Parameter("name") Optional<String> nameParam;
|
||||
@Inject @Parameter("nsLdhName") Optional<String> nsLdhNameParam;
|
||||
@Inject @Parameter("nsIp") Optional<InetAddress> nsIpParam;
|
||||
@Inject RdapDomainSearchAction() {}
|
||||
|
||||
@Override
|
||||
public String getHumanReadableObjectTypeName() {
|
||||
return "domain search";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getActionPath() {
|
||||
return PATH;
|
||||
}
|
||||
|
||||
/** Parses the parameters and calls the appropriate search function. */
|
||||
@Override
|
||||
public ImmutableMap<String, Object> getJsonObjectForResource(
|
||||
String pathSearchString, boolean isHeadRequest, String linkBase) throws HttpException {
|
||||
DateTime now = clock.nowUtc();
|
||||
// RDAP syntax example: /rdap/domains?name=exam*.com.
|
||||
// The pathSearchString is not used by search commands.
|
||||
if (pathSearchString.length() > 0) {
|
||||
throw new BadRequestException("Unexpected path");
|
||||
}
|
||||
if (Booleans.countTrue(nameParam.isPresent(), nsLdhNameParam.isPresent(), nsIpParam.isPresent())
|
||||
!= 1) {
|
||||
throw new BadRequestException(
|
||||
"You must specify either name=XXXX, nsLdhName=YYYY or nsIp=ZZZZ");
|
||||
}
|
||||
ImmutableList<ImmutableMap<String, Object>> results;
|
||||
if (nameParam.isPresent()) {
|
||||
// syntax: /rdap/domains?name=exam*.com
|
||||
String asciiName;
|
||||
try {
|
||||
asciiName = Idn.toASCII(nameParam.get());
|
||||
} catch (Exception e) {
|
||||
throw new BadRequestException("Invalid value of nsLdhName parameter");
|
||||
}
|
||||
results = searchByDomainName(RdapSearchPattern.create(asciiName, true), now);
|
||||
} else if (nsLdhNameParam.isPresent()) {
|
||||
// syntax: /rdap/domains?nsLdhName=ns1.exam*.com
|
||||
// RFC 7482 appears to say that Unicode domains must be specified using punycode when
|
||||
// passed to nsLdhName, so IDN.toASCII is not called here.
|
||||
if (!LDH_PATTERN.matcher(nsLdhNameParam.get()).matches()) {
|
||||
throw new BadRequestException("Invalid value of nsLdhName parameter");
|
||||
}
|
||||
results = searchByNameserverLdhName(
|
||||
RdapSearchPattern.create(nsLdhNameParam.get(), true), now);
|
||||
} else {
|
||||
// syntax: /rdap/domains?nsIp=1.2.3.4
|
||||
results = searchByNameserverIp(nsIpParam.get(), now);
|
||||
}
|
||||
if (results.isEmpty()) {
|
||||
throw new NotFoundException("No domains found");
|
||||
}
|
||||
ImmutableMap.Builder<String, Object> builder = new ImmutableMap.Builder<>();
|
||||
builder.put("domainSearchResults", results);
|
||||
RdapJsonFormatter.addTopLevelEntries(builder, BoilerplateType.DOMAIN, null, 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) {
|
||||
// Handle queries without a wildcard -- just load by foreign key.
|
||||
if (!partialStringQuery.getHasWildcard()) {
|
||||
DomainResource domainResource =
|
||||
loadByUniqueId(DomainResource.class, partialStringQuery.getInitialString(), now);
|
||||
if (domainResource == null) {
|
||||
return ImmutableList.of();
|
||||
}
|
||||
return ImmutableList.of(
|
||||
RdapJsonFormatter.makeRdapJsonForDomain(
|
||||
domainResource, false, rdapLinkBase, rdapWhoisServer));
|
||||
// Handle queries with a wildcard.
|
||||
} else {
|
||||
Query<DomainResource> query = ofy().load()
|
||||
.type(DomainResource.class)
|
||||
// TODO(b/24463238): figure out how to limit the size of these queries effectively
|
||||
.filter("fullyQualifiedDomainName >=", partialStringQuery.getInitialString())
|
||||
.filter("fullyQualifiedDomainName <", partialStringQuery.getNextInitialString())
|
||||
.limit(1000);
|
||||
if (partialStringQuery.getSuffix() != null) {
|
||||
query = query.filter("tld", partialStringQuery.getSuffix());
|
||||
}
|
||||
ImmutableList.Builder<ImmutableMap<String, Object>> builder = new ImmutableList.Builder<>();
|
||||
for (DomainResource domainResource : query) {
|
||||
if (domainResource.getDeletionTime().isAfter(now)) {
|
||||
builder.add(
|
||||
RdapJsonFormatter.makeRdapJsonForDomain(
|
||||
domainResource, false, rdapLinkBase, rdapWhoisServer));
|
||||
}
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Searches for domains by nameserver name, returning a JSON array of domain info maps. */
|
||||
private ImmutableList<ImmutableMap<String, Object>>
|
||||
searchByNameserverLdhName(final RdapSearchPattern partialStringQuery, final DateTime now)
|
||||
throws HttpException {
|
||||
ImmutableList<Ref<HostResource>> hostRefs;
|
||||
// Handle queries without a wildcard; just load the host by foreign key in the usual way.
|
||||
if (!partialStringQuery.getHasWildcard()) {
|
||||
Ref<HostResource> hostRef = loadAndGetReference(
|
||||
HostResource.class, partialStringQuery.getInitialString(), clock.nowUtc());
|
||||
if (hostRef == null) {
|
||||
return ImmutableList.of();
|
||||
}
|
||||
hostRefs = ImmutableList.of(hostRef);
|
||||
// Handle queries with a wildcard, but no suffix. Query the host resources themselves, rather
|
||||
// than the foreign key index, because then we have an index on fully qualified host name and
|
||||
// deletion time, so we can check the deletion status in the query itself. There are no pending
|
||||
// deletes for hosts, so we can call queryUndeleted.
|
||||
} else if (partialStringQuery.getSuffix() == null) {
|
||||
// TODO (b/24463238): figure out how to limit the size of these queries effectively
|
||||
Query<HostResource> query = queryUndeleted(
|
||||
HostResource.class,
|
||||
"fullyQualifiedHostName",
|
||||
partialStringQuery, 1000);
|
||||
ImmutableList.Builder<Ref<HostResource>> builder = new ImmutableList.Builder<>();
|
||||
for (Key<HostResource> hostResourceKey : query.keys()) {
|
||||
builder.add(Ref.create(hostResourceKey));
|
||||
}
|
||||
hostRefs = builder.build();
|
||||
if (hostRefs.isEmpty()) {
|
||||
throw new NotFoundException("No matching nameservers found");
|
||||
}
|
||||
// Handle queries with a wildcard and a suffix. In this case, it is more efficient to do things
|
||||
// differently. We use the suffix to look up the domain, then loop through the subordinate hosts
|
||||
// looking for matches.
|
||||
} else {
|
||||
DomainResource domainResource = loadByUniqueId(
|
||||
DomainResource.class, partialStringQuery.getSuffix(), clock.nowUtc());
|
||||
if (domainResource == null) {
|
||||
throw new NotFoundException("No domain found for specified nameserver suffix");
|
||||
}
|
||||
ImmutableList.Builder<Ref<HostResource>> builder = new ImmutableList.Builder<>();
|
||||
for (String fqhn : ImmutableSortedSet.copyOf(domainResource.getSubordinateHosts())) {
|
||||
// We can't just check that the host name starts with the initial query string, because then
|
||||
// the query ns.exam*.example.com would match against nameserver ns.example.com.
|
||||
if (partialStringQuery.matches(fqhn)) {
|
||||
Ref<HostResource> hostRef = loadAndGetReference(HostResource.class, fqhn, clock.nowUtc());
|
||||
if (hostRef != null) {
|
||||
builder.add(hostRef);
|
||||
}
|
||||
}
|
||||
}
|
||||
hostRefs = builder.build();
|
||||
if (hostRefs.isEmpty()) {
|
||||
throw new NotFoundException("No matching nameservers found");
|
||||
}
|
||||
}
|
||||
// Find all domains that link to any of these hosts, and return information about them.
|
||||
return searchByNameserverRefs(hostRefs, now);
|
||||
}
|
||||
|
||||
/** 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) {
|
||||
// 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.
|
||||
QueryKeys<HostResource> query = ofy()
|
||||
.load()
|
||||
.type(HostResource.class)
|
||||
.filter("inetAddresses", inetAddress.getHostAddress())
|
||||
.filter("deletionTime", END_OF_TIME)
|
||||
.limit(1000)
|
||||
.keys();
|
||||
ImmutableList.Builder<Ref<HostResource>> builder = new ImmutableList.Builder<>();
|
||||
for (Key<HostResource> key : query) {
|
||||
builder.add(Ref.create(key));
|
||||
}
|
||||
ImmutableList<Ref<HostResource>> hostRefs = builder.build();
|
||||
if (hostRefs.isEmpty()) {
|
||||
return ImmutableList.of();
|
||||
}
|
||||
// Find all domains that link to any of these hosts, and return information about them.
|
||||
return searchByNameserverRefs(hostRefs, now);
|
||||
}
|
||||
|
||||
/**
|
||||
* Locates all domains which are linked to a set of host refs. This method is called by
|
||||
* {@link #searchByNameserverLdhName} and {@link #searchByNameserverIp} after they assemble the
|
||||
* relevant host refs.
|
||||
*/
|
||||
private ImmutableList<ImmutableMap<String, Object>>
|
||||
searchByNameserverRefs(final Iterable<Ref<HostResource>> hostRefs, final DateTime now) {
|
||||
// We must break the query up into chunks, because the in operator is limited to 30 subqueries.
|
||||
ImmutableList.Builder<ImmutableMap<String, Object>> builder = new ImmutableList.Builder<>();
|
||||
for (List<Ref<HostResource>> chunk : Iterables.partition(hostRefs, 30)) {
|
||||
Query<DomainResource> query = ofy().load()
|
||||
.type(DomainResource.class)
|
||||
.filter("nameservers.linked in", chunk)
|
||||
.filter("deletionTime >", now)
|
||||
.limit(1000);
|
||||
for (DomainResource domainResource : query) {
|
||||
builder.add(
|
||||
RdapJsonFormatter.makeRdapJsonForDomain(
|
||||
domainResource, false, rdapLinkBase, rdapWhoisServer));
|
||||
}
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
}
|
94
java/google/registry/rdap/RdapEntityAction.java
Normal file
94
java/google/registry/rdap/RdapEntityAction.java
Normal file
|
@ -0,0 +1,94 @@
|
|||
// 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 com.google.domain.registry.rdap;
|
||||
|
||||
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
|
||||
import static com.google.domain.registry.request.Action.Method.GET;
|
||||
import static com.google.domain.registry.request.Action.Method.HEAD;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.domain.registry.model.contact.ContactResource;
|
||||
import com.google.domain.registry.model.domain.DesignatedContact;
|
||||
import com.google.domain.registry.model.registrar.Registrar;
|
||||
import com.google.domain.registry.request.Action;
|
||||
import com.google.domain.registry.request.HttpException;
|
||||
import com.google.domain.registry.request.HttpException.BadRequestException;
|
||||
import com.google.domain.registry.request.HttpException.NotFoundException;
|
||||
import com.google.domain.registry.util.Clock;
|
||||
|
||||
import com.googlecode.objectify.Key;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* RDAP (new WHOIS) action for entity (contact and registrar) requests.
|
||||
*/
|
||||
@Action(path = RdapEntityAction.PATH, method = {GET, HEAD}, isPrefix = true)
|
||||
public class RdapEntityAction extends RdapActionBase {
|
||||
|
||||
public static final String PATH = "/rdap/entity/";
|
||||
|
||||
private static final Pattern ROID_PATTERN = Pattern.compile("[-.a-zA-Z0-9]+");
|
||||
|
||||
@Inject Clock clock;
|
||||
@Inject RdapEntityAction() {}
|
||||
|
||||
@Override
|
||||
public String getHumanReadableObjectTypeName() {
|
||||
return "entity";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getActionPath() {
|
||||
return PATH;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutableMap<String, Object> getJsonObjectForResource(
|
||||
String pathSearchString, boolean isHeadRequest, String linkBase) throws HttpException {
|
||||
// The query string is not used; the RDAP syntax is /rdap/entity/handle (the handle is the roid
|
||||
// for contacts and the client identifier for registrars). Since RDAP's concept of an entity
|
||||
// includes both contacts and registrars, search for one first, then the other.
|
||||
boolean wasValidKey = false;
|
||||
if (ROID_PATTERN.matcher(pathSearchString).matches()) {
|
||||
wasValidKey = true;
|
||||
Key<ContactResource> contactKey = Key.create(ContactResource.class, pathSearchString);
|
||||
ContactResource contactResource = ofy().load().key(contactKey).now();
|
||||
if ((contactResource != null) && clock.nowUtc().isBefore(contactResource.getDeletionTime())) {
|
||||
return RdapJsonFormatter.makeRdapJsonForContact(
|
||||
contactResource,
|
||||
true,
|
||||
Optional.<DesignatedContact.Type>absent(),
|
||||
rdapLinkBase,
|
||||
rdapWhoisServer);
|
||||
}
|
||||
}
|
||||
String clientId = pathSearchString.trim();
|
||||
if ((clientId.length() >= 3) && (clientId.length() <= 16)) {
|
||||
wasValidKey = true;
|
||||
Registrar registrar = Registrar.loadByClientId(clientId);
|
||||
if ((registrar != null) && registrar.isActiveAndPubliclyVisible()) {
|
||||
return RdapJsonFormatter.makeRdapJsonForRegistrar(
|
||||
registrar, true, rdapLinkBase, rdapWhoisServer);
|
||||
}
|
||||
}
|
||||
throw !wasValidKey
|
||||
? new BadRequestException(pathSearchString + " is not a valid entity handle")
|
||||
: new NotFoundException(pathSearchString + " not found");
|
||||
}
|
||||
}
|
171
java/google/registry/rdap/RdapEntitySearchAction.java
Normal file
171
java/google/registry/rdap/RdapEntitySearchAction.java
Normal file
|
@ -0,0 +1,171 @@
|
|||
// 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 com.google.domain.registry.rdap;
|
||||
|
||||
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
|
||||
import static com.google.domain.registry.request.Action.Method.GET;
|
||||
import static com.google.domain.registry.request.Action.Method.HEAD;
|
||||
import static com.google.domain.registry.util.DateTimeUtils.END_OF_TIME;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.primitives.Booleans;
|
||||
import com.google.domain.registry.config.ConfigModule.Config;
|
||||
import com.google.domain.registry.model.contact.ContactResource;
|
||||
import com.google.domain.registry.model.domain.DesignatedContact;
|
||||
import com.google.domain.registry.model.registrar.Registrar;
|
||||
import com.google.domain.registry.rdap.RdapJsonFormatter.BoilerplateType;
|
||||
import com.google.domain.registry.request.Action;
|
||||
import com.google.domain.registry.request.HttpException;
|
||||
import com.google.domain.registry.request.HttpException.BadRequestException;
|
||||
import com.google.domain.registry.request.HttpException.NotFoundException;
|
||||
import com.google.domain.registry.request.HttpException.NotImplementedException;
|
||||
import com.google.domain.registry.request.HttpException.UnprocessableEntityException;
|
||||
import com.google.domain.registry.request.Parameter;
|
||||
import com.google.domain.registry.util.Clock;
|
||||
|
||||
import com.googlecode.objectify.Key;
|
||||
import com.googlecode.objectify.cmd.Query;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* RDAP (new WHOIS) action for entity (contact and registrar) search requests.
|
||||
*
|
||||
* <p>All commands and responses conform to the RDAP spec as defined in RFCs 7480 through 7485.
|
||||
*
|
||||
* @see <a href="http://tools.ietf.org/html/rfc7482">
|
||||
* RFC 7482: Registration Data Access Protocol (RDAP) Query Format</a>
|
||||
* @see <a href="http://tools.ietf.org/html/rfc7483">
|
||||
* RFC 7483: JSON Responses for the Registration Data Access Protocol (RDAP)</a>
|
||||
*/
|
||||
@Action(path = RdapEntitySearchAction.PATH, method = {GET, HEAD}, isPrefix = true)
|
||||
public class RdapEntitySearchAction extends RdapActionBase {
|
||||
|
||||
public static final String PATH = "/rdap/entities";
|
||||
|
||||
@Inject Clock clock;
|
||||
@Inject @Parameter("fn") Optional<String> fnParam;
|
||||
@Inject @Parameter("handle") Optional<String> handleParam;
|
||||
@Inject @Config("rdapResultSetMaxSize") int rdapResultSetMaxSize;
|
||||
@Inject RdapEntitySearchAction() {}
|
||||
|
||||
@Override
|
||||
public String getHumanReadableObjectTypeName() {
|
||||
return "entity search";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getActionPath() {
|
||||
return PATH;
|
||||
}
|
||||
|
||||
/** Parses the parameters and calls the appropriate search function. */
|
||||
@Override
|
||||
public ImmutableMap<String, Object> getJsonObjectForResource(
|
||||
String pathSearchString, boolean isHeadRequest, String linkBase) throws HttpException {
|
||||
// RDAP syntax example: /rdap/entities?fn=Bobby%20Joe*.
|
||||
// The pathSearchString is not used by search commands.
|
||||
if (pathSearchString.length() > 0) {
|
||||
throw new BadRequestException("Unexpected path");
|
||||
}
|
||||
if (Booleans.countTrue(fnParam.isPresent(), handleParam.isPresent()) != 1) {
|
||||
throw new BadRequestException("You must specify either fn=XXXX or handle=YYYY");
|
||||
}
|
||||
ImmutableList<ImmutableMap<String, Object>> results;
|
||||
if (fnParam.isPresent()) {
|
||||
// syntax: /rdap/entities?fn=Bobby%20Joe*
|
||||
throw new NotImplementedException("Entity name search not implemented");
|
||||
} else {
|
||||
// syntax: /rdap/entities?handle=12345-*
|
||||
// The handle is either the contact roid or the registrar clientId.
|
||||
results = searchByHandle(RdapSearchPattern.create(handleParam.get(), false));
|
||||
}
|
||||
if (results.isEmpty()) {
|
||||
throw new NotFoundException("No entities found");
|
||||
}
|
||||
ImmutableMap.Builder<String, Object> builder = new ImmutableMap.Builder<>();
|
||||
builder.put("entitySearchResults", results);
|
||||
RdapJsonFormatter.addTopLevelEntries(builder, BoilerplateType.ENTITY, null, rdapLinkBase);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/** Searches for entities by handle, returning a JSON array of entity info maps. */
|
||||
private ImmutableList<ImmutableMap<String, Object>>
|
||||
searchByHandle(final RdapSearchPattern partialStringQuery) throws HttpException {
|
||||
// Handle queries without a wildcard -- load by ID.
|
||||
if (!partialStringQuery.getHasWildcard()) {
|
||||
ContactResource contactResource = ofy().load()
|
||||
.type(ContactResource.class)
|
||||
.id(partialStringQuery.getInitialString())
|
||||
.now();
|
||||
Registrar registrar = Registrar.loadByClientId(partialStringQuery.getInitialString());
|
||||
ImmutableList.Builder<ImmutableMap<String, Object>> builder = new ImmutableList.Builder<>();
|
||||
if ((contactResource != null) && contactResource.getDeletionTime().isEqual(END_OF_TIME)) {
|
||||
builder.add(RdapJsonFormatter.makeRdapJsonForContact(
|
||||
contactResource,
|
||||
false,
|
||||
Optional.<DesignatedContact.Type>absent(),
|
||||
rdapLinkBase,
|
||||
rdapWhoisServer));
|
||||
}
|
||||
if ((registrar != null) && registrar.isActiveAndPubliclyVisible()) {
|
||||
builder.add(RdapJsonFormatter.makeRdapJsonForRegistrar(
|
||||
registrar, false, rdapLinkBase, rdapWhoisServer));
|
||||
}
|
||||
return builder.build();
|
||||
// Handle queries with a wildcard, but no suffix. For contact resources, the deletion time will
|
||||
// always be END_OF_TIME for non-deleted records; unlike domain resources, we don't need to
|
||||
// worry about deletion times in the future. That allows us to use an equality query for the
|
||||
// deletion time.
|
||||
} else if (partialStringQuery.getSuffix() == null) {
|
||||
ImmutableList.Builder<ImmutableMap<String, Object>> builder = new ImmutableList.Builder<>();
|
||||
Query<ContactResource> query = ofy().load()
|
||||
.type(ContactResource.class)
|
||||
.filterKey(">=", Key.create(ContactResource.class, partialStringQuery.getInitialString()))
|
||||
.filterKey(
|
||||
"<", Key.create(ContactResource.class, partialStringQuery.getNextInitialString()))
|
||||
.filter("deletionTime", END_OF_TIME)
|
||||
.limit(rdapResultSetMaxSize);
|
||||
for (ContactResource contactResource : query) {
|
||||
builder.add(RdapJsonFormatter.makeRdapJsonForContact(
|
||||
contactResource,
|
||||
false,
|
||||
Optional.<DesignatedContact.Type>absent(),
|
||||
rdapLinkBase,
|
||||
rdapWhoisServer));
|
||||
}
|
||||
for (Registrar registrar
|
||||
: Registrar.loadByClientIdRange(
|
||||
partialStringQuery.getInitialString(),
|
||||
partialStringQuery.getNextInitialString(),
|
||||
rdapResultSetMaxSize)) {
|
||||
if (registrar.isActiveAndPubliclyVisible()) {
|
||||
builder.add(RdapJsonFormatter.makeRdapJsonForRegistrar(
|
||||
registrar, false, rdapLinkBase, rdapWhoisServer));
|
||||
}
|
||||
}
|
||||
// In theory, there could be more results than our max size, so limit the size.
|
||||
ImmutableList<ImmutableMap<String, Object>> resultSet = builder.build();
|
||||
return (resultSet.size() <= rdapResultSetMaxSize)
|
||||
? resultSet
|
||||
: resultSet.subList(0, rdapResultSetMaxSize);
|
||||
// Don't allow suffixes in entity handle search queries.
|
||||
} else {
|
||||
throw new UnprocessableEntityException("Suffixes not allowed in entity handle searches");
|
||||
}
|
||||
}
|
||||
}
|
161
java/google/registry/rdap/RdapHelpAction.java
Normal file
161
java/google/registry/rdap/RdapHelpAction.java
Normal file
|
@ -0,0 +1,161 @@
|
|||
// 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 com.google.domain.registry.rdap;
|
||||
|
||||
import static com.google.domain.registry.request.Action.Method.GET;
|
||||
import static com.google.domain.registry.request.Action.Method.HEAD;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.domain.registry.rdap.RdapJsonFormatter.BoilerplateType;
|
||||
import com.google.domain.registry.rdap.RdapJsonFormatter.MakeRdapJsonNoticeParameters;
|
||||
import com.google.domain.registry.request.Action;
|
||||
import com.google.domain.registry.request.HttpException;
|
||||
import com.google.domain.registry.request.HttpException.InternalServerErrorException;
|
||||
import com.google.domain.registry.request.HttpException.NotFoundException;
|
||||
import com.google.domain.registry.util.Clock;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* RDAP (new WHOIS) action for help requests.
|
||||
*/
|
||||
@Action(path = RdapHelpAction.PATH, method = {GET, HEAD}, isPrefix = true)
|
||||
public class RdapHelpAction extends RdapActionBase {
|
||||
|
||||
public static final String PATH = "/rdap/help";
|
||||
|
||||
/**
|
||||
* Path for the terms of service. The terms of service are also used to create the required
|
||||
* boilerplate notice, so we make it a publicly visible that we can use elsewhere to reference it.
|
||||
*/
|
||||
public static final String TERMS_OF_SERVICE_PATH = "/tos";
|
||||
|
||||
/**
|
||||
* Map from a relative path underneath the RDAP root path to the appropriate
|
||||
* {@link MakeRdapJsonNoticeParameters} object.
|
||||
*/
|
||||
private static final ImmutableMap<String, MakeRdapJsonNoticeParameters> HELP_MAP =
|
||||
ImmutableMap.of(
|
||||
"/",
|
||||
MakeRdapJsonNoticeParameters.builder()
|
||||
.title("RDAP Help")
|
||||
.description(ImmutableList.of(
|
||||
"RDAP Help Topics (use /help/topic for information)",
|
||||
"syntax",
|
||||
"tos (Terms of Service)"))
|
||||
.linkValueSuffix("help/")
|
||||
.linkHrefUrlString("https://www.registry.google/about/rdap/index.html")
|
||||
.build(),
|
||||
"/index",
|
||||
MakeRdapJsonNoticeParameters.builder()
|
||||
.title("RDAP Help")
|
||||
.description(ImmutableList.of(
|
||||
"RDAP Help Topics (use /help/topic for information)",
|
||||
"syntax",
|
||||
"tos (Terms of Service)"))
|
||||
.linkValueSuffix("help/index")
|
||||
.linkHrefUrlString("https://www.registry.google/about/rdap/index.html")
|
||||
.build(),
|
||||
"/syntax",
|
||||
MakeRdapJsonNoticeParameters.builder()
|
||||
.title("RDAP Command Syntax")
|
||||
.description(ImmutableList.of(
|
||||
"domain/XXXX",
|
||||
"nameserver/XXXX",
|
||||
"entity/XXXX",
|
||||
"domains?name=XXXX",
|
||||
"domains?nsLdhName=XXXX",
|
||||
"domains?nsIp=XXXX",
|
||||
"nameservers?name=XXXX",
|
||||
"nameservers?ip=XXXX",
|
||||
"entities?fn=XXXX",
|
||||
"entities?handle=XXXX",
|
||||
"help/XXXX"))
|
||||
.linkValueSuffix("help/syntax")
|
||||
.linkHrefUrlString("https://www.registry.google/about/rdap/syntax.html")
|
||||
.build(),
|
||||
TERMS_OF_SERVICE_PATH,
|
||||
MakeRdapJsonNoticeParameters.builder()
|
||||
.title("RDAP Terms of Service")
|
||||
.description(ImmutableList.of(
|
||||
"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."))
|
||||
.linkValueSuffix("help/tos")
|
||||
.linkHrefUrlString("https://www.registry.google/about/rdap/tos.html")
|
||||
.build());
|
||||
|
||||
@Inject Clock clock;
|
||||
@Inject RdapHelpAction() {}
|
||||
|
||||
@Override
|
||||
public String getHumanReadableObjectTypeName() {
|
||||
return "help";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getActionPath() {
|
||||
return PATH;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutableMap<String, Object> getJsonObjectForResource(
|
||||
String pathSearchString, boolean isHeadRequest, String linkBase) throws HttpException {
|
||||
// We rely on addTopLevelEntries to notice if we are sending the TOS notice, and not add a
|
||||
// duplicate boilerplate entry.
|
||||
ImmutableMap.Builder<String, Object> builder = new ImmutableMap.Builder<>();
|
||||
RdapJsonFormatter.addTopLevelEntries(
|
||||
builder,
|
||||
BoilerplateType.OTHER,
|
||||
ImmutableList.of(getJsonHelpNotice(pathSearchString, rdapLinkBase)),
|
||||
rdapLinkBase);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
static ImmutableMap<String, Object> getJsonHelpNotice(
|
||||
String pathSearchString, String rdapLinkBase) {
|
||||
if (pathSearchString.isEmpty()) {
|
||||
pathSearchString = "/";
|
||||
}
|
||||
if (!HELP_MAP.containsKey(pathSearchString)) {
|
||||
throw new NotFoundException("no help found for " + pathSearchString);
|
||||
}
|
||||
try {
|
||||
return RdapJsonFormatter.makeRdapJsonNotice(
|
||||
HELP_MAP.get(pathSearchString), rdapLinkBase);
|
||||
} catch (Exception e) {
|
||||
throw new InternalServerErrorException("unable to read help for " + pathSearchString);
|
||||
}
|
||||
}
|
||||
}
|
74
java/google/registry/rdap/RdapIcannStandardInformation.java
Normal file
74
java/google/registry/rdap/RdapIcannStandardInformation.java
Normal file
|
@ -0,0 +1,74 @@
|
|||
// 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 com.google.domain.registry.rdap;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
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"
|
||||
*/
|
||||
|
||||
public class RdapIcannStandardInformation {
|
||||
|
||||
/** Required by ICANN RDAP Profile section 1.4.10. */
|
||||
private static final ImmutableMap<String, Object> CONFORMANCE_REMARK =
|
||||
ImmutableMap.<String, Object>of(
|
||||
"description",
|
||||
ImmutableList.of(
|
||||
"This response conforms to the RDAP Operational Profile for gTLD Registries and"
|
||||
+ " Registrars version 1.0"));
|
||||
|
||||
/** Required by ICANN RDAP Profile section 1.5.18. */
|
||||
private static final ImmutableMap<String, Object> DOMAIN_STATUS_CODES_REMARK =
|
||||
ImmutableMap.<String, Object> of(
|
||||
"title",
|
||||
"EPP Status Codes",
|
||||
"description",
|
||||
ImmutableList.of(
|
||||
"For more information on domain status codes, please visit https://icann.org/epp"),
|
||||
"links",
|
||||
ImmutableList.of(
|
||||
ImmutableMap.of(
|
||||
"value", "https://icann.org/epp",
|
||||
"rel", "alternate",
|
||||
"href", "https://icann.org/epp",
|
||||
"type", "text/html")));
|
||||
|
||||
/** Required by ICANN RDAP Profile section 1.5.20. */
|
||||
private static final ImmutableMap<String, Object> INACCURACY_COMPLAINT_FORM_REMARK =
|
||||
ImmutableMap.<String, Object> of(
|
||||
"description",
|
||||
ImmutableList.of(
|
||||
"URL of the ICANN Whois Inaccuracy Complaint Form: https://www.icann.org/wicf"),
|
||||
"links",
|
||||
ImmutableList.of(
|
||||
ImmutableMap.of(
|
||||
"value", "https://www.icann.org/wicf",
|
||||
"rel", "alternate",
|
||||
"href", "https://www.icann.org/wicf",
|
||||
"type", "text/html")));
|
||||
|
||||
/** Boilerplate remarks required by domain responses. */
|
||||
static final ImmutableList<ImmutableMap<String, Object>> domainBoilerplateRemarks =
|
||||
ImmutableList.of(
|
||||
CONFORMANCE_REMARK, DOMAIN_STATUS_CODES_REMARK, INACCURACY_COMPLAINT_FORM_REMARK);
|
||||
|
||||
/** Boilerplate remarks required by nameserver and entity responses. */
|
||||
static final ImmutableList<ImmutableMap<String, Object>> nameserverAndEntityBoilerplateRemarks =
|
||||
ImmutableList.of(CONFORMANCE_REMARK);
|
||||
}
|
56
java/google/registry/rdap/RdapIpAction.java
Normal file
56
java/google/registry/rdap/RdapIpAction.java
Normal file
|
@ -0,0 +1,56 @@
|
|||
// 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 com.google.domain.registry.rdap;
|
||||
|
||||
import static com.google.domain.registry.request.Action.Method.GET;
|
||||
import static com.google.domain.registry.request.Action.Method.HEAD;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.domain.registry.request.Action;
|
||||
import com.google.domain.registry.request.HttpException;
|
||||
import com.google.domain.registry.request.HttpException.NotImplementedException;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* RDAP (new WHOIS) action for RDAP IP address requests.
|
||||
*
|
||||
* <p>This feature is not implemented because it's only necessary for <i>address</i> registries like
|
||||
* ARIN, not domain registries.
|
||||
|
||||
*/
|
||||
@Action(path = RdapIpAction.PATH, method = {GET, HEAD}, isPrefix = true)
|
||||
public class RdapIpAction extends RdapActionBase {
|
||||
|
||||
public static final String PATH = "/rdap/ip";
|
||||
|
||||
@Inject RdapIpAction() {}
|
||||
|
||||
@Override
|
||||
public String getHumanReadableObjectTypeName() {
|
||||
return "ip";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getActionPath() {
|
||||
return PATH;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutableMap<String, Object> getJsonObjectForResource(
|
||||
String pathSearchString, boolean isHeadRequest, String linkBase) throws HttpException {
|
||||
throw new NotImplementedException("Domain Name Registry information only");
|
||||
}
|
||||
}
|
920
java/google/registry/rdap/RdapJsonFormatter.java
Normal file
920
java/google/registry/rdap/RdapJsonFormatter.java
Normal file
|
@ -0,0 +1,920 @@
|
|||
// 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 com.google.domain.registry.rdap;
|
||||
|
||||
import static com.google.common.base.Strings.nullToEmpty;
|
||||
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
|
||||
|
||||
import com.google.auto.value.AutoValue;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Functions;
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.common.collect.FluentIterable;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Ordering;
|
||||
import com.google.common.net.InetAddresses;
|
||||
import com.google.domain.registry.model.EppResource;
|
||||
import com.google.domain.registry.model.contact.ContactPhoneNumber;
|
||||
import com.google.domain.registry.model.contact.ContactResource;
|
||||
import com.google.domain.registry.model.contact.PostalInfo;
|
||||
import com.google.domain.registry.model.domain.DesignatedContact;
|
||||
import com.google.domain.registry.model.domain.DesignatedContact.Type;
|
||||
import com.google.domain.registry.model.domain.DomainResource;
|
||||
import com.google.domain.registry.model.domain.ReferenceUnion;
|
||||
import com.google.domain.registry.model.eppcommon.Address;
|
||||
import com.google.domain.registry.model.eppcommon.StatusValue;
|
||||
import com.google.domain.registry.model.host.HostResource;
|
||||
import com.google.domain.registry.model.registrar.Registrar;
|
||||
import com.google.domain.registry.model.registrar.RegistrarAddress;
|
||||
import com.google.domain.registry.model.registrar.RegistrarContact;
|
||||
import com.google.domain.registry.model.reporting.HistoryEntry;
|
||||
import com.google.domain.registry.util.Idn;
|
||||
|
||||
import com.googlecode.objectify.Key;
|
||||
import com.googlecode.objectify.Ref;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Helper class to create RDAP JSON objects for various registry entities and objects.
|
||||
*
|
||||
* <p>The JSON format specifies that entities should be supplied with links indicating how to fetch
|
||||
* them via RDAP, which requires the URL to the RDAP server. The linkBase parameter, passed to many
|
||||
* of the methods, is used as the first part of the link URL. For instance, if linkBase is
|
||||
* "http://rdap.org/dir/", the link URLs will look like "http://rdap.org/dir/domain/XXXX", etc.
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc7483">
|
||||
* RFC 7483: JSON Responses for the Registration Data Access Protocol (RDAP)</a>
|
||||
*/
|
||||
public class RdapJsonFormatter {
|
||||
|
||||
/**
|
||||
* Indication of what type of boilerplate notices are required for the RDAP JSON messages. The
|
||||
* ICANN RDAP Profile specifies that, for instance, domain name responses should include a remark
|
||||
* about domain status codes. So we need to know when to include such boilerplate. On the other
|
||||
* hand, remarks are not allowed except in domain, nameserver and entity objects, so we need to
|
||||
* suppress them for other types of responses (e.g. help).
|
||||
*/
|
||||
public enum BoilerplateType {
|
||||
DOMAIN,
|
||||
NAMESERVER,
|
||||
ENTITY,
|
||||
OTHER
|
||||
}
|
||||
|
||||
private static final String RDAP_CONFORMANCE_LEVEL = "rdap_level_0";
|
||||
private static final String VCARD_VERSION_NUMBER = "4.0";
|
||||
static final String NOTICES = "notices";
|
||||
private static final String REMARKS = "remarks";
|
||||
|
||||
/** Status values specified in RFC 7483 § 10.2.2. */
|
||||
private enum RdapStatus {
|
||||
VALIDATED("validated"),
|
||||
RENEW_PROHIBITED("renew prohibited"),
|
||||
UPDATE_PROHIBITED("update prohibited"),
|
||||
TRANSFER_PROHIBITED("transfer prohibited"),
|
||||
DELETE_PROHIBITED("delete prohibited"),
|
||||
PROXY("proxy"),
|
||||
PRIVATE("private"),
|
||||
REMOVED("removed"),
|
||||
OBSCURED("obscured"),
|
||||
ASSOCIATED("associated"),
|
||||
ACTIVE("active"),
|
||||
INACTIVE("inactive"),
|
||||
LOCKED("locked"),
|
||||
PENDING_CREATE("pending create"),
|
||||
PENDING_RENEW("pending renew"),
|
||||
PENDING_TRANSFER("pending transfer"),
|
||||
PENDING_UPDATE("pending update"),
|
||||
PENDING_DELETE("pending delete");
|
||||
|
||||
/** Value as it appears in RDAP messages. */
|
||||
private final String rfc7483String;
|
||||
|
||||
private RdapStatus(String rfc7483String) {
|
||||
this.rfc7483String = rfc7483String;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return rfc7483String;
|
||||
}
|
||||
}
|
||||
|
||||
/** Map of EPP status values to the RDAP equivalents. */
|
||||
private static final ImmutableMap<StatusValue, RdapStatus> statusToRdapStatusMap =
|
||||
Maps.immutableEnumMap(
|
||||
new ImmutableMap.Builder<StatusValue, RdapStatus>()
|
||||
.put(StatusValue.CLIENT_DELETE_PROHIBITED, RdapStatus.DELETE_PROHIBITED)
|
||||
.put(StatusValue.CLIENT_HOLD, RdapStatus.INACTIVE)
|
||||
.put(StatusValue.CLIENT_RENEW_PROHIBITED, RdapStatus.RENEW_PROHIBITED)
|
||||
.put(StatusValue.CLIENT_TRANSFER_PROHIBITED, RdapStatus.TRANSFER_PROHIBITED)
|
||||
.put(StatusValue.CLIENT_UPDATE_PROHIBITED, RdapStatus.UPDATE_PROHIBITED)
|
||||
.put(StatusValue.INACTIVE, RdapStatus.INACTIVE)
|
||||
.put(StatusValue.LINKED, RdapStatus.ASSOCIATED)
|
||||
.put(StatusValue.OK, RdapStatus.ACTIVE)
|
||||
.put(StatusValue.PENDING_CREATE, RdapStatus.PENDING_CREATE)
|
||||
.put(StatusValue.PENDING_DELETE, RdapStatus.PENDING_DELETE)
|
||||
.put(StatusValue.PENDING_TRANSFER, RdapStatus.PENDING_TRANSFER)
|
||||
.put(StatusValue.PENDING_UPDATE, RdapStatus.PENDING_UPDATE)
|
||||
.put(StatusValue.SERVER_DELETE_PROHIBITED, RdapStatus.DELETE_PROHIBITED)
|
||||
.put(StatusValue.SERVER_HOLD, RdapStatus.INACTIVE)
|
||||
.put(StatusValue.SERVER_RENEW_PROHIBITED, RdapStatus.RENEW_PROHIBITED)
|
||||
.put(StatusValue.SERVER_TRANSFER_PROHIBITED, RdapStatus.TRANSFER_PROHIBITED)
|
||||
.put(StatusValue.SERVER_UPDATE_PROHIBITED, RdapStatus.UPDATE_PROHIBITED)
|
||||
.build());
|
||||
|
||||
/** Role values specified in RFC 7483 § 10.2.4. */
|
||||
private enum RdapEntityRole {
|
||||
REGISTRANT("registrant"),
|
||||
TECH("technical"),
|
||||
ADMIN("administrative"),
|
||||
ABUSE("abuse"),
|
||||
BILLING("billing"),
|
||||
REGISTRAR("registrar"),
|
||||
RESELLER("reseller"),
|
||||
SPONSOR("sponsor"),
|
||||
PROXY("proxy"),
|
||||
NOTIFICATIONS("notifications"),
|
||||
NOC("noc");
|
||||
|
||||
/** Value as it appears in RDAP messages. */
|
||||
final String rfc7483String;
|
||||
|
||||
private RdapEntityRole(String rfc7483String) {
|
||||
this.rfc7483String = rfc7483String;
|
||||
}
|
||||
}
|
||||
|
||||
/** Status values specified in RFC 7483 § 10.2.2. */
|
||||
private enum RdapEventAction {
|
||||
REGISTRATION("registration"),
|
||||
REREGISTRATION("reregistration"),
|
||||
LAST_CHANGED("last changed"),
|
||||
EXPIRATION("expiration"),
|
||||
DELETION("deletion"),
|
||||
REINSTANTIATION("reinstantiation"),
|
||||
TRANSFER("transfer"),
|
||||
LOCKED("locked"),
|
||||
UNLOCKED("unlocked");
|
||||
|
||||
/** Value as it appears in RDAP messages. */
|
||||
private final String rfc7483String;
|
||||
|
||||
private RdapEventAction(String rfc7483String) {
|
||||
this.rfc7483String = rfc7483String;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return rfc7483String;
|
||||
}
|
||||
}
|
||||
|
||||
/** Map of EPP status values to the RDAP equivalents. */
|
||||
private static final ImmutableMap<HistoryEntry.Type, RdapEventAction>
|
||||
historyEntryTypeToRdapEventActionMap =
|
||||
Maps.immutableEnumMap(
|
||||
new ImmutableMap.Builder<HistoryEntry.Type, RdapEventAction>()
|
||||
.put(HistoryEntry.Type.CONTACT_CREATE, RdapEventAction.REGISTRATION)
|
||||
.put(HistoryEntry.Type.CONTACT_DELETE, RdapEventAction.DELETION)
|
||||
.put(HistoryEntry.Type.CONTACT_TRANSFER_APPROVE, RdapEventAction.TRANSFER)
|
||||
.put(HistoryEntry.Type.DOMAIN_APPLICATION_CREATE, RdapEventAction.REGISTRATION)
|
||||
.put(HistoryEntry.Type.DOMAIN_APPLICATION_DELETE, RdapEventAction.DELETION)
|
||||
.put(HistoryEntry.Type.DOMAIN_CREATE, RdapEventAction.REGISTRATION)
|
||||
.put(HistoryEntry.Type.DOMAIN_DELETE, RdapEventAction.DELETION)
|
||||
.put(HistoryEntry.Type.DOMAIN_RENEW, RdapEventAction.REREGISTRATION)
|
||||
.put(HistoryEntry.Type.DOMAIN_RESTORE, RdapEventAction.REINSTANTIATION)
|
||||
.put(HistoryEntry.Type.DOMAIN_TRANSFER_APPROVE, RdapEventAction.TRANSFER)
|
||||
.put(HistoryEntry.Type.HOST_CREATE, RdapEventAction.REGISTRATION)
|
||||
.put(HistoryEntry.Type.HOST_DELETE, RdapEventAction.DELETION)
|
||||
.build());
|
||||
|
||||
private static final ImmutableList<String> CONFORMANCE_LIST =
|
||||
ImmutableList.of(RDAP_CONFORMANCE_LEVEL);
|
||||
|
||||
private static final ImmutableList<String> STATUS_LIST_ACTIVE =
|
||||
ImmutableList.of(RdapStatus.ACTIVE.rfc7483String);
|
||||
private static final ImmutableMap<String, ImmutableList<String>> PHONE_TYPE_VOICE =
|
||||
ImmutableMap.of("type", ImmutableList.of("voice"));
|
||||
private static final ImmutableMap<String, ImmutableList<String>> PHONE_TYPE_FAX =
|
||||
ImmutableMap.of("type", ImmutableList.of("fax"));
|
||||
private static final ImmutableList<?> VCARD_ENTRY_VERSION =
|
||||
ImmutableList.of("version", ImmutableMap.of(), "text", VCARD_VERSION_NUMBER);
|
||||
|
||||
/** Sets the ordering for hosts; just use the fully qualified host name. */
|
||||
private static final Ordering<HostResource> HOST_RESOURCE_ORDERING =
|
||||
Ordering.natural().onResultOf(new Function<HostResource, String>() {
|
||||
@Override
|
||||
public String apply(HostResource host) {
|
||||
return host.getFullyQualifiedHostName();
|
||||
}});
|
||||
|
||||
/** Sets the ordering for designated contacts; order them in a fixed order by contact type. */
|
||||
private static final Ordering<DesignatedContact> DESIGNATED_CONTACT_ORDERING =
|
||||
Ordering.natural().onResultOf(new Function<DesignatedContact, DesignatedContact.Type>() {
|
||||
@Override
|
||||
public DesignatedContact.Type apply(DesignatedContact designatedContact) {
|
||||
return designatedContact.getType();
|
||||
}});
|
||||
|
||||
/**
|
||||
* Adds the required top-level boilerplate. RFC 7483 specifies that the top-level object should
|
||||
* include an entry indicating the conformance level. The ICANN RDAP Profile document (dated 3
|
||||
* December 2015) mandates several additional entries, in sections 1.4.4, 1.4.10, 1.5.18 and
|
||||
* 1.5.20. Note that this method will only work if there are no object-specific remarks already in
|
||||
* the JSON object being built. If there are, the boilerplate must be merged in.
|
||||
*
|
||||
* @param builder a builder for a JSON map object
|
||||
* @param boilerplateType type of boilerplate to be added; the ICANN RDAP Profile document
|
||||
* mandates extra boilerplate for domain objects
|
||||
* @param notices a list of notices to be inserted before the boilerplate notices. If the TOS
|
||||
* notice is in this list, the method avoids adding a second copy.
|
||||
* @param rdapLinkBase the base for link URLs
|
||||
*/
|
||||
static void addTopLevelEntries(
|
||||
ImmutableMap.Builder<String, Object> builder,
|
||||
BoilerplateType boilerplateType,
|
||||
@Nullable Iterable<ImmutableMap<String, Object>> notices,
|
||||
String rdapLinkBase) {
|
||||
builder.put("rdapConformance", CONFORMANCE_LIST);
|
||||
ImmutableList.Builder<ImmutableMap<String, Object>> noticesBuilder = ImmutableList.builder();
|
||||
ImmutableMap<String, Object> tosNotice =
|
||||
RdapHelpAction.getJsonHelpNotice(RdapHelpAction.TERMS_OF_SERVICE_PATH, rdapLinkBase);
|
||||
boolean tosNoticeFound = false;
|
||||
if (notices != null) {
|
||||
noticesBuilder.addAll(notices);
|
||||
for (ImmutableMap<String, Object> notice : notices) {
|
||||
if (notice.equals(tosNotice)) {
|
||||
tosNoticeFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!tosNoticeFound) {
|
||||
noticesBuilder.add(tosNotice);
|
||||
}
|
||||
builder.put(NOTICES, noticesBuilder.build());
|
||||
switch (boilerplateType) {
|
||||
case DOMAIN:
|
||||
builder.put(REMARKS, RdapIcannStandardInformation.domainBoilerplateRemarks);
|
||||
break;
|
||||
case NAMESERVER:
|
||||
case ENTITY:
|
||||
builder.put(REMARKS, RdapIcannStandardInformation.nameserverAndEntityBoilerplateRemarks);
|
||||
break;
|
||||
default: // things other than domains, nameservers and entities cannot contain remarks
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/** AutoValue class to build parameters to {@link #makeRdapJsonNotice}. */
|
||||
@AutoValue
|
||||
abstract static class MakeRdapJsonNoticeParameters {
|
||||
static Builder builder() {
|
||||
return new AutoValue_RdapJsonFormatter_MakeRdapJsonNoticeParameters.Builder();
|
||||
}
|
||||
|
||||
@Nullable abstract String title();
|
||||
abstract ImmutableList<String> description();
|
||||
@Nullable abstract String typeString();
|
||||
@Nullable abstract String linkValueSuffix();
|
||||
@Nullable abstract String linkHrefUrlString();
|
||||
|
||||
@AutoValue.Builder
|
||||
abstract static class Builder {
|
||||
abstract Builder title(@Nullable String title);
|
||||
abstract Builder description(Iterable<String> description);
|
||||
abstract Builder typeString(@Nullable String typeString);
|
||||
abstract Builder linkValueSuffix(@Nullable String linkValueSuffix);
|
||||
abstract Builder linkHrefUrlString(@Nullable String linkHrefUrlString);
|
||||
|
||||
abstract MakeRdapJsonNoticeParameters build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a JSON object containing a notice or remark object, as defined by RFC 7483 § 4.3.
|
||||
* The object should then be inserted into a notices or remarks array. The builder fields are:
|
||||
*
|
||||
* <p>title: the title of the notice; if null, the notice will have no title
|
||||
*
|
||||
* <p>description: objects which will be converted to strings to form the description of the
|
||||
* notice (this is the only required field; all others are optional)
|
||||
*
|
||||
* <p>typeString: the notice or remark type as defined in § 10.2.1; if null, no type
|
||||
*
|
||||
* <p>linkValueSuffix: the path at the end of the URL used in the value field of the link,
|
||||
* without any initial slash (e.g. a suffix of help/toc equates to a URL of
|
||||
* http://example.net/help/toc); if null, no link is created; if it is not null, a single link is
|
||||
* created; this method never creates more than one link)
|
||||
*
|
||||
* <p>htmlUrlString: the path, if any, to be used in the href value of the link; if the URL is
|
||||
* absolute, it is used as is; if it is relative, starting with a slash, it is appended to the
|
||||
* protocol and host of the link base; if it is relative, not starting with a slash, it is
|
||||
* appended to the complete link base; if null, a self link is generated instead, using the link
|
||||
* link value
|
||||
*
|
||||
* <p>linkBase: the base for the link value and href; if null, it is assumed to be the empty
|
||||
* string
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc7483">
|
||||
* RFC 7483: JSON Responses for the Registration Data Access Protocol (RDAP)</a>
|
||||
*/
|
||||
static ImmutableMap<String, Object> makeRdapJsonNotice(
|
||||
MakeRdapJsonNoticeParameters parameters, @Nullable String linkBase) {
|
||||
ImmutableMap.Builder<String, Object> builder = new ImmutableMap.Builder<>();
|
||||
if (parameters.title() != null) {
|
||||
builder.put("title", parameters.title());
|
||||
}
|
||||
ImmutableList.Builder<String> descriptionBuilder = new ImmutableList.Builder<>();
|
||||
for (String line : parameters.description()) {
|
||||
descriptionBuilder.add(nullToEmpty(line));
|
||||
}
|
||||
builder.put("description", descriptionBuilder.build());
|
||||
if (parameters.typeString() != null) {
|
||||
builder.put("typeString", parameters.typeString());
|
||||
}
|
||||
String linkValueString =
|
||||
nullToEmpty(linkBase) + nullToEmpty(parameters.linkValueSuffix());
|
||||
if (parameters.linkHrefUrlString() == null) {
|
||||
builder.put("links", ImmutableList.of(ImmutableMap.of(
|
||||
"value", linkValueString,
|
||||
"rel", "self",
|
||||
"href", linkValueString,
|
||||
"type", "application/rdap+json")));
|
||||
} else {
|
||||
URI htmlBaseURI = URI.create(nullToEmpty(linkBase));
|
||||
URI htmlUri = htmlBaseURI.resolve(parameters.linkHrefUrlString());
|
||||
builder.put("links", ImmutableList.of(ImmutableMap.of(
|
||||
"value", linkValueString,
|
||||
"rel", "alternate",
|
||||
"href", htmlUri.toString(),
|
||||
"type", "text/html")));
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a JSON object for a {@link DomainResource}.
|
||||
*
|
||||
* @param domainResource the domain resource object from which the JSON object should be created
|
||||
* @param linkBase the URL base to be used when creating links
|
||||
* @param whoisServer the fully-qualified domain name of the WHOIS server to be listed in the
|
||||
* port43 field; if null, port43 is not added to the object
|
||||
*/
|
||||
static ImmutableMap<String, Object> makeRdapJsonForDomain(
|
||||
DomainResource domainResource,
|
||||
boolean isTopLevel,
|
||||
@Nullable String linkBase,
|
||||
@Nullable String whoisServer) {
|
||||
// Kick off the database loads of the nameservers that we will need.
|
||||
Set<Ref<HostResource>> hostRefs = new LinkedHashSet<>();
|
||||
for (ReferenceUnion<HostResource> hostReference : domainResource.getNameservers()) {
|
||||
hostRefs.add(hostReference.getLinked());
|
||||
}
|
||||
Map<Key<HostResource>, HostResource> loadedHosts = ofy().load().refs(hostRefs);
|
||||
// And the registrant and other contacts.
|
||||
List<DesignatedContact> allContacts = new ArrayList<>();
|
||||
if (domainResource.getRegistrant() != null) {
|
||||
allContacts.add(DesignatedContact.create(Type.REGISTRANT, domainResource.getRegistrant()));
|
||||
}
|
||||
allContacts.addAll(domainResource.getContacts());
|
||||
Set<Ref<ContactResource>> contactRefs = new LinkedHashSet<>();
|
||||
for (DesignatedContact designatedContact : allContacts) {
|
||||
contactRefs.add(designatedContact.getContactId().getLinked());
|
||||
}
|
||||
Map<Key<ContactResource>, ContactResource> loadedContacts = ofy().load().refs(contactRefs);
|
||||
// Now, assemble the results, using the loaded objects as needed.
|
||||
ImmutableMap.Builder<String, Object> builder = new ImmutableMap.Builder<>();
|
||||
builder.put("objectClassName", "domain");
|
||||
builder.put("handle", domainResource.getRepoId());
|
||||
builder.put("ldhName", domainResource.getFullyQualifiedDomainName());
|
||||
// Only include the unicodeName field if there are unicode characters.
|
||||
if (hasUnicodeComponents(domainResource.getFullyQualifiedDomainName())) {
|
||||
builder.put("unicodeName", Idn.toUnicode(domainResource.getFullyQualifiedDomainName()));
|
||||
}
|
||||
builder.put("status", makeStatusValueList(domainResource.getStatusValues()));
|
||||
builder.put("links", ImmutableList.of(
|
||||
makeLink("domain", domainResource.getFullyQualifiedDomainName(), linkBase)));
|
||||
ImmutableList<Object> events = makeEvents(domainResource);
|
||||
if (!events.isEmpty()) {
|
||||
builder.put("events", events);
|
||||
}
|
||||
// Nameservers
|
||||
ImmutableList.Builder<Object> nsBuilder = new ImmutableList.Builder<>();
|
||||
for (HostResource hostResource
|
||||
: HOST_RESOURCE_ORDERING.immutableSortedCopy(loadedHosts.values())) {
|
||||
nsBuilder.add(makeRdapJsonForHost(hostResource, false, linkBase, null));
|
||||
}
|
||||
ImmutableList<Object> ns = nsBuilder.build();
|
||||
if (!ns.isEmpty()) {
|
||||
builder.put("nameservers", ns);
|
||||
}
|
||||
// Contacts
|
||||
ImmutableList.Builder<Object> entitiesBuilder = new ImmutableList.Builder<>();
|
||||
for (DesignatedContact designatedContact
|
||||
: DESIGNATED_CONTACT_ORDERING.immutableSortedCopy(allContacts)) {
|
||||
ContactResource loadedContact =
|
||||
loadedContacts.get(designatedContact.getContactId().getLinked().key());
|
||||
entitiesBuilder.add(makeRdapJsonForContact(
|
||||
loadedContact, false, Optional.of(designatedContact.getType()), linkBase, null));
|
||||
}
|
||||
ImmutableList<Object> entities = entitiesBuilder.build();
|
||||
if (!entities.isEmpty()) {
|
||||
builder.put("entities", entities);
|
||||
}
|
||||
if (whoisServer != null) {
|
||||
builder.put("port43", whoisServer);
|
||||
}
|
||||
if (isTopLevel) {
|
||||
addTopLevelEntries(builder, BoilerplateType.DOMAIN, null, linkBase);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a JSON object for a {@link HostResource}.
|
||||
*
|
||||
* @param hostResource the host resource object from which the JSON object should be created
|
||||
* @param linkBase the URL base to be used when creating links
|
||||
* @param whoisServer the fully-qualified domain name of the WHOIS server to be listed in the
|
||||
* port43 field; if null, port43 is not added to the object
|
||||
*/
|
||||
static ImmutableMap<String, Object> makeRdapJsonForHost(
|
||||
HostResource hostResource,
|
||||
boolean isTopLevel,
|
||||
@Nullable String linkBase,
|
||||
@Nullable String whoisServer) {
|
||||
ImmutableMap.Builder<String, Object> builder = new ImmutableMap.Builder<>();
|
||||
builder.put("objectClassName", "nameserver");
|
||||
builder.put("handle", hostResource.getRepoId());
|
||||
builder.put("ldhName", hostResource.getFullyQualifiedHostName());
|
||||
// Only include the unicodeName field if there are unicode characters.
|
||||
if (hasUnicodeComponents(hostResource.getFullyQualifiedHostName())) {
|
||||
builder.put("unicodeName", Idn.toUnicode(hostResource.getFullyQualifiedHostName()));
|
||||
}
|
||||
builder.put("status", makeStatusValueList(hostResource.getStatusValues()));
|
||||
builder.put("links", ImmutableList.of(
|
||||
makeLink("nameserver", hostResource.getFullyQualifiedHostName(), linkBase)));
|
||||
ImmutableList<Object> events = makeEvents(hostResource);
|
||||
if (!events.isEmpty()) {
|
||||
builder.put("events", events);
|
||||
}
|
||||
ImmutableSet<InetAddress> inetAddresses = hostResource.getInetAddresses();
|
||||
if (!inetAddresses.isEmpty()) {
|
||||
ImmutableList.Builder<String> v4AddressesBuilder = new ImmutableList.Builder<>();
|
||||
ImmutableList.Builder<String> v6AddressesBuilder = new ImmutableList.Builder<>();
|
||||
for (InetAddress inetAddress : inetAddresses) {
|
||||
if (inetAddress instanceof Inet4Address) {
|
||||
v4AddressesBuilder.add(InetAddresses.toAddrString(inetAddress));
|
||||
} else if (inetAddress instanceof Inet6Address) {
|
||||
v6AddressesBuilder.add(InetAddresses.toAddrString(inetAddress));
|
||||
}
|
||||
}
|
||||
ImmutableMap.Builder<String, ImmutableList<String>> ipAddressesBuilder =
|
||||
new ImmutableMap.Builder<>();
|
||||
ImmutableList<String> v4Addresses = v4AddressesBuilder.build();
|
||||
if (!v4Addresses.isEmpty()) {
|
||||
ipAddressesBuilder.put("v4", v4Addresses);
|
||||
}
|
||||
ImmutableList<String> v6Addresses = v6AddressesBuilder.build();
|
||||
if (!v6Addresses.isEmpty()) {
|
||||
ipAddressesBuilder.put("v6", v6Addresses);
|
||||
}
|
||||
ImmutableMap<String, ImmutableList<String>> ipAddresses = ipAddressesBuilder.build();
|
||||
if (!ipAddresses.isEmpty()) {
|
||||
builder.put("ipAddresses", ipAddressesBuilder.build());
|
||||
}
|
||||
}
|
||||
if (whoisServer != null) {
|
||||
builder.put("port43", whoisServer);
|
||||
}
|
||||
if (isTopLevel) {
|
||||
addTopLevelEntries(builder, BoilerplateType.NAMESERVER, null, linkBase);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a JSON object for a {@link ContactResource} and associated contact type.
|
||||
*
|
||||
* @param contactResource the contact resource object from which the JSON object should be created
|
||||
* @param contactType the contact type to map to an RDAP role; if absent, no role is listed
|
||||
* @param linkBase the URL base to be used when creating links
|
||||
* @param whoisServer the fully-qualified domain name of the WHOIS server to be listed in the
|
||||
* port43 field; if null, port43 is not added to the object
|
||||
*/
|
||||
static ImmutableMap<String, Object> makeRdapJsonForContact(
|
||||
ContactResource contactResource,
|
||||
boolean isTopLevel,
|
||||
Optional<DesignatedContact.Type> contactType,
|
||||
@Nullable String linkBase,
|
||||
@Nullable String whoisServer) {
|
||||
ImmutableMap.Builder<String, Object> builder = new ImmutableMap.Builder<>();
|
||||
builder.put("objectClassName", "entity");
|
||||
builder.put("handle", contactResource.getRepoId());
|
||||
builder.put("status", makeStatusValueList(contactResource.getStatusValues()));
|
||||
if (contactType.isPresent()) {
|
||||
builder.put("roles", ImmutableList.of(convertContactTypeToRdapRole(contactType.get())));
|
||||
}
|
||||
builder.put("links",
|
||||
ImmutableList.of(makeLink("entity", contactResource.getRepoId(), linkBase)));
|
||||
// Create the vCard.
|
||||
ImmutableList.Builder<Object> vcardBuilder = new ImmutableList.Builder<>();
|
||||
vcardBuilder.add(VCARD_ENTRY_VERSION);
|
||||
PostalInfo postalInfo = contactResource.getInternationalizedPostalInfo();
|
||||
if (postalInfo == null) {
|
||||
postalInfo = contactResource.getLocalizedPostalInfo();
|
||||
}
|
||||
if (postalInfo != null) {
|
||||
if (postalInfo.getName() != null) {
|
||||
vcardBuilder.add(ImmutableList.of("fn", ImmutableMap.of(), "text", postalInfo.getName()));
|
||||
}
|
||||
if (postalInfo.getOrg() != null) {
|
||||
vcardBuilder.add(ImmutableList.of("org", ImmutableMap.of(), "text", postalInfo.getOrg()));
|
||||
}
|
||||
ImmutableList<Object> addressEntry = makeVCardAddressEntry(postalInfo.getAddress());
|
||||
if (addressEntry != null) {
|
||||
vcardBuilder.add(addressEntry);
|
||||
}
|
||||
}
|
||||
ContactPhoneNumber voicePhoneNumber = contactResource.getVoiceNumber();
|
||||
if (voicePhoneNumber != null) {
|
||||
vcardBuilder.add(makePhoneEntry(PHONE_TYPE_VOICE, makePhoneString(voicePhoneNumber)));
|
||||
}
|
||||
ContactPhoneNumber faxPhoneNumber = contactResource.getFaxNumber();
|
||||
if (faxPhoneNumber != null) {
|
||||
vcardBuilder.add(makePhoneEntry(PHONE_TYPE_FAX, makePhoneString(faxPhoneNumber)));
|
||||
}
|
||||
String emailAddress = contactResource.getEmailAddress();
|
||||
if (emailAddress != null) {
|
||||
vcardBuilder.add(ImmutableList.of("email", ImmutableMap.of(), "text", emailAddress));
|
||||
}
|
||||
builder.put("vcardArray", ImmutableList.of("vcard", vcardBuilder.build()));
|
||||
ImmutableList<Object> events = makeEvents(contactResource);
|
||||
if (!events.isEmpty()) {
|
||||
builder.put("events", events);
|
||||
}
|
||||
if (whoisServer != null) {
|
||||
builder.put("port43", whoisServer);
|
||||
}
|
||||
if (isTopLevel) {
|
||||
addTopLevelEntries(builder, BoilerplateType.ENTITY, null, linkBase);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a JSON object for a {@link Registrar}.
|
||||
*
|
||||
* @param registrar the registrar object from which the JSON object should be created
|
||||
* @param linkBase the URL base to be used when creating links
|
||||
* @param whoisServer the fully-qualified domain name of the WHOIS server to be listed in the
|
||||
* port43 field; if null, port43 is not added to the object
|
||||
*/
|
||||
static ImmutableMap<String, Object> makeRdapJsonForRegistrar(
|
||||
Registrar registrar,
|
||||
boolean isTopLevel,
|
||||
@Nullable String linkBase,
|
||||
@Nullable String whoisServer) {
|
||||
ImmutableMap.Builder<String, Object> builder = new ImmutableMap.Builder<>();
|
||||
builder.put("objectClassName", "entity");
|
||||
builder.put("handle", registrar.getClientIdentifier());
|
||||
builder.put("status", STATUS_LIST_ACTIVE);
|
||||
builder.put("roles", ImmutableList.of(RdapEntityRole.REGISTRAR.rfc7483String));
|
||||
builder.put("links",
|
||||
ImmutableList.of(makeLink("entity", registrar.getClientIdentifier(), linkBase)));
|
||||
builder.put("publicIds",
|
||||
ImmutableList.of(
|
||||
ImmutableMap.of(
|
||||
"type", "IANA Registrar ID",
|
||||
"identifier", registrar.getIanaIdentifier().toString())));
|
||||
// Create the vCard.
|
||||
ImmutableList.Builder<Object> vcardBuilder = new ImmutableList.Builder<>();
|
||||
vcardBuilder.add(VCARD_ENTRY_VERSION);
|
||||
String registrarName = registrar.getRegistrarName();
|
||||
if (registrarName != null) {
|
||||
vcardBuilder.add(ImmutableList.of("fn", ImmutableMap.of(), "text", registrarName));
|
||||
}
|
||||
RegistrarAddress address = registrar.getInternationalizedAddress();
|
||||
if (address == null) {
|
||||
address = registrar.getLocalizedAddress();
|
||||
}
|
||||
if (address != null) {
|
||||
ImmutableList<Object> addressEntry = makeVCardAddressEntry(address);
|
||||
if (addressEntry != null) {
|
||||
vcardBuilder.add(addressEntry);
|
||||
}
|
||||
}
|
||||
String voicePhoneNumber = registrar.getPhoneNumber();
|
||||
if (voicePhoneNumber != null) {
|
||||
vcardBuilder.add(makePhoneEntry(PHONE_TYPE_VOICE, "tel:" + voicePhoneNumber));
|
||||
}
|
||||
String faxPhoneNumber = registrar.getFaxNumber();
|
||||
if (faxPhoneNumber != null) {
|
||||
vcardBuilder.add(makePhoneEntry(PHONE_TYPE_FAX, "tel:" + faxPhoneNumber));
|
||||
}
|
||||
String emailAddress = registrar.getEmailAddress();
|
||||
if (emailAddress != null) {
|
||||
vcardBuilder.add(ImmutableList.of("email", ImmutableMap.of(), "text", emailAddress));
|
||||
}
|
||||
builder.put("vcardArray", ImmutableList.of("vcard", vcardBuilder.build()));
|
||||
ImmutableList<Object> events = makeEvents(registrar);
|
||||
if (!events.isEmpty()) {
|
||||
builder.put("events", events);
|
||||
}
|
||||
// include the registrar contacts as subentities
|
||||
ImmutableList.Builder<Map<String, Object>> registrarContactsBuilder =
|
||||
new ImmutableList.Builder<>();
|
||||
for (RegistrarContact registrarContact : registrar.getContacts()) {
|
||||
if (isVisible(registrarContact)) {
|
||||
registrarContactsBuilder.add(makeRdapJsonForRegistrarContact(registrarContact, null));
|
||||
}
|
||||
}
|
||||
ImmutableList<Map<String, Object>> registrarContacts = registrarContactsBuilder.build();
|
||||
if (!registrarContacts.isEmpty()) {
|
||||
builder.put("entities", registrarContacts);
|
||||
}
|
||||
if (whoisServer != null) {
|
||||
builder.put("port43", whoisServer);
|
||||
}
|
||||
if (isTopLevel) {
|
||||
addTopLevelEntries(builder, BoilerplateType.ENTITY, null, linkBase);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a JSON object for a {@link RegistrarContact}.
|
||||
*
|
||||
* @param registrarContact the registrar contact for which the JSON object should be created
|
||||
* @param whoisServer the fully-qualified domain name of the WHOIS server to be listed in the
|
||||
* port43 field; if null, port43 is not added to the object
|
||||
*/
|
||||
static ImmutableMap<String, Object> makeRdapJsonForRegistrarContact(
|
||||
RegistrarContact registrarContact, @Nullable String whoisServer) {
|
||||
ImmutableMap.Builder<String, Object> builder = new ImmutableMap.Builder<>();
|
||||
builder.put("objectClassName", "entity");
|
||||
String gaeUserId = registrarContact.getGaeUserId();
|
||||
if (gaeUserId != null) {
|
||||
builder.put("handle", registrarContact.getGaeUserId());
|
||||
}
|
||||
builder.put("status", STATUS_LIST_ACTIVE);
|
||||
builder.put("roles", makeRdapRoleList(registrarContact));
|
||||
// Create the vCard.
|
||||
ImmutableList.Builder<Object> vcardBuilder = new ImmutableList.Builder<>();
|
||||
vcardBuilder.add(VCARD_ENTRY_VERSION);
|
||||
String name = registrarContact.getName();
|
||||
if (name != null) {
|
||||
vcardBuilder.add(ImmutableList.of("fn", ImmutableMap.of(), "text", name));
|
||||
}
|
||||
String voicePhoneNumber = registrarContact.getPhoneNumber();
|
||||
if (voicePhoneNumber != null) {
|
||||
vcardBuilder.add(makePhoneEntry(PHONE_TYPE_VOICE, "tel:" + voicePhoneNumber));
|
||||
}
|
||||
String faxPhoneNumber = registrarContact.getFaxNumber();
|
||||
if (faxPhoneNumber != null) {
|
||||
vcardBuilder.add(makePhoneEntry(PHONE_TYPE_FAX, "tel:" + faxPhoneNumber));
|
||||
}
|
||||
String emailAddress = registrarContact.getEmailAddress();
|
||||
if (emailAddress != null) {
|
||||
vcardBuilder.add(ImmutableList.of("email", ImmutableMap.of(), "text", emailAddress));
|
||||
}
|
||||
builder.put("vcardArray", ImmutableList.of("vcard", vcardBuilder.build()));
|
||||
if (whoisServer != null) {
|
||||
builder.put("port43", whoisServer);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/** Converts a domain registry contact type into a role as defined by RFC 7483. */
|
||||
private static String convertContactTypeToRdapRole(DesignatedContact.Type contactType) {
|
||||
switch (contactType) {
|
||||
case REGISTRANT:
|
||||
return RdapEntityRole.REGISTRANT.rfc7483String;
|
||||
case TECH:
|
||||
return RdapEntityRole.TECH.rfc7483String;
|
||||
case BILLING:
|
||||
return RdapEntityRole.BILLING.rfc7483String;
|
||||
case ADMIN:
|
||||
return RdapEntityRole.ADMIN.rfc7483String;
|
||||
default:
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the list of RDAP roles for a registrar contact, using the visibleInWhoisAs* flags.
|
||||
*/
|
||||
private static ImmutableList<String> makeRdapRoleList(RegistrarContact registrarContact) {
|
||||
ImmutableList.Builder<String> rolesBuilder = new ImmutableList.Builder<>();
|
||||
if (registrarContact.getVisibleInWhoisAsAdmin()) {
|
||||
rolesBuilder.add(RdapEntityRole.ADMIN.rfc7483String);
|
||||
}
|
||||
if (registrarContact.getVisibleInWhoisAsTech()) {
|
||||
rolesBuilder.add(RdapEntityRole.TECH.rfc7483String);
|
||||
}
|
||||
return rolesBuilder.build();
|
||||
}
|
||||
|
||||
/** Checks whether the registrar contact should be visible (because it has visible roles). */
|
||||
private static boolean isVisible(RegistrarContact registrarContact) {
|
||||
return registrarContact.getVisibleInWhoisAsAdmin()
|
||||
|| registrarContact.getVisibleInWhoisAsTech();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an event list for a domain, host or contact resource.
|
||||
*/
|
||||
private static ImmutableList<Object> makeEvents(EppResource resource) {
|
||||
ImmutableList.Builder<Object> eventsBuilder = new ImmutableList.Builder<>();
|
||||
for (HistoryEntry historyEntry : ofy().load()
|
||||
.type(HistoryEntry.class)
|
||||
.ancestor(resource)
|
||||
.order("modificationTime")) {
|
||||
// Only create an event if this is a type we care about.
|
||||
if (!historyEntryTypeToRdapEventActionMap.containsKey(historyEntry.getType())) {
|
||||
continue;
|
||||
}
|
||||
RdapEventAction eventAction =
|
||||
historyEntryTypeToRdapEventActionMap.get(historyEntry.getType());
|
||||
eventsBuilder.add(makeEvent(
|
||||
eventAction, historyEntry.getClientId(), historyEntry.getModificationTime()));
|
||||
}
|
||||
if (resource instanceof DomainResource) {
|
||||
DateTime expirationTime = ((DomainResource) resource).getRegistrationExpirationTime();
|
||||
if (expirationTime != null) {
|
||||
eventsBuilder.add(makeEvent(RdapEventAction.EXPIRATION, null, expirationTime));
|
||||
}
|
||||
}
|
||||
if ((resource.getLastEppUpdateTime() != null)
|
||||
&& resource.getLastEppUpdateTime().isAfter(resource.getCreationTime())) {
|
||||
eventsBuilder.add(makeEvent(
|
||||
RdapEventAction.LAST_CHANGED, null, resource.getLastEppUpdateTime()));
|
||||
}
|
||||
return eventsBuilder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an event list for a {@link Registrar}.
|
||||
*/
|
||||
private static ImmutableList<Object> makeEvents(Registrar registrar) {
|
||||
ImmutableList.Builder<Object> eventsBuilder = new ImmutableList.Builder<>();
|
||||
eventsBuilder.add(makeEvent(
|
||||
RdapEventAction.REGISTRATION,
|
||||
registrar.getClientIdentifier(),
|
||||
registrar.getCreationTime()));
|
||||
if ((registrar.getLastUpdateTime() != null)
|
||||
&& registrar.getLastUpdateTime().isAfter(registrar.getCreationTime())) {
|
||||
eventsBuilder.add(makeEvent(
|
||||
RdapEventAction.LAST_CHANGED, null, registrar.getLastUpdateTime()));
|
||||
}
|
||||
return eventsBuilder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an RDAP event object as defined by RFC 7483.
|
||||
*/
|
||||
private static ImmutableMap<String, Object> makeEvent(
|
||||
RdapEventAction eventAction, @Nullable String eventActor, DateTime eventDate) {
|
||||
ImmutableMap.Builder<String, Object> builder = new ImmutableMap.Builder<>();
|
||||
builder.put("eventAction", eventAction.toString());
|
||||
if (eventActor != null) {
|
||||
builder.put("eventActor", eventActor);
|
||||
}
|
||||
builder.put("eventDate", eventDate.toString());
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a vCard address entry: array of strings specifying the components of the address.
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc7095">
|
||||
* RFC 7095: jCard: The JSON Format for vCard</a>
|
||||
*/
|
||||
private static ImmutableList<Object> makeVCardAddressEntry(Address address) {
|
||||
if (address == null) {
|
||||
return null;
|
||||
}
|
||||
ImmutableList.Builder<Object> builder = new ImmutableList.Builder<>();
|
||||
builder.add(""); // PO box
|
||||
builder.add(""); // extended address
|
||||
ImmutableList<String> street = address.getStreet();
|
||||
if (street.isEmpty()) {
|
||||
builder.add("");
|
||||
} else if (street.size() == 1) {
|
||||
builder.add(street.get(0));
|
||||
} else {
|
||||
builder.add(street);
|
||||
}
|
||||
builder.add(nullToEmpty(address.getCity()));
|
||||
builder.add(nullToEmpty(address.getState()));
|
||||
builder.add(nullToEmpty(address.getZip()));
|
||||
builder.add(new Locale("en", address.getCountryCode()).getDisplayCountry(new Locale("en")));
|
||||
return ImmutableList.<Object>of(
|
||||
"adr",
|
||||
ImmutableMap.of(),
|
||||
"text",
|
||||
builder.build());
|
||||
}
|
||||
|
||||
/** Creates a vCard phone number entry. */
|
||||
private static ImmutableList<Object> makePhoneEntry(
|
||||
ImmutableMap<String, ImmutableList<String>> type, String phoneNumber) {
|
||||
return ImmutableList.<Object>of("tel", type, "uri", phoneNumber);
|
||||
}
|
||||
|
||||
/** Creates a phone string in URI format, as per the vCard spec. */
|
||||
private static String makePhoneString(ContactPhoneNumber phoneNumber) {
|
||||
String phoneString = String.format("tel:%s", phoneNumber.getPhoneNumber());
|
||||
if (phoneNumber.getExtension() != null) {
|
||||
phoneString = phoneString + ";ext=" + phoneNumber.getExtension();
|
||||
}
|
||||
return phoneString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a string array of status values; the spec indicates that OK should be listed as
|
||||
* "active".
|
||||
*/
|
||||
private static ImmutableList<String> makeStatusValueList(ImmutableSet<StatusValue> statusValues) {
|
||||
return FluentIterable
|
||||
.from(statusValues)
|
||||
.transform(Functions.forMap(statusToRdapStatusMap, RdapStatus.OBSCURED))
|
||||
.transform(Functions.toStringFunction())
|
||||
.toSortedSet(Ordering.natural())
|
||||
.asList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a self link as directed by the spec.
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc7483">
|
||||
* RFC 7483: JSON Responses for the Registration Data Access Protocol (RDAP)</a>
|
||||
*/
|
||||
private static ImmutableMap<String, String> makeLink(
|
||||
String type, String name, @Nullable String linkBase) {
|
||||
String url;
|
||||
if (linkBase == null) {
|
||||
url = type + '/' + name;
|
||||
} else if (linkBase.endsWith("/")) {
|
||||
url = linkBase + type + '/' + name;
|
||||
} else {
|
||||
url = linkBase + '/' + type + '/' + name;
|
||||
}
|
||||
return ImmutableMap.of(
|
||||
"value", url,
|
||||
"rel", "self",
|
||||
"href", url,
|
||||
"type", "application/rdap+json");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a JSON error indication.
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc7483">
|
||||
* RFC 7483: JSON Responses for the Registration Data Access Protocol (RDAP)</a>
|
||||
*/
|
||||
static ImmutableMap<String, Object> makeError(
|
||||
int status, String title, String description) {
|
||||
return ImmutableMap.<String, Object>of(
|
||||
"rdapConformance", CONFORMANCE_LIST,
|
||||
"lang", "en",
|
||||
"errorCode", (long) status,
|
||||
"title", title,
|
||||
"description", ImmutableList.of(description));
|
||||
}
|
||||
|
||||
private static boolean hasUnicodeComponents(String fullyQualifiedName) {
|
||||
return fullyQualifiedName.startsWith("xn--") || fullyQualifiedName.contains(".xn--");
|
||||
}
|
||||
}
|
67
java/google/registry/rdap/RdapModule.java
Normal file
67
java/google/registry/rdap/RdapModule.java
Normal file
|
@ -0,0 +1,67 @@
|
|||
// 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 com.google.domain.registry.rdap;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.domain.registry.request.Parameter;
|
||||
import com.google.domain.registry.request.RequestParameters;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
import java.net.InetAddress;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/** Dagger module for the RDAP package. */
|
||||
@Module
|
||||
public final class RdapModule {
|
||||
|
||||
@Provides
|
||||
@Parameter("name")
|
||||
static Optional<String> provideName(HttpServletRequest req) {
|
||||
return RequestParameters.extractOptionalParameter(req, "name");
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Parameter("nsLdhName")
|
||||
static Optional<String> provideNsLdhName(HttpServletRequest req) {
|
||||
return RequestParameters.extractOptionalParameter(req, "nsLdhName");
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Parameter("nsIp")
|
||||
static Optional<InetAddress> provideNsIp(HttpServletRequest req) {
|
||||
return RequestParameters.extractOptionalInetAddressParameter(req, "nsIp");
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Parameter("ip")
|
||||
static Optional<InetAddress> provideIp(HttpServletRequest req) {
|
||||
return RequestParameters.extractOptionalInetAddressParameter(req, "ip");
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Parameter("fn")
|
||||
static Optional<String> provideFn(HttpServletRequest req) {
|
||||
return RequestParameters.extractOptionalParameter(req, "fn");
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Parameter("handle")
|
||||
static Optional<String> provideHandle(HttpServletRequest req) {
|
||||
return RequestParameters.extractOptionalParameter(req, "handle");
|
||||
}
|
||||
}
|
64
java/google/registry/rdap/RdapNameserverAction.java
Normal file
64
java/google/registry/rdap/RdapNameserverAction.java
Normal file
|
@ -0,0 +1,64 @@
|
|||
// 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 com.google.domain.registry.rdap;
|
||||
|
||||
import static com.google.domain.registry.model.EppResourceUtils.loadByUniqueId;
|
||||
import static com.google.domain.registry.request.Action.Method.GET;
|
||||
import static com.google.domain.registry.request.Action.Method.HEAD;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.domain.registry.model.host.HostResource;
|
||||
import com.google.domain.registry.request.Action;
|
||||
import com.google.domain.registry.request.HttpException;
|
||||
import com.google.domain.registry.request.HttpException.NotFoundException;
|
||||
import com.google.domain.registry.util.Clock;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* RDAP (new WHOIS) action for nameserver requests.
|
||||
*/
|
||||
@Action(path = RdapNameserverAction.PATH, method = {GET, HEAD}, isPrefix = true)
|
||||
public class RdapNameserverAction extends RdapActionBase {
|
||||
|
||||
public static final String PATH = "/rdap/nameserver/";
|
||||
|
||||
@Inject Clock clock;
|
||||
@Inject RdapNameserverAction() {}
|
||||
|
||||
@Override
|
||||
public String getHumanReadableObjectTypeName() {
|
||||
return "nameserver";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getActionPath() {
|
||||
return PATH;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutableMap<String, Object> getJsonObjectForResource(
|
||||
String pathSearchString, boolean isHeadRequest, String linkBase) throws HttpException {
|
||||
pathSearchString = canonicalizeName(pathSearchString);
|
||||
// The RDAP syntax is /rdap/nameserver/ns1.mydomain.com.
|
||||
validateDomainName(pathSearchString);
|
||||
HostResource hostResource =
|
||||
loadByUniqueId(HostResource.class, pathSearchString, clock.nowUtc());
|
||||
if (hostResource == null) {
|
||||
throw new NotFoundException(pathSearchString + " not found");
|
||||
}
|
||||
return RdapJsonFormatter.makeRdapJsonForHost(hostResource, true, rdapLinkBase, rdapWhoisServer);
|
||||
}
|
||||
}
|
188
java/google/registry/rdap/RdapNameserverSearchAction.java
Normal file
188
java/google/registry/rdap/RdapNameserverSearchAction.java
Normal file
|
@ -0,0 +1,188 @@
|
|||
// 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 com.google.domain.registry.rdap;
|
||||
|
||||
import static com.google.domain.registry.model.EppResourceUtils.loadByUniqueId;
|
||||
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
|
||||
import static com.google.domain.registry.request.Action.Method.GET;
|
||||
import static com.google.domain.registry.request.Action.Method.HEAD;
|
||||
import static com.google.domain.registry.util.DateTimeUtils.END_OF_TIME;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSortedSet;
|
||||
import com.google.common.primitives.Booleans;
|
||||
import com.google.domain.registry.config.ConfigModule.Config;
|
||||
import com.google.domain.registry.model.domain.DomainResource;
|
||||
import com.google.domain.registry.model.host.HostResource;
|
||||
import com.google.domain.registry.rdap.RdapJsonFormatter.BoilerplateType;
|
||||
import com.google.domain.registry.request.Action;
|
||||
import com.google.domain.registry.request.HttpException;
|
||||
import com.google.domain.registry.request.HttpException.BadRequestException;
|
||||
import com.google.domain.registry.request.HttpException.NotFoundException;
|
||||
import com.google.domain.registry.request.Parameter;
|
||||
import com.google.domain.registry.util.Clock;
|
||||
import com.google.domain.registry.util.Idn;
|
||||
|
||||
import com.googlecode.objectify.cmd.Query;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
import java.net.InetAddress;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* RDAP (new WHOIS) action for nameserver search requests.
|
||||
*
|
||||
* <p>All commands and responses conform to the RDAP spec as defined in RFCs 7480 through 7485.
|
||||
*
|
||||
* @see <a href="http://tools.ietf.org/html/rfc7482">
|
||||
* RFC 7482: Registration Data Access Protocol (RDAP) Query Format</a>
|
||||
* @see <a href="http://tools.ietf.org/html/rfc7483">
|
||||
* RFC 7483: JSON Responses for the Registration Data Access Protocol (RDAP)</a>
|
||||
*/
|
||||
@Action(path = RdapNameserverSearchAction.PATH, method = {GET, HEAD}, isPrefix = true)
|
||||
public class RdapNameserverSearchAction extends RdapActionBase {
|
||||
|
||||
public static final String PATH = "/rdap/nameservers";
|
||||
|
||||
@Inject Clock clock;
|
||||
@Inject @Parameter("name") Optional<String> nameParam;
|
||||
@Inject @Parameter("ip") Optional<InetAddress> ipParam;
|
||||
@Inject @Config("rdapResultSetMaxSize") int rdapResultSetMaxSize;
|
||||
@Inject RdapNameserverSearchAction() {}
|
||||
|
||||
@Override
|
||||
public String getHumanReadableObjectTypeName() {
|
||||
return "nameserver search";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getActionPath() {
|
||||
return PATH;
|
||||
}
|
||||
|
||||
/** Parses the parameters and calls the appropriate search function. */
|
||||
@Override
|
||||
public ImmutableMap<String, Object> getJsonObjectForResource(
|
||||
String pathSearchString, boolean isHeadRequest, String linkBase) throws HttpException {
|
||||
DateTime now = clock.nowUtc();
|
||||
// RDAP syntax example: /rdap/nameservers?name=ns*.example.com.
|
||||
// The pathSearchString is not used by search commands.
|
||||
if (pathSearchString.length() > 0) {
|
||||
throw new BadRequestException("Unexpected path");
|
||||
}
|
||||
if (Booleans.countTrue(nameParam.isPresent(), ipParam.isPresent()) != 1) {
|
||||
throw new BadRequestException("You must specify either name=XXXX or ip=YYYY");
|
||||
}
|
||||
ImmutableList<ImmutableMap<String, Object>> results;
|
||||
if (nameParam.isPresent()) {
|
||||
// syntax: /rdap/nameservers?name=exam*.com
|
||||
if (!LDH_PATTERN.matcher(nameParam.get()).matches()) {
|
||||
throw new BadRequestException(
|
||||
"Name parameter must contain only letters, dots"
|
||||
+ " and hyphens, and an optional single wildcard");
|
||||
}
|
||||
results = searchByName(RdapSearchPattern.create(Idn.toASCII(nameParam.get()), true), now);
|
||||
} else {
|
||||
// syntax: /rdap/nameservers?ip=1.2.3.4
|
||||
results = searchByIp(ipParam.get());
|
||||
}
|
||||
if (results.isEmpty()) {
|
||||
throw new NotFoundException("No nameservers found");
|
||||
}
|
||||
ImmutableMap.Builder<String, Object> builder = new ImmutableMap.Builder<>();
|
||||
builder.put("nameserverSearchResults", results);
|
||||
RdapJsonFormatter.addTopLevelEntries(builder, BoilerplateType.NAMESERVER, null, rdapLinkBase);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/** Searches for nameservers by name, returning a JSON array of nameserver info maps. */
|
||||
private ImmutableList<ImmutableMap<String, Object>>
|
||||
searchByName(final RdapSearchPattern partialStringQuery, final DateTime now)
|
||||
throws HttpException {
|
||||
// Handle queries without a wildcard -- just load by foreign key.
|
||||
if (!partialStringQuery.getHasWildcard()) {
|
||||
HostResource hostResource =
|
||||
loadByUniqueId(HostResource.class, partialStringQuery.getInitialString(), now);
|
||||
if (hostResource == null) {
|
||||
throw new NotFoundException("No nameservers found");
|
||||
}
|
||||
return ImmutableList.of(
|
||||
RdapJsonFormatter.makeRdapJsonForHost(
|
||||
hostResource, false, rdapLinkBase, rdapWhoisServer));
|
||||
// Handle queries with a wildcard, but no suffix. There are no pending deletes for hosts, so we
|
||||
// can call queryUndeleted.
|
||||
} else if (partialStringQuery.getSuffix() == null) {
|
||||
Query<HostResource> query = queryUndeleted(
|
||||
HostResource.class,
|
||||
"fullyQualifiedHostName",
|
||||
partialStringQuery, rdapResultSetMaxSize);
|
||||
ImmutableList.Builder<ImmutableMap<String, Object>> builder = new ImmutableList.Builder<>();
|
||||
for (HostResource hostResource : query) {
|
||||
builder.add(
|
||||
RdapJsonFormatter.makeRdapJsonForHost(
|
||||
hostResource, false, rdapLinkBase, rdapWhoisServer));
|
||||
}
|
||||
return builder.build();
|
||||
// Handle queries with a wildcard and a suffix. In this case, it is more efficient to do things
|
||||
// differently. We use the suffix to look up the domain, then loop through the subordinate hosts
|
||||
// looking for matches.
|
||||
} else {
|
||||
DomainResource domainResource =
|
||||
loadByUniqueId(DomainResource.class, partialStringQuery.getSuffix(), clock.nowUtc());
|
||||
if (domainResource == null) {
|
||||
throw new NotFoundException("No domain found for specified nameserver suffix");
|
||||
}
|
||||
ImmutableList.Builder<ImmutableMap<String, Object>> builder = new ImmutableList.Builder<>();
|
||||
for (String fqhn : ImmutableSortedSet.copyOf(domainResource.getSubordinateHosts())) {
|
||||
// We can't just check that the host name starts with the initial query string, because then
|
||||
// the query ns.exam*.example.com would match against nameserver ns.example.com.
|
||||
if (partialStringQuery.matches(fqhn)) {
|
||||
HostResource hostResource = loadByUniqueId(HostResource.class, fqhn, clock.nowUtc());
|
||||
if (hostResource != null) {
|
||||
builder.add(
|
||||
RdapJsonFormatter.makeRdapJsonForHost(
|
||||
hostResource, false, rdapLinkBase, rdapWhoisServer));
|
||||
}
|
||||
}
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Searches for nameservers by IP address, returning a JSON array of nameserver info maps. */
|
||||
private ImmutableList<ImmutableMap<String, Object>>
|
||||
searchByIp(final InetAddress inetAddress) throws HttpException {
|
||||
// In theory, we could filter on deletion time being in the future. But we can't do that in the
|
||||
// name query above (because we already have an inequality filter), and filtering on deletion
|
||||
// time differently in the two cases seems like a recipe for future confusion.
|
||||
Query<HostResource> query = ofy()
|
||||
.load()
|
||||
.type(HostResource.class)
|
||||
.filter("inetAddresses", inetAddress.getHostAddress())
|
||||
.filter("deletionTime", END_OF_TIME)
|
||||
.limit(rdapResultSetMaxSize);
|
||||
ImmutableList.Builder<ImmutableMap<String, Object>> builder = new ImmutableList.Builder<>();
|
||||
for (HostResource hostResource : query) {
|
||||
builder.add(
|
||||
RdapJsonFormatter.makeRdapJsonForHost(
|
||||
hostResource, false, rdapLinkBase, rdapWhoisServer));
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
}
|
155
java/google/registry/rdap/RdapSearchPattern.java
Normal file
155
java/google/registry/rdap/RdapSearchPattern.java
Normal file
|
@ -0,0 +1,155 @@
|
|||
// 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 com.google.domain.registry.rdap;
|
||||
|
||||
import com.google.domain.registry.request.HttpException.UnprocessableEntityException;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Object containing the results of parsing an RDAP partial match search pattern. Search patterns
|
||||
* are of the form XXXX, XXXX* or XXXX*.YYYY. There can be at most one wildcard character, and it
|
||||
* must be at the end, except for a possible suffix string on the end to restrict the search to a
|
||||
* particular TLD (for domains) or domain (for nameservers).
|
||||
*
|
||||
* @see <a href="http://www.ietf.org/rfc/rfc7482.txt">
|
||||
* RFC 7482: Registration Data Access Protocol (RDAP) Query Format</a>
|
||||
*/
|
||||
public final class RdapSearchPattern {
|
||||
|
||||
/** String before the wildcard character. */
|
||||
private final String initialString;
|
||||
|
||||
/** If false, initialString contains the entire search string. */
|
||||
private final boolean hasWildcard;
|
||||
|
||||
/**
|
||||
* Terminating suffix after the wildcard, or null if none was specified; for domains, it should be
|
||||
* a TLD, for nameservers, a domain. RFC 7482 requires only that it be a sequence of domain
|
||||
* labels, but this definition is stricter for efficiency purposes.
|
||||
*/
|
||||
@Nullable
|
||||
private final String suffix;
|
||||
|
||||
private RdapSearchPattern(
|
||||
final String initialString, final boolean hasWildcard, @Nullable final String suffix) {
|
||||
this.initialString = initialString;
|
||||
this.hasWildcard = hasWildcard;
|
||||
this.suffix = suffix;
|
||||
}
|
||||
|
||||
public String getInitialString() {
|
||||
return initialString;
|
||||
}
|
||||
|
||||
public boolean getHasWildcard() {
|
||||
return hasWildcard;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getSuffix() {
|
||||
return suffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to return the next string in sort order after {@link #getInitialString()
|
||||
* initialString}. This can be used to convert a wildcard query into a range query, by looking for
|
||||
* strings greater than or equal to {@link #getInitialString() initialString} and less than {@link
|
||||
* #getNextInitialString() nextInitialString}.
|
||||
*/
|
||||
public String getNextInitialString() {
|
||||
return initialString.substring(0, initialString.length() - 1)
|
||||
+ (char) (initialString.charAt(initialString.length() - 1) + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a SearchPattern using the provided search pattern string.
|
||||
*
|
||||
* @param pattern the string containing the partial match pattern
|
||||
* @param allowSuffix true if a suffix is allowed after the wildcard
|
||||
*
|
||||
* @throws UnprocessableEntityException if {@code pattern} does not meet the requirements of RFC
|
||||
* 7482
|
||||
*/
|
||||
public static RdapSearchPattern create(
|
||||
String pattern, boolean allowSuffix) throws UnprocessableEntityException {
|
||||
String initialString;
|
||||
boolean hasWildcard;
|
||||
String suffix;
|
||||
// If there's no wildcard character, just lump everything into the initial string.
|
||||
int wildcardPos = pattern.indexOf('*');
|
||||
if (wildcardPos < 0) {
|
||||
initialString = pattern;
|
||||
hasWildcard = false;
|
||||
suffix = null;
|
||||
} else if (pattern.indexOf('*', wildcardPos + 1) >= 0) {
|
||||
throw new UnprocessableEntityException("Only one wildcard allowed");
|
||||
} else {
|
||||
hasWildcard = true;
|
||||
// Check for a suffix (e.g. exam*.com or ns*.example.com).
|
||||
if (pattern.length() > wildcardPos + 1) {
|
||||
if (!allowSuffix) {
|
||||
throw new UnprocessableEntityException("Suffix not allowed after wildcard");
|
||||
}
|
||||
if ((pattern.length() == wildcardPos + 2) || (pattern.charAt(wildcardPos + 1) != '.')) {
|
||||
throw new UnprocessableEntityException(
|
||||
"Suffix after wildcard must be one or more domain"
|
||||
+ " name labels, e.g. exam*.tld, ns*.example.tld");
|
||||
}
|
||||
suffix = pattern.substring(wildcardPos + 2);
|
||||
} else {
|
||||
suffix = null;
|
||||
}
|
||||
initialString = pattern.substring(0, wildcardPos);
|
||||
}
|
||||
if (initialString.length() < 2) {
|
||||
throw new UnprocessableEntityException("At least two characters must be specified");
|
||||
}
|
||||
if (initialString.startsWith("xn--")
|
||||
&& (initialString.length() < 7)) {
|
||||
throw new UnprocessableEntityException(
|
||||
"At least seven characters must be specified for punycode domain searches");
|
||||
}
|
||||
return new RdapSearchPattern(initialString, hasWildcard, suffix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks a string to make sure that it matches the search pattern.
|
||||
*
|
||||
* @param string the string to be matched
|
||||
* @return true if the pattern matches the string
|
||||
*/
|
||||
public boolean matches(@Nullable String string) {
|
||||
if (string == null) {
|
||||
return false;
|
||||
}
|
||||
int lengthAccountedFor = 0;
|
||||
if (initialString != null) {
|
||||
if (!string.startsWith(initialString)) {
|
||||
return false;
|
||||
}
|
||||
lengthAccountedFor += initialString.length();
|
||||
}
|
||||
if (suffix != null) {
|
||||
if (!string.endsWith(suffix)) {
|
||||
return false;
|
||||
}
|
||||
lengthAccountedFor += suffix.length();
|
||||
}
|
||||
return hasWildcard
|
||||
? (lengthAccountedFor <= string.length())
|
||||
: (lengthAccountedFor == string.length());
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue