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.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.app.NotificationManagerCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import com.grack.nanojson.JsonObject;
|
||||
import com.grack.nanojson.JsonParser;
|
||||
import com.grack.nanojson.JsonParserException;
|
||||
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
import org.schabi.newpipe.report.ErrorInfo;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.core.Maybe;
|
||||
import io.reactivex.rxjava3.disposables.Disposable;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.security.MessageDigest;
|
||||
|
@ -34,11 +31,9 @@ import java.security.cert.CertificateEncodingException;
|
|||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.core.Maybe;
|
||||
import io.reactivex.rxjava3.disposables.Disposable;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
import org.schabi.newpipe.report.ErrorInfo;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
|
||||
public final class CheckForNewAppVersion {
|
||||
private CheckForNewAppVersion() { }
|
||||
|
@ -176,6 +171,7 @@ public final class CheckForNewAppVersion {
|
|||
@Nullable
|
||||
public static Disposable checkNewVersion(@NonNull final App app) {
|
||||
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(app);
|
||||
final NewVersionManager manager = new NewVersionManager();
|
||||
|
||||
// Check if user has enabled/disabled update checking
|
||||
// and if the current apk is a github one or not.
|
||||
|
@ -183,31 +179,48 @@ public final class CheckForNewAppVersion {
|
|||
return null;
|
||||
}
|
||||
|
||||
return Maybe
|
||||
.fromCallable(() -> {
|
||||
if (!isConnected(app)) {
|
||||
return null;
|
||||
}
|
||||
final long expiry = prefs.getLong(app.getString(R.string.update_expiry_key), 0);
|
||||
if (manager.isExpired(expiry)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Make a network request to get latest NewPipe data.
|
||||
return DownloaderImpl.getInstance().get(NEWPIPE_API_URL).responseBody();
|
||||
})
|
||||
return Maybe
|
||||
.fromCallable(() -> {
|
||||
if (!isConnected(app)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Make a network request to get latest NewPipe data.
|
||||
return DownloaderImpl.getInstance().get(NEWPIPE_API_URL);
|
||||
})
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
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.
|
||||
try {
|
||||
final JsonObject githubStableObject = JsonParser.object()
|
||||
.from(response).getObject("flavors").getObject("github")
|
||||
.getObject("stable");
|
||||
.from(response.responseBody()).getObject("flavors")
|
||||
.getObject("github").getObject("stable");
|
||||
|
||||
final String versionName = githubStableObject
|
||||
.getString("version");
|
||||
.getString("version");
|
||||
final int versionCode = githubStableObject
|
||||
.getInt("version_code");
|
||||
.getInt("version_code");
|
||||
final String apkLocationUrl = githubStableObject
|
||||
.getString("apk");
|
||||
.getString("apk");
|
||||
|
||||
compareAppVersionAndShowNotification(app, versionName,
|
||||
apkLocationUrl, versionCode);
|
||||
|
|
|
@ -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 -->
|
||||
<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_expiry_key" translatable="false">update_expiry_key</string>
|
||||
|
||||
<!-- Localizations -->
|
||||
<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