arr-pm 0.0.10 → 0.0.12

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: 60fcb2287ddbb0f718255da5beb0033d9459be16
4
- data.tar.gz: 1586517c23d99e2c5dead4a45128cfd49c9ff87c
2
+ SHA256:
3
+ metadata.gz: 1b18d6ce9e276f133f3456d3f680c9727936e0be921d1f2492f9541aeefcbca9
4
+ data.tar.gz: d5bca7b579c4ff1f28b411a0d5195a83d34c023a749d563cbc95b9cb0bb5716b
5
5
  SHA512:
6
- metadata.gz: e44130d06daa88d4b84b0c800fb54dd8ca8cb4f0b75185c98e450e6b241d30fe666e4a491c6a8113c837d162b95783dcd805c293b1af5fc0d99b2e74ad86eb30
7
- data.tar.gz: 1c7b02c83d66bcc5964faeeab66b3cc179acda31225039ba60ef0e05311ab9824887830e2ee568ef7946e18d594b274e11141fde2b92eb4a3418c3b85123abd0
6
+ metadata.gz: 58e476d730ac09d22ea1ee60cf463f3cc26b4cb3cc99f1f8b44d7cd323084d3ca1208a550b069f56afb2414759b9f3e5475716dfee0f2363ae5a10dc0235ae38
7
+ data.tar.gz: 361d8dfca458258a617e71153ff8e757c4f8963abe5d07d9020a120407f52059a2f3b8bd0a3e2495f9764a2cc02f18d4755d2ba532f670d16c1adfc3eb85a3cb
data/CHANGELOG.md ADDED
@@ -0,0 +1,18 @@
1
+
2
+
3
+ # v0.0.12
4
+
5
+ * Security: Fixed a safety problem which would allow for arbitrary shell execution when invoking `RPM::File#extract` or `RPM::File#files`. (Jordan Sissel, @joernchen; #14, #15)
6
+ * This library now has no external dependencies. (Jordan Sissel, #18)
7
+ * `RPM::File#extract` now correctly works when the target directory contains spaces or other special characters. (@joernchen, #19)
8
+ * Listing files (`RPM::File#files`) no longer requires external tools like `cpio` (Jordan Sissel, #17)
9
+
10
+
11
+ # v0.0.11
12
+
13
+ * Support Ruby 3.0 (Alexey Morozov, #12)
14
+ * Fix bug caused when listing config_files on an rpm with no config files. (Daniel Jay Haskin, #7)
15
+
16
+ # Older versions
17
+
18
+ Changelog not tracked for older versions.
data/arr-pm.gemspec CHANGED
@@ -2,14 +2,13 @@ Gem::Specification.new do |spec|
2
2
  files = %x{git ls-files}.split("\n")
3
3
 
4
4
  spec.name = "arr-pm"
5
- spec.version = "0.0.10"
5
+ spec.version = "0.0.12"
6
6
  spec.summary = "RPM reader and writer library"
7
7
  spec.description = "This library allows to you to read and write rpm " \
8
8
  "packages. Written in pure ruby because librpm is not available " \
9
9
  "on all systems"
10
10
  spec.license = "Apache 2"
11
11
 
12
- spec.add_dependency "cabin", ">0" # for logging. apache 2 license
13
12
  spec.files = files
14
13
  spec.require_paths << "lib"
15
14
  spec.bindir = "bin"
@@ -18,6 +17,9 @@ Gem::Specification.new do |spec|
18
17
  spec.email = ["jls@semicomplete.com"]
19
18
 
20
19
  spec.add_development_dependency "flores", ">0"
20
+ spec.add_development_dependency "rspec", ">3.0.0"
21
+ spec.add_development_dependency "stud", ">=0.0.23"
22
+ spec.add_development_dependency "insist", ">=1.0.0"
21
23
  #spec.homepage = "..."
22
24
  end
23
25
 
@@ -1,9 +1,7 @@
1
1
  require File.expand_path(File.join(File.dirname(__FILE__), "..", "namespace"))
2
2
  require File.join(File.dirname(__FILE__), "tag")
3
- require "cabin"
4
3
 
5
4
  class RPM::File::Header
6
- include Cabin::Inspectable
7
5
  attr_reader :tags
8
6
  attr_reader :length
9
7
 
@@ -12,16 +10,8 @@ class RPM::File::Header
12
10
  attr_accessor :data_length # rpmlib calls this field 'dl' unhelpfully
13
11
 
14
12
  HEADER_SIGNED_TYPE = 5
15
-
16
- if RUBY_VERSION =~ /^2\./
17
- # Ruby 2 forces all strings to be UTF-8, so "\x01" becomes "\u0001"
18
- # which is two bytes 00 01 which is not what we want. I can't find
19
- # a sane way to create a string without this madness in Ruby 2,
20
- # so let's just pack two 4-byte integers and go about our day.
21
- HEADER_MAGIC = [0x8eade801, 0x00000000].pack("NN")
22
- else
23
- HEADER_MAGIC = "\x8e\xad\xe8\x01\x00\x00\x00\x00"
24
- end
13
+ HEADER_MAGIC = "\x8e\xad\xe8\x01\x00\x00\x00\x00".force_encoding("BINARY")
14
+
25
15
  # magic + index_count + data_length
26
16
  HEADER_HEADER_LENGTH = HEADER_MAGIC.length + 4 + 4
27
17
  TAG_ENTRY_SIZE = 16 # tag id, type, offset, count == 16 bytes
@@ -1,8 +1,6 @@
1
1
  require File.expand_path(File.join(File.dirname(__FILE__), "..", "namespace"))
2
- require "cabin"
3
2
 
4
3
  class RPM::File::Lead
5
- include Cabin::Inspectable
6
4
 
7
5
  #struct rpmlead {
8
6
  attr_accessor :magic #unsigned char magic[4];
@@ -1,8 +1,6 @@
1
1
  require File.expand_path(File.join(File.dirname(__FILE__), "..", "namespace"))
2
- require "cabin"
3
2
 
4
3
  class RPM::File::Tag
5
- include Cabin::Inspectable
6
4
 
7
5
  attr_accessor :tag
8
6
  attr_accessor :type
data/lib/arr-pm/file.rb CHANGED
@@ -3,6 +3,7 @@ require File.join(File.dirname(__FILE__), "file", "header")
3
3
  require File.join(File.dirname(__FILE__), "file", "lead")
4
4
  require File.join(File.dirname(__FILE__), "file", "tag")
5
5
  require "fcntl"
6
+ require "shellwords"
6
7
 
7
8
  # Much of the code here is derived from knowledge gained by reading the rpm
8
9
  # source code, but mostly it started making more sense after reading this site:
@@ -88,6 +89,14 @@ class RPM::File
88
89
  return @payload
89
90
  end # def payload
90
91
 
92
+ def valid_compressor?(name)
93
+ # I scanned rpm's rpmio.c for payload implementation names and found the following.
94
+ # sed -rne '/struct FDIO_s \w+ *= *\{/{ n; s/^.*"(\w+)",$/\1/p }' rpmio/rpmio.c
95
+ # It's possible this misses some supported rpm payload compressors.
96
+
97
+ [ "gzip", "bzip2", "xz", "lzma", "zstd" ].include?(name)
98
+ end
99
+
91
100
  # Extract this RPM to a target directory.
92
101
  #
93
102
  # This should have roughly the same effect as:
@@ -97,8 +106,13 @@ class RPM::File
97
106
  if !File.directory?(target)
98
107
  raise Errno::ENOENT.new(target)
99
108
  end
109
+
110
+ compressor = tags[:payloadcompressor]
111
+ if !valid_compressor?(compressor)
112
+ raise "Cannot decompress. This RPM uses an invalid compressor '#{compressor}'"
113
+ end
100
114
 
101
- extractor = IO.popen("#{tags[:payloadcompressor]} -d | (cd #{target}; cpio -i --quiet --make-directories)", "w")
115
+ extractor = IO.popen("#{compressor} -d | (cd #{Shellwords.escape(target)}; cpio -i --quiet --make-directories)", "w")
102
116
  buffer = ""
103
117
  begin
104
118
  buffer.force_encoding("BINARY")
@@ -179,10 +193,12 @@ class RPM::File
179
193
  results = []
180
194
  # short-circuit if there's no :fileflags tag
181
195
  return results unless tags.include?(:fileflags)
182
- tags[:fileflags].each_with_index do |flag, i|
183
- # The :fileflags (and other :file... tags) are an array, in order of
184
- # files in the rpm payload, we want a list of paths of config files.
185
- results << files[i] if mask?(flag, FLAG_CONFIG_FILE)
196
+ if !tags[:fileflags].nil?
197
+ tags[:fileflags].each_with_index do |flag, i|
198
+ # The :fileflags (and other :file... tags) are an array, in order of
199
+ # files in the rpm payload, we want a list of paths of config files.
200
+ results << files[i] if mask?(flag, FLAG_CONFIG_FILE)
201
+ end
186
202
  end
187
203
  return results
188
204
  end # def config_files
@@ -193,49 +209,13 @@ class RPM::File
193
209
  #
194
210
  # % rpm2cpio blah.rpm | cpio -it
195
211
  def files
196
- return @files unless @files.nil?
197
-
198
- lister = IO.popen("#{tags[:payloadcompressor]} -d | cpio -it --quiet", "r+")
199
- buffer = ""
200
- begin
201
- buffer.force_encoding("BINARY")
202
- rescue NoMethodError
203
- # Do Nothing
204
- end
205
- payload_fd = payload.clone
206
- output = ""
207
- loop do
208
- data = payload_fd.read(16384, buffer)
209
- break if data.nil? # listerextractor.write(data)
210
- lister.write(data)
211
-
212
- # Read output from the pipe.
213
- begin
214
- output << lister.read_nonblock(16384)
215
- rescue Errno::EAGAIN
216
- # Nothing to read, move on!
217
- end
218
- end
219
- lister.close_write
220
-
221
- # Read remaining output
222
- begin
223
- output << lister.read
224
- rescue Errno::EAGAIN
225
- # Because read_nonblock enables NONBLOCK the 'lister' fd,
226
- # and we may have invoked a read *before* cpio has started
227
- # writing, let's keep retrying this read until we get an EOF
228
- retry
229
- rescue EOFError
230
- # At EOF, hurray! We're done reading.
231
- end
232
-
233
- # Split output by newline and strip leading "."
234
- @files = output.split("\n").collect { |s| s.gsub(/^\./, "") }
235
- return @files
236
- ensure
237
- lister.close unless lister.nil?
238
- payload_fd.close unless payload_fd.nil?
212
+ # RPM stores the file metadata split across multiple tags.
213
+ # A single path's filename (with no directories) is stored in the "basename" tag.
214
+ # The directory a file lives in is stored in the "dirnames" tag
215
+ # We can find out what directory a file is in using the "dirindexes" tag.
216
+ #
217
+ # We can join each entry of dirnames and basenames to make the full filename.
218
+ return tags[:basenames].zip(tags[:dirindexes]).map { |name, i| File.join(tags[:dirnames][i], name) }
239
219
  end # def files
240
220
 
241
221
  def mask?(value, mask)
@@ -1,3 +1,9 @@
1
1
  class RPM
2
2
  class File; end
3
3
  end
4
+
5
+ module ArrPM
6
+ module V2
7
+ class Package; end
8
+ end
9
+ end
@@ -0,0 +1,32 @@
1
+ require "arr-pm/namespace"
2
+
3
+ module ArrPM::V2::Architecture
4
+
5
+ NOARCH = 0
6
+ I386 = 1
7
+ ALPHA = 2
8
+ SPARC = 3
9
+ MIPS = 4
10
+ PPC = 5
11
+ M68K = 6
12
+ IP = 7
13
+ RS6000 = 8
14
+ IA64 = 9
15
+ SPARC64 = 10
16
+ MIPSEL = 11
17
+ ARM = 12
18
+ MK68KMINT = 13
19
+ S390 = 14
20
+ S390X = 15
21
+ PPC64 = 16
22
+ SH = 17
23
+ XTENSA = 18
24
+ X86_64 = 19
25
+
26
+ module_function
27
+
28
+ # Is a given rpm architecture value valid?
29
+ def valid?(value)
30
+ return value >= 0 && value <= 19
31
+ end
32
+ end
@@ -0,0 +1,32 @@
1
+ # encoding: utf-8
2
+
3
+ require "arr-pm/namespace"
4
+ require "arr-pm/v2/format"
5
+
6
+ module ArrPM::V2::Error
7
+ class Base < StandardError; end
8
+
9
+ class InvalidMagicValue < Base
10
+ def initialize(value)
11
+ super("Got invalid magic value '#{value}'. Expected #{ArrPM::V2::Format::MAGIC}.")
12
+ end
13
+ end
14
+
15
+ class InvalidHeaderMagicValue < Base
16
+ def initialize(value)
17
+ super("Got invalid magic value '#{value}'. Expected #{ArrPM::V2::HeaderHeader::MAGIC}.")
18
+ end
19
+ end
20
+
21
+ class EmptyFile < Base; end
22
+ class ShortFile < Base; end
23
+ class InvalidVersion < Base; end
24
+ class InvalidType < Base
25
+ def initialize(value)
26
+ super("Invalid type: #{value.inspect}")
27
+ end
28
+ end
29
+ class InvalidName < Base; end
30
+ class InvalidArchitecture < Base; end
31
+
32
+ end
@@ -0,0 +1,16 @@
1
+ # encoding: utf-8
2
+
3
+ require "arr-pm/namespace"
4
+
5
+ module ArrPM::V2::Format
6
+ MAGIC = [0x8e, 0xad, 0xe8]
7
+ MAGIC_LENGTH = MAGIC.count
8
+ MAGIC_STRING = MAGIC.pack("C#{MAGIC_LENGTH}")
9
+
10
+ module_function
11
+ def valid_magic?(magic)
12
+ magic = magic.bytes if magic.is_a?(String)
13
+
14
+ magic == MAGIC
15
+ end
16
+ end
@@ -0,0 +1,35 @@
1
+ # encoding: utf-8
2
+
3
+ require "arr-pm/namespace"
4
+ require "arr-pm/v2/format"
5
+ require "arr-pm/v2/header_header"
6
+ require "arr-pm/v2/tag"
7
+ require "arr-pm/v2/error"
8
+
9
+ class ArrPM::V2::Header
10
+ attr_reader :tags
11
+
12
+ def load(io)
13
+ headerheader = ArrPM::V2::HeaderHeader.new
14
+ headerheader.load(io)
15
+ headerdata = io.read(headerheader.entries * 16)
16
+ tagdata = io.read(headerheader.bytesize)
17
+ parse(headerdata, headerheader.entries, tagdata)
18
+
19
+ # signature headers are padded up to an 8-byte boundar, details here:
20
+ # http://rpm.org/gitweb?p=rpm.git;a=blob;f=lib/signature.c;h=63e59c00f255a538e48cbc8b0cf3b9bd4a4dbd56;hb=HEAD#l204
21
+ # Throw away the pad.
22
+ io.read(tagdata.length % 8)
23
+ end
24
+
25
+ def parse(data, entry_count, tagdata)
26
+ @tags = entry_count.times.collect do |i|
27
+ tag_number, type_number, offset, count = data[i * 16, 16].unpack("NNNN")
28
+
29
+ tag = ArrPM::V2::Tag.new(tag_number, type_number)
30
+ tag.parse(tagdata, offset, count)
31
+ tag
32
+ end
33
+ nil
34
+ end
35
+ end
@@ -0,0 +1,36 @@
1
+ # encoding: utf-8
2
+
3
+ require "arr-pm/namespace"
4
+ require "arr-pm/v2/format"
5
+ require "arr-pm/v2/error"
6
+
7
+ # The header of an rpm has ... a header. Funky naming :)
8
+ class ArrPM::V2::HeaderHeader
9
+ MAGIC = [ 0x8e, 0xad, 0xe8 ]
10
+ MAGIC_LENGTH = MAGIC.count
11
+
12
+ attr_accessor :version, :entries, :bytesize
13
+
14
+ def load(io)
15
+ data = io.read(16)
16
+ parse(data)
17
+ end
18
+
19
+ def parse(data)
20
+ magic, version, reserved, entries, bytesize = data.unpack("a3Ca4NN")
21
+ self.class.validate_magic(magic.bytes)
22
+
23
+ @version = version
24
+ @entries = entries
25
+ @bytesize = bytesize
26
+ nil
27
+ end
28
+
29
+ def dump
30
+ [magic, 1, 0, @entries, @bytesize].pack("a3Ca4NN")
31
+ end
32
+
33
+ def self.validate_magic(value)
34
+ raise ArrPM::V2::Error::InvalidHeaderMagicValue, value if value != MAGIC
35
+ end
36
+ end
@@ -0,0 +1,121 @@
1
+ # encoding: utf-8
2
+
3
+ require "arr-pm/namespace"
4
+ require "arr-pm/v2/format"
5
+ require "arr-pm/v2/error"
6
+
7
+ class ArrPM::V2::Lead
8
+ LENGTH = 96
9
+ MAGIC = [ 0xed, 0xab, 0xee, 0xdb ]
10
+ MAGIC_LENGTH = MAGIC.count
11
+
12
+ SIGNED_TYPE = 5
13
+
14
+ attr_accessor :major, :minor, :type, :architecture, :name, :os, :signature_type, :reserved
15
+
16
+ def validate
17
+ self.class.validate_type(type)
18
+ self.class.validate_architecture(architecture)
19
+ if name.length > 65
20
+ raise ArrPM::V2::Error::InvalidName, "Name is longer than 65 chracters. This is invalid."
21
+ end
22
+ end
23
+
24
+ def dump(io)
25
+ io.write(serialize)
26
+ end
27
+
28
+ def serialize
29
+ validate
30
+ [ *MAGIC, major, minor, type, architecture, name, os, signature_type, *reserved ].pack("C4CCnnZ66nnC16")
31
+ end
32
+
33
+ def load(io)
34
+ data = io.read(LENGTH)
35
+ parse(data)
36
+ end
37
+
38
+ def parse(bytestring)
39
+ raise ArrPM::V2::Error::EmptyFile if bytestring.nil?
40
+ data = bytestring.bytes
41
+
42
+ @magic = self.class.parse_magic(data)
43
+ @major, @minor = self.class.parse_version(data)
44
+ @type = self.class.parse_type(data)
45
+ @architecture = self.class.parse_architecture(data)
46
+ @name = self.class.parse_name(data)
47
+ @os = self.class.parse_os(data)
48
+ @signature_type = self.class.parse_signature_type(data)
49
+ @reserved = self.class.parse_reserved(data)
50
+ self
51
+ end
52
+
53
+ def signature?
54
+ @signature_type == SIGNED_TYPE
55
+ end
56
+
57
+ def self.valid_version?(version)
58
+ version == 1
59
+ end
60
+
61
+ def self.parse_magic(data)
62
+ magic = data[0, MAGIC_LENGTH]
63
+ validate_magic(magic)
64
+ magic
65
+ end
66
+
67
+ def self.validate_magic(magic)
68
+ raise ArrPM::V2::Error::InvalidMagicValue, magic unless magic == MAGIC
69
+ end
70
+
71
+ def self.parse_version(data)
72
+ offset = MAGIC_LENGTH
73
+ major, minor = data[offset, 2]
74
+ return major, minor
75
+ end
76
+
77
+ def self.parse_type(data)
78
+ offset = MAGIC_LENGTH + 2
79
+ type = data[offset, 2].pack("CC").unpack("n").first
80
+ validate_type(type)
81
+ type
82
+ end
83
+
84
+ def self.validate_type(type)
85
+ raise ArrPM::V2::Error::InvalidType, type unless ArrPM::V2::Type.valid?(type)
86
+ end
87
+
88
+ def self.parse_architecture(data)
89
+ offset = MAGIC_LENGTH + 4
90
+ architecture = data[offset, 2].pack("C*").unpack("n").first
91
+ validate_architecture(architecture)
92
+ architecture
93
+ end
94
+
95
+ def self.validate_architecture(architecture)
96
+ raise ArrPM::V2::Error::InvalidArchitecture unless ArrPM::V2::Architecture.valid?(architecture)
97
+ end
98
+
99
+ def self.parse_name(data)
100
+ offset = MAGIC_LENGTH + 6
101
+ name = data[offset, 66]
102
+ length = name.find_index(0) # find the first null byte
103
+ raise ArrPM::V2::Error::InvalidName unless length
104
+ return name[0, length].pack("C*")
105
+ end
106
+
107
+ def self.parse_os(data)
108
+ offset = MAGIC_LENGTH + 72
109
+ data[offset, 2].pack("C*").unpack("n").first
110
+ end
111
+
112
+ def self.parse_signature_type(data)
113
+ offset = MAGIC_LENGTH + 74
114
+ data[offset, 2].pack("C*").unpack("n").first
115
+ end
116
+
117
+ def self.parse_reserved(data)
118
+ offset = MAGIC_LENGTH + 76
119
+ data[offset, 16]
120
+ end
121
+ end
@@ -0,0 +1,18 @@
1
+ # encoding: utf-8
2
+
3
+ require "arr-pm/namespace"
4
+
5
+ class ArrPM::V2::RPM
6
+ attr_accessor :name
7
+ attr_accessor :epoch
8
+ attr_accessor :version
9
+ attr_accessor :release
10
+
11
+ def initialize
12
+ defaults
13
+ end
14
+
15
+ def defaults
16
+ @type = Type::BINARY
17
+ end
18
+ end
@@ -0,0 +1,295 @@
1
+ require "arr-pm/namespace"
2
+
3
+ class ArrPM::V2::Tag
4
+ module Type
5
+ NULL = 0
6
+ CHAR = 1
7
+ INT8 = 2
8
+ INT16 = 3
9
+ INT32 = 4
10
+ INT64 = 5
11
+ STRING = 6
12
+ BINARY = 7
13
+ STRING_ARRAY = 8
14
+ I18NSTRING = 9
15
+
16
+ TYPE_MAP = Hash[constants.collect { |c| [const_get(c), c] }]
17
+
18
+ def self.parse(data, type, offset, count)
19
+ case type
20
+ when NULL
21
+ nil
22
+ when CHAR
23
+ data[offset, count].unpack("A#{count}")
24
+ when INT8
25
+ data[offset, count].unpack("C" * count)
26
+ when INT16
27
+ data[offset, 2 * count].unpack("n" * count)
28
+ when INT32
29
+ data[offset, 4 * count].unpack("N" * count)
30
+ when INT64
31
+ a, b = data[offset, 8].unpack("NN")
32
+ a << 32 + b
33
+ when STRING, I18NSTRING
34
+ data[offset..-1][/^[^\0]*/]
35
+ when BINARY
36
+ data[offset, count]
37
+ when STRING_ARRAY
38
+ data[offset..-1].split("\0")[0...count]
39
+ else
40
+ raise ArrPM::V2::Error::InvalidType, type
41
+ end
42
+ end
43
+ end # module Type
44
+
45
+ HEADERIMAGE = 61
46
+ HEADERSIGNATURES = 62
47
+ HEADERIMMUTABLE = 63
48
+ HEADERREGIONS = 64
49
+ HEADERI18NTABLE = 100
50
+ SIG_BASE = 256
51
+
52
+ SIGSIZE = 257
53
+ SIGLEMD5_1 = 258
54
+ SIGPGP = 259
55
+ SIGLEMD5_2 = 260
56
+ SIGMD5 = 261
57
+ SIGGPG = 262
58
+ SIGPGP5 = 263
59
+ BADSHA1_1 = 264
60
+ BADSHA1_2 = 265
61
+ PUBKEYS = 266
62
+ DSAHEADER = 267
63
+ RSAHEADER = 268
64
+ SHA1HEADER = 269
65
+ LONGSIGSIZE = 270
66
+ LONGARCHIVESIZE = 271
67
+
68
+ NAME = 1000
69
+ VERSION = 1001
70
+ RELEASE = 1002
71
+ EPOCH = 1003
72
+ SUMMARY = 1004
73
+ DESCRIPTION = 1005
74
+ BUILDTIME = 1006
75
+ BUILDHOST = 1007
76
+ INSTALLTIME = 1008
77
+ SIZE = 1009
78
+ DISTRIBUTION = 1010
79
+ VENDOR = 1011
80
+ GIF = 1012
81
+ XPM = 1013
82
+ LICENSE = 1014
83
+ PACKAGER = 1015
84
+ GROUP = 1016
85
+ CHANGELOG = 1017
86
+ SOURCE = 1018
87
+ PATCH = 1019
88
+ URL = 1020
89
+ OS = 1021
90
+ ARCH = 1022
91
+ PREIN = 1023
92
+ POSTIN = 1024
93
+ PREUN = 1025
94
+ POSTUN = 1026
95
+ OLDFILENAMES = 1027
96
+ FILESIZES = 1028
97
+ FILESTATES = 1029
98
+ FILEMODES = 1030
99
+ FILEUIDS = 1031
100
+ FILEGIDS = 1032
101
+ FILERDEVS = 1033
102
+ FILEMTIMES = 1034
103
+ FILEDIGESTS = 1035
104
+ FILELINKTOS = 1036
105
+ FILEFLAGS = 1037
106
+ ROOT = 1038
107
+ FILEUSERNAME = 1039
108
+ FILEGROUPNAME = 1040
109
+ EXCLUDE = 1041
110
+ EXCLUSIVE = 1042
111
+ ICON = 1043
112
+ SOURCERPM = 1044
113
+ FILEVERIFYFLAGS = 1045
114
+ ARCHIVESIZE = 1046
115
+ PROVIDENAME = 1047
116
+ REQUIREFLAGS = 1048
117
+ REQUIRENAME = 1049
118
+ REQUIREVERSION = 1050
119
+ NOSOURCE = 1051
120
+ NOPATCH = 1052
121
+ CONFLICTFLAGS = 1053
122
+ CONFLICTNAME = 1054
123
+ CONFLICTVERSION = 1055
124
+ DEFAULTPREFIX = 1056
125
+ BUILDROOT = 1057
126
+ INSTALLPREFIX = 1058
127
+ EXCLUDEARCH = 1059
128
+ EXCLUDEOS = 1060
129
+ EXCLUSIVEARCH = 1061
130
+ EXCLUSIVEOS = 1062
131
+ AUTOREQPROV = 1063
132
+ RPMVERSION = 1064
133
+ TRIGGERSCRIPTS = 1065
134
+ TRIGGERNAME = 1066
135
+ TRIGGERVERSION = 1067
136
+ TRIGGERFLAGS = 1068
137
+ TRIGGERINDEX = 1069
138
+ VERIFYSCRIPT = 1079
139
+ CHANGELOGTIME = 1080
140
+ CHANGELOGNAME = 1081
141
+ CHANGELOGTEXT = 1082
142
+ BROKENMD5 = 1083
143
+ PREREQ = 1084
144
+ PREINPROG = 1085
145
+ POSTINPROG = 1086
146
+ PREUNPROG = 1087
147
+ POSTUNPROG = 1088
148
+ BUILDARCHS = 1089
149
+ OBSOLETENAME = 1090
150
+ VERIFYSCRIPTPROG = 1091
151
+ TRIGGERSCRIPTPROG = 1092
152
+ DOCDIR = 1093
153
+ COOKIE = 1094
154
+ FILEDEVICES = 1095
155
+ FILEINODES = 1096
156
+ FILELANGS = 1097
157
+ PREFIXES = 1098
158
+ INSTPREFIXES = 1099
159
+ TRIGGERIN = 1100
160
+ TRIGGERUN = 1101
161
+ TRIGGERPOSTUN = 1102
162
+ AUTOREQ = 1103
163
+ AUTOPROV = 1104
164
+ CAPABILITY = 1105
165
+ SOURCEPACKAGE = 1106
166
+ OLDORIGFILENAMES = 1107
167
+ BUILDPREREQ = 1108
168
+ BUILDREQUIRES = 1109
169
+ BUILDCONFLICTS = 1110
170
+ BUILDMACROS = 1111
171
+ PROVIDEFLAGS = 1112
172
+ PROVIDEVERSION = 1113
173
+ OBSOLETEFLAGS = 1114
174
+ OBSOLETEVERSION = 1115
175
+ DIRINDEXES = 1116
176
+ BASENAMES = 1117
177
+ DIRNAMES = 1118
178
+ ORIGDIRINDEXES = 1119
179
+ ORIGBASENAMES = 1120
180
+ ORIGDIRNAMES = 1121
181
+ OPTFLAGS = 1122
182
+ DISTURL = 1123
183
+ PAYLOADFORMAT = 1124
184
+ PAYLOADCOMPRESSOR = 1125
185
+ PAYLOADFLAGS = 1126
186
+ INSTALLCOLOR = 1127
187
+ INSTALLTID = 1128
188
+ REMOVETID = 1129
189
+ SHA1RHN = 1130
190
+ RHNPLATFORM = 1131
191
+ PLATFORM = 1132
192
+ PATCHESNAME = 1133
193
+ PATCHESFLAGS = 1134
194
+ PATCHESVERSION = 1135
195
+ CACHECTIME = 1136
196
+ CACHEPKGPATH = 1137
197
+ CACHEPKGSIZE = 1138
198
+ CACHEPKGMTIME = 1139
199
+ FILECOLORS = 1140
200
+ FILECLASS = 1141
201
+ CLASSDICT = 1142
202
+ FILEDEPENDSX = 1143
203
+ FILEDEPENDSN = 1144
204
+ DEPENDSDICT = 1145
205
+ SOURCEPKGID = 1146
206
+ FILECONTEXTS = 1147
207
+ FSCONTEXTS = 1148
208
+ RECONTEXTS = 1149
209
+ POLICIES = 1150
210
+ PRETRANS = 1151
211
+ POSTTRANS = 1152
212
+ PRETRANSPROG = 1153
213
+ POSTTRANSPROG = 1154
214
+ DISTTAG = 1155
215
+ SUGGESTSNAME = 1156
216
+ SUGGESTSVERSION = 1157
217
+ SUGGESTSFLAGS = 1158
218
+ ENHANCESNAME = 1159
219
+ ENHANCESVERSION = 1160
220
+ ENHANCESFLAGS = 1161
221
+ PRIORITY = 1162
222
+ CVSID = 1163
223
+ BLINKPKGID = 1164
224
+ BLINKHDRID = 1165
225
+ BLINKNEVRA = 1166
226
+ FLINKPKGID = 1167
227
+ FLINKHDRID = 1168
228
+ FLINKNEVRA = 1169
229
+ PACKAGEORIGIN = 1170
230
+ TRIGGERPREIN = 1171
231
+ BUILDSUGGESTS = 1172
232
+ BUILDENHANCES = 1173
233
+ SCRIPTSTATES = 1174
234
+ SCRIPTMETRICS = 1175
235
+ BUILDCPUCLOCK = 1176
236
+ FILEDIGESTALGOS = 1177
237
+ VARIANTS = 1178
238
+ XMAJOR = 1179
239
+ XMINOR = 1180
240
+ REPOTAG = 1181
241
+ KEYWORDS = 1182
242
+ BUILDPLATFORMS = 1183
243
+ PACKAGECOLOR = 1184
244
+ PACKAGEPREFCOLOR = 1185
245
+ XATTRSDICT = 1186
246
+ FILEXATTRSX = 1187
247
+ DEPATTRSDICT = 1188
248
+ CONFLICTATTRSX = 1189
249
+ OBSOLETEATTRSX = 1190
250
+ PROVIDEATTRSX = 1191
251
+ REQUIREATTRSX = 1192
252
+ BUILDPROVIDES = 1193
253
+ BUILDOBSOLETES = 1194
254
+ DBINSTANCE = 1195
255
+ NVRA = 1196
256
+ FILENAMES = 5000
257
+ FILEPROVIDE = 5001
258
+ FILEREQUIRE = 5002
259
+ FSNAMES = 5003
260
+ FSSIZES = 5004
261
+ TRIGGERCONDS = 5005
262
+ TRIGGERTYPE = 5006
263
+ ORIGFILENAMES = 5007
264
+ LONGFILESIZES = 5008
265
+ LONGSIZE = 5009
266
+ FILECAPS = 5010
267
+ FILEDIGESTALGO = 5011
268
+ BUGURL = 5012
269
+ EVR = 5013
270
+ NVR = 5014
271
+ NEVR = 5015
272
+ NEVRA = 5016
273
+ HEADERCOLOR = 5017
274
+ VERBOSE = 5018
275
+ EPOCHNUM = 5019
276
+ ENCODING = 5062
277
+
278
+ TAG_MAP = Hash[constants.collect { |c| [const_get(c), c] }]
279
+
280
+ attr_accessor :tag, :type, :value
281
+
282
+ def initialize(tag_number, type_number)
283
+ @tag = self.class::TAG_MAP[tag_number] || tag_number
284
+ @type = type_number
285
+ end
286
+
287
+ def parse(data, offset, count)
288
+ @value = Type.parse(data, @type, offset, count)
289
+ nil
290
+ end
291
+
292
+ def inspect
293
+ format("<%s#%s> %s/%d value=%s>", self.class.name, self.object_id, @tag, @type, @value.inspect)
294
+ end
295
+ end # module ArrPM::V2::Tag
@@ -0,0 +1,15 @@
1
+ require "arr-pm/namespace"
2
+
3
+ module ArrPM::V2::Type
4
+ BINARY = 0
5
+ SOURCE = 1
6
+
7
+ module_function
8
+
9
+ # Is a given rpm type value valid?
10
+ #
11
+ # The only valid types are BINARY (0) or SOURCE (1)
12
+ def valid?(value)
13
+ return (value == BINARY || value == SOURCE)
14
+ end
15
+ end
@@ -0,0 +1,34 @@
1
+ # encoding: utf-8
2
+
3
+ require "flores/random"
4
+
5
+ require "arr-pm/v2/header"
6
+ require "arr-pm/v2/type"
7
+ require "arr-pm/v2/architecture"
8
+ require "json"
9
+
10
+ describe ArrPM::V2::Header do
11
+ context "with a known good rpm" do
12
+ let(:path) { File.join(File.dirname(__FILE__), "../../fixtures/example-1.0-1.x86_64.rpm") }
13
+ let(:file) { File.new(path) }
14
+
15
+ before do
16
+ lead = ArrPM::V2::Lead.new
17
+ lead.load(file)
18
+
19
+ # Throw away the signature if we have one
20
+ described_class.new.load(file) if lead.signature?
21
+
22
+ subject.load(file)
23
+ end
24
+
25
+ expectations = JSON.parse(File.read(File.join(File.dirname(__FILE__), "../../fixtures/example.json")))
26
+
27
+ expectations.each do |name, expected_value|
28
+ it "should have expected value for the #{name} tag" do
29
+ tag = subject.tags.find { |t| t.tag.to_s == name }
30
+ expect(tag.value).to be == expected_value
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,125 @@
1
+ # encoding: utf-8
2
+
3
+ require "flores/random"
4
+
5
+ require "arr-pm/v2/lead"
6
+ require "arr-pm/v2/type"
7
+ require "arr-pm/v2/architecture"
8
+
9
+ describe ArrPM::V2::Lead do
10
+ let(:major) { Flores::Random.integer(0..255) }
11
+ let(:minor) { Flores::Random.integer(0..255) }
12
+ let(:magic) { described_class::MAGIC }
13
+ let(:type) { ArrPM::V2::Type::BINARY }
14
+ let(:architecture) { ArrPM::V2::Architecture::I386 }
15
+ let(:os) { 0 }
16
+ let(:os_bytes) { [os].pack("n").unpack("C2") }
17
+ let(:signature_type) { 0 }
18
+ let(:signature_type_bytes) { [signature_type].pack("n").unpack("C2") }
19
+ let(:longname) { "test-1.0-1" }
20
+ let(:longnamebytes) { longname.bytes + (66-longname.bytesize).times.collect { 0 } }
21
+ let(:leadbytes) { magic + [major, minor] + [0, type, 0, architecture] + longnamebytes + os_bytes + signature_type_bytes + 16.times.collect { 0 } }
22
+ let(:lead) { leadbytes.pack("C*") }
23
+
24
+ describe ".parse_magic" do
25
+ context "when given an invalid magic value" do
26
+ # Generate random bytes for the magic value, should be bad.
27
+ let(:magic) { Flores::Random.iterations(0..10).collect { Flores::Random.integer(0..255) } }
28
+
29
+ it "should fail" do
30
+ expect { described_class.parse_magic(leadbytes) }.to raise_error(ArrPM::V2::Error::InvalidMagicValue)
31
+ end
32
+ end
33
+
34
+ context "when given a valid magic value" do
35
+ it "should succeed" do
36
+ expect { described_class.parse_magic(leadbytes) }.not_to raise_error
37
+ end
38
+ end
39
+ end
40
+
41
+ describe ".parse_version" do
42
+ context "when given an invalid version value" do
43
+ let(:data) { magic + [major, minor] }
44
+
45
+ it "should return an array of two values " do
46
+ expect(described_class.parse_version(leadbytes)).to be == [major, minor]
47
+ end
48
+ end
49
+ end
50
+
51
+ describe ".parse_type" do
52
+ context "when given an invalid type" do
53
+ let(:type) { Flores::Random.integer(2..1000) }
54
+ it "should fail" do
55
+ expect { described_class.parse_type(leadbytes) }.to raise_error(ArrPM::V2::Error::InvalidType)
56
+ end
57
+ end
58
+ context "with a valid type" do
59
+ it "should return the type" do
60
+ expect(described_class.parse_type(leadbytes)).to be == type
61
+ end
62
+ end
63
+ end
64
+
65
+ describe ".parse_name" do
66
+ context "with a valid name" do
67
+ it "should return the name" do
68
+ expect(described_class.parse_name(leadbytes)).to be == longname
69
+ end
70
+ end
71
+ end
72
+
73
+ describe ".parse_signature_type" do
74
+ it "should return the signature type" do
75
+ expect(described_class.parse_signature_type(leadbytes)).to be == signature_type
76
+ end
77
+ end
78
+
79
+ describe ".parse_reserved" do
80
+ it "should return exactly 16 bytes" do
81
+ expect(described_class.parse_reserved(leadbytes).count).to be == 16
82
+ end
83
+ end
84
+
85
+ describe "#parse" do
86
+ before do
87
+ subject.parse(lead)
88
+ end
89
+
90
+ it "should have a correct parsed values" do
91
+ expect(subject.name).to be == longname
92
+ expect(subject.major).to be == major
93
+ expect(subject.minor).to be == minor
94
+ expect(subject.type).to be == type
95
+ expect(subject.architecture).to be == architecture
96
+ end
97
+ end
98
+
99
+ describe "#dump" do
100
+ before do
101
+ subject.parse(lead)
102
+ end
103
+
104
+ let(:blob) { subject.serialize }
105
+
106
+ it "should parse successfully" do
107
+ subject.parse(blob)
108
+ end
109
+ end
110
+
111
+ context "with a known good rpm" do
112
+ let(:path) { File.join(File.dirname(__FILE__), "../../fixtures/example-1.0-1.x86_64.rpm") }
113
+
114
+ before do
115
+ subject.load(File.new(path))
116
+ end
117
+
118
+ it "should have expected values" do
119
+ expect(subject.name).to be == "example-1.0-1"
120
+ expect(subject.major).to be == 3
121
+ expect(subject.minor).to be == 0
122
+ expect(subject.architecture).to be == architecture
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,55 @@
1
+ {
2
+ "NAME": "example",
3
+ "VERSION": "1.0",
4
+ "RELEASE": "1",
5
+ "SUMMARY": "no description given",
6
+ "DESCRIPTION": "no description given",
7
+ "BUILDTIME": [
8
+ 1466326707
9
+ ],
10
+ "BUILDHOST": "localhost",
11
+ "SIZE": [
12
+ 0
13
+ ],
14
+ "VENDOR": "none",
15
+ "LICENSE": "unknown",
16
+ "PACKAGER": "<jls@localhost.localdomain>",
17
+ "GROUP": "default",
18
+ "URL": "http://example.com/no-uri-given",
19
+ "OS": "linux",
20
+ "ARCH": "x86_64",
21
+ "SOURCERPM": "example-1.0-1.src.rpm",
22
+ "PROVIDENAME": [
23
+ "example",
24
+ "example(x86-64)"
25
+ ],
26
+ "REQUIREFLAGS": [
27
+ 16777226,
28
+ 16777226
29
+ ],
30
+ "REQUIRENAME": [
31
+ "rpmlib(CompressedFileNames)",
32
+ "rpmlib(PayloadFilesHavePrefix)"
33
+ ],
34
+ "REQUIREVERSION": [
35
+ "3.0.4-1",
36
+ "4.0-1"
37
+ ],
38
+ "RPMVERSION": "4.13.0-rc1",
39
+ "PREFIXES": [
40
+ "/"
41
+ ],
42
+ "PROVIDEFLAGS": [
43
+ 8,
44
+ 8
45
+ ],
46
+ "PROVIDEVERSION": [
47
+ "1.0-1",
48
+ "1.0-1"
49
+ ],
50
+ "PAYLOADFORMAT": "cpio",
51
+ "PAYLOADCOMPRESSOR": "gzip",
52
+ "PAYLOADFLAGS": "9",
53
+ "PLATFORM": "x86_64-redhat-linux-gnu",
54
+ "ENCODING": "utf-8"
55
+ }
@@ -0,0 +1,57 @@
1
+ # encoding: utf-8
2
+
3
+ require "arr-pm/file"
4
+ require "stud/temporary"
5
+ require "insist"
6
+
7
+ describe ::RPM::File do
8
+ subject { described_class.new(path) }
9
+
10
+ context "with a known good rpm" do
11
+ let(:path) { File.join(File.dirname(__FILE__), "../fixtures/pagure-mirror-5.13.2-5.fc35.noarch.rpm") }
12
+
13
+ context "#files" do
14
+ let(:files) { [
15
+ "/usr/lib/systemd/system/pagure_mirror.service",
16
+ "/usr/share/licenses/pagure-mirror",
17
+ "/usr/share/licenses/pagure-mirror/LICENSE"
18
+ ]}
19
+
20
+ it "should have the correct list of files" do
21
+ expect(subject.files).to eq(files)
22
+ end
23
+ end
24
+ end
25
+
26
+ context "#extract" do
27
+ # This RPM should be correctly built, but we will modify the tags at runtime to force an error.
28
+ let(:path) { File.join(File.dirname(__FILE__), "../fixtures/example-1.0-1.x86_64.rpm") }
29
+ let(:dir) { dir = Stud::Temporary.directory }
30
+
31
+ after do
32
+ FileUtils.rm_rf(dir)
33
+ end
34
+
35
+ context "with an invalid payloadcompressor" do
36
+ before do
37
+ subject.tags[:payloadcompressor] = "some invalid | string"
38
+ end
39
+
40
+ it "should raise an error" do
41
+ insist { subject.extract(dir) }.raises(RuntimeError)
42
+ end
43
+ end
44
+
45
+ [ "gzip", "bzip2", "xz", "lzma", "zstd" ].each do |name|
46
+ context "with a '#{name}' payloadcompressor" do
47
+ before do
48
+ subject.tags[:payloadcompressor] = name
49
+ end
50
+
51
+ it "should succeed" do
52
+ reject { subject.extract(dir) }.raises(RuntimeError)
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
metadata CHANGED
@@ -1,23 +1,23 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: arr-pm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.10
4
+ version: 0.0.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jordan Sissel
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-04-14 00:00:00.000000000 Z
11
+ date: 2022-09-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: cabin
14
+ name: flores
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - ">"
18
18
  - !ruby/object:Gem::Version
19
19
  version: '0'
20
- type: :runtime
20
+ type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
@@ -25,19 +25,47 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
- name: flores
28
+ name: rspec
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">"
32
32
  - !ruby/object:Gem::Version
33
- version: '0'
33
+ version: 3.0.0
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - ">"
39
39
  - !ruby/object:Gem::Version
40
- version: '0'
40
+ version: 3.0.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: stud
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: 0.0.23
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: 0.0.23
55
+ - !ruby/object:Gem::Dependency
56
+ name: insist
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: 1.0.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: 1.0.0
41
69
  description: This library allows to you to read and write rpm packages. Written in
42
70
  pure ruby because librpm is not available on all systems
43
71
  email:
@@ -46,9 +74,9 @@ executables: []
46
74
  extensions: []
47
75
  extra_rdoc_files: []
48
76
  files:
49
- - ".batcave/manifest"
50
77
  - ".gitignore"
51
78
  - ".rubocop.yml"
79
+ - CHANGELOG.md
52
80
  - Gemfile
53
81
  - LICENSE
54
82
  - Makefile
@@ -63,11 +91,26 @@ files:
63
91
  - lib/arr-pm/file/tag.rb
64
92
  - lib/arr-pm/namespace.rb
65
93
  - lib/arr-pm/requires.rb
66
- homepage:
94
+ - lib/arr-pm/v2/architecture.rb
95
+ - lib/arr-pm/v2/error.rb
96
+ - lib/arr-pm/v2/format.rb
97
+ - lib/arr-pm/v2/header.rb
98
+ - lib/arr-pm/v2/header_header.rb
99
+ - lib/arr-pm/v2/lead.rb
100
+ - lib/arr-pm/v2/package.rb
101
+ - lib/arr-pm/v2/tag.rb
102
+ - lib/arr-pm/v2/type.rb
103
+ - spec/arr-pm/v2/header_spec.rb
104
+ - spec/arr-pm/v2/lead_spec.rb
105
+ - spec/fixtures/example-1.0-1.x86_64.rpm
106
+ - spec/fixtures/example.json
107
+ - spec/fixtures/pagure-mirror-5.13.2-5.fc35.noarch.rpm
108
+ - spec/rpm/file_spec.rb
109
+ homepage:
67
110
  licenses:
68
111
  - Apache 2
69
112
  metadata: {}
70
- post_install_message:
113
+ post_install_message:
71
114
  rdoc_options: []
72
115
  require_paths:
73
116
  - lib
@@ -83,9 +126,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
83
126
  - !ruby/object:Gem::Version
84
127
  version: '0'
85
128
  requirements: []
86
- rubyforge_project:
87
- rubygems_version: 2.4.3
88
- signing_key:
129
+ rubygems_version: 3.3.3
130
+ signing_key:
89
131
  specification_version: 4
90
132
  summary: RPM reader and writer library
91
133
  test_files: []
data/.batcave/manifest DELETED
@@ -1,5 +0,0 @@
1
- ---
2
- things:
3
- ruby:
4
- args:
5
- - arr-pm