moved ProxyAutoConfig.java from org.ibex.net.HTTP to this package
[org.ibex.js.git] / src / org / ibex / js / ProxyAutoConfig.java
1 // Copyright 2000-2005 the Contributors, as shown in the revision logs.
2 // Licensed under the Apache Public Source License 2.0 ("the License").
3 // You may not use this file except in compliance with the License.
4
5 package org.ibex.js;
6
7 import java.net.*;
8 import java.io.*;
9 import java.util.*;
10 import java.net.*;
11 import org.ibex.util.*;
12 import org.ibex.net.*;
13 import org.ibex.crypto.*;
14
15 public class ProxyAutoConfig {
16
17     public static class PacProxy extends HTTP.Proxy {
18         private final JS pacFunc;
19         public PacProxy(JS pacFunc) { this.pacFunc = pacFunc; }
20         public Socket attempt(String url, String host, HTTP http) {
21             if (Log.verbose) Log.info(this, "evaluating PAC script");
22             String pac = null;
23             try {
24                 pac = JSU.toString(pacFunc.call(null, new JS[] { JSU.S(url), JSU.S(host) }));
25                 if (Log.verbose) Log.info(this, "  PAC script returned \"" + pac + "\"");
26             } catch (Throwable e) {
27                 if (Log.on) Log.info(this, "PAC script threw exception " + e);
28                 return null;
29             }
30             StringTokenizer st = new StringTokenizer(pac, ";", false);
31             while (st.hasMoreTokens()) {
32                 String token = st.nextToken().trim();
33                 if (Log.verbose) Log.info(this, "  trying \"" + token + "\"...");
34                 try {
35                     Socket ret = null;
36                     if (token.startsWith("DIRECT"))
37                         ret = attemptDirect(http);
38                     else if (token.startsWith("PROXY"))
39                         ret = attemptHttpProxy(http,
40                                                token.substring(token.indexOf(' ') + 1, token.indexOf(':')),
41                                                Integer.parseInt(token.substring(token.indexOf(':') + 1)));
42                     else if (token.startsWith("SOCKS"))
43                         ret = attemptSocksProxy(http,
44                                                 token.substring(token.indexOf(' ') + 1, token.indexOf(':')),
45                                                 Integer.parseInt(token.substring(token.indexOf(':') + 1)));
46                     if (ret != null) return ret;
47                 } catch (Throwable e) {
48                     if (Log.on) Log.info(this, "attempt at \"" + token + "\" failed due to " + e + "; trying next token");
49                 }
50             }
51             if (Log.on) Log.info(this, "all PAC results exhausted");
52             return null;
53         }
54     }
55     
56     public static class ProxyAutoConfigRootScope extends JSScope.Global {
57
58         public ProxyAutoConfigRootScope() { super(); }
59         
60         private final static JS.Method METHOD = new JS.Method();
61         public JS get(JS name) throws JSExn {
62             //#jsswitch(name)
63             case "isPlainHostName": return METHOD;
64             case "dnsDomainIs": return METHOD;
65             case "localHostOrDomainIs": return METHOD;
66             case "isResolvable": return METHOD;
67             case "isInNet": return METHOD;
68             case "dnsResolve": return METHOD;
69             case "myIpAddress": return METHOD;
70             case "dnsDomainLevels": return METHOD;
71             case "shExpMatch": return METHOD;
72             case "weekdayRange": return METHOD;
73             case "dateRange": return METHOD;
74             case "timeRange": return METHOD;
75             case "ProxyConfig": return ProxyConfig;
76                 //#end
77                 return super.get(name);
78         }
79         
80         private static final JS proxyConfigBindings = new JS.Obj();
81         private static final JS ProxyConfig = new JS.Obj() {
82                 public JS get(JS name) throws JSExn {
83                     //#jsswitch(name)
84                     case "bindings": return proxyConfigBindings;
85                         //#end
86                         return null;
87                 }
88             };
89
90         public JS call(JS method, JS[] args) throws JSExn {
91             //#jsswitch(method)
92             case "isPlainHostName": return JSU.B(JSU.toString(args[0]).indexOf('.') == -1);
93             case "dnsDomainIs":     return JSU.B(JSU.toString(args[0]).endsWith(JSU.toString(args[1])));
94             case "localHostOrDomainIs":
95                 return JSU.B(args[0].equals(args[1]) ||
96                              (JSU.toString(args[0]).indexOf('.') == -1 && JSU.toString(args[1]).startsWith(JSU.toString(args[0]))));
97             case "isResolvable": try {
98                 return JSU.B(InetAddress.getByName(JSU.toString(args[0])) != null);
99             } catch (UnknownHostException e) { return JSU.F; }
100             case "isInNet":
101                 if (args.length != 3) return JSU.F;
102                 try {
103                     byte[] host = InetAddress.getByName(JSU.toString(args[0])).getAddress();
104                     byte[] net = InetAddress.getByName(JSU.toString(args[1])).getAddress();
105                     byte[] mask = InetAddress.getByName(args[2].toString()).getAddress();
106                     return JSU.B((host[0] & mask[0]) == net[0] &&
107                                  (host[1] & mask[1]) == net[1] &&
108                                  (host[2] & mask[2]) == net[2] &&
109                                  (host[3] & mask[3]) == net[3]);
110                 } catch (Exception e) {
111                     throw new JSExn("exception in isInNet(): " + e);
112                 }
113             case "dnsResolve":
114                 try {
115                     return JSU.S(InetAddress.getByName(JSU.toString(args[0])).getHostAddress());
116                 } catch (UnknownHostException e) {
117                     return null;
118                 }
119             case "myIpAddress":
120                 try {
121                     return JSU.S(InetAddress.getLocalHost().getHostAddress());
122                 } catch (UnknownHostException e) {
123                     if (Log.on) Log.info(this, "strange... host does not know its own address");
124                     return null;
125                 }
126             case "dnsDomainLevels":
127                 String s = JSU.toString(args[0]);
128                 int i = 0;
129                 while((i = s.indexOf('.', i)) != -1) i++;
130                 return JSU.N(i);
131             case "shExpMatch":
132                 StringTokenizer st = new StringTokenizer(JSU.toString(args[1]), "*", false);
133                 String[] arr = new String[st.countTokens()];
134                 String s = JSU.toString(args[0]);
135                 for (int i=0; st.hasMoreTokens(); i++) arr[i] = st.nextToken();
136                 return JSU.B(match(arr, s, 0));
137             case "weekdayRange":
138                 TimeZone tz = (args.length < 3 || args[2] == null || !args[2].equals("GMT")) ?
139                     TimeZone.getTimeZone("UTC") : TimeZone.getDefault();
140                 Calendar c = new GregorianCalendar();
141                 c.setTimeZone(tz);
142                 c.setTime(new java.util.Date());
143                 java.util.Date d = c.getTime();
144                 int day = d.getDay();
145                 String d1s = JSU.toString(args[0]).toUpperCase();
146                 int d1 = 0, d2 = 0;
147                 for(int i=0; i<days.length; i++) if (days[i].equals(d1s)) d1 = i;
148                     
149                 if (args.length == 1) return JSU.B(d1 == day);
150                     
151                 String d2s = JSU.toString(args[1]).toUpperCase();
152                 for(int i=0; i<days.length; i++) if (days[i].equals(d2s)) d2 = i;
153                     
154                 return JSU.B((d1 <= d2 && day >= d1 && day <= d2) || (d1 > d2 && (day >= d1 || day <= d2)));
155                     
156             case "dateRange": throw new JSExn("Ibex does not support dateRange() in PAC scripts");
157             case "timeRange": throw new JSExn("Ibex does not support timeRange() in PAC scripts");
158                 //#end
159                 return super.call(method, args);
160         }       
161         private static boolean match(String[] arr, String s, int index) {
162             if (index >= arr.length) return true;
163             for(int i=0; i<s.length(); i++) {
164                 String s2 = s.substring(i);
165                 if (s2.startsWith(arr[index]) && match(arr, s2.substring(arr[index].length()), index + 1)) return true;
166             }
167             return false;
168         }
169         public static String[] days = { "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" };
170     }
171
172     public static JS proxyAutoConfigRootScope = new ProxyAutoConfigRootScope();
173     public static JS getProxyAutoConfigFunction(String url) {
174         try { 
175             BufferedReader br = new BufferedReader(new InputStreamReader(new HTTP(url, true).GET(null, null)));
176             String s = null;
177             String script = "";
178             while((s = br.readLine()) != null) script += s + "\n";
179             if (Log.on) Log.info(Proxy.class, "successfully retrieved WPAD PAC:");
180             if (Log.on) Log.info(Proxy.class, script);
181             
182             // MS CARP hack
183             Vector carpHosts = new Vector();
184             for(int i=0; i<script.length(); i++)
185                 if (script.regionMatches(i, "new Node(", 0, 9)) {
186                     String host = script.substring(i + 10, script.indexOf('\"', i + 11));
187                     if (Log.on) Log.info(Proxy.class, "Detected MS Proxy Server CARP Script, Host=" + host);
188                     carpHosts.addElement(host);
189                 }
190             if (carpHosts.size() > 0) {
191                 script = "function FindProxyForURL(url, host) {\nreturn \"";
192                 for(int i=0; i<carpHosts.size(); i++)
193                     script += "PROXY " + carpHosts.elementAt(i) + "; ";
194                 script += "\";\n}";
195                 if (Log.on) Log.info(Proxy.class, "DeCARPed PAC script:");
196                 if (Log.on) Log.info(Proxy.class, script);
197             }
198
199             JS scr = JSU.fromReader("PAC script at " + url, 0, new StringReader(script));
200             JSU.cloneWithNewGlobalScope(scr, proxyAutoConfigRootScope).call(null, null);
201             return (JS)proxyAutoConfigRootScope.get(JSU.S("FindProxyForURL"));
202         } catch (Exception e) {
203             if (Log.on) {
204                 Log.info(ProxyAutoConfig.class, "WPAD detection failed due to:");
205                 if (e instanceof JSExn) {
206                     try {
207                         org.ibex.js.JSArray arr = new org.ibex.js.JSArray();
208                         arr.add(((JSExn)e).getObject());
209                     } catch (Exception e2) {
210                         Log.info(ProxyAutoConfig.class, e);
211                     }
212                 }
213                 else Log.info(ProxyAutoConfig.class, e);
214             }
215             return null;
216         }
217     }
218 }