diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..2b75303a --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 00000000..34dc27cb --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 00000000..79ee123c --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 00000000..7ac24c77 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 00000000..dfd2c799 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 00000000..7f68460d --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 00000000..e177897a --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,44 @@ +apply plugin: 'com.android.application' + +apply plugin: 'kotlin-android' + +apply plugin: 'kotlin-android-extensions' +apply plugin: 'maven' + +android { + compileSdkVersion 28 + defaultConfig { + applicationId "com.example.myapplication" + minSdkVersion 21 + targetSdkVersion 28 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation 'com.android.support:appcompat-v7:28.0.0' + implementation 'com.android.support.constraint:constraint-layout:1.1.3' + testImplementation 'junit:junit:4.12' + androidTestImplementation 'com.android.support.test:runner:1.0.2' + androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' + implementation 'com.google.android.gms:play-services-ads:17.1.1' + implementation "cz.msebera.android:httpclient:4.4.1.2" + compile 'info.guardianproject.netcipher:netcipher:2.0.0-alpha1' + compile 'info.guardianproject.netcipher:netcipher-okhttp3:2.0.0-alpha1' + compile 'com.squareup.okhttp3:okhttp:3.4.2' + compile 'org.apache.httpcomponents:httpcore:4.4.1' +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 00000000..f1b42451 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# 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 *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/app/release/app-release.apk b/app/release/app-release.apk new file mode 100644 index 00000000..dad6346e Binary files /dev/null and b/app/release/app-release.apk differ diff --git a/app/release/output.json b/app/release/output.json new file mode 100644 index 00000000..9f0c9596 --- /dev/null +++ b/app/release/output.json @@ -0,0 +1 @@ +[{"outputType":{"type":"APK"},"apkInfo":{"type":"MAIN","splits":[],"versionCode":1,"versionName":"1.0","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}] \ No newline at end of file diff --git a/app/src/androidTest/java/com/example/myapplication/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/example/myapplication/ExampleInstrumentedTest.kt new file mode 100644 index 00000000..7a945382 --- /dev/null +++ b/app/src/androidTest/java/com/example/myapplication/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.example.myapplication + +import android.support.test.InstrumentationRegistry +import android.support.test.runner.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getTargetContext() + assertEquals("com.example.myapplication", appContext.packageName) + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..c324e7db --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/SSLConnectionSocketFactory.java b/app/src/main/java/com/example/myapplication/SSLConnectionSocketFactory.java new file mode 100644 index 00000000..2a36a26a --- /dev/null +++ b/app/src/main/java/com/example/myapplication/SSLConnectionSocketFactory.java @@ -0,0 +1,490 @@ +package com.example.myapplication; + +import android.os.Build; +import android.util.Log; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Method; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.List; + +import javax.net.SocketFactory; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLHandshakeException; +import javax.net.ssl.SSLPeerUnverifiedException; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocket; +import javax.security.auth.x500.X500Principal; + +import cz.msebera.android.httpclient.HttpHost; +import cz.msebera.android.httpclient.conn.socket.LayeredConnectionSocketFactory; +import cz.msebera.android.httpclient.conn.ssl.DefaultHostnameVerifier; +import cz.msebera.android.httpclient.conn.ssl.SSLContexts; +import cz.msebera.android.httpclient.conn.ssl.SSLInitializationException; +import cz.msebera.android.httpclient.conn.ssl.X509HostnameVerifier; +import cz.msebera.android.httpclient.conn.util.PublicSuffixMatcherLoader; +import cz.msebera.android.httpclient.protocol.HttpContext; +import cz.msebera.android.httpclient.util.Args; +import cz.msebera.android.httpclient.util.TextUtils; + +/* +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.HttpHost; +import org.apache.http.annotation.ThreadSafe; +import org.apache.http.conn.socket.LayeredConnectionSocketFactory; +import org.apache.http.conn.util.PublicSuffixMatcherLoader; +import org.apache.http.protocol.HttpContext; +import org.apache.http.ssl.SSLContexts; +import org.apache.http.util.Args; +import org.apache.http.util.TextUtils; +*/ + +/** + * Layered socket factory for TLS/SSL connections. + *

+ * SSLSocketFactory can be used to validate the identity of the HTTPS server against a list of + * trusted certificates and to authenticate to the HTTPS server using a private key. + *

+ * SSLSocketFactory will enable server authentication when supplied with + * a {@link java.security.KeyStore trust-store} file containing one or several trusted certificates. The client + * secure socket will reject the connection during the SSL session handshake if the target HTTPS + * server attempts to authenticate itself with a non-trusted certificate. + *

+ * Use JDK keytool utility to import a trusted certificate and generate a trust-store file: + *

+ *     keytool -import -alias "my server cert" -file server.crt -keystore my.truststore
+ *    
+ *

+ * In special cases the standard trust verification process can be bypassed by using a custom + * {@link org.apache.http.conn.ssl.TrustStrategy}. This interface is primarily intended for allowing self-signed + * certificates to be accepted as trusted without having to add them to the trust-store file. + *

+ * SSLSocketFactory will enable client authentication when supplied with + * a {@link java.security.KeyStore key-store} file containing a private key/public certificate + * pair. The client secure socket will use the private key to authenticate + * itself to the target HTTPS server during the SSL session handshake if + * requested to do so by the server. + * The target HTTPS server will in its turn verify the certificate presented + * by the client in order to establish client's authenticity. + *

+ * Use the following sequence of actions to generate a key-store file + *

+ * + * + * @since 4.3 + */ +// @ThreadSafe @SuppressWarnings("deprecation") +public class SSLConnectionSocketFactory implements + LayeredConnectionSocketFactory { + private final static String TAG = "HttpClient"; + public static final String TLS = "TLS"; + public static final String SSL = "SSL"; + public static final String SSLV2 = "SSLv2"; + +/* + @Deprecated + public static final X509HostnameVerifier ALLOW_ALL_HOSTNAME_VERIFIER + = AllowAllHostnameVerifier.INSTANCE; + @Deprecated + public static final X509HostnameVerifier BROWSER_COMPATIBLE_HOSTNAME_VERIFIER + = BrowserCompatHostnameVerifier.INSTANCE; + @Deprecated + public static final X509HostnameVerifier STRICT_HOSTNAME_VERIFIER + = StrictHostnameVerifier.INSTANCE; +*/ + +// private final Log log = LogFactory.getLog(getClass()); + + /** + * @since 4.4 + */ + public static HostnameVerifier getDefaultHostnameVerifier() { + return new DefaultHostnameVerifier( + PublicSuffixMatcherLoader.getDefault()); + } + + /** + * Obtains default SSL socket factory with an SSL context based on the standard JSSE + * trust material ({@code cacerts} file in the security properties directory). + * System properties are not taken into consideration. + * + * @return default SSL socket factory + */ + public static SSLConnectionSocketFactory getSocketFactory() throws + SSLInitializationException { + return new SSLConnectionSocketFactory( + SSLContexts.createDefault(), getDefaultHostnameVerifier()); + } + + private static String[] split(final String s) { + if (TextUtils.isBlank(s)) { + return null; + } + return s.split(" *, *"); + } + + /** + * Obtains default SSL socket factory with an SSL context based on system properties + * as described in + * + * Java™ Secure Socket Extension (JSSE) Reference Guide. + * + * @return default system SSL socket factory + */ + public static SSLConnectionSocketFactory getSystemSocketFactory() throws + SSLInitializationException { + return new SSLConnectionSocketFactory( + (javax.net.ssl.SSLSocketFactory) javax.net.ssl.SSLSocketFactory.getDefault(), + split(System.getProperty("https.protocols")), + split(System.getProperty("https.cipherSuites")), + getDefaultHostnameVerifier()); + } + + private final javax.net.ssl.SSLSocketFactory socketfactory; + private final HostnameVerifier hostnameVerifier; + private final String[] supportedProtocols; + private final String[] supportedCipherSuites; + + public SSLConnectionSocketFactory(final SSLContext sslContext) { + this(sslContext, getDefaultHostnameVerifier()); + } + + /** + * @deprecated (4.4) Use {@link #SSLConnectionSocketFactory(SSLContext, + * HostnameVerifier)} + */ + @Deprecated + public SSLConnectionSocketFactory( + final SSLContext sslContext, final X509HostnameVerifier hostnameVerifier) { + this(Args.notNull(sslContext, "SSL context").getSocketFactory(), + null, null, hostnameVerifier); + } + + /** + * @deprecated (4.4) Use {@link #SSLConnectionSocketFactory(SSLContext, + * String[], String[], HostnameVerifier)} + */ + @Deprecated + public SSLConnectionSocketFactory( + final SSLContext sslContext, + final String[] supportedProtocols, + final String[] supportedCipherSuites, + final X509HostnameVerifier hostnameVerifier) { + this(Args.notNull(sslContext, "SSL context").getSocketFactory(), + supportedProtocols, supportedCipherSuites, hostnameVerifier); + } + + /** + * @deprecated (4.4) Use {@link #SSLConnectionSocketFactory(javax.net.ssl.SSLSocketFactory, + * HostnameVerifier)} + */ + @Deprecated + public SSLConnectionSocketFactory( + final javax.net.ssl.SSLSocketFactory socketfactory, + final X509HostnameVerifier hostnameVerifier) { + this(socketfactory, null, null, hostnameVerifier); + } + + /** + * @deprecated (4.4) Use {@link #SSLConnectionSocketFactory(javax.net.ssl.SSLSocketFactory, + * String[], String[], HostnameVerifier)} + */ + @Deprecated + public SSLConnectionSocketFactory( + final javax.net.ssl.SSLSocketFactory socketfactory, + final String[] supportedProtocols, + final String[] supportedCipherSuites, + final X509HostnameVerifier hostnameVerifier) { + this(socketfactory, supportedProtocols, supportedCipherSuites, (HostnameVerifier) hostnameVerifier); + } + + /** + * @since 4.4 + */ + public SSLConnectionSocketFactory( + final SSLContext sslContext, final HostnameVerifier hostnameVerifier) { + this(Args.notNull(sslContext, "SSL context").getSocketFactory(), + null, null, hostnameVerifier); + } + + /** + * @since 4.4 + */ + public SSLConnectionSocketFactory( + final SSLContext sslContext, + final String[] supportedProtocols, + final String[] supportedCipherSuites, + final HostnameVerifier hostnameVerifier) { + this(Args.notNull(sslContext, "SSL context").getSocketFactory(), + supportedProtocols, supportedCipherSuites, hostnameVerifier); + } + + /** + * @since 4.4 + */ + public SSLConnectionSocketFactory( + final javax.net.ssl.SSLSocketFactory socketfactory, + final HostnameVerifier hostnameVerifier) { + this(socketfactory, null, null, hostnameVerifier); + } + + /** + * @since 4.4 + */ + public SSLConnectionSocketFactory( + final javax.net.ssl.SSLSocketFactory socketfactory, + final String[] supportedProtocols, + final String[] supportedCipherSuites, + final HostnameVerifier hostnameVerifier) { + this.socketfactory = Args.notNull(socketfactory, "SSL socket factory"); + this.supportedProtocols = supportedProtocols; + this.supportedCipherSuites = supportedCipherSuites; + this.hostnameVerifier = hostnameVerifier != null ? hostnameVerifier : getDefaultHostnameVerifier(); + } + + /** + * Performs any custom initialization for a newly created SSLSocket + * (before the SSL handshake happens). + *

+ * The default implementation is a no-op, but could be overridden to, e.g., + * call {@link SSLSocket#setEnabledCipherSuites(String[])}. + * + * @throws IOException may be thrown if overridden + */ + protected void prepareSocket(final SSLSocket socket) throws IOException { + } + + @Override + public Socket createSocket(final HttpContext context) throws IOException { + return SocketFactory.getDefault().createSocket(); + } + + @Override + public Socket connectSocket( + final int connectTimeout, + final Socket socket, + final HttpHost host, + final InetSocketAddress remoteAddress, + final InetSocketAddress localAddress, + final HttpContext context) throws IOException { + Args.notNull(host, "HTTP host"); + Args.notNull(remoteAddress, "Remote address"); + final Socket sock = socket != null ? socket : createSocket(context); + if (localAddress != null) { + sock.bind(localAddress); + } + try { + if (connectTimeout > 0 && sock.getSoTimeout() == 0) { + sock.setSoTimeout(connectTimeout); + } +/* + if (this.log.isDebugEnabled()) { + this.log.debug("Connecting socket to " + remoteAddress + " with timeout " + connectTimeout); + } +*/ + sock.connect(remoteAddress, connectTimeout); + } catch (final IOException ex) { + try { + sock.close(); + } catch (final IOException ignore) { + } + throw ex; + } + // Setup SSL layering if necessary + if (sock instanceof SSLSocket) { + final SSLSocket sslsock = (SSLSocket) sock; +// this.log.debug("Starting handshake"); + sslsock.startHandshake(); + verifyHostname(sslsock, host.getHostName()); + return sock; + } else { + return createLayeredSocket(sock, host.getHostName(), remoteAddress.getPort(), context); + } + } + + @Override + public Socket createLayeredSocket( + final Socket socket, + final String target, + final int port, + final HttpContext context) throws IOException { + final SSLSocket sslsock = (SSLSocket) this.socketfactory.createSocket( + socket, + target, + port, + true); + if (supportedProtocols != null) { + sslsock.setEnabledProtocols(supportedProtocols); + } else { + // If supported protocols are not explicitly set, remove all SSL protocol versions + final String[] allProtocols = sslsock.getEnabledProtocols(); + final List enabledProtocols = new ArrayList(allProtocols.length); + for (String protocol : allProtocols) { + if (!protocol.startsWith("SSL")) { + enabledProtocols.add(protocol); + } + } + if (!enabledProtocols.isEmpty()) { + sslsock.setEnabledProtocols(enabledProtocols.toArray(new String[enabledProtocols.size()])); + } + } + if (supportedCipherSuites != null) { + sslsock.setEnabledCipherSuites(supportedCipherSuites); + } + +/* + if (this.log.isDebugEnabled()) { + this.log.debug("Enabled protocols: " + Arrays.asList(sslsock.getEnabledProtocols())); + this.log.debug("Enabled cipher suites:" + Arrays.asList(sslsock.getEnabledCipherSuites())); + } +*/ + + prepareSocket(sslsock); + + // Android specific code to enable SNI + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "Enabling SNI for " + target); + } + try { + Method method = sslsock.getClass().getMethod("setHostname", String.class); + method.invoke(sslsock, target); + } catch (Exception ex) { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "SNI configuration failed", ex); + } + } + } + // End of Android specific code + +// this.log.debug("Starting handshake"); + sslsock.startHandshake(); + verifyHostname(sslsock, target); + return sslsock; + } + + private void verifyHostname(final SSLSocket sslsock, final String hostname) throws IOException { + try { + SSLSession session = sslsock.getSession(); + if (session == null) { + // In our experience this only happens under IBM 1.4.x when + // spurious (unrelated) certificates show up in the server' + // chain. Hopefully this will unearth the real problem: + final InputStream in = sslsock.getInputStream(); + in.available(); + // If ssl.getInputStream().available() didn't cause an + // exception, maybe at least now the session is available? + session = sslsock.getSession(); + if (session == null) { + // If it's still null, probably a startHandshake() will + // unearth the real problem. + sslsock.startHandshake(); + session = sslsock.getSession(); + } + } + if (session == null) { + throw new SSLHandshakeException("SSL session not available"); + } + +/* + if (this.log.isDebugEnabled()) { + this.log.debug("Secure session established"); + this.log.debug(" negotiated protocol: " + session.getProtocol()); + this.log.debug(" negotiated cipher suite: " + session.getCipherSuite()); + try { + final Certificate[] certs = session.getPeerCertificates(); + final X509Certificate x509 = (X509Certificate) certs[0]; + final X500Principal peer = x509.getSubjectX500Principal(); + this.log.debug(" peer principal: " + peer.toString()); + final Collection> altNames1 = x509.getSubjectAlternativeNames(); + if (altNames1 != null) { + final List altNames = new ArrayList(); + for (final List aC : altNames1) { + if (!aC.isEmpty()) { + altNames.add((String) aC.get(1)); + } + } + this.log.debug(" peer alternative names: " + altNames); + } + final X500Principal issuer = x509.getIssuerX500Principal(); + this.log.debug(" issuer principal: " + issuer.toString()); + final Collection> altNames2 = x509.getIssuerAlternativeNames(); + if (altNames2 != null) { + final List altNames = new ArrayList(); + for (final List aC : altNames2) { + if (!aC.isEmpty()) { + altNames.add((String) aC.get(1)); + } + } + this.log.debug(" issuer alternative names: " + altNames); + } + } catch (Exception ignore) { + } + } +*/ + + if (!this.hostnameVerifier.verify(hostname, session)) { + final Certificate[] certs = session.getPeerCertificates(); + final X509Certificate x509 = (X509Certificate) certs[0]; + final X500Principal x500Principal = x509.getSubjectX500Principal(); + throw new SSLPeerUnverifiedException("Host name '" + hostname + "' does not match " + + "the certificate subject provided by the peer (" + x500Principal.toString() + ")"); + } + // verifyHostName() didn't blowup - good! + } catch (final IOException iox) { + // close the socket before re-throwing the exception + try { + sslsock.close(); + } catch (final Exception x) { /*ignore*/ } + throw iox; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/StrongBuild.java b/app/src/main/java/com/example/myapplication/StrongBuild.java new file mode 100644 index 00000000..9c5b512e --- /dev/null +++ b/app/src/main/java/com/example/myapplication/StrongBuild.java @@ -0,0 +1,85 @@ +package com.example.myapplication; + +import android.app.Activity; +import android.content.Context; +import android.util.Log; + +import javax.net.ssl.TrustManager; + +import android.webkit.WebView; +import info.guardianproject.netcipher.client.StrongBuilder; +import info.guardianproject.netcipher.client.StrongOkHttpClientBuilder; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + +public class StrongBuild implements StrongBuilder.Callback { + + private static final StrongBuild ourInstance = new StrongBuild(); + + public static StrongBuild getInstance() { + return ourInstance; + } + + public String url; + public String htmlCode; + public WebView view; + + public void loadURL(String url, WebView view, Context applicationContext){ + try{ + this.url = url; + this.view = view; + StrongOkHttpClientBuilder. + forMaxSecurity(applicationContext). + withTorValidation(). + withBestProxy(). + build(StrongBuild.this); + + }catch(Exception e){ + e.printStackTrace(); + Log.e("info", "ERROR"); + } + } + + @Override + public void onConnected(final OkHttpClient okHttpClient){ + Log.e("info" , "CONNECTED Strong Builder"); + + view.loadDataWithBaseURL(null, "ASD", "text/html", "utf-8", null); + new Thread(new Runnable(){ + @Override + public void run(){ + try{ + Request request = new Request.Builder().url(url).build(); + Response response = okHttpClient.newCall(request).execute(); + + Log.e("info", "RESPONSE: "+response.toString()); + Log.e("info", response.body().string()); + htmlCode = response.body().string(); + + Log.d("LOADING : " ,"LOADING"); + view.loadDataWithBaseURL(null, htmlCode, "text/html", "utf-8", null); + + }catch(Exception e){ + e.printStackTrace(); + Log.e("info", "ERROR - ATTEMPTING CONNECTION TO ONION DOMAIN"); + } + } + }).start(); + } + + @Override + public void onConnectionException(Exception e){ + Log.e("info" , "Exception"); + } + + @Override + public void onTimeout(){ + Log.e("info" , "Timeout"); + } + + @Override + public void onInvalid(){ + Log.e("info" , "Invalid"); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/admanager.java b/app/src/main/java/com/example/myapplication/admanager.java new file mode 100644 index 00000000..81bc9b01 --- /dev/null +++ b/app/src/main/java/com/example/myapplication/admanager.java @@ -0,0 +1,64 @@ +package com.example.myapplication; + +import android.content.Context; +import com.google.android.gms.ads.AdListener; +import com.google.android.gms.ads.AdRequest; +import com.google.android.gms.ads.InterstitialAd; +import com.google.android.gms.ads.MobileAds; + +public class admanager { + private static final admanager ourInstance = new admanager(); + + public static admanager getInstance() { + return ourInstance; + } + private InterstitialAd mInterstitialAd; + + + private admanager() { + } + + public void initialize(Context applicationContext) + { + MobileAds.initialize(applicationContext, "ca-app-pub-5074525529134731~2926711128"); + mInterstitialAd = new InterstitialAd(applicationContext); + mInterstitialAd.setAdUnitId("ca-app-pub-3940256099942544/1033173712"); + implementListeners(); + mInterstitialAd.loadAd(new AdRequest.Builder().build()); + } + + public void implementListeners() + { + mInterstitialAd.setAdListener(new AdListener() { + @Override + public void onAdLoaded() { + // Code to be executed when an ad finishes loading. + } + + @Override + public void onAdFailedToLoad(int errorCode) { + mInterstitialAd.loadAd(new AdRequest.Builder().build()); + } + + @Override + public void onAdOpened() { + } + + @Override + public void onAdLeftApplication() { + // Code to be executed when the user has left the app. + } + + @Override + public void onAdClosed() { + mInterstitialAd.loadAd(new AdRequest.Builder().build()); + } + }); + } + + public void showAd() + { + mInterstitialAd.show(); + } + +} diff --git a/app/src/main/java/com/example/myapplication/applicationController.java b/app/src/main/java/com/example/myapplication/applicationController.java new file mode 100644 index 00000000..0f90abb8 --- /dev/null +++ b/app/src/main/java/com/example/myapplication/applicationController.java @@ -0,0 +1,303 @@ +package com.example.myapplication; + +import android.annotation.TargetApi; +import android.content.Context; +import android.graphics.Color; +import android.os.Build; +import android.support.constraint.ConstraintLayout; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import android.util.Log; +import android.util.Patterns; +import android.view.KeyEvent; +import android.view.View; +import android.view.WindowManager; +import android.view.inputmethod.InputMethodManager; +import android.webkit.*; +import android.widget.*; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.Stack; + +import static java.lang.Thread.sleep; + +public class applicationController extends AppCompatActivity +{ + + /*View Objects*/ + private WebView webView1; + private WebView webView2; + private ProgressBar progressBar; + private ConstraintLayout requestFailure; + private ConstraintLayout splashScreen; + private Button reloadButton; + private ImageButton homeButton; + private EditText searchbar; + private LinearLayout topbar; + + /*helper Variables*/ + String currentURL = "http://boogle.store/"; + boolean isRequestError = false; + boolean hasApplicationLoaded = false; + Stack urlList = new Stack(); + int scroll1y=0; + int scroll2y=0; + + /*Initialization*/ + @Override + protected void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_application_controller); + initializeView(); + initializeProxy(); + initializeAds(); + } + + public void initializeProxy() + { + //torProxyServer.getInstance().initialize(this,this); + } + + public void initializeAds() + { + + admanager.getInstance().initialize(this); + } + + /*Initialization*/ + public void initializeView() + { + webView1 = (WebView) findViewById(R.id.pageLoader1); + webView2 = (WebView) findViewById(R.id.pageLoader2); + progressBar = (ProgressBar) findViewById(R.id.progressBar); + requestFailure = (ConstraintLayout) findViewById(R.id.requestFailure); + splashScreen = (ConstraintLayout) findViewById(R.id.splashScreen); + reloadButton = (Button) findViewById(R.id.reloadButton); + homeButton = (ImageButton) findViewById(R.id.home); + searchbar = (EditText) findViewById(R.id.search); + topbar = (LinearLayout) findViewById(R.id.topbar); + webRequestHandler.getInstance().initialization(webView1,webView2,progressBar,searchbar,requestFailure); + webView1.bringToFront(); + + webView2.animate().setDuration(0).alpha(0f); + progressBar.setVisibility(View.INVISIBLE); + getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN); + + webView1.getSettings().setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK); + webView1.setBackgroundColor(Color.WHITE); + webView1.setWebViewClient(loadWebViewClient()); + webView1.getSettings().setJavaScriptEnabled(true); + webView1.loadUrl("http://boogle.store/"); + + webView2.getSettings().setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK); + webView2.setBackgroundColor(Color.WHITE); + webView2.setWebViewClient(loadWebViewClient()); + webView2.getSettings().setJavaScriptEnabled(true); + + requestFailure.animate().setDuration(0).alpha(0.0f); + initializeViewClients(); + + } + + private void initializeViewClients() + { + homeButton.setOnClickListener(new View.OnClickListener(){ + + @Override + public void onClick(View v) + { + webRequestHandler.getInstance().loadURL("http://boogle.store/"); + isRequestError = false; + } + }); + + searchbar.setOnEditorActionListener(new EditText.OnEditorActionListener() + { + @Override + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) + { + try + { + String url = v.getText().toString(); + if(!url.startsWith("www.")&& !url.startsWith("http://")){ + url = "www."+url; + } + if(!url.startsWith("http://")){ + url = "http://"+url; + } + + + boolean isUrlValid = Patterns.WEB_URL.matcher(url).matches(); + + URL host = new URL(url); + Log.i("SUPER WOW",host.getHost()); + if(isUrlValid && host.getHost().replace("www.","").contains(".")) + { + Log.i("WOW1","WOW1"); + if(host.getHost().contains(".onion")||host.getHost().contains("boogle.store")||host.getHost().contains("genesis.store")) + { + webRequestHandler.getInstance().loadURL(v.getText().toString()); + } + else + { + Toast.makeText(getApplicationContext(), "Only onion urls allowed", Toast.LENGTH_SHORT).show(); + } + } + else + { + webRequestHandler.getInstance().loadURL("http://boogle.store/search?q="+v.getText().toString().replaceAll(" ","+")+"&p_num=1&s_type=all"); + } + } + catch (IOException e) + { + webRequestHandler.getInstance().loadURL("http://boogle.store/search?q="+v.getText().toString().replaceAll(" ","+")+"&p_num=1&s_type=all"); + e.printStackTrace(); + } + return false; + } + }); + + Log.i("HELL1","HELL1"); + reloadButton.setOnClickListener(new View.OnClickListener(){ + + @Override + public void onClick(View v) + { + Log.i("HELL2","HELL2"); + webRequestHandler.getInstance().loadURL(currentURL); + isRequestError = false; + } + }); + + } + + public static String getDomainName(String url) throws URISyntaxException + { + URI uri = new URI(url); + String domain = uri.getHost(); + return domain.startsWith("www.") ? domain.substring(4) : domain; + } + + + @TargetApi(Build.VERSION_CODES.M) + private void onChromeClientProgressChanged(int newProgress) + { + + } + + @Override + public void onBackPressed() + { + if(urlList.size()>0) + { + boolean crul = !currentURL.equals("http://boogle.store/"); + boolean peek = !urlList.peek().equals("http://boogle.store/"); + + Log.i("WOW",urlList.size() + "---" + urlList.peek() + "---" + currentURL + "---" + peek); + currentURL = urlList.peek().toString(); + + if(!currentURL.equals(urlList.peek()) && crul) + { + webRequestHandler.getInstance().loadURL("http://boogle.store/"); + } + else + { + webRequestHandler.getInstance().loadURL(urlList.pop().toString()); + } + } + } + + private WebViewClient loadWebViewClient() + { + WebViewClient client = new WebViewClient() + { + @Override + public boolean shouldOverrideUrlLoading(WebView view, String url) + { + Log.i("OVERRIDING URL 1 : ","SUCCESS : " + view); + System.out.println(url); + if(!currentURL.equals(url)) + { + urlList.add(currentURL); + currentURL = url; + } + if(!url.toString().contains("boogle")) + { + Log.i("OVERRIDING URL 2 : ","SUCCESS : " + url); + //admanager.getInstance().showAd(); + //StrongBuild.getInstance().loadURL("https://stackoverflow.com/questions/4543349/load-local-html-in-webview",view,getApplicationContext()); + } + else + { + Log.i("OVERRIDING URL 3 : ","SUCCESS : " + url); + webRequestHandler.getInstance().loadURL(url); + return true; + } + return false; + } + @Override + public void onPageFinished(WebView view, String url) + { + Log.i("APPLIED : ","SUCCESS : APPLIED"); + super.onPageFinished(view, url); + webView1.animate().setDuration(250).alpha(1f); + webView2.animate().setDuration(250).alpha(1f); + + if(!hasApplicationLoaded) + { + try + { + sleep(2000); + } + catch (InterruptedException e) + { + e.printStackTrace(); + } + hasApplicationLoaded = true; + splashScreen.animate().alpha(0.0f).setDuration(500).setListener(null).withEndAction((new Runnable() { + @Override + public void run() + { + splashScreen.setVisibility(View.GONE); + } + })); + } + if(!isRequestError) + { + requestFailure.animate().alpha(0.0f).setDuration(500).setListener(null).withEndAction((new Runnable() { + @Override + public void run() + { + requestFailure.setVisibility(View.GONE); + //reloadButton.setEnabled(false); + } + })); + } + isRequestError = false; + + InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(searchbar.getWindowToken(), 0); + } + + @Override + public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) + { + Log.i("SUPME3 : ","ERROR"); + //reloadButton.setEnabled(true); + System.out.println("SUP2"); + requestFailure.setVisibility(View.VISIBLE); + requestFailure.animate().alpha(1.0f); + isRequestError = true; + + super.onReceivedError(view, errorCode, description, failingUrl); + } + }; + + return client; + } + +} diff --git a/app/src/main/java/com/example/myapplication/torProxyServer.java b/app/src/main/java/com/example/myapplication/torProxyServer.java new file mode 100644 index 00000000..ff99eab3 --- /dev/null +++ b/app/src/main/java/com/example/myapplication/torProxyServer.java @@ -0,0 +1,52 @@ +package com.example.myapplication; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import info.guardianproject.netcipher.NetCipher; +import info.guardianproject.netcipher.proxy.OrbotHelper; +import android.util.Log; +import info.guardianproject.netcipher.proxy.StatusCallback; + +public class torProxyServer { + private static final torProxyServer ourInstance = new torProxyServer(); + + public static torProxyServer getInstance() { + return ourInstance; + } + + private torProxyServer() { + } + + public void initialize(Context applicationContext, final Activity activity) + { + if (OrbotHelper.isOrbotInstalled(activity)) + { + NetCipher.useTor(); + OrbotHelper.get(activity).statusTimeout(60000).addStatusCallback(new StatusCallback(){ + @Override + public void onEnabled(Intent intent){ Log.d("Log Started","Log Started"); } + + @Override + public void onStarting() { } + + @Override + public void onStopping(){ } + + @Override + public void onDisabled(){ } + + @Override + public void onStatusTimeout(){ } + + @Override + public void onNotYetInstalled(){ } + }).init(); + } + else + { + Intent intent = OrbotHelper.getOrbotInstallIntent(activity); + activity.startActivityForResult(intent,0); + } + } +} diff --git a/app/src/main/java/com/example/myapplication/webRequestHandler.java b/app/src/main/java/com/example/myapplication/webRequestHandler.java new file mode 100644 index 00000000..e467c781 --- /dev/null +++ b/app/src/main/java/com/example/myapplication/webRequestHandler.java @@ -0,0 +1,184 @@ +package com.example.myapplication; + +import android.os.Message; +import android.support.constraint.ConstraintLayout; +import android.util.Log; +import android.view.View; +import android.webkit.WebView; +import android.widget.EditText; +import android.widget.ProgressBar; +import cz.msebera.android.httpclient.HttpResponse; +import cz.msebera.android.httpclient.client.HttpClient; +import cz.msebera.android.httpclient.client.methods.HttpGet; +import cz.msebera.android.httpclient.impl.client.DefaultHttpClient; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import android.os.Handler; + +public class webRequestHandler +{ + private static final webRequestHandler ourInstance = new webRequestHandler(); + private WebView[] view = new WebView[2];; + private int viewIndex = 1; + private int currentViewIndex = 0; + private String html = ""; + private String baseURL = ""; + private Boolean isLoading = false; + private ProgressBar progressBar; + private EditText searchbar; + private HttpClient client = null; + private Thread clientThread = null; + private Boolean isRendering = false; + private ConstraintLayout requestFailure; + + public static webRequestHandler getInstance() { + return ourInstance; + } + + private webRequestHandler() + { + } + + public void initialization(WebView view1,WebView view2,ProgressBar progressBar,EditText searchbar,ConstraintLayout requestFailure) + { + this.view[0] = view1; + this.view[1] = view2; + this.progressBar = progressBar; + this.searchbar = searchbar; + this.requestFailure = requestFailure; + createUpdateUiHandler(); + } + + public void loadURL(final String url) + { + try + { + progressBar.setVisibility(View.VISIBLE); + if(isRendering) + { + return; + } + + if(!isLoading) + { + searchbar.setText(url.replace("http://boogle.store","http://genesis.onion")); + } + else if(client!=null) + { + client.getConnectionManager().shutdown(); + isLoading = false; + } + if(clientThread!=null) + { + clientThread.interrupt(); + } + client = new DefaultHttpClient(); + } + catch (Exception ex) + { + Log.d("SUPER WOW1","SUPER WOW"); + } + clientThread = new Thread(new Runnable() { + + @Override + public void run() { + try + { + isLoading = true; + HttpGet request = new HttpGet(url); + baseURL = url; + HttpResponse response = client.execute(request); + InputStream in = response.getEntity().getContent(); + BufferedReader reader = new BufferedReader(new InputStreamReader(in)); + StringBuilder str = new StringBuilder(); + String line = null; + + while((line = reader.readLine()) != null) + { + if(!isLoading) + { + return; + } + str.append(line); + } + in.close(); + html = str.toString(); + Message message = new Message(); + message.what = MESSAGE_UPDATE_TEXT_CHILD_THREAD; + updateUIHandler.sendMessage(message); + } + catch (Exception e) + { + Message message = new Message(); + message.what = INTERNET_ERROR; + updateUIHandler.sendMessage(message); + Log.d("SUPER WOW2","SUPER WOW"); + e.printStackTrace(); + } + } + }); + clientThread.start(); + } + + private Handler updateUIHandler = null; + private final static int MESSAGE_UPDATE_TEXT_CHILD_THREAD =1; + private final static int INTERNET_ERROR =2; + + public WebView getView() + { + return view[currentViewIndex]; + } + + private void createUpdateUiHandler() + { + + if(updateUIHandler == null) + { + updateUIHandler = new Handler() + { + @Override + public void handleMessage(Message msg) { + Log.i("APPLYING : ","SUCCESS : APPLYING"); + if(msg.what == MESSAGE_UPDATE_TEXT_CHILD_THREAD && isLoading &&!isRendering) + { + isRendering = true; + view[viewIndex].animate().setDuration(0).alpha(0f); + view[viewIndex].bringToFront(); + view[viewIndex].loadDataWithBaseURL(baseURL,html, "text/html", "utf-8", null); + + if(viewIndex==1) + { + viewIndex = 0; + currentViewIndex =1; + } + else + { + viewIndex = 1; + currentViewIndex=0; + } + view[currentViewIndex].animate().setDuration(0).alpha(0f).withEndAction((new Runnable() { + @Override + public void run() + { + isLoading = false; + progressBar.setVisibility(View.INVISIBLE); + isRendering = false; + } + })); + } + else if (msg.what == INTERNET_ERROR) + { + isRendering = false; + isLoading = false; + progressBar.setVisibility(View.INVISIBLE); + requestFailure.setVisibility(View.VISIBLE); + requestFailure.animate().alpha(1f).setDuration(500); + } + } + }; + } + } +} diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 00000000..6348baae --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/backgradient.xml b/app/src/main/res/drawable/backgradient.xml new file mode 100644 index 00000000..b08efe05 --- /dev/null +++ b/app/src/main/res/drawable/backgradient.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/border.xml b/app/src/main/res/drawable/border.xml new file mode 100644 index 00000000..f26d56cb --- /dev/null +++ b/app/src/main/res/drawable/border.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/cursorcolor.xml b/app/src/main/res/drawable/cursorcolor.xml new file mode 100644 index 00000000..711a1507 --- /dev/null +++ b/app/src/main/res/drawable/cursorcolor.xml @@ -0,0 +1,4 @@ + + + + diff --git a/app/src/main/res/drawable/homeicon.png b/app/src/main/res/drawable/homeicon.png new file mode 100644 index 00000000..60f3721f Binary files /dev/null and b/app/src/main/res/drawable/homeicon.png differ diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 00000000..8428ee45 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/interneticon.png b/app/src/main/res/drawable/interneticon.png new file mode 100644 index 00000000..5a89c082 Binary files /dev/null and b/app/src/main/res/drawable/interneticon.png differ diff --git a/app/src/main/res/drawable/logolarge.png b/app/src/main/res/drawable/logolarge.png new file mode 100644 index 00000000..50585dfb Binary files /dev/null and b/app/src/main/res/drawable/logolarge.png differ diff --git a/app/src/main/res/drawable/pressedcolor.xml b/app/src/main/res/drawable/pressedcolor.xml new file mode 100644 index 00000000..a829bc6a --- /dev/null +++ b/app/src/main/res/drawable/pressedcolor.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/progress_style.xml b/app/src/main/res/drawable/progress_style.xml new file mode 100644 index 00000000..9b4949da --- /dev/null +++ b/app/src/main/res/drawable/progress_style.xml @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/app/src/main/res/drawable/searchbar.xml b/app/src/main/res/drawable/searchbar.xml new file mode 100644 index 00000000..1a9831bf --- /dev/null +++ b/app/src/main/res/drawable/searchbar.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/shadow.xml b/app/src/main/res/drawable/shadow.xml new file mode 100644 index 00000000..f382569f --- /dev/null +++ b/app/src/main/res/drawable/shadow.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape.xml b/app/src/main/res/drawable/shape.xml new file mode 100644 index 00000000..3adaf3de --- /dev/null +++ b/app/src/main/res/drawable/shape.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/topbar.xml b/app/src/main/res/drawable/topbar.xml new file mode 100644 index 00000000..21ef3716 --- /dev/null +++ b/app/src/main/res/drawable/topbar.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/touch_selector.xml b/app/src/main/res/drawable/touch_selector.xml new file mode 100644 index 00000000..fdabe290 --- /dev/null +++ b/app/src/main/res/drawable/touch_selector.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_application_controller.xml b/app/src/main/res/layout/activity_application_controller.xml new file mode 100644 index 00000000..0bbdbf9d --- /dev/null +++ b/app/src/main/res/layout/activity_application_controller.xml @@ -0,0 +1,199 @@ + + + + + + + + + + + + + +