Serenity Operating System
at portability 147 lines 5.1 kB view raw
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}