Line data Source code
1 : // Copyright (c) 2011-2022 Made to Order Software Corp. All Rights Reserved
2 : //
3 : // https://snapwebsites.org/project/edhttp
4 : // contact@m2osw.com
5 : //
6 : // This program is free software: you can redistribute it and/or modify
7 : // it under the terms of the GNU General Public License as published by
8 : // the Free Software Foundation, either version 3 of the License, or
9 : // (at your option) any later version.
10 : //
11 : // This program is distributed in the hope that it will be useful,
12 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : // GNU General Public License for more details.
15 : //
16 : // You should have received a copy of the GNU General Public License
17 : // along with this program. If not, see <https://www.gnu.org/licenses/>.
18 :
19 : /** \file
20 : * \brief Verify the snap_uri class.
21 : *
22 : * This file implements tests to verify that the snap_uri
23 : * class functions as expected.
24 : */
25 :
26 : // edhttp
27 : //
28 : #include <edhttp/compression/compressor.h>
29 :
30 : #include <edhttp/exception.h>
31 : #include <edhttp/token.h>
32 :
33 :
34 : // self
35 : //
36 : #include "catch_main.h"
37 :
38 :
39 : // snapdev
40 : //
41 : #include <snapdev/file_contents.h>
42 : #include <snapdev/not_used.h>
43 :
44 :
45 : // snaplogger
46 : //
47 : #include <snaplogger/message.h>
48 :
49 :
50 : // last include
51 : //
52 : #include <snapdev/poison.h>
53 :
54 :
55 :
56 : namespace
57 : {
58 :
59 :
60 :
61 : class compressor_named
62 : : public edhttp::compressor
63 : {
64 : public:
65 182 : compressor_named(char const * name) : compressor(name) {}
66 :
67 0 : virtual char const * get_name() const override { return nullptr; }
68 0 : virtual edhttp::buffer_t compress(edhttp::buffer_t const & input, edhttp::level_t level, bool text) override { snapdev::NOT_USED(input, level, text); return edhttp::buffer_t(); }
69 0 : virtual bool compatible(edhttp::buffer_t const & input) const override { snapdev::NOT_USED(input); return false; }
70 0 : virtual edhttp::buffer_t decompress(edhttp::buffer_t const & input) override { snapdev::NOT_USED(input); return edhttp::buffer_t(); }
71 0 : virtual edhttp::buffer_t decompress(edhttp::buffer_t const & input, std::size_t uncompressed_size) override { snapdev::NOT_USED(input, uncompressed_size); return edhttp::buffer_t(); }
72 : };
73 :
74 :
75 :
76 : } // no name namespace
77 :
78 :
79 :
80 4 : CATCH_TEST_CASE("compressor_bz2", "[compression]")
81 : {
82 4 : CATCH_START_SECTION("compressor_bz2: verify bz2 compressor")
83 : {
84 1 : edhttp::compressor * bz2(edhttp::get_compressor("bz2"));
85 1 : CATCH_REQUIRE(bz2 != nullptr);
86 1 : CATCH_REQUIRE(strcmp(bz2->get_name(), "bz2") == 0);
87 :
88 : // generate a random buffer to compress of 1kb to 16kb in size
89 : //
90 1 : auto const input(SNAP_CATCH2_NAMESPACE::random_buffer(1024, 1024 * 16));
91 12 : for(edhttp::level_t level(0); level <= 100; level += 10)
92 : {
93 11 : edhttp::buffer_t const compressed(bz2->compress(input, level, rand() & 1 == 0));
94 :
95 11 : bool equal(true);
96 11 : if(compressed.size() == input.size())
97 : {
98 0 : for(std::size_t pos(0); pos < compressed.size(); ++pos)
99 : {
100 0 : if(compressed[pos] != input[pos])
101 : {
102 0 : equal = false;
103 0 : break;
104 : }
105 : }
106 : }
107 : else
108 : {
109 11 : equal = false;
110 : }
111 11 : CATCH_REQUIRE_FALSE(equal);
112 :
113 11 : edhttp::buffer_t const decompressed(bz2->decompress(compressed));
114 11 : CATCH_REQUIRE(decompressed.size() == input.size());
115 :
116 11 : equal = true;
117 107734 : for(std::size_t pos(0); pos < decompressed.size(); ++pos)
118 : {
119 107723 : if(decompressed[pos] != input[pos])
120 : {
121 0 : equal = false;
122 0 : break;
123 : }
124 : }
125 11 : CATCH_REQUIRE(equal);
126 :
127 88 : for(std::size_t s(2); s < 9; ++s)
128 : {
129 154 : edhttp::buffer_t const broken_compressed_small(compressed.data(), compressed.data() + s);
130 77 : edhttp::buffer_t const compressed_repeat(bz2->decompress(broken_compressed_small));
131 77 : CATCH_REQUIRE(compressed_repeat == broken_compressed_small);
132 :
133 154 : edhttp::buffer_t broken_compressed_small2(compressed.data(), compressed.data() + s);
134 77 : broken_compressed_small2.back() ^= 0xff;
135 77 : edhttp::buffer_t const compressed_repeat2(bz2->decompress(broken_compressed_small2));
136 77 : CATCH_REQUIRE(compressed_repeat2 == broken_compressed_small2);
137 :
138 77 : edhttp::buffer_t const compressed_repeat3(bz2->decompress(broken_compressed_small2, input.size()));
139 77 : CATCH_REQUIRE(compressed_repeat3 == broken_compressed_small2);
140 77 : }
141 :
142 : // we do recognize a bz2 buffer
143 : //
144 11 : CATCH_REQUIRE_FALSE(bz2->compatible(input));
145 11 : CATCH_REQUIRE(bz2->compatible(compressed));
146 11 : CATCH_REQUIRE_FALSE(bz2->compatible(decompressed));
147 11 : }
148 1 : }
149 4 : CATCH_END_SECTION()
150 :
151 4 : CATCH_START_SECTION("compressor_bz2: verify small buffers with bz2 compressor")
152 : {
153 1 : edhttp::compressor * bz2(edhttp::get_compressor("bz2"));
154 1 : CATCH_REQUIRE(bz2 != nullptr);
155 :
156 : // generate a random buffer to compress of 1kb to 16kb in size
157 : //
158 20 : for(int size(1); size < 20; ++size)
159 : {
160 19 : auto const input(SNAP_CATCH2_NAMESPACE::random_buffer(size, size));
161 19 : edhttp::buffer_t const compressed(bz2->compress(input, rand() % 95 + 5, rand() & 1 == 0));
162 :
163 19 : bool equal(true);
164 19 : if(compressed.size() == input.size())
165 : {
166 0 : for(std::size_t pos(0); pos < compressed.size(); ++pos)
167 : {
168 0 : if(compressed[pos] != input[pos])
169 : {
170 0 : equal = false;
171 0 : break;
172 : }
173 : }
174 : }
175 : else
176 : {
177 19 : equal = false;
178 : }
179 19 : CATCH_REQUIRE_FALSE(equal);
180 :
181 19 : edhttp::buffer_t const decompressed(bz2->decompress(compressed));
182 19 : CATCH_REQUIRE(decompressed.size() == input.size());
183 :
184 19 : equal = true;
185 209 : for(std::size_t pos(0); pos < decompressed.size(); ++pos)
186 : {
187 190 : if(decompressed[pos] != input[pos])
188 : {
189 0 : equal = false;
190 0 : break;
191 : }
192 : }
193 19 : CATCH_REQUIRE(equal);
194 :
195 : // we do recognize a bz2 buffer
196 : //
197 19 : CATCH_REQUIRE(bz2->compatible(compressed));
198 19 : }
199 : }
200 4 : CATCH_END_SECTION()
201 :
202 4 : CATCH_START_SECTION("compressor_bz2: verify invalid bz2 magic length")
203 : {
204 1 : edhttp::compressor * bz2(edhttp::get_compressor("bz2"));
205 1 : CATCH_REQUIRE(bz2 != nullptr);
206 1 : CATCH_REQUIRE(strcmp(bz2->get_name(), "bz2") == 0);
207 :
208 10 : for(std::size_t size(0); size < 9; ++size)
209 : {
210 : // generate a random buffer to compress of 1kb to 16kb in size
211 : //
212 9 : auto input(SNAP_CATCH2_NAMESPACE::random_buffer(size, size));
213 9 : if(size >= 1)
214 : {
215 8 : input[0] = 'B';
216 : }
217 9 : if(size >= 2)
218 : {
219 7 : input[1] = 'Z';
220 : }
221 9 : if(size >= 3)
222 : {
223 6 : input[2] = 'h';
224 : }
225 9 : if(size >= 4)
226 : {
227 5 : input[3] = rand() % 10 + '0';
228 : }
229 9 : CATCH_REQUIRE_FALSE(bz2->compatible(input));
230 9 : }
231 : }
232 4 : CATCH_END_SECTION()
233 :
234 4 : CATCH_START_SECTION("compressor_bz2: attempt bz2 compressing an empty buffer")
235 : {
236 1 : edhttp::compressor * bz2(edhttp::get_compressor("bz2"));
237 1 : CATCH_REQUIRE(bz2 != nullptr);
238 1 : CATCH_REQUIRE(strcmp(bz2->get_name(), "bz2") == 0);
239 :
240 1 : edhttp::buffer_t const empty;
241 1 : CATCH_REQUIRE_FALSE(bz2->compatible(empty));
242 :
243 23 : for(edhttp::level_t level(0); level <= 120; level += rand() % 10 + 1)
244 : {
245 22 : edhttp::buffer_t const compressed(bz2->compress(empty, level, false));
246 :
247 : // an empty buffer can be "compressed" (the output is bigger, but it
248 : // does not fail)
249 : //
250 : //CATCH_REQUIRE(empty == compressed);
251 :
252 22 : edhttp::buffer_t const decompressed(bz2->decompress(compressed));
253 22 : CATCH_REQUIRE(empty == decompressed);
254 22 : }
255 1 : }
256 4 : CATCH_END_SECTION()
257 4 : }
258 :
259 :
260 3 : CATCH_TEST_CASE("compressor_deflate", "[compression]")
261 : {
262 3 : CATCH_START_SECTION("compressor_deflate: verify deflate compressor")
263 : {
264 1 : edhttp::compressor * deflate(edhttp::get_compressor("deflate"));
265 1 : CATCH_REQUIRE(deflate != nullptr);
266 1 : CATCH_REQUIRE(strcmp(deflate->get_name(), "deflate") == 0);
267 :
268 : // generate a random buffer to compress of 1kb to 16kb in size
269 : //
270 1 : auto const input(SNAP_CATCH2_NAMESPACE::random_buffer(1024, 1024 * 16));
271 12 : for(edhttp::level_t level(0); level <= 100; level += 10)
272 : {
273 11 : edhttp::buffer_t const compressed(deflate->compress(input, level, rand() & 1 == 0));
274 :
275 11 : bool equal(true);
276 11 : if(compressed.size() == input.size())
277 : {
278 0 : for(std::size_t pos(0); pos < compressed.size(); ++pos)
279 : {
280 0 : if(compressed[pos] != input[pos])
281 : {
282 0 : equal = false;
283 0 : break;
284 : }
285 : }
286 : }
287 : else
288 : {
289 11 : equal = false;
290 : }
291 11 : CATCH_REQUIRE_FALSE(equal);
292 :
293 11 : edhttp::buffer_t const decompressed(deflate->decompress(compressed, input.size()));
294 11 : CATCH_REQUIRE(decompressed.size() == input.size());
295 :
296 11 : equal = true;
297 59510 : for(std::size_t pos(0); pos < decompressed.size(); ++pos)
298 : {
299 59499 : if(decompressed[pos] != input[pos])
300 : {
301 0 : equal = false;
302 0 : break;
303 : }
304 : }
305 11 : CATCH_REQUIRE(equal);
306 :
307 : // decompress with the wrong size has to fail
308 : // failure in this case means the input buffer is returned as is
309 : //
310 11 : edhttp::buffer_t compressed_repeat(deflate->decompress(compressed, input.size() / 2));
311 11 : CATCH_REQUIRE(compressed_repeat.size() == compressed.size());
312 11 : CATCH_REQUIRE(compressed_repeat == compressed);
313 :
314 11 : compressed_repeat = deflate->decompress(compressed, 3);
315 11 : CATCH_REQUIRE(compressed_repeat.size() == compressed.size());
316 11 : CATCH_REQUIRE(compressed_repeat == compressed);
317 :
318 : // we cannot recognize a deflated buffer because it has no magic
319 : //
320 11 : CATCH_REQUIRE_FALSE(deflate->compatible(input));
321 11 : CATCH_REQUIRE_FALSE(deflate->compatible(compressed));
322 11 : CATCH_REQUIRE_FALSE(deflate->compatible(decompressed));
323 11 : }
324 1 : }
325 3 : CATCH_END_SECTION()
326 :
327 3 : CATCH_START_SECTION("compressor_deflate: attempt deflate compressing an empty buffer")
328 : {
329 1 : edhttp::compressor * deflate(edhttp::get_compressor("deflate"));
330 1 : CATCH_REQUIRE(deflate != nullptr);
331 1 : CATCH_REQUIRE(strcmp(deflate->get_name(), "deflate") == 0);
332 :
333 1 : edhttp::buffer_t const empty;
334 1 : CATCH_REQUIRE_FALSE(deflate->compatible(empty));
335 :
336 25 : for(edhttp::level_t level(rand() % 6); level <= 120; level += rand() % 10 + 1)
337 : {
338 24 : edhttp::buffer_t const compressed(deflate->compress(empty, level, false));
339 :
340 : // an empty buffer can be "compressed" (the output is bigger, but it
341 : // does not fail)
342 : //
343 : //CATCH_REQUIRE(empty == compressed);
344 :
345 24 : edhttp::buffer_t const decompressed(deflate->decompress(compressed, 0));
346 24 : CATCH_REQUIRE(empty == decompressed);
347 24 : }
348 1 : }
349 3 : CATCH_END_SECTION()
350 :
351 3 : CATCH_START_SECTION("compressor_deflate: compress small buffers with deflate")
352 : {
353 1 : edhttp::compressor * deflate(edhttp::get_compressor("deflate"));
354 1 : CATCH_REQUIRE(deflate != nullptr);
355 1 : CATCH_REQUIRE(strcmp(deflate->get_name(), "deflate") == 0);
356 :
357 1024 : for(std::size_t size(1); size < 1024; ++size)
358 : {
359 1023 : auto const input(SNAP_CATCH2_NAMESPACE::random_buffer(size, size));
360 1023 : edhttp::buffer_t const compressed(deflate->compress(input, rand() % 95 + 5, (rand() & 1) == 0));
361 :
362 1023 : edhttp::buffer_t const decompressed(deflate->decompress(compressed, size));
363 1023 : CATCH_REQUIRE(input == decompressed);
364 1023 : }
365 : }
366 3 : CATCH_END_SECTION()
367 3 : }
368 :
369 :
370 4 : CATCH_TEST_CASE("compressor_gzip", "[compression]")
371 : {
372 4 : CATCH_START_SECTION("compressor_gzip: verify gzip compressor")
373 : {
374 1 : edhttp::compressor * gzip(edhttp::get_compressor("gzip"));
375 1 : CATCH_REQUIRE(gzip != nullptr);
376 1 : CATCH_REQUIRE(strcmp(gzip->get_name(), "gzip") == 0);
377 :
378 : // generate a random buffer to compress of 1kb to 16kb in size
379 : //
380 1 : auto const input(SNAP_CATCH2_NAMESPACE::random_buffer(1024, 1024 * 16));
381 12 : for(edhttp::level_t level(0); level <= 100; level += 10)
382 : {
383 11 : edhttp::buffer_t const compressed(gzip->compress(input, level, rand() & 1 == 0));
384 :
385 11 : bool equal(true);
386 11 : if(compressed.size() == input.size())
387 : {
388 0 : for(std::size_t pos(0); pos < compressed.size(); ++pos)
389 : {
390 0 : if(compressed[pos] != input[pos])
391 : {
392 0 : equal = false;
393 0 : break;
394 : }
395 : }
396 : }
397 : else
398 : {
399 11 : equal = false;
400 : }
401 11 : CATCH_REQUIRE_FALSE(equal);
402 :
403 11 : edhttp::buffer_t const decompressed(gzip->decompress(compressed));
404 11 : CATCH_REQUIRE(decompressed.size() == input.size());
405 :
406 11 : equal = true;
407 70323 : for(std::size_t pos(0); pos < decompressed.size(); ++pos)
408 : {
409 70312 : if(decompressed[pos] != input[pos])
410 : {
411 0 : equal = false;
412 0 : break;
413 : }
414 : }
415 11 : CATCH_REQUIRE(equal);
416 :
417 88 : for(std::size_t s(2); s < 9; ++s)
418 : {
419 154 : edhttp::buffer_t const broken_compressed_small(compressed.data(), compressed.data() + s);
420 77 : edhttp::buffer_t const compressed_repeat(gzip->decompress(broken_compressed_small));
421 77 : CATCH_REQUIRE(compressed_repeat == broken_compressed_small);
422 77 : }
423 :
424 : // we do recognize a gzip buffer
425 : //
426 11 : CATCH_REQUIRE_FALSE(gzip->compatible(input));
427 11 : CATCH_REQUIRE(gzip->compatible(compressed));
428 11 : CATCH_REQUIRE_FALSE(gzip->compatible(decompressed));
429 11 : }
430 1 : }
431 4 : CATCH_END_SECTION()
432 :
433 4 : CATCH_START_SECTION("compressor_gzip: verify small buffers with gzip compressor")
434 : {
435 1 : edhttp::compressor * gzip(edhttp::get_compressor("gzip"));
436 1 : CATCH_REQUIRE(gzip != nullptr);
437 :
438 : // generate a random buffer to compress of 1kb to 16kb in size
439 : //
440 20 : for(int size(1); size < 20; ++size)
441 : {
442 19 : auto const input(SNAP_CATCH2_NAMESPACE::random_buffer(size, size));
443 19 : edhttp::buffer_t const compressed(gzip->compress(input, rand() % 95 + 5, rand() & 1 == 0));
444 :
445 19 : bool equal(true);
446 19 : if(compressed.size() == input.size())
447 : {
448 0 : for(std::size_t pos(0); pos < compressed.size(); ++pos)
449 : {
450 0 : if(compressed[pos] != input[pos])
451 : {
452 0 : equal = false;
453 0 : break;
454 : }
455 : }
456 : }
457 : else
458 : {
459 19 : equal = false;
460 : }
461 19 : CATCH_REQUIRE_FALSE(equal);
462 :
463 19 : edhttp::buffer_t const decompressed(gzip->decompress(compressed));
464 19 : CATCH_REQUIRE(decompressed.size() == input.size());
465 :
466 19 : equal = true;
467 209 : for(std::size_t pos(0); pos < decompressed.size(); ++pos)
468 : {
469 190 : if(decompressed[pos] != input[pos])
470 : {
471 0 : equal = false;
472 0 : break;
473 : }
474 : }
475 19 : CATCH_REQUIRE(equal);
476 :
477 : // we do recognize a gzip buffer
478 : //
479 19 : CATCH_REQUIRE(gzip->compatible(compressed));
480 19 : }
481 : }
482 4 : CATCH_END_SECTION()
483 :
484 4 : CATCH_START_SECTION("compressor_gzip: verify invalid gzip magic length")
485 : {
486 1 : edhttp::compressor * gzip(edhttp::get_compressor("gzip"));
487 1 : CATCH_REQUIRE(gzip != nullptr);
488 1 : CATCH_REQUIRE(strcmp(gzip->get_name(), "gzip") == 0);
489 :
490 11 : for(std::size_t size(0); size < 10; ++size)
491 : {
492 : // generate a random buffer to compress of 1kb to 16kb in size
493 : //
494 10 : auto input(SNAP_CATCH2_NAMESPACE::random_buffer(size, size));
495 10 : if(size >= 1)
496 : {
497 9 : input[0] = 0x1F;
498 : }
499 10 : if(size >= 2)
500 : {
501 8 : input[1] = 0x8B;
502 : }
503 10 : CATCH_REQUIRE_FALSE(gzip->compatible(input));
504 10 : }
505 : }
506 4 : CATCH_END_SECTION()
507 :
508 4 : CATCH_START_SECTION("compressor_gzip: attempt gzip compressing an empty buffer")
509 : {
510 1 : edhttp::compressor * gzip(edhttp::get_compressor("gzip"));
511 1 : CATCH_REQUIRE(gzip != nullptr);
512 1 : CATCH_REQUIRE(strcmp(gzip->get_name(), "gzip") == 0);
513 :
514 1 : edhttp::buffer_t const empty;
515 1 : CATCH_REQUIRE_FALSE(gzip->compatible(empty));
516 :
517 25 : for(edhttp::level_t level(0); level <= 120; level += rand() % 10 + 1)
518 : {
519 24 : edhttp::buffer_t const compressed(gzip->compress(empty, level, false));
520 :
521 : // an empty buffer can be "compressed" (but the output is bigger,
522 : // yet it does not fail with gzip)
523 : //
524 : //CATCH_REQUIRE(empty == compressed);
525 :
526 24 : edhttp::buffer_t const decompressed(gzip->decompress(compressed));
527 24 : CATCH_REQUIRE(empty == decompressed);
528 24 : }
529 1 : }
530 4 : CATCH_END_SECTION()
531 4 : }
532 :
533 :
534 4 : CATCH_TEST_CASE("compressor_xz", "[compression]")
535 : {
536 4 : CATCH_START_SECTION("compressor_xz: verify xz compressor")
537 : {
538 : // use a text file (this very file) because that compresses well
539 : //
540 2 : snapdev::file_contents source(SNAP_CATCH2_NAMESPACE::g_source_dir() + "/tests/catch_compressor.cpp");
541 1 : CATCH_REQUIRE(source.read_all());
542 1 : std::string const data(source.contents());
543 2 : edhttp::buffer_t const input(data.begin(), data.end());
544 :
545 1 : edhttp::compressor * xz(edhttp::get_compressor("xz"));
546 1 : CATCH_REQUIRE(xz != nullptr);
547 1 : CATCH_REQUIRE(strcmp(xz->get_name(), "xz") == 0);
548 :
549 12 : for(edhttp::level_t level(0); level <= 100; level += 10)
550 : {
551 11 : edhttp::buffer_t const compressed(xz->compress(input, level, rand() & 1 == 0));
552 :
553 11 : bool equal(true);
554 11 : if(compressed.size() == input.size())
555 : {
556 0 : for(std::size_t pos(0); pos < compressed.size(); ++pos)
557 : {
558 0 : if(compressed[pos] != input[pos])
559 : {
560 0 : equal = false;
561 0 : break;
562 : }
563 : }
564 : }
565 : else
566 : {
567 11 : equal = false;
568 : }
569 11 : CATCH_REQUIRE_FALSE(equal);
570 :
571 11 : edhttp::buffer_t const decompressed(xz->decompress(compressed));
572 11 : CATCH_REQUIRE(decompressed.size() == input.size());
573 :
574 11 : equal = true;
575 461164 : for(std::size_t pos(0); pos < decompressed.size(); ++pos)
576 : {
577 461153 : if(decompressed[pos] != input[pos])
578 : {
579 0 : equal = false;
580 0 : break;
581 : }
582 : }
583 11 : CATCH_REQUIRE(equal);
584 :
585 88 : for(std::size_t s(2); s < 9; ++s)
586 : {
587 154 : edhttp::buffer_t const broken_compressed_small(compressed.data(), compressed.data() + s);
588 77 : edhttp::buffer_t const compressed_repeat(xz->decompress(broken_compressed_small));
589 77 : CATCH_REQUIRE(compressed_repeat == broken_compressed_small);
590 77 : }
591 :
592 : // we do recognize a xz buffer
593 : //
594 11 : CATCH_REQUIRE_FALSE(xz->compatible(input));
595 11 : CATCH_REQUIRE(xz->compatible(compressed));
596 11 : CATCH_REQUIRE_FALSE(xz->compatible(decompressed));
597 11 : }
598 1 : }
599 4 : CATCH_END_SECTION()
600 :
601 4 : CATCH_START_SECTION("compressor_xz: verify small buffers with xz compressor")
602 : {
603 1 : edhttp::compressor * xz(edhttp::get_compressor("xz"));
604 1 : CATCH_REQUIRE(xz != nullptr);
605 :
606 : // generate a random buffer to compress of 1kb to 16kb in size
607 : //
608 20 : for(int size(1); size < 20; ++size)
609 : {
610 19 : auto const input(SNAP_CATCH2_NAMESPACE::random_buffer(size, size));
611 19 : edhttp::buffer_t const compressed(xz->compress(input, rand() % 95 + 5, rand() & 1 == 0));
612 :
613 19 : bool equal(true);
614 19 : if(compressed.size() == input.size())
615 : {
616 0 : for(std::size_t pos(0); pos < compressed.size(); ++pos)
617 : {
618 0 : if(compressed[pos] != input[pos])
619 : {
620 0 : equal = false;
621 0 : break;
622 : }
623 : }
624 : }
625 : else
626 : {
627 19 : equal = false;
628 : }
629 19 : CATCH_REQUIRE_FALSE(equal);
630 :
631 19 : edhttp::buffer_t const decompressed(xz->decompress(compressed));
632 19 : CATCH_REQUIRE(decompressed.size() == input.size());
633 :
634 19 : equal = true;
635 209 : for(std::size_t pos(0); pos < decompressed.size(); ++pos)
636 : {
637 190 : if(decompressed[pos] != input[pos])
638 : {
639 0 : equal = false;
640 0 : break;
641 : }
642 : }
643 19 : CATCH_REQUIRE(equal);
644 :
645 : // we do recognize a xz buffer
646 : //
647 19 : CATCH_REQUIRE(xz->compatible(compressed));
648 19 : }
649 : }
650 4 : CATCH_END_SECTION()
651 :
652 4 : CATCH_START_SECTION("compressor_xz: verify invalid xz magic length")
653 : {
654 1 : edhttp::compressor * xz(edhttp::get_compressor("xz"));
655 1 : CATCH_REQUIRE(xz != nullptr);
656 1 : CATCH_REQUIRE(strcmp(xz->get_name(), "xz") == 0);
657 :
658 11 : for(std::size_t size(0); size < 10; ++size)
659 : {
660 : // generate a random buffer to compress of 1kb to 16kb in size
661 : //
662 10 : auto input(SNAP_CATCH2_NAMESPACE::random_buffer(size, size));
663 10 : if(size >= 1)
664 : {
665 9 : input[0] = 0xFD;
666 : }
667 10 : if(size >= 2)
668 : {
669 8 : input[1] = '7';
670 : }
671 10 : if(size >= 3)
672 : {
673 7 : input[2] = 'z';
674 : }
675 10 : if(size >= 4)
676 : {
677 6 : input[3] = 'X';
678 : }
679 10 : if(size >= 5)
680 : {
681 5 : input[4] = 'Z';
682 : }
683 10 : CATCH_REQUIRE_FALSE(xz->compatible(input));
684 10 : }
685 : }
686 4 : CATCH_END_SECTION()
687 :
688 4 : CATCH_START_SECTION("compressor_xz: attempt xz compressing an empty buffer")
689 : {
690 1 : edhttp::compressor * xz(edhttp::get_compressor("xz"));
691 1 : CATCH_REQUIRE(xz != nullptr);
692 1 : CATCH_REQUIRE(strcmp(xz->get_name(), "xz") == 0);
693 :
694 1 : edhttp::buffer_t const empty;
695 1 : CATCH_REQUIRE_FALSE(xz->compatible(empty));
696 :
697 21 : for(edhttp::level_t level(0); level <= 120; level += rand() % 10 + 1)
698 : {
699 20 : edhttp::buffer_t const compressed(xz->compress(empty, level, false));
700 :
701 : // an empty buffer cannot be "compressed" (the output is bigger,
702 : // but it does not fail)
703 : //
704 : //CATCH_REQUIRE(empty == compressed);
705 :
706 20 : edhttp::buffer_t const decompressed(xz->decompress(compressed));
707 20 : CATCH_REQUIRE(empty == decompressed);
708 20 : }
709 1 : }
710 4 : CATCH_END_SECTION()
711 4 : }
712 :
713 :
714 14 : CATCH_TEST_CASE("compressor", "[compression]")
715 : {
716 14 : CATCH_START_SECTION("compressor: verify list of compressors in our library")
717 : {
718 1 : advgetopt::string_list_t const list(edhttp::compressor_list());
719 :
720 1 : CATCH_REQUIRE(list.size() == 4);
721 :
722 : // internally it's in a map so it remains sorted
723 : //
724 1 : CATCH_REQUIRE(list[0] == "bz2");
725 1 : CATCH_REQUIRE(list[1] == "deflate");
726 1 : CATCH_REQUIRE(list[2] == "gzip");
727 1 : CATCH_REQUIRE(list[3] == "xz");
728 :
729 5 : for(auto const & name : list)
730 : {
731 4 : edhttp::compressor * c(edhttp::get_compressor(name));
732 4 : CATCH_REQUIRE(c != nullptr);
733 : }
734 1 : }
735 14 : CATCH_END_SECTION()
736 :
737 14 : CATCH_START_SECTION("compressor: verify the \"unknown\" compressor does not exist")
738 : {
739 1 : edhttp::compressor * unknown(edhttp::get_compressor("unknown"));
740 1 : CATCH_REQUIRE(unknown == nullptr);
741 : }
742 14 : CATCH_END_SECTION()
743 :
744 14 : CATCH_START_SECTION("compressor: compress() with an empty input buffer")
745 : {
746 : // compress() with an empty buffer ignores the other parameters
747 : //
748 1 : advgetopt::string_list_t const list(edhttp::compressor_list());
749 11 : for(int i(0); i < 10; ++i)
750 : {
751 10 : edhttp::buffer_t const buffer;
752 10 : std::string compressor_name(list[rand() % list.size()]);
753 50 : edhttp::result_t const compressed(edhttp::compress({compressor_name}, buffer, rand() % 96 + 5, rand() % 1 == 0));
754 10 : CATCH_REQUIRE(compressed.first == buffer);
755 10 : CATCH_REQUIRE(compressed.second == edhttp::compressor::NO_COMPRESSION);
756 10 : }
757 1 : }
758 14 : CATCH_END_SECTION()
759 :
760 14 : CATCH_START_SECTION("compressor: compress() with too small a level")
761 : {
762 : // compress() with an empty buffer ignores the other parameters
763 : //
764 1 : advgetopt::string_list_t const list(edhttp::compressor_list());
765 11 : for(int i(0); i < 10; ++i)
766 : {
767 10 : edhttp::buffer_t const buffer(SNAP_CATCH2_NAMESPACE::random_buffer(1, 1024));
768 10 : std::string compressor_name(list[rand() % list.size()]);
769 50 : edhttp::result_t const compressed(edhttp::compress({compressor_name}, buffer, rand() % 5, rand() % 1 == 0));
770 10 : CATCH_REQUIRE(compressed.first == buffer);
771 10 : CATCH_REQUIRE(compressed.second == edhttp::compressor::NO_COMPRESSION);
772 10 : }
773 1 : }
774 14 : CATCH_END_SECTION()
775 :
776 14 : CATCH_START_SECTION("compressor: compress() with unknown compressor")
777 : {
778 : // compress() with an empty buffer ignores the other parameters
779 : //
780 11 : for(int i(0); i < 10; ++i)
781 : {
782 10 : edhttp::buffer_t const buffer(SNAP_CATCH2_NAMESPACE::random_buffer(1024, 1024 * 16));
783 20 : std::string compressor_name("unknown");
784 50 : edhttp::result_t const compressed(edhttp::compress({compressor_name}, buffer, rand() % 96 + 5, rand() % 1 == 0));
785 10 : CATCH_REQUIRE(compressed.second == edhttp::compressor::NO_COMPRESSION);
786 10 : CATCH_REQUIRE(compressed.first == buffer);
787 10 : }
788 : }
789 14 : CATCH_END_SECTION()
790 :
791 14 : CATCH_START_SECTION("compressor: compress() small buffers with bz2 return input")
792 : {
793 : // compress() with an empty buffer ignores the other parameters
794 : //
795 10 : for(int i(1); i < 10; ++i)
796 : {
797 9 : edhttp::buffer_t const buffer(SNAP_CATCH2_NAMESPACE::random_buffer(1, i));
798 18 : std::string compressor_name("bz2");
799 45 : edhttp::result_t const compressed(edhttp::compress({compressor_name}, buffer, 100, rand() % 1 == 0));
800 9 : CATCH_REQUIRE(compressed.first == buffer);
801 9 : CATCH_REQUIRE(compressed.second == edhttp::compressor::NO_COMPRESSION);
802 9 : }
803 : }
804 14 : CATCH_END_SECTION()
805 :
806 14 : CATCH_START_SECTION("compressor: compress() small buffers with gzip return input")
807 : {
808 : // compress() with an empty buffer ignores the other parameters
809 : //
810 10 : for(int i(1); i < 10; ++i)
811 : {
812 9 : edhttp::buffer_t const buffer(SNAP_CATCH2_NAMESPACE::random_buffer(1, i));
813 54 : edhttp::result_t const compressed(edhttp::compress({"gzip"}, buffer, rand() % 96 + 5, rand() % 1 == 0));
814 9 : CATCH_REQUIRE(compressed.first == buffer);
815 9 : CATCH_REQUIRE(compressed.second == edhttp::compressor::NO_COMPRESSION);
816 9 : }
817 : }
818 14 : CATCH_END_SECTION()
819 :
820 14 : CATCH_START_SECTION("compressor: compress() small buffers with xz return input")
821 : {
822 : // compress() with a small buffer ignores the other parameters
823 : //
824 10 : for(int i(1); i < 10; ++i)
825 : {
826 9 : edhttp::buffer_t const buffer(SNAP_CATCH2_NAMESPACE::random_buffer(1, i));
827 54 : edhttp::result_t const compressed(edhttp::compress({"xz"}, buffer, rand() % 96 + 5, rand() % 1 == 0));
828 9 : CATCH_REQUIRE(compressed.first == buffer);
829 9 : CATCH_REQUIRE(compressed.second == edhttp::compressor::NO_COMPRESSION);
830 9 : }
831 : }
832 14 : CATCH_END_SECTION()
833 :
834 14 : CATCH_START_SECTION("compressor: compress() with deflate; decompress explicitly")
835 : {
836 : // get text because it compresses well and the test will work
837 : //
838 2 : snapdev::file_contents source(SNAP_CATCH2_NAMESPACE::g_source_dir() + "/tests/catch_compressor.cpp");
839 1 : CATCH_REQUIRE(source.read_all());
840 1 : std::string const data(source.contents());
841 2 : edhttp::buffer_t const buffer(data.begin(), data.end());
842 1 : edhttp::compressor * deflate(edhttp::get_compressor("deflate"));
843 1 : CATCH_REQUIRE(deflate != nullptr);
844 1 : CATCH_REQUIRE(strcmp(deflate->get_name(), "deflate") == 0);
845 10 : for(int i(1); i < 10; ++i)
846 : {
847 18 : std::string compressor_name("deflate");
848 45 : edhttp::result_t const compressed(edhttp::compress({compressor_name}, buffer, rand() % 96 + 5, true));
849 9 : CATCH_REQUIRE(compressed.second == "deflate");
850 :
851 : // deflate data as no magic, so calling decompress() "fails"
852 : //
853 9 : edhttp::result_t const decompressed(edhttp::decompress(compressed.first));
854 9 : CATCH_REQUIRE(decompressed.second == edhttp::compressor::NO_COMPRESSION);
855 9 : CATCH_REQUIRE(decompressed.first == compressed.first); // still compressed
856 :
857 : // instead we have to explicitly decompress
858 : //
859 9 : edhttp::buffer_t const original(deflate->decompress(compressed.first, buffer.size()));
860 9 : CATCH_REQUIRE(original == buffer);
861 9 : }
862 1 : }
863 14 : CATCH_END_SECTION()
864 :
865 14 : CATCH_START_SECTION("compressor: compress()/decompress() with bz2")
866 : {
867 : // get text because it compresses well and the test will work
868 : //
869 2 : snapdev::file_contents source(SNAP_CATCH2_NAMESPACE::g_source_dir() + "/tests/catch_compressor.cpp");
870 1 : CATCH_REQUIRE(source.read_all());
871 1 : std::string const data(source.contents());
872 2 : edhttp::buffer_t const buffer(data.begin(), data.end());
873 1 : edhttp::compressor * bz2(edhttp::get_compressor("bz2"));
874 1 : CATCH_REQUIRE(bz2 != nullptr);
875 1 : CATCH_REQUIRE(strcmp(bz2->get_name(), "bz2") == 0);
876 10 : for(int i(1); i < 10; ++i)
877 : {
878 18 : std::string compressor_name("bz2");
879 45 : edhttp::result_t const compressed(edhttp::compress({compressor_name}, buffer, rand() % 96 + 5, true));
880 9 : CATCH_REQUIRE(compressed.second == "bz2");
881 9 : CATCH_REQUIRE(bz2->compatible(compressed.first));
882 9 : CATCH_REQUIRE(compressed.first != buffer);
883 9 : edhttp::result_t const decompressed(edhttp::decompress(compressed.first));
884 9 : CATCH_REQUIRE(decompressed.second == "bz2");
885 9 : CATCH_REQUIRE(decompressed.first == buffer);
886 9 : }
887 1 : }
888 14 : CATCH_END_SECTION()
889 :
890 14 : CATCH_START_SECTION("compressor: compress()/decompress() a large buffer with bz2")
891 : {
892 : // get text because it compresses well and the test will work
893 : //
894 2 : snapdev::file_contents source(SNAP_CATCH2_NAMESPACE::g_source_dir() + "/tests/catch_compressor.cpp");
895 1 : CATCH_REQUIRE(source.read_all());
896 1 : std::string data(source.contents());
897 3 : while(data.length() < 1024 * 100 + 1)
898 : {
899 2 : data += data;
900 : }
901 2 : edhttp::buffer_t const buffer(data.begin(), data.end());
902 1 : edhttp::compressor * bz2(edhttp::get_compressor("bz2"));
903 1 : CATCH_REQUIRE(bz2 != nullptr);
904 1 : CATCH_REQUIRE(strcmp(bz2->get_name(), "bz2") == 0);
905 10 : for(int i(1); i < 10; ++i)
906 : {
907 54 : edhttp::result_t const compressed(edhttp::compress({"bz2"}, buffer, rand() % 96 + 5, true));
908 9 : CATCH_REQUIRE(compressed.second == "bz2");
909 9 : CATCH_REQUIRE(bz2->compatible(compressed.first));
910 9 : CATCH_REQUIRE(compressed.first != buffer);
911 9 : edhttp::result_t const decompressed(edhttp::decompress(compressed.first));
912 9 : CATCH_REQUIRE(decompressed.second == "bz2");
913 9 : CATCH_REQUIRE(decompressed.first == buffer);
914 :
915 : // also test with the size
916 : //
917 9 : edhttp::buffer_t const decompressed2(bz2->decompress(compressed.first, buffer.size()));
918 9 : CATCH_REQUIRE(decompressed2 == buffer);
919 9 : }
920 1 : }
921 14 : CATCH_END_SECTION()
922 :
923 14 : CATCH_START_SECTION("compressor: compress()/decompress() with gzip")
924 : {
925 : // get text because it compresses well and the test will work
926 : //
927 2 : snapdev::file_contents source(SNAP_CATCH2_NAMESPACE::g_source_dir() + "/tests/catch_compressor.cpp");
928 1 : CATCH_REQUIRE(source.read_all());
929 1 : std::string const data(source.contents());
930 2 : edhttp::buffer_t const buffer(data.begin(), data.end());
931 1 : edhttp::compressor * gzip(edhttp::get_compressor("gzip"));
932 1 : CATCH_REQUIRE(gzip != nullptr);
933 1 : CATCH_REQUIRE(strcmp(gzip->get_name(), "gzip") == 0);
934 10 : for(int i(1); i < 10; ++i)
935 : {
936 18 : std::string compressor_name("gzip");
937 45 : edhttp::result_t const compressed(edhttp::compress({compressor_name}, buffer, rand() % 96 + 5, true));
938 9 : CATCH_REQUIRE(compressed.second == "gzip");
939 9 : CATCH_REQUIRE(gzip->compatible(compressed.first));
940 9 : CATCH_REQUIRE(compressed.first != buffer);
941 9 : edhttp::result_t const decompressed(edhttp::decompress(compressed.first));
942 9 : CATCH_REQUIRE(decompressed.second == "gzip");
943 9 : CATCH_REQUIRE(decompressed.first == buffer);
944 9 : }
945 1 : }
946 14 : CATCH_END_SECTION()
947 :
948 14 : CATCH_START_SECTION("compressor: compress()/decompress() with best compressor")
949 : {
950 : // get text because it compresses well and the test works every time
951 : //
952 2 : snapdev::file_contents source(SNAP_CATCH2_NAMESPACE::g_source_dir() + "/tests/catch_compressor.cpp");
953 1 : CATCH_REQUIRE(source.read_all());
954 1 : std::string const data(source.contents());
955 2 : edhttp::buffer_t const buffer(data.begin(), data.end());
956 11 : for(int i(0); i < 10; ++i)
957 : {
958 10 : advgetopt::string_list_t names;
959 10 : if((i & 1) == 1)
960 : {
961 5 : names.push_back("bz2");
962 5 : names.push_back("gzip");
963 5 : std::random_shuffle(names.begin(), names.end());
964 : }
965 20 : edhttp::result_t const compressed(edhttp::compress({}, buffer, rand() % 96 + 5, true));
966 10 : if(compressed.second == edhttp::compressor::NO_COMPRESSION)
967 : {
968 : // no compression possible
969 : //
970 0 : CATCH_REQUIRE(compressed.first == buffer);
971 : }
972 : else
973 : {
974 10 : edhttp::compressor * c(edhttp::get_compressor(compressed.second));
975 10 : CATCH_REQUIRE(c != nullptr);
976 10 : CATCH_REQUIRE(c->get_name() == compressed.second);
977 10 : SNAP_LOG_WARNING << "--- c = " << c->get_name() << SNAP_LOG_SEND;
978 10 : if(compressed.second != "deflate")
979 : {
980 10 : CATCH_REQUIRE(c->compatible(compressed.first));
981 10 : edhttp::result_t const decompressed(edhttp::decompress(compressed.first));
982 10 : CATCH_REQUIRE(decompressed.second == compressed.second);
983 10 : CATCH_REQUIRE(decompressed.first == buffer);
984 10 : }
985 : else
986 : {
987 : // deflate cannot be accessed from edhttp::decompress() because
988 : // there is no magic and thus it cannot be a sure thing
989 : //
990 0 : edhttp::buffer_t const decompressed(c->decompress(compressed.first, buffer.size()));
991 0 : CATCH_REQUIRE(decompressed == buffer);
992 0 : }
993 : }
994 10 : }
995 1 : }
996 14 : CATCH_END_SECTION()
997 :
998 14 : CATCH_START_SECTION("compressor: compress() too small a buffer with any compressor")
999 : {
1000 11 : for(int i(0); i < 10; ++i)
1001 : {
1002 : // generate a very small random buffer to compress so that the
1003 : // result is larger than the input and thus "fails" in a slightly
1004 : // different path
1005 : //
1006 10 : edhttp::buffer_t const buffer(SNAP_CATCH2_NAMESPACE::random_buffer(1, 5));
1007 20 : edhttp::result_t const compressed(edhttp::compress({}, buffer, rand() % 96 + 5, true));
1008 10 : CATCH_REQUIRE(compressed.second == edhttp::compressor::NO_COMPRESSION);
1009 10 : CATCH_REQUIRE(compressed.first == buffer);
1010 10 : }
1011 : }
1012 14 : CATCH_END_SECTION()
1013 14 : }
1014 :
1015 :
1016 6 : CATCH_TEST_CASE("compressor_error", "[compression][error]")
1017 : {
1018 6 : CATCH_START_SECTION("compressor_error: deflate decompress() requires a size")
1019 : {
1020 1 : edhttp::compressor * deflate(edhttp::get_compressor("deflate"));
1021 1 : CATCH_REQUIRE(deflate != nullptr);
1022 1 : CATCH_REQUIRE(strcmp(deflate->get_name(), "deflate") == 0);
1023 :
1024 : // generate a random buffer to compress of 1kb to 16kb in size
1025 : //
1026 1 : auto const buffer(SNAP_CATCH2_NAMESPACE::random_buffer(1024, 1024 * 16));
1027 1 : CATCH_REQUIRE_THROWS_MATCHES(
1028 : deflate->decompress(buffer)
1029 : , edhttp::not_implemented
1030 : , Catch::Matchers::ExceptionMessage(
1031 : "not_implemented: deflate::decompress() without the uncompressed_size parameter is not implemented."));
1032 1 : }
1033 6 : CATCH_END_SECTION()
1034 :
1035 6 : CATCH_START_SECTION("compressor_error: gzip decompress() does not support a size")
1036 : {
1037 1 : edhttp::compressor * gzip(edhttp::get_compressor("gzip"));
1038 1 : CATCH_REQUIRE(gzip != nullptr);
1039 1 : CATCH_REQUIRE(strcmp(gzip->get_name(), "gzip") == 0);
1040 :
1041 : // generate a random buffer to compress of 1kb to 16kb in size
1042 : //
1043 1 : auto const buffer(SNAP_CATCH2_NAMESPACE::random_buffer(1024, 1024 * 16));
1044 1 : CATCH_REQUIRE_THROWS_MATCHES(
1045 : gzip->decompress(buffer, buffer.size())
1046 : , edhttp::not_implemented
1047 : , Catch::Matchers::ExceptionMessage(
1048 : "not_implemented: gzip::decompress() with a size is not implemented."));
1049 1 : }
1050 6 : CATCH_END_SECTION()
1051 :
1052 6 : CATCH_START_SECTION("compressor_error: xz decompress() does not support a size")
1053 : {
1054 1 : edhttp::compressor * gzip(edhttp::get_compressor("xz"));
1055 1 : CATCH_REQUIRE(gzip != nullptr);
1056 1 : CATCH_REQUIRE(strcmp(gzip->get_name(), "xz") == 0);
1057 :
1058 : // generate a random buffer to compress of 1kb to 16kb in size
1059 : //
1060 1 : auto const buffer(SNAP_CATCH2_NAMESPACE::random_buffer(1024, 1024 * 16));
1061 1 : CATCH_REQUIRE_THROWS_MATCHES(
1062 : gzip->decompress(buffer, buffer.size())
1063 : , edhttp::not_implemented
1064 : , Catch::Matchers::ExceptionMessage(
1065 : "not_implemented: xz::decompress() with a size is not implemented."));
1066 1 : }
1067 6 : CATCH_END_SECTION()
1068 :
1069 6 : CATCH_START_SECTION("compressor_error: compressor name cannot be nullptr or empty")
1070 : {
1071 2 : CATCH_REQUIRE_THROWS_MATCHES(
1072 : new compressor_named(nullptr)
1073 : , edhttp::invalid_token
1074 : , Catch::Matchers::ExceptionMessage(
1075 : "edhttp_exception: the name of a compressor cannot be empty."));
1076 :
1077 2 : CATCH_REQUIRE_THROWS_MATCHES(
1078 : new compressor_named("")
1079 : , edhttp::invalid_token
1080 : , Catch::Matchers::ExceptionMessage(
1081 : "edhttp_exception: the name of a compressor cannot be empty."));
1082 : }
1083 6 : CATCH_END_SECTION()
1084 :
1085 6 : CATCH_START_SECTION("compressor_error: compressor name cannot be a special name")
1086 : {
1087 2 : CATCH_REQUIRE_THROWS_MATCHES(
1088 : new compressor_named(edhttp::compressor::NO_COMPRESSION)
1089 : , edhttp::incompatible
1090 : , Catch::Matchers::ExceptionMessage(
1091 : "edhttp_exception: name \"none\" is not available as a compressor name."));
1092 : }
1093 6 : CATCH_END_SECTION()
1094 :
1095 6 : CATCH_START_SECTION("compressor_error: compressor name cannot include a special character")
1096 : {
1097 : // Note: we cannot test character '\0' since we use a C-string to
1098 : // name the compressor (so '\0' is end of string in this case)
1099 : //
1100 256 : for(int c(1); c < 256; ++c)
1101 : {
1102 510 : std::string name("name");
1103 255 : name += c;
1104 255 : if(!edhttp::is_token(name))
1105 : {
1106 : // if the following doesn't throw, then we have an issue
1107 : // because the compressor will have a temporary string
1108 : // as its name
1109 : //
1110 356 : CATCH_REQUIRE_THROWS_MATCHES(
1111 : new compressor_named(name.c_str())
1112 : , edhttp::invalid_token
1113 : , Catch::Matchers::ExceptionMessage(
1114 : "edhttp_exception: a compressor name (\"" + name + "\") must be a valid HTTP token."));
1115 : }
1116 255 : }
1117 :
1118 : // also a token cannot start with the '$' character
1119 : //
1120 2 : CATCH_REQUIRE_THROWS_MATCHES(
1121 : new compressor_named("$name")
1122 : , edhttp::invalid_token
1123 : , Catch::Matchers::ExceptionMessage(
1124 : "edhttp_exception: a compressor name (\"$name\") must be a valid HTTP token."));
1125 : }
1126 6 : CATCH_END_SECTION()
1127 6 : }
1128 :
1129 :
1130 : // vim: ts=4 sw=4 et
|