codemirror-mode-ipaddr
Version:
IP addresses mode for CodeMirror
208 lines (194 loc) • 6.77 kB
JavaScript
CodeMirror.defineMode('ipaddr', function(editor_options, mode_options) {
var
UNKNOWN = 0,
IPV4 = 1, // Pure IPv4 address
IPV6 = 2, // Pure IPv6 address
DUAL = 3; // Dual IPv6+IPv4 address, e.g. 2001:db8::1234:5678:5.6.7.8
// Default settings:
var options = {
ipv4_ranges: true, // Accept IPv4 ranges, e.g. 10.0.0.0-10.255.255.255
ipv6_ranges: true, // Accept IPv6 ranges, e.g. 2001:0db8::-2001:0db8:ffff:ffff:ffff:ffff:ffff:ffff
ipv4_cidr: true, // Accept IPv4 CIDR, e.g. 10.0.0.0/8
ipv6_cidr: true, // Accept IPv6 CIDR, e.g. 2001:db8::/32
strict: true, // Strict mode: accept only one address or range (relevant as a nested mode)
default_state: UNKNOWN, // default state; best set through a MIME type
};
// Override options.default_state if invoked through a MIME type:
function is_mime_type(str) { return (typeof str === 'string') && (str.indexOf('/') !== -1); }
var invoked_mime;
if (is_mime_type(mode_options.name)) invoked_mime = mode_options.name;
else if (is_mime_type(editor_options.mode)) invoked_mime = editor_options.mode;
else if (is_mime_type(editor_options.mode.name)) invoked_mime = editor_options.mode.name;
if (invoked_mime === 'text/x-ipv4-address') options.default_state = IPV4;
else if (invoked_mime === 'text/x-ipv6-address') options.default_state = IPV6;
// Override default settings with user-provided settings:
for (option in options) {
if (option in mode_options) options[option] = mode_options[option];
}
// Helpers:
ipv4_re = new RegExp('^(?:\\d+\\.){1,3}\\d*');
function error(stream, state) {
if (options.strict) state.mark_as_error = true;
reset(state);
if (!stream.current()) stream.next();
return 'error';
}
function guess_mode(stream, state) {
var rem;
// Determine whether we should expect an IPv4 or IPv6 address based on
// the first encountered separator:
if (rem = stream.match(/^[^.:]*([.:])/, false)) {
reset(state);
state.current_mode = rem[1] === '.' ? IPV4 : IPV6;
}
}
function ipv6_accept_trailing_ipv4(state) {
// An IPv6 address has up to 8 blocks.
// A trailing IPv4 is 32 bits, i.e. 2 blocks.
// A double-colon represents at least 2 blocks.
// Consequently:
// if the current IP address features a double-colon *and*
// a trailing IPv4, it cannot have more than 4 blocks (8 - 2 - 2):
if (state.double_colon) return state.block_count <= 4;
// if the current IP address does not feature a double-colon but
// features a trailing IPv4, then it must have exactly 6 blocks (8 - 2):
return state.block_count === 6;
}
function ipv6_enough_bits(state) {
return state.double_colon ? true : (state.block_count === 8);
}
function ipv6_max_blocks(state) {
return state.double_colon ? 6 : 8;
}
// Handlers:
function handle_cidr_suffix(stream, state) {
if (state.range) return false;
var rem;
if (!state.cidr) {
if (stream.eat('/')) {
state.cidr = true;
return 'meta';
}
return false;
} else if (rem = stream.match(/^(\d+)/)) {
if (Number(rem[1]) <= (state.current_mode === IPV4 ? 32 : 128)) {
if (options.strict) state.mark_as_error = true;
reset(state);
return 'attribute';
}
}
return error(stream, state);
}
function handle_range(stream, state) {
if (!state.range && stream.eat('-')) {
var new_mode = (state.current_mode === IPV4) ? IPV4 : IPV6;
reset(state);
// Ensure the second address is the same type as the first one:
state.current_mode = new_mode;
state.range = true;
return 'attribute';
}
return false;
}
function handle_ipv4(stream, state) {
if (state.block_count === 4) {
var ret;
if (options.ipv4_cidr) {
if (ret = handle_cidr_suffix(stream, state)) return ret;
}
if (options.ipv4_ranges) {
if (ret = handle_range(stream, state)) return ret;
}
// All done:
reset(state);
if (options.strict) state.mark_as_error = true;
}
if (state.expect_dot && stream.match('.')) {
state.expect_dot = false;
return 'punctuation';
}
if (rem = stream.match(/^(\d+)/)) {
if (Number(rem[1]) < 256) {
++ state.block_count;
state.expect_dot = state.block_count < 4;
return 'number';
}
}
return error(stream, state);
}
function handle_ipv6(stream, state) {
if (ipv6_enough_bits(state)) {
var ret;
if (options.ipv6_cidr) {
if (ret = handle_cidr_suffix(stream, state)) return ret;
}
if (options.ipv6_ranges) {
if (ret = handle_range(stream, state)) return ret;
}
}
if (state.expect_digits) {
// Handle dual addresses:
if (stream.match(ipv4_re, false)) {
if (ipv6_accept_trailing_ipv4(state)) {
reset(state);
state.current_mode = DUAL;
return tokenBase(stream, state);
}
stream.match(ipv4_re);
return 'error';
}
else if (stream.match(/^([0-9a-fA-F]{1,4})/)) {
++ state.block_count;
state.expect_colon = state.block_count < ipv6_max_blocks(state);
state.expect_double_colon = !state.double_colon && state.block_count <= 6;
state.expect_digits = false;
return 'number';
}
}
if (state.expect_double_colon && stream.match('::')) {
state.double_colon = true; // mark the use of a double-colon
state.expect_colon = false;
state.expect_double_colon = false;
state.expect_digits = state.block_count < 6;
return 'punctuation';
}
if (state.expect_colon && stream.eat(':')) {
state.expect_colon = false;
state.expect_double_colon = false;
state.expect_digits = state.block_count < ipv6_max_blocks(state);
return 'punctuation';
}
return error(stream, state);
}
function tokenBase(stream, state) {
if (stream.sol() && !state.just_begun) { // 2nd line or more
if (options.strict) state.mark_as_error = true; // forbidden in strict mode
reset(state);
}
if (state.just_begun) delete state.just_begun;
if (state.mark_as_error) return error(stream, state);
if (state.cidr) return handle_cidr_suffix(stream, state);
if (state.current_mode === UNKNOWN) guess_mode(stream, state);
if (state.current_mode === IPV6) return handle_ipv6(stream, state);
if (state.current_mode & IPV4) return handle_ipv4(stream, state);
return error(stream, state);
}
function reset(state) {
state.current_mode = options.default_state;
state.block_count = 0;
state.range = state.cidr = false;
// IPv4-specific:
state.expect_dot = false;
// IPv6-specific:
state.expect_digits = state.expect_double_colon = true;
state.expect_colon = state.double_colon = false;
return state;
}
return {
startState: function() { return reset({just_begun: true}); },
token: tokenBase,
};
});
CodeMirror.defineMIME('text/x-ip-address', 'ipaddr');
CodeMirror.defineMIME('text/x-ipv4-address', 'ipaddr');
CodeMirror.defineMIME('text/x-ipv6-address', 'ipaddr');