从ColdFusion 11 CFZIP创建的Zip文件上的ColdFusion 9 CFZIP解压缩错误 [英] ColdFusion 9 CFZIP Unzip Error On a Zip File Created From ColdFusion 11 CFZIP

查看:72
本文介绍了从ColdFusion 11 CFZIP创建的Zip文件上的ColdFusion 9 CFZIP解压缩错误的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我支持的ColdFusion应用程序安装在两个不同的位置.一个位置正在运行带有ColdFusion 9和MS SQL Server 2008的Windows Server 2008,而另一个位置正在运行带有ColdFusion 11和MS SQL Server 2012的Windows Server2012.该应用程序使用 CFZIP action ="zip" ,然后使用 CFZIP action ="unzip" 将zip文件导入目标计算机.

这是我生成用于导出的zip文件的代码:

 < cfzip file ="exportFileName.zip"source =#exportDirectory#"action ="zip"overwrite ="yes"recurse ="yes"> 

.zip文件已正确生成,我可以在Windows资源管理器和7Zip中正常打开它.

以下是解压缩上面创建的zip文件的代码:

 < cfzip action ="unzip"file ="exportFileName.zip"destination =#destination#\ xml"递归=是"storepath ="yes"> 

在ColdFusion 9和ColdFusion 11实例上,这是相同的代码,但是,当我们尝试在ColdFusion 9实例上解压缩ColdFusion 11实例生成的zip文件时,会收到以下错误消息:

确保该文件是有效的zip文件,并且可以访问.原因:java.util.zip.ZipException:仅DEFLATED条目可以具有EXT描述符

我们仅在ColdFusion 9服务器上使用 CFZIP 解压缩ColdFusion 11服务器上使用 CFZIP 生成的zip文件时才看到此问题.我可以在ColdFusion 9服务器上提取从ColdFusion 11生成的zip文件的内容,使用7Zip创建一个新的Zip文件,其中包含原始ColdFusion 11生成的zip文件的确切内容,但我没有得到错误.

无论何时我们测试从ColdFusion 11源到ColdFusion 11目的地或从ColdFusion 9源到ColdFusion 9目的地的过程,该过程都可以正常工作.仅在压缩ColdFusion 11并尝试在ColdFusion 9上解压缩时才遇到问题.我曾搜索过Google,但似乎找不到任何类似的问题.任何帮助将不胜感激.

解决方案

[TL/DR]

    ColdFusion 11上的
  • CFZip 遵循的zip规范晚于PKZIP 2.04g
  • Java的 ZipInputStream (似乎是CF9使用的)严格符合PKZIP 2.04g(或更早版本)的规范.

这两个版本之间的细微差别是它们对零长度条目(即子目录或mime类型等)的处理.CF11中的 CFZip 将这些条目标记为未经压缩的 STORED (从直觉上和规范的更高版本来看,这是很好的,因为它们的长度为零,因此压缩将不会进行.不会对它们执行任何操作),但是 ZipInputStream (在CF9中由 CFZip 调用)希望使用 DEFLATE 方法将其标记为压缩.

如果您没有零长度的条目,则生成的文件为ColdFusion 可以使用Java的 ZipInputStream进行读取(我认为可以,但我不能确定地证明);但是,如果文件的长度为零,则会引发错误.

或者,或者:

  • 使用CF9中的 CFZip 以外的其他内容,它可以读取符合更高标准的zip文件(即,您可以使用 cfexecute 在ColdFusion外部运行7zip或使用ColdFusion中的java org.apache.commons.compress 库);或
  • 在CF11中使用除 CFZip 之外的其他内容,这会将其创建的zip文件限制为较早的标准(请参见下文).

详细答案:

邮编格式规范部分4.4.4通用位标志:

位3:如果设置了该位,则将crc-32字段压缩大小和未压缩的大小在本地头.正确的值放在紧随压缩后的数据描述符数据.(注意:PKZIP版本2.04g仅用于DOS识别此位用于方法8压缩,较新版本的PKZIP可以识别此位压缩方法.)

当本地文件头指示zip文件中存在零长度条目(即,目录或zip文件中的某些其他内容(例如嵌入式mime类型))时,将设置此位.

在PKZIP版本2.04g(或更早版本)下,它将期望将压缩方法标志设置为 DEFLATE (方法8压缩).Java的 ZipInputStream 严格遵守该标准,并抛出 ZipException (带有消息仅DEFLATED条目可以具有EXT描述符).),如果找不到该压缩方法(请参见基于此答案

(注意:错误处理是最少的,因此如果要在生产环境中使用它,则可能希望使其更强大).

如果编译该Java类并将 .class 文件放在类路径的 zip 子目录中(可以在ColdFusion管理面板中添加条目)

然后您可以使用:

 < cfscript>zip = CreateObject("java",zip.Zip);zip.zipFolder("/path/to/folder/to/be/zipped/","/path/to/output/zip/file.zip",true,//递归true//覆盖现有文件);</cfscript> 

测试

如果您想测试一下这种情况如何发生,则会根据后面的说明生成一个有效的zip文件,该文件会产生错误:

  import java.io. *;导入java.util.zip.*;公共课程TestZip {公共静态void main(final String [] args)引发IOException {最终文件file =新文件(args [0]);如果(file.exists()){System.out.println(文件已经存在");返回;}final boolean general_purpose_bit_flag_bit3_on = true;最后字节gpbf = general_purpose_bit_flag_bit3_on吗?0x08:0x00;最后的字节[]内容=新的字节[] {//本地文件头'P','K',3,4,//本地文件头签名13,0,//需要提取的版本gpbf,8,//通用位标志ZipEntry.STORED,0,//压缩方法'q','l','t','G',//上次修改时间&日期0,0,0,0,//CRC320,0,0,0,//压缩大小0,0,0,0,//未压缩的大小12,0,//文件名长度0,0,//额外的字段长度'文件夹名称', '/',//文件名//中央目录文件头'P','K',1,2,//中央目录文件头签名13,0,//版本由13,0,//需要提取的版本gpbf,8,//通用位标志ZipEntry.STORED,0,//压缩方法'q','l','t','G',//上次修改时间&日期0,0,0,0,//CRC320,0,0,0,//压缩大小0,0,0,0,//未压缩的大小12,0,//文件名长度0,0,//额外的字段长度0,0,//文件注释长度0,0,//文件开始所在的磁盘号0,0,//内部文件属性0,0,0,0,//外部文件属性0,0,0,0,//本地头文件的相对偏移'文件夹名称', '/',//文件名//中央目录记录的结尾'P','K',5,6,//本地文件头签名0,0,//此磁盘号0,0,//CD所在的磁盘1,0,//此磁盘上的CD记录数1,0,//记录总数58,0,0,0,//CD的大小42,0,0,0,//CD开始偏移0,0,//注释长度};尝试(FileOutputStream fos = new FileOutputStream(file)){fos.write(内容);}试试(ZipInputStream zis = new ZipInputStream(new FileInputStream(file))){ZipEntry条目= zis.getNextEntry();System.out.println(entry.getName());}}} 

The ColdFusion application that I support is installed at two different locations. One location is running Windows Server 2008 with ColdFusion 9 and MS SQL Server 2008 and the other is running Windows Server 2012 with ColdFusion 11 and MS SQL Server 2012. The application provides an export process, using CFZIP action = "zip" and then the zip file is imported on the destination machine, using CFZIP action = "unzip".

Here is the code I have to generate the zip file for export:

<cfzip file="exportFileName.zip"
       source="#exportDirectory#" 
       action="zip" 
       overwrite="yes" 
       recurse="yes">

The .zip file gets generated properly and I can open it in Windows Explorer and 7Zip with no issues.

Here is the code to unzip the zip file created above:

<cfzip action="unzip" 
       file="exportFileName.zip" 
       destination="#destination#\xml" 
       recurse ="yes" 
       storepath="yes">

This is the same code on both the ColdFusion 9 and ColdFusion 11 instances, however when we try to unzip a zip file generated by the ColdFusion 11 instance on the ColdFusion 9 instance I receive the following error:

Ensure that the file is a valid zip file and it is accessible. Cause: java.util.zip.ZipException: only DEFLATED entries can have EXT descriptor

We're only seeing this issue when using CFZIP on a ColdFusion 9 server to unzip a zip file generated using CFZIP on a ColdFusion 11 server. I can extract the contents of the zip file generated from ColdFusion 11 on the ColdFusion 9 server, create a new Zip file, using 7Zip, that contains the exact contents of the original ColdFusion 11-generated zip file, and I don't get the error.

The process works fine whenever we test it going from a ColdFusion 11 source to a ColdFusion 11 destination or from a ColdFusion 9 source to a ColdFusion 9 destination. We're only getting the issue when zipping on ColdFusion 11 and trying to unzip on ColdFusion 9. I've scoured Google, but can't seem to find any issues like this. Any help would be greatly appreciated.

解决方案

[TL/DR]

  • CFZip on ColdFusion 11 complies with a zip specification later than PKZIP 2.04g
  • Java's ZipInputStream (which CF9 appears to use) complies strictly to the PKZIP 2.04g (or earlier) specification.

A subtle difference between the two versions is their handling of zero-length entries (i.e. sub-directories or mime-types among others). CFZip in CF11 marks these entries as being STORED with no compression (which, intuitively and by later versions of the specification, is fine as they are zero-length so compression won't do anything to them) but ZipInputStream (called by CFZip in CF9) expects them to be marked as compressed using the DEFLATE method.

If you have no zero-length entries then the files generated is ColdFusion may be able to be read (I think they will but I can't prove it definitively) using java's ZipInputStream; however, if you have zero-length files then it will throw errors.

Alternatively, either:

  • Use something other than CFZip in CF9 which is able to read zip files compliant against later standards (i.e. you could use cfexecute to run 7zip external to ColdFusion or use the java org.apache.commons.compress library inside ColdFusion); or
  • Use something other than CFZip in CF11 which will restrict the zip files it creates to the earlier standard (see below).

Detailed answer:

From the Zip Format Specification section 4.4.4 general purpose bit flag:

Bit 3: If this bit is set, the fields crc-32, compressed size and uncompressed size are set to zero in the local header. The correct values are put in the data descriptor immediately following the compressed data. (Note: PKZIP version 2.04g for DOS only recognizes this bit for method 8 compression, newer versions of PKZIP recognize this bit for any compression method.)

This bit is set when the local file header indicates that there is a zero-length entry in the zip file (i.e. a directory or certain other things in a zip-file like an embedded mime-type).

Under the PKZIP version 2.04g (or earlier) standard it would then expect the compression method flag to be set to DEFLATE (method 8 compression). Java's ZipInputStream complies strictly to this standard and throws a ZipException (with the message only DEFLATED entries can have EXT descriptor) if it does not find this compression method (see source code here).

cfzip appears to set the compression method to STORED (method 0 compression - or no compression) when the General Purpose Flag bit is set. From what I have read this complies with later versions of the PKZIP standard but is not backwards compatible with the version of the standard implemented by Java in the ZipInputStream class.

How to mitigate for this:

  1. Either don't store zero-length files in the zip (which means I don't think you can use the recurse option in CFZip); or
  2. Don't use CFZip, in CF11, if you want strict backwards compatability and use something else (see below for a suggestion).
  3. Use CFZip in CF11 but use something else to unzip the files in CF9 that is compatible with a greater range of compression algorithms (i.e. use cfexecute to call an external program to handle the zip files; use the java org.apache.commons.compress library; etc).

If you go for option 2 and want to use something compatible with the eariler versions then this worked for me and I can unzip the files using ZipInputStream (tested directly in Java as I don't have CF9):

package zip;

import java.io.*;
import java.util.zip.*;

public class Zip {
    private static void processFolder(
            final File folder,
            final ZipOutputStream zos,
            final boolean recurse,
            final int prefixLength,
            final byte[] buffer
    )
            throws IOException
    {
        for ( final File file : folder.listFiles() )
        {
            if ( file.isFile() )
            {
                final String name = file.getPath().substring( prefixLength );
                // System.out.println( name );
                final ZipEntry entry = new ZipEntry( name );
                zos.putNextEntry(entry);
                try (FileInputStream is = new FileInputStream( file ) ){
                    int read;
                    while( (read = is.read( buffer ) ) != -1 )
                    {
                        zos.write( buffer, 0, read );
                    }
                }
                zos.closeEntry();
            }
            else if ( recurse && file.isDirectory() )
            {
                processFolder( file, zos, recurse, prefixLength, buffer );
            }
        }
    }

    public static void zipFolder(
            final String folderPath,
            final String outputName,
            final boolean recurse,
            final boolean overwrite
    ) throws IOException
    {
        final File folder = new File( folderPath );
        if ( folder.exists() && folder.isDirectory() ) {
            final File output = new File( outputName );
            if ( overwrite || !output.exists() )
            {
                try ( ZipOutputStream zos = new ZipOutputStream( new FileOutputStream( output ) ) )
                {
                    processFolder( folder, zos, recurse, folder.getPath().length() + 1, new byte[1024*4] );
                }
            }
        }
    }
}

Based on this answer

(Note: the error handling is minimal so you probably want to make this more robust if you are going to use it in a production environment).

If you compile that java class and put the .class file in a zip subdirectory on your class path (which you can add entries to in the ColdFusion Administration panel).

Then you can call it using:

<cfscript>
  zip = CreateObject( "java", zip.Zip" );
  zip.zipFolder(
    "/path/to/folder/to/be/zipped/",
    "/path/to/output/zip/file.zip",
    true, // recurse
    true  // overwrite existing file
  );
</cfscript>

Testing

If you want something to test how this occurs then this will generate a valid zip file under the later specifiction which will generate the error:

import java.io.*;
import java.util.zip.*;

public class TestZip {
    public static void main( final String[] args ) throws IOException {
        final File file = new File( args[0] );
        if ( file.exists() )
        {
            System.out.println( "File already exists" );
            return;
        }
        final boolean general_purpose_bit_flag_bit3_on = true;
        final byte gpbf = general_purpose_bit_flag_bit3_on ? 0x08 : 0x00;

        final byte[] contents = new byte[]{
            // Local File header
            'P', 'K', 3, 4,     // Local File Header Signature
            13, 0,              // Version needed to extract
            gpbf, 8,            // General purpose bit flag
            ZipEntry.STORED, 0, // Compression method
            'q', 'l', 't', 'G', // Last Modification time & date
            0, 0, 0, 0,         // CRC32
            0, 0, 0, 0,         // Compressed Size
            0, 0, 0, 0,         // Uncompressed Size
            12, 0,              // File name length
            0, 0,               // Extra field length
            'F', 'o', 'l', 'd', 'e', 'r', '_', 'n', 'a', 'm', 'e', '/',
                                // File name
            // Central directory file header
            'P', 'K', 1, 2,     // Central Directory File Header Signature
            13, 0,              // Version made by
            13, 0,              // Version needed to extract
            gpbf, 8,            // General purpose bit flag
            ZipEntry.STORED, 0, // Compression method
            'q', 'l', 't', 'G', // Last Modification time & date
            0, 0, 0, 0,         // CRC32
            0, 0, 0, 0,         // Compressed Size
            0, 0, 0, 0,         // Uncompressed Size
            12, 0,              // File name length
            0, 0,               // Extra field length
            0, 0,               // File comment length
            0, 0,               // Disk number where file starts
            0, 0,               // Internal File attributes
            0, 0, 0, 0,         // External File attributes
            0, 0, 0, 0,         // Relative offset of local header file
            'F', 'o', 'l', 'd', 'e', 'r', '_', 'n', 'a', 'm', 'e', '/',
                                // File name
            // End of Central Directory Record
            'P', 'K', 5, 6,     // Local File Header Signature
            0, 0,               // Number of this disk
            0, 0,               // Disk where CD starts
            1, 0,               // Number of CD records on this disk
            1, 0,               // Total number of records
            58, 0, 0, 0,        // Size of CD
            42, 0, 0, 0,        // Offset of start of CD
            0, 0,               // Comment length
        };
        try ( FileOutputStream fos = new FileOutputStream( file ) )
        {
            fos.write(contents);
        }
        try ( ZipInputStream zis = new ZipInputStream( new FileInputStream( file ) ) )
        {
            ZipEntry entry = zis.getNextEntry();
            System.out.println( entry.getName() );
        }
    }
}

这篇关于从ColdFusion 11 CFZIP创建的Zip文件上的ColdFusion 9 CFZIP解压缩错误的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆