com.github.markusbernhardt.proxy.selector.pac.PacScriptMethods Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of proxy-vole Show documentation
Show all versions of proxy-vole Show documentation
Proxy Vole is a Java library to auto detect the platform network proxy settings.
package com.github.markusbernhardt.proxy.selector.pac;
import java.io.IOException;
import java.math.BigInteger;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.TimeZone;
import java.util.TreeMap;
import com.github.markusbernhardt.proxy.util.Logger;
import com.github.markusbernhardt.proxy.util.Logger.LogLevel;
/***************************************************************************
* Implementation of PAC JavaScript functions.
*
* @author Markus Bernhardt, Copyright 2016
* @author Bernd Rosstauscher, Copyright 2009
***************************************************************************
*/
public class PacScriptMethods implements ScriptMethods {
// TODO 30.03.2015 bros Test for IP6 compatibility
public static final String OVERRIDE_LOCAL_IP = "com.btr.proxy.pac.overrideLocalIP";
private final static String GMT = "GMT";
private final static List DAYS = Collections
.unmodifiableList(Arrays.asList("SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"));
private final static List MONTH = Collections.unmodifiableList(
Arrays.asList("JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"));
private Calendar currentTime;
/*************************************************************************
* Constructor
************************************************************************/
public PacScriptMethods() {
super();
}
/*************************************************************************
* isPlainHostName
*
* @see com.github.markusbernhardt.proxy.selector.pac.ScriptMethods#isPlainHostName(java.lang.String)
************************************************************************/
public boolean isPlainHostName(String host) {
return host.indexOf(".") < 0;
}
/*************************************************************************
* Tests if an URL is in a given domain.
*
* @param host
* is the host name from the URL.
* @param domain
* is the domain name to test the host name against.
* @return true if the domain of host name matches.
************************************************************************/
public boolean dnsDomainIs(String host, String domain) {
return host.endsWith(domain);
}
/*************************************************************************
* Is true if the host name matches exactly the specified host name, or if
* there is no domain name part in the host name, but the unqualified host
* name matches.
*
* @param host
* the host name from the URL.
* @param domain
* fully qualified host name with domain to match against.
* @return true if matches else false.
************************************************************************/
public boolean localHostOrDomainIs(String host, String domain) {
return domain.startsWith(host);
}
/*************************************************************************
* Tries to resolve the host name. Returns true if succeeds.
*
* @param host
* is the host name from the URL.
* @return true if resolvable else false.
************************************************************************/
public boolean isResolvable(String host) {
try {
InetAddress.getByName(host).getHostAddress();
return true;
} catch (UnknownHostException ex) {
Logger.log(JavaxPacScriptParser.class, LogLevel.DEBUG, "Hostname not resolveable {0}.", host);
}
return false;
}
/*************************************************************************
* Returns true if the IP address of the host matches the specified IP
* address pattern. Pattern and mask specification is done the same way as
* for SOCKS configuration.
*
* Example: isInNet(host, "198.95.0.0", "255.255.0.0") is true if the IP
* address of the host matches 198.95.*.*.
*
* @param host
* a DNS host name, or IP address. If a host name is passed, it
* will be resolved into an IP address by this function.
* @param pattern
* an IP address pattern in the dot-separated format.
* @param mask
* mask for the IP address pattern informing which parts of the
* IP address should be matched against. 0 means ignore, 255
* means match.
* @return true if it matches else false.
************************************************************************/
public boolean isInNet(String host, String pattern, String mask) {
host = dnsResolve(host);
if (host == null || host.length() == 0) {
return false;
}
long lhost = parseIpAddressToLong(host);
long lpattern = parseIpAddressToLong(pattern);
long lmask = parseIpAddressToLong(mask);
return (lhost & lmask) == lpattern;
}
/*************************************************************************
* Convert a string representation of a IP to a long.
*
* @param address
* to convert.
* @return the address as long.
************************************************************************/
private long parseIpAddressToLong(String address) {
long result = 0;
String[] parts = address.split("\\.");
long shift = 24;
for (String part : parts) {
long lpart = Long.parseLong(part);
result |= (lpart << shift);
shift -= 8;
}
return result;
}
/*************************************************************************
* Resolves the given DNS host name into an IP address, and returns it in
* the dot separated format as a string.
*
* @param host
* the host to resolve.
* @return the resolved IP, empty string if not resolvable.
************************************************************************/
public String dnsResolve(String host) {
try {
InetAddress ina = InetAddress.getByName(host);
return ina.getHostAddress();
} catch (UnknownHostException e) {
Logger.log(JavaxPacScriptParser.class, LogLevel.DEBUG, "DNS name not resolvable {0}.", host);
}
return "";
}
/*************************************************************************
* Returns the IP address of the host that the process is running on, as a
* string in the dot-separated integer format.
*
* @return an IP as string.
************************************************************************/
public String myIpAddress() {
return getLocalAddressOfType(Inet4Address.class);
}
/*************************************************************************
* Get the current IP address of the computer. This will return the first
* address of the first network interface that is a "real" IP address of the
* given type.
*
* @param cl
* the type of address we are searching for.
* @return the address as string or "" if not found.
************************************************************************/
private String getLocalAddressOfType(Class extends InetAddress> cl) {
try {
String overrideIP = System.getProperty(OVERRIDE_LOCAL_IP);
if (overrideIP != null && overrideIP.trim().length() > 0) {
return overrideIP.trim();
}
Enumeration interfaces = NetworkInterface.getNetworkInterfaces();
while (interfaces.hasMoreElements()) {
NetworkInterface current = interfaces.nextElement();
if (!current.isUp() || current.isLoopback() || current.isVirtual()) {
continue;
}
Enumeration addresses = current.getInetAddresses();
while (addresses.hasMoreElements()) {
InetAddress adr = addresses.nextElement();
if (cl.isInstance(adr)) {
Logger.log(JavaxPacScriptParser.class, LogLevel.TRACE, "Local address resolved to {0}", adr);
return adr.getHostAddress();
}
}
}
return "";
} catch (IOException e) {
Logger.log(JavaxPacScriptParser.class, LogLevel.DEBUG, "Local address not resolvable.");
return "";
}
}
/*************************************************************************
* Returns the number of DNS domain levels (number of dots) in the host
* name.
*
* @param host
* is the host name from the URL.
* @return number of DNS domain levels.
************************************************************************/
public int dnsDomainLevels(String host) {
int count = 0;
int startPos = 0;
while ((startPos = host.indexOf(".", startPos + 1)) > -1) {
count++;
}
return count;
}
/*************************************************************************
* Returns true if the string matches the specified shell expression.
* Actually, currently the patterns are shell expressions, not regular
* expressions.
*
* @param str
* is any string to compare (e.g. the URL, or the host name).
* @param shexp
* is a shell expression to compare against.
* @return true if the string matches, else false.
************************************************************************/
public boolean shExpMatch(String str, String shexp) {
StringTokenizer tokenizer = new StringTokenizer(shexp, "*");
int startPos = 0;
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken();
int temp = str.indexOf(token, startPos);
// Must start with first token
if (startPos == 0 && !shexp.startsWith("*") && temp != 0) {
return false;
}
// Last one ends with last token
if (!tokenizer.hasMoreTokens() && !shexp.endsWith("*") && !str.endsWith(token)) {
return false;
}
if (temp == -1) {
return false;
} else {
startPos = temp + token.length();
}
}
return true;
}
/*************************************************************************
* Only the first parameter is mandatory. Either the second, the third, or
* both may be left out. If only one parameter is present, the function
* yields a true value on the weekday that the parameter represents. If the
* string "GMT" is specified as a second parameter, times are taken to be in
* GMT, otherwise in local time zone. If both wd1 and wd2 are defined, the
* condition is true if the current weekday is in between those two
* weekdays. Bounds are inclusive. If the "GMT" parameter is specified,
* times are taken to be in GMT, otherwise the local time zone is used.
*
* @param wd1
* weekday 1 is one of SUN MON TUE WED THU FRI SAT
* @param wd2
* weekday 2 is one of SUN MON TUE WED THU FRI SAT
* @param gmt
* "GMT" for gmt time format else "undefined"
* @return true if current day matches the criteria.
************************************************************************/
public boolean weekdayRange(String wd1, String wd2, String gmt) {
boolean useGmt = GMT.equalsIgnoreCase(wd2) || GMT.equalsIgnoreCase(gmt);
Calendar cal = getCurrentTime(useGmt);
int currentDay = cal.get(Calendar.DAY_OF_WEEK) - 1;
int from = DAYS.indexOf(wd1 == null ? null : wd1.toUpperCase());
int to = DAYS.indexOf(wd2 == null ? null : wd2.toUpperCase());
if (to == -1) {
to = from;
}
if (to < from) {
return currentDay >= from || currentDay <= to;
} else {
return currentDay >= from && currentDay <= to;
}
}
/*************************************************************************
* Sets a calendar with the current time. If this is set all date and time
* based methods will use this calendar to determine the current time
* instead of the real time. This is only be used by unit tests and is not
* part of the public API.
*
* @param cal
* a Calendar to set.
************************************************************************/
public void setCurrentTime(Calendar cal) {
this.currentTime = cal;
}
/*************************************************************************
* Gets a calendar set to the current time. This is used by the date and
* time based methods.
*
* @param useGmt
* flag to indicate if the calendar is to be created in GMT time
* or local time.
* @return a Calendar set to the current time.
************************************************************************/
private Calendar getCurrentTime(boolean useGmt) {
if (this.currentTime != null) { // Only used for unit tests
return (Calendar) this.currentTime.clone();
}
return Calendar.getInstance(useGmt ? TimeZone.getTimeZone(GMT) : TimeZone.getDefault());
}
/*************************************************************************
* Only the first parameter is mandatory. All other parameters can be left
* out therefore the meaning of the parameters changes. The method
* definition shows the version with the most possible parameters filled.
* The real meaning of the parameters is guessed from it's value. If "from"
* and "to" are specified then the bounds are inclusive. If the "GMT"
* parameter is specified, times are taken to be in GMT, otherwise the local
* time zone is used.
*
* @param day1
* is the day of month between 1 and 31 (as an integer).
* @param month1
* one of JAN FEB MAR APR MAY JUN JUL AUG SEP OCT NOV DEC
* @param year1
* is the full year number, for example 1995 (but not 95).
* Integer.
* @param day2
* is the day of month between 1 and 31 (as an integer).
* @param month2
* one of JAN FEB MAR APR MAY JUN JUL AUG SEP OCT NOV DEC
* @param year2
* is the full year number, for example 1995 (but not 95).
* Integer.
* @param gmt
* "GMT" for gmt time format else "undefined"
* @return true if the current date matches the given range.
************************************************************************/
public boolean dateRange(Object day1, Object month1, Object year1, Object day2, Object month2, Object year2,
Object gmt) {
// Guess the parameter meanings.
Map params = new HashMap();
parseDateParam(params, day1);
parseDateParam(params, month1);
parseDateParam(params, year1);
parseDateParam(params, day2);
parseDateParam(params, month2);
parseDateParam(params, year2);
parseDateParam(params, gmt);
// Get current date
boolean useGmt = params.get("gmt") != null;
Calendar cal = getCurrentTime(useGmt);
Date current = cal.getTime();
// Build the "from" date
if (params.get("day1") != null) {
cal.set(Calendar.DAY_OF_MONTH, params.get("day1"));
}
if (params.get("month1") != null) {
cal.set(Calendar.MONTH, params.get("month1"));
}
if (params.get("year1") != null) {
cal.set(Calendar.YEAR, params.get("year1"));
}
Date from = cal.getTime();
// Build the "to" date
Date to;
if (params.get("day2") != null) {
cal.set(Calendar.DAY_OF_MONTH, params.get("day2"));
}
if (params.get("month2") != null) {
cal.set(Calendar.MONTH, params.get("month2"));
}
if (params.get("year2") != null) {
cal.set(Calendar.YEAR, params.get("year2"));
}
to = cal.getTime();
// Need to increment to the next month?
if (to.before(from)) {
cal.add(Calendar.MONTH, +1);
to = cal.getTime();
}
// Need to increment to the next year?
if (to.before(from)) {
cal.add(Calendar.YEAR, +1);
cal.add(Calendar.MONTH, -1);
to = cal.getTime();
}
return current.compareTo(from) >= 0 && current.compareTo(to) <= 0;
}
/*************************************************************************
* Try to guess the type of the given parameter and put it into the params
* map.
*
* @param params
* a map to put the parsed parameters into.
* @param value
* to parse and specify the type for.
************************************************************************/
private void parseDateParam(Map params, Object value) {
if (value instanceof Number) {
int n = ((Number) value).intValue();
if (n <= 31) {
// Its a day
if (params.get("day1") == null) {
params.put("day1", n);
} else {
params.put("day2", n);
}
} else {
// Its a year
if (params.get("year1") == null) {
params.put("year1", n);
} else {
params.put("year2", n);
}
}
}
if (value instanceof String) {
int n = MONTH.indexOf(((String) value).toUpperCase());
if (n > -1) {
// Its a month
if (params.get("month1") == null) {
params.put("month1", n);
} else {
params.put("month2", n);
}
}
}
if (GMT.equalsIgnoreCase(String.valueOf(value))) {
params.put("gmt", 1);
}
}
/*************************************************************************
* Some parameters can be left out therefore the meaning of the parameters
* changes. The method definition shows the version with the most possible
* parameters filled. The real meaning of the parameters is guessed from
* it's value. If "from" and "to" are specified then the bounds are
* inclusive. If the "GMT" parameter is specified, times are taken to be in
* GMT, otherwise the local time zone is used.
*
*
* timeRange(hour)
* timeRange(hour1, hour2)
* timeRange(hour1, min1, hour2, min2)
* timeRange(hour1, min1, sec1, hour2, min2, sec2)
* timeRange(hour1, min1, sec1, hour2, min2, sec2, gmt)
*
*
* @param hour1
* is the hour from 0 to 23. (0 is midnight, 23 is 11 pm.)
* @param min1
* minutes from 0 to 59.
* @param sec1
* seconds from 0 to 59.
* @param hour2
* is the hour from 0 to 23. (0 is midnight, 23 is 11 pm.)
* @param min2
* minutes from 0 to 59.
* @param sec2
* seconds from 0 to 59.
* @param gmt
* "GMT" for gmt time format else "undefined"
* @return true if the current time matches the given range.
************************************************************************/
public boolean timeRange(Object hour1, Object min1, Object sec1, Object hour2, Object min2, Object sec2,
Object gmt) {
boolean useGmt = GMT.equalsIgnoreCase(String.valueOf(min1)) || GMT.equalsIgnoreCase(String.valueOf(sec1))
|| GMT.equalsIgnoreCase(String.valueOf(min2)) || GMT.equalsIgnoreCase(String.valueOf(gmt));
Calendar cal = getCurrentTime(useGmt);
cal.set(Calendar.MILLISECOND, 0);
Date current = cal.getTime();
Date from;
Date to;
if (sec2 instanceof Number) {
cal.set(Calendar.HOUR_OF_DAY, ((Number) hour1).intValue());
cal.set(Calendar.MINUTE, ((Number) min1).intValue());
cal.set(Calendar.SECOND, ((Number) sec1).intValue());
from = cal.getTime();
cal.set(Calendar.HOUR_OF_DAY, ((Number) hour2).intValue());
cal.set(Calendar.MINUTE, ((Number) min2).intValue());
cal.set(Calendar.SECOND, ((Number) sec2).intValue());
to = cal.getTime();
} else if (hour2 instanceof Number) {
cal.set(Calendar.HOUR_OF_DAY, ((Number) hour1).intValue());
cal.set(Calendar.MINUTE, ((Number) min1).intValue());
cal.set(Calendar.SECOND, 0);
from = cal.getTime();
cal.set(Calendar.HOUR_OF_DAY, ((Number) sec1).intValue());
cal.set(Calendar.MINUTE, ((Number) hour2).intValue());
cal.set(Calendar.SECOND, 59);
to = cal.getTime();
} else if (min1 instanceof Number) {
cal.set(Calendar.HOUR_OF_DAY, ((Number) hour1).intValue());
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
from = cal.getTime();
cal.set(Calendar.HOUR_OF_DAY, ((Number) min1).intValue());
cal.set(Calendar.MINUTE, 59);
cal.set(Calendar.SECOND, 59);
to = cal.getTime();
} else {
cal.set(Calendar.HOUR_OF_DAY, ((Number) hour1).intValue());
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
from = cal.getTime();
cal.set(Calendar.HOUR_OF_DAY, ((Number) hour1).intValue());
cal.set(Calendar.MINUTE, 59);
cal.set(Calendar.SECOND, 59);
to = cal.getTime();
}
if (to.before(from)) {
cal.setTime(to);
cal.add(Calendar.DATE, +1);
to = cal.getTime();
}
return current.compareTo(from) >= 0 && current.compareTo(to) <= 0;
}
// Microsoft PAC extensions for IPv6 support.
/*************************************************************************
* isResolvableEx
*
* @see com.github.markusbernhardt.proxy.selector.pac.ScriptMethods#isResolvableEx(java.lang.String)
************************************************************************/
public boolean isResolvableEx(String host) {
return isResolvable(host);
}
// constants
private static final BigInteger HIGH_32_INT = new BigInteger(new byte[] { -1, -1, -1, -1 });
private static final BigInteger HIGH_128_INT = new BigInteger(
new byte[] { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 });
/*************************************************************************
* isInNetEx Implementation from
* http://fhanik.blogspot.ch/2013/11/ip-magic-check-if-ipv6-address-is.html
*
* @see com.github.markusbernhardt.proxy.selector.pac.ScriptMethods#isInNetEx(java.lang.String,
* java.lang.String)
************************************************************************/
public boolean isInNetEx(String ipOrHost, String cidr) {
if (ipOrHost == null || ipOrHost.length() == 0 || cidr == null || cidr.length() == 0) {
return false;
}
try {
// Split CIDR, usually written like 2000::/64"
String[] cidrParts = cidr.split("/");
if (cidrParts.length != 2) {
return false;
}
String cidrRange = cidrParts[0];
int cidrBits = Integer.parseInt(cidrParts[1]);
byte[] addressBytes = InetAddress.getByName(ipOrHost).getAddress();
BigInteger ip = new BigInteger(addressBytes);
BigInteger mask = addressBytes.length == 4 ? HIGH_32_INT.shiftLeft(32 - cidrBits)
: HIGH_128_INT.shiftLeft(128 - cidrBits);
byte[] rangeBytes = InetAddress.getByName(cidrRange).getAddress();
BigInteger range = new BigInteger(rangeBytes);
BigInteger lowIP = range.and(mask);
BigInteger highIP = lowIP.add(mask.not());
return lowIP.compareTo(ip) <= 0 && highIP.compareTo(ip) >= 0;
} catch (UnknownHostException e) {
return false;
}
}
/*************************************************************************
* dnsResolveEx
*
* @see com.github.markusbernhardt.proxy.selector.pac.ScriptMethods#dnsResolveEx(java.lang.String)
************************************************************************/
public String dnsResolveEx(String host) {
StringBuilder result = new StringBuilder();
try {
InetAddress[] list = InetAddress.getAllByName(host);
for (InetAddress inetAddress : list) {
result.append(inetAddress.getHostAddress());
result.append("; ");
}
} catch (UnknownHostException e) {
Logger.log(JavaxPacScriptParser.class, LogLevel.DEBUG, "DNS name not resolvable {0}.", host);
}
return result.toString();
}
/*************************************************************************
* myIpAddressEx
*
* @see com.github.markusbernhardt.proxy.selector.pac.ScriptMethods#myIpAddressEx()
************************************************************************/
public String myIpAddressEx() {
return getLocalAddressOfType(Inet6Address.class);
}
/*************************************************************************
* sortIpAddressList
*
* @see com.github.markusbernhardt.proxy.selector.pac.ScriptMethods#sortIpAddressList(java.lang.String)
************************************************************************/
public String sortIpAddressList(String ipAddressList) {
if (ipAddressList == null || ipAddressList.trim().length() == 0) {
return "";
}
try {
String[] ipAddressToken = ipAddressList.split(";");
TreeMap sorting = new TreeMap(new Comparator() {
public int compare(byte[] b1, byte[] b2) {
if (b1.length != b2.length) {
return b2.length - b1.length;
}
return new BigInteger(b1).compareTo(new BigInteger(b2));
}
});
for (String ip : ipAddressToken) {
String cleanIP = ip.trim();
sorting.put(InetAddress.getByName(cleanIP).getAddress(), cleanIP);
}
StringBuilder result = new StringBuilder();
for (String ip : sorting.values()) {
if (result.length() > 0) {
result.append(";");
}
result.append(ip);
}
return result.toString();
} catch (Exception e) {
Logger.log(JavaxPacScriptParser.class, LogLevel.DEBUG, "Cannot sort invalid IP list: {0}.", ipAddressList);
return "";
}
}
/*************************************************************************
* getClientVersion
*
* @see com.github.markusbernhardt.proxy.selector.pac.ScriptMethods#getClientVersion()
************************************************************************/
public String getClientVersion() {
return "1.0";
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy