rupert 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.ruby-version +1 -0
- data/.travis.yml +11 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +42 -0
- data/Rakefile +34 -0
- data/TODO.md +39 -0
- data/lib/rupert/errors.rb +3 -0
- data/lib/rupert/parser.rb +60 -0
- data/lib/rupert/rpm/lead.rb +143 -0
- data/lib/rupert/rpm/signature/entry.rb +19 -0
- data/lib/rupert/rpm/signature/index.rb +32 -0
- data/lib/rupert/rpm/signature/store.rb +35 -0
- data/lib/rupert/rpm/signature.rb +43 -0
- data/lib/rupert/rpm.rb +118 -0
- data/lib/rupert/version.rb +3 -0
- data/lib/rupert.rb +2 -0
- data/rupert.gemspec +34 -0
- data/test/end_to_end/rpm_signature_test.rb +14 -0
- data/test/end_to_end/rpm_test.rb +50 -0
- data/test/fixtures/README.md +6 -0
- data/test/fixtures/notanrpm-0.0.1-1.el6.noarch.rpm +0 -0
- data/test/fixtures/redhat-lsb-4.0-7.el6.centos.src.rpm +0 -0
- data/test/fixtures/rpm-4.8.0-32.el6.x86_64.rpm +0 -0
- data/test/fixtures/rpm-libs-4.8.0-32.el6.i686.rpm +0 -0
- data/test/fixtures/rpm-libs-4.8.0-32.el6.x86_64.rpm +0 -0
- data/test/fixtures/rpmdevtools-7.5-2.el6.noarch.rpm +0 -0
- data/test/test_helper.rb +65 -0
- data/test/unit/rpm/lead_test.rb +100 -0
- data/test/unit/rpm/signature/index_test.rb +14 -0
- data/test/unit/rpm/signature/store_test.rb +12 -0
- data/test/unit/rpm/signature_test.rb +25 -0
- data/test/unit/rpm_test.rb +19 -0
- metadata +208 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 41a2074b3a0e08e088990219f5659323705498e3
|
4
|
+
data.tar.gz: 810e02415872bb758efa3b109f70e5d264f8455f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3b351ae5740f728a267a41afc8a474921aaf072d3141209cc86d743e4011ebe223adb5c6fb45db7c3bb29d4b67d5cbae99d6748c564264f97b2160508db7f626
|
7
|
+
data.tar.gz: 1585d64649df3e91617ddb3b4b88464d8a4f3aac419e18c862ee1009de1915554502be5c793257d79d4a0f39164008d98170bb79bbe59787f3a9ff75e2eba24f
|
data/.gitignore
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.0.0-p247
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Stefano Zanella
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
[![Build Status](https://travis-ci.org/stefanozanella/rupert.png?branch=master)](https://travis-ci.org/stefanozanella/rupert)
|
2
|
+
[![Code Climate](https://codeclimate.com/github/stefanozanella/rupert.png)](https://codeclimate.com/github/stefanozanella/rupert)
|
3
|
+
[![Coverage Status](https://coveralls.io/repos/stefanozanella/rupert/badge.png?branch=master)](https://coveralls.io/r/stefanozanella/rupert?branch=master)
|
4
|
+
[![Dependency Status](https://gemnasium.com/stefanozanella/rupert.png)](https://gemnasium.com/stefanozanella/rupert)
|
5
|
+
|
6
|
+
# Rupert
|
7
|
+
|
8
|
+
Pure Ruby RPM Library
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
|
12
|
+
Add this line to your application's Gemfile:
|
13
|
+
|
14
|
+
gem 'rupert'
|
15
|
+
|
16
|
+
And then execute:
|
17
|
+
|
18
|
+
$ bundle
|
19
|
+
|
20
|
+
Or install it yourself as:
|
21
|
+
|
22
|
+
$ gem install rupert
|
23
|
+
|
24
|
+
## Usage
|
25
|
+
|
26
|
+
You can read an RPM simply with:
|
27
|
+
|
28
|
+
rpm = Rupert::RPM.load('rpm-4.8.0-32.el6.x86_64.rpm')
|
29
|
+
|
30
|
+
or just check if a specific file is an RPM with:
|
31
|
+
|
32
|
+
Rupert::RPM.rpm? 'iamtrollingyou' # false
|
33
|
+
|
34
|
+
(note that loading a file that is not an RPM generates an exception)
|
35
|
+
|
36
|
+
## Contributing
|
37
|
+
|
38
|
+
1. Fork it
|
39
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
40
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
41
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
42
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'rake/testtask'
|
2
|
+
require 'coveralls/rake/task'
|
3
|
+
|
4
|
+
namespace :gem do
|
5
|
+
require "bundler/gem_tasks"
|
6
|
+
end
|
7
|
+
|
8
|
+
Rake::TestTask.new do |t|
|
9
|
+
t.verbose = true
|
10
|
+
t.libs << 'lib'
|
11
|
+
t.libs << 'test'
|
12
|
+
t.test_files = FileList["test/**/*_test.rb"]
|
13
|
+
end
|
14
|
+
|
15
|
+
namespace :test do
|
16
|
+
%w{end_to_end unit}.each do |suite|
|
17
|
+
Rake::TestTask.new(suite.to_sym) do |t|
|
18
|
+
t.verbose = true
|
19
|
+
t.libs << 'lib'
|
20
|
+
t.libs << 'test'
|
21
|
+
t.test_files = FileList["test/#{suite}/**/*_test.rb"]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
desc "Analyze code duplication"
|
27
|
+
task :flay do
|
28
|
+
system "flay lib/*.rb"
|
29
|
+
end
|
30
|
+
|
31
|
+
desc "Analyze code complexity"
|
32
|
+
task :flog do
|
33
|
+
system "find lib -name \*.rb | xargs flog"
|
34
|
+
end
|
data/TODO.md
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# TODO (Features, Tests et al.)
|
2
|
+
|
3
|
+
* If a file is not an RPM, every attempt to read anything should misreably
|
4
|
+
fail.
|
5
|
+
* Add control logic to verify if a file exists before creating a new RPM in
|
6
|
+
Rupture::RPM#load
|
7
|
+
* describe RPM::Lead it fails nicely when architecture is unknown -> def arch
|
8
|
+
return @@arch_map[@archnum] || "unknown"
|
9
|
+
* Recognize all architectures
|
10
|
+
* Edge case: verify correct idenfitication of `noarch`
|
11
|
+
* Recognize all the OSes (maybe also add `osnum` method?)
|
12
|
+
* How to fail when os is not recognized?
|
13
|
+
* Document references to RPM spec docs and source code
|
14
|
+
* Document Rupture::RPM::Lead (summarize structure and purpose from rpm file
|
15
|
+
format document)
|
16
|
+
* Return nil or "" as md5 if no signature found (this means not creating the
|
17
|
+
signature if lead says no). Consider also this: if a signature is found and
|
18
|
+
md5 is not present, then mandatory rules are not violated. Finally, I'm not
|
19
|
+
sure it's so optional to include a signature; I suspect it's mandatory
|
20
|
+
(perhaps because md5 is mandatory?).
|
21
|
+
* Document Rupture::RPM::Signature::Index::Entry class (in particular, how the
|
22
|
+
meaning of the `count` field changes with data type
|
23
|
+
* Store#get error handling. Pass nil, out of bound address, non-numerical
|
24
|
+
address
|
25
|
+
* Use actual size contained in the signature to calculate checksum?
|
26
|
+
* Remember that signature is not mandatory in RPM!
|
27
|
+
* It looks like size is also a form of signature, in the sense that RPM uses
|
28
|
+
the stored value to check actual size of header + payload (or did I
|
29
|
+
understood wrong?).
|
30
|
+
* I18N ???
|
31
|
+
|
32
|
+
# Roadmap
|
33
|
+
|
34
|
+
* Pick the entry type table. For each type, pick a meaningful header tag (like
|
35
|
+
name, file list, etc.) to derive a feature to be implemented. At the end, all
|
36
|
+
types should be correctly handled
|
37
|
+
* Decide a sensible behaviour for missing mandatory tags. Then, pick all
|
38
|
+
mandatory header tags and build a feature for them if not already covered
|
39
|
+
with previous method.
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'rupert/rpm/lead'
|
2
|
+
require 'rupert/rpm/signature'
|
3
|
+
|
4
|
+
module Rupert
|
5
|
+
class Parser
|
6
|
+
def initialize(raw_io)
|
7
|
+
@raw_io = raw_io
|
8
|
+
end
|
9
|
+
|
10
|
+
def parse
|
11
|
+
# TODO Fit to current design (i.e. no parsing in Lead c'tor?)
|
12
|
+
lead = RPM::Lead.new(@raw_io)
|
13
|
+
|
14
|
+
entry_count, store_size = parse_header
|
15
|
+
entries = parse_entries(entry_count)
|
16
|
+
|
17
|
+
store = parse_store(store_size)
|
18
|
+
content = parse_content
|
19
|
+
|
20
|
+
signature = RPM::Signature.new(RPM::Signature::Index.new(entries, store))
|
21
|
+
|
22
|
+
RPM.new(lead, signature, content)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def parse_header
|
28
|
+
header_size = 16
|
29
|
+
header_format = "@8NN"
|
30
|
+
|
31
|
+
@raw_io.read(header_size).unpack(header_format)
|
32
|
+
end
|
33
|
+
|
34
|
+
def parse_entries(count)
|
35
|
+
entry_size = 16
|
36
|
+
entry_format = "NNNN"
|
37
|
+
|
38
|
+
entries = Hash.new
|
39
|
+
count.times do
|
40
|
+
tag, type, offset, count = @raw_io.read(entry_size).unpack(entry_format)
|
41
|
+
entry = RPM::Signature::Entry.new tag, type, offset, count
|
42
|
+
entries[entry.tag] = entry
|
43
|
+
end
|
44
|
+
|
45
|
+
entries
|
46
|
+
end
|
47
|
+
|
48
|
+
def parse_store(size)
|
49
|
+
RPM::Signature::Store.new(StringIO.new(@raw_io.read(nearest_multiple(size, 8))))
|
50
|
+
end
|
51
|
+
|
52
|
+
def parse_content
|
53
|
+
@raw_io.read
|
54
|
+
end
|
55
|
+
|
56
|
+
def nearest_multiple(size, modulo)
|
57
|
+
(size / modulo.to_f).ceil * modulo
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
module Rupert
|
2
|
+
class RPM
|
3
|
+
class Lead
|
4
|
+
# Lead has a fixed length
|
5
|
+
LEAD_LENGTH = 96.freeze # byte
|
6
|
+
|
7
|
+
# The magic header that identifies an RPM beyond a shadow of a doubt, as
|
8
|
+
# every good RPM starts with hex `ed ab ee db`.
|
9
|
+
MAGIC = "\xed\xab\xee\xdb".force_encoding(Encoding::ASCII_8BIT).freeze
|
10
|
+
|
11
|
+
# RPM of type binary
|
12
|
+
TYPE_BINARY = 0.freeze
|
13
|
+
|
14
|
+
# RPM of type source
|
15
|
+
TYPE_SOURCE = 1.freeze
|
16
|
+
|
17
|
+
@@arch_map = {
|
18
|
+
1 => "i386/x86_64"
|
19
|
+
}.freeze
|
20
|
+
|
21
|
+
@@os_map = {
|
22
|
+
1 => "Linux"
|
23
|
+
}.freeze
|
24
|
+
|
25
|
+
# Only valid and recognized signature type
|
26
|
+
SIGNATURE_TYPE_HEADER = 5.freeze
|
27
|
+
|
28
|
+
# Chomps given IO, producing a `Lead` object and returning the remaining
|
29
|
+
# part for subsequent processing.
|
30
|
+
#
|
31
|
+
# Lead data is expected to begin at IO start, so returned scrap is
|
32
|
+
# basically the input IO without its first
|
33
|
+
# `Rupert::RPM::Lead::LEAD_LENGTH` bytes.
|
34
|
+
#
|
35
|
+
# @param io [IO] IO object containing lead data at its start, possibly
|
36
|
+
# with additional bytes at the end
|
37
|
+
#
|
38
|
+
# @return [Rupert::RPM::Lead, IO] the lead object corresponding to the
|
39
|
+
# data at the beginning of the IO, and the part of the input remaining
|
40
|
+
# after parsing.
|
41
|
+
def self.chomp(io)
|
42
|
+
[ self.new(io), io ]
|
43
|
+
end
|
44
|
+
|
45
|
+
# Initializes a lead section, parsing given IO
|
46
|
+
#
|
47
|
+
# @param lead [IO] An IO containing the lead information at the start
|
48
|
+
def initialize(lead)
|
49
|
+
lead_data = lead.read(LEAD_LENGTH)
|
50
|
+
parse(lead_data) if lead_data
|
51
|
+
end
|
52
|
+
|
53
|
+
# Major number of the RPM version used to format the package
|
54
|
+
#
|
55
|
+
# @return [Fixnum] RPM major version number
|
56
|
+
def rpm_version_major
|
57
|
+
@rpm_major
|
58
|
+
end
|
59
|
+
|
60
|
+
# Minor number of the RPM version used to format the package
|
61
|
+
#
|
62
|
+
# @return [Fixnum] RPM minor version number
|
63
|
+
def rpm_version_minor
|
64
|
+
@rpm_minor
|
65
|
+
end
|
66
|
+
|
67
|
+
# RPM version used to format the package
|
68
|
+
#
|
69
|
+
# @return [String] RPM version in <major>.<minor> format
|
70
|
+
def rpm_version
|
71
|
+
"#{rpm_version_major}.#{rpm_version_minor}"
|
72
|
+
end
|
73
|
+
|
74
|
+
# Tells if the file is recognized as an RPM or not
|
75
|
+
#
|
76
|
+
# @return `true` if magic number is found at lead's start, `false`
|
77
|
+
# otherwise
|
78
|
+
def rpm?
|
79
|
+
@magic == MAGIC
|
80
|
+
end
|
81
|
+
|
82
|
+
# @return `true` if lead reports RPM as of binary type
|
83
|
+
def binary_type?
|
84
|
+
@type == TYPE_BINARY
|
85
|
+
end
|
86
|
+
|
87
|
+
# @return `true` if lead reports RPM as of source type
|
88
|
+
def source_type?
|
89
|
+
@type == TYPE_SOURCE
|
90
|
+
end
|
91
|
+
|
92
|
+
# The architecture the package was built for. A list of supported
|
93
|
+
# architectures can be found in `/usr/lib/rpm/rpmrc` on RedHat based
|
94
|
+
# systems.
|
95
|
+
#
|
96
|
+
# @return [String] a string representing the architecture name(s)
|
97
|
+
def arch
|
98
|
+
@@arch_map[@archnum]
|
99
|
+
end
|
100
|
+
|
101
|
+
# The name of the package
|
102
|
+
#
|
103
|
+
# @return [String] package name in the form <name>-<version>-<rev>.<suffix>
|
104
|
+
def name
|
105
|
+
@name
|
106
|
+
end
|
107
|
+
|
108
|
+
# OS for which the package was built
|
109
|
+
#
|
110
|
+
# @return [String] OS canonical name as defined in `/usr/lib/rpm/rpmrc`
|
111
|
+
def os
|
112
|
+
@@os_map[@osnum]
|
113
|
+
end
|
114
|
+
|
115
|
+
# @return `true` if the RPM is recognized as being signed, `false` otherwise
|
116
|
+
def signed?
|
117
|
+
@signature_type == SIGNATURE_TYPE_HEADER
|
118
|
+
end
|
119
|
+
|
120
|
+
# String of reserved bits. It's here for completeness of the lead's
|
121
|
+
# implementation but it isn't intended to be actually used
|
122
|
+
#
|
123
|
+
# @return [String] the raw 16 bytes long reserved string at the end of
|
124
|
+
# the lead
|
125
|
+
def reserved
|
126
|
+
@reserved
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
130
|
+
|
131
|
+
# :nodoc
|
132
|
+
# The format string passed to `unpack` to parse the lead
|
133
|
+
LEAD_FORMAT = "A4CCnnZ66nna16".freeze
|
134
|
+
|
135
|
+
# :nodoc
|
136
|
+
# Unpacks lead raw bytes into its semantic components
|
137
|
+
def parse(lead_data)
|
138
|
+
@magic, @rpm_major, @rpm_minor, @type, @archnum, @name, @osnum,
|
139
|
+
@signature_type, @reserved = lead_data.unpack(LEAD_FORMAT)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,19 @@
|
|
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
|
@@ -0,0 +1,32 @@
|
|
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
|
@@ -0,0 +1,35 @@
|
|
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
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'rupert/rpm/signature/index'
|
2
|
+
|
3
|
+
module Rupert
|
4
|
+
class RPM
|
5
|
+
class Signature
|
6
|
+
# Tag holding 128-bit MD5 checksum of header and payload
|
7
|
+
MD5_TAG = 1004.freeze
|
8
|
+
|
9
|
+
# Creates a new signature given its components.
|
10
|
+
#
|
11
|
+
# @param index [Rupert::RPM::Signature::Index] the signature index
|
12
|
+
# containing actual signature data
|
13
|
+
def initialize(index)
|
14
|
+
@index = index
|
15
|
+
end
|
16
|
+
|
17
|
+
# MD5 checksum contained in the RPM.
|
18
|
+
#
|
19
|
+
# @return [String] 128-bit MD5 checksum of RPM's header and payload, in
|
20
|
+
# raw binary form.
|
21
|
+
def md5
|
22
|
+
@index.get MD5_TAG
|
23
|
+
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
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/rupert/rpm.rb
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'rupert/errors'
|
2
|
+
require 'rupert/parser'
|
3
|
+
require 'rupert/rpm/lead'
|
4
|
+
require 'rupert/rpm/signature'
|
5
|
+
|
6
|
+
require 'base64'
|
7
|
+
|
8
|
+
module Rupert
|
9
|
+
class RPM
|
10
|
+
class << self
|
11
|
+
# Loads a RPM file and parses its structure
|
12
|
+
#
|
13
|
+
# @param filename [String] filename of the RPM to load
|
14
|
+
# @return [Rupert::RPM] the parsed RPM
|
15
|
+
def load(filename)
|
16
|
+
raise NotAnRPM,
|
17
|
+
"File #{filename} isn't a valid RPM" unless rpm?(filename)
|
18
|
+
|
19
|
+
raw_io = File.open(filename, 'r')
|
20
|
+
rpm = Parser.new(raw_io).parse
|
21
|
+
raw_io.close
|
22
|
+
|
23
|
+
return rpm
|
24
|
+
end
|
25
|
+
|
26
|
+
# Tells whether given filename points to a valid RPM or not.
|
27
|
+
#
|
28
|
+
# @param filename [String] filename to inspect
|
29
|
+
# @return `true` if file starts with the correct magic header
|
30
|
+
def rpm?(filename)
|
31
|
+
Lead.new(File.open(filename, 'r')).rpm?
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Initialize the RPM object, given its components.
|
36
|
+
#
|
37
|
+
# This method is not intended to be used to instantiate RPM objects
|
38
|
+
# directly. Instead, use Rupert::RPM::load for a more straightforward
|
39
|
+
# alternative.
|
40
|
+
#
|
41
|
+
# @param lead [Rupert::RPM::Lead] RPM lead section
|
42
|
+
# @param signature [Rupert::RPM::Signature] RPM signature section
|
43
|
+
# @param content [String] Raw content found after the signature structure
|
44
|
+
def initialize(lead, signature, content)
|
45
|
+
@lead = lead
|
46
|
+
@signature = signature
|
47
|
+
@content = content
|
48
|
+
end
|
49
|
+
|
50
|
+
# RPM version used to encode the package.
|
51
|
+
#
|
52
|
+
# @return [String] the RPM version in `<major>.<minor>` format
|
53
|
+
def rpm_version
|
54
|
+
@lead.rpm_version
|
55
|
+
end
|
56
|
+
|
57
|
+
# @return `true` if the RPM is of type binary, `false` otherwise
|
58
|
+
def binary?
|
59
|
+
@lead.binary_type?
|
60
|
+
end
|
61
|
+
|
62
|
+
# @return `true` if the RPM is of type source, `false` otherwise
|
63
|
+
def source?
|
64
|
+
@lead.source_type?
|
65
|
+
end
|
66
|
+
|
67
|
+
# Which architecture the package was built for, e.g. `i386/x86_64` or
|
68
|
+
# `arm`
|
69
|
+
#
|
70
|
+
# @return [String] package architecture name
|
71
|
+
def rpm_arch
|
72
|
+
@lead.arch
|
73
|
+
end
|
74
|
+
|
75
|
+
# Full package name
|
76
|
+
#
|
77
|
+
# @return [String] package name in the form <name>-<version>-<rev>.<suffix>
|
78
|
+
def name
|
79
|
+
@lead.name
|
80
|
+
end
|
81
|
+
|
82
|
+
# OS for which the package was built
|
83
|
+
#
|
84
|
+
# @return [String] as defined in /usr/lib/rpm/rpmrc under the canonical OS
|
85
|
+
# names section
|
86
|
+
def os
|
87
|
+
@lead.os
|
88
|
+
end
|
89
|
+
|
90
|
+
# @return `true` if the package is signed, `false` otherwise
|
91
|
+
def signed?
|
92
|
+
@lead.signed?
|
93
|
+
end
|
94
|
+
|
95
|
+
# MD5 checksum stored in the package (base64 encoded). To be used to check
|
96
|
+
# package integrity.
|
97
|
+
#
|
98
|
+
# NOTE: This is not the MD5 of the whole package; rather, the digest is
|
99
|
+
# calculated over the header and payload, leaving out the lead and the
|
100
|
+
# signature header. I.e., running `md5sum <myrpm>` won't held the same
|
101
|
+
# result as `Rupert::RPM.load('<myrpm>').md5`.
|
102
|
+
#
|
103
|
+
# @return [String] Base64-encoded MD5 checksum of package's header and
|
104
|
+
# payload, stored in the RPM itself
|
105
|
+
def md5
|
106
|
+
Base64.strict_encode64(@signature.md5)
|
107
|
+
end
|
108
|
+
|
109
|
+
# Verifies package integrity. Compares MD5 checksum stored in the package
|
110
|
+
# with checksum calculated over header(s) and payload (archive).
|
111
|
+
#
|
112
|
+
# @return `true` if package is intact, `false` if package (either stored MD5 or
|
113
|
+
# payload) is corrupted
|
114
|
+
def intact?
|
115
|
+
@signature.verify_checksum(@content)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
data/lib/rupert.rb
ADDED
data/rupert.gemspec
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
$:.unshift('lib') unless $:.include?('lib')
|
3
|
+
require 'rupert/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "rupert"
|
7
|
+
spec.version = Rupert::VERSION
|
8
|
+
spec.authors = ["Stefano Zanella"]
|
9
|
+
spec.email = ["zanella.stefano@gmail.com"]
|
10
|
+
spec.summary = %q{Pure Ruby RPM Library}
|
11
|
+
spec.homepage = ""
|
12
|
+
spec.license = "MIT"
|
13
|
+
|
14
|
+
spec.files = `git ls-files`.split($/)
|
15
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
16
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
17
|
+
spec.require_paths = ["lib"]
|
18
|
+
|
19
|
+
spec.extra_rdoc_files = ["README.md"]
|
20
|
+
spec.rdoc_options = ["--charset=UTF-8"]
|
21
|
+
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
23
|
+
spec.add_development_dependency "rake"
|
24
|
+
spec.add_development_dependency "minitest", "~> 5"
|
25
|
+
spec.add_development_dependency "minitest-spec-context"
|
26
|
+
spec.add_development_dependency "mocha", "~> 0"
|
27
|
+
spec.add_development_dependency "coveralls"
|
28
|
+
spec.add_development_dependency "flog"
|
29
|
+
spec.add_development_dependency "flay"
|
30
|
+
|
31
|
+
spec.description = %s{
|
32
|
+
Rupert allows to manipulate RPM files independently from availability of rpmlib.
|
33
|
+
}
|
34
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe Rupert::RPM do
|
4
|
+
let(:valid_rpm) { fixture('rpm-4.8.0-32.el6.x86_64.rpm') }
|
5
|
+
let(:rpm) { Rupert::RPM.load(valid_rpm) }
|
6
|
+
|
7
|
+
it "reads the package checksum" do
|
8
|
+
rpm.md5.must_equal "jvA7YQW9TICIGWjaSLF/sA=="
|
9
|
+
end
|
10
|
+
|
11
|
+
it "verifies package (non-cryptographic) integrity" do
|
12
|
+
assert rpm.intact?, "expected package to be intact, but it was not."
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe Rupert::RPM do
|
4
|
+
let(:valid_rpm) { fixture('rpm-4.8.0-32.el6.x86_64.rpm') }
|
5
|
+
let(:invalid_rpm) { fixture('notanrpm-0.0.1-1.el6.noarch.rpm') }
|
6
|
+
let(:bin_rpm) { fixture('rpm-4.8.0-32.el6.x86_64.rpm') }
|
7
|
+
let(:src_rpm) { fixture('redhat-lsb-4.0-7.el6.centos.src.rpm') }
|
8
|
+
|
9
|
+
let(:rpm) { Rupert::RPM.load(valid_rpm) }
|
10
|
+
let(:binary_rpm) { Rupert::RPM.load(bin_rpm) }
|
11
|
+
let(:source_rpm) { Rupert::RPM.load(src_rpm) }
|
12
|
+
|
13
|
+
it "correctly loads a valid RPM" do
|
14
|
+
Rupert::RPM.load(valid_rpm)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "raises an error if the RPM is not in valid format" do
|
18
|
+
proc {
|
19
|
+
Rupert::RPM.load(invalid_rpm)
|
20
|
+
}.must_raise Rupert::NotAnRPM
|
21
|
+
end
|
22
|
+
|
23
|
+
it "knows which version of RPM the file is" do
|
24
|
+
rpm.rpm_version.must_equal "3.0"
|
25
|
+
end
|
26
|
+
|
27
|
+
it "tells which architecture the package is built for" do
|
28
|
+
rpm.rpm_arch.must_equal "i386/x86_64"
|
29
|
+
end
|
30
|
+
|
31
|
+
it "tells if the file is a binary or source RPM" do
|
32
|
+
assert binary_rpm.binary?, "failed to recognize RPM as binary"
|
33
|
+
refute binary_rpm.source?, "RPM misrecognized as of source type"
|
34
|
+
|
35
|
+
assert source_rpm.source?, "failed to recognize RPM as source"
|
36
|
+
refute source_rpm.binary?, "RPM misrecognized as of binary type"
|
37
|
+
end
|
38
|
+
|
39
|
+
it "tells the package's name" do
|
40
|
+
rpm.name.must_equal "rpm-4.8.0-32.el6"
|
41
|
+
end
|
42
|
+
|
43
|
+
it "tells the operating system for which the package has been built" do
|
44
|
+
rpm.os.must_equal "Linux"
|
45
|
+
end
|
46
|
+
|
47
|
+
it "tells if package is signed" do
|
48
|
+
assert rpm.signed?, "expected the package to be signed, but it was not"
|
49
|
+
end
|
50
|
+
end
|
File without changes
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'coveralls'
|
2
|
+
Coveralls.wear!
|
3
|
+
|
4
|
+
require 'minitest/autorun'
|
5
|
+
require 'minitest/pride'
|
6
|
+
require 'minitest/spec'
|
7
|
+
require 'minitest-spec-context'
|
8
|
+
require 'mocha/setup'
|
9
|
+
|
10
|
+
require 'rupert'
|
11
|
+
require 'rupert/errors'
|
12
|
+
|
13
|
+
# Returns the absolute path for the given fixture filename, or the path of
|
14
|
+
# fixture directory if no argument present.
|
15
|
+
def fixture(filename='')
|
16
|
+
File.join(File.dirname(__FILE__), 'fixtures', filename)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Forces the string to be encoded as 8 bit ASCII (instead of, for example,
|
20
|
+
# UTF-8). This is to be used whenever a (binary) string is manually hardcoded
|
21
|
+
# in tests, since using a different encoding (e.g. UTF-8) breaks
|
22
|
+
# parsing/generating the IO stream.
|
23
|
+
def ascii(string)
|
24
|
+
string.force_encoding(Encoding::ASCII_8BIT)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Pads a string with nulls to fill given length
|
28
|
+
def pad(string, length)
|
29
|
+
("%-#{length}.#{length}s" % string).gsub(" ", "\x00")
|
30
|
+
end
|
31
|
+
|
32
|
+
# A string of nulls of given length
|
33
|
+
def null(length)
|
34
|
+
pad("", length)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Returns 128-bit MD5 checksum of given string
|
38
|
+
def md5(str)
|
39
|
+
Digest::MD5.digest(str)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Transforms given string into an IO object.
|
43
|
+
def io(string)
|
44
|
+
StringIO.new(string)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Helpers that builds a lead header from a plain string. Use it to make tests
|
48
|
+
# more readable.
|
49
|
+
def lead_from(string)
|
50
|
+
Rupert::RPM::Lead.new(io(string))
|
51
|
+
end
|
52
|
+
|
53
|
+
def lead_with(string)
|
54
|
+
lead_from(string)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Helpers that builds a signature header from a plain string. Use it to make tests
|
58
|
+
# more readable.
|
59
|
+
def signature_from(string)
|
60
|
+
Rupert::RPM::Signature.new(io(string))
|
61
|
+
end
|
62
|
+
|
63
|
+
def signature_with(string)
|
64
|
+
signature_from(string)
|
65
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe Rupert::RPM::Lead do
|
4
|
+
context "when reading a proper RPM" do
|
5
|
+
let(:rpm_version_raw) { ascii("\x02\x01") }
|
6
|
+
let(:binary_type_raw) { ascii("\x00\x00") }
|
7
|
+
let(:source_type_raw) { ascii("\x00\x01") }
|
8
|
+
let(:archnum_raw) { ascii("\x00\x01") }
|
9
|
+
let(:pkg_name_raw) { ascii(pad("awesomepkg-1.0-127.fedora19", 66)) }
|
10
|
+
let(:os_raw) { ascii("\x00\x01") }
|
11
|
+
let(:sig_type_header_raw) { ascii("\x00\x05") }
|
12
|
+
let(:unknown_sig_type_raw) { ascii("\x03\x03") }
|
13
|
+
let(:reserved_string_raw) { ascii(null(16)) }
|
14
|
+
|
15
|
+
let(:rpm_version) { "#{Rupert::RPM::Lead::MAGIC}#{rpm_version_raw}" }
|
16
|
+
let(:binary_type) { "#{rpm_version}#{binary_type_raw}" }
|
17
|
+
let(:source_type) { "#{rpm_version}#{source_type_raw}" }
|
18
|
+
let(:arch) { "#{binary_type}#{archnum_raw}" }
|
19
|
+
let(:pkg_name) { "#{arch}#{pkg_name_raw}" }
|
20
|
+
let(:os) { "#{pkg_name}#{os_raw}" }
|
21
|
+
let(:signature_type_header) { "#{os}#{sig_type_header_raw}" }
|
22
|
+
let(:unknown_signature_type) { "#{os}#{unknown_sig_type_raw}" }
|
23
|
+
let(:reserved_bits) { "#{signature_type_header}#{reserved_string_raw}" }
|
24
|
+
let(:additional_content) { "#{reserved_bits}this_is_not_part_of_the_lead" }
|
25
|
+
|
26
|
+
it "parses RPM major and minor version numbers" do
|
27
|
+
lead_with(rpm_version).rpm_version_major.must_equal 2
|
28
|
+
lead_with(rpm_version).rpm_version_minor.must_equal 1
|
29
|
+
end
|
30
|
+
|
31
|
+
it "outputs RPM version number if common string format" do
|
32
|
+
lead_with(rpm_version).rpm_version.must_equal "2.1"
|
33
|
+
end
|
34
|
+
|
35
|
+
it "tells if a file is recognized as an RPM or not" do
|
36
|
+
assert lead_with(rpm_version).rpm?,
|
37
|
+
"failed to recognize the file as an RPM"
|
38
|
+
end
|
39
|
+
|
40
|
+
it "recognizes RPM as binary" do
|
41
|
+
assert lead_with(binary_type).binary_type?,
|
42
|
+
"failed to recognize RPM as of binary type"
|
43
|
+
end
|
44
|
+
|
45
|
+
it "recognizes RPM as source" do
|
46
|
+
assert lead_with(source_type).source_type?,
|
47
|
+
"failed to recognize RPM as of source type"
|
48
|
+
end
|
49
|
+
|
50
|
+
it "tells which architecture the package is built for" do
|
51
|
+
lead_with(arch).arch.must_equal "i386/x86_64"
|
52
|
+
end
|
53
|
+
|
54
|
+
it "tells the package name" do
|
55
|
+
lead_with(pkg_name).name.must_equal "awesomepkg-1.0-127.fedora19"
|
56
|
+
end
|
57
|
+
|
58
|
+
it "tells the os for which the package was built" do
|
59
|
+
lead_with(os).os.must_equal "Linux"
|
60
|
+
end
|
61
|
+
|
62
|
+
it "tells whether the package is signed or not" do
|
63
|
+
assert lead_with(signature_type_header).signed?,
|
64
|
+
"expected to recognize the RPM as signed, but it was not"
|
65
|
+
|
66
|
+
refute lead_with(unknown_signature_type).signed?,
|
67
|
+
"expected to recognize the RPM as NOT signed, but it was"
|
68
|
+
end
|
69
|
+
|
70
|
+
it "exposes the reserved bits at the end of the lead" do
|
71
|
+
lead_with(reserved_bits).reserved.length.must_equal 16
|
72
|
+
end
|
73
|
+
|
74
|
+
it "can parse an incoming IO returning itself and the remaining part for
|
75
|
+
subsequent elaboration" do
|
76
|
+
lead, scrap = Rupert::RPM::Lead.chomp(io(additional_content))
|
77
|
+
|
78
|
+
lead.must_be_instance_of Rupert::RPM::Lead
|
79
|
+
scrap.read.must_equal "this_is_not_part_of_the_lead"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
context "when reading an invalid RPM" do
|
84
|
+
let(:invalid_magic) { "\x00\x01\x02\x03" }
|
85
|
+
|
86
|
+
it "recognizes the file is not an RPM" do
|
87
|
+
refute lead_with(invalid_magic).rpm?,
|
88
|
+
"failed to recognize the file as NOT an RPM"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
context "when reading an empty file" do
|
93
|
+
let(:empty_lead) { lead_from "" }
|
94
|
+
|
95
|
+
it "recognizes the file is not an RPM" do
|
96
|
+
refute empty_lead.rpm?,
|
97
|
+
"failed to recognize the file as NOT an RPM"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,14 @@
|
|
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
|
@@ -0,0 +1,12 @@
|
|
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
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe Rupert::RPM::Signature do
|
4
|
+
let(:md5_signature_tag) { Rupert::RPM::Signature::MD5_TAG }
|
5
|
+
let(:index) { mock }
|
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
|
+
|
10
|
+
it "fetches the MD5 from its index" do
|
11
|
+
index.expects(:get).once.with(md5_signature_tag)
|
12
|
+
|
13
|
+
signature.md5
|
14
|
+
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
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe Rupert::RPM do
|
4
|
+
let(:signature) { mock }
|
5
|
+
let(:rpm) { Rupert::RPM.new(nil, signature, signed_content) }
|
6
|
+
let(:signed_content) { ascii("\x01\x02\x03\x04") }
|
7
|
+
|
8
|
+
it "exposes the MD5 digest held by the signature" do
|
9
|
+
signature.expects(:md5).once.returns("abc")
|
10
|
+
|
11
|
+
rpm.md5
|
12
|
+
end
|
13
|
+
|
14
|
+
it "asks the signature to verify content integrity" do
|
15
|
+
signature.expects(:verify_checksum).once.with(signed_content)
|
16
|
+
|
17
|
+
rpm.intact?
|
18
|
+
end
|
19
|
+
end
|
metadata
ADDED
@@ -0,0 +1,208 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rupert
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Stefano Zanella
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-08-01 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '5'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '5'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: minitest-spec-context
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: mocha
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ~>
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ~>
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: coveralls
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: flog
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - '>='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: flay
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - '>='
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
description: "\n Rupert allows to manipulate RPM files independently from availability
|
126
|
+
of rpmlib.\n "
|
127
|
+
email:
|
128
|
+
- zanella.stefano@gmail.com
|
129
|
+
executables: []
|
130
|
+
extensions: []
|
131
|
+
extra_rdoc_files:
|
132
|
+
- README.md
|
133
|
+
files:
|
134
|
+
- .gitignore
|
135
|
+
- .ruby-version
|
136
|
+
- .travis.yml
|
137
|
+
- Gemfile
|
138
|
+
- LICENSE.txt
|
139
|
+
- README.md
|
140
|
+
- Rakefile
|
141
|
+
- TODO.md
|
142
|
+
- lib/rupert.rb
|
143
|
+
- lib/rupert/errors.rb
|
144
|
+
- lib/rupert/parser.rb
|
145
|
+
- lib/rupert/rpm.rb
|
146
|
+
- lib/rupert/rpm/lead.rb
|
147
|
+
- 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
|
+
- lib/rupert/version.rb
|
152
|
+
- rupert.gemspec
|
153
|
+
- test/end_to_end/rpm_signature_test.rb
|
154
|
+
- test/end_to_end/rpm_test.rb
|
155
|
+
- test/fixtures/README.md
|
156
|
+
- test/fixtures/notanrpm-0.0.1-1.el6.noarch.rpm
|
157
|
+
- test/fixtures/redhat-lsb-4.0-7.el6.centos.src.rpm
|
158
|
+
- test/fixtures/rpm-4.8.0-32.el6.x86_64.rpm
|
159
|
+
- test/fixtures/rpm-libs-4.8.0-32.el6.i686.rpm
|
160
|
+
- test/fixtures/rpm-libs-4.8.0-32.el6.x86_64.rpm
|
161
|
+
- test/fixtures/rpmdevtools-7.5-2.el6.noarch.rpm
|
162
|
+
- test/test_helper.rb
|
163
|
+
- test/unit/rpm/lead_test.rb
|
164
|
+
- test/unit/rpm/signature/index_test.rb
|
165
|
+
- test/unit/rpm/signature/store_test.rb
|
166
|
+
- test/unit/rpm/signature_test.rb
|
167
|
+
- test/unit/rpm_test.rb
|
168
|
+
homepage: ''
|
169
|
+
licenses:
|
170
|
+
- MIT
|
171
|
+
metadata: {}
|
172
|
+
post_install_message:
|
173
|
+
rdoc_options:
|
174
|
+
- --charset=UTF-8
|
175
|
+
require_paths:
|
176
|
+
- lib
|
177
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
178
|
+
requirements:
|
179
|
+
- - '>='
|
180
|
+
- !ruby/object:Gem::Version
|
181
|
+
version: '0'
|
182
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
183
|
+
requirements:
|
184
|
+
- - '>='
|
185
|
+
- !ruby/object:Gem::Version
|
186
|
+
version: '0'
|
187
|
+
requirements: []
|
188
|
+
rubyforge_project:
|
189
|
+
rubygems_version: 2.0.3
|
190
|
+
signing_key:
|
191
|
+
specification_version: 4
|
192
|
+
summary: Pure Ruby RPM Library
|
193
|
+
test_files:
|
194
|
+
- test/end_to_end/rpm_signature_test.rb
|
195
|
+
- test/end_to_end/rpm_test.rb
|
196
|
+
- test/fixtures/README.md
|
197
|
+
- test/fixtures/notanrpm-0.0.1-1.el6.noarch.rpm
|
198
|
+
- test/fixtures/redhat-lsb-4.0-7.el6.centos.src.rpm
|
199
|
+
- test/fixtures/rpm-4.8.0-32.el6.x86_64.rpm
|
200
|
+
- test/fixtures/rpm-libs-4.8.0-32.el6.i686.rpm
|
201
|
+
- test/fixtures/rpm-libs-4.8.0-32.el6.x86_64.rpm
|
202
|
+
- test/fixtures/rpmdevtools-7.5-2.el6.noarch.rpm
|
203
|
+
- test/test_helper.rb
|
204
|
+
- test/unit/rpm/lead_test.rb
|
205
|
+
- test/unit/rpm/signature/index_test.rb
|
206
|
+
- test/unit/rpm/signature/store_test.rb
|
207
|
+
- test/unit/rpm/signature_test.rb
|
208
|
+
- test/unit/rpm_test.rb
|