1 /*
2 * Copyright 2014 The Netty Project
3 *
4 * The Netty Project licenses this file to you under the Apache License,
5 * version 2.0 (the "License"); you may not use this file except in compliance
6 * with the License. You may obtain a copy of the License at:
7 *
8 * https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations
14 * under the License.
15 */
16
17 package io.netty.util;
18
19 import io.netty.util.internal.StringUtil;
20
21 import java.net.IDN;
22 import java.util.Collections;
23 import java.util.LinkedHashMap;
24 import java.util.Locale;
25 import java.util.Map;
26
27 import static io.netty.util.internal.ObjectUtil.checkNotNull;
28 import static io.netty.util.internal.StringUtil.commonSuffixOfLength;
29
30 /**
31 * Maps a domain name to its associated value object.
32 * <p>
33 * DNS wildcard is supported as hostname, so you can use {@code *.netty.io} to match both {@code netty.io}
34 * and {@code downloads.netty.io}.
35 * </p>
36 * @deprecated Use {@link DomainWildcardMappingBuilder}}
37 */
38 @Deprecated
39 public class DomainNameMapping<V> implements Mapping<String, V> {
40
41 final V defaultValue;
42 private final Map<String, V> map;
43 private final Map<String, V> unmodifiableMap;
44
45 /**
46 * Creates a default, order-sensitive mapping. If your hostnames are in conflict, the mapping
47 * will choose the one you add first.
48 *
49 * @param defaultValue the default value for {@link #map(String)} to return when nothing matches the input
50 * @deprecated use {@link DomainNameMappingBuilder} to create and fill the mapping instead
51 */
52 @Deprecated
53 public DomainNameMapping(V defaultValue) {
54 this(4, defaultValue);
55 }
56
57 /**
58 * Creates a default, order-sensitive mapping. If your hostnames are in conflict, the mapping
59 * will choose the one you add first.
60 *
61 * @param initialCapacity initial capacity for the internal map
62 * @param defaultValue the default value for {@link #map(String)} to return when nothing matches the input
63 * @deprecated use {@link DomainNameMappingBuilder} to create and fill the mapping instead
64 */
65 @Deprecated
66 public DomainNameMapping(int initialCapacity, V defaultValue) {
67 this(new LinkedHashMap<String, V>(initialCapacity), defaultValue);
68 }
69
70 DomainNameMapping(Map<String, V> map, V defaultValue) {
71 this.defaultValue = checkNotNull(defaultValue, "defaultValue");
72 this.map = map;
73 unmodifiableMap = map != null ? Collections.unmodifiableMap(map)
74 : null;
75 }
76
77 /**
78 * Adds a mapping that maps the specified (optionally wildcard) host name to the specified output value.
79 * <p>
80 * <a href="https://en.wikipedia.org/wiki/Wildcard_DNS_record">DNS wildcard</a> is supported as hostname.
81 * For example, you can use {@code *.netty.io} to match {@code netty.io} and {@code downloads.netty.io}.
82 * </p>
83 *
84 * @param hostname the host name (optionally wildcard)
85 * @param output the output value that will be returned by {@link #map(String)} when the specified host name
86 * matches the specified input host name
87 * @deprecated use {@link DomainNameMappingBuilder} to create and fill the mapping instead
88 */
89 @Deprecated
90 public DomainNameMapping<V> add(String hostname, V output) {
91 map.put(normalizeHostname(checkNotNull(hostname, "hostname")), checkNotNull(output, "output"));
92 return this;
93 }
94
95 /**
96 * Simple function to match <a href="https://en.wikipedia.org/wiki/Wildcard_DNS_record">DNS wildcard</a>.
97 */
98 static boolean matches(String template, String hostName) {
99 if (template.startsWith("*.")) {
100 return template.regionMatches(2, hostName, 0, hostName.length())
101 || commonSuffixOfLength(hostName, template, template.length() - 1);
102 }
103 return template.equals(hostName);
104 }
105
106 /**
107 * IDNA ASCII conversion and case normalization
108 */
109 static String normalizeHostname(String hostname) {
110 if (needsNormalization(hostname)) {
111 hostname = IDN.toASCII(hostname, IDN.ALLOW_UNASSIGNED);
112 }
113 return hostname.toLowerCase(Locale.US);
114 }
115
116 private static boolean needsNormalization(String hostname) {
117 final int length = hostname.length();
118 for (int i = 0; i < length; i++) {
119 int c = hostname.charAt(i);
120 if (c > 0x7F) {
121 return true;
122 }
123 }
124 return false;
125 }
126
127 @Override
128 public V map(String hostname) {
129 if (hostname != null) {
130 hostname = normalizeHostname(hostname);
131
132 for (Map.Entry<String, V> entry : map.entrySet()) {
133 if (matches(entry.getKey(), hostname)) {
134 return entry.getValue();
135 }
136 }
137 }
138 return defaultValue;
139 }
140
141 /**
142 * Returns a read-only {@link Map} of the domain mapping patterns and their associated value objects.
143 */
144 public Map<String, V> asMap() {
145 return unmodifiableMap;
146 }
147
148 @Override
149 public String toString() {
150 return StringUtil.simpleClassName(this) + "(default: " + defaultValue + ", map: " + map + ')';
151 }
152 }