amazon-route-53-dns-zone-file
Version:
Makes DNS Zone File easy. Parses and validates BIND zone files and can be extended for custom features. Functionality is modular. Features are made open for extension and closed for runtime mutation. Written in TypeScript.
159 lines (140 loc) • 4.17 kB
text/typescript
import {
trimDot,
withDot,
newLinesIntoSpaces,
splitByNewLine,
UNIX_NEW_LINE_CHAR,
} from '../shared/utils_generic';
import { ParsedTypings } from '../shared/types_domain_specific';
import { ParserTypings, ParsingRecordDataType } from './types_parser';
/**
* @perf use collections, benchmark
* @note we do not validate invalid quotes (missing close)
*/
export const splitArgs = (input: string, sep?: RegExp | string): string[] => {
const separator = sep || /\s/g;
let isSingleQuoteOpen = false;
let isDoubleQuoteOpen = false;
let tokenBuffer = [];
const tokensFromArgs = [];
const characters = input.split('');
for (const element of characters) {
const matches = element.match(separator);
if (element === "'" && !isDoubleQuoteOpen) {
tokenBuffer.push(element);
isSingleQuoteOpen = !isSingleQuoteOpen;
continue;
} else if (element === '"' && !isSingleQuoteOpen) {
tokenBuffer.push(element);
isDoubleQuoteOpen = !isDoubleQuoteOpen;
continue;
}
if (!isSingleQuoteOpen && !isDoubleQuoteOpen && matches) {
if (tokenBuffer.length > 0) {
tokensFromArgs.push(tokenBuffer.join(''));
tokenBuffer = [];
} else if (!!sep) {
tokensFromArgs.push(element);
}
} else {
tokenBuffer.push(element);
}
}
if (tokenBuffer.length > 0) {
tokensFromArgs.push(tokenBuffer.join(''));
} else if (!!sep) {
tokensFromArgs.push('');
}
return tokensFromArgs;
};
/** @note not checking invalid format of nested parens */
export const flattenStartOfAuthority = (input: string): string => {
let isParenOpen = false;
let isInSoa = false;
let soa = '';
let flattened = '';
const addSoa = () => {
flattened +=
newLinesIntoSpaces(soa).replace(/\s+/gm, ' ') + UNIX_NEW_LINE_CHAR;
soa = '';
isInSoa = false;
};
for (let index = 0; index < input.length; ++index) {
const element = input[index];
if (
element === 'S' &&
input[index + 1] === 'O' &&
input[index + 2] === 'A'
) {
isInSoa = true;
soa = '';
}
if (!isInSoa) {
flattened += element;
} else {
if (element === '(' && !isParenOpen) {
isParenOpen = true;
} else if (element === ')' && isParenOpen) {
addSoa();
isParenOpen = false;
} else if (element === UNIX_NEW_LINE_CHAR && !isParenOpen) {
addSoa();
} else {
soa += element;
}
}
}
return flattened;
};
export const removeComments = (text: string): string => {
const matchComment = /\\"|"(?:\\"|[^"])*"|((^|[^\\])\;.*)/g;
return text.replace(matchComment, (match: string, g1: unknown) => {
// if g1 is not set/matched, re-insert it, else remove
return !g1 ? match : '';
});
};
export const transformInputText = (text: string): [string, string[]] => {
const withoutComments = removeComments(text);
const flattened = flattenStartOfAuthority(withoutComments);
const lines = splitByNewLine(flattened).filter(x => x.trim() !== '');
return [flattened, lines];
};
export type ZipMapperFnType<T extends {}> = (
value: T & Omit<ParsingRecordDataType, 'tokens'>,
parser: ParserTypings.Parser
) => T;
export type ZipObjFnType<T extends {}> = (
x: ParsingRecordDataType,
parser: ParserTypings.Parser
) => T;
export const zipArrayToObj = <T extends {}>(
template: string,
mapper?: ZipMapperFnType<T>
): ZipObjFnType<T> => (recordData, parser) => {
const { tokens, ...rest } = recordData;
const obj = { ...rest } as any;
template.split(' ').forEach((key, index) => {
obj[key] = tokens[index];
});
return (mapper ? mapper(obj as any, parser) : obj) as T;
};
export const getRecordNameUsingOrigin = (
name: string,
zoneObj: ParsedTypings.Obj
): string => {
const $origin = zoneObj.$ORIGIN;
if (
!name ||
name === '@' ||
name === $origin ||
trimDot(name) === trimDot($origin)
) {
return withDot($origin);
} else if (!name.includes($origin) && name.endsWith('.')) {
return name + $origin;
} else if (!name.includes($origin)) {
return name + '.' + $origin;
} else {
return name;
}
};