mirror of
https://github.com/discourse/discourse.git
synced 2026-08-06 13:08:40 +08:00
## Summary EmbeddableHost allowed_paths values are stored as regular expression source and compiled during request handling without validation or error handling. Now new or updated allowlists are validated on save, and legacy invalid or timed-out patterns are safely skipped as non-matches rather than causing HTTP 500 errors on public embed endpoints. This prevents an administrator from accidentally breaking embed functionality across their site. ## Source - Patch Triage: `patch-triage/1556` Co-authored-by: discourse-patch-triage <272280883+discourse-patch-triage[bot]@users.noreply.github.com>
99 lines
2.5 KiB
Ruby
Vendored
99 lines
2.5 KiB
Ruby
Vendored
# frozen_string_literal: true
|
|
|
|
class EmbeddableHost < ActiveRecord::Base
|
|
validate :host_must_be_valid
|
|
validate :allowed_paths_must_be_valid
|
|
belongs_to :category
|
|
belongs_to :user, optional: true
|
|
has_many :embeddable_host_tags
|
|
has_many :tags, through: :embeddable_host_tags
|
|
after_destroy :reset_embedding_settings
|
|
|
|
before_validation do
|
|
host.sub!(%r{\Ahttps?://}, "")
|
|
host.sub!(%r{/.*\z}, "")
|
|
end
|
|
|
|
def self.record_for_url(uri)
|
|
if uri.is_a?(String)
|
|
uri =
|
|
begin
|
|
URI(UrlHelper.normalized_encode(uri))
|
|
rescue URI::Error, Addressable::URI::InvalidURIError
|
|
end
|
|
end
|
|
|
|
return false if uri.blank?
|
|
|
|
host = uri.host
|
|
return false if host.blank?
|
|
|
|
host << ":#{uri.port}" if uri.port.present? && uri.port != 80 && uri.port != 443
|
|
|
|
path = uri.path
|
|
path << "?" << uri.query if uri.query.present?
|
|
|
|
where("lower(host) = ?", host).each do |eh|
|
|
return eh if eh.allowed_paths.blank?
|
|
|
|
begin
|
|
path_regexp = Regexp.new(eh.allowed_paths)
|
|
return eh if path_regexp.match(path) || path_regexp.match(UrlHelper.unencode(path))
|
|
rescue RegexpError
|
|
next
|
|
end
|
|
end
|
|
|
|
nil
|
|
end
|
|
|
|
def self.url_allowed?(url)
|
|
return false if url.nil?
|
|
|
|
uri =
|
|
begin
|
|
URI(UrlHelper.normalized_encode(url))
|
|
rescue URI::Error
|
|
end
|
|
|
|
uri.present? && record_for_url(uri).present?
|
|
end
|
|
|
|
private
|
|
|
|
def reset_embedding_settings
|
|
unless EmbeddableHost.exists?
|
|
Embedding.settings.each { |s| SiteSetting.set(s.to_s, SiteSetting.defaults[s]) }
|
|
end
|
|
end
|
|
|
|
def allowed_paths_must_be_valid
|
|
return if allowed_paths.blank?
|
|
|
|
Regexp.new(allowed_paths)
|
|
rescue RegexpError
|
|
errors.add(:allowed_paths, I18n.t("errors.messages.invalid"))
|
|
end
|
|
|
|
def host_must_be_valid
|
|
if host !~ /\A[a-z0-9]+([\-\.]+{1}[a-z0-9]+)*\.[a-z]{2,24}(:[0-9]{1,5})?(\/.*)?\Z/i &&
|
|
host !~ /\A(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})(:[0-9]{1,5})?(\/.*)?\Z/ &&
|
|
host !~ /\A([a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.)?localhost(\:[0-9]{1,5})?(\/.*)?\Z/i
|
|
errors.add(:host, I18n.t("errors.messages.invalid"))
|
|
end
|
|
end
|
|
end
|
|
|
|
# == Schema Information
|
|
#
|
|
# Table name: embeddable_hosts
|
|
#
|
|
# id :integer not null, primary key
|
|
# allowed_paths :string
|
|
# class_name :string
|
|
# host :string not null
|
|
# created_at :datetime not null
|
|
# updated_at :datetime not null
|
|
# category_id :integer not null
|
|
# user_id :integer
|
|
#
|