request-public-ip
Version:
Node.js module for retrieving a request's public IP address
207 lines (203 loc) • 7.38 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
/* eslint-disable @typescript-eslint/no-unsafe-return */
class Parser {
index = 0;
input = "";
new(input) {
this.index = 0;
this.input = input;
return this;
}
/** Run a parser, and restore the pre-parse state if it fails. */
readAtomically(fn) {
const index = this.index;
const result = fn();
if (result === undefined) {
this.index = index;
}
return result;
}
/** Run a parser, but fail if the entire input wasn't consumed. Doesn't run atomically. */
parseWith(fn) {
const result = fn();
if (this.index !== this.input.length) {
return undefined;
}
return result;
}
/** Peek the next character from the input */
peekChar() {
if (this.index >= this.input.length) {
return undefined;
}
return this.input[this.index];
}
/** Read the next character from the input */
readChar() {
if (this.index >= this.input.length) {
return undefined;
}
return this.input[this.index++];
}
/** Read the next character from the input if it matches the target. */
readGivenChar(target) {
return this.readAtomically(() => {
const char = this.readChar();
if (char !== target) {
return undefined;
}
return char;
});
}
/**
* Helper for reading separators in an indexed loop. Reads the separator
* character iff index > 0, then runs the parser. When used in a loop,
* the separator character will only be read on index > 0 (see
* readIPv4Addr for an example)
*/
readSeparator(sep, index, inner) {
return this.readAtomically(() => {
if (index > 0) {
if (this.readGivenChar(sep) === undefined) {
return undefined;
}
}
return inner();
});
}
/**
* Read a number off the front of the input in the given radix, stopping
* at the first non-digit character or eof. Fails if the number has more
* digits than max_digits or if there is no number.
*/
readNumber(radix, maxDigits, allowZeroPrefix, maxBytes) {
return this.readAtomically(() => {
let result = 0;
let digitCount = 0;
const leadingChar = this.peekChar();
if (leadingChar === undefined) {
return undefined;
}
const hasLeadingZero = leadingChar === "0";
const maxValue = 2 ** (8 * maxBytes) - 1;
// eslint-disable-next-line no-constant-condition
while (true) {
const digit = this.readAtomically(() => {
const char = this.readChar();
if (char === undefined) {
return undefined;
}
const num = Number.parseInt(char, radix);
if (Number.isNaN(num)) {
return undefined;
}
return num;
});
if (digit === undefined) {
break;
}
result *= radix;
result += digit;
if (result > maxValue) {
return undefined;
}
digitCount += 1;
if (maxDigits !== undefined) {
if (digitCount > maxDigits) {
return undefined;
}
}
}
if (digitCount === 0) {
return undefined;
}
else if (!allowZeroPrefix && hasLeadingZero && digitCount > 1) {
return undefined;
}
else {
return result;
}
});
}
/** Read an IPv4 address. */
readIPv4Addr() {
return this.readAtomically(() => {
const out = new Uint8Array(4);
for (let i = 0; i < out.length; i++) {
const ix = this.readSeparator(".", i, () => this.readNumber(10, 3, false, 1));
if (ix === undefined) {
return undefined;
}
out[i] = ix;
}
return out;
});
}
/** Read an IPv6 Address. */
readIPv6Addr() {
/**
* Read a chunk of an IPv6 address into `groups`. Returns the number
* of groups read, along with a bool indicating if an embedded
* trailing IPv4 address was read. Specifically, read a series of
* colon-separated IPv6 groups (0x0000 - 0xFFFF), with an optional
* trailing embedded IPv4 address.
*/
const readGroups = (groups) => {
for (let i = 0; i < groups.length / 2; i++) {
const ix = i * 2;
// Try to read a trailing embedded IPv4 address. There must be at least 4 groups left.
if (i < groups.length - 3) {
const ipv4 = this.readSeparator(":", i, () => this.readIPv4Addr());
if (ipv4 !== undefined) {
groups[ix] = ipv4[0];
groups[ix + 1] = ipv4[1];
groups[ix + 2] = ipv4[2];
groups[ix + 3] = ipv4[3];
return [ix + 4, true];
}
}
const group = this.readSeparator(":", i, () => this.readNumber(16, 4, true, 2));
if (group === undefined) {
return [ix, false];
}
groups[ix] = group >> 8;
groups[ix + 1] = group & 255;
}
return [groups.length, false];
};
return this.readAtomically(() => {
// Read the front part of the address; either the whole thing, or up to the first ::
const head = new Uint8Array(16);
const [headSize, headIp4] = readGroups(head);
if (headSize === 16) {
return head;
}
// IPv4 part is not allowed before `::`
if (headIp4) {
return undefined;
}
// Read `::` if previous code parsed less than 8 groups.
// `::` indicates one or more groups of 16 bits of zeros.
if (this.readGivenChar(":") === undefined) {
return undefined;
}
if (this.readGivenChar(":") === undefined) {
return undefined;
}
// Read the back part of the address. The :: must contain at least one
// set of zeroes, so our max length is 7.
const tail = new Uint8Array(14);
const limit = 16 - (headSize + 2);
const [tailSize] = readGroups(tail.subarray(0, limit));
// Concat the head and tail of the IP address
head.set(tail.subarray(0, tailSize), 16 - tailSize);
return head;
});
}
/** Read an IP Address, either IPv4 or IPv6. */
readIPAddr() {
return this.readIPv4Addr() ?? this.readIPv6Addr();
}
}
exports.Parser = Parser;