indieweb-endpoints 0.4.0 → 0.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1788ffc5ea1dd7ec1a6d4c1839e19c4b208b38044a05aef7d618bad2485b7638
4
- data.tar.gz: 4458a1d611d045035916ec9fc6f69c752f628a6c65776fbfdc0fdfc4bb823c83
3
+ metadata.gz: 9cb1b27367683210b63651d8195e6d12013f994be97d8c216b2e2165429c4adb
4
+ data.tar.gz: 587e29ea23c02d6de575cefbc6a5f9a808c5e6b47bf662ee138ceaadba03c57f
5
5
  SHA512:
6
- metadata.gz: cd530923575c34d392253a090fb9458c0709f6049ba055e9665879c067cc9571814b6079806773b3080c1b63def074b9331d8e9415855c91987acc56679090bb
7
- data.tar.gz: 4f2836abdae4e424d2981b654d1d9a8b07cf84a970ee47571dca2235f85905c788136cc63ecde32d3ffbcc632e6f77e96d90aa6f3fc2c4f4bcb1d556d07ad587
6
+ metadata.gz: 1ca06c752aa3ff624d1ab3dbcad733d69bd5ab701ccdac754e7f5bc4e4d7c70ee8b42b23dd911448fb85fab44264e2e7f3c9c136cb23f5b4aaad282a21555ec7
7
+ data.tar.gz: adbb98ffc16e4664d8ea47a94bdfb32b5fbd552155716a1928bf3b62fbdd4bc545768d00ce046101db454b8a71752acdb14330c99e57c1597eca73b8b0e71b93
data/.rubocop.yml CHANGED
@@ -7,6 +7,7 @@ Layout/AlignHash:
7
7
 
8
8
  Metrics/BlockLength:
9
9
  Exclude:
10
+ - indieweb-endpoints.gemspec
10
11
  - spec/**/*.rb
11
12
 
12
13
  Metrics/LineLength:
data/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.0 / 2019-05-09
4
+
5
+ - Add support for Microsub endpoint discovery ([5e81d9f](https://github.com/indieweb/indieweb-endpoints-ruby/commit/5e81d9f)).
6
+ - Refactor parsers to ignore URLs with fragments ([797b376](https://github.com/indieweb/indieweb-endpoints-ruby/commit/797b376)).
7
+ - Rescue `NoMethodError` (for `nil`) and `TypeError` (for non-`String`) ([e33522e](https://github.com/indieweb/indieweb-endpoints-ruby/commit/e33522e)).
8
+ - Raise `ArgumentError` if url scheme is not `http` or `https` ([8eb1b1a](https://github.com/indieweb/indieweb-endpoints-ruby/commit/8eb1b1a)).
9
+ - Shorten up User Agent string ([f9717b4](https://github.com/indieweb/indieweb-endpoints-ruby/commit/f9717b4)).
10
+ - Refactor `HTTPRequest` class using specification defaults ([feef2ba](https://github.com/indieweb/indieweb-endpoints-ruby/commit/feef2ba)).
11
+
3
12
  ## 0.4.0 / 2019-05-01
4
13
 
5
14
  - Add `IndieWeb::Endpoints.client` method ([c4d42d0](https://github.com/indieweb/indieweb-endpoints-ruby/commit/c4d42d0)).
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # indieweb-endpoints-ruby
2
2
 
3
- **A Ruby gem for discovering a URL's [IndieAuth](https://indieweb.org/IndieAuth), [Micropub](https://indieweb.org/Micropub), and [Webmention](https://indieweb.org/Webmention) endpoints.**
3
+ **A Ruby gem for discovering a URL's [IndieAuth](https://indieweb.org/IndieAuth), [Micropub](https://indieweb.org/Micropub), [Microsub](https://indieweb.org/Microsub), and [Webmention](https://indieweb.org/Webmention) endpoints.**
4
4
 
5
5
  [![Gem](https://img.shields.io/gem/v/indieweb-endpoints.svg?style=for-the-badge)](https://rubygems.org/gems/indieweb-endpoints)
6
6
  [![Downloads](https://img.shields.io/gem/dt/indieweb-endpoints.svg?style=for-the-badge)](https://rubygems.org/gems/indieweb-endpoints)
@@ -56,6 +56,7 @@ This example will search `https://aaronparecki.com` for valid IndieAuth, Micropu
56
56
  {
57
57
  authorization_endpoint: 'https://aaronparecki.com/auth',
58
58
  micropub: 'https://aaronparecki.com/micropub',
59
+ microsub: 'https://aperture.p3k.io/microsub/1',
59
60
  redirect_uri: nil,
60
61
  token_endpoint: 'https://aaronparecki.com/auth/token',
61
62
  webmention: 'https://webmention.io/aaronpk/webmention'
@@ -3,7 +3,6 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
3
 
4
4
  require 'indieweb/endpoints/version'
5
5
 
6
- # rubocop:disable Metrics/BlockLength
7
6
  Gem::Specification.new do |spec|
8
7
  spec.required_ruby_version = ['>= 2.4', '< 2.7']
9
8
 
@@ -12,7 +11,7 @@ Gem::Specification.new do |spec|
12
11
  spec.authors = ['Jason Garber']
13
12
  spec.email = ['jason@sixtwothree.org']
14
13
 
15
- spec.summary = 'Discover a URL’s IndieAuth, Micropub, and Webmention endpoints.'
14
+ spec.summary = 'Discover a URL’s IndieAuth, Micropub, Microsub, and Webmention endpoints.'
16
15
  spec.description = spec.summary
17
16
  spec.homepage = 'https://github.com/indieweb/indieweb-endpoints-ruby'
18
17
  spec.license = 'MIT'
@@ -41,4 +40,3 @@ Gem::Specification.new do |spec|
41
40
  spec.add_runtime_dependency 'http', '~> 5.0.0.pre'
42
41
  spec.add_runtime_dependency 'nokogiri', '~> 1.10'
43
42
  end
44
- # rubocop:enable Metrics/BlockLength
@@ -2,13 +2,13 @@ module IndieWeb
2
2
  module Endpoints
3
3
  class Client
4
4
  def initialize(url)
5
- raise ArgumentError, "url must be a String (given #{url.class.name})" unless url.is_a?(String)
6
-
7
5
  @uri = Addressable::URI.parse(url)
8
6
 
9
- raise ArgumentError, 'url must be an absolute URL (e.g. https://example.com)' unless @uri.absolute?
7
+ raise ArgumentError, 'url must be an absolute URL (e.g. https://example.com)' unless @uri.absolute? && @uri.scheme.match?(/^https?$/)
10
8
  rescue Addressable::URI::InvalidURIError => exception
11
9
  raise InvalidURIError, exception
10
+ rescue NoMethodError, TypeError
11
+ raise ArgumentError, "url must be a String (given #{url.class})"
12
12
  end
13
13
 
14
14
  def endpoints
@@ -1,13 +1,24 @@
1
1
  module IndieWeb
2
2
  module Endpoints
3
3
  class HttpRequest
4
- HTTP_HEADERS_OPTS = {
5
- accept: '*/*',
6
- user_agent: 'IndieAuth, Micropub, and Webmention Endpoint Discovery (https://rubygems.org/gems/indieweb-endpoints)'
4
+ # Defaults derived from Webmention specification examples
5
+ # https://www.w3.org/TR/webmention/#limits-on-get-requests
6
+ HTTP_CLIENT_OPTS = {
7
+ follow: {
8
+ max_hops: 20
9
+ },
10
+ headers: {
11
+ accept: '*/*',
12
+ user_agent: 'IndieWeb Endpoint Discovery (https://rubygems.org/gems/indieweb-endpoints)'
13
+ },
14
+ timeout_options: {
15
+ connect_timeout: 5,
16
+ read_timeout: 5
17
+ }
7
18
  }.freeze
8
19
 
9
20
  def self.get(uri)
10
- HTTP.follow.headers(HTTP_HEADERS_OPTS).timeout(connect: 10, read: 10).get(uri)
21
+ HTTP::Client.new(HTTP_CLIENT_OPTS).request(:get, uri)
11
22
  rescue HTTP::ConnectionError,
12
23
  HTTP::TimeoutError,
13
24
  HTTP::Redirector::TooManyRedirectsError => exception
@@ -0,0 +1,13 @@
1
+ module IndieWeb
2
+ module Endpoints
3
+ module Parsers
4
+ class MicrosubParser < BaseParser
5
+ def self.identifier
6
+ :microsub
7
+ end
8
+
9
+ Parsers.register(self)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -11,7 +11,7 @@ module IndieWeb
11
11
  def results
12
12
  return unless results_from_http_request.any?
13
13
 
14
- @results ||= results_from_http_request.map { |endpoint| Absolutely.to_abs(base: @response.uri.to_s, relative: endpoint) }.uniq.sort
14
+ @results ||= results_from_http_request.map { |endpoint| Absolutely.to_abs(base: response.uri.to_s, relative: endpoint) }.uniq.sort
15
15
  rescue Absolutely::InvalidURIError => exception
16
16
  raise InvalidURIError, exception
17
17
  end
@@ -19,10 +19,26 @@ module IndieWeb
19
19
  private
20
20
 
21
21
  def results_from_body
22
- link_elements.map { |element| element['href'] } if response_is_html && link_elements.any?
22
+ RedirectUriLinkElementParser.new(response, self.class.identifier).results
23
23
  end
24
24
 
25
25
  def results_from_headers
26
+ RedirectUriLinkHeaderParser.new(response, self.class.identifier).results
27
+ end
28
+
29
+ def results_from_http_request
30
+ @results_from_http_request ||= [results_from_headers, results_from_body].flatten.compact
31
+ end
32
+ end
33
+
34
+ class RedirectUriLinkElementParser < LinkElementParser
35
+ def results
36
+ link_elements.map { |element| element['href'] } if response_is_html && link_elements.any?
37
+ end
38
+ end
39
+
40
+ class RedirectUriLinkHeaderParser < LinkHeaderParser
41
+ def results
26
42
  return unless link_headers.any?
27
43
 
28
44
  link_headers.map do |header|
@@ -31,10 +47,6 @@ module IndieWeb
31
47
  endpoint_match_data[1] if endpoint_match_data
32
48
  end
33
49
  end
34
-
35
- def results_from_http_request
36
- @results_from_http_request ||= [results_from_headers, results_from_body].flatten.compact
37
- end
38
50
  end
39
51
  end
40
52
  end
@@ -10,6 +10,14 @@ module IndieWeb
10
10
 
11
11
  private
12
12
 
13
+ def results_from_body
14
+ WebmentionLinkElementParser.new(response, self.class.identifier).results
15
+ end
16
+ end
17
+
18
+ class WebmentionLinkElementParser < LinkElementParser
19
+ private
20
+
13
21
  def link_element
14
22
  # Return first `a` or `link` element with valid `rel` attribute
15
23
  # https://www.w3.org/TR/webmention/#sender-discovers-receiver-webmention-endpoint
@@ -17,7 +25,7 @@ module IndieWeb
17
25
  end
18
26
 
19
27
  def link_elements_css_selector
20
- @link_elements_css_selector ||= %([rel~="#{self.class.identifier}"][href])
28
+ @link_elements_css_selector ||= %([rel~="#{identifier}"][href]:not([href*="#"]))
21
29
  end
22
30
  end
23
31
  end
@@ -4,13 +4,7 @@ module IndieWeb
4
4
  extend Registerable
5
5
 
6
6
  class BaseParser
7
- # Ultra-orthodox pattern matching allowed values in Link header `rel` parameter
8
- # https://tools.ietf.org/html/rfc8288#section-3.3
9
- REGEXP_REG_REL_TYPE_PATTERN = '[a-z\d][a-z\d\-\.]*'.freeze
10
-
11
- # Liberal pattern matching a string of text between angle brackets
12
- # https://tools.ietf.org/html/rfc5988#section-5.1
13
- REGEXP_TARGET_URI_PATTERN = /^<(.*)>;/.freeze
7
+ attr_reader :response
14
8
 
15
9
  def initialize(response)
16
10
  raise ArgumentError, "response must be an HTTP::Response (given #{response.class.name})" unless response.is_a?(HTTP::Response)
@@ -21,21 +15,42 @@ module IndieWeb
21
15
  def results
22
16
  return unless results_from_http_request
23
17
 
24
- @results ||= Absolutely.to_abs(base: @response.uri.to_s, relative: results_from_http_request)
18
+ @results ||= Absolutely.to_abs(base: response.uri.to_s, relative: results_from_http_request)
25
19
  rescue Absolutely::InvalidURIError => exception
26
20
  raise InvalidURIError, exception
27
21
  end
28
22
 
29
23
  private
30
24
 
31
- def discrete_link_headers
32
- # Split Link headers with multiple values, flatten the resulting array, and strip whitespace
33
- # https://webmention.rocks/test/19
34
- @discrete_link_headers ||= @response.headers.get('link').map { |header| header.split(',') }.flatten.map(&:strip)
25
+ def results_from_body
26
+ LinkElementParser.new(response, self.class.identifier).results
27
+ end
28
+
29
+ def results_from_headers
30
+ LinkHeaderParser.new(response, self.class.identifier).results
31
+ end
32
+
33
+ def results_from_http_request
34
+ @results_from_http_request ||= results_from_headers || results_from_body || nil
35
+ end
36
+ end
37
+
38
+ class LinkElementParser
39
+ attr_reader :identifier, :response
40
+
41
+ def initialize(response, identifier)
42
+ @response = response
43
+ @identifier = identifier
35
44
  end
36
45
 
46
+ def results
47
+ link_element['href'] if response_is_html && link_element
48
+ end
49
+
50
+ private
51
+
37
52
  def doc
38
- @doc ||= Nokogiri::HTML(@response.body.to_s)
53
+ @doc ||= Nokogiri::HTML(response.body.to_s)
39
54
  end
40
55
 
41
56
  def link_element
@@ -50,42 +65,59 @@ module IndieWeb
50
65
  end
51
66
 
52
67
  def link_elements_css_selector
53
- @link_elements_css_selector ||= %(link[rel~="#{self.class.identifier}"][href])
68
+ @link_elements_css_selector ||= %(link[rel~="#{identifier}"][href]:not([href*="#"]))
54
69
  end
55
70
 
56
- def link_header
57
- @link_header ||= link_headers.shift
71
+ def response_is_html
72
+ @response_is_html ||= response.mime_type == 'text/html'
58
73
  end
74
+ end
59
75
 
60
- def link_headers
61
- # Reduce Link headers to those with valid `rel` attribute
62
- @link_headers ||= discrete_link_headers.find_all { |header| header.match?(regexp_rel_paramater_pattern) }
63
- end
76
+ class LinkHeaderParser
77
+ # Ultra-orthodox pattern matching allowed values in Link header `rel` parameter
78
+ # https://tools.ietf.org/html/rfc8288#section-3.3
79
+ REGEXP_REG_REL_TYPE_PATTERN = '[a-z\d][a-z\d\-\.]*'.freeze
64
80
 
65
- def regexp_rel_paramater_pattern
66
- # Ultra-orthodox pattern matching Link header `rel` parameter including a matching identifier value
67
- # https://www.w3.org/TR/webmention/#sender-discovers-receiver-webmention-endpoint
68
- @regexp_rel_paramater_pattern ||= /(?:;|\s)rel="?(?:#{REGEXP_REG_REL_TYPE_PATTERN}+\s)?#{self.class.identifier}(?:\s#{REGEXP_REG_REL_TYPE_PATTERN})?"?/
69
- end
81
+ # Liberal pattern capturing a string of text (excepting the octothorp) between angle brackets
82
+ # https://tools.ietf.org/html/rfc5988#section-5.1
83
+ REGEXP_TARGET_URI_PATTERN = '^<(.[^#]*)>;'.freeze
70
84
 
71
- def results_from_body
72
- link_element['href'] if response_is_html && link_element
85
+ attr_reader :identifier, :response
86
+
87
+ def initialize(response, identifier)
88
+ @response = response
89
+ @identifier = identifier
73
90
  end
74
91
 
75
- def results_from_headers
92
+ def results
76
93
  return unless link_header
77
94
 
78
- endpoint_match_data = link_header.match(REGEXP_TARGET_URI_PATTERN)
95
+ endpoint_match_data = link_header.match(/#{REGEXP_TARGET_URI_PATTERN}/)
79
96
 
80
97
  return endpoint_match_data[1] if endpoint_match_data
81
98
  end
82
99
 
83
- def response_is_html
84
- @response_is_html ||= @response.mime_type == 'text/html'
100
+ private
101
+
102
+ def discrete_link_headers
103
+ # Split Link headers with multiple values, flatten the resulting array, and strip whitespace
104
+ # https://webmention.rocks/test/19
105
+ @discrete_link_headers ||= response.headers.get('link').map { |header| header.split(',') }.flatten.map(&:strip)
85
106
  end
86
107
 
87
- def results_from_http_request
88
- @results_from_http_request ||= results_from_headers || results_from_body || nil
108
+ def link_header
109
+ @link_header ||= link_headers.shift
110
+ end
111
+
112
+ def link_headers
113
+ # Reduce Link headers to those with valid `rel` attribute
114
+ @link_headers ||= discrete_link_headers.find_all { |header| header.match?(/#{REGEXP_TARGET_URI_PATTERN}\s*#{regexp_rel_paramater_pattern}/) }
115
+ end
116
+
117
+ def regexp_rel_paramater_pattern
118
+ # Ultra-orthodox pattern matching Link header `rel` parameter including a matching identifier value
119
+ # https://www.w3.org/TR/webmention/#sender-discovers-receiver-webmention-endpoint
120
+ @regexp_rel_paramater_pattern ||= %(rel="?(?:#{REGEXP_REG_REL_TYPE_PATTERN}+\s)?#{identifier}(?:\s#{REGEXP_REG_REL_TYPE_PATTERN})?"?)
89
121
  end
90
122
  end
91
123
  end
@@ -1,5 +1,5 @@
1
1
  module IndieWeb
2
2
  module Endpoints
3
- VERSION = '0.4.0'.freeze
3
+ VERSION = '0.5.0'.freeze
4
4
  end
5
5
  end
@@ -13,6 +13,7 @@ require 'indieweb/endpoints/registerable'
13
13
  require 'indieweb/endpoints/parsers'
14
14
  require 'indieweb/endpoints/parsers/authorization_endpoint_parser'
15
15
  require 'indieweb/endpoints/parsers/micropub_parser'
16
+ require 'indieweb/endpoints/parsers/microsub_parser'
16
17
  require 'indieweb/endpoints/parsers/redirect_uri_parser'
17
18
  require 'indieweb/endpoints/parsers/token_endpoint_parser'
18
19
  require 'indieweb/endpoints/parsers/webmention_parser'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: indieweb-endpoints
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jason Garber
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-05-02 00:00:00.000000000 Z
11
+ date: 2019-05-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -192,7 +192,7 @@ dependencies:
192
192
  - - "~>"
193
193
  - !ruby/object:Gem::Version
194
194
  version: '1.10'
195
- description: Discover a URL’s IndieAuth, Micropub, and Webmention endpoints.
195
+ description: Discover a URL’s IndieAuth, Micropub, Microsub, and Webmention endpoints.
196
196
  email:
197
197
  - jason@sixtwothree.org
198
198
  executables: []
@@ -224,6 +224,7 @@ files:
224
224
  - lib/indieweb/endpoints/parsers.rb
225
225
  - lib/indieweb/endpoints/parsers/authorization_endpoint_parser.rb
226
226
  - lib/indieweb/endpoints/parsers/micropub_parser.rb
227
+ - lib/indieweb/endpoints/parsers/microsub_parser.rb
227
228
  - lib/indieweb/endpoints/parsers/redirect_uri_parser.rb
228
229
  - lib/indieweb/endpoints/parsers/token_endpoint_parser.rb
229
230
  - lib/indieweb/endpoints/parsers/webmention_parser.rb
@@ -234,7 +235,7 @@ licenses:
234
235
  - MIT
235
236
  metadata:
236
237
  bug_tracker_uri: https://github.com/indieweb/indieweb-endpoints-ruby/issues
237
- changelog_uri: https://github.com/indieweb/indieweb-endpoints-ruby/blob/v0.4.0/CHANGELOG.md
238
+ changelog_uri: https://github.com/indieweb/indieweb-endpoints-ruby/blob/v0.5.0/CHANGELOG.md
238
239
  post_install_message:
239
240
  rdoc_options: []
240
241
  require_paths:
@@ -256,5 +257,5 @@ requirements: []
256
257
  rubygems_version: 3.0.3
257
258
  signing_key:
258
259
  specification_version: 4
259
- summary: Discover a URL’s IndieAuth, Micropub, and Webmention endpoints.
260
+ summary: Discover a URL’s IndieAuth, Micropub, Microsub, and Webmention endpoints.
260
261
  test_files: []