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 | |
23 | using namespace boost; |
24 | using namespace boost::iostreams; |
25 | using namespace boost::iostreams::test; |
26 | namespace io = boost::iostreams; |
27 | using boost::unit_test::test_suite; |
28 | |
29 | template<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 | |
50 | typedef basic_test_alloc<char> gzip_alloc; |
51 | |
52 | void 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 | |
84 | void 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 | |
119 | void 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 | |
141 | void () |
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 [] = { |
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 | |
184 | void 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 | |
197 | void 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 | |
252 | test_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 | |