mirror of
https://github.com/google/nomulus.git
synced 2025-06-16 09:24:47 +02:00
PDT testing revealed a couple ways in which our WHOIS output was non-compliant. First, the Consistent Labeling & Display policy dictates that the contact IDs must be ROIDs. See rule 11 in https://www.icann.org/resources/pages/rdds-labeling-policy-2017-02-01-en. Second, PDT tests expect that a WHOIS response will treat missing values either by omitting the line entirely, or by including the line with a blank value, but not both. So this is legal: Phone Number: 123-4567 Phone Number Ext: Fax Number: 123-4568 Fax Number Ext: and this is legal: Phone Number: 123-4567 Fax Number: 123-4568 but this is not: Phone Number: 123-4567 Phone Number Ext: Fax Number: 123-4568 In the last example, one extension line is present with a blank value, while the other extension line is omitted. We cannot do both. Therefore, we should update our code to omit lines with no value. Since we can't guarantee that we will always emit all lines that the parse might expect to see, it is safe to use the policy of omitting lines with no value. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=158184150
204 lines
7.3 KiB
Java
204 lines
7.3 KiB
Java
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package google.registry.whois;
|
|
|
|
import static com.google.common.base.Preconditions.checkNotNull;
|
|
import static com.google.common.base.Strings.isNullOrEmpty;
|
|
import static com.google.common.html.HtmlEscapers.htmlEscaper;
|
|
|
|
import com.google.common.base.Function;
|
|
import com.google.common.base.Joiner;
|
|
import com.google.common.base.Optional;
|
|
import com.google.common.collect.FluentIterable;
|
|
import com.google.common.collect.Ordering;
|
|
import google.registry.model.eppcommon.Address;
|
|
import google.registry.util.Idn;
|
|
import google.registry.xml.UtcDateTimeAdapter;
|
|
import java.util.List;
|
|
import java.util.Set;
|
|
import javax.annotation.Nullable;
|
|
import org.joda.time.DateTime;
|
|
|
|
/** Base class for responses to WHOIS queries. */
|
|
abstract class WhoisResponseImpl implements WhoisResponse {
|
|
|
|
/** Field name for ICANN problem reporting URL appended to all WHOIS responses. */
|
|
private static final String ICANN_REPORTING_URL_FIELD =
|
|
"URL of the ICANN Whois Inaccuracy Complaint Form";
|
|
|
|
/** ICANN problem reporting URL appended to all WHOIS responses. */
|
|
private static final String ICANN_REPORTING_URL = "https://www.icann.org/wicf/";
|
|
|
|
/** The time at which this response was created. */
|
|
private final DateTime timestamp;
|
|
|
|
WhoisResponseImpl(DateTime timestamp) {
|
|
this.timestamp = checkNotNull(timestamp, "timestamp");
|
|
}
|
|
|
|
@Override
|
|
public DateTime getTimestamp() {
|
|
return timestamp;
|
|
}
|
|
|
|
/**
|
|
* Translates a hostname to its unicode representation if desired.
|
|
*
|
|
* @param hostname is assumed to be in its canonical ASCII form from the database.
|
|
*/
|
|
static String maybeFormatHostname(String hostname, boolean preferUnicode) {
|
|
return preferUnicode ? Idn.toUnicode(hostname) : hostname;
|
|
}
|
|
|
|
static <T> T chooseByUnicodePreference(
|
|
boolean preferUnicode, @Nullable T localized, @Nullable T internationalized) {
|
|
if (preferUnicode) {
|
|
return Optional.fromNullable(localized).or(Optional.fromNullable(internationalized)).orNull();
|
|
} else {
|
|
return Optional.fromNullable(internationalized).or(Optional.fromNullable(localized)).orNull();
|
|
}
|
|
}
|
|
|
|
/** Writer for outputting data in the WHOIS format. */
|
|
abstract static class Emitter<E extends Emitter<E>> {
|
|
|
|
private final StringBuilder stringBuilder = new StringBuilder();
|
|
|
|
@SuppressWarnings("unchecked")
|
|
private E thisCastToDerived() {
|
|
return (E) this;
|
|
}
|
|
|
|
E emitNewline() {
|
|
stringBuilder.append("\r\n");
|
|
return thisCastToDerived();
|
|
}
|
|
|
|
/**
|
|
* Helper method that loops over a set of values and calls {@link #emitField}. This method will
|
|
* turn each value into a string using the provided callback and then sort those strings so the
|
|
* textual output is deterministic (which is important for unit tests). The ideal solution would
|
|
* be to use {@link java.util.SortedSet} but that would require reworking the models.
|
|
*/
|
|
<T> E emitSet(String title, Set<T> values, Function<T, String> transform) {
|
|
return emitList(title, FluentIterable
|
|
.from(values)
|
|
.transform(transform)
|
|
.toSortedList(Ordering.natural()));
|
|
}
|
|
|
|
/** Helper method that loops over a list of values and calls {@link #emitField}. */
|
|
E emitList(String title, Iterable<String> values) {
|
|
for (String value : values) {
|
|
emitField(title, value);
|
|
}
|
|
return thisCastToDerived();
|
|
}
|
|
|
|
/** Emit the field name and value followed by a newline, but only if a value exists. */
|
|
E emitFieldIfDefined(String name, @Nullable String value) {
|
|
if (isNullOrEmpty(value)) {
|
|
return thisCastToDerived();
|
|
}
|
|
stringBuilder.append(cleanse(name)).append(':');
|
|
stringBuilder.append(' ').append(cleanse(value));
|
|
return emitNewline();
|
|
}
|
|
|
|
/** Emit the field name and value followed by a newline. */
|
|
E emitField(String name, @Nullable String value) {
|
|
stringBuilder.append(cleanse(name)).append(':');
|
|
if (!isNullOrEmpty(value)) {
|
|
stringBuilder.append(' ').append(cleanse(value));
|
|
}
|
|
return emitNewline();
|
|
}
|
|
|
|
/** Emit a multi-part field name and value followed by a newline, but only if a value exists. */
|
|
E emitFieldIfDefined(List<String> nameParts, String value) {
|
|
if (isNullOrEmpty(value)) {
|
|
return thisCastToDerived();
|
|
}
|
|
return emitField(nameParts, value);
|
|
}
|
|
|
|
/** Emit a multi-part field name and value followed by a newline. */
|
|
E emitField(List<String> nameParts, String value) {
|
|
return emitField(Joiner.on(' ').join(nameParts), value);
|
|
}
|
|
|
|
/** Emit a contact address. */
|
|
E emitAddress(@Nullable String prefix, @Nullable Address address) {
|
|
prefix = isNullOrEmpty(prefix) ? "" : prefix + " ";
|
|
if (address != null) {
|
|
emitList(prefix + "Street", address.getStreet());
|
|
emitField(prefix + "City", address.getCity());
|
|
emitField(prefix + "State/Province", address.getState());
|
|
emitField(prefix + "Postal Code", address.getZip());
|
|
emitField(prefix + "Country", address.getCountryCode());
|
|
}
|
|
return thisCastToDerived();
|
|
}
|
|
|
|
/** Emit Whois Inaccuracy Complaint Form link. Only used for domain queries. */
|
|
E emitWicfLink() {
|
|
emitField(ICANN_REPORTING_URL_FIELD, ICANN_REPORTING_URL);
|
|
return thisCastToDerived();
|
|
}
|
|
|
|
/** Returns raw text that should be appended to the end of ALL WHOIS responses. */
|
|
E emitLastUpdated(DateTime timestamp) {
|
|
// We are assuming that our WHOIS database is always completely up to date, since it's
|
|
// querying the live backend Datastore.
|
|
stringBuilder
|
|
.append(">>> Last update of WHOIS database: ")
|
|
.append(UtcDateTimeAdapter.getFormattedString(timestamp))
|
|
.append(" <<<\r\n\r\n");
|
|
return thisCastToDerived();
|
|
}
|
|
|
|
/** Returns raw text that should be appended to the end of ALL WHOIS responses. */
|
|
E emitFooter(String disclaimer) {
|
|
stringBuilder.append(disclaimer.replaceAll("\r?\n", "\r\n").trim()).append("\r\n");
|
|
return thisCastToDerived();
|
|
}
|
|
|
|
/** Emits a string directly, followed by a newline. */
|
|
protected E emitRawLine(String string) {
|
|
stringBuilder.append(string);
|
|
return emitNewline();
|
|
}
|
|
|
|
/**
|
|
* Remove potentially dangerous stuff from WHOIS output fields.
|
|
*
|
|
* <ul>
|
|
* <li>Remove ASCII control characters like {@code \n} which could be used to forge output.
|
|
* <li>Escape HTML entities, just in case this gets injected poorly into a webpage.
|
|
* </ul>
|
|
*/
|
|
private String cleanse(String value) {
|
|
return htmlEscaper().escape(value).replaceAll("[\\x00-\\x1f]", " ");
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return stringBuilder.toString();
|
|
}
|
|
}
|
|
|
|
/** An emitter that needs no special logic. */
|
|
static class BasicEmitter extends Emitter<BasicEmitter> {}
|
|
}
|