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

Configure Feed

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

at e8d67e630419cfcff8ecc3f2e8d9dc968a2f884d 188 lines 5.8 kB view raw
1#!/usr/bin/env crystal 2 3require "binary_parser" 4require "compress/zlib" 5require "xml" 6 7def perror(msg : String) 8 STDERR.write Slice.new (msg + "\n").to_unsafe, (msg + "\n").size 9 exit 1 10end 11 12def xml_select(xml : XML::Node, node : String) 13 perror "error in xml_select" if xml.nil? 14 xml.children.select { |e| e.name == node } 15end 16 17def 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 } 20end 21 22enum XARChecksumAlgo 23 NONE 24 SHA1 25 MD5 26end 27 28enum XARFileType 29 FILE 30 DIRECTORY 31end 32 33enum XARFileEncoding 34 NONE 35 GZIP 36 BZIP2 37end 38 39class XARHeader < BinaryParser 40 endian :big 41 string :magic, {count: 4} 42 uint16 :header_size 43 uint16 :version 44 uint64 :length_compressed 45 uint64 :length_uncompressed 46 uint32 :checksum_algo 47end 48 49class XARFileData 50 property offset : UInt64 = 0 51 property size : UInt64 = 0 52 property length : UInt64 = 0 53 property checksum_extracted : String = "" 54 property checksum_extracted_style : XARChecksumAlgo = XARChecksumAlgo::NONE 55 property checksum_archived : String = "" 56 property checksum_archived_style : XARChecksumAlgo = XARChecksumAlgo::NONE 57 property encoding : XARFileEncoding = XARFileEncoding::NONE 58end 59 60class XARFileEAttrs < XARFileData 61 property name : String = "" 62end 63 64class XARChecksum 65 property style : XARChecksumAlgo = XARChecksumAlgo::NONE 66 property size : UInt64 = 0 67 property offset : UInt64 = 0 68end 69 70class XARFile 71 property path : String = "" 72 property name : String = "" 73 property type : XARFileType = XARFileType::FILE 74 property mode : Array(UInt8) = [0_u8, 0_u8, 0_u8, 0_u8] 75 property uid : UInt64 = 0 76 property gid : UInt64 = 0 77 property user : String = "" 78 property group : String = "" 79 property size : UInt64 = 0 80 property data : XARFileData = XARFileData.new 81 property ea : XARFileEAttrs = XARFileEAttrs.new 82end 83 84class XAR 85 property checksum : XARChecksum = XARChecksum.new 86 property files : Array(XARFile) = [] of XARFile 87end 88 89def xar_decode_data(entity : XML::Node, data : XARFileData = XARFileData.new) 90 data.offset = (xml_value(entity, "offset").first rescue 0).to_u64 91 data.size = (xml_value(entity, "size").first rescue 0).to_u64 92 data.length = (xml_value(entity, "length").first rescue 0).to_u64 93 data.checksum_extracted = xml_value(entity, "extracted-checksum").first rescue "" 94 data.checksum_extracted_style = XARChecksumAlgo.parse(xml_select(entity, "extracted-checksum").first["style"]) rescue XARChecksumAlgo::NONE 95 data.checksum_archived = xml_value(entity, "archived-checksum").first rescue "" 96 data.checksum_archived_style = XARChecksumAlgo.parse(xml_select(entity, "archived-checksum").first["style"]) rescue XARChecksumAlgo::NONE 97 data.encoding = XARFileEncoding.parse(xml_select(entity, "encoding").first["style"].split("/x-").last) rescue XARFileEncoding::NONE 98 data 99end 100 101def xar_decode_ea(entity : XML::Node, ea : XARFileEAttrs = XARFileEAttrs.new) 102 xar_decode_data entity, ea 103 ea.name = xml_value(entity, "name").first rescue "" 104 ea 105end 106 107def xar_decode_file(entity : XML::Node, path : String = "./") 108 file = XARFile.new 109 file.path = path 110 file.name = xml_value(entity, "name").first rescue "" 111 file.type = XARFileType.parse(xml_value(entity, "type").first) rescue XARFileType::FILE 112 file.mode = (xml_value(entity, "mode").first rescue "0000").split("").map { |p| p.to_u8 } 113 file.uid = (xml_value(entity, "uid").first rescue 0).to_u64 114 file.gid = (xml_value(entity, "gid").first rescue 0).to_u64 115 file.user = xml_value(entity, "user").first rescue "" 116 file.group = xml_value(entity, "group").first rescue "" 117 file.size = (xml_value(entity, "size").first rescue 0).to_u64 118 119 data = xml_select(entity, "data") 120 unless data.size < 1 121 xar_decode_data data.first, file.data 122 end 123 124 ea = xml_select(entity, "ea") 125 unless ea.size < 1 126 xar_decode_ea ea.first, file.ea 127 end 128 129 files = [file] 130 children = xml_select(entity, "file") 131 if children.size > 0 132 if file.type != XARFileType::DIRECTORY 133 puts "warn: found a #{file.type} with #{children.size} children" 134 end 135 children.each do |child| 136 files += xar_decode_file child, "#{path}#{file.name}/" 137 end 138 end 139 files 140end 141 142perror "no filename given" if ARGV.size == 0 143 144File.open ARGV.first, "r" do |file| 145 header = XARHeader.new 146 header.load file 147 148 perror "not a xar file" if header.magic != "xar!" 149 150 puts "#{header.magic}" 151 puts "header size #{header.header_size}" 152 puts "format version #{header.version}" 153 puts "TOC length (compressed) #{header.length_compressed}" 154 puts "TOC length (uncompressed) #{header.length_uncompressed}" 155 puts "checksum algo #{XARChecksumAlgo.new(header.checksum_algo.to_i32).to_s}" 156 157 toc_data = Bytes.new header.length_uncompressed 158 file.seek header.header_size 159 Compress::Zlib::Reader.open file do |zfile| 160 zfile.read toc_data 161 end 162 163 xar_xml = XML.parse String.new(toc_data) 164 xar_obj = xml_select(xar_xml, "xar") 165 perror "empty xar object" if xar_obj.empty? 166 167 tocs = xml_select(xar_obj.first, "toc") 168 perror "empty TOC" if tocs.empty? 169 toc = tocs.first 170 171 puts "reading TOC" 172 xar = XAR.new 173 174 elem = xml_select(toc, "checksum").first 175 xar.checksum.style = XARChecksumAlgo.parse elem["style"] 176 xar.checksum.size = xml_value(elem, "size").first.to_u64 177 xar.checksum.offset = xml_value(elem, "offset").first.to_u64 178 179 puts "TOC is checksummed as #{xar.checksum.style}, #{xar.checksum.size} bytes at offset #{xar.checksum.offset}" 180 181 xml_select(toc, "file").each do |entity| 182 xar.files += xar_decode_file entity 183 end 184 185 puts "contains #{xar.files.select { |e| e.type == XARFileType::FILE }.size} files across #{xar.files.select { |e| e.type == XARFileType::DIRECTORY }.size} directories" 186 187 puts xar.files.map{ |e| "#{e.path}#{e.name}" }.join " " 188end