ffi_libarchive 1.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.
@@ -0,0 +1,227 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Archive
4
+ class Reader < BaseArchive
5
+ private_class_method :new
6
+
7
+ # @param [String] file_name
8
+ # @return [Reader]
9
+ # @yieldparam [Reader]
10
+ def self.open_filename(file_name, command = nil)
11
+ if block_given?
12
+ reader = open_filename file_name, command
13
+ begin
14
+ yield reader
15
+ ensure
16
+ reader.close if reader.respond_to?(:close)
17
+ end
18
+ else
19
+ new file_name: file_name, command: command
20
+ end
21
+ end
22
+
23
+ # @param [String] string
24
+ # @return [Reader]
25
+ # @yieldparam [Reader]
26
+ def self.open_memory(string, command = nil)
27
+ if block_given?
28
+ reader = open_memory string, command
29
+ begin
30
+ yield reader
31
+ ensure
32
+ reader.close if reader.respond_to?(:close)
33
+ end
34
+ else
35
+ new memory: string, command: command
36
+ end
37
+ end
38
+
39
+ # @param [#call] stream
40
+ # @return [Reader]
41
+ # @yieldparam [Reader]
42
+ def self.open_stream(stream, command = nil)
43
+ if block_given?
44
+ reader = open_stream stream, command
45
+ begin
46
+ yield reader
47
+ ensure
48
+ reader.close if reader.respond_to?(:close)
49
+ end
50
+ else
51
+ new reader: stream, command: command
52
+ end
53
+ end
54
+
55
+ # @param [Hash] params
56
+ # @option params [Object] :command
57
+ # @option params [String] :file_name
58
+ # @option params [String] :memory
59
+ # @option params [#call] :reader
60
+ def initialize(params = {})
61
+ super C.method(:archive_read_new), C.method(:archive_read_free)
62
+
63
+ begin
64
+ init_compression params[:command]
65
+ init_format
66
+
67
+ if params[:file_name]
68
+ init_for_filename params[:file_name]
69
+ elsif params[:memory]
70
+ init_for_memory params[:memory]
71
+ elsif params[:reader]
72
+ init_for_stream params[:reader]
73
+ end
74
+ rescue StandardError
75
+ close
76
+ raise
77
+ end
78
+ end
79
+
80
+ # @param [Entry] entry
81
+ # @param [Integer] flags see ::EXTRACT_*
82
+ def extract(entry, flags = 0)
83
+ raise ArgumentError, 'Expected Archive::Entry as first argument' unless entry.is_a? Entry
84
+ raise ArgumentError, 'Expected Integer as second argument' unless flags.is_a? Integer
85
+
86
+ flags |= EXTRACT_FFLAGS
87
+ raise Error, self if C.archive_read_extract(archive, entry.entry, flags) != C::OK
88
+ end
89
+
90
+ # Retrieve the byte offset in UNCOMPRESSED data where last-read header started.
91
+ # @return [Integer]
92
+ def header_position
93
+ C.archive_read_header_position archive
94
+ end
95
+
96
+ # @return [Entry]
97
+ def next_header
98
+ entry_ptr = FFI::MemoryPointer.new(:pointer)
99
+
100
+ case C.archive_read_next_header(archive, entry_ptr)
101
+ when C::OK
102
+ Entry.from_pointer entry_ptr.get_pointer(0)
103
+ when C::EOF
104
+ nil
105
+ else
106
+ raise Error, self
107
+ end
108
+ end
109
+
110
+ # @yieldparam [Entry]
111
+ def each_entry
112
+ while (entry = next_header)
113
+ yield entry
114
+ end
115
+ end
116
+
117
+ # @yieldparam [Entry] entry
118
+ # @yieldparam [String] data
119
+ def each_entry_with_data(size = C::DATA_BUFFER_SIZE)
120
+ while (entry = next_header)
121
+ yield entry, read_data(size)
122
+ end
123
+ end
124
+
125
+ # @yieldparam [Entry] entry
126
+ def each_entry_skip_data
127
+ while (entry = next_header)
128
+ begin
129
+ yield entry
130
+ ensure
131
+ C.archive_read_data_skip archive
132
+ end
133
+ end
134
+ end
135
+
136
+ # @return [String, Integer]
137
+ # @yieldparam [String] chunk
138
+ def read_data(size = C::DATA_BUFFER_SIZE)
139
+ raise ArgumentError, "Buffer size must be > 0 (was: #{size})" if !size.is_a?(Integer) || size <= 0
140
+
141
+ data = nil
142
+ buffer = FFI::MemoryPointer.new(:char, size)
143
+ len = 0
144
+
145
+ while (n = C.archive_read_data(archive, buffer, size)) != 0
146
+ # TODO: C::FATAL, C::WARN, C::RETRY
147
+ raise Error, self if n < 0
148
+
149
+ chunk = buffer.get_bytes(0, n)
150
+ if block_given?
151
+ yield chunk
152
+ elsif data
153
+ data << chunk
154
+ else
155
+ data = chunk.dup
156
+ end
157
+
158
+ len += n
159
+ end
160
+
161
+ data || len
162
+ end
163
+
164
+ # @param [String] file_name
165
+ def save_data(file_name)
166
+ File.open(file_name, 'wb') do |f|
167
+ raise Error, self if C.archive_read_data_into_fd(archive, f.fileno) != C::OK
168
+ end
169
+ end
170
+
171
+ protected
172
+
173
+ def init_compression(command)
174
+ if command && !(cmd = command.to_s).empty?
175
+ raise Error, self if C.archive_read_support_compression_program(archive, cmd) != C::OK
176
+ elsif C.respond_to?(:archive_read_support_filter_all)
177
+ raise Error, self if C.archive_read_support_filter_all(archive) != C::OK
178
+ elsif C.archive_read_support_compression_all(archive) != C::OK
179
+ raise Error, self
180
+ end
181
+ end
182
+
183
+ def init_format
184
+ raise Error, self if C.archive_read_support_format_all(archive) != C::OK
185
+ end
186
+
187
+ BLOCK_SIZE = 1024
188
+
189
+ def init_for_filename(file_name)
190
+ raise Error, self if C.archive_read_open_filename(archive, file_name, BLOCK_SIZE) != C::OK
191
+ end
192
+
193
+ def init_for_memory(string)
194
+ buffer = Utils.get_memory_ptr(string)
195
+ raise Error, self if C.archive_read_open_memory(archive, buffer, string.bytesize) != C::OK
196
+ end
197
+
198
+ def init_for_stream(reader)
199
+ read_callback = proc do |_ar, _client_data, buffer|
200
+ # @type [String]
201
+ data = reader.call
202
+
203
+ if data.is_a?(String)
204
+ buffer.put_pointer 0, Utils.get_memory_ptr(data)
205
+ data.bytesize
206
+ else
207
+ 0
208
+ end
209
+ end
210
+ raise Error, self if C.archive_read_set_read_callback(archive, read_callback) != C::OK
211
+
212
+ if reader.respond_to?(:skip)
213
+ skip_callback = proc { |_ar, _client_data, request| reader.skip(request) }
214
+ raise Error, self if C.archive_read_set_skip_callback(archive, skip_callback) != C::OK
215
+ end
216
+
217
+ if reader.respond_to?(:seek)
218
+ seek_callback = proc { |_ar, _client_data, offset, whence| reader.seek(offset, whence) }
219
+ raise Error, self if C.archive_read_set_seek_callback(archive, seek_callback) != C::OK
220
+ end
221
+
222
+ # Required or open1 will segfault, even though the callback data is not used.
223
+ raise Error, self if C.archive_read_set_callback_data(archive, FFI::Pointer::NULL) != C::OK
224
+ raise Error, self if C.archive_read_open1(archive) != C::OK
225
+ end
226
+ end
227
+ end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ffi'
4
+
5
+ module Archive
6
+ module Utils
7
+ module LibC
8
+ extend FFI::Library
9
+ ffi_lib FFI::Library::LIBC
10
+
11
+ # @!method mbstowcs(dest, src, max)
12
+ # Convert multibyte string to wide-character string
13
+ # @param [FFI::Pointer] dest of :wchar_t*
14
+ # @param [String] src
15
+ # @param [Integer] max Maximum number of :wchar_t characters to write to dest
16
+ # @return [Integer] Number of wide characters written to dest (excluding null-terminated), or -1 if error
17
+ attach_function :mbstowcs, [:pointer, :string, :size_t], :size_t
18
+
19
+ # @!method chdir(path)
20
+ # Changes the current working directory
21
+ # @param [String] path
22
+ # @return [Integer] 0 if successful. Otherwise, -1
23
+ begin
24
+ attach_function :chdir, [:string], :int
25
+ rescue FFI::NotFoundError
26
+ attach_function :chdir, :_chdir, [:string], :int
27
+ end
28
+ end
29
+
30
+ WCHAR_ENCODINGS = {
31
+ 'UTF-16LE' => "!\x00@\x00\x00\x00",
32
+ 'UTF-16BE' => "\x00!\x00@\x00\x00",
33
+ 'UTF-32LE' => "!\x00\x00\x00@\x00",
34
+ 'UTF-32BE' => "\x00\x00\x00!\x00\x00"
35
+ }.freeze
36
+
37
+ class << self
38
+ # @return [String]
39
+ def wchar_encoding
40
+ @wchar_encoding ||=
41
+ begin
42
+ ptr = FFI::MemoryPointer.new :char, 12
43
+ rc = LibC.mbstowcs ptr, '!@', 3
44
+
45
+ str = ptr.get_bytes 0, 6
46
+ enc = WCHAR_ENCODINGS.key(str)
47
+ raise "Unsupported wide-character: #{rc} - #{str.inspect}" unless enc
48
+
49
+ enc
50
+ end
51
+ end
52
+
53
+ # @param [FFI::Pointer] ptr
54
+ # @return [String]
55
+ def read_wide_string(ptr)
56
+ return nil if !ptr.respond_to?(:null?) || ptr.null?
57
+
58
+ if wchar_encoding.include?('32')
59
+ wchar_sz = 4
60
+ wchar_t = :int32
61
+ else
62
+ wchar_sz = 2
63
+ wchar_t = :int16
64
+ end
65
+
66
+ # detect string length in bytes
67
+ sz = ptr.size
68
+ sz -= wchar_sz if sz
69
+
70
+ len = 0
71
+ len += wchar_sz while (!sz || len < sz) && ptr.send("get_#{wchar_t}", len) != 0
72
+
73
+ ptr.get_bytes(0, len).force_encoding(wchar_encoding)
74
+ end
75
+
76
+ # @param [String] str
77
+ # @return [String]
78
+ def to_wide_string(str)
79
+ str ? str.encode(wchar_encoding) : str
80
+ end
81
+
82
+ # @param [String] dir
83
+ # @return [Integer]
84
+ def change_cwd(dir)
85
+ if block_given?
86
+ # @type [String]
87
+ cwd = Dir.getwd
88
+ change_cwd dir
89
+
90
+ begin
91
+ yield
92
+ ensure
93
+ change_cwd cwd
94
+ end
95
+ else
96
+ rc = Dir.chdir dir
97
+ rc != 0 ? rc : LibC.chdir(dir)
98
+ end
99
+ end
100
+
101
+ # @param [String] string MANDATORY
102
+ # @return [FFI::Pointer]
103
+ # @yieldparam [FFI::Pointer]
104
+ def get_memory_ptr(string)
105
+ len = string.bytesize
106
+ if block_given?
107
+ FFI::MemoryPointer.new(:char, len, false) do |ptr|
108
+ ptr.put_bytes(0, string, 0, len)
109
+ yield ptr
110
+ end
111
+ else
112
+ ptr = FFI::MemoryPointer.new(:char, len, false)
113
+ ptr.put_bytes(0, string, 0, len)
114
+ ptr
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,178 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Archive
4
+ class Writer < BaseArchive
5
+ private_class_method :new
6
+
7
+ # @param [String] file_name
8
+ # @return [Writer]
9
+ # @yieldparam [Writer]
10
+ def self.open_filename(file_name, compression, format)
11
+ if block_given?
12
+ writer = open_filename file_name, compression, format
13
+
14
+ begin
15
+ yield writer
16
+ ensure
17
+ writer.close if writer.respond_to?(:close)
18
+ end
19
+ else
20
+ new file_name: file_name, compression: compression, format: format
21
+ end
22
+ end
23
+
24
+ # @param [String] string
25
+ # @return [Writer]
26
+ # @yieldparam [Writer]
27
+ def self.open_memory(string, compression, format)
28
+ if block_given?
29
+ writer = open_memory string, compression, format
30
+
31
+ begin
32
+ yield writer
33
+ ensure
34
+ writer.close if writer.respond_to?(:close)
35
+ end
36
+ else
37
+ new memory: string, compression: compression, format: format
38
+ end
39
+ end
40
+
41
+ # @param [Hash] params
42
+ # @option params [Object] :compression
43
+ # @option params [Object] :format
44
+ # @option params [String] :file_name
45
+ # @option params [String] :memory
46
+ def initialize(params = {})
47
+ super C.method(:archive_write_new), C.method(:archive_write_free)
48
+
49
+ begin
50
+ init_compression params[:compression]
51
+ init_format params[:format]
52
+
53
+ if params[:file_name]
54
+ init_for_filename params[:file_name]
55
+ elsif params[:memory]
56
+ init_for_memory params[:memory]
57
+ end
58
+ rescue StandardError
59
+ close
60
+ raise
61
+ end
62
+ end
63
+
64
+ # @return [Entry]
65
+ # @yieldparam [Entry]
66
+ def new_entry
67
+ entry = Entry.new
68
+
69
+ if block_given?
70
+ begin
71
+ yield entry
72
+ ensure
73
+ entry.close
74
+ end
75
+ else
76
+ entry
77
+ end
78
+ end
79
+
80
+ # @raise [ArgumentError] If no block given
81
+ # @return [NilClass]
82
+ # @yieldparam [Entry]
83
+ # @yieldreturn [String]
84
+ def add_entry
85
+ raise ArgumentError, 'No block given' unless block_given?
86
+
87
+ entry = Entry.new
88
+ begin
89
+ data = yield entry
90
+
91
+ if data
92
+ entry.size = data.bytesize
93
+
94
+ write_header entry
95
+ write_data data
96
+ else
97
+ write_header entry
98
+ end
99
+
100
+ nil
101
+ ensure
102
+ entry.close
103
+ end
104
+ end
105
+
106
+ # @param [Array<String>] args
107
+ # @return [Integer]
108
+ # @raise [ArgumentError]
109
+ # @yieldreturn [String]
110
+ def write_data(*args)
111
+ if block_given?
112
+ raise ArgumentError, 'Not support arguments when block given' unless args.empty?
113
+
114
+ len = 0
115
+ loop do
116
+ str = yield len
117
+ n = str.is_a?(String) ? C.archive_write_data(archive, Utils.get_memory_ptr(str), str.bytesize) : 0
118
+
119
+ raise Error, self if n < 0
120
+ break if n.zero?
121
+
122
+ len += n
123
+ end
124
+
125
+ len
126
+ else
127
+ str = args[0]
128
+ raise ArgumentError, 'Invalid String argument' unless str.is_a?(String)
129
+
130
+ n = C.archive_write_data(archive, Utils.get_memory_ptr(str), str.bytesize)
131
+ raise Error, self if n < 0
132
+
133
+ n
134
+ end
135
+ end
136
+
137
+ # @param [Entry] entry
138
+ def write_header(entry)
139
+ raise Error, self if C.archive_write_header(archive, entry.entry) != C::OK
140
+ end
141
+
142
+ protected
143
+
144
+ def init_compression(compression)
145
+ raise ArgumentError, 'Missing :compression argument' if !compression || compression.to_s.empty?
146
+
147
+ unless compression.is_a?(Integer) || compression.is_a?(String)
148
+ prefix = C.respond_to?(:archive_write_add_filter) ? 'FILTER' : 'COMPRESSION'
149
+ compression = Archive.const_get("#{prefix}_#{compression}".upcase)
150
+ end
151
+
152
+ raise Error, self if C.archive_write_set_compression(archive, compression) != C::OK
153
+ end
154
+
155
+ def init_format(format)
156
+ raise ArgumentError, 'Missing :format argument' if !format || format.to_s.empty?
157
+
158
+ format = Archive.const_get("FORMAT_#{format}".upcase) unless format.is_a?(Integer)
159
+ raise Error, self if C.archive_write_set_format(archive, format) != C::OK
160
+ end
161
+
162
+ def init_for_filename(file_name)
163
+ raise Error, self if C.archive_write_open_filename(archive, file_name) != C::OK
164
+ end
165
+
166
+ def init_for_memory(memory)
167
+ C.archive_write_set_bytes_in_last_block(archive, 1) if C.archive_write_get_bytes_in_last_block(archive) < 0
168
+
169
+ write_callback = proc do |_ar, _client_data, buffer, length|
170
+ memory << buffer.get_bytes(0, length)
171
+ length
172
+ end
173
+
174
+ null_ptr = FFI::Pointer::NULL
175
+ raise Error, self if C.archive_write_open(archive, null_ptr, null_ptr, write_callback, null_ptr) != C::OK
176
+ end
177
+ end
178
+ end