diff --git a/app/src/main/java/com/nextcloud/talk/dagger/modules/RestModule.java b/app/src/main/java/com/nextcloud/talk/dagger/modules/RestModule.java deleted file mode 100644 index 6da4de8c3..000000000 --- a/app/src/main/java/com/nextcloud/talk/dagger/modules/RestModule.java +++ /dev/null @@ -1,320 +0,0 @@ -/* - * Nextcloud Talk application - * - * @author Mario Danic - * Copyright (C) 2017 Mario Danic (mario@lovelyhq.com) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.nextcloud.talk.dagger.modules; - -import android.content.Context; -import android.text.TextUtils; -import android.util.Log; - -import com.github.aurae.retrofit2.LoganSquareConverterFactory; -import com.nextcloud.talk.BuildConfig; -import com.nextcloud.talk.R; -import com.nextcloud.talk.api.NcApi; -import com.nextcloud.talk.application.NextcloudTalkApplication; -import com.nextcloud.talk.users.UserManager; -import com.nextcloud.talk.utils.ApiUtils; -import com.nextcloud.talk.utils.LoggingUtils; -import com.nextcloud.talk.utils.preferences.AppPreferences; -import com.nextcloud.talk.utils.ssl.KeyManager; -import com.nextcloud.talk.utils.ssl.SSLSocketFactoryCompat; -import com.nextcloud.talk.utils.ssl.TrustManager; - -import java.io.IOException; -import java.net.CookieManager; -import java.net.InetSocketAddress; -import java.net.Proxy; -import java.nio.charset.StandardCharsets; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.UnrecoverableKeyException; -import java.security.cert.CertificateException; -import java.util.concurrent.TimeUnit; - -import javax.inject.Singleton; -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.X509KeyManager; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import dagger.Module; -import dagger.Provides; -import io.reactivex.schedulers.Schedulers; -import okhttp3.Authenticator; -import okhttp3.Cache; -import okhttp3.Credentials; -import okhttp3.Dispatcher; -import okhttp3.Interceptor; -import okhttp3.JavaNetCookieJar; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; -import okhttp3.Route; -import okhttp3.internal.tls.OkHostnameVerifier; -import okhttp3.logging.HttpLoggingInterceptor; -import retrofit2.Retrofit; -import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; - -@Module(includes = DatabaseModule.class) -public class RestModule { - - private static final String TAG = "RestModule"; - private final Context context; - - public RestModule(Context context) { - this.context = context; - } - - @Singleton - @Provides - NcApi provideNcApi(Retrofit retrofit) { - return retrofit.create(NcApi.class); - } - - @Singleton - @Provides - Proxy provideProxy(AppPreferences appPreferences) { - if (!TextUtils.isEmpty(appPreferences.getProxyType()) && !"No proxy".equals(appPreferences.getProxyType()) - && !TextUtils.isEmpty(appPreferences.getProxyHost())) { - GetProxyRunnable getProxyRunnable = new GetProxyRunnable(appPreferences); - Thread getProxyThread = new Thread(getProxyRunnable); - getProxyThread.start(); - try { - getProxyThread.join(); - return getProxyRunnable.getProxyValue(); - } catch (InterruptedException e) { - Log.e(TAG, "Failed to join the thread while getting proxy: " + e.getLocalizedMessage()); - return Proxy.NO_PROXY; - } - } else { - return Proxy.NO_PROXY; - } - } - - @Singleton - @Provides - Retrofit provideRetrofit(OkHttpClient httpClient) { - Retrofit.Builder retrofitBuilder = new Retrofit.Builder() - .client(httpClient) - .baseUrl("https://nextcloud.com") - .addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io())) - .addConverterFactory(LoganSquareConverterFactory.create()); - - return retrofitBuilder.build(); - } - - @Singleton - @Provides - TrustManager provideTrustManager() { - return new TrustManager(); - } - - @Singleton - @Provides - KeyManager provideKeyManager(AppPreferences appPreferences, UserManager userManager) { - KeyStore keyStore = null; - try { - keyStore = KeyStore.getInstance("AndroidKeyStore"); - keyStore.load(null); - KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - kmf.init(keyStore, null); - X509KeyManager origKm = (X509KeyManager) kmf.getKeyManagers()[0]; - return new KeyManager(origKm, userManager, appPreferences); - } catch (KeyStoreException e) { - Log.e(TAG, "KeyStoreException " + e.getLocalizedMessage()); - } catch (CertificateException e) { - Log.e(TAG, "CertificateException " + e.getLocalizedMessage()); - } catch (NoSuchAlgorithmException e) { - Log.e(TAG, "NoSuchAlgorithmException " + e.getLocalizedMessage()); - } catch (IOException e) { - Log.e(TAG, "IOException " + e.getLocalizedMessage()); - } catch (UnrecoverableKeyException e) { - Log.e(TAG, "UnrecoverableKeyException " + e.getLocalizedMessage()); - } - - return null; - } - - @Singleton - @Provides - SSLSocketFactoryCompat provideSslSocketFactoryCompat(KeyManager keyManager, TrustManager - trustManager) { - return new SSLSocketFactoryCompat(keyManager, trustManager); - } - - @Singleton - @Provides - CookieManager provideCookieManager() { - return new CookieManager(); - } - - @Singleton - @Provides - Cache provideCache() { - int cacheSize = 128 * 1024 * 1024; // 128 MB - - return new Cache(NextcloudTalkApplication.Companion.getSharedApplication().getCacheDir(), cacheSize); - } - - @Singleton - @Provides - Dispatcher provideDispatcher() { - Dispatcher dispatcher = new Dispatcher(); - dispatcher.setMaxRequestsPerHost(100); - dispatcher.setMaxRequests(100); - return dispatcher; - } - - @Singleton - @Provides - OkHttpClient provideHttpClient(Proxy proxy, AppPreferences appPreferences, - TrustManager trustManager, - SSLSocketFactoryCompat sslSocketFactoryCompat, Cache cache, - CookieManager cookieManager, Dispatcher dispatcher) { - OkHttpClient.Builder httpClient = new OkHttpClient.Builder(); - - httpClient.retryOnConnectionFailure(true); - httpClient.connectTimeout(45, TimeUnit.SECONDS); - httpClient.readTimeout(45, TimeUnit.SECONDS); - httpClient.writeTimeout(45, TimeUnit.SECONDS); - - httpClient.cookieJar(new JavaNetCookieJar(cookieManager)); - httpClient.cache(cache); - - // Trust own CA and all self-signed certs - httpClient.sslSocketFactory(sslSocketFactoryCompat, trustManager); - httpClient.retryOnConnectionFailure(true); - httpClient.hostnameVerifier(trustManager.getHostnameVerifier(OkHostnameVerifier.INSTANCE)); - - httpClient.dispatcher(dispatcher); - if (!Proxy.NO_PROXY.equals(proxy)) { - httpClient.proxy(proxy); - - if (appPreferences.getProxyCredentials() && - !TextUtils.isEmpty(appPreferences.getProxyUsername()) && - !TextUtils.isEmpty(appPreferences.getProxyPassword())) { - httpClient.proxyAuthenticator(new HttpAuthenticator( - Credentials.basic( - appPreferences.getProxyUsername(), - appPreferences.getProxyPassword(), - StandardCharsets.UTF_8), - "Proxy-Authorization")); - } - } - - httpClient.addInterceptor(new HeadersInterceptor()); - - if (BuildConfig.DEBUG && !context.getResources().getBoolean(R.bool.nc_is_debug)) { - HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(); - loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY); - loggingInterceptor.redactHeader("Authorization"); - loggingInterceptor.redactHeader("Proxy-Authorization"); - httpClient.addInterceptor(loggingInterceptor); - } else if (context.getResources().getBoolean(R.bool.nc_is_debug)) { - HttpLoggingInterceptor.Logger fileLogger = - s -> LoggingUtils.INSTANCE.writeLogEntryToFile(context, s); - HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(fileLogger); - loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY); - loggingInterceptor.redactHeader("Authorization"); - loggingInterceptor.redactHeader("Proxy-Authorization"); - httpClient.addInterceptor(loggingInterceptor); - } - - return httpClient.build(); - } - - public static class HeadersInterceptor implements Interceptor { - - @NonNull - @Override - public Response intercept(@NonNull Chain chain) throws IOException { - Request original = chain.request(); - Request request = original.newBuilder() - .header("User-Agent", ApiUtils.getUserAgent()) - .header("Accept", "application/json") - .header("OCS-APIRequest", "true") - .header("ngrok-skip-browser-warning", "true") - .method(original.method(), original.body()) - .build(); - - return chain.proceed(request); - } - } - - public static class HttpAuthenticator implements Authenticator { - - private String credentials; - private String authenticatorType; - - public HttpAuthenticator(@NonNull String credentials, @NonNull String authenticatorType) { - this.credentials = credentials; - this.authenticatorType = authenticatorType; - } - - @Nullable - @Override - public Request authenticate(@Nullable Route route, @NonNull Response response) { - if (response.request().header(authenticatorType) != null) { - return null; - } - - Response countedResponse = response; - - int attemptsCount = 0; - - while ((countedResponse = countedResponse.priorResponse()) != null) { - attemptsCount++; - if (attemptsCount == 3) { - return null; - } - } - - return response.request().newBuilder() - .header(authenticatorType, credentials) - .build(); - } - } - - private class GetProxyRunnable implements Runnable { - private volatile Proxy proxy; - private AppPreferences appPreferences; - - GetProxyRunnable(AppPreferences appPreferences) { - this.appPreferences = appPreferences; - } - - @Override - public void run() { - if (Proxy.Type.valueOf(appPreferences.getProxyType()) == Proxy.Type.SOCKS) { - proxy = new Proxy(Proxy.Type.valueOf(appPreferences.getProxyType()), - InetSocketAddress.createUnresolved(appPreferences.getProxyHost(), Integer.parseInt( - appPreferences.getProxyPort()))); - } else { - proxy = new Proxy(Proxy.Type.valueOf(appPreferences.getProxyType()), - new InetSocketAddress(appPreferences.getProxyHost(), - Integer.parseInt(appPreferences.getProxyPort()))); - } - } - - Proxy getProxyValue() { - return proxy; - } - } -} diff --git a/app/src/main/java/com/nextcloud/talk/dagger/modules/RestModule.kt b/app/src/main/java/com/nextcloud/talk/dagger/modules/RestModule.kt new file mode 100644 index 000000000..cb1760bc2 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/dagger/modules/RestModule.kt @@ -0,0 +1,290 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * Copyright (C) 2017 Mario Danic (mario@lovelyhq.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.nextcloud.talk.dagger.modules + +import android.content.Context +import android.text.TextUtils +import android.util.Log +import com.github.aurae.retrofit2.LoganSquareConverterFactory +import com.nextcloud.talk.BuildConfig +import com.nextcloud.talk.R +import com.nextcloud.talk.api.NcApi +import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication +import com.nextcloud.talk.users.UserManager +import com.nextcloud.talk.utils.ApiUtils.userAgent +import com.nextcloud.talk.utils.LoggingUtils.writeLogEntryToFile +import com.nextcloud.talk.utils.preferences.AppPreferences +import com.nextcloud.talk.utils.ssl.KeyManager +import com.nextcloud.talk.utils.ssl.SSLSocketFactoryCompat +import com.nextcloud.talk.utils.ssl.TrustManager +import dagger.Module +import dagger.Provides +import io.reactivex.schedulers.Schedulers +import okhttp3.Authenticator +import okhttp3.Cache +import okhttp3.Credentials.basic +import okhttp3.Dispatcher +import okhttp3.Interceptor +import okhttp3.Interceptor.Chain +import okhttp3.JavaNetCookieJar +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import okhttp3.Route +import okhttp3.internal.tls.OkHostnameVerifier +import okhttp3.logging.HttpLoggingInterceptor +import retrofit2.Retrofit +import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory +import java.io.IOException +import java.net.CookieManager +import java.net.InetSocketAddress +import java.net.Proxy +import java.nio.charset.StandardCharsets +import java.security.KeyStore +import java.security.KeyStoreException +import java.security.NoSuchAlgorithmException +import java.security.UnrecoverableKeyException +import java.security.cert.CertificateException +import java.util.concurrent.TimeUnit +import javax.inject.Singleton +import javax.net.ssl.KeyManagerFactory +import javax.net.ssl.X509KeyManager +import kotlin.concurrent.Volatile + +@Module(includes = [DatabaseModule::class]) +class RestModule(private val context: Context) { + @Singleton + @Provides + fun provideNcApi(retrofit: Retrofit): NcApi { + return retrofit.create(NcApi::class.java) + } + + @Singleton + @Provides + fun provideProxy(appPreferences: AppPreferences): Proxy? { + return if (!TextUtils.isEmpty(appPreferences.getProxyType()) && "No proxy" != appPreferences.getProxyType() && + !TextUtils.isEmpty(appPreferences.getProxyHost()) + ) { + val getProxyRunnable = GetProxyRunnable(appPreferences) + val getProxyThread = Thread(getProxyRunnable) + getProxyThread.start() + try { + getProxyThread.join() + getProxyRunnable.proxyValue + } catch (e: InterruptedException) { + Log.e(TAG, "Failed to join the thread while getting proxy: " + e.localizedMessage) + Proxy.NO_PROXY + } + } else { + Proxy.NO_PROXY + } + } + + @Singleton + @Provides + fun provideRetrofit(httpClient: OkHttpClient?): Retrofit { + val retrofitBuilder = Retrofit.Builder() + .client(httpClient!!) + .baseUrl("https://nextcloud.com") + .addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io())) + .addConverterFactory(LoganSquareConverterFactory.create()) + return retrofitBuilder.build() + } + + @Singleton + @Provides + fun provideTrustManager(): TrustManager { + return TrustManager() + } + + @Singleton + @Provides + fun provideKeyManager(appPreferences: AppPreferences?, userManager: UserManager?): KeyManager? { + val keyStore: KeyStore? + try { + keyStore = KeyStore.getInstance("AndroidKeyStore") + keyStore.load(null) + val kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()) + kmf.init(keyStore, null) + val origKm = kmf.keyManagers[0] as X509KeyManager + return KeyManager(origKm, userManager, appPreferences) + } catch (e: KeyStoreException) { + Log.e(TAG, "KeyStoreException " + e.localizedMessage) + } catch (e: CertificateException) { + Log.e(TAG, "CertificateException " + e.localizedMessage) + } catch (e: NoSuchAlgorithmException) { + Log.e(TAG, "NoSuchAlgorithmException " + e.localizedMessage) + } catch (e: IOException) { + Log.e(TAG, "IOException " + e.localizedMessage) + } catch (e: UnrecoverableKeyException) { + Log.e(TAG, "UnrecoverableKeyException " + e.localizedMessage) + } + return null + } + + @Singleton + @Provides + fun provideSslSocketFactoryCompat(keyManager: KeyManager?, trustManager: TrustManager?): SSLSocketFactoryCompat { + return SSLSocketFactoryCompat(keyManager, trustManager!!) + } + + @Singleton + @Provides + fun provideCookieManager(): CookieManager { + return CookieManager() + } + + @Singleton + @Provides + fun provideCache(): Cache { + val cacheSize = 128 * 1024 * 1024 // 128 MB + return Cache(sharedApplication!!.cacheDir, cacheSize.toLong()) + } + + @Singleton + @Provides + fun provideDispatcher(): Dispatcher { + val dispatcher = Dispatcher() + dispatcher.maxRequestsPerHost = 100 + dispatcher.maxRequests = 100 + return dispatcher + } + + @Singleton + @Provides + fun provideHttpClient( + proxy: Proxy?, + appPreferences: AppPreferences, + trustManager: TrustManager, + sslSocketFactoryCompat: SSLSocketFactoryCompat?, + cache: Cache?, + cookieManager: CookieManager?, + dispatcher: Dispatcher? + ): OkHttpClient { + val httpClient = OkHttpClient.Builder() + httpClient.retryOnConnectionFailure(true) + httpClient.connectTimeout(45, TimeUnit.SECONDS) + httpClient.readTimeout(45, TimeUnit.SECONDS) + httpClient.writeTimeout(45, TimeUnit.SECONDS) + httpClient.cookieJar(JavaNetCookieJar(cookieManager!!)) + httpClient.cache(cache) + + // Trust own CA and all self-signed certs + httpClient.sslSocketFactory(sslSocketFactoryCompat!!, trustManager) + httpClient.retryOnConnectionFailure(true) + httpClient.hostnameVerifier(trustManager.getHostnameVerifier(OkHostnameVerifier)) + httpClient.dispatcher(dispatcher!!) + if (Proxy.NO_PROXY != proxy) { + httpClient.proxy(proxy) + if (appPreferences.getProxyCredentials() && + !TextUtils.isEmpty(appPreferences.getProxyUsername()) && + !TextUtils.isEmpty(appPreferences.getProxyPassword()) + ) { + httpClient.proxyAuthenticator( + HttpAuthenticator( + basic( + appPreferences.getProxyUsername(), + appPreferences.getProxyPassword(), + StandardCharsets.UTF_8 + ), + "Proxy-Authorization" + ) + ) + } + } + httpClient.addInterceptor(HeadersInterceptor()) + if (BuildConfig.DEBUG && !context.resources.getBoolean(R.bool.nc_is_debug)) { + val loggingInterceptor = HttpLoggingInterceptor() + loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY) + loggingInterceptor.redactHeader("Authorization") + loggingInterceptor.redactHeader("Proxy-Authorization") + httpClient.addInterceptor(loggingInterceptor) + } else if (context.resources.getBoolean(R.bool.nc_is_debug)) { + val fileLogger = HttpLoggingInterceptor.Logger { s: String? -> writeLogEntryToFile(context, s!!) } + val loggingInterceptor = HttpLoggingInterceptor(fileLogger) + loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY) + loggingInterceptor.redactHeader("Authorization") + loggingInterceptor.redactHeader("Proxy-Authorization") + httpClient.addInterceptor(loggingInterceptor) + } + return httpClient.build() + } + + class HeadersInterceptor : Interceptor { + @Throws(IOException::class) + override fun intercept(chain: Chain): Response { + val original: Request = chain.request() + val request = original.newBuilder() + .header("User-Agent", userAgent) + .header("Accept", "application/json") + .header("OCS-APIRequest", "true") + .header("ngrok-skip-browser-warning", "true") + .method(original.method, original.body) + .build() + return chain.proceed(request) + } + } + + class HttpAuthenticator(private val credentials: String, private val authenticatorType: String) : Authenticator { + override fun authenticate(route: Route?, response: Response): Request? { + if (response.request.header(authenticatorType) != null) { + return null + } + var countedResponse = response + var attemptsCount = 0 + while (countedResponse.priorResponse.also { countedResponse = it!! } != null) { + attemptsCount++ + if (attemptsCount == 3) { + return null + } + } + return response.request.newBuilder() + .header(authenticatorType, credentials) + .build() + } + } + + private inner class GetProxyRunnable(private val appPreferences: AppPreferences) : Runnable { + @Volatile + var proxyValue: Proxy? = null + private set + + override fun run() { + proxyValue = if (Proxy.Type.valueOf(appPreferences.getProxyType()) == Proxy.Type.SOCKS) { + Proxy( + Proxy.Type.valueOf(appPreferences.getProxyType()), + InetSocketAddress.createUnresolved( + appPreferences.getProxyHost(), + appPreferences.getProxyPort().toInt() + ) + ) + } else { + Proxy( + Proxy.Type.valueOf(appPreferences.getProxyType()), + InetSocketAddress(appPreferences.getProxyHost(), appPreferences.getProxyPort().toInt()) + ) + } + } + } + + companion object { + private const val TAG = "RestModule" + } +}