rupert 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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