decrease buffer
[adBlock.git] / src / de / ub0r / android / adBlock / Proxy.java
blob391bc21d2baad33b26b661a8e0277f6d6854585a
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 /** Preferences: Port. */
51 static final String PREFS_PORT = "port";
52 /** Preferences: Filter. */
53 static final String PREFS_FILTER = "filter";
55 /** HTTP Response: blocked. */
56 private static final String HTTP_BLOCK = "HTTP/1.1 500 blocked by AdBlock";
57 /** HTTP Response: error. */
58 private static final String HTTP_ERROR = "HTTP/1.1 500 error by AdBlock";
59 /** HTTP Response: connected. */
60 private static final String HTTP_CONNECTED = "HTTP/1.1 200 connected";
61 /** HTTP Response: flush. */
62 private static final String HTTP_RESPONSE = "\n\n";
64 /** Proxy. */
65 private Thread proxy = null;
66 /** Proxy's port. */
67 private int port = -1;
68 /** Proxy's filter. */
69 private ArrayList<String> filter = new ArrayList<String>();
70 /** Stop proxy? */
71 private boolean stop = false;
73 /** Tag for output. */
74 private static final String TAG = "AdBlock.Proxy";
76 /**
77 * Connection handles a single HTTP Connection. Run this as a Thread.
79 * @author Felix Bechstein
81 private class Connection implements Runnable {
83 /** Local Socket. */
84 private final Socket local;
85 /** Remote Socket. */
86 private Socket remote;
88 /**
89 * CopyStream reads one stream and writes it's data into an other
90 * stream. Run this as a Thread.
92 * @author Felix Bechstein
94 private class CopyStream implements Runnable {
95 /** Reader. */
96 private final InputStream reader;
97 /** Writer. */
98 private final OutputStream writer;
99 /** Object to notify with at EOF. */
100 private final Object sync;
101 /** Size of buffer. */
102 private static final short BUFFSIZE = 256;
105 * Constructor.
107 * @param r
108 * reader
109 * @param w
110 * writer
111 * @param s
112 * object to sync with
114 public CopyStream(final InputStream r, final OutputStream w,
115 final Object s) {
116 this.reader = r;
117 this.writer = w;
118 this.sync = s;
122 * Run by Thread.start().
124 @Override
125 public void run() {
126 try {
127 byte[] buf = new byte[BUFFSIZE];
128 int read = -1;
129 while (true) {
130 read = this.reader.read(buf);
131 if (read < 0) {
132 break;
134 this.writer.write(buf, 0, read);
135 this.writer.flush();
137 synchronized (this.sync) {
138 this.sync.notify();
140 } catch (IOException e) {
141 Log.e(TAG, null, e);
147 * Constructor.
149 * @param socket
150 * local Socket
151 * @param context
152 * global Context
154 public Connection(final Socket socket) {
155 this.local = socket;
159 * Check if URL is blocked.
161 * @param url
162 * URL
163 * @return if URL is blocked?
165 private boolean checkURL(final String url) {
166 if (url.indexOf("admob") >= 0 || url.indexOf("google") >= 0) {
167 return false;
169 for (String f : Proxy.this.filter) {
170 if (url.indexOf(f) >= 0) {
171 return true;
174 return false;
178 * Run by Thread.start().
180 @Override
181 public void run() {
182 try {
183 InputStream localInputStream = this.local.getInputStream();
184 OutputStream localOutputStream = this.local.getOutputStream();
185 BufferedReader localReader = new BufferedReader(
186 new InputStreamReader(localInputStream),
187 CopyStream.BUFFSIZE);
188 BufferedWriter localWriter = new BufferedWriter(
189 new OutputStreamWriter(localOutputStream),
190 CopyStream.BUFFSIZE);
191 try {
192 InputStream remoteInputStream = null;
193 OutputStream remoteOutputStream = null;
194 StringBuilder buffer = new StringBuilder();
195 String s;
196 boolean firstLine = true;
197 boolean block = false;
198 boolean uncompleteURL = true;
199 boolean connectHTTPS = false;
200 String url = null;
201 String targetHost = null;
202 int targetPort = -1;
203 while (this.remote == null && !block) {
204 s = localReader.readLine();
205 buffer.append(s + "\n");
206 Log.v(TAG, s);
207 if (firstLine) {
208 url = s.split(" ")[1];
209 if (s.startsWith("CONNECT ")) {
210 targetPort = 443;
211 targetHost = url;
212 int i = targetHost.indexOf(':');
213 if (i > 0) {
214 targetPort = Integer.parseInt(targetHost
215 .substring(i + 1));
216 targetHost = targetHost.substring(0, i);
218 connectHTTPS = true;
219 } else if (url.startsWith("http:")) {
220 uncompleteURL = false;
221 block = this.checkURL(url);
222 } else {
223 uncompleteURL = true;
225 firstLine = false;
227 if (!block && s.startsWith("Host:")) {
228 // init remote socket
229 targetPort = 80;
230 targetHost = s.substring(6).trim();
231 int i = targetHost.indexOf(':');
232 if (i > 0) {
233 targetPort = Integer.parseInt(targetHost
234 .substring(i + 1));
235 targetHost = targetHost.substring(0, i);
237 if (uncompleteURL) {
238 url = targetHost + url;
239 block = this.checkURL(url);
241 if (block) {
242 break;
244 } else if (!block && s.length() == 0) { // end of header
245 if (url.startsWith("http")) {
246 URL u = new URL(url);
247 targetHost = u.getHost();
248 targetPort = u.getPort();
249 if (targetPort < 0) {
250 targetPort = 80;
252 } else {
253 localWriter.append(HTTP_ERROR
254 + " - PROTOCOL ERROR" + HTTP_RESPONSE
255 + "PROTOCOL ERROR");
256 localWriter.flush();
257 localWriter.close();
258 this.local.close();
259 break;
262 if (targetHost != null && targetPort > 0) {
263 Log.v(TAG, "connect to " + targetHost + " "
264 + targetPort);
265 this.remote = new Socket();
266 this.remote.connect(new InetSocketAddress(
267 targetHost, targetPort));
268 remoteInputStream = this.remote.getInputStream();
269 remoteOutputStream = this.remote.getOutputStream();
270 if (connectHTTPS) {
271 localWriter.write(HTTP_CONNECTED
272 + HTTP_RESPONSE);
273 } else {
274 BufferedWriter remoteWriter = new BufferedWriter(
275 new OutputStreamWriter(
276 remoteOutputStream),
277 CopyStream.BUFFSIZE);
278 while (localReader.ready()) {
279 buffer
280 .append(localReader.readLine()
281 + "\n");
283 remoteWriter.append(buffer);
284 remoteWriter.flush();
286 buffer = null;
287 // remoteWriter.close(); // writer.close() will
288 // close underlying socket!
291 if (this.remote != null && this.remote.isConnected()) {
292 Object sync = new Object();
293 localWriter.flush();
294 localOutputStream.flush();
295 remoteOutputStream.flush();
296 Thread t1 = new Thread(new CopyStream(
297 remoteInputStream, localOutputStream, sync));
298 Thread t2 = new Thread(new CopyStream(localInputStream,
299 remoteOutputStream, sync));
300 try {
301 synchronized (sync) {
302 t1.start();
303 t2.start();
304 sync.wait();
306 } catch (InterruptedException e) {
307 Log.e(TAG, null, e);
309 this.local.shutdownInput();
310 this.remote.shutdownInput();
311 this.local.shutdownOutput();
312 this.remote.shutdownOutput();
313 this.remote.close();
314 this.local.close();
315 t1.join();
316 t2.join();
317 } else if (block) {
318 while (localReader.ready()) {
319 localReader.readLine();
321 localWriter.append(HTTP_BLOCK + HTTP_RESPONSE
322 + "BLOCKED by AdBlock!");
323 localWriter.flush();
324 localWriter.close();
325 this.local.close();
327 } catch (InterruptedException e) {
328 // do nothing
329 } catch (NullPointerException e) {
330 // do nothing
331 } catch (Exception e) {
332 Log.e(TAG, null, e);
333 localWriter.append(HTTP_ERROR + " - " + e.toString()
334 + HTTP_RESPONSE + e.toString());
335 localWriter.flush();
336 localWriter.close();
337 this.local.close();
339 } catch (IOException e1) {
340 Log.e(TAG, null, e1);
346 * Default Implementation.
348 * @param intent
349 * called Intent
350 * @return IBinder
351 * @see android.app.Service#onBind(android.content.Intent)
353 @Override
354 public final IBinder onBind(final Intent intent) {
355 return null;
359 * Called on start.
361 * @param intent
362 * Intent called
363 * @param startId
364 * start ID
366 @Override
367 public final void onStart(final Intent intent, final int startId) {
368 super.onStart(intent, startId);
370 // Don't kill me!
371 this.setForeground(true);
373 SharedPreferences preferences = PreferenceManager
374 .getDefaultSharedPreferences(this);
375 int p = Integer.parseInt(preferences.getString(PREFS_PORT,
376 "8080"));
377 boolean portChanged = p != this.port;
378 this.port = p;
380 String f = preferences.getString(PREFS_FILTER, "");
381 this.filter.clear();
382 for (String s : f.split("\n")) {
383 if (s.length() > 0) {
384 this.filter.add(s);
387 if (this.proxy == null) {
388 // Toast.makeText(this, "starting proxy on port: " + this.port,
389 // Toast.LENGTH_SHORT).show();
390 this.proxy = new Thread(this);
391 this.proxy.start();
392 } else {
393 Toast.makeText(this, "proxy running on port " + this.port,
394 Toast.LENGTH_SHORT).show();
395 if (portChanged) {
396 this.proxy.interrupt();
397 this.proxy = new Thread(this);
398 this.proxy.start();
404 * Called on destroy.
406 @Override
407 public final void onDestroy() {
408 super.onDestroy();
409 Toast.makeText(this, "stopping proxy..", Toast.LENGTH_LONG).show();
410 this.stop = true;
414 * Run by Thread.start().
416 @Override
417 public final void run() {
418 try {
419 int p = this.port;
420 ServerSocket sock = new ServerSocket(p);
421 Socket client;
422 while (!this.stop && p == this.port) {
423 if (p != this.port) {
424 break;
426 client = sock.accept();
427 if (client != null) {
428 Thread t = new Thread(new Connection(client));
429 t.start();
432 sock.close();
433 } catch (IOException e) {
434 Log.e(TAG, null, e);