1# -*- coding: utf-8 -*- 2#-- 3# Copyright (C) 2004 Mauricio Julio Fernández Pradier 4# See LICENSE.txt for additional licensing information. 5#++ 6 7## 8# Allows writing of tar files 9 10class Gem::Package::TarWriter 11 12 class FileOverflow < StandardError; end 13 14 ## 15 # IO wrapper that allows writing a limited amount of data 16 17 class BoundedStream 18 19 ## 20 # Maximum number of bytes that can be written 21 22 attr_reader :limit 23 24 ## 25 # Number of bytes written 26 27 attr_reader :written 28 29 ## 30 # Wraps +io+ and allows up to +limit+ bytes to be written 31 32 def initialize(io, limit) 33 @io = io 34 @limit = limit 35 @written = 0 36 end 37 38 ## 39 # Writes +data+ onto the IO, raising a FileOverflow exception if the 40 # number of bytes will be more than #limit 41 42 def write(data) 43 if data.bytesize + @written > @limit 44 raise FileOverflow, "You tried to feed more data than fits in the file." 45 end 46 @io.write data 47 @written += data.bytesize 48 data.bytesize 49 end 50 51 end 52 53 ## 54 # IO wrapper that provides only #write 55 56 class RestrictedStream 57 58 ## 59 # Creates a new RestrictedStream wrapping +io+ 60 61 def initialize(io) 62 @io = io 63 end 64 65 ## 66 # Writes +data+ onto the IO 67 68 def write(data) 69 @io.write data 70 end 71 72 end 73 74 ## 75 # Creates a new TarWriter, yielding it if a block is given 76 77 def self.new(io) 78 writer = super 79 80 return writer unless block_given? 81 82 begin 83 yield writer 84 ensure 85 writer.close 86 end 87 88 nil 89 end 90 91 ## 92 # Creates a new TarWriter that will write to +io+ 93 94 def initialize(io) 95 @io = io 96 @closed = false 97 end 98 99 ## 100 # Adds file +name+ with permissions +mode+, and yields an IO for writing the 101 # file to 102 103 def add_file(name, mode) # :yields: io 104 check_closed 105 106 raise Gem::Package::NonSeekableIO unless @io.respond_to? :pos= 107 108 name, prefix = split_name name 109 110 init_pos = @io.pos 111 @io.write "\0" * 512 # placeholder for the header 112 113 yield RestrictedStream.new(@io) if block_given? 114 115 size = @io.pos - init_pos - 512 116 117 remainder = (512 - (size % 512)) % 512 118 @io.write "\0" * remainder 119 120 final_pos = @io.pos 121 @io.pos = init_pos 122 123 header = Gem::Package::TarHeader.new :name => name, :mode => mode, 124 :size => size, :prefix => prefix 125 126 @io.write header 127 @io.pos = final_pos 128 129 self 130 end 131 132 ## 133 # Adds +name+ with permissions +mode+ to the tar, yielding +io+ for writing 134 # the file. The +digest_algorithm+ is written to a read-only +name+.sum 135 # file following the given file contents containing the digest name and 136 # hexdigest separated by a tab. 137 # 138 # The created digest object is returned. 139 140 def add_file_digest name, mode, digest_algorithms # :yields: io 141 digests = digest_algorithms.map do |digest_algorithm| 142 digest = digest_algorithm.new 143 [digest.name, digest] 144 end 145 146 digests = Hash[*digests.flatten] 147 148 add_file name, mode do |io| 149 Gem::Package::DigestIO.wrap io, digests do |digest_io| 150 yield digest_io 151 end 152 end 153 154 digests 155 end 156 157 ## 158 # Adds +name+ with permissions +mode+ to the tar, yielding +io+ for writing 159 # the file. The +signer+ is used to add a digest file using its 160 # digest_algorithm per add_file_digest and a cryptographic signature in 161 # +name+.sig. If the signer has no key only the checksum file is added. 162 # 163 # Returns the digest. 164 165 def add_file_signed name, mode, signer 166 digest_algorithms = [ 167 signer.digest_algorithm, 168 OpenSSL::Digest::SHA512, 169 ].uniq 170 171 digests = add_file_digest name, mode, digest_algorithms do |io| 172 yield io 173 end 174 175 signature_digest = digests.values.find do |digest| 176 digest.name == signer.digest_name 177 end 178 179 signature = signer.sign signature_digest.digest 180 181 add_file_simple "#{name}.sig", 0444, signature.length do |io| 182 io.write signature 183 end if signature 184 185 digests 186 end 187 188 ## 189 # Add file +name+ with permissions +mode+ +size+ bytes long. Yields an IO 190 # to write the file to. 191 192 def add_file_simple(name, mode, size) # :yields: io 193 check_closed 194 195 name, prefix = split_name name 196 197 header = Gem::Package::TarHeader.new(:name => name, :mode => mode, 198 :size => size, :prefix => prefix).to_s 199 200 @io.write header 201 os = BoundedStream.new @io, size 202 203 yield os if block_given? 204 205 min_padding = size - os.written 206 @io.write("\0" * min_padding) 207 208 remainder = (512 - (size % 512)) % 512 209 @io.write("\0" * remainder) 210 211 self 212 end 213 214 ## 215 # Raises IOError if the TarWriter is closed 216 217 def check_closed 218 raise IOError, "closed #{self.class}" if closed? 219 end 220 221 ## 222 # Closes the TarWriter 223 224 def close 225 check_closed 226 227 @io.write "\0" * 1024 228 flush 229 230 @closed = true 231 end 232 233 ## 234 # Is the TarWriter closed? 235 236 def closed? 237 @closed 238 end 239 240 ## 241 # Flushes the TarWriter's IO 242 243 def flush 244 check_closed 245 246 @io.flush if @io.respond_to? :flush 247 end 248 249 ## 250 # Creates a new directory in the tar file +name+ with +mode+ 251 252 def mkdir(name, mode) 253 check_closed 254 255 name, prefix = split_name(name) 256 257 header = Gem::Package::TarHeader.new :name => name, :mode => mode, 258 :typeflag => "5", :size => 0, 259 :prefix => prefix 260 261 @io.write header 262 263 self 264 end 265 266 ## 267 # Splits +name+ into a name and prefix that can fit in the TarHeader 268 269 def split_name(name) # :nodoc: 270 raise Gem::Package::TooLongFileName if name.bytesize > 256 271 272 if name.bytesize <= 100 then 273 prefix = "" 274 else 275 parts = name.split(/\//) 276 newname = parts.pop 277 nxt = "" 278 279 loop do 280 nxt = parts.pop 281 break if newname.bytesize + 1 + nxt.bytesize > 100 282 newname = nxt + "/" + newname 283 end 284 285 prefix = (parts + [nxt]).join "/" 286 name = newname 287 288 if name.bytesize > 100 or prefix.bytesize > 155 then 289 raise Gem::Package::TooLongFileName 290 end 291 end 292 293 return name, prefix 294 end 295 296end 297 298