Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions lib/ip.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ ip.toString = function (buff, offset, length) {

const ipv4Regex = /^(\d{1,3}\.){3,3}\d{1,3}$/;
const ipv6Regex = /^(::)?(((\d{1,3}\.){3}(\d{1,3}){1})?([0-9a-f]){0,4}:{0,2}){1,8}(::)?$/i;
const octalRegex = /^0[0-7]+(\.[0-7]+){0,3}$/;

ip.isV4Format = function (ip) {
return ipv4Regex.test(ip);
Expand All @@ -93,6 +94,10 @@ ip.isV6Format = function (ip) {
return ipv6Regex.test(ip);
};

ip.isOctalFormat = function (ip) {
return octalRegex.test(ip);
};

function _normalizeFamily(family) {
if (family === 4) {
return 'ipv4';
Expand Down Expand Up @@ -312,7 +317,7 @@ ip.isPrivate = function (addr) {
}

// ensure the ipv4 address is valid
if (!ip.isV6Format(addr)) {
if (!ip.isV6Format(addr) || ip.isOctalFormat(addr)) {
const ipl = ip.normalizeToLong(addr);
if (ipl < 0) {
throw new Error('invalid ipv4 address');
Expand Down Expand Up @@ -340,15 +345,18 @@ ip.isPublic = function (addr) {
ip.isLoopback = function (addr) {
// If addr is an IPv4 address in long integer form (no dots and no colons), convert it
if (!/\./.test(addr) && !/:/.test(addr)) {
addr = ip.fromLong(Number(addr));
const num = ip.isOctalFormat(addr) ? parseInt(addr, 8) : parseInt(addr, 10);
addr = ip.fromLong(num >>> 0);
}

return /^(::f{4}:)?127\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})/
.test(addr)
return /^(::f{4}:)?127\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})/i.test(addr)
// covers 127.* short forms like 127.1 or 127.0.1
|| /^127(\.|$)/.test(addr)
|| /^0177\./.test(addr)
|| /^0x7f\./i.test(addr)
|| /^fe80::1$/i.test(addr)
|| /^::1$/.test(addr)
// ::1, also covers padding zeros 0:0:0:0:0:0:0:1, 000:0:0000::01
|| /^(0*:)*0*1$/i.test(addr)
|| /^::$/.test(addr);
};

Expand Down
77 changes: 74 additions & 3 deletions test/api-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,10 @@ describe('IP library for node.js', () => {
assert.equal(ip.normalizeToLong('010.0.0.01'), 134217729);
});

it('should correctly handle octal notation "01200034567"', () => {
assert.equal(ip.normalizeToLong('01200034567'), 167786871);
});

// Testing invalid inputs
it('should return -1 for an invalid address "256.100.50.25"', () => {
assert.equal(ip.normalizeToLong('256.100.50.25'), -1);
Expand Down Expand Up @@ -378,10 +382,22 @@ describe('IP library for node.js', () => {
});

describe('isLoopback() method', () => {
describe('127.0.0.1', () => {
it('should respond with true', () => {
describe('127.x loopback range', () => {
it('should return true for 127.0.0.1', () => {
assert.ok(ip.isLoopback('127.0.0.1'));
});

it('should return true for 127.1', () => {
assert.ok(ip.isLoopback('127.1'));
});

it('should return true for 127.0.1', () => {
assert.ok(ip.isLoopback('127.0.1'));
});

it('should return true for 127.255.255.255 (end of loopback range)', () => {
assert.ok(ip.isLoopback('127.255.255.255'));
});
});

describe('127.8.8.8', () => {
Expand All @@ -402,7 +418,19 @@ describe('IP library for node.js', () => {
});
});

describe('::1', () => {
describe('::1 loopback range', () => {
it('should return true for ::1', () => {
assert.ok(ip.isLoopback('::1'));
});

it('should return true for 0:0:0:0:0:0:0:1', () => {
assert.ok(ip.isLoopback('0:0:0:0:0:0:0:1'));
});

it('should return true for 000:0:0000::01', () => {
assert.ok(ip.isLoopback('000:0:0000::01'));
});

it('should respond with true', () => {
assert.ok(ip.isLoopback('::1'));
});
Expand Down Expand Up @@ -506,4 +534,47 @@ describe('IP library for node.js', () => {
it('should return false for "192.168.1.1"', () => {
assert.equal(ip.isLoopback('192.168.1.1'), false);
});

describe('isOctalFormat() method', () => {
it('should return true for "01200034567"', () => {
assert.equal(ip.isOctalFormat('01200034567'), true);
});

it('should return true for "0177.0.0.1"', () => {
assert.equal(ip.isOctalFormat('0177.0.0.1'), true);
});

it('should return true for "0177.0.1"', () => {
assert.equal(ip.isOctalFormat('0177.0.1'), true);
});

it('should return true for "0177.1"', () => {
assert.equal(ip.isOctalFormat('0177.1'), true);
});

it('should return true for "010.0.0.01"', () => {
assert.equal(ip.isOctalFormat('010.0.0.01'), true);
});

it('should return true for "019.0.0.1"', () => {
assert.equal(ip.isOctalFormat('019.0.0.1'), false);
});
});

describe('isPublic (CVE-2024-29415 cases)', () => {
const nonPublicCases = [
'127.1', // valid ip4 loopback
'01200034567', // valid octal format
'017700000001', // valid octal format
'012.1.2.3', // leading-zero octal fragments
'000:0:0000::01', // zero-padding IPv6 representation
'::fFFf:127.0.0.1' // IPv6 case insensitive case
];

nonPublicCases.forEach((ipAddr) =>
it(`${ipAddr} should be non-public`, () => {
assert.equal(ip.isPublic(ipAddr), false);
})
);
});
});