aboutsummaryrefslogtreecommitdiff
path: root/src/org/xbill/DNS/Tokenizer.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/org/xbill/DNS/Tokenizer.java')
-rw-r--r--src/org/xbill/DNS/Tokenizer.java713
1 files changed, 713 insertions, 0 deletions
diff --git a/src/org/xbill/DNS/Tokenizer.java b/src/org/xbill/DNS/Tokenizer.java
new file mode 100644
index 0000000..bc637ab
--- /dev/null
+++ b/src/org/xbill/DNS/Tokenizer.java
@@ -0,0 +1,713 @@
+// Copyright (c) 2003-2004 Brian Wellington (bwelling@xbill.org)
+//
+// Copyright (C) 2003-2004 Nominum, Inc.
+//
+// Permission to use, copy, modify, and distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR ANY
+// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+// OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+//
+
+package org.xbill.DNS;
+
+import java.io.*;
+import java.net.*;
+
+import org.xbill.DNS.utils.*;
+
+/**
+ * Tokenizer is used to parse DNS records and zones from text format,
+ *
+ * @author Brian Wellington
+ * @author Bob Halley
+ */
+
+public class Tokenizer {
+
+private static String delim = " \t\n;()\"";
+private static String quotes = "\"";
+
+/** End of file */
+public static final int EOF = 0;
+
+/** End of line */
+public static final int EOL = 1;
+
+/** Whitespace; only returned when wantWhitespace is set */
+public static final int WHITESPACE = 2;
+
+/** An identifier (unquoted string) */
+public static final int IDENTIFIER = 3;
+
+/** A quoted string */
+public static final int QUOTED_STRING = 4;
+
+/** A comment; only returned when wantComment is set */
+public static final int COMMENT = 5;
+
+private PushbackInputStream is;
+private boolean ungottenToken;
+private int multiline;
+private boolean quoting;
+private String delimiters;
+private Token current;
+private StringBuffer sb;
+private boolean wantClose;
+
+private String filename;
+private int line;
+
+public static class Token {
+ /** The type of token. */
+ public int type;
+
+ /** The value of the token, or null for tokens without values. */
+ public String value;
+
+ private
+ Token() {
+ type = -1;
+ value = null;
+ }
+
+ private Token
+ set(int type, StringBuffer value) {
+ if (type < 0)
+ throw new IllegalArgumentException();
+ this.type = type;
+ this.value = value == null ? null : value.toString();
+ return this;
+ }
+
+ /**
+ * Converts the token to a string containing a representation useful
+ * for debugging.
+ */
+ public String
+ toString() {
+ switch (type) {
+ case EOF:
+ return "<eof>";
+ case EOL:
+ return "<eol>";
+ case WHITESPACE:
+ return "<whitespace>";
+ case IDENTIFIER:
+ return "<identifier: " + value + ">";
+ case QUOTED_STRING:
+ return "<quoted_string: " + value + ">";
+ case COMMENT:
+ return "<comment: " + value + ">";
+ default:
+ return "<unknown>";
+ }
+ }
+
+ /** Indicates whether this token contains a string. */
+ public boolean
+ isString() {
+ return (type == IDENTIFIER || type == QUOTED_STRING);
+ }
+
+ /** Indicates whether this token contains an EOL or EOF. */
+ public boolean
+ isEOL() {
+ return (type == EOL || type == EOF);
+ }
+}
+
+static class TokenizerException extends TextParseException {
+ String message;
+
+ public
+ TokenizerException(String filename, int line, String message) {
+ super(filename + ":" + line + ": " + message);
+ this.message = message;
+ }
+
+ public String
+ getBaseMessage() {
+ return message;
+ }
+}
+
+/**
+ * Creates a Tokenizer from an arbitrary input stream.
+ * @param is The InputStream to tokenize.
+ */
+public
+Tokenizer(InputStream is) {
+ if (!(is instanceof BufferedInputStream))
+ is = new BufferedInputStream(is);
+ this.is = new PushbackInputStream(is, 2);
+ ungottenToken = false;
+ multiline = 0;
+ quoting = false;
+ delimiters = delim;
+ current = new Token();
+ sb = new StringBuffer();
+ filename = "<none>";
+ line = 1;
+}
+
+/**
+ * Creates a Tokenizer from a string.
+ * @param s The String to tokenize.
+ */
+public
+Tokenizer(String s) {
+ this(new ByteArrayInputStream(s.getBytes()));
+}
+
+/**
+ * Creates a Tokenizer from a file.
+ * @param f The File to tokenize.
+ */
+public
+Tokenizer(File f) throws FileNotFoundException {
+ this(new FileInputStream(f));
+ wantClose = true;
+ filename = f.getName();
+}
+
+private int
+getChar() throws IOException {
+ int c = is.read();
+ if (c == '\r') {
+ int next = is.read();
+ if (next != '\n')
+ is.unread(next);
+ c = '\n';
+ }
+ if (c == '\n')
+ line++;
+ return c;
+}
+
+private void
+ungetChar(int c) throws IOException {
+ if (c == -1)
+ return;
+ is.unread(c);
+ if (c == '\n')
+ line--;
+}
+
+private int
+skipWhitespace() throws IOException {
+ int skipped = 0;
+ while (true) {
+ int c = getChar();
+ if (c != ' ' && c != '\t') {
+ if (!(c == '\n' && multiline > 0)) {
+ ungetChar(c);
+ return skipped;
+ }
+ }
+ skipped++;
+ }
+}
+
+private void
+checkUnbalancedParens() throws TextParseException {
+ if (multiline > 0)
+ throw exception("unbalanced parentheses");
+}
+
+/**
+ * Gets the next token from a tokenizer.
+ * @param wantWhitespace If true, leading whitespace will be returned as a
+ * token.
+ * @param wantComment If true, comments are returned as tokens.
+ * @return The next token in the stream.
+ * @throws TextParseException The input was invalid.
+ * @throws IOException An I/O error occurred.
+ */
+public Token
+get(boolean wantWhitespace, boolean wantComment) throws IOException {
+ int type;
+ int c;
+
+ if (ungottenToken) {
+ ungottenToken = false;
+ if (current.type == WHITESPACE) {
+ if (wantWhitespace)
+ return current;
+ } else if (current.type == COMMENT) {
+ if (wantComment)
+ return current;
+ } else {
+ if (current.type == EOL)
+ line++;
+ return current;
+ }
+ }
+ int skipped = skipWhitespace();
+ if (skipped > 0 && wantWhitespace)
+ return current.set(WHITESPACE, null);
+ type = IDENTIFIER;
+ sb.setLength(0);
+ while (true) {
+ c = getChar();
+ if (c == -1 || delimiters.indexOf(c) != -1) {
+ if (c == -1) {
+ if (quoting)
+ throw exception("EOF in " +
+ "quoted string");
+ else if (sb.length() == 0)
+ return current.set(EOF, null);
+ else
+ return current.set(type, sb);
+ }
+ if (sb.length() == 0 && type != QUOTED_STRING) {
+ if (c == '(') {
+ multiline++;
+ skipWhitespace();
+ continue;
+ } else if (c == ')') {
+ if (multiline <= 0)
+ throw exception("invalid " +
+ "close " +
+ "parenthesis");
+ multiline--;
+ skipWhitespace();
+ continue;
+ } else if (c == '"') {
+ if (!quoting) {
+ quoting = true;
+ delimiters = quotes;
+ type = QUOTED_STRING;
+ } else {
+ quoting = false;
+ delimiters = delim;
+ skipWhitespace();
+ }
+ continue;
+ } else if (c == '\n') {
+ return current.set(EOL, null);
+ } else if (c == ';') {
+ while (true) {
+ c = getChar();
+ if (c == '\n' || c == -1)
+ break;
+ sb.append((char)c);
+ }
+ if (wantComment) {
+ ungetChar(c);
+ return current.set(COMMENT, sb);
+ } else if (c == -1 &&
+ type != QUOTED_STRING)
+ {
+ checkUnbalancedParens();
+ return current.set(EOF, null);
+ } else if (multiline > 0) {
+ skipWhitespace();
+ sb.setLength(0);
+ continue;
+ } else
+ return current.set(EOL, null);
+ } else
+ throw new IllegalStateException();
+ } else
+ ungetChar(c);
+ break;
+ } else if (c == '\\') {
+ c = getChar();
+ if (c == -1)
+ throw exception("unterminated escape sequence");
+ sb.append('\\');
+ } else if (quoting && c == '\n') {
+ throw exception("newline in quoted string");
+ }
+ sb.append((char)c);
+ }
+ if (sb.length() == 0 && type != QUOTED_STRING) {
+ checkUnbalancedParens();
+ return current.set(EOF, null);
+ }
+ return current.set(type, sb);
+}
+
+/**
+ * Gets the next token from a tokenizer, ignoring whitespace and comments.
+ * @return The next token in the stream.
+ * @throws TextParseException The input was invalid.
+ * @throws IOException An I/O error occurred.
+ */
+public Token
+get() throws IOException {
+ return get(false, false);
+}
+
+/**
+ * Returns a token to the stream, so that it will be returned by the next call
+ * to get().
+ * @throws IllegalStateException There are already ungotten tokens.
+ */
+public void
+unget() {
+ if (ungottenToken)
+ throw new IllegalStateException
+ ("Cannot unget multiple tokens");
+ if (current.type == EOL)
+ line--;
+ ungottenToken = true;
+}
+
+/**
+ * Gets the next token from a tokenizer and converts it to a string.
+ * @return The next token in the stream, as a string.
+ * @throws TextParseException The input was invalid or not a string.
+ * @throws IOException An I/O error occurred.
+ */
+public String
+getString() throws IOException {
+ Token next = get();
+ if (!next.isString()) {
+ throw exception("expected a string");
+ }
+ return next.value;
+}
+
+private String
+_getIdentifier(String expected) throws IOException {
+ Token next = get();
+ if (next.type != IDENTIFIER)
+ throw exception("expected " + expected);
+ return next.value;
+}
+
+/**
+ * Gets the next token from a tokenizer, ensures it is an unquoted string,
+ * and converts it to a string.
+ * @return The next token in the stream, as a string.
+ * @throws TextParseException The input was invalid or not an unquoted string.
+ * @throws IOException An I/O error occurred.
+ */
+public String
+getIdentifier() throws IOException {
+ return _getIdentifier("an identifier");
+}
+
+/**
+ * Gets the next token from a tokenizer and converts it to a long.
+ * @return The next token in the stream, as a long.
+ * @throws TextParseException The input was invalid or not a long.
+ * @throws IOException An I/O error occurred.
+ */
+public long
+getLong() throws IOException {
+ String next = _getIdentifier("an integer");
+ if (!Character.isDigit(next.charAt(0)))
+ throw exception("expected an integer");
+ try {
+ return Long.parseLong(next);
+ } catch (NumberFormatException e) {
+ throw exception("expected an integer");
+ }
+}
+
+/**
+ * Gets the next token from a tokenizer and converts it to an unsigned 32 bit
+ * integer.
+ * @return The next token in the stream, as an unsigned 32 bit integer.
+ * @throws TextParseException The input was invalid or not an unsigned 32
+ * bit integer.
+ * @throws IOException An I/O error occurred.
+ */
+public long
+getUInt32() throws IOException {
+ long l = getLong();
+ if (l < 0 || l > 0xFFFFFFFFL)
+ throw exception("expected an 32 bit unsigned integer");
+ return l;
+}
+
+/**
+ * Gets the next token from a tokenizer and converts it to an unsigned 16 bit
+ * integer.
+ * @return The next token in the stream, as an unsigned 16 bit integer.
+ * @throws TextParseException The input was invalid or not an unsigned 16
+ * bit integer.
+ * @throws IOException An I/O error occurred.
+ */
+public int
+getUInt16() throws IOException {
+ long l = getLong();
+ if (l < 0 || l > 0xFFFFL)
+ throw exception("expected an 16 bit unsigned integer");
+ return (int) l;
+}
+
+/**
+ * Gets the next token from a tokenizer and converts it to an unsigned 8 bit
+ * integer.
+ * @return The next token in the stream, as an unsigned 8 bit integer.
+ * @throws TextParseException The input was invalid or not an unsigned 8
+ * bit integer.
+ * @throws IOException An I/O error occurred.
+ */
+public int
+getUInt8() throws IOException {
+ long l = getLong();
+ if (l < 0 || l > 0xFFL)
+ throw exception("expected an 8 bit unsigned integer");
+ return (int) l;
+}
+
+/**
+ * Gets the next token from a tokenizer and parses it as a TTL.
+ * @return The next token in the stream, as an unsigned 32 bit integer.
+ * @throws TextParseException The input was not valid.
+ * @throws IOException An I/O error occurred.
+ * @see TTL
+ */
+public long
+getTTL() throws IOException {
+ String next = _getIdentifier("a TTL value");
+ try {
+ return TTL.parseTTL(next);
+ }
+ catch (NumberFormatException e) {
+ throw exception("expected a TTL value");
+ }
+}
+
+/**
+ * Gets the next token from a tokenizer and parses it as if it were a TTL.
+ * @return The next token in the stream, as an unsigned 32 bit integer.
+ * @throws TextParseException The input was not valid.
+ * @throws IOException An I/O error occurred.
+ * @see TTL
+ */
+public long
+getTTLLike() throws IOException {
+ String next = _getIdentifier("a TTL-like value");
+ try {
+ return TTL.parse(next, false);
+ }
+ catch (NumberFormatException e) {
+ throw exception("expected a TTL-like value");
+ }
+}
+
+/**
+ * Gets the next token from a tokenizer and converts it to a name.
+ * @param origin The origin to append to relative names.
+ * @return The next token in the stream, as a name.
+ * @throws TextParseException The input was invalid or not a valid name.
+ * @throws IOException An I/O error occurred.
+ * @throws RelativeNameException The parsed name was relative, even with the
+ * origin.
+ * @see Name
+ */
+public Name
+getName(Name origin) throws IOException {
+ String next = _getIdentifier("a name");
+ try {
+ Name name = Name.fromString(next, origin);
+ if (!name.isAbsolute())
+ throw new RelativeNameException(name);
+ return name;
+ }
+ catch (TextParseException e) {
+ throw exception(e.getMessage());
+ }
+}
+
+/**
+ * Gets the next token from a tokenizer and converts it to an IP Address.
+ * @param family The address family.
+ * @return The next token in the stream, as an InetAddress
+ * @throws TextParseException The input was invalid or not a valid address.
+ * @throws IOException An I/O error occurred.
+ * @see Address
+ */
+public InetAddress
+getAddress(int family) throws IOException {
+ String next = _getIdentifier("an address");
+ try {
+ return Address.getByAddress(next, family);
+ }
+ catch (UnknownHostException e) {
+ throw exception(e.getMessage());
+ }
+}
+
+/**
+ * Gets the next token from a tokenizer, which must be an EOL or EOF.
+ * @throws TextParseException The input was invalid or not an EOL or EOF token.
+ * @throws IOException An I/O error occurred.
+ */
+public void
+getEOL() throws IOException {
+ Token next = get();
+ if (next.type != EOL && next.type != EOF) {
+ throw exception("expected EOL or EOF");
+ }
+}
+
+/**
+ * Returns a concatenation of the remaining strings from a Tokenizer.
+ */
+private String
+remainingStrings() throws IOException {
+ StringBuffer buffer = null;
+ while (true) {
+ Tokenizer.Token t = get();
+ if (!t.isString())
+ break;
+ if (buffer == null)
+ buffer = new StringBuffer();
+ buffer.append(t.value);
+ }
+ unget();
+ if (buffer == null)
+ return null;
+ return buffer.toString();
+}
+
+/**
+ * Gets the remaining string tokens until an EOL/EOF is seen, concatenates
+ * them together, and converts the base64 encoded data to a byte array.
+ * @param required If true, an exception will be thrown if no strings remain;
+ * otherwise null be be returned.
+ * @return The byte array containing the decoded strings, or null if there
+ * were no strings to decode.
+ * @throws TextParseException The input was invalid.
+ * @throws IOException An I/O error occurred.
+ */
+public byte []
+getBase64(boolean required) throws IOException {
+ String s = remainingStrings();
+ if (s == null) {
+ if (required)
+ throw exception("expected base64 encoded string");
+ else
+ return null;
+ }
+ byte [] array = base64.fromString(s);
+ if (array == null)
+ throw exception("invalid base64 encoding");
+ return array;
+}
+
+/**
+ * Gets the remaining string tokens until an EOL/EOF is seen, concatenates
+ * them together, and converts the base64 encoded data to a byte array.
+ * @return The byte array containing the decoded strings, or null if there
+ * were no strings to decode.
+ * @throws TextParseException The input was invalid.
+ * @throws IOException An I/O error occurred.
+ */
+public byte []
+getBase64() throws IOException {
+ return getBase64(false);
+}
+
+/**
+ * Gets the remaining string tokens until an EOL/EOF is seen, concatenates
+ * them together, and converts the hex encoded data to a byte array.
+ * @param required If true, an exception will be thrown if no strings remain;
+ * otherwise null be be returned.
+ * @return The byte array containing the decoded strings, or null if there
+ * were no strings to decode.
+ * @throws TextParseException The input was invalid.
+ * @throws IOException An I/O error occurred.
+ */
+public byte []
+getHex(boolean required) throws IOException {
+ String s = remainingStrings();
+ if (s == null) {
+ if (required)
+ throw exception("expected hex encoded string");
+ else
+ return null;
+ }
+ byte [] array = base16.fromString(s);
+ if (array == null)
+ throw exception("invalid hex encoding");
+ return array;
+}
+
+/**
+ * Gets the remaining string tokens until an EOL/EOF is seen, concatenates
+ * them together, and converts the hex encoded data to a byte array.
+ * @return The byte array containing the decoded strings, or null if there
+ * were no strings to decode.
+ * @throws TextParseException The input was invalid.
+ * @throws IOException An I/O error occurred.
+ */
+public byte []
+getHex() throws IOException {
+ return getHex(false);
+}
+
+/**
+ * Gets the next token from a tokenizer and decodes it as hex.
+ * @return The byte array containing the decoded string.
+ * @throws TextParseException The input was invalid.
+ * @throws IOException An I/O error occurred.
+ */
+public byte []
+getHexString() throws IOException {
+ String next = _getIdentifier("a hex string");
+ byte [] array = base16.fromString(next);
+ if (array == null)
+ throw exception("invalid hex encoding");
+ return array;
+}
+
+/**
+ * Gets the next token from a tokenizer and decodes it as base32.
+ * @param b32 The base32 context to decode with.
+ * @return The byte array containing the decoded string.
+ * @throws TextParseException The input was invalid.
+ * @throws IOException An I/O error occurred.
+ */
+public byte []
+getBase32String(base32 b32) throws IOException {
+ String next = _getIdentifier("a base32 string");
+ byte [] array = b32.fromString(next);
+ if (array == null)
+ throw exception("invalid base32 encoding");
+ return array;
+}
+
+/**
+ * Creates an exception which includes the current state in the error message
+ * @param s The error message to include.
+ * @return The exception to be thrown
+ */
+public TextParseException
+exception(String s) {
+ return new TokenizerException(filename, line, s);
+}
+
+/**
+ * Closes any files opened by this tokenizer.
+ */
+public void
+close() {
+ if (wantClose) {
+ try {
+ is.close();
+ }
+ catch (IOException e) {
+ }
+ }
+}
+
+protected void
+finalize() {
+ close();
+}
+
+}