如果使用 Gzip 类压缩,如何将文件名添加到存档?
How to add filename to archive if compressing using Gzip class?
我在加密前使用 Gzip 压缩数据。
Gzip gz;
gz.Put(file,size)
gz.MessageEnd();
gz.Get(file,gz.MaxRetrievable());
我希望创建的 gzip 文件包含原始文件名作为元数据。我如何通过 Crypto++ 界面执行此操作?
I want the created gzip file to include the original filename as metadata. How do i do this through the Crypto++ interface?
你不能out-of-the-box。这似乎是 Crypto++ 的限制。 (但见下文)。
来自 RFC 1952 显然有一个字段:
(if FLG.FNAME set)
+=========================================+
|...original file name, zero-terminated...| (more-->)
+=========================================+
但 Crypto++ 不允许您设置它(来自 gzip.c source code):
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 source code):
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 年来第一个提出要求的人,所以这不是一个受欢迎的要求:)
以下是修改源代码以添加文件时间、文件名和注释的方法。您可以在 Gzip and Gunzip. You can find the SVN diff on Pastebin at Diff for Crypto++ gzip for filename and comment processing 的 Crypto++ wiki 页面上找到补丁。文件大部分相同,但 wiki 更新,因为它在 Gzip::WritePoststreamTail
.
中有一些重置代码
首先,将以下受保护成员添加到 Gzip
和 Gunzip
classes:
word32 m_filetime;
std::string m_filename;
std::string m_comment;
其次,为每个构造函数(Gzip
和 Gunzip
)添加 m_filetime
的初始值设定项。例如,这是 Gzip
演员之一:
Gzip(const NameValuePairs ¶meters, BufferedTransformation *attachment=NULL)
: Deflator(parameters, attachment), m_filetime(0) {}
三、将public setter 添加到Gzip
class:
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
中添加public getter class:
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
。 header中的原始文件名字段为test-filename.txt
,header中的注释为This is a test of filenames and comments
.
您可以使用十六进制编辑器查看它的运行情况:
以下是 gzip -d
和 Mac OS X 上的默认存档提取器的实际工作方式:忽略嵌入的文件名,并使用存档保存文件文件名减去 gz
扩展名:
我不知道这是否是预期的行为,因为文件名成员与在不支持它们的文件系统上保留长文件名有关。
关于超级用户的行为有一个悬而未决的问题:Is Gzip supposed to honor original filename for decompress?。
编辑:感谢 Simon on Super User,必须使用 gunzip -N <gz-file>
使用隐藏在 header 中的原始文件名进行提取。
为了完整起见,这是将存档写入 in-memory 字符串而不是 on-disk 文件时的往返行程。
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;
它产生了预期的结果。
上面的修改有一个重要的警告。当存档包含单个文件时,mods 运行良好。但是如果流中包含多个文件,则只会为最后一个文件流提供文件时间、文件名和注释。如果它们在最后一个文件流中丢失,那么它们在 getter 中将为空。
限制的原因与 Crypto++ 管道的构建方式有关。在 Crypto++ 中,每个流都被视为一条消息。您可以调用 NextMessage()
并取回流中的字节。但是流是 collection 的未压缩字节,而不是可以容纳额外字段的更高级别的结构。
我很确定它的修复是 non-trivial。我相信这意味着 Gzip
压缩器和 Gunzip
解压缩器将需要添加通道,以便您可以检索其他数据,例如检索密文 (DEFAULT_CHANNEL
) 或身份验证标签的方式(AAD_CHANNEL
) 来自经过身份验证的加密过滤器。
我在加密前使用 Gzip 压缩数据。
Gzip gz;
gz.Put(file,size)
gz.MessageEnd();
gz.Get(file,gz.MaxRetrievable());
我希望创建的 gzip 文件包含原始文件名作为元数据。我如何通过 Crypto++ 界面执行此操作?
I want the created gzip file to include the original filename as metadata. How do i do this through the Crypto++ interface?
你不能out-of-the-box。这似乎是 Crypto++ 的限制。 (但见下文)。
来自 RFC 1952 显然有一个字段:
(if FLG.FNAME set)
+=========================================+
|...original file name, zero-terminated...| (more-->)
+=========================================+
但 Crypto++ 不允许您设置它(来自 gzip.c source code):
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 source code):
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 年来第一个提出要求的人,所以这不是一个受欢迎的要求:)
以下是修改源代码以添加文件时间、文件名和注释的方法。您可以在 Gzip and Gunzip. You can find the SVN diff on Pastebin at Diff for Crypto++ gzip for filename and comment processing 的 Crypto++ wiki 页面上找到补丁。文件大部分相同,但 wiki 更新,因为它在 Gzip::WritePoststreamTail
.
首先,将以下受保护成员添加到 Gzip
和 Gunzip
classes:
word32 m_filetime;
std::string m_filename;
std::string m_comment;
其次,为每个构造函数(Gzip
和 Gunzip
)添加 m_filetime
的初始值设定项。例如,这是 Gzip
演员之一:
Gzip(const NameValuePairs ¶meters, BufferedTransformation *attachment=NULL)
: Deflator(parameters, attachment), m_filetime(0) {}
三、将public setter 添加到Gzip
class:
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
中添加public getter class:
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
。 header中的原始文件名字段为test-filename.txt
,header中的注释为This is a test of filenames and comments
.
您可以使用十六进制编辑器查看它的运行情况:
以下是 gzip -d
和 Mac OS X 上的默认存档提取器的实际工作方式:忽略嵌入的文件名,并使用存档保存文件文件名减去 gz
扩展名:
我不知道这是否是预期的行为,因为文件名成员与在不支持它们的文件系统上保留长文件名有关。
关于超级用户的行为有一个悬而未决的问题:Is Gzip supposed to honor original filename for decompress?。
编辑:感谢 Simon on Super User,必须使用 gunzip -N <gz-file>
使用隐藏在 header 中的原始文件名进行提取。
为了完整起见,这是将存档写入 in-memory 字符串而不是 on-disk 文件时的往返行程。
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;
它产生了预期的结果。
上面的修改有一个重要的警告。当存档包含单个文件时,mods 运行良好。但是如果流中包含多个文件,则只会为最后一个文件流提供文件时间、文件名和注释。如果它们在最后一个文件流中丢失,那么它们在 getter 中将为空。
限制的原因与 Crypto++ 管道的构建方式有关。在 Crypto++ 中,每个流都被视为一条消息。您可以调用 NextMessage()
并取回流中的字节。但是流是 collection 的未压缩字节,而不是可以容纳额外字段的更高级别的结构。
我很确定它的修复是 non-trivial。我相信这意味着 Gzip
压缩器和 Gunzip
解压缩器将需要添加通道,以便您可以检索其他数据,例如检索密文 (DEFAULT_CHANNEL
) 或身份验证标签的方式(AAD_CHANNEL
) 来自经过身份验证的加密过滤器。