From ed06b22a7733b7d889b9271437aa8311506d9572 Mon Sep 17 00:00:00 2001 From: davidoskky Date: Sat, 15 Apr 2023 18:49:46 +0200 Subject: [PATCH 01/12] Tentative self signed ssl support --- shared/build.gradle.kts | 15 ++-- .../readerforselfossv2/rest/SelfossApi.kt | 82 +++++++++++++++++++ .../readerforselfossv2/rest/SelfossApi.kt | 69 ++-------------- 3 files changed, 96 insertions(+), 70 deletions(-) create mode 100644 shared/src/androidMain/kotlin/bou/amine/apps/readerforselfossv2/rest/SelfossApi.kt diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts index 8408522..747592b 100644 --- a/shared/build.gradle.kts +++ b/shared/build.gradle.kts @@ -29,13 +29,13 @@ kotlin { sourceSets { val commonMain by getting { dependencies { - implementation("io.ktor:ktor-client-core:2.1.1") - implementation("io.ktor:ktor-client-content-negotiation:2.1.1") - implementation("io.ktor:ktor-serialization-kotlinx-json:2.1.1") - implementation("io.ktor:ktor-client-logging:2.1.1") + implementation("io.ktor:ktor-client-core:2.2.4") + implementation("io.ktor:ktor-client-content-negotiation:2.2.4") + implementation("io.ktor:ktor-serialization-kotlinx-json:2.2.4") + implementation("io.ktor:ktor-client-logging:2.2.4") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0") - implementation("io.ktor:ktor-client-auth:2.1.1") - implementation("org.jsoup:jsoup:1.14.3") + implementation("io.ktor:ktor-client-auth:2.2.4") + implementation("org.jsoup:jsoup:1.15.4") //Dependency Injection implementation("org.kodein.di:kodein-di:7.12.0") @@ -58,7 +58,8 @@ kotlin { } val androidMain by getting { dependencies { - implementation("io.ktor:ktor-client-okhttp:2.1.1") + implementation("com.squareup.okhttp3:okhttp:4.10.0") + implementation("io.ktor:ktor-client-okhttp:2.2.4") implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0") // Sql diff --git a/shared/src/androidMain/kotlin/bou/amine/apps/readerforselfossv2/rest/SelfossApi.kt b/shared/src/androidMain/kotlin/bou/amine/apps/readerforselfossv2/rest/SelfossApi.kt new file mode 100644 index 0000000..80c9b08 --- /dev/null +++ b/shared/src/androidMain/kotlin/bou/amine/apps/readerforselfossv2/rest/SelfossApi.kt @@ -0,0 +1,82 @@ +package bou.amine.apps.readerforselfossv2.rest + +import bou.amine.apps.readerforselfossv2.service.AppSettingsService +import io.github.aakira.napier.Napier +import io.ktor.client.* +import io.ktor.client.engine.okhttp.* +import io.ktor.client.plugins.* +import io.ktor.client.plugins.cache.* +import io.ktor.client.plugins.contentnegotiation.* +import io.ktor.client.plugins.cookies.* +import io.ktor.client.plugins.logging.* +import io.ktor.http.* +import io.ktor.serialization.kotlinx.json.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.serialization.json.Json +import okhttp3.OkHttpClient +import org.apache.http.conn.ssl.AllowAllHostnameVerifier +import java.security.cert.X509Certificate +import javax.net.ssl.SSLContext +import javax.net.ssl.X509TrustManager + +class NaiveTrustManager : X509TrustManager { + override fun checkClientTrusted(chain: Array?, authType: String?) {} + + override fun checkServerTrusted(chain: Array?, authType: String?) {} + + override fun getAcceptedIssuers(): Array = arrayOf() +} + +actual fun createHttpClient(appSettingsService: AppSettingsService, api: SelfossApi) = + HttpClient(OkHttp) { + engine { + val trustManager = NaiveTrustManager() + val sslContext = SSLContext.getInstance("TLS").apply { + init(null, arrayOf(trustManager), null) + } + preconfigured = OkHttpClient().newBuilder() + .sslSocketFactory( + sslSocketFactory = sslContext.socketFactory, + trustManager = trustManager + ) + .hostnameVerifier(AllowAllHostnameVerifier()) + .build() + } + install(ContentNegotiation) { + install(HttpCache) + json(Json { + prettyPrint = true + isLenient = true + ignoreUnknownKeys = true + }) + } + install(Logging) { + logger = object : Logger { + override fun log(message: String) { + Napier.d(message, tag = "LogApiCalls") + } + } + level = LogLevel.INFO + } + install(HttpTimeout) { + requestTimeoutMillis = appSettingsService.getApiTimeout() + } + install(HttpCookies) + install(HttpRequestRetry) { + maxRetries = 2 + retryIf { _, response -> + response.status == HttpStatusCode.Forbidden && api.shouldHavePostLogin() && api.hasLoginInfo() + } + modifyRequest { + Napier.i("Will modify", tag = "HttpSend") + CoroutineScope(Dispatchers.Main).launch { + Napier.i("Will login", tag = "HttpSend") + api.login() + Napier.i("Did login", tag = "HttpSend") + } + } + } + expectSuccess = false + } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/rest/SelfossApi.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/rest/SelfossApi.kt index b97bd93..924ef12 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/rest/SelfossApi.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/rest/SelfossApi.kt @@ -4,80 +4,23 @@ import bou.amine.apps.readerforselfossv2.model.SelfossModel import bou.amine.apps.readerforselfossv2.model.StatusAndData import bou.amine.apps.readerforselfossv2.model.SuccessResponse import bou.amine.apps.readerforselfossv2.service.AppSettingsService -import io.github.aakira.napier.Napier import io.ktor.client.* -import io.ktor.client.plugins.* -import io.ktor.client.plugins.auth.providers.* -import io.ktor.client.plugins.cache.* -import io.ktor.client.plugins.contentnegotiation.* -import io.ktor.client.plugins.cookies.* -import io.ktor.client.plugins.logging.* import io.ktor.client.request.* import io.ktor.client.statement.* import io.ktor.http.* -import io.ktor.serialization.kotlinx.json.* -import io.ktor.util.* -import io.ktor.utils.io.charsets.* -import io.ktor.utils.io.core.* -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.serialization.json.Json + +expect fun createHttpClient(appSettingsService: AppSettingsService, api: SelfossApi): HttpClient class SelfossApi(private val appSettingsService: AppSettingsService) { - var client = createHttpClient() - - private fun createHttpClient(): HttpClient { - val client = HttpClient { - install(ContentNegotiation) { - install(HttpCache) - json(Json { - prettyPrint = true - isLenient = true - ignoreUnknownKeys = true - explicitNulls = false - }) - } - install(Logging) { - logger = object : Logger { - override fun log(message: String) { - Napier.d(message, tag = "LogApiCalls") - } - } - level = LogLevel.INFO - } - install(HttpTimeout) { - requestTimeoutMillis = appSettingsService.getApiTimeout() - } - install(HttpCookies) - install(HttpRequestRetry) { - maxRetries = 2 - retryIf { _, response -> - response.status == HttpStatusCode.Forbidden && shouldHavePostLogin() && hasLoginInfo() - } - modifyRequest { - Napier.i("Will modify", tag = "HttpSend") - CoroutineScope(Dispatchers.Main).launch { - Napier.i("Will login", tag = "HttpSend") - this@SelfossApi.login() - Napier.i("Did login", tag = "HttpSend") - } - } - } - expectSuccess = false - } - - - return client - } + var client = createHttpClient(appSettingsService, this) fun url(path: String) = "${appSettingsService.getBaseUrl()}$path" fun refreshLoginInformation() { appSettingsService.refreshApiSettings() - client = createHttpClient() + client = createHttpClient(appSettingsService, this) } fun constructBasicAuthValue(credentials: BasicAuthCredentials): String { @@ -88,8 +31,8 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { } // Api version was introduces after the POST login, so when there is a version, it should be available - private fun shouldHavePostLogin() = appSettingsService.getApiVersion() != -1 - private fun hasLoginInfo() = + fun shouldHavePostLogin() = appSettingsService.getApiVersion() != -1 + fun hasLoginInfo() = appSettingsService.getUserName().isNotEmpty() && appSettingsService.getPassword() .isNotEmpty() From c4f4bafe851d824af772438ce559e2100038c50b Mon Sep 17 00:00:00 2001 From: davidoskky Date: Sat, 6 May 2023 14:36:14 +0200 Subject: [PATCH 02/12] Add a switch in the login screen to disable SSL --- androidApp/src/main/res/layout/activity_login.xml | 7 +++++++ androidApp/src/main/res/values/strings.xml | 3 ++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/androidApp/src/main/res/layout/activity_login.xml b/androidApp/src/main/res/layout/activity_login.xml index 7edc0e4..aaddf93 100644 --- a/androidApp/src/main/res/layout/activity_login.xml +++ b/androidApp/src/main/res/layout/activity_login.xml @@ -51,6 +51,13 @@ android:maxLines="1" android:minHeight="48dp" /> + + + "Reader for Selfoss" "Log in" "Password" @@ -6,6 +6,7 @@ "Password not long enough" "Field required" "Url" + "Disable SSL" "Login required ?" "Oops. You may need to add a \"/\" at the end of the url." "Username" From d167092c830a8c04aacafbabad0d6fc9b967fa75 Mon Sep 17 00:00:00 2001 From: davidoskky Date: Thu, 13 Jul 2023 14:52:25 +0200 Subject: [PATCH 03/12] Add a login switch to disable SSL verification --- .../android/LoginActivity.kt | 2 + .../readerforselfossv2/rest/SelfossApi.kt | 50 +++++++++++-------- .../readerforselfossv2/rest/SelfossApi.kt | 20 ++++++-- .../service/AppSettingsService.kt | 20 ++++++++ 4 files changed, 66 insertions(+), 26 deletions(-) diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/LoginActivity.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/LoginActivity.kt index f54bce0..5ccfe43 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/LoginActivity.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/LoginActivity.kt @@ -139,6 +139,8 @@ class LoginActivity : AppCompatActivity(), DIAware { showProgress(true) + appSettingsService.updateSelfSigned(binding.selfSigned.isChecked) + repository.refreshLoginInformation(url, login, password) CoroutineScope(Dispatchers.Main).launch { diff --git a/shared/src/androidMain/kotlin/bou/amine/apps/readerforselfossv2/rest/SelfossApi.kt b/shared/src/androidMain/kotlin/bou/amine/apps/readerforselfossv2/rest/SelfossApi.kt index 80c9b08..6baade8 100644 --- a/shared/src/androidMain/kotlin/bou/amine/apps/readerforselfossv2/rest/SelfossApi.kt +++ b/shared/src/androidMain/kotlin/bou/amine/apps/readerforselfossv2/rest/SelfossApi.kt @@ -2,15 +2,18 @@ package bou.amine.apps.readerforselfossv2.rest import bou.amine.apps.readerforselfossv2.service.AppSettingsService import io.github.aakira.napier.Napier -import io.ktor.client.* -import io.ktor.client.engine.okhttp.* -import io.ktor.client.plugins.* -import io.ktor.client.plugins.cache.* -import io.ktor.client.plugins.contentnegotiation.* -import io.ktor.client.plugins.cookies.* -import io.ktor.client.plugins.logging.* -import io.ktor.http.* -import io.ktor.serialization.kotlinx.json.* +import io.ktor.client.HttpClient +import io.ktor.client.engine.okhttp.OkHttp +import io.ktor.client.plugins.HttpRequestRetry +import io.ktor.client.plugins.HttpTimeout +import io.ktor.client.plugins.cache.HttpCache +import io.ktor.client.plugins.contentnegotiation.ContentNegotiation +import io.ktor.client.plugins.cookies.HttpCookies +import io.ktor.client.plugins.logging.LogLevel +import io.ktor.client.plugins.logging.Logger +import io.ktor.client.plugins.logging.Logging +import io.ktor.http.HttpStatusCode +import io.ktor.serialization.kotlinx.json.json import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -29,20 +32,25 @@ class NaiveTrustManager : X509TrustManager { override fun getAcceptedIssuers(): Array = arrayOf() } -actual fun createHttpClient(appSettingsService: AppSettingsService, api: SelfossApi) = +actual fun createHttpClient( + appSettingsService: AppSettingsService, + api: SelfossApi +) = HttpClient(OkHttp) { - engine { - val trustManager = NaiveTrustManager() - val sslContext = SSLContext.getInstance("TLS").apply { - init(null, arrayOf(trustManager), null) + if (appSettingsService.getSelfSigned()) { + engine { + val trustManager = NaiveTrustManager() + val sslContext = SSLContext.getInstance("TLS").apply { + init(null, arrayOf(trustManager), null) + } + preconfigured = OkHttpClient().newBuilder() + .sslSocketFactory( + sslSocketFactory = sslContext.socketFactory, + trustManager = trustManager + ) + .hostnameVerifier(AllowAllHostnameVerifier()) + .build() } - preconfigured = OkHttpClient().newBuilder() - .sslSocketFactory( - sslSocketFactory = sslContext.socketFactory, - trustManager = trustManager - ) - .hostnameVerifier(AllowAllHostnameVerifier()) - .build() } install(ContentNegotiation) { install(HttpCache) diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/rest/SelfossApi.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/rest/SelfossApi.kt index 924ef12..25677d5 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/rest/SelfossApi.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/rest/SelfossApi.kt @@ -4,12 +4,22 @@ import bou.amine.apps.readerforselfossv2.model.SelfossModel import bou.amine.apps.readerforselfossv2.model.StatusAndData import bou.amine.apps.readerforselfossv2.model.SuccessResponse import bou.amine.apps.readerforselfossv2.service.AppSettingsService -import io.ktor.client.* -import io.ktor.client.request.* -import io.ktor.client.statement.* -import io.ktor.http.* +import io.ktor.client.HttpClient +import io.ktor.client.plugins.auth.providers.BasicAuthCredentials +import io.ktor.client.request.get +import io.ktor.client.request.headers +import io.ktor.client.request.parameter +import io.ktor.client.statement.HttpResponse +import io.ktor.http.HttpHeaders +import io.ktor.http.Parameters +import io.ktor.util.encodeBase64 +import io.ktor.utils.io.charsets.Charsets +import io.ktor.utils.io.core.toByteArray -expect fun createHttpClient(appSettingsService: AppSettingsService, api: SelfossApi): HttpClient +expect fun createHttpClient( + appSettingsService: AppSettingsService, + api: SelfossApi +): HttpClient class SelfossApi(private val appSettingsService: AppSettingsService) { diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/service/AppSettingsService.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/service/AppSettingsService.kt index 22da4fa..4118781 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/service/AppSettingsService.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/service/AppSettingsService.kt @@ -8,6 +8,7 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) { // Api related private var _apiVersion: Int = -1 private var _publicAccess: Boolean? = null + private var _selfSigned: Boolean? = null private var _baseUrl: String = "" private var _userName: String = "" private var _basicUserName: String = "" @@ -77,6 +78,22 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) { _publicAccess = settings.getBoolean(API_PUBLIC_ACCESS, false) } + fun getSelfSigned(): Boolean { + if (_selfSigned == null) { + refreshSelfSigned() + } + return _selfSigned!! + } + + fun updateSelfSigned(selfSigned: Boolean) { + settings.putBoolean(API_SELF_SIGNED, selfSigned) + refreshSelfSigned() + } + + private fun refreshSelfSigned() { + _selfSigned = settings.getBoolean(API_SELF_SIGNED, false) + } + fun getBaseUrl(): String { if (_baseUrl.isEmpty()) { refreshBaseUrl() @@ -383,6 +400,7 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) { refreshBaseUrl() refreshApiVersion() refreshPublicAccess() + refreshSelfSigned() } fun refreshUserSettings() { @@ -468,6 +486,8 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) { const val API_PUBLIC_ACCESS = "apiPublicAccess" + const val API_SELF_SIGNED = "apiSelfSigned" + const val API_ITEMS_NUMBER = "prefer_api_items_number" const val API_TIMEOUT = "api_timeout" From 70ad5f322c491e2400b83c1a680bea35da77d000 Mon Sep 17 00:00:00 2001 From: davidoskky Date: Thu, 10 Aug 2023 21:43:13 +0200 Subject: [PATCH 04/12] Handle most HTTP client creation in common code --- .../android/LoginActivity.kt | 1 + shared/build.gradle.kts | 14 +-- .../readerforselfossv2/rest/NetworkSSL.kt | 17 ++++ .../readerforselfossv2/rest/SelfossApi.kt | 90 ------------------- .../readerforselfossv2/rest/SelfossApi.kt | 65 +++++++++++++- 5 files changed, 90 insertions(+), 97 deletions(-) create mode 100644 shared/src/androidMain/kotlin/bou/amine/apps/readerforselfossv2/rest/NetworkSSL.kt delete mode 100644 shared/src/androidMain/kotlin/bou/amine/apps/readerforselfossv2/rest/SelfossApi.kt diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/LoginActivity.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/LoginActivity.kt index 5ccfe43..5e337f9 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/LoginActivity.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/LoginActivity.kt @@ -144,6 +144,7 @@ class LoginActivity : AppCompatActivity(), DIAware { repository.refreshLoginInformation(url, login, password) CoroutineScope(Dispatchers.Main).launch { + repository.updateApiInformation() val result = repository.login() if (result) { val (errorFetching, displaySelfossOnly) = repository.shouldBeSelfossInstance() diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts index 747592b..493ae4b 100644 --- a/shared/build.gradle.kts +++ b/shared/build.gradle.kts @@ -29,12 +29,16 @@ kotlin { sourceSets { val commonMain by getting { dependencies { - implementation("io.ktor:ktor-client-core:2.2.4") - implementation("io.ktor:ktor-client-content-negotiation:2.2.4") - implementation("io.ktor:ktor-serialization-kotlinx-json:2.2.4") - implementation("io.ktor:ktor-client-logging:2.2.4") + val ktorVersion = "2.3.2" + + implementation("io.ktor:ktor-client-core:$ktorVersion") + implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion") + implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion") + implementation("io.ktor:ktor-client-logging:$ktorVersion") + implementation("io.ktor:ktor-client-auth:$ktorVersion") + implementation("io.ktor:ktor-client-cio:$ktorVersion") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0") - implementation("io.ktor:ktor-client-auth:2.2.4") + implementation("org.jsoup:jsoup:1.15.4") //Dependency Injection diff --git a/shared/src/androidMain/kotlin/bou/amine/apps/readerforselfossv2/rest/NetworkSSL.kt b/shared/src/androidMain/kotlin/bou/amine/apps/readerforselfossv2/rest/NetworkSSL.kt new file mode 100644 index 0000000..c2b6698 --- /dev/null +++ b/shared/src/androidMain/kotlin/bou/amine/apps/readerforselfossv2/rest/NetworkSSL.kt @@ -0,0 +1,17 @@ +package bou.amine.apps.readerforselfossv2.rest + +import io.ktor.client.engine.cio.CIOEngineConfig +import java.security.cert.X509Certificate +import javax.net.ssl.X509TrustManager + +class NaiveTrustManager : X509TrustManager { + override fun checkClientTrusted(chain: Array?, authType: String?) {} + + override fun checkServerTrusted(chain: Array?, authType: String?) {} + + override fun getAcceptedIssuers(): Array = arrayOf() +} + +actual fun setupInsecureHTTPEngine(config: CIOEngineConfig) { + config.https.trustManager = NaiveTrustManager() +} \ No newline at end of file diff --git a/shared/src/androidMain/kotlin/bou/amine/apps/readerforselfossv2/rest/SelfossApi.kt b/shared/src/androidMain/kotlin/bou/amine/apps/readerforselfossv2/rest/SelfossApi.kt deleted file mode 100644 index 6baade8..0000000 --- a/shared/src/androidMain/kotlin/bou/amine/apps/readerforselfossv2/rest/SelfossApi.kt +++ /dev/null @@ -1,90 +0,0 @@ -package bou.amine.apps.readerforselfossv2.rest - -import bou.amine.apps.readerforselfossv2.service.AppSettingsService -import io.github.aakira.napier.Napier -import io.ktor.client.HttpClient -import io.ktor.client.engine.okhttp.OkHttp -import io.ktor.client.plugins.HttpRequestRetry -import io.ktor.client.plugins.HttpTimeout -import io.ktor.client.plugins.cache.HttpCache -import io.ktor.client.plugins.contentnegotiation.ContentNegotiation -import io.ktor.client.plugins.cookies.HttpCookies -import io.ktor.client.plugins.logging.LogLevel -import io.ktor.client.plugins.logging.Logger -import io.ktor.client.plugins.logging.Logging -import io.ktor.http.HttpStatusCode -import io.ktor.serialization.kotlinx.json.json -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.serialization.json.Json -import okhttp3.OkHttpClient -import org.apache.http.conn.ssl.AllowAllHostnameVerifier -import java.security.cert.X509Certificate -import javax.net.ssl.SSLContext -import javax.net.ssl.X509TrustManager - -class NaiveTrustManager : X509TrustManager { - override fun checkClientTrusted(chain: Array?, authType: String?) {} - - override fun checkServerTrusted(chain: Array?, authType: String?) {} - - override fun getAcceptedIssuers(): Array = arrayOf() -} - -actual fun createHttpClient( - appSettingsService: AppSettingsService, - api: SelfossApi -) = - HttpClient(OkHttp) { - if (appSettingsService.getSelfSigned()) { - engine { - val trustManager = NaiveTrustManager() - val sslContext = SSLContext.getInstance("TLS").apply { - init(null, arrayOf(trustManager), null) - } - preconfigured = OkHttpClient().newBuilder() - .sslSocketFactory( - sslSocketFactory = sslContext.socketFactory, - trustManager = trustManager - ) - .hostnameVerifier(AllowAllHostnameVerifier()) - .build() - } - } - install(ContentNegotiation) { - install(HttpCache) - json(Json { - prettyPrint = true - isLenient = true - ignoreUnknownKeys = true - }) - } - install(Logging) { - logger = object : Logger { - override fun log(message: String) { - Napier.d(message, tag = "LogApiCalls") - } - } - level = LogLevel.INFO - } - install(HttpTimeout) { - requestTimeoutMillis = appSettingsService.getApiTimeout() - } - install(HttpCookies) - install(HttpRequestRetry) { - maxRetries = 2 - retryIf { _, response -> - response.status == HttpStatusCode.Forbidden && api.shouldHavePostLogin() && api.hasLoginInfo() - } - modifyRequest { - Napier.i("Will modify", tag = "HttpSend") - CoroutineScope(Dispatchers.Main).launch { - Napier.i("Will login", tag = "HttpSend") - api.login() - Napier.i("Did login", tag = "HttpSend") - } - } - } - expectSuccess = false - } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/rest/SelfossApi.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/rest/SelfossApi.kt index 25677d5..b637b90 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/rest/SelfossApi.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/rest/SelfossApi.kt @@ -4,22 +4,83 @@ import bou.amine.apps.readerforselfossv2.model.SelfossModel import bou.amine.apps.readerforselfossv2.model.StatusAndData import bou.amine.apps.readerforselfossv2.model.SuccessResponse import bou.amine.apps.readerforselfossv2.service.AppSettingsService +import io.github.aakira.napier.Napier import io.ktor.client.HttpClient +import io.ktor.client.engine.cio.CIO +import io.ktor.client.engine.cio.CIOEngineConfig +import io.ktor.client.plugins.HttpRequestRetry +import io.ktor.client.plugins.HttpTimeout import io.ktor.client.plugins.auth.providers.BasicAuthCredentials +import io.ktor.client.plugins.cache.HttpCache +import io.ktor.client.plugins.contentnegotiation.ContentNegotiation +import io.ktor.client.plugins.cookies.HttpCookies +import io.ktor.client.plugins.logging.LogLevel +import io.ktor.client.plugins.logging.Logger +import io.ktor.client.plugins.logging.Logging import io.ktor.client.request.get import io.ktor.client.request.headers import io.ktor.client.request.parameter import io.ktor.client.statement.HttpResponse import io.ktor.http.HttpHeaders +import io.ktor.http.HttpStatusCode import io.ktor.http.Parameters +import io.ktor.serialization.kotlinx.json.json import io.ktor.util.encodeBase64 import io.ktor.utils.io.charsets.Charsets import io.ktor.utils.io.core.toByteArray +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.serialization.json.Json -expect fun createHttpClient( +expect fun setupInsecureHTTPEngine(config: CIOEngineConfig) + +fun createHttpClient( appSettingsService: AppSettingsService, api: SelfossApi -): HttpClient +) = + HttpClient(CIO) { + if (appSettingsService.getSelfSigned()) { + engine { + setupInsecureHTTPEngine(this) + } + } + install(ContentNegotiation) { + install(HttpCache) + json(Json { + prettyPrint = true + isLenient = true + ignoreUnknownKeys = true + }) + } + install(Logging) { + logger = object : Logger { + override fun log(message: String) { + Napier.d(message, tag = "LogApiCalls") + } + } + level = LogLevel.INFO + } + install(HttpTimeout) { + requestTimeoutMillis = appSettingsService.getApiTimeout() + } + install(HttpCookies) + install(HttpRequestRetry) { + maxRetries = 2 + retryIf { _, response -> + response.status == HttpStatusCode.Forbidden && api.shouldHavePostLogin() && api.hasLoginInfo() + } + modifyRequest { + Napier.i("Will modify", tag = "HttpSend") + CoroutineScope(Dispatchers.Main).launch { + Napier.i("Will login", tag = "HttpSend") + api.login() + Napier.i("Did login", tag = "HttpSend") + } + } + } + expectSuccess = false + } class SelfossApi(private val appSettingsService: AppSettingsService) { From b5de30f56162b81f06958f5fc6b09a20a947d516 Mon Sep 17 00:00:00 2001 From: davidoskky Date: Sat, 15 Jul 2023 13:46:52 +0200 Subject: [PATCH 05/12] Add translations --- androidApp/src/main/res/layout/activity_login.xml | 2 +- androidApp/src/main/res/values-ca-rES/strings.xml | 1 + androidApp/src/main/res/values-de-rDE/strings.xml | 1 + androidApp/src/main/res/values-es-rES/strings.xml | 1 + androidApp/src/main/res/values-fa-rIR/strings.xml | 1 + androidApp/src/main/res/values-fr-rFR/strings.xml | 1 + androidApp/src/main/res/values-gl-rES/strings.xml | 1 + androidApp/src/main/res/values-in-rID/strings.xml | 1 + androidApp/src/main/res/values-it-rIT/strings.xml | 3 ++- androidApp/src/main/res/values-ko-rKR/strings.xml | 1 + androidApp/src/main/res/values-night/strings.xml | 2 +- androidApp/src/main/res/values-nl-rNL/strings.xml | 1 + androidApp/src/main/res/values-pt-rBR/strings.xml | 1 + androidApp/src/main/res/values-pt-rPT/strings.xml | 1 + androidApp/src/main/res/values-si-rLK/strings.xml | 1 + androidApp/src/main/res/values-tr-rTR/strings.xml | 1 + androidApp/src/main/res/values-zh-rCN/strings.xml | 1 + androidApp/src/main/res/values-zh-rTW/strings.xml | 1 + androidApp/src/main/res/values/strings.xml | 4 ++-- 19 files changed, 21 insertions(+), 5 deletions(-) diff --git a/androidApp/src/main/res/layout/activity_login.xml b/androidApp/src/main/res/layout/activity_login.xml index aaddf93..c09300b 100644 --- a/androidApp/src/main/res/layout/activity_login.xml +++ b/androidApp/src/main/res/layout/activity_login.xml @@ -55,7 +55,7 @@ android:id="@+id/selfSigned" android:layout_width="match_parent" android:layout_height="wrap_content" - android:text="@string/disableSSL" + android:text="@string/disable_ssl" android:textAlignment="viewStart" /> Update source Disconnect ? You will be disconnected from your selfoss instance. + Disable SSL diff --git a/androidApp/src/main/res/values-de-rDE/strings.xml b/androidApp/src/main/res/values-de-rDE/strings.xml index 036dcb9..7c18d95 100644 --- a/androidApp/src/main/res/values-de-rDE/strings.xml +++ b/androidApp/src/main/res/values-de-rDE/strings.xml @@ -130,4 +130,5 @@ Update source Disconnect ? You will be disconnected from your selfoss instance. + Disable SSL diff --git a/androidApp/src/main/res/values-es-rES/strings.xml b/androidApp/src/main/res/values-es-rES/strings.xml index 9f9f902..bd58c51 100644 --- a/androidApp/src/main/res/values-es-rES/strings.xml +++ b/androidApp/src/main/res/values-es-rES/strings.xml @@ -130,4 +130,5 @@ Update source Disconnect ? You will be disconnected from your selfoss instance. + Disable SSL diff --git a/androidApp/src/main/res/values-fa-rIR/strings.xml b/androidApp/src/main/res/values-fa-rIR/strings.xml index d4951bb..fb94070 100644 --- a/androidApp/src/main/res/values-fa-rIR/strings.xml +++ b/androidApp/src/main/res/values-fa-rIR/strings.xml @@ -130,4 +130,5 @@ Update source Disconnect ? You will be disconnected from your selfoss instance. + Disable SSL diff --git a/androidApp/src/main/res/values-fr-rFR/strings.xml b/androidApp/src/main/res/values-fr-rFR/strings.xml index d99f94e..4753a53 100644 --- a/androidApp/src/main/res/values-fr-rFR/strings.xml +++ b/androidApp/src/main/res/values-fr-rFR/strings.xml @@ -130,4 +130,5 @@ Update source Disconnect ? You will be disconnected from your selfoss instance. + Disable SSL diff --git a/androidApp/src/main/res/values-gl-rES/strings.xml b/androidApp/src/main/res/values-gl-rES/strings.xml index d32b7d1..a24ab3b 100644 --- a/androidApp/src/main/res/values-gl-rES/strings.xml +++ b/androidApp/src/main/res/values-gl-rES/strings.xml @@ -130,4 +130,5 @@ Update source Disconnect ? You will be disconnected from your selfoss instance. + Disable SSL diff --git a/androidApp/src/main/res/values-in-rID/strings.xml b/androidApp/src/main/res/values-in-rID/strings.xml index 0f48e73..248a2ab 100644 --- a/androidApp/src/main/res/values-in-rID/strings.xml +++ b/androidApp/src/main/res/values-in-rID/strings.xml @@ -130,4 +130,5 @@ Update source Disconnect ? You will be disconnected from your selfoss instance. + Disable SSL diff --git a/androidApp/src/main/res/values-it-rIT/strings.xml b/androidApp/src/main/res/values-it-rIT/strings.xml index 5b28be0..842bd3f 100644 --- a/androidApp/src/main/res/values-it-rIT/strings.xml +++ b/androidApp/src/main/res/values-it-rIT/strings.xml @@ -1,5 +1,5 @@ - + "Lettore RSS per Selfoss" "Accedi" "Password" @@ -130,4 +130,5 @@ Update source Disconnect ? You will be disconnected from your selfoss instance. + Disable SSL diff --git a/androidApp/src/main/res/values-ko-rKR/strings.xml b/androidApp/src/main/res/values-ko-rKR/strings.xml index e155386..93d133b 100644 --- a/androidApp/src/main/res/values-ko-rKR/strings.xml +++ b/androidApp/src/main/res/values-ko-rKR/strings.xml @@ -130,4 +130,5 @@ Update source Disconnect ? You will be disconnected from your selfoss instance. + Disable SSL diff --git a/androidApp/src/main/res/values-night/strings.xml b/androidApp/src/main/res/values-night/strings.xml index aa33111..5a30ec3 100644 --- a/androidApp/src/main/res/values-night/strings.xml +++ b/androidApp/src/main/res/values-night/strings.xml @@ -1,5 +1,5 @@ - + The app does not share any personal data about you. A crash occured. Sending the details to the developper. diff --git a/androidApp/src/main/res/values-nl-rNL/strings.xml b/androidApp/src/main/res/values-nl-rNL/strings.xml index 21b72a2..e16d314 100644 --- a/androidApp/src/main/res/values-nl-rNL/strings.xml +++ b/androidApp/src/main/res/values-nl-rNL/strings.xml @@ -130,4 +130,5 @@ Update source Disconnect ? You will be disconnected from your selfoss instance. + Disable SSL diff --git a/androidApp/src/main/res/values-pt-rBR/strings.xml b/androidApp/src/main/res/values-pt-rBR/strings.xml index a13d2dd..7ae3e67 100644 --- a/androidApp/src/main/res/values-pt-rBR/strings.xml +++ b/androidApp/src/main/res/values-pt-rBR/strings.xml @@ -130,4 +130,5 @@ Update source Disconnect ? You will be disconnected from your selfoss instance. + Disable SSL diff --git a/androidApp/src/main/res/values-pt-rPT/strings.xml b/androidApp/src/main/res/values-pt-rPT/strings.xml index 84bdc30..79c39c4 100644 --- a/androidApp/src/main/res/values-pt-rPT/strings.xml +++ b/androidApp/src/main/res/values-pt-rPT/strings.xml @@ -130,4 +130,5 @@ Update source Disconnect ? You will be disconnected from your selfoss instance. + Disable SSL diff --git a/androidApp/src/main/res/values-si-rLK/strings.xml b/androidApp/src/main/res/values-si-rLK/strings.xml index a6fcab7..2f46a3e 100644 --- a/androidApp/src/main/res/values-si-rLK/strings.xml +++ b/androidApp/src/main/res/values-si-rLK/strings.xml @@ -130,4 +130,5 @@ Update source Disconnect ? You will be disconnected from your selfoss instance. + Disable SSL diff --git a/androidApp/src/main/res/values-tr-rTR/strings.xml b/androidApp/src/main/res/values-tr-rTR/strings.xml index 8cd2816..e6bab9a 100644 --- a/androidApp/src/main/res/values-tr-rTR/strings.xml +++ b/androidApp/src/main/res/values-tr-rTR/strings.xml @@ -130,4 +130,5 @@ Update source Disconnect ? You will be disconnected from your selfoss instance. + Disable SSL diff --git a/androidApp/src/main/res/values-zh-rCN/strings.xml b/androidApp/src/main/res/values-zh-rCN/strings.xml index 9198954..c562215 100644 --- a/androidApp/src/main/res/values-zh-rCN/strings.xml +++ b/androidApp/src/main/res/values-zh-rCN/strings.xml @@ -130,4 +130,5 @@ 更新源 Disconnect ? You will be disconnected from your selfoss instance. + Disable SSL diff --git a/androidApp/src/main/res/values-zh-rTW/strings.xml b/androidApp/src/main/res/values-zh-rTW/strings.xml index 45c006d..0a44316 100644 --- a/androidApp/src/main/res/values-zh-rTW/strings.xml +++ b/androidApp/src/main/res/values-zh-rTW/strings.xml @@ -130,4 +130,5 @@ Update source Disconnect ? You will be disconnected from your selfoss instance. + Disable SSL diff --git a/androidApp/src/main/res/values/strings.xml b/androidApp/src/main/res/values/strings.xml index 1b1d975..6cbc2cd 100644 --- a/androidApp/src/main/res/values/strings.xml +++ b/androidApp/src/main/res/values/strings.xml @@ -1,4 +1,4 @@ - + "Reader for Selfoss" "Log in" "Password" @@ -6,7 +6,7 @@ "Password not long enough" "Field required" "Url" - "Disable SSL" + "Disable SSL" "Login required ?" "Oops. You may need to add a \"/\" at the end of the url." "Username" From 4482234e1a3f9964d04c9b1ffc86d0490a8dd93e Mon Sep 17 00:00:00 2001 From: davidoskky Date: Sun, 10 Sep 2023 21:15:04 +0200 Subject: [PATCH 06/12] Remove unused strings file --- androidApp/src/main/res/values-night/strings.xml | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 androidApp/src/main/res/values-night/strings.xml diff --git a/androidApp/src/main/res/values-night/strings.xml b/androidApp/src/main/res/values-night/strings.xml deleted file mode 100644 index 5a30ec3..0000000 --- a/androidApp/src/main/res/values-night/strings.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - The app does not share any personal data about you. - - A crash occured. Sending the details to the developper. - "Disable automatic bug reporting. " - Filters - This app only works with a Selfoss instance, and no other RSS feed. - Sources - Update source - \ No newline at end of file From a029d8a7dcde290f975a28de12aba6e4b2ad9749 Mon Sep 17 00:00:00 2001 From: davidoskky Date: Sun, 10 Sep 2023 21:33:28 +0200 Subject: [PATCH 07/12] Move api client creation function within api class --- .../readerforselfossv2/rest/SelfossApi.kt | 94 +++++++++---------- 1 file changed, 45 insertions(+), 49 deletions(-) diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/rest/SelfossApi.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/rest/SelfossApi.kt index b637b90..71e75bb 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/rest/SelfossApi.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/rest/SelfossApi.kt @@ -35,63 +35,59 @@ import kotlinx.serialization.json.Json expect fun setupInsecureHTTPEngine(config: CIOEngineConfig) -fun createHttpClient( - appSettingsService: AppSettingsService, - api: SelfossApi -) = - HttpClient(CIO) { - if (appSettingsService.getSelfSigned()) { - engine { - setupInsecureHTTPEngine(this) - } - } - install(ContentNegotiation) { - install(HttpCache) - json(Json { - prettyPrint = true - isLenient = true - ignoreUnknownKeys = true - }) - } - install(Logging) { - logger = object : Logger { - override fun log(message: String) { - Napier.d(message, tag = "LogApiCalls") - } - } - level = LogLevel.INFO - } - install(HttpTimeout) { - requestTimeoutMillis = appSettingsService.getApiTimeout() - } - install(HttpCookies) - install(HttpRequestRetry) { - maxRetries = 2 - retryIf { _, response -> - response.status == HttpStatusCode.Forbidden && api.shouldHavePostLogin() && api.hasLoginInfo() - } - modifyRequest { - Napier.i("Will modify", tag = "HttpSend") - CoroutineScope(Dispatchers.Main).launch { - Napier.i("Will login", tag = "HttpSend") - api.login() - Napier.i("Did login", tag = "HttpSend") - } - } - } - expectSuccess = false - } - class SelfossApi(private val appSettingsService: AppSettingsService) { - var client = createHttpClient(appSettingsService, this) + var client = createHttpClient() + fun createHttpClient() = + HttpClient(CIO) { + if (appSettingsService.getSelfSigned()) { + engine { + setupInsecureHTTPEngine(this) + } + } + install(ContentNegotiation) { + install(HttpCache) + json(Json { + prettyPrint = true + isLenient = true + ignoreUnknownKeys = true + }) + } + install(Logging) { + logger = object : Logger { + override fun log(message: String) { + Napier.d(message, tag = "LogApiCalls") + } + } + level = LogLevel.INFO + } + install(HttpTimeout) { + requestTimeoutMillis = appSettingsService.getApiTimeout() + } + install(HttpCookies) + install(HttpRequestRetry) { + maxRetries = 2 + retryIf { _, response -> + response.status == HttpStatusCode.Forbidden && shouldHavePostLogin() && hasLoginInfo() + } + modifyRequest { + Napier.i("Will modify", tag = "HttpSend") + CoroutineScope(Dispatchers.Main).launch { + Napier.i("Will login", tag = "HttpSend") + login() + Napier.i("Did login", tag = "HttpSend") + } + } + } + expectSuccess = false + } fun url(path: String) = "${appSettingsService.getBaseUrl()}$path" fun refreshLoginInformation() { appSettingsService.refreshApiSettings() - client = createHttpClient(appSettingsService, this) + client = createHttpClient() } fun constructBasicAuthValue(credentials: BasicAuthCredentials): String { From 2b446ab22bd2db463bd419dc8f422dc12db5d990 Mon Sep 17 00:00:00 2001 From: davidoskky Date: Tue, 12 Sep 2023 00:36:04 +0200 Subject: [PATCH 08/12] Revert dependency version changes --- shared/build.gradle.kts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts index 493ae4b..38af55c 100644 --- a/shared/build.gradle.kts +++ b/shared/build.gradle.kts @@ -62,8 +62,7 @@ kotlin { } val androidMain by getting { dependencies { - implementation("com.squareup.okhttp3:okhttp:4.10.0") - implementation("io.ktor:ktor-client-okhttp:2.2.4") + implementation("io.ktor:ktor-client-okhttp:2.1.1") implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0") // Sql From d8c215eacc7ad342aedfa8df226391ac7b44eca2 Mon Sep 17 00:00:00 2001 From: davidoskky Date: Tue, 12 Sep 2023 00:36:56 +0200 Subject: [PATCH 09/12] Reintroduce removed parameter --- .../kotlin/bou/amine/apps/readerforselfossv2/rest/SelfossApi.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/rest/SelfossApi.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/rest/SelfossApi.kt index 71e75bb..81e4684 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/rest/SelfossApi.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/rest/SelfossApi.kt @@ -51,6 +51,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { prettyPrint = true isLenient = true ignoreUnknownKeys = true + explicitNulls = false }) } install(Logging) { From 4ad4a23ed8beab6d9f727682f1ab77b6c20405de Mon Sep 17 00:00:00 2001 From: davidoskky Date: Tue, 12 Sep 2023 00:38:37 +0200 Subject: [PATCH 10/12] Revert to private functions --- .../bou/amine/apps/readerforselfossv2/rest/SelfossApi.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/rest/SelfossApi.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/rest/SelfossApi.kt index 81e4684..d010080 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/rest/SelfossApi.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/rest/SelfossApi.kt @@ -99,8 +99,8 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { } // Api version was introduces after the POST login, so when there is a version, it should be available - fun shouldHavePostLogin() = appSettingsService.getApiVersion() != -1 - fun hasLoginInfo() = + private fun shouldHavePostLogin() = appSettingsService.getApiVersion() != -1 + private fun hasLoginInfo() = appSettingsService.getUserName().isNotEmpty() && appSettingsService.getPassword() .isNotEmpty() From 16b19fc5ce66e12ae8c3756a5cd037629591d54d Mon Sep 17 00:00:00 2001 From: davidoskky Date: Sun, 17 Sep 2023 11:59:08 +0200 Subject: [PATCH 11/12] Revert version upgrades --- shared/build.gradle.kts | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts index 38af55c..24eccab 100644 --- a/shared/build.gradle.kts +++ b/shared/build.gradle.kts @@ -29,17 +29,14 @@ kotlin { sourceSets { val commonMain by getting { dependencies { - val ktorVersion = "2.3.2" - - implementation("io.ktor:ktor-client-core:$ktorVersion") - implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion") - implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion") - implementation("io.ktor:ktor-client-logging:$ktorVersion") - implementation("io.ktor:ktor-client-auth:$ktorVersion") - implementation("io.ktor:ktor-client-cio:$ktorVersion") + implementation("io.ktor:ktor-client-core:2.1.1") + implementation("io.ktor:ktor-client-content-negotiation:2.1.1") + implementation("io.ktor:ktor-serialization-kotlinx-json:2.1.1") + implementation("io.ktor:ktor-client-logging:2.1.1") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0") - - implementation("org.jsoup:jsoup:1.15.4") + implementation("io.ktor:ktor-client-auth:2.1.1") + implementation("io.ktor:ktor-client-cio:2.1.1") + implementation("org.jsoup:jsoup:1.14.3") //Dependency Injection implementation("org.kodein.di:kodein-di:7.12.0") From 056825aa0cbdebf4d4ff6fa2e062cc3a2a3ce1b0 Mon Sep 17 00:00:00 2001 From: davidoskky Date: Sun, 17 Sep 2023 12:01:32 +0200 Subject: [PATCH 12/12] Revert xmlns changes --- androidApp/src/main/res/values-it-rIT/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/androidApp/src/main/res/values-it-rIT/strings.xml b/androidApp/src/main/res/values-it-rIT/strings.xml index 842bd3f..3379b85 100644 --- a/androidApp/src/main/res/values-it-rIT/strings.xml +++ b/androidApp/src/main/res/values-it-rIT/strings.xml @@ -1,5 +1,5 @@ - + "Lettore RSS per Selfoss" "Accedi" "Password"