mirror of
				https://github.com/TeamNewPipe/NewPipe
				synced 2025-10-31 15:23:00 +00:00 
			
		
		
		
	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:
		| @@ -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); | ||||
|   | ||||
							
								
								
									
										28
									
								
								app/src/main/java/org/schabi/newpipe/NewVersionManager.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								app/src/main/java/org/schabi/newpipe/NewVersionManager.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 XiangRongLin
					XiangRongLin