kataba 1.1.3 → 1.1.4

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.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/lib/kataba/fetcher.rb +12 -1
  3. data/lib/kataba.rb +51 -14
  4. metadata +12 -12
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0c1a21b706cc4e327925cd9d936247e6f5ce17ea3964853f1b442e5ee8ecae3b
4
- data.tar.gz: 11ad87d5a2baede9403e4b83e2e5db01f5f2b8fe9702371228018cb96534ec22
3
+ metadata.gz: 8ae88ae5dafc92e24f90d773300f44d096a5efb5d42e82c047e48c413ffcf852
4
+ data.tar.gz: 6954878cb4e2fb52646f294efda7a53dd0c11ac97e9a08554691d298ac5e961e
5
5
  SHA512:
6
- metadata.gz: e2bdb712cd9c063469ee35b6dd5c3f1321018fc490ed25cb095c63715aaf63829915db1b99bfac94d87e574c88e301145fde7359cc57139dd1a997d61954bddc
7
- data.tar.gz: d21c6a7c83b2a93c89115305856cf2824a62cfa75e7aaa32bda7ea0f4d648bb4d8e6e4db3d4e45ea734a2bc495ea33d3737eb62bc85b3fd076958375d3d0e6fa
6
+ metadata.gz: 4be9c7f61034d3d0dbabcb65ccd56169f1f1f7e5aafeaa583a6cd5ab5cf348a5dac1c378d44a715f8b2aeecac8059a1af39a47a1cce53120bf2a5f3153d20630
7
+ data.tar.gz: bf9b63fcf72d6ae6c4ee1e515ff81854ae4f88d79b0a4496bdc2bdccd63fce49bcded29d8eb0fddc7c22f83860956716c7c114d101fe4e18a5b02e5f2e735c7e
@@ -87,7 +87,7 @@ module Kataba
87
87
  def request_with_redirects(uri, redirect_depth:)
88
88
  raise FetchError, "too many redirects fetching #{@original_uri}" if redirect_depth > MAX_REDIRECTS
89
89
 
90
- parsed = URI.parse(uri)
90
+ parsed = parse_http_uri(uri)
91
91
  http = Net::HTTP.new(parsed.host, parsed.port)
92
92
  http.use_ssl = (parsed.scheme == 'https')
93
93
  http.open_timeout = @open_timeout
@@ -116,6 +116,17 @@ module Kataba
116
116
  end
117
117
  end
118
118
 
119
+ # A bare filename parses as a URI with no host, and Net::HTTP then fails
120
+ # with NoMethodError, which no consumer's fetch-failure branch rescues.
121
+ def parse_http_uri(uri)
122
+ parsed = URI.parse(uri)
123
+ return parsed if parsed.is_a?(URI::HTTP) && parsed.host
124
+
125
+ raise FetchError, "not an absolute http(s) URI: #{uri}"
126
+ rescue URI::InvalidURIError => e
127
+ raise FetchError, "not a valid URI: #{uri} (#{e.message})"
128
+ end
129
+
119
130
  def resolve_redirect(from, location)
120
131
  raise FetchError, "redirect with no Location header from #{from}" if location.nil? || location.empty?
121
132
 
data/lib/kataba.rb CHANGED
@@ -3,6 +3,8 @@ require 'tmpdir'
3
3
  require 'digest/md5'
4
4
  require 'fileutils'
5
5
  require 'yaml'
6
+ require 'uri'
7
+ require 'set'
6
8
  require 'kataba/fetcher'
7
9
 
8
10
  module Kataba
@@ -116,12 +118,17 @@ module Kataba
116
118
 
117
119
  private
118
120
 
119
- def self.download_xsd(xsd_uri_array)
121
+ def self.download_xsd(xsd_uri_array, visited = Set.new)
120
122
  new_xsd_uris = []
121
123
  file_paths = []
122
124
 
123
125
  # Download files
124
126
  xsd_uri_array.each do |xsd_uri|
127
+ # Schemas may import each other (tdwg_dwc_simple.xsd and
128
+ # tdwg_dwc_extensions.xsd do), so fetch each URI once per call or the
129
+ # recursion never ends.
130
+ next unless visited.add?(xsd_uri)
131
+
125
132
  uri_md5 = Digest::MD5.hexdigest(xsd_uri)
126
133
 
127
134
  dir_name = "#{self.configuration.offline_storage}"
@@ -137,18 +144,22 @@ module Kataba
137
144
  # Resolve mirror first, if configured, then fetch. Fetching BEFORE
138
145
  # opening tmp_path means a failed network fetch can't leave an
139
146
  # orphaned 0-byte .part on disk — the file simply isn't created.
140
- fetch_uri = xsd_uri
147
+ mirror = nil
141
148
  if !self.configuration.mirror_list.to_s.empty?
142
149
  # YAML.load_file returns nil for a comments-only or empty file.
143
150
  # Treat that as "no mirror configured" rather than NoMethodError.
144
151
  mirror_list = YAML.load_file(self.configuration.mirror_list) || {}
145
- mirror = mirror_list[xsd_uri]
146
- fetch_uri = mirror unless mirror.to_s.empty?
152
+ mirror = mirror_list[xsd_uri].to_s
153
+ mirror = nil if mirror.empty?
147
154
  end
148
155
 
149
- body = Kataba::Fetcher.new(fetch_uri,
150
- open_timeout: self.configuration.open_timeout,
151
- read_timeout: self.configuration.read_timeout).fetch
156
+ body = if mirror && !mirror.match?(%r{\Ahttps?://}i)
157
+ read_local_mirror(mirror)
158
+ else
159
+ Kataba::Fetcher.new(mirror || xsd_uri,
160
+ open_timeout: self.configuration.open_timeout,
161
+ read_timeout: self.configuration.read_timeout).fetch
162
+ end
152
163
 
153
164
  # Write to a .part file first; only rename to the final cache path
154
165
  # after we've confirmed the bytes parse as XML. Without this, a
@@ -165,20 +176,20 @@ module Kataba
165
176
  end
166
177
 
167
178
  File.rename(tmp_path, file_path)
168
- file_paths << file_path
179
+ file_paths << [file_path, xsd_uri]
169
180
  end
170
181
 
171
182
  # Search inside for other schemaLocations
172
- file_paths.each do |file_path|
173
- new_xsd_uris = find_schemas(file_path)
183
+ file_paths.each do |file_path, xsd_uri|
184
+ new_xsd_uris.concat(find_schemas(file_path, xsd_uri))
174
185
  end
175
186
 
176
187
  if !new_xsd_uris.reject(&:empty?).empty?
177
- download_xsd(new_xsd_uris)
188
+ download_xsd(new_xsd_uris, visited)
178
189
  end
179
190
  end
180
191
 
181
- def self.find_schemas(xml_file_path)
192
+ def self.find_schemas(xml_file_path, base_uri)
182
193
  xsd_uri_array = []
183
194
 
184
195
  # Open XML file
@@ -186,10 +197,11 @@ module Kataba
186
197
  # search for schemaLocations
187
198
  doc.xpath("//@schemaLocation").each do |node|
188
199
  if !node.value.to_s.empty?
200
+ schema_uri = resolve_schema_location(node.value, base_uri)
189
201
  # Add to array
190
- xsd_uri_array << node.value
202
+ xsd_uri_array << schema_uri
191
203
  # Get MD5
192
- uri_md5 = Digest::MD5.hexdigest(node.value)
204
+ uri_md5 = Digest::MD5.hexdigest(schema_uri)
193
205
  # Reassign attribute value
194
206
  node.value = "#{uri_md5}.xsd"
195
207
  end
@@ -200,4 +212,29 @@ module Kataba
200
212
 
201
213
  return xsd_uri_array
202
214
  end
215
+
216
+ # A mirror value that is not an http(s) URL is a file, so a consumer can
217
+ # ship a corrected schema with its own code. A relative path resolves
218
+ # against the mirror list's directory, never the working directory, which
219
+ # fetch_schema changes while it parses.
220
+ def self.read_local_mirror(path)
221
+ full_path = File.expand_path(path, File.dirname(self.configuration.mirror_list))
222
+ File.read(full_path)
223
+ rescue SystemCallError => e
224
+ raise Kataba::Fetcher::FetchError, "could not read mirror file #{full_path} (#{e.class})"
225
+ end
226
+
227
+ # An absolute location is returned byte for byte. Its MD5 is the cache
228
+ # filename and the mirror_list key, so normalising it would orphan every
229
+ # cached schema and miss every mirror. A relative one resolves against the
230
+ # canonical URI of the schema importing it, never a mirror or a redirect
231
+ # target, so its cache name does not depend on where the bytes came from.
232
+ def self.resolve_schema_location(location, base_uri)
233
+ return location if URI.parse(location).absolute?
234
+
235
+ URI.join(base_uri, location).to_s
236
+ rescue URI::Error
237
+ # Left as written, so the Fetcher reports it as a FetchError.
238
+ location
239
+ end
203
240
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kataba
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.3
4
+ version: 1.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Cliff
@@ -14,56 +14,56 @@ dependencies:
14
14
  name: nokogiri
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - '>='
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '1.19'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - '>='
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.19'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - '>='
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
33
  version: '0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - '>='
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: minitest
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - '>='
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
47
  version: '0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - '>='
52
+ - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: webmock
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - '>='
59
+ - - ">="
60
60
  - !ruby/object:Gem::Version
61
61
  version: '0'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - '>='
66
+ - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
69
  description: Kataba allows for mirroring and offline storage of XSD files, to enhance
@@ -85,16 +85,16 @@ require_paths:
85
85
  - lib
86
86
  required_ruby_version: !ruby/object:Gem::Requirement
87
87
  requirements:
88
- - - '>='
88
+ - - ">="
89
89
  - !ruby/object:Gem::Version
90
90
  version: '3.2'
91
91
  required_rubygems_version: !ruby/object:Gem::Requirement
92
92
  requirements:
93
- - - '>='
93
+ - - ">="
94
94
  - !ruby/object:Gem::Version
95
95
  version: '0'
96
96
  requirements: []
97
- rubygems_version: 3.0.9
97
+ rubygems_version: 3.2.33
98
98
  signing_key:
99
99
  specification_version: 4
100
100
  summary: XML Schema Definition (XSD) mirroring and offline validation for Nokogiri