open_graph_fetcher 0.1.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 46670fff3dd827c35482bd63b1c246aa876fd85f82b3fdff752144e4f07e537b
4
- data.tar.gz: a260be55faeb2038d4aeacfa9d419a1b7301ec023d327eb51fc2d5a1992ff260
3
+ metadata.gz: 48a6853f8f4665764d603a012713c1e8a5cd44c9ad649efc7626b2e210bf0931
4
+ data.tar.gz: 53ef6ecb11ab043f4d905a659a834254169721d6018b3a7428f535ceaad635fe
5
5
  SHA512:
6
- metadata.gz: f23ee76f381d04b9fcc1bb60eb2f1baf4a438647009987a072f7a23334008a18614f11bb9a9cf74065e5deb695f65a3d8bed786ea2e6f32137c61a4c14a4ae06
7
- data.tar.gz: 005b86f83da1f16ce55998bcefc1bceb93c0e0137bccab08e72ad1da2e749077fe2a0f5e85cd0bbf6abc005181cb16c2abab01adc1e2fa623344146743d198a7
6
+ metadata.gz: aa81fcbfff70cb71a13c599ceaf4be687e97e38d2889424290fd0b6c7487c967575fae28864d47cc46988d164ed18894df257c2a6838a2c55e7d40d7b07c3318
7
+ data.tar.gz: 75e7edea3e478392eccfd6fdde4f25e8b33d711d539746f09dd63543a15faaaae1dd4fbb621348e3cd7ec4ad7d1a5042dd6b7a88cda35e118203e19adeb25460
data/README.md CHANGED
@@ -3,10 +3,11 @@
3
3
  Fetch Open Graph metadata in a safer way.
4
4
 
5
5
  - Includes some mitigations for SSRF attacks
6
- - Blocks private and local IP ranges
6
+ - Blocks the direct usage of IP addresses in URLs
7
+ - Blocks private and local IP ranges (after DNS resolution)
7
8
  - Avoids TOC/TOU when connecting to the IP
8
9
  - Supports only HTTPS on the standard port (443)
9
- - Includes request timeouts
10
+ - Includes request timeouts (for DNS and HTTP)
10
11
  - Avoids redirects
11
12
  - Allows only text/html responses
12
13
  - Returns only known OG properties and nothing else
data/examples/fetch.rb ADDED
@@ -0,0 +1,6 @@
1
+ require 'open_graph_fetcher'
2
+
3
+ url = "https://ogp.me"
4
+ fetcher = OpenGraphFetcher::Fetcher.new(url)
5
+ og_data = fetcher.fetch
6
+ puts og_data
@@ -1,8 +1,10 @@
1
1
  module OpenGraphFetcher
2
2
  class Error < StandardError; end
3
3
 
4
+ class InvalidURIError < Error; end
4
5
  class InvalidSchemeError < Error; end
5
6
  class InvalidPortError < Error; end
7
+ class InvalidHostError < Error; end
6
8
  class IPResolutionError < Error; end
7
9
  class PrivateIPError < Error; end
8
10
  class FetchError < Error; end
@@ -5,8 +5,9 @@ require 'ipaddr'
5
5
 
6
6
  module OpenGraphFetcher
7
7
  class Fetcher
8
- OG_PROPERTIES = %w[title type image url description].freeze
8
+ OG_PROPERTIES = %w[title type image url description site_name].freeze
9
9
 
10
+ DNS_TIMEOUT = 3
10
11
  OPEN_TIMEOUT = 3
11
12
  READ_TIMEOUT = 3
12
13
 
@@ -19,9 +20,10 @@ module OpenGraphFetcher
19
20
  end
20
21
 
21
22
  def fetch
22
- uri = URI.parse(@url)
23
+ uri = parse_uri(@url)
23
24
  raise InvalidSchemeError, "Only HTTPS URLs are allowed" unless uri.scheme == "https"
24
- raise InvalidPortError, "Only the default HTTPS port (443) is allowed" if uri.port && uri.port != 443
25
+ raise InvalidPortError, "Only the default HTTPS port (443) is allowed" unless uri.port == 443
26
+ raise InvalidHostError, "Using an IP as host is not allowed" if ip_address?(uri.hostname)
25
27
 
26
28
  ip_address = resolve_ip(uri)
27
29
  raise PrivateIPError, "Resolved IP address is in a private or reserved range" if private_ip?(ip_address)
@@ -34,12 +36,25 @@ module OpenGraphFetcher
34
36
  end
35
37
 
36
38
  private
39
+
40
+ def parse_uri(url)
41
+ URI.parse(url)
42
+ rescue URI::InvalidURIError => e
43
+ raise InvalidURIError, "Could not parse URI: #{e.message}"
44
+ end
37
45
 
38
46
  def resolve_ip(uri)
39
- Resolv.getaddress(uri.host)
47
+ Resolv::DNS.open do |dns|
48
+ dns.timeouts = DNS_TIMEOUT
49
+ dns.getaddress(uri.hostname).to_s
50
+ end
40
51
  rescue Resolv::ResolvError => e
41
52
  raise IPResolutionError, "Could not resolve IP: #{e.message}"
42
53
  end
54
+
55
+ def ip_address?(host)
56
+ host =~ Resolv::IPv4::Regex || host =~ Resolv::IPv6::Regex
57
+ end
43
58
 
44
59
  def private_ip?(ip)
45
60
  ip_addr = IPAddr.new(ip)
@@ -48,9 +63,12 @@ module OpenGraphFetcher
48
63
 
49
64
  def fetch_data(uri, ip)
50
65
  request = Net::HTTP::Get.new(uri.request_uri)
51
- Net::HTTP.start(uri.hostname, uri.port, ipaddr: ip, use_ssl: true, open_timeout: OPEN_TIMEOUT, read_timeout: READ_TIMEOUT) do |http|
52
- http.request(request)
53
- end
66
+ http = Net::HTTP.new(uri.hostname, 443)
67
+ http.ipaddr = ip
68
+ http.use_ssl = true
69
+ http.open_timeout = OPEN_TIMEOUT
70
+ http.read_timeout = READ_TIMEOUT
71
+ http.request(request)
54
72
  rescue Net::OpenTimeout, Net::ReadTimeout => e
55
73
  raise FetchError, "Request timed out: #{e.message}"
56
74
  rescue StandardError => e
@@ -1,3 +1,3 @@
1
1
  module OpenGraphFetcher
2
- VERSION = '0.1.0'.freeze
2
+ VERSION = '0.3.0'.freeze
3
3
  end
data/spec/fetcher_spec.rb CHANGED
@@ -4,7 +4,7 @@ require 'webmock/rspec'
4
4
  RSpec.describe OpenGraphFetcher::Fetcher do
5
5
 
6
6
  before do
7
- allow(Resolv).to receive(:getaddress).and_return("203.0.113.0")
7
+ allow(Resolv::DNS).to receive(:open).and_return("203.0.113.0")
8
8
  end
9
9
 
10
10
  describe ".fetch" do
@@ -38,6 +38,28 @@ RSpec.describe OpenGraphFetcher::Fetcher do
38
38
  "description" => "Example description"
39
39
  })
40
40
  end
41
+
42
+ it 'sets the ipaddr and other options on the Net::HTTP instance' do
43
+ http_instance = instance_double(Net::HTTP)
44
+ expect(Net::HTTP).to receive(:new).and_return(http_instance)
45
+
46
+ expect(http_instance).to receive(:ipaddr=).with("203.0.113.0")
47
+ expect(http_instance).to receive(:use_ssl=).with(true)
48
+ expect(http_instance).to receive(:open_timeout=).with(3)
49
+ expect(http_instance).to receive(:read_timeout=).with(3)
50
+
51
+ response_double = double("Net::HTTPResponse", code: "200", body: "<!DOCTYPE html><title>OK</title>")
52
+ expect(response_double).to receive(:[]).with("Content-Type").and_return("text/html")
53
+ expect(http_instance).to receive(:request).and_return(response_double)
54
+
55
+ OpenGraphFetcher::Fetcher.fetch("https://example.com")
56
+ end
57
+ end
58
+
59
+ context "when given an invalid URL" do
60
+ it "raises an InvalidURIError" do
61
+ expect { OpenGraphFetcher::Fetcher.fetch("# test") }.to raise_error(OpenGraphFetcher::InvalidURIError)
62
+ end
41
63
  end
42
64
 
43
65
  context "when given an HTTP URL" do
@@ -51,20 +73,26 @@ RSpec.describe OpenGraphFetcher::Fetcher do
51
73
  expect { OpenGraphFetcher::Fetcher.fetch("https://example.com:8443") }.to raise_error(OpenGraphFetcher::InvalidPortError)
52
74
  end
53
75
  end
76
+
77
+ context "when given a URL with an IP address" do
78
+ it "raises an InvalidHostError" do
79
+ expect { OpenGraphFetcher::Fetcher.fetch("https://203.0.113.0/test") }.to raise_error(OpenGraphFetcher::InvalidHostError)
80
+ end
81
+ end
54
82
 
55
83
  context "when the IP address cannot be resolved" do
56
84
  it "raises an IPResolutionError with an appropriate error message" do
57
- allow(Resolv).to receive(:getaddress).and_raise(Resolv::ResolvError, "DNS resolution failed")
85
+ allow(Resolv::DNS).to receive(:open).and_raise(Resolv::ResolvError, "DNS resolution failed")
58
86
 
59
- expect { OpenGraphFetcher::Fetcher.fetch("https://nonexistent-domain.com") }.to raise_error(OpenGraphFetcher::IPResolutionError, /Could not resolve IP: DNS resolution failed/)
87
+ expect { OpenGraphFetcher::Fetcher.fetch("https://nonexistent.example.com") }.to raise_error(OpenGraphFetcher::IPResolutionError, /Could not resolve IP: DNS resolution failed/)
60
88
  end
61
89
  end
62
90
 
63
91
  context "when the resolved IP address is private" do
64
92
  it "raises a PrivateIPError" do
65
- allow(Resolv).to receive(:getaddress).with("10.0.0.1").and_return("10.0.0.1")
93
+ allow(Resolv::DNS).to receive(:open).and_return("10.0.0.1")
66
94
 
67
- expect { OpenGraphFetcher::Fetcher.fetch("https://10.0.0.1") }.to raise_error(OpenGraphFetcher::PrivateIPError)
95
+ expect { OpenGraphFetcher::Fetcher.fetch("https://ssrf.example.com") }.to raise_error(OpenGraphFetcher::PrivateIPError)
68
96
  end
69
97
  end
70
98
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: open_graph_fetcher
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marco Colli
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-11-04 00:00:00.000000000 Z
11
+ date: 2024-11-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri
@@ -52,8 +52,8 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
- description:
56
- email:
55
+ description:
56
+ email:
57
57
  executables: []
58
58
  extensions: []
59
59
  extra_rdoc_files: []
@@ -63,6 +63,7 @@ files:
63
63
  - Gemfile
64
64
  - LICENSE
65
65
  - README.md
66
+ - examples/fetch.rb
66
67
  - lib/open_graph_fetcher.rb
67
68
  - lib/open_graph_fetcher/errors.rb
68
69
  - lib/open_graph_fetcher/fetcher.rb
@@ -73,7 +74,7 @@ homepage: https://github.com/collimarco/open_graph_fetcher
73
74
  licenses:
74
75
  - MIT
75
76
  metadata: {}
76
- post_install_message:
77
+ post_install_message:
77
78
  rdoc_options: []
78
79
  require_paths:
79
80
  - lib
@@ -88,8 +89,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
88
89
  - !ruby/object:Gem::Version
89
90
  version: '0'
90
91
  requirements: []
91
- rubygems_version: 3.5.16
92
- signing_key:
92
+ rubygems_version: 3.0.3.1
93
+ signing_key:
93
94
  specification_version: 4
94
95
  summary: Fetch Open Graph metadata in a safer way.
95
96
  test_files: []