java.util.zip.ZipEntry и Apache сжимают ZipEntry повреждать некоторые файлы изображений

Я использовал многокомпонентные контроллеры загрузчика Spring для загрузки и хранения записей из заархивированных файлов, но обнаружил, что случайный PNG-файл поврежден, и вместо того, чтобы начинать с чего-то вроде "PNG..." в его байте [], он запускается с "fþ" ÀÃgÞÉ"или подобным. Кажется, это происходит с одними и теми же файлами при каждом запуске. Я попробовал все это, используя java.util.ZipEntry, а затем я попробовал Apache Compress и обнаружил, что Apache сжимает поврежденные файлы в утилиту Java 7, но всегда одни и те же файлы при последующих запусках.

Код (в первую очередь java.util.zip.ZipEntry):

protected void processZipFile(String path, MultipartFile file, String signature) throws IOException {

    DateFormat df = new SimpleDateFormat("yyyyMMddhhmmss");
    File tempFile = new File(System.getProperty("user.dir") + "/" + file.getName() + df.format(new Date()));
    file.transferTo(tempFile);

    ZipFile zipFile = null;
    try {
        zipFile = new ZipFile(tempFile);

        LOG.debug("Processing archive with name={}, size={}.", file.getName(), file.getSize());

        final Enumeration<? extends ZipEntry> entries = zipFile.entries();
        while ( entries.hasMoreElements() )
        {
            ZipEntry entry = entries.nextElement();

            LOG.debug("Processing file={} is directory?={}.", entry.getName(), entry.isDirectory());

            // we don't bother processing directories, and we don't process any resource fork info
            // from Mac OS X (which does not seem to be transparent to ZipFile).

            if (!(entry.isDirectory() || entry.getName().contains("__MACOSX")  || entry.getName().contains(".DS_Store"))) {
                // if the entry is a file, extract it


                Content contentToSave = null;

                if(entry.getName().contains("gif") || entry.getName().contains("png") || entry.getName().contains("jpeg")) {

                    byte[] bytes = readInputStream( zipFile.getInputStream( entry ), entry.getSize() );

                    LOG.debug("{} is of inflated-length={} from compressed-length={}",
                            entry.getName(), bytes.length, entry.getCompressedSize());

                    if(entry.getName().contains("gif")) {
                        contentToSave = Content.makeImage(path + entry.getName(), Content.GIF, signature, bytes);

                    } else if (entry.getName().contains("png")) {
                        contentToSave = Content.makeImage(path + entry.getName(), Content.PNG, signature, bytes);

                    } else if (entry.getName().contains("jpeg")) {
                        contentToSave = Content.makeImage(path + entry.getName(), Content.JPEG, signature, bytes);

                    }
                } else {

                    InputStream is = zipFile.getInputStream(entry);

                    if (entry.getName().contains("json")) {
                        contentToSave = Content.makeFile(path + entry.getName(), Content.JSON, signature, convertStreamToString(is));
                    } else if (entry.getName().contains("js")) {
                        contentToSave = Content.makeFile(path + entry.getName(), Content.JS, signature, convertStreamToString(is));
                    } else if (entry.getName().contains("css")) {
                        contentToSave = Content.makeFile(path + entry.getName(), Content.CSS, signature, convertStreamToString(is));
                    } else if (entry.getName().contains("xml")) {
                        contentToSave = Content.makeFile(path + entry.getName(), Content.XML, signature, convertStreamToString(is));
                    } else if (entry.getName().contains("html")) {
                        contentToSave = Content.makeFile(path + entry.getName(), Content.HTML, signature, convertStreamToString(is));
                    }
                }

                contentService.putOrReplace(contentToSave);
                LOG.info("Persisted file: {} from uploaded version.", contentToSave.getName());
            }

        }
    } catch (ZipException e) {
        // If I can't create a ZipFile, then this is not a zip file at all and it cannot be processed
        // by this method. Its pretty dumb that there's no way to determine whether the contents are zipped through
        // the ZipFile API, but that's just one of its many problems.
        e.printStackTrace();
        LOG.error("{} is not a zipped file, or it is empty", file.getName());
    } finally {
        zipFile = null;
    }
    tempFile.delete();

}

И теперь то же самое для org.apache.commons.compress.archivers.zip.ZipFile:

protected void processZipFile(String path, MultipartFile file, String signature) throws IOException {

    DateFormat df = new SimpleDateFormat("yyyyMMddhhmmss");
    File tempFile = new File(System.getProperty("user.dir") + "/" + file.getName() + df.format(new Date()));
    file.transferTo(tempFile);

    ZipFile zipFile = null;
    try {
        zipFile = new ZipFile(tempFile);

        LOG.debug("Processing archive with name={}, size={}.", file.getName(), file.getSize());

        final Enumeration<? extends ZipArchiveEntry> entries = zipFile.getEntries();
        while ( entries.hasMoreElements() ) {
            ZipArchiveEntry entry = entries.nextElement();

            LOG.debug("Processing file={} is directory?={}.", entry.getName(), entry.isDirectory());

                // we don't bother processing directories, and we don't process any resource fork info
                // from Mac OS X (which does not seem to be transparent to ZipFile).

                if (!(entry.isDirectory() || entry.getName().contains("__MACOSX")  || entry.getName().contains(".DS_Store"))) {
                    // if the entry is a file, extract it

                    Content contentToSave = null;

                    if(entry.getName().contains("gif") || entry.getName().contains("png") || entry.getName().contains("jpeg")) {

                        byte[] bytes = readInputStream( zipFile.getInputStream( entry ), entry.getSize() );

                        LOG.debug("{} is of inflated-length={} from compressed-length={}",
                            entry.getName(), bytes.length, entry.getCompressedSize());

                        if(entry.getName().contains("gif")) {
                            contentToSave = Content.makeImage(path + entry.getName(), Content.GIF, signature, bytes);

                        } else if (entry.getName().contains("png")) {
                            contentToSave = Content.makeImage(path + entry.getName(), Content.PNG, signature, bytes);

                        } else if (entry.getName().contains("jpeg")) {
                            contentToSave = Content.makeImage(path + entry.getName(), Content.JPEG, signature, bytes);

                        }
                    } else {

                        InputStream is = zipFile.getInputStream(entry);

                        if (entry.getName().contains("json")) {
                            contentToSave = Content.makeFile(path + entry.getName(), Content.JSON, signature, convertStreamToString(is));
                        } else if (entry.getName().contains("js")) {
                            contentToSave = Content.makeFile(path + entry.getName(), Content.JS, signature, convertStreamToString(is));
                        } else if (entry.getName().contains("css")) {
                            contentToSave = Content.makeFile(path + entry.getName(), Content.CSS, signature, convertStreamToString(is));
                        } else if (entry.getName().contains("xml")) {
                            contentToSave = Content.makeFile(path + entry.getName(), Content.XML, signature, convertStreamToString(is));
                        } else if (entry.getName().contains("html")) {
                            contentToSave = Content.makeFile(path + entry.getName(), Content.HTML, signature, convertStreamToString(is));
                        }
                    }

                    contentService.putOrReplace(contentToSave);
                    LOG.info("Persisted file: {} from uploaded version.", contentToSave.getName());
                }

        }
    } catch (ZipException e) {
        e.printStackTrace();
        LOG.error("{} is not a zipped file, or it is empty", file.getName());
    } catch (IOException e) {
        e.printStackTrace();
        LOG.error("{} is not a file, or it is empty", file.getName());
    } finally {
        zipFile = null;
    }
    tempFile.delete();
}

Два вызванных метода:

private static byte[] readInputStream( final InputStream is, final long length ) throws IOException {
    final byte[] buf = new byte[ (int) length ];
    int read = 0;
    int cntRead;
    while ( ( cntRead = is.read( buf, 0, buf.length ) ) >=0  )
    {
        read += cntRead;
    }
    return buf;
}

а также:

public String convertStreamToString(InputStream is) throws IOException {
    StringBuilder sb = new StringBuilder(2048);
    char[] read = new char[128];
    try (InputStreamReader ir = new InputStreamReader(is, StandardCharsets.UTF_8)) {
        for (int i; -1 != (i = ir.read(read)); sb.append(read, 0, i));
    }

    // need to remove the ? at teh beginning of some files. This comes from the UTF8 BOM
    // that is added to some files saved as UTF8
    String out = sb.toString();
    String utf8Bom = new String(new char[]{'\ufeff'});
    if(out.contains(utf8Bom)) {
        out = out.replace(utf8Bom,"");
    }
    return out;
}

Второй, конечно, вряд ли является частью проблемы.

Я гуглил вокруг, и похоже, что проблемы, подобные этому, были найдены, но это всегда была какая-то внешняя проблема. Кто-нибудь знает, почему это может иметь место?

Я заново отредактировал некоторые изображения и обнаружил, что если я изменю изображение на черно-белое или изменим оттенок всего изображения, проблема исчезнет, ​​но если я добавлю границу или изменим один цвет, проблема останется. Похоже, что конкретное расположение байтов в некоторых файлах исправляет ошибку в любом базовом API, который используют как собственные, так и читатели сжатых файлов Apache, но это только предположение.

РЕДАКТИРОВАТЬ: дополнительное использование показывает, что повреждение происходит в GIF размером более 10 КБ, так что, возможно, это как-то связано с ошибкой? Я попытался произвольно удвоить размер буфера при вызове ReadInputStream(), но он ничего не сделал, кроме переполнения размера BLOB-объекта в MySQL в особенно больших изображениях (49K стало 98K, что было слишком большим).

com.mysql.jdbc.MysqlDataTruncation: Data truncation: Data too long for column 'encoded_content' at row 1

1 ответ

Я обнаружил, что эта проблема возникает, когда "упакованный размер" больше, чем фактический размер, это может произойти, например, с файлами png, которые уже "заархивированы" сами по себе.

Другие вопросы по тегам