Zip.cpp 8.5 KB

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