gemirro 3.0.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f4433d72ad5aef6af085761295239943b68ca7afbf8decf4a6e4fd1724eae1d3
4
- data.tar.gz: 73492105d34fa6b3dbd0da1f0158e9a9c6ac95915e2b714be1c107f375473264
3
+ metadata.gz: bb35f2d2c69224e8bebd764161f0c855af924b03ab98eb0089473d11489a6ec9
4
+ data.tar.gz: 4fb54e6dcce8923151dd273f4e5b45f8f64e90a6ad28cc8c06d8ed3c10085893
5
5
  SHA512:
6
- metadata.gz: 1c5ab9469369f159fe071f0f9eb8215971018f5880cbbf4236333c30675a460f8b5936f136d2becc15203b07f8065d9f2c6309d3c58f5807e7be7555e7804314
7
- data.tar.gz: bff9e9a7c93475fcfa4a8a341bc57823e3267b759c3602905449af813a4d239fc29f9bec3d12976b2e08e1bfb79daa27f5d2fe442c08e09c41547e2ba81d1415
6
+ metadata.gz: e5bb871e3788098502d5f76a6b6b568910c511392d21c53b13b6e069e2c1fc85329bea413d6ae6c02efd55a0a0d3a6c5d432d4dccfb2487c1b607136188286f2
7
+ data.tar.gz: bc363cf380cb471dddac0c68717112d3c15169175c2aba17503e46e566bc9a170e49ebf810a3c3e9c761acee1e65dcd4c9379fb833aeabc0b4ade2a3bb7749b1
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- gemirro (3.0.0)
4
+ gemirro (3.0.1)
5
5
  addressable (~> 2.8)
6
6
  builder (~> 3.2)
7
7
  compact_index (~> 0.15)
data/MANIFEST CHANGED
@@ -45,6 +45,7 @@ spec/gemirro/mirror_directory_spec.rb
45
45
  spec/gemirro/mirror_file_spec.rb
46
46
  spec/gemirro/server_spec.rb
47
47
  spec/gemirro/source_spec.rb
48
+ spec/gemirro/utils_spec.rb
48
49
  spec/gemirro/versions_fetcher_spec.rb
49
50
  spec/gemirro/versions_file_spec.rb
50
51
  spec/spec_helper.rb
data/lib/gemirro/utils.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'addressable/uri'
3
4
  require 'gemirro/gem_version'
4
5
 
5
6
  module Gemirro
@@ -25,6 +26,7 @@ module Gemirro
25
26
  URI_REGEXP = /^(.*)-(\d+(?:\.\d+){1,4}.*?)(?:-(x86-(?:(?:mswin|mingw)(?:32|64)).*?|java))?\.(gem(?:spec\.rz)?)$/
26
27
  GEMSPEC_TYPE = 'gemspec.rz'
27
28
  GEM_TYPE = 'gem'
29
+ SAFE_HOMEPAGE_SCHEMES = %w[http https].freeze
28
30
 
29
31
  ##
30
32
  # Generate Gems collection from Marshal dump - always the .local file
@@ -156,6 +158,31 @@ module Gemirro
156
158
  end
157
159
  end
158
160
 
161
+ ##
162
+ # Return a normalized homepage URL that is safe to use as a link target,
163
+ # or nil when it is missing, unparsable or not an http(s) URL with a host.
164
+ # Gem metadata comes from upstream sources and must not be trusted.
165
+ #
166
+ # @param [String] homepage
167
+ # @return [String, nil]
168
+ #
169
+ def self.safe_homepage(homepage)
170
+ return if homepage.nil?
171
+
172
+ # Browsers ignore control characters and whitespace inside a scheme
173
+ # (e.g. "java\tscript:"), so drop them before validating.
174
+ value = homepage.to_s.gsub(/[[:cntrl:][:space:]]/, '')
175
+ return if value.empty?
176
+
177
+ uri = Addressable::URI.parse(value).normalize
178
+ return unless SAFE_HOMEPAGE_SCHEMES.include?(uri.scheme&.downcase)
179
+ return if uri.host.nil? || uri.host.empty?
180
+
181
+ uri.to_s
182
+ rescue Addressable::URI::InvalidURIError, ArgumentError, TypeError
183
+ nil
184
+ end
185
+
159
186
  ##
160
187
  # Try to fetch gem and download its if it's possible, and
161
188
  # build and install indicies.
@@ -2,5 +2,5 @@
2
2
 
3
3
  # Gemirro Version
4
4
  module Gemirro
5
- VERSION = '3.0.0'
5
+ VERSION = '3.0.1'
6
6
  end
@@ -81,6 +81,39 @@ module Gemirro
81
81
  expect(last_response.status).to eq(200)
82
82
  expect(last_response).to be_ok
83
83
  end
84
+
85
+ it 'should not link a javascript: homepage from gem metadata' do
86
+ MirrorFile.new('/var/www/gemirro/versions.md5.aaa256.list').write(%(created_at: 2025-01-01T00:00:00Z\n---\nvolay 0.1.0 checksum))
87
+ spec = ::Gem::Specification.new do |s|
88
+ s.name = 'volay'
89
+ s.version = '0.1.0'
90
+ s.authors = ['Mallory']
91
+ s.homepage = 'javascript:alert(1)'
92
+ end
93
+ allow(Utils).to receive(:spec_for).and_return(spec)
94
+
95
+ get '/gem/volay'
96
+
97
+ expect(last_response).to be_ok
98
+ expect(last_response.body).to include('Mallory')
99
+ expect(last_response.body).not_to include('javascript:')
100
+ end
101
+
102
+ it 'should link an https homepage from gem metadata' do
103
+ MirrorFile.new('/var/www/gemirro/versions.md5.aaa256.list').write(%(created_at: 2025-01-01T00:00:00Z\n---\nvolay 0.1.0 checksum))
104
+ spec = ::Gem::Specification.new do |s|
105
+ s.name = 'volay'
106
+ s.version = '0.1.0'
107
+ s.authors = ['Alice']
108
+ s.homepage = 'https://example.com/volay'
109
+ end
110
+ allow(Utils).to receive(:spec_for).and_return(spec)
111
+
112
+ get '/gem/volay'
113
+
114
+ expect(last_response).to be_ok
115
+ expect(last_response.body).to include('href="https://example.com/volay"')
116
+ end
84
117
 
85
118
  it 'responds to compact_index /names' do
86
119
  MirrorFile.new('/var/www/gemirro/names.md5.aaa256.list').write(%(---\nvolay))
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'gemirro/utils'
5
+
6
+ # Utils tests
7
+ module Gemirro
8
+ describe Utils do
9
+ describe '.safe_homepage' do
10
+ it 'keeps http and https URLs' do
11
+ expect(Utils.safe_homepage('http://example.com')).to eq('http://example.com/')
12
+ expect(Utils.safe_homepage('https://example.com/foo?a=1')).to eq('https://example.com/foo?a=1')
13
+ expect(Utils.safe_homepage('HTTPS://Example.com/x')).to eq('https://example.com/x')
14
+ end
15
+
16
+ it 'rejects missing or empty values' do
17
+ expect(Utils.safe_homepage(nil)).to be_nil
18
+ expect(Utils.safe_homepage('')).to be_nil
19
+ expect(Utils.safe_homepage(" \n")).to be_nil
20
+ end
21
+
22
+ [
23
+ 'javascript:alert(1)',
24
+ 'JavaScript:alert(1)',
25
+ ' javascript:alert(1)',
26
+ "java\tscript:alert(1)",
27
+ "java\nscript:alert(1)",
28
+ "\u0000javascript:alert(1)",
29
+ 'data:text/html,<script>alert(1)</script>',
30
+ 'vbscript:msgbox(1)',
31
+ 'file:///etc/passwd',
32
+ 'javascript%3Aalert(1)',
33
+ '//evil.example.com',
34
+ '/relative/path',
35
+ 'https://'
36
+ ].each do |value|
37
+ it "rejects #{value.inspect}" do
38
+ expect(Utils.safe_homepage(value)).to be_nil
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
data/views/gem.erb CHANGED
@@ -34,12 +34,17 @@
34
34
  <% end %>
35
35
 
36
36
  <% if spec.authors.size > 0 %>
37
+ <% homepage = Gemirro::Utils.safe_homepage(spec.homepage) %>
37
38
  <dd class="authors">
38
39
  <h3>Authors</h3>
39
40
  <ul>
40
41
  <% spec.authors.each do |author| %>
41
42
  <li class="list-group-item">
42
- <a href="<%= URI.parse(Addressable::URI.escape(spec.homepage)) %>"><%= Rack::Utils.escape_html(author) %></a>
43
+ <% if homepage %>
44
+ <a href="<%= Rack::Utils.escape_html(homepage) %>" rel="noopener noreferrer nofollow"><%= Rack::Utils.escape_html(author) %></a>
45
+ <% else %>
46
+ <%= Rack::Utils.escape_html(author) %>
47
+ <% end %>
43
48
  </li>
44
49
  <% end %>
45
50
  </ul>
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gemirro
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.0
4
+ version: 3.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pierre Rambaud
@@ -245,6 +245,7 @@ files:
245
245
  - spec/gemirro/mirror_file_spec.rb
246
246
  - spec/gemirro/server_spec.rb
247
247
  - spec/gemirro/source_spec.rb
248
+ - spec/gemirro/utils_spec.rb
248
249
  - spec/gemirro/versions_fetcher_spec.rb
249
250
  - spec/gemirro/versions_file_spec.rb
250
251
  - spec/spec_helper.rb