Skip to content

Commit e51265a

Browse files
committed
improve auto_source ux
1 parent 46a11c9 commit e51265a

File tree

16 files changed

+1749
-681
lines changed

16 files changed

+1749
-681
lines changed

app.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
require_relative 'app/api_routes'
1414
require_relative 'app/response_helpers'
1515
require_relative 'app/static_file_helpers'
16+
require_relative 'app/xml_builder'
1617

1718
module Html2rss
1819
module Web

app/auto_source.rb

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

33
require 'uri'
44
require_relative 'auth'
5+
require_relative 'xml_builder'
56

67
module Html2rss
78
module Web
@@ -87,50 +88,64 @@ def generate_feed_from_stable_id(feed_id, token_data)
8788
end
8889

8990
def generate_feed_content(url, strategy = 'ssrf_filter')
90-
call_strategy(url, strategy)
91+
feed_content = call_strategy(url, strategy)
92+
93+
# Check if feed is empty and provide better error handling
94+
if feed_content.respond_to?(:to_s)
95+
feed_xml = feed_content.to_s
96+
if feed_xml.include?('<item>') == false
97+
# Feed has no items - this might be a content extraction issue
98+
return create_empty_feed_warning(url, strategy)
99+
end
100+
end
101+
102+
feed_content
103+
end
104+
105+
def create_empty_feed_warning(url, strategy)
106+
site_title = extract_site_title(url)
107+
XmlBuilder.build_empty_feed_warning(
108+
url: url,
109+
strategy: strategy,
110+
site_title: site_title
111+
)
91112
end
92113

114+
# rubocop:disable Metrics/MethodLength
93115
def call_strategy(url, strategy)
94116
config = {
95117
stylesheets: [{ href: '/rss.xsl', type: 'text/xsl' }],
96118
strategy: strategy.to_sym,
97119
channel: {
98120
url: url,
99-
title: "Auto-generated feed for #{url}"
121+
title: extract_channel_title(url)
100122
},
101-
auto_source: {}
123+
auto_source: {
124+
# Auto source configuration for automatic content detection
125+
# This allows Html2rss to automatically detect content on the page
126+
}
102127
}
103128

104129
Html2rss.feed(config)
105130
end
131+
# rubocop:enable Metrics/MethodLength
106132

107-
def error_feed(message)
108-
sanitized_message = Auth.sanitize_xml(message)
109-
build_rss_feed('Error', "Failed to generate auto-source feed: #{sanitized_message}", sanitized_message)
133+
def extract_channel_title(url)
134+
Html2rss::Url.for_channel(url).channel_titleized || 'RSS Feed'
110135
end
111136

112-
def access_denied_feed(url)
113-
sanitized_url = Auth.sanitize_xml(url)
114-
title = 'Access Denied'
115-
description = 'This URL is not allowed for public auto source generation.'
116-
item_description = "URL '#{sanitized_url}' is not in the allowed list for public auto source."
117-
build_rss_feed(title, description, item_description)
137+
def extract_site_title(url)
138+
Html2rss::Url.for_channel(url).channel_titleized
139+
rescue StandardError
140+
nil
141+
end
142+
143+
def error_feed(message)
144+
XmlBuilder.build_error_feed(message: message)
118145
end
119146

120-
def build_rss_feed(title, description, item_description)
121-
<<~RSS
122-
<?xml version="1.0" encoding="UTF-8"?>
123-
<rss version="2.0">
124-
<channel>
125-
<title>#{title}</title>
126-
<description>#{description}</description>
127-
<item>
128-
<title>#{title}</title>
129-
<description>#{item_description}</description>
130-
</item>
131-
</channel>
132-
</rss>
133-
RSS
147+
def access_denied_feed(url)
148+
XmlBuilder.build_access_denied_feed(url)
134149
end
135150
end
136151
end

app/feeds.rb

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

33
require_relative 'auth'
4+
require_relative 'xml_builder'
45

56
module Html2rss
67
module Web
@@ -28,24 +29,7 @@ def generate_feed(feed_name, params = {})
2829
end
2930

3031
def error_feed(message)
31-
sanitized_message = Auth.sanitize_xml(message)
32-
build_error_rss(sanitized_message)
33-
end
34-
35-
def build_error_rss(sanitized_message)
36-
<<~RSS
37-
<?xml version="1.0" encoding="UTF-8"?>
38-
<rss version="2.0">
39-
<channel>
40-
<title>Error</title>
41-
<description>Failed to generate feed: #{sanitized_message}</description>
42-
<item>
43-
<title>Error</title>
44-
<description>#{sanitized_message}</description>
45-
</item>
46-
</channel>
47-
</rss>
48-
RSS
32+
XmlBuilder.build_error_feed(message: message)
4933
end
5034
end
5135
end

app/response_helpers.rb

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# frozen_string_literal: true
2+
3+
module Html2rss
4+
module Web
5+
##
6+
# Response helper methods for the main App class
7+
module ResponseHelpers
8+
module_function
9+
10+
def unauthorized_response
11+
response.status = 401
12+
response['WWW-Authenticate'] = 'Basic realm="Auto Source"'
13+
'Unauthorized'
14+
end
15+
16+
def forbidden_origin_response
17+
response.status = 403
18+
'Origin is not allowed.'
19+
end
20+
21+
def access_denied_response(url)
22+
response.status = 403
23+
response['Content-Type'] = 'application/xml'
24+
AutoSource.access_denied_feed(url)
25+
end
26+
27+
def not_found_response
28+
response.status = 404
29+
'Feed not found'
30+
end
31+
32+
def bad_request_response(message)
33+
response.status = 400
34+
message
35+
end
36+
37+
def method_not_allowed_response
38+
response.status = 405
39+
'Method Not Allowed'
40+
end
41+
42+
def internal_error_response
43+
response.status = 500
44+
'Internal Server Error'
45+
end
46+
47+
def set_auto_source_headers
48+
response['Content-Type'] = 'application/xml'
49+
response['Cache-Control'] = 'private, must-revalidate, no-cache, no-store, max-age=0'
50+
response['X-Content-Type-Options'] = 'nosniff'
51+
response['X-XSS-Protection'] = '1; mode=block'
52+
end
53+
54+
def health_check_unauthorized
55+
response.status = 401
56+
response['WWW-Authenticate'] = 'Bearer realm="Health Check"'
57+
'Unauthorized'
58+
end
59+
end
60+
end
61+
end

app/static_file_helpers.rb

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# frozen_string_literal: true
2+
3+
module Html2rss
4+
module Web
5+
##
6+
# Static file handling helpers for the main App class
7+
module StaticFileHelpers
8+
module_function
9+
10+
def handle_static_files(router)
11+
router.on do
12+
if router.path_info == '/'
13+
serve_root_path
14+
else
15+
serve_astro_files(router)
16+
end
17+
end
18+
end
19+
20+
def serve_root_path
21+
index_path = 'public/frontend/index.html'
22+
response['Content-Type'] = 'text/html'
23+
24+
if File.exist?(index_path)
25+
File.read(index_path)
26+
else
27+
fallback_html
28+
end
29+
end
30+
31+
def fallback_html
32+
<<~HTML
33+
<!DOCTYPE html>
34+
<html>
35+
<head>
36+
<title>html2rss-web</title>
37+
<link rel="stylesheet" href="/water.css">
38+
</head>
39+
<body>
40+
<h1>html2rss-web</h1>
41+
<p>Convert websites to RSS feeds</p>
42+
<p>API available at <code>/api/</code></p>
43+
</body>
44+
</html>
45+
HTML
46+
end
47+
48+
def serve_astro_files(router)
49+
astro_path = "public/frontend#{router.path_info}"
50+
if File.exist?("#{astro_path}/index.html")
51+
serve_astro_file("#{astro_path}/index.html")
52+
elsif File.exist?(astro_path) && File.file?(astro_path)
53+
serve_astro_file(astro_path)
54+
else
55+
not_found_response
56+
end
57+
end
58+
59+
def serve_astro_file(file_path)
60+
response['Content-Type'] = 'text/html'
61+
File.read(file_path)
62+
end
63+
64+
##
65+
# Validate and decode Base64 string safely
66+
# @param encoded_string [String] Base64 encoded string
67+
# @return [String, nil] decoded string if valid, nil if invalid
68+
def validate_and_decode_base64(encoded_string)
69+
return nil unless encoded_string.is_a?(String)
70+
return nil if encoded_string.empty?
71+
72+
# Check if string contains only valid Base64 characters
73+
return nil unless encoded_string.match?(%r{\A[A-Za-z0-9+/]*={0,2}\z})
74+
75+
# Attempt to decode
76+
Base64.decode64(encoded_string)
77+
rescue ArgumentError
78+
nil
79+
end
80+
end
81+
end
82+
end

0 commit comments

Comments
 (0)