View Javadoc
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    *   http://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   */
37  public class DomainNameMapping<V> implements Mapping<String, V> {
38  
39      final V defaultValue;
40      private final Map<String, V> map;
41      private final Map<String, V> unmodifiableMap;
42  
43      /**
44       * Creates a default, order-sensitive mapping. If your hostnames are in conflict, the mapping
45       * will choose the one you add first.
46       *
47       * @param defaultValue the default value for {@link #map(String)} to return when nothing matches the input
48       * @deprecated use {@link DomainNameMappingBuilder} to create and fill the mapping instead
49       */
50      @Deprecated
51      public DomainNameMapping(V defaultValue) {
52          this(4, defaultValue);
53      }
54  
55      /**
56       * Creates a default, order-sensitive mapping. If your hostnames are in conflict, the mapping
57       * will choose the one you add first.
58       *
59       * @param initialCapacity initial capacity for the internal map
60       * @param defaultValue    the default value for {@link #map(String)} to return when nothing matches the input
61       * @deprecated use {@link DomainNameMappingBuilder} to create and fill the mapping instead
62       */
63      @Deprecated
64      public DomainNameMapping(int initialCapacity, V defaultValue) {
65          this(new LinkedHashMap<String, V>(initialCapacity), defaultValue);
66      }
67  
68      DomainNameMapping(Map<String, V> map, V defaultValue) {
69          this.defaultValue = checkNotNull(defaultValue, "defaultValue");
70          this.map = map;
71          unmodifiableMap = map != null ? Collections.unmodifiableMap(map)
72                                        : null;
73      }
74  
75      /**
76       * Adds a mapping that maps the specified (optionally wildcard) host name to the specified output value.
77       * <p>
78       * <a href="http://en.wikipedia.org/wiki/Wildcard_DNS_record">DNS wildcard</a> is supported as hostname.
79       * For example, you can use {@code *.netty.io} to match {@code netty.io} and {@code downloads.netty.io}.
80       * </p>
81       *
82       * @param hostname the host name (optionally wildcard)
83       * @param output   the output value that will be returned by {@link #map(String)} when the specified host name
84       *                 matches the specified input host name
85       * @deprecated use {@link DomainNameMappingBuilder} to create and fill the mapping instead
86       */
87      @Deprecated
88      public DomainNameMapping<V> add(String hostname, V output) {
89          map.put(normalizeHostname(checkNotNull(hostname, "hostname")), checkNotNull(output, "output"));
90          return this;
91      }
92  
93      /**
94       * Simple function to match <a href="http://en.wikipedia.org/wiki/Wildcard_DNS_record">DNS wildcard</a>.
95       */
96      static boolean matches(String template, String hostName) {
97          if (template.startsWith("*.")) {
98              return template.regionMatches(2, hostName, 0, hostName.length())
99                  || commonSuffixOfLength(hostName, template, template.length() - 1);
100         }
101         return template.equals(hostName);
102     }
103 
104     /**
105      * IDNA ASCII conversion and case normalization
106      */
107     static String normalizeHostname(String hostname) {
108         if (needsNormalization(hostname)) {
109             hostname = IDN.toASCII(hostname, IDN.ALLOW_UNASSIGNED);
110         }
111         return hostname.toLowerCase(Locale.US);
112     }
113 
114     private static boolean needsNormalization(String hostname) {
115         final int length = hostname.length();
116         for (int i = 0; i < length; i++) {
117             int c = hostname.charAt(i);
118             if (c > 0x7F) {
119                 return true;
120             }
121         }
122         return false;
123     }
124 
125     @Override
126     public V map(String hostname) {
127         if (hostname != null) {
128             hostname = normalizeHostname(hostname);
129 
130             for (Map.Entry<String, V> entry : map.entrySet()) {
131                 if (matches(entry.getKey(), hostname)) {
132                     return entry.getValue();
133                 }
134             }
135         }
136         return defaultValue;
137     }
138 
139     /**
140      * Returns a read-only {@link Map} of the domain mapping patterns and their associated value objects.
141      */
142     public Map<String, V> asMap() {
143         return unmodifiableMap;
144     }
145 
146     @Override
147     public String toString() {
148         return StringUtil.simpleClassName(this) + "(default: " + defaultValue + ", map: " + map + ')';
149     }
150 }