mirror of
https://0xacab.org/sutty/sutty
synced 2024-11-22 10:06:23 +00:00
158 lines
4.1 KiB
Ruby
158 lines
4.1 KiB
Ruby
# 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
|
|
self[:value] || legacy_content || default_value
|
|
end
|
|
|
|
def front_matter?
|
|
false
|
|
end
|
|
|
|
def document_value
|
|
document.content
|
|
end
|
|
|
|
def indexable?
|
|
true && !private?
|
|
end
|
|
|
|
def to_s
|
|
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
|
|
|
|
# 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
|
|
|
|
CommonMarker.render_doc(document_value, %i[FOOTNOTES UNSAFE], %i[table strikethrough autolink]).to_html
|
|
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)
|
|
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|
|
|
begin
|
|
raise URI::Error unless element['src'].present?
|
|
|
|
uri = URI element['src']
|
|
|
|
# No permitimos recursos externos, solo si sabemos cuales son
|
|
# los recursos locales
|
|
if Rails.application.config.hosts.present?
|
|
unless Rails.application.config.hosts.include?(uri.hostname)
|
|
raise URI::Error
|
|
end
|
|
end
|
|
|
|
element['src'] = convert_src_to_internal_path uri
|
|
|
|
raise URI::Error if element['src'].blank?
|
|
rescue URI::Error
|
|
element.remove
|
|
end
|
|
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)
|
|
style.split(';').reduce({}) do |style_hash, style_string|
|
|
key, value = style_string.split(':', 2)
|
|
|
|
style_hash[key] ||= value
|
|
style_hash
|
|
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
|
|
#
|
|
# @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
|