optic14n 2.0.1 → 2.1.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 +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
|