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