mirror of
https://github.com/basecamp/once-campfire.git
synced 2026-09-13 20:12:06 +09:00
436ea06457
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
87 lines
2.8 KiB
Ruby
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
|