| // Copyright 2011 The Closure Library Authors. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS-IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| /** |
| * @fileoverview This file contains classes to handle IPv4 and IPv6 addresses. |
| * This implementation is mostly based on Google's project: |
| * http://code.google.com/p/ipaddr-py/. |
| * |
| */ |
| |
| goog.provide('goog.net.IpAddress'); |
| goog.provide('goog.net.Ipv4Address'); |
| goog.provide('goog.net.Ipv6Address'); |
| |
| goog.require('goog.array'); |
| goog.require('goog.math.Integer'); |
| goog.require('goog.object'); |
| goog.require('goog.string'); |
| |
| |
| |
| /** |
| * Abstract class defining an IP Address. |
| * |
| * Please use goog.net.IpAddress static methods or |
| * goog.net.Ipv4Address/Ipv6Address classes. |
| * |
| * @param {!goog.math.Integer} address The Ip Address. |
| * @param {number} version The version number (4, 6). |
| * @constructor |
| */ |
| goog.net.IpAddress = function(address, version) { |
| /** |
| * The IP Address. |
| * @type {!goog.math.Integer} |
| * @private |
| */ |
| this.ip_ = address; |
| |
| /** |
| * The IP Address version. |
| * @type {number} |
| * @private |
| */ |
| this.version_ = version; |
| |
| }; |
| |
| |
| /** |
| * @return {number} The IP Address version. |
| */ |
| goog.net.IpAddress.prototype.getVersion = function() { |
| return this.version_; |
| }; |
| |
| |
| /** |
| * @param {!goog.net.IpAddress} other The other IP Address. |
| * @return {boolean} true if the IP Addresses are equal. |
| */ |
| goog.net.IpAddress.prototype.equals = function(other) { |
| return (this.version_ == other.getVersion() && |
| this.ip_.equals(other.toInteger())); |
| }; |
| |
| |
| /** |
| * @return {!goog.math.Integer} The IP Address, as an Integer. |
| */ |
| goog.net.IpAddress.prototype.toInteger = function() { |
| return /** @type {!goog.math.Integer} */ (goog.object.clone(this.ip_)); |
| }; |
| |
| |
| /** |
| * @return {string} The IP Address, as an URI string following RFC 3986. |
| */ |
| goog.net.IpAddress.prototype.toUriString = goog.abstractMethod; |
| |
| |
| /** |
| * @return {string} The IP Address, as a string. |
| * @override |
| */ |
| goog.net.IpAddress.prototype.toString = goog.abstractMethod; |
| |
| |
| /** |
| * Parses an IP Address in a string. |
| * If the string is malformed, the function will simply return null |
| * instead of raising an exception. |
| * |
| * @param {string} address The IP Address. |
| * @see {goog.net.Ipv4Address} |
| * @see {goog.net.Ipv6Address} |
| * @return {goog.net.IpAddress} The IP Address or null. |
| */ |
| goog.net.IpAddress.fromString = function(address) { |
| try { |
| if (address.indexOf(':') != -1) { |
| return new goog.net.Ipv6Address(address); |
| } |
| |
| return new goog.net.Ipv4Address(address); |
| } catch (e) { |
| // Both constructors raise exception if the address is malformed (ie. |
| // invalid). The user of this function should not care about catching |
| // the exception, espcially if it's used to validate an user input. |
| return null; |
| } |
| }; |
| |
| |
| /** |
| * Tries to parse a string represented as a host portion of an URI. |
| * See RFC 3986 for more details on IPv6 addresses inside URI. |
| * If the string is malformed, the function will simply return null |
| * instead of raising an exception. |
| * |
| * @param {string} address A RFC 3986 encoded IP address. |
| * @see {goog.net.Ipv4Address} |
| * @see {goog.net.Ipv6Address} |
| * @return {goog.net.IpAddress} The IP Address. |
| */ |
| goog.net.IpAddress.fromUriString = function(address) { |
| try { |
| if (goog.string.startsWith(address, '[') && |
| goog.string.endsWith(address, ']')) { |
| return new goog.net.Ipv6Address( |
| address.substring(1, address.length - 1)); |
| } |
| |
| return new goog.net.Ipv4Address(address); |
| } catch (e) { |
| // Both constructors raise exception if the address is malformed (ie. |
| // invalid). The user of this function should not care about catching |
| // the exception, espcially if it's used to validate an user input. |
| return null; |
| } |
| }; |
| |
| |
| |
| /** |
| * Takes a string or a number and returns a IPv4 Address. |
| * |
| * This constructor accepts strings and instance of goog.math.Integer. |
| * If you pass a goog.math.Integer, make sure that its sign is set to positive. |
| * @param {(string|!goog.math.Integer)} address The address to store. |
| * @extends {goog.net.IpAddress} |
| * @constructor |
| * @final |
| */ |
| goog.net.Ipv4Address = function(address) { |
| var ip = goog.math.Integer.ZERO; |
| if (address instanceof goog.math.Integer) { |
| if (address.getSign() != 0 || |
| address.lessThan(goog.math.Integer.ZERO) || |
| address.greaterThan(goog.net.Ipv4Address.MAX_ADDRESS_)) { |
| throw Error('The address does not look like an IPv4.'); |
| } else { |
| ip = goog.object.clone(address); |
| } |
| } else { |
| if (!goog.net.Ipv4Address.REGEX_.test(address)) { |
| throw Error(address + ' does not look like an IPv4 address.'); |
| } |
| |
| var octets = address.split('.'); |
| if (octets.length != 4) { |
| throw Error(address + ' does not look like an IPv4 address.'); |
| } |
| |
| for (var i = 0; i < octets.length; i++) { |
| var parsedOctet = goog.string.toNumber(octets[i]); |
| if (isNaN(parsedOctet) || |
| parsedOctet < 0 || parsedOctet > 255 || |
| (octets[i].length != 1 && goog.string.startsWith(octets[i], '0'))) { |
| throw Error('In ' + address + ', octet ' + i + ' is not valid'); |
| } |
| var intOctet = goog.math.Integer.fromNumber(parsedOctet); |
| ip = ip.shiftLeft(8).or(intOctet); |
| } |
| } |
| goog.net.Ipv4Address.base( |
| this, 'constructor', /** @type {!goog.math.Integer} */ (ip), 4); |
| }; |
| goog.inherits(goog.net.Ipv4Address, goog.net.IpAddress); |
| |
| |
| /** |
| * Regular expression matching all the allowed chars for IPv4. |
| * @type {RegExp} |
| * @private |
| * @const |
| */ |
| goog.net.Ipv4Address.REGEX_ = /^[0-9.]*$/; |
| |
| |
| /** |
| * The Maximum length for a netmask (aka, the number of bits for IPv4). |
| * @type {number} |
| * @const |
| */ |
| goog.net.Ipv4Address.MAX_NETMASK_LENGTH = 32; |
| |
| |
| /** |
| * The Maximum address possible for IPv4. |
| * @type {goog.math.Integer} |
| * @private |
| * @const |
| */ |
| goog.net.Ipv4Address.MAX_ADDRESS_ = goog.math.Integer.ONE.shiftLeft( |
| goog.net.Ipv4Address.MAX_NETMASK_LENGTH).subtract(goog.math.Integer.ONE); |
| |
| |
| /** |
| * @override |
| */ |
| goog.net.Ipv4Address.prototype.toString = function() { |
| if (this.ipStr_) { |
| return this.ipStr_; |
| } |
| |
| var ip = this.ip_.getBitsUnsigned(0); |
| var octets = []; |
| for (var i = 3; i >= 0; i--) { |
| octets[i] = String((ip & 0xff)); |
| ip = ip >>> 8; |
| } |
| |
| this.ipStr_ = octets.join('.'); |
| |
| return this.ipStr_; |
| }; |
| |
| |
| /** |
| * @override |
| */ |
| goog.net.Ipv4Address.prototype.toUriString = function() { |
| return this.toString(); |
| }; |
| |
| |
| |
| /** |
| * Takes a string or a number and returns an IPv6 Address. |
| * |
| * This constructor accepts strings and instance of goog.math.Integer. |
| * If you pass a goog.math.Integer, make sure that its sign is set to positive. |
| * @param {(string|!goog.math.Integer)} address The address to store. |
| * @constructor |
| * @extends {goog.net.IpAddress} |
| * @final |
| */ |
| goog.net.Ipv6Address = function(address) { |
| var ip = goog.math.Integer.ZERO; |
| if (address instanceof goog.math.Integer) { |
| if (address.getSign() != 0 || |
| address.lessThan(goog.math.Integer.ZERO) || |
| address.greaterThan(goog.net.Ipv6Address.MAX_ADDRESS_)) { |
| throw Error('The address does not look like a valid IPv6.'); |
| } else { |
| ip = goog.object.clone(address); |
| } |
| } else { |
| if (!goog.net.Ipv6Address.REGEX_.test(address)) { |
| throw Error(address + ' is not a valid IPv6 address.'); |
| } |
| |
| var splitColon = address.split(':'); |
| if (splitColon[splitColon.length - 1].indexOf('.') != -1) { |
| var newHextets = goog.net.Ipv6Address.dottedQuadtoHextets_( |
| splitColon[splitColon.length - 1]); |
| goog.array.removeAt(splitColon, splitColon.length - 1); |
| goog.array.extend(splitColon, newHextets); |
| address = splitColon.join(':'); |
| } |
| |
| var splitDoubleColon = address.split('::'); |
| if (splitDoubleColon.length > 2 || |
| (splitDoubleColon.length == 1 && splitColon.length != 8)) { |
| throw Error(address + ' is not a valid IPv6 address.'); |
| } |
| |
| var ipArr; |
| if (splitDoubleColon.length > 1) { |
| ipArr = goog.net.Ipv6Address.explode_(splitDoubleColon); |
| } else { |
| ipArr = splitColon; |
| } |
| |
| if (ipArr.length != 8) { |
| throw Error(address + ' is not a valid IPv6 address'); |
| } |
| |
| for (var i = 0; i < ipArr.length; i++) { |
| var parsedHextet = goog.math.Integer.fromString(ipArr[i], 16); |
| if (parsedHextet.lessThan(goog.math.Integer.ZERO) || |
| parsedHextet.greaterThan(goog.net.Ipv6Address.MAX_HEXTET_VALUE_)) { |
| throw Error(ipArr[i] + ' in ' + address + ' is not a valid hextet.'); |
| } |
| ip = ip.shiftLeft(16).or(parsedHextet); |
| } |
| } |
| goog.net.Ipv6Address.base( |
| this, 'constructor', /** @type {!goog.math.Integer} */ (ip), 6); |
| }; |
| goog.inherits(goog.net.Ipv6Address, goog.net.IpAddress); |
| |
| |
| /** |
| * Regular expression matching all allowed chars for an IPv6. |
| * @type {RegExp} |
| * @private |
| * @const |
| */ |
| goog.net.Ipv6Address.REGEX_ = /^([a-fA-F0-9]*:){2}[a-fA-F0-9:.]*$/; |
| |
| |
| /** |
| * The Maximum length for a netmask (aka, the number of bits for IPv6). |
| * @type {number} |
| * @const |
| */ |
| goog.net.Ipv6Address.MAX_NETMASK_LENGTH = 128; |
| |
| |
| /** |
| * The maximum value of a hextet. |
| * @type {goog.math.Integer} |
| * @private |
| * @const |
| */ |
| goog.net.Ipv6Address.MAX_HEXTET_VALUE_ = goog.math.Integer.fromInt(65535); |
| |
| |
| /** |
| * The Maximum address possible for IPv6. |
| * @type {goog.math.Integer} |
| * @private |
| * @const |
| */ |
| goog.net.Ipv6Address.MAX_ADDRESS_ = goog.math.Integer.ONE.shiftLeft( |
| goog.net.Ipv6Address.MAX_NETMASK_LENGTH).subtract(goog.math.Integer.ONE); |
| |
| |
| /** |
| * @override |
| */ |
| goog.net.Ipv6Address.prototype.toString = function() { |
| if (this.ipStr_) { |
| return this.ipStr_; |
| } |
| |
| var outputArr = []; |
| for (var i = 3; i >= 0; i--) { |
| var bits = this.ip_.getBitsUnsigned(i); |
| var firstHextet = bits >>> 16; |
| var secondHextet = bits & 0xffff; |
| outputArr.push(firstHextet.toString(16)); |
| outputArr.push(secondHextet.toString(16)); |
| } |
| |
| outputArr = goog.net.Ipv6Address.compress_(outputArr); |
| this.ipStr_ = outputArr.join(':'); |
| return this.ipStr_; |
| }; |
| |
| |
| /** |
| * @override |
| */ |
| goog.net.Ipv6Address.prototype.toUriString = function() { |
| return '[' + this.toString() + ']'; |
| }; |
| |
| |
| /** |
| * This method is in charge of expanding/exploding an IPv6 string from its |
| * compressed form. |
| * @private |
| * @param {!Array<string>} address An IPv6 address split around '::'. |
| * @return {!Array<string>} The expanded version of the IPv6. |
| */ |
| goog.net.Ipv6Address.explode_ = function(address) { |
| var basePart = address[0].split(':'); |
| var secondPart = address[1].split(':'); |
| |
| if (basePart.length == 1 && basePart[0] == '') { |
| basePart = []; |
| } |
| if (secondPart.length == 1 && secondPart[0] == '') { |
| secondPart = []; |
| } |
| |
| // Now we fill the gap with 0. |
| var gap = 8 - (basePart.length + secondPart.length); |
| |
| if (gap < 1) { |
| return []; |
| } |
| |
| goog.array.extend(basePart, goog.array.repeat('0', gap)); |
| |
| // Now we merge the basePart + gap + secondPart |
| goog.array.extend(basePart, secondPart); |
| |
| return basePart; |
| }; |
| |
| |
| /** |
| * This method is in charge of compressing an expanded IPv6 array of hextets. |
| * @private |
| * @param {!Array<string>} hextets The array of hextet. |
| * @return {!Array<string>} The compressed version of this array. |
| */ |
| goog.net.Ipv6Address.compress_ = function(hextets) { |
| var bestStart = -1; |
| var start = -1; |
| var bestSize = 0; |
| var size = 0; |
| for (var i = 0; i < hextets.length; i++) { |
| if (hextets[i] == '0') { |
| size++; |
| if (start == -1) { |
| start = i; |
| } |
| if (size > bestSize) { |
| bestSize = size; |
| bestStart = start; |
| } |
| } else { |
| start = -1; |
| size = 0; |
| } |
| } |
| |
| if (bestSize > 0) { |
| if ((bestStart + bestSize) == hextets.length) { |
| hextets.push(''); |
| } |
| hextets.splice(bestStart, bestSize, ''); |
| |
| if (bestStart == 0) { |
| hextets = [''].concat(hextets); |
| } |
| } |
| return hextets; |
| }; |
| |
| |
| /** |
| * This method will convert an IPv4 to a list of 2 hextets. |
| * |
| * For instance, 1.2.3.4 will be converted to ['0102', '0304']. |
| * @private |
| * @param {string} quads An IPv4 as a string. |
| * @return {!Array<string>} A list of 2 hextets. |
| */ |
| goog.net.Ipv6Address.dottedQuadtoHextets_ = function(quads) { |
| var ip4 = new goog.net.Ipv4Address(quads).toInteger(); |
| var bits = ip4.getBitsUnsigned(0); |
| var hextets = []; |
| |
| hextets.push(((bits >>> 16) & 0xffff).toString(16)); |
| hextets.push((bits & 0xffff).toString(16)); |
| |
| return hextets; |
| }; |
| |
| |
| /** |
| * @return {boolean} true if the IPv6 contains a mapped IPv4. |
| */ |
| goog.net.Ipv6Address.prototype.isMappedIpv4Address = function() { |
| return (this.ip_.getBitsUnsigned(3) == 0 && |
| this.ip_.getBitsUnsigned(2) == 0 && |
| this.ip_.getBitsUnsigned(1) == 0xffff); |
| }; |
| |
| |
| /** |
| * Will return the mapped IPv4 address in this IPv6 address. |
| * @return {goog.net.Ipv4Address} an IPv4 or null. |
| */ |
| goog.net.Ipv6Address.prototype.getMappedIpv4Address = function() { |
| if (!this.isMappedIpv4Address()) { |
| return null; |
| } |
| |
| var newIpv4 = new goog.math.Integer([this.ip_.getBitsUnsigned(0)], 0); |
| return new goog.net.Ipv4Address(newIpv4); |
| }; |