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