fix for the real world
[adBlock.git] / src / de / ub0r / android / adBlock / Proxy.java
blob1aab821c0c83d1e5a9a06ded38836a3bf78b75c7
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.BufferedReader;
22 import java.io.BufferedWriter;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.io.InputStreamReader;
26 import java.io.OutputStream;
27 import java.io.OutputStreamWriter;
28 import java.net.InetSocketAddress;
29 import java.net.ServerSocket;
30 import java.net.Socket;
31 import java.net.URL;
32 import java.util.ArrayList;
34 import android.app.Service;
35 import android.content.Intent;
36 import android.content.SharedPreferences;
37 import android.os.IBinder;
38 import android.preference.PreferenceManager;
39 import android.util.Log;
40 import android.widget.Toast;
42 /**
43 * This ad blocking Proxy Service will work as an ordinary HTTP proxy. Set APN's
44 * proxy preferences to proxy's connection parameters.
46 * @author Felix Bechstein
48 public class Proxy extends Service implements Runnable {
50 /** HTTP Response: blocked. */
51 private static final String HTTP_BLOCK = "HTTP/1.1 500 blocked by AdBlock";
52 /** HTTP Response: error. */
53 private static final String HTTP_ERROR = "HTTP/1.1 500 error by AdBlock";
54 /** HTTP Response: connected. */
55 private static final String HTTP_CONNECTED = "HTTP/1.1 200 connected";
56 /** HTTP Response: flush. */
57 private static final String HTTP_RESPONSE = "\n\n";
59 /** Proxy. */
60 private Thread proxy = null;
61 /** Proxy's port. */
62 private int port = -1;
63 /** Proxy's filter. */
64 private ArrayList<String> filter = new ArrayList<String>();
65 /** Stop proxy? */
66 private boolean stop = false;
68 /** Tag for output. */
69 private static final String TAG = "AdBlock.Proxy";
71 /**
72 * Connection handles a single HTTP Connection. Run this as a Thread.
74 * @author Felix Bechstein
76 private class Connection implements Runnable {
78 /** Local Socket. */
79 private final Socket local;
80 /** Remote Socket. */
81 private Socket remote;
83 /**
84 * CopyStream reads one stream and writes it's data into an other
85 * stream. Run this as a Thread.
87 * @author Felix Bechstein
89 private class CopyStream implements Runnable {
90 /** Reader. */
91 private final InputStream reader;
92 /** Writer. */
93 private final OutputStream writer;
94 /** Object to notify with at EOF. */
95 private final Object sync;
96 /** Size of buffer. */
97 private static final short BUFFSIZE = 1024;
99 /**
100 * Constructor.
102 * @param r
103 * reader
104 * @param w
105 * writer
106 * @param s
107 * object to sync with
109 public CopyStream(final InputStream r, final OutputStream w,
110 final Object s) {
111 this.reader = r;
112 this.writer = w;
113 this.sync = s;
117 * Run by Thread.start().
119 @Override
120 public void run() {
121 try {
122 byte[] buf = new byte[BUFFSIZE];
123 int read = -1;
124 while (true) {
125 read = this.reader.read(buf);
126 if (read < 0) {
127 break;
129 this.writer.write(buf, 0, read);
130 this.writer.flush();
132 synchronized (this.sync) {
133 this.sync.notify();
135 } catch (IOException e) {
136 Log.e(TAG, null, e);
142 * Constructor.
144 * @param socket
145 * local Socket
146 * @param context
147 * global Context
149 public Connection(final Socket socket) {
150 this.local = socket;
154 * Check if URL is blocked.
156 * @param url
157 * URL
158 * @return if URL is blocked?
160 private boolean checkURL(final String url) {
161 for (String f : Proxy.this.filter) {
162 if (url.indexOf(f) >= 0) {
163 return true;
166 return false;
170 * Run by Thread.start().
172 @Override
173 public void run() {
174 try {
175 InputStream localInputStream = this.local.getInputStream();
176 OutputStream localOutputStream = this.local.getOutputStream();
177 BufferedReader localReader = new BufferedReader(
178 new InputStreamReader(localInputStream),
179 CopyStream.BUFFSIZE);
180 BufferedWriter localWriter = new BufferedWriter(
181 new OutputStreamWriter(localOutputStream),
182 CopyStream.BUFFSIZE);
183 try {
184 InputStream remoteInputStream = null;
185 OutputStream remoteOutputStream = null;
186 StringBuilder buffer = new StringBuilder();
187 String s;
188 boolean firstLine = true;
189 boolean block = false;
190 boolean uncompleteURL = true;
191 boolean connectHTTPS = false;
192 String url = null;
193 String targetHost = null;
194 int targetPort = -1;
195 while (this.remote == null && !block) {
196 s = localReader.readLine();
197 buffer.append(s + "\n");
198 Log.v(TAG, s);
199 if (firstLine) {
200 url = s.split(" ")[1];
201 if (s.startsWith("CONNECT ")) {
202 targetPort = 443;
203 targetHost = url;
204 int i = targetHost.indexOf(':');
205 if (i > 0) {
206 targetPort = Integer.parseInt(targetHost
207 .substring(i + 1));
208 targetHost = targetHost.substring(0, i);
210 connectHTTPS = true;
211 } else if (url.startsWith("http:")) {
212 uncompleteURL = false;
213 block = this.checkURL(url);
214 } else {
215 uncompleteURL = true;
217 firstLine = false;
219 if (!block && s.startsWith("Host:")) {
220 // init remote socket
221 targetPort = 80;
222 targetHost = s.substring(6).trim();
223 int i = targetHost.indexOf(':');
224 if (i > 0) {
225 targetPort = Integer.parseInt(targetHost
226 .substring(i + 1));
227 targetHost = targetHost.substring(0, i);
229 if (uncompleteURL) {
230 url = targetHost + url;
231 block = this.checkURL(url);
233 if (block) {
234 break;
236 } else if (!block && s.length() == 0) { // end of header
237 if (url.startsWith("http")) {
238 URL u = new URL(url);
239 targetHost = u.getHost();
240 targetPort = u.getPort();
241 if (targetPort < 0) {
242 targetPort = 80;
244 } else {
245 localWriter.append(HTTP_ERROR
246 + " - PROTOCOL ERROR" + HTTP_RESPONSE
247 + "PROTOCOL ERROR");
248 localWriter.flush();
249 localWriter.close();
250 this.local.close();
251 break;
254 if (targetHost != null && targetPort > 0) {
255 Log.v(TAG, "connect to " + targetHost + " "
256 + targetPort);
257 this.remote = new Socket();
258 this.remote.connect(new InetSocketAddress(
259 targetHost, targetPort));
260 remoteInputStream = this.remote.getInputStream();
261 remoteOutputStream = this.remote.getOutputStream();
262 if (connectHTTPS) {
263 localWriter.write(HTTP_CONNECTED
264 + HTTP_RESPONSE);
265 } else {
266 BufferedWriter remoteWriter = new BufferedWriter(
267 new OutputStreamWriter(
268 remoteOutputStream),
269 CopyStream.BUFFSIZE);
270 while (localReader.ready()) {
271 buffer
272 .append(localReader.readLine()
273 + "\n");
275 remoteWriter.append(buffer);
276 remoteWriter.flush();
278 buffer = null;
279 // remoteWriter.close(); // writer.close() will
280 // close underlying socket!
283 if (this.remote != null && this.remote.isConnected()) {
284 Object sync = new Object();
285 localWriter.flush();
286 localOutputStream.flush();
287 remoteOutputStream.flush();
288 Thread t1 = new Thread(new CopyStream(
289 remoteInputStream, localOutputStream, sync));
290 Thread t2 = new Thread(new CopyStream(localInputStream,
291 remoteOutputStream, sync));
292 try {
293 synchronized (sync) {
294 t1.start();
295 t2.start();
296 sync.wait();
298 } catch (InterruptedException e) {
299 Log.e(TAG, null, e);
301 this.local.shutdownInput();
302 this.remote.shutdownInput();
303 this.local.shutdownOutput();
304 this.remote.shutdownOutput();
305 this.remote.close();
306 this.local.close();
307 t1.join();
308 t2.join();
309 } else if (block) {
310 while (localReader.ready()) {
311 localReader.readLine();
313 localWriter.append(HTTP_BLOCK + HTTP_RESPONSE
314 + "BLOCKED by AdBlock!");
315 localWriter.flush();
316 localWriter.close();
317 this.local.close();
319 } catch (InterruptedException e) {
320 // do nothing
321 } catch (NullPointerException e) {
322 // do nothing
323 } catch (Exception e) {
324 Log.e(TAG, null, e);
325 localWriter.append(HTTP_ERROR + " - " + e.toString()
326 + HTTP_RESPONSE + e.toString());
327 localWriter.flush();
328 localWriter.close();
329 this.local.close();
331 } catch (IOException e1) {
332 Log.e(TAG, null, e1);
338 * Default Implementation.
340 * @param intent
341 * called Intent
342 * @return IBinder
343 * @see android.app.Service#onBind(android.content.Intent)
345 @Override
346 public final IBinder onBind(final Intent intent) {
347 return null;
351 * Called on start.
353 * @param intent
354 * Intent called
355 * @param startId
356 * start ID
358 @Override
359 public final void onStart(final Intent intent, final int startId) {
360 super.onStart(intent, startId);
362 // Don't kill me!
363 this.setForeground(true);
365 SharedPreferences preferences = PreferenceManager
366 .getDefaultSharedPreferences(this);
367 int p = Integer.parseInt(preferences.getString(AdBlock.PREFS_PORT,
368 "8080"));
369 boolean portChanged = p != this.port;
370 this.port = p;
372 String f = preferences.getString(AdBlock.PREFS_FILTER, "");
373 this.filter.clear();
374 for (String s : f.split("\n")) {
375 if (s.length() > 0) {
376 this.filter.add(s);
379 if (this.proxy == null) {
380 // Toast.makeText(this, "starting proxy on port: " + this.port,
381 // Toast.LENGTH_SHORT).show();
382 this.proxy = new Thread(this);
383 this.proxy.start();
384 } else {
385 Toast.makeText(this, "proxy running on port " + this.port,
386 Toast.LENGTH_SHORT).show();
387 if (portChanged) {
388 this.proxy.interrupt();
389 this.proxy = new Thread(this);
390 this.proxy.start();
396 * Called on destroy.
398 @Override
399 public final void onDestroy() {
400 super.onDestroy();
401 Toast.makeText(this, "stopping proxy..", Toast.LENGTH_LONG).show();
402 this.stop = true;
406 * Run by Thread.start().
408 @Override
409 public final void run() {
410 try {
411 int p = this.port;
412 ServerSocket sock = new ServerSocket(p);
413 Socket client;
414 while (!this.stop && p == this.port) {
415 if (p != this.port) {
416 break;
418 client = sock.accept();
419 if (client != null) {
420 Thread t = new Thread(new Connection(client));
421 t.start();
424 sock.close();
425 } catch (IOException e) {
426 Log.e(TAG, null, e);