rubyzip/zip.rb

408 lines
9.9 KiB
Ruby
Raw Normal View History

2002-01-03 01:48:31 +08:00
#!/usr/bin/env ruby
require 'singleton'
require 'zlib'
# Implements many of the convenience methods of IO
# such as gets, getc, readline and readlines
module PseudoIO
def readlines(aSepString = $/)
retVal = []
each_line(aSepString) { |line| retVal << line }
return retVal
end
def gets(aSepString=$/)
@outputBuffer="" unless @outputBuffer
return read if aSepString == nil
aSepString="#{$/}#{$/}" if aSepString == ""
bufferIndex=0
while ((matchIndex = @outputBuffer.index(aSepString, bufferIndex)) == nil)
bufferIndex=@outputBuffer.length
if inputFinished?
return @outputBuffer.length==0 ? nil : flush
end
@outputBuffer << produceInput
end
sepIndex=matchIndex + aSepString.length
return @outputBuffer.slice!(0...sepIndex)
end
def flush
retVal=@outputBuffer
@outputBuffer=""
return retVal
end
def readline(aSepString = $/)
retVal = gets(aSepString)
raise EOFError if retVal == nil
return retVal
end
def each_line(aSepString = $/)
while true
yield readline
end
rescue EOFError
end
alias_method :each, :each_line
end
class ZipInputStream
include PseudoIO
2002-01-05 08:13:58 +08:00
def initialize(filename, offset = 0)
2002-01-03 01:48:31 +08:00
@archiveIO = File.open(filename, "rb")
2002-01-05 08:13:58 +08:00
@archiveIO.seek(offset, IO::SEEK_SET)
2002-01-03 01:48:31 +08:00
@decompressor = NullDecompressor.instance
end
def close
2002-01-05 08:13:58 +08:00
@archiveIO.close
2002-01-03 01:48:31 +08:00
end
def ZipInputStream.open(filename)
return new(filename) unless block_given?
zio = new(filename)
yield zio
zio.close
end
def getNextEntry
@archiveIO.seek(@currentEntry.nextHeaderOffset,
IO::SEEK_SET) if @currentEntry
@currentEntry = ZipLocalEntry.readFromStream(@archiveIO)
if (@currentEntry == nil)
@decompressor = NullDecompressor.instance
elsif @currentEntry.compressionMethod == ZipEntry::STORED
@decompressor = PassThruDecompressor.new(@archiveIO,
@currentEntry.size)
elsif @currentEntry.compressionMethod == ZipEntry::DEFLATED
@decompressor = Inflater.new(@archiveIO)
else
raise "Unsupported compression method #{@currentEntry.compressionMethod}"
end
flush
return @currentEntry
end
def read(numberOfBytes = nil)
@decompressor.read(numberOfBytes)
end
protected
def produceInput
@decompressor.produceInput
end
def inputFinished?
@decompressor.inputFinished?
end
end
class Decompressor
CHUNK_SIZE=8192
def initialize(inputStream)
@inputStream=inputStream
end
end
class Inflater < Decompressor
def initialize(inputStream)
super
@zlibInflater = Inflate.new(-Inflate::MAX_WBITS)
@outputBuffer=""
end
def read(numberOfBytes = nil)
readEverything = (numberOfBytes == nil)
while (readEverything || @outputBuffer.length < numberOfBytes)
break if inputFinished?
@outputBuffer << produceInput
end
return nil if @outputBuffer.length==0 && inputFinished?
endIndex= numberOfBytes==nil ? @outputBuffer.length : numberOfBytes
return @outputBuffer.slice!(0...endIndex)
end
def produceInput
@zlibInflater.inflate(@inputStream.read(Decompressor::CHUNK_SIZE))
end
def inputFinished?
@zlibInflater.finished?
end
end
class PassThruDecompressor < Decompressor
def initialize(inputStream, charsToRead)
super inputStream
@charsToRead = charsToRead
@readSoFar = 0
@isFirst=true
end
def read(numberOfBytes = nil)
if inputFinished?
isFirstVal=@isFirst
@isFirst=false
return "" if isFirstVal
return nil
end
if (numberOfBytes == nil || @readSoFar+numberOfBytes > @charsToRead)
numberOfBytes = @charsToRead-@readSoFar
end
@readSoFar += numberOfBytes
@inputStream.read(numberOfBytes)
end
def produceInput
read(Decompressor::CHUNK_SIZE)
end
def inputFinished?
(@readSoFar >= @charsToRead)
end
end
class NullDecompressor
include Singleton
def read(numberOfBytes = nil)
nil
end
def produceInput
nil
end
def inputFinished?
true
end
end
class ZipEntry
STORED = 0
DEFLATED = 8
attr_reader :comment, :compressedSize, :crc, :extra, :compressionMethod,
:name, :size, :localHeaderOffset
def initialize(comment = nil, compressedSize = nil, crc = nil, extra = nil,
compressionMethod = nil, name = nil, size = nil)
@comment, @compressedSize, @crc, @extra, @compressionMethod,
@name, @size, @isDirectory = comment, compressedSize, crc,
extra, compressionMethod, name, size
end
def isDirectory
2002-01-05 04:51:24 +08:00
return (%r{\/$} =~ @name) != nil
2002-01-03 01:48:31 +08:00
end
def localEntryOffset
localHeaderOffset + localHeaderSize
end
def localHeaderSize
30 + name.size + extra.size
end
def nextHeaderOffset
localEntryOffset + self.compressedSize
end
2002-01-05 08:13:58 +08:00
def to_s
@name
end
2002-01-03 01:48:31 +08:00
protected
def ZipEntry.readZipShort(io)
io.read(2).unpack('v')[0]
end
def ZipEntry.readZipLong(io)
io.read(4).unpack('V')[0]
end
end
class ZipLocalEntry < ZipEntry
LOCAL_ENTRY_SIGNATURE = 0x04034b50
def readFromStream(io)
unless (ZipEntry::readZipLong(io) == LOCAL_ENTRY_SIGNATURE)
raise ZipError,
2002-01-05 04:51:24 +08:00
"Zip local header magic '#{LOCAL_ENTRY_SIGNATURE} not found"
2002-01-03 01:48:31 +08:00
end
@localHeaderOffset = io. tell - 4
@version = ZipEntry::readZipShort(io)
@gpFlags = ZipEntry::readZipShort(io)
@compressionMethod = ZipEntry::readZipShort(io)
@lastModTime = ZipEntry::readZipShort(io)
@lastModDate = ZipEntry::readZipShort(io)
@crc = ZipEntry::readZipLong(io)
@compressedSize = ZipEntry::readZipLong(io)
@size = ZipEntry::readZipLong(io)
nameLength = ZipEntry::readZipShort(io)
extraLength = ZipEntry::readZipShort(io)
@name = io.read(nameLength)
@extra = io.read(extraLength)
end
def ZipLocalEntry.readFromStream(io)
entry = new()
entry.readFromStream(io)
return entry
rescue ZipError
return nil
end
end
2002-01-05 04:51:24 +08:00
class ZipCentralDirectoryEntry < ZipEntry
CENTRAL_DIRECTORY_ENTRY_SIGNATURE = 0x02014b50
def readFromStream(io)
unless (ZipEntry::readZipLong(io) == CENTRAL_DIRECTORY_ENTRY_SIGNATURE)
raise ZipError,
"Zip central directory header magic '#{CENTRAL_DIRECTORY_ENTRY_SIGNATURE} not found"
end
@version = ZipEntry::readZipShort(io)
@versionNeededToExtract = ZipEntry::readZipShort(io)
@gpFlags = ZipEntry::readZipShort(io)
@compressionMethod = ZipEntry::readZipShort(io)
@lastModTime = ZipEntry::readZipShort(io)
@lastModDate = ZipEntry::readZipShort(io)
@crc = ZipEntry::readZipLong(io)
@compressedSize = ZipEntry::readZipLong(io)
@size = ZipEntry::readZipLong(io)
nameLength = ZipEntry::readZipShort(io)
extraLength = ZipEntry::readZipShort(io)
commentLength = ZipEntry::readZipShort(io)
diskNumberStart = ZipEntry::readZipShort(io)
@internalFileAttributes = ZipEntry::readZipShort(io)
@externalFileAttributes = ZipEntry::readZipLong(io)
@localHeaderOffset = ZipEntry::readZipLong(io)
@name = io.read(nameLength)
@extra = io.read(extraLength)
@comment = io.read(commentLength)
end
def ZipCentralDirectoryEntry.readFromStream(io)
entry = new()
entry.readFromStream(io)
return entry
rescue ZipError
return nil
end
end
2002-01-05 08:13:58 +08:00
class ZipCentralDirectory
include Enumerable
2002-01-05 04:51:24 +08:00
2002-01-05 08:13:58 +08:00
END_OF_CENTRAL_DIRECTORY_SIGNATURE = 0x06054b50
MAX_END_OF_CENTRAL_DIRECTORY_STRUCTURE_SIZE = 65536 + 18
attr_reader :size, :comment
def readEOCD(io)
buf = getEOCD(io)
puts "*************** CRAP *******" unless buf
@numberOfThisDisk = ZipEntry::readZipShort(buf)
@numberOfDiskWithStartOfCDir = ZipEntry::readZipShort(buf)
@totalNumberOfEntriesInCDirOnThisDisk = ZipEntry::readZipShort(buf)
@size = ZipEntry::readZipShort(buf)
@sizeInBytes = ZipEntry::readZipLong(buf)
@cdirOffset = ZipEntry::readZipLong(buf)
commentLength = ZipEntry::readZipShort(buf)
@comment = buf.read(commentLength)
raise ZipError, "Zip consistency problem while reading eocd structure" unless buf.size == 0
end
2002-01-05 04:51:24 +08:00
2002-01-05 08:13:58 +08:00
def readCentralDirectoryEntries(io)
io.seek(@cdirOffset, IO::SEEK_SET)
@entries = []
@size.times {
@entries << ZipCentralDirectoryEntry.readFromStream(io)
}
end
def readFromStream(io)
readEOCD(io)
readCentralDirectoryEntries(io)
end
def getEOCD(io)
begin
io.seek(-MAX_END_OF_CENTRAL_DIRECTORY_STRUCTURE_SIZE, IO::SEEK_END)
rescue Errno::EINVAL
io.seek(0, IO::SEEK_SET)
end
buf = io.read
sigIndex = buf.rindex([END_OF_CENTRAL_DIRECTORY_SIGNATURE].pack('V'))
raise ZipError, "Zip end of central directory signature not found" unless sigIndex
buf=buf.slice!((sigIndex+4)...(buf.size))
def buf.read(count)
slice!(0, count)
end
return buf
end
2002-01-05 04:51:24 +08:00
2002-01-05 08:13:58 +08:00
def each(&proc)
@entries.each &proc
end
2002-01-05 04:51:24 +08:00
2002-01-05 08:13:58 +08:00
def ZipCentralDirectory.readFromStream(io)
cdir = new
cdir.readFromStream(io)
return cdir
rescue ZipError
return nil
end
end
2002-01-05 04:51:24 +08:00
2002-01-03 01:48:31 +08:00
class ZipError < RuntimeError
end
2002-01-05 08:13:58 +08:00
class ZipFile < ZipCentralDirectory
2002-01-03 01:48:31 +08:00
include Enumerable
attr_reader :name
def initialize(name)
@name=name
2002-01-05 08:13:58 +08:00
File.open(name) {
|file|
readFromStream(file)
}
2002-01-03 01:48:31 +08:00
end
def ZipFile.foreach(aZipFileName, &block)
zipFile = ZipFile.new(aZipFileName)
zipFile.each &block
end
def getInputStream(entry)
2002-01-05 08:13:58 +08:00
selectedEntry = @entries.detect { |e| e.name == entry.to_s }
raise ZipError, "No matching entry found in zip file '#{@name}' for '#{}'" unless selectedEntry
zis = ZipInputStream.new(@name, selectedEntry.localHeaderOffset)
zis.getNextEntry
return zis
2002-01-03 01:48:31 +08:00
end
end