| 
									
										
										
										
											2020-08-28 17:53:57 +02:00
										 |  |  | /*
 | 
					
						
							| 
									
										
										
										
											2022-02-26 09:06:40 -07:00
										 |  |  |  * Copyright (c) 2020-2022, the SerenityOS developers. | 
					
						
							| 
									
										
										
										
											2021-04-22 23:40:43 +03:00
										 |  |  |  * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org> | 
					
						
							| 
									
										
										
										
											2020-08-28 17:53:57 +02:00
										 |  |  |  * | 
					
						
							| 
									
										
										
										
											2021-04-22 01:24:48 -07:00
										 |  |  |  * SPDX-License-Identifier: BSD-2-Clause | 
					
						
							| 
									
										
										
										
											2020-08-28 17:53:57 +02:00
										 |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <LibCompress/Gzip.h>
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-01 11:43:32 +02:00
										 |  |  | #include <AK/MemoryStream.h>
 | 
					
						
							| 
									
										
										
										
											2020-08-28 17:53:57 +02:00
										 |  |  | #include <AK/String.h>
 | 
					
						
							| 
									
										
										
										
											2021-05-07 23:40:02 +03:00
										 |  |  | #include <LibCore/DateTime.h>
 | 
					
						
							| 
									
										
										
										
											2020-08-28 17:53:57 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | namespace Compress { | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-03 23:54:07 +02:00
										 |  |  | bool GzipDecompressor::is_likely_compressed(ReadonlyBytes bytes) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     return bytes.size() >= 2 && bytes[0] == gzip_magic_1 && bytes[1] == gzip_magic_2; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-13 01:26:44 +02:00
										 |  |  | bool BlockHeader::valid_magic_number() const | 
					
						
							| 
									
										
										
										
											2020-08-28 17:53:57 +02:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2021-03-03 23:54:07 +02:00
										 |  |  |     return identification_1 == gzip_magic_1 && identification_2 == gzip_magic_2; | 
					
						
							| 
									
										
										
										
											2020-08-28 17:53:57 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-13 01:26:44 +02:00
										 |  |  | bool BlockHeader::supported_by_implementation() const | 
					
						
							| 
									
										
										
										
											2020-08-28 17:53:57 +02:00
										 |  |  | { | 
					
						
							|  |  |  |     if (compression_method != 0x08) { | 
					
						
							|  |  |  |         // RFC 1952 does not define any compression methods other than deflate.
 | 
					
						
							|  |  |  |         return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (flags > Flags::MAX) { | 
					
						
							|  |  |  |         // RFC 1952 does not define any more flags.
 | 
					
						
							|  |  |  |         return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return true; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | GzipDecompressor::GzipDecompressor(InputStream& stream) | 
					
						
							|  |  |  |     : m_input_stream(stream) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | GzipDecompressor::~GzipDecompressor() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     m_current_member.clear(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // FIXME: Again, there are surely a ton of bugs because the code doesn't check for read errors.
 | 
					
						
							|  |  |  | size_t GzipDecompressor::read(Bytes bytes) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2021-03-21 15:13:19 +02:00
										 |  |  |     size_t total_read = 0; | 
					
						
							|  |  |  |     while (total_read < bytes.size()) { | 
					
						
							|  |  |  |         if (has_any_error() || m_eof) | 
					
						
							|  |  |  |             break; | 
					
						
							| 
									
										
										
										
											2020-09-05 16:39:56 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-21 15:13:19 +02:00
										 |  |  |         auto slice = bytes.slice(total_read); | 
					
						
							| 
									
										
										
										
											2020-08-28 17:53:57 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-21 15:13:19 +02:00
										 |  |  |         if (m_current_member.has_value()) { | 
					
						
							|  |  |  |             size_t nread = current_member().m_stream.read(slice); | 
					
						
							|  |  |  |             current_member().m_checksum.update(slice.trim(nread)); | 
					
						
							|  |  |  |             current_member().m_nread += nread; | 
					
						
							| 
									
										
										
										
											2020-09-05 13:18:01 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-21 15:13:19 +02:00
										 |  |  |             if (current_member().m_stream.handle_any_error()) { | 
					
						
							| 
									
										
										
										
											2020-08-31 13:12:15 +02:00
										 |  |  |                 set_fatal_error(); | 
					
						
							| 
									
										
										
										
											2021-03-21 15:13:19 +02:00
										 |  |  |                 break; | 
					
						
							| 
									
										
										
										
											2020-08-28 17:53:57 +02:00
										 |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-21 15:13:19 +02:00
										 |  |  |             if (nread < slice.size()) { | 
					
						
							|  |  |  |                 LittleEndian<u32> crc32, input_size; | 
					
						
							|  |  |  |                 m_input_stream >> crc32 >> input_size; | 
					
						
							| 
									
										
										
										
											2020-08-28 17:53:57 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-21 15:13:19 +02:00
										 |  |  |                 if (crc32 != current_member().m_checksum.digest()) { | 
					
						
							|  |  |  |                     // FIXME: Somehow the checksum is incorrect?
 | 
					
						
							| 
									
										
										
										
											2020-08-28 17:53:57 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-21 15:13:19 +02:00
										 |  |  |                     set_fatal_error(); | 
					
						
							|  |  |  |                     break; | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2020-08-28 17:53:57 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-21 15:13:19 +02:00
										 |  |  |                 if (input_size != current_member().m_nread) { | 
					
						
							|  |  |  |                     set_fatal_error(); | 
					
						
							|  |  |  |                     break; | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2020-08-28 17:53:57 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-21 15:13:19 +02:00
										 |  |  |                 m_current_member.clear(); | 
					
						
							| 
									
										
										
										
											2020-09-13 12:24:17 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-21 15:13:19 +02:00
										 |  |  |                 total_read += nread; | 
					
						
							|  |  |  |                 continue; | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2021-03-16 17:39:50 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-21 15:13:19 +02:00
										 |  |  |             total_read += nread; | 
					
						
							|  |  |  |             continue; | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |             m_partial_header_offset += m_input_stream.read(Bytes { m_partial_header, sizeof(BlockHeader) }.slice(m_partial_header_offset)); | 
					
						
							| 
									
										
										
										
											2021-03-16 17:39:50 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-21 15:13:19 +02:00
										 |  |  |             if (m_input_stream.handle_any_error() || m_input_stream.unreliable_eof()) { | 
					
						
							|  |  |  |                 m_eof = true; | 
					
						
							|  |  |  |                 break; | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2020-08-28 17:53:57 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-21 15:13:19 +02:00
										 |  |  |             if (m_partial_header_offset < sizeof(BlockHeader)) { | 
					
						
							|  |  |  |                 break; // partial header read
 | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             m_partial_header_offset = 0; | 
					
						
							| 
									
										
										
										
											2020-08-28 17:53:57 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-21 15:13:19 +02:00
										 |  |  |             BlockHeader header = *(reinterpret_cast<BlockHeader*>(m_partial_header)); | 
					
						
							| 
									
										
										
										
											2020-08-28 17:53:57 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-21 15:13:19 +02:00
										 |  |  |             if (!header.valid_magic_number() || !header.supported_by_implementation()) { | 
					
						
							|  |  |  |                 set_fatal_error(); | 
					
						
							|  |  |  |                 break; | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2020-08-28 17:53:57 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-21 15:13:19 +02:00
										 |  |  |             if (header.flags & Flags::FEXTRA) { | 
					
						
							|  |  |  |                 LittleEndian<u16> subfield_id, length; | 
					
						
							|  |  |  |                 m_input_stream >> subfield_id >> length; | 
					
						
							|  |  |  |                 m_input_stream.discard_or_error(length); | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2021-03-03 23:54:07 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-18 00:26:16 +03:00
										 |  |  |             auto discard_string = [&]() { | 
					
						
							|  |  |  |                 char next_char; | 
					
						
							|  |  |  |                 do { | 
					
						
							|  |  |  |                     m_input_stream >> next_char; | 
					
						
							|  |  |  |                     if (m_input_stream.has_any_error()) { | 
					
						
							|  |  |  |                         set_fatal_error(); | 
					
						
							|  |  |  |                         break; | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                 } while (next_char); | 
					
						
							|  |  |  |             }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-21 15:13:19 +02:00
										 |  |  |             if (header.flags & Flags::FNAME) { | 
					
						
							| 
									
										
										
										
											2021-05-18 00:26:16 +03:00
										 |  |  |                 discard_string(); | 
					
						
							|  |  |  |                 if (has_any_error()) | 
					
						
							|  |  |  |                     break; | 
					
						
							| 
									
										
										
										
											2021-03-21 15:13:19 +02:00
										 |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if (header.flags & Flags::FCOMMENT) { | 
					
						
							| 
									
										
										
										
											2021-05-18 00:26:16 +03:00
										 |  |  |                 discard_string(); | 
					
						
							|  |  |  |                 if (has_any_error()) | 
					
						
							|  |  |  |                     break; | 
					
						
							| 
									
										
										
										
											2021-03-21 15:13:19 +02:00
										 |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if (header.flags & Flags::FHCRC) { | 
					
						
							|  |  |  |                 LittleEndian<u16> crc16; | 
					
						
							|  |  |  |                 m_input_stream >> crc16; | 
					
						
							|  |  |  |                 // FIXME: we should probably verify this instead of just assuming it matches
 | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             m_current_member.emplace(header, m_input_stream); | 
					
						
							|  |  |  |             continue; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2020-08-28 17:53:57 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-03-21 15:13:19 +02:00
										 |  |  |     return total_read; | 
					
						
							| 
									
										
										
										
											2020-08-28 17:53:57 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-07 23:40:02 +03:00
										 |  |  | Optional<String> GzipDecompressor::describe_header(ReadonlyBytes bytes) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (bytes.size() < sizeof(BlockHeader)) | 
					
						
							|  |  |  |         return {}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     auto& header = *(reinterpret_cast<const BlockHeader*>(bytes.data())); | 
					
						
							|  |  |  |     if (!header.valid_magic_number() || !header.supported_by_implementation()) | 
					
						
							|  |  |  |         return {}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     LittleEndian<u32> original_size = *reinterpret_cast<const u32*>(bytes.offset(bytes.size() - sizeof(u32))); | 
					
						
							|  |  |  |     return String::formatted("last modified: {}, original size {}", Core::DateTime::from_timestamp(header.modification_time).to_string(), (u32)original_size); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-28 17:53:57 +02:00
										 |  |  | bool GzipDecompressor::read_or_error(Bytes bytes) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (read(bytes) < bytes.size()) { | 
					
						
							| 
									
										
										
										
											2020-08-31 13:12:15 +02:00
										 |  |  |         set_fatal_error(); | 
					
						
							| 
									
										
										
										
											2020-08-28 17:53:57 +02:00
										 |  |  |         return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return true; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | bool GzipDecompressor::discard_or_error(size_t count) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     u8 buffer[4096]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     size_t ndiscarded = 0; | 
					
						
							|  |  |  |     while (ndiscarded < count) { | 
					
						
							| 
									
										
										
										
											2020-09-13 12:24:17 +02:00
										 |  |  |         if (unreliable_eof()) { | 
					
						
							| 
									
										
										
										
											2020-08-31 13:12:15 +02:00
										 |  |  |             set_fatal_error(); | 
					
						
							| 
									
										
										
										
											2020-08-28 17:53:57 +02:00
										 |  |  |             return false; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         ndiscarded += read({ buffer, min<size_t>(count - ndiscarded, sizeof(buffer)) }); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return true; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-08 16:49:05 +02:00
										 |  |  | Optional<ByteBuffer> GzipDecompressor::decompress_all(ReadonlyBytes bytes) | 
					
						
							| 
									
										
										
										
											2020-08-28 17:53:57 +02:00
										 |  |  | { | 
					
						
							|  |  |  |     InputMemoryStream memory_stream { bytes }; | 
					
						
							|  |  |  |     GzipDecompressor gzip_stream { memory_stream }; | 
					
						
							| 
									
										
										
										
											2020-09-15 11:48:54 +02:00
										 |  |  |     DuplexMemoryStream output_stream; | 
					
						
							| 
									
										
										
										
											2020-08-28 17:53:57 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-07 19:56:06 +02:00
										 |  |  |     u8 buffer[4096]; | 
					
						
							| 
									
										
										
										
											2020-09-13 12:24:17 +02:00
										 |  |  |     while (!gzip_stream.has_any_error() && !gzip_stream.unreliable_eof()) { | 
					
						
							| 
									
										
										
										
											2020-09-07 19:56:06 +02:00
										 |  |  |         const auto nread = gzip_stream.read({ buffer, sizeof(buffer) }); | 
					
						
							|  |  |  |         output_stream.write_or_error({ buffer, nread }); | 
					
						
							| 
									
										
										
										
											2020-08-28 17:53:57 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-07 19:56:06 +02:00
										 |  |  |     if (gzip_stream.handle_any_error()) | 
					
						
							|  |  |  |         return {}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return output_stream.copy_into_contiguous_buffer(); | 
					
						
							| 
									
										
										
										
											2020-08-28 17:53:57 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-13 12:24:17 +02:00
										 |  |  | bool GzipDecompressor::unreliable_eof() const { return m_eof; } | 
					
						
							| 
									
										
										
										
											2020-08-28 17:53:57 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-16 15:20:46 +02:00
										 |  |  | bool GzipDecompressor::handle_any_error() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     bool handled_errors = m_input_stream.handle_any_error(); | 
					
						
							|  |  |  |     return Stream::handle_any_error() || handled_errors; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-13 01:26:44 +02:00
										 |  |  | GzipCompressor::GzipCompressor(OutputStream& stream) | 
					
						
							|  |  |  |     : m_output_stream(stream) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | size_t GzipCompressor::write(ReadonlyBytes bytes) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     BlockHeader header; | 
					
						
							|  |  |  |     header.identification_1 = 0x1f; | 
					
						
							|  |  |  |     header.identification_2 = 0x8b; | 
					
						
							|  |  |  |     header.compression_method = 0x08; | 
					
						
							|  |  |  |     header.flags = 0; | 
					
						
							|  |  |  |     header.modification_time = 0; | 
					
						
							|  |  |  |     header.extra_flags = 3;      // DEFLATE sets 2 for maximum compression and 4 for minimum compression
 | 
					
						
							|  |  |  |     header.operating_system = 3; // unix
 | 
					
						
							|  |  |  |     m_output_stream << Bytes { &header, sizeof(header) }; | 
					
						
							|  |  |  |     DeflateCompressor compressed_stream { m_output_stream }; | 
					
						
							|  |  |  |     VERIFY(compressed_stream.write_or_error(bytes)); | 
					
						
							|  |  |  |     compressed_stream.final_flush(); | 
					
						
							|  |  |  |     Crypto::Checksum::CRC32 crc32; | 
					
						
							|  |  |  |     crc32.update(bytes); | 
					
						
							|  |  |  |     LittleEndian<u32> digest = crc32.digest(); | 
					
						
							|  |  |  |     LittleEndian<u32> size = bytes.size(); | 
					
						
							|  |  |  |     m_output_stream << digest << size; | 
					
						
							|  |  |  |     return bytes.size(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | bool GzipCompressor::write_or_error(ReadonlyBytes bytes) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (write(bytes) < bytes.size()) { | 
					
						
							|  |  |  |         set_fatal_error(); | 
					
						
							|  |  |  |         return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return true; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-11 01:06:34 +01:00
										 |  |  | Optional<ByteBuffer> GzipCompressor::compress_all(ReadonlyBytes bytes) | 
					
						
							| 
									
										
										
										
											2021-03-13 01:26:44 +02:00
										 |  |  | { | 
					
						
							|  |  |  |     DuplexMemoryStream output_stream; | 
					
						
							|  |  |  |     GzipCompressor gzip_stream { output_stream }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     gzip_stream.write_or_error(bytes); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (gzip_stream.handle_any_error()) | 
					
						
							|  |  |  |         return {}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return output_stream.copy_into_contiguous_buffer(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-28 17:53:57 +02:00
										 |  |  | } |