xar unarchiver (.xar, .pkg, .xip)
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Fully implement TOC parsing

+128 -56
+2 -2
README.md
··· 15 15 - [x] SHA1 16 16 - [x] MD5 17 17 - [ ] Custom 18 - - [ ] TOC 18 + - [x] TOC 19 19 - [x] Extraction 20 - - [ ] Parsing 20 + - [x] Parsing 21 21 - [ ] Heap 22 22 - [ ] Extraction 23 23 - [ ] gzip
+126 -54
czar.cr
··· 9 9 exit 1 10 10 end 11 11 12 - CHECKSUM_ALGO = { 13 - 0 => :NONE, 14 - 1 => :SHA1, 15 - 2 => :MD5, 16 - } 12 + def xml_select(xml : XML::Node, node : String) 13 + perror "error in xml_select" if xml.nil? 14 + xml.children.select { |e| e.name == node } 15 + end 16 + 17 + def xml_value(xml : XML::Node, name : String) 18 + perror "error in xml_value" if xml.nil? 19 + xml.children.select { |e| e.name == name }.map { |e| e.content } 20 + end 21 + 22 + enum XARChecksumAlgo 23 + NONE 24 + SHA1 25 + MD5 26 + end 27 + 28 + enum XARFileType 29 + FILE 30 + DIRECTORY 31 + end 17 32 18 - class XARHeader < BinaryParser 19 - endian :big 20 - string :magic, {count: 4} 21 - uint16 :header_size 22 - uint16 :version 23 - uint64 :length_compressed 24 - uint64 :length_uncompressed 25 - uint32 :checksum_algo 33 + enum XARFileEncoding 34 + NONE 35 + GZIP 36 + BZIP2 26 37 end 27 38 28 39 class SliceIO < IO ··· 46 57 end 47 58 end 48 59 49 - def xml_select(xml : XML::Node, node : String) 50 - perror "error in xml_select" if xml.nil? 51 - xml.children.select { |e| e.name == node } 60 + class XARHeader < BinaryParser 61 + endian :big 62 + string :magic, {count: 4} 63 + uint16 :header_size 64 + uint16 :version 65 + uint64 :length_compressed 66 + uint64 :length_uncompressed 67 + uint32 :checksum_algo 52 68 end 53 69 54 - def xml_value(xml : XML::Node, name : String) 55 - perror "error in xml_value" if xml.nil? 56 - xml.children.select { |e| e.name == name }.map { |e| e.content } 70 + class XARFileData 71 + property offset : UInt64 = 0 72 + property size : UInt64 = 0 73 + property length : UInt64 = 0 74 + property checksum_extracted : String = "" 75 + property checksum_extracted_style : XARChecksumAlgo = XARChecksumAlgo::NONE 76 + property checksum_archived : String = "" 77 + property checksum_archived_style : XARChecksumAlgo = XARChecksumAlgo::NONE 78 + property encoding : XARFileEncoding = XARFileEncoding::NONE 79 + end 80 + 81 + class XARFileEAttrs < XARFileData 82 + property name : String = "" 83 + end 84 + 85 + class XARChecksum 86 + property style : XARChecksumAlgo = XARChecksumAlgo::NONE 87 + property size : UInt64 = 0 88 + property offset : UInt64 = 0 89 + end 90 + 91 + class XARFile 92 + property path : String = "" 93 + property name : String = "" 94 + property type : XARFileType = XARFileType::FILE 95 + property mode : Array(UInt8) = [0_u8, 0_u8, 0_u8, 0_u8] 96 + property uid : UInt64 = 0 97 + property gid : UInt64 = 0 98 + property user : String = "" 99 + property group : String = "" 100 + property size : UInt64 = 0 101 + property data : XARFileData = XARFileData.new 102 + property ea : XARFileEAttrs = XARFileEAttrs.new 57 103 end 58 104 59 - def xar_decode(entity : XML::Node, path : String = "./") 60 - entity_name = xml_value(entity, "name") 61 - if entity_name.size < 1 62 - puts "entity without name: #{entity.to_s}" 63 - return 105 + class XAR 106 + property checksum : XARChecksum = XARChecksum.new 107 + property files : Array(XARFile) = [] of XARFile 108 + end 109 + 110 + def xar_decode_data(entity : XML::Node, data : XARFileData = XARFileData.new) 111 + data.offset = (xml_value(entity, "offset").first rescue 0).to_u64 112 + data.size = (xml_value(entity, "size").first rescue 0).to_u64 113 + data.length = (xml_value(entity, "length").first rescue 0).to_u64 114 + data.checksum_extracted = xml_value(entity, "extracted-checksum").first rescue "" 115 + data.checksum_extracted_style = XARChecksumAlgo.parse(xml_select(entity, "extracted-checksum").first["style"]) rescue XARChecksumAlgo::NONE 116 + data.checksum_archived = xml_value(entity, "archived-checksum").first rescue "" 117 + data.checksum_archived_style = XARChecksumAlgo.parse(xml_select(entity, "archived-checksum").first["style"]) rescue XARChecksumAlgo::NONE 118 + data.encoding = XARFileEncoding.parse(xml_select(entity, "encoding").first["style"].split("/x-").last) rescue XARFileEncoding::NONE 119 + data 120 + end 121 + 122 + def xar_decode_ea(entity : XML::Node, ea : XARFileEAttrs = XARFileEAttrs.new) 123 + xar_decode_data entity, ea 124 + ea.name = xml_value(entity, "name").first rescue "" 125 + ea 126 + end 127 + 128 + def xar_decode_file(entity : XML::Node, path : String = "./") 129 + file = XARFile.new 130 + file.path = path 131 + file.name = xml_value(entity, "name").first rescue "" 132 + file.type = XARFileType.parse(xml_value(entity, "type").first) rescue XARFileType::FILE 133 + file.mode = (xml_value(entity, "mode").first rescue "0000").split("").map { |p| p.to_u8 } 134 + file.uid = (xml_value(entity, "uid").first rescue 0).to_u64 135 + file.gid = (xml_value(entity, "gid").first rescue 0).to_u64 136 + file.user = xml_value(entity, "user").first rescue "" 137 + file.group = xml_value(entity, "group").first rescue "" 138 + file.size = (xml_value(entity, "size").first rescue 0).to_u64 139 + 140 + data = xml_select(entity, "data") 141 + unless data.size < 1 142 + xar_decode_data data.first, file.data 64 143 end 65 - entity_name = entity_name.first 66 144 67 - entity_type = xml_value(entity, "type") 68 - if entity_type.size < 1 69 - puts "entity without type: #{entity.to_s}" 70 - return 145 + ea = xml_select(entity, "ea") 146 + unless ea.size < 1 147 + xar_decode_ea ea.first, file.ea 71 148 end 72 - entity_type = entity_type.first 73 149 74 - puts "#{path}#{entity_name} (#{entity_type})" 150 + files = [file] 75 151 children = xml_select(entity, "file") 76 152 if children.size > 0 77 - if entity_type != "directory" 78 - puts "warn: found a #{entity_type} with #{children.size} children" 153 + if file.type != XARFileType::DIRECTORY 154 + puts "warn: found a #{file.type} with #{children.size} children" 79 155 end 80 - 81 156 children.each do |child| 82 - xar_decode child, "#{path}#{entity_name}/" 157 + files += xar_decode_file child, "#{path}#{file.name}/" 83 158 end 84 159 end 160 + files 85 161 end 86 162 87 163 File.open "test.xar", "r" do |file| ··· 95 171 puts "format version #{header.version}" 96 172 puts "TOC length (compressed) #{header.length_compressed}" 97 173 puts "TOC length (uncompressed) #{header.length_uncompressed}" 98 - puts "checksum algo #{CHECKSUM_ALGO[header.checksum_algo]}" 174 + puts "checksum algo #{XARChecksumAlgo.new(header.checksum_algo.to_i32).to_s}" 99 175 100 176 toc_data = Bytes.new header.length_uncompressed 101 177 file.seek header.header_size ··· 103 179 zfile.read toc_data 104 180 end 105 181 106 - xar = XML.parse SliceIO.new toc_data 182 + xar_xml = XML.parse SliceIO.new toc_data 183 + toc = xml_select(xml_select(xar_xml, "xar").first, "toc").first 107 184 108 - toc = xml_select(xar, "xar").first 109 - toc = xml_select(toc, "toc").first 185 + puts "reading TOC" 186 + xar = XAR.new 110 187 111 - data_offset = header.header_size + header.length_compressed 112 - data_checksum = xml_select(toc, "checksum").first 113 - data_checksum_offset = xml_value(data_checksum, "offset").first.to_i 114 - data_checksum_size = xml_value(data_checksum, "size").first.to_i 115 - data_checksum_type = data_checksum["style"] 116 - 117 - puts "TOC is checksummed as #{data_checksum_type}, #{data_checksum_size} bytes stored at offset #{data_checksum_offset}" 118 - 119 - file.seek data_offset 120 - 121 - file.seek data_checksum_offset, IO::Seek::Current 188 + elem = xml_select(toc, "checksum").first 189 + xar.checksum.style = XARChecksumAlgo.parse elem["style"] 190 + xar.checksum.size = xml_value(elem, "size").first.to_u64 191 + xar.checksum.offset = xml_value(elem, "offset").first.to_u64 122 192 123 - data_files = xml_select(toc, "file") 124 - puts "found #{data_files.size} files at the root" 193 + puts "TOC is checksummed as #{xar.checksum.style}, #{xar.checksum.size} bytes at offset #{xar.checksum.offset}" 125 194 126 - data_files.each do |entity| 127 - xar_decode entity 195 + xml_select(toc, "file").each do |entity| 196 + xar.files += xar_decode_file entity 128 197 end 198 + 199 + puts "#{xar.files.select { |e| e.type == XARFileType::FILE }.size} files" 200 + puts "#{xar.files.select { |e| e.type == XARFileType::DIRECTORY }.size} directories" 129 201 end