Serenity Operating System
1/*
2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice, this
9 * list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include <AK/ByteBuffer.h>
28#include <AK/Optional.h>
29#include <LibCore/Gzip.h>
30#include <LibCore/puff.h>
31#include <limits.h>
32#include <stddef.h>
33
34bool CGzip::is_compressed(const ByteBuffer& data)
35{
36 return data.size() > 2 && data[0] == 0x1F && data[1] == 0x8b;
37}
38
39// skips the gzip header
40// see: https://tools.ietf.org/html/rfc1952#page-5
41static Optional<ByteBuffer> get_gzip_payload(const ByteBuffer& data)
42{
43 size_t current = 0;
44 auto read_byte = [&]() {
45 if (current >= data.size()) {
46 ASSERT_NOT_REACHED();
47 return (u8)0;
48 }
49 // dbg() << "read_byte: " << String::format("%x", data[current]);
50 return data[current++];
51 };
52
53 dbg() << "get_gzip_payload: Skipping over gzip header.";
54
55 // Magic Header
56 if (read_byte() != 0x1F || read_byte() != 0x8B) {
57 dbg() << "get_gzip_payload: Wrong magic number.";
58 return Optional<ByteBuffer>();
59 }
60
61 // Compression method
62 auto method = read_byte();
63 if (method != 8) {
64 dbg() << "get_gzip_payload: Wrong compression method = " << method;
65 return Optional<ByteBuffer>();
66 }
67
68 u8 flags = read_byte();
69
70 // Timestamp, Extra flags, OS
71 current += 6;
72
73 // FEXTRA
74 if (flags & 4) {
75 u16 length = read_byte() & read_byte() << 8;
76 dbg() << "get_gzip_payload: Header has FEXTRA flag set. Length = " << length;
77 current += length;
78 }
79
80 // FNAME
81 if (flags & 8) {
82 dbg() << "get_gzip_payload: Header has FNAME flag set.";
83 while (read_byte() != '\0')
84 ;
85 }
86
87 // FCOMMENT
88 if (flags & 16) {
89 dbg() << "get_gzip_payload: Header has FCOMMENT flag set.";
90 while (read_byte() != '\0')
91 ;
92 }
93
94 // FHCRC
95 if (flags & 2) {
96 dbg() << "get_gzip_payload: Header has FHCRC flag set.";
97 current += 2;
98 }
99
100 auto new_size = data.size() - current;
101 dbg() << "get_gzip_payload: Returning slice from " << current << " with size " << new_size;
102 return data.slice(current, new_size);
103}
104
105Optional<ByteBuffer> CGzip::decompress(const ByteBuffer& data)
106{
107 ASSERT(is_compressed(data));
108
109 dbg() << "Gzip::decompress: Decompressing gzip compressed data. Size = " << data.size();
110 auto optional_payload = get_gzip_payload(data);
111 if (!optional_payload.has_value()) {
112 return Optional<ByteBuffer>();
113 }
114
115 auto source = optional_payload.value();
116 unsigned long source_len = source.size();
117 auto destination = ByteBuffer::create_uninitialized(1024);
118 while (true) {
119 unsigned long destination_len = destination.size();
120 // FIXME: dbg() cannot take ulong?
121 // dbg() << "Gzip::decompress: Calling puff()\n"
122 // << " destination_data = " << destination.data() << "\n"
123 // << " destination_len = " << (int)destination_len << "\n"
124 // << " source_data = " << source.data() << "\n"
125 // << " source_len = " << (int)source_len;
126
127 auto puff_ret = puff(
128 destination.data(), &destination_len,
129 source.data(), &source_len);
130
131 if (puff_ret == 0) {
132 dbg() << "Gzip::decompress: Decompression success.";
133 break;
134 }
135
136 if (puff_ret == 1) {
137 // FIXME: Find a better way of decompressing without needing to try over and over again.
138 dbg() << "Gzip::decompress: Output buffer exhausted. Growing.";
139 destination.grow(destination.size() * 2);
140 } else {
141 dbg() << "Gzip::decompress: Error. puff() returned: " << puff_ret;
142 ASSERT_NOT_REACHED();
143 }
144 }
145
146 return destination;
147}