net: update net.blocklist to allow file save and file management · nodejs/node@f753645

11

'use strict';

2233

const {

4+

ArrayIsArray,

45

Boolean,

6+

JSONParse,

7+

NumberParseInt,

58

ObjectSetPrototypeOf,

69

Symbol,

710

} = primordials;

@@ -32,6 +35,7 @@ const { owner_symbol } = internalBinding('symbols');

32353336

const {

3437

ERR_INVALID_ARG_VALUE,

38+

ERR_INVALID_ARG_TYPE,

3539

} = require('internal/errors').codes;

36403741

const { validateInt32, validateString } = require('internal/validators');

@@ -139,10 +143,130 @@ class BlockList {

139143

return Boolean(this[kHandle].check(address[kSocketAddressHandle]));

140144

}

141145146+

/*

147+

* @param {string[]} data

148+

* @example

149+

* const data = [

150+

* // IPv4 examples

151+

* 'Subnet: IPv4 192.168.1.0/24',

152+

* 'Address: IPv4 10.0.0.5',

153+

* 'Range: IPv4 192.168.2.1-192.168.2.10',

154+

* 'Range: IPv4 10.0.0.1-10.0.0.10',

155+

*

156+

* // IPv6 examples

157+

* 'Subnet: IPv6 2001:0db8:85a3:0000:0000:8a2e:0370:7334/64',

158+

* 'Address: IPv6 2001:0db8:85a3:0000:0000:8a2e:0370:7334',

159+

* 'Range: IPv6 2001:0db8:85a3:0000:0000:8a2e:0370:7334-2001:0db8:85a3:0000:0000:8a2e:0370:7335',

160+

* 'Subnet: IPv6 2001:db8:1234::/48',

161+

* 'Address: IPv6 2001:db8:1234::1',

162+

* 'Range: IPv6 2001:db8:1234::1-2001:db8:1234::10'

163+

* ];

164+

*/

165+

#parseIPInfo(data) {

166+

for (const item of data) {

167+

if (item.includes('IPv4')) {

168+

const subnetMatch = item.match(

169+

/Subnet: IPv4 (\d{1,3}(?:\.\d{1,3}){3})\/(\d{1,2})/,

170+

);

171+

if (subnetMatch) {

172+

const { 1: network, 2: prefix } = subnetMatch;

173+

this.addSubnet(network, NumberParseInt(prefix));

174+

continue;

175+

}

176+

const addressMatch = item.match(/Address: IPv4 (\d{1,3}(?:\.\d{1,3}){3})/);

177+

if (addressMatch) {

178+

const { 1: address } = addressMatch;

179+

this.addAddress(address);

180+

continue;

181+

}

182+183+

const rangeMatch = item.match(

184+

/Range: IPv4 (\d{1,3}(?:\.\d{1,3}){3})-(\d{1,3}(?:\.\d{1,3}){3})/,

185+

);

186+

if (rangeMatch) {

187+

const { 1: start, 2: end } = rangeMatch;

188+

this.addRange(start, end);

189+

continue;

190+

}

191+

}

192+

// IPv6 parsing with support for compressed addresses

193+

if (item.includes('IPv6')) {

194+

// IPv6 subnet pattern: supports both full and compressed formats

195+

// Examples:

196+

// - 2001:0db8:85a3:0000:0000:8a2e:0370:7334/64 (full)

197+

// - 2001:db8:85a3::8a2e:370:7334/64 (compressed)

198+

// - 2001:db8:85a3::192.0.2.128/64 (mixed)

199+

const ipv6SubnetMatch = item.match(

200+

/Subnet: IPv6 ([0-9a-fA-F:]{1,39})\/([0-9]{1,3})/i,

201+

);

202+

if (ipv6SubnetMatch) {

203+

const { 1: network, 2: prefix } = ipv6SubnetMatch;

204+

this.addSubnet(network, NumberParseInt(prefix), 'ipv6');

205+

continue;

206+

}

207+208+

// IPv6 address pattern: supports both full and compressed formats

209+

// Examples:

210+

// - 2001:0db8:85a3:0000:0000:8a2e:0370:7334 (full)

211+

// - 2001:db8:85a3::8a2e:370:7334 (compressed)

212+

// - 2001:db8:85a3::192.0.2.128 (mixed)

213+

const ipv6AddressMatch = item.match(/Address: IPv6 ([0-9a-fA-F:]{1,39})/i);

214+

if (ipv6AddressMatch) {

215+

const { 1: address } = ipv6AddressMatch;

216+

this.addAddress(address, 'ipv6');

217+

continue;

218+

}

219+220+

// IPv6 range pattern: supports both full and compressed formats

221+

// Examples:

222+

// - 2001:0db8:85a3:0000:0000:8a2e:0370:7334-2001:0db8:85a3:0000:0000:8a2e:0370:7335 (full)

223+

// - 2001:db8:85a3::8a2e:370:7334-2001:db8:85a3::8a2e:370:7335 (compressed)

224+

// - 2001:db8:85a3::192.0.2.128-2001:db8:85a3::192.0.2.129 (mixed)

225+

const ipv6RangeMatch = item.match(/Range: IPv6 ([0-9a-fA-F:]{1,39})-([0-9a-fA-F:]{1,39})/i);

226+

if (ipv6RangeMatch) {

227+

const { 1: start, 2: end } = ipv6RangeMatch;

228+

this.addRange(start, end, 'ipv6');

229+

continue;

230+

}

231+

}

232+

}

233+

}

234+235+236+

toJSON() {

237+

return this.rules;

238+

}

239+240+

fromJSON(data) {

241+

// The data argument must be a string, or an array of strings that

242+

// is JSON parseable.

243+

if (ArrayIsArray(data)) {

244+

for (const n of data) {

245+

if (typeof n !== 'string') {

246+

throw new ERR_INVALID_ARG_TYPE('data', ['string', 'string[]'], data);

247+

}

248+

}

249+

} else if (typeof data !== 'string') {

250+

throw new ERR_INVALID_ARG_TYPE('data', ['string', 'string[]'], data);

251+

} else {

252+

data = JSONParse(data);

253+

if (!ArrayIsArray(data)) {

254+

throw new ERR_INVALID_ARG_TYPE('data', ['string', 'string[]'], data);

255+

}

256+

for (const n of data) {

257+

if (typeof n !== 'string') {

258+

throw new ERR_INVALID_ARG_TYPE('data', ['string', 'string[]'], data);

259+

}

260+

}

261+

}

262+263+

this.#parseIPInfo(data);

264+

}

265+266+142267

get rules() {

143268

return this[kHandle].getRules();

144269

}

145-146270

[kClone]() {

147271

const handle = this[kHandle];

148272

return {