Files
once-campfire/lib/rails_ext/actiontext_opengraph_embeds.rb
T
Rosa Gutierrez 436ea06457 Read a preview's host as a name by its last label
A domain name ends in a word, which is what keeps it from reading as an
address. "0x7f.0.0.1" carries a dot and a letter, so the previous shape
check let it through while a browser fetched 127.0.0.1.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0186eyzivcTn6wqjEE4Wnxdt
2026-09-11 20:25:35 +02:00

87 lines
2.8 KiB
Ruby

class ActionText::Attachment::OpengraphEmbed
include ActiveModel::Model
OPENGRAPH_EMBED_CONTENT_TYPE = "application/vnd.actiontext.opengraph-embed"
class << self
def from_node(node)
if node["content-type"]
if matches = node["content-type"].match(OPENGRAPH_EMBED_CONTENT_TYPE)
attachment = new(attributes_from_node(node))
attachment if attachment.valid?
end
end
end
private
def attributes_from_node(node)
{
href: web_url(node["href"]),
url: web_url(node["url"]),
filename: node["filename"],
description: node["caption"]
}
end
# A link preview points at what we unfurled: an absolute http or https URL
# on some other host. Drop anything else a message body asks for, so it
# can't aim the preview's link or its image at this Campfire and have every
# reader's browser fetch it with their session attached.
def web_url(value)
return if value.blank?
parsed = URI.parse(value)
value if parsed.is_a?(URI::HTTP) && elsewhere?(parsed.host)
rescue URI::InvalidURIError
nil
end
# "https:/rooms/1" parses as HTTPS with no host at all, and a browser
# resolves both that and our own hostname against the origin Campfire is
# served from. A percent-escape hides our hostname from this comparison
# while a browser still unescapes it back to us, so an escaped host is out
# too, and neither case is anything an unfurl could have produced.
def elsewhere?(host)
return false unless named_host?(host)
canonical_host(host) != canonical_host(Current.request_host.to_s)
end
# A preview names a page on the public internet, so its host is a domain
# name, written plainly. A bare address is not one, and a browser rewrites
# the many spellings of an address ("2130706433", "0x7f.0.0.1") into a
# single one before it fetches, which is a race a comparison here loses.
def named_host?(host)
host.present? && host.exclude?("%") && host.include?(".") && domain_ending?(host.split(".").last)
end
# What keeps a name from reading as an address is its last label, which is
# a word: never a number, and never the hexadecimal spelling of one.
def domain_ending?(label)
label.match?(/[a-z]/i) && !label.match?(/\A0x/i)
end
def canonical_host(host)
host.downcase.delete_suffix(".")
end
end
attr_accessor :href, :url, :filename, :description
def attachable_content_type
OPENGRAPH_EMBED_CONTENT_TYPE
end
def attachable_plain_text_representation(caption)
""
end
def to_partial_path
"action_text/attachables/opengraph_embed"
end
def to_trix_content_attachment_partial_path
"action_text/attachables/opengraph_embed"
end
end