package net.dongliu.apk.parser;

import net.dongliu.apk.parser.bean.ApkSignStatus;
import net.dongliu.apk.parser.utils.Inputs;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;


/**
 * ApkFile, for parsing apk file info.
 * This class is not thread-safe.
 *
 * @author dongliu
 */
public class ApkFile extends AbstractApkFile implements Closeable {

    private final ZipFile zf;
    private File apkFile;

    public ApkFile(File apkFile) throws IOException {
        this.apkFile = apkFile;
        // create zip file cost time, use one zip file for apk parser life cycle
        this.zf = new ZipFile(apkFile);
    }

    public ApkFile(String filePath) throws IOException {
        this(new File(filePath));
    }

    @Override
    protected List<CertificateFile> getAllCertificateData() throws IOException {
        Enumeration<? extends ZipEntry> enu = zf.entries();
        List<CertificateFile> list = new ArrayList<>();
        while (enu.hasMoreElements()) {
            ZipEntry ne = enu.nextElement();
            if (ne.isDirectory()) {
                continue;
            }
            String name = ne.getName().toUpperCase();
            if (name.endsWith(".RSA") || name.endsWith(".DSA")) {
                list.add(new CertificateFile(name, Inputs.readAllAndClose(zf.getInputStream(ne))));
            }
        }
        return list;
    }

    @Override
    public byte[] getFileData(String path) throws IOException {
        ZipEntry entry = zf.getEntry(path);
        if (entry == null) {
            return null;
        }

        InputStream inputStream = zf.getInputStream(entry);
        return Inputs.readAllAndClose(inputStream);
    }

    @Override
    protected ByteBuffer fileData() throws IOException {
        FileChannel channel = new FileInputStream(apkFile).getChannel();
        return channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());
    }


    /**
     * {@inheritDoc}
     *
     * @deprecated using google official ApkVerifier of apksig lib instead.
     */
    @Override
    @Deprecated
    public ApkSignStatus verifyApk() throws IOException {
        ZipEntry entry = zf.getEntry("META-INF/MANIFEST.MF");
        if (entry == null) {
            // apk is not signed;
            return ApkSignStatus.notSigned;
        }

        try (JarFile jarFile = new JarFile(this.apkFile)) {
            Enumeration<JarEntry> entries = jarFile.entries();
            byte[] buffer = new byte[8192];

            while (entries.hasMoreElements()) {
                JarEntry e = entries.nextElement();
                if (e.isDirectory()) {
                    continue;
                }
                try (InputStream in = jarFile.getInputStream(e)) {
                    // Read in each jar entry. A security exception will be thrown if a signature/digest check fails.
                    int count;
                    while ((count = in.read(buffer, 0, buffer.length)) != -1) {
                        // Don't care
                    }
                } catch (SecurityException se) {
                    return ApkSignStatus.incorrect;
                }
            }
        }
        return ApkSignStatus.signed;
    }

    @Override
    public void close() throws IOException {
        super.close();
        zf.close();
    }

}

@@ -121,16 +121,50 @@
 nputs;%0A%0A
+import javax.annotation.Nullable;%0A
 import j
@@ -173,16 +173,16 @@
 a.io.*;%0A
-
 import j
@@ -671,16 +671,67 @@
 apkFile;
+%0A    @Nullable%0A    private FileChannel fileChannel;
 %0A%0A    pu
@@ -2065,17 +2065,17 @@
         
-F
+f
 ileChann
@@ -2081,16 +2081,8 @@
 nel 
-channel 
 = ne
@@ -2137,17 +2137,21 @@
  return 
-c
+fileC
 hannel.m
@@ -2187,17 +2187,21 @@
 NLY, 0, 
-c
+fileC
 hannel.s
@@ -3573,42 +3573,311 @@
     
-super.close();%0A        zf.close();
+try (Closeable superClosable = new Closeable() %7B%0A            @Override%0A            public void close() throws IOException %7B%0A                ApkFile.super.close();%0A            %7D%0A        %7D;%0A             Closeable zipFileClosable = zf;%0A             Closeable fileChannelClosable = fileChannel) %7B%0A%0A        %7D
 %0A   

fix #94: file channel not closed when close apk file