optic14n 2.0.1 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.rubocop.yml +3 -0
- data/.ruby-version +1 -2
- data/Gemfile +1 -5
- data/Jenkinsfile +9 -0
- data/Rakefile +9 -13
- data/lib/optic14n.rb +8 -8
- data/lib/optic14n/canonicalized_urls.rb +6 -4
- data/lib/optic14n/version.rb +1 -1
- data/lib/tasks/measure_reduction.rake +3 -3
- data/lib/uri/bluri.rb +40 -26
- data/lib/uri/query_hash.rb +7 -7
- data/optic14n.gemspec +14 -12
- data/spec/bluri_spec.rb +48 -48
- data/spec/c14n_spec.rb +124 -110
- data/spec/canonicalized_urls_spec.rb +27 -18
- data/spec/query_hash_spec.rb +22 -8
- data/spec/spec_helper.rb +1 -1
- data/spec/uri/query_hash_spec.rb +18 -7
- metadata +27 -13
- data/jenkins.sh +0 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: c6082fa0cb69d729e1329bb5fd04ffd5f40e9632bfa91008bd323e6c2f3c9222
|
4
|
+
data.tar.gz: 46da722d37cc308d168b935df6c0458437a5cbb993e30a8e603cdd58dbfbc75a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8578848462e54c7f940cbb6fe939059150f36ae28026faa1f7a7b2803ec21d44d2f8af8c624be5ccea539b6cb4b7c3646e5092f24ec9e5a7eb4952a9fcd79d4d
|
7
|
+
data.tar.gz: 94ba871eaf0864f8d4dde07428ccae007aee0e895b1ac9fa7a5876b05388b678697ce38159aac44177b4863c4a3279c8089e6cf652fcc32f89c578ec8ccf44f3
|
data/.rubocop.yml
ADDED
data/.ruby-version
CHANGED
@@ -1,2 +1 @@
|
|
1
|
-
2.
|
2
|
-
|
1
|
+
2.6.5
|
data/Gemfile
CHANGED
data/Jenkinsfile
ADDED
data/Rakefile
CHANGED
@@ -1,19 +1,15 @@
|
|
1
|
-
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__),
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
|
2
2
|
|
3
|
-
|
4
|
-
require
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
require 'gem_publisher'
|
10
|
-
desc 'Publish gem to Rubygems'
|
11
|
-
task :publish_gem do
|
12
|
-
gem = GemPublisher.publish_if_updated('optic14n.gemspec', :rubygems)
|
13
|
-
puts "Published #{gem}" if gem
|
3
|
+
begin
|
4
|
+
require "rspec/core/rake_task"
|
5
|
+
RSpec::Core::RakeTask.new(:spec)
|
6
|
+
rescue LoadError
|
7
|
+
puts "Running in production mode"
|
14
8
|
end
|
15
9
|
|
16
|
-
|
10
|
+
require "bundler/gem_tasks"
|
11
|
+
require "optic14n"
|
12
|
+
Dir.glob("lib/tasks/*.rake").each { |r| import r }
|
17
13
|
|
18
14
|
task default: :spec
|
19
15
|
task test: :spec
|
data/lib/optic14n.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
|
-
require
|
1
|
+
require "optic14n/version"
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
3
|
+
require "uri"
|
4
|
+
require "addressable/uri"
|
5
|
+
require "cgi"
|
6
|
+
require "forwardable"
|
7
|
+
require "uri/query_hash"
|
8
|
+
require "uri/bluri"
|
9
9
|
|
10
|
-
require
|
10
|
+
require "optic14n/canonicalized_urls"
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require "set"
|
2
|
+
|
1
3
|
module Optic14n
|
2
4
|
##
|
3
5
|
# Canonicalizes a set of URLs
|
@@ -21,7 +23,7 @@ module Optic14n
|
|
21
23
|
@urls.each do |url|
|
22
24
|
begin
|
23
25
|
@output_set.add(BLURI(url).canonicalize!(@options))
|
24
|
-
rescue
|
26
|
+
rescue StandardError => e
|
25
27
|
failures[url] = e
|
26
28
|
end
|
27
29
|
@seen += 1
|
@@ -29,7 +31,7 @@ module Optic14n
|
|
29
31
|
end
|
30
32
|
|
31
33
|
def write(filename)
|
32
|
-
File.open(filename,
|
34
|
+
File.open(filename, "w") do |file|
|
33
35
|
@output_set.each do |url|
|
34
36
|
file.puts url
|
35
37
|
end
|
@@ -39,7 +41,7 @@ module Optic14n
|
|
39
41
|
##
|
40
42
|
# Canonicalize given urls. +options+ will be passed to +BLURI.parse+
|
41
43
|
def self.from_urls(urls, options = {})
|
42
|
-
CanonicalizedUrls.new(urls, options).tap
|
44
|
+
CanonicalizedUrls.new(urls, options).tap(&:canonicalize!)
|
43
45
|
end
|
44
46
|
end
|
45
|
-
end
|
47
|
+
end
|
data/lib/optic14n/version.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
require
|
1
|
+
require "set"
|
2
2
|
|
3
3
|
namespace :opt do
|
4
|
-
desc
|
4
|
+
desc "Measure reduction from canonicalisation"
|
5
5
|
task :measure, [:filename, :output_file] do |_, args|
|
6
6
|
filename = args[:filename]
|
7
7
|
output_file = args[:output_file]
|
@@ -12,4 +12,4 @@ namespace :opt do
|
|
12
12
|
puts "#{urls.seen} urls seen, #{urls.size} after canonicalisation"
|
13
13
|
end
|
14
14
|
end
|
15
|
-
end
|
15
|
+
end
|
data/lib/uri/bluri.rb
CHANGED
@@ -6,20 +6,20 @@ module URI
|
|
6
6
|
#
|
7
7
|
class BLURI < URI::HTTP
|
8
8
|
PATH_ESCAPE_MAPPINGS = {
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
'"' =>
|
13
|
-
"'" =>
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
}
|
9
|
+
"[" => "%5b",
|
10
|
+
"]" => "%5d",
|
11
|
+
"," => "%2c",
|
12
|
+
'"' => "%22",
|
13
|
+
"'" => "%27",
|
14
|
+
"|" => "%7c",
|
15
|
+
"!" => "%21",
|
16
|
+
"£" => "%c2%a3",
|
17
|
+
}.freeze
|
18
18
|
|
19
19
|
PATH_UNESCAPE_MAPPINGS = {
|
20
|
-
|
21
|
-
|
22
|
-
}
|
20
|
+
"%7e" => "~",
|
21
|
+
"%21" => "!",
|
22
|
+
}.freeze
|
23
23
|
|
24
24
|
REQUIRE_REGEX_ESCAPE = %w<. | ( ) [ ] { } + \ ^ $ * ?> & PATH_ESCAPE_MAPPINGS.keys
|
25
25
|
|
@@ -29,11 +29,18 @@ module URI
|
|
29
29
|
|
30
30
|
def initialize(uri_str)
|
31
31
|
@uri = ::Addressable::URI.parse(uri_str)
|
32
|
-
|
32
|
+
|
33
|
+
raise URI::InvalidURIError, "'#{uri_str}' not a valid URI" unless valid_uri?
|
34
|
+
end
|
35
|
+
|
36
|
+
def valid_uri?
|
37
|
+
return unless @uri
|
38
|
+
|
39
|
+
%w[http https mailto].include?(@uri.scheme)
|
33
40
|
end
|
34
41
|
|
35
42
|
def query_hash
|
36
|
-
@query_hash ||= CGI::parse(self.query ||
|
43
|
+
@query_hash ||= CGI::parse(self.query || "").tap do |query_hash|
|
37
44
|
# By default, CGI::parse produces lots of arrays. Usually they have a single element
|
38
45
|
# in them. That's correct but not terribly usable. Fix it here.
|
39
46
|
query_hash.each_pair { |k, v| query_hash[k] = v[0] if v.length == 1 }
|
@@ -43,19 +50,19 @@ module URI
|
|
43
50
|
|
44
51
|
def query_hash=(value)
|
45
52
|
@query_hash = value
|
46
|
-
@uri.query = @query_hash.to_s ==
|
53
|
+
@uri.query = @query_hash.to_s == "" ? nil : @query_hash.to_s
|
47
54
|
end
|
48
55
|
|
49
56
|
def query=(query_str)
|
50
57
|
@query_hash = nil
|
51
|
-
@uri.query = query_str ==
|
58
|
+
@uri.query = query_str == "" ? nil : query_str
|
52
59
|
end
|
53
60
|
|
54
61
|
def self.parse(uri_str)
|
55
62
|
# Deal with known URI spec breaks - leading/trailing spaces and unencoded entities
|
56
63
|
if uri_str.is_a? String
|
57
|
-
uri_str = uri_str.strip.downcase.gsub(
|
58
|
-
uri_str.gsub!(
|
64
|
+
uri_str = uri_str.strip.downcase.gsub(" ", "%20")
|
65
|
+
uri_str.gsub!("&", "%26") if uri_str =~ /^mailto:.*&.*/
|
59
66
|
end
|
60
67
|
BLURI.new(uri_str)
|
61
68
|
end
|
@@ -65,9 +72,9 @@ module URI
|
|
65
72
|
end
|
66
73
|
|
67
74
|
def canonicalize!(options = {})
|
68
|
-
@uri.scheme =
|
75
|
+
@uri.scheme = "http" if @uri.scheme == "https"
|
69
76
|
|
70
|
-
@uri.path = @uri.path.sub(/\/*$/,
|
77
|
+
@uri.path = @uri.path.sub(/\/*$/, "") if @uri.path =~ /^*\/$/
|
71
78
|
@uri.path.gsub!(BLURI.path_escape_char_regex, PATH_ESCAPE_MAPPINGS)
|
72
79
|
@uri.path.gsub!(BLURI.path_unescape_code_regex, PATH_UNESCAPE_MAPPINGS)
|
73
80
|
|
@@ -82,7 +89,7 @@ module URI
|
|
82
89
|
allowed_keys = [options[:allow_query]].flatten.compact.map(&:to_s) unless allow_all
|
83
90
|
|
84
91
|
query_hash.keep_if do |k, _|
|
85
|
-
allow_all ||
|
92
|
+
allow_all || allowed_keys.include?(k.to_s)
|
86
93
|
end
|
87
94
|
|
88
95
|
self.query_hash = QueryHash[query_hash.sort_by { |k, _| k }]
|
@@ -91,26 +98,33 @@ module URI
|
|
91
98
|
##
|
92
99
|
# Generate a regex which matches all characters in PATH_ESCAPE_MAPPINGS
|
93
100
|
def self.path_escape_char_regex
|
94
|
-
@path_escape_char_regex ||=
|
95
|
-
|
96
|
-
|
97
|
-
|
101
|
+
@path_escape_char_regex ||= begin
|
102
|
+
escaped_characters_for_regex = PATH_ESCAPE_MAPPINGS.keys.map do |char|
|
103
|
+
REQUIRE_REGEX_ESCAPE.include?(char) ? "\\#{char}" : char
|
104
|
+
end
|
105
|
+
|
106
|
+
Regexp.new("[" + escaped_characters_for_regex.join + "]")
|
107
|
+
end
|
98
108
|
end
|
99
109
|
|
100
110
|
##
|
101
111
|
# Generate a regex which matches all escape sequences in PATH_UNESCAPE_MAPPINGS
|
102
112
|
def self.path_unescape_code_regex
|
103
113
|
@path_unescape_code_regex ||= Regexp.new(
|
104
|
-
PATH_UNESCAPE_MAPPINGS.keys.map { |code| "(?:#{code})" }.join(
|
114
|
+
PATH_UNESCAPE_MAPPINGS.keys.map { |code| "(?:#{code})" }.join("|"),
|
105
115
|
)
|
106
116
|
end
|
107
117
|
end
|
108
118
|
end
|
109
119
|
|
110
120
|
module Kernel
|
121
|
+
# rubocop:disable Naming/MethodName
|
111
122
|
def BLURI(uri_str)
|
112
123
|
::URI::BLURI.parse(uri_str)
|
113
124
|
end
|
125
|
+
# rubocop:enable Naming/MethodName
|
114
126
|
|
127
|
+
# rubocop:disable Style/AccessModifierDeclarations
|
115
128
|
module_function :BLURI
|
129
|
+
# rubocop:enable Style/AccessModifierDeclarations
|
116
130
|
end
|
data/lib/uri/query_hash.rb
CHANGED
@@ -4,12 +4,12 @@ module URI
|
|
4
4
|
module QueryHash
|
5
5
|
def [](key)
|
6
6
|
item = super key
|
7
|
-
item = super(key.to_s) if item.nil? || item.
|
8
|
-
item.class == Array && item.
|
7
|
+
item = super(key.to_s) if item.nil? || item.empty?
|
8
|
+
item.class == Array && item.empty? ? nil : item
|
9
9
|
end
|
10
10
|
|
11
11
|
def to_s
|
12
|
-
keys.map { |key| render_value(key, self[key]) }.join(
|
12
|
+
keys.map { |key| render_value(key, self[key]) }.join("&")
|
13
13
|
end
|
14
14
|
|
15
15
|
##
|
@@ -20,13 +20,13 @@ module URI
|
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
|
-
|
23
|
+
private
|
24
24
|
|
25
25
|
def render_value(key, value)
|
26
26
|
case value
|
27
|
-
|
28
|
-
|
29
|
-
|
27
|
+
when nil then key
|
28
|
+
when Array then value.map { |el| render_value(key, el) }.join("&")
|
29
|
+
else URI.encode_www_form_component(key) << "=" << URI.encode_www_form_component(value)
|
30
30
|
end
|
31
31
|
end
|
32
32
|
end
|
data/optic14n.gemspec
CHANGED
@@ -1,25 +1,27 @@
|
|
1
1
|
# coding: utf-8
|
2
|
-
|
2
|
+
|
3
|
+
lib = File.expand_path("lib", __dir__)
|
3
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require
|
5
|
+
require "optic14n/version"
|
5
6
|
|
6
7
|
Gem::Specification.new do |spec|
|
7
|
-
spec.name =
|
8
|
+
spec.name = "optic14n"
|
8
9
|
spec.version = Optic14n::VERSION
|
9
|
-
spec.authors = [
|
10
|
-
spec.email =
|
11
|
-
spec.description =
|
12
|
-
spec.summary =
|
13
|
-
spec.homepage =
|
14
|
-
spec.license =
|
10
|
+
spec.authors = ["GOV.UK Dev"]
|
11
|
+
spec.email = ["govuk-dev@digital.cabinet-office.gov.uk"]
|
12
|
+
spec.description = "Canonicalises URLs."
|
13
|
+
spec.summary = "Specifically, HTTP URLs, for a limited purpose"
|
14
|
+
spec.homepage = "https://github.com/alphagov/optic14n"
|
15
|
+
spec.license = "MIT"
|
15
16
|
|
16
17
|
spec.files = `git ls-files`.split($/)
|
17
18
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
19
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
20
|
spec.require_paths = %w(lib)
|
20
21
|
|
21
|
-
spec.add_dependency
|
22
|
+
spec.add_dependency "addressable", "~> 2.7"
|
22
23
|
|
23
|
-
spec.add_development_dependency
|
24
|
-
spec.add_development_dependency
|
24
|
+
spec.add_development_dependency "rake"
|
25
|
+
spec.add_development_dependency "rspec"
|
26
|
+
spec.add_development_dependency "rubocop-govuk", "~> 1.0.0"
|
25
27
|
end
|
data/spec/bluri_spec.rb
CHANGED
@@ -1,88 +1,88 @@
|
|
1
|
-
require
|
1
|
+
require "spec_helper"
|
2
2
|
|
3
3
|
describe URI::BLURI do
|
4
|
-
it
|
5
|
-
bluri = BLURI(
|
6
|
-
bluri.
|
4
|
+
it "should be an HTTP URI" do
|
5
|
+
bluri = BLURI("http://some.where.com")
|
6
|
+
expect(bluri).to be_a URI::HTTP
|
7
7
|
end
|
8
8
|
|
9
|
-
it
|
10
|
-
|
9
|
+
it "should not allow other schemes" do
|
10
|
+
expect { BLURI("ftp://foo") }.to raise_error(URI::InvalidURIError)
|
11
11
|
end
|
12
12
|
|
13
|
-
it
|
14
|
-
|
13
|
+
it "should not allow nil" do
|
14
|
+
expect { BLURI(nil) }.to raise_error(URI::InvalidURIError)
|
15
15
|
end
|
16
16
|
|
17
|
-
it
|
18
|
-
BLURI(
|
17
|
+
it "supports scheme" do
|
18
|
+
expect(BLURI("http://foo").scheme).to eq("http")
|
19
19
|
end
|
20
|
-
it
|
21
|
-
BLURI(
|
20
|
+
it "supports host" do
|
21
|
+
expect(BLURI("http://foo").host).to eq("foo")
|
22
22
|
end
|
23
|
-
it
|
24
|
-
BLURI(
|
23
|
+
it "supports path" do
|
24
|
+
expect(BLURI("http://foo/a/path").path).to eq("/a/path")
|
25
25
|
end
|
26
|
-
it
|
27
|
-
BLURI(
|
26
|
+
it "supports query" do
|
27
|
+
expect(BLURI("http://foo?to=you&you=foo").query).to eq("to=you&you=foo")
|
28
28
|
end
|
29
|
-
it
|
30
|
-
BLURI(
|
29
|
+
it "supports fragment" do
|
30
|
+
expect(BLURI("http://foo#fragment").fragment).to eq("fragment")
|
31
31
|
end
|
32
|
-
it
|
33
|
-
BLURI(
|
32
|
+
it "supports mailto:someone@somewhere" do
|
33
|
+
expect(BLURI("mailto:me@there.com").to_s).to eq("mailto:me@there.com")
|
34
34
|
end
|
35
|
-
it
|
36
|
-
BLURI(
|
35
|
+
it "corrects unencoded ampersands ins mailto" do # http://www.faqs.org/rfcs/rfc2368.html
|
36
|
+
expect(BLURI("mailto:fruit&veg.newcastle@rpa.gsi.gov.uk").to_s).to eq("mailto:fruit%26veg.newcastle@rpa.gsi.gov.uk")
|
37
37
|
end
|
38
|
-
it
|
39
|
-
BLURI(
|
38
|
+
it "corrects trailing spaces" do
|
39
|
+
expect(BLURI("http://www.newspapersoc.org.uk ").to_s).to eq("http://www.newspapersoc.org.uk")
|
40
40
|
end
|
41
|
-
it
|
42
|
-
BLURI(
|
41
|
+
it "corrects leading spaces" do
|
42
|
+
expect(BLURI(" http://www.newspapersoc.org.uk").to_s).to eq("http://www.newspapersoc.org.uk")
|
43
43
|
end
|
44
44
|
|
45
|
-
describe
|
46
|
-
context
|
45
|
+
describe "Query string parsing" do
|
46
|
+
context "the query string is of HTML-encoded form k=v&q=p" do
|
47
47
|
before do
|
48
|
-
@bluri = BLURI(
|
48
|
+
@bluri = BLURI("http://some.com/a/path?itemid=1&type=RESOURCE")
|
49
49
|
end
|
50
50
|
|
51
|
-
it
|
52
|
-
@bluri.query_hash[
|
51
|
+
it "indexes the query string" do
|
52
|
+
expect(@bluri.query_hash["itemid"]).to eq("1")
|
53
53
|
end
|
54
54
|
|
55
|
-
it
|
56
|
-
@bluri.query_hash[:itemid].
|
55
|
+
it "allows indexing by symbol" do
|
56
|
+
expect(@bluri.query_hash[:itemid]).to eq("1")
|
57
57
|
end
|
58
58
|
|
59
|
-
it
|
60
|
-
@bluri.query_hash[:eerie_flash].
|
59
|
+
it "shows nil for absent items" do
|
60
|
+
expect(@bluri.query_hash[:eerie_flash]).to eq(nil)
|
61
61
|
end
|
62
62
|
|
63
|
-
it
|
64
|
-
@bluri.query_hash[
|
63
|
+
it "indexes the second query string item" do
|
64
|
+
expect(@bluri.query_hash["type"]).to eq("resource")
|
65
65
|
end
|
66
66
|
|
67
|
-
it
|
68
|
-
@bluri.query =
|
69
|
-
@bluri.to_s.
|
67
|
+
it "allows setting of the query" do
|
68
|
+
@bluri.query = "furry=really"
|
69
|
+
expect(@bluri.to_s).to eq("http://some.com/a/path?furry=really")
|
70
70
|
end
|
71
71
|
end
|
72
72
|
|
73
|
-
context
|
73
|
+
context "the querystring is not an HTML-encoded thing" do
|
74
74
|
before do
|
75
|
-
@bluri = BLURI(
|
75
|
+
@bluri = BLURI("http://some.com/a/path?foo&bar")
|
76
76
|
end
|
77
77
|
|
78
|
-
it
|
79
|
-
@bluri.query.
|
78
|
+
it "retains the query string" do
|
79
|
+
expect(@bluri.query).to eq("foo&bar")
|
80
80
|
end
|
81
81
|
|
82
|
-
it
|
83
|
-
@bluri.query_hash[
|
84
|
-
@bluri.query_hash[
|
82
|
+
it "has a query hash with empty elements" do
|
83
|
+
expect(@bluri.query_hash["foo"]).to eq(nil)
|
84
|
+
expect(@bluri.query_hash["foo"]).to eq(nil)
|
85
85
|
end
|
86
86
|
end
|
87
87
|
end
|
88
|
-
end
|
88
|
+
end
|
data/spec/c14n_spec.rb
CHANGED
@@ -1,195 +1,209 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
-
require
|
3
|
+
require "spec_helper"
|
4
4
|
|
5
5
|
describe "Paul's tests, translated from Perl" do
|
6
|
-
it
|
7
|
-
BLURI(
|
6
|
+
it "lowercases URLs" do
|
7
|
+
expect(BLURI("http://www.EXAMPLE.COM/Foo/Bar/BAZ").canonicalize!.to_s).to eq("http://www.example.com/foo/bar/baz")
|
8
8
|
end
|
9
9
|
|
10
|
-
describe
|
11
|
-
it
|
12
|
-
BLURI(
|
10
|
+
describe "protocol" do
|
11
|
+
it "translates protocol to http", reason: "Reduces our input space, everything public anyway" do
|
12
|
+
expect(BLURI("https://www.example.com").canonicalize!.to_s).to eq("http://www.example.com")
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
16
|
-
describe
|
17
|
-
it
|
18
|
-
BLURI(
|
16
|
+
describe "slashes" do
|
17
|
+
it "drops single trailing slashes" do
|
18
|
+
expect(BLURI("http://www.example.com/").canonicalize!.to_s).to eq("http://www.example.com")
|
19
19
|
end
|
20
20
|
|
21
|
-
it
|
22
|
-
BLURI(
|
21
|
+
it "drops multiple trailing slashes" do
|
22
|
+
expect(BLURI("http://www.example.com////").canonicalize!.to_s).to eq("http://www.example.com")
|
23
23
|
end
|
24
24
|
|
25
|
-
it
|
26
|
-
BLURI(
|
25
|
+
it "drops multiple trailing slashes on the path" do
|
26
|
+
expect(BLURI("http://www.example.com/foo///").canonicalize!.to_s).to eq("http://www.example.com/foo")
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
|
-
describe
|
31
|
-
it
|
32
|
-
BLURI(
|
30
|
+
describe "fragments" do
|
31
|
+
it "drops fragment identifier", reason: "They won't be mapped, so are redundant" do
|
32
|
+
expect(BLURI("http://www.example.com#foo").canonicalize!.to_s).to eq("http://www.example.com")
|
33
33
|
end
|
34
|
-
it
|
35
|
-
BLURI(
|
34
|
+
it "drops fragment identifier and slashes" do
|
35
|
+
expect(BLURI("http://www.example.com/#foo").canonicalize!.to_s).to eq("http://www.example.com")
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
39
|
-
describe
|
40
|
-
it
|
41
|
-
BLURI(
|
39
|
+
describe "Things to keep verbatim or encode", reason: "http://tools.ietf.org/html/rfc3986" do
|
40
|
+
it "retains colons" do
|
41
|
+
expect(BLURI("http://www.example.com/:colon:").canonicalize!.to_s).to eq("http://www.example.com/:colon:")
|
42
42
|
end
|
43
|
-
it
|
44
|
-
BLURI(
|
43
|
+
it "retains tilde" do
|
44
|
+
expect(BLURI("http://www.example.com/~tilde").canonicalize!.to_s).to eq("http://www.example.com/~tilde")
|
45
45
|
end
|
46
|
-
it
|
47
|
-
BLURI(
|
46
|
+
it "retains underscores" do
|
47
|
+
expect(BLURI("http://www.example.com/_underscore_").canonicalize!.to_s).to eq("http://www.example.com/_underscore_")
|
48
48
|
end
|
49
|
-
it
|
50
|
-
BLURI(
|
49
|
+
it "retains asterisks" do
|
50
|
+
expect(BLURI("http://www.example.com/*asterisk*").canonicalize!.to_s).to eq("http://www.example.com/*asterisk*")
|
51
51
|
end
|
52
|
-
it
|
53
|
-
BLURI(
|
52
|
+
it "retains parens" do
|
53
|
+
expect(BLURI("http://www.example.com/(parens)").canonicalize!.to_s).to eq("http://www.example.com/(parens)")
|
54
54
|
end
|
55
|
-
it
|
56
|
-
BLURI(
|
55
|
+
it "escapes square brackets" do
|
56
|
+
expect(BLURI("http://www.example.com/[square-brackets]").canonicalize!.to_s).to eq("http://www.example.com/%5bsquare-brackets%5d")
|
57
57
|
end
|
58
|
-
it
|
59
|
-
BLURI("http://www.example.com/commas,and-\"quotes\"-make-CSV-harder-to-'awk'").canonicalize!.to_s.
|
60
|
-
|
58
|
+
it "encodes commas and quotes", reason: "They make csv harder to awk" do
|
59
|
+
expect(BLURI("http://www.example.com/commas,and-\"quotes\"-make-CSV-harder-to-'awk'").canonicalize!.to_s).to eq(
|
60
|
+
"http://www.example.com/commas%2cand-%22quotes%22-make-csv-harder-to-%27awk%27",
|
61
|
+
)
|
61
62
|
end
|
62
|
-
it
|
63
|
-
BLURI(
|
64
|
-
|
63
|
+
it "encodes square brackets and pipes", reason: "It's problematic in curl and regexes" do
|
64
|
+
expect(BLURI("http://www.example.com/problematic-in-curl[]||[and-regexes]").canonicalize!.to_s).to eq(
|
65
|
+
"http://www.example.com/problematic-in-curl%5b%5d%7c%7c%5band-regexes%5d",
|
66
|
+
)
|
65
67
|
end
|
66
|
-
it
|
68
|
+
it "decodes non-reserved characters (! and ~)" do
|
67
69
|
# My god, it's full of stars
|
68
|
-
BLURI(
|
69
|
-
canonicalize!.to_s.
|
70
|
+
expect(BLURI("http://www.example.com/%7eyes%20I%20have%20now%20read%20%5brfc%203986%5d%2C%20%26%20I%27m%20a%20%3Dlot%3D%20more%20reassured%21%21").
|
71
|
+
canonicalize!.to_s).to eq("http://www.example.com/~yes%20i%20have%20now%20read%20%5brfc%203986%5d%2c%20%26%20i%27m%20a%20%3dlot%3d%20more%20reassured!!")
|
70
72
|
end
|
71
|
-
it
|
72
|
-
BLURI(
|
73
|
+
it "encodes pound signs" do
|
74
|
+
expect(BLURI("https://www.example.com/pound-sign-£").canonicalize!.to_s).to eq("http://www.example.com/pound-sign-%c2%a3")
|
73
75
|
end
|
74
76
|
end
|
75
77
|
|
76
|
-
describe
|
77
|
-
it
|
78
|
-
BLURI(
|
78
|
+
describe "query strings" do
|
79
|
+
it "disallows all query string params by default" do
|
80
|
+
expect(BLURI("http://www.example.com?q=foo").canonicalize!.to_s).to eq("http://www.example.com")
|
79
81
|
end
|
80
|
-
it
|
81
|
-
BLURI(
|
82
|
+
it "disallows all params when there's a slash" do
|
83
|
+
expect(BLURI("http://www.example.com/?q=foo").canonicalize!.to_s).to eq("http://www.example.com")
|
82
84
|
end
|
83
|
-
it
|
84
|
-
BLURI(
|
85
|
+
it "disallows all params after a slash with fragid" do
|
86
|
+
expect(BLURI("http://www.example.com/?q=foo#bar").canonicalize!.to_s).to eq("http://www.example.com")
|
85
87
|
end
|
86
88
|
|
87
|
-
describe
|
88
|
-
it
|
89
|
-
BLURI(
|
90
|
-
|
89
|
+
describe "allowing some or all query string values" do
|
90
|
+
it "allows named query_string parameters" do
|
91
|
+
expect(BLURI("http://www.example.com/?q=foo&r=bar").canonicalize!(allow_query: "q").to_s).to eq(
|
92
|
+
"http://www.example.com?q=foo",
|
93
|
+
)
|
91
94
|
end
|
92
|
-
it
|
93
|
-
BLURI(
|
94
|
-
canonicalize!(allow_query: [
|
95
|
+
it "sorts query string values" do
|
96
|
+
expect(BLURI("http://www.example.com?c=23&d=1&b=909&e=33&a=1").
|
97
|
+
canonicalize!(allow_query: %i[b e c d a]).to_s).to eq("http://www.example.com?a=1&b=909&c=23&d=1&e=33")
|
95
98
|
end
|
96
|
-
it
|
97
|
-
BLURI("http://www.example.com?a=you're_dangerous").canonicalize!(allow_query: :all).to_s.
|
98
|
-
|
99
|
+
it "encodes querystring values" do
|
100
|
+
expect(BLURI("http://www.example.com?a=you're_dangerous").canonicalize!(allow_query: :all).to_s).to eq(
|
101
|
+
"http://www.example.com?a=you%27re_dangerous",
|
102
|
+
)
|
99
103
|
end
|
100
|
-
it
|
101
|
-
BLURI(
|
102
|
-
|
104
|
+
it "whitelists and sorts query strings" do
|
105
|
+
expect(BLURI("http://www.example.com?a=1&c=3&b=2").canonicalize!(allow_query: :all).to_s).to eq(
|
106
|
+
"http://www.example.com?a=1&b=2&c=3",
|
107
|
+
)
|
103
108
|
end
|
104
|
-
it
|
105
|
-
BLURI(
|
106
|
-
canonicalize!(allow_query: [
|
109
|
+
it "converts matrix URI to query_string" do
|
110
|
+
expect(BLURI("http://www.example.com?c=23;d=1;b=909;e=33;a=1").
|
111
|
+
canonicalize!(allow_query: %i[b e c d a]).to_s).to eq("http://www.example.com?a=1&b=909&c=23&d=1&e=33")
|
107
112
|
end
|
108
|
-
it
|
109
|
-
BLURI(
|
110
|
-
canonicalize!(allow_query: [
|
113
|
+
it "sorts cherry-picked query string arguments" do
|
114
|
+
expect(BLURI("http://www.example.com?a=2322sdfsf&topic=334499&q=909&item=23444").
|
115
|
+
canonicalize!(allow_query: %i[topic item]).to_s).to eq("http://www.example.com?item=23444&topic=334499")
|
111
116
|
end
|
112
|
-
it
|
113
|
-
BLURI(
|
114
|
-
canonicalize!(allow_query: %w(foo bar baz)).to_s.
|
117
|
+
it "ignores empty querystring values" do
|
118
|
+
expect(BLURI("http://www.example.com?a=2322sdfsf&topic=334499&q=909&item=23444").
|
119
|
+
canonicalize!(allow_query: %w(foo bar baz)).to_s).to eq("http://www.example.com")
|
115
120
|
end
|
116
121
|
|
117
|
-
describe
|
122
|
+
describe "querystrings that are not an HTML-encoded thing" do
|
118
123
|
before do
|
119
|
-
@bluri = BLURI(
|
124
|
+
@bluri = BLURI("http://some.com/a/path?foo&bar").canonicalize!(allow_query: :all)
|
120
125
|
end
|
121
126
|
|
122
|
-
it
|
123
|
-
@bluri.query.
|
127
|
+
it "retains the query string" do
|
128
|
+
expect(@bluri.query).to eq("bar&foo")
|
124
129
|
end
|
125
130
|
|
126
|
-
it
|
127
|
-
@bluri.query_hash[
|
128
|
-
@bluri.query_hash[
|
131
|
+
it "has a query hash with empty elements" do
|
132
|
+
expect(@bluri.query_hash["foo"]).to eq(nil)
|
133
|
+
expect(@bluri.query_hash["bar"]).to eq(nil)
|
129
134
|
end
|
130
135
|
|
131
|
-
it
|
132
|
-
@bluri.query_hash.to_s.
|
136
|
+
it "renders the string properly" do
|
137
|
+
expect(@bluri.query_hash.to_s).to eq("bar&foo")
|
133
138
|
end
|
134
139
|
end
|
135
140
|
|
136
|
-
describe
|
137
|
-
context
|
138
|
-
it
|
139
|
-
BLURI(
|
140
|
-
|
141
|
+
describe "casing of allowed query params" do
|
142
|
+
context "when the query param contains upper-case letters" do
|
143
|
+
it "does not preserve the query string, even when it appeared identically in the URL" do
|
144
|
+
expect(BLURI("http://www.example.com/?Foo=bar").canonicalize!(allow_query: "Foo").to_s).to eq(
|
145
|
+
"http://www.example.com",
|
146
|
+
)
|
141
147
|
end
|
142
148
|
end
|
143
149
|
|
144
|
-
context
|
145
|
-
it
|
146
|
-
BLURI(
|
147
|
-
|
150
|
+
context "when the query param is lower-cased" do
|
151
|
+
it "preserves the query string and lower-cases it" do
|
152
|
+
expect(BLURI("http://www.example.com/?Foo=bar").canonicalize!(allow_query: "foo").to_s).to eq(
|
153
|
+
"http://www.example.com?foo=bar",
|
154
|
+
)
|
148
155
|
end
|
149
156
|
end
|
150
157
|
end
|
151
158
|
|
152
|
-
describe
|
153
|
-
context
|
154
|
-
it
|
155
|
-
url =
|
159
|
+
describe "indifferent specfication of allowed query params" do
|
160
|
+
context "specifying the allowed query param using either a symbol or a string" do
|
161
|
+
it "should behave the same" do
|
162
|
+
url = "http://example.com/some?significant=1&query_params=2"
|
156
163
|
|
157
164
|
using_symbol = BLURI(url).canonicalize!(allow_query: :significant)
|
158
|
-
using_string = BLURI(url).canonicalize!(allow_query:
|
165
|
+
using_string = BLURI(url).canonicalize!(allow_query: "significant")
|
159
166
|
|
160
|
-
using_symbol.
|
167
|
+
expect(using_symbol).to eq(using_string)
|
161
168
|
end
|
162
169
|
end
|
163
170
|
end
|
164
171
|
end
|
165
172
|
|
166
|
-
describe
|
167
|
-
describe
|
168
|
-
|
169
|
-
it
|
170
|
-
BLURI(
|
173
|
+
describe "degenerate cases" do
|
174
|
+
describe "the treatment of query strings when there are query string octets that unescape to "\
|
175
|
+
"invalid UTF-8 sequences (we no longer treat these as failures)" do
|
176
|
+
it "no longer raises exceptions when there are bad things in query values" do
|
177
|
+
expect(BLURI("http://example.com/path?view=%ED").
|
171
178
|
canonicalize!(allow_query: :all).
|
172
|
-
to_s.
|
179
|
+
to_s).to eql("http://example.com/path?view=%ED")
|
173
180
|
end
|
174
181
|
|
175
|
-
it
|
176
|
-
BLURI(
|
182
|
+
it "re-encodes correctly when there are bad things in query keys" do
|
183
|
+
expect(BLURI("http://example.com/path?%ED=view").
|
177
184
|
canonicalize!(allow_query: :all).
|
178
|
-
to_s.
|
185
|
+
to_s).to eql("http://example.com/path?%ED=view")
|
179
186
|
end
|
180
187
|
|
181
|
-
it
|
182
|
-
expect { BLURI(
|
188
|
+
it "does not error when there are bad things in query keys when allow_query isn't :all" do
|
189
|
+
expect { BLURI("http://some.com/a/path?%E2").canonicalize! }.not_to raise_error
|
183
190
|
end
|
184
191
|
end
|
185
192
|
|
186
|
-
describe
|
193
|
+
describe "failure to canonicalize paths correctly" do
|
187
194
|
# see https://www.pivotaltracker.com/s/projects/860575/stories/54502932
|
188
195
|
|
189
|
-
subject { BLURI(
|
196
|
+
subject { BLURI("http://www.voa.gov.uk/stuff/?query=thing").canonicalize!(allow_query: :all) }
|
190
197
|
|
191
|
-
|
192
|
-
|
198
|
+
describe "#path" do
|
199
|
+
subject { super().path }
|
200
|
+
it { is_expected.to eql("/stuff") }
|
201
|
+
end
|
202
|
+
|
203
|
+
describe "#query" do
|
204
|
+
subject { super().query }
|
205
|
+
it { is_expected.to eql("query=thing") }
|
206
|
+
end
|
193
207
|
end
|
194
208
|
end
|
195
209
|
end
|
@@ -1,7 +1,7 @@
|
|
1
|
-
require
|
1
|
+
require "spec_helper"
|
2
2
|
|
3
3
|
describe Optic14n::CanonicalizedUrls do
|
4
|
-
describe
|
4
|
+
describe "c14nize" do
|
5
5
|
let(:test_urls) do
|
6
6
|
%w(
|
7
7
|
http://www.qhm.mod.uk/portsmouth/leisure/fuel
|
@@ -13,45 +13,54 @@ describe Optic14n::CanonicalizedUrls do
|
|
13
13
|
)
|
14
14
|
end
|
15
15
|
|
16
|
-
context
|
16
|
+
context "options[:allow_query] is false" do
|
17
17
|
subject(:c14nizer) { Optic14n::CanonicalizedUrls.from_urls(test_urls, allow_query: false) }
|
18
18
|
|
19
|
-
it {
|
19
|
+
it { is_expected.to be_a(Optic14n::CanonicalizedUrls) }
|
20
20
|
|
21
|
-
|
21
|
+
describe "#seen" do
|
22
|
+
subject { super().seen }
|
23
|
+
it { is_expected.to eql(6) }
|
24
|
+
end
|
22
25
|
|
23
|
-
describe
|
26
|
+
describe "the output set" do
|
24
27
|
subject(:output_set) { c14nizer.output_set }
|
25
28
|
|
26
|
-
|
29
|
+
describe "#size" do
|
30
|
+
subject { super().size }
|
31
|
+
it { is_expected.to eql(3) }
|
32
|
+
end
|
27
33
|
|
28
|
-
describe
|
34
|
+
describe "the items" do
|
29
35
|
subject { output_set.map(&:to_s) }
|
30
36
|
|
31
|
-
it {
|
32
|
-
it {
|
33
|
-
it {
|
37
|
+
it { is_expected.to include("http://www.qhm.mod.uk/portsmouth/leisure/fuel") }
|
38
|
+
it { is_expected.to include("http://www.qhm.mod.uk/portsmouth/leisure/lntm") }
|
39
|
+
it { is_expected.to include("http://unistats.direct.gov.uk/searchresults.do") }
|
34
40
|
end
|
35
41
|
end
|
36
42
|
end
|
37
43
|
|
38
|
-
context
|
44
|
+
context "options[:allow_query] is :all" do
|
39
45
|
subject(:c14nizer) { Optic14n::CanonicalizedUrls.from_urls(test_urls, allow_query: :all) }
|
40
46
|
|
41
|
-
describe
|
47
|
+
describe "the output set" do
|
42
48
|
subject(:output_set) { c14nizer.output_set }
|
43
49
|
|
44
|
-
|
50
|
+
describe "#size" do
|
51
|
+
subject { super().size }
|
52
|
+
it { is_expected.to eql(5) }
|
53
|
+
end
|
45
54
|
end
|
46
55
|
|
47
|
-
describe
|
56
|
+
describe "failures" do
|
48
57
|
subject(:failures) { c14nizer.failures }
|
49
58
|
|
50
|
-
it {
|
59
|
+
it { is_expected.to be_a(Hash) }
|
51
60
|
|
52
|
-
it
|
61
|
+
it "has our last URL and an error" do
|
53
62
|
e = failures[test_urls.last]
|
54
|
-
e.
|
63
|
+
expect(e).to be_an(Addressable::URI::InvalidURIError)
|
55
64
|
end
|
56
65
|
end
|
57
66
|
end
|
data/spec/query_hash_spec.rb
CHANGED
@@ -1,15 +1,29 @@
|
|
1
|
-
require
|
1
|
+
require "spec_helper"
|
2
2
|
|
3
3
|
describe URI::QueryHash do
|
4
4
|
subject(:hash) { {}.extend URI::QueryHash }
|
5
5
|
|
6
|
-
|
6
|
+
describe "#to_s" do
|
7
|
+
subject { super().to_s }
|
8
|
+
it { is_expected.to eql("") }
|
9
|
+
end
|
10
|
+
|
11
|
+
describe "setting a value by symbol" do
|
12
|
+
before { hash["x"] = "1" }
|
13
|
+
|
14
|
+
describe "[:x]" do
|
15
|
+
subject { super()[:x] }
|
16
|
+
it { is_expected.to eql("1") }
|
17
|
+
end
|
7
18
|
|
8
|
-
|
9
|
-
|
19
|
+
describe "['x']" do
|
20
|
+
subject { super()["x"] }
|
21
|
+
it { is_expected.to eql("1") }
|
22
|
+
end
|
10
23
|
|
11
|
-
|
12
|
-
|
13
|
-
|
24
|
+
describe "#to_s" do
|
25
|
+
subject { super().to_s }
|
26
|
+
it { is_expected.to eql("x=1") }
|
27
|
+
end
|
14
28
|
end
|
15
|
-
end
|
29
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1 +1 @@
|
|
1
|
-
require
|
1
|
+
require "optic14n"
|
data/spec/uri/query_hash_spec.rb
CHANGED
@@ -1,11 +1,22 @@
|
|
1
|
-
require
|
1
|
+
require "spec_helper"
|
2
2
|
|
3
3
|
describe URI::QueryHash do
|
4
|
-
describe
|
5
|
-
subject { {
|
4
|
+
describe "non-HTML encoded query strings" do
|
5
|
+
subject { { "foo" => nil, "bar" => nil }.extend URI::QueryHash }
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
describe "['foo']" do
|
8
|
+
subject { super()["foo"] }
|
9
|
+
it { is_expected.to be_nil }
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "['bar']" do
|
13
|
+
subject { super()["bar"] }
|
14
|
+
it { is_expected.to be_nil }
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "#to_s" do
|
18
|
+
subject { super().to_s }
|
19
|
+
it { is_expected.to eql("foo&bar") }
|
20
|
+
end
|
10
21
|
end
|
11
|
-
end
|
22
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: optic14n
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0
|
4
|
+
version: 2.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
-
|
7
|
+
- GOV.UK Dev
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-02-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: addressable
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '2.
|
19
|
+
version: '2.7'
|
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
|
-
version: '2.
|
26
|
+
version: '2.7'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -39,33 +39,48 @@ dependencies:
|
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rubocop-govuk
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
44
58
|
requirements:
|
45
59
|
- - "~>"
|
46
60
|
- !ruby/object:Gem::Version
|
47
|
-
version: 1.
|
61
|
+
version: 1.0.0
|
48
62
|
type: :development
|
49
63
|
prerelease: false
|
50
64
|
version_requirements: !ruby/object:Gem::Requirement
|
51
65
|
requirements:
|
52
66
|
- - "~>"
|
53
67
|
- !ruby/object:Gem::Version
|
54
|
-
version: 1.
|
68
|
+
version: 1.0.0
|
55
69
|
description: Canonicalises URLs.
|
56
70
|
email:
|
57
|
-
-
|
71
|
+
- govuk-dev@digital.cabinet-office.gov.uk
|
58
72
|
executables: []
|
59
73
|
extensions: []
|
60
74
|
extra_rdoc_files: []
|
61
75
|
files:
|
62
76
|
- ".gitignore"
|
77
|
+
- ".rubocop.yml"
|
63
78
|
- ".ruby-version"
|
64
79
|
- Gemfile
|
80
|
+
- Jenkinsfile
|
65
81
|
- LICENSE.txt
|
66
82
|
- README.md
|
67
83
|
- Rakefile
|
68
|
-
- jenkins.sh
|
69
84
|
- lib/optic14n.rb
|
70
85
|
- lib/optic14n/canonicalized_urls.rb
|
71
86
|
- lib/optic14n/version.rb
|
@@ -80,7 +95,7 @@ files:
|
|
80
95
|
- spec/query_hash_spec.rb
|
81
96
|
- spec/spec_helper.rb
|
82
97
|
- spec/uri/query_hash_spec.rb
|
83
|
-
homepage:
|
98
|
+
homepage: https://github.com/alphagov/optic14n
|
84
99
|
licenses:
|
85
100
|
- MIT
|
86
101
|
metadata: {}
|
@@ -99,8 +114,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
99
114
|
- !ruby/object:Gem::Version
|
100
115
|
version: '0'
|
101
116
|
requirements: []
|
102
|
-
|
103
|
-
rubygems_version: 2.5.1
|
117
|
+
rubygems_version: 3.1.4
|
104
118
|
signing_key:
|
105
119
|
specification_version: 4
|
106
120
|
summary: Specifically, HTTP URLs, for a limited purpose
|