Respect expires header when checking for new version
It was called to many times and acted similar to a DOS attack.
This commit is contained in:
parent
cce896e900
commit
2926cb7682
|
@ -10,22 +10,19 @@ import android.content.pm.Signature;
|
||||||
import android.net.ConnectivityManager;
|
import android.net.ConnectivityManager;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.core.app.NotificationCompat;
|
import androidx.core.app.NotificationCompat;
|
||||||
import androidx.core.app.NotificationManagerCompat;
|
import androidx.core.app.NotificationManagerCompat;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
|
|
||||||
import com.grack.nanojson.JsonObject;
|
import com.grack.nanojson.JsonObject;
|
||||||
import com.grack.nanojson.JsonParser;
|
import com.grack.nanojson.JsonParser;
|
||||||
import com.grack.nanojson.JsonParserException;
|
import com.grack.nanojson.JsonParserException;
|
||||||
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
import io.reactivex.rxjava3.core.Maybe;
|
||||||
import org.schabi.newpipe.report.ErrorInfo;
|
import io.reactivex.rxjava3.disposables.Disposable;
|
||||||
import org.schabi.newpipe.report.UserAction;
|
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
|
@ -34,11 +31,9 @@ import java.security.cert.CertificateEncodingException;
|
||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
import java.security.cert.CertificateFactory;
|
import java.security.cert.CertificateFactory;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
|
import org.schabi.newpipe.report.ErrorActivity;
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
import org.schabi.newpipe.report.ErrorInfo;
|
||||||
import io.reactivex.rxjava3.core.Maybe;
|
import org.schabi.newpipe.report.UserAction;
|
||||||
import io.reactivex.rxjava3.disposables.Disposable;
|
|
||||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
|
||||||
|
|
||||||
public final class CheckForNewAppVersion {
|
public final class CheckForNewAppVersion {
|
||||||
private CheckForNewAppVersion() { }
|
private CheckForNewAppVersion() { }
|
||||||
|
@ -176,6 +171,7 @@ public final class CheckForNewAppVersion {
|
||||||
@Nullable
|
@Nullable
|
||||||
public static Disposable checkNewVersion(@NonNull final App app) {
|
public static Disposable checkNewVersion(@NonNull final App app) {
|
||||||
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(app);
|
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(app);
|
||||||
|
final NewVersionManager manager = new NewVersionManager();
|
||||||
|
|
||||||
// Check if user has enabled/disabled update checking
|
// Check if user has enabled/disabled update checking
|
||||||
// and if the current apk is a github one or not.
|
// and if the current apk is a github one or not.
|
||||||
|
@ -183,6 +179,11 @@ public final class CheckForNewAppVersion {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final long expiry = prefs.getLong(app.getString(R.string.update_expiry_key), 0);
|
||||||
|
if (manager.isExpired(expiry)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return Maybe
|
return Maybe
|
||||||
.fromCallable(() -> {
|
.fromCallable(() -> {
|
||||||
if (!isConnected(app)) {
|
if (!isConnected(app)) {
|
||||||
|
@ -190,17 +191,29 @@ public final class CheckForNewAppVersion {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make a network request to get latest NewPipe data.
|
// Make a network request to get latest NewPipe data.
|
||||||
return DownloaderImpl.getInstance().get(NEWPIPE_API_URL).responseBody();
|
return DownloaderImpl.getInstance().get(NEWPIPE_API_URL);
|
||||||
})
|
})
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(
|
.subscribe(
|
||||||
response -> {
|
response -> {
|
||||||
|
try {
|
||||||
|
final long newExpiry = manager
|
||||||
|
.coerceExpiry(response.getHeader("expires"));
|
||||||
|
prefs.edit()
|
||||||
|
.putLong(app.getString(R.string.update_expiry_key), newExpiry)
|
||||||
|
.apply();
|
||||||
|
} catch (final Exception e) {
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.w(TAG, "Could not extract and save new expiry date", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Parse the json from the response.
|
// Parse the json from the response.
|
||||||
try {
|
try {
|
||||||
final JsonObject githubStableObject = JsonParser.object()
|
final JsonObject githubStableObject = JsonParser.object()
|
||||||
.from(response).getObject("flavors").getObject("github")
|
.from(response.responseBody()).getObject("flavors")
|
||||||
.getObject("stable");
|
.getObject("github").getObject("stable");
|
||||||
|
|
||||||
final String versionName = githubStableObject
|
final String versionName = githubStableObject
|
||||||
.getString("version");
|
.getString("version");
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
package org.schabi.newpipe
|
||||||
|
|
||||||
|
import java.time.Instant
|
||||||
|
import java.time.ZonedDateTime
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
|
|
||||||
|
class NewVersionManager {
|
||||||
|
|
||||||
|
fun isExpired(expiry: Long): Boolean {
|
||||||
|
return Instant.ofEpochSecond(expiry).isBefore(Instant.now())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Coerce expiry date time in between 6 hours and 72 hours from now
|
||||||
|
*
|
||||||
|
* @return Epoch second of expiry date time
|
||||||
|
*/
|
||||||
|
fun coerceExpiry(expiryString: String?): Long {
|
||||||
|
val now = ZonedDateTime.now()
|
||||||
|
return expiryString?.let {
|
||||||
|
|
||||||
|
var expiry = ZonedDateTime.from(DateTimeFormatter.RFC_1123_DATE_TIME.parse(expiryString))
|
||||||
|
expiry = maxOf(expiry, now.plusHours(6))
|
||||||
|
expiry = minOf(expiry, now.plusHours(72))
|
||||||
|
expiry.toEpochSecond()
|
||||||
|
} ?: now.plusHours(6).toEpochSecond()
|
||||||
|
}
|
||||||
|
}
|
|
@ -342,6 +342,7 @@
|
||||||
<!-- Updates -->
|
<!-- Updates -->
|
||||||
<string name="update_app_key" translatable="false">update_app_key</string>
|
<string name="update_app_key" translatable="false">update_app_key</string>
|
||||||
<string name="update_pref_screen_key" translatable="false">update_pref_screen_key</string>
|
<string name="update_pref_screen_key" translatable="false">update_pref_screen_key</string>
|
||||||
|
<string name="update_expiry_key" translatable="false">update_expiry_key</string>
|
||||||
|
|
||||||
<!-- Localizations -->
|
<!-- Localizations -->
|
||||||
<string name="default_localization_key" translatable="false">system</string>
|
<string name="default_localization_key" translatable="false">system</string>
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
package org.schabi.newpipe
|
||||||
|
|
||||||
|
import org.junit.Assert.assertFalse
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import java.time.Instant
|
||||||
|
import java.time.ZoneId
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
|
import kotlin.math.abs
|
||||||
|
|
||||||
|
class NewVersionManagerTest {
|
||||||
|
|
||||||
|
private lateinit var manager: NewVersionManager
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
manager = NewVersionManager()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Expiry is reached`() {
|
||||||
|
val oneHourEarlier = Instant.now().atZone(ZoneId.of("GMT")).minusHours(1)
|
||||||
|
|
||||||
|
val expired = manager.isExpired(oneHourEarlier.toEpochSecond())
|
||||||
|
|
||||||
|
assertTrue(expired)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Expiry is not reached`() {
|
||||||
|
val oneHourLater = Instant.now().atZone(ZoneId.of("GMT")).plusHours(1)
|
||||||
|
|
||||||
|
val expired = manager.isExpired(oneHourLater.toEpochSecond())
|
||||||
|
|
||||||
|
assertFalse(expired)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Equal within a range of 5 seconds
|
||||||
|
*/
|
||||||
|
private fun assertNearlyEqual(a: Long, b: Long) {
|
||||||
|
assertTrue(abs(a - b) < 5)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Expiry must be returned as is because it is inside the acceptable range of 6-72 hours`() {
|
||||||
|
val sixHoursLater = Instant.now().atZone(ZoneId.of("GMT")).plusHours(6)
|
||||||
|
|
||||||
|
val coerced = manager.coerceExpiry(DateTimeFormatter.RFC_1123_DATE_TIME.format(sixHoursLater))
|
||||||
|
|
||||||
|
assertNearlyEqual(sixHoursLater.toEpochSecond(), coerced)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Expiry must be increased to 6 hours if below`() {
|
||||||
|
val tooLow = Instant.now().atZone(ZoneId.of("GMT")).plusHours(5)
|
||||||
|
|
||||||
|
val coerced = manager.coerceExpiry(DateTimeFormatter.RFC_1123_DATE_TIME.format(tooLow))
|
||||||
|
|
||||||
|
assertNearlyEqual(tooLow.plusHours(1).toEpochSecond(), coerced)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Expiry must be decreased to 72 hours if above`() {
|
||||||
|
val tooHigh = Instant.now().atZone(ZoneId.of("GMT")).plusHours(73)
|
||||||
|
|
||||||
|
val coerced = manager.coerceExpiry(DateTimeFormatter.RFC_1123_DATE_TIME.format(tooHigh))
|
||||||
|
|
||||||
|
assertNearlyEqual(tooHigh.minusHours(1).toEpochSecond(), coerced)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue