Skip to content

Commit

Permalink
Merge pull request #5474 from XiangRongLin/expires_header
Browse files Browse the repository at this point in the history
Respect expires header when checking for new versions
  • Loading branch information
TobiGr committed Jan 31, 2021
2 parents 950997e + bdc85b4 commit 0b0305e
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 25 deletions.
67 changes: 42 additions & 25 deletions app/src/main/java/org/schabi/newpipe/CheckForNewAppVersion.java
Expand Up @@ -10,22 +10,19 @@
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;
Expand All @@ -34,11 +31,9 @@
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() { }
Expand Down Expand Up @@ -176,38 +171,60 @@ public static boolean isGithubApk(@NonNull final App app) {
@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.
if (!prefs.getBoolean(app.getString(R.string.update_app_key), true) || !isGithubApk(app)) {
return null;
}

// Check if the last request has happened a certain time ago
// to reduce the number of API requests.
final long expiry = prefs.getLong(app.getString(R.string.update_expiry_key), 0);
if (!manager.isExpired(expiry)) {
return null;
}

return Maybe
.fromCallable(() -> {
if (!isConnected(app)) {
return null;
}

// Make a network request to get latest NewPipe data.
return DownloaderImpl.getInstance().get(NEWPIPE_API_URL).responseBody();
})
.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 {
// Store a timestamp which needs to be exceeded,
// before a new request to the API is made.
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);
Expand Down
28 changes: 28 additions & 0 deletions app/src/main/java/org/schabi/newpipe/NewVersionManager.kt
@@ -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()
}
}
1 change: 1 addition & 0 deletions app/src/main/res/values/settings_keys.xml
Expand Up @@ -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>
Expand Down
72 changes: 72 additions & 0 deletions app/src/test/java/org/schabi/newpipe/NewVersionManagerTest.kt
@@ -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)
}
}

0 comments on commit 0b0305e

Please sign in to comment.