Pier Angelo Vendrame pushed to branch tor-browser-152.0a1-16.0-2 at The Tor Project / Applications / Tor Browser

Commits:

16 changed files:

Changes:

  • mobile/android/fenix/app/build.gradle
    ... ... @@ -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 {
    

  • mobile/android/geckoview/build.gradle
    ... ... @@ -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) {
    

  • mobile/android/geckoview/omnijar_repack.sh
    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"

  • mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java
    ... ... @@ -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();
    

  • mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoThread.java
    ... ... @@ -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) {
    

  • mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/XzExtractor.java
    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
    +}

  • mobile/android/geckoview/src/test/assets/empty-hash.sha256

  • mobile/android/geckoview/src/test/assets/empty-hash.xz
    No preview for this file type
  • mobile/android/geckoview/src/test/assets/no-hash.xz
    No preview for this file type
  • mobile/android/geckoview/src/test/assets/test.sha256
    1
    +d05557c2592a0b2f4d5553ff10a810168755dd98adfabae60fc055273819fd06

  • mobile/android/geckoview/src/test/assets/test.xz
    No preview for this file type
  • mobile/android/geckoview/src/test/java/org/mozilla/gecko/util/XzExtractorTest.java
    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
    +}

  • mobile/android/gradle/with_gecko_binaries.gradle
    ... ... @@ -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
     
    

  • netwerk/protocol/res/nsResProtocolHandler.cpp
    ... ... @@ -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
    

  • python/mozbuild/mozbuild/action/fat_aar.py
    ... ... @@ -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()
    

  • python/mozbuild/mozpack/packager/unpack.py
    ... ... @@ -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
             """