5
0
Fork 0
mirror of https://0xacab.org/sutty/sutty synced 2024-11-22 18:06:23 +00:00
panel/app/models/metadata_content.rb

154 lines
4 KiB
Ruby
Raw Permalink Normal View History

# frozen_string_literal: true
require 'htmlbeautifier'
# Se encarga del contenido del artículo y quizás otros campos que
# requieran texto largo.
class MetadataContent < MetadataTemplate
def default_value
super || ''
end
def value
2021-02-24 19:57:20 +00:00
self[:value] || legacy_content || default_value
end
2019-08-13 23:33:57 +00:00
def front_matter?
false
end
def document_value
document.content
end
def indexable?
2021-05-09 15:25:16 +00:00
true && !private?
end
def to_s
2023-02-01 20:51:30 +00:00
Nokogiri::HTML5.fragment(value).tap do |html|
html.css('[src^="public/"]').each do |element|
element['src'] = convert_internal_path_to_src element['src']
end
end.to_s
end
private
2021-02-24 19:57:20 +00:00
# Detectar si el contenido estaba en Markdown y pasarlo a HTML
def legacy_content
return unless document_value
return document_value if /^\s*</ =~ document_value
2021-02-24 19:57:20 +00:00
CommonMarker.render_doc(document_value, %i[FOOTNOTES UNSAFE], %i[table strikethrough autolink]).to_html
2021-02-24 19:57:20 +00:00
end
# Limpiar el HTML que recibimos
#
# TODO: En lugar de comprobar el Content Type acá, restringir los
# tipos de archivo a aceptar en ActiveStorage.
def sanitize(html_string)
2023-02-01 20:51:30 +00:00
html = Nokogiri::HTML5.fragment(super html_string)
elements = 'img,audio,video,iframe'
# Eliminar elementos sin src y comprobar su origen
html.css(elements).each do |element|
2024-06-04 15:49:37 +00:00
raise URI::Error unless element['src'].present?
2024-06-04 15:49:37 +00:00
uri = URI element['src']
2024-06-04 15:49:37 +00:00
# No permitimos recursos externos, solo si sabemos cuales son
# los recursos locales
if Rails.application.config.hosts.present? && !Rails.application.config.hosts.include?(uri.hostname)
raise URI::Error
end
2024-06-04 15:49:37 +00:00
element['src'] = convert_src_to_internal_path uri
2024-06-04 15:49:37 +00:00
raise URI::Error if element['src'].blank?
rescue URI::Error
element.remove
end
# Eliminar figure sin contenido
html.css('figure').each do |figure|
figure.remove if figure.css(elements).empty?
end
# Los videos y audios necesitan controles
html.css('audio,video').each do |resource|
resource['controls'] = true
end
# Elimina los estilos salvo los que asigne el editor
html.css('[style]').each do |element|
if (style = sanitize_style(element['style'])).present?
element['style'] = style
else
element.remove_attribute('style')
end
end
HtmlBeautifier.beautify(html.to_s).html_safe
end
# Limpia estilos en base a una lista de permitidos
#
# @param style [String]
# @return [String]
def sanitize_style(style)
2024-06-04 15:49:37 +00:00
style.split(';').each_with_object({}) do |style_string, style_hash|
key, value = style_string.split(':', 2)
style_hash[key] ||= value
end.slice(*allowed_styles).map do |style_pair|
style_pair.join(':')
end.join(';')
end
# Estilos permitidos
#
# @return [Array<String>]
def allowed_styles
@allowed_styles ||= %w[text-align color background-color]
end
# Convierte una ubicación local al sitio en una URL de ActiveStorage
#
# XXX: Por qué son tan díficiles de encontrar las rutas de AS
2024-06-04 15:49:37 +00:00
#
# @param path [String]
# @return [String]
def convert_internal_path_to_src(path)
key = path.split('/').second
blob = ActiveStorage::Blob.find_by(service_name: site.name, key: key)
return unless blob
"/rails/active_storage/blobs/#{blob.signed_id}/#{blob.filename}"
end
# Convierte una URI en una ruta interna del sitio actual
#
# XXX: No verifica si el archivo existe o no. Se supone que existe
# porque ya fue subido antes.
#
# @param uri [URI]
# @return [String,nil]
def convert_src_to_internal_path(uri)
signed_id = uri.path.split('/').fifth
blob = ActiveStorage::Blob.find_signed(signed_id)
return unless blob
return unless blob.service_name == site.name
blob_path = Pathname.new(blob.service.path_for(blob.key)).realpath
site_path = Pathname.new(site.path).realpath
blob_path.relative_path_from(site_path).to_s
rescue ActiveSupport::MessageVerifier::InvalidSignature => e
ExceptionNotifier.notify_exception(e, data: { site: site.name })
nil
end
end