NetworkDNS/src-wip/org/xbill/DNS2/clients/Master.java

479 lines
14 KiB
Java
Executable File

// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
package org.xbill.DNS2.clients;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import dorkbox.network.dns.Name;
import dorkbox.network.dns.constants.DnsClass;
import dorkbox.network.dns.constants.DnsRecordType;
import dorkbox.network.dns.exceptions.RelativeNameException;
import dorkbox.network.dns.exceptions.TextParseException;
import dorkbox.network.dns.records.DnsRecord;
import dorkbox.network.dns.records.SOARecord;
import dorkbox.network.dns.records.TTL;
import dorkbox.network.dns.utils.Tokenizer;
/**
* A DNS master file parser. This incrementally parses the file, returning
* one record at a time. When directives are seen, they are added to the
* state and used when parsing future records.
*
* @author Brian Wellington
*/
public
class Master {
private Name origin;
private File file;
private DnsRecord last = null;
private long defaultTTL;
private Master included = null;
private Tokenizer st;
private int currentType;
private int currentDClass;
private long currentTTL;
private boolean needSOATTL;
private Generator generator;
private List generators;
private boolean noExpandGenerate;
/**
* Initializes the master file reader and opens the specified master file.
*
* @param filename The master file.
* @param origin The initial origin to append to relative names.
* @param ttl The initial default TTL.
*
* @throws IOException The master file could not be opened.
*/
public
Master(String filename, Name origin, long ttl) throws IOException {
this(new File(filename), origin, ttl);
}
Master(File file, Name origin, long initialTTL) throws IOException {
if (origin != null && !origin.isAbsolute()) {
throw new RelativeNameException(origin);
}
this.file = file;
st = new Tokenizer(file);
this.origin = origin;
defaultTTL = initialTTL;
}
/**
* Initializes the master file reader and opens the specified master file.
*
* @param filename The master file.
* @param origin The initial origin to append to relative names.
*
* @throws IOException The master file could not be opened.
*/
public
Master(String filename, Name origin) throws IOException {
this(new File(filename), origin, -1);
}
/**
* Initializes the master file reader and opens the specified master file.
*
* @param filename The master file.
*
* @throws IOException The master file could not be opened.
*/
public
Master(String filename) throws IOException {
this(new File(filename), null, -1);
}
/**
* Initializes the master file reader.
*
* @param in The input stream containing a master file.
* @param origin The initial origin to append to relative names.
*/
public
Master(InputStream in, Name origin) {
this(in, origin, -1);
}
/**
* Initializes the master file reader.
*
* @param in The input stream containing a master file.
* @param origin The initial origin to append to relative names.
* @param ttl The initial default TTL.
*/
public
Master(InputStream in, Name origin, long ttl) {
if (origin != null && !origin.isAbsolute()) {
throw new RelativeNameException(origin);
}
st = new Tokenizer(in);
this.origin = origin;
defaultTTL = ttl;
}
/**
* Initializes the master file reader.
*
* @param in The input stream containing a master file.
*/
public
Master(InputStream in) {
this(in, null, -1);
}
private
Name parseName(String s, Name origin) throws TextParseException {
try {
return Name.Companion.fromString(s, origin);
} catch (TextParseException e) {
throw st.exception(e.getMessage());
}
}
private
void parseTTLClassAndType() throws IOException {
String s;
boolean seen_class = false;
// This is a bit messy, since any of the following are legal:
// class ttl type
// ttl class type
// class type
// ttl type
// type
seen_class = false;
s = st.getString();
if ((currentDClass = DnsClass.value(s)) >= 0) {
s = st.getString();
seen_class = true;
}
currentTTL = -1;
try {
currentTTL = TTL.parseTTL(s);
s = st.getString();
} catch (NumberFormatException e) {
if (defaultTTL >= 0) {
currentTTL = defaultTTL;
}
else if (last != null) {
currentTTL = last.getTTL();
}
}
if (!seen_class) {
if ((currentDClass = DnsClass.value(s)) >= 0) {
s = st.getString();
}
else {
currentDClass = DnsClass.IN;
}
}
if ((currentType = DnsRecordType.value(s)) < 0) {
throw st.exception("Invalid type '" + s + "'");
}
// BIND allows a missing TTL for the initial SOA record, and uses
// the SOA minimum value. If the SOA is not the first record,
// this is an error.
if (currentTTL < 0) {
if (currentType != DnsRecordType.SOA) {
throw st.exception("missing TTL");
}
needSOATTL = true;
currentTTL = 0;
}
}
private
long parseUInt32(String s) {
if (!Character.isDigit(s.charAt(0))) {
return -1;
}
try {
long l = Long.parseLong(s);
if (l < 0 || l > 0xFFFFFFFFL) {
return -1;
}
return l;
} catch (NumberFormatException e) {
return -1;
}
}
private
void startGenerate() throws IOException {
String s;
int n;
// The first field is of the form start-end[/step]
// Regexes would be useful here.
s = st.getIdentifier();
n = s.indexOf("-");
if (n < 0) {
throw st.exception("Invalid $GENERATE range specifier: " + s);
}
String startstr = s.substring(0, n);
String endstr = s.substring(n + 1);
String stepstr = null;
n = endstr.indexOf("/");
if (n >= 0) {
stepstr = endstr.substring(n + 1);
endstr = endstr.substring(0, n);
}
long start = parseUInt32(startstr);
long end = parseUInt32(endstr);
long step;
if (stepstr != null) {
step = parseUInt32(stepstr);
}
else {
step = 1;
}
if (start < 0 || end < 0 || start > end || step <= 0) {
throw st.exception("Invalid $GENERATE range specifier: " + s);
}
// The next field is the name specification.
String nameSpec = st.getIdentifier();
// Then the ttl/class/type, in the same form as a normal record.
// Only some types are supported.
parseTTLClassAndType();
if (!Generator.supportedType(currentType)) {
throw st.exception("$GENERATE does not support " + DnsRecordType.string(currentType) + " records");
}
// Next comes the rdata specification.
String rdataSpec = st.getIdentifier();
// That should be the end. However, we don't want to move past the
// line yet, so put back the EOL after reading it.
st.getEOL();
st.unget();
generator = new Generator(start, end, step, nameSpec, currentType, currentDClass, currentTTL, rdataSpec, origin);
if (generators == null) {
generators = new ArrayList(1);
}
generators.add(generator);
}
private
void endGenerate() throws IOException {
// Read the EOL that we put back before.
st.getEOL();
generator = null;
}
private
DnsRecord nextGenerated() throws IOException {
try {
return generator.nextRecord();
} catch (Tokenizer.TokenizerException e) {
throw st.exception("Parsing $GENERATE: " + e.getBaseMessage());
} catch (TextParseException e) {
throw st.exception("Parsing $GENERATE: " + e.getMessage());
}
}
/**
* Returns the next record in the master file. This will process any
* directives before the next record.
*
* @return The next record.
*
* @throws IOException The master file could not be read, or was syntactically
* invalid.
*/
public
DnsRecord _nextRecord() throws IOException {
Tokenizer.Token token;
String s;
if (included != null) {
DnsRecord rec = included.nextRecord();
if (rec != null) {
return rec;
}
included = null;
}
if (generator != null) {
DnsRecord rec = nextGenerated();
if (rec != null) {
return rec;
}
endGenerate();
}
while (true) {
Name name;
token = st.get(true, false);
if (token.type == Tokenizer.WHITESPACE) {
Tokenizer.Token next = st.get();
if (next.type == Tokenizer.EOL) {
continue;
}
else if (next.type == Tokenizer.EOF) {
return null;
}
else {
st.unget();
}
if (last == null) {
throw st.exception("no owner");
}
name = last.getName();
}
else if (token.type == Tokenizer.EOL) {
continue;
}
else if (token.type == Tokenizer.EOF) {
return null;
}
else if (((String) token.value).charAt(0) == '$') {
s = token.value;
if (s.equalsIgnoreCase("$ORIGIN")) {
origin = st.getName(Name.root);
st.getEOL();
continue;
}
else if (s.equalsIgnoreCase("$TTL")) {
defaultTTL = st.getTTL();
st.getEOL();
continue;
}
else if (s.equalsIgnoreCase("$INCLUDE")) {
String filename = st.getString();
File newfile;
if (file != null) {
String parent = file.getParent();
newfile = new File(parent, filename);
}
else {
newfile = new File(filename);
}
Name incorigin = origin;
token = st.get();
if (token.isString()) {
incorigin = parseName(token.value, Name.root);
st.getEOL();
}
included = new Master(newfile, incorigin, defaultTTL);
/*
* If we continued, we wouldn't be looking in
* the new file. Recursing works better.
*/
return nextRecord();
}
else if (s.equalsIgnoreCase("$GENERATE")) {
if (generator != null) {
throw new IllegalStateException("cannot nest $GENERATE");
}
startGenerate();
if (noExpandGenerate) {
endGenerate();
continue;
}
return nextGenerated();
}
else {
throw st.exception("Invalid directive: " + s);
}
}
else {
s = token.value;
name = parseName(s, origin);
if (last != null && name.equals(last.getName())) {
name = last.getName();
}
}
parseTTLClassAndType();
last = DnsRecord.fromString(name, currentType, currentDClass, currentTTL, st, origin);
if (needSOATTL) {
long ttl = ((SOARecord) last).getMinimum();
last.setTTL(ttl);
defaultTTL = ttl;
needSOATTL = false;
}
return last;
}
}
/**
* Returns the next record in the master file. This will process any
* directives before the next record.
*
* @return The next record.
*
* @throws IOException The master file could not be read, or was syntactically
* invalid.
*/
public
DnsRecord nextRecord() throws IOException {
DnsRecord rec = null;
try {
rec = _nextRecord();
} finally {
if (rec == null) {
st.close();
}
}
return rec;
}
/**
* Specifies whether $GENERATE statements should be expanded. Whether
* expanded or not, the specifications for generated records are available
* by calling {@link #generators}. This must be called before a $GENERATE
* statement is seen during iteration to have an effect.
*/
public
void expandGenerate(boolean wantExpand) {
noExpandGenerate = !wantExpand;
}
/**
* Returns an iterator over the generators specified in the master file; that
* is, the parsed contents of $GENERATE statements.
*
* @see Generator
*/
public
Iterator generators() {
if (generators != null) {
return Collections.unmodifiableList(generators)
.iterator();
}
else {
return Collections.EMPTY_LIST.iterator();
}
}
@Override
protected
void finalize() {
if (st != null) {
st.close();
}
}
}