Pier Angelo Vendrame pushed to branch tor-browser-152.0a1-16.0-2 at The Tor Project / Applications / Tor Browser
Commits:
-
5c903fac
by Pier Angelo Vendrame at 2026-07-01T08:31:43+02:00
-
63c810a9
by Beatriz Rizental at 2026-07-01T08:31:43+02:00
16 changed files:
- mobile/android/fenix/app/build.gradle
- mobile/android/geckoview/build.gradle
- + mobile/android/geckoview/omnijar_repack.sh
- mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java
- mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoThread.java
- + mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/XzExtractor.java
- + mobile/android/geckoview/src/test/assets/empty-hash.sha256
- + mobile/android/geckoview/src/test/assets/empty-hash.xz
- + mobile/android/geckoview/src/test/assets/no-hash.xz
- + mobile/android/geckoview/src/test/assets/test.sha256
- + mobile/android/geckoview/src/test/assets/test.xz
- + mobile/android/geckoview/src/test/java/org/mozilla/gecko/util/XzExtractorTest.java
- mobile/android/gradle/with_gecko_binaries.gradle
- netwerk/protocol/res/nsResProtocolHandler.cpp
- python/mozbuild/mozbuild/action/fat_aar.py
- python/mozbuild/mozpack/packager/unpack.py
Changes:
| ... | ... | @@ -197,7 +197,7 @@ android { |
| 197 | 197 | |
| 198 | 198 | // manifest.template.json is converted to manifest.json at build time.
|
| 199 | 199 | // No need to package the template in the APK.
|
| 200 | - ignoreAssetsPattern = "manifest.template.json"
|
|
| 200 | + ignoreAssetsPattern = "manifest.template.json:omni.ja"
|
|
| 201 | 201 | }
|
| 202 | 202 | |
| 203 | 203 | testOptions {
|
| ... | ... | @@ -82,10 +82,20 @@ android { |
| 82 | 82 | buildConfigField 'boolean', 'MOZ_ANDROID_CONTENT_SERVICE_ISOLATED_PROCESS', mozconfig.substs.MOZ_ANDROID_CONTENT_SERVICE_ISOLATED_PROCESS ? 'true' : 'false';
|
| 83 | 83 | }
|
| 84 | 84 | |
| 85 | + aaptOptions {
|
|
| 86 | + noCompress 'xz'
|
|
| 87 | + }
|
|
| 88 | + |
|
| 85 | 89 | lintOptions {
|
| 86 | 90 | abortOnError = false
|
| 87 | 91 | }
|
| 88 | 92 | |
| 93 | + testOptions {
|
|
| 94 | + unitTests {
|
|
| 95 | + includeAndroidResources = true
|
|
| 96 | + }
|
|
| 97 | + }
|
|
| 98 | + |
|
| 89 | 99 | sourceSets {
|
| 90 | 100 | main {
|
| 91 | 101 | java {
|
| ... | ... | @@ -194,6 +204,7 @@ dependencies { |
| 194 | 204 | implementation libs.androidx.lifecycle.common
|
| 195 | 205 | implementation libs.androidx.lifecycle.process
|
| 196 | 206 | implementation libs.play.services.fido
|
| 207 | + implementation "org.tukaani:xz:1.12"
|
|
| 197 | 208 | implementation "org.yaml:snakeyaml:2.2"
|
| 198 | 209 | |
| 199 | 210 | if (mozconfig.substs.MOZ_ANDROID_HLS_SUPPORT) {
|
| 1 | +#!/bin/sh
|
|
| 2 | +# Repack a .ja (ZIP) stored uncompressed, compress with XZ, write SHA-256 sidecar.
|
|
| 3 | +# Usage: omnijar_repack.sh <src> <dst-file> <dst-tag>
|
|
| 4 | +set -e
|
|
| 5 | +src="$1" dst_file="$2" dst_tag="$3"
|
|
| 6 | +tmp=$(mktemp -d)
|
|
| 7 | +trap 'rm -rf "$tmp"' EXIT
|
|
| 8 | +cp "$src" "$tmp/omni.ja"
|
|
| 9 | +# Repack all entries as ZIP_STORED (strip per-entry deflate)
|
|
| 10 | +( cd "$tmp" && unzip -q omni.ja -d unpacked && cd unpacked && zip -q -0 -r ../repacked.ja . )
|
|
| 11 | +xz -9e -k -c "$tmp/repacked.ja" > "$dst_file"
|
|
| 12 | +sha256sum -b "$tmp/repacked.ja" | awk '{print $1}' > "$dst_tag" |
| ... | ... | @@ -1761,6 +1761,11 @@ public class GeckoAppShell { |
| 1761 | 1761 | return id == 0 ? info.nonLocalizedLabel.toString() : context.getString(id);
|
| 1762 | 1762 | }
|
| 1763 | 1763 | |
| 1764 | + @WrapForJNI
|
|
| 1765 | + public static String getApkPath() {
|
|
| 1766 | + return getApplicationContext().getPackageResourcePath();
|
|
| 1767 | + }
|
|
| 1768 | + |
|
| 1764 | 1769 | @WrapForJNI(calledFrom = "gecko")
|
| 1765 | 1770 | private static int getMemoryUsage(final String stateName) {
|
| 1766 | 1771 | final Debug.MemoryInfo memInfo = new Debug.MemoryInfo();
|
| ... | ... | @@ -19,6 +19,8 @@ import android.util.Log; |
| 19 | 19 | import androidx.annotation.NonNull;
|
| 20 | 20 | import androidx.annotation.Nullable;
|
| 21 | 21 | import androidx.annotation.UiThread;
|
| 22 | +import java.io.File;
|
|
| 23 | +import java.io.IOException;
|
|
| 22 | 24 | import java.util.ArrayList;
|
| 23 | 25 | import java.util.Arrays;
|
| 24 | 26 | import java.util.LinkedList;
|
| ... | ... | @@ -31,6 +33,7 @@ import org.mozilla.gecko.annotation.WrapForJNI; |
| 31 | 33 | import org.mozilla.gecko.mozglue.GeckoLoader;
|
| 32 | 34 | import org.mozilla.gecko.process.MemoryController;
|
| 33 | 35 | import org.mozilla.gecko.util.GeckoBundle;
|
| 36 | +import org.mozilla.gecko.util.XzExtractor;
|
|
| 34 | 37 | import org.mozilla.gecko.util.ThreadUtils;
|
| 35 | 38 | import org.mozilla.geckoview.BuildConfig;
|
| 36 | 39 | import org.mozilla.geckoview.GeckoResult;
|
| ... | ... | @@ -329,6 +332,15 @@ public class GeckoThread extends Thread { |
| 329 | 332 | loadGeckoLibs(context);
|
| 330 | 333 | }
|
| 331 | 334 | |
| 335 | + private static File extractOmnijar(final Context context) throws IOException {
|
|
| 336 | + return new XzExtractor(
|
|
| 337 | + context,
|
|
| 338 | + "omni.ja",
|
|
| 339 | + context.getNoBackupFilesDir(),
|
|
| 340 | + "omni.ja")
|
|
| 341 | + .extract();
|
|
| 342 | + }
|
|
| 343 | + |
|
| 332 | 344 | private String[] getMainProcessArgs() {
|
| 333 | 345 | final Context context = GeckoAppShell.getApplicationContext();
|
| 334 | 346 | final ArrayList<String> args = new ArrayList<>();
|
| ... | ... | @@ -337,8 +349,13 @@ public class GeckoThread extends Thread { |
| 337 | 349 | args.add(context.getPackageName());
|
| 338 | 350 | |
| 339 | 351 | if (!mInitInfo.xpcshell) {
|
| 340 | - args.add("-greomni");
|
|
| 341 | - args.add(context.getPackageResourcePath());
|
|
| 352 | + try {
|
|
| 353 | + args.add("-greomni");
|
|
| 354 | + String greomni = extractOmnijar(context).getAbsolutePath();
|
|
| 355 | + args.add(greomni);
|
|
| 356 | + } catch (final IOException e) {
|
|
| 357 | + throw new RuntimeException("Failed to extract omnijar", e);
|
|
| 358 | + }
|
|
| 342 | 359 | }
|
| 343 | 360 | |
| 344 | 361 | if (mInitInfo.args != null) {
|
| 1 | +package org.mozilla.gecko.util;
|
|
| 2 | + |
|
| 3 | +import android.content.Context;
|
|
| 4 | +import android.content.res.AssetManager;
|
|
| 5 | +import android.util.Log;
|
|
| 6 | +import java.io.BufferedReader;
|
|
| 7 | +import java.io.File;
|
|
| 8 | +import java.io.IOException;
|
|
| 9 | +import java.io.InputStream;
|
|
| 10 | +import java.io.InputStreamReader;
|
|
| 11 | +import java.nio.charset.StandardCharsets;
|
|
| 12 | +import java.nio.file.Files;
|
|
| 13 | +import java.nio.file.StandardCopyOption;
|
|
| 14 | +import org.tukaani.xz.XZInputStream;
|
|
| 15 | + |
|
| 16 | +public class XzExtractor {
|
|
| 17 | + private static final String TAG = "XzExtractor";
|
|
| 18 | + private final Context mContext;
|
|
| 19 | + private final String mSource;
|
|
| 20 | + private String mSourceHash;
|
|
| 21 | + private final File mTarget;
|
|
| 22 | + private final File mTargetHash;
|
|
| 23 | + |
|
| 24 | + public XzExtractor(Context context, String source, File targetDir, String destination) {
|
|
| 25 | + mContext = context;
|
|
| 26 | + mSource = source;
|
|
| 27 | + mTarget = new File(targetDir, destination);
|
|
| 28 | + mTargetHash = new File(targetDir, destination + ".sha256");
|
|
| 29 | + }
|
|
| 30 | + |
|
| 31 | + public File extract() throws IOException {
|
|
| 32 | + Log.d(TAG, "Extracting " + mSource + " -> " + mTarget.getAbsolutePath());
|
|
| 33 | + readSourceTag();
|
|
| 34 | + if (checkExisting()) {
|
|
| 35 | + Log.d(TAG, mSource + ": up to date, skipping extraction");
|
|
| 36 | + } else {
|
|
| 37 | + Log.d(TAG, mSource + ": extracting from assets");
|
|
| 38 | + extractFromAssets();
|
|
| 39 | + Log.d(TAG, mSource + ": extraction complete");
|
|
| 40 | + }
|
|
| 41 | + return mTarget;
|
|
| 42 | + }
|
|
| 43 | + |
|
| 44 | + private void readSourceTag() throws IOException {
|
|
| 45 | + // We use sha256 because we need a unique deterministic string
|
|
| 46 | + // to verify if current resources are up to date or need to be
|
|
| 47 | + // re-extracted. THIS IS NOT AN INTEGRITY CHECK.
|
|
| 48 | + final String asset = mSource + ".sha256";
|
|
| 49 | + final Context context = mContext;
|
|
| 50 | + try (InputStream in = context.getAssets().open(asset, AssetManager.ACCESS_BUFFER);
|
|
| 51 | + BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) {
|
|
| 52 | + mSourceHash = reader.readLine();
|
|
| 53 | + }
|
|
| 54 | + if (mSourceHash == null || mSourceHash.isEmpty()) {
|
|
| 55 | + throw new IOException(asset + " is empty");
|
|
| 56 | + }
|
|
| 57 | + }
|
|
| 58 | + |
|
| 59 | + private boolean checkExisting() throws IOException {
|
|
| 60 | + if (!mTarget.exists() || !mTargetHash.exists()) {
|
|
| 61 | + return false;
|
|
| 62 | + }
|
|
| 63 | + String destHash = new String(Files.readAllBytes(mTargetHash.toPath()), StandardCharsets.UTF_8).trim();
|
|
| 64 | + return destHash.equals(mSourceHash);
|
|
| 65 | + }
|
|
| 66 | + |
|
| 67 | + private void extractFromAssets() throws IOException {
|
|
| 68 | + final Context context = mContext;
|
|
| 69 | + try (InputStream assetIn = context.getAssets().open(mSource + ".xz");
|
|
| 70 | + XZInputStream xzIn = new XZInputStream(assetIn)) {
|
|
| 71 | + Files.copy(xzIn, mTarget.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
|
| 72 | + }
|
|
| 73 | + Files.write(mTargetHash.toPath(), mSourceHash.getBytes(StandardCharsets.UTF_8));
|
|
| 74 | + }
|
|
| 75 | +} |
| 1 | +d05557c2592a0b2f4d5553ff10a810168755dd98adfabae60fc055273819fd06 |
| 1 | +package org.mozilla.gecko.util;
|
|
| 2 | + |
|
| 3 | +import static org.junit.Assert.assertEquals;
|
|
| 4 | +import static org.junit.Assert.assertThrows;
|
|
| 5 | +import static org.junit.Assert.assertTrue;
|
|
| 6 | + |
|
| 7 | +import android.content.Context;
|
|
| 8 | +import android.test.suitebuilder.annotation.SmallTest;
|
|
| 9 | +import java.io.File;
|
|
| 10 | +import java.io.IOException;
|
|
| 11 | +import java.nio.charset.StandardCharsets;
|
|
| 12 | +import java.nio.file.Files;
|
|
| 13 | +import org.junit.After;
|
|
| 14 | +import org.junit.Before;
|
|
| 15 | +import org.junit.Test;
|
|
| 16 | +import org.junit.runner.RunWith;
|
|
| 17 | +import org.robolectric.RobolectricTestRunner;
|
|
| 18 | +import org.robolectric.RuntimeEnvironment;
|
|
| 19 | + |
|
| 20 | +@RunWith(RobolectricTestRunner.class)
|
|
| 21 | +@SmallTest
|
|
| 22 | +public class XzExtractorTest {
|
|
| 23 | + private Context mContext;
|
|
| 24 | + private File mTargetDir;
|
|
| 25 | + |
|
| 26 | + private String mTestFileContents = "#!/bin/sh\necho hello world";
|
|
| 27 | + |
|
| 28 | + @Before
|
|
| 29 | + public void setUp() throws IOException {
|
|
| 30 | + mContext = RuntimeEnvironment.getApplication();
|
|
| 31 | + mTargetDir = Files.createTempDirectory("xz-extractor-test").toFile();
|
|
| 32 | + }
|
|
| 33 | + |
|
| 34 | + @After
|
|
| 35 | + public void tearDown() {
|
|
| 36 | + deleteRecursively(mTargetDir);
|
|
| 37 | + }
|
|
| 38 | + |
|
| 39 | + @Test
|
|
| 40 | + public void freshExtraction() throws IOException, InterruptedException {
|
|
| 41 | + XzExtractor extractor = new XzExtractor(mContext, "test", mTargetDir, "test");
|
|
| 42 | + File extracted = extractor.extract();
|
|
| 43 | + |
|
| 44 | + assertTrue(extracted.exists());
|
|
| 45 | + assertTrue(new File(mTargetDir, "test.sha256").exists());
|
|
| 46 | + }
|
|
| 47 | + |
|
| 48 | + @Test
|
|
| 49 | + public void upToDateSkipsExtraction() throws IOException {
|
|
| 50 | + XzExtractor extractor = new XzExtractor(mContext, "test", mTargetDir, "test");
|
|
| 51 | + File extracted = extractor.extract();
|
|
| 52 | + assertEquals(
|
|
| 53 | + mTestFileContents,
|
|
| 54 | + new String(Files.readAllBytes(extracted.toPath()), StandardCharsets.UTF_8).trim());
|
|
| 55 | + |
|
| 56 | + Files.write(extracted.toPath(), "tampered".getBytes(StandardCharsets.UTF_8));
|
|
| 57 | + extractor.extract();
|
|
| 58 | + |
|
| 59 | + assertEquals(
|
|
| 60 | + "tampered",
|
|
| 61 | + new String(Files.readAllBytes(extracted.toPath()), StandardCharsets.UTF_8));
|
|
| 62 | + }
|
|
| 63 | + |
|
| 64 | + @Test
|
|
| 65 | + public void staleHashTriggersReExtraction() throws IOException, InterruptedException {
|
|
| 66 | + XzExtractor extractor = new XzExtractor(mContext, "test", mTargetDir, "test");
|
|
| 67 | + File extracted = extractor.extract();
|
|
| 68 | + File hashFile = new File(mTargetDir, "test.sha256");
|
|
| 69 | + |
|
| 70 | + assertEquals(
|
|
| 71 | + mTestFileContents,
|
|
| 72 | + new String(Files.readAllBytes(extracted.toPath()), StandardCharsets.UTF_8).trim());
|
|
| 73 | + |
|
| 74 | + Files.write(hashFile.toPath(), "new-hash".getBytes(StandardCharsets.UTF_8));
|
|
| 75 | + Files.write(extracted.toPath(), "tampered".getBytes(StandardCharsets.UTF_8));
|
|
| 76 | + |
|
| 77 | + extractor.extract();
|
|
| 78 | + assertEquals(
|
|
| 79 | + mTestFileContents,
|
|
| 80 | + new String(Files.readAllBytes(extracted.toPath()), StandardCharsets.UTF_8).trim());
|
|
| 81 | + }
|
|
| 82 | + |
|
| 83 | + @Test
|
|
| 84 | + public void missingAssetThrows() {
|
|
| 85 | + XzExtractor extractor = new XzExtractor(mContext, "nonexistent", mTargetDir, "test");
|
|
| 86 | + assertThrows(IOException.class, extractor::extract);
|
|
| 87 | + }
|
|
| 88 | + |
|
| 89 | + @Test
|
|
| 90 | + public void missingHashFileThrows() {
|
|
| 91 | + XzExtractor extractor = new XzExtractor(mContext, "no-hash", mTargetDir, "test");
|
|
| 92 | + assertThrows(IOException.class, extractor::extract);
|
|
| 93 | + }
|
|
| 94 | + |
|
| 95 | + @Test
|
|
| 96 | + public void emptyHashFileThrows() {
|
|
| 97 | + XzExtractor extractor = new XzExtractor(mContext, "empty-hash", mTargetDir, "test");
|
|
| 98 | + assertThrows(IOException.class, extractor::extract);
|
|
| 99 | + }
|
|
| 100 | + |
|
| 101 | + private void deleteRecursively(final File file) {
|
|
| 102 | + if (file.isDirectory()) {
|
|
| 103 | + final File[] children = file.listFiles();
|
|
| 104 | + if (children != null) {
|
|
| 105 | + for (final File child : children) {
|
|
| 106 | + deleteRecursively(child);
|
|
| 107 | + }
|
|
| 108 | + }
|
|
| 109 | + }
|
|
| 110 | + file.delete();
|
|
| 111 | + }
|
|
| 112 | +} |
| ... | ... | @@ -23,10 +23,27 @@ ext.configureVariantWithGeckoBinaries = { variant -> |
| 23 | 23 | // the moz.build system and should never be re-entrant in this way.
|
| 24 | 24 | def assetGenTask = tasks.findByName("generate${variant.name.capitalize()}Assets")
|
| 25 | 25 | def jniLibFoldersTask = tasks.findByName("merge${variant.name.capitalize()}JniLibFolders")
|
| 26 | + |
|
| 27 | + def omnijarName = "omni.ja"
|
|
| 28 | + def assetsDir = "${topobjdir}/dist/geckoview/assets"
|
|
| 29 | + def compressOmnijarTask = tasks.register("compressOmnijar${variant.name.capitalize()}", Exec) {
|
|
| 30 | + inputs.file("${assetsDir}/${omnijarName}")
|
|
| 31 | + outputs.files(
|
|
| 32 | + "${assetsDir}/${omnijarName}.xz",
|
|
| 33 | + "${assetsDir}/${omnijarName}.sha256"
|
|
| 34 | + )
|
|
| 35 | + commandLine "sh",
|
|
| 36 | + "${topsrcdir}/mobile/android/geckoview/omnijar_repack.sh",
|
|
| 37 | + "${assetsDir}/${omnijarName}",
|
|
| 38 | + "${assetsDir}/${omnijarName}.xz",
|
|
| 39 | + "${assetsDir}/${omnijarName}.sha256"
|
|
| 40 | + }
|
|
| 41 | + |
|
| 26 | 42 | if (!mozconfig.substs.MOZILLA_OFFICIAL && !mozconfig.substs.ENABLE_MOZSEARCH_PLUGIN) {
|
| 27 | - assetGenTask.dependsOn rootProject.machStagePackage
|
|
| 43 | + compressOmnijarTask.configure { dependsOn rootProject.machStagePackage }
|
|
| 28 | 44 | jniLibFoldersTask.dependsOn rootProject.machStagePackage
|
| 29 | 45 | }
|
| 46 | + assetGenTask.dependsOn compressOmnijarTask
|
|
| 30 | 47 | }
|
| 31 | 48 | }
|
| 32 | 49 |
| ... | ... | @@ -14,6 +14,10 @@ |
| 14 | 14 | |
| 15 | 15 | #include "mozilla/Omnijar.h"
|
| 16 | 16 | |
| 17 | +#ifdef MOZ_WIDGET_ANDROID
|
|
| 18 | +# include "mozilla/java/GeckoAppShellWrappers.h"
|
|
| 19 | +#endif
|
|
| 20 | + |
|
| 17 | 21 | using mozilla::LogLevel;
|
| 18 | 22 | using mozilla::dom::ContentParent;
|
| 19 | 23 | |
| ... | ... | @@ -68,29 +72,12 @@ nsresult nsResProtocolHandler::Init() { |
| 68 | 72 | |
| 69 | 73 | #ifdef ANDROID
|
| 70 | 74 | nsresult nsResProtocolHandler::GetApkURI(nsACString& aResult) {
|
| 71 | - nsCString::const_iterator start, iter;
|
|
| 72 | - mGREURI.BeginReading(start);
|
|
| 73 | - mGREURI.EndReading(iter);
|
|
| 74 | - nsCString::const_iterator start_iter = start;
|
|
| 75 | - |
|
| 76 | - // This is like jar:jar:file://path/to/apk/base.apk!/path/to/omni.ja!/
|
|
| 77 | - bool found = FindInReadable("!/"_ns, start_iter, iter);
|
|
| 78 | - NS_ENSURE_TRUE(found, NS_ERROR_UNEXPECTED);
|
|
| 79 | - |
|
| 80 | - // like jar:jar:file://path/to/apk/base.apk!/
|
|
| 81 | - const nsDependentCSubstring& withoutPath = Substring(start, iter);
|
|
| 82 | - NS_ENSURE_TRUE(withoutPath.Length() >= 4, NS_ERROR_UNEXPECTED);
|
|
| 83 | - |
|
| 84 | - // Let's make sure we're removing what we expect to remove
|
|
| 85 | - NS_ENSURE_TRUE(Substring(withoutPath, 0, 4).EqualsLiteral("jar:"),
|
|
| 86 | - NS_ERROR_UNEXPECTED);
|
|
| 87 | - |
|
| 88 | - // like jar:file://path/to/apk/base.apk!/
|
|
| 89 | - aResult = ToNewCString(Substring(withoutPath, 4));
|
|
| 90 | - |
|
| 91 | - // Remove the trailing /
|
|
| 92 | - NS_ENSURE_TRUE(aResult.Length() >= 1, NS_ERROR_UNEXPECTED);
|
|
| 93 | - aResult.Truncate(aResult.Length() - 1);
|
|
| 75 | + mozilla::jni::String::LocalRef path =
|
|
| 76 | + mozilla::java::GeckoAppShell::GetApkPath();
|
|
| 77 | + if (!path) {
|
|
| 78 | + return NS_ERROR_UNEXPECTED;
|
|
| 79 | + }
|
|
| 80 | + aResult = "jar:file://"_ns + path->ToCString() + "!"_ns;
|
|
| 94 | 81 | return NS_OK;
|
| 95 | 82 | }
|
| 96 | 83 | #endif
|
| ... | ... | @@ -90,7 +90,7 @@ def fat_aar(distdir, zip_paths, no_process=False, no_compatibility_check=False): |
| 90 | 90 | jar_finder = JarFinder(
|
| 91 | 91 | aar_file.file.filename, JarReader(fileobj=aar_file.open())
|
| 92 | 92 | )
|
| 93 | - for path, fileobj in UnpackFinder(jar_finder):
|
|
| 93 | + for path, fileobj in UnpackFinder(jar_finder, omnijar_name="assets/omni.ja.xz"):
|
|
| 94 | 94 | # Native libraries go straight through.
|
| 95 | 95 | if mozpath.match(path, "jni/**"):
|
| 96 | 96 | copier.add(path, fileobj)
|
| ... | ... | @@ -130,6 +130,7 @@ def fat_aar(distdir, zip_paths, no_process=False, no_compatibility_check=False): |
| 130 | 130 | "**/*.ftl",
|
| 131 | 131 | "**/*.dtd",
|
| 132 | 132 | "**/*.properties",
|
| 133 | + "assets/omni.ja.sha256",
|
|
| 133 | 134 | }
|
| 134 | 135 | |
| 135 | 136 | not_allowed = OrderedDict()
|
| ... | ... | @@ -3,6 +3,7 @@ |
| 3 | 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
| 4 | 4 | |
| 5 | 5 | import codecs
|
| 6 | +import lzma
|
|
| 6 | 7 | from urllib.parse import urlparse
|
| 7 | 8 | |
| 8 | 9 | import mozpack.path as mozpath
|
| ... | ... | @@ -143,7 +144,11 @@ class UnpackFinder(BaseFinder): |
| 143 | 144 | Return a JarReader for the given BaseFile instance, keeping a log of
|
| 144 | 145 | the preloaded entries it has.
|
| 145 | 146 | """
|
| 146 | - jar = JarReader(fileobj=file.open())
|
|
| 147 | + if path.endswith(".xz"):
|
|
| 148 | + f = lzma.open(file.open())
|
|
| 149 | + else:
|
|
| 150 | + f = file.open()
|
|
| 151 | + jar = JarReader(fileobj=f)
|
|
| 147 | 152 | self.compressed = max(self.compressed, jar.compression)
|
| 148 | 153 | if jar.last_preloaded:
|
| 149 | 154 | jarlog = list(jar.entries.keys())
|
| ... | ... | @@ -158,8 +163,17 @@ class UnpackFinder(BaseFinder): |
| 158 | 163 | """
|
| 159 | 164 | Return whether the given BaseFile looks like a ZIP/Jar.
|
| 160 | 165 | """
|
| 166 | + |
|
| 167 | + def check_header(header):
|
|
| 168 | + return len(header) == 8 and (header[0:2] == b"PK" or header[4:6] == b"PK")
|
|
| 169 | + |
|
| 161 | 170 | header = file.open().read(8)
|
| 162 | - return len(header) == 8 and (header[0:2] == b"PK" or header[4:6] == b"PK")
|
|
| 171 | + if check_header(header):
|
|
| 172 | + return True
|
|
| 173 | + if header[0:6] == b"\xfd7zXZ\x00":
|
|
| 174 | + with lzma.open(file.open()) as f:
|
|
| 175 | + return check_header(f.read(8))
|
|
| 176 | + return False
|
|
| 163 | 177 | |
| 164 | 178 | def _unjarize(self, entry, relpath):
|
| 165 | 179 | """
|