rupert 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Gem Version](https://badge.fury.io/rb/rupert.png)](http://badge.fury.io/rb/rupert)
|
1
2
|
[![Build Status](https://travis-ci.org/stefanozanella/rupert.png?branch=master)](https://travis-ci.org/stefanozanella/rupert)
|
2
3
|
[![Code Climate](https://codeclimate.com/github/stefanozanella/rupert.png)](https://codeclimate.com/github/stefanozanella/rupert)
|
3
4
|
[![Coverage Status](https://coveralls.io/repos/stefanozanella/rupert/badge.png?branch=master)](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
|