001/**
002 *
003 * Copyright 2019-2023 Florian Schmaus
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.jivesoftware.smack.util;
018
019import java.net.Inet4Address;
020import java.net.Inet6Address;
021import java.net.InetAddress;
022import java.net.UnknownHostException;
023
024import org.minidns.dnslabel.DnsLabel;
025import org.minidns.dnsname.DnsName;
026import org.minidns.dnsname.InvalidDnsNameException;
027import org.minidns.util.InetAddressUtil;
028
029/**
030 * An internet address, can be given as IP or as DNS name.
031 * <p>
032 * This type is meant for strings that hold an internet address. The original string used to construct this type is
033 * stored and returning in the {@link #toString()} method.
034 * </p>
035 *
036 * @since 4.4.0
037 */
038public abstract class InternetAddress implements CharSequence {
039
040    protected final String originalString;
041
042    protected InternetAddress(String originalString) {
043        this.originalString = Objects.requireNonNull(originalString, "The 'originalString' argument must not be null");
044    }
045
046    public abstract InetAddress asInetAddress() throws UnknownHostException;
047
048    @Override
049    public String toString() {
050        return originalString;
051    }
052
053    @Override
054    public int length() {
055        return originalString.length();
056    }
057
058    @Override
059    public char charAt(int index) {
060        return originalString.charAt(index);
061    }
062
063    @Override
064    public CharSequence subSequence(int start, int end) {
065        return originalString.subSequence(start, end);
066    }
067
068    public String getRaw() {
069        return originalString;
070    }
071
072    public static InternetAddress fromIgnoringZoneId(String address) {
073        return from(address, true);
074    }
075
076    public static InternetAddress from(String address) {
077        return from(address, false);
078    }
079
080    private static InternetAddress from(String address, boolean ignoreZoneId) {
081        String raw = address;
082        if (ignoreZoneId) {
083            int percentPosition = address.indexOf('%');
084            if (percentPosition > 1) {
085                address = address.substring(0, percentPosition);
086            }
087        }
088
089        final InternetAddress internetAddress;
090        if (InetAddressUtil.isIpV4Address(address)) {
091            internetAddress = new InternetAddress.Ipv4(address, raw);
092        } else if (InetAddressUtil.isIpV6Address(address)) {
093            internetAddress = new InternetAddress.Ipv6(address, raw);
094        } else if (address.contains(".")) {
095            InternetAddress domainNameInternetAddress;
096            try {
097                DnsName dnsName = DnsName.from(address);
098                domainNameInternetAddress = new InternetAddress.DomainName(address, dnsName);
099            } catch (InvalidDnsNameException e) {
100                domainNameInternetAddress = new InternetAddress.InvalidDomainName(address, e);
101            }
102            internetAddress = domainNameInternetAddress;
103        } else {
104            DnsLabel dnsLabel = DnsLabel.from(address);
105            internetAddress = new InternetAddress.DomainNameLabel(address, dnsLabel);
106        }
107        return internetAddress;
108    }
109
110    public static InternetAddress from(InetAddress inetAddress) {
111        if (inetAddress instanceof Inet4Address) {
112            return new InternetAddress.Ipv4(inetAddress.getHostAddress(), (Inet4Address) inetAddress);
113        } else if (inetAddress instanceof Inet6Address) {
114            return new InternetAddress.Ipv6(inetAddress.getHostAddress(), (Inet6Address) inetAddress);
115        } else {
116            throw new IllegalArgumentException("Unknown type " + inetAddress.getClass() + " of " + inetAddress);
117        }
118    }
119
120    private static class InetAddressInternetAddress extends InternetAddress {
121        private final InetAddress inetAddress;
122        private final String raw;
123
124        protected InetAddressInternetAddress(String originalString, String raw, InetAddress inetAddress) {
125            super(originalString);
126            this.raw = raw;
127            this.inetAddress = inetAddress;
128        }
129
130        @Override
131        public InetAddress asInetAddress() {
132            return inetAddress;
133        }
134
135        @Override
136        public final String getRaw() {
137            return raw;
138        }
139    }
140
141    public static final class Ipv4 extends InetAddressInternetAddress {
142
143        private final Inet4Address inet4Address;
144
145        private Ipv4(String originalString, String raw) {
146            this(originalString, raw, InetAddressUtil.ipv4From(originalString));
147        }
148
149        private Ipv4(String originalString, Inet4Address inet4Address) {
150            this(originalString, originalString, inet4Address);
151        }
152
153        private Ipv4(String originalString, String raw, Inet4Address inet4Address) {
154            super(originalString, raw, inet4Address);
155            this.inet4Address = inet4Address;
156        }
157
158        public Inet4Address getInet4Address() {
159            return inet4Address;
160        }
161    }
162
163    public static final class Ipv6 extends InetAddressInternetAddress {
164
165        private Inet6Address inet6Address;
166
167        private Ipv6(String originalString, String raw) {
168            this(originalString, raw, InetAddressUtil.ipv6From(originalString));
169        }
170
171        private Ipv6(String originalString, Inet6Address inet6Address) {
172            this(originalString, originalString, inet6Address);
173        }
174
175        private Ipv6(String originalString, String raw, Inet6Address inet6Address) {
176            super(originalString, raw, inet6Address);
177            this.inet6Address = inet6Address;
178        }
179
180        public Inet6Address getInet6Address() {
181            return inet6Address;
182        }
183    }
184
185    private static class NonNumericInternetAddress extends InternetAddress {
186        private boolean attemptedToResolveInetAddress;
187        private InetAddress inetAddress;
188
189        protected NonNumericInternetAddress(String originalString) {
190            super(originalString);
191        }
192
193        @Override
194        public InetAddress asInetAddress() throws UnknownHostException {
195            if (inetAddress != null || attemptedToResolveInetAddress) {
196                return inetAddress;
197            }
198
199            attemptedToResolveInetAddress = true;
200            inetAddress = InetAddress.getByName(originalString);
201
202            return inetAddress;
203        }
204    }
205
206    public static final class DomainName extends NonNumericInternetAddress {
207
208        private final DnsName dnsName;
209
210        private DomainName(String originalString, DnsName dnsName) {
211            super(originalString);
212            this.dnsName = dnsName;
213        }
214
215        public DnsName getDnsName() {
216            return dnsName;
217        }
218
219    }
220
221    public static final class DomainNameLabel extends NonNumericInternetAddress {
222
223        private final DnsLabel dnsLabel;
224
225        private DomainNameLabel(String originalString, DnsLabel dnsLabel) {
226            super(originalString);
227            this.dnsLabel = dnsLabel;
228        }
229
230        public DnsLabel getDnsLabel() {
231            return dnsLabel;
232        }
233    }
234
235    public static final class InvalidDomainName extends NonNumericInternetAddress {
236
237        private final InvalidDnsNameException invalidDnsNameException;
238
239        private InvalidDomainName(String originalString, InvalidDnsNameException invalidDnsNameException) {
240            super(originalString);
241            this.invalidDnsNameException = invalidDnsNameException;
242        }
243
244        public InvalidDnsNameException getInvalidDnsNameException() {
245            return invalidDnsNameException;
246        }
247    }
248}