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 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
- * META-INF directory support. Everything within the META-INF directory is
37
- optional but will supported in a near future version.
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, :name,
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
- # Reserved file and directory names for standard UCF documents.
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
- check_document!
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
- Container.open(filename, &block)
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
- Container.verify!(filename)
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).close
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
- raise ReservedNameClashError.new(entry.to_s) if reserved_entry?(entry)
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
- raise ReservedNameClashError.new(entry.to_s) if reserved_entry?(entry)
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
- raise ReservedNameClashError.new(name) if reserved_entry?(name)
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 check_document!
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