# -*- coding: utf-8 -*-# frozen_string_literal: true#--# Copyright (C) 2004 Mauricio Julio Fernández Pradier# See LICENSE.txt for additional licensing information.#++require'digest'### Allows writing of tar filesclassGem::Package::TarWriterclassFileOverflow<StandardError;end### IO wrapper that allows writing a limited amount of dataclassBoundedStream### Maximum number of bytes that can be writtenattr_reader:limit### Number of bytes writtenattr_reader:written### Wraps +io+ and allows up to +limit+ bytes to be writtendefinitialize(io,limit)@io=io@limit=limit@written=0end### Writes +data+ onto the IO, raising a FileOverflow exception if the# number of bytes will be more than #limitdefwrite(data)ifdata.bytesize+@written>@limitraiseFileOverflow,"You tried to feed more data than fits in the file."end@io.writedata@written+=data.bytesizedata.bytesizeendend### IO wrapper that provides only #writeclassRestrictedStream### Creates a new RestrictedStream wrapping +io+definitialize(io)@io=ioend### Writes +data+ onto the IOdefwrite(data)@io.writedataendend### Creates a new TarWriter, yielding it if a block is givendefself.new(io)writer=superreturnwriterunlessblock_given?beginyieldwriterensurewriter.closeendnilend### Creates a new TarWriter that will write to +io+definitialize(io)@io=io@closed=falseend### Adds file +name+ with permissions +mode+, and yields an IO for writing the# file todefadd_file(name,mode)# :yields: iocheck_closedraiseGem::Package::NonSeekableIOunless@io.respond_to?:pos=name,prefix=split_namenameinit_pos=@io.pos@io.write"\0"*512# placeholder for the headeryieldRestrictedStream.new(@io)ifblock_given?size=@io.pos-init_pos-512remainder=(512-(size%512))%512@io.write"\0"*remainderfinal_pos=@io.pos@io.pos=init_posheader=Gem::Package::TarHeader.new:name=>name,:mode=>mode,:size=>size,:prefix=>prefix,:mtime=>Time.now@io.writeheader@io.pos=final_posselfend### Adds +name+ with permissions +mode+ to the tar, yielding +io+ for writing# the file. The +digest_algorithm+ is written to a read-only +name+.sum# file following the given file contents containing the digest name and# hexdigest separated by a tab.## The created digest object is returned.defadd_file_digestname,mode,digest_algorithms# :yields: iodigests=digest_algorithms.mapdo|digest_algorithm|digest=digest_algorithm.newdigest_name=ifdigest.respond_to?:namethendigest.nameelse/::([^:]+)$/=~digest_algorithm.name$1end[digest_name,digest]enddigests=Hash[*digests.flatten]add_filename,modedo|io|Gem::Package::DigestIO.wrapio,digestsdo|digest_io|yielddigest_ioendenddigestsend### Adds +name+ with permissions +mode+ to the tar, yielding +io+ for writing# the file. The +signer+ is used to add a digest file using its# digest_algorithm per add_file_digest and a cryptographic signature in# +name+.sig. If the signer has no key only the checksum file is added.## Returns the digest.defadd_file_signedname,mode,signerdigest_algorithms=[signer.digest_algorithm,Digest::SHA512,].compact.uniqdigests=add_file_digestname,mode,digest_algorithmsdo|io|yieldioendsignature_digest=digests.values.compact.finddo|digest|digest_name=ifdigest.respond_to?:namethendigest.nameelse/::([^:]+)$/=~digest.class.name$1enddigest_name==signer.digest_nameendifsigner.keythensignature=signer.signsignature_digest.digestadd_file_simple"#{name}.sig",0444,signature.lengthdo|io|io.writesignatureendenddigestsend### Add file +name+ with permissions +mode+ +size+ bytes long. Yields an IO# to write the file to.defadd_file_simple(name,mode,size)# :yields: iocheck_closedname,prefix=split_namenameheader=Gem::Package::TarHeader.new(:name=>name,:mode=>mode,:size=>size,:prefix=>prefix,:mtime=>Time.now).to_s@io.writeheaderos=BoundedStream.new@io,sizeyieldosifblock_given?min_padding=size-os.written@io.write("\0"*min_padding)remainder=(512-(size%512))%512@io.write("\0"*remainder)selfend### Adds symlink +name+ with permissions +mode+, linking to +target+.defadd_symlink(name,target,mode)check_closedname,prefix=split_namenameheader=Gem::Package::TarHeader.new(:name=>name,:mode=>mode,:size=>0,:typeflag=>"2",:linkname=>target,:prefix=>prefix,:mtime=>Time.now).to_s@io.writeheaderselfend### Raises IOError if the TarWriter is closeddefcheck_closedraiseIOError,"closed #{self.class}"ifclosed?end### Closes the TarWriterdefclosecheck_closed@io.write"\0"*1024flush@closed=trueend### Is the TarWriter closed?defclosed?@closedend### Flushes the TarWriter's IOdefflushcheck_closed@io.flushif@io.respond_to?:flushend### Creates a new directory in the tar file +name+ with +mode+defmkdir(name,mode)check_closedname,prefix=split_name(name)header=Gem::Package::TarHeader.new:name=>name,:mode=>mode,:typeflag=>"5",:size=>0,:prefix=>prefix,:mtime=>Time.now@io.writeheaderselfend### Splits +name+ into a name and prefix that can fit in the TarHeaderdefsplit_name(name)# :nodoc:ifname.bytesize>256raiseGem::Package::TooLongFileName.new("File \"#{name}\" has a too long path (should be 256 or less)")endifname.bytesize<=100thenprefix=""elseparts=name.split(/\//)newname=parts.popnxt=""loopdonxt=parts.popbreakifnewname.bytesize+1+nxt.bytesize>100newname=nxt+"/"+newnameendprefix=(parts+[nxt]).join"/"name=newnameifname.bytesize>100raiseGem::Package::TooLongFileName.new("File \"#{prefix}/#{name}\" has a too long name (should be 100 or less)")endifprefix.bytesize>155thenraiseGem::Package::TooLongFileName.new("File \"#{prefix}/#{name}\" has a too long base path (should be 155 or less)")endendreturnname,prefixendend