1// (C) Copyright 2008 CodeRage, LLC (turkanis at coderage dot com)
2// (C) Copyright 2004-2007 Jonathan Turkanis
3// Distributed under the Boost Software License, Version 1.0. (See accompanying
4// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt.)
5
6// See http://www.boost.org/libs/iostreams for documentation.
7
8#include <cstddef>
9#include <string>
10#include <boost/iostreams/copy.hpp>
11#include <boost/iostreams/device/array.hpp>
12#include <boost/iostreams/device/back_inserter.hpp>
13#include <boost/iostreams/filter/gzip.hpp>
14#include <boost/iostreams/filter/test.hpp>
15#include <boost/iostreams/filtering_stream.hpp>
16#include <boost/ref.hpp>
17#include <boost/range/iterator_range.hpp>
18#include <boost/test/test_tools.hpp>
19#include <boost/test/unit_test.hpp>
20#include "detail/sequence.hpp"
21#include "detail/verification.hpp"
22
23using namespace boost;
24using namespace boost::iostreams;
25using namespace boost::iostreams::test;
26namespace io = boost::iostreams;
27using boost::unit_test::test_suite;
28
29template<class T> struct basic_test_alloc: std::allocator<T>
30{
31 basic_test_alloc()
32 {
33 }
34
35 basic_test_alloc( basic_test_alloc const& /*other*/ )
36 {
37 }
38
39 template<class U>
40 basic_test_alloc( basic_test_alloc<U> const & /*other*/ )
41 {
42 }
43
44 template<class U> struct rebind
45 {
46 typedef basic_test_alloc<U> other;
47 };
48};
49
50typedef basic_test_alloc<char> gzip_alloc;
51
52void compression_test()
53{
54 text_sequence data;
55
56 // Test compression and decompression with metadata
57 for (int i = 0; i < 4; ++i) {
58 gzip_params params;
59 if (i & 1) {
60 params.file_name = "original file name";
61 }
62 if (i & 2) {
63 params.comment = "detailed file description";
64 }
65 gzip_compressor out(params);
66 gzip_decompressor in;
67 BOOST_CHECK(
68 test_filter_pair( boost::ref(out),
69 boost::ref(in),
70 std::string(data.begin(), data.end()) )
71 );
72 BOOST_CHECK(in.file_name() == params.file_name);
73 BOOST_CHECK(in.comment() == params.comment);
74 }
75
76 // Test compression and decompression with custom allocator
77 BOOST_CHECK(
78 test_filter_pair( basic_gzip_compressor<gzip_alloc>(),
79 basic_gzip_decompressor<gzip_alloc>(),
80 std::string(data.begin(), data.end()) )
81 );
82}
83
84void multiple_member_test()
85{
86 text_sequence data;
87 std::vector<char> temp, dest;
88
89 // Write compressed data to temp, twice in succession
90 filtering_ostream out;
91 out.push(t: gzip_compressor());
92 out.push(t: io::back_inserter(cnt&: temp));
93 io::copy(src: make_iterator_range(r&: data), snk&: out);
94 out.push(t: io::back_inserter(cnt&: temp));
95 io::copy(src: make_iterator_range(r&: data), snk&: out);
96
97 // Read compressed data from temp into dest
98 filtering_istream in;
99 in.push(t: gzip_decompressor());
100 in.push(t: array_source(&temp[0], temp.size()));
101 io::copy(src&: in, snk: io::back_inserter(cnt&: dest));
102
103 // Check that dest consists of two copies of data
104 BOOST_REQUIRE_EQUAL(data.size() * 2, dest.size());
105 BOOST_CHECK(std::equal(data.begin(), data.end(), dest.begin()));
106 BOOST_CHECK(std::equal(data.begin(), data.end(), dest.begin() + dest.size() / 2));
107
108 dest.clear();
109 io::copy(
110 src: array_source(&temp[0], temp.size()),
111 snk: io::compose(filter: gzip_decompressor(), fod: io::back_inserter(cnt&: dest)));
112
113 // Check that dest consists of two copies of data
114 BOOST_REQUIRE_EQUAL(data.size() * 2, dest.size());
115 BOOST_CHECK(std::equal(data.begin(), data.end(), dest.begin()));
116 BOOST_CHECK(std::equal(data.begin(), data.end(), dest.begin() + dest.size() / 2));
117}
118
119void array_source_test()
120{
121 std::string data = "simple test string.";
122 std::string encoded;
123
124 filtering_ostream out;
125 out.push(t: gzip_compressor());
126 out.push(t: io::back_inserter(cnt&: encoded));
127 io::copy(src: make_iterator_range(r&: data), snk&: out);
128
129 std::string res;
130 io::array_source src(encoded.data(),encoded.length());
131 io::copy(src: io::compose(filter: io::gzip_decompressor(), fod: src), snk: io::back_inserter(cnt&: res));
132
133 BOOST_CHECK_EQUAL(data, res);
134}
135
136#if defined(BOOST_MSVC)
137# pragma warning(push)
138# pragma warning(disable:4309) // Truncation of constant value
139#endif
140
141void header_test()
142{
143 // This test is in response to https://svn.boost.org/trac/boost/ticket/5908
144 // which describes a problem parsing gzip headers with extra fields as
145 // defined in RFC 1952 (http://www.ietf.org/rfc/rfc1952.txt).
146 // The extra field data used here is characteristic of the tabix file
147 // format (http://samtools.sourceforge.net/tabix.shtml).
148 const char header_bytes[] = {
149 static_cast<char>(gzip::magic::id1),
150 static_cast<char>(gzip::magic::id2),
151 gzip::method::deflate, // Compression Method: deflate
152 gzip::flags::extra | gzip::flags::name | gzip::flags::comment, // flags
153 '\x22', '\x9c', '\xf3', '\x4e', // 4 byte modification time (little endian)
154 gzip::extra_flags::best_compression, // XFL
155 gzip::os_unix, // OS
156 6, 0, // 2 byte length of extra field (little endian, 6 bytes)
157 'B', 'C', 2, 0, 0, 0, // 6 bytes worth of extra field data
158 'a', 'b', 'c', 0, // original filename, null terminated
159 'n', 'o', ' ', 'c', 'o', 'm', 'm', 'e', 'n', 't', 0, // comment
160 };
161 size_t sz = sizeof(header_bytes)/sizeof(header_bytes[0]);
162
163 boost::iostreams::detail::gzip_header hdr;
164 for (size_t i = 0; i < sz; ++i) {
165 hdr.process(c: header_bytes[i]);
166
167 // Require that we are done at the last byte, not before.
168 if (i == sz-1)
169 BOOST_REQUIRE(hdr.done());
170 else
171 BOOST_REQUIRE(!hdr.done());
172 }
173
174 BOOST_CHECK_EQUAL("abc", hdr.file_name());
175 BOOST_CHECK_EQUAL("no comment", hdr.comment());
176 BOOST_CHECK_EQUAL(0x4ef39c22, hdr.mtime());
177 BOOST_CHECK_EQUAL(gzip::os_unix, hdr.os());
178}
179
180#if defined(BOOST_MSVC)
181# pragma warning(pop)
182#endif
183
184void empty_file_test()
185{
186 // This test is in response to https://svn.boost.org/trac/boost/ticket/5237
187 // The previous implementation of gzip_compressor only wrote the gzip file
188 // header when the first bytes of uncompressed input were processed, causing
189 // incorrect behavior for empty files
190 BOOST_CHECK(
191 test_filter_pair( gzip_compressor(),
192 gzip_decompressor(),
193 std::string() )
194 );
195}
196
197void multipart_test()
198{
199 // This test verifies that the gzip_decompressor properly handles a file
200 // that was written in multiple parts using Z_FULL_FLUSH, and in particular
201 // handles the CRC properly when one of those parts is empty.
202 const char multipart_file[] = {
203 '\x1f', '\x8b', '\x08', '\x00', '\x00', '\x00', '\x00', '\x00', '\x02', '\xff', '\xf2', '\xc9',
204 '\xcc', '\x4b', '\x55', '\x30', '\xe4', '\xf2', '\x01', '\x51', '\x46', '\x10', '\xca', '\x98',
205 '\x0b', '\x00', '\x00', '\x00', '\xff', '\xff', '\x03', '\x00', '\xdb', '\xa7', '\x83', '\xc9',
206 '\x15', '\x00', '\x00', '\x00', '\x1f', '\x8b', '\x08', '\x00', '\x00', '\x00', '\x00', '\x00',
207 '\x02', '\xff', '\xf2', '\xc9', '\xcc', '\x4b', '\x55', '\x30', '\xe1', '\xf2', '\x01', '\x51',
208 '\xa6', '\x10', '\xca', '\x8c', '\x0b', '\x00', '\x00', '\x00', '\xff', '\xff', '\x03', '\x00',
209 '\x41', '\xe3', '\xcc', '\xaa', '\x15', '\x00', '\x00', '\x00', '\x1f', '\x8b', '\x08', '\x00',
210 '\x00', '\x00', '\x00', '\x00', '\x02', '\xff', '\x02', '\x00', '\x00', '\x00', '\xff', '\xff',
211 '\x03', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x1f', '\x8b',
212 '\x08', '\x00', '\x00', '\x00', '\x00', '\x00', '\x02', '\xff', '\xf2', '\xc9', '\xcc', '\x4b',
213 '\x55', '\x30', '\xe7', '\xf2', '\x01', '\x51', '\x16', '\x10', '\xca', '\x92', '\x0b', '\x00',
214 '\x00', '\x00', '\xff', '\xff', '\x03', '\x00', '\x2b', '\xac', '\xd3', '\xf5', '\x15', '\x00',
215 '\x00', '\x00'
216 };
217
218 filtering_istream in;
219 std::string line;
220
221 in.push(t: gzip_decompressor());
222 in.push(t: io::array_source(multipart_file, sizeof(multipart_file)));
223
224 // First part
225 std::getline(is&: in, str&: line);
226 BOOST_CHECK_EQUAL("Line 1", line);
227 std::getline(is&: in, str&: line);
228 BOOST_CHECK_EQUAL("Line 2", line);
229 std::getline(is&: in, str&: line);
230 BOOST_CHECK_EQUAL("Line 3", line);
231
232 // Second part immediately follows
233 std::getline(is&: in, str&: line);
234 BOOST_CHECK_EQUAL("Line 4", line);
235 std::getline(is&: in, str&: line);
236 BOOST_CHECK_EQUAL("Line 5", line);
237 std::getline(is&: in, str&: line);
238 BOOST_CHECK_EQUAL("Line 6", line);
239
240 // Then an empty part, followed by one last 3-line part.
241 std::getline(is&: in, str&: line);
242 BOOST_CHECK_EQUAL("Line 7", line);
243 std::getline(is&: in, str&: line);
244 BOOST_CHECK_EQUAL("Line 8", line);
245 std::getline(is&: in, str&: line);
246 BOOST_CHECK_EQUAL("Line 9", line);
247
248 // Check for gzip errors too.
249 BOOST_CHECK(!in.bad());
250}
251
252test_suite* init_unit_test_suite(int, char* [])
253{
254 test_suite* test = BOOST_TEST_SUITE("gzip test");
255
256#if !defined(__APPLE__)
257
258 // this test fails on macOS by throwing zlib_error, and it's not clear why
259 test->add(BOOST_TEST_CASE(&compression_test));
260
261#endif
262
263 test->add(BOOST_TEST_CASE(&multiple_member_test));
264 test->add(BOOST_TEST_CASE(&array_source_test));
265 test->add(BOOST_TEST_CASE(&header_test));
266 test->add(BOOST_TEST_CASE(&empty_file_test));
267 test->add(BOOST_TEST_CASE(&multipart_test));
268 return test;
269}
270

source code of boost/libs/iostreams/test/gzip_test.cpp