fix: Close resources we no longer need
[gigi.git] / src / club / wpia / gigi / util / DomainAssessment.java
1 package club.wpia.gigi.util;
2
3 import java.io.BufferedReader;
4 import java.io.File;
5 import java.io.FileInputStream;
6 import java.io.IOException;
7 import java.io.InputStreamReader;
8 import java.io.Reader;
9 import java.net.IDN;
10 import java.net.URL;
11 import java.util.Collections;
12 import java.util.HashSet;
13 import java.util.Properties;
14 import java.util.Set;
15
16 import club.wpia.gigi.GigiApiException;
17
18 public class DomainAssessment {
19
20     private static class DomainSet {
21
22         private final Set<String> data;
23
24         public DomainSet(URL u) {
25             this(openStream(u));
26         }
27
28         private static Reader openStream(URL u) {
29             try {
30                 return new InputStreamReader(u.openStream(), "UTF-8");
31             } catch (IOException e) {
32                 throw new Error(e);
33             }
34         }
35
36         public DomainSet(Reader r) {
37             HashSet<String> contents = new HashSet<>();
38             try {
39                 BufferedReader br = new BufferedReader(r);
40                 String line;
41                 while ((line = br.readLine()) != null) {
42                     if (line.startsWith("#")) {
43                         continue;
44                     }
45                     if (line.isEmpty()) {
46                         continue;
47                     }
48                     contents.add(line);
49                 }
50             } catch (IOException e) {
51                 throw new Error(e);
52             }
53             data = Collections.unmodifiableSet(contents);
54         }
55
56         public boolean contains(String suffix) {
57             while (suffix.contains(".")) {
58                 if (data.contains(suffix)) {
59                     return true;
60                 }
61                 suffix = suffix.substring(suffix.indexOf('.') + 1);
62             }
63             return data.contains(suffix);
64         }
65     }
66
67     private static DomainSet financial;
68
69     private static final DomainSet idn_enabled = new DomainSet(DomainAssessment.class.getResource("idn_enabled.dat"));
70
71     public static boolean isHighFinancialValue(String suffix) {
72         return financial.contains(suffix);
73     }
74
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.");
78         }
79         String[] parts = domain.split("\\.", -1);
80         if (parts.length < 2) {
81             throw new GigiApiException("Domain does not contain '.'.");
82         }
83
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.");
88             }
89             boolean canBePunycode = parts[i].length() >= 4 && parts[i].charAt(2) == '-' && parts[i].charAt(3) == '-';
90             if (canBePunycode) {
91                 if ( !hasPunycodeRight) {
92                     throw new GigiApiException("Punycode domain without specific right.");
93                 }
94                 punycodeDecode(parts[i]);
95                 neededPunycode = true;
96             }
97
98         }
99         if (neededPunycode && !idn_enabled.contains(domain)) {
100             throw new GigiApiException("Punycode not allowed under this TLD.");
101         }
102
103         if (asRegister) {
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.");
107             }
108         }
109
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.");
115         }
116     }
117
118     private static String punycodeDecode(String label) throws GigiApiException {
119         if (label.charAt(2) != '-' || label.charAt(3) != '-') {
120             return label; // is no punycode
121         }
122         if ( !label.startsWith("xn--")) {
123             throw new GigiApiException("Unknown ACE prefix.");
124         }
125         try {
126             String unicode = IDN.toUnicode(label);
127             if (unicode.startsWith("xn--")) {
128                 throw new GigiApiException("Punycode label could not be positively verified.");
129             }
130             return unicode;
131         } catch (IllegalArgumentException e) {
132             throw new GigiApiException("Punycode label could not be positively verified.");
133         }
134     }
135
136     public static boolean isValidDomainPart(String s) {
137         if ( !s.matches("[a-z0-9-]+")) {
138             return false;
139         }
140         if (s.charAt(0) == '-' || s.charAt(s.length() - 1) == '-') {
141             return false;
142         }
143         if (s.length() > 63) {
144             return false;
145         }
146         return true;
147     }
148
149     public static void init(Properties conf) {
150         String financialName = conf.getProperty("highFinancialValue");
151
152         if (financialName == null) {
153             throw new Error("No property highFinancialValue was configured");
154         }
155
156         try (FileInputStream fis = new FileInputStream(new File(financialName)); //
157                 InputStreamReader isr = new InputStreamReader(fis, "UTF-8")) {
158             financial = new DomainSet(isr);
159         } catch (IOException e) {
160             throw new Error(e);
161         }
162     }
163 }