rupert 0.0.1 → 0.0.2
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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/Changelog.md +15 -0
- data/README.md +24 -0
- data/TODO.md +10 -4
- data/lib/rupert/parser.rb +41 -24
- data/lib/rupert/rpm.rb +47 -18
- data/lib/rupert/rpm/entry.rb +127 -0
- data/lib/rupert/rpm/header.rb +55 -0
- data/lib/rupert/rpm/index.rb +51 -0
- data/lib/rupert/rpm/lead.rb +10 -10
- data/lib/rupert/rpm/signature.rb +0 -19
- data/lib/rupert/version.rb +1 -1
- data/rubygems-stefanozanella.crt +20 -0
- data/rupert.gemspec +3 -0
- data/test/end_to_end/rpm_test.rb +21 -9
- data/test/test_helper.rb +13 -0
- data/test/unit/rpm/entry_test.rb +48 -0
- data/test/unit/rpm/header_test.rb +42 -0
- data/test/unit/rpm/index_test.rb +13 -0
- data/test/unit/rpm/signature_test.rb +2 -14
- data/test/unit/rpm_test.rb +37 -9
- metadata +35 -10
- metadata.gz.sig +0 -0
- data/lib/rupert/rpm/signature/entry.rb +0 -19
- data/lib/rupert/rpm/signature/index.rb +0 -32
- data/lib/rupert/rpm/signature/store.rb +0 -35
- data/test/unit/rpm/signature/index_test.rb +0 -14
- data/test/unit/rpm/signature/store_test.rb +0 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8a0baa0b545ca0c693abd38c0c23e6631d441982
|
4
|
+
data.tar.gz: f786fc3a79ef710b65d0c8c63bfc652368b21c58
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c8757b87031ea4a3744a80bb0b2664f606c7e1f5b49aa77ae77f9a1fb32b6dd8fc806dc8016b3ef3da125aac8dad3bcf294df4825f98d6b8cbea1e2128371006
|
7
|
+
data.tar.gz: 09a9460c7382da48467712467a059e6c9518b65c1693073715f1181d3f845be5e2dab03166b12102ce5fd215f1e20e05a53143ed83810d8e53396dbb54511579
|
checksums.yaml.gz.sig
ADDED
Binary file
|
data.tar.gz.sig
ADDED
Binary file
|
data/Changelog.md
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
## 0.0.2
|
4
|
+
|
5
|
+
* First pieces of metadata fetched from RPM header structure:
|
6
|
+
* package name
|
7
|
+
* list of installed files
|
8
|
+
* installed package size
|
9
|
+
* Major design improvements
|
10
|
+
|
11
|
+
## 0.0.1
|
12
|
+
|
13
|
+
* Read information from RPM lead section
|
14
|
+
* Read MD5 checksum information from RPM signature structure
|
15
|
+
* Perform basic (non-crypto) package integrity verification
|
data/README.md
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
[](http://badge.fury.io/rb/rupert)
|
1
2
|
[](https://travis-ci.org/stefanozanella/rupert)
|
2
3
|
[](https://codeclimate.com/github/stefanozanella/rupert)
|
3
4
|
[](https://coveralls.io/r/stefanozanella/rupert?branch=master)
|
@@ -23,6 +24,8 @@ Or install it yourself as:
|
|
23
24
|
|
24
25
|
## Usage
|
25
26
|
|
27
|
+
### Parsing an RPM
|
28
|
+
|
26
29
|
You can read an RPM simply with:
|
27
30
|
|
28
31
|
rpm = Rupert::RPM.load('rpm-4.8.0-32.el6.x86_64.rpm')
|
@@ -33,6 +36,23 @@ or just check if a specific file is an RPM with:
|
|
33
36
|
|
34
37
|
(note that loading a file that is not an RPM generates an exception)
|
35
38
|
|
39
|
+
### Verifying RPM for corruption
|
40
|
+
|
41
|
+
You can verify if an RPM is corrupted after loading it with:
|
42
|
+
|
43
|
+
rpm.intact?
|
44
|
+
|
45
|
+
Note that this only verifies if the MD5 stored in RPM metadata corresponds to
|
46
|
+
the MD5 calculated over the content and metadata itself. It doesn't provide any
|
47
|
+
warranty that the packages has been _maliciously_ altered. For this, you need
|
48
|
+
to check package _signature_.
|
49
|
+
|
50
|
+
### List of installed files
|
51
|
+
|
52
|
+
The list of installed files is returned as an array of absolute filenames with:
|
53
|
+
|
54
|
+
rpm.filenames
|
55
|
+
|
36
56
|
## Contributing
|
37
57
|
|
38
58
|
1. Fork it
|
@@ -40,3 +60,7 @@ or just check if a specific file is an RPM with:
|
|
40
60
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
41
61
|
4. Push to the branch (`git push origin my-new-feature`)
|
42
62
|
5. Create new Pull Request
|
63
|
+
|
64
|
+
## Changelog
|
65
|
+
|
66
|
+
See [Changelog.md](Changelog.md)
|
data/TODO.md
CHANGED
@@ -26,14 +26,20 @@
|
|
26
26
|
* Remember that signature is not mandatory in RPM!
|
27
27
|
* It looks like size is also a form of signature, in the sense that RPM uses
|
28
28
|
the stored value to check actual size of header + payload (or did I
|
29
|
-
understood wrong?)
|
29
|
+
understood wrong?) -> make integrity + auth verification check everything at
|
30
|
+
once?.
|
30
31
|
* I18N ???
|
32
|
+
* Improve Index robustness against null values of store and entries, and for
|
33
|
+
missing tags -> decide what to return. Also improve robustness for invalid
|
34
|
+
types (cryptic metaprogramming errors are returned as of now).
|
31
35
|
|
32
36
|
# Roadmap
|
33
37
|
|
34
|
-
* Pick the entry type table. For each type, pick a meaningful
|
35
|
-
|
36
|
-
|
38
|
+
* Pick the entry type table. For each type, pick a meaningful (mandatory may be
|
39
|
+
* easy - see below) header tag (like
|
40
|
+
name, file list, etc.) to derive a feature to be implemented. At the end,
|
41
|
+
(almost) all types should be correctly handled
|
37
42
|
* Decide a sensible behaviour for missing mandatory tags. Then, pick all
|
38
43
|
mandatory header tags and build a feature for them if not already covered
|
39
44
|
with previous method.
|
45
|
+
* Decide how to handle optional tags (return nil?)
|
data/lib/rupert/parser.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'rupert/rpm/lead'
|
2
|
-
require 'rupert/rpm/
|
2
|
+
require 'rupert/rpm/index'
|
3
|
+
require 'rupert/rpm/entry'
|
3
4
|
|
4
5
|
module Rupert
|
5
6
|
class Parser
|
@@ -11,49 +12,65 @@ module Rupert
|
|
11
12
|
# TODO Fit to current design (i.e. no parsing in Lead c'tor?)
|
12
13
|
lead = RPM::Lead.new(@raw_io)
|
13
14
|
|
14
|
-
|
15
|
-
entries = parse_entries(entry_count)
|
15
|
+
signature = signature_from(parse_index(@raw_io))
|
16
16
|
|
17
|
-
|
18
|
-
content
|
17
|
+
# TODO I'd like to get rid of this duplication, but still don't know how.
|
18
|
+
# Ideally, raw signed content should be available from both archive and
|
19
|
+
# header, and concatenated to calculate checksum.
|
20
|
+
content = parse_content @raw_io
|
21
|
+
@raw_io.seek(-content.length, IO::SEEK_CUR)
|
19
22
|
|
20
|
-
|
23
|
+
header = header_from(parse_index(@raw_io))
|
21
24
|
|
22
|
-
RPM.new(lead, signature, content)
|
25
|
+
RPM.new(lead, signature, content, header)
|
23
26
|
end
|
24
27
|
|
25
28
|
private
|
26
29
|
|
27
|
-
def
|
30
|
+
def header_from(index)
|
31
|
+
RPM::Header.new index
|
32
|
+
end
|
33
|
+
|
34
|
+
def signature_from(index)
|
35
|
+
RPM::Signature.new index
|
36
|
+
end
|
37
|
+
|
38
|
+
def parse_header(raw_io)
|
28
39
|
header_size = 16
|
29
40
|
header_format = "@8NN"
|
30
41
|
|
31
|
-
|
42
|
+
raw_io.read(header_size).unpack(header_format)
|
32
43
|
end
|
33
44
|
|
34
|
-
def
|
35
|
-
|
36
|
-
|
45
|
+
def parse_store(size, raw_io)
|
46
|
+
StringIO.new(raw_io.read(nearest_multiple(8, size)))
|
47
|
+
end
|
37
48
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
49
|
+
def parse_content(raw_io)
|
50
|
+
raw_io.read.force_encoding(Encoding::ASCII_8BIT)
|
51
|
+
end
|
52
|
+
|
53
|
+
def parse_index(raw_io)
|
54
|
+
index = RPM::Index.new
|
55
|
+
|
56
|
+
entry_count, store_size = parse_header(raw_io)
|
57
|
+
entry_count.times do
|
58
|
+
index.add parse_entry(raw_io)
|
43
59
|
end
|
44
60
|
|
45
|
-
|
46
|
-
end
|
61
|
+
index.store = parse_store(store_size, raw_io)
|
47
62
|
|
48
|
-
|
49
|
-
RPM::Signature::Store.new(StringIO.new(@raw_io.read(nearest_multiple(size, 8))))
|
63
|
+
index
|
50
64
|
end
|
51
65
|
|
52
|
-
def
|
53
|
-
|
66
|
+
def parse_entry(raw_io)
|
67
|
+
entry_size = 16
|
68
|
+
entry_format = "NNNN"
|
69
|
+
|
70
|
+
RPM::Entry.new(*raw_io.read(entry_size).unpack(entry_format))
|
54
71
|
end
|
55
72
|
|
56
|
-
def nearest_multiple(
|
73
|
+
def nearest_multiple(modulo, size)
|
57
74
|
(size / modulo.to_f).ceil * modulo
|
58
75
|
end
|
59
76
|
end
|
data/lib/rupert/rpm.rb
CHANGED
@@ -2,6 +2,7 @@ require 'rupert/errors'
|
|
2
2
|
require 'rupert/parser'
|
3
3
|
require 'rupert/rpm/lead'
|
4
4
|
require 'rupert/rpm/signature'
|
5
|
+
require 'rupert/rpm/header'
|
5
6
|
|
6
7
|
require 'base64'
|
7
8
|
|
@@ -26,7 +27,7 @@ module Rupert
|
|
26
27
|
# Tells whether given filename points to a valid RPM or not.
|
27
28
|
#
|
28
29
|
# @param filename [String] filename to inspect
|
29
|
-
# @return
|
30
|
+
# @return +true+ if file starts with the correct magic header
|
30
31
|
def rpm?(filename)
|
31
32
|
Lead.new(File.open(filename, 'r')).rpm?
|
32
33
|
end
|
@@ -35,37 +36,39 @@ module Rupert
|
|
35
36
|
# Initialize the RPM object, given its components.
|
36
37
|
#
|
37
38
|
# This method is not intended to be used to instantiate RPM objects
|
38
|
-
# directly. Instead, use Rupert::RPM
|
39
|
+
# directly. Instead, use {Rupert::RPM.load} for a more straightforward
|
39
40
|
# alternative.
|
40
41
|
#
|
41
42
|
# @param lead [Rupert::RPM::Lead] RPM lead section
|
42
|
-
# @param signature [Rupert::RPM::Signature] RPM signature
|
43
|
+
# @param signature [Rupert::RPM::Signature] RPM signature information
|
43
44
|
# @param content [String] Raw content found after the signature structure
|
44
|
-
|
45
|
+
# @param header [Rupert::RPM::Header] RPM header holding package metadata
|
46
|
+
def initialize(lead, signature, content, header)
|
45
47
|
@lead = lead
|
46
48
|
@signature = signature
|
47
49
|
@content = content
|
50
|
+
@header = header
|
48
51
|
end
|
49
52
|
|
50
53
|
# RPM version used to encode the package.
|
51
54
|
#
|
52
|
-
# @return [String] the RPM version in
|
55
|
+
# @return [String] the RPM version in +<major>.<minor>+ format
|
53
56
|
def rpm_version
|
54
57
|
@lead.rpm_version
|
55
58
|
end
|
56
59
|
|
57
|
-
# @return
|
60
|
+
# @return +true+ if the RPM is of type binary, +false+ otherwise
|
58
61
|
def binary?
|
59
62
|
@lead.binary_type?
|
60
63
|
end
|
61
64
|
|
62
|
-
# @return
|
65
|
+
# @return +true+ if the RPM is of type source, +false+ otherwise
|
63
66
|
def source?
|
64
67
|
@lead.source_type?
|
65
68
|
end
|
66
69
|
|
67
|
-
# Which architecture the package was built for, e.g.
|
68
|
-
#
|
70
|
+
# Which architecture the package was built for, e.g. +i386/x86_64+ or
|
71
|
+
# +arm+
|
69
72
|
#
|
70
73
|
# @return [String] package architecture name
|
71
74
|
def rpm_arch
|
@@ -74,20 +77,20 @@ module Rupert
|
|
74
77
|
|
75
78
|
# Full package name
|
76
79
|
#
|
77
|
-
# @return [String] package name in the form
|
80
|
+
# @return [String] package name in the form +<name>-<version>-<rev>.<suffix>+
|
78
81
|
def name
|
79
|
-
@
|
82
|
+
@header.name
|
80
83
|
end
|
81
84
|
|
82
85
|
# OS for which the package was built
|
83
86
|
#
|
84
|
-
# @return [String] as defined in /usr/lib/rpm/
|
87
|
+
# @return [String] as defined in _/usr/lib/rpm/rpmrc_ under the canonical OS
|
85
88
|
# names section
|
86
89
|
def os
|
87
90
|
@lead.os
|
88
91
|
end
|
89
92
|
|
90
|
-
# @return
|
93
|
+
# @return +true+ if the package is signed, +false+ otherwise
|
91
94
|
def signed?
|
92
95
|
@lead.signed?
|
93
96
|
end
|
@@ -95,10 +98,10 @@ module Rupert
|
|
95
98
|
# MD5 checksum stored in the package (base64 encoded). To be used to check
|
96
99
|
# package integrity.
|
97
100
|
#
|
98
|
-
# NOTE
|
101
|
+
# *NOTE*: This is not the MD5 of the whole package; rather, the digest is
|
99
102
|
# calculated over the header and payload, leaving out the lead and the
|
100
|
-
# signature header. I.e., running
|
101
|
-
# result as
|
103
|
+
# signature header. I.e., running +md5sum <myrpm>+ won't held the same
|
104
|
+
# result as +Rupert::RPM.load('<myrpm>').md5+.
|
102
105
|
#
|
103
106
|
# @return [String] Base64-encoded MD5 checksum of package's header and
|
104
107
|
# payload, stored in the RPM itself
|
@@ -109,10 +112,36 @@ module Rupert
|
|
109
112
|
# Verifies package integrity. Compares MD5 checksum stored in the package
|
110
113
|
# with checksum calculated over header(s) and payload (archive).
|
111
114
|
#
|
112
|
-
# @return
|
115
|
+
# @return +true+ if package is intact, +false+ if package (either stored MD5 or
|
113
116
|
# payload) is corrupted
|
114
117
|
def intact?
|
115
|
-
@signature.
|
118
|
+
@signature.md5 == Digest::MD5.digest(@content)
|
119
|
+
end
|
120
|
+
|
121
|
+
# Package uncompressed size.
|
122
|
+
#
|
123
|
+
# This is the size (in bytes) of the uncompressed archive, or if you
|
124
|
+
# prefer, package's installed size.
|
125
|
+
#
|
126
|
+
# *NOTE*: if reading a package built with native +rpmbuild+, this number
|
127
|
+
# (which is stored in the RPM itself) might not be precise, as
|
128
|
+
# this[http://rpm5.org/community/rpm-devel/2689.html] thread explains.
|
129
|
+
#
|
130
|
+
# @return [Fixnum] package uncompressed size (bytes)
|
131
|
+
def uncompressed_size
|
132
|
+
@header.uncompressed_size
|
133
|
+
end
|
134
|
+
|
135
|
+
# List of installed files (full paths).
|
136
|
+
#
|
137
|
+
# @return [Array] array of +String+, with entries corresponding to
|
138
|
+
# absolute filenames
|
139
|
+
def filenames
|
140
|
+
@header.dirindexes.map { |idx|
|
141
|
+
@header.dirnames[idx]
|
142
|
+
}.zip(@header.basenames).map { |dir, name|
|
143
|
+
File.join(dir, name)
|
144
|
+
}
|
116
145
|
end
|
117
146
|
end
|
118
147
|
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
module Rupert
|
2
|
+
class RPM
|
3
|
+
class Entry
|
4
|
+
# Initializes a new index entry.
|
5
|
+
#
|
6
|
+
# @param tag [String] 4 byte entry tag (semantic data type)
|
7
|
+
# @param type [String] 4 byte entry data format
|
8
|
+
# @param offset [String] 4 byte pointer to data in the index store
|
9
|
+
# @param count [String] 4 byte number of data items held by the entry
|
10
|
+
def initialize(tag, type, offset, count)
|
11
|
+
@tag, @type, @offset, @count = tag, type, offset, count
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_accessor :tag
|
15
|
+
|
16
|
+
# Fetches referenced data from a store.
|
17
|
+
#
|
18
|
+
# An entry contains only information about a piece of data, but not the
|
19
|
+
# actual data. In essence, it behaves more or less like a pointer,
|
20
|
+
# which contains the address at which data is available.
|
21
|
+
#
|
22
|
+
# This method behaves exactly like pointer dereference, i.e. it returns
|
23
|
+
# the actual data at the address held by the entry itself. In addition,
|
24
|
+
# data is not returned in raw form; instead, it is returned in the
|
25
|
+
# format declared in the entry itself. The available RPM formats are the
|
26
|
+
# following:
|
27
|
+
#
|
28
|
+
# * NULL
|
29
|
+
# * CHAR
|
30
|
+
# * INT8
|
31
|
+
# * INT16
|
32
|
+
# * INT32
|
33
|
+
# * INT64 (not supported yet even in rpmlib?)
|
34
|
+
# * STRING
|
35
|
+
# * BIN
|
36
|
+
# * STRING_ARRAY
|
37
|
+
# * I18NSTRING
|
38
|
+
#
|
39
|
+
# which are in turn mapped in Ruby with:
|
40
|
+
#
|
41
|
+
# * +nil+
|
42
|
+
# * (+Array+ of) +String+ of length 1
|
43
|
+
# * (+Array+ of) +Fixnum+
|
44
|
+
# * (+Array+ of) +Fixnum+
|
45
|
+
# * (+Array+ of) +Fixnum+
|
46
|
+
# * (+Array+ of) +Fixnum+/+Bignum+
|
47
|
+
# * +String+ of arbitrary length
|
48
|
+
# * +String+ of arbitrary length, 8-bit ASCII encoded
|
49
|
+
# * +Array+ of +String+
|
50
|
+
# * +Array+ of +String+
|
51
|
+
#
|
52
|
+
# *NOTE*: The store is sought to retrieve data. Do not make any
|
53
|
+
# assumptions on IO's pointer state after method call. If you need to
|
54
|
+
# perform subsequent operations on the IO that require a particular
|
55
|
+
# cursor position, seek the IO to wanted position before performing
|
56
|
+
# the operation.
|
57
|
+
#
|
58
|
+
# @param store [IO] raw data store, represented by an IO object
|
59
|
+
# @return [Object] data referenced by this entry, in whatever format
|
60
|
+
# the entry prescribe
|
61
|
+
def resolve(store)
|
62
|
+
store.seek(@offset, IO::SEEK_SET)
|
63
|
+
read_and_convert(@type, store)
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
# :nodoc: Null byte used to indicate string termination
|
69
|
+
NULL_CHAR = "\x00".force_encoding(Encoding::ASCII_8BIT)
|
70
|
+
|
71
|
+
# :nodoc: Map of numerical entry types to parsing functions.
|
72
|
+
#
|
73
|
+
# NOTE: It turns out that distinguishing between strings and string
|
74
|
+
# arrays is irrelevant (at least with this implementation), since for
|
75
|
+
# every data type, if the count is 1 data is not wrapped in an array.
|
76
|
+
TYPE_MAP = {
|
77
|
+
4 => :int32,
|
78
|
+
6 => :string,
|
79
|
+
7 => :binary,
|
80
|
+
8 => :string
|
81
|
+
}
|
82
|
+
|
83
|
+
# :nodoc: Reads given type of data from given store
|
84
|
+
def read_and_convert(type, store)
|
85
|
+
method(TYPE_MAP[type]).call(store)
|
86
|
+
end
|
87
|
+
|
88
|
+
# :nodoc: Returns a list of integers, or a single element if count is 1
|
89
|
+
def int32(store)
|
90
|
+
first_or(array_of(lambda { one_int_32(store) }))
|
91
|
+
end
|
92
|
+
|
93
|
+
# :nodoc: Returns an array with given number of null-terminated strings,
|
94
|
+
# or a single string if count is 1
|
95
|
+
def string(store)
|
96
|
+
first_or(array_of(lambda { one_string(store) }))
|
97
|
+
end
|
98
|
+
|
99
|
+
def binary(store)
|
100
|
+
store.read(@count)
|
101
|
+
end
|
102
|
+
|
103
|
+
# :nodoc: Parses a single int32 from the store
|
104
|
+
def one_int_32(store)
|
105
|
+
store.read(4).unpack("N").first
|
106
|
+
end
|
107
|
+
|
108
|
+
# :nodoc: Parses a null-terminated string, without the trailing null
|
109
|
+
# character.
|
110
|
+
def one_string(store)
|
111
|
+
store.gets(NULL_CHAR).chomp(NULL_CHAR)
|
112
|
+
end
|
113
|
+
|
114
|
+
# :nodoc: Strips off array if it contains only one element
|
115
|
+
def first_or(ary)
|
116
|
+
ary.length == 1 ? ary.first : ary
|
117
|
+
end
|
118
|
+
|
119
|
+
# :nodoc: Builds an array of count elements parsed used given function
|
120
|
+
def array_of(parse_fun)
|
121
|
+
(1..@count).inject([]) { |ary|
|
122
|
+
ary << parse_fun.call
|
123
|
+
}
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Rupert
|
2
|
+
class RPM
|
3
|
+
class Header
|
4
|
+
NAME_TAG = 1000.freeze
|
5
|
+
SIZE_TAG = 1009.freeze
|
6
|
+
DIRINDEXES_TAG = 1116.freeze
|
7
|
+
BASENAMES_TAG = 1117.freeze
|
8
|
+
DIRNAMES_TAG = 1118.freeze
|
9
|
+
|
10
|
+
# Creates a new header.
|
11
|
+
#
|
12
|
+
# @param index [Rupert::RPM::Index] index structure holding actual
|
13
|
+
# information
|
14
|
+
def initialize(index)
|
15
|
+
@index = index
|
16
|
+
end
|
17
|
+
|
18
|
+
# Package name.
|
19
|
+
#
|
20
|
+
# @return [String]
|
21
|
+
def name
|
22
|
+
@index.get(NAME_TAG)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Package uncompressed size (bytes).
|
26
|
+
#
|
27
|
+
# @return [Fixnum]
|
28
|
+
def uncompressed_size
|
29
|
+
@index.get(SIZE_TAG)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Package files basename list.
|
33
|
+
#
|
34
|
+
# @return [Array] of +String+
|
35
|
+
def basenames
|
36
|
+
@index.get(BASENAMES_TAG)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Installed directory list.
|
40
|
+
#
|
41
|
+
# @return [Array] of +String+
|
42
|
+
def dirnames
|
43
|
+
@index.get(DIRNAMES_TAG)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Map between basenames and relative directories.
|
47
|
+
#
|
48
|
+
# @return [Array] of +Fixnum+, where each number represents an index in
|
49
|
+
# the +dirnames+ array
|
50
|
+
def dirindexes
|
51
|
+
@index.get(DIRINDEXES_TAG)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'rupert/rpm/entry'
|
2
|
+
|
3
|
+
module Rupert
|
4
|
+
class RPM
|
5
|
+
class Index
|
6
|
+
attr_writer :store
|
7
|
+
|
8
|
+
# Initializes a new signature index, given the header's entries and the
|
9
|
+
# store containing actual data.
|
10
|
+
#
|
11
|
+
# @param entries [Array] a list of
|
12
|
+
# {Rupert::RPM::Entry}, or optionally a
|
13
|
+
# single {Rupert::RPM::Entry} not included in
|
14
|
+
# any array
|
15
|
+
# @param store [IO] raw store containing data pointed by entries
|
16
|
+
def initialize(entries=[], store=nil)
|
17
|
+
@entries = index list_of entries
|
18
|
+
@store = store
|
19
|
+
end
|
20
|
+
|
21
|
+
# Retrieves data pointed by given tag.
|
22
|
+
#
|
23
|
+
# @param tag [Fixnum] data type
|
24
|
+
# @return [Object] data associated to given tag, with variable format
|
25
|
+
# depending on how it's stored (see
|
26
|
+
# {Rupert::RPM::Entry#resolve})
|
27
|
+
def get(tag)
|
28
|
+
@entries[tag].resolve(@store)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Adds an entry to the index.
|
32
|
+
#
|
33
|
+
# @param entry [Rupert::RPM::Entry] new entry to add to the index
|
34
|
+
def add(entry)
|
35
|
+
@entries[entry.tag] = entry
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
# :nodoc: Force returned object to be an array
|
41
|
+
def list_of(maybe_an_array)
|
42
|
+
[maybe_an_array].flatten
|
43
|
+
end
|
44
|
+
|
45
|
+
# :nodoc: Given an array of entries, index them by tag
|
46
|
+
def index(entries)
|
47
|
+
Hash[entries.collect { |entry| [entry.tag, entry] }]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/lib/rupert/rpm/lead.rb
CHANGED
@@ -5,7 +5,7 @@ module Rupert
|
|
5
5
|
LEAD_LENGTH = 96.freeze # byte
|
6
6
|
|
7
7
|
# The magic header that identifies an RPM beyond a shadow of a doubt, as
|
8
|
-
# every good RPM starts with hex
|
8
|
+
# every good RPM starts with hex +ed ab ee db+.
|
9
9
|
MAGIC = "\xed\xab\xee\xdb".force_encoding(Encoding::ASCII_8BIT).freeze
|
10
10
|
|
11
11
|
# RPM of type binary
|
@@ -25,12 +25,12 @@ module Rupert
|
|
25
25
|
# Only valid and recognized signature type
|
26
26
|
SIGNATURE_TYPE_HEADER = 5.freeze
|
27
27
|
|
28
|
-
# Chomps given IO, producing a
|
28
|
+
# Chomps given IO, producing a {Lead} object and returning the remaining
|
29
29
|
# part for subsequent processing.
|
30
30
|
#
|
31
31
|
# Lead data is expected to begin at IO start, so returned scrap is
|
32
32
|
# basically the input IO without its first
|
33
|
-
#
|
33
|
+
# {Rupert::RPM::Lead::LEAD_LENGTH} bytes.
|
34
34
|
#
|
35
35
|
# @param io [IO] IO object containing lead data at its start, possibly
|
36
36
|
# with additional bytes at the end
|
@@ -73,24 +73,24 @@ module Rupert
|
|
73
73
|
|
74
74
|
# Tells if the file is recognized as an RPM or not
|
75
75
|
#
|
76
|
-
# @return
|
76
|
+
# @return +true+ if magic number is found at lead's start, +false+
|
77
77
|
# otherwise
|
78
78
|
def rpm?
|
79
79
|
@magic == MAGIC
|
80
80
|
end
|
81
81
|
|
82
|
-
# @return
|
82
|
+
# @return +true+ if lead reports RPM as of binary type
|
83
83
|
def binary_type?
|
84
84
|
@type == TYPE_BINARY
|
85
85
|
end
|
86
86
|
|
87
|
-
# @return
|
87
|
+
# @return +true+ if lead reports RPM as of source type
|
88
88
|
def source_type?
|
89
89
|
@type == TYPE_SOURCE
|
90
90
|
end
|
91
91
|
|
92
92
|
# The architecture the package was built for. A list of supported
|
93
|
-
# architectures can be found in
|
93
|
+
# architectures can be found in _/usr/lib/rpm/rpmrc_ on RedHat based
|
94
94
|
# systems.
|
95
95
|
#
|
96
96
|
# @return [String] a string representing the architecture name(s)
|
@@ -107,12 +107,12 @@ module Rupert
|
|
107
107
|
|
108
108
|
# OS for which the package was built
|
109
109
|
#
|
110
|
-
# @return [String] OS canonical name as defined in
|
110
|
+
# @return [String] OS canonical name as defined in _/usr/lib/rpm/rpmrc_
|
111
111
|
def os
|
112
112
|
@@os_map[@osnum]
|
113
113
|
end
|
114
114
|
|
115
|
-
# @return
|
115
|
+
# @return +true+ if the RPM is recognized as being signed, +false+ otherwise
|
116
116
|
def signed?
|
117
117
|
@signature_type == SIGNATURE_TYPE_HEADER
|
118
118
|
end
|
@@ -129,7 +129,7 @@ module Rupert
|
|
129
129
|
private
|
130
130
|
|
131
131
|
# :nodoc
|
132
|
-
# The format string passed to
|
132
|
+
# The format string passed to +unpack+ to parse the lead
|
133
133
|
LEAD_FORMAT = "A4CCnnZ66nna16".freeze
|
134
134
|
|
135
135
|
# :nodoc
|
data/lib/rupert/rpm/signature.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'rupert/rpm/signature/index'
|
2
|
-
|
3
1
|
module Rupert
|
4
2
|
class RPM
|
5
3
|
class Signature
|
@@ -21,23 +19,6 @@ module Rupert
|
|
21
19
|
def md5
|
22
20
|
@index.get MD5_TAG
|
23
21
|
end
|
24
|
-
|
25
|
-
# Verifies if stored MD5 checksum corresponds to digest calculated over
|
26
|
-
# given content.
|
27
|
-
#
|
28
|
-
# @return `true` if stored MD5 checksum corresponds to MD5 calculated
|
29
|
-
# over given content, `false` otherwise
|
30
|
-
def verify_checksum(content)
|
31
|
-
md5 == md5_checksum(content)
|
32
|
-
end
|
33
|
-
|
34
|
-
private
|
35
|
-
|
36
|
-
# :nodoc
|
37
|
-
# MD5 checksum of given string
|
38
|
-
def md5_checksum(str)
|
39
|
-
Digest::MD5.digest(str)
|
40
|
-
end
|
41
22
|
end
|
42
23
|
end
|
43
24
|
end
|
data/lib/rupert/version.rb
CHANGED
@@ -0,0 +1,20 @@
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
2
|
+
MIIDQDCCAiigAwIBAgIBADANBgkqhkiG9w0BAQUFADBGMRgwFgYDVQQDDA96YW5l
|
3
|
+
bGxhLnN0ZWZhbm8xFTATBgoJkiaJk/IsZAEZFgVnbWFpbDETMBEGCgmSJomT8ixk
|
4
|
+
ARkWA2NvbTAeFw0xMzA4MDcxOTA2MDZaFw0xNDA4MDcxOTA2MDZaMEYxGDAWBgNV
|
5
|
+
BAMMD3phbmVsbGEuc3RlZmFubzEVMBMGCgmSJomT8ixkARkWBWdtYWlsMRMwEQYK
|
6
|
+
CZImiZPyLGQBGRYDY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
|
7
|
+
2JCnJCnjjs62MR/Tw/4WgSG42ruiCEqXV1ECMsXymHPE8xyHkYAwLXvBRzOkZ/IA
|
8
|
+
puJ1XhScduMRcUuE0ZPA5N2HBZI0WsmyyNTYBjOob8m0SNInoRZfIMloj3D8QzB7
|
9
|
+
/6G5HLMWNx60JEpIDgfXvIuSRKNKQ0/0+/G/H4COgj72pd3F4dYltvx+mSwPRq7Q
|
10
|
+
MdZsK3T5Q3d4eLBY1VSlJpq0wkwdEWTXAhR0Mfmbn1D8m9IhJfubgXuXVBY4OPO8
|
11
|
+
KAF/wWqTkzA6guVQlSKdZR4vwms7OpeFkotnivBKa6JwUQSXO8AZEyy53V8cSYDu
|
12
|
+
dbaFi53YbEwOWSMQnW8/kQIDAQABozkwNzAdBgNVHQ4EFgQUcBKkmJAvSTKfDf7z
|
13
|
+
LEu1wE+Rk+swCQYDVR0TBAIwADALBgNVHQ8EBAMCBLAwDQYJKoZIhvcNAQEFBQAD
|
14
|
+
ggEBAMeqfk1l4Y0iZ8jNiu0afcQ60DVqBkRtrT/rsZEqGsdOw11bntOE4yWpo4Kd
|
15
|
+
Y0C/kYrVQ/mIN7IGKbCSjES3aYdQftV9SRW77FA25m2KXRbnEYtJDUX35gAqSdRY
|
16
|
+
9IiYivsMq2dr70eKPNFrFOwWvmwhcGyEG8EDvYoXWllke7RGz1Dn/AZx6jPnShO+
|
17
|
+
0ru4OXsM9++md3sGXIugEFNygvo2/1yQoTe6+XiBocS+pWsJd6JZBfkxPRT4Dz4H
|
18
|
+
RigBD0E3/t/ABjCXkmqwp5gnAZmP8JiVUkn8rp5E0FXvC8T7nsPs2TW/TAmUV6rN
|
19
|
+
hK25FX8YWgT9fD9y3PpWjiYcrCo=
|
20
|
+
-----END CERTIFICATE-----
|
data/rupert.gemspec
CHANGED
@@ -31,4 +31,7 @@ Gem::Specification.new do |spec|
|
|
31
31
|
spec.description = %s{
|
32
32
|
Rupert allows to manipulate RPM files independently from availability of rpmlib.
|
33
33
|
}
|
34
|
+
|
35
|
+
spec.signing_key = File.expand_path "~/.ssh/rubygems-stefanozanella.key"
|
36
|
+
spec.cert_chain = ["rubygems-stefanozanella.crt"]
|
34
37
|
end
|
data/test/end_to_end/rpm_test.rb
CHANGED
@@ -20,6 +20,22 @@ describe Rupert::RPM do
|
|
20
20
|
}.must_raise Rupert::NotAnRPM
|
21
21
|
end
|
22
22
|
|
23
|
+
it "tells the package's name" do
|
24
|
+
rpm.name.must_equal "rpm"
|
25
|
+
end
|
26
|
+
|
27
|
+
it "tells the package uncompressed size (in bytes)" do
|
28
|
+
rpm.uncompressed_size.must_equal 2031240
|
29
|
+
end
|
30
|
+
|
31
|
+
it "tells the full name of the files contained in the package" do
|
32
|
+
rpm.filenames.length.must_equal 0x8c
|
33
|
+
|
34
|
+
rpm.filenames.must_include "/bin/rpm"
|
35
|
+
rpm.filenames.must_include "/usr/share/doc/rpm-4.8.0/ChangeLog.bz2"
|
36
|
+
rpm.filenames.must_include "/var/lib/rpm/__db.009"
|
37
|
+
end
|
38
|
+
|
23
39
|
it "knows which version of RPM the file is" do
|
24
40
|
rpm.rpm_version.must_equal "3.0"
|
25
41
|
end
|
@@ -29,15 +45,11 @@ describe Rupert::RPM do
|
|
29
45
|
end
|
30
46
|
|
31
47
|
it "tells if the file is a binary or source RPM" do
|
32
|
-
|
33
|
-
|
48
|
+
binary_rpm.must_be :binary?
|
49
|
+
binary_rpm.wont_be :source?
|
34
50
|
|
35
|
-
|
36
|
-
|
37
|
-
end
|
38
|
-
|
39
|
-
it "tells the package's name" do
|
40
|
-
rpm.name.must_equal "rpm-4.8.0-32.el6"
|
51
|
+
source_rpm.must_be :source?
|
52
|
+
source_rpm.wont_be :binary?
|
41
53
|
end
|
42
54
|
|
43
55
|
it "tells the operating system for which the package has been built" do
|
@@ -45,6 +57,6 @@ describe Rupert::RPM do
|
|
45
57
|
end
|
46
58
|
|
47
59
|
it "tells if package is signed" do
|
48
|
-
|
60
|
+
rpm.must_be :signed?
|
49
61
|
end
|
50
62
|
end
|
data/test/test_helper.rb
CHANGED
@@ -24,6 +24,19 @@ def ascii(string)
|
|
24
24
|
string.force_encoding(Encoding::ASCII_8BIT)
|
25
25
|
end
|
26
26
|
|
27
|
+
# Returns a random binary string (8-bit ASCII encoding) of given length
|
28
|
+
def random_ascii(size)
|
29
|
+
require 'securerandom'
|
30
|
+
ascii(SecureRandom.random_bytes(size))
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns the (strict) base64 representation of given string
|
34
|
+
def base64(string)
|
35
|
+
require 'base64'
|
36
|
+
|
37
|
+
Base64.strict_encode64(string)
|
38
|
+
end
|
39
|
+
|
27
40
|
# Pads a string with nulls to fill given length
|
28
41
|
def pad(string, length)
|
29
42
|
("%-#{length}.#{length}s" % string).gsub(" ", "\x00")
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe Rupert::RPM::Entry do
|
4
|
+
describe "fetching binary data" do
|
5
|
+
let(:store) { io(ascii("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a")) }
|
6
|
+
let(:entry) { Rupert::RPM::Entry.new(nil, 7, 4, 5) }
|
7
|
+
|
8
|
+
it "fetches binary data which length and position is given by the entry" do
|
9
|
+
entry.resolve(store).must_equal ascii("\x04\x05\x06\x07\x08")
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "fetching a single string" do
|
14
|
+
let(:store) { io(ascii("\x00\x01Null-terminated string.\x00LOLTHISCRAP")) }
|
15
|
+
let(:entry) { Rupert::RPM::Entry.new(nil, 6, 2, 1) }
|
16
|
+
|
17
|
+
it "retrieves all chars starting from entry offset until it encounters `\x00`" do
|
18
|
+
entry.resolve(store).must_equal "Null-terminated string."
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "fetching an array of strings" do
|
23
|
+
let(:store) { io(ascii("One\x00Two\x00Three")) }
|
24
|
+
let(:entry) { Rupert::RPM::Entry.new(nil, 8, 0, 3) }
|
25
|
+
|
26
|
+
it "retrieves all chars starting from entry offset until it encounters `\x00`" do
|
27
|
+
entry.resolve(store).must_equal [ "One", "Two", "Three" ]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "fetching a single 32-bit integer" do
|
32
|
+
let(:store) { io(ascii("Somecrap\x00\x12\x34\x56\x78Tail content")) }
|
33
|
+
let(:entry) { Rupert::RPM::Entry.new(nil, 4, 9, 1) }
|
34
|
+
|
35
|
+
it "successfully retrieves a single 32-bit integer" do
|
36
|
+
entry.resolve(store).must_equal 0x12345678
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "fetching an array of 32-bit integer" do
|
41
|
+
let(:store) { io(ascii("\x11\x11\x11\x11\x22\x22\x22\x22\x33\x33\x33\x33")) }
|
42
|
+
let(:entry) { Rupert::RPM::Entry.new(nil, 4, 0, 3) }
|
43
|
+
|
44
|
+
it "successfully retrieves the given number of 32-bit integers" do
|
45
|
+
entry.resolve(store).must_equal [ 0x11111111, 0x22222222, 0x33333333 ]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe Rupert::RPM::Header do
|
4
|
+
let(:name_tag) { Rupert::RPM::Header::NAME_TAG }
|
5
|
+
let(:size_tag) { Rupert::RPM::Header::SIZE_TAG }
|
6
|
+
let(:basenames_tag) { Rupert::RPM::Header::BASENAMES_TAG }
|
7
|
+
let(:dirnames_tag) { Rupert::RPM::Header::DIRNAMES_TAG }
|
8
|
+
let(:dirindexes_tag) { Rupert::RPM::Header::DIRINDEXES_TAG }
|
9
|
+
|
10
|
+
let(:index) { mock }
|
11
|
+
let(:header) { Rupert::RPM::Header.new index }
|
12
|
+
|
13
|
+
it "maps RPM name stored in the header" do
|
14
|
+
index.expects(:get).once.with(name_tag)
|
15
|
+
|
16
|
+
header.name
|
17
|
+
end
|
18
|
+
|
19
|
+
it "maps RPM uncompressed size stored in the header" do
|
20
|
+
index.expects(:get).once.with(size_tag)
|
21
|
+
|
22
|
+
header.uncompressed_size
|
23
|
+
end
|
24
|
+
|
25
|
+
it "maps RPM basenames stored in the header" do
|
26
|
+
index.expects(:get).once.with(basenames_tag)
|
27
|
+
|
28
|
+
header.basenames
|
29
|
+
end
|
30
|
+
|
31
|
+
it "maps RPM dirnames stored in the header" do
|
32
|
+
index.expects(:get).once.with(dirnames_tag)
|
33
|
+
|
34
|
+
header.dirnames
|
35
|
+
end
|
36
|
+
|
37
|
+
it "maps RPM dirindexes stored in the header" do
|
38
|
+
index.expects(:get).once.with(dirindexes_tag)
|
39
|
+
|
40
|
+
header.dirindexes
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe Rupert::RPM::Index do
|
4
|
+
let(:entry) { Rupert::RPM::Entry.new(3145, nil, nil, nil) }
|
5
|
+
let(:store) { mock }
|
6
|
+
let(:index) { Rupert::RPM::Index.new(entry, store) }
|
7
|
+
|
8
|
+
it "retrieves data associated to a specific tag" do
|
9
|
+
entry.expects(:resolve).once.with(store)
|
10
|
+
|
11
|
+
index.get(3145)
|
12
|
+
end
|
13
|
+
end
|
@@ -4,22 +4,10 @@ describe Rupert::RPM::Signature do
|
|
4
4
|
let(:md5_signature_tag) { Rupert::RPM::Signature::MD5_TAG }
|
5
5
|
let(:index) { mock }
|
6
6
|
let(:signature) { Rupert::RPM::Signature.new(index) }
|
7
|
-
let(:pristine_content) { ascii("\x01\x02\x03\x04") }
|
8
|
-
let(:corrupted_content) { ascii("\xf4\x04\x57\x1e") }
|
9
7
|
|
10
|
-
it "
|
11
|
-
index.expects(:get).once.with(md5_signature_tag)
|
8
|
+
it "maps the MD5 stored in the index" do
|
9
|
+
index.expects(:get).once.with(md5_signature_tag)
|
12
10
|
|
13
11
|
signature.md5
|
14
12
|
end
|
15
|
-
|
16
|
-
it "correctly verifies integrity of pristine and corrupted packages" do
|
17
|
-
index.stubs(:get).returns(md5(pristine_content))
|
18
|
-
|
19
|
-
assert signature.verify_checksum(pristine_content),
|
20
|
-
"expected pristine content to be verified correctly, but it was not"
|
21
|
-
|
22
|
-
refute signature.verify_checksum(corrupted_content),
|
23
|
-
"expected corrupted content not to be verified correctly, but it was"
|
24
|
-
end
|
25
13
|
end
|
data/test/unit/rpm_test.rb
CHANGED
@@ -1,19 +1,47 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
3
|
describe Rupert::RPM do
|
4
|
-
let(:signature)
|
5
|
-
let(:
|
6
|
-
let(:
|
4
|
+
let(:signature) { mock }
|
5
|
+
let(:header) { mock }
|
6
|
+
let(:rpm) { Rupert::RPM.new(nil, signature, signed_content, header) }
|
7
|
+
let(:corrupted_rpm) { Rupert::RPM.new(nil, signature, corrupted_content, header) }
|
8
|
+
let(:signed_content) { ascii("\x01\x02\x03\x04") }
|
9
|
+
let(:corrupted_content) { ascii("\xf4\x04\x57\x1e") }
|
7
10
|
|
8
|
-
it "exposes
|
9
|
-
|
11
|
+
it "exposes MD5 checksum in base64 encoding" do
|
12
|
+
random_md5 = random_ascii(128)
|
13
|
+
signature.stubs(:md5).returns(random_md5)
|
10
14
|
|
11
|
-
rpm.md5
|
15
|
+
rpm.md5.must_equal base64(random_md5)
|
12
16
|
end
|
13
17
|
|
14
|
-
it "
|
15
|
-
signature.
|
18
|
+
it "correctly verifies integrity of pristine and corrupted packages" do
|
19
|
+
signature.stubs(:md5).returns(md5(signed_content))
|
16
20
|
|
17
|
-
rpm.intact
|
21
|
+
assert rpm.intact?,
|
22
|
+
"expected RPM to be intact, but it wasn't"
|
23
|
+
|
24
|
+
refute corrupted_rpm.intact?,
|
25
|
+
"expected RPM not to be intact, but it was"
|
26
|
+
end
|
27
|
+
|
28
|
+
it "exposes RPM name stored in the header" do
|
29
|
+
header.stubs(:name).returns("package-name")
|
30
|
+
|
31
|
+
rpm.name.must_equal("package-name")
|
32
|
+
end
|
33
|
+
|
34
|
+
it "exposes RPM uncompressed size stored in the header" do
|
35
|
+
header.stubs(:uncompressed_size).returns(1234)
|
36
|
+
|
37
|
+
rpm.uncompressed_size.must_equal 1234
|
38
|
+
end
|
39
|
+
|
40
|
+
it "exposes RPM basenames stored in the header" do
|
41
|
+
header.stubs(:basenames).returns(["file1", "file1", "file2"])
|
42
|
+
header.stubs(:dirnames).returns(["/dir1", "/dir2"])
|
43
|
+
header.stubs(:dirindexes).returns([0, 1, 0])
|
44
|
+
|
45
|
+
rpm.filenames.must_equal [ "/dir1/file1", "/dir2/file1", "/dir1/file2" ]
|
18
46
|
end
|
19
47
|
end
|
metadata
CHANGED
@@ -1,14 +1,35 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rupert
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stefano Zanella
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
|
-
cert_chain:
|
11
|
-
|
10
|
+
cert_chain:
|
11
|
+
- |
|
12
|
+
-----BEGIN CERTIFICATE-----
|
13
|
+
MIIDQDCCAiigAwIBAgIBADANBgkqhkiG9w0BAQUFADBGMRgwFgYDVQQDDA96YW5l
|
14
|
+
bGxhLnN0ZWZhbm8xFTATBgoJkiaJk/IsZAEZFgVnbWFpbDETMBEGCgmSJomT8ixk
|
15
|
+
ARkWA2NvbTAeFw0xMzA4MDcxOTA2MDZaFw0xNDA4MDcxOTA2MDZaMEYxGDAWBgNV
|
16
|
+
BAMMD3phbmVsbGEuc3RlZmFubzEVMBMGCgmSJomT8ixkARkWBWdtYWlsMRMwEQYK
|
17
|
+
CZImiZPyLGQBGRYDY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
|
18
|
+
2JCnJCnjjs62MR/Tw/4WgSG42ruiCEqXV1ECMsXymHPE8xyHkYAwLXvBRzOkZ/IA
|
19
|
+
puJ1XhScduMRcUuE0ZPA5N2HBZI0WsmyyNTYBjOob8m0SNInoRZfIMloj3D8QzB7
|
20
|
+
/6G5HLMWNx60JEpIDgfXvIuSRKNKQ0/0+/G/H4COgj72pd3F4dYltvx+mSwPRq7Q
|
21
|
+
MdZsK3T5Q3d4eLBY1VSlJpq0wkwdEWTXAhR0Mfmbn1D8m9IhJfubgXuXVBY4OPO8
|
22
|
+
KAF/wWqTkzA6guVQlSKdZR4vwms7OpeFkotnivBKa6JwUQSXO8AZEyy53V8cSYDu
|
23
|
+
dbaFi53YbEwOWSMQnW8/kQIDAQABozkwNzAdBgNVHQ4EFgQUcBKkmJAvSTKfDf7z
|
24
|
+
LEu1wE+Rk+swCQYDVR0TBAIwADALBgNVHQ8EBAMCBLAwDQYJKoZIhvcNAQEFBQAD
|
25
|
+
ggEBAMeqfk1l4Y0iZ8jNiu0afcQ60DVqBkRtrT/rsZEqGsdOw11bntOE4yWpo4Kd
|
26
|
+
Y0C/kYrVQ/mIN7IGKbCSjES3aYdQftV9SRW77FA25m2KXRbnEYtJDUX35gAqSdRY
|
27
|
+
9IiYivsMq2dr70eKPNFrFOwWvmwhcGyEG8EDvYoXWllke7RGz1Dn/AZx6jPnShO+
|
28
|
+
0ru4OXsM9++md3sGXIugEFNygvo2/1yQoTe6+XiBocS+pWsJd6JZBfkxPRT4Dz4H
|
29
|
+
RigBD0E3/t/ABjCXkmqwp5gnAZmP8JiVUkn8rp5E0FXvC8T7nsPs2TW/TAmUV6rN
|
30
|
+
hK25FX8YWgT9fD9y3PpWjiYcrCo=
|
31
|
+
-----END CERTIFICATE-----
|
32
|
+
date: 2013-08-07 00:00:00.000000000 Z
|
12
33
|
dependencies:
|
13
34
|
- !ruby/object:Gem::Dependency
|
14
35
|
name: bundler
|
@@ -134,6 +155,7 @@ files:
|
|
134
155
|
- .gitignore
|
135
156
|
- .ruby-version
|
136
157
|
- .travis.yml
|
158
|
+
- Changelog.md
|
137
159
|
- Gemfile
|
138
160
|
- LICENSE.txt
|
139
161
|
- README.md
|
@@ -143,12 +165,13 @@ files:
|
|
143
165
|
- lib/rupert/errors.rb
|
144
166
|
- lib/rupert/parser.rb
|
145
167
|
- lib/rupert/rpm.rb
|
168
|
+
- lib/rupert/rpm/entry.rb
|
169
|
+
- lib/rupert/rpm/header.rb
|
170
|
+
- lib/rupert/rpm/index.rb
|
146
171
|
- lib/rupert/rpm/lead.rb
|
147
172
|
- lib/rupert/rpm/signature.rb
|
148
|
-
- lib/rupert/rpm/signature/entry.rb
|
149
|
-
- lib/rupert/rpm/signature/index.rb
|
150
|
-
- lib/rupert/rpm/signature/store.rb
|
151
173
|
- lib/rupert/version.rb
|
174
|
+
- rubygems-stefanozanella.crt
|
152
175
|
- rupert.gemspec
|
153
176
|
- test/end_to_end/rpm_signature_test.rb
|
154
177
|
- test/end_to_end/rpm_test.rb
|
@@ -160,9 +183,10 @@ files:
|
|
160
183
|
- test/fixtures/rpm-libs-4.8.0-32.el6.x86_64.rpm
|
161
184
|
- test/fixtures/rpmdevtools-7.5-2.el6.noarch.rpm
|
162
185
|
- test/test_helper.rb
|
186
|
+
- test/unit/rpm/entry_test.rb
|
187
|
+
- test/unit/rpm/header_test.rb
|
188
|
+
- test/unit/rpm/index_test.rb
|
163
189
|
- test/unit/rpm/lead_test.rb
|
164
|
-
- test/unit/rpm/signature/index_test.rb
|
165
|
-
- test/unit/rpm/signature/store_test.rb
|
166
190
|
- test/unit/rpm/signature_test.rb
|
167
191
|
- test/unit/rpm_test.rb
|
168
192
|
homepage: ''
|
@@ -201,8 +225,9 @@ test_files:
|
|
201
225
|
- test/fixtures/rpm-libs-4.8.0-32.el6.x86_64.rpm
|
202
226
|
- test/fixtures/rpmdevtools-7.5-2.el6.noarch.rpm
|
203
227
|
- test/test_helper.rb
|
228
|
+
- test/unit/rpm/entry_test.rb
|
229
|
+
- test/unit/rpm/header_test.rb
|
230
|
+
- test/unit/rpm/index_test.rb
|
204
231
|
- test/unit/rpm/lead_test.rb
|
205
|
-
- test/unit/rpm/signature/index_test.rb
|
206
|
-
- test/unit/rpm/signature/store_test.rb
|
207
232
|
- test/unit/rpm/signature_test.rb
|
208
233
|
- test/unit/rpm_test.rb
|
metadata.gz.sig
ADDED
Binary file
|
@@ -1,19 +0,0 @@
|
|
1
|
-
module Rupert
|
2
|
-
class RPM
|
3
|
-
class Signature
|
4
|
-
class Entry
|
5
|
-
# Initializes a new index entry.
|
6
|
-
#
|
7
|
-
# @param tag [String] 4 byte entry tag (semantic data type)
|
8
|
-
# @param type [String] 4 byte entry data format
|
9
|
-
# @param offset [String] 4 byte pointer to data in the index store
|
10
|
-
# @param count [String] 4 byte number of data items held by the entry
|
11
|
-
def initialize(tag, type, offset, count)
|
12
|
-
@tag, @type, @offset, @count = tag, type, offset, count
|
13
|
-
end
|
14
|
-
|
15
|
-
attr_accessor :tag, :type, :offset, :count
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
@@ -1,32 +0,0 @@
|
|
1
|
-
require 'rupert/rpm/signature/entry'
|
2
|
-
require 'rupert/rpm/signature/store'
|
3
|
-
|
4
|
-
module Rupert
|
5
|
-
class RPM
|
6
|
-
class Signature
|
7
|
-
class Index
|
8
|
-
# Initializes a new signature index, given the header's entries and the
|
9
|
-
# store containing actual data.
|
10
|
-
#
|
11
|
-
# @param entries [Hash] a map of
|
12
|
-
# Rupert::RPM::Signature::Entry, indexed by tag
|
13
|
-
# @param store [Rupert::RPM::Signature::Store] store containing
|
14
|
-
# data pointed by entries
|
15
|
-
def initialize(entries, store)
|
16
|
-
@entries = entries
|
17
|
-
@store = store
|
18
|
-
end
|
19
|
-
|
20
|
-
# Retrieves data pointed by given tag.
|
21
|
-
#
|
22
|
-
# @param tag [Fixnum] data type
|
23
|
-
# @return [Object] data associated to given tag, with variable format
|
24
|
-
# depending on how it's stored
|
25
|
-
def get(tag)
|
26
|
-
entry = @entries[tag]
|
27
|
-
@store.fetch(entry)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
@@ -1,35 +0,0 @@
|
|
1
|
-
module Rupert
|
2
|
-
class RPM
|
3
|
-
class Signature
|
4
|
-
# Package information is mostly contained in headers. Headers are composed
|
5
|
-
# of an index and a store.
|
6
|
-
# The (raw) store holds semantic RPM information in an undefined
|
7
|
-
# order and without any structure (i.e. by concatenating all pieces
|
8
|
-
# together). Addressing of resources in the store is handled by header
|
9
|
-
# entries, which define data format, position and size. Responsibility
|
10
|
-
# of the store is to take care of extracting these pieces given proper
|
11
|
-
# addressing information.
|
12
|
-
class Store
|
13
|
-
# Creates a new store by wrapping given chunck of raw data into a Store
|
14
|
-
# object.
|
15
|
-
#
|
16
|
-
# @param io [IO] raw binary data carved from RPM header. Must be an IO
|
17
|
-
# containing only store's data (i.e. entry addresses are considered
|
18
|
-
# relative to 0)
|
19
|
-
def initialize(io)
|
20
|
-
@io = io
|
21
|
-
end
|
22
|
-
|
23
|
-
# Fetches data pointed by given entry.
|
24
|
-
#
|
25
|
-
# @param entry [Rupert::RPM::Signature::Entry] entry containing address
|
26
|
-
# and type of needed information
|
27
|
-
# @return [String] binary string containing asked data
|
28
|
-
def fetch(entry)
|
29
|
-
@io.seek(entry.offset, IO::SEEK_SET)
|
30
|
-
@io.read(entry.count)
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
@@ -1,14 +0,0 @@
|
|
1
|
-
require 'test_helper'
|
2
|
-
|
3
|
-
describe Rupert::RPM::Signature::Index do
|
4
|
-
let(:entry) { Rupert::RPM::Signature::Entry.new(1234, 7, 567, 890) }
|
5
|
-
let(:entries) { { entry.tag => entry } }
|
6
|
-
let(:store) { mock }
|
7
|
-
let(:index) { Rupert::RPM::Signature::Index.new(entries, store) }
|
8
|
-
|
9
|
-
it "retrieves binary data marked with a specific tag from the store" do
|
10
|
-
store.expects(:fetch).once.with(entry)
|
11
|
-
|
12
|
-
index.get(entry.tag)
|
13
|
-
end
|
14
|
-
end
|
@@ -1,12 +0,0 @@
|
|
1
|
-
require 'test_helper'
|
2
|
-
|
3
|
-
describe Rupert::RPM::Signature::Store do
|
4
|
-
let(:raw_io) { io(ascii("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a")) }
|
5
|
-
let(:entry) { Rupert::RPM::Signature::Entry.new(nil, 7, 4, 5) }
|
6
|
-
let(:store) { Rupert::RPM::Signature::Store.new(raw_io) }
|
7
|
-
|
8
|
-
it "fetches a raw chunck of data given the entry that points to it" do
|
9
|
-
data = store.fetch(entry)
|
10
|
-
data.must_equal ascii("\x04\x05\x06\x07\x08")
|
11
|
-
end
|
12
|
-
end
|