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.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"); | ||||||
|   | |||||||
							
								
								
									
										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 --> |     <!-- 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) | ||||||
|  |     } | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user
	 XiangRongLin
					XiangRongLin