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
34//#define DEBUG_GZIP
35
36namespace Core {
37
38bool Gzip::is_compressed(const ByteBuffer& data)
39{
40 return data.size() > 2 && data[0] == 0x1F && data[1] == 0x8b;
41}
42
43// skips the gzip header
44// see: https://tools.ietf.org/html/rfc1952#page-5
45static Optional<ByteBuffer> get_gzip_payload(const ByteBuffer& data)
46{
47 size_t current = 0;
48 auto read_byte = [&]() {
49 if (current >= data.size()) {
50 ASSERT_NOT_REACHED();
51 return (u8)0;
52 }
53 // dbg() << "read_byte: " << String::format("%x", data[current]);
54 return data[current++];
55 };
56
57 dbg() << "get_gzip_payload: Skipping over gzip header.";
58
59 // Magic Header
60 if (read_byte() != 0x1F || read_byte() != 0x8B) {
61 dbg() << "get_gzip_payload: Wrong magic number.";
62 return Optional<ByteBuffer>();
63 }
64
65 // Compression method
66 auto method = read_byte();
67 if (method != 8) {
68 dbg() << "get_gzip_payload: Wrong compression method = " << method;
69 return Optional<ByteBuffer>();
70 }
71
72 u8 flags = read_byte();
73
74 // Timestamp, Extra flags, OS
75 current += 6;
76
77 // FEXTRA
78 if (flags & 4) {
79 u16 length = read_byte() & read_byte() << 8;
80 dbg() << "get_gzip_payload: Header has FEXTRA flag set. Length = " << length;
81 current += length;
82 }
83
84 // FNAME
85 if (flags & 8) {
86 dbg() << "get_gzip_payload: Header has FNAME flag set.";
87 while (read_byte() != '\0')
88 ;
89 }
90
91 // FCOMMENT
92 if (flags & 16) {
93 dbg() << "get_gzip_payload: Header has FCOMMENT flag set.";
94 while (read_byte() != '\0')
95 ;
96 }
97
98 // FHCRC
99 if (flags & 2) {
100 dbg() << "get_gzip_payload: Header has FHCRC flag set.";
101 current += 2;
102 }
103
104 auto new_size = data.size() - current;
105 dbg() << "get_gzip_payload: Returning slice from " << current << " with size " << new_size;
106 return data.slice(current, new_size);
107}
108
109Optional<ByteBuffer> Gzip::decompress(const ByteBuffer& data)
110{
111 ASSERT(is_compressed(data));
112
113 dbg() << "Gzip::decompress: Decompressing gzip compressed data. Size = " << data.size();
114 auto optional_payload = get_gzip_payload(data);
115 if (!optional_payload.has_value()) {
116 return Optional<ByteBuffer>();
117 }
118
119 auto source = optional_payload.value();
120 unsigned long source_len = source.size();
121 auto destination = ByteBuffer::create_uninitialized(1024);
122 while (true) {
123 unsigned long destination_len = destination.size();
124
125#ifdef DEBUG_GZIP
126 dbg() << "Gzip::decompress: Calling puff()\n"
127 << " destination_data = " << destination.data() << "\n"
128 << " destination_len = " << destination_len << "\n"
129 << " source_data = " << source.data() << "\n"
130 << " source_len = " << source_len;
131#endif
132
133 auto puff_ret = puff(
134 destination.data(), &destination_len,
135 source.data(), &source_len);
136
137 if (puff_ret == 0) {
138 dbg() << "Gzip::decompress: Decompression success.";
139 break;
140 }
141
142 if (puff_ret == 1) {
143 // FIXME: Find a better way of decompressing without needing to try over and over again.
144 dbg() << "Gzip::decompress: Output buffer exhausted. Growing.";
145 destination.grow(destination.size() * 2);
146 } else {
147 dbg() << "Gzip::decompress: Error. puff() returned: " << puff_ret;
148 ASSERT_NOT_REACHED();
149 }
150 }
151
152 return destination;
153}
154
155}