class Gem::Package::TarHeader

def self.from(stream)

def self.from(stream)
  header = stream.read 512
  return EMPTY if header == EMPTY_HEADER
  fields = header.unpack UNPACK_FORMAT
  new name: fields.shift,
      mode: strict_oct(fields.shift),
      uid: oct_or_256based(fields.shift),
      gid: oct_or_256based(fields.shift),
      size: strict_oct(fields.shift),
      mtime: strict_oct(fields.shift),
      checksum: strict_oct(fields.shift),
      typeflag: fields.shift,
      linkname: fields.shift,
      magic: fields.shift,
      version: strict_oct(fields.shift),
      uname: fields.shift,
      gname: fields.shift,
      devmajor: strict_oct(fields.shift),
      devminor: strict_oct(fields.shift),
      prefix: fields.shift,
      empty: false
end

def self.oct_or_256based(str)

def self.oct_or_256based(str)
  # \x80 flags a positive 256-based number
  # \ff flags a negative 256-based number
  # In case we have a match, parse it as a signed binary value
  # in big-endian order, except that the high-order bit is ignored.
  return str.unpack1("@4N") if /\A[\x80\xff]/n.match?(str)
  strict_oct(str)
end

def self.strict_oct(str)

def self.strict_oct(str)
  str.strip!
  return str.oct if /\A[0-7]*\z/.match?(str)
  raise ArgumentError, "#{str.inspect} is not an octal string"
end

def ==(other) # :nodoc:

:nodoc:
def ==(other) # :nodoc:
  self.class === other &&
    @checksum == other.checksum &&
    @devmajor == other.devmajor &&
    @devminor == other.devminor &&
    @gid      == other.gid      &&
    @gname    == other.gname    &&
    @linkname == other.linkname &&
    @magic    == other.magic    &&
    @mode     == other.mode     &&
    @mtime    == other.mtime    &&
    @name     == other.name     &&
    @prefix   == other.prefix   &&
    @size     == other.size     &&
    @typeflag == other.typeflag &&
    @uid      == other.uid      &&
    @uname    == other.uname    &&
    @version  == other.version
end

def calculate_checksum(header)

def calculate_checksum(header)
  header.sum(0)
end

def empty?

def empty?
  @empty
end

def full_name

def full_name
  if prefix != ""
    File.join prefix, name
  else
    name
  end
end

def header(checksum = @checksum)

def header(checksum = @checksum)
  header = [
    name,
    oct(mode, 7),
    oct(uid, 7),
    oct(gid, 7),
    oct(size, 11),
    oct(mtime, 11),
    checksum,
    " ",
    typeflag,
    linkname,
    magic,
    oct(version, 2),
    uname,
    gname,
    oct(devmajor, 7),
    oct(devminor, 7),
    prefix,
  ]
  header = header.pack PACK_FORMAT
  header.ljust 512, "\0"
end

def initialize(vals)

def initialize(vals)
  unless vals[:name] && vals[:size] && vals[:prefix] && vals[:mode]
    raise ArgumentError, ":name, :size, :prefix and :mode required"
  end
  @checksum = vals[:checksum] || ""
  @devmajor = vals[:devmajor] || 0
  @devminor = vals[:devminor] || 0
  @gid = vals[:gid] || 0
  @gname = vals[:gname] || "wheel"
  @linkname = vals[:linkname]
  @magic = vals[:magic] || "ustar"
  @mode = vals[:mode]
  @mtime = vals[:mtime] || 0
  @name = vals[:name]
  @prefix = vals[:prefix]
  @size = vals[:size]
  @typeflag = vals[:typeflag]
  @typeflag = "0" if @typeflag.nil? || @typeflag.empty?
  @uid = vals[:uid] || 0
  @uname = vals[:uname] || "wheel"
  @version = vals[:version] || "00"
  @empty = vals[:empty]
end

def oct(num, len)

def oct(num, len)
  format("%0#{len}o", num)
end

def to_s # :nodoc:

:nodoc:
def to_s # :nodoc:
  update_checksum
  header
end

def update_checksum

def update_checksum
  header = header " " * 8
  @checksum = oct calculate_checksum(header), 6
end