Merge pull request #6 from GideonLeGrange/parser-fix
Fixed parser problems described in issues #3, #4 and #5
This commit is contained in:
commit
a115355d22
|
|
@ -8,9 +8,10 @@ This project provides a Java client to manipulate Mikrotik routers using the rem
|
||||||
Versions
|
Versions
|
||||||
--------
|
--------
|
||||||
|
|
||||||
The current stable version 1.1.2, which adds support for handling multi-line results, like for example /file print.
|
The current stable version 1.1.3, which fixes two severe command line parsing bugs, #3 and #4
|
||||||
|
|
||||||
Version 1.1 added TLS (SSL) support to encrypt API traffic.
|
* 1.1.2 added support for handling multi-line results, like for example /file print.
|
||||||
|
* 1.1 added TLS (SSL) support to encrypt API traffic.
|
||||||
|
|
||||||
|
|
||||||
Examples
|
Examples
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ package examples;
|
||||||
public class Config {
|
public class Config {
|
||||||
|
|
||||||
|
|
||||||
public static final String HOST = "10.0.1.3";
|
public static final String HOST = "10.0.1.134";
|
||||||
public static final String USERNAME = "admin";
|
public static final String USERNAME = "admin";
|
||||||
public static final String PASSWORD = "";
|
public static final String PASSWORD = "";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,13 @@ public class Example6 extends Example {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void test() throws MikrotikApiException, InterruptedException {
|
private void test() throws MikrotikApiException, InterruptedException {
|
||||||
con.execute("/interface/gre/add remote-address=10.0.1.1 name=gre1 keepalive=10");
|
System.out.println("Creating interface gre1");
|
||||||
|
con.execute("/interface/gre/add remote-address=1.2.3.4 name=gre1 keepalive=10 comment='test comment'");
|
||||||
|
System.out.println("Adding firewall rule for interface gre1");
|
||||||
|
con.execute("/ip/firewall/filter/add action=drop chain=forward in-interface=gre1 protocol=udp dst-port=78,80");//,80,32");
|
||||||
|
System.out.println("Waiting 10 seconds");
|
||||||
Thread.sleep(10000); // 10 seconds for the user to look on the router to see the interface with /interface gre print
|
Thread.sleep(10000); // 10 seconds for the user to look on the router to see the interface with /interface gre print
|
||||||
|
System.out.println("Changing IP for interface gre1");
|
||||||
con.execute("/interface/gre/set remote-address=172.16.1.1 .id=gre1");
|
con.execute("/interface/gre/set remote-address=172.16.1.1 .id=gre1");
|
||||||
// now look again and the IP has changed
|
// now look again and the IP has changed
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -486,7 +486,7 @@ public final class ApiConnectionImpl extends ApiConnection {
|
||||||
throw new ApiConnectionException(ex.getMessage(), ex);
|
throw new ApiConnectionException(ex.getMessage(), ex);
|
||||||
}
|
}
|
||||||
if (err != null) {
|
if (err != null) {
|
||||||
throw err;
|
throw new MikrotikApiException(err.getMessage(), err);
|
||||||
}
|
}
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
package me.legrange.mikrotik.impl;
|
package me.legrange.mikrotik.impl;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import me.legrange.mikrotik.impl.Scanner.Token;
|
||||||
import java.util.StringTokenizer;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse the pseudo-command line into command objects.
|
* Parse the pseudo-command line into command objects.
|
||||||
|
|
@ -21,11 +19,8 @@ class Parser {
|
||||||
|
|
||||||
/** run parse on the internal data and return the command object */
|
/** run parse on the internal data and return the command object */
|
||||||
private Command parse() throws ParseException {
|
private Command parse() throws ParseException {
|
||||||
next();
|
command();
|
||||||
expect(Token.COMMAND);
|
while (!is(Token.WHERE, Token.RETURN, Token.EOL)) {
|
||||||
cmd = new Command(text);
|
|
||||||
next();
|
|
||||||
while (!((token == Token.WHERE) || (token == Token.RETURN) || (token == Token.EOL))) {
|
|
||||||
param();
|
param();
|
||||||
}
|
}
|
||||||
if (token == Token.WHERE) {
|
if (token == Token.WHERE) {
|
||||||
|
|
@ -38,14 +33,35 @@ class Parser {
|
||||||
return cmd;
|
return cmd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void command() throws ParseException {
|
||||||
|
StringBuilder path = new StringBuilder();
|
||||||
|
do {
|
||||||
|
expect(Token.SLASH);
|
||||||
|
path.append("/");
|
||||||
|
next();
|
||||||
|
expect(Token.TEXT);
|
||||||
|
path.append(text);
|
||||||
|
next();
|
||||||
|
} while (token == Token.SLASH);
|
||||||
|
cmd = new Command(path.toString());
|
||||||
|
}
|
||||||
|
|
||||||
private void param() throws ParseException {
|
private void param() throws ParseException {
|
||||||
String name = text;
|
String name = text;
|
||||||
next();
|
next();
|
||||||
if (token == Token.EQUALS) {
|
if (token == Token.EQUALS) {
|
||||||
next();
|
next();
|
||||||
expect(Token.NAME);
|
expect(Token.TEXT);
|
||||||
cmd.addParameter(new Parameter(name, text));
|
StringBuilder val = new StringBuilder(text);
|
||||||
next();
|
next();
|
||||||
|
while (is(Token.COMMA, Token.SLASH)) {
|
||||||
|
val.append(token);
|
||||||
|
next();
|
||||||
|
expect(Token.TEXT);
|
||||||
|
val.append(text);
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
cmd.addParameter(new Parameter(name, val.toString()));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
cmd.addParameter(new Parameter(name));
|
cmd.addParameter(new Parameter(name));
|
||||||
|
|
@ -58,12 +74,12 @@ class Parser {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void expr() throws ParseException {
|
private void expr() throws ParseException {
|
||||||
expect(Token.NOT, Token.NAME);
|
expect(Token.NOT, Token.TEXT);
|
||||||
switch (token) {
|
switch (token) {
|
||||||
case NOT:
|
case NOT:
|
||||||
notExpr();
|
notExpr();
|
||||||
break;
|
break;
|
||||||
case NAME: {
|
case TEXT: {
|
||||||
String name = text;
|
String name = text;
|
||||||
next();
|
next();
|
||||||
expect(Token.EQUALS, Token.LESS, Token.MORE);
|
expect(Token.EQUALS, Token.LESS, Token.MORE);
|
||||||
|
|
@ -110,19 +126,20 @@ class Parser {
|
||||||
cmd.addQuery("?#!");
|
cmd.addQuery("?#!");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void eqExpr(String name) {
|
private void eqExpr(String name) throws ParseException {
|
||||||
next(); // eat =
|
next(); // eat =
|
||||||
|
expect(Token.TEXT);
|
||||||
cmd.addQuery(String.format("?%s=%s", name, text));
|
cmd.addQuery(String.format("?%s=%s", name, text));
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void lessExpr(String name) {
|
private void lessExpr(String name) throws ScanException {
|
||||||
next(); // eat <
|
next(); // eat <
|
||||||
cmd.addQuery(String.format("?<%s=%s", name, text));
|
cmd.addQuery(String.format("?<%s=%s", name, text));
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void moreExpr(String name) {
|
private void moreExpr(String name) throws ScanException {
|
||||||
next(); // eat >
|
next(); // eat >
|
||||||
cmd.addQuery(String.format("?>%s=%s", name, text));
|
cmd.addQuery(String.format("?>%s=%s", name, text));
|
||||||
next();
|
next();
|
||||||
|
|
@ -134,8 +151,8 @@ class Parser {
|
||||||
|
|
||||||
private void returns() throws ParseException {
|
private void returns() throws ParseException {
|
||||||
next();
|
next();
|
||||||
expect(Token.NAME);
|
expect(Token.TEXT);
|
||||||
List<String> props = new LinkedList<String>();
|
List<String> props = new LinkedList<>();
|
||||||
while (!(token == Token.EOL)) {
|
while (!(token == Token.EOL)) {
|
||||||
if (token != Token.COMMA) {
|
if (token != Token.COMMA) {
|
||||||
props.add(text);
|
props.add(text);
|
||||||
|
|
@ -146,63 +163,35 @@ class Parser {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void expect(Token...tokens) throws ParseException {
|
private void expect(Token...tokens) throws ParseException {
|
||||||
|
if (!is(tokens))
|
||||||
|
throw new ParseException(String.format("Expected %s but found %s at position %d", Arrays.asList(tokens), this.token, scanner.pos()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean is(Token...tokens) {
|
||||||
for (Token want : tokens) {
|
for (Token want : tokens) {
|
||||||
if (this.token == want) return;
|
if (this.token == want) return true;
|
||||||
}
|
}
|
||||||
throw new ParseException(String.format("Expected %s but found %s", Arrays.asList(tokens), this.token));
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void next() {
|
/** move to the next token returned by the scanner */
|
||||||
if (!words.isEmpty()) {
|
private void next() throws ScanException {
|
||||||
text = words.remove(0);
|
token = scanner.next();
|
||||||
if (text.startsWith("/")) {
|
while (token == Token.WS) {
|
||||||
token = Token.COMMAND;
|
token = scanner.next();
|
||||||
} else {
|
|
||||||
Token t = lookup.get(text);
|
|
||||||
if (t != null) {
|
|
||||||
token = t;
|
|
||||||
text = "";
|
|
||||||
} else {
|
|
||||||
token = Token.NAME;
|
|
||||||
}
|
}
|
||||||
}
|
text = scanner.text();
|
||||||
} else {
|
|
||||||
token = Token.EOL;
|
|
||||||
text = "";
|
|
||||||
}
|
|
||||||
// System.out.printf("%s: %s\n", token, text);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Parser(String text) {
|
private Parser(String line) throws ScanException {
|
||||||
text = text.trim();
|
line = line.trim();
|
||||||
StringTokenizer st = new StringTokenizer(text, " \t,=", true);
|
scanner = new Scanner(line);
|
||||||
while (st.hasMoreElements()) {
|
next();
|
||||||
String t = st.nextToken().trim();
|
|
||||||
if (!t.equals("")) {
|
|
||||||
words.add(t);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
private final Scanner scanner;
|
||||||
private final List<String> words = new LinkedList<String>();
|
|
||||||
private String text;
|
|
||||||
private Token token;
|
private Token token;
|
||||||
|
private String text;
|
||||||
private Command cmd;
|
private Command cmd;
|
||||||
private static final Map<String, Token> lookup = new HashMap<String, Token>();
|
|
||||||
|
|
||||||
private enum Token {
|
|
||||||
|
|
||||||
COMMAND, WHERE, RETURN, EOL, NOT, AND, OR, NAME, EQUALS, MORE, LESS, COMMA;
|
|
||||||
}
|
|
||||||
|
|
||||||
static {
|
|
||||||
lookup.put("where", Token.WHERE);
|
|
||||||
lookup.put("return", Token.RETURN);
|
|
||||||
lookup.put("not", Token.NOT);
|
|
||||||
lookup.put("and", Token.AND);
|
|
||||||
lookup.put("or", Token.OR);
|
|
||||||
lookup.put("=", Token.EQUALS);
|
|
||||||
lookup.put(">", Token.MORE);
|
|
||||||
lookup.put("<", Token.LESS);
|
|
||||||
lookup.put(",", Token.COMMA);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
package me.legrange.mikrotik.impl;
|
||||||
|
|
||||||
|
import me.legrange.mikrotik.MikrotikApiException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception thrown if the scanner encounters an error while scanning a command line.
|
||||||
|
* @author GideonLeGrange
|
||||||
|
*/
|
||||||
|
public class ScanException extends ParseException {
|
||||||
|
|
||||||
|
ScanException(String msg) {
|
||||||
|
super(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
ScanException(String msg, Throwable err) {
|
||||||
|
super(msg, err);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,156 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2014 GideonLeGrange.
|
||||||
|
*
|
||||||
|
* Licensed 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 me.legrange.mikrotik.impl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple scanner.
|
||||||
|
* @author gideon
|
||||||
|
*/
|
||||||
|
class Scanner {
|
||||||
|
|
||||||
|
enum Token {
|
||||||
|
SLASH("/"), COMMA(","), EOL(), WS, TEXT,
|
||||||
|
LESS("<"), MORE(">"), EQUALS("="),
|
||||||
|
WHERE, NOT, AND, OR, RETURN;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return (symb == null) ? name() : symb;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Token(String symb) {
|
||||||
|
this.symb = symb;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Token() {
|
||||||
|
symb = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private final String symb;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** create a scanner for the given line of text */
|
||||||
|
Scanner(String line) {
|
||||||
|
this.line = line;
|
||||||
|
nextChar();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** return the next token from the text */
|
||||||
|
Token next() throws ScanException {
|
||||||
|
text = null;
|
||||||
|
switch (c) {
|
||||||
|
case '\n' : return Token.EOL;
|
||||||
|
case ' ' :
|
||||||
|
case '\t' :
|
||||||
|
return whiteSpace();
|
||||||
|
case ',' :
|
||||||
|
nextChar();
|
||||||
|
return Token.COMMA;
|
||||||
|
case '/' :
|
||||||
|
nextChar();
|
||||||
|
return Token.SLASH;
|
||||||
|
case '<' :
|
||||||
|
nextChar();
|
||||||
|
return Token.LESS;
|
||||||
|
case '>' :
|
||||||
|
nextChar();
|
||||||
|
return Token.MORE;
|
||||||
|
case '=' :
|
||||||
|
nextChar();
|
||||||
|
return Token.EQUALS;
|
||||||
|
case '"' :
|
||||||
|
return quotedText('"');
|
||||||
|
case '\'' :
|
||||||
|
return quotedText('\'');
|
||||||
|
default :
|
||||||
|
return name();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/** return the text associated with the last token returned */
|
||||||
|
String text() {
|
||||||
|
if (text != null) return text.toString();
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/** return the position of the scanner */
|
||||||
|
int pos() { return pos; }
|
||||||
|
|
||||||
|
/** process 'name' tokens which could be key words or text */
|
||||||
|
private Token name() throws ScanException {
|
||||||
|
text = new StringBuilder();
|
||||||
|
while (in(c,"[A-Za-z0-9-\\.]")) {
|
||||||
|
text.append(c);
|
||||||
|
nextChar();
|
||||||
|
}
|
||||||
|
String val = text.toString().toLowerCase();
|
||||||
|
switch (val) {
|
||||||
|
case "where" : return Token.WHERE;
|
||||||
|
case "not" : return Token.NOT;
|
||||||
|
case "and" : return Token.AND;
|
||||||
|
case "or" : return Token.OR;
|
||||||
|
case "return" : return Token.RETURN;
|
||||||
|
}
|
||||||
|
return Token.TEXT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** process quoted text */
|
||||||
|
private Token quotedText(char quote) throws ScanException {
|
||||||
|
nextChar(); // eat the '"'
|
||||||
|
text = new StringBuilder();
|
||||||
|
while (c != quote) {
|
||||||
|
if (c == '\n') {
|
||||||
|
throw new ScanException("Unclosed quoted text, reached end of line.");
|
||||||
|
}
|
||||||
|
text.append(c);
|
||||||
|
nextChar();
|
||||||
|
}
|
||||||
|
nextChar(); // eat the '"'
|
||||||
|
return Token.TEXT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** process white space */
|
||||||
|
private Token whiteSpace() {
|
||||||
|
while ((c == ' ') || (c == '\t')) {
|
||||||
|
nextChar();
|
||||||
|
}
|
||||||
|
return Token.WS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** return the next character from the line of text */
|
||||||
|
private void nextChar() {
|
||||||
|
if (pos < line.length()) {
|
||||||
|
c = line.charAt(pos);
|
||||||
|
pos ++;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
c = '\n';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** check if the character matches the give expression */
|
||||||
|
private boolean in(char c, String cs) {
|
||||||
|
return ("" + c).matches(cs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final String line;
|
||||||
|
private int pos = 0;
|
||||||
|
private char c;
|
||||||
|
private StringBuilder text;
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue