1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
|
package de.measite.minidns.util;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.IDN;
import java.util.HashSet;
import java.util.Arrays;
/**
* Utilities related to internationalized domain names and dns name handling.
*/
public class NameUtil {
/**
* Retrieve the rough binary length of a string
* (length + 2 bytes length prefix).
* @param name The name string.
* @return The binary size of the string (length + 2).
*/
public static int size(String name) {
return name.length() + 2;
}
/**
* Check if two internationalized domain names are equal, possibly causing
* a serialization of both domain names.
* @param name1 The first domain name.
* @param name2 The second domain name.
* @return True if both domain names are the same.
*/
public static boolean idnEquals(String name1, String name2) {
if (name1 == name2) return true; // catches null, null
if (name1 == null) return false;
if (name2 == null) return false;
if (name1.equals(name2)) return true;
try {
return Arrays.equals(toByteArray(name1),toByteArray(name2));
} catch (IOException e) {
return false; // impossible
}
}
/**
* Serialize a domain name under IDN rules.
* @param name The domain name.
* @return The binary domain name representation.
* @throws IOException Should never happen.
*/
public static byte[] toByteArray(String name) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream(64);
DataOutputStream dos = new DataOutputStream(baos);
for (String s: name.split("[.\u3002\uFF0E\uFF61]")) {
byte[] buffer = IDN.toASCII(s).getBytes();
dos.writeByte(buffer.length);
dos.write(buffer);
}
dos.writeByte(0);
dos.flush();
return baos.toByteArray();
}
/**
* Parse a domain name starting at the current offset and moving the input
* stream pointer past this domain name (even if cross references occure).
* @param dis The input stream.
* @param data The raw data (for cross references).
* @return The domain name string.
* @throws IOException Should never happen.
*/
public static String parse(DataInputStream dis, byte data[])
throws IOException
{
int c = dis.readUnsignedByte();
if ((c & 0xc0) == 0xc0) {
c = ((c & 0x3f) << 8) + dis.readUnsignedByte();
HashSet<Integer> jumps = new HashSet<Integer>();
jumps.add(c);
return parse(data, c, jumps);
}
if (c == 0) {
return "";
}
byte b[] = new byte[c];
dis.readFully(b);
String s = IDN.toUnicode(new String(b));
String t = parse(dis, data);
if (t.length() > 0) {
s = s + "." + t;
}
return s;
}
/**
* Parse a domain name starting at the given offset.
* @param data The raw data.
* @param offset The offset.
* @param jumps The list of jumps (by now).
* @return The parsed domain name.
* @throws IllegalStateException on cycles.
*/
public static String parse(
byte data[],
int offset,
HashSet<Integer> jumps
) {
int c = data[offset] & 0xff;
if ((c & 0xc0) == 0xc0) {
c = ((c & 0x3f) << 8) + (data[offset + 1] & 0xff);
if (jumps.contains(c)) {
throw new IllegalStateException("Cyclic offsets detected.");
}
jumps.add(c);
return parse(data, c, jumps);
}
if (c == 0) {
return "";
}
String s = new String(data,offset + 1, c);
String t = parse(data, offset + 1 + c, jumps);
if (t.length() > 0) {
s = s + "." + t;
}
return s;
}
}
|