// Copyright 2017 The Nomulus Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package google.registry.reporting; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.net.MediaType.CSV_UTF_8; import static google.registry.model.registry.Registries.assertTldExists; import static java.nio.charset.StandardCharsets.UTF_8; import com.google.api.client.http.ByteArrayContent; import com.google.api.client.http.GenericUrl; import com.google.api.client.http.HttpHeaders; import com.google.api.client.http.HttpRequest; import com.google.api.client.http.HttpResponse; import com.google.api.client.http.HttpTransport; import com.google.common.base.Ascii; import com.google.common.base.Splitter; import com.google.common.io.ByteStreams; import google.registry.config.RegistryConfig.Config; import google.registry.keyring.api.KeyModule.Key; import google.registry.reporting.IcannReportingModule.ReportType; import google.registry.request.HttpException.InternalServerErrorException; import google.registry.util.FormattingLogger; import google.registry.xjc.XjcXmlTransformer; import google.registry.xjc.iirdea.XjcIirdeaResponseElement; import google.registry.xjc.iirdea.XjcIirdeaResult; import google.registry.xml.XmlException; import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.List; import javax.inject.Inject; import org.joda.time.YearMonth; import org.joda.time.format.DateTimeFormat; /** * Class that uploads a CSV file to ICANN's endpoint via an HTTP PUT call. * *
It uses basic authorization credentials as specified in the "Registry Interfaces" draft. * *
Note that there's a lot of hard-coded logic extracting parameters from the report filenames.
* These are safe, as long as they follow the tld-reportType-yearMonth.csv filename format.
*
* @see IcannReportingUploadAction
* @see ICANN
* Reporting Specification
*/
public class IcannHttpReporter {
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
@Inject HttpTransport httpTransport;
@Inject @Key("icannReportingPassword") String password;
@Inject @Config("icannTransactionsReportingUploadUrl") String icannTransactionsUrl;
@Inject @Config("icannActivityReportingUploadUrl") String icannActivityUrl;
@Inject IcannHttpReporter() {}
/** Uploads {@code reportBytes} to ICANN. */
public void send(byte[] reportBytes, String reportFilename) throws XmlException, IOException {
validateReportFilename(reportFilename);
GenericUrl uploadUrl = new GenericUrl(makeUrl(reportFilename));
HttpRequest request =
httpTransport
.createRequestFactory()
.buildPutRequest(uploadUrl, new ByteArrayContent(CSV_UTF_8.toString(), reportBytes));
HttpHeaders headers = request.getHeaders();
headers.setBasicAuthentication(getTld(reportFilename) + "_ry", password);
headers.setContentType(CSV_UTF_8.toString());
request.setHeaders(headers);
request.setFollowRedirects(false);
HttpResponse response = null;
logger.infofmt(
"Sending report to %s with content length %s",
uploadUrl.toString(), request.getContent().getLength());
try {
response = request.execute();
byte[] content;
try {
content = ByteStreams.toByteArray(response.getContent());
} finally {
response.getContent().close();
}
logger.infofmt("Received response code %s", response.getStatusCode());
logger.infofmt("Response content: %s", new String(content, UTF_8));
XjcIirdeaResult result = parseResult(content);
if (result.getCode().getValue() != 1000) {
logger.warningfmt(
"PUT rejected, status code %s:\n%s\n%s",
result.getCode(),
result.getMsg(),
result.getDescription());
throw new InternalServerErrorException(result.getMsg());
}
} finally {
if (response != null) {
response.disconnect();
} else {
logger.warningfmt(
"Received null response from ICANN server at %s", uploadUrl.toString());
}
}
}
private XjcIirdeaResult parseResult(byte[] content) throws XmlException, IOException {
XjcIirdeaResponseElement response =
XjcXmlTransformer.unmarshal(
XjcIirdeaResponseElement.class, new ByteArrayInputStream(content));
XjcIirdeaResult result = response.getResult();
return result;
}
/** Verifies a given report filename matches the pattern tld-reportType-yyyyMM.csv. */
private void validateReportFilename(String filename) {
checkArgument(
filename.matches("[a-z0-9.\\-]+-((activity)|(transactions))-[0-9]{6}\\.csv"),
"Expected file format: tld-reportType-yyyyMM.csv, got %s instead",
filename);
assertTldExists(getTld(filename));
}
private String getTld(String filename) {
// Extract the TLD, up to second-to-last hyphen in the filename (works with international TLDs)
return filename.substring(0, filename.lastIndexOf('-', filename.lastIndexOf('-') - 1));
}
private String makeUrl(String filename) {
// Filename is in the format tld-reportType-yearMonth.csv
String tld = getTld(filename);
// Remove the tld- prefix and csv suffix
String remainder = filename.substring(tld.length() + 1, filename.length() - 4);
List