rupert 0.0.1

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 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
@@ -0,0 +1,9 @@
1
+ .DS_Store
2
+ *.gem
3
+ *.swo
4
+ *.swp
5
+ .bundle
6
+ Gemfile.lock
7
+ coverage
8
+ pkg
9
+ vendor
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.0.0-p247
data/.travis.yml ADDED
@@ -0,0 +1,11 @@
1
+ ---
2
+ language: ruby
3
+
4
+ rvm:
5
+ - 1.9.3
6
+ - 2.0.0
7
+
8
+ bundler_args: --path vendor/bundle
9
+
10
+ script:
11
+ - bundle exec rake test
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rupture.gemspec
4
+ gemspec
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,3 @@
1
+ module Rupert
2
+ class NotAnRPM < StandardError; end
3
+ end
@@ -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
@@ -0,0 +1,3 @@
1
+ module Rupert
2
+ VERSION = "0.0.1"
3
+ end
data/lib/rupert.rb ADDED
@@ -0,0 +1,2 @@
1
+ require "rupert/version"
2
+ require "rupert/rpm"
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
@@ -0,0 +1,6 @@
1
+ # Test Fixtures
2
+
3
+ ## Here lie some RPMs used to test rupture against the real world.
4
+
5
+ RPM have been downloaded from http://yum.singlehop.com/CentOS/6.4/os/x86_64/Packages/.
6
+ Source RPM have been downloaded from http://vault.centos.org/6.4/os/Source/SPackages/.
File without changes
@@ -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