package net.pms.configuration;
/*
* PS3 Media Server, for streaming any medias to your PS3.
* Copyright (C) 2011 Zsombor G.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; version 2
* of the License only.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* IP Filter class, which supports multiple wildcards, ranges. For example :
* 127.0.0.1,192.168.0-1.*
*
* @author zsombor
*
*/
public class IpFilter {
private final static String IP_FILTER_RULE_CHAR = "0123456789-.* ";
private final static Pattern PATTERN = Pattern.compile("(([0-9]*)(-([0-9]*))?)");
private static final Logger logger = LoggerFactory.getLogger(IpFilter.class);
interface Predicate {
boolean match(InetAddress addr);
}
static class HostNamePredicate implements Predicate {
String name;
public HostNamePredicate(String n) {
this.name = n.toLowerCase().trim();
}
@Override
public boolean match(InetAddress addr) {
return addr.getHostName().contains(name);
}
@Override
public String toString() {
return name;
}
}
static class IpPredicate implements Predicate {
static class ByteRule {
int min;
int max;
public ByteRule(int a, int b) {
this.min = a;
this.max = b;
if (b > -1 && a > -1 && b < a) {
this.max = a;
this.min = b;
}
}
boolean match(int value) {
if (min >= 0 && value < min) {
return false;
}
if (max >= 0 && value > max) {
return false;
}
return true;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
if (min >= 0) {
builder.append(min);
if (max > min) {
builder.append('-').append(max);
} else {
if (max < 0) {
builder.append('-');
}
}
} else {
if (max >= 0) {
builder.append('-').append(max);
} else {
builder.append('*');
}
}
return builder.toString();
}
}
List<ByteRule> rules;
public IpPredicate(String[] tags) {
this.rules = new ArrayList<ByteRule>(tags.length);
for (String s : tags) {
s = s.trim();
rules.add(parseTag(s));
}
}
ByteRule parseTag(String s) {
if ("*".equals(s)) {
return new ByteRule(-1, -1);
} else {
Matcher matcher = PATTERN.matcher(s);
if (matcher.matches()) {
String start = matcher.group(2);
String middle = matcher.group(3);
String ending = matcher.group(4);
if (valid(start)) {
// x , x-, x-y
int x = Integer.parseInt(start);
if (valid(ending)) {
// x-y
return new ByteRule(x, Integer.parseInt(ending));
} else {
if (valid(middle)) {
// x -
return new ByteRule(x, -1);
} else {
// x
return new ByteRule(x, x);
}
}
} else {
// -y
if (valid(ending)) {
return new ByteRule(-1, Integer.parseInt(ending));
}
}
}
}
throw new IllegalArgumentException("Tag is not understood:" + s);
}
private static boolean valid(String s) {
return s != null && s.length() > 0;
}
@Override
public boolean match(InetAddress addr) {
byte[] b = addr.getAddress();
for (int i = 0; i < rules.size() && i < b.length; i++) {
int value = b[i] < 0 ? (int) b[i] + 256 : b[i];
if (!rules.get(i).match(value)) {
return false;
}
}
return true;
}
@Override
public String toString() {
StringBuilder b = new StringBuilder();
for (ByteRule r : rules) {
if (b.length() > 0) {
b.append('.');
}
b.append(r);
}
return b.toString();
}
}
String rawFilter;
List<Predicate> matchers = new ArrayList<Predicate>();
Set<String> logged = new HashSet<String>();
public IpFilter() {
}
public IpFilter(String f) {
setRawFilter(f);
}
public synchronized String getRawFilter() {
return rawFilter;
}
public synchronized void setRawFilter(String rawFilter) {
if (this.rawFilter != null && this.rawFilter.equals(rawFilter)) {
return;
}
this.matchers.clear();
this.logged.clear();
this.rawFilter = rawFilter;
if (rawFilter != null) {
String[] rules = rawFilter.split("[,;]");
for (String r : rules) {
Predicate p = parse(r);
if (p != null) {
matchers.add(p);
}
}
}
}
@Override
public String toString() {
return "IpFilter:" + getNormalizedFilter();
}
public String getNormalizedFilter() {
StringBuilder b = new StringBuilder();
for (Predicate r : matchers) {
if (b.length() > 0) {
b.append(',');
}
b.append(r);
}
return b.toString();
}
/**
* Decides whether or not the IP address matches this filter. If the filter
* is empty, false is returned.
*
* @param addr The address to match.
* @return True if the address matches, false otherwise.
*/
public boolean isMatch(InetAddress addr) {
for (Predicate predicate : matchers) {
if (predicate.match(addr)) {
return true;
}
}
return false;
}
public boolean allowed(InetAddress addr) {
boolean log = isFirstDecision(addr);
if (matchers.size() == 0) {
if (log) {
logger.info("No IP filter specified, access granted to " + addr);
}
return true;
}
for (Predicate p : matchers) {
if (p.match(addr)) {
if (log) {
logger.info("Access granted to " + addr + " by rule: " + p);
}
return true;
}
}
if (log) {
logger.info("Access denied to " + addr);
}
return false;
}
Predicate parse(String rule) {
rule = rule.trim();
if (rule.length() == 0) {
return null;
}
for (int i = 0; i < rule.length(); i++) {
if (IP_FILTER_RULE_CHAR.indexOf(rule.charAt(i)) == -1) {
return new HostNamePredicate(rule);
}
}
String[] tags = rule.split("\\.");
return new IpPredicate(tags);
}
private synchronized boolean isFirstDecision(InetAddress addr) {
String ip = addr.getHostAddress();
if (!logged.contains(ip)) {
logged.add(ip);
return true;
}
return false;
}
private static void eq(String name, Object obj, Object obj2) {
if (obj != null && obj.equals(obj2)) {
logger.debug("EQ: " + name + '=' + obj);
} else {
throw new RuntimeException(name + " expected : '" + obj + "' <> actual : '" + obj2 + "'");
}
}
public static void main(String[] args) {
eq("f1", "192.168.0.1,192.168.0.5", new IpFilter(" 192.168.0.1, 192.168.0.5").getNormalizedFilter());
eq("f2", "192.168.0.*,192.1-6.3-.5", new IpFilter(" 192.168.0.*, 192.1-6.3-.5").getNormalizedFilter());
eq("f3", "2-3.5,myhost", new IpFilter(" 3-2. 5;myhost").getNormalizedFilter());
}
}