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:
Justine Tunney 2016-05-13 18:55:08 -04:00
parent a41677aea1
commit 5012893c1d
2396 changed files with 0 additions and 0 deletions

View file

@ -0,0 +1,83 @@
// 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.util;
import static com.google.appengine.api.ThreadManager.currentRequestThreadFactory;
import com.google.common.util.concurrent.SimpleTimeLimiter;
import com.google.common.util.concurrent.TimeLimiter;
import java.util.List;
import java.util.concurrent.AbstractExecutorService;
import java.util.concurrent.TimeUnit;
/**
* A factory for {@link TimeLimiter} instances that use request threads, which carry the namespace
* and live only as long as the request that spawned them.
* <p>
* It is safe to reuse instances of this class, but there is no benefit in doing so over creating a
* fresh instance each time.
*/
public class AppEngineTimeLimiter {
/**
* An {@code ExecutorService} that uses a new thread for every task.
* <p>
* We need to use fresh threads for each request so that we can use App Engine's request threads.
* If we cached these threads in a thread pool (and if we were executing on a backend, where there
* is no time limit on requests) the caching would cause the thread to keep the task that opened
* it alive even after returning an http response, and would also cause the namespace that the
* original thread was created in to leak out to later reuses of the thread.
* <p>
* Since there are no cached resources, this class doesn't have to support being shutdown.
*/
private static class NewRequestThreadExecutorService extends AbstractExecutorService {
@Override
public void execute(Runnable command) {
currentRequestThreadFactory().newThread(command).start();
}
@Override
public boolean isShutdown() {
return false;
}
@Override
public boolean isTerminated() {
return false;
}
@Override
public void shutdown() {
throw new UnsupportedOperationException();
}
@Override
public List<Runnable> shutdownNow() {
throw new UnsupportedOperationException();
}
@Override
public boolean awaitTermination(long timeout, TimeUnit unit) {
throw new UnsupportedOperationException();
}
}
public static TimeLimiter create() {
return new SimpleTimeLimiter(new NewRequestThreadExecutorService());
}
}

View file

@ -0,0 +1,33 @@
package(
default_visibility = ["//java/com/google/domain/registry:registry_project"],
)
java_library(
name = "util",
srcs = glob(["*.java"]),
visibility = ["//visibility:public"],
deps = [
"//java/com/google/common/annotations",
"//java/com/google/common/base",
"//java/com/google/common/cache",
"//java/com/google/common/collect",
"//java/com/google/common/escape",
"//java/com/google/common/io",
"//java/com/google/common/math",
"//java/com/google/common/net",
"//java/com/google/common/primitives",
"//java/com/google/common/reflect",
"//java/com/google/common/util/concurrent",
"//java/com/google/domain/registry/config",
"//third_party/java/appengine:appengine-api",
"//third_party/java/dagger",
"//third_party/java/icu4j",
"//third_party/java/joda_time",
"//third_party/java/jsr305_annotations",
"//third_party/java/jsr330_inject",
"//third_party/java/objectify:objectify-v4_1",
"//third_party/java/servlet/servlet_api",
],
)

View file

@ -0,0 +1,127 @@
// 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.util;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Nullable;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionContext;
/** An {@link HttpSession} that only provides support for getting/setting attributes. */
@SuppressWarnings("deprecation")
public class BasicHttpSession implements HttpSession {
private final Map<String, Object> map = new HashMap<>();
boolean isValid = true;
@Override
public long getCreationTime() {
throw new UnsupportedOperationException();
}
@Override
public String getId() {
throw new UnsupportedOperationException();
}
@Override
public long getLastAccessedTime() {
throw new UnsupportedOperationException();
}
@Override
public ServletContext getServletContext() {
throw new UnsupportedOperationException();
}
@Override
public void setMaxInactiveInterval(int interval) {
throw new UnsupportedOperationException();
}
@Override
public int getMaxInactiveInterval() {
throw new UnsupportedOperationException();
}
@Override
public HttpSessionContext getSessionContext() {
throw new UnsupportedOperationException();
}
@Override
public Object getAttribute(@Nullable String name) {
checkValid();
return map.get(name);
}
@Override
public Object getValue(@Nullable String name) {
throw new UnsupportedOperationException();
}
@Override
public Enumeration<?> getAttributeNames() {
throw new UnsupportedOperationException();
}
@Override
public String[] getValueNames() {
throw new UnsupportedOperationException();
}
@Override
public void setAttribute(@Nullable String name, @Nullable Object value) {
checkValid();
map.put(name, value);
}
@Override
public void putValue(@Nullable String name, @Nullable Object value) {
throw new UnsupportedOperationException();
}
@Override
public void removeAttribute(@Nullable String name) {
checkValid();
map.remove(name);
}
@Override
public void removeValue(@Nullable String name) {
throw new UnsupportedOperationException();
}
@Override
public void invalidate() {
isValid = false;
map.clear();
}
@Override
public boolean isNew() {
throw new UnsupportedOperationException();
}
private void checkValid() {
if (!isValid) {
throw new IllegalStateException("This session has been invalidated.");
}
}
}

View file

@ -0,0 +1,59 @@
// 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.util;
import static com.google.common.base.Suppliers.memoizeWithExpiration;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.joda.time.Duration.ZERO;
import com.google.common.base.Supplier;
import com.google.domain.registry.config.RegistryEnvironment;
import org.joda.time.Duration;
/** Utility methods related to caching. */
public class CacheUtils {
private static final RegistryEnvironment ENVIRONMENT = RegistryEnvironment.get();
/**
* Memoize a supplier, with a short expiration specified in the environment config.
*
* <p>Use this for things that might change while code is running. (For example, the various
* lists downloaded from the TMCH get updated in datastore and the caches need to be refreshed.)
*/
public static <T> Supplier<T> memoizeWithShortExpiration(Supplier<T> original) {
return memoizeForDuration(original, ENVIRONMENT.config().getSingletonCacheRefreshDuration());
}
/**
* Memoize a supplier, with a long expiration specified in the environment config.
*
* <p>Use this for things that are loaded lazily but then will never change. This allows the test
* config to set the expiration time to zero so that different test values can be substituted in,
* while allowing the production config to set the expiration to forever.
*/
public static <T> Supplier<T> memoizeWithLongExpiration(Supplier<T> original) {
return memoizeForDuration(original, ENVIRONMENT.config().getSingletonCachePersistDuration());
}
/** Memoize a supplier, with a given expiration. */
private static <T> Supplier<T> memoizeForDuration(Supplier<T> original, Duration expiration) {
return expiration.isEqual(ZERO)
? original // memoizeWithExpiration won't accept 0 as a refresh duration.
: memoizeWithExpiration(original, expiration.getMillis(), MILLISECONDS);
}
}

View file

@ -0,0 +1,47 @@
// 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.util;
import com.google.common.collect.Iterables;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Handler;
import java.util.logging.LogRecord;
import javax.annotation.Nullable;
/** A log handler that captures logs. */
public final class CapturingLogHandler extends Handler {
private final List<LogRecord> records = new ArrayList<>();
@Override
public void publish(@Nullable LogRecord record) {
if (record != null) {
records.add(record);
}
}
@Override
public void flush() {}
@Override
public void close() {}
public Iterable<LogRecord> getRecords() {
return Iterables.unmodifiableIterable(records);
}
}

View file

@ -0,0 +1,471 @@
// 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.util;
import static com.google.common.base.Preconditions.checkArgument;
import com.google.common.collect.AbstractSequentialIterator;
import com.google.common.net.InetAddresses;
import java.io.Serializable;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.Iterator;
import java.util.logging.Logger;
import javax.annotation.Nullable;
/**
* Class representing an RFC 1519 CIDR IP address block.
*
* <p>When creating a CidrAddressBlock from an IP string literal
* without a specified CIDR netmask (i.e. no trailing "/16" or "/64")
* or an InetAddress with an accompanying integer netmask, then the
* maximum length netmask for the address famiy of the specified
* address is used (i.e. 32 for IPv4, 128 for IPv6). I.e. "1.2.3.4"
* is automatically treated as "1.2.3.4/32" and, similarly,
* "2001:db8::1" is automatically treated as "2001:db8::1/128".
*
*/
// TODO(b/21870796): Migrate to Guava version when this is open-sourced.
public class CidrAddressBlock implements Iterable<InetAddress>, Serializable {
private static final Logger logger =
Logger.getLogger(CidrAddressBlock.class.getName());
private final InetAddress ip;
/**
* The number of block or mask bits needed to create the address block
* (starting from the most-significant side of the address).
*/
private final int netmask;
/**
* Attempts to parse the given String into a CIDR block.
*
* <p>If the string is an IP string literal without a specified
* CIDR netmask (i.e. no trailing "/16" or "/64") then the maximum
* length netmask for the address famiy of the specified address is
* used (i.e. 32 for IPv4, 128 for IPv6).
*
* <p>The specified IP address portion must be properly truncated
* (i.e. all the host bits must be zero) or the input is considered
* malformed. For example, "1.2.3.0/24" is accepted but "1.2.3.4/24"
* is not. Similarly, for IPv6, "2001:db8::/32" is accepted whereas
* "2001:db8::1/32" is not.
*
* <p>If inputs might not be properly truncated but would be acceptable
* to the application consider constructing a {@code CidrAddressBlock}
* via {@code create()}.
*
* @param s a String of the form "217.68.0.0/16" or "2001:db8::/32".
*
* @throws IllegalArgumentException if s is malformed or does not
* represent a valid CIDR block.
*/
public CidrAddressBlock(String s) {
this(parseInetAddress(s), parseNetmask(s), false);
}
/**
* Attempts to parse the given String and int into a CIDR block.
*
* <p>The specified IP address portion must be properly truncated
* (i.e. all the host bits must be zero) or the input is considered
* malformed. For example, "1.2.3.0/24" is accepted but "1.2.3.4/24"
* is not. Similarly, for IPv6, "2001:db8::/32" is accepted whereas
* "2001:db8::1/32" is not.
*
* <p>An IP address without a netmask will automatically have the
* maximum applicable netmask for its address family. I.e. "1.2.3.4"
* is automatically treated as "1.2.3.4/32", and "2001:db8::1" is
* automatically treated as "2001:db8::1/128".
*
* <p>If inputs might not be properly truncated but would be acceptable
* to the application consider constructing a {@code CidrAddressBlock}
* via {@code create()}.
*
* @param ip a String of the form "217.68.0.0" or "2001:db8::".
* @param netmask an int between 0 and 32 (for IPv4) or 128 (for IPv6).
* This is the number of bits, starting from the big end of the IP,
* that will be used for network bits (as opposed to host bits)
* in this CIDR block.
*
* @throws IllegalArgumentException if the params are malformed or do not
* represent a valid CIDR block.
*/
public CidrAddressBlock(String ip, int netmask) {
this(InetAddresses.forString(ip), checkNotNegative(netmask), false);
}
public CidrAddressBlock(InetAddress ip) {
this(ip, AUTO_NETMASK, false);
}
public CidrAddressBlock(InetAddress ip, int netmask) {
this(ip, checkNotNegative(netmask), false);
}
/**
* Attempts to construct a CIDR block from the IP address and netmask,
* truncating the IP address as required.
*
* <p>The specified IP address portion need not be properly truncated
* (i.e. all the host bits need not be zero); truncation will be silently
* performed. For example, "1.2.3.4/24" is accepted and returns the
* same {@code CidrAddressBlock} as "1.2.3.0/24". Similarly, for IPv6,
* "2001:db8::1/32" is accepted and returns the same
* {@code CidrAddressBlock} as "2001:db8::/32".
*
* @param ip {@link InetAddress}, possibly requiring truncation.
* @param netmask an int between 0 and 32 (for IPv4) or 128 (for IPv6).
* This is the number of bits, starting from the big end of the IP,
* that will be used for network bits (as opposed to host bits)
* when truncating the supplied {@link InetAddress}.
*
* @throws IllegalArgumentException if the params are malformed or do not
* represent a valid CIDR block.
* @throws NullPointerException if a parameter is null.
*/
public static CidrAddressBlock create(InetAddress ip, int netmask) {
return new CidrAddressBlock(ip, checkNotNegative(netmask), true);
}
/**
* Attempts to construct a CIDR block from the IP address and netmask
* expressed as a String, truncating the IP address as required.
*
* <p>The specified IP address portion need not be properly truncated
* (i.e. all the host bits need not be zero); truncation will be silently
* performed. For example, "1.2.3.4/24" is accepted and returns the
* same {@code CidrAddressBlock} as "1.2.3.0/24". Similarly, for IPv6,
* "2001:db8::1/32" is accepted and returns the same
* {@code CidrAddressBlock} as "2001:db8::/32".
*
* @param s {@code String} representing either a single IP address or
* a CIDR netblock, possibly requiring truncation.
*
* @throws IllegalArgumentException if the params are malformed or do not
* represent a valid CIDR block.
* @throws NullPointerException if a parameter is null.
*/
public static CidrAddressBlock create(String s) {
return new CidrAddressBlock(parseInetAddress(s), parseNetmask(s), true);
}
private static final int AUTO_NETMASK = -1;
/**
* The universal constructor. All public constructors should lead here.
*
* @param ip {@link InetAddress}, possibly requiring truncation.
* @param netmask the number of prefix bits to include in the netmask.
* This is between 0 and 32 (for IPv4) or 128 (for IPv6).
* The special value {@code AUTO_NETMASK} indicates that the CIDR block
* should cover exactly one IP address.
* @param truncate controls the behavior when an address has extra trailing
* bits. If true, these bits are silently truncated, otherwise this
* triggers an exception.
*
* @throws IllegalArgumentException if netmask is out of range, or ip has
* unexpected trailing bits.
* @throws NullPointerException if a parameter is null.
*/
private CidrAddressBlock(InetAddress ip, int netmask, boolean truncate) {
// A single IP address is always truncated, by definition.
if (netmask == AUTO_NETMASK) {
this.ip = ip;
this.netmask = ip.getAddress().length * 8;
return;
}
// Determine the truncated form of this CIDR block.
InetAddress truncatedIp = applyNetmask(ip, netmask);
// If we're not truncating silently, then check for trailing bits.
if (!truncate && !truncatedIp.equals(ip)) {
throw new IllegalArgumentException(
"CIDR block: " + getCidrString(ip, netmask)
+ " is not properly truncated, should have been: "
+ getCidrString(truncatedIp, netmask));
}
this.ip = truncatedIp;
this.netmask = netmask;
}
private static String getCidrString(InetAddress ip, int netmask) {
return ip.getHostAddress() + "/" + netmask;
}
/**
* Attempts to parse an {@link InetAddress} prefix from the given String.
*
* @param s a String of the form "217.68.0.0/16" or "2001:db8::/32".
*
* @throws IllegalArgumentException if s does not begin with an IP address.
*/
private static InetAddress parseInetAddress(String s) {
int slash = s.indexOf('/');
return InetAddresses.forString((slash < 0) ? s : s.substring(0, slash));
}
/**
* Attempts to parse a netmask from the given String.
*
* <p>If the string does not end with a "/xx" suffix, then return AUTO_NETMASK
* and let the constructor handle it. Otherwise, we only verify that the
* suffix is a well-formed nonnegative integer.
*
* @param s a String of the form "217.68.0.0/16" or "2001:db8::/32".
*
* @throws IllegalArgumentException if s is malformed or does not end with a
* valid nonnegative integer.
*/
private static int parseNetmask(String s) {
int slash = s.indexOf('/');
if (slash < 0) {
return AUTO_NETMASK;
}
try {
return checkNotNegative(Integer.parseInt(s.substring(slash + 1)));
} catch (NumberFormatException nfe) {
throw new IllegalArgumentException("Invalid netmask: " + s.substring(slash + 1));
}
}
private static int checkNotNegative(int netmask) {
checkArgument(netmask >= 0, "CIDR netmask '%s' must not be negative.", netmask);
return netmask;
}
private static InetAddress applyNetmask(InetAddress ip, int netmask) {
byte[] bytes = ip.getAddress();
checkArgument(
(netmask >= 0) && (netmask <= (bytes.length * 8)),
"CIDR netmask '%s' is out of range: 0 <= netmask <= %s.",
netmask, (bytes.length * 8));
// The byte in which the CIDR boundary falls.
int cidrByte = (netmask == 0) ? 0 : ((netmask - 1) / 8);
// The number of mask bits within this byte.
int numBits = netmask - (cidrByte * 8);
// The bitmask for this byte.
int bitMask = (-1 << (8 - numBits));
// Truncate the byte in which the CIDR boundary falls.
bytes[cidrByte] = (byte) (bytes[cidrByte] & bitMask);
// All bytes following the cidrByte get zeroed.
for (int i = cidrByte + 1; i < bytes.length; ++i) {
bytes[i] = 0;
}
try {
return InetAddress.getByAddress(bytes);
} catch (UnknownHostException uhe) {
throw new IllegalArgumentException(
String.format("Error creating InetAddress from byte array '%s'.",
Arrays.toString(bytes)),
uhe);
}
}
/**
* @return the standard {@code String} representation of the IP portion
* of this CIDR block (a.b.c.d, or a:b:c::d)
*
* <p>NOTE: This is not reliable for comparison operations. It is
* more reliable to normalize strings into {@link InetAddress}s and
* then compare.
*
* <p>Consider:
* <ul>
* <li>{@code "10.11.12.0"} is equivalent to {@code "10.11.12.000"}
* <li>{@code "2001:db8::"} is equivalent to
* {@code "2001:0DB8:0000:0000:0000:0000:0000:0000"}
* </ul>
*/
public String getIp() {
return ip.getHostAddress();
}
public InetAddress getInetAddress() {
return ip;
}
/**
* Returns the number of leading bits (prefix size) of the routing prefix.
*/
public int getNetmask() {
return netmask;
}
/**
* Returns {@code true} if the supplied {@link InetAddress} is within
* this {@code CidrAddressBlock}, {@code false} otherwise.
*
* <p>This can be used to test if the argument falls within a well-known
* network range, a la GoogleIp's isGoogleIp(), isChinaIp(), et alia.
*
* @param ipAddr {@link InetAddress} to evaluate.
* @return {@code true} if {@code ipAddr} is logically within this block,
* {@code false} otherwise.
*/
public boolean contains(@Nullable InetAddress ipAddr) {
if (ipAddr == null) {
return false;
}
// IPv4 CIDR netblocks can never contain IPv6 addresses, and vice versa.
// Calling getClass() is safe because the Inet4Address and Inet6Address
// classes are final.
if (ipAddr.getClass() != ip.getClass()) {
return false;
}
try {
return ip.equals(applyNetmask(ipAddr, netmask));
} catch (IllegalArgumentException iae) {
// Something has gone very wrong. This CidrAddressBlock should
// not have been created with an invalid netmask and a valid
// netmask should have been successfully applied to "ipAddr" as long
// as it represents an address of the same family as "this.ip".
logger.warning(iae.getMessage());
return false;
}
}
/**
* Returns {@code true} if the supplied {@code CidrAddressBlock} is within
* this {@code CidrAddressBlock}, {@code false} otherwise.
*
* <p>This can be used to test if the argument falls within a well-known
* network range, a la GoogleIp's isGoogleIp(), isChinaIp(), et alia.
*
* @param cidr {@code CidrAddressBlock} to evaluate.
* @return {@code true} if {@code cidr} is logically within this block,
* {@code false} otherwise.
*/
public boolean contains(@Nullable CidrAddressBlock cidr) {
if (cidr == null) {
return false;
}
if (cidr.netmask < netmask) {
// No block can contain a network larger than it
// (in CIDR larger blocks have smaller netmasks).
return false;
}
return contains(cidr.getInetAddress());
}
/**
* Returns {@code true} if the supplied {@code String} is within
* this {@code CidrAddressBlock}, {@code false} otherwise.
*
* <p>This can be used to test if the argument falls within a well-known
* network range, a la GoogleIp's isGoogleIp(), isChinaIp(), et alia.
*
* @param s {@code String} to evaluate.
* @return {@code true} if {@code s} is logically within this block,
* {@code false} otherwise.
*/
public boolean contains(@Nullable String s) {
if (s == null) {
return false;
}
try {
return contains(create(s));
} catch (IllegalArgumentException iae) {
return false;
}
}
/**
* Returns the address that is contained in this {@code CidrAddressBlock}
* with the most bits set.
*
* <p>This can be used to calculate the upper bound address of the address
* range for this {@code CidrAddressBlock}.
*/
public InetAddress getAllOnesAddress() {
byte[] bytes = ip.getAddress();
// The byte in which the CIDR boundary falls.
int cidrByte = (netmask == 0) ? 0 : ((netmask - 1) / 8);
// The number of mask bits within this byte.
int numBits = netmask - (cidrByte * 8);
// The bitmask for this byte.
int bitMask = ~(-1 << (8 - numBits));
// Set all non-prefix bits where the CIDR boundary falls.
bytes[cidrByte] = (byte) (bytes[cidrByte] | bitMask);
// All bytes following the cidrByte get set to all ones.
for (int i = cidrByte + 1; i < bytes.length; ++i) {
bytes[i] = (byte) 0xff;
}
try {
return InetAddress.getByAddress(bytes);
} catch (UnknownHostException uhe) {
throw new IllegalArgumentException(
String.format("Error creating InetAddress from byte array '%s'.",
Arrays.toString(bytes)),
uhe);
}
}
@Override
public Iterator<InetAddress> iterator() {
return new AbstractSequentialIterator<InetAddress>(ip) {
@Override
protected InetAddress computeNext(InetAddress previous) {
if (InetAddresses.isMaximum(previous)) {
return null;
}
InetAddress next = InetAddresses.increment(previous);
return (contains(next)) ? next : null;
}
};
}
@Override
public int hashCode() {
return InetAddresses.coerceToInteger(ip);
}
@Override
public boolean equals(@Nullable Object o) {
if (!(o instanceof CidrAddressBlock)) {
return false;
}
CidrAddressBlock cidr = (CidrAddressBlock) o;
return ip.equals(cidr.ip) && (netmask == cidr.netmask);
}
@Override
public String toString() {
return getCidrString(ip, netmask);
}
}

View file

@ -0,0 +1,27 @@
// 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.util;
import org.joda.time.DateTime;
import javax.annotation.concurrent.ThreadSafe;
/** A clock that tells the current time in milliseconds or nanoseconds. */
@ThreadSafe
public interface Clock {
/** Returns current time in UTC timezone. */
DateTime nowUtc();
}

View file

@ -0,0 +1,153 @@
// 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.util;
import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.collect.Iterables.isEmpty;
import static com.google.common.collect.Iterables.partition;
import com.google.common.collect.HashMultiset;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multisets;
import com.google.common.collect.Sets;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import javax.annotation.Nullable;
/** Utility methods related to collections. */
public class CollectionUtils {
/** Checks if an iterable is null or empty. */
public static boolean isNullOrEmpty(@Nullable Iterable<?> potentiallyNull) {
return potentiallyNull == null || isEmpty(potentiallyNull);
}
/** Checks if a map is null or empty. */
public static boolean isNullOrEmpty(@Nullable Map<?, ?> potentiallyNull) {
return potentiallyNull == null || potentiallyNull.isEmpty();
}
/** Turns a null set into an empty set. JAXB leaves lots of null sets lying around. */
public static <T> Set<T> nullToEmpty(@Nullable Set<T> potentiallyNull) {
return firstNonNull(potentiallyNull, ImmutableSet.<T>of());
}
/** Turns a null list into an empty list. */
public static <T> List<T> nullToEmpty(@Nullable List<T> potentiallyNull) {
return firstNonNull(potentiallyNull, ImmutableList.<T>of());
}
/** Turns a null map into an empty map. */
public static <T, U> Map<T, U> nullToEmpty(@Nullable Map<T, U> potentiallyNull) {
return firstNonNull(potentiallyNull, ImmutableMap.<T, U>of());
}
/** Turns a null multimap into an empty multimap. */
public static <T, U> Multimap<T, U> nullToEmpty(@Nullable Multimap<T, U> potentiallyNull) {
return firstNonNull(potentiallyNull, ImmutableMultimap.<T, U>of());
}
/** Turns a null sorted map into an empty sorted map.. */
public static <T, U> SortedMap<T, U> nullToEmpty(@Nullable SortedMap<T, U> potentiallyNull) {
return firstNonNull(potentiallyNull, ImmutableSortedMap.<T, U>of());
}
/** Defensive copy helper for {@link Set}. */
public static <V> ImmutableSet<V> nullSafeImmutableCopy(Set<V> data) {
return data == null ? null : ImmutableSet.copyOf(data);
}
/** Defensive copy helper for {@link List}. */
public static <V> ImmutableList<V> nullSafeImmutableCopy(List<V> data) {
return data == null ? null : ImmutableList.copyOf(data);
}
/** Defensive copy helper for {@link Set}. */
public static <V> ImmutableSet<V> nullToEmptyImmutableCopy(Set<V> data) {
return data == null ? ImmutableSet.<V>of() : ImmutableSet.copyOf(data);
}
/** Defensive copy helper for {@link Set}. */
public static <V extends Comparable<V>>
ImmutableSortedSet<V> nullToEmptyImmutableSortedCopy(Set<V> data) {
return data == null ? ImmutableSortedSet.<V>of() : ImmutableSortedSet.copyOf(data);
}
/** Defensive copy helper for {@link SortedMap}. */
public static <K, V> ImmutableSortedMap<K, V> nullToEmptyImmutableCopy(SortedMap<K, V> data) {
return data == null ? ImmutableSortedMap.<K, V>of() : ImmutableSortedMap.copyOfSorted(data);
}
/** Defensive copy helper for {@link List}. */
public static <V> ImmutableList<V> nullToEmptyImmutableCopy(List<V> data) {
return data == null ? ImmutableList.<V>of() : ImmutableList.copyOf(data);
}
/** Defensive copy helper for {@link Map}. */
public static <K, V> ImmutableMap<K, V> nullToEmptyImmutableCopy(Map<K, V> data) {
return data == null ? ImmutableMap.<K, V>of() : ImmutableMap.copyOf(data);
}
/**
* Turns an empty collection into a null collection.
* <p>
* This is unwise in the general case (nulls are bad; empties are good) but occasionally needed to
* cause JAXB not to emit a field, or to avoid saving something to datastore. The method name
* includes "force" to indicate that you should think twice before using it.
*/
@Nullable
public static <T, C extends Collection<T>> C forceEmptyToNull(@Nullable C potentiallyEmpty) {
return potentiallyEmpty == null || potentiallyEmpty.isEmpty() ? null : potentiallyEmpty;
}
/** Copy an {@link ImmutableSet} and add members. */
@SafeVarargs
public static <T> ImmutableSet<T> union(Set<T> set, T... newMembers) {
return Sets.union(set, ImmutableSet.copyOf(newMembers)).immutableCopy();
}
/** Copy an {@link ImmutableSet} and remove members. */
@SafeVarargs
public static <T> ImmutableSet<T> difference(Set<T> set, T... toRemove) {
return Sets.difference(set, ImmutableSet.copyOf(toRemove)).immutableCopy();
}
/** Returns any duplicates in an iterable. */
public static <T> Set<T> findDuplicates(Iterable<T> iterable) {
return Multisets.difference(
HashMultiset.create(iterable),
HashMultiset.create(ImmutableSet.copyOf(iterable))).elementSet();
}
/** Partitions a Map into a Collection of Maps, each of max size n. */
public static <K, V> ImmutableList<ImmutableMap<K, V>> partitionMap(Map<K, V> map, int size) {
ImmutableList.Builder<ImmutableMap<K, V>> shards = new ImmutableList.Builder<>();
for (Iterable<Map.Entry<K, V>> entriesShard : partition(map.entrySet(), size)) {
shards.add(ImmutableMap.copyOf(entriesShard));
}
return shards.build();
}
}

View file

@ -0,0 +1,102 @@
// 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.util;
import static com.google.appengine.api.ThreadManager.currentRequestThreadFactory;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.lang.Math.max;
import static java.lang.Math.min;
import static java.util.concurrent.Executors.newFixedThreadPool;
import com.google.common.base.Function;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.UncheckedExecutionException;
import com.google.common.util.concurrent.Uninterruptibles;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
/** Utilities for multithreaded operations in App Engine requests. */
public final class Concurrent {
/** Maximum number of threads per pool. The actual GAE per-request limit is 50. */
private static final int MAX_THREADS = 10;
/**
* Runs transform with the default number of threads.
*
* @see #transform(Collection, int, Function)
*/
public static <A, B> ImmutableList<B> transform(Collection<A> items, final Function<A, B> funk) {
return transform(items, max(1, min(items.size(), MAX_THREADS)), funk);
}
/**
* Processes {@code items} in parallel using {@code funk}, with the specified number of threads.
*
* <p><b>Note:</b> Spawned threads will inherit the same namespace.
*
* @throws UncheckedExecutionException to wrap the exception thrown by {@code funk}. This will
* only contain the exception information for the first exception thrown.
* @return transformed {@code items} in the same order.
*/
public static <A, B> ImmutableList<B> transform(
Collection<A> items,
int threadCount,
final Function<A, B> funk) {
checkNotNull(funk);
checkNotNull(items);
ThreadFactory threadFactory = currentRequestThreadFactory();
if (threadFactory == null) {
// Fall back to non-concurrent transform if we can't get an App Engine thread factory (most
// likely caused by hitting this code from a command-line tool). Default Java system threads
// are not compatible with code that needs to interact with App Engine (such as Objectify),
// which we often have in funk when calling Concurrent.transform().
// For more info see: http://stackoverflow.com/questions/15976406
return FluentIterable.from(items).transform(funk).toList();
}
ExecutorService executor = newFixedThreadPool(threadCount, threadFactory);
try {
List<Future<B>> futures = new ArrayList<>();
for (final A item : items) {
futures.add(executor.submit(new Callable<B>() {
@Override
public B call() {
return funk.apply(item);
}}));
}
ImmutableList.Builder<B> results = new ImmutableList.Builder<>();
for (Future<B> future : futures) {
try {
results.add(Uninterruptibles.getUninterruptibly(future));
} catch (ExecutionException e) {
throw new UncheckedExecutionException(e.getCause());
}
}
return results.build();
} finally {
executor.shutdownNow();
}
}
private Concurrent() {}
}

View file

@ -0,0 +1,35 @@
// 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.util;
import com.google.appengine.api.datastore.Key;
import com.google.common.base.Function;
import com.google.common.base.Optional;
/** Utility methods for working with the AppEngine datastore service. */
public class DatastoreServiceUtils {
/** Helper function that extracts the kind from a regular datastore entity key. */
public static final Function<Key, String> KEY_TO_KIND_FUNCTION = new Function<Key, String>() {
@Override
public String apply(Key key) {
return key.getKind();
}};
/** Returns the name or id of a key, which may be a string or a long. */
public static Object getNameOrId(Key key) {
return Optional.<Object>fromNullable(key.getName()).or(key.getId());
}
}

View file

@ -0,0 +1,81 @@
// 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.util;
import static com.google.common.base.Preconditions.checkArgument;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Ordering;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
/** Utilities methods and constants related to Joda {@link DateTime} objects. */
public class DateTimeUtils {
/** The start of the epoch, in a convenient constant. */
public static final DateTime START_OF_TIME = new DateTime(0, DateTimeZone.UTC);
/**
* A date in the far future that we can treat as infinity.
* <p>
* This value is (2^63-1)/1000 rounded down. AppEngine stores dates as 64 bit microseconds, but
* Java uses milliseconds, so this is the largest representable date that will survive
* a round-trip through the datastore.
*/
public static final DateTime END_OF_TIME = new DateTime(Long.MAX_VALUE / 1000, DateTimeZone.UTC);
/** Returns the earliest of a number of given {@link DateTime} instances. */
public static DateTime earliestOf(DateTime first, DateTime... rest) {
return earliestOf(Lists.asList(first, rest));
}
/** Returns the earliest element in a {@link DateTime} iterable. */
public static DateTime earliestOf(Iterable<DateTime> dates) {
checkArgument(!Iterables.isEmpty(dates));
return Ordering.<DateTime>natural().min(dates);
}
/** Returns the latest of a number of given {@link DateTime} instances. */
public static DateTime latestOf(DateTime first, DateTime... rest) {
return latestOf(Lists.asList(first, rest));
}
/** Returns the latest element in a {@link DateTime} iterable. */
public static DateTime latestOf(Iterable<DateTime> dates) {
checkArgument(!Iterables.isEmpty(dates));
return Ordering.<DateTime>natural().max(dates);
}
/** Returns whether the first {@link DateTime} is equal to or earlier than the second. */
public static boolean isBeforeOrAt(DateTime timeToCheck, DateTime timeToCompareTo) {
return !timeToCheck.isAfter(timeToCompareTo);
}
/** Returns whether the first {@link DateTime} is equal to or later than the second. */
public static boolean isAtOrAfter(DateTime timeToCheck, DateTime timeToCompareTo) {
return !timeToCheck.isBefore(timeToCompareTo);
}
/**
* Adds years to a date, in the {@code Duration} sense of semantic years. Use this instead of
* {@link DateTime#plusYears} to ensure that we never end up on February 29.
*/
public static DateTime leapSafeAddYears(DateTime now, int years) {
checkArgument(years >= 0);
return years == 0 ? now : now.plusYears(1).plusYears(years - 1);
}
}

View file

@ -0,0 +1,173 @@
// 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.util;
import static com.google.common.base.Predicates.notNull;
import static com.google.common.collect.Lists.newArrayList;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Ordering;
import com.google.common.collect.Sets;
import com.google.common.primitives.Primitives;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import javax.annotation.Nullable;
/** Helper class for diff utilities. */
public final class DiffUtils {
/**
* A helper class to store the two sides of a diff. If both sides are Sets then they will be
* diffed, otherwise the two objects are toStringed in Collection format "[a, b]".
*/
private static class DiffPair {
@Nullable
final Object a;
@Nullable
final Object b;
DiffPair(@Nullable Object a, @Nullable Object b) {
this.a = a;
this.b = b;
}
@Override
public String toString() {
// Note that we use newArrayList here instead of ImmutableList because a and b can be null.
return newArrayList(a, b).toString();
}
}
/** Pretty-prints a deep diff between two maps. */
public static String prettyPrintDeepDiff(Map<?, ?> a, Map<?, ?> b) {
return prettyPrintDiffedMap(deepDiff(a, b), null);
}
/**
* Pretty-prints a deep diff between two maps. Path is prefixed to each output line of the diff.
*/
public static String prettyPrintDeepDiff(Map<?, ?> a, Map<?, ?> b, @Nullable String path) {
return prettyPrintDiffedMap(deepDiff(a, b), path);
}
/** Compare two maps and return a map containing, at each key where they differed, both values. */
public static ImmutableMap<?, ?> deepDiff(Map<?, ?> a, Map<?, ?> b) {
ImmutableMap.Builder<Object, Object> diff = new ImmutableMap.Builder<>();
for (Object key : Sets.union(a.keySet(), b.keySet())) {
Object aValue = a.get(key);
Object bValue = b.get(key);
if (!Objects.equals(aValue, bValue)) {
if (aValue instanceof String && bValue instanceof String
&& a.toString().contains("\n") && b.toString().contains("\n")) {
aValue = stringToMap((String) aValue);
bValue = stringToMap((String) bValue);
} else if (aValue instanceof Set && bValue instanceof Set) {
// Leave Sets alone; prettyPrintDiffedMap has special handling for Sets.
} else if (aValue instanceof Iterable && bValue instanceof Iterable) {
aValue = iterableToSortedMap((Iterable<?>) aValue);
bValue = iterableToSortedMap((Iterable<?>) bValue);
}
diff.put(key, (aValue instanceof Map && bValue instanceof Map)
? deepDiff((Map<?, ?>) aValue, (Map<?, ?>) bValue)
: new DiffPair(aValue, bValue));
}
}
return diff.build();
}
private static Map<Integer, ?> iterableToSortedMap(Iterable<?> iterable) {
// We use a sorted map here so that the iteration across the keySet is consistent.
ImmutableSortedMap.Builder<Integer, Object> builder =
new ImmutableSortedMap.Builder<>(Ordering.natural());
int i = 0;
for (Object item : Iterables.filter(iterable, notNull())) {
builder.put(i++, item);
}
return builder.build();
}
private static Map<String, ?> stringToMap(String string) {
ImmutableMap.Builder<String, Object> builder = new ImmutableMap.Builder<>();
int i = 0;
for (String item : Splitter.on('\n').split(string)) {
builder.put("Line " + i++, item);
}
return builder.build();
}
/** Recursively pretty prints the contents of a diffed map generated by {@link #deepDiff}. */
public static String prettyPrintDiffedMap(Map<?, ?> map, @Nullable String path) {
StringBuilder builder = new StringBuilder();
for (Map.Entry<?, ?> entry : map.entrySet()) {
String newPath = (path == null ? "" : path + ".") + entry.getKey();
String output;
Object value = entry.getValue();
if (value instanceof Map) {
output = prettyPrintDiffedMap((Map<?, ?>) entry.getValue(), newPath);
} else if (value instanceof DiffPair
&& ((DiffPair) value).a instanceof Set
&& ((DiffPair) value).b instanceof Set) {
DiffPair pair = ((DiffPair) value);
String prettyLineDiff = prettyPrintSetDiff((Set<?>) pair.a, (Set<?>) pair.b) + "\n";
output = newPath + ((prettyLineDiff.startsWith("\n")) ? " ->" : " -> ") + prettyLineDiff;
} else {
output = newPath + " -> " + value + "\n";
}
builder.append(output);
}
return builder.toString();
}
/**
* Returns a string displaying the differences between the old values in a set and the new ones.
*/
@VisibleForTesting
static String prettyPrintSetDiff(Set<?> a, Set<?> b) {
Set<?> removed = Sets.difference(a, b);
Set<?> added = Sets.difference(b, a);
if (removed.isEmpty() && added.isEmpty()) {
return "NO DIFFERENCES";
}
return Joiner.on("\n ").skipNulls().join("",
!added.isEmpty() ? ("ADDED:" + formatSetContents(added)) : null,
!removed.isEmpty() ? ("REMOVED:" + formatSetContents(removed)) : null,
"FINAL CONTENTS:" + formatSetContents(b));
}
/**
* Returns a formatted listing of Set contents, using a single line format if all elements are
* wrappers of primitive types or Strings, and a multiline (one object per line) format if they
* are not.
*/
private static String formatSetContents(Set<?> set) {
for (Object obj : set) {
if (!Primitives.isWrapperType(obj.getClass()) && !(obj instanceof String)) {
return "\n " + Joiner.on(",\n ").join(set);
}
}
return " " + set;
}
private DiffUtils() {}
}

View 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.util;
import static com.google.common.base.Preconditions.checkArgument;
import com.google.common.base.Ascii;
import com.google.common.base.Strings;
import com.google.common.net.InternetDomainName;
/** Utility methods related to domain names. */
public final class DomainNameUtils {
/** Prefix for unicode domain name parts. */
public static final String ACE_PREFIX = "xn--";
public static final String ACE_PREFIX_REGEX = "^xn--";
/** Checks whether "name" is a strict subdomain of "potentialParent". */
public static boolean isUnder(InternetDomainName name, InternetDomainName potentialParent) {
int numNameParts = name.parts().size();
int numParentParts = potentialParent.parts().size();
return numNameParts > numParentParts
&& name.parts().subList(numNameParts - numParentParts, numNameParts)
.equals(potentialParent.parts());
}
/** Canonicalizes a domain name by lowercasing and converting unicode to punycode. */
public static String canonicalizeDomainName(String label) {
return Idn.toASCII(Ascii.toLowerCase(label));
}
/**
* Returns the canonicalized TLD part of a valid domain name (just an SLD, no subdomains) by
* stripping off the leftmost part.
*
* <p>This function is compatible with multi-part tlds, e.g. {@code co.uk}. This function will
* also work on domains for which the registry is not authoritative. If you are certain that the
* input will be under a TLD this registry controls, then it is preferable to use
* {@link com.google.domain.registry.model.registry.Registries#findTldForName(InternetDomainName)
* Registries#findTldForName}, which will work on hostnames in addition to domains.
*
* @param fullyQualifiedDomainName must be a punycode SLD (not a host or unicode)
* @throws IllegalArgumentException if there is no TLD
*/
public static String getTldFromDomainName(String fullyQualifiedDomainName) {
checkArgument(
!Strings.isNullOrEmpty(fullyQualifiedDomainName),
"fullyQualifiedDomainName cannot be null or empty");
InternetDomainName domainName = InternetDomainName.from(fullyQualifiedDomainName);
checkArgument(domainName.hasParent(), "fullyQualifiedDomainName does not have a TLD");
return domainName.parent().toString();
}
private DomainNameUtils() {}
}

View file

@ -0,0 +1,32 @@
// 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.util;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
/** A simple clock that always returns a fixed time. */
public class FixedClock implements Clock {
private final DateTime nowUtc;
public FixedClock(long millisSinceEpoch) {
this.nowUtc = new DateTime(millisSinceEpoch, DateTimeZone.UTC);
}
@Override
public DateTime nowUtc() {
return nowUtc;
}
}

View file

@ -0,0 +1,114 @@
// 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.util;
import static java.util.Arrays.asList;
import com.google.common.base.Predicate;
import com.google.common.collect.FluentIterable;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;
/** Logging wrapper. */
public class FormattingLogger {
private final Logger logger;
/** Returns an instance using the caller's class name for the underlying logger's name. */
public static FormattingLogger getLoggerForCallerClass() {
return new FormattingLogger(new Exception().getStackTrace()[1].getClassName());
}
private FormattingLogger(String name) {
this.logger = Logger.getLogger(name);
}
private void log(Level level, Throwable cause, String msg) {
StackTraceElement callerFrame = FluentIterable
.from(asList(new Exception().getStackTrace()))
.firstMatch(new Predicate<StackTraceElement>() {
@Override
public boolean apply(StackTraceElement frame) {
return !frame.getClassName().equals(FormattingLogger.class.getName());
}}).get();
if (cause == null) {
logger.logp(level, callerFrame.getClassName(), callerFrame.getMethodName(), msg);
} else {
logger.logp(level, callerFrame.getClassName(), callerFrame.getMethodName(), msg, cause);
}
}
public void finefmt(String fmt, Object... args) {
log(Level.FINE, null, String.format(fmt, args));
}
public void info(String msg) {
log(Level.INFO, null, msg);
}
public void info(Throwable cause, String msg) {
log(Level.INFO, cause, msg);
}
public void infofmt(String fmt, Object... args) {
log(Level.INFO, null, String.format(fmt, args));
}
public void infofmt(Throwable cause, String fmt, Object... args) {
log(Level.INFO, cause, String.format(fmt, args));
}
public void warning(String msg) {
log(Level.WARNING, null, msg);
}
public void warning(Throwable cause, String msg) {
log(Level.WARNING, cause, msg);
}
public void warningfmt(String fmt, Object... args) {
log(Level.WARNING, null, String.format(fmt, args));
}
public void warningfmt(Throwable cause, String fmt, Object... args) {
log(Level.WARNING, cause, String.format(fmt, args));
}
public void severe(String msg) {
log(Level.SEVERE, null, msg);
}
public void severe(Throwable cause, String msg) {
log(Level.SEVERE, cause, msg);
}
public void severefmt(String fmt, Object... args) {
log(Level.SEVERE, null, String.format(fmt, args));
}
public void severefmt(Throwable cause, String fmt, Object... args) {
log(Level.SEVERE, cause, String.format(fmt, args));
}
public void addHandler(Handler handler) {
logger.addHandler(handler);
}
public void removeHandler(Handler handler) {
logger.removeHandler(handler);
}
}

View file

@ -0,0 +1,230 @@
// 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.util;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import java.io.IOException;
import java.io.OutputStream;
import java.io.StringWriter;
import java.io.Writer;
import javax.annotation.Nonnegative;
import javax.annotation.WillNotClose;
import javax.annotation.concurrent.NotThreadSafe;
/**
* Hex Dump Utility.
*
* <p>This class takes binary data and prints it out in a way that humans can read. It's just like
* the hex dump programs you remembered as a kid. There a column on the left for the address (or
* offset) in decimal, the middle columns are each digit in hexadecimal, and the column on the
* right is the ASCII representation where non-printable characters are represented by {@code '.'}.
*
* <p>It's easiest to generate a simple {@link String} by calling {@link #dumpHex(byte[])}, or you
* can stream data with {@link #HexDumper(Writer, int, int)}.
*
* <p>Example output:
* <pre> {@code
* [222 bytes total]
* 00000000 90 0d 03 00 08 03 35 58 61 46 fd f3 f3 73 01 88 ......5XaF...s..
* 00000016 cd 04 00 03 08 00 37 05 02 52 09 3f 8d 30 1c 45 ......7..R.?.0.E
* 00000032 72 69 63 20 45 63 68 69 64 6e 61 20 28 74 65 73 ric Echidna (tes
* 00000048 74 20 6b 65 79 29 20 3c 65 72 69 63 40 62 6f 75 t key) <eric@bou
* 00000064 6e 63 79 63 61 73 74 6c 65 2e 6f 72 67 3e 00 0a ncycastle.org>..
* 00000080 09 10 35 58 61 46 fd f3 f3 73 4b 5b 03 fe 2e 53 ..5XaF...sK[...S
* 00000096 04 28 ab cb 35 3b e2 1b 63 91 65 3a 86 b9 fb 47 .(..5;..c.e:...G
* 00000112 d5 4c 6a 21 50 f5 2e 39 76 aa d5 86 d7 96 3b 9a .Lj!P..9v.....;.
* 00000128 1a c3 6d c0 50 7f c6 25 9a 04 de 0f 1f 20 ae 70 ..m.P..%..... .p
* 00000144 f9 77 c4 8b bf ec 3c 2f 59 58 b8 47 81 6a 59 25 .w....</YX.G.jY%
* 00000160 82 b0 ba e2 a9 43 94 aa fc 92 2b b3 76 77 f5 ba .....C....+.vw..
* 00000176 5b 59 9a de 22 1c 79 06 88 d2 ba 97 51 e3 11 2e [Y..".y.....Q...
* 00000192 5b c0 c6 8c 34 4d a7 28 77 bf 11 27 e7 6c 8e 1c [...4M.(w..'.l..
* 00000208 b4 a6 66 18 8e 69 3c 18 b7 97 d5 34 9a bb ..f..i<....4..
* }</pre>
*/
@NotThreadSafe
public final class HexDumper extends OutputStream {
@Nonnegative
public static final int DEFAULT_PER_LINE = 16;
@Nonnegative
public static final int DEFAULT_PER_GROUP = 4;
private Writer upstream;
@Nonnegative
private final int perLine;
@Nonnegative
private final int perGroup;
private long totalBytes;
@Nonnegative
private int lineCount;
private StringBuilder line;
private final char[] asciis;
/**
* Calls {@link #dumpHex(byte[], int, int)} with {@code perLine} set to
* {@value #DEFAULT_PER_LINE} and {@code perGroup} set to {@value #DEFAULT_PER_GROUP}.
*/
public static String dumpHex(byte[] data) {
return dumpHex(data, DEFAULT_PER_LINE, DEFAULT_PER_GROUP);
}
/**
* Convenience static method for generating a hex dump as a {@link String}.
*
* <p>This method adds an additional line to the beginning with the total number of bytes.
*
* @see #HexDumper(Writer, int, int)
*/
public static String dumpHex(byte[] data, @Nonnegative int perLine, @Nonnegative int perGroup) {
checkNotNull(data, "data");
StringWriter writer = new StringWriter();
writer.write(String.format("[%d bytes total]\n", data.length));
try (HexDumper hexDump = new HexDumper(writer, perLine, perGroup)) {
hexDump.write(data);
} catch (IOException e) {
throw new RuntimeException(e);
}
return writer.toString();
}
/**
* Calls {@link #HexDumper(Writer, int, int)} with {@code perLine} set to
* {@value #DEFAULT_PER_LINE} and {@code perGroup} set to {@value #DEFAULT_PER_GROUP}.
*/
public HexDumper(@WillNotClose Writer writer) {
this(writer, DEFAULT_PER_LINE, DEFAULT_PER_GROUP);
}
/**
* Construct a new streaming {@link HexDumper} object.
*
* <p>The output is line-buffered so a single write call is made to {@code out} for each line.
* This is done to avoid system call overhead {@code out} is a resource, and reduces the chance
* of lines being broken by other threads writing to {@code out} (or its underlying resource) at
* the same time.
*
* <p>This object will <i>not</i> close {@code out}. You must close <i>both</i> this object and
* {@code out}, and this object must be closed <i>first</i>.
*
* @param out is the stream to which the hex dump text is written. It is <i>not</i> closed.
* @param perLine determines how many hex characters to show on each line.
* @param perGroup how many columns of hex digits should be grouped together. If this value is
* {@code > 0}, an extra space will be inserted after each Nth column for readability.
* Grouping can be disabled by setting this to {@code 0}.
* @see #dumpHex(byte[])
*/
public HexDumper(@WillNotClose Writer out, @Nonnegative int perLine, @Nonnegative int perGroup) {
checkArgument(0 < perLine, "0 < perLine <= INT32_MAX");
checkArgument(0 <= perGroup && perGroup < perLine, "0 <= perGroup < perLine");
this.upstream = checkNotNull(out, "out");
this.totalBytes = 0L;
this.perLine = perLine;
this.perGroup = perGroup;
this.asciis = new char[perLine];
this.line = newLine();
}
/** Initializes member variables at the beginning of a new line. */
private StringBuilder newLine() {
lineCount = 0;
return new StringBuilder(String.format("%08d ", totalBytes));
}
/**
* Writes a single byte to the current line buffer, flushing if end of line.
*
* @throws IOException upon failure to write to upstream {@link Writer#write(String) writer}.
* @throws IllegalStateException if this object has been {@link #close() closed}.
*/
@Override
public void write(int b) throws IOException {
String flush = null;
line.append(String.format("%02x ", (byte) b));
asciis[lineCount] = b >= 32 && b <= 126 ? (char) b : '.';
++lineCount;
++totalBytes;
if (lineCount == perLine) {
line.append(' ');
line.append(asciis);
line.append('\n');
flush = line.toString();
line = newLine();
} else {
if (perGroup > 0 && lineCount % perGroup == 0) {
line.append(' ');
}
}
// Writing upstream is deferred until the end in order to *somewhat* maintain a correct
// internal state in the event that an exception is thrown. This also avoids the need for
// a try statement which usually makes code run slower.
if (flush != null) {
upstream.write(flush);
}
}
/**
* Writes partial line buffer (if any) to upstream {@link Writer}.
*
* @throws IOException upon failure to write to upstream {@link Writer#write(String) writer}.
* @throws IllegalStateException if this object has been {@link #close() closed}.
*/
@Override
public void flush() throws IOException {
if (line.length() > 0) {
upstream.write(line.toString());
line = new StringBuilder();
}
}
/**
* Writes out the final line (if incomplete) and invalidates this object.
*
* <p>This object must be closed <i>before</i> you close the upstream writer. Please note that
* this method <i>does not</i> close upstream writer for you.
*
* <p>If you attempt to write to this object after calling this method,
* {@link IllegalStateException} will be thrown. However, it's safe to call close multiple times,
* as subsequent calls will be treated as a no-op.
*
* @throws IOException upon failure to write to upstream {@link Writer#write(String) writer}.
*/
@Override
public void close() throws IOException {
if (lineCount > 0) {
while (lineCount < perLine) {
asciis[lineCount] = ' ';
line.append(" ");
++lineCount;
if (perGroup > 0 && lineCount % perGroup == 0 && lineCount != perLine) {
line.append(' ');
}
}
line.append(' ');
line.append(asciis);
line.append('\n');
flush();
}
}
}

View file

@ -0,0 +1,78 @@
// 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.util;
import com.google.common.base.Joiner;
import com.ibm.icu.text.IDNA;
import com.ibm.icu.text.IDNA.Info;
/**
* A partial API-compatible replacement for {@link java.net.IDN} that replaces <a
* href="http://www.ietf.org/rfc/rfc3490.txt">IDNA2003</a> processing with <a
* href="http://unicode.org/reports/tr46/">UTS46 transitional processing</a>, with differences as
* described in the <a href="http://www.unicode.org/reports/tr46/#IDNAComparison">UTS46
* documentation</a>/
*
* <p>This class provides methods to convert internationalized domain names (IDNs) between Unicode
* and ASCII-only <a href="http://www.ietf.org/rfc/rfc3492.txt">Punycode</a> form. It implements the
* parts of the API from {@link java.net.IDN} that we care about, but will return different results
* as defined by the differences between IDNA2003 and transitional UTS46.
*/
public final class Idn {
/** Cached UTS46 with the flags we want. */
private static final IDNA UTS46_INSTANCE = IDNA.getUTS46Instance(IDNA.CHECK_BIDI);
/**
* Translates a string from Unicode to ASCII Compatible Encoding (ACE), as defined by the ToASCII
* operation of <a href="http://www.ietf.org/rfc/rfc3490.txt">RFC 3490</a>.
*
* <p>This method always uses <a href="http://unicode.org/reports/tr46/">UTS46 transitional
* processing</a>.
*
* @param name a domain name, which may include multiple labels separated by dots
* @throws IllegalArgumentException if the input string doesn't conform to RFC 3490 specification
* @see java.net.IDN#toASCII(String)
*/
public static String toASCII(String name) {
Info info = new Info();
StringBuilder result = new StringBuilder();
UTS46_INSTANCE.nameToASCII(name, result, info);
if (info.hasErrors()) {
throw new IllegalArgumentException("Errors: " + Joiner.on(',').join(info.getErrors()));
}
return result.toString();
}
/**
* Translates a string from Unicode to ASCII Compatible Encoding (ACE), as defined by the ToASCII
* operation of <a href="http://www.ietf.org/rfc/rfc3490.txt">RFC 3490</a>.
*
* <p>This method always uses <a href="http://unicode.org/reports/tr46/">UTS46 transitional
* processing</a>.
*
* <p>ToUnicode never fails. In case of any error, the input string is returned unmodified.
*
* @param name a domain name, which may include multiple labels separated by dots
* @see java.net.IDN#toUnicode(String)
*/
public static String toUnicode(String name) {
Info info = new Info();
StringBuilder result = new StringBuilder();
UTS46_INSTANCE.nameToUnicode(name, result, info);
return info.hasErrors() ? name : result.toString();
}
}

View file

@ -0,0 +1,151 @@
// 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.util;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import javax.annotation.OverridingMethodsMustInvokeSuper;
import javax.annotation.WillCloseWhenClosed;
import javax.annotation.concurrent.NotThreadSafe;
/**
* {@link InputStream} wrapper that offers some additional magic.
*
* <ul>
* <li>Byte counting
* <li>Log byte count on close
* <li>Check expected byte count when closed (Optional)
* <li>Close original {@link InputStream} when closed (Optional)
* <li>Overridable {@link #onClose()} method
* <li>Throws {@link NullPointerException} if read after {@link #close()}
* </ul>
*
* @see ImprovedOutputStream
* @see com.google.common.io.CountingInputStream
*/
@NotThreadSafe
public class ImprovedInputStream extends FilterInputStream {
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
private long count;
private long mark = -1;
private final long expected;
private final boolean shouldClose;
public ImprovedInputStream(@WillCloseWhenClosed InputStream out) {
this(out, true, -1);
}
public ImprovedInputStream(InputStream in, boolean shouldClose, long expected) {
super(checkNotNull(in, "in"));
checkArgument(expected >= -1, "expected >= 0 or -1");
this.shouldClose = shouldClose;
this.expected = expected;
}
@Override
@OverridingMethodsMustInvokeSuper
public int read() throws IOException {
int result = in.read();
if (result != -1) {
count++;
}
return result;
}
@Override
public final int read(byte[] b) throws IOException {
return this.read(b, 0, b.length);
}
@Override
@OverridingMethodsMustInvokeSuper
public int read(byte[] b, int off, int len) throws IOException {
int result = in.read(b, off, len);
if (result != -1) {
count += result;
}
return result;
}
@Override
public long skip(long n) throws IOException {
long result = in.skip(n);
count += result;
return result;
}
@Override
public synchronized void mark(int readlimit) {
in.mark(readlimit);
mark = count;
// it's okay to mark even if mark isn't supported, as reset won't work
}
@Override
public synchronized void reset() throws IOException {
if (!in.markSupported()) {
throw new IOException("Mark not supported");
}
if (mark == -1) {
throw new IOException("Mark not set");
}
in.reset();
count = mark;
}
/**
* Logs byte count, checks byte count (optional), closes (optional), and self-sabotages.
*
* <p>This method may not be overridden, use {@link #onClose()} instead.
*
* @see InputStream#close()
*/
@Override
@NonFinalForTesting
public void close() throws IOException {
if (in == null) {
return;
}
logger.infofmt("%s closed with %,d bytes read", getClass().getSimpleName(), count);
if (expected != -1 && count != expected) {
throw new IOException(String.format("%s expected %,d bytes but read %,d bytes",
getClass().getCanonicalName(), expected, count));
}
onClose();
if (shouldClose) {
in.close();
}
in = null;
}
/**
* Overridable method that's called by {@link #close()}.
*
* <p>This method does nothing by default.
*
* @throws IOException
*/
protected void onClose() throws IOException {
// Does nothing by default.
}
}

View file

@ -0,0 +1,130 @@
// 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.util;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import javax.annotation.OverridingMethodsMustInvokeSuper;
import javax.annotation.WillCloseWhenClosed;
import javax.annotation.concurrent.NotThreadSafe;
/**
* {@link OutputStream} wrapper that offers some additional magic.
*
* <ul>
* <li>Byte counting
* <li>Always {@link #flush()} on {@link #close()}
* <li>Check expected byte count when closed (Optional)
* <li>Close original {@link OutputStream} when closed (Optional)
* <li>Overridable {@link #onClose()} method
* <li>Throws {@link NullPointerException} if written after {@link #close()}
* </ul>
*
* @see ImprovedInputStream
* @see com.google.common.io.CountingOutputStream
*/
@NotThreadSafe
public class ImprovedOutputStream extends FilterOutputStream {
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
private long count;
private final long expected;
private final boolean shouldClose;
public ImprovedOutputStream(@WillCloseWhenClosed OutputStream out) {
this(out, true, -1);
}
public ImprovedOutputStream(OutputStream out, boolean shouldClose, long expected) {
super(checkNotNull(out, "out"));
checkArgument(expected >= -1, "expected >= 0 or -1");
this.shouldClose = shouldClose;
this.expected = expected;
}
/** Returns the number of bytes that have been written to this stream thus far. */
public long getBytesWritten() {
return count;
}
/** @see java.io.FilterOutputStream#write(int) */
@Override
@OverridingMethodsMustInvokeSuper
public void write(int b) throws IOException {
out.write(b);
++count;
}
/** @see #write(byte[], int, int) */
@Override
public final void write(byte[] b) throws IOException {
this.write(b, 0, b.length);
}
/** @see java.io.FilterOutputStream#write(byte[], int, int) */
@Override
@OverridingMethodsMustInvokeSuper
public void write(byte[] b, int off, int len) throws IOException {
out.write(b, off, len);
count += len;
}
/**
* Flushes, logs byte count, checks byte count (optional), closes (optional), and self-sabotages.
*
* <p>This method may not be overridden, use {@link #onClose()} instead.
*
* @see java.io.OutputStream#close()
*/
@Override
@NonFinalForTesting
public void close() throws IOException {
if (out == null) {
return;
}
try {
flush();
} catch (IOException e) {
logger.warningfmt(e, "%s.flush() failed", getClass().getSimpleName());
}
logger.infofmt("%s closed with %,d bytes written", getClass().getSimpleName(), count);
if (expected != -1 && count != expected) {
throw new IOException(String.format(
"%s expected %,d bytes but got %,d bytes", getClass().getSimpleName(), expected, count));
}
onClose();
if (shouldClose) {
out.close();
}
out = null;
}
/**
* Overridable method that's called by {@link #close()}.
*
* <p>This method does nothing by default.
*
* @throws IOException
*/
protected void onClose() throws IOException {
// Does nothing by default.
}
}

View file

@ -0,0 +1,30 @@
// 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.util;
import com.google.common.io.Files;
import java.nio.file.Path;
/**
* A utility class for conversion of input file paths into names for entities in Datastore.
*/
public final class ListNamingUtils {
/** Turns a file path into a name suitable for use as the name of a premium or reserved list. */
public static String convertFilePathToName(Path file) {
return Files.getNameWithoutExtension(file.getFileName().toString());
}
}

View file

@ -0,0 +1,135 @@
// 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.util;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.domain.registry.util.CollectionUtils.union;
import com.google.common.collect.ImmutableSet;
import java.io.IOException;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.ServerSocket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.security.SecureRandom;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Random;
import java.util.Set;
import javax.annotation.concurrent.GuardedBy;
/** Utilities for networking. */
public final class NetworkUtils {
private static final int PICK_ATTEMPTS = 16;
private static final int RANDOM_PORT_BASE = 32768;
private static final int RANDOM_PORT_RANGE = 60000 - RANDOM_PORT_BASE + 1;
@GuardedBy("random")
private static final Random random = new SecureRandom();
/**
* Returns random unused local port that can be used for TCP listening server.
*
* @throws IOException if failed to find free port after {@value #PICK_ATTEMPTS} attempts
*/
public static int pickUnusedPort() throws IOException {
// In an ideal world, we would just listen on port 0 and use whatever random port the kernel
// assigns us. But our CI testing system reports there are rare circumstances in which this
// doesn't work.
Iterator<Integer> ports = union(generateRandomPorts(PICK_ATTEMPTS), 0).iterator();
while (true) {
try (ServerSocket serverSocket = new ServerSocket(ports.next())) {
return serverSocket.getLocalPort();
} catch (IOException e) {
if (!ports.hasNext()) {
throw new IOException("Failed to acquire random port", e);
}
}
}
}
/**
* Returns the fully-qualified domain name of the local host in all lower case.
*
* @throws RuntimeException to wrap {@link UnknownHostException} if the local host could not be
* resolved into an address
*/
public static String getCanonicalHostName() {
try {
return getExternalAddressOfLocalSystem().getCanonicalHostName().toLowerCase();
} catch (UnknownHostException e) {
throw new RuntimeException(e);
}
}
/**
* Returns the externally-facing IPv4 network address of the local host.
*
* <p>This function implements a workaround for an
* <a href="http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4665037">issue</a> in
* {@link InetAddress#getLocalHost}.
*
* <p><b>Note:</b> This code was pilfered from {@link "com.google.net.base.LocalHost"} which was
* never made open source.
*
* @throws UnknownHostException if the local host could not be resolved into an address
*/
private static InetAddress getExternalAddressOfLocalSystem() throws UnknownHostException {
InetAddress localhost = InetAddress.getLocalHost();
// If we have a loopback address, look for an address using the network cards.
if (localhost.isLoopbackAddress()) {
try {
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
if (interfaces == null) {
return localhost;
}
while (interfaces.hasMoreElements()) {
NetworkInterface networkInterface = interfaces.nextElement();
Enumeration<InetAddress> addresses = networkInterface.getInetAddresses();
while (addresses.hasMoreElements()) {
InetAddress address = addresses.nextElement();
if (!(address.isLoopbackAddress()
|| address.isLinkLocalAddress()
|| address instanceof Inet6Address)) {
return address;
}
}
}
} catch (SocketException e) {
// Fall-through.
}
}
return localhost;
}
private static ImmutableSet<Integer> generateRandomPorts(int count) {
checkArgument(count > 0);
Set<Integer> result = new HashSet<>();
synchronized (random) {
while (result.size() < count) {
result.add(RANDOM_PORT_BASE + random.nextInt(RANDOM_PORT_RANGE));
}
}
return ImmutableSet.copyOf(result);
}
private NetworkUtils() {}
}

View file

@ -0,0 +1,40 @@
// 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.util;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.SOURCE;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
/**
* Annotation that discourages future maintainers from marking a field {@code final}.
*
* <p>This annotation serves purely as documention to indicate that even though a {@code private}
* field may <em>appear</em> safe to change to {@code final}, it will actually be reflectively
* modified by a unit test, and therefore should not be {@code final}.
*
* <p>When this annotation is used on methods, it means that you should not override the method
* and it's only non-{@code final} so it can be mocked.
*
* @see com.google.domain.registry.testing.InjectRule
*/
@Documented
@Retention(SOURCE)
@Target({FIELD, METHOD})
public @interface NonFinalForTesting {}

View file

@ -0,0 +1,29 @@
// 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.util;
import com.google.common.base.Function;
import com.googlecode.objectify.Key;
/** Utilities for working with Objectify. */
public class ObjectifyUtils {
public static final Function<Object, Key<?>> OBJECTS_TO_KEYS = new Function<Object, Key<?>>() {
@Override
public Key<?> apply(Object obj) {
return Key.create(obj);
}};
}

View file

@ -0,0 +1,69 @@
// 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.util;
import java.util.regex.Pattern;
/** Helper for extracting pathargs from the servlet request pathinfo with colon notation. */
public final class PathargMatcher {
private static final Pattern PATHARG_MATCHER = Pattern.compile(":(?<name>[^/]+)");
/**
* Returns regex pattern for parsing an HTTP path using Java 7 named groups.
*
* <p>The named groups should use colon notation. For example:<pre> {@code
*
* Pattern pattern = PathargMatcher.forPath("/task/:tld/:registrar");
* Matcher matcher = pattern.matcher("/task/soy/TheRegistrar");
* assertTrue(matcher.matches());
* assertEquals("soy", matcher.group("soy"));
* assertEquals("TheRegistrar", matcher.group("registrar"));}</pre>
*
* <h3>Best Practices</h3>
*
* <p>Pathargs is a good choice for parameters that:
*
* <ol>
* <li>Are mandatory
* <li>Never contain slashes
* <li>Part of the resource name
* <li>About 1-10 characters in length
* <li>Matches the pattern {@code [-._a-zA-Z0-9]+} (not enforced)
* </ol>
*
* <p>Otherwise you may want to consider using normal parameters.
*
* <h3>Trailing Slash</h3>
*
* <p>This class does not allow you to make the trailing slash in the path optional. You must
* either forbid it (recommended) or require it (not recommended). If you wish to make the
* trailing slash optional, you should configure your frontend webserver to 30x redirect to the
* canonical path.
*
* <p>There's no consensus in the web development community about whether or not the canonical
* path should include or omit the trailing slash. The Django community prefers to keep the
* trailing slash; whereas the Ruby on Rails community has traditionally omitted it.
*
* @see java.util.regex.Matcher
*/
public static Pattern forPath(String pathSpec) {
return Pattern.compile(convertPathSpecToRegex(pathSpec));
}
private static String convertPathSpecToRegex(String pathSpec) {
return PATHARG_MATCHER.matcher(pathSpec).replaceAll("(?<${name}>[^/]+)");
}
}

View file

@ -0,0 +1,27 @@
// 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.util;
/** Utilities for working with pipeline jobs. */
public class PipelineUtils {
private static final String URL_PREFIX = "/_ah/pipeline/status.html?root=";
private PipelineUtils() {}
public static String createJobPath(String jobId) {
return URL_PREFIX + jobId;
}
}

View file

@ -0,0 +1,511 @@
// 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.util;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Strings.isNullOrEmpty;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.joda.time.DateTimeConstants.MILLIS_PER_SECOND;
import static org.joda.time.DateTimeZone.UTC;
import org.joda.time.DateTime;
import java.util.Arrays;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
/**
* POSIX Tar Header.
*
* <p>This class represents the 512-byte header that precedes each file within a tar file archive.
* It's in the POSIX ustar format which is equivalent to using the following GNU tar flags:
* {@code tar --format=ustar}. It's called ustar because you're a star!
*
* <p><b>Warning:</b> This class is not a complete tar implementation. It also offers no
* abstractions beyond the header format and has only been tested against very simple archives
* created in the ustar format and the default format for gnu and bsd tar. If your goal is to be
* able to read generic tar files, you should use a more mature tar implementation like
* <a href="http://commons.apache.org/proper/commons-compress/">Apache Commons Compress</a>.
*
* <p>This class is only really useful in situations where the following statements are true:
*
* <ol>
* <li>You want to <i>create</i> tar archives.
* <li>You don't need to be able to read tar files from external sources.
* <li>You don't want additional dependencies.
* <li>You don't need fancy features like symbolic links.
* <li>You want something that's a step above writing out the bytes by hand.
* </ol>
*
* <p>To create a tar archive using this class, you must do the following: For each file in the
* archive, output a header and the file contents ({@code null}-padded to the nearest 512-byte
* boundary). Then output another 1024 {@code null} bytes to indicate end of archive.
*
* <p>The ustar tar header contains the following fields:
*
* <dl>
* <dt>name<dd><i>[Offset: 0, Length: 100]</i><br>
* C String which we'll assume is UTF-8 (Offset: 0)
* <dt>mode<dd><i>[Offset: 100, Length: 8]</i><br>
* ASCII 7-digit zero-padded octal file mode and {@code null} byte.
* <dt>uid<dd><i>[Offset: 108, Length: 8]</i><br>
* ASCII 7-digit zero-padded octal UNIX user ID and {@code null} byte.
* <dt>gid<dd><i>[Offset: 116, Length: 8]</i><br>
* ASCII 7-digit zero-padded octal UNIX group ID and {@code null} byte.
* <dt>size<dd><i>[Offset: 124, Length: 12]</i><br>
* ASCII 11-digit zero-padded octal file size and {@code null} byte.
* <dt>mtime<dd><i>[Offset: 136, Length: 12]</i><br>
* ASCII octal UNIX timestamp modified time and {@code null} byte.
* <dt>chksum<dd><i>[Offset: 148, Length: 8]</i><br>
* ASCII octal sum of all header bytes where chksum are 0's.
* <dt>typeflag<dd><i>[Offset: 156]</i><br>
* Always {@code '0'} (zero character) for regular type of file.
* <dt>linkname<dd><i>[Offset: 157, Length: 100]</i><br>
* All {@code null} bytes because we don't support symbolic links.
* <dt>magic<dd><i>[Offset: 257, Length: 6]</i><br>
* Always the C string "ustar".
* <dt>version<dd><i>[Offset: 263, Length: 2]</i><br>
* Always "00" without a {@code null} or blank on GNU systems.
* <dt>uname<dd><i>[Offset: 265, Length: 32]</i><br>
* The C string UNIX user name corresponding to {@code uid}.
* <dt>gname<dd><i>[Offset: 297, Length: 32]</i><br>
* The C string UNIX group name corresponding to {@code gid}.
* <dt>devmajor<dd><i>[Offset: 329, Length: 8]</i><br>
* Not supported; set to zero.
* <dt>devminor<dd><i>[Offset: 337, Length: 8]</i><br>
* Not supported; set to zero.
* <dt>prefix<dd><i>[Offset: 345, Length: 155]</i><br>
* Not supported; set to {@code null}.
* </dl>
*
* @see <a href="http://www.gnu.org/software/tar/manual/html_node/Standard.html">Tar Standard</a>
*/
@Immutable
public final class PosixTarHeader {
/** Type of file stored in the archive. Only normal files and directories are supported. */
public enum Type {
/** A regular file. This can't be a symbolic link or anything interesting. */
REGULAR,
/** A directory AKA folder. */
DIRECTORY,
/** This indicates we read a file from an archive with an unsupported type. */
UNSUPPORTED;
}
private static final int HEADER_LENGTH = 512;
private final byte[] header;
/**
* Create a new header from the bytes of an existing tar file header (Safe).
*
* <p>This routine validates the checksum to ensure the data header is correct and supported.
*
* @param header the existing and assumed correct header. Value is defensively copied.
* @throws IllegalArgumentException if header isn't ustar or has a bad checksum.
* @throws NullPointerException if {@code header} is {@code null}.
*/
public static PosixTarHeader from(byte[] header) {
checkNotNull(header, "header");
checkArgument(header.length == HEADER_LENGTH,
"POSIX tar header length should be %s but was %s", HEADER_LENGTH, header.length);
PosixTarHeader res = new PosixTarHeader(header.clone());
checkArgument(res.getMagic().equals("ustar"),
"Not a POSIX tar ustar header.");
String version = res.getVersion();
checkArgument(version.isEmpty() || version.equals("00"),
"Only POSIX tar ustar version 00 and the GNU variant supported.");
checkArgument(res.getChksum() == checksum(header),
"POSIX tar header chksum invalid.");
return res;
}
/** Constructs a new instance (Unsafe). */
PosixTarHeader(byte[] header) {
this.header = header;
}
/** Returns 512-byte tar header (safe copy). */
public byte[] getBytes() {
return header.clone();
}
/** Returns the filename. */
public String getName() {
return extractField(0, 100);
}
/** Returns the octal UNIX mode aka permissions. */
public int getMode() {
return Integer.parseInt(extractField(100, 8).trim(), 8);
}
/** Returns the UNIX owner/user id. */
public int getUid() {
return Integer.parseInt(extractField(108, 8).trim(), 8);
}
/** Returns the UNIX group id. */
public int getGid() {
return Integer.parseInt(extractField(116, 8).trim(), 8);
}
/** Returns the file size in bytes. */
public int getSize() {
return Integer.parseInt(extractField(124, 12).trim(), 8);
}
/** Returns the modified time as a UTC {@link DateTime} object. */
public DateTime getMtime() {
return new DateTime(Long.parseLong(extractField(136, 12).trim(), 8) * MILLIS_PER_SECOND, UTC);
}
/** Returns the checksum value stored in the . */
public int getChksum() {
return Integer.parseInt(extractField(148, 8).trim(), 8);
}
/** Returns the {@link Type} of file. */
public Type getType() {
switch (header[156]) {
case '\0':
case '0':
return Type.REGULAR;
case '5':
return Type.DIRECTORY;
default:
return Type.UNSUPPORTED;
}
}
/**
* Returns the UNIX symbolic link name.
*
* <p>This feature is unsupported but the getter is included for completeness.
*/
public String getLinkName() {
return extractField(157, 100);
}
/** Returns the {@code magic} field. Only {@code "ustar"} is supported. */
public String getMagic() {
return extractField(257, 6).trim();
}
/** Returns the {@code magic} field. Only {@code "00"} is supported. */
public String getVersion() {
return extractField(263, 2).trim();
}
/** Returns the UNIX user name (or owner) of the file. */
public String getUname() {
return extractField(265, 32).trim();
}
/** Returns the UNIX group name associated with the file. */
public String getGname() {
return extractField(297, 32).trim();
}
/**
* Returns the {@code devmajor} field.
*
* <p>This feature is unsupported but the getter is included for completeness.
*/
public String getDevMajor() {
return extractField(329, 8);
}
/**
* Returns the {@code devminor} field.
*
* <p>This feature is unsupported but the getter is included for completeness.
*/
public String getDevMinor() {
return extractField(337, 8);
}
/**
* Returns the {@code prefix} field.
*
* <p>This feature is unsupported but the getter is included for completeness.
*/
public String getPrefix() {
return extractField(345, 155);
}
/**
* Extracts a C string field. This routine is lenient when it comes to the terminating
* {@code null} byte. If it's not present, it'll be assumed that the string length is the
* size of the field. The encoding is always assumed to be UTF-8.
*/
private String extractField(int offset, int max) {
return new String(header, offset, extractFieldLength(offset, max), UTF_8);
}
/** Returns length of C string in field, or {@code max} if there's no {@code null} byte. */
private int extractFieldLength(int offset, int max) {
for (int n = 0; n < max; ++n) {
if (header[offset + n] == '\0') {
return n;
}
}
return max;
}
/** @see Arrays#hashCode(byte[]) */
@Override
public int hashCode() {
return Arrays.hashCode(header);
}
/** @see Arrays#equals(byte[], byte[]) */
@Override
public boolean equals(@Nullable Object rhs) {
return rhs == this
|| rhs != null
&& getClass() == rhs.getClass()
&& Arrays.equals(header, ((PosixTarHeader) rhs).header);
}
/** @see Arrays#toString(byte[]) */
@Override
public String toString() {
return Arrays.toString(header);
}
/** Simple checksum algorithm specified by tar. */
static int checksum(byte[] bytes) {
int sum = 0;
for (int n = 0; n < 148; ++n) {
sum += bytes[n];
}
sum += ' ' * 8; // We pretend the chksum field stores spaces.
for (int n = 148 + 8; n < bytes.length; ++n) {
sum += bytes[n];
}
return sum;
}
/**
* Builder for {@link PosixTarHeader}.
*
* <p>The following fields are required:<ul>
* <li>{@link #setName(String)}
* <li>{@link #setSize(long)}</ul>
*
* <p>{@link #build()} may be called multiple times. With the exception of the required fields
* listed above, fields will retain the values. This is useful if you want to construct many
* file headers with the same value for certain certain fields (e.g. uid, gid, uname, gname)
* but don't want to have to call their setters repeatedly.
*/
public static class Builder {
private static final int DEFAULT_MODE = 0640;
private static final int DEFAULT_UID = 0;
private static final int DEFAULT_GID = 0;
private static final String DEFAULT_UNAME = "root";
private static final String DEFAULT_GNAME = "wheel";
private static final Type DEFAULT_TYPE = Type.REGULAR;
private final byte[] header = new byte[HEADER_LENGTH];
private boolean hasName = false;
private boolean hasSize = false;
public Builder() {
setMode(DEFAULT_MODE);
setUid(DEFAULT_UID);
setGid(DEFAULT_GID);
setMtime(new DateTime(UTC));
setType(DEFAULT_TYPE);
setMagic();
setVersion();
setUname(DEFAULT_UNAME);
setGname(DEFAULT_GNAME);
setField("devmajor", 329, 8, "0000000"); // I have no clue what this is.
setField("devminor", 337, 8, "0000000"); // I have no clue what this is.
}
/**
* Sets the file name. (Required)
*
* @param name must be {@code <100} characters in length.
*/
public Builder setName(String name) {
checkArgument(!isNullOrEmpty(name), "name");
setField("name", 0, 100, name);
hasName = true;
return this;
}
/**
* Sets the UNIX file mode aka permissions. By default this is {@value #DEFAULT_MODE}.
*
* @param mode <u>This value is octal</u>. Just in case you were wondering, {@code 416} is the
* decimal representation of {@code 0640}. If that number doesn't look familiar to you,
* search Google for "chmod". The value must be {@code >=0} and {@code <8^7}.
*/
public Builder setMode(int mode) {
checkArgument(0 <= mode && mode <= 07777777,
"Tar mode out of range: %s", mode);
setField("mode", 100, 8, String.format("%07o", mode));
return this;
}
/**
* Sets the owner's UNIX user ID. By default this is {@value #DEFAULT_UID}.
*
* @param uid must be {@code >=0} and {@code <8^7}.
* @see #setUname(String)
*/
public Builder setUid(int uid) {
checkArgument(0 <= uid && uid <= 07777777,
"Tar uid out of range: %s", uid);
setField("uid", 108, 8, String.format("%07o", uid));
return this;
}
/**
* Sets the UNIX group ID. By default this is {@value #DEFAULT_GID}.
*
* @param gid must be {@code >=0} and {@code <8^7}.
* @see #setGname(String)
*/
public Builder setGid(int gid) {
checkArgument(0 <= gid && gid <= 07777777,
"Tar gid out of range: %s", gid);
setField("gid", 116, 8, String.format("%07o", gid));
return this;
}
/**
* Sets the file size. (Required)
*
* <p>This value must be known in advance. There's no such thing as a streaming tar archive.
*
* @param size must be {@code >=0} and {@code <8^11} which places an eight gigabyte limit.
*/
public Builder setSize(long size) {
checkArgument(0 <= size && size <= 077777777777L,
"Tar size out of range: %s", size);
setField("size", 124, 12, String.format("%011o", size));
hasSize = true;
return this;
}
/**
* Sets the modified time of the file. By default, this is the time the builder object was
* constructed.
*
* <p>The modified time is always stored as a UNIX timestamp which is seconds since the UNIX
* epoch in UTC time. Because {@link DateTime} has millisecond precision, it gets rounded down
* (floor) to the second.
*
* @throws NullPointerException
*/
public Builder setMtime(DateTime mtime) {
checkNotNull(mtime, "mtime");
setField("mtime", 136, 12, String.format("%011o", mtime.getMillis() / MILLIS_PER_SECOND));
return this;
}
private void setChksum() {
setField("chksum", 148, 8, String.format("%06o", checksum(header)));
}
/**
* Sets the file {@link Type}. By default this is {@link Type#REGULAR}.
*/
public Builder setType(Type type) {
switch (type) {
case REGULAR:
header[156] = '0';
break;
case DIRECTORY:
header[156] = '5';
break;
default:
throw new UnsupportedOperationException();
}
return this;
}
/**
* Sets the UNIX owner of the file. By default this is {@value #DEFAULT_UNAME}.
*
* @param uname must be {@code <32} characters in length.
* @see #setUid(int)
* @see #setGname(String)
*/
public Builder setUname(String uname) {
checkArgument(!isNullOrEmpty(uname), "uname");
setField("uname", 265, 32, uname);
return this;
}
/**
* Sets the UNIX group of the file. By default this is {@value #DEFAULT_GNAME}.
*
* @param gname must be {@code <32} characters in length.
* @see #setGid(int)
* @see #setUname(String)
*/
public Builder setGname(String gname) {
checkArgument(!isNullOrEmpty(gname), "gname");
setField("gname", 297, 32, gname);
return this;
}
private void setMagic() {
setField("magic", 257, 6, "ustar");
}
private void setVersion() {
header[263] = '0';
header[264] = '0';
}
/**
* Returns a new immutable {@link PosixTarHeader} instance.
*
* <p>It's safe to save a reference to the builder instance and call this method multiple times
* because the header data is copied into the resulting object.
*
* @throws IllegalStateException if you forgot to call required setters.
*/
public PosixTarHeader build() {
checkState(hasName, "name not set");
checkState(hasSize, "size not set");
hasName = false;
hasSize = false;
setChksum(); // Calculate the checksum last.
return new PosixTarHeader(header.clone());
}
private void setField(String fieldName, int offset, int max, String data) {
byte[] bytes = (data + "\0").getBytes(UTF_8);
checkArgument(bytes.length <= max,
"%s field exceeds max length of %s: %s", fieldName, max - 1, data);
System.arraycopy(bytes, 0, header, offset, bytes.length);
Arrays.fill(header, offset + bytes.length, offset + max, (byte) 0);
}
}
}

View file

@ -0,0 +1,48 @@
// 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.util;
import static com.google.common.base.Preconditions.checkArgument;
import javax.annotation.Nullable;
/** Utility methods related to preconditions checking. */
public class PreconditionsUtils {
/**
* Checks whether the provided reference is null, throws IAE if it is, and returns it if not.
*
* <p>This method and its overloads are to substitute for checkNotNull() in cases where it's
* preferable to throw an IAE instead of an NPE, such as where we want an IAE to indicate that
* it's just a bad argument/parameter and reserve NPEs for bugs and unexpected null values.
*/
public static <T> T checkArgumentNotNull(T reference) {
checkArgument(reference != null);
return reference;
}
/** Checks whether the provided reference is null, throws IAE if it is, and returns it if not. */
public static <T> T checkArgumentNotNull(T reference, @Nullable Object errorMessage) {
checkArgument(reference != null, errorMessage);
return reference;
}
/** Checks whether the provided reference is null, throws IAE if it is, and returns it if not. */
public static <T> T checkArgumentNotNull(
T reference, @Nullable String errorMessageTemplate, @Nullable Object... errorMessageArgs) {
checkArgument(reference != null, errorMessageTemplate, errorMessageArgs);
return reference;
}
}

View file

@ -0,0 +1,45 @@
// 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.util;
import com.google.common.base.Predicate;
/** Utility class containing {@link Predicate} methods. */
public class PredicateUtils {
/**
* A predicate for a given class X that checks if tested classes are supertypes of X.
*
* <p>We need our own predicate because Guava's class predicates are backwards.
* @see "https://github.com/google/guava/issues/1444"
*/
private static class SupertypeOfPredicate implements Predicate<Class<?>> {
private final Class<?> subClass;
SupertypeOfPredicate(Class<?> subClass) {
this.subClass = subClass;
}
@Override
public boolean apply(Class<?> superClass) {
return superClass.isAssignableFrom(subClass);
}
}
public static Predicate<Class<?>> supertypeOf(Class<?> subClass) {
return new SupertypeOfPredicate(subClass);
}
}

View file

@ -0,0 +1,34 @@
// 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.util;
import static com.google.common.base.CharMatcher.javaLetterOrDigit;
/** Utilities for working with {@code Registrar} objects. */
public class RegistrarUtils {
/** Strip out anything that isn't a letter or digit, and lowercase. */
public static String normalizeRegistrarName(String name) {
return javaLetterOrDigit().retainFrom(name).toLowerCase();
}
/**
* Returns a normalized registrar clientId by taking the input and making it lowercase and
* removing all characters that aren't alphanumeric or hyphens. The normalized id should be unique
* in Datastore, and is suitable for use in email addresses.
*/
public static String normalizeClientId(String clientId) {
return clientId.toLowerCase().replaceAll("[^a-z0-9\\-]", "");
}
}

View file

@ -0,0 +1,52 @@
// 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.util;
import static com.google.common.io.Resources.asByteSource;
import static com.google.common.io.Resources.getResource;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.io.ByteSource;
import com.google.common.io.Resources;
import java.io.IOException;
import java.net.URL;
/** Utility methods related to reading java resources. */
public final class ResourceUtils {
/** Loads a file as a string, assuming UTF-8 encoding. */
public static String readResourceUtf8(String filename) {
return resourceToString(getResource(filename));
}
/** Loads a file (specified relative to the contextClass) as a string, assuming UTF-8 encoding. */
public static String readResourceUtf8(Class<?> contextClass, String filename) {
return resourceToString(getResource(contextClass, filename));
}
private static String resourceToString(URL url) {
try {
return Resources.toString(url, UTF_8);
} catch (IOException e) {
throw new IllegalArgumentException("Failed to load resource: " + url, e);
}
}
/** Loads a file (specified relative to the contextClass) as a ByteSource. */
public static ByteSource readResourceBytes(Class<?> contextClass, String filename) {
return asByteSource(getResource(contextClass, filename));
}
}

View file

@ -0,0 +1,108 @@
// 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.util;
import static com.google.appengine.api.search.checkers.Preconditions.checkArgument;
import static com.google.common.base.Throwables.propagate;
import static com.google.common.collect.Iterables.any;
import static com.google.common.math.IntMath.pow;
import static com.google.domain.registry.util.PredicateUtils.supertypeOf;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableSet;
import com.google.domain.registry.config.ConfigModule.Config;
import org.joda.time.Duration;
import java.io.Serializable;
import java.util.Set;
import java.util.concurrent.Callable;
import javax.inject.Inject;
/** Wrapper that does retry with exponential backoff. */
public class Retrier implements Serializable {
private static final long serialVersionUID = 1167386907195735483L;
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
private final Sleeper sleeper;
private final int attempts;
@Inject
public Retrier(Sleeper sleeper, @Config("transientFailureRetries") int transientFailureRetries) {
this.sleeper = sleeper;
checkArgument(transientFailureRetries > 0, "Number of attempts must be positive");
this.attempts = transientFailureRetries;
}
/**
* Retries a unit of work in the face of transient errors.
*
* <p>Retrying is done a fixed number of times, with exponential backoff, if the exception that is
* thrown is deemed retryable by the predicate. If the error is not considered retryable, or if
* the thread is interrupted, or if the allowable number of attempts has been exhausted, the
* original exception is propagated through to the caller.
*
* @return <V> the value returned by the {@link Callable}.
*/
public final <V> V callWithRetry(Callable<V> callable, Predicate<Throwable> isRetryable) {
int failures = 0;
while (true) {
try {
return callable.call();
} catch (Throwable e) {
if (++failures == attempts || !isRetryable.apply(e)) {
propagate(e);
}
logger.info(e, "Retrying transient error, attempt " + failures);
try {
// Wait 100ms on the first attempt, doubling on each subsequent attempt.
sleeper.sleep(Duration.millis(pow(2, failures) * 100));
} catch (InterruptedException e2) {
// Since we're not rethrowing InterruptedException, set the interrupt state on the thread
// so the next blocking operation will know to abort the thread.
Thread.currentThread().interrupt();
propagate(e);
}
}
}
}
/**
* Retries a unit of work in the face of transient errors.
*
* <p>Retrying is done a fixed number of times, with exponential backoff, if the exception that is
* thrown is on a whitelist of retryable errors. If the error is not on the whitelist, or if the
* thread is interrupted, or if the allowable number of attempts has been exhausted, the original
* exception is propagated through to the caller.
*
* @return <V> the value returned by the {@link Callable}.
*/
@SafeVarargs
public final <V> V callWithRetry(
Callable<V> callable,
Class<? extends RuntimeException> retryableError,
Class<? extends RuntimeException>... moreRetryableErrors) {
final Set<Class<?>> retryables =
new ImmutableSet.Builder<Class<?>>().add(retryableError).add(moreRetryableErrors).build();
return callWithRetry(callable, new Predicate<Throwable>() {
@Override
public boolean apply(Throwable e) {
return any(retryables, supertypeOf(e.getClass()));
}});
}
}

View file

@ -0,0 +1,37 @@
// 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.util;
import java.util.Properties;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.MimeMessage;
/** Wrapper around javax.mail's Transport.send that can be mocked for testing purposes. */
public class SendEmailService {
/** Returns a new MimeMessage using default App Engine transport settings. */
public Message createMessage() {
return new MimeMessage(Session.getDefaultInstance(new Properties(), null));
}
/** Sends a message using default App Engine transport. */
public void sendMessage(Message msg) throws MessagingException {
Transport.send(msg);
}
}

View file

@ -0,0 +1,98 @@
// 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.util;
import static com.google.common.collect.Iterables.toArray;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Predicates;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.domain.registry.config.RegistryConfig;
import com.google.domain.registry.config.RegistryEnvironment;
import java.util.List;
import javax.mail.Message;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
/**
* Utility class for sending emails from the app.
*/
public class SendEmailUtils {
private static final RegistryConfig CONFIG = RegistryEnvironment.get().config();
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
@NonFinalForTesting
private static SendEmailService emailService = new SendEmailService();
/**
* Sends an email from Domain Registry to the specified recipient. Returns true iff sending was
* successful.
*/
public static boolean sendEmail(String address, String subject, String body) {
return sendEmail(ImmutableList.of(address), subject, body);
}
/**
* Sends an email from Domain Registry to the specified recipients. Returns true iff sending was
* successful.
*/
public static boolean sendEmail(Iterable<String> addresses, final String subject, String body) {
try {
Message msg = emailService.createMessage();
msg.setFrom(new InternetAddress(
CONFIG.getGoogleAppsSendFromEmailAddress(),
CONFIG.getGoogleAppsAdminEmailDisplayName()));
List<InternetAddress> emails = FluentIterable
.from(addresses)
.transform(new Function<String, InternetAddress>() {
@Override
public InternetAddress apply(String emailAddress) {
try {
return new InternetAddress(emailAddress);
} catch (AddressException e) {
logger.severefmt(
e,
"Could not send email to %s with subject '%s'.",
emailAddress,
subject);
// Returning null excludes this address from the list of recipients on the email.
return null;
}
}})
.filter(Predicates.notNull())
.toList();
if (emails.isEmpty()) {
return false;
}
msg.addRecipients(Message.RecipientType.TO, toArray(emails, InternetAddress.class));
msg.setSubject(subject);
msg.setText(body);
emailService.sendMessage(msg);
} catch (Throwable t) {
logger.severefmt(
t,
"Could not email to addresses %s with subject '%s'.",
Joiner.on(", ").join(addresses),
subject);
return false;
}
return true;
}
}

View file

@ -0,0 +1,70 @@
// 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.util;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.io.BaseEncoding.base16;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import javax.annotation.Nullable;
/** Utilities for easy serialization with informative error messages. */
public final class SerializeUtils {
/**
* Turns an object into a byte array.
*
* @return serialized object or {@code null} if {@code value} is {@code null}
*/
@Nullable
public static byte[] serialize(@Nullable Object value) {
if (value == null) {
return null;
}
ByteArrayOutputStream objectBytes = new ByteArrayOutputStream();
try (ObjectOutputStream oos = new ObjectOutputStream(objectBytes)) {
oos.writeObject(value);
} catch (IOException e) {
throw new IllegalArgumentException("Unable to serialize: " + value, e);
}
return objectBytes.toByteArray();
}
/**
* Turns a byte array into an object.
*
* @return deserialized object or {@code null} if {@code objectBytes} is {@code null}
*/
@Nullable
public static <T> T deserialize(Class<T> type, @Nullable byte[] objectBytes) {
checkNotNull(type);
if (objectBytes == null) {
return null;
}
try {
return type.cast(new ObjectInputStream(new ByteArrayInputStream(objectBytes)).readObject());
} catch (ClassNotFoundException | IOException e) {
throw new IllegalArgumentException(
"Unable to deserialize: objectBytes=" + base16().encode(objectBytes), e);
}
}
private SerializeUtils() {}
}

View file

@ -0,0 +1,46 @@
// 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.util;
import org.joda.time.ReadableDuration;
import javax.annotation.concurrent.ThreadSafe;
/**
* An object which accepts requests to put the current thread to sleep.
*
* @see SystemSleeper
* @see com.google.domain.registry.testing.FakeSleeper
*/
@ThreadSafe
public interface Sleeper {
/**
* Puts the current thread to sleep.
*
* @throws InterruptedException if this thread was interrupted
*/
void sleep(ReadableDuration duration) throws InterruptedException;
/**
* Puts the current thread to sleep, ignoring interrupts.
*
* <p>If {@link InterruptedException} was caught, then {@code Thread.currentThread().interrupt()}
* will be called at the end of the {@code duration}.
*
* @see com.google.common.util.concurrent.Uninterruptibles#sleepUninterruptibly
*/
void sleepUninterruptibly(ReadableDuration duration);
}

View file

@ -0,0 +1,105 @@
// 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.util;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.Sets.difference;
import static com.google.domain.registry.util.PreconditionsUtils.checkArgumentNotNull;
import com.google.common.base.CharMatcher;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableMap;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.concurrent.Immutable;
/** SQL template variable substitution. */
@Immutable
public final class SqlTemplate {
private static final Pattern KEY_PATTERN = Pattern.compile("[A-Z][_A-Z0-9]*");
private static final Pattern SEARCH_PATTERN =
Pattern.compile("(['\"]?)%(" + KEY_PATTERN + ")%(['\"]?)");
private static final CharMatcher LEGAL_SUBSTITUTIONS =
CharMatcher.javaLetterOrDigit().or(CharMatcher.anyOf("-_.,: "));
/** Returns a new immutable SQL template builder object, for query parameter substitution. */
public static SqlTemplate create(String template) {
return new SqlTemplate(template, ImmutableMap.<String, String>of());
}
/**
* Adds a key/value that should be substituted an individual variable in the template.
*
* <p>Your template variables should appear as follows: {@code WHERE foo = '%BAR%'} and you
* would call {@code .put("BAR", "some value"} to safely substitute it with a value. Only
* whitelisted characters (as defined by {@link #LEGAL_SUBSTITUTIONS}) are allowed in values.
*
* @param key uppercase string that can have digits and underscores
* @param value substitution value, comprised of whitelisted characters
* @throws IllegalArgumentException if key or value has bad chars or duplicate keys were added
*/
public SqlTemplate put(String key, String value) {
checkArgument(KEY_PATTERN.matcher(key).matches(), "Bad substitution key: %s", key);
checkArgument(LEGAL_SUBSTITUTIONS.matchesAllOf(value), "Illegal characters in %s", value);
return new SqlTemplate(template, new ImmutableMap.Builder<String, String>()
.putAll(substitutions)
.put(key, value)
.build());
}
/**
* Returns the freshly substituted SQL code.
*
* @throws IllegalArgumentException if any substitution variable is not found in the template,
* or if there are any variable-like strings (%something%) left after substitution.
*/
public String build() {
StringBuffer result = new StringBuffer(template.length());
Set<String> found = new HashSet<>();
Matcher matcher = SEARCH_PATTERN.matcher(template);
while (matcher.find()) {
String wholeMatch = matcher.group(0);
String leftQuote = matcher.group(1);
String key = matcher.group(2);
String rightQuote = matcher.group(3);
String value = substitutions.get(key);
checkArgumentNotNull(value, "%%s% found in template but no substitution specified", key);
checkArgument(leftQuote.equals(rightQuote), "Quote mismatch: %s", wholeMatch);
matcher.appendReplacement(result, String.format("%s%s%s", leftQuote, value, rightQuote));
found.add(key);
}
matcher.appendTail(result);
Set<String> remaining = difference(substitutions.keySet(), found);
checkArgument(remaining.isEmpty(),
"Not found in template: %s", Joiner.on(", ").join(remaining));
return result.toString();
}
private final String template;
private final ImmutableMap<String, String> substitutions;
private SqlTemplate(String template, ImmutableMap<String, String> substitutions) {
this.template = checkNotNull(template);
this.substitutions = checkNotNull(substitutions);
}
}

View file

@ -0,0 +1,46 @@
// 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.util;
import static org.joda.time.DateTimeZone.UTC;
import dagger.Module;
import dagger.Provides;
import org.joda.time.DateTime;
import javax.annotation.concurrent.ThreadSafe;
/** Clock implementation that proxies to the real system clock. */
@ThreadSafe
public class SystemClock implements Clock {
/** Returns the current time. */
@Override
public DateTime nowUtc() {
return DateTime.now(UTC);
}
/** Dagger module for {@link SystemClock}. */
@Module
public static final class SystemClockModule {
private static final Clock clock = new SystemClock();
@Provides
static Clock provideClock() {
return clock;
}
}
}

View file

@ -0,0 +1,63 @@
// 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.util;
import static com.google.common.base.Preconditions.checkArgument;
import com.google.common.util.concurrent.Uninterruptibles;
import dagger.Module;
import dagger.Provides;
import org.joda.time.ReadableDuration;
import java.io.Serializable;
import java.util.concurrent.TimeUnit;
import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject;
/** Implementation of {@link Sleeper} for production use. */
@ThreadSafe
public final class SystemSleeper implements Sleeper, Serializable {
private static final long serialVersionUID = 2003215961965322843L;
@Inject
public SystemSleeper() {}
@Override
public void sleep(ReadableDuration duration) throws InterruptedException {
checkArgument(duration.getMillis() >= 0);
Thread.sleep(duration.getMillis());
}
@Override
public void sleepUninterruptibly(ReadableDuration duration) {
checkArgument(duration.getMillis() >= 0);
Uninterruptibles.sleepUninterruptibly(duration.getMillis(), TimeUnit.MILLISECONDS);
}
/** Dagger module for {@link SystemSleeper}. */
@Module
public static final class SystemSleeperModule {
private static final Sleeper sleeper = new SystemSleeper();
@Provides
static Sleeper provideSleeper() {
return sleeper;
}
}
}

View file

@ -0,0 +1,81 @@
// 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.util;
import static java.util.Arrays.asList;
import com.google.appengine.api.taskqueue.Queue;
import com.google.appengine.api.taskqueue.TaskHandle;
import com.google.appengine.api.taskqueue.TaskOptions;
import com.google.appengine.api.taskqueue.TransientFailureException;
import java.io.Serializable;
import java.util.List;
import java.util.concurrent.Callable;
import javax.inject.Inject;
/** Utilities for dealing with App Engine task queues. */
public class TaskEnqueuer implements Serializable {
private static final long serialVersionUID = 7893211200220508362L;
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
private final Retrier retrier;
@Inject
public TaskEnqueuer(Retrier retrier) {
this.retrier = retrier;
}
/**
* Adds a task to a App Engine task queue in a reliable manner.
*
* <p>This is the same as {@link Queue#add(TaskOptions)} except it'll automatically retry with
* exponential backoff if {@link TransientFailureException} is thrown.
*
* @throws TransientFailureException if retrying failed for the maximum period of time, or an
* {@link InterruptedException} told us to stop trying
* @return successfully enqueued task
*/
public TaskHandle enqueue(Queue queue, TaskOptions task) {
return enqueue(queue, asList(task)).get(0);
}
/**
* Adds tasks to an App Engine task queue in a reliable manner.
*
* <p>This is the same as {@link Queue#add(Iterable)} except it'll automatically retry with
* exponential backoff if {@link TransientFailureException} is thrown.
*
* @throws TransientFailureException if retrying failed for the maximum period of time, or an
* {@link InterruptedException} told us to stop trying
* @return successfully enqueued tasks
*/
public List<TaskHandle> enqueue(final Queue queue, final Iterable<TaskOptions> tasks) {
return retrier.callWithRetry(
new Callable<List<TaskHandle>>() {
@Override
public List<TaskHandle> call() {
for (TaskOptions task : tasks) {
logger.infofmt(
"Enqueuing queue='%s' endpoint='%s'", queue.getQueueName(), task.getUrl());
}
return queue.add(tasks);
}},
TransientFailureException.class);
}
}

View file

@ -0,0 +1,69 @@
// 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.util;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.io.OutputStream;
import javax.annotation.WillNotClose;
/**
* {@link OutputStream} delegate that writes simultaneously to multiple other output streams.
*/
public final class TeeOutputStream extends OutputStream {
private final ImmutableList<? extends OutputStream> outputs;
private boolean isClosed;
public TeeOutputStream(@WillNotClose Iterable<? extends OutputStream> outputs) {
this.outputs = ImmutableList.copyOf(outputs);
checkArgument(!this.outputs.isEmpty(), "must provide at least one output stream");
}
/** @see java.io.OutputStream#write(int) */
@Override
public void write(int b) throws IOException {
checkState(!isClosed, "outputstream closed");
for (OutputStream out : outputs) {
out.write(b);
}
}
/** @see #write(byte[], int, int) */
@Override
public void write(byte[] b) throws IOException {
this.write(b, 0, b.length);
}
/** @see java.io.OutputStream#write(byte[], int, int) */
@Override
public void write(byte[] b, int off, int len) throws IOException {
checkState(!isClosed, "outputstream closed");
for (OutputStream out : outputs) {
out.write(b, off, len);
}
}
/** Closes the stream. Any calls to a {@code write()} method after this will throw. */
@Override
public void close() throws IOException {
isClosed = true;
}
}

View file

@ -0,0 +1,105 @@
// 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.util;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.domain.registry.util.CollectionUtils.difference;
import static java.lang.reflect.Modifier.isFinal;
import static java.lang.reflect.Modifier.isStatic;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.reflect.TypeToken;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
/** Utilities methods related to reflection. */
public class TypeUtils {
/** A {@TypeToken} that removes an ugly cast in the common cases of getting a known type. */
public static class TypeInstantiator<T> extends TypeToken<T> {
protected TypeInstantiator(Class<?> declaringClass) {
super(declaringClass);
}
@SuppressWarnings("unchecked")
public Class<T> getExactType() {
return (Class<T>) getRawType();
}
public T instantiate() {
return TypeUtils.instantiate(getExactType());
}
}
@SuppressWarnings("unchecked")
public static <T> T instantiate(Class<?> clazz) {
checkArgument(Modifier.isPublic(clazz.getModifiers()),
"AppEngine's custom security manager won't let us reflectively access non-public types");
try {
return (T) clazz.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
/**
* Aggregates enum "values" in a typesafe enum pattern into a string->field map.
*/
@SuppressWarnings("unchecked")
public static <T> ImmutableMap<String, T> getTypesafeEnumMapping(Class<T> clazz) {
ImmutableMap.Builder<String, T> builder = new ImmutableMap.Builder<>();
for (Field field : clazz.getFields()) {
if (isFinal(field.getModifiers())
&& isStatic(field.getModifiers())
&& clazz.isAssignableFrom(field.getType())) {
try {
T enumField = (T) field.get(null);
builder.put(field.getName(), enumField);
} catch (IllegalArgumentException | IllegalAccessException e) {
throw new RuntimeException(String.format(
"Could not retrieve static final field mapping for %s", clazz.getName()), e);
}
}
}
return builder.build();
}
/** Returns a predicate that tests whether classes are annotated with the given annotation. */
public static final Predicate<Class<?>> hasAnnotation(
final Class<? extends Annotation> annotation) {
return new Predicate<Class<?>>() {
@Override
public boolean apply(Class<?> clazz) {
return clazz.isAnnotationPresent(annotation);
}
};
}
public static void checkNoInheritanceRelationships(ImmutableSet<Class<?>> resourceClasses) {
for (Class<?> resourceClass : resourceClasses) {
for (Class<?> potentialSuperclass : difference(resourceClasses, resourceClass)) {
checkArgument(
!potentialSuperclass.isAssignableFrom(resourceClass),
"Cannot specify resource classes with inheritance relationship: %s extends %s",
resourceClass,
potentialSuperclass);
}
}
}
}

View file

@ -0,0 +1,60 @@
// 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.util;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.appengine.api.urlfetch.HTTPHeader;
import com.google.appengine.api.urlfetch.HTTPRequest;
import com.google.appengine.api.urlfetch.HTTPResponse;
/**
* Exception for when App Engine HTTP requests return a bad response.
*
* <p>This class displays lots of helpful troubleshooting information.
*/
public class UrlFetchException extends RuntimeException {
private final HTTPRequest req;
private final HTTPResponse rsp;
public UrlFetchException(String message, HTTPRequest req, HTTPResponse rsp) {
super(message);
this.req = checkNotNull(req, "req");
this.rsp = checkNotNull(rsp, "rsp");
}
@Override
public String toString() {
StringBuilder res = new StringBuilder(2048 + rsp.getContent().length).append(String.format(
"%s: %s (HTTP Status %d)\nX-Fetch-URL: %s\nX-Final-URL: %s\n",
getClass().getSimpleName(),
getMessage(),
rsp.getResponseCode(),
req.getURL().toString(),
rsp.getFinalUrl()));
for (HTTPHeader header : rsp.getHeadersUncombined()) {
res.append(header.getName());
res.append(": ");
res.append(header.getValue());
res.append('\n');
}
res.append(">>>\n");
res.append(new String(rsp.getContent(), UTF_8));
res.append("\n<<<");
return res.toString();
}
}

View file

@ -0,0 +1,108 @@
// 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.util;
import static com.google.common.io.BaseEncoding.base32;
import static com.google.common.io.BaseEncoding.base64;
import static com.google.common.net.HttpHeaders.AUTHORIZATION;
import static com.google.common.net.HttpHeaders.CONTENT_DISPOSITION;
import static com.google.common.net.HttpHeaders.CONTENT_LENGTH;
import static com.google.common.net.HttpHeaders.CONTENT_TYPE;
import static java.lang.String.format;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.appengine.api.urlfetch.HTTPHeader;
import com.google.appengine.api.urlfetch.HTTPRequest;
import com.google.appengine.api.urlfetch.HTTPResponse;
import com.google.common.base.Optional;
import com.google.common.net.MediaType;
import java.security.NoSuchAlgorithmException;
import java.security.ProviderException;
import java.security.SecureRandom;
/** Helper methods for the App Engine URL fetch service. */
public final class UrlFetchUtils {
@NonFinalForTesting
private static SecureRandom secureRandom = initSecureRandom();
/** Returns value of first header matching {@code name}. */
public static Optional<String> getHeaderFirst(HTTPResponse rsp, String name) {
return getHeaderFirstInternal(rsp.getHeadersUncombined(), name);
}
/** Returns value of first header matching {@code name}. */
public static Optional<String> getHeaderFirst(HTTPRequest req, String name) {
return getHeaderFirstInternal(req.getHeaders(), name);
}
private static Optional<String> getHeaderFirstInternal(Iterable<HTTPHeader> hdrs, String name) {
name = name.toLowerCase();
for (HTTPHeader header : hdrs) {
if (header.getName().toLowerCase().equals(name)) {
return Optional.of(header.getValue());
}
}
return Optional.absent();
}
/**
* Sets payload on request as a {@code multipart/form-data} request.
*
* <p>This is equivalent to running the command: {@code curl -F fieldName=@payload.txt URL}
*
* @see "http://www.ietf.org/rfc/rfc2388.txt"
*/
public static <T> void setPayloadMultipart(
HTTPRequest request, String name, String filename, MediaType contentType, T data) {
String boundary = createMultipartBoundary();
StringBuilder multipart = new StringBuilder();
multipart.append(format("--%s\r\n", boundary));
multipart.append(format("%s: form-data; name=\"%s\"; filename=\"%s\"\r\n",
CONTENT_DISPOSITION, name, filename));
multipart.append(format("%s: %s\r\n", CONTENT_TYPE, contentType.toString()));
multipart.append("\r\n");
multipart.append(data);
multipart.append("\r\n");
multipart.append(format("--%s--", boundary));
byte[] payload = multipart.toString().getBytes(UTF_8);
request.addHeader(new HTTPHeader(CONTENT_TYPE, "multipart/form-data; boundary=" + boundary));
request.addHeader(new HTTPHeader(CONTENT_LENGTH, Integer.toString(payload.length)));
request.setPayload(payload);
}
private static String createMultipartBoundary() {
byte[] rand = new byte[5]; // Avoid base32 padding since `5 * 8 % log2(32) == 0`
secureRandom.nextBytes(rand);
return "------------------------------" + base32().encode(rand);
}
private static SecureRandom initSecureRandom() {
try {
return SecureRandom.getInstance("NativePRNG");
} catch (NoSuchAlgorithmException e) {
throw new ProviderException(e);
}
}
/** Sets the HTTP Basic Authentication header on an {@link HTTPRequest}. */
public static void setAuthorizationHeader(HTTPRequest req, Optional<String> login) {
if (login.isPresent()) {
String token = base64().encode(login.get().getBytes(UTF_8));
req.addHeader(new HTTPHeader(AUTHORIZATION, "Basic " + token));
}
}
}

View file

@ -0,0 +1,183 @@
// 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.util;
import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Throwables.propagateIfInstanceOf;
import static com.google.common.io.BaseEncoding.base64;
import static java.nio.charset.StandardCharsets.US_ASCII;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CRLException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.CertificateParsingException;
import java.security.cert.CertificateRevokedException;
import java.security.cert.Extension;
import java.security.cert.X509CRL;
import java.security.cert.X509CRLEntry;
import java.security.cert.X509Certificate;
import java.util.Date;
import java.util.NoSuchElementException;
import javax.annotation.Tainted;
/** X.509 Public Key Infrastructure (PKI) helper functions. */
public final class X509Utils {
/**
* Parse the encoded certificate and return a base64 encoded string (without padding) of the
* SHA-256 digest of the certificate.
* <p>
* Note that this must match the method used by the GFE to generate the client certificate hash so
* that the two will match when we check against the whitelist.
*/
public static String getCertificateHash(X509Certificate cert) {
try {
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
messageDigest.update(cert.getEncoded());
return base64().omitPadding().encode(messageDigest.digest());
} catch (CertificateException | NoSuchAlgorithmException e) {
throw new IllegalArgumentException(e);
}
}
/**
* Loads an ASCII-armored public X.509 certificate.
*
* @throws CertificateParsingException on parsing errors.
*/
public static X509Certificate loadCertificate(InputStream input)
throws CertificateParsingException {
try {
return Iterables.getOnlyElement(FluentIterable
.from(CertificateFactory.getInstance("X.509").generateCertificates(input))
.filter(X509Certificate.class));
} catch (CertificateException e) { // CertificateParsingException by specification.
propagateIfInstanceOf(e, CertificateParsingException.class);
throw new CertificateParsingException(e);
} catch (NoSuchElementException e) {
throw new CertificateParsingException("No X509Certificate found.");
} catch (IllegalArgumentException e) {
throw new CertificateParsingException("Multiple X509Certificate found.");
}
}
/**
* Loads an ASCII-armored public X.509 certificate.
*
* @throws CertificateParsingException on parsing errors
*/
public static X509Certificate loadCertificate(String asciiCrt)
throws CertificateParsingException {
return loadCertificate(new ByteArrayInputStream(asciiCrt.getBytes(US_ASCII)));
}
/**
* Loads an ASCII-armored public X.509 certificate.
*
* @throws CertificateParsingException on parsing errors
* @throws IOException on file system errors
*/
public static X509Certificate loadCertificate(Path certPath)
throws CertificateParsingException, IOException {
return loadCertificate(Files.newInputStream(certPath));
}
/**
* Loads an ASCII-armored X.509 certificate revocation list (CRL).
*
* @throws CRLException on parsing errors.
*/
public static X509CRL loadCrl(String asciiCrl) throws GeneralSecurityException {
ByteArrayInputStream input = new ByteArrayInputStream(asciiCrl.getBytes(US_ASCII));
try {
return Iterables.getOnlyElement(FluentIterable
.from(CertificateFactory.getInstance("X.509").generateCRLs(input))
.filter(X509CRL.class));
} catch (NoSuchElementException e) {
throw new CRLException("No X509CRL found.");
} catch (IllegalArgumentException e) {
throw new CRLException("Multiple X509CRL found.");
}
}
/**
* Check that {@code cert} is signed by the {@code ca} and not revoked.
*
* <p>Support for certificate chains has not been implemented.
*
* @throws GeneralSecurityException for unsupported protocols, certs not signed by the TMCH,
* parsing errors, encoding errors, if the CRL is expired, or if the CRL is older than the
* one currently in memory.
*/
public static void verifyCertificate(
X509Certificate rootCert, X509CRL crl, @Tainted X509Certificate cert, Date now)
throws GeneralSecurityException {
cert.checkValidity(checkNotNull(now, "now"));
try {
cert.verify(rootCert.getPublicKey());
} catch (CertificateException e) {
propagateIfInstanceOf(e, CertificateException.class);
throw new CertificateEncodingException(e); // Coercion by specification.
}
if (crl.isRevoked(cert)) {
X509CRLEntry entry = crl.getRevokedCertificate(cert);
throw new CertificateRevokedException(
checkNotNull(entry.getRevocationDate(), "revocationDate"),
checkNotNull(entry.getRevocationReason(), "revocationReason"),
firstNonNull(entry.getCertificateIssuer(), crl.getIssuerX500Principal()),
ImmutableMap.<String, Extension>of());
}
}
/**
* Checks if an X.509 CRL you downloaded can safely replace your current CRL.
*
* <p>This routine makes sure {@code newCrl} is signed by {@code rootCert} and that its timestamps
* are correct with respect to {@code now}.
*
* @throws GeneralSecurityException for unsupported protocols, certs not signed by the TMCH,
* incorrect keys, and for invalid, old, not-yet-valid or revoked certificates.
*/
public static void verifyCrl(
X509Certificate rootCert, X509CRL oldCrl, @Tainted X509CRL newCrl, Date now)
throws GeneralSecurityException {
if (newCrl.getThisUpdate().before(oldCrl.getThisUpdate())) {
throw new CRLException(String.format(
"New CRL is more out of date than our current CRL. %s < %s\n%s",
newCrl.getThisUpdate(), oldCrl.getThisUpdate(), newCrl));
}
if (newCrl.getNextUpdate().before(now)) {
throw new CRLException("CRL has expired.\n" + newCrl);
}
newCrl.verify(rootCert.getPublicKey());
}
private X509Utils() {}
}

View file

@ -0,0 +1,29 @@
// 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.util;
import javax.xml.bind.annotation.XmlEnumValue;
/** Utility methods related to xml enums. */
public class XmlEnumUtils {
/** Read the {@link XmlEnumValue} string off of an enum. */
public static String enumToXml(Enum<?> input) {
try {
return input.getClass().getField(input.name()).getAnnotation(XmlEnumValue.class).value();
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
}
}
}

View file

@ -0,0 +1,16 @@
// 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.
@javax.annotation.ParametersAreNonnullByDefault
package com.google.domain.registry.util;