working mostly
[adBlock.git] / src / de / ub0r / android / adBlock / Proxy.java
blob2fc949224851860c50da254a54a5a09b193d8f05
1 /*
2 * Copyright (C) 2009 Felix Bechstein
3 *
4 * This file is part of AdBlock.
5 *
6 * This program is free software; you can redistribute it and/or modify it under
7 * the terms of the GNU General Public License as published by the Free Software
8 * Foundation; either version 3 of the License, or (at your option) any later
9 * version.
11 * This program is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
14 * details.
16 * You should have received a copy of the GNU General Public License along with
17 * this program; If not, see <http://www.gnu.org/licenses/>.
19 package de.ub0r.android.adBlock;
21 import java.io.BufferedInputStream;
22 import java.io.BufferedReader;
23 import java.io.BufferedWriter;
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.io.InputStreamReader;
27 import java.io.OutputStream;
28 import java.io.OutputStreamWriter;
29 import java.net.InetSocketAddress;
30 import java.net.ServerSocket;
31 import java.net.Socket;
32 import java.net.URL;
33 import java.util.ArrayList;
35 import android.app.Service;
36 import android.content.Intent;
37 import android.content.SharedPreferences;
38 import android.os.IBinder;
39 import android.preference.PreferenceManager;
40 import android.util.Log;
41 import android.widget.Toast;
43 /**
44 * This ad blocking Proxy Service will work as an ordinary HTTP proxy. Set APN's
45 * proxy preferences to proxy's connection parameters.
47 * @author Felix Bechstein
49 public class Proxy extends Service implements Runnable {
51 /** Preferences: Port. */
52 static final String PREFS_PORT = "port";
53 /** Preferences: Filter. */
54 static final String PREFS_FILTER = "filter";
56 /** HTTP Response: blocked. */
57 private static final String HTTP_BLOCK = "HTTP/1.1 500 blocked by AdBlock";
58 /** HTTP Response: error. */
59 private static final String HTTP_ERROR = "HTTP/1.1 500 error by AdBlock";
60 /** HTTP Response: connected. */
61 private static final String HTTP_CONNECTED = "HTTP/1.1 200 connected";
62 /** HTTP Response: flush. */
63 private static final String HTTP_RESPONSE = "\n\n";
65 /** Default Port for HTTP. */
66 private static final int PORT_HTTP = 80;
67 /** Default Port for HTTPS. */
68 private static final int PORT_HTTPS = 443;
70 /** Proxy. */
71 private Thread proxy = null;
72 /** Proxy's port. */
73 private int port = -1;
74 /** Proxy's filter. */
75 private ArrayList<String> filter = new ArrayList<String>();
76 /** Stop proxy? */
77 private boolean stop = false;
79 /** Tag for output. */
80 private static final String TAG = "AdBlock.Proxy";
82 /**
83 * Connection handles a single HTTP Connection. Run this as a Thread.
85 * @author Felix Bechstein
87 private class Connection implements Runnable {
89 /** Local Socket. */
90 private final Socket local;
91 /** Remote Socket. */
92 private Socket remote;
93 /** Time to wait for new input. */
94 private static final long SLEEP = 100;
96 /**
97 * CopyStream reads one stream and writes it's data into an other
98 * stream. Run this as a Thread.
100 * @author Felix Bechstein
102 private class CopyStream implements Runnable {
103 /** Reader. */
104 private final InputStream reader;
105 /** Writer. */
106 private final OutputStream writer;
108 /** Size of buffer. */
109 private static final short BUFFSIZE = 512;
112 * Constructor.
114 * @param r
115 * reader
116 * @param w
117 * writer
119 public CopyStream(final InputStream r, final OutputStream w) {
120 this.reader = new BufferedInputStream(r, BUFFSIZE);
121 this.writer = w;
125 * Run by Thread.start().
127 @Override
128 public void run() {
129 try {
130 byte[] buf = new byte[BUFFSIZE];
131 int read = 0;
132 while (true) {
133 read = this.reader.available();
134 if (read < 1 || read > BUFFSIZE) {
135 read = BUFFSIZE;
137 read = this.reader.read(buf, 0, read);
138 if (read < 0) {
139 break;
141 // s = new String(buf, 0, read);
142 // Log.d(TAG, s);
143 this.writer.write(buf, 0, read);
144 this.writer.flush();
146 this.writer.close();
147 Connection.this.close();
148 } catch (IOException e) {
149 Log.e(TAG, null, e);
155 * Constructor.
157 * @param socket
158 * local Socket
160 public Connection(final Socket socket) {
161 this.local = socket;
165 * Check if URL is blocked.
167 * @param url
168 * URL
169 * @return if URL is blocked?
171 private boolean checkURL(final String url) {
172 if (url.indexOf("admob") >= 0 || url.indexOf("google") >= 0) {
173 return false;
175 for (String f : Proxy.this.filter) {
176 if (url.indexOf(f) >= 0) {
177 return true;
180 return false;
184 * Read in HTTP Header. Parse for URL to connect to.
186 * @param reader
187 * buffer reader from which we read the header
188 * @param buffer
189 * buffer into which the header is written
190 * @return URL to which we should connect, port other than 80 is given
191 * explicitly
192 * @throws IOException
193 * inner IOException
195 private URL readHeader(final BufferedReader reader,
196 final StringBuilder buffer) throws IOException {
197 URL ret = null;
198 String[] strings;
199 // read first line
200 while (!reader.ready() && this.local.isConnected()) {
201 try {
202 Thread.sleep(SLEEP);
203 } catch (InterruptedException e) {
204 Log.e(TAG, null, e);
207 if (!this.local.isConnected()) {
208 return null;
210 String line = reader.readLine();
211 if (line == null) {
212 return null;
214 buffer.append(line + "\n");
215 strings = line.split(" ");
216 if (strings.length > 1) {
217 if (strings[0].equals("CONNECT")) {
218 String targetHost = strings[1];
219 int targetPort = PORT_HTTPS;
220 strings = targetHost.split(":");
221 if (strings.length > 1) {
222 targetPort = Integer.parseInt(strings[1]);
223 targetHost = strings[0];
225 ret = new URL("https://" + targetHost + ":" + targetPort);
226 } else if (strings[0].equals("GET")
227 || strings[1].equals("POST")) {
228 String path = null;
229 if (strings[1].startsWith("http://")) {
230 ret = new URL(strings[1]);
231 path = ret.getPath();
232 } else {
233 path = strings[1];
235 // read header
236 while (true) {
237 line = reader.readLine();
238 buffer.append(line + "\n");
239 if (line.length() == 0) {
240 break;
242 if (line.startsWith("Host:")) {
243 strings = line.split(" ");
244 if (strings.length > 1) {
245 ret = new URL("http://" + strings[1] + path);
247 break;
252 strings = null;
254 // copy rest of reader's buffer
255 while (reader.ready()) {
256 buffer.append(reader.readLine() + "\n");
258 return ret;
262 * Close local and remote socket.
264 * @throws IOException
265 * IOException
267 private void close() throws IOException {
268 if (this.remote != null) {
269 if (this.remote.isConnected()) {
270 try {
271 this.remote.shutdownInput();
272 this.remote.shutdownOutput();
273 } catch (IOException e) {
274 // Log.e(TAG, null, e);
276 this.remote.close();
278 this.remote = null;
280 if (this.local != null) {
281 if (this.local.isConnected()) {
282 try {
283 this.local.shutdownOutput();
284 this.local.shutdownInput();
285 } catch (IOException e) {
286 // Log.e(TAG, null, e);
289 this.local.close();
295 * Run by Thread.start().
297 @Override
298 public void run() {
299 InputStream lInStream;
300 OutputStream lOutStream;
301 BufferedReader lReader;
302 BufferedWriter lWriter;
303 try {
304 lInStream = this.local.getInputStream();
305 lOutStream = this.local.getOutputStream();
306 lReader = new BufferedReader(new InputStreamReader(lInStream),
307 CopyStream.BUFFSIZE);
308 lWriter = new BufferedWriter(
309 new OutputStreamWriter(lOutStream), CopyStream.BUFFSIZE);
310 } catch (IOException e) {
311 Log.e(TAG, null, e);
312 return;
314 try {
315 InputStream rInStream = null;
316 OutputStream rOutStream = null;
317 BufferedWriter remoteWriter = null;
318 Thread remoteThread = null;
319 StringBuilder buffer = new StringBuilder();
320 boolean block = false;
321 String tHost = null;
322 int tPort = -1;
323 URL url;
324 boolean connectSSL = false;
325 while (this.local.isConnected()) {
326 buffer = new StringBuilder();
327 url = this.readHeader(lReader, buffer);
328 if (this.local.isConnected() && remoteThread != null
329 && !remoteThread.isAlive()) {
330 remoteThread.join();
331 tHost = null;
332 if (connectSSL) {
333 this.local.close();
335 this.remote.shutdownInput();
336 this.remote.shutdownOutput();
337 this.remote.close();
338 this.remote = null;
339 rInStream = null;
340 rOutStream = null;
341 remoteThread = null;
343 if (url != null) {
344 block = this.checkURL(url.toString());
345 Log.d(TAG, "new url: " + url.toString());
346 if (!block) {
347 // new connection needed?
348 int p = url.getPort();
349 if (p < 0) {
350 p = PORT_HTTP;
352 if (tHost == null || !tHost.equals(url.getHost())
353 || tPort != p) {
354 // create new connection
355 if (this.remote != null) {
356 this.remote.shutdownInput();
357 this.remote.shutdownOutput();
359 tHost = url.getHost();
360 tPort = url.getPort();
361 if (tPort < 0) {
362 tPort = PORT_HTTP;
364 Log.d(TAG, "new socket: " + url.toString());
365 this.remote = new Socket();
366 this.remote.connect(new InetSocketAddress(
367 tHost, tPort));
368 rInStream = this.remote.getInputStream();
369 rOutStream = this.remote.getOutputStream();
370 remoteThread = new Thread(new CopyStream(
371 rInStream, lOutStream));
372 remoteThread.start();
373 if (url.getProtocol().startsWith("https")) {
374 connectSSL = true;
375 lWriter.write(HTTP_CONNECTED
376 + HTTP_RESPONSE);
377 lWriter.flush();
378 // copy local to remote by blocks
379 Thread t2 = new Thread(new CopyStream(
380 lInStream, rOutStream));
382 t2.start();
383 remoteWriter = null;
384 break; // copy in separate thread. break
385 // while here
386 } else {
387 remoteWriter = new BufferedWriter(
388 new OutputStreamWriter(rOutStream),
389 CopyStream.BUFFSIZE);
394 // push data to remote if not blocked
395 if (block) {
396 lWriter.append(HTTP_BLOCK + HTTP_RESPONSE
397 + "BLOCKED by AdBlock!");
398 lWriter.flush();
399 } else if (this.remote != null && this.remote.isConnected()
400 && remoteWriter != null) {
401 remoteWriter.append(buffer);
402 remoteWriter.flush();
405 } catch (Exception e) {
406 Log.e(TAG, null, e);
407 try {
408 lWriter.append(HTTP_ERROR + " - " + e.toString()
409 + HTTP_RESPONSE + e.toString());
410 lWriter.flush();
411 lWriter.close();
412 this.local.close();
413 } catch (IOException e1) {
414 Log.e(TAG, null, e1);
417 Log.d(TAG, "close connection");
422 * Default Implementation.
424 * @param intent
425 * called Intent
426 * @return IBinder
427 * @see android.app.Service#onBind(android.content.Intent)
429 @Override
430 public final IBinder onBind(final Intent intent) {
431 return null;
435 * Called on start.
437 * @param intent
438 * Intent called
439 * @param startId
440 * start ID
442 @Override
443 public final void onStart(final Intent intent, final int startId) {
444 super.onStart(intent, startId);
446 // Don't kill me!
447 this.setForeground(true);
449 SharedPreferences preferences = PreferenceManager
450 .getDefaultSharedPreferences(this);
451 int p = Integer.parseInt(preferences.getString(PREFS_PORT, "8080"));
452 boolean portChanged = p != this.port;
453 this.port = p;
455 String f = preferences.getString(PREFS_FILTER, "");
456 this.filter.clear();
457 for (String s : f.split("\n")) {
458 if (s.length() > 0) {
459 this.filter.add(s);
462 if (this.proxy == null) {
463 // Toast.makeText(this, "starting proxy on port: " + this.port,
464 // Toast.LENGTH_SHORT).show();
465 this.proxy = new Thread(this);
466 this.proxy.start();
467 } else {
468 Toast.makeText(this, "proxy running on port " + this.port,
469 Toast.LENGTH_SHORT).show();
470 if (portChanged) {
471 this.proxy.interrupt();
472 this.proxy = new Thread(this);
473 this.proxy.start();
479 * Called on destroy.
481 @Override
482 public final void onDestroy() {
483 super.onDestroy();
484 Toast.makeText(this, "stopping proxy..", Toast.LENGTH_LONG).show();
485 this.stop = true;
489 * Run by Thread.start().
491 @Override
492 public final void run() {
493 try {
494 int p = this.port;
495 ServerSocket sock = new ServerSocket(p);
496 Socket client;
497 while (!this.stop && p == this.port) {
498 if (p != this.port) {
499 break;
501 client = sock.accept();
502 if (client != null) {
503 Log.d(TAG, "new client");
504 Thread t = new Thread(new Connection(client));
505 t.start();
508 sock.close();
509 } catch (IOException e) {
510 Log.e(TAG, null, e);