diff options
Diffstat (limited to 'src/org/xbill/DNS/Master.java')
-rw-r--r-- | src/org/xbill/DNS/Master.java | 427 |
1 files changed, 427 insertions, 0 deletions
diff --git a/src/org/xbill/DNS/Master.java b/src/org/xbill/DNS/Master.java new file mode 100644 index 0000000..c795a9c --- /dev/null +++ b/src/org/xbill/DNS/Master.java @@ -0,0 +1,427 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.io.*; +import java.util.*; + +/** + * 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 Record 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; + +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. + * @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); +} + +/** + * 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. + * @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. + * @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. + */ +public +Master(InputStream in) { + this(in, null, -1); +} + +private Name +parseName(String s, Name origin) throws TextParseException { + try { + return Name.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 = DClass.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 = DClass.value(s)) >= 0) { + s = st.getString(); + } else { + currentDClass = DClass.IN; + } + } + + if ((currentType = Type.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 != Type.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 " + + Type.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 Record +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 Record +_nextRecord() throws IOException { + Tokenizer.Token token; + String s; + + if (included != null) { + Record rec = included.nextRecord(); + if (rec != null) + return rec; + included = null; + } + if (generator != null) { + Record 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 = Record.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 Record +nextRecord() throws IOException { + Record 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(); +} + +protected void +finalize() { + st.close(); +} + +} |