Skip to content

Commit c91c3df

Browse files
committed
fix: accept escaped public feed tokens
1 parent d6f7fb3 commit c91c3df

2 files changed

Lines changed: 57 additions & 5 deletions

File tree

app/feeds/request_parser.rb

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# frozen_string_literal: true
22

3+
require 'cgi'
34
require_relative 'request'
45
require_relative 'response_format'
56

@@ -15,17 +16,37 @@ class << self
1516
# @param identifier [String]
1617
# @return [Html2rss::Web::Feeds::Request]
1718
def call(request:, target_kind:, identifier:)
18-
representation = ResponseFormat.for_request(request)
19-
normalized_identifier = ResponseFormat.strip_known_extension(identifier)
19+
build_request(
20+
request: request,
21+
target_kind: target_kind,
22+
identifier: normalize_identifier(target_kind, ResponseFormat.strip_known_extension(identifier))
23+
)
24+
end
25+
26+
private
2027

28+
# @param request [Rack::Request]
29+
# @param target_kind [Symbol]
30+
# @param identifier [String]
31+
# @return [Html2rss::Web::Feeds::Request]
32+
def build_request(request:, target_kind:, identifier:)
2133
Request.new(
2234
target_kind: target_kind,
23-
representation: representation,
24-
feed_name: target_kind == :static ? normalized_identifier : nil,
25-
token: target_kind == :token ? normalized_identifier : nil,
35+
representation: ResponseFormat.for_request(request),
36+
feed_name: target_kind == :static ? identifier : nil,
37+
token: target_kind == :token ? identifier : nil,
2638
params: request.params.to_h
2739
)
2840
end
41+
42+
# @param target_kind [Symbol]
43+
# @param identifier [String]
44+
# @return [String]
45+
def normalize_identifier(target_kind, identifier)
46+
return identifier unless target_kind == :token
47+
48+
CGI.unescape(identifier)
49+
end
2950
end
3051
end
3152
end

spec/html2rss/web/app_integration_spec.rb

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
require 'spec_helper'
44
require 'rack/test'
5+
require 'cgi'
56
require 'json'
67
require 'securerandom'
78
require_relative '../../../app'
@@ -13,6 +14,7 @@
1314

1415
let(:feed_url) { 'https://example.com/articles' }
1516
let(:feed_token) { "valid-feed-token-#{SecureRandom.hex(4)}" }
17+
let(:encoded_feed_token) { CGI.escape(feed_token) }
1618

1719
let(:account) do
1820
{
@@ -94,6 +96,18 @@
9496
expect(last_response.body).to eq('<rss version="2.0"></rss>')
9597
end
9698

99+
it 'accepts URL-escaped public feed tokens', :aggregate_failures do
100+
padded_feed_token = 'signed-public-token='
101+
encoded_padded_feed_token = CGI.escape(padded_feed_token)
102+
103+
stub_escaped_feed_token(raw_token: padded_feed_token, encoded_token: encoded_padded_feed_token)
104+
105+
get "/api/v1/feeds/#{encoded_padded_feed_token}", {}, { 'HTTP_ACCEPT' => 'application/xml' }
106+
107+
expect(last_response.status).to eq(200)
108+
expect(last_response.headers['Content-Type']).to eq('application/xml')
109+
end
110+
97111
it 'renders the JSON feed when requested by extension', :aggregate_failures do
98112
get "/api/v1/feeds/#{feed_token}.json"
99113

@@ -166,6 +180,23 @@
166180
[401, 'application/feed+json', { 'version' => 'https://jsonfeed.org/version/1.1', 'title' => 'Error' }]
167181
)
168182
end
183+
184+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
185+
def stub_escaped_feed_token(raw_token:, encoded_token:)
186+
escaped_token_payload = instance_double(
187+
Html2rss::Web::FeedToken,
188+
url: feed_url,
189+
username: account[:username],
190+
strategy: 'ssrf_filter'
191+
)
192+
193+
allow(Html2rss::Web::FeedToken).to receive(:decode).with(raw_token).and_return(escaped_token_payload)
194+
allow(Html2rss::Web::FeedToken).to receive(:decode).with(encoded_token).and_return(nil)
195+
allow(Html2rss::Web::FeedToken)
196+
.to receive(:validate_and_decode).with(raw_token, feed_url, anything)
197+
.and_return(escaped_token_payload)
198+
end
199+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
169200
end
170201

171202
describe 'POST /api/v1/feeds' do # rubocop:disable RSpec/MultipleMemoizedHelpers

0 commit comments

Comments
 (0)