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 +4 -4
- data/.github/workflows/docs.yml +1 -1
- data/.github/workflows/tests.yml +2 -2
- data/.ruby-version +1 -1
- data/README.md +3 -3
- data/Rakefile +0 -9
- data/lib/ro_crate/model/crate.rb +9 -2
- data/lib/ro_crate/model/directory.rb +2 -3
- data/lib/ro_crate/model/metadata.rb +38 -5
- data/lib/ro_crate/reader.rb +70 -22
- data/lib/ro_crate/writer.rb +1 -1
- data/ro_crate.gemspec +7 -6
- data/test/crate_test.rb +29 -0
- data/test/fixtures/just_a_zip.zip +0 -0
- data/test/fixtures/unsafe/absolute1.zip +0 -0
- data/test/fixtures/unsafe/relative0.zip +0 -0
- data/test/reader_test.rb +51 -8
- data/test/test_helper.rb +28 -1
- metadata +25 -20
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2c4aabc50994541bbcbb2071b805f457e47c9e30176299fdde40741aa5ff1094
|
|
4
|
+
data.tar.gz: 0f4d7df6b4eb21961446ce2e3c05fe337fa7ed9e161eb21a7651b8ea6292f988
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f590b0e08e5f813474e4c46e3230b01999d17cf349645b8c86285c8dbe3cd371563405e1e1143a7b68d115764373847ace352029beed404a5cc069e64ef40dab
|
|
7
|
+
data.tar.gz: 972a1e14b6faa4986bad9138fdd39861bf54af81dd5678354f84cc0f4bbc3b50252e38cf948d013bb6a979365dedc74e81c48661115f0cd3f408fcdd0744f777
|
data/.github/workflows/docs.yml
CHANGED
|
@@ -7,7 +7,7 @@ jobs:
|
|
|
7
7
|
runs-on: ubuntu-latest
|
|
8
8
|
steps:
|
|
9
9
|
- name: Checkout
|
|
10
|
-
uses: actions/checkout@
|
|
10
|
+
uses: actions/checkout@v6
|
|
11
11
|
with:
|
|
12
12
|
persist-credentials: false
|
|
13
13
|
- name: Setup Ruby
|
data/.github/workflows/tests.yml
CHANGED
|
@@ -5,11 +5,11 @@ jobs:
|
|
|
5
5
|
runs-on: ubuntu-latest
|
|
6
6
|
strategy:
|
|
7
7
|
matrix:
|
|
8
|
-
ruby: ['2.
|
|
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@
|
|
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.
|
|
1
|
+
ruby-3.4.9
|
data/README.md
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|
|
|
5
|
-
This is a WIP gem for creating, manipulating and reading RO-Crates (conforming to version 1.
|
|
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.
|
|
8
|
-
* RO-Crate spec (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'
|
data/lib/ro_crate/model/crate.rb
CHANGED
|
@@ -21,9 +21,16 @@ module ROCrate
|
|
|
21
21
|
|
|
22
22
|
##
|
|
23
23
|
# Initialize an empty RO-Crate.
|
|
24
|
-
|
|
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
|
-
|
|
78
|
-
|
|
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
|
-
|
|
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 ||
|
|
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' =>
|
|
75
|
+
'conformsTo' => { '@id' => spec_url }
|
|
43
76
|
}
|
|
44
77
|
end
|
|
45
78
|
end
|
data/lib/ro_crate/reader.rb
CHANGED
|
@@ -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(
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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(
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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-
|
|
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
|
data/lib/ro_crate/writer.rb
CHANGED
|
@@ -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,
|
|
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.
|
|
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.
|
|
12
|
-
s.add_runtime_dependency '
|
|
13
|
-
s.
|
|
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.
|
|
18
|
-
s.add_development_dependency 'rexml', '~> 3.
|
|
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
|
-
|
|
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
|
-
|
|
387
|
-
e = nil
|
|
388
|
-
assert_raise(exception_class) do
|
|
418
|
+
# Simulate ArgumentError in safe_join
|
|
389
419
|
begin
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
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
|
-
|
|
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.
|
|
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:
|
|
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: '
|
|
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: '
|
|
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.
|
|
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:
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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:
|
|
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.
|
|
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: []
|