Merge pull request #82 from n8fr8/master
Initial work to support Tor (Orbot) and proxying
This commit is contained in:
commit
94d39dd56b
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
[submodule "external/netcipher"]
|
||||
path = external/netcipher
|
||||
url = https://github.com/guardianproject/NetCipher.git
|
@ -31,6 +31,7 @@
|
||||
android:targetSdkVersion="19" />
|
||||
|
||||
<application
|
||||
android:name="acr.browser.lightning.BrowserApp"
|
||||
android:allowBackup="true"
|
||||
android:hardwareAccelerated="true"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
|
1
external/netcipher
vendored
Submodule
1
external/netcipher
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit ba9d13676669f7ef32df43a023a4d4a33385e013
|
61
proguard-project.txt
Normal file
61
proguard-project.txt
Normal file
@ -0,0 +1,61 @@
|
||||
# To enable ProGuard in your project, edit project.properties
|
||||
# to define the proguard.config property as described in that file.
|
||||
#
|
||||
# Add project specific ProGuard rules here.
|
||||
# By default, the flags in this file are appended to flags specified
|
||||
# in ${sdk.dir}/tools/proguard/proguard-android.txt
|
||||
# You can edit the include path and order by changing the ProGuard
|
||||
# include property in project.properties.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# Add any project specific keep options here:
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
-optimizationpasses 5
|
||||
-dontusemixedcaseclassnames
|
||||
-dontskipnonpubliclibraryclasses
|
||||
-dontpreverify
|
||||
-verbose
|
||||
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
|
||||
|
||||
-keep public class * extends android.app.Activity
|
||||
-keep public class * extends android.app.Application
|
||||
-keep public class * extends android.app.Service
|
||||
-keep public class * extends android.content.BroadcastReceiver
|
||||
-keep public class * extends android.content.ContentProvider
|
||||
-keep public class * extends android.app.backup.BackupAgentHelper
|
||||
-keep public class * extends android.preference.Preference
|
||||
-keep public class com.android.vending.licensing.ILicensingService
|
||||
|
||||
-keepclasseswithmembernames class * {
|
||||
native <methods>;
|
||||
}
|
||||
|
||||
-keepclasseswithmembers class * {
|
||||
public <init>(android.content.Context, android.util.AttributeSet);
|
||||
}
|
||||
|
||||
-keepclasseswithmembers class * {
|
||||
public <init>(android.content.Context, android.util.AttributeSet, int);
|
||||
}
|
||||
|
||||
-keepclassmembers class * extends android.app.Activity {
|
||||
public void *(android.view.View);
|
||||
}
|
||||
|
||||
-keepclassmembers enum * {
|
||||
public static **[] values();
|
||||
public static ** valueOf(java.lang.String);
|
||||
}
|
||||
|
||||
-keep class * implements android.os.Parcelable {
|
||||
public static final android.os.Parcelable$Creator *;
|
||||
}
|
@ -69,7 +69,7 @@
|
||||
</RelativeLayout>
|
||||
</RelativeLayout>
|
||||
|
||||
<LinearLayout
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginLeft="5dp"
|
||||
|
@ -158,5 +158,6 @@
|
||||
<string name="powered_by_google">Powered by Google</string>
|
||||
<string name="title_adblock">AdBlock</string>
|
||||
<string name="message_adblock">AdBlock is currently only available in the browser\'s paid version, Lightning Browser+. It is available to download on Google Play.</string>
|
||||
<string name="use_tor_prompt">It looks like you have Orbot installed. Do you want to use Tor?</string>
|
||||
|
||||
</resources>
|
13
setup-ant.sh
Executable file
13
setup-ant.sh
Executable file
@ -0,0 +1,13 @@
|
||||
#!/bin/sh
|
||||
|
||||
target="android-19"
|
||||
|
||||
for f in `find external/ -name project.properties`; do
|
||||
projectdir=`dirname $f`
|
||||
echo "Updating ant setup in $projectdir:"
|
||||
android update lib-project -p $projectdir -t $target
|
||||
done
|
||||
android update project -p . --subprojects -t $target --name Lightning
|
||||
|
||||
cp libs/android-support-v4.jar external/netcipher/libnetcipher/libs/android-support-v4.jar
|
||||
|
@ -4,6 +4,9 @@
|
||||
|
||||
package acr.browser.lightning;
|
||||
|
||||
import info.guardianproject.onionkit.ui.OrbotHelper;
|
||||
import info.guardianproject.onionkit.web.WebkitProxy;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
@ -73,27 +76,27 @@ import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.webkit.ValueCallback;
|
||||
import android.webkit.WebChromeClient.CustomViewCallback;
|
||||
import android.webkit.CookieManager;
|
||||
import android.webkit.CookieSyncManager;
|
||||
import android.webkit.ValueCallback;
|
||||
import android.webkit.WebChromeClient.CustomViewCallback;
|
||||
import android.webkit.WebIconDatabase;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebView.HitTestResult;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.AutoCompleteTextView;
|
||||
import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ListView;
|
||||
import android.widget.AutoCompleteTextView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.VideoView;
|
||||
import android.widget.TextView.OnEditorActionListener;
|
||||
import android.widget.VideoView;
|
||||
|
||||
public class BrowserActivity extends Activity implements BrowserController {
|
||||
private static DrawerLayout mDrawerLayout;
|
||||
@ -445,8 +448,76 @@ public class BrowserActivity extends Activity implements BrowserController {
|
||||
WebIconDatabase.getInstance().open(
|
||||
getDir("icons", MODE_PRIVATE).getPath());
|
||||
}
|
||||
|
||||
boolean useProxy = mPreferences.getBoolean(PreferenceConstants.USE_PROXY, false);
|
||||
|
||||
if (useProxy)
|
||||
initializeTor();
|
||||
else
|
||||
checkForTor();
|
||||
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* If Orbot/Tor is installed, prompt the user if they want to enable proxying for this session
|
||||
*/
|
||||
public boolean checkForTor ()
|
||||
{
|
||||
|
||||
OrbotHelper oh = new OrbotHelper(this);
|
||||
if (oh.isOrbotInstalled())
|
||||
{
|
||||
DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
switch (which){
|
||||
case DialogInterface.BUTTON_POSITIVE:
|
||||
|
||||
mPreferences.edit().putBoolean(PreferenceConstants.USE_PROXY, true).apply();
|
||||
|
||||
initializeTor ();
|
||||
|
||||
break;
|
||||
|
||||
case DialogInterface.BUTTON_NEGATIVE:
|
||||
|
||||
mPreferences.edit().putBoolean(PreferenceConstants.USE_PROXY, false).apply();
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setMessage(R.string.use_tor_prompt).setPositiveButton(android.R.string.yes, dialogClickListener)
|
||||
.setNegativeButton(android.R.string.no, dialogClickListener).show();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize WebKit Proxying for Tor
|
||||
*/
|
||||
public void initializeTor ()
|
||||
{
|
||||
|
||||
OrbotHelper oh = new OrbotHelper(this);
|
||||
if (!oh.isOrbotRunning())
|
||||
oh.requestOrbotStart(this);
|
||||
|
||||
WebkitProxy wkp = new WebkitProxy();
|
||||
try {
|
||||
String host = mPreferences.getString(PreferenceConstants.USE_PROXY_HOST, "localhost");
|
||||
int port = mPreferences.getInt(PreferenceConstants.USE_PROXY_PORT, 8118);
|
||||
wkp.setProxy("acr.browser.lightning.BrowserApp", getApplicationContext(), host, port);
|
||||
} catch (Exception e) {
|
||||
Log.d("Lightning","error enabling web proxying",e);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void initializeTabs() {
|
||||
mIdGenerator = 0;
|
||||
|
||||
@ -945,7 +1016,7 @@ public class BrowserActivity extends Activity implements BrowserController {
|
||||
|
||||
private synchronized void newTab(String url, boolean show) {
|
||||
mIsNewIntent = false;
|
||||
LightningView startingTab = new LightningView(mActivity, url);
|
||||
LightningView startingTab = new LightningView(mActivity, url, mCookieManager);
|
||||
if (mIdGenerator == 0) {
|
||||
startingTab.resumeTimers();
|
||||
}
|
||||
|
7
src/acr/browser/lightning/BrowserApp.java
Normal file
7
src/acr/browser/lightning/BrowserApp.java
Normal file
@ -0,0 +1,7 @@
|
||||
package acr.browser.lightning;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
public class BrowserApp extends Application {
|
||||
|
||||
}
|
@ -908,7 +908,7 @@ public class IncognitoActivity extends Activity implements BrowserController {
|
||||
|
||||
private synchronized void newTab(String url, boolean show) {
|
||||
mIsNewIntent = false;
|
||||
LightningView startingTab = new LightningView(mActivity, url);
|
||||
LightningView startingTab = new LightningView(mActivity, url, mCookieManager);
|
||||
if (mIdGenerator == 0) {
|
||||
startingTab.resumeTimers();
|
||||
}
|
||||
|
@ -4,11 +4,19 @@
|
||||
|
||||
package acr.browser.lightning;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Proxy;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
|
||||
import org.apache.http.util.ByteArrayBuffer;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
@ -28,10 +36,11 @@ import android.text.TextUtils;
|
||||
import android.text.method.PasswordTransformationMethod;
|
||||
import android.util.Log;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.GestureDetector.SimpleOnGestureListener;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.GestureDetector.SimpleOnGestureListener;
|
||||
import android.view.View.OnTouchListener;
|
||||
import android.webkit.CookieManager;
|
||||
import android.webkit.GeolocationPermissions;
|
||||
import android.webkit.HttpAuthHandler;
|
||||
import android.webkit.SslErrorHandler;
|
||||
@ -44,7 +53,9 @@ import android.webkit.WebSettings.PluginState;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.VideoView;
|
||||
|
||||
public class LightningView {
|
||||
|
||||
@ -62,9 +73,12 @@ public class LightningView {
|
||||
private static SharedPreferences mPreferences;
|
||||
private static boolean mWideViewPort;
|
||||
private static AdBlock mAdBlock;
|
||||
|
||||
public LightningView(Activity activity, String url) {
|
||||
private CookieManager mCookieManager;
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
public LightningView(Activity activity, String url, CookieManager cookieManager) {
|
||||
mActivity = activity;
|
||||
mCookieManager = cookieManager;
|
||||
mWebView = new WebView(activity);
|
||||
mTitle = new Title(activity);
|
||||
mAdBlock = new AdBlock(activity);
|
||||
@ -249,6 +263,7 @@ public class LightningView {
|
||||
return Constants.FILE + homepage;
|
||||
}
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
public synchronized void initializePreferences(Context context) {
|
||||
mPreferences = context.getSharedPreferences(
|
||||
PreferenceConstants.PREFERENCES, 0);
|
||||
@ -348,7 +363,7 @@ public class LightningView {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
@SuppressLint({ "SetJavaScriptEnabled", "NewApi" })
|
||||
public void initializeSettings(WebSettings settings, Context context) {
|
||||
if (API < 18) {
|
||||
settings.setAppCacheMaxSize(Long.MAX_VALUE);
|
||||
@ -357,7 +372,7 @@ public class LightningView {
|
||||
settings.setEnableSmoothTransition(true);
|
||||
}
|
||||
if (API > 16) {
|
||||
settings.setMediaPlaybackRequiresUserGesture(true);
|
||||
settings.setMediaPlaybackRequiresUserGesture(true);
|
||||
}
|
||||
if (API < 19) {
|
||||
settings.setDatabasePath(context.getFilesDir().getAbsolutePath()
|
||||
@ -450,6 +465,7 @@ public class LightningView {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
public synchronized void find(String text) {
|
||||
if (mWebView != null) {
|
||||
if (API > 16) {
|
||||
@ -536,6 +552,7 @@ public class LightningView {
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
public class LightningWebClient extends WebViewClient {
|
||||
|
||||
Context mActivity;
|
||||
@ -548,14 +565,121 @@ public class LightningView {
|
||||
public WebResourceResponse shouldInterceptRequest(WebView view,
|
||||
String url) {
|
||||
if (mAdBlock.isAd(url)) {
|
||||
Log.i("Blocked Domain:", url);
|
||||
ByteArrayInputStream EMPTY = new ByteArrayInputStream(
|
||||
"".getBytes());
|
||||
WebResourceResponse response = new WebResourceResponse(
|
||||
"text/plain", "utf-8", EMPTY);
|
||||
return response;
|
||||
}
|
||||
return super.shouldInterceptRequest(view, url);
|
||||
|
||||
boolean useProxy = mPreferences.getBoolean(PreferenceConstants.USE_PROXY, false);
|
||||
boolean mDoLeakHardening = false;
|
||||
|
||||
if (!useProxy)
|
||||
return null;
|
||||
|
||||
if (!mDoLeakHardening)
|
||||
return null;
|
||||
|
||||
//now we are going to proxy!
|
||||
try
|
||||
{
|
||||
|
||||
|
||||
URL uURl = new URL(url);
|
||||
|
||||
Proxy proxy = null;
|
||||
|
||||
String host = mPreferences.getString(PreferenceConstants.USE_PROXY_HOST, "localhost");
|
||||
int port = mPreferences.getInt(PreferenceConstants.USE_PROXY_PORT, 8118);
|
||||
proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(host, port));
|
||||
|
||||
HttpURLConnection.setFollowRedirects(true);
|
||||
HttpURLConnection conn = (HttpURLConnection)uURl.openConnection(proxy);
|
||||
conn.setInstanceFollowRedirects(true);
|
||||
conn.setRequestProperty("User-Agent", mSettings.getUserAgentString());
|
||||
|
||||
//conn.setRequestProperty("Transfer-Encoding", "chunked");
|
||||
//conn.setUseCaches(false);
|
||||
|
||||
final int bufferSize = 1024 * 32;
|
||||
conn.setChunkedStreamingMode(bufferSize);
|
||||
|
||||
String cType = conn.getContentType();
|
||||
String cEnc = conn.getContentEncoding();
|
||||
int connLen = conn.getContentLength();
|
||||
|
||||
|
||||
if (cType != null)
|
||||
{
|
||||
String[] ctArray = cType.split(";");
|
||||
cType = ctArray[0].trim();
|
||||
|
||||
if (cEnc == null && ctArray.length > 1)
|
||||
{
|
||||
cEnc = ctArray[1];
|
||||
if (cEnc.indexOf('=')!=-1)
|
||||
cEnc = cEnc.split("=")[1].trim();
|
||||
}
|
||||
}
|
||||
|
||||
if (connLen <= 0)
|
||||
connLen = 2048;
|
||||
|
||||
if (cType != null && cType.startsWith("text"))
|
||||
{
|
||||
InputStream fStream = null;
|
||||
|
||||
BufferedInputStream bis = new BufferedInputStream(conn.getInputStream());
|
||||
ByteArrayBuffer baf = new ByteArrayBuffer(connLen);
|
||||
int read = 0;
|
||||
int bufSize = 2048;
|
||||
byte[] buffer = new byte[bufSize];
|
||||
while(true){
|
||||
read = bis.read(buffer);
|
||||
if(read==-1){
|
||||
break;
|
||||
}
|
||||
baf.append(buffer, 0, read);
|
||||
}
|
||||
byte[] plainText = baf.toByteArray();
|
||||
|
||||
fStream = new ByteArrayInputStream(plainText);
|
||||
|
||||
fStream = new ReplacingInputStream(new ByteArrayInputStream(plainText),"poster=".getBytes(),"foo=".getBytes());
|
||||
fStream = new ReplacingInputStream(fStream,"Poster=".getBytes(),"foo=".getBytes());
|
||||
fStream = new ReplacingInputStream(fStream,"Poster=".getBytes(),"foo=".getBytes());
|
||||
fStream = new ReplacingInputStream(fStream,".poster".getBytes(),".foo".getBytes());
|
||||
fStream = new ReplacingInputStream(fStream,"\"poster\"".getBytes(),"\"foo\"".getBytes());
|
||||
|
||||
WebResourceResponse response = new WebResourceResponse(
|
||||
cType, cEnc, fStream);
|
||||
|
||||
return response;
|
||||
}/**
|
||||
else if (mDoLeakHardening)
|
||||
{
|
||||
WebResourceResponse response = new WebResourceResponse(
|
||||
cType, cEnc, conn.getInputStream());
|
||||
|
||||
return response;
|
||||
|
||||
}*/
|
||||
else
|
||||
{
|
||||
return null; //let webkit handle it
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.e("Lightning","Error filtering stream",e);
|
||||
ByteArrayInputStream EMPTY = new ByteArrayInputStream(
|
||||
"".getBytes());
|
||||
WebResourceResponse response = new WebResourceResponse(
|
||||
"text/plain", "utf-8", EMPTY);
|
||||
return response;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -871,9 +995,24 @@ public class LightningView {
|
||||
|
||||
@Override
|
||||
public void onShowCustomView(View view, CustomViewCallback callback) {
|
||||
Activity activity = mBrowserController.getActivity();
|
||||
mBrowserController.onShowCustomView(view,
|
||||
activity.getRequestedOrientation(), callback);
|
||||
|
||||
if (view instanceof FrameLayout){
|
||||
FrameLayout frame = (FrameLayout) view;
|
||||
if (frame.getFocusedChild() instanceof VideoView){
|
||||
VideoView video = (VideoView) frame.getFocusedChild();
|
||||
video.stopPlayback();
|
||||
frame.removeView(video);
|
||||
video.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Activity activity = mBrowserController.getActivity();
|
||||
mBrowserController.onShowCustomView(view,
|
||||
activity.getRequestedOrientation(), callback);
|
||||
|
||||
}
|
||||
|
||||
super.onShowCustomView(view, callback);
|
||||
}
|
||||
|
||||
@ -881,8 +1020,23 @@ public class LightningView {
|
||||
@Deprecated
|
||||
public void onShowCustomView(View view, int requestedOrientation,
|
||||
CustomViewCallback callback) {
|
||||
mBrowserController.onShowCustomView(view, requestedOrientation,
|
||||
callback);
|
||||
|
||||
if (view instanceof FrameLayout){
|
||||
FrameLayout frame = (FrameLayout) view;
|
||||
if (frame.getFocusedChild() instanceof VideoView){
|
||||
VideoView video = (VideoView) frame.getFocusedChild();
|
||||
video.stopPlayback();
|
||||
frame.removeView(video);
|
||||
video.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mBrowserController.onShowCustomView(view, requestedOrientation,
|
||||
callback);
|
||||
|
||||
}
|
||||
|
||||
super.onShowCustomView(view, requestedOrientation, callback);
|
||||
}
|
||||
|
||||
|
@ -31,4 +31,8 @@ public class PreferenceConstants {
|
||||
public static final String USER_AGENT = "agentchoose";
|
||||
public static final String USER_AGENT_STRING = "userAgentString";
|
||||
public static final String GOOGLE_SEARCH_SUGGESTIONS = "GoogleSearchSuggestions";
|
||||
|
||||
public static final String USE_PROXY = "useProxy";
|
||||
public static final String USE_PROXY_HOST = "useProxyHost";
|
||||
public static final String USE_PROXY_PORT = "useProxyPort";
|
||||
}
|
||||
|
91
src/acr/browser/lightning/ReplacingInputStream.java
Normal file
91
src/acr/browser/lightning/ReplacingInputStream.java
Normal file
@ -0,0 +1,91 @@
|
||||
package acr.browser.lightning;
|
||||
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
|
||||
public class ReplacingInputStream extends FilterInputStream {
|
||||
|
||||
LinkedList<Integer> inQueue = new LinkedList<Integer>();
|
||||
LinkedList<Integer> outQueue = new LinkedList<Integer>();
|
||||
final byte[] search, replacement;
|
||||
|
||||
protected ReplacingInputStream(InputStream in, byte[] search,
|
||||
byte[] replacement) {
|
||||
super(in);
|
||||
this.search = search;
|
||||
this.replacement = replacement;
|
||||
}
|
||||
|
||||
private boolean isMatchFound() {
|
||||
Iterator<Integer> inIter = inQueue.iterator();
|
||||
for (int i = 0; i < search.length; i++)
|
||||
if (!inIter.hasNext() || search[i] != inIter.next())
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void readAhead() throws IOException {
|
||||
// Work up some look-ahead.
|
||||
while (inQueue.size() < search.length) {
|
||||
int next = super.read();
|
||||
inQueue.offer(next);
|
||||
if (next == -1)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
|
||||
// Next byte already determined.
|
||||
if (outQueue.isEmpty()) {
|
||||
|
||||
readAhead();
|
||||
|
||||
if (isMatchFound()) {
|
||||
for (int i = 0; i < search.length; i++)
|
||||
inQueue.remove();
|
||||
|
||||
for (byte b : replacement)
|
||||
outQueue.offer((int) b);
|
||||
} else
|
||||
outQueue.add(inQueue.remove());
|
||||
}
|
||||
|
||||
return outQueue.remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns false. REFilterInputStream does not support mark() and
|
||||
* reset() methods.
|
||||
*/
|
||||
public boolean markSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Reads from the stream into the provided array.
|
||||
* @throws IOException */
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
int i;
|
||||
int ok = 0;
|
||||
while (len-- > 0) {
|
||||
i = read();
|
||||
if (i == -1) return (ok == 0) ? -1 : ok;
|
||||
b[off++] = (byte) i;
|
||||
ok++;
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] buffer) throws IOException {
|
||||
|
||||
return read(buffer, 0, buffer.length);
|
||||
}
|
||||
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user