# Samizdat HTML validation
#
# Copyright (c) 2002-2005 Dmitry Borodaenko
#
# This program is free software.
# You can distribute/modify this program under the terms of
# the GNU General Public License version 2 or later.
#
# vim: et sw=2 sts=2 ts=8 tw=0
require 'cgi'
require 'yaml'
require 'rexml/document'
$TIDYLIB = 'libtidy.so'
require 'tidy'
# use (") instead of (') in XML attributes, escape both of them
#
module REXML
class Attribute
def to_string
%{#@expanded_name="#{to_s().gsub(/"/, '"').gsub(/'/, ''')}"}
end
end
end # module REXML
module Samizdat
class Sanitize
def initialize(xhtml)
@xhtml = xhtml
end
attr_reader :xhtml
# compare elements and attributes with xhtml.yaml
#
def sanitize_element(xml, filter=@xhtml)
if not filter.keys.include?(xml.name)
xml.document.delete_element(xml)
return
end
if xml.has_attributes?
attrs = filter['_common'].merge((filter[xml.name] or {}))
xml.attributes.each_attribute do |a|
xml.delete_attribute(a.name) unless attrs[a.name] === a.to_s
# todo: sanitize CSS in style="" attributes
end
end
if xml.has_elements?
xml.elements.each {|e| sanitize_element(e) }
end
end
# return sanitized HTML
#
def sanitize(html, filter=@xhtml, fragment=true)
xhtml = Tidy.open(:output_xhtml => true, :literal_attributes => true,
:tidy_mark => false, :wrap => 0, :char_encoding => 'utf8'
) {|tidy| tidy.clean(html.to_s.untaint) }
xhtml.taint
begin
xml = REXML::Document.new(xhtml).root
xml = xml.elements['//html/body'] if fragment # work around tidy
rescue REXML::ParseException
raise RuntimeError, "Invalid HTML detected: " +
CGI::escapeHTML($!.continued_exception.to_s.gsub!(/\n.*/, ''))
end
sanitize_element(xml, filter)
html = ''
if fragment
xml.each {|child| child.write(html, -1, false, true) }
else
xml.write(html, -1, false, true)
end
html
end
end
end # module Samizdat