gzipped_tar 0.0.4 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: c9020c4f3848255eb93c6dd596cfa4216346253e
4
- data.tar.gz: 41c20c89d72237fec73913c44810769ea61a7e6d
2
+ SHA256:
3
+ metadata.gz: ca76d13e235befa915315db06a2cf6e319686dc848c6bfbbd627d87c9021c90c
4
+ data.tar.gz: 35e618c62c9c440b74b17b6fcd0c1157f0898ae4ec428156b0273f3a4ff11cee
5
5
  SHA512:
6
- metadata.gz: cea6867c8e703e071716760e0d7c9f00239fbe6fca68b9a53325ee4b0d5c0fe98a0b75a29e66afb549f401573e6da1eb153005edc9e2016e79c9e6b2cb9caa7c
7
- data.tar.gz: 7f2a741f3c222085ac7b33112e5c0caa09270827971344e781d9476de92dd7e3b9462c6a9134a3ab4723df21ac12dd104e11003f79a0b58f3904054978b9a1d9
6
+ metadata.gz: bea79c295ca72b0c62d7bdb63a295c37e415135397d0f133ec9d639164219fd2d5f750c158f3975289dad08c8b75e388bca396dfecc1c627b6ad33628713d570
7
+ data.tar.gz: 1afb1ce58a5ecb2a3181788cb13375ae171d6890600b92ae71d7e456ad9fa05b8f78bd51afbac76a888f5b0e38015e7b49b7a0d279c67ecfac8554a4bff41b71
@@ -4,6 +4,12 @@ inherit_from:
4
4
  AllCops:
5
5
  TargetRubyVersion: 2.1
6
6
 
7
+ Performance/RedundantBlockCall:
8
+ Enabled: false
9
+
10
+ Style/AsciiComments:
11
+ Enabled: false
12
+
7
13
  Style/Documentation:
8
14
  Enabled: false
9
15
 
@@ -5,7 +5,8 @@ rvm:
5
5
  - 1.9.3
6
6
  - 2.0
7
7
  - 2.1.10
8
- - 2.2.8
9
- - 2.3.5
10
- - 2.4.2
8
+ - 2.2.9
9
+ - 2.3.6
10
+ - 2.4.3
11
+ - 2.5.0
11
12
  - jruby-9.1.14.0
data/README.md CHANGED
@@ -5,7 +5,7 @@ A simple interface for reading and writing gzipped tar files (.tar.gz) in memory
5
5
  ## Installation
6
6
 
7
7
  ```ruby
8
- gem "gzipped_tar", "~> 0.0.4"
8
+ gem "gzipped_tar", "~> 0.1.0"
9
9
  ```
10
10
 
11
11
  ## Usage
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |spec|
4
4
  spec.name = "gzipped_tar"
5
- spec.version = "0.0.4"
5
+ spec.version = "0.1.0"
6
6
  spec.authors = ["Pat Allan"]
7
7
  spec.email = ["pat@freelancing-gods.com"]
8
8
 
@@ -1,8 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "digest"
4
+
3
5
  module GZippedTar
4
6
  #
5
7
  end
6
8
 
9
+ require "gzipped_tar/tar"
7
10
  require "gzipped_tar/reader"
8
11
  require "gzipped_tar/writer"
@@ -20,7 +20,7 @@ module GZippedTar
20
20
  attr_reader :raw
21
21
 
22
22
  def reader
23
- Gem::Package::TarReader.new unzipped
23
+ GZippedTar::Tar::Reader.new unzipped
24
24
  end
25
25
 
26
26
  def unzipped
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GZippedTar
4
+ module Tar
5
+ ROW_WIDTH = 512
6
+ EMPTY_ROW = "\0" * ROW_WIDTH
7
+
8
+ Error = Class.new StandardError
9
+
10
+ FileOverflow = Class.new Error
11
+ NonSeekableIO = Class.new Error
12
+ TarInvalidError = Class.new Error
13
+ TooLongFileName = Class.new Error
14
+ UnexpectedEOF = Class.new Error
15
+ end
16
+ end
17
+
18
+ require "gzipped_tar/tar/bounded_stream"
19
+ require "gzipped_tar/tar/digest_io"
20
+ require "gzipped_tar/tar/entry"
21
+ require "gzipped_tar/tar/field"
22
+ require "gzipped_tar/tar/checksum_field"
23
+ require "gzipped_tar/tar/header"
24
+ require "gzipped_tar/tar/reader"
25
+ require "gzipped_tar/tar/restricted_stream"
26
+ require "gzipped_tar/tar/split_name"
27
+ require "gzipped_tar/tar/writer"
28
+ require "gzipped_tar/tar/write_file"
29
+ require "gzipped_tar/tar/write_signed_file"
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ #--
4
+ # Original source is copyright (C) 2004 Mauricio Julio Fernández Pradier
5
+ # This file has been copied and adapted to avoid reliance on differing
6
+ # behaviour across versions of rubygems, and to handle nil header values at
7
+ # the end of a file.
8
+ #++
9
+
10
+ class GZippedTar::Tar::BoundedStream
11
+ # Maximum number of bytes that can be written
12
+ attr_reader :limit
13
+
14
+ # Number of bytes written
15
+ attr_reader :written
16
+
17
+ # Wraps +io+ and allows up to +limit+ bytes to be written
18
+ def initialize(io, limit)
19
+ @io = io
20
+ @limit = limit
21
+ @written = 0
22
+ end
23
+
24
+ # Writes +data+ onto the IO, raising a FileOverflow exception if the
25
+ # number of bytes will be more than #limit
26
+ def write(data)
27
+ if data.bytesize + @written > @limit
28
+ raise GZippedTar::Tar::FileOverflow,
29
+ "You tried to feed more data than fits in the file."
30
+ end
31
+ @io.write data
32
+ @written += data.bytesize
33
+ data.bytesize
34
+ end
35
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ class GZippedTar::Tar::ChecksumField < GZippedTar::Tar::Field
4
+ EMPTY_VALUE = [" " * 8, " "].freeze
5
+
6
+ def pack
7
+ "a7a"
8
+ end
9
+
10
+ def unpack
11
+ "A8"
12
+ end
13
+
14
+ def to_s(value)
15
+ return EMPTY_VALUE if value.nil?
16
+
17
+ [format("%06o", value), " "]
18
+ end
19
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ #--
4
+ # Original source is from Rubygems.
5
+ # This file has been copied and adapted to avoid reliance on differing
6
+ # behaviour across versions of rubygems, and to handle nil header values at
7
+ # the end of a file.
8
+ #++
9
+
10
+ # IO wrapper that creates digests of contents written to the IO it wraps.
11
+ class GZippedTar::Tar::DigestIO
12
+ # Collected digests for wrapped writes.
13
+ #
14
+ # {
15
+ # 'SHA1' => #<OpenSSL::Digest: [...]>,
16
+ # 'SHA512' => #<OpenSSL::Digest: [...]>,
17
+ # }
18
+ attr_reader :digests
19
+
20
+ # Wraps +io+ and updates digest for each of the digest algorithms in
21
+ # the +digests+ Hash. Returns the digests hash. Example:
22
+ #
23
+ # io = StringIO.new
24
+ # digests = {
25
+ # 'SHA1' => OpenSSL::Digest.new('SHA1'),
26
+ # 'SHA512' => OpenSSL::Digest.new('SHA512'),
27
+ # }
28
+ #
29
+ # GZippedTar::Tar::DigestIO.wrap io, digests do |digest_io|
30
+ # digest_io.write "hello"
31
+ # end
32
+ #
33
+ # digests['SHA1'].hexdigest #=> "aaf4c61d[...]"
34
+ # digests['SHA512'].hexdigest #=> "9b71d224[...]"
35
+ def self.wrap(io, digests)
36
+ digest_io = new io, digests
37
+
38
+ yield digest_io
39
+
40
+ digests
41
+ end
42
+
43
+ # Creates a new DigestIO instance. Using ::wrap is recommended, see the
44
+ # ::wrap documentation for documentation of +io+ and +digests+.
45
+ def initialize(io, digests)
46
+ @io = io
47
+ @digests = digests
48
+ end
49
+
50
+ # Writes +data+ to the underlying IO and updates the digests
51
+ def write(data)
52
+ result = @io.write data
53
+
54
+ @digests.each_value do |digest|
55
+ digest << data
56
+ end
57
+
58
+ result
59
+ end
60
+ end
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ #++
4
+ # Original source is copyright (C) 2004 Mauricio Julio Fernández Pradier
5
+ # This file has been copied and adapted to avoid reliance on differing
6
+ # behaviour across versions of rubygems, and to handle nil header values at
7
+ # the end of a file.
8
+ #--
9
+
10
+ class GZippedTar::Tar::Entry
11
+ attr_reader :header
12
+
13
+ # Creates a new tar entry for +header+ that will be read from +io+
14
+ def initialize(header, io)
15
+ @closed = false
16
+ @header = header
17
+ @io = io
18
+ @orig_pos = @io.pos
19
+ @read = 0
20
+ end
21
+
22
+ def check_closed # :nodoc:
23
+ raise IOError, "closed #{self.class}" if closed?
24
+ end
25
+
26
+ # Number of bytes read out of the tar entry
27
+ def bytes_read
28
+ @read
29
+ end
30
+
31
+ # Closes the tar entry
32
+ def close
33
+ @closed = true
34
+ end
35
+
36
+ # Is the tar entry closed?
37
+ def closed?
38
+ @closed
39
+ end
40
+
41
+ # Are we at the end of the tar entry?
42
+ def eof?
43
+ check_closed
44
+
45
+ @read >= @header.size
46
+ end
47
+
48
+ # Full name of the tar entry
49
+ def full_name
50
+ if @header.prefix != ""
51
+ File.join @header.prefix, @header.name
52
+ else
53
+ @header.name
54
+ end
55
+ rescue ArgumentError => e
56
+ raise unless e.message == "string contains null byte"
57
+ raise GZippedTar::Tar::TarInvalidError,
58
+ "tar is corrupt, name contains null byte"
59
+ end
60
+
61
+ # Read one byte from the tar entry
62
+ def getc
63
+ check_closed
64
+
65
+ return nil if @read >= @header.size
66
+
67
+ ret = @io.getc
68
+ @read += 1 if ret
69
+
70
+ ret
71
+ end
72
+
73
+ # Is this tar entry a directory?
74
+ def directory?
75
+ @header.typeflag == "5"
76
+ end
77
+
78
+ # Is this tar entry a file?
79
+ def file?
80
+ @header.typeflag == "0"
81
+ end
82
+
83
+ # Is this tar entry a symlink?
84
+ def symlink?
85
+ @header.typeflag == "2"
86
+ end
87
+
88
+ # The position in the tar entry
89
+ def pos
90
+ check_closed
91
+
92
+ bytes_read
93
+ end
94
+
95
+ # Reads +len+ bytes from the tar file entry, or the rest of the entry if
96
+ # nil
97
+ def read(len = nil)
98
+ check_closed
99
+
100
+ return nil if @read >= @header.size
101
+
102
+ len ||= @header.size - @read
103
+ max_read = [len, @header.size - @read].min
104
+
105
+ ret = @io.read max_read
106
+ @read += ret.size
107
+
108
+ ret
109
+ end
110
+
111
+ alias readpartial read # :nodoc:
112
+
113
+ # Rewinds to the beginning of the tar file entry
114
+ def rewind
115
+ check_closed
116
+
117
+ raise GZippedTar::Tar::NonSeekableIO unless @io.respond_to? :pos=
118
+
119
+ @io.pos = @orig_pos
120
+ @read = 0
121
+ end
122
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ class GZippedTar::Tar::Field
4
+ attr_reader :name, :length, :octal, :required, :default
5
+
6
+ def initialize(name, length, options = {})
7
+ @name = name
8
+ @length = length
9
+ @octal = options[:octal]
10
+ @required = options[:required]
11
+ @default = options[:default]
12
+ end
13
+
14
+ def octal?
15
+ octal
16
+ end
17
+
18
+ def pack
19
+ return "a" if length == 1
20
+
21
+ "a#{length}"
22
+ end
23
+
24
+ def required?
25
+ required
26
+ end
27
+
28
+ def to_s(value)
29
+ return value unless octal?
30
+
31
+ if length == 2
32
+ format "%0#{length}o", value
33
+ else
34
+ format "%0#{length - 1}o", value
35
+ end
36
+ end
37
+
38
+ def translate(value)
39
+ value ||= default
40
+
41
+ return value unless octal? && value.is_a?(String)
42
+ return value.oct if value[/\A[0-7]*\z/]
43
+
44
+ raise ArgumentError, "#{value.inspect} is not an octal string"
45
+ end
46
+
47
+ def unpack
48
+ return "A" if length == 1
49
+
50
+ "A#{length}"
51
+ end
52
+ end
@@ -0,0 +1,161 @@
1
+ # frozen_string_literal: true
2
+
3
+ #++
4
+ # Original source is copyright (C) 2004 Mauricio Julio Fernández Pradier
5
+ # This file has been copied and adapted to avoid reliance on differing
6
+ # behaviour across versions of rubygems, and to handle nil header values at
7
+ # the end of a file.
8
+ #--
9
+
10
+ ##
11
+ #--
12
+ # struct tarfile_entry_posix {
13
+ # char name[100]; # ASCII + (Z unless filled)
14
+ # char mode[8]; # 0 padded, octal, null
15
+ # char uid[8]; # ditto
16
+ # char gid[8]; # ditto
17
+ # char size[12]; # 0 padded, octal, null
18
+ # char mtime[12]; # 0 padded, octal, null
19
+ # char checksum[8]; # 0 padded, octal, null, space
20
+ # char typeflag[1]; # file: "0" dir: "5"
21
+ # char linkname[100]; # ASCII + (Z unless filled)
22
+ # char magic[6]; # "ustar\0"
23
+ # char version[2]; # "00"
24
+ # char uname[32]; # ASCIIZ
25
+ # char gname[32]; # ASCIIZ
26
+ # char devmajor[8]; # 0 padded, octal, null
27
+ # char devminor[8]; # o padded, octal, null
28
+ # char prefix[155]; # ASCII + (Z unless filled)
29
+ # };
30
+ #++
31
+ # A header for a tar file
32
+
33
+ class GZippedTar::Tar::Header
34
+ Field = GZippedTar::Tar::Field
35
+ ChecksumField = GZippedTar::Tar::ChecksumField
36
+
37
+ # rubocop:disable Layout/ExtraSpacing
38
+ FIELDS = [
39
+ Field.new(:name, 100, :required => true),
40
+ Field.new(:mode, 8, :required => true, :octal => true),
41
+ Field.new(:uid, 8, :default => 0, :octal => true),
42
+ Field.new(:gid, 8, :default => 0, :octal => true),
43
+ Field.new(:size, 12, :required => true, :octal => true),
44
+ Field.new(:mtime, 12, :default => 0, :octal => true),
45
+ # Custom behaviour for the checksum:
46
+ ChecksumField.new(:checksum, 8, :octal => true),
47
+ # And back to normal:
48
+ Field.new(:typeflag, 1, :default => "0"),
49
+ Field.new(:linkname, 100),
50
+ Field.new(:magic, 6, :default => "ustar"),
51
+ Field.new(:version, 2, :default => "00", :octal => true),
52
+ Field.new(:uname, 32, :default => "wheel"),
53
+ Field.new(:gname, 32, :default => "wheel"),
54
+ Field.new(:devmajor, 8, :default => 0, :octal => true),
55
+ Field.new(:devminor, 8, :default => 0, :octal => true),
56
+ Field.new(:prefix, 155, :required => true)
57
+ ].freeze
58
+ # rubocop:enable Layout/ExtraSpacing
59
+
60
+ PACK_FORMAT = FIELDS.collect(&:pack).join("")
61
+ UNPACK_FORMAT = FIELDS.collect(&:unpack).join("")
62
+ HEADER_LENGTH = 512
63
+ BLANK = {
64
+ :name => "",
65
+ :size => "0",
66
+ :prefix => "",
67
+ :mode => "644",
68
+ :empty => true
69
+ }.freeze
70
+
71
+ # Creates a tar header from IO +stream+
72
+ def self.from(stream)
73
+ header = stream.read HEADER_LENGTH
74
+ return new(BLANK) if header.nil?
75
+
76
+ empty = (header == "\0" * HEADER_LENGTH)
77
+
78
+ new values_from_array(header.unpack(UNPACK_FORMAT)).merge(:empty => empty)
79
+ end
80
+
81
+ def self.values_from_array(values)
82
+ hash = {}
83
+
84
+ FIELDS.each_with_index do |field, index|
85
+ hash[field.name] = field.translate values[index]
86
+ end
87
+
88
+ hash
89
+ end
90
+
91
+ def initialize(values)
92
+ validate values
93
+
94
+ @empty = values.delete :empty
95
+ @values = FIELDS.inject({}) do |hash, field|
96
+ hash[field.name] = field.translate values[field.name]
97
+ hash
98
+ end
99
+
100
+ @values[:typeflag] = "0" if @values[:typeflag].empty?
101
+ end
102
+
103
+ def ==(other) # :nodoc:
104
+ other.is_a?(self.class) &&
105
+ FIELDS.all? { |field| values[field.name] == other.values[field.name] }
106
+ end
107
+
108
+ def empty?
109
+ @empty
110
+ end
111
+
112
+ def name
113
+ @values[:name]
114
+ end
115
+
116
+ def prefix
117
+ @values[:prefix]
118
+ end
119
+
120
+ def size
121
+ values[:size]
122
+ end
123
+
124
+ def to_s # :nodoc:
125
+ update_checksum
126
+ build_header
127
+ end
128
+
129
+ protected
130
+
131
+ attr_reader :values
132
+
133
+ private
134
+
135
+ def calculate_checksum(header)
136
+ header.unpack("C*").inject { |a, b| a + b }
137
+ end
138
+
139
+ def build_header
140
+ header = header_values.pack PACK_FORMAT
141
+
142
+ header + ("\0" * ((HEADER_LENGTH - header.size) % HEADER_LENGTH))
143
+ end
144
+
145
+ def header_values
146
+ FIELDS.collect { |field| field.to_s values[field.name] }.flatten
147
+ end
148
+
149
+ def update_checksum
150
+ values[:checksum] = nil
151
+ values[:checksum] = calculate_checksum(build_header)
152
+ end
153
+
154
+ def validate(values)
155
+ FIELDS.select(&:required?).each do |field|
156
+ next if values[field.name]
157
+
158
+ raise ArgumentError, ":name, :size, :prefix and :mode required"
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ #--
4
+ # Original source is copyright (C) 2004 Mauricio Julio Fernández Pradier
5
+ # This file has been copied and adapted to avoid reliance on differing
6
+ # behaviour across versions of rubygems, and to handle nil header values at
7
+ # the end of a file.
8
+ #++
9
+
10
+ class GZippedTar::Tar::Reader
11
+ include Enumerable
12
+
13
+ ##
14
+ # Creates a new TarReader on +io+ and yields it to the block, if given.
15
+
16
+ def self.new(io)
17
+ reader = super
18
+
19
+ return reader unless block_given?
20
+
21
+ begin
22
+ yield reader
23
+ ensure
24
+ reader.close
25
+ end
26
+
27
+ nil
28
+ end
29
+
30
+ # Creates a new tar file reader on +io+ which needs to respond to #pos,
31
+ # #eof?, #read, #getc and #pos=
32
+ def initialize(io)
33
+ @io = io
34
+ @init_pos = io.pos
35
+ end
36
+
37
+ # Close the tar file
38
+ def close; end
39
+
40
+ # Iterates over files in the tarball yielding each entry
41
+ def each
42
+ return enum_for __method__ unless block_given?
43
+
44
+ until @io.eof?
45
+ header = GZippedTar::Tar::Header.from @io
46
+ return if header.empty?
47
+ entry = GZippedTar::Tar::Entry.new header, @io
48
+
49
+ yield entry
50
+
51
+ skip_past_entry header.size, entry.bytes_read
52
+ skip_past_trailing header.size
53
+
54
+ # make sure nobody can use #read, #getc or #rewind anymore
55
+ entry.close
56
+ end
57
+ end
58
+
59
+ alias each_entry each
60
+
61
+ # NOTE: Do not call #rewind during #each
62
+ def rewind
63
+ if @init_pos.zero?
64
+ raise GZippedTar::Tar::NonSeekableIO unless @io.respond_to? :rewind
65
+ @io.rewind
66
+ else
67
+ raise GZippedTar::Tar::NonSeekableIO unless @io.respond_to? :pos=
68
+ @io.pos = @init_pos
69
+ end
70
+ end
71
+
72
+ # Seeks through the tar file until it finds the +entry+ with +name+ and
73
+ # yields it. Rewinds the tar file to the beginning when the block
74
+ # terminates.
75
+ def seek(name)
76
+ found = find do |entry|
77
+ entry.full_name == name
78
+ end
79
+
80
+ return unless found
81
+
82
+ return yield found
83
+ ensure
84
+ rewind
85
+ end
86
+
87
+ private
88
+
89
+ attr_reader :io
90
+
91
+ def skip_past_entry(size, read)
92
+ # skip over the rest of the entry
93
+ pending = size - read
94
+
95
+ skip_by_seek
96
+ rescue Errno::EINVAL, NameError
97
+ skip_by_read pending
98
+ end
99
+
100
+ def skip_by_read(pending)
101
+ while pending > 0
102
+ bytes_read = io.read([pending, 4096].min).size
103
+ pending -= bytes_read
104
+ raise GZippedTar::Tar::UnexpectedEOF if io.eof? && !pending.zero?
105
+ end
106
+ end
107
+
108
+ def skip_by_seek
109
+ # avoid reading...
110
+ io.seek pending, IO::SEEK_CUR
111
+ end
112
+
113
+ def skip_past_trailing(size)
114
+ # discard trailing zeros
115
+ skip = (512 - (size % 512)) % 512
116
+
117
+ skip_by_read skip
118
+ end
119
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ #--
4
+ # Original source is copyright (C) 2004 Mauricio Julio Fernández Pradier
5
+ # This file has been copied and adapted to avoid reliance on differing
6
+ # behaviour across versions of rubygems, and to handle nil header values at
7
+ # the end of a file.
8
+ #++
9
+
10
+ # IO wrapper that provides only #write
11
+ class GZippedTar::Tar::RestrictedStream
12
+ # Creates a new RestrictedStream wrapping +io+
13
+ def initialize(io)
14
+ @io = io
15
+ end
16
+
17
+ # Writes +data+ onto the IO
18
+ def write(data)
19
+ @io.write data
20
+ end
21
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ class GZippedTar::Tar::SplitName
4
+ MAXIMUM_LENGTH = 256
5
+ MAXIMUM_NAME_LENGTH = 100
6
+ MAXIMUM_PATH_LENGTH = 155
7
+
8
+ def self.call(file)
9
+ new(file).call
10
+ end
11
+
12
+ def initialize(file)
13
+ @file = file
14
+ end
15
+
16
+ def call
17
+ return ["", file] unless split?
18
+
19
+ raise_if_too_long file, MAXIMUM_LENGTH, "path"
20
+ raise_if_too_long name, MAXIMUM_NAME_LENGTH, "name"
21
+ raise_if_too_long prefix, MAXIMUM_PATH_LENGTH, "base path"
22
+
23
+ [prefix, name]
24
+ end
25
+
26
+ private
27
+
28
+ attr_reader :file
29
+
30
+ def last_prefix_index
31
+ @last_prefix_index ||= begin
32
+ index = parts.length - 2
33
+ index -= 1 while parts[0..index].join("/").bytesize >= MAXIMUM_PATH_LENGTH
34
+ index
35
+ end
36
+ end
37
+
38
+ def name
39
+ parts[(last_prefix_index + 1)..-1].join("/")
40
+ end
41
+
42
+ def parts
43
+ @parts ||= file.split "/", -1
44
+ end
45
+
46
+ def prefix
47
+ parts[0..last_prefix_index].join("/")
48
+ end
49
+
50
+ def raise_if_too_long(string, maximum, description)
51
+ return if string.bytesize <= maximum
52
+
53
+ raise GZippedTar::Tar::TooLongFileName,
54
+ "File \"#{string}\" has a too long #{description} (should be " \
55
+ "#{maximum} or less)"
56
+ end
57
+
58
+ # If the file is less than MAXIMUM_NAME_LENGTH, it doesn't need to be split,
59
+ # and the prefix can be blank.
60
+ def split?
61
+ file.bytesize > MAXIMUM_NAME_LENGTH
62
+ end
63
+
64
+ def too_long?
65
+ file.bytesize > 256
66
+ end
67
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ class GZippedTar::Tar::WriteFile
4
+ def self.call(writer, io, name, mode, &block)
5
+ new(writer, io, name, mode).call(&block)
6
+ end
7
+
8
+ def initialize(writer, io, name, mode)
9
+ @writer = writer
10
+ @io = io
11
+ @name = name
12
+ @mode = mode
13
+ @initial = io.pos
14
+ end
15
+
16
+ def call(&block)
17
+ # placeholder for the header
18
+ io.write GZippedTar::Tar::EMPTY_ROW
19
+
20
+ block.call GZippedTar::Tar::RestrictedStream.new(io) if block
21
+
22
+ writer.pad_rows size
23
+ overwrite_header
24
+ end
25
+
26
+ private
27
+
28
+ attr_reader :writer, :io, :name, :mode, :initial
29
+
30
+ def overwrite_header
31
+ current_position = io.pos
32
+ io.pos = initial
33
+
34
+ writer.write_header name, mode, :size => size
35
+
36
+ io.pos = current_position
37
+ end
38
+
39
+ def size
40
+ @size ||= io.pos - initial - GZippedTar::Tar::ROW_WIDTH
41
+ end
42
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ class GZippedTar::Tar::WriteSignedFile
4
+ def self.call(writer, name, mode, signer, &block)
5
+ new(writer, name, mode, signer).call(&block)
6
+ end
7
+
8
+ def initialize(writer, name, mode, signer)
9
+ @writer = writer
10
+ @name = name
11
+ @mode = mode
12
+ @signer = signer
13
+ end
14
+
15
+ def call(&block)
16
+ add_file_digest(&block)
17
+
18
+ raise "no #{signer.digest_name} in #{digests.values.compact}" unless digest
19
+
20
+ write_signature if signer.key
21
+
22
+ digests
23
+ end
24
+
25
+ private
26
+
27
+ attr_reader :writer, :name, :mode, :signer
28
+
29
+ def add_file_digest(&block)
30
+ writer.add_file name, mode do |io|
31
+ GZippedTar::Tar::DigestIO.wrap io, digests, &block
32
+ end
33
+ end
34
+
35
+ def algorithms
36
+ [
37
+ signer.digest_algorithm,
38
+ Digest::SHA512
39
+ ].compact.uniq
40
+ end
41
+
42
+ def digest
43
+ @digest ||= digests.values.compact.detect do |digest|
44
+ digest_name(digest) == signer.digest_name
45
+ end
46
+ end
47
+
48
+ def digests
49
+ @digests ||= begin
50
+ digests = algorithms.collect(&:new).collect do |digest|
51
+ [digest_name(digest), digest]
52
+ end
53
+
54
+ Hash[*digests.flatten]
55
+ end
56
+ end
57
+
58
+ def digest_name(instance)
59
+ if instance.respond_to? :name
60
+ instance.name
61
+ else
62
+ instance.class.name[/::([^:]+)$/, 1]
63
+ end
64
+ end
65
+
66
+ def write_signature
67
+ signature = signer.sign digest.digest
68
+
69
+ writer.add_file_simple "#{name}.sig", 0o444, signature.length do |io|
70
+ io.write signature
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ #--
4
+ # Original source is copyright (C) 2004 Mauricio Julio Fernández Pradier
5
+ # This file has been copied and adapted to avoid reliance on differing
6
+ # behaviour across versions of rubygems, and to handle nil header values at
7
+ # the end of a file.
8
+ #++
9
+
10
+ class GZippedTar::Tar::Writer
11
+ ROW_WIDTH = GZippedTar::Tar::ROW_WIDTH
12
+
13
+ def self.new(io)
14
+ writer = super
15
+
16
+ return writer unless block_given?
17
+
18
+ begin
19
+ yield writer
20
+ ensure
21
+ writer.close
22
+ end
23
+
24
+ nil
25
+ end
26
+
27
+ # Creates a new TarWriter that will write to +io+
28
+ def initialize(io)
29
+ raise GZippedTar::Tar::NonSeekableIO unless io.respond_to? :pos=
30
+
31
+ @io = io
32
+ @closed = false
33
+ end
34
+
35
+ # Adds file +name+ with permissions +mode+, and yields an IO for writing the
36
+ # file to.
37
+ def add_file(name, mode, &block)
38
+ check_closed
39
+
40
+ GZippedTar::Tar::WriteFile.call self, io, name, mode, &block
41
+
42
+ self
43
+ end
44
+
45
+ # Adds +name+ with permissions +mode+ to the tar, yielding +io+ for writing
46
+ # the file. The +signer+ is used to add a digest file using its
47
+ # digest_algorithm per add_file_digest and a cryptographic signature in
48
+ # +name+.sig. If the signer has no key only the checksum file is added.
49
+ #
50
+ # Returns the digest.
51
+ def add_file_signed(name, mode, signer, &block)
52
+ GZippedTar::Tar::WriteSignedFile.call self, name, mode, signer, &block
53
+ end
54
+
55
+ # Add file +name+ with permissions +mode+ +size+ bytes long. Yields an IO
56
+ # to write the file to.
57
+ def add_file_simple(name, mode, size)
58
+ write_header name, mode, :size => size
59
+
60
+ stream = GZippedTar::Tar::BoundedStream.new io, size
61
+
62
+ yield stream if block_given?
63
+
64
+ min_padding = size - stream.written
65
+ io.write("\0" * min_padding)
66
+
67
+ pad_rows size
68
+
69
+ self
70
+ end
71
+
72
+ # Adds symlink +name+ with permissions +mode+, linking to +target+.
73
+ def add_symlink(name, target, mode)
74
+ write_header name, mode, :typeflag => "2", :linkname => target
75
+ end
76
+
77
+ # Closes the TarWriter
78
+ def close
79
+ check_closed
80
+
81
+ io.write GZippedTar::Tar::EMPTY_ROW
82
+ io.write GZippedTar::Tar::EMPTY_ROW
83
+ flush
84
+
85
+ @closed = true
86
+ end
87
+
88
+ # Is the TarWriter closed?
89
+ def closed?
90
+ @closed
91
+ end
92
+
93
+ # Flushes the TarWriter's IO
94
+ def flush
95
+ check_closed
96
+
97
+ io.flush if io.respond_to? :flush
98
+ end
99
+
100
+ # Creates a new directory in the tar file +name+ with +mode+
101
+ def mkdir(name, mode)
102
+ write_header name, mode, :typeflag => "5"
103
+ end
104
+
105
+ def pad_rows(size)
106
+ remainder = (ROW_WIDTH - (size % ROW_WIDTH)) % ROW_WIDTH
107
+
108
+ io.write "\0" * remainder
109
+ end
110
+
111
+ def write_header(name, mode, options = {})
112
+ check_closed
113
+
114
+ prefix, name = GZippedTar::Tar::SplitName.call name
115
+
116
+ io.write GZippedTar::Tar::Header.new({
117
+ :name => name,
118
+ :mode => mode,
119
+ :prefix => prefix,
120
+ :size => 0,
121
+ :mtime => Time.now
122
+ }.merge(options))
123
+
124
+ self
125
+ end
126
+
127
+ private
128
+
129
+ attr_reader :io
130
+
131
+ # Raises IOError if the TarWriter is closed
132
+ def check_closed
133
+ raise IOError, "closed #{self.class}" if closed?
134
+ end
135
+ end
@@ -32,7 +32,7 @@ module GZippedTar
32
32
  end
33
33
 
34
34
  def writer
35
- @writer ||= Gem::Package::TarWriter.new input_io
35
+ @writer ||= GZippedTar::Tar::Writer.new input_io
36
36
  end
37
37
  end
38
38
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gzipped_tar
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pat Allan
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-12-14 00:00:00.000000000 Z
11
+ date: 2018-03-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -58,6 +58,19 @@ files:
58
58
  - gzipped_tar.gemspec
59
59
  - lib/gzipped_tar.rb
60
60
  - lib/gzipped_tar/reader.rb
61
+ - lib/gzipped_tar/tar.rb
62
+ - lib/gzipped_tar/tar/bounded_stream.rb
63
+ - lib/gzipped_tar/tar/checksum_field.rb
64
+ - lib/gzipped_tar/tar/digest_io.rb
65
+ - lib/gzipped_tar/tar/entry.rb
66
+ - lib/gzipped_tar/tar/field.rb
67
+ - lib/gzipped_tar/tar/header.rb
68
+ - lib/gzipped_tar/tar/reader.rb
69
+ - lib/gzipped_tar/tar/restricted_stream.rb
70
+ - lib/gzipped_tar/tar/split_name.rb
71
+ - lib/gzipped_tar/tar/write_file.rb
72
+ - lib/gzipped_tar/tar/write_signed_file.rb
73
+ - lib/gzipped_tar/tar/writer.rb
61
74
  - lib/gzipped_tar/writer.rb
62
75
  homepage: https://github.com/pat/gzipped_tar
63
76
  licenses:
@@ -79,7 +92,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
79
92
  version: '0'
80
93
  requirements: []
81
94
  rubyforge_project:
82
- rubygems_version: 2.6.13
95
+ rubygems_version: 2.7.6
83
96
  signing_key:
84
97
  specification_version: 4
85
98
  summary: In-memory reading/writing of .tar.gz files