1 package org.cacert.gigi.util;
3 import java.io.BufferedReader;
5 import java.io.FileInputStream;
6 import java.io.IOException;
7 import java.io.InputStreamReader;
11 import java.util.Collections;
12 import java.util.HashSet;
13 import java.util.Properties;
16 import org.cacert.gigi.GigiApiException;
18 public class DomainAssessment {
20 private static class DomainSet {
22 private final Set<String> data;
24 public DomainSet(URL u) {
28 private static Reader openStream(URL u) {
30 return new InputStreamReader(u.openStream(), "UTF-8");
31 } catch (IOException e) {
36 public DomainSet(Reader r) {
37 HashSet<String> contents = new HashSet<>();
39 BufferedReader br = new BufferedReader(r);
41 while ((line = br.readLine()) != null) {
42 if (line.startsWith("#")) {
50 } catch (IOException e) {
53 data = Collections.unmodifiableSet(contents);
56 public boolean contains(String suffix) {
57 while (suffix.contains(".")) {
58 if (data.contains(suffix)) {
61 suffix = suffix.substring(suffix.indexOf('.') + 1);
63 return data.contains(suffix);
67 private static DomainSet financial;
69 private static final DomainSet idn_enabled = new DomainSet(DomainAssessment.class.getResource("idn_enabled.dat"));
71 public static boolean isHighFinancialValue(String suffix) {
72 return financial.contains(suffix);
75 public static void checkCertifiableDomain(String domain, boolean hasPunycodeRight, boolean asRegister) throws GigiApiException {
76 if (isHighFinancialValue(domain)) {
77 throw new GigiApiException("Domain blocked for automatic adding.");
79 String[] parts = domain.split("\\.", -1);
80 if (parts.length < 2) {
81 throw new GigiApiException("Domain does not contain '.'.");
84 boolean neededPunycode = false;
85 for (int i = parts.length - 1; i >= 0; i--) {
86 if ( !isValidDomainPart(parts[i])) {
87 throw new GigiApiException("Syntax error in Domain.");
89 boolean canBePunycode = parts[i].length() >= 4 && parts[i].charAt(2) == '-' && parts[i].charAt(3) == '-';
91 if ( !hasPunycodeRight) {
92 throw new GigiApiException("Punycode domain without specific right.");
94 punycodeDecode(parts[i]);
95 neededPunycode = true;
99 if (neededPunycode && !idn_enabled.contains(domain)) {
100 throw new GigiApiException("Punycode not allowed under this TLD.");
104 String publicSuffix = PublicSuffixes.getInstance().getRegistrablePart(domain);
105 if ( !domain.equals(publicSuffix)) {
106 throw new GigiApiException("You may only register a domain with exactly one label before the public suffix.");
110 if (("." + domain).matches("(\\.[0-9]*)*")) {
111 // This is not reached because we currently have no TLD that is
112 // numbers only. But who knows..
113 // Better safe than sorry.
114 throw new GigiApiException("IP Addresses are not allowed.");
118 private static String punycodeDecode(String label) throws GigiApiException {
119 if (label.charAt(2) != '-' || label.charAt(3) != '-') {
120 return label; // is no punycode
122 if ( !label.startsWith("xn--")) {
123 throw new GigiApiException("Unknown ACE prefix.");
126 String unicode = IDN.toUnicode(label);
127 if (unicode.startsWith("xn--")) {
128 throw new GigiApiException("Punycode label could not be positively verified.");
131 } catch (IllegalArgumentException e) {
132 throw new GigiApiException("Punycode label could not be positively verified.");
136 public static boolean isValidDomainPart(String s) {
137 if ( !s.matches("[a-z0-9-]+")) {
140 if (s.charAt(0) == '-' || s.charAt(s.length() - 1) == '-') {
143 if (s.length() > 63) {
149 public static void init(Properties conf) {
150 String financialName = conf.getProperty("highFinancialValue");
151 if (financialName == null) {
152 throw new Error("No property highFinancialValue was configured");
155 financial = new DomainSet(new InputStreamReader(new FileInputStream(new File(financialName)), "UTF-8"));
156 } catch (IOException e) {