diff options
Diffstat (limited to 'src/org/xbill/DNS/Tokenizer.java')
-rw-r--r-- | src/org/xbill/DNS/Tokenizer.java | 713 |
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(); +} + +} |