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 package org.apache.hc.core5.net;
28
29 import java.io.Serializable;
30 import java.net.IDN;
31 import java.net.URISyntaxException;
32
33 import org.apache.hc.core5.annotation.Contract;
34 import org.apache.hc.core5.annotation.ThreadingBehavior;
35 import org.apache.hc.core5.util.Args;
36 import org.apache.hc.core5.util.LangUtils;
37 import org.apache.hc.core5.util.TextUtils;
38 import org.apache.hc.core5.util.Tokenizer;
39
40
41
42
43
44
45
46 @Contract(threading = ThreadingBehavior.IMMUTABLE)
47 public final class Host implements NamedEndpoint, Serializable {
48
49 private static final long serialVersionUID = 1L;
50 private final String name;
51 private final String lcName;
52 private final int port;
53
54 static boolean isPunyCode(final CharSequence s) {
55 if (s == null || s.length() < 4) {
56 return false;
57 }
58 return ((s.charAt(0) == 'x' || s.charAt(0) == 'X') &&
59 (s.charAt(1) == 'n' || s.charAt(1) == 'N') &&
60 s.charAt(2) == '-' &&
61 s.charAt(3) == '-');
62 }
63
64 public Host(final String name, final int port) {
65 super();
66 Args.notNull(name, "Host name");
67 Ports.checkWithDefault(port);
68 this.name = isPunyCode(name) ? IDN.toUnicode(name) : name;
69 this.port = port;
70 this.lcName = TextUtils.toLowerCase(this.name);
71 }
72
73 static Host parse(final CharSequence s, final Tokenizer.Cursor cursor) throws URISyntaxException {
74 final Tokenizer tokenizer = Tokenizer.INSTANCE;
75 final String hostName;
76 final boolean ipv6Brackets = !cursor.atEnd() && s.charAt(cursor.getPos()) == '[';
77 if (ipv6Brackets) {
78 cursor.updatePos(cursor.getPos() + 1);
79 hostName = tokenizer.parseContent(s, cursor, URISupport.IPV6_HOST_TERMINATORS);
80 if (cursor.atEnd() || !(s.charAt(cursor.getPos()) == ']')) {
81 throw URISupport.createException(s, cursor, "Expected an IPv6 closing bracket ']'");
82 }
83 cursor.updatePos(cursor.getPos() + 1);
84 if (!InetAddressUtils.isIPv6Address(hostName)) {
85 throw URISupport.createException(s, cursor, "Expected an IPv6 address");
86 }
87 } else {
88 hostName = tokenizer.parseContent(s, cursor, URISupport.PORT_SEPARATORS);
89 }
90 String portText = null;
91 if (!cursor.atEnd() && s.charAt(cursor.getPos()) == ':') {
92 cursor.updatePos(cursor.getPos() + 1);
93 portText = tokenizer.parseContent(s, cursor, URISupport.TERMINATORS);
94 }
95 final int port;
96 if (!TextUtils.isBlank(portText)) {
97 if (!ipv6Brackets && portText.contains(":")) {
98 throw URISupport.createException(s, cursor, "Expected IPv6 address to be enclosed in brackets");
99 }
100 try {
101 port = Integer.parseInt(portText);
102 } catch (final NumberFormatException ex) {
103 throw URISupport.createException(s, cursor, "Port is invalid");
104 }
105 } else {
106 port = -1;
107 }
108 return new Host(hostName, port);
109 }
110
111 static Host parse(final CharSequence s) throws URISyntaxException {
112 final Tokenizer.Cursor cursor = new Tokenizer.Cursor(0, s.length());
113 return parse(s, cursor);
114 }
115
116 static void format(final StringBuilder buf, final NamedEndpoint endpoint) {
117 final String hostName = endpoint.getHostName();
118 if (InetAddressUtils.isIPv6Address(hostName)) {
119 buf.append('[').append(hostName).append(']');
120 } else {
121 if (TextUtils.isAllASCII(hostName)) {
122 buf.append(hostName);
123 } else {
124 buf.append(IDN.toASCII(hostName));
125 }
126 }
127 if (endpoint.getPort() != -1) {
128 buf.append(":");
129 buf.append(endpoint.getPort());
130 }
131 }
132
133 static void format(final StringBuilder buf, final Host host) {
134 format(buf, (NamedEndpoint) host);
135 }
136
137 static String format(final Host host) {
138 final StringBuilder buf = new StringBuilder();
139 format(buf, host);
140 return buf.toString();
141 }
142
143 public static Host create(final String s) throws URISyntaxException {
144 Args.notEmpty(s, "HTTP Host");
145 final Tokenizer.Cursor cursor = new Tokenizer.Cursor(0, s.length());
146 final Host host = parse(s, cursor);
147 if (TextUtils.isBlank(host.getHostName())) {
148 throw URISupport.createException(s, cursor, "Hostname is invalid");
149 }
150 if (!cursor.atEnd()) {
151 throw URISupport.createException(s, cursor, "Unexpected content");
152 }
153 return host;
154 }
155
156 @Override
157 public String getHostName() {
158 return name;
159 }
160
161 @Override
162 public int getPort() {
163 return port;
164 }
165
166 @Override
167 public boolean equals(final Object o) {
168 if (this == o) {
169 return true;
170 }
171 if (o instanceof Host) {
172 final Host that = (Host) o;
173 return this.lcName.equals(that.lcName) && this.port == that.port;
174 }
175 return false;
176 }
177
178 @Override
179 public int hashCode() {
180 int hash = LangUtils.HASH_SEED;
181 hash = LangUtils.hashCode(hash, this.lcName);
182 hash = LangUtils.hashCode(hash, this.port);
183 return hash;
184 }
185
186 @Override
187 public String toString() {
188 return format(this);
189 }
190
191 }