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) 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 '.'.");
83 boolean neededPunycode = false;
84 for (int i = parts.length - 1; i >= 0; i--) {
85 if ( !isValidDomainPart(parts[i])) {
86 throw new GigiApiException("Syntax error in Domain.");
88 boolean canBePunycode = parts[i].length() >= 4 && parts[i].charAt(2) == '-' && parts[i].charAt(3) == '-';
90 if ( !hasPunycodeRight) {
91 throw new GigiApiException("Punycode domain without specific right.");
93 punycodeDecode(parts[i]);
94 neededPunycode = true;
98 if (neededPunycode && !idn_enabled.contains(domain)) {
99 throw new GigiApiException("Punycode not allowed under this TLD.");
102 String publicSuffix = PublicSuffixes.getInstance().getRegistrablePart(domain);
103 if ( !domain.equals(publicSuffix)) {
104 throw new GigiApiException("You may only register a domain with exactly one label before the public suffix.");
107 if (("." + domain).matches("(\\.[0-9]*)*")) {
108 // This is not reached because we currently have no TLD that is
109 // numbers only. But who knows..
110 // Better safe than sorry.
111 throw new GigiApiException("IP Addresses are not allowed.");
115 private static String punycodeDecode(String label) throws GigiApiException {
116 if (label.charAt(2) != '-' || label.charAt(3) != '-') {
117 return label; // is no punycode
119 if ( !label.startsWith("xn--")) {
120 throw new GigiApiException("Unknown ACE prefix.");
123 String unicode = IDN.toUnicode(label);
124 if (unicode.startsWith("xn--")) {
125 throw new GigiApiException("Punycode label could not be positively verified.");
128 } catch (IllegalArgumentException e) {
129 throw new GigiApiException("Punycode label could not be positively verified.");
133 public static boolean isValidDomainPart(String s) {
134 if ( !s.matches("[a-z0-9-]+")) {
137 if (s.charAt(0) == '-' || s.charAt(s.length() - 1) == '-') {
140 if (s.length() > 63) {
146 public static void init(Properties conf) {
147 String financialName = conf.getProperty("highFinancialValue");
148 if (financialName == null) {
149 throw new Error("No property highFinancialValue was configured");
152 financial = new DomainSet(new InputStreamReader(new FileInputStream(new File(financialName)), "UTF-8"));
153 } catch (IOException e) {