2 * Copyright (C) 2009 Felix Bechstein
4 * This file is part of AdBlock.
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
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
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
;
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
;
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";
65 private Thread proxy
= null;
67 private int port
= -1;
68 /** Proxy's filter. */
69 private ArrayList
<String
> filter
= new ArrayList
<String
>();
71 private boolean stop
= false;
73 /** Tag for output. */
74 private static final String TAG
= "AdBlock.Proxy";
77 * Connection handles a single HTTP Connection. Run this as a Thread.
79 * @author Felix Bechstein
81 private class Connection
implements Runnable
{
84 private final Socket local
;
86 private Socket remote
;
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
{
96 private final InputStream reader
;
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;
112 * object to sync with
114 public CopyStream(final InputStream r
, final OutputStream w
,
122 * Run by Thread.start().
127 byte[] buf
= new byte[BUFFSIZE
];
130 read
= this.reader
.read(buf
);
134 this.writer
.write(buf
, 0, read
);
137 synchronized (this.sync
) {
140 } catch (IOException e
) {
154 public Connection(final Socket socket
) {
159 * Check if URL is blocked.
163 * @return if URL is blocked?
165 private boolean checkURL(final String url
) {
166 if (url
.indexOf("admob") >= 0 || url
.indexOf("google") >= 0) {
169 for (String f
: Proxy
.this.filter
) {
170 if (url
.indexOf(f
) >= 0) {
178 * Run by Thread.start().
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
);
192 InputStream remoteInputStream
= null;
193 OutputStream remoteOutputStream
= null;
194 StringBuilder buffer
= new StringBuilder();
196 boolean firstLine
= true;
197 boolean block
= false;
198 boolean uncompleteURL
= true;
199 boolean connectHTTPS
= false;
201 String targetHost
= null;
203 while (this.remote
== null && !block
) {
204 s
= localReader
.readLine();
205 buffer
.append(s
+ "\n");
208 url
= s
.split(" ")[1];
209 if (s
.startsWith("CONNECT ")) {
212 int i
= targetHost
.indexOf(':');
214 targetPort
= Integer
.parseInt(targetHost
216 targetHost
= targetHost
.substring(0, i
);
219 } else if (url
.startsWith("http:")) {
220 uncompleteURL
= false;
221 block
= this.checkURL(url
);
223 uncompleteURL
= true;
227 if (!block
&& s
.startsWith("Host:")) {
228 // init remote socket
230 targetHost
= s
.substring(6).trim();
231 int i
= targetHost
.indexOf(':');
233 targetPort
= Integer
.parseInt(targetHost
235 targetHost
= targetHost
.substring(0, i
);
238 url
= targetHost
+ url
;
239 block
= this.checkURL(url
);
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) {
253 localWriter
.append(HTTP_ERROR
254 + " - PROTOCOL ERROR" + HTTP_RESPONSE
262 if (targetHost
!= null && targetPort
> 0) {
263 Log
.v(TAG
, "connect to " + targetHost
+ " "
265 this.remote
= new Socket();
266 this.remote
.connect(new InetSocketAddress(
267 targetHost
, targetPort
));
268 remoteInputStream
= this.remote
.getInputStream();
269 remoteOutputStream
= this.remote
.getOutputStream();
271 localWriter
.write(HTTP_CONNECTED
274 BufferedWriter remoteWriter
= new BufferedWriter(
275 new OutputStreamWriter(
277 CopyStream
.BUFFSIZE
);
278 while (localReader
.ready()) {
280 .append(localReader
.readLine()
283 remoteWriter
.append(buffer
);
284 remoteWriter
.flush();
287 // remoteWriter.close(); // writer.close() will
288 // close underlying socket!
291 if (this.remote
!= null && this.remote
.isConnected()) {
292 Object sync
= new Object();
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
));
301 synchronized (sync
) {
306 } catch (InterruptedException e
) {
309 this.local
.shutdownInput();
310 this.remote
.shutdownInput();
311 this.local
.shutdownOutput();
312 this.remote
.shutdownOutput();
318 while (localReader
.ready()) {
319 localReader
.readLine();
321 localWriter
.append(HTTP_BLOCK
+ HTTP_RESPONSE
322 + "BLOCKED by AdBlock!");
327 } catch (InterruptedException e
) {
329 } catch (NullPointerException e
) {
331 } catch (Exception e
) {
333 localWriter
.append(HTTP_ERROR
+ " - " + e
.toString()
334 + HTTP_RESPONSE
+ e
.toString());
339 } catch (IOException e1
) {
340 Log
.e(TAG
, null, e1
);
346 * Default Implementation.
351 * @see android.app.Service#onBind(android.content.Intent)
354 public final IBinder
onBind(final Intent intent
) {
367 public final void onStart(final Intent intent
, final int startId
) {
368 super.onStart(intent
, startId
);
371 this.setForeground(true);
373 SharedPreferences preferences
= PreferenceManager
374 .getDefaultSharedPreferences(this);
375 int p
= Integer
.parseInt(preferences
.getString(PREFS_PORT
,
377 boolean portChanged
= p
!= this.port
;
380 String f
= preferences
.getString(PREFS_FILTER
, "");
382 for (String s
: f
.split("\n")) {
383 if (s
.length() > 0) {
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);
393 Toast
.makeText(this, "proxy running on port " + this.port
,
394 Toast
.LENGTH_SHORT
).show();
396 this.proxy
.interrupt();
397 this.proxy
= new Thread(this);
407 public final void onDestroy() {
409 Toast
.makeText(this, "stopping proxy..", Toast
.LENGTH_LONG
).show();
414 * Run by Thread.start().
417 public final void run() {
420 ServerSocket sock
= new ServerSocket(p
);
422 while (!this.stop
&& p
== this.port
) {
423 if (p
!= this.port
) {
426 client
= sock
.accept();
427 if (client
!= null) {
428 Thread t
= new Thread(new Connection(client
));
433 } catch (IOException e
) {