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 /** 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";
60 private Thread proxy
= null;
62 private int port
= -1;
63 /** Proxy's filter. */
64 private ArrayList
<String
> filter
= new ArrayList
<String
>();
66 private boolean stop
= false;
68 /** Tag for output. */
69 private static final String TAG
= "AdBlock.Proxy";
72 * Connection handles a single HTTP Connection. Run this as a Thread.
74 * @author Felix Bechstein
76 private class Connection
implements Runnable
{
79 private final Socket local
;
81 private Socket remote
;
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
{
91 private final InputStream reader
;
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;
107 * object to sync with
109 public CopyStream(final InputStream r
, final OutputStream w
,
117 * Run by Thread.start().
122 byte[] buf
= new byte[BUFFSIZE
];
125 read
= this.reader
.read(buf
);
129 this.writer
.write(buf
, 0, read
);
132 synchronized (this.sync
) {
135 } catch (IOException e
) {
149 public Connection(final Socket socket
) {
154 * Check if URL is blocked.
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) {
170 * Run by Thread.start().
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
);
184 InputStream remoteInputStream
= null;
185 OutputStream remoteOutputStream
= null;
186 StringBuilder buffer
= new StringBuilder();
188 boolean firstLine
= true;
189 boolean block
= false;
190 boolean uncompleteURL
= true;
191 boolean connectHTTPS
= false;
193 String targetHost
= null;
195 while (this.remote
== null && !block
) {
196 s
= localReader
.readLine();
197 buffer
.append(s
+ "\n");
200 url
= s
.split(" ")[1];
201 if (s
.startsWith("CONNECT ")) {
204 int i
= targetHost
.indexOf(':');
206 targetPort
= Integer
.parseInt(targetHost
208 targetHost
= targetHost
.substring(0, i
);
211 } else if (url
.startsWith("http:")) {
212 uncompleteURL
= false;
213 block
= this.checkURL(url
);
215 uncompleteURL
= true;
219 if (!block
&& s
.startsWith("Host:")) {
220 // init remote socket
222 targetHost
= s
.substring(6).trim();
223 int i
= targetHost
.indexOf(':');
225 targetPort
= Integer
.parseInt(targetHost
227 targetHost
= targetHost
.substring(0, i
);
230 url
= targetHost
+ url
;
231 block
= this.checkURL(url
);
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) {
245 localWriter
.append(HTTP_ERROR
246 + " - PROTOCOL ERROR" + HTTP_RESPONSE
254 if (targetHost
!= null && targetPort
> 0) {
255 Log
.v(TAG
, "connect to " + targetHost
+ " "
257 this.remote
= new Socket();
258 this.remote
.connect(new InetSocketAddress(
259 targetHost
, targetPort
));
260 remoteInputStream
= this.remote
.getInputStream();
261 remoteOutputStream
= this.remote
.getOutputStream();
263 localWriter
.write(HTTP_CONNECTED
266 BufferedWriter remoteWriter
= new BufferedWriter(
267 new OutputStreamWriter(
269 CopyStream
.BUFFSIZE
);
270 while (localReader
.ready()) {
272 .append(localReader
.readLine()
275 remoteWriter
.append(buffer
);
276 remoteWriter
.flush();
279 // remoteWriter.close(); // writer.close() will
280 // close underlying socket!
283 if (this.remote
!= null && this.remote
.isConnected()) {
284 Object sync
= new Object();
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
));
293 synchronized (sync
) {
298 } catch (InterruptedException e
) {
301 this.local
.shutdownInput();
302 this.remote
.shutdownInput();
303 this.local
.shutdownOutput();
304 this.remote
.shutdownOutput();
310 while (localReader
.ready()) {
311 localReader
.readLine();
313 localWriter
.append(HTTP_BLOCK
+ HTTP_RESPONSE
314 + "BLOCKED by AdBlock!");
319 } catch (InterruptedException e
) {
321 } catch (NullPointerException e
) {
323 } catch (Exception e
) {
325 localWriter
.append(HTTP_ERROR
+ " - " + e
.toString()
326 + HTTP_RESPONSE
+ e
.toString());
331 } catch (IOException e1
) {
332 Log
.e(TAG
, null, e1
);
338 * Default Implementation.
343 * @see android.app.Service#onBind(android.content.Intent)
346 public final IBinder
onBind(final Intent intent
) {
359 public final void onStart(final Intent intent
, final int startId
) {
360 super.onStart(intent
, startId
);
363 this.setForeground(true);
365 SharedPreferences preferences
= PreferenceManager
366 .getDefaultSharedPreferences(this);
367 int p
= Integer
.parseInt(preferences
.getString(AdBlock
.PREFS_PORT
,
369 boolean portChanged
= p
!= this.port
;
372 String f
= preferences
.getString(AdBlock
.PREFS_FILTER
, "");
374 for (String s
: f
.split("\n")) {
375 if (s
.length() > 0) {
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);
385 Toast
.makeText(this, "proxy running on port " + this.port
,
386 Toast
.LENGTH_SHORT
).show();
388 this.proxy
.interrupt();
389 this.proxy
= new Thread(this);
399 public final void onDestroy() {
401 Toast
.makeText(this, "stopping proxy..", Toast
.LENGTH_LONG
).show();
406 * Run by Thread.start().
409 public final void run() {
412 ServerSocket sock
= new ServerSocket(p
);
414 while (!this.stop
&& p
== this.port
) {
415 if (p
!= this.port
) {
418 client
= sock
.accept();
419 if (client
!= null) {
420 Thread t
= new Thread(new Connection(client
));
425 } catch (IOException e
) {