// 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; /** * @return {boolean} Whether or not the address is site-local. */ goog.net.IpAddress.prototype.isSiteLocal = goog.abstractMethod; /** * @return {boolean} Whether or not the address is link-local. */ goog.net.IpAddress.prototype.isLinkLocal = 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) { /** * The cached string representation of the IP Address. * @type {?string} * @private */ this.ipStr_ = null; 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(); }; /** * @override */ goog.net.Ipv4Address.prototype.isSiteLocal = function() { // Check for prefix 10/8, 172.16/12, or 192.168/16. var ipInt = this.ip_.toInt(); return (((ipInt >>> 24) & 0xff) == 10) || ((((ipInt >>> 24) & 0xff) == 172) && (((ipInt >>> 16) & 0xf0) == 16)) || ((((ipInt >>> 24) & 0xff) == 192) && (((ipInt >>> 16) & 0xff) == 168)); }; /** * @override */ goog.net.Ipv4Address.prototype.isLinkLocal = function() { // Check for prefix 169.254/16. var ipInt = this.ip_.toInt(); return (((ipInt >>> 24) & 0xff) == 169) && (((ipInt >>> 16) & 0xff) == 254); }; /** * 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) { /** * The cached string representation of the IP Address. * @type {?string} * @private */ this.ipStr_ = null; 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() + ']'; }; /** * @override */ goog.net.Ipv6Address.prototype.isSiteLocal = function() { // Check for prefix fd00::/8. var firstDWord = this.ip_.getBitsUnsigned(3); var firstHextet = firstDWord >>> 16; return (firstHextet & 0xff00) == 0xfd00; }; /** * @override */ goog.net.Ipv6Address.prototype.isLinkLocal = function() { // Check for prefix fe80::/10. var firstDWord = this.ip_.getBitsUnsigned(3); var firstHextet = firstDWord >>> 16; return (firstHextet & 0xffc0) == 0xfe80; }; /** * This method is in charge of expanding/exploding an IPv6 string from its * compressed form. * @private * @param {!Array} address An IPv6 address split around '::'. * @return {!Array} 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 []; } return goog.array.join(basePart, goog.array.repeat('0', gap), secondPart); }; /** * This method is in charge of compressing an expanded IPv6 array of hextets. * @private * @param {!Array} hextets The array of hextet. * @return {!Array} 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} 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); };