diff --git a/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java b/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java index 0c686f7202..344bf988db 100644 --- a/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java +++ b/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java @@ -9,7 +9,7 @@ import com.airbnb.lottie.model.LottieCompositionCache; import com.airbnb.lottie.parser.LottieCompositionMoshiParser; import com.airbnb.lottie.parser.moshi.JsonReader; - +import com.airbnb.lottie.utils.Logger; import com.airbnb.lottie.utils.Utils; import org.json.JSONObject; @@ -27,8 +27,10 @@ import androidx.annotation.Nullable; import androidx.annotation.RawRes; import androidx.annotation.WorkerThread; +import okio.BufferedSource; +import okio.Okio; -import static com.airbnb.lottie.parser.moshi.JsonReader.*; +import static com.airbnb.lottie.parser.moshi.JsonReader.of; import static com.airbnb.lottie.utils.Utils.closeQuietly; import static okio.Okio.buffer; import static okio.Okio.source; @@ -49,6 +51,13 @@ public class LottieCompositionFactory { */ private static final Map> taskCache = new HashMap<>(); + /** + * reference magic bytes for zip compressed files. + * useful to determine if an InputStream is a zip file or not + */ + private static final byte[] MAGIC = new byte[] { 0x50, 0x4b, 0x03, 0x04 }; + + private LottieCompositionFactory() { } @@ -181,7 +190,7 @@ public static LottieResult fromAssetSync(Context context, Str @WorkerThread public static LottieResult fromAssetSync(Context context, String fileName, @Nullable String cacheKey) { try { - if (fileName.endsWith(".zip")) { + if (fileName.endsWith(".zip") || fileName.endsWith(".lottie")) { return fromZipStreamSync(new ZipInputStream(context.getAssets().open(fileName)), cacheKey); } return fromJsonInputStreamSync(context.getAssets().open(fileName), cacheKey); @@ -253,7 +262,11 @@ public static LottieResult fromRawResSync(Context context, @R @WorkerThread public static LottieResult fromRawResSync(Context context, @RawRes int rawRes, @Nullable String cacheKey) { try { - return fromJsonInputStreamSync(context.getResources().openRawResource(rawRes), cacheKey); + BufferedSource source = Okio.buffer(source(context.getResources().openRawResource(rawRes))); + if (isZipCompressed(source)) { + return fromZipStreamSync(new ZipInputStream(source.inputStream()), cacheKey); + } + return fromJsonInputStreamSync(source.inputStream(), cacheKey); } catch (Resources.NotFoundException e) { return new LottieResult<>(e); } @@ -423,6 +436,8 @@ private static LottieResult fromZipStreamSyncInternal(ZipInpu final String entryName = entry.getName(); if (entryName.contains("__MACOSX")) { inputStream.closeEntry(); + } else if (entry.getName().equalsIgnoreCase("manifest.json")) { //ignore .lottie manifest + inputStream.closeEntry(); } else if (entry.getName().contains(".json")) { com.airbnb.lottie.parser.moshi.JsonReader reader = of(buffer(source(inputStream))); composition = LottieCompositionFactory.fromJsonReaderSyncInternal(reader, null, false).getValue(); @@ -465,6 +480,26 @@ private static LottieResult fromZipStreamSyncInternal(ZipInpu return new LottieResult<>(composition); } + /** + * Check if a given InputStream points to a .zip compressed file + */ + private static Boolean isZipCompressed(BufferedSource inputSource) { + + try { + BufferedSource peek = inputSource.peek(); + for (byte b: MAGIC) { + if(peek.readByte() != b) + return false; + } + peek.close(); + return true; + } catch (Exception e) { + Logger.error("Failed to check zip file header", e); + return false; + } + + } + @Nullable private static LottieImageAsset findImageAssetForFileName(LottieComposition composition, String fileName) { for (LottieImageAsset asset : composition.getImages().values()) { diff --git a/lottie/src/main/java/com/airbnb/lottie/network/NetworkFetcher.java b/lottie/src/main/java/com/airbnb/lottie/network/NetworkFetcher.java index 21fbc3400e..75a8ab69d7 100644 --- a/lottie/src/main/java/com/airbnb/lottie/network/NetworkFetcher.java +++ b/lottie/src/main/java/com/airbnb/lottie/network/NetworkFetcher.java @@ -108,7 +108,7 @@ private LottieResult fromInputStream(@NonNull String url, @No // in the result which is more useful than failing here. contentType = "application/json"; } - if (contentType.contains("application/zip")) { + if (contentType.contains("application/zip") || url.split("\\?")[0].endsWith(".lottie")) { Logger.debug("Handling zip response."); extension = FileExtension.ZIP; result = fromZipStream(url, inputStream, cacheKey);