summaryrefslogtreecommitdiffstats
path: root/src/main/java/com/btr/proxy/selector/pac/PacScriptMethods.java
blob: 101c267c980389ae2810d849919ec56f7c92dfd6 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
package com.btr.proxy.selector.pac;

import java.io.IOException;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
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 com.btr.proxy.util.Logger;
import com.btr.proxy.util.Logger.LogLevel;

/***************************************************************************
 * Implementation of PAC JavaScript functions.
 *  
 * @author Bernd Rosstauscher (proxyvole@rosstauscher.de) Copyright 2009
 ***************************************************************************
 */
public class PacScriptMethods implements ScriptMethods {

    public static final String OVERRIDE_LOCAL_IP = "com.btr.proxy.pac.overrideLocalIP";

	private final static String GMT = "GMT";
    
    private final static List<String> DAYS = Collections.unmodifiableList(
            Arrays.asList("SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT")); 

    private final static List<String> 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.btr.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 {
            return InetAddress.getByName(host).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<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
        	while (interfaces.hasMoreElements()){
        	    NetworkInterface current = interfaces.nextElement();
        	    if (!current.isUp() || current.isLoopback() || current.isVirtual()) {
        	    	continue;
        	    }
        	    Enumeration<InetAddress> 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();
            // 07.05.2009 Incorrect? first token can be startsWith and last one
            // can be endsWith
            int temp = str.indexOf(token, startPos);
            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<String, Integer> params = new HashMap<String, Integer>();
        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<String, Integer> 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.<br/>
     * 
     * <pre>
     * 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)
     * </pre>
     * 
     * @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.btr.proxy.selector.pac.ScriptMethods#isResolvableEx(java.lang.String)
	 ************************************************************************/

    public boolean isResolvableEx(String host) {
		return isResolvable(host);
	}

	/*************************************************************************
	 * isInNetEx
	 * @see com.btr.proxy.selector.pac.ScriptMethods#isInNetEx(java.lang.String, java.lang.String)
	 ************************************************************************/

	public boolean isInNetEx(String ipAddress, String ipPrefix) {
		// TODO rossi 27.06.2011 Auto-generated method stub
		return false;
	}

	/*************************************************************************
	 * dnsResolveEx
	 * @see com.btr.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.btr.proxy.selector.pac.ScriptMethods#myIpAddressEx()
	 ************************************************************************/

	public String myIpAddressEx() {
		return getLocalAddressOfType(Inet6Address.class);
	}

	/*************************************************************************
	 * sortIpAddressList
	 * @see com.btr.proxy.selector.pac.ScriptMethods#sortIpAddressList(java.lang.String)
	 ************************************************************************/

	public String sortIpAddressList(String ipAddressList) {
		if (ipAddressList == null || ipAddressList.trim().length() == 0) {
			return "";
		}
		String[] ipAddressToken = ipAddressList.split(";");
		List<InetAddress> parsedAddresses = new ArrayList<InetAddress>();
		for (String ip : ipAddressToken) {
			try {
				parsedAddresses.add(InetAddress.getByName(ip));
			} catch (UnknownHostException e) {
				// TODO rossi 01.11.2011 Auto-generated catch block
				e.printStackTrace();
			}
		}
		Collections.sort(parsedAddresses, null);
		// TODO rossi 27.06.2011 Implement me.
		return ipAddressList;
	}

	/*************************************************************************
	 * getClientVersion
	 * @see com.btr.proxy.selector.pac.ScriptMethods#getClientVersion()
	 ************************************************************************/

	public String getClientVersion() {
		return "1.0";
	}

}