/*
 * Copyright (c) 2005, 2023, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

/* @test
 * @bug 4770745 6218846 6218848 6237956 8313765
 * @summary test for correct detection and reporting of corrupted zip files
 * @author Martin Buchholz
 */

import java.util.*;
import java.util.zip.*;
import java.io.*;
import static java.lang.System.*;
import static java.util.zip.ZipFile.*;

public class CorruptedZipFiles {
    static int passed = 0, failed = 0;

    static void fail(String msg) {
        failed++;
        err.println(msg);
    }

    static void unexpected(Throwable t) {
        failed++;
        t.printStackTrace();
    }

    public static void main(String[] args) throws Exception {
        try (FileOutputStream fos = new FileOutputStream("x.zip");
             ZipOutputStream zos = new ZipOutputStream(fos))
        {
            ZipEntry e = new ZipEntry("x");
            zos.putNextEntry(e);
            zos.write((int)'x');
        }

        int len = (int)(new File("x.zip").length());
        byte[] good = new byte[len];
        try (FileInputStream fis = new FileInputStream("x.zip")) {
            fis.read(good);
        }
        new File("x.zip").delete();

        int endpos = len - ENDHDR;
        int cenpos = u16(good, endpos+ENDOFF);
        int locpos = u16(good, cenpos+CENOFF);
        if (u32(good, endpos) != ENDSIG) fail("Where's ENDSIG?");
        if (u32(good, cenpos) != CENSIG) fail("Where's CENSIG?");
        if (u32(good, locpos) != LOCSIG) fail("Where's LOCSIG?");
        if (u16(good, locpos+LOCNAM) != u16(good,cenpos+CENNAM))
            fail("Name field length mismatch");
        if (u16(good, locpos+LOCEXT) != u16(good,cenpos+CENEXT))
            fail("Extra field length mismatch");

        byte[] bad;

        err.println("corrupted ENDSIZ");
        bad = good.clone();
        bad[endpos+ENDSIZ]=(byte)0xff;
        checkZipException(bad, ".*bad central directory size.*");

        err.println("corrupted ENDOFF");
        bad = good.clone();
        bad[endpos+ENDOFF]=(byte)0xff;
        checkZipException(bad, ".*bad central directory offset.*");

        err.println("corrupted CENSIG");
        bad = good.clone();
        bad[cenpos]++;
        checkZipException(bad, ".*bad signature.*");

        err.println("corrupted CENFLG");
        bad = good.clone();
        bad[cenpos+CENFLG] |= 1;
        checkZipException(bad, ".*encrypted entry.*");

        err.println("corrupted CENNAM 1");
        bad = good.clone();
        bad[cenpos+CENNAM]++;
        checkZipException(bad, ".*bad header size.*");

        err.println("corrupted CENNAM 2");
        bad = good.clone();
        bad[cenpos+CENNAM]--;
        checkZipException(bad, ".*bad header size.*");

        err.println("corrupted CENNAM 3");
        bad = good.clone();
        bad[cenpos+CENNAM]   = (byte)0xfd;
        bad[cenpos+CENNAM+1] = (byte)0xfd;
        checkZipException(bad, ".*bad header size.*");

        err.println("corrupted CENEXT 1");
        bad = good.clone();
        bad[cenpos+CENEXT]   = (byte)0xff;
        bad[cenpos+CENEXT+1] = (byte)0xff;
        checkZipException(bad, ".*extra data field size too long.*");

        err.println("corrupted CENEXT 2");
        bad = good.clone();
        bad[cenpos+CENEXT]   = (byte)0xfd;
        bad[cenpos+CENEXT+1] = (byte)0xfd;
        checkZipException(bad, ".*extra data field size too long.*");

        err.println("corrupted CENCOM");
        bad = good.clone();
        bad[cenpos+CENCOM]++;
        checkZipException(bad, ".*bad header size.*");

        err.println("corrupted CENHOW");
        bad = good.clone();
        bad[cenpos+CENHOW] = 2;
        checkZipException(bad, ".*bad compression method.*");

        err.println("corrupted LOCSIG");
        bad = good.clone();
        bad[locpos]++;
        checkZipExceptionInGetInputStream(bad, ".*bad signature.*");

        out.printf("passed = %d, failed = %d%n", passed, failed);
        if (failed > 0) throw new Exception("Some tests failed");
    }

    static int uniquifier = 432;

    static void checkZipExceptionImpl(byte[] data,
                                      String msgPattern,
                                      boolean getInputStream) {
        String zipName = "bad" + (uniquifier++) + ".zip";
        try {
            try (FileOutputStream fos = new FileOutputStream(zipName)) {
                fos.write(data);
            }
            try (ZipFile zf = new ZipFile(zipName)) {
                if (getInputStream) {
                    InputStream is = zf.getInputStream(new ZipEntry("x"));
                    is.read();
                }
            }
            fail("Failed to throw expected ZipException");
        } catch (ZipException e) {
            if (e.getMessage().matches(msgPattern))
                passed++;
            else
                unexpected(e);
        } catch (Throwable t) {
            unexpected(t);
        } finally {
            new File(zipName).delete();
        }
    }

    static void checkZipException(byte[] data, String msgPattern) {
        checkZipExceptionImpl(data, msgPattern, false);
    }

    static void checkZipExceptionInGetInputStream(byte[] data, String msgPattern) {
        checkZipExceptionImpl(data, msgPattern, true);
    }

    static int u8(byte[] data, int offset) {
        return data[offset]&0xff;
    }

    static int u16(byte[] data, int offset) {
        return u8(data,offset) + (u8(data,offset+1)<<8);
    }

    static int u32(byte[] data, int offset) {
        return u16(data,offset) + (u16(data,offset+2)<<16);
    }

    // The following can be deleted once this bug is fixed:
    // 6225935: "import static" accessibility rules for symbols different for no reason
    static final long LOCSIG = ZipFile.LOCSIG;
    static final long EXTSIG = ZipFile.EXTSIG;
    static final long CENSIG = ZipFile.CENSIG;
    static final long ENDSIG = ZipFile.ENDSIG;

    static final int LOCHDR = ZipFile.LOCHDR;
    static final int EXTHDR = ZipFile.EXTHDR;
    static final int CENHDR = ZipFile.CENHDR;
    static final int ENDHDR = ZipFile.ENDHDR;

    static final int LOCVER = ZipFile.LOCVER;
    static final int LOCFLG = ZipFile.LOCFLG;
    static final int LOCHOW = ZipFile.LOCHOW;
    static final int LOCTIM = ZipFile.LOCTIM;
    static final int LOCCRC = ZipFile.LOCCRC;
    static final int LOCSIZ = ZipFile.LOCSIZ;
    static final int LOCLEN = ZipFile.LOCLEN;
    static final int LOCNAM = ZipFile.LOCNAM;
    static final int LOCEXT = ZipFile.LOCEXT;

    static final int CENVEM = ZipFile.CENVEM;
    static final int CENVER = ZipFile.CENVER;
    static final int CENFLG = ZipFile.CENFLG;
    static final int CENHOW = ZipFile.CENHOW;
    static final int CENTIM = ZipFile.CENTIM;
    static final int CENCRC = ZipFile.CENCRC;
    static final int CENSIZ = ZipFile.CENSIZ;
    static final int CENLEN = ZipFile.CENLEN;
    static final int CENNAM = ZipFile.CENNAM;
    static final int CENEXT = ZipFile.CENEXT;
    static final int CENCOM = ZipFile.CENCOM;
    static final int CENDSK = ZipFile.CENDSK;
    static final int CENATT = ZipFile.CENATT;
    static final int CENATX = ZipFile.CENATX;
    static final int CENOFF = ZipFile.CENOFF;

    static final int ENDSUB = ZipFile.ENDSUB;
    static final int ENDTOT = ZipFile.ENDTOT;
    static final int ENDSIZ = ZipFile.ENDSIZ;
    static final int ENDOFF = ZipFile.ENDOFF;
    static final int ENDCOM = ZipFile.ENDCOM;
}
