class AmpkReader

Attributes

items[R]
path[R]

Public Class Methods

new(path, public_key) click to toggle source
# File lib/ampk/reader.rb, line 5
def initialize(path, public_key)
        @fp = File.open(@path=path,'rb')
        @public_key = public_key
end

Public Instance Methods

close() click to toggle source
# File lib/ampk/reader.rb, line 72
def close
        @fp.close
end
entities() click to toggle source
# File lib/ampk/reader.rb, line 38
def entities
        auto_scan
        @items.map { |item| item[:name] }
end
entity(name) click to toggle source
# File lib/ampk/reader.rb, line 60
def entity(name)
        auto_scan
        item = @items.find { |item| item[:name] == name }
        raise Errno::ENOENT, name if item.nil?
        item
end
entity_compressed?(name) click to toggle source
# File lib/ampk/reader.rb, line 49
def entity_compressed?(name)
        item = entity(name)
        item.has_key?(:filter) and item[:filter].include?(?Z)
end
entity_encrypted?(name) click to toggle source
# File lib/ampk/reader.rb, line 45
def entity_encrypted?(name)
        item = entity(name)
        item.has_key?(:filter) and item[:filter].include?(?C)
end
entity_length(name)
Alias for: entity_size
entity_signed?(name) click to toggle source
# File lib/ampk/reader.rb, line 42
def entity_signed?(name)
        entity(name).has_key?(:signature)
end
entity_size(name) click to toggle source
# File lib/ampk/reader.rb, line 53
def entity_size(name)
        entity(name)[:length]
end
Also aliased as: entity_length
entity_type(name) click to toggle source
# File lib/ampk/reader.rb, line 57
def entity_type(name)
        entity(name)[:type]
end
read_entity(name) click to toggle source
# File lib/ampk/reader.rb, line 66
def read_entity(name)
        auto_scan unless @items
        item = @items.find { |item| item[:name] == name }
        raise Errno::ENOENT, name if item.nil?
        read_entity_data(item)
end
verify!(verify_data=false) click to toggle source
# File lib/ampk/reader.rb, line 9
def verify!(verify_data=false)
        @fp.rewind
        must('start AMPK') { read4 == 'AMPK' }
        until @fp.eof?
                break if peek4 == 'ENDS'
                name = verify_entry('NAME', true)
                headers = {}
                signature = nil
                
                until (header = peek4).eql?('DATA')
                        headers[header] ||= 0
                        headers[header] += 1
                        raise "Duplicate #{header} entry for #{name}" if headers[header] > 1
                        
                        case header
                        when 'FILT', 'DLEN', 'TYPE'
                                verify_entry(header, false)
                        when 'SIGN'
                                signature = verify_entry('SIGN', true)
                        end
                end
                
                if signature
                        verify_entry_with_sig('DATA', verify_data, signature)
                else
                        verify_entry('DATA', verify_data)
                end
        end
end

Protected Instance Methods

auto_scan() click to toggle source
# File lib/ampk/reader.rb, line 77
def auto_scan
        read_headers unless @items
        nil
end
decode(item, data) click to toggle source
# File lib/ampk/reader.rb, line 144
def decode(item, data)
        if item[:encoding] && data.respond_to?(:encoding)
                data.force_encoding(item[:encoding])
        else
                data
        end
end
get_byte() click to toggle source
# File lib/ampk/reader.rb, line 210
def get_byte
        if @fp.respond_to?(:getbyte)
                @fp.getbyte
        else
                @fp.getc
        end
end
must(description) { || ... } click to toggle source
# File lib/ampk/reader.rb, line 186
def must(description,&block)
        raise RuntimeError, "Archive is invalid: It must #{description}" unless yield
end
peek4() click to toggle source
# File lib/ampk/reader.rb, line 195
def peek4
        pos = @fp.tell
        str = @fp.read(4)
        @fp.seek(pos, IO::SEEK_SET)
        str
end
read4() click to toggle source
# File lib/ampk/reader.rb, line 190
def read4
        str = @fp.read(4)
        str
end
read_data() click to toggle source
# File lib/ampk/reader.rb, line 218
def read_data
        size = read_data_size
        data = @fp.read(size)
        [size, data]
end
read_data_size() click to toggle source
# File lib/ampk/reader.rb, line 202
def read_data_size
        size = ""
        while (c = get_byte).nonzero?
                size << c
        end
        size.to_i
end
read_entity_data(item) click to toggle source
# File lib/ampk/reader.rb, line 118
def read_entity_data(item)
        @fp.seek(item[:offset])
        data = read_entry('DATA')
        
        if item[:filter]
                item[:filter].reverse.each_byte do |byte|
                        case byte
                        when ?Z.ord
                                data = Zlib::Inflate.inflate(data)
                        when ?C.ord
                                data = @public_key.decrypt(data)
                        end
                end
        end
        
        if item[:signature]
                if @public_key.verify(item[:signature], data)
                        return decode(item, data)
                else
                        raise RuntimeError, "Invalid data for #{item[:name]}"
                end
        else
                return decode(item, data)
        end
end
read_entity_header() click to toggle source
# File lib/ampk/reader.rb, line 94
def read_entity_header
        start = @fp.tell
        name = read_entry('NAME')
        item = { :name => name, :header => start }
        until (header=peek4).eql?('DATA')
                case header
                when 'SIGN'
                        item[:signature] = read_entry(header)
                when 'DLEN'
                        item[:length] = read_entry(header).to_i
                when 'FILT'
                        item[:filter] = read_entry(header)
                when 'TYPE'
                        item[:type] = read_entry(header)
                when 'ENCD'
                        item[:encoding] = read_entry(header)
                else
                        raise RuntimeError, "Unknown/invalid header #{header}"
                end
        end
        item[:offset] = @fp.tell
        item
end
read_entry(name) click to toggle source
# File lib/ampk/reader.rb, line 152
def read_entry(name)
        must("have a #{name} entry") { read4 == name }
        size, data = read_data
        must("have some #{name} data") { size == data.size }
        data
end
read_headers() click to toggle source
# File lib/ampk/reader.rb, line 82
def read_headers
        @fp.rewind
        must('start AMPK') { read4 == 'AMPK' }
        @items = []
        until @fp.eof?
                break if peek4 == 'ENDS'
                @items << read_entity_header()
                skip_entry('DATA')
        end
        @items
end
skip_data() click to toggle source
# File lib/ampk/reader.rb, line 224
def skip_data
        size = read_data_size
        posn = @fp.tell
        @fp.seek(size, IO::SEEK_CUR)
        skipped = @fp.tell - posn
        [size, skipped]
end
skip_entry(name) click to toggle source
# File lib/ampk/reader.rb, line 159
def skip_entry(name)
        must("have a #{name} entry'") { read4 == name }
        size, skipped = skip_data
        must("have some #{name} data") { size == skipped }
        nil
end
verify_entry(name, verify_data) click to toggle source
# File lib/ampk/reader.rb, line 166
def verify_entry(name, verify_data)
        must("have a #{name} entry") { read4 == name }
        if verify_data
                size, data = read_data
                must("have some #{name} data") { size == data.size }
                data
        else
                size, skipped = skip_data
                must("have at least as much data left") { skipped == size }
                nil
        end
end
verify_entry_with_sig(name, verify_data, sig) click to toggle source
# File lib/ampk/reader.rb, line 179
def verify_entry_with_sig(name, verify_data, sig)
        data = verify_entry(name, verify_data)
        if data and sig
                must('be valid, signed data') { @public_key.verify(sig, data) }
        end
end