| OLD | NEW | 
|---|
| 1 /* | 1 /* | 
| 2  * This file is part of Adblock Plus <http://adblockplus.org/>, | 2  * This file is part of Adblock Plus <http://adblockplus.org/>, | 
| 3  * Copyright (C) 2006-2014 Eyeo GmbH | 3  * Copyright (C) 2006-2014 Eyeo GmbH | 
| 4  * | 4  * | 
| 5  * Adblock Plus is free software: you can redistribute it and/or modify | 5  * Adblock Plus is free software: you can redistribute it and/or modify | 
| 6  * it under the terms of the GNU General Public License version 3 as | 6  * it under the terms of the GNU General Public License version 3 as | 
| 7  * published by the Free Software Foundation. | 7  * published by the Free Software Foundation. | 
| 8  * | 8  * | 
| 9  * Adblock Plus is distributed in the hope that it will be useful, | 9  * Adblock Plus is distributed in the hope that it will be useful, | 
| 10  * but WITHOUT ANY WARRANTY; without even the implied warranty of | 10  * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
| (...skipping 30 matching lines...) Expand all  Loading... | 
| 41 | 41 | 
| 42 import sunlabs.brazil.server.Request; | 42 import sunlabs.brazil.server.Request; | 
| 43 import sunlabs.brazil.server.Server; | 43 import sunlabs.brazil.server.Server; | 
| 44 import sunlabs.brazil.util.MatchString; | 44 import sunlabs.brazil.util.MatchString; | 
| 45 import sunlabs.brazil.util.http.HttpInputStream; | 45 import sunlabs.brazil.util.http.HttpInputStream; | 
| 46 import sunlabs.brazil.util.http.HttpRequest; | 46 import sunlabs.brazil.util.http.HttpRequest; | 
| 47 import android.util.Log; | 47 import android.util.Log; | 
| 48 | 48 | 
| 49 /** | 49 /** | 
| 50  * The <code>RequestHandler</code> implements a proxy service optionally | 50  * The <code>RequestHandler</code> implements a proxy service optionally | 
| 51  * modifying output. | 51  * modifying output. The | 
| 52  * The following configuration parameters are used to initialize this | 52  * following configuration parameters are used to initialize this | 
| 53  * <code>Handler</code>: | 53  * <code>Handler</code>: | 
| 54  * <dl class=props> | 54  * <dl class=props> | 
| 55  * | 55  * | 
| 56  * <dt>prefix, suffix, glob, match | 56  * <dt>prefix, suffix, glob, match | 
| 57  * <dd>Specify the URL that triggers this handler. (See {@link MatchString}). | 57  * <dd>Specify the URL that triggers this handler. (See {@link MatchString}). | 
| 58  * <dt>auth | 58  * <dt>auth | 
| 59  * <dd>The value of the proxy-authenticate header (if any) sent to the upstream | 59  * <dd>The value of the proxy-authenticate header (if any) sent to the upstream | 
| 60  * proxy | 60  * proxy | 
| 61  * <dt>proxyHost | 61  * <dt>proxyHost | 
| 62  * <dd>If specified, the name of the upstream proxy | 62  * <dd>If specified, the name of the upstream proxy | 
| (...skipping 12 matching lines...) Expand all  Loading... | 
| 75  * handler=adblock | 75  * handler=adblock | 
| 76  * adblock.class=org.adblockplus.brazil.RequestHandler | 76  * adblock.class=org.adblockplus.brazil.RequestHandler | 
| 77  * </pre> | 77  * </pre> | 
| 78  * | 78  * | 
| 79  * See the description under {@link sunlabs.brazil.server.Handler#respond | 79  * See the description under {@link sunlabs.brazil.server.Handler#respond | 
| 80  * respond} for a more detailed explanation. | 80  * respond} for a more detailed explanation. | 
| 81  */ | 81  */ | 
| 82 | 82 | 
| 83 public class RequestHandler extends BaseRequestHandler | 83 public class RequestHandler extends BaseRequestHandler | 
| 84 { | 84 { | 
|  | 85   private static final Pattern RE_HTTP = Pattern.compile("^https?:"); | 
|  | 86 | 
| 85   private AdblockPlus application; | 87   private AdblockPlus application; | 
| 86   private String via; | 88   private String via; | 
| 87   private static Pattern RE_HTTP = Pattern.compile("^https?:"); |  | 
| 88 | 89 | 
| 89   @Override | 90   @Override | 
| 90   public boolean init(Server server, String prefix) | 91   public boolean init(final Server server, final String prefix) | 
| 91   { | 92   { | 
| 92     super.init(server, prefix); | 93     super.init(server, prefix); | 
| 93 | 94 | 
| 94     application = AdblockPlus.getApplication(); | 95     application = AdblockPlus.getApplication(); | 
| 95     via = " " + server.hostName + ":" + server.listen.getLocalPort() + " (" + se
     rver.name + ")"; | 96     via = " " + server.hostName + ":" + server.listen.getLocalPort() + " (" + se
     rver.name + ")"; | 
| 96 | 97 | 
| 97     return true; | 98     return true; | 
| 98   } | 99   } | 
| 99 | 100 | 
| 100   @Override | 101   @Override | 
| 101   public boolean respond(Request request) throws IOException | 102   public boolean respond(final Request request) throws IOException | 
| 102   { | 103   { | 
| 103     boolean block = false; | 104     boolean block = false; | 
| 104 | 105 | 
| 105     try | 106     try | 
| 106     { | 107     { | 
| 107       block = application.matches(request.url, request.query, request.getRequest
     Header("referer"), request.getRequestHeader("accept")); | 108       block = application.matches(request.url, request.query, request.getRequest
     Header("referer"), request.getRequestHeader("accept")); | 
| 108     } | 109     } | 
| 109     catch (Exception e) | 110     catch (final Exception e) | 
| 110     { | 111     { | 
| 111       Log.e(prefix, "Filter error", e); | 112       Log.e(prefix, "Filter error", e); | 
| 112     } | 113     } | 
| 113 | 114 | 
| 114     request.log(Server.LOG_LOG, prefix, block + ": " + request.url); | 115     request.log(Server.LOG_LOG, prefix, block + ": " + request.url); | 
| 115 | 116 | 
| 116     int count = request.server.requestCount; | 117     int count = request.server.requestCount; | 
| 117     if (shouldLogHeaders) | 118     if (shouldLogHeaders) | 
| 118     { | 119     { | 
| 119       System.err.println(dumpHeaders(count, request, request.headers, true)); | 120       System.err.println(dumpHeaders(count, request, request.headers, true)); | 
| (...skipping 15 matching lines...) Expand all  Loading... | 
| 135 | 136 | 
| 136     if ((request.query != null) && (request.query.length() > 0)) | 137     if ((request.query != null) && (request.query.length() > 0)) | 
| 137     { | 138     { | 
| 138       url += "?" + request.query; | 139       url += "?" + request.query; | 
| 139     } | 140     } | 
| 140 | 141 | 
| 141     /* | 142     /* | 
| 142      * "Proxy-Connection" may be used (instead of just "Connection") | 143      * "Proxy-Connection" may be used (instead of just "Connection") | 
| 143      * to keep alive a connection between a client and this proxy. | 144      * to keep alive a connection between a client and this proxy. | 
| 144      */ | 145      */ | 
| 145     String pc = request.headers.get("Proxy-Connection"); | 146     final String pc = request.headers.get("Proxy-Connection"); | 
| 146     if (pc != null) | 147     if (pc != null) | 
| 147     { | 148     { | 
| 148       request.connectionHeader = "Proxy-Connection"; | 149       request.connectionHeader = "Proxy-Connection"; | 
| 149       request.keepAlive = pc.equalsIgnoreCase("Keep-Alive"); | 150       request.keepAlive = pc.equalsIgnoreCase("Keep-Alive"); | 
| 150     } | 151     } | 
| 151 | 152 | 
| 152     HttpRequest.removePointToPointHeaders(request.headers, false); | 153     HttpRequest.removePointToPointHeaders(request.headers, false); | 
| 153 | 154 | 
| 154     HttpRequest target = new HttpRequest(url); | 155     final HttpRequest target = new HttpRequest(url); | 
| 155     try | 156     try | 
| 156     { | 157     { | 
| 157       target.setMethod(request.method); | 158       target.setMethod(request.method); | 
| 158       request.headers.copyTo(target.requestHeaders); | 159       request.headers.copyTo(target.requestHeaders); | 
| 159 | 160 | 
| 160       if (proxyHost != null) | 161       if (proxyHost != null) | 
| 161       { | 162       { | 
| 162         target.setProxy(proxyHost, proxyPort); | 163         target.setProxy(proxyHost, proxyPort); | 
| 163         if (auth != null) | 164         if (auth != null) | 
| 164         { | 165         { | 
| 165           target.requestHeaders.add("Proxy-Authorization", auth); | 166           target.requestHeaders.add("Proxy-Authorization", auth); | 
| 166         } | 167         } | 
| 167       } | 168       } | 
| 168 | 169 | 
| 169       if (request.postData != null) | 170       if (request.postData != null) | 
| 170       { | 171       { | 
| 171         OutputStream out = target.getOutputStream(); | 172         final OutputStream out = target.getOutputStream(); | 
| 172         out.write(request.postData); | 173         out.write(request.postData); | 
| 173         out.close(); | 174         out.close(); | 
| 174       } | 175       } | 
| 175       else | 176       else | 
| 176       { | 177       { | 
| 177         target.setHttpInputStream(request.in); | 178         target.setHttpInputStream(request.in); | 
| 178       } | 179       } | 
| 179       target.connect(); | 180       target.connect(); | 
| 180 | 181 | 
| 181       if (shouldLogHeaders) | 182       if (shouldLogHeaders) | 
| 182       { | 183       { | 
| 183         System.err.println("      " + target.status + "\n" + dumpHeaders(count, 
     request, target.responseHeaders, false)); | 184         System.err.println("      " + target.status + "\n" + dumpHeaders(count, 
     request, target.responseHeaders, false)); | 
| 184       } | 185       } | 
| 185       HttpRequest.removePointToPointHeaders(target.responseHeaders, true); | 186       HttpRequest.removePointToPointHeaders(target.responseHeaders, true); | 
| 186 | 187 | 
| 187       request.setStatus(target.getResponseCode()); | 188       request.setStatus(target.getResponseCode()); | 
| 188       target.responseHeaders.copyTo(request.responseHeaders); | 189       target.responseHeaders.copyTo(request.responseHeaders); | 
| 189       try | 190       try | 
| 190       { | 191       { | 
| 191         request.responseHeaders.add("Via", target.status.substring(0, 8) + via); | 192         request.responseHeaders.add("Via", target.status.substring(0, 8) + via); | 
| 192       } | 193       } | 
| 193       catch (StringIndexOutOfBoundsException e) | 194       catch (final StringIndexOutOfBoundsException e) | 
| 194       { | 195       { | 
| 195         request.responseHeaders.add("Via", via); | 196         request.responseHeaders.add("Via", via); | 
| 196       } | 197       } | 
| 197 | 198 | 
| 198       // Detect if we need to add ElemHide filters | 199       // Detect if we need to add ElemHide filters | 
| 199       String type = request.responseHeaders.get("Content-Type"); | 200       final String type = request.responseHeaders.get("Content-Type"); | 
| 200 | 201 | 
| 201       String[] selectors = null; | 202       String[] selectors = null; | 
| 202       if (type != null && type.toLowerCase().startsWith("text/html")) | 203       if (type != null && type.toLowerCase().startsWith("text/html")) | 
| 203       { | 204       { | 
| 204         String reqHost = ""; | 205         String reqHost = ""; | 
| 205 | 206 | 
| 206         try | 207         try | 
| 207         { | 208         { | 
| 208           reqHost = (new URL(request.url)).getHost(); | 209           reqHost = (new URL(request.url)).getHost(); | 
| 209         } | 210         } | 
| 210         catch (MalformedURLException e) | 211         catch (final MalformedURLException e) | 
| 211         { | 212         { | 
| 212           // We are transparent, it's not our deal if it's malformed. | 213           // We are transparent, it's not our deal if it's malformed. | 
| 213         } | 214         } | 
| 214 | 215 | 
| 215         selectors = application.getSelectorsForDomain(reqHost); | 216         selectors = application.getSelectorsForDomain(reqHost); | 
| 216       } | 217       } | 
| 217       // If no filters are applicable just pass through the response | 218       // If no filters are applicable just pass through the response | 
| 218       if (selectors == null || target.getResponseCode() != 200) | 219       if (selectors == null || target.getResponseCode() != 200) | 
| 219       { | 220       { | 
| 220         int contentLength = target.getContentLength(); | 221         final int contentLength = target.getContentLength(); | 
| 221         if (contentLength == 0) | 222         if (contentLength == 0) | 
| 222         { | 223         { | 
| 223           // we do not use request.sendResponse to avoid arbitrary | 224           // we do not use request.sendResponse to avoid arbitrary | 
| 224           // 200 -> 204 response code conversion | 225           // 200 -> 204 response code conversion | 
| 225           request.sendHeaders(-1, null, -1); | 226           request.sendHeaders(-1, null, -1); | 
| 226         } | 227         } | 
| 227         else | 228         else | 
| 228         { | 229         { | 
| 229           request.sendResponse(target.getInputStream(), contentLength, null, -1)
     ; | 230           request.sendResponse(target.getInputStream(), contentLength, null, -1)
     ; | 
| 230         } | 231         } | 
| 231       } | 232       } | 
| 232       // Insert filters otherwise | 233       // Insert filters otherwise | 
| 233       else | 234       else | 
| 234       { | 235       { | 
| 235         HttpInputStream his = target.getInputStream(); | 236         final HttpInputStream his = target.getInputStream(); | 
| 236         int size = target.getContentLength(); | 237         int size = target.getContentLength(); | 
| 237         if (size < 0) | 238         if (size < 0) | 
| 238         { | 239         { | 
| 239           size = Integer.MAX_VALUE; | 240           size = Integer.MAX_VALUE; | 
| 240         } | 241         } | 
| 241 | 242 | 
| 242         FilterInputStream in = null; | 243         FilterInputStream in = null; | 
| 243         FilterOutputStream out = null; | 244         FilterOutputStream out = null; | 
| 244 | 245 | 
| 245         // Detect if content needs decoding | 246         // Detect if content needs decoding | 
| (...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
| 283         { | 284         { | 
| 284           final Matcher matcher = Pattern.compile("charset=([^;]*)").matcher(con
     tentType); | 285           final Matcher matcher = Pattern.compile("charset=([^;]*)").matcher(con
     tentType); | 
| 285           if (matcher.matches()) | 286           if (matcher.matches()) | 
| 286           { | 287           { | 
| 287             try | 288             try | 
| 288             { | 289             { | 
| 289               final String extractedCharsetName = matcher.group(0); | 290               final String extractedCharsetName = matcher.group(0); | 
| 290               Charset.forName(extractedCharsetName); | 291               Charset.forName(extractedCharsetName); | 
| 291               charsetName = extractedCharsetName; | 292               charsetName = extractedCharsetName; | 
| 292             } | 293             } | 
| 293             catch (IllegalArgumentException e) | 294             catch (final IllegalArgumentException e) | 
| 294             { | 295             { | 
| 295               Log.e(prefix, "Unsupported site charset, falling back to " + chars
     etName, e); | 296               Log.e(prefix, "Unsupported site charset, falling back to " + chars
     etName, e); | 
| 296             } | 297             } | 
| 297           } | 298           } | 
| 298         } | 299         } | 
| 299 | 300 | 
| 300         request.sendHeaders(-1, null, -1); | 301         request.sendHeaders(-1, null, -1); | 
| 301 | 302 | 
| 302         byte[] buf = new byte[Math.min(4096, size)]; | 303         final byte[] buf = new byte[Math.min(4096, size)]; | 
| 303 | 304 | 
| 304         boolean sent = selectors == null; | 305         boolean sent = selectors == null; | 
| 305         BoyerMoore matcher = new BoyerMoore("<html".getBytes()); | 306         final BoyerMoore matcher = new BoyerMoore("<html".getBytes()); | 
| 306 | 307 | 
| 307         while (size > 0) | 308         while (size > 0) | 
| 308         { | 309         { | 
| 309           out.flush(); | 310           out.flush(); | 
| 310 | 311 | 
| 311           count = in.read(buf, 0, Math.min(buf.length, size)); | 312           count = in.read(buf, 0, Math.min(buf.length, size)); | 
| 312           if (count < 0) | 313           if (count < 0) | 
| 313           { | 314           { | 
| 314             break; | 315             break; | 
| 315           } | 316           } | 
| 316           size -= count; | 317           size -= count; | 
| 317           try | 318           try | 
| 318           { | 319           { | 
| 319             // Search for <html> tag | 320             // Search for <html> tag | 
| 320             if (!sent && count > 0) | 321             if (!sent && count > 0) | 
| 321             { | 322             { | 
| 322               List<Integer> matches = matcher.match(buf, 0, count); | 323               final List<Integer> matches = matcher.match(buf, 0, count); | 
| 323               if (!matches.isEmpty()) | 324               if (!matches.isEmpty()) | 
| 324               { | 325               { | 
| 325                 // Add filters right before match | 326                 // Add filters right before match | 
| 326                 int m = matches.get(0); | 327                 final int m = matches.get(0); | 
| 327                 out.write(buf, 0, m); | 328                 out.write(buf, 0, m); | 
| 328                 out.write("<style type=\"text/css\">\n".getBytes()); | 329                 out.write("<style type=\"text/css\">\n".getBytes()); | 
| 329                 out.write(StringUtils.join(selectors, ",\r\n").getBytes(charsetN
     ame)); | 330                 out.write(StringUtils.join(selectors, ",\r\n").getBytes(charsetN
     ame)); | 
| 330                 out.write("{ display: none !important }</style>\n".getBytes()); | 331                 out.write("{ display: none !important }</style>\n".getBytes()); | 
| 331                 out.write(buf, m, count - m); | 332                 out.write(buf, m, count - m); | 
| 332                 sent = true; | 333                 sent = true; | 
| 333                 continue; | 334                 continue; | 
| 334               } | 335               } | 
| 335             } | 336             } | 
| 336             out.write(buf, 0, count); | 337             out.write(buf, 0, count); | 
| 337           } | 338           } | 
| 338           catch (IOException e) | 339           catch (final IOException e) | 
| 339           { | 340           { | 
| 340             break; | 341             break; | 
| 341           } | 342           } | 
| 342         } | 343         } | 
| 343         // The correct way would be to close ChunkedOutputStream | 344         // The correct way would be to close ChunkedOutputStream | 
| 344         // but we can not do it because underlying output stream is | 345         // but we can not do it because underlying output stream is | 
| 345         // used later in caller code. So we use this ugly hack: | 346         // used later in caller code. So we use this ugly hack: | 
| 346         if (out instanceof ChunkedOutputStream) | 347         if (out instanceof ChunkedOutputStream) | 
| 347           ((ChunkedOutputStream) out).writeFinalChunk(); | 348           ((ChunkedOutputStream) out).writeFinalChunk(); | 
| 348       } | 349       } | 
| 349     } | 350     } | 
| 350     catch (InterruptedIOException e) | 351     catch (final InterruptedIOException e) | 
| 351     { | 352     { | 
| 352       /* | 353       /* | 
| 353        * Read timeout while reading from the remote side. We use a | 354        * Read timeout while reading from the remote side. We use a | 
| 354        * read timeout in case the target never responds. | 355        * read timeout in case the target never responds. | 
| 355        */ | 356        */ | 
| 356       request.sendError(408, "Timeout / No response"); | 357       request.sendError(408, "Timeout / No response"); | 
| 357     } | 358     } | 
| 358     catch (EOFException e) | 359     catch (final EOFException e) | 
| 359     { | 360     { | 
| 360       request.sendError(500, "No response"); | 361       request.sendError(500, "No response"); | 
| 361     } | 362     } | 
| 362     catch (UnknownHostException e) | 363     catch (final UnknownHostException e) | 
| 363     { | 364     { | 
| 364       request.sendError(500, "Unknown host"); | 365       request.sendError(500, "Unknown host"); | 
| 365     } | 366     } | 
| 366     catch (ConnectException e) | 367     catch (final ConnectException e) | 
| 367     { | 368     { | 
| 368       request.sendError(500, "Connection refused"); | 369       request.sendError(500, "Connection refused"); | 
| 369     } | 370     } | 
| 370     catch (IOException e) | 371     catch (final IOException e) | 
| 371     { | 372     { | 
| 372       /* | 373       /* | 
| 373        * An IOException will happen if we can't communicate with the | 374        * An IOException will happen if we can't communicate with the | 
| 374        * target or the client. Rather than attempting to discriminate, | 375        * target or the client. Rather than attempting to discriminate, | 
| 375        * just send an error message to the client, and let the send | 376        * just send an error message to the client, and let the send | 
| 376        * fail if the client was the one that was in error. | 377        * fail if the client was the one that was in error. | 
| 377        */ | 378        */ | 
| 378 | 379 | 
| 379       String msg = "Error from proxy"; | 380       String msg = "Error from proxy"; | 
| 380       if (e.getMessage() != null) | 381       if (e.getMessage() != null) | 
| 381       { | 382       { | 
| 382         msg += ": " + e.getMessage(); | 383         msg += ": " + e.getMessage(); | 
| 383       } | 384       } | 
| 384       request.sendError(500, msg); | 385       request.sendError(500, msg); | 
| 385       Log.e(prefix, msg, e); | 386       Log.e(prefix, msg, e); | 
| 386     } | 387     } | 
| 387     finally | 388     finally | 
| 388     { | 389     { | 
| 389       target.close(); | 390       target.close(); | 
| 390     } | 391     } | 
| 391     return true; | 392     return true; | 
| 392   } | 393   } | 
| 393 } | 394 } | 
| OLD | NEW | 
|---|