ro-crate 0.5.3 → 0.6.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 987f4bf5543890ca23264eaa771134f6ec2ba848a84ea32424cf951429141523
4
- data.tar.gz: f05c5166d95be3cec706fbb5d550c5af916f5f4c444112575ec49d5fe961e12a
3
+ metadata.gz: 2c4aabc50994541bbcbb2071b805f457e47c9e30176299fdde40741aa5ff1094
4
+ data.tar.gz: 0f4d7df6b4eb21961446ce2e3c05fe337fa7ed9e161eb21a7651b8ea6292f988
5
5
  SHA512:
6
- metadata.gz: '0848a81d03d8cdf7b4809de8613d90b67950f4339ae20ab5ce4ba6fa8d9e9559033c4a53ca47391b99ed5287ba7a2f2be7976b87c7ea8479051b8e7dfc9da6e8'
7
- data.tar.gz: c1fc1ffdadafd04c19d81c37befec9eed9bcf9e40db9c39158b20a2a8ec8b8368841c65211d31439ba585f637de9541e2b7d58673da4964c7b8afccbfb9bf6ff
6
+ metadata.gz: f590b0e08e5f813474e4c46e3230b01999d17cf349645b8c86285c8dbe3cd371563405e1e1143a7b68d115764373847ace352029beed404a5cc069e64ef40dab
7
+ data.tar.gz: 972a1e14b6faa4986bad9138fdd39861bf54af81dd5678354f84cc0f4bbc3b50252e38cf948d013bb6a979365dedc74e81c48661115f0cd3f408fcdd0744f777
@@ -7,7 +7,7 @@ jobs:
7
7
  runs-on: ubuntu-latest
8
8
  steps:
9
9
  - name: Checkout
10
- uses: actions/checkout@v2.3.1 # If you're using actions/checkout@v2 you must set persist-credentials to false in most cases for the deployment to work correctly.
10
+ uses: actions/checkout@v6
11
11
  with:
12
12
  persist-credentials: false
13
13
  - name: Setup Ruby
@@ -5,11 +5,11 @@ jobs:
5
5
  runs-on: ubuntu-latest
6
6
  strategy:
7
7
  matrix:
8
- ruby: ['2.6', '2.7', '3.0', '3.1', '3.2', '3.3']
8
+ ruby: ['2.7', '3.0', '3.1', '3.2', '3.3', '3.4', '4.0']
9
9
  fail-fast: false
10
10
  steps:
11
11
  - name: Checkout
12
- uses: actions/checkout@v2.3.1 # If you're using actions/checkout@v2 you must set persist-credentials to false in most cases for the deployment to work correctly.
12
+ uses: actions/checkout@v6
13
13
  with:
14
14
  persist-credentials: false
15
15
  - name: Setup Ruby
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- ruby-3.2.5
1
+ ruby-3.4.9
data/README.md CHANGED
@@ -2,10 +2,10 @@
2
2
 
3
3
  ![Tests](https://github.com/ResearchObject/ro-crate-ruby/actions/workflows/tests.yml/badge.svg)
4
4
 
5
- This is a WIP gem for creating, manipulating and reading RO-Crates (conforming to version 1.1 of the specification).
5
+ This is a WIP gem for creating, manipulating and reading RO-Crates (conforming to version 1.2 of the specification). RO-Crates produced by older versions (1.0, 1.1) of the spec can still be read.
6
6
 
7
- * RO-Crate - https://researchobject.github.io/ro-crate/
8
- * RO-Crate spec (1.1) - https://researchobject.github.io/ro-crate/1.1/
7
+ * RO-Crate - https://www.researchobject.org/ro-crate/
8
+ * RO-Crate spec (1.2) - https://www.researchobject.org/ro-crate/specification/1.2/
9
9
 
10
10
  ## Installation
11
11
 
data/Rakefile CHANGED
@@ -1,6 +1,5 @@
1
1
  require 'bundler/gem_tasks'
2
2
  require 'rake/testtask'
3
- require 'rdoc/task'
4
3
 
5
4
  desc 'Default: run unit tests.'
6
5
  task default: :test
@@ -13,14 +12,6 @@ Rake::TestTask.new(:test) do |t|
13
12
  t.warning = false
14
13
  end
15
14
 
16
- Rake::RDocTask.new(:rdoc) do |rdoc|
17
- rdoc.rdoc_dir = 'rdoc'
18
- rdoc.title = 'Devise'
19
- rdoc.options << '--line-numbers' << '--inline-source'
20
- rdoc.rdoc_files.include('README.md')
21
- rdoc.rdoc_files.include('lib/**/*.rb')
22
- end
23
-
24
15
  task :console do
25
16
  require 'irb'
26
17
  require 'irb/completion'
@@ -21,9 +21,16 @@ module ROCrate
21
21
 
22
22
  ##
23
23
  # Initialize an empty RO-Crate.
24
- def initialize(id = IDENTIFIER, properties = {})
24
+ #
25
+ # @param id [String] The crate's identifier.
26
+ # @param properties [Hash] Initial properties for the root data entity.
27
+ # @param version [String] RO-Crate spec version to declare (default: ROCrate::Metadata::DEFAULT_VERSION).
28
+ # Must be one of ROCrate::Metadata::SUPPORTED_VERSIONS.
29
+ def initialize(id = IDENTIFIER, properties = {}, version: ROCrate::Metadata::DEFAULT_VERSION)
30
+ ROCrate::Metadata.warn_unrecognized_version(version)
25
31
  @data_entities = Set.new
26
32
  @contextual_entities = Set.new
33
+ @metadata_version = version
27
34
  super(self, nil, id, properties)
28
35
  end
29
36
 
@@ -168,7 +175,7 @@ module ROCrate
168
175
  #
169
176
  # @return [Metadata]
170
177
  def metadata
171
- @metadata ||= ROCrate::Metadata.new(self)
178
+ @metadata ||= ROCrate::Metadata.new(self, {}, version: @metadata_version || ROCrate::Metadata::DEFAULT_VERSION)
172
179
  end
173
180
 
174
181
  ##
@@ -74,9 +74,8 @@ module ROCrate
74
74
  end
75
75
 
76
76
  def list_all_files(source_directory, include_hidden: false)
77
- args = ['**/*']
78
- args << ::File::FNM_DOTMATCH if include_hidden
79
- Dir.chdir(source_directory) { Dir.glob(*args) }.reject do |path|
77
+ flags = include_hidden ? ::File::FNM_DOTMATCH : 0
78
+ Dir.glob('**/*', flags, base: source_directory).reject do |path|
80
79
  path == '.' || path == '..' || path.end_with?('/.')
81
80
  end
82
81
  end
@@ -5,13 +5,46 @@ module ROCrate
5
5
  IDENTIFIER = 'ro-crate-metadata.json'.freeze
6
6
  IDENTIFIER_1_0 = 'ro-crate-metadata.jsonld'.freeze # 1.0 spec identifier
7
7
  RO_CRATE_BASE = 'https://w3id.org/ro/crate/'
8
- CONTEXT = "#{RO_CRATE_BASE}1.1/context".freeze
9
- SPEC = "#{RO_CRATE_BASE}1.1".freeze
10
8
 
11
- def initialize(crate, properties = {})
9
+ SUPPORTED_VERSIONS = %w[1.0 1.0-DRAFT 1.1 1.1-DRAFT 1.2 1.2-DRAFT].freeze
10
+ DEFAULT_VERSION = '1.2'.freeze
11
+
12
+ CONTEXT = "#{RO_CRATE_BASE}#{DEFAULT_VERSION}/context".freeze
13
+ SPEC = "#{RO_CRATE_BASE}#{DEFAULT_VERSION}".freeze
14
+
15
+ attr_reader :version
16
+
17
+ ##
18
+ # Emit a warning if the given version is not in SUPPORTED_VERSIONS.
19
+ # Does not raise — unrecognized versions are still accepted so the library
20
+ # stays forward-compatible with future spec versions that need no changes.
21
+ def self.warn_unrecognized_version(v)
22
+ return if SUPPORTED_VERSIONS.include?(v)
23
+ warn "Unrecognized RO-Crate version: #{v.inspect}. Known versions: #{SUPPORTED_VERSIONS.join(', ')}"
24
+ end
25
+
26
+ def initialize(crate, properties = {}, version: DEFAULT_VERSION)
27
+ self.class.warn_unrecognized_version(version)
28
+ @version = version
12
29
  super(crate, nil, IDENTIFIER, properties)
13
30
  end
14
31
 
32
+ ##
33
+ # Update the spec version this metadata declares.
34
+ # Used by the Reader to preserve the version of a parsed crate.
35
+ def version=(v)
36
+ self.class.warn_unrecognized_version(v)
37
+ @version = v
38
+ end
39
+
40
+ def context_url
41
+ "#{RO_CRATE_BASE}#{@version}/context"
42
+ end
43
+
44
+ def spec_url
45
+ "#{RO_CRATE_BASE}#{@version}"
46
+ end
47
+
15
48
  ##
16
49
  # Generate the crate's `ro-crate-metadata.jsonld`.
17
50
  # @return [String] The rendered JSON-LD as a "prettified" string.
@@ -21,7 +54,7 @@ module ROCrate
21
54
  end
22
55
 
23
56
  def context
24
- @context || CONTEXT
57
+ @context || context_url
25
58
  end
26
59
 
27
60
  def context= c
@@ -39,7 +72,7 @@ module ROCrate
39
72
  '@id' => IDENTIFIER,
40
73
  '@type' => 'CreativeWork',
41
74
  'about' => { '@id' => crate.id },
42
- 'conformsTo' => { '@id' => SPEC }
75
+ 'conformsTo' => { '@id' => spec_url }
43
76
  }
44
77
  end
45
78
  end
@@ -1,7 +1,11 @@
1
+ require 'zip/version'
2
+
1
3
  module ROCrate
2
4
  ##
3
5
  # A class to handle reading of RO-Crates from Zip files or directories.
4
6
  class Reader
7
+ LEGACY_EXTRACT = Zip::VERSION.start_with?('2.').freeze
8
+
5
9
  ##
6
10
  # Reads an RO-Crate from a directory or zip file.
7
11
  #
@@ -42,15 +46,15 @@ module ROCrate
42
46
  #
43
47
  # @param source [#read] An IO-like object containing a Zip file.
44
48
  # @param target [String, ::File, Pathname] The target directory where the file should be unzipped.
45
- def self.unzip_io_to(io, target)
46
- Dir.chdir(target) do
47
- Zip::InputStream.open(io) do |input|
48
- while (entry = input.get_next_entry)
49
- unless ::File.exist?(entry.name) || entry.name_is_directory?
50
- FileUtils::mkdir_p(::File.dirname(entry.name))
51
- ::File.binwrite(entry.name, input.read)
52
- end
53
- end
49
+ def self.unzip_io_to(source, target)
50
+ target_path = Pathname(target)
51
+ Zip::InputStream.open(source) do |input|
52
+ while (entry = input.get_next_entry)
53
+ next if entry.name_is_directory?
54
+ dest = safe_join(target_path, entry.name)
55
+ next if dest.exist?
56
+ FileUtils.mkdir_p(dest.dirname)
57
+ ::File.binwrite(dest, input.read)
54
58
  end
55
59
  end
56
60
  end
@@ -60,15 +64,14 @@ module ROCrate
60
64
  #
61
65
  # @param source [String, ::File, Pathname] The location of the zip file.
62
66
  # @param target [String, ::File, Pathname] The target directory where the file should be unzipped.
63
- def self.unzip_file_to(file_or_path, target)
64
- Dir.chdir(target) do
65
- Zip::File.open(file_or_path) do |zipfile|
66
- zipfile.each do |entry|
67
- unless ::File.exist?(entry.name)
68
- FileUtils::mkdir_p(::File.dirname(entry.name))
69
- zipfile.extract(entry, entry.name)
70
- end
71
- end
67
+ def self.unzip_file_to(source, target)
68
+ target_path = Pathname(target)
69
+ Zip::File.open(source) do |zipfile|
70
+ zipfile.each do |entry|
71
+ dest = safe_join(target_path, entry.name)
72
+ next if dest.exist?
73
+ FileUtils.mkdir_p(dest.dirname)
74
+ LEGACY_EXTRACT ? entry.extract(dest) : entry.extract(entry.name, destination_directory: target_path)
72
75
  end
73
76
  end
74
77
  end
@@ -87,6 +90,7 @@ module ROCrate
87
90
 
88
91
  # Traverse the unzipped directory to try and find the crate's root
89
92
  root_dir = detect_root_directory(target_dir)
93
+ raise ROCrate::ReadException, "No metadata found!" unless root_dir
90
94
 
91
95
  read_directory(root_dir)
92
96
  end
@@ -184,7 +188,10 @@ module ROCrate
184
188
  def self.initialize_crate(entity_hash, source, crate_class: ROCrate::Crate, context:)
185
189
  crate_class.new.tap do |crate|
186
190
  crate.properties = entity_hash.delete(ROCrate::Crate::IDENTIFIER)
187
- crate.metadata.properties = entity_hash.delete(ROCrate::Metadata::IDENTIFIER)
191
+ metadata_props = entity_hash.delete(ROCrate::Metadata::IDENTIFIER)
192
+ crate.metadata.properties = metadata_props
193
+ parsed_version = extract_version(metadata_props)
194
+ crate.metadata.version = parsed_version if parsed_version
188
195
  crate.metadata.context = context
189
196
  preview_properties = entity_hash.delete(ROCrate::Preview::IDENTIFIER)
190
197
  preview_path = ::File.join(source, ROCrate::Preview::IDENTIFIER)
@@ -265,7 +272,7 @@ module ROCrate
265
272
 
266
273
  ##
267
274
  # Extract the metadata entity from the entity hash, according to the rules defined here:
268
- # https://www.researchobject.org/ro-crate/1.1/root-data-entity.html#finding-the-root-data-entity
275
+ # https://www.researchobject.org/ro-crate/specification/1.2/root-data-entity.html#finding-the-root-data-entity
269
276
  # @return [nil, Hash{String => Hash}] A Hash containing (hopefully) one value, the metadata entity's properties
270
277
  # mapped by its @id, or nil if nothing is found.
271
278
  def self.extract_metadata_entity(entities)
@@ -284,6 +291,24 @@ module ROCrate
284
291
  entities.delete(ROCrate::Metadata::IDENTIFIER_1_0))
285
292
  end
286
293
 
294
+ ##
295
+ # Extract the spec version from the metadata entity's `conformsTo`.
296
+ # Looks for an `@id` matching `https://w3id.org/ro/crate/<version>` and returns `<version>`.
297
+ # @param metadata_props [Hash, nil] The metadata entity's properties.
298
+ # @return [String, nil] The parsed version string, or nil if not found.
299
+ def self.extract_version(metadata_props)
300
+ return nil unless metadata_props
301
+ conforms = metadata_props['conformsTo']
302
+ conforms = [conforms] unless conforms.is_a?(Array)
303
+ conforms.compact.each do |c|
304
+ id = c.is_a?(Hash) ? c['@id'] : c
305
+ next unless id&.start_with?(ROCrate::Metadata::RO_CRATE_BASE)
306
+ version = id.sub(ROCrate::Metadata::RO_CRATE_BASE, '').split('/').first
307
+ return version if version && !version.empty?
308
+ end
309
+ nil
310
+ end
311
+
287
312
  ##
288
313
  # Extract the ro-crate-preview entity from the entity hash.
289
314
  # @return [Hash{String => Hash}] A Hash containing the preview entity's properties mapped by its @id, or nil if nothing is found.
@@ -293,7 +318,7 @@ module ROCrate
293
318
 
294
319
  ##
295
320
  # Extract the root entity from the entity hash, according to the rules defined here:
296
- # https://www.researchobject.org/ro-crate/1.1/root-data-entity.html#finding-the-root-data-entity
321
+ # https://www.researchobject.org/ro-crate/specification/1.2/root-data-entity.html#finding-the-root-data-entity
297
322
  # @return [Hash{String => Hash}] A Hash containing (hopefully) one value, the root entity's properties,
298
323
  # mapped by its @id.
299
324
  def self.extract_root_entity(entities)
@@ -303,7 +328,7 @@ module ROCrate
303
328
  end
304
329
 
305
330
  ##
306
- # Finds an RO-Crate's root directory (where `ro-crate-metdata.json` is located) within a given directory.
331
+ # Finds an RO-Crate's root directory (where `ro-crate-metadata.json` is located) within a given directory.
307
332
  #
308
333
  # @param source [String, ::File, Pathname] The location of the directory.
309
334
  # @return [Pathname, nil] The path to the root, or nil if not found.
@@ -323,5 +348,28 @@ module ROCrate
323
348
 
324
349
  nil
325
350
  end
351
+
352
+ ##
353
+ # Safely joins a desired file path onto a base directory, raising an exception if the path attempts to traverse
354
+ # outside it.
355
+ #
356
+ # @param base [Pathname] The base directory where the file will go.
357
+ # @param path [String] The desired file path.
358
+ #
359
+ # @raise [ROCrate::ReadException] Raised if an unsafe path is given.
360
+ #
361
+ # @return [Pathname] The safely joined base + path.
362
+ def self.safe_join(base, path)
363
+ dest = base.join(path)
364
+ # Guard against zip-slip attacks.
365
+ begin
366
+ unsafe = dest.expand_path.relative_path_from(base.expand_path).each_filename.first == '..'
367
+ rescue ArgumentError # Handle unjoinable paths, e.g. on different drives.
368
+ unsafe = true
369
+ end
370
+ raise ROCrate::ReadException, "Unsafe path in zip entry: #{path}" if unsafe
371
+
372
+ dest
373
+ end
326
374
  end
327
375
  end
@@ -44,7 +44,7 @@ module ROCrate
44
44
  # @param destination [String, ::File] The destination where to write the RO-Crate zip.
45
45
  # @param skip_preview [Boolean] Whether or not to skip generation of the RO-Crate preview HTML file.
46
46
  def write_zip(destination, skip_preview: false)
47
- Zip::File.open(destination, Zip::File::CREATE) do |zip|
47
+ Zip::File.open(destination, create: true) do |zip|
48
48
  @crate.payload.each do |path, entry|
49
49
  next if entry.directory?
50
50
  next if skip_preview && entry&.source.is_a?(ROCrate::PreviewGenerator)
data/ro_crate.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'ro-crate'
3
- s.version = '0.5.3'
3
+ s.version = '0.6.0'
4
4
  s.summary = 'Create, manipulate, read RO-Crates.'
5
5
  s.authors = ['Finn Bacall']
6
6
  s.email = 'finn.bacall@manchester.ac.uk'
@@ -8,12 +8,13 @@ Gem::Specification.new do |s|
8
8
  s.homepage = 'https://github.com/ResearchObject/ro-crate-ruby'
9
9
  s.require_paths = ['lib']
10
10
  s.licenses = ['MIT']
11
- s.add_runtime_dependency 'addressable', '>= 2.7', '< 2.9'
12
- s.add_runtime_dependency 'rubyzip', '~> 2.0.0'
13
- s.add_development_dependency 'rake', '~> 13.0.0'
11
+ s.required_ruby_version = '>= 2.7.0'
12
+ s.add_runtime_dependency 'addressable', '>= 2.7', '< 3'
13
+ s.add_runtime_dependency 'rubyzip', '>= 2.3', '< 4'
14
+ s.add_development_dependency 'rake', '~> 13.4.2'
14
15
  s.add_development_dependency 'test-unit', '~> 3.5.3'
15
16
  s.add_development_dependency 'simplecov', '~> 0.21.2'
16
17
  s.add_development_dependency 'yard', '~> 0.9.25'
17
- s.add_development_dependency 'webmock', '~> 3.8.3'
18
- s.add_development_dependency 'rexml', '~> 3.2.5'
18
+ s.add_development_dependency 'webmock', '~> 3.26.2'
19
+ s.add_development_dependency 'rexml', '~> 3.4.4'
19
20
  end
data/test/crate_test.rb CHANGED
@@ -377,4 +377,33 @@ class CrateTest < Test::Unit::TestCase
377
377
  assert_nil crate.get('#joe')
378
378
  assert crate.get('#joehouse')
379
379
  end
380
+
381
+ test 'defaults to RO-Crate spec 1.2' do
382
+ crate = ROCrate::Crate.new
383
+ assert_equal '1.2', crate.metadata.version
384
+ assert_equal 'https://w3id.org/ro/crate/1.2/context', crate.metadata.context
385
+ assert_equal 'https://w3id.org/ro/crate/1.2', crate.metadata.spec_url
386
+ assert_equal({ '@id' => 'https://w3id.org/ro/crate/1.2' }, crate.metadata.properties['conformsTo'])
387
+ end
388
+
389
+ test 'can write older spec version' do
390
+ crate = ROCrate::Crate.new(ROCrate::Crate::IDENTIFIER, {}, version: '1.1')
391
+ assert_equal '1.1', crate.metadata.version
392
+ assert_equal 'https://w3id.org/ro/crate/1.1/context', crate.metadata.context
393
+ assert_equal({ '@id' => 'https://w3id.org/ro/crate/1.1' }, crate.metadata.properties['conformsTo'])
394
+ end
395
+
396
+ test 'warns but accepts unrecognized spec version' do
397
+ original_stderr = $stderr
398
+ begin
399
+ $stderr = StringIO.new
400
+ crate = ROCrate::Crate.new(ROCrate::Crate::IDENTIFIER, {}, version: '1.5')
401
+ err = $stderr.string
402
+ assert_match(/Unrecognized RO-Crate version/, err)
403
+ assert_equal '1.5', crate.metadata.version
404
+ assert_equal 'https://w3id.org/ro/crate/1.5', crate.metadata.spec_url
405
+ ensure
406
+ $stderr = original_stderr
407
+ end
408
+ end
380
409
  end
Binary file
Binary file
Binary file
data/test/reader_test.rb CHANGED
@@ -358,6 +358,11 @@ class ReaderTest < Test::Unit::TestCase
358
358
  ROCrate::Reader.read(fixture_file('broken/missing_file'))
359
359
  end
360
360
  assert_include e.message, 'not found in crate: file1.txt'
361
+
362
+ e = check_exception(ROCrate::ReadException) do
363
+ ROCrate::Reader.read(fixture_file('just_a_zip.zip').path)
364
+ end
365
+ assert_include e.message, 'No metadata found'
361
366
  end
362
367
 
363
368
  test 'tolerates arcp identifier on root data entity (and missing hasPart)' do
@@ -381,18 +386,56 @@ class ReaderTest < Test::Unit::TestCase
381
386
  assert_equal 'a_file', data.first.name
382
387
  end
383
388
 
384
- private
389
+ test 'protect against zip-slip' do
390
+ Dir.mktmpdir do |dir|
391
+ subdir = ::File.join(dir, 'subdir')
392
+ ::Dir.mkdir(subdir)
393
+
394
+ # Relative
395
+ e = check_exception(ROCrate::ReadException) do
396
+ ROCrate::Reader.unzip_file_to(fixture_file('unsafe/relative0.zip').path, subdir)
397
+ end
398
+ assert_include e.message, 'Unsafe path in zip entry: ../moo'
399
+ refute ::File.exist?(::File.join(dir, 'moo'))
400
+
401
+ e = check_exception(ROCrate::ReadException) do
402
+ ROCrate::Reader.unzip_io_to(fixture_file('unsafe/relative0.zip'), subdir)
403
+ end
404
+ assert_include e.message, 'Unsafe path in zip entry: ../moo'
405
+ refute ::File.exist?(::File.join(dir, 'moo'))
406
+
407
+ # Absolute
408
+ e = check_exception(ROCrate::ReadException) do
409
+ ROCrate::Reader.unzip_file_to(fixture_file('unsafe/absolute1.zip').path, subdir)
410
+ end
411
+ assert_include e.message, 'Unsafe path in zip entry: /tmp/moo'
412
+
413
+ e = check_exception(ROCrate::ReadException) do
414
+ ROCrate::Reader.unzip_io_to(fixture_file('unsafe/absolute1.zip'), subdir)
415
+ end
416
+ assert_include e.message, 'Unsafe path in zip entry: /tmp/moo'
385
417
 
386
- def check_exception(exception_class)
387
- e = nil
388
- assert_raise(exception_class) do
418
+ # Simulate ArgumentError in safe_join
389
419
  begin
390
- yield
391
- rescue exception_class => e
392
- raise e
420
+ original_expand_path = Pathname.instance_method(:expand_path)
421
+ Pathname.define_method(:expand_path) do |*args|
422
+ raise ArgumentError, 'Oh no'
423
+ end
424
+ e = check_exception(ROCrate::ReadException) do
425
+ ROCrate::Reader.unzip_file_to(fixture_file('unsafe/absolute1.zip').path, subdir)
426
+ end
427
+ assert_include e.message, 'Unsafe path in zip entry: /tmp/moo'
428
+ ensure
429
+ Pathname.define_method(:expand_path, original_expand_path)
393
430
  end
394
431
  end
432
+ end
433
+
434
+ test 'reads spec 1.1 RO-Crate and preserves version' do
435
+ crate = ROCrate::Reader.read(fixture_file('crate-spec1.1').path)
395
436
 
396
- e
437
+ assert_equal '1.1', crate.metadata.version
438
+ assert_equal 'https://w3id.org/ro/crate/1.1', crate.metadata.spec_url
439
+ assert_equal 'https://w3id.org/ro/crate/1.1/context', crate.metadata.context_url
397
440
  end
398
441
  end
data/test/test_helper.rb CHANGED
@@ -5,10 +5,37 @@ require 'test/unit'
5
5
  require 'ro_crate'
6
6
  require 'webmock/test_unit'
7
7
 
8
+ class Test::Unit::TestCase
9
+ def teardown
10
+ self._opened_files.each do |f|
11
+ f.close unless f.closed?
12
+ end
13
+ end
14
+
15
+ def _opened_files
16
+ @opened_files ||= []
17
+ end
18
+ end
19
+
8
20
  def fixture_file(name, *args)
9
- ::File.open(::File.join(fixture_dir, name), *args)
21
+ f = ::File.open(::File.join(fixture_dir, name), *args)
22
+ self._opened_files << f
23
+ f
10
24
  end
11
25
 
12
26
  def fixture_dir
13
27
  ::File.join(::File.dirname(__FILE__), 'fixtures')
14
28
  end
29
+
30
+ def check_exception(exception_class)
31
+ e = nil
32
+ assert_raise(exception_class) do
33
+ begin
34
+ yield
35
+ rescue exception_class => e
36
+ raise e
37
+ end
38
+ end
39
+
40
+ e
41
+ end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ro-crate
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.3
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Finn Bacall
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2025-01-30 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: addressable
@@ -19,7 +18,7 @@ dependencies:
19
18
  version: '2.7'
20
19
  - - "<"
21
20
  - !ruby/object:Gem::Version
22
- version: '2.9'
21
+ version: '3'
23
22
  type: :runtime
24
23
  prerelease: false
25
24
  version_requirements: !ruby/object:Gem::Requirement
@@ -29,35 +28,41 @@ dependencies:
29
28
  version: '2.7'
30
29
  - - "<"
31
30
  - !ruby/object:Gem::Version
32
- version: '2.9'
31
+ version: '3'
33
32
  - !ruby/object:Gem::Dependency
34
33
  name: rubyzip
35
34
  requirement: !ruby/object:Gem::Requirement
36
35
  requirements:
37
- - - "~>"
36
+ - - ">="
38
37
  - !ruby/object:Gem::Version
39
- version: 2.0.0
38
+ version: '2.3'
39
+ - - "<"
40
+ - !ruby/object:Gem::Version
41
+ version: '4'
40
42
  type: :runtime
41
43
  prerelease: false
42
44
  version_requirements: !ruby/object:Gem::Requirement
43
45
  requirements:
44
- - - "~>"
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '2.3'
49
+ - - "<"
45
50
  - !ruby/object:Gem::Version
46
- version: 2.0.0
51
+ version: '4'
47
52
  - !ruby/object:Gem::Dependency
48
53
  name: rake
49
54
  requirement: !ruby/object:Gem::Requirement
50
55
  requirements:
51
56
  - - "~>"
52
57
  - !ruby/object:Gem::Version
53
- version: 13.0.0
58
+ version: 13.4.2
54
59
  type: :development
55
60
  prerelease: false
56
61
  version_requirements: !ruby/object:Gem::Requirement
57
62
  requirements:
58
63
  - - "~>"
59
64
  - !ruby/object:Gem::Version
60
- version: 13.0.0
65
+ version: 13.4.2
61
66
  - !ruby/object:Gem::Dependency
62
67
  name: test-unit
63
68
  requirement: !ruby/object:Gem::Requirement
@@ -106,29 +111,28 @@ dependencies:
106
111
  requirements:
107
112
  - - "~>"
108
113
  - !ruby/object:Gem::Version
109
- version: 3.8.3
114
+ version: 3.26.2
110
115
  type: :development
111
116
  prerelease: false
112
117
  version_requirements: !ruby/object:Gem::Requirement
113
118
  requirements:
114
119
  - - "~>"
115
120
  - !ruby/object:Gem::Version
116
- version: 3.8.3
121
+ version: 3.26.2
117
122
  - !ruby/object:Gem::Dependency
118
123
  name: rexml
119
124
  requirement: !ruby/object:Gem::Requirement
120
125
  requirements:
121
126
  - - "~>"
122
127
  - !ruby/object:Gem::Version
123
- version: 3.2.5
128
+ version: 3.4.4
124
129
  type: :development
125
130
  prerelease: false
126
131
  version_requirements: !ruby/object:Gem::Requirement
127
132
  requirements:
128
133
  - - "~>"
129
134
  - !ruby/object:Gem::Version
130
- version: 3.2.5
131
- description:
135
+ version: 3.4.4
132
136
  email: finn.bacall@manchester.ac.uk
133
137
  executables: []
134
138
  extensions: []
@@ -200,6 +204,7 @@ files:
200
204
  - test/fixtures/directory_crate/ro-crate-preview.html
201
205
  - test/fixtures/file with spaces.txt
202
206
  - test/fixtures/info.txt
207
+ - test/fixtures/just_a_zip.zip
203
208
  - test/fixtures/misc_data_entity_crate/ro-crate-metadata.json
204
209
  - test/fixtures/multi_metadata_crate.crate.zip
205
210
  - test/fixtures/nested_directory.zip
@@ -232,6 +237,8 @@ files:
232
237
  - test/fixtures/unlinked_entity_crate/test/test1/input.bed
233
238
  - test/fixtures/unlinked_entity_crate/test/test1/output_exp.bed
234
239
  - test/fixtures/unlinked_entity_crate/test/test1/sort-and-change-case-test.yml
240
+ - test/fixtures/unsafe/absolute1.zip
241
+ - test/fixtures/unsafe/relative0.zip
235
242
  - test/fixtures/uri_heavy_crate/main.nf
236
243
  - test/fixtures/uri_heavy_crate/ro-crate-metadata.json
237
244
  - test/fixtures/uri_heavy_crate/ro-crate-preview.html
@@ -2473,7 +2480,6 @@ homepage: https://github.com/ResearchObject/ro-crate-ruby
2473
2480
  licenses:
2474
2481
  - MIT
2475
2482
  metadata: {}
2476
- post_install_message:
2477
2483
  rdoc_options: []
2478
2484
  require_paths:
2479
2485
  - lib
@@ -2481,15 +2487,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
2481
2487
  requirements:
2482
2488
  - - ">="
2483
2489
  - !ruby/object:Gem::Version
2484
- version: '0'
2490
+ version: 2.7.0
2485
2491
  required_rubygems_version: !ruby/object:Gem::Requirement
2486
2492
  requirements:
2487
2493
  - - ">="
2488
2494
  - !ruby/object:Gem::Version
2489
2495
  version: '0'
2490
2496
  requirements: []
2491
- rubygems_version: 3.4.19
2492
- signing_key:
2497
+ rubygems_version: 3.6.9
2493
2498
  specification_version: 4
2494
2499
  summary: Create, manipulate, read RO-Crates.
2495
2500
  test_files: []