ucf 0.1.0 → 0.5.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.
- data/Changes.rdoc +11 -0
- data/ReadMe.rdoc +3 -2
- data/lib/ucf.rb +6 -0
- data/lib/ucf/container.rb +54 -57
- data/lib/ucf/entries/directory.rb +71 -0
- data/lib/ucf/entries/entry.rb +134 -0
- data/lib/ucf/entries/file.rb +101 -0
- data/lib/ucf/entries/managed.rb +183 -0
- data/lib/ucf/entries/reserved.rb +88 -0
- data/lib/ucf/exceptions.rb +3 -1
- data/lib/ucf/meta-inf.rb +52 -0
- data/test/tc_managed_entries.rb +206 -0
- data/test/tc_reserved_names.rb +126 -78
- data/test/ts_ucf.rb +1 -0
- data/ucf.gemspec +9 -2
- data/version.yml +1 -1
- metadata +9 -2
data/Changes.rdoc
CHANGED
@@ -1,5 +1,16 @@
|
|
1
1
|
= Changes log for the UCF Ruby Gem
|
2
2
|
|
3
|
+
== Version 0.5.0
|
4
|
+
|
5
|
+
* Add support for managed entries in the container.
|
6
|
+
* Verify and "optional" functionality for ManagedEntry.
|
7
|
+
* Use reserved and managed entry mixins for managed directories.
|
8
|
+
* Better initialization of managed entry support.
|
9
|
+
* Fix creation of subclassed Container objects.
|
10
|
+
* Add extra tests for managed entries.
|
11
|
+
* Make file validation more flexible.
|
12
|
+
* Fix the source of failing validation messages.
|
13
|
+
|
3
14
|
== Version 0.1.0
|
4
15
|
|
5
16
|
* Improvements to the reserved names code to allow sub-classing.
|
data/ReadMe.rdoc
CHANGED
@@ -33,8 +33,9 @@ number of optional features that are not yet provided.
|
|
33
33
|
documents that are resident on disk as the underlying
|
34
34
|
{rubyzip library}[https://github.com/aussiegeek/rubyzip] currently
|
35
35
|
{cannot do anything else}[https://github.com/aussiegeek/rubyzip/issues/74].
|
36
|
-
*
|
37
|
-
|
36
|
+
* Validation of file contents in the META-INF directory. Most of the machinery
|
37
|
+
for this has been implemented but files are not validated against the schema
|
38
|
+
yet.
|
38
39
|
* Digital signatures (this feature has been deferred until a future revision
|
39
40
|
of the UCF specification. It will be supported by this gem when it is added
|
40
41
|
to the specification).
|
data/lib/ucf.rb
CHANGED
@@ -32,6 +32,12 @@
|
|
32
32
|
|
33
33
|
require 'yaml'
|
34
34
|
require 'ucf/exceptions'
|
35
|
+
require 'ucf/entries/reserved'
|
36
|
+
require 'ucf/entries/managed'
|
37
|
+
require 'ucf/entries/entry'
|
38
|
+
require 'ucf/entries/file'
|
39
|
+
require 'ucf/entries/directory'
|
40
|
+
require 'ucf/meta-inf'
|
35
41
|
require 'ucf/container'
|
36
42
|
|
37
43
|
# This is a ruby library to read and write UCF files in PK Zip format. See the
|
data/lib/ucf/container.rb
CHANGED
@@ -46,11 +46,13 @@ module UCF
|
|
46
46
|
#
|
47
47
|
# There are code examples available with the source code of this library.
|
48
48
|
class Container
|
49
|
+
include ReservedNames
|
50
|
+
include ManagedEntries
|
49
51
|
|
50
52
|
extend Forwardable
|
51
53
|
def_delegators :@zipfile, :comment, :comment=, :commit_required?, :each,
|
52
|
-
:extract, :find_entry, :get_entry, :get_input_stream, :glob,
|
53
|
-
:read, :size
|
54
|
+
:entries, :extract, :find_entry, :get_entry, :get_input_stream, :glob,
|
55
|
+
:name, :read, :size
|
54
56
|
|
55
57
|
private_class_method :new
|
56
58
|
|
@@ -61,17 +63,22 @@ module UCF
|
|
61
63
|
# :stopdoc:
|
62
64
|
DEFAULT_MIMETYPE = "application/epub+zip"
|
63
65
|
|
64
|
-
#
|
66
|
+
# The reserved mimetype file name for standard UCF documents.
|
65
67
|
MIMETYPE_FILE = "mimetype"
|
66
|
-
META_INF_DIR = "META-INF"
|
67
68
|
|
68
69
|
def initialize(document)
|
69
70
|
@zipfile = open_document(document)
|
70
|
-
|
71
|
+
check_mimetype!
|
71
72
|
|
72
73
|
@mimetype = read_mimetype
|
73
74
|
@on_disk = true
|
74
75
|
|
76
|
+
# Reserved entry names. Just the mimetype file by default.
|
77
|
+
register_reserved_name(MIMETYPE_FILE)
|
78
|
+
|
79
|
+
# Initialize the managed entries and register the META-INF directory.
|
80
|
+
initialize_managed_entries(MetaInf.new)
|
81
|
+
|
75
82
|
# Here we fake up the connection to the rubyzip filesystem classes so
|
76
83
|
# that they also respect the reserved names that we define.
|
77
84
|
mapped_zip = ::Zip::ZipFileSystem::ZipFileNameMapper.new(self)
|
@@ -93,7 +100,18 @@ module UCF
|
|
93
100
|
stream.write mimetype
|
94
101
|
end
|
95
102
|
|
96
|
-
|
103
|
+
# Now open the newly created container.
|
104
|
+
c = new(filename)
|
105
|
+
|
106
|
+
if block_given?
|
107
|
+
begin
|
108
|
+
yield c
|
109
|
+
ensure
|
110
|
+
c.close
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
c
|
97
115
|
end
|
98
116
|
|
99
117
|
# :call-seq:
|
@@ -145,7 +163,7 @@ module UCF
|
|
145
163
|
# all with the file (including if it cannot be found).
|
146
164
|
def Container.verify(filename)
|
147
165
|
begin
|
148
|
-
|
166
|
+
new(filename).verify!
|
149
167
|
rescue
|
150
168
|
return false
|
151
169
|
end
|
@@ -161,8 +179,7 @@ module UCF
|
|
161
179
|
# there is something fundamental wrong with the file itself (e.g. it
|
162
180
|
# cannot be found).
|
163
181
|
def Container.verify!(filename)
|
164
|
-
new(filename).
|
165
|
-
nil
|
182
|
+
new(filename).verify!
|
166
183
|
end
|
167
184
|
|
168
185
|
# :call-seq:
|
@@ -176,7 +193,9 @@ module UCF
|
|
176
193
|
# See the rubyzip documentation for details of the
|
177
194
|
# +continue_on_exists_proc+ parameter.
|
178
195
|
def add(entry, src_path, &continue_on_exists_proc)
|
179
|
-
|
196
|
+
if reserved_entry?(entry) || managed_directory?(entry)
|
197
|
+
raise ReservedNameClashError.new(entry.to_s)
|
198
|
+
end
|
180
199
|
|
181
200
|
@zipfile.add(entry, src_path, &continue_on_exists_proc)
|
182
201
|
end
|
@@ -233,7 +252,9 @@ module UCF
|
|
233
252
|
# See the rubyzip documentation for details of the +permission_int+
|
234
253
|
# parameter.
|
235
254
|
def get_output_stream(entry, permission = nil, &block)
|
236
|
-
|
255
|
+
if reserved_entry?(entry) || managed_directory?(entry)
|
256
|
+
raise ReservedNameClashError.new(entry.to_s)
|
257
|
+
end
|
237
258
|
|
238
259
|
@zipfile.get_output_stream(entry, permission, &block)
|
239
260
|
end
|
@@ -256,7 +277,9 @@ module UCF
|
|
256
277
|
# permissions. The default (+0755+) is owner read, write and list; group
|
257
278
|
# read and list; and world read and list.
|
258
279
|
def mkdir(name, permission = 0755)
|
259
|
-
|
280
|
+
if reserved_entry?(name) || managed_file?(name)
|
281
|
+
raise ReservedNameClashError.new(name)
|
282
|
+
end
|
260
283
|
|
261
284
|
@zipfile.mkdir(name, permission)
|
262
285
|
end
|
@@ -309,50 +332,6 @@ module UCF
|
|
309
332
|
@zipfile.replace(entry, src_path)
|
310
333
|
end
|
311
334
|
|
312
|
-
# :call-seq:
|
313
|
-
# reserved_files -> Array
|
314
|
-
#
|
315
|
-
# Return a list of reserved file names for this UCF document.
|
316
|
-
#
|
317
|
-
# When creating a more specialized sub-class of this class then this
|
318
|
-
# method should be overridden to add any extra reserved file names.
|
319
|
-
def reserved_files
|
320
|
-
[MIMETYPE_FILE]
|
321
|
-
end
|
322
|
-
|
323
|
-
# :call-seq:
|
324
|
-
# reserved_directories -> Array
|
325
|
-
#
|
326
|
-
# Return a list of reserved directory names for this UCF document.
|
327
|
-
#
|
328
|
-
# When creating a more specialized sub-class of this class then this
|
329
|
-
# method should be overridden to add any extra reserved directory names.
|
330
|
-
def reserved_directories
|
331
|
-
[META_INF_DIR]
|
332
|
-
end
|
333
|
-
|
334
|
-
# :call-seq:
|
335
|
-
# reserved_entry?(entry) -> boolean
|
336
|
-
#
|
337
|
-
# Is the given entry name in the reserved list of file or directory names?
|
338
|
-
def reserved_entry?(entry)
|
339
|
-
name = entry.kind_of?(::Zip::ZipEntry) ? entry.name : entry
|
340
|
-
name.chop! if name.end_with? "/"
|
341
|
-
reserved_names.map { |n| n.downcase }.include? name.downcase
|
342
|
-
end
|
343
|
-
|
344
|
-
# :call-seq:
|
345
|
-
# reserved_names -> Array
|
346
|
-
#
|
347
|
-
# Return a list of reserved file and directory names for this UCF
|
348
|
-
# document.
|
349
|
-
#
|
350
|
-
# In practice this method simply returns the joined lists of reserved file
|
351
|
-
# and directory names.
|
352
|
-
def reserved_names
|
353
|
-
reserved_files + reserved_directories
|
354
|
-
end
|
355
|
-
|
356
335
|
# :call-seq:
|
357
336
|
# to_s -> String
|
358
337
|
#
|
@@ -361,13 +340,22 @@ module UCF
|
|
361
340
|
@zipfile.to_s + " - #{@mimetype}"
|
362
341
|
end
|
363
342
|
|
343
|
+
# :call-seq:
|
344
|
+
# verify!
|
345
|
+
#
|
346
|
+
# Verify the contents of this UCF document. All managed files and
|
347
|
+
# directories are checked to make sure that they exist, if required.
|
348
|
+
def verify!
|
349
|
+
verify_managed_entries!
|
350
|
+
end
|
351
|
+
|
364
352
|
private
|
365
353
|
|
366
354
|
def open_document(document)
|
367
355
|
::Zip::ZipFile.new(document)
|
368
356
|
end
|
369
357
|
|
370
|
-
def
|
358
|
+
def check_mimetype!
|
371
359
|
# Check mimetype file is present and correct.
|
372
360
|
entry = @zipfile.find_entry(MIMETYPE_FILE)
|
373
361
|
|
@@ -422,6 +410,15 @@ module UCF
|
|
422
410
|
# by this method are Zip::ZipEntry objects. Please see the rubyzip
|
423
411
|
# documentation for details.
|
424
412
|
|
413
|
+
##
|
414
|
+
# :method:
|
415
|
+
# :call-seq:
|
416
|
+
# entries -> Enumerable
|
417
|
+
#
|
418
|
+
# Returns an Enumerable containing all the entries in the UCF Document.
|
419
|
+
# The entry objects returned by this method are Zip::ZipEntry objects.
|
420
|
+
# Please see the rubyzip documentation for details.
|
421
|
+
|
425
422
|
##
|
426
423
|
# :method: extract
|
427
424
|
# :call-seq:
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# Copyright (c) 2013 The University of Manchester, UK.
|
2
|
+
#
|
3
|
+
# All rights reserved.
|
4
|
+
#
|
5
|
+
# Redistribution and use in source and binary forms, with or without
|
6
|
+
# modification, are permitted provided that the following conditions are met:
|
7
|
+
#
|
8
|
+
# * Redistributions of source code must retain the above copyright notice,
|
9
|
+
# this list of conditions and the following disclaimer.
|
10
|
+
#
|
11
|
+
# * Redistributions in binary form must reproduce the above copyright notice,
|
12
|
+
# this list of conditions and the following disclaimer in the documentation
|
13
|
+
# and/or other materials provided with the distribution.
|
14
|
+
#
|
15
|
+
# * Neither the names of The University of Manchester nor the names of its
|
16
|
+
# contributors may be used to endorse or promote products derived from this
|
17
|
+
# software without specific prior written permission.
|
18
|
+
#
|
19
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
20
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
21
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
22
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
23
|
+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
24
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
25
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
26
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
27
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
28
|
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
29
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
30
|
+
#
|
31
|
+
# Author: Robert Haines
|
32
|
+
|
33
|
+
#
|
34
|
+
module UCF
|
35
|
+
|
36
|
+
# A ManagedDirectory acts as the interface to a set of (possibly) managed
|
37
|
+
# files within it and also reserves the directory name in the Container
|
38
|
+
# namespace.
|
39
|
+
#
|
40
|
+
# Once a ManagedDirectory is registered in a Container then only it can be
|
41
|
+
# used to write to its contents.
|
42
|
+
class ManagedDirectory < ManagedEntry
|
43
|
+
include ReservedNames
|
44
|
+
include ManagedEntries
|
45
|
+
|
46
|
+
# :call-seq:
|
47
|
+
# new(name, required = false) -> ManagedDirectory
|
48
|
+
#
|
49
|
+
# Create a new ManagedDirectory with the supplied name and whether it is
|
50
|
+
# required to exist or not. Any ManagedFile or ManagedDirectory objects
|
51
|
+
# that are within this directory can also be given if required.
|
52
|
+
def initialize(name, required = false, entries = [])
|
53
|
+
super(name, required)
|
54
|
+
|
55
|
+
initialize_managed_entries(entries)
|
56
|
+
end
|
57
|
+
|
58
|
+
# :call-seq:
|
59
|
+
# verify!
|
60
|
+
#
|
61
|
+
# Verify this ManagedDirectory for correctness. ManagedFiles registered
|
62
|
+
# within it are verified recursively.
|
63
|
+
#
|
64
|
+
# A MalformedUCFError is raised if it does not pass verification.
|
65
|
+
def verify!
|
66
|
+
super
|
67
|
+
@files.values.each { |f| f.verify! }
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
# Copyright (c) 2013 The University of Manchester, UK.
|
2
|
+
#
|
3
|
+
# All rights reserved.
|
4
|
+
#
|
5
|
+
# Redistribution and use in source and binary forms, with or without
|
6
|
+
# modification, are permitted provided that the following conditions are met:
|
7
|
+
#
|
8
|
+
# * Redistributions of source code must retain the above copyright notice,
|
9
|
+
# this list of conditions and the following disclaimer.
|
10
|
+
#
|
11
|
+
# * Redistributions in binary form must reproduce the above copyright notice,
|
12
|
+
# this list of conditions and the following disclaimer in the documentation
|
13
|
+
# and/or other materials provided with the distribution.
|
14
|
+
#
|
15
|
+
# * Neither the names of The University of Manchester nor the names of its
|
16
|
+
# contributors may be used to endorse or promote products derived from this
|
17
|
+
# software without specific prior written permission.
|
18
|
+
#
|
19
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
20
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
21
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
22
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
23
|
+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
24
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
25
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
26
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
27
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
28
|
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
29
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
30
|
+
#
|
31
|
+
# Author: Robert Haines
|
32
|
+
|
33
|
+
#
|
34
|
+
module UCF
|
35
|
+
|
36
|
+
# ManagedEntry is the superclass of ManagedDirectory and ManagedFile. It
|
37
|
+
# should not be used directly but may be subclassed if necessary.
|
38
|
+
class ManagedEntry
|
39
|
+
|
40
|
+
# The name of the ManagedEntry. For the full path name of this entry use
|
41
|
+
# full_name.
|
42
|
+
attr_reader :name
|
43
|
+
|
44
|
+
# :call-seq:
|
45
|
+
# new(name, required) -> ManagedEntry
|
46
|
+
#
|
47
|
+
# Create a new ManagedEntry with the supplied name. The entry should also
|
48
|
+
# be marked as required or not.
|
49
|
+
def initialize(name, required)
|
50
|
+
@parent = nil
|
51
|
+
@name = name
|
52
|
+
@required = required
|
53
|
+
end
|
54
|
+
|
55
|
+
# :call-seq:
|
56
|
+
# full_name -> string
|
57
|
+
#
|
58
|
+
# The fully qualified name of this ManagedEntry.
|
59
|
+
def full_name
|
60
|
+
@parent.is_a?(Container) ? @name : "#{@parent.name}/#{@name}"
|
61
|
+
end
|
62
|
+
|
63
|
+
# :call-seq:
|
64
|
+
# required? -> true or false
|
65
|
+
#
|
66
|
+
# Is this ManagedEntry required to be present according to the
|
67
|
+
# specification of its Container?
|
68
|
+
def required?
|
69
|
+
@required
|
70
|
+
end
|
71
|
+
|
72
|
+
# :call-seq:
|
73
|
+
# exists? -> true or false
|
74
|
+
#
|
75
|
+
# Does this ManagedEntry exist in the Container?
|
76
|
+
def exists?
|
77
|
+
container.entries.each do |entry|
|
78
|
+
test = (entry.ftype == :directory) ? "#{full_name}/" : full_name
|
79
|
+
return true if entry.name == test
|
80
|
+
end
|
81
|
+
|
82
|
+
false
|
83
|
+
end
|
84
|
+
|
85
|
+
# :stopdoc:
|
86
|
+
# Allows the object in which this entry has been registered in to tell it
|
87
|
+
# who it is.
|
88
|
+
def parent=(parent)
|
89
|
+
@parent = parent
|
90
|
+
end
|
91
|
+
# :startdoc:
|
92
|
+
|
93
|
+
# :call-seq:
|
94
|
+
# verify -> true or false
|
95
|
+
#
|
96
|
+
# Verify this ManagedEntry by checking that it exists if it is required
|
97
|
+
# according to its Container specification and validating its contents if
|
98
|
+
# necessary.
|
99
|
+
def verify
|
100
|
+
begin
|
101
|
+
verify!
|
102
|
+
rescue
|
103
|
+
return false
|
104
|
+
end
|
105
|
+
|
106
|
+
true
|
107
|
+
end
|
108
|
+
|
109
|
+
protected
|
110
|
+
|
111
|
+
# :call-seq:
|
112
|
+
# verify!
|
113
|
+
#
|
114
|
+
# Verify this ManagedEntry raising a MalformedUCFError if it fails.
|
115
|
+
#
|
116
|
+
# Subclasses should override this method if they require more complex
|
117
|
+
# verification to be done.
|
118
|
+
def verify!
|
119
|
+
unless !@required || exists?
|
120
|
+
raise MalformedUCFError.new("Entry '#{full_name}' is required but "\
|
121
|
+
"missing.")
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# :call-seq:
|
126
|
+
# container -> Container
|
127
|
+
#
|
128
|
+
# Return the Container that this ManagedEntry resides in.
|
129
|
+
def container
|
130
|
+
@parent.is_a?(Container) ? @parent : @parent.container
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
end
|