gzipped_tar 0.0.4 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.rubocop.yml +6 -0
- data/.travis.yml +4 -3
- data/README.md +1 -1
- data/gzipped_tar.gemspec +1 -1
- data/lib/gzipped_tar.rb +3 -0
- data/lib/gzipped_tar/reader.rb +1 -1
- data/lib/gzipped_tar/tar.rb +29 -0
- data/lib/gzipped_tar/tar/bounded_stream.rb +35 -0
- data/lib/gzipped_tar/tar/checksum_field.rb +19 -0
- data/lib/gzipped_tar/tar/digest_io.rb +60 -0
- data/lib/gzipped_tar/tar/entry.rb +122 -0
- data/lib/gzipped_tar/tar/field.rb +52 -0
- data/lib/gzipped_tar/tar/header.rb +161 -0
- data/lib/gzipped_tar/tar/reader.rb +119 -0
- data/lib/gzipped_tar/tar/restricted_stream.rb +21 -0
- data/lib/gzipped_tar/tar/split_name.rb +67 -0
- data/lib/gzipped_tar/tar/write_file.rb +42 -0
- data/lib/gzipped_tar/tar/write_signed_file.rb +73 -0
- data/lib/gzipped_tar/tar/writer.rb +135 -0
- data/lib/gzipped_tar/writer.rb +1 -1
- metadata +16 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: ca76d13e235befa915315db06a2cf6e319686dc848c6bfbbd627d87c9021c90c
|
4
|
+
data.tar.gz: 35e618c62c9c440b74b17b6fcd0c1157f0898ae4ec428156b0273f3a4ff11cee
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bea79c295ca72b0c62d7bdb63a295c37e415135397d0f133ec9d639164219fd2d5f750c158f3975289dad08c8b75e388bca396dfecc1c627b6ad33628713d570
|
7
|
+
data.tar.gz: 1afb1ce58a5ecb2a3181788cb13375ae171d6890600b92ae71d7e456ad9fa05b8f78bd51afbac76a888f5b0e38015e7b49b7a0d279c67ecfac8554a4bff41b71
|
data/.rubocop.yml
CHANGED
data/.travis.yml
CHANGED
data/README.md
CHANGED
data/gzipped_tar.gemspec
CHANGED
data/lib/gzipped_tar.rb
CHANGED
data/lib/gzipped_tar/reader.rb
CHANGED
@@ -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
|
data/lib/gzipped_tar/writer.rb
CHANGED
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
|
+
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:
|
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
|
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
|