Zip.cpp 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. /*
  2. * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
  3. * Copyright (c) 2022, the SerenityOS developers.
  4. *
  5. * SPDX-License-Identifier: BSD-2-Clause
  6. */
  7. #include <LibArchive/Zip.h>
  8. namespace Archive {
  9. bool Zip::find_end_of_central_directory_offset(ReadonlyBytes buffer, size_t& offset)
  10. {
  11. for (size_t backwards_offset = 0; backwards_offset <= UINT16_MAX; backwards_offset++) // the file may have a trailing comment of an arbitrary 16 bit length
  12. {
  13. if (buffer.size() < (sizeof(EndOfCentralDirectory) - sizeof(u8*)) + backwards_offset)
  14. return false;
  15. auto const signature_offset = (buffer.size() - (sizeof(EndOfCentralDirectory) - sizeof(u8*)) - backwards_offset);
  16. if (auto signature = ReadonlyBytes { buffer.data() + signature_offset, EndOfCentralDirectory::signature.size() };
  17. signature == EndOfCentralDirectory::signature) {
  18. offset = signature_offset;
  19. return true;
  20. }
  21. }
  22. return false;
  23. }
  24. Optional<Zip> Zip::try_create(ReadonlyBytes buffer)
  25. {
  26. size_t end_of_central_directory_offset;
  27. if (!find_end_of_central_directory_offset(buffer, end_of_central_directory_offset))
  28. return {};
  29. EndOfCentralDirectory end_of_central_directory {};
  30. if (!end_of_central_directory.read(buffer.slice(end_of_central_directory_offset)))
  31. return {};
  32. if (end_of_central_directory.disk_number != 0 || end_of_central_directory.central_directory_start_disk != 0 || end_of_central_directory.disk_records_count != end_of_central_directory.total_records_count)
  33. return {}; // TODO: support multi-volume zip archives
  34. size_t member_offset = end_of_central_directory.central_directory_offset;
  35. for (size_t i = 0; i < end_of_central_directory.total_records_count; i++) {
  36. CentralDirectoryRecord central_directory_record {};
  37. if (member_offset > buffer.size())
  38. return {};
  39. if (!central_directory_record.read(buffer.slice(member_offset)))
  40. return {};
  41. if (central_directory_record.general_purpose_flags.encrypted)
  42. return {}; // TODO: support encrypted zip members
  43. if (central_directory_record.general_purpose_flags.data_descriptor)
  44. return {}; // TODO: support zip data descriptors
  45. if (central_directory_record.compression_method != ZipCompressionMethod::Store && central_directory_record.compression_method != ZipCompressionMethod::Deflate)
  46. return {}; // TODO: support obsolete zip compression methods
  47. if (central_directory_record.compression_method == ZipCompressionMethod::Store && central_directory_record.uncompressed_size != central_directory_record.compressed_size)
  48. return {};
  49. if (central_directory_record.start_disk != 0)
  50. return {}; // TODO: support multi-volume zip archives
  51. if (memchr(central_directory_record.name, 0, central_directory_record.name_length) != nullptr)
  52. return {};
  53. LocalFileHeader local_file_header {};
  54. if (central_directory_record.local_file_header_offset > buffer.size())
  55. return {};
  56. if (!local_file_header.read(buffer.slice(central_directory_record.local_file_header_offset)))
  57. return {};
  58. if (buffer.size() - (local_file_header.compressed_data - buffer.data()) < central_directory_record.compressed_size)
  59. return {};
  60. member_offset += central_directory_record.size();
  61. }
  62. return Zip {
  63. end_of_central_directory.total_records_count,
  64. end_of_central_directory.central_directory_offset,
  65. buffer,
  66. };
  67. }
  68. ErrorOr<bool> Zip::for_each_member(Function<IterationDecision(ZipMember const&)> callback)
  69. {
  70. size_t member_offset = m_members_start_offset;
  71. for (size_t i = 0; i < m_member_count; i++) {
  72. CentralDirectoryRecord central_directory_record {};
  73. VERIFY(central_directory_record.read(m_input_data.slice(member_offset)));
  74. LocalFileHeader local_file_header {};
  75. VERIFY(local_file_header.read(m_input_data.slice(central_directory_record.local_file_header_offset)));
  76. ZipMember member;
  77. member.name = TRY(String::from_utf8({ central_directory_record.name, central_directory_record.name_length }));
  78. member.compressed_data = { local_file_header.compressed_data, central_directory_record.compressed_size };
  79. member.compression_method = central_directory_record.compression_method;
  80. member.uncompressed_size = central_directory_record.uncompressed_size;
  81. member.crc32 = central_directory_record.crc32;
  82. member.modification_time = central_directory_record.modification_time;
  83. member.modification_date = central_directory_record.modification_date;
  84. member.is_directory = central_directory_record.external_attributes & zip_directory_external_attribute || member.name.bytes_as_string_view().ends_with('/'); // FIXME: better directory detection
  85. if (callback(member) == IterationDecision::Break)
  86. return false;
  87. member_offset += central_directory_record.size();
  88. }
  89. return true;
  90. }
  91. ZipOutputStream::ZipOutputStream(NonnullOwnPtr<Stream> stream)
  92. : m_stream(move(stream))
  93. {
  94. }
  95. static u16 minimum_version_needed(ZipCompressionMethod method)
  96. {
  97. // Deflate was added in PKZip 2.0
  98. return method == ZipCompressionMethod::Deflate ? 20 : 10;
  99. }
  100. ErrorOr<void> ZipOutputStream::add_member(ZipMember const& member)
  101. {
  102. VERIFY(!m_finished);
  103. VERIFY(member.name.bytes_as_string_view().length() <= UINT16_MAX);
  104. VERIFY(member.compressed_data.size() <= UINT32_MAX);
  105. TRY(m_members.try_append(member));
  106. LocalFileHeader local_file_header {
  107. .minimum_version = minimum_version_needed(member.compression_method),
  108. .general_purpose_flags = { .flags = 0 },
  109. .compression_method = static_cast<u16>(member.compression_method),
  110. .modification_time = member.modification_time,
  111. .modification_date = member.modification_date,
  112. .crc32 = member.crc32,
  113. .compressed_size = static_cast<u32>(member.compressed_data.size()),
  114. .uncompressed_size = member.uncompressed_size,
  115. .name_length = static_cast<u16>(member.name.bytes_as_string_view().length()),
  116. .extra_data_length = 0,
  117. .name = reinterpret_cast<u8 const*>(member.name.bytes_as_string_view().characters_without_null_termination()),
  118. .extra_data = nullptr,
  119. .compressed_data = member.compressed_data.data(),
  120. };
  121. return local_file_header.write(*m_stream);
  122. }
  123. ErrorOr<void> ZipOutputStream::finish()
  124. {
  125. VERIFY(!m_finished);
  126. m_finished = true;
  127. auto file_header_offset = 0u;
  128. auto central_directory_size = 0u;
  129. for (ZipMember const& member : m_members) {
  130. auto zip_version = minimum_version_needed(member.compression_method);
  131. CentralDirectoryRecord central_directory_record {
  132. .made_by_version = zip_version,
  133. .minimum_version = zip_version,
  134. .general_purpose_flags = { .flags = 0 },
  135. .compression_method = member.compression_method,
  136. .modification_time = member.modification_time,
  137. .modification_date = member.modification_date,
  138. .crc32 = member.crc32,
  139. .compressed_size = static_cast<u32>(member.compressed_data.size()),
  140. .uncompressed_size = member.uncompressed_size,
  141. .name_length = static_cast<u16>(member.name.bytes_as_string_view().length()),
  142. .extra_data_length = 0,
  143. .comment_length = 0,
  144. .start_disk = 0,
  145. .internal_attributes = 0,
  146. .external_attributes = member.is_directory ? zip_directory_external_attribute : 0,
  147. .local_file_header_offset = file_header_offset, // FIXME: we assume the wrapped output stream was never written to before us
  148. .name = reinterpret_cast<u8 const*>(member.name.bytes_as_string_view().characters_without_null_termination()),
  149. .extra_data = nullptr,
  150. .comment = nullptr,
  151. };
  152. file_header_offset += sizeof(LocalFileHeader::signature) + (sizeof(LocalFileHeader) - (sizeof(u8*) * 3)) + member.name.bytes_as_string_view().length() + member.compressed_data.size();
  153. TRY(central_directory_record.write(*m_stream));
  154. central_directory_size += central_directory_record.size();
  155. }
  156. EndOfCentralDirectory end_of_central_directory {
  157. .disk_number = 0,
  158. .central_directory_start_disk = 0,
  159. .disk_records_count = static_cast<u16>(m_members.size()),
  160. .total_records_count = static_cast<u16>(m_members.size()),
  161. .central_directory_size = central_directory_size,
  162. .central_directory_offset = file_header_offset,
  163. .comment_length = 0,
  164. .comment = nullptr,
  165. };
  166. return end_of_central_directory.write(*m_stream);
  167. }
  168. }