使用Gzip类进行压缩时如何将文件名添加到归档文件中?

5

我在加密之前使用Gzip压缩数据。

Gzip gz;
gz.Put(file,size)
gz.MessageEnd();
gz.Get(file,gz.MaxRetrievable());

我希望创建的gzip文件包含原始文件名作为元数据。如何通过Crypto++接口实现这一点?

Crypto++维基现在有一个关于Gzip的页面,包括下面的补丁。请参阅Gzip维基页面 - jww
1个回答

6
我希望创建的gzip文件包含原始文件名作为元数据。如何通过Crypto++接口实现?
你不能直接实现。这似乎是Crypto++的限制。(但请参见下文。)
根据RFC 1952,其中明确有一个字段用于此目的。
  (if FLG.FNAME set)

     +=========================================+
     |...original file name, zero-terminated...| (more-->)
     +=========================================+

但是Crypto++不允许您设置它(来自gzip.c源代码):

void Gzip::WritePrestreamHeader()
{
    m_totalLen = 0;
    m_crc.Restart();

    AttachedTransformation()->Put(MAGIC1);
    AttachedTransformation()->Put(MAGIC2);
    AttachedTransformation()->Put(DEFLATED);
    AttachedTransformation()->Put(0);       // general flag
    AttachedTransformation()->PutWord32(0); // time stamp
    byte extra = (GetDeflateLevel() == 1) ? FAST : ((GetDeflateLevel() == 9) ? SLOW : 0);
    AttachedTransformation()->Put(extra);
    AttachedTransformation()->Put(GZIP_OS_CODE);
}

如果在解压缩时存在,Crypto++将会默默地将其丢弃(Gunzip 是 GZIP 解压器)(来自 gzip.c 源代码):

void Gunzip::ProcessPrestreamHeader()
{
    ...

    if (flags & EXTRA_FIELDS)   // skip extra fields
    {
        word16 length;
        if (m_inQueue.GetWord16(length, LITTLE_ENDIAN_ORDER) != 2) throw HeaderErr();
        if (m_inQueue.Skip(length)!=length) throw HeaderErr();
    }

    if (flags & FILENAME)   // skip filename
        do
            if(!m_inQueue.Get(b)) throw HeaderErr();
        while (b);

    if (flags & COMMENTS)   // skip comments
        do
            if(!m_inQueue.Get(b)) throw HeaderErr();
        while (b);
}

我记得大约15年来没有人要求过它,所以这不是一个流行的请求 :)

以下是如何修改源代码以添加文件时间、文件名和注释的方法。您可以在Crypto++维基页面上找到GzipGunzip的补丁。您可以在Pastebin上找到SVN差异Crypto ++ gzip用于文件名和注释处理的差异。这些文件大多相同,但维基更加实时,因为它有一些重置代码在Gzip :: WritePoststreamTail中。

首先,在GzipGunzip类中添加以下受保护成员:

word32 m_filetime;
std::string m_filename;
std::string m_comment;

其次,为每个构造函数(包括 GzipGunzip)添加一个 m_filetime 的初始化器。例如,下面是其中一个 Gzip 构造函数的示例:

Gzip(const NameValuePairs &parameters, BufferedTransformation *attachment=NULL)
    : Deflator(parameters, attachment), m_filetime(0) {}

第三步,将公共的setter添加到Gzip类中:
void SetFiletime(word32 filetime) { m_filetime = filetime; }
void SetFilename(const std::string& filename) { m_filename = filename; }
void SetComment(const std::string& comment) { m_comment = comment; }

第四步,向Gunzip类添加公共getter方法:
word32 GetFiletime() const { return m_filetime; }
const std::string& GetFilename() const { return m_filename; }
const std::string& GetComment() const { return m_comment; }

第五步,将cpp文件中的Gzip :: WritePrestreamHeader 更改为以下内容:
void Gzip::WritePrestreamHeader()
{
    m_totalLen = 0;
    m_crc.Restart();

    int flags = 0;
    if(!m_filename.empty())
        flags |= FILENAME;
    if(!m_comment.empty())
        flags |= COMMENTS;

    AttachedTransformation()->Put(MAGIC1);
    AttachedTransformation()->Put(MAGIC2);
    AttachedTransformation()->Put(DEFLATED);
    AttachedTransformation()->Put((byte)flags);    // general flag
    AttachedTransformation()->PutWord32(m_filetime, LITTLE_ENDIAN_ORDER);    // time stamp

    byte extra = (GetDeflateLevel() == 1) ? FAST : ((GetDeflateLevel() == 9) ? SLOW : 0);
    AttachedTransformation()->Put(extra);        
    AttachedTransformation()->Put(GZIP_OS_CODE);

    if(!m_filename.empty())
        AttachedTransformation()->Put((const unsigned char*)m_filename.data(), m_filename.size() +1);

    if(!m_comment.empty())
        AttachedTransformation()->Put((const unsigned char*)m_comment.data(), m_comment.size() +1);
}

第六步,将cpp文件中的Gzip::WritePoststreamTail更改为以下内容:
void Gzip::WritePoststreamTail()
{
    SecByteBlock crc(4);
    m_crc.Final(crc);
    AttachedTransformation()->Put(crc, 4);
    AttachedTransformation()->PutWord32(m_totalLen, LITTLE_ENDIAN_ORDER);

    m_filetime = 0;

    m_filename.erase(0);
    m_filename.reserve(16);

    m_comment.erase(0);
    m_comment.reserve(32);
}

第七步,将cpp文件中的Gunzip::ProcessPrestreamHeader更改为以下内容:
void Gunzip::ProcessPrestreamHeader()
{
    m_length = 0;
    m_crc.Restart();

    m_filetime = 0;

    m_filename.erase(0);
    m_filename.reserve(16);

    m_comment.erase(0);
    m_comment.reserve(32);

    byte buf[6];
    byte b, flags;

    if (m_inQueue.Get(buf, 2)!=2) throw HeaderErr();
    if (buf[0] != MAGIC1 || buf[1] != MAGIC2) throw HeaderErr();
    if (!m_inQueue.Get(b) || (b != DEFLATED)) throw HeaderErr();     // skip CM flag
    if (!m_inQueue.Get(flags)) throw HeaderErr();
    if (flags & (ENCRYPTED | CONTINUED)) throw HeaderErr();
    if (m_inQueue.GetWord32(m_filetime, LITTLE_ENDIAN_ORDER) != 4) throw HeaderErr();
    if (m_inQueue.Skip(2)!=2) throw HeaderErr();    // Skip extra flags and OS type

    if (flags & EXTRA_FIELDS)   // skip extra fields
    {
        word16 length;
        if (m_inQueue.GetWord16(length, LITTLE_ENDIAN_ORDER) != 2) throw HeaderErr();
        if (m_inQueue.Skip(length)!=length) throw HeaderErr();
    }

    if (flags & FILENAME)   // extract filename
    {
        do
        {
            if(!m_inQueue.Get(b)) throw HeaderErr();
            if(b) m_filename.append( 1, (char)b );
        }
        while (b);
    }

    if (flags & COMMENTS)   // extract comments
    {
        do
        {
            if(!m_inQueue.Get(b)) throw HeaderErr();
            if(b) m_comment.append( 1, (char)b );
        }
        while (b);
    }
}

这是如何使用它的:
try {

    Gzip zipper(new FileSink("gzip-test.gz", true));
    zipper.SetFilename("test-filename.txt");
    zipper.SetComment("This is a test of filenames and comments");

    string data = "abcdefghijklmnopqrstuvwxyz";
    zipper.Put( (unsigned char*) data.c_str(), data.size());
    zipper.MessageEnd();        
}
catch(CryptoPP::Exception& ex)
{
    cerr << ex.what() << endl;
}

压缩文件名为gzip-test.gz。标题中的原始文件名字段为test-filename.txt,标题中的注释为This is a test of filenames and comments
您可以使用十六进制编辑器查看其操作。

enter image description here


以下是关于如何在实践中使用gzip -d和Mac OS X上的默认存档提取程序的工作原理:嵌入式文件名被忽略,文件将使用存档文件名保存,但不包括gz扩展名:

enter image description here

我不知道这是否是预期行为,因为文件名成员与在不支持长文件名的文件系统上保留长文件名有关。
在 Super User 上有一个有关行为的开放性问题:Is Gzip supposed to honor original filename for decompress?编辑:感谢 Super User 上的 Simon,必须使用 gunzip -N <gz-file> 来提取使用标头中存储的原始文件名。
为了完整起见,这是将存档写入内存字符串而不是磁盘文件时往返旅行的样子。
string s1, s2;
string data = "abcdefghijklmnopqrstuvwxyz";

Gzip zipper(new StringSink(s1));
zipper.SetFilename("test-filename.txt");
zipper.SetComment("This is a test of filenames and comments");

zipper.Put( (unsigned char*) data.c_str(), data.size());
zipper.MessageEnd();

Gunzip unzipper(new StringSink(s2));
unzipper.Put( (unsigned char*) s1.data(), s1.size());
unzipper.MessageEnd();

cout << "Filename: " << unzipper.GetFilename() << endl;
cout << "Comment: " << unzipper.GetComment() << endl;
cout << "Data: " << s2 << endl;

它产生了预期的结果。
上述修改中有一项重要的限制。当存档包含多个文件时,这些修改只适用于最后一个文件流,并且仅提供最后一个文件流的文件时间、文件名和注释。如果它们在最后一个文件流中不存在,则在getter中它们将为空。
这种限制的原因与Crypto++管道的架构方式有关。在Crypto++中,每个流都被视为消息。您可以调用NextMessage()并返回流中的字节。但是,该流是未打包的字节集合,而不是可以容纳额外字段的更高级别结构。
我相信修复它是非常困难的。我认为这意味着需要添加通道来获取附加数据,就像从Authenticated Encryption过滤器中检索密文(DEFAULT_CHANNEL)或认证标记(AAD_CHANNEL)的方式一样,Gzip压缩器和Gunzip解压缩器都需要进行修改。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接