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 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