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, которые уже "заархивированы" сами по себе.