blob: 326d48c133f4fae7152b45e19a9603f1b096388d [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.qpid.server.user.connection.limits.config;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StreamTokenizer;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;
import java.util.ArrayDeque;
import java.util.Iterator;
import java.util.Locale;
import java.util.Queue;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.qpid.server.configuration.IllegalConfigurationException;
public class FileParser
{
private static final Logger LOGGER = LoggerFactory.getLogger(FileParser.class);
private static final Character COMMENT = '#';
private static final Character CONTINUATION = '\\';
private static final Pattern NUMBER = Pattern.compile("\\s*(\\d+)\\s*");
private static final String ACCESS_CONTROL = "acl";
public static final String CONNECTION_LIMIT = "clt";
private static final String CONFIG = "config";
public static final String DEFAULT_FREQUENCY_PERIOD = "default_frequency_period";
public static final String DEFAULT_FREQUENCY_PERIOD_ALTERNATIVE = "defaultfrequencyperiod";
private static final String LOG_ALL = "log_all";
private static final String LOG_ALL_ALTERNATIVE = "logall";
public static final String BLOCK = "BLOCK";
static final String UNRECOGNISED_INITIAL_TOKEN = "Unrecognised initial token '%s' at line %d";
static final String NOT_ENOUGH_TOKENS = "Not enough tokens at line %d";
static final String NUMBER_NOT_ALLOWED = "Number not allowed before '%s' at line %d";
static final String CANNOT_LOAD_CONFIGURATION = "I/O Error while reading configuration";
static final String PREMATURE_CONTINUATION = "Premature continuation character at line %d";
static final String PARSE_TOKEN_FAILED = "Failed to parse token at line %d";
static final String UNKNOWN_CLT_PROPERTY_MSG = "Unknown connection limit property: %s at line %d";
static final String PROPERTY_KEY_ONLY_MSG = "Incomplete property (key only) at line %d";
static final String PROPERTY_NO_EQUALS_MSG = "Incomplete property (no equals) at line %d";
static final String PROPERTY_NO_VALUE_MSG = "Incomplete property (no value) at line %d";
public static RuleSetCreator parse(String name)
{
return new FileParser().readAndParse(name);
}
public RuleSetCreator readAndParse(String name)
{
return readAndParse(getReaderFromURLString(name));
}
RuleSetCreator readAndParse(Reader reader)
{
final RuleSetCreator ruleSetCreator = new RuleSetCreator();
int line = 0;
try (final Reader fileReader = new BufferedReader(reader))
{
LOGGER.debug("About to load connection limit file");
final StreamTokenizer tokenizer = new StreamTokenizer(fileReader);
tokenizer.resetSyntax(); // setup the tokenizer
tokenizer.commentChar(COMMENT); // single line comments
tokenizer.eolIsSignificant(true); // return EOL as a token
tokenizer.ordinaryChar('='); // equals is a token
tokenizer.ordinaryChar(CONTINUATION); // continuation character (when followed by EOL)
tokenizer.quoteChar('"'); // double quote
tokenizer.quoteChar('\''); // single quote
tokenizer.whitespaceChars('\u0000', '\u0020');
tokenizer.wordChars('a', 'z'); // unquoted token characters [a-z]
tokenizer.wordChars('A', 'Z'); // [A-Z]
tokenizer.wordChars('0', '9'); // [0-9]
tokenizer.wordChars('_', '_'); // underscore
tokenizer.wordChars('-', '-'); // dash
tokenizer.wordChars('.', '.'); // dot
tokenizer.wordChars('*', '*'); // star
tokenizer.wordChars('@', '@'); // at
tokenizer.wordChars(':', ':'); // colon
tokenizer.wordChars('+', '+'); // plus
tokenizer.wordChars('/', '/');
final Queue<String> stack = new ArrayDeque<>();
int current;
do
{
current = tokenizer.nextToken();
line = tokenizer.lineno() - 1;
switch (current)
{
case StreamTokenizer.TT_EOF:
case StreamTokenizer.TT_EOL:
processLine(ruleSetCreator, line, stack);
break;
case StreamTokenizer.TT_WORD:
addLast(stack, tokenizer.sval);
break;
default:
parseToken(tokenizer, stack);
}
}
while (current != StreamTokenizer.TT_EOF);
}
catch (IllegalConfigurationException ice)
{
throw ice;
}
catch (IOException ioe)
{
throw new IllegalConfigurationException(CANNOT_LOAD_CONFIGURATION, ioe);
}
catch (RuntimeException re)
{
throw new IllegalConfigurationException(String.format(PARSE_TOKEN_FAILED, line), re);
}
return ruleSetCreator;
}
private void processLine(RuleSetCreator ruleSetCreator, int line, Queue<String> stack)
{
if (stack.isEmpty())
{
return; // blank line
}
final String first = stack.poll();
if (first == null || stack.isEmpty())
{
throw new IllegalConfigurationException(String.format(NOT_ENOUGH_TOKENS, line));
}
final Matcher matcher = NUMBER.matcher(first);
if (matcher.matches())
{
final String commandType = stack.poll();
if (ACCESS_CONTROL.equalsIgnoreCase(commandType))
{
parseAccessControl(stack, ruleSetCreator, line);
stack.clear();
return;
}
throw new IllegalConfigurationException(String.format(NUMBER_NOT_ALLOWED, commandType, line));
}
if (CONNECTION_LIMIT.equalsIgnoreCase(first))
{
parseConnectionLimit(stack, ruleSetCreator, line);
}
else if (CONFIG.equalsIgnoreCase(first))
{
parseConfig(stack, ruleSetCreator, line);
}
else if (ACCESS_CONTROL.equalsIgnoreCase(first))
{
parseAccessControl(stack, ruleSetCreator, line);
}
else
{
throw new IllegalConfigurationException(String.format(UNRECOGNISED_INITIAL_TOKEN, first, line));
}
stack.clear();
}
private void parseToken(StreamTokenizer tokenizer, Queue<String> stack) throws IOException
{
if (tokenizer.ttype == CONTINUATION)
{
if (tokenizer.nextToken() != StreamTokenizer.TT_EOL)
{
// invalid location for continuation character (add one to line because we ate the EOL)
throw new IllegalConfigurationException(String.format(PREMATURE_CONTINUATION, tokenizer.lineno()));
}
}
else if (tokenizer.ttype == '\'' || tokenizer.ttype == '"')
{
addLast(stack, tokenizer.sval);
}
else if (!Character.isWhitespace(tokenizer.ttype))
{
addLast(stack, Character.toString((char) tokenizer.ttype));
}
}
private void addLast(Queue<String> queue, String value)
{
if (value != null)
{
queue.add(value);
}
}
private void parseConnectionLimit(Queue<String> args, final RuleSetCreator ruleSetCreator, final int line)
{
final String identity = args.poll();
final RulePredicates predicates = new RulePredicates();
final Iterator<String> i = args.iterator();
while (i.hasNext())
{
final String key = i.next();
if (BLOCK.equalsIgnoreCase(key))
{
predicates.setBlockedUser();
}
else
{
if (predicates.parse(key, readValue(i, line)) == null)
{
throw new IllegalConfigurationException(String.format(UNKNOWN_CLT_PROPERTY_MSG, key, line));
}
}
}
if (!predicates.isEmpty())
{
ruleSetCreator.add(Rule.newInstance(identity, predicates));
}
}
private void parseAccessControl(Queue<String> args, final RuleSetCreator ruleSetCreator, final int line)
{
if (args.size() < 4)
{
return;
}
// poll outcome
args.poll();
final String identity = args.poll();
// poll operation
args.poll();
// poll object
args.poll();
final RulePredicates predicates = new RulePredicates();
final Iterator<String> i = args.iterator();
while (i.hasNext())
{
predicates.parse(i.next(), readValue(i, line));
}
if (!predicates.isEmpty())
{
ruleSetCreator.add(Rule.newInstance(identity, predicates));
}
}
private static void parseConfig(final Queue<String> args, final RuleSetCreator ruleSetCreator, final int line)
{
final Iterator<String> i = args.iterator();
while (i.hasNext())
{
final String key = i.next().replace("-", "_").toLowerCase(Locale.ENGLISH);
final String value = readValue(i, line);
switch (key)
{
case LOG_ALL:
case LOG_ALL_ALTERNATIVE:
ruleSetCreator.setLogAllMessages(Boolean.parseBoolean(value));
break;
case DEFAULT_FREQUENCY_PERIOD:
case DEFAULT_FREQUENCY_PERIOD_ALTERNATIVE:
ruleSetCreator.setDefaultFrequencyPeriod(Long.parseLong(value));
break;
default:
}
}
}
private static String readValue(Iterator<String> i, int line)
{
if (!i.hasNext())
{
throw new IllegalConfigurationException(String.format(PROPERTY_KEY_ONLY_MSG, line));
}
if (!"=".equals(i.next()))
{
throw new IllegalConfigurationException(String.format(PROPERTY_NO_EQUALS_MSG, line));
}
if (!i.hasNext())
{
throw new IllegalConfigurationException(String.format(PROPERTY_NO_VALUE_MSG, line));
}
return i.next();
}
private Reader getReaderFromURLString(String urlString)
{
try
{
return new InputStreamReader(new URL(urlString).openStream(), StandardCharsets.UTF_8);
}
catch (MalformedURLException e)
{
return getReaderFromPath(urlString);
}
catch (IOException | RuntimeException e)
{
throw createReaderError(urlString, e);
}
}
private Reader getReaderFromPath(String path)
{
try
{
return new InputStreamReader(Paths.get(path).toUri().toURL().openStream(), StandardCharsets.UTF_8);
}
catch (IOException | RuntimeException e)
{
throw createReaderError(path, e);
}
}
private static IllegalConfigurationException createReaderError(String urlString, Exception e)
{
return new IllegalConfigurationException("Cannot convert " + urlString + " to a readable resource", e);
}
}