archive-zip 0.1.0

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.
@@ -0,0 +1,22 @@
1
+ module Archive; class Zip;
2
+ # Archive::Zip::Error is the base class of all archive-related errors raised
3
+ # by the Archive::Zip library.
4
+ class Error < StandardError; end
5
+
6
+ # Archive::Zip::EntryError is raised when there is an error while processing
7
+ # ZIP archive entries.
8
+ class EntryError < Error; end
9
+
10
+ # Archive::Zip::ExtraFieldError is raised when there is an error while
11
+ # processing an extra field in a ZIP archive entry.
12
+ class ExtraFieldError < Error; end
13
+
14
+ # Archive::Zip::IOError is raised in various places where either an IOError is
15
+ # raised or an operation on an Archive::Zip object is disallowed because of
16
+ # the state of the object.
17
+ class IOError < Error; end
18
+
19
+ # Archive::Zip::UnzipError is raised when attempting to extract an archive
20
+ # fails but no more exact error class exists for reporting the error.
21
+ class UnzipError < Error; end
22
+ end; end
@@ -0,0 +1,23 @@
1
+ module Archive; class Zip
2
+ module ExtraField
3
+ # A Hash used to map extra field header identifiers to extra field classes.
4
+ EXTRA_FIELDS = {}
5
+
6
+ # Returns an instance of an extra field class by selecting the class using
7
+ # _header_id_ and passing _data_ to the class' _parse_ method. If there is
8
+ # no mapping from a given value of _header_id_ to an extra field class, an
9
+ # instance of Archive::Zip::Entry::ExtraField::Raw is returned.
10
+ def self.parse(header_id, data)
11
+ if EXTRA_FIELDS.has_key?(header_id) then
12
+ EXTRA_FIELDS[header_id].parse(data)
13
+ else
14
+ Raw.parse(header_id, data)
15
+ end
16
+ end
17
+ end
18
+ end; end
19
+
20
+ # Load the standard extra field classes.
21
+ require 'archive/zip/extrafield/extendedtimestamp'
22
+ require 'archive/zip/extrafield/raw'
23
+ require 'archive/zip/extrafield/unix'
@@ -0,0 +1,101 @@
1
+ require 'archive/zip/error'
2
+
3
+ module Archive; class Zip; module ExtraField
4
+ # Archive::Zip::Entry::ExtraField::ExtendedTimestamp represents an extra field
5
+ # which optionally contains the last modified time, last accessed time, and
6
+ # file creation time for a ZIP archive entry and stored in a Unix time format
7
+ # (seconds since the epoc).
8
+ class ExtendedTimestamp
9
+ # The identifier reserved for this extra field type.
10
+ ID = 0x5455
11
+
12
+ # Register this extra field for use.
13
+ EXTRA_FIELDS[ID] = self
14
+
15
+ # This method signature is part of the interface contract expected by
16
+ # Archive::Zip::Entry for extra field objects.
17
+ #
18
+ # Parses _data_ which is expected to be a String formatted according to the
19
+ # documentation provided with InfoZip's sources.
20
+ #
21
+ # Raises Archive::Zip::ExtraFieldError if _data_ contains invalid data.
22
+ def self.parse(data)
23
+ unless data.size == 5 || data.size == 9 || data.size == 13 then
24
+ raise Zip::ExtraFieldError,
25
+ "invalid size for extended timestamp: #{data.size}"
26
+ end
27
+ flags, *times = data.unpack('C' + 'V' * ((data.size - 1) / 4))
28
+ mtime = nil
29
+ atime = nil
30
+ crtime = nil
31
+ if flags & 0b001 != 0 then
32
+ if times.size == 0 then
33
+ # Report an error if the flags indicate that the last modified time
34
+ # field should be present when it is not.
35
+ raise Zip::ExtraFieldError,
36
+ 'corrupt extended timestamp: last modified time field not present'
37
+ end
38
+ mtime = Time.at(times.shift)
39
+ end
40
+ if flags & 0b010 != 0 then
41
+ # HACK:
42
+ # InfoZip does not follow their own documentation for this extra field
43
+ # when creating one for an entry's central file record. They flag that
44
+ # the atime field should be present when it is not. Ignore the flag in
45
+ # that case.
46
+ if times.size > 0 then
47
+ atime = Time.at(times.shift)
48
+ end
49
+ end
50
+ if flags & 0b100 != 0 then
51
+ if times.size == 0 then
52
+ # Report an error if the flags indicate that the file creation time
53
+ # field should be present when it is not.
54
+ raise Zip::ExtraFieldError,
55
+ 'corrupt extended timestamp: file creation time field not present'
56
+ end
57
+ crtime = Time.at(times.shift)
58
+ end
59
+ new(mtime, atime, crtime)
60
+ end
61
+
62
+ # Creates a new instance of this class. _mtime_, _atime_, and _crtime_
63
+ # should be Time instances or +nil+. When set to +nil+ the field is
64
+ # considered to be unset and will not be stored in the archive.
65
+ def initialize(mtime, atime, crtime)
66
+ @mtime = mtime
67
+ @atime = atime
68
+ @crtime = crtime
69
+ end
70
+
71
+ # The last modified time for an entry.
72
+ attr_accessor :mtime
73
+ # The last accessed time for an entry.
74
+ attr_accessor :atime
75
+ # The creation time for an entry.
76
+ attr_accessor :crtime
77
+
78
+ # This method signature is part of the interface contract expected by
79
+ # Archive::Zip::Entry for extra field objects.
80
+ #
81
+ # Returns a String suitable to writing to a ZIP archive file which contains
82
+ # the data for this object.
83
+ def dump
84
+ flags = 0
85
+ times = []
86
+ unless mtime.nil? then
87
+ flags |= 0b001
88
+ times << mtime.to_i
89
+ end
90
+ unless atime.nil? then
91
+ flags |= 0b010
92
+ times << atime.to_i
93
+ end
94
+ unless crtime.nil? then
95
+ flags |= 0b100
96
+ times << crtime.to_i
97
+ end
98
+ ([ID, 4 * times.size + 1, flags] + times).pack('vvC' + 'V' * times.size)
99
+ end
100
+ end
101
+ end; end; end
@@ -0,0 +1,32 @@
1
+ module Archive; class Zip; module ExtraField
2
+ # Archive::Zip::Entry::ExtraField::Raw represents an unknown extra field. It
3
+ # is used to store extra fields the Archive::Zip library does not directly
4
+ # support.
5
+ class Raw
6
+ # Simply stores _header_id_ and _data_ for later reproduction by #dump.
7
+ # This is essentially and alias for #new.
8
+ def self.parse(header_id, data)
9
+ new(header_id, data)
10
+ end
11
+
12
+ # Simply stores _header_id_ and _data_ for later reproduction by #dump.
13
+ def initialize(header_id, data)
14
+ @header_id = header_id
15
+ @data = data
16
+ end
17
+
18
+ # Returns the header ID for this ExtraField.
19
+ attr_reader :header_id
20
+ # Returns the data contained within this ExtraField.
21
+ attr_reader :data
22
+
23
+ # This method signature is part of the interface contract expected by
24
+ # Archive::Zip::Entry for extra field objects.
25
+ #
26
+ # Returns a String suitable to writing to a ZIP archive file which contains
27
+ # the data for this object.
28
+ def dump
29
+ [header_id, @data.size].pack('vv') + @data
30
+ end
31
+ end
32
+ end; end; end
@@ -0,0 +1,101 @@
1
+ require 'archive/zip/error'
2
+
3
+ module Archive; class Zip; module ExtraField
4
+ # Archive::Zip::Entry::ExtraField::Unix represents an extra field which
5
+ # contains the last modified time, last accessed time, user name, and group
6
+ # name for a ZIP archive entry. Times are in Unix time format (seconds since
7
+ # the epoc).
8
+ #
9
+ # This class also optionally stores either major and minor numbers for devices
10
+ # or a link target for either hard or soft links. Which is in use when given
11
+ # and instance of this class depends upon the external file attributes for the
12
+ # ZIP archive entry associated with this extra field.
13
+ class Unix
14
+ # The identifier reserved for this extra field type.
15
+ ID = 0x000d
16
+
17
+ # Register this extra field for use.
18
+ EXTRA_FIELDS[ID] = self
19
+
20
+ # This method signature is part of the interface contract expected by
21
+ # Archive::Zip::Entry for extra field objects.
22
+ #
23
+ # Parses _data_ which is expected to be a String formatted according to the
24
+ # official ZIP specification.
25
+ #
26
+ # Raises Archive::Zip::ExtraFieldError if _data_ contains invalid data.
27
+ def self.parse(data)
28
+ unless data.length >= 12 then
29
+ raise Zip::ExtraFieldError, "invalid size for Unix data: #{data.size}"
30
+ end
31
+ atime, mtime, uid, gid, rest = data.unpack('VVvva')
32
+ new(Time.at(mtime), Time.at(atime), uid, gid, rest)
33
+ end
34
+
35
+ # Creates a new instance of this class. _mtime_ and _atime_ should be Time
36
+ # instances. _uid_ and _gid_ should be user and group names as strings
37
+ # respectively. _data_ should be a string containing either major and minor
38
+ # device numbers consecutively packed as little endian, 4-byte, unsigned
39
+ # integers (see the _V_ directive of Array#pack) or a path to use as a link
40
+ # target.
41
+ def initialize(mtime, atime, uid, gid, data = '')
42
+ @mtime = mtime
43
+ @atime = atime
44
+ @uid = uid
45
+ @gid = gid
46
+ @data = data
47
+ end
48
+
49
+ # A Time object representing the last accessed time for an entry.
50
+ attr_accessor :atime
51
+ # A Time object representing the last modified time for an entry.
52
+ attr_accessor :mtime
53
+ # An integer representing the user ownership for an entry.
54
+ attr_accessor :uid
55
+ # An integer representing the group ownership for an entry.
56
+ attr_accessor :gid
57
+
58
+ # Attempts to return a two element array representing the major and minor
59
+ # device numbers which may be stored in the variable data section of this
60
+ # object.
61
+ def device_numbers
62
+ @data.unpack('VV')
63
+ end
64
+
65
+ # Takes a two element array containing major and minor device numbers and
66
+ # stores the numbers into the variable data section of this object.
67
+ def device_numbers=(major_minor)
68
+ @data = major_minor.pack('VV')
69
+ end
70
+
71
+ # Attempts to return a string representing the path of a file which is
72
+ # either a symlink or hard link target which may be stored in the variable
73
+ # data section of this object.
74
+ def link_target
75
+ @data
76
+ end
77
+
78
+ # Takes a string containing the path to a file which is either a symlink or
79
+ # a hardlink target and stores it in the variable data section of this
80
+ # object.
81
+ def link_target=(link_target)
82
+ @data = link_target
83
+ end
84
+
85
+ # This method signature is part of the interface contract expected by
86
+ # Archive::Zip::Entry for extra field objects.
87
+ #
88
+ # Returns a String suitable to writing to a ZIP archive file which contains
89
+ # the data for this object.
90
+ def dump
91
+ [
92
+ ID,
93
+ 12 + @data.size,
94
+ @atime.to_i,
95
+ @mtime.to_i,
96
+ @uid,
97
+ @gid
98
+ ].pack('vvVVvv') + @data
99
+ end
100
+ end
101
+ end; end; end
@@ -0,0 +1,8 @@
1
+ require 'archive/zip'
2
+
3
+ FileUtils.rm_f('export.zip')
4
+ FileUtils.rm_rf('extract_test')
5
+
6
+ Archive::Zip.archive('export.zip', 'test/data/.', :symlinks => false, :ignore_error => true)
7
+ #Archive::Zip.archive('export.zip', 'test/data/.', :symlinks => true, :exclude => lambda { |e| e.file? })
8
+ Archive::Zip.extract('export.zip', 'extract_test', :symlinks => true, :exclude => lambda { |e| e.symlink? })
metadata ADDED
@@ -0,0 +1,98 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: archive-zip
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Jeremy Bopp
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-07-10 00:00:00 -05:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: io-like
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 0.1.0
24
+ version:
25
+ description: Archive::Zip provides a simple Ruby-esque interface to creating, extracting, and updating ZIP archives. This implementation is 100% Ruby and loosely modeled on the archive creation and extraction capabilities of InfoZip's zip and unzip tools.
26
+ email: jeremy at bopp dot net
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - CONTRIBUTORS
33
+ - HACKING
34
+ - LICENSE
35
+ - GPL
36
+ - LEGAL
37
+ - NEWS
38
+ - README
39
+ files:
40
+ - lib/archive/support/io-like.rb
41
+ - lib/archive/support/io.rb
42
+ - lib/archive/support/iowindow.rb
43
+ - lib/archive/support/stringio.rb
44
+ - lib/archive/support/time.rb
45
+ - lib/archive/support/zlib.rb
46
+ - lib/archive/zip/codec/deflate.rb
47
+ - lib/archive/zip/codec/store.rb
48
+ - lib/archive/zip/codec.rb
49
+ - lib/archive/zip/datadescriptor.rb
50
+ - lib/archive/zip/entry.rb
51
+ - lib/archive/zip/error.rb
52
+ - lib/archive/zip/extrafield/extendedtimestamp.rb
53
+ - lib/archive/zip/extrafield/raw.rb
54
+ - lib/archive/zip/extrafield/unix.rb
55
+ - lib/archive/zip/extrafield.rb
56
+ - lib/archive/zip.rb
57
+ - test/test_archive.rb
58
+ - CONTRIBUTORS
59
+ - HACKING
60
+ - LICENSE
61
+ - GPL
62
+ - LEGAL
63
+ - NEWS
64
+ - README
65
+ - MANIFEST
66
+ has_rdoc: true
67
+ homepage: http://archive-zip.rubyforge.org
68
+ post_install_message:
69
+ rdoc_options:
70
+ - --title
71
+ - Archive::Zip Documentation
72
+ - --charset
73
+ - utf-8
74
+ - --line-numbers
75
+ - --inline-source
76
+ require_paths:
77
+ - lib
78
+ required_ruby_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: 1.8.1
83
+ version:
84
+ required_rubygems_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: "0"
89
+ version:
90
+ requirements: []
91
+
92
+ rubyforge_project: archive-zip
93
+ rubygems_version: 1.2.0
94
+ signing_key:
95
+ specification_version: 2
96
+ summary: Simple, extensible, pure Ruby ZIP archive support.
97
+ test_files: []
98
+