open_graph_fetcher 0.1.0 → 0.3.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 +4 -4
- data/README.md +3 -2
- data/examples/fetch.rb +6 -0
- data/lib/open_graph_fetcher/errors.rb +2 -0
- data/lib/open_graph_fetcher/fetcher.rb +25 -7
- data/lib/open_graph_fetcher/version.rb +1 -1
- data/spec/fetcher_spec.rb +33 -5
- metadata +9 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 48a6853f8f4665764d603a012713c1e8a5cd44c9ad649efc7626b2e210bf0931
|
4
|
+
data.tar.gz: 53ef6ecb11ab043f4d905a659a834254169721d6018b3a7428f535ceaad635fe
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
@@ -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 =
|
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"
|
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.
|
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.
|
52
|
-
|
53
|
-
|
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
|
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(:
|
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(:
|
85
|
+
allow(Resolv::DNS).to receive(:open).and_raise(Resolv::ResolvError, "DNS resolution failed")
|
58
86
|
|
59
|
-
expect { OpenGraphFetcher::Fetcher.fetch("https://nonexistent
|
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(:
|
93
|
+
allow(Resolv::DNS).to receive(:open).and_return("10.0.0.1")
|
66
94
|
|
67
|
-
expect { OpenGraphFetcher::Fetcher.fetch("https://
|
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.
|
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-
|
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.
|
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: []
|