mirror of
https://github.com/google/nomulus.git
synced 2025-05-14 16:37:13 +02:00
Restrict domain transfer periods to 1 year
It turns out that this ICANN policy appears to prohibit transfers with registration extensions other than 1 year (section A.8): https://www.icann.org/resources/pages/policy-2012-03-07-en This is backed up by the practical fact that we've never seen a registrar request a transfer for any period other than one year. And removing the support for multi-year transfers vastly simplifies transfer logic and eliminates a bunch of annoying corner cases. Users still can achieve the same thing by doing a 1-year transfer plus a manual renewal afterwards for the remainder of the desired extension. This change leaves in place lots of infrastructure to support multi-year transfers that is now obsolete (e.g. TransferData.extendedRegistrationYears). This should all be cleaned up, but it's a lower priority than fixing the gap itself and insulating ourselves against needing to handle any real multi-year transfer case. Once this CL goes in, we can start ignoring extendedRegistrationYears entirely because it'll always be 1 year, which makes the cleanup process easier. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=150212864
This commit is contained in:
parent
852f1afb6c
commit
f28104ad03
13 changed files with 97 additions and 42 deletions
|
@ -332,6 +332,7 @@ new ones with the correct approval time).
|
||||||
* The requested domain name is on the premium price list, and this
|
* The requested domain name is on the premium price list, and this
|
||||||
registrar has blocked premium registrations.
|
registrar has blocked premium registrations.
|
||||||
* 2306
|
* 2306
|
||||||
|
* Domain transfer period must be one year.
|
||||||
* Periods for domain registrations must be specified in years.
|
* Periods for domain registrations must be specified in years.
|
||||||
* The requested fees cannot be provided in the requested currency.
|
* The requested fees cannot be provided in the requested currency.
|
||||||
|
|
||||||
|
|
|
@ -44,6 +44,7 @@ import google.registry.flows.FlowModule.TargetId;
|
||||||
import google.registry.flows.TransactionalFlow;
|
import google.registry.flows.TransactionalFlow;
|
||||||
import google.registry.flows.exceptions.AlreadyPendingTransferException;
|
import google.registry.flows.exceptions.AlreadyPendingTransferException;
|
||||||
import google.registry.flows.exceptions.ObjectAlreadySponsoredException;
|
import google.registry.flows.exceptions.ObjectAlreadySponsoredException;
|
||||||
|
import google.registry.flows.exceptions.TransferPeriodMustBeOneYearException;
|
||||||
import google.registry.model.domain.DomainCommand.Transfer;
|
import google.registry.model.domain.DomainCommand.Transfer;
|
||||||
import google.registry.model.domain.DomainResource;
|
import google.registry.model.domain.DomainResource;
|
||||||
import google.registry.model.domain.Period;
|
import google.registry.model.domain.Period;
|
||||||
|
@ -89,6 +90,7 @@ import org.joda.time.DateTime;
|
||||||
* @error {@link google.registry.flows.exceptions.MissingTransferRequestAuthInfoException}
|
* @error {@link google.registry.flows.exceptions.MissingTransferRequestAuthInfoException}
|
||||||
* @error {@link google.registry.flows.exceptions.ObjectAlreadySponsoredException}
|
* @error {@link google.registry.flows.exceptions.ObjectAlreadySponsoredException}
|
||||||
* @error {@link google.registry.flows.exceptions.ResourceStatusProhibitsOperationException}
|
* @error {@link google.registry.flows.exceptions.ResourceStatusProhibitsOperationException}
|
||||||
|
* @error {@link google.registry.flows.exceptions.TransferPeriodMustBeOneYearException}
|
||||||
* @error {@link DomainFlowUtils.BadPeriodUnitException}
|
* @error {@link DomainFlowUtils.BadPeriodUnitException}
|
||||||
* @error {@link DomainFlowUtils.CurrencyUnitMismatchException}
|
* @error {@link DomainFlowUtils.CurrencyUnitMismatchException}
|
||||||
* @error {@link DomainFlowUtils.CurrencyValueScaleException}
|
* @error {@link DomainFlowUtils.CurrencyValueScaleException}
|
||||||
|
@ -216,12 +218,39 @@ public final class DomainTransferRequestFlow implements TransactionalFlow {
|
||||||
throw new ObjectAlreadySponsoredException();
|
throw new ObjectAlreadySponsoredException();
|
||||||
}
|
}
|
||||||
checkAllowedAccessToTld(gainingClientId, existingDomain.getTld());
|
checkAllowedAccessToTld(gainingClientId, existingDomain.getTld());
|
||||||
verifyUnitIsYears(period);
|
verifyTransferPeriodIsOneYear(period);
|
||||||
if (!isSuperuser) {
|
if (!isSuperuser) {
|
||||||
verifyPremiumNameIsNotBlocked(targetId, now, gainingClientId);
|
verifyPremiumNameIsNotBlocked(targetId, now, gainingClientId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify that the transfer period is one year.
|
||||||
|
*
|
||||||
|
* <p>Restricting transfers to one year is seemingly required by ICANN's <a
|
||||||
|
* href="https://www.icann.org/resources/pages/policy-2012-03-07-en">Policy on Transfer of
|
||||||
|
* Registrations between Registrars</a>, section A.8. It states that "the completion by Registry
|
||||||
|
* Operator of a holder-authorized transfer under this Part A shall result in a one-year extension
|
||||||
|
* of the existing registration, provided that in no event shall the total unexpired term of a
|
||||||
|
* registration exceed ten (10) years."
|
||||||
|
*
|
||||||
|
* <p>Even if not required, this policy is desirable because it dramatically simplifies the logic
|
||||||
|
* in transfer flows. Registrars appear to never request 2+ year transfers in practice, and they
|
||||||
|
* can always decompose an multi-year transfer into a 1-year transfer followed by a manual renewal
|
||||||
|
* afterwards. The <a href="https://tools.ietf.org/html/rfc5731#section-3.2.4">EPP Domain RFC,
|
||||||
|
* section 3.2.4</a> says about EPP transfer periods that "the number of units available MAY be
|
||||||
|
* subject to limits imposed by the server" so we're just limiting the units to one.
|
||||||
|
*
|
||||||
|
* <p>Note that clients can omit the period element from the transfer EPP entirely, but then it
|
||||||
|
* will simply default to one year.
|
||||||
|
*/
|
||||||
|
private static void verifyTransferPeriodIsOneYear(Period period) throws EppException {
|
||||||
|
verifyUnitIsYears(period);
|
||||||
|
if (period.getValue() != 1) {
|
||||||
|
throw new TransferPeriodMustBeOneYearException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private HistoryEntry buildHistory(Period period, DomainResource existingResource, DateTime now) {
|
private HistoryEntry buildHistory(Period period, DomainResource existingResource, DateTime now) {
|
||||||
return historyBuilder
|
return historyBuilder
|
||||||
.setType(HistoryEntry.Type.DOMAIN_TRANSFER_REQUEST)
|
.setType(HistoryEntry.Type.DOMAIN_TRANSFER_REQUEST)
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
// 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.flows.exceptions;
|
||||||
|
|
||||||
|
import google.registry.flows.EppException.ParameterValuePolicyErrorException;
|
||||||
|
|
||||||
|
/** Domain transfer period must be one year. */
|
||||||
|
public class TransferPeriodMustBeOneYearException extends ParameterValuePolicyErrorException {
|
||||||
|
public TransferPeriodMustBeOneYearException() {
|
||||||
|
super("Domain transfer period must be one year");
|
||||||
|
}
|
||||||
|
}
|
|
@ -373,7 +373,8 @@ public class EppLifecycleDomainTest extends EppTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testIgnoredTransferDuringAutoRenewPeriod_succeeds() throws Exception {
|
public void testTransfer_autoRenewGraceActive_onlyAtAutomaticTransferTime_getsSubsumed()
|
||||||
|
throws Exception {
|
||||||
// Register the domain as the first registrar.
|
// Register the domain as the first registrar.
|
||||||
assertCommandAndResponse("login_valid.xml", "login_response.xml");
|
assertCommandAndResponse("login_valid.xml", "login_response.xml");
|
||||||
createFakesite();
|
createFakesite();
|
||||||
|
@ -382,8 +383,8 @@ public class EppLifecycleDomainTest extends EppTestCase {
|
||||||
// Request a transfer of the domain to the second registrar.
|
// Request a transfer of the domain to the second registrar.
|
||||||
assertCommandAndResponse("login2_valid.xml", "login_response.xml");
|
assertCommandAndResponse("login2_valid.xml", "login_response.xml");
|
||||||
assertCommandAndResponse(
|
assertCommandAndResponse(
|
||||||
"domain_transfer_request_2_years.xml",
|
"domain_transfer_request.xml",
|
||||||
"domain_transfer_response_2_years.xml",
|
"domain_transfer_response.xml",
|
||||||
DateTime.parse("2002-05-30T00:00:00Z"));
|
DateTime.parse("2002-05-30T00:00:00Z"));
|
||||||
assertCommandAndResponse("logout.xml", "logout_response.xml");
|
assertCommandAndResponse("logout.xml", "logout_response.xml");
|
||||||
|
|
||||||
|
@ -402,7 +403,8 @@ public class EppLifecycleDomainTest extends EppTestCase {
|
||||||
|
|
||||||
// Log back in as the second registrar and verify transfer details.
|
// Log back in as the second registrar and verify transfer details.
|
||||||
assertCommandAndResponse("login2_valid.xml", "login_response.xml");
|
assertCommandAndResponse("login2_valid.xml", "login_response.xml");
|
||||||
// Verify that domain is in the transfer period now with expiration date two years out.
|
// Verify that domain is in the transfer period now with expiration date still one year out,
|
||||||
|
// since the transfer should subsume the autorenew that happened during the transfer window.
|
||||||
assertCommandAndResponse(
|
assertCommandAndResponse(
|
||||||
"domain_info_fakesite.xml",
|
"domain_info_fakesite.xml",
|
||||||
"domain_info_response_fakesite_transfer_period.xml",
|
"domain_info_response_fakesite_transfer_period.xml",
|
||||||
|
@ -425,8 +427,8 @@ public class EppLifecycleDomainTest extends EppTestCase {
|
||||||
// Request a transfer of the domain to the second registrar.
|
// Request a transfer of the domain to the second registrar.
|
||||||
assertCommandAndResponse("login2_valid.xml", "login_response.xml");
|
assertCommandAndResponse("login2_valid.xml", "login_response.xml");
|
||||||
assertCommandAndResponse(
|
assertCommandAndResponse(
|
||||||
"domain_transfer_request_2_years.xml",
|
"domain_transfer_request.xml",
|
||||||
"domain_transfer_response_2_years.xml",
|
"domain_transfer_response.xml",
|
||||||
DateTime.parse("2002-05-30T00:00:00Z"));
|
DateTime.parse("2002-05-30T00:00:00Z"));
|
||||||
assertCommandAndResponse("logout.xml", "logout_response.xml");
|
assertCommandAndResponse("logout.xml", "logout_response.xml");
|
||||||
|
|
||||||
|
@ -464,8 +466,8 @@ public class EppLifecycleDomainTest extends EppTestCase {
|
||||||
// Request a transfer of the domain to the second registrar.
|
// Request a transfer of the domain to the second registrar.
|
||||||
assertCommandAndResponse("login2_valid.xml", "login_response.xml");
|
assertCommandAndResponse("login2_valid.xml", "login_response.xml");
|
||||||
assertCommandAndResponse(
|
assertCommandAndResponse(
|
||||||
"domain_transfer_request_2_years.xml",
|
"domain_transfer_request.xml",
|
||||||
"domain_transfer_response_2_years.xml",
|
"domain_transfer_response.xml",
|
||||||
DateTime.parse("2002-05-30T00:00:00Z"));
|
DateTime.parse("2002-05-30T00:00:00Z"));
|
||||||
assertCommandAndResponse("logout.xml", "logout_response.xml");
|
assertCommandAndResponse("logout.xml", "logout_response.xml");
|
||||||
|
|
||||||
|
|
|
@ -53,6 +53,7 @@ import google.registry.flows.exceptions.AlreadyPendingTransferException;
|
||||||
import google.registry.flows.exceptions.MissingTransferRequestAuthInfoException;
|
import google.registry.flows.exceptions.MissingTransferRequestAuthInfoException;
|
||||||
import google.registry.flows.exceptions.ObjectAlreadySponsoredException;
|
import google.registry.flows.exceptions.ObjectAlreadySponsoredException;
|
||||||
import google.registry.flows.exceptions.ResourceStatusProhibitsOperationException;
|
import google.registry.flows.exceptions.ResourceStatusProhibitsOperationException;
|
||||||
|
import google.registry.flows.exceptions.TransferPeriodMustBeOneYearException;
|
||||||
import google.registry.model.billing.BillingEvent;
|
import google.registry.model.billing.BillingEvent;
|
||||||
import google.registry.model.billing.BillingEvent.Cancellation.Builder;
|
import google.registry.model.billing.BillingEvent.Cancellation.Builder;
|
||||||
import google.registry.model.billing.BillingEvent.Reason;
|
import google.registry.model.billing.BillingEvent.Reason;
|
||||||
|
@ -500,22 +501,35 @@ public class DomainTransferRequestFlowTest
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSuccess_missingPeriod() throws Exception {
|
public void testSuccess_missingPeriod_defaultsToOneYear() throws Exception {
|
||||||
setupDomain("example", "tld");
|
setupDomain("example", "tld");
|
||||||
doSuccessfulTest("domain_transfer_request_missing_period.xml",
|
doSuccessfulTest(
|
||||||
|
"domain_transfer_request_missing_period.xml",
|
||||||
"domain_transfer_request_response.xml");
|
"domain_transfer_request_response.xml");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFailure_multiYearPeriod() throws Exception {
|
||||||
|
setupDomain("example", "tld");
|
||||||
|
clock.advanceOneMilli();
|
||||||
|
thrown.expect(TransferPeriodMustBeOneYearException.class);
|
||||||
|
doFailingTest("domain_transfer_request_2_years.xml");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSuccess_cappedExpiration() throws Exception {
|
public void testSuccess_cappedExpiration() throws Exception {
|
||||||
setupDomain("example", "tld");
|
setupDomain("example", "tld");
|
||||||
// The current expiration is in 15 months, so requesting 10 years would give 11 years 3 months,
|
// Set the domain to expire 10 years from now (as if it were just created with a 10-year term).
|
||||||
// were it not that we cap at 10 years. (MAX_REGISTRATION_YEARS == 10 and is unlikely to ever
|
domain = persistResource(domain.asBuilder()
|
||||||
// change; we just use a constant for readability.)
|
.setRegistrationExpirationTime(clock.nowUtc().plusYears(10))
|
||||||
|
.build());
|
||||||
|
// New expiration time should be capped at exactly 10 years from the transfer server-approve
|
||||||
|
// time, so the domain only ends up gaining the 5-day transfer window's worth of extra
|
||||||
|
// registration time.
|
||||||
clock.advanceOneMilli();
|
clock.advanceOneMilli();
|
||||||
doSuccessfulTest(
|
doSuccessfulTest(
|
||||||
"domain_transfer_request_10_years.xml",
|
"domain_transfer_request.xml",
|
||||||
"domain_transfer_request_response_10_years.xml",
|
"domain_transfer_request_response_10_year_cap.xml",
|
||||||
clock.nowUtc().plus(Registry.get("tld").getAutomaticTransferLength()).plusYears(10));
|
clock.nowUtc().plus(Registry.get("tld").getAutomaticTransferLength()).plusYears(10));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -534,16 +548,16 @@ public class DomainTransferRequestFlowTest
|
||||||
doSuccessfulTest(
|
doSuccessfulTest(
|
||||||
"domain_transfer_request_fee.xml",
|
"domain_transfer_request_fee.xml",
|
||||||
"domain_transfer_request_response_fees.xml",
|
"domain_transfer_request_response_fees.xml",
|
||||||
domain.getRegistrationExpirationTime().plusYears(3),
|
domain.getRegistrationExpirationTime().plusYears(1),
|
||||||
new ImmutableMap.Builder<String, String>()
|
new ImmutableMap.Builder<String, String>()
|
||||||
.put("DOMAIN", "expensive-domain.foo")
|
.put("DOMAIN", "expensive-domain.foo")
|
||||||
.put("YEARS", "3")
|
.put("YEARS", "1")
|
||||||
.put("AMOUNT", "133.00")
|
.put("AMOUNT", "111.00")
|
||||||
.put("EXDATE", "2004-09-08T22:00:00.0Z")
|
.put("EXDATE", "2002-09-08T22:00:00.0Z")
|
||||||
.put("FEE_VERSION", "0.6")
|
.put("FEE_VERSION", "0.6")
|
||||||
.put("FEE_NS", "fee")
|
.put("FEE_NS", "fee")
|
||||||
.build(),
|
.build(),
|
||||||
Optional.of(Money.of(USD, 133)));
|
Optional.of(Money.of(USD, 111)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
|
|
||||||
<command>
|
|
||||||
<transfer op="request">
|
|
||||||
<domain:transfer
|
|
||||||
xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
|
|
||||||
<domain:name>example.tld</domain:name>
|
|
||||||
<domain:period unit="y">10</domain:period>
|
|
||||||
<domain:authInfo>
|
|
||||||
<domain:pw roid="JD1234-REP">2fooBAR</domain:pw>
|
|
||||||
</domain:authInfo>
|
|
||||||
</domain:transfer>
|
|
||||||
</transfer>
|
|
||||||
<clTRID>ABC-12345</clTRID>
|
|
||||||
</command>
|
|
||||||
</epp>
|
|
|
@ -18,7 +18,7 @@
|
||||||
<extension>
|
<extension>
|
||||||
<fee:trnData xmlns:fee="urn:ietf:params:xml:ns:fee-%FEE_VERSION%">
|
<fee:trnData xmlns:fee="urn:ietf:params:xml:ns:fee-%FEE_VERSION%">
|
||||||
<fee:currency>USD</fee:currency>
|
<fee:currency>USD</fee:currency>
|
||||||
<fee:fee description="renew">33.00</fee:fee>
|
<fee:fee description="renew">11.00</fee:fee>
|
||||||
<fee:fee description="transfer">100.00</fee:fee>
|
<fee:fee description="transfer">100.00</fee:fee>
|
||||||
</fee:trnData>
|
</fee:trnData>
|
||||||
</extension>
|
</extension>
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
<domain:crDate>2000-06-01T00:04:00Z</domain:crDate>
|
<domain:crDate>2000-06-01T00:04:00Z</domain:crDate>
|
||||||
<domain:upID>NewRegistrar</domain:upID>
|
<domain:upID>NewRegistrar</domain:upID>
|
||||||
<domain:upDate>2000-06-08T00:00:00Z</domain:upDate>
|
<domain:upDate>2000-06-08T00:00:00Z</domain:upDate>
|
||||||
<domain:exDate>2004-06-01T00:04:00Z</domain:exDate>
|
<domain:exDate>2003-06-01T00:04:00Z</domain:exDate>
|
||||||
<domain:trDate>2002-06-04T00:00:00Z</domain:trDate>
|
<domain:trDate>2002-06-04T00:00:00Z</domain:trDate>
|
||||||
<domain:authInfo>
|
<domain:authInfo>
|
||||||
<domain:pw>2fooBAR</domain:pw>
|
<domain:pw>2fooBAR</domain:pw>
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
<domain:clID>TheRegistrar</domain:clID>
|
<domain:clID>TheRegistrar</domain:clID>
|
||||||
<domain:crID>NewRegistrar</domain:crID>
|
<domain:crID>NewRegistrar</domain:crID>
|
||||||
<domain:crDate>2000-06-01T00:04:00Z</domain:crDate>
|
<domain:crDate>2000-06-01T00:04:00Z</domain:crDate>
|
||||||
<domain:exDate>2004-06-01T00:04:00Z</domain:exDate>
|
<domain:exDate>2003-06-01T00:04:00Z</domain:exDate>
|
||||||
<domain:trDate>2002-06-04T00:00:00Z</domain:trDate>
|
<domain:trDate>2002-06-04T00:00:00Z</domain:trDate>
|
||||||
<domain:authInfo>
|
<domain:authInfo>
|
||||||
<domain:pw>2fooBAR</domain:pw>
|
<domain:pw>2fooBAR</domain:pw>
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
<domain:clID>TheRegistrar</domain:clID>
|
<domain:clID>TheRegistrar</domain:clID>
|
||||||
<domain:crID>NewRegistrar</domain:crID>
|
<domain:crID>NewRegistrar</domain:crID>
|
||||||
<domain:crDate>2000-06-01T00:04:00Z</domain:crDate>
|
<domain:crDate>2000-06-01T00:04:00Z</domain:crDate>
|
||||||
<domain:exDate>2004-06-01T00:04:00Z</domain:exDate>
|
<domain:exDate>2003-06-01T00:04:00Z</domain:exDate>
|
||||||
<domain:trDate>2002-06-04T00:00:00Z</domain:trDate>
|
<domain:trDate>2002-06-04T00:00:00Z</domain:trDate>
|
||||||
<domain:authInfo>
|
<domain:authInfo>
|
||||||
<domain:pw>2fooBAR</domain:pw>
|
<domain:pw>2fooBAR</domain:pw>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<domain:transfer
|
<domain:transfer
|
||||||
xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
|
xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
|
||||||
<domain:name>fakesite.example</domain:name>
|
<domain:name>fakesite.example</domain:name>
|
||||||
<domain:period unit="y">2</domain:period>
|
<domain:period unit="y">1</domain:period>
|
||||||
<domain:authInfo>
|
<domain:authInfo>
|
||||||
<domain:pw>2fooBAR</domain:pw>
|
<domain:pw>2fooBAR</domain:pw>
|
||||||
</domain:authInfo>
|
</domain:authInfo>
|
|
@ -11,7 +11,7 @@
|
||||||
<domain:reDate>2002-05-30T00:00:00Z</domain:reDate>
|
<domain:reDate>2002-05-30T00:00:00Z</domain:reDate>
|
||||||
<domain:acID>NewRegistrar</domain:acID>
|
<domain:acID>NewRegistrar</domain:acID>
|
||||||
<domain:acDate>2002-06-04T00:00:00Z</domain:acDate>
|
<domain:acDate>2002-06-04T00:00:00Z</domain:acDate>
|
||||||
<domain:exDate>2004-06-01T00:04:00Z</domain:exDate>
|
<domain:exDate>2003-06-01T00:04:00Z</domain:exDate>
|
||||||
</domain:trnData>
|
</domain:trnData>
|
||||||
</resData>
|
</resData>
|
||||||
<trID>
|
<trID>
|
Loading…
Add table
Add a link
Reference in a new issue