diff --git a/ext/java/nokogiri/XsltStylesheet.java b/ext/java/nokogiri/XsltStylesheet.java index d35ee8ff91..b3379c9222 100644 --- a/ext/java/nokogiri/XsltStylesheet.java +++ b/ext/java/nokogiri/XsltStylesheet.java @@ -350,4 +350,22 @@ public class XsltStylesheet extends RubyObject if (arg instanceof XmlDocument) { return; } throw runtime.newArgumentError("argument must be a Nokogiri::XML::Document"); } + + @JRubyMethod(meta = true, rest = true, name = "default_security_prefs") + public static IRubyObject + get_default_security_options(ThreadContext context, IRubyObject klazz, IRubyObject[] args) + { + // This method is not supported because the Java XML backend does not support the + // security controls supported by the libxml backend + throw context.getRuntime().newNotImplementedError("Nokogiri::XSLT::Stylesheet.default_security_prefs method is not implemented"); + } + + @JRubyMethod(meta = true, rest = true, name = "default_security_prefs=") + public static IRubyObject + set_default_security_options(ThreadContext context, IRubyObject klazz, IRubyObject[] args) + { + // This method is not supported because the Java XML backend does not support the + // security controls supported by the libxml backend + throw context.getRuntime().newNotImplementedError("Nokogiri::XSLT::Stylesheet.default_security_prefs= method is not implemented"); + } } diff --git a/ext/nokogiri/nokogiri.h b/ext/nokogiri/nokogiri.h index ffc1749b91..50b3466e2a 100644 --- a/ext/nokogiri/nokogiri.h +++ b/ext/nokogiri/nokogiri.h @@ -50,6 +50,7 @@ #include #include #include +#include #include #include #include diff --git a/ext/nokogiri/xslt_stylesheet.c b/ext/nokogiri/xslt_stylesheet.c index 097b8bf704..f8fed97167 100644 --- a/ext/nokogiri/xslt_stylesheet.c +++ b/ext/nokogiri/xslt_stylesheet.c @@ -1,6 +1,8 @@ #include VALUE cNokogiriXsltStylesheet ; +VALUE mNokogiriXsltSecurity ; +VALUE cNokogiriXsltSecurityConfig ; static void mark(void *data) @@ -400,6 +402,50 @@ rb_xslt_s_register(VALUE self, VALUE uri, VALUE obj) return self; } +static void +add_sec_option(xsltSecurityPrefsPtr xsltPrefs, int option, VALUE val) +{ + if (val == Qtrue) { + xsltSetSecurityPrefs(xsltPrefs, option, xsltSecurityAllow); + } else if (val == Qfalse) { + xsltSetSecurityPrefs(xsltPrefs, option, xsltSecurityForbid); + } +} + +static VALUE +rb_set_default_security_options(VALUE self, VALUE options) +{ + Check_Type(options, T_OBJECT); + xsltSecurityPrefsPtr oldDefaults = xsltGetDefaultSecurityPrefs(); + xsltSecurityPrefsPtr xsltPrefs = xsltNewSecurityPrefs(); + add_sec_option(xsltPrefs, XSLT_SECPREF_READ_FILE, rb_iv_get(options, "@allow_read_file")); + add_sec_option(xsltPrefs, XSLT_SECPREF_WRITE_FILE, rb_iv_get(options, "@allow_write_file")); + add_sec_option(xsltPrefs, XSLT_SECPREF_CREATE_DIRECTORY, rb_iv_get(options, "@allow_create_directory")); + add_sec_option(xsltPrefs, XSLT_SECPREF_READ_NETWORK, rb_iv_get(options, "@allow_read_network")); + add_sec_option(xsltPrefs, XSLT_SECPREF_WRITE_NETWORK, rb_iv_get(options, "@allow_write_network")); + xsltSetDefaultSecurityPrefs(xsltPrefs); + if(oldDefaults) { + xsltFreeSecurityPrefs(oldDefaults); + } + return Qnil; +} + +static VALUE +rb_get_default_security_options(VALUE self) +{ + VALUE prefs = rb_funcall(cNokogiriXsltSecurityConfig, rb_intern("new"), 0); + xsltSecurityPrefsPtr xsltPrefs = xsltGetDefaultSecurityPrefs(); + if(xsltPrefs == NULL) { + return prefs; + } + rb_iv_set(prefs, "@allow_read_file", xsltGetSecurityPrefs(xsltPrefs, XSLT_SECPREF_READ_FILE) == xsltSecurityAllow ? Qtrue : Qfalse); + rb_iv_set(prefs, "@allow_write_file", xsltGetSecurityPrefs(xsltPrefs, XSLT_SECPREF_WRITE_FILE) == xsltSecurityAllow ? Qtrue : Qfalse); + rb_iv_set(prefs, "@allow_create_directory", xsltGetSecurityPrefs(xsltPrefs, XSLT_SECPREF_CREATE_DIRECTORY) == xsltSecurityAllow ? Qtrue : Qfalse); + rb_iv_set(prefs, "@allow_read_network", xsltGetSecurityPrefs(xsltPrefs, XSLT_SECPREF_READ_NETWORK) == xsltSecurityAllow ? Qtrue : Qfalse); + rb_iv_set(prefs, "@allow_write_network", xsltGetSecurityPrefs(xsltPrefs, XSLT_SECPREF_WRITE_NETWORK) == xsltSecurityAllow ? Qtrue : Qfalse); + return prefs; +} + void noko_init_xslt_stylesheet(void) { @@ -407,10 +453,14 @@ noko_init_xslt_stylesheet(void) rb_iv_set(mNokogiriXslt, "@modules", rb_hash_new()); cNokogiriXsltStylesheet = rb_define_class_under(mNokogiriXslt, "Stylesheet", rb_cObject); + mNokogiriXsltSecurity = rb_define_module_under(mNokogiriXslt, "Security"); + cNokogiriXsltSecurityConfig = rb_define_class_under(mNokogiriXsltSecurity, "Config", rb_cObject); rb_undef_alloc_func(cNokogiriXsltStylesheet); rb_define_singleton_method(cNokogiriXsltStylesheet, "parse_stylesheet_doc", parse_stylesheet_doc, 1); + rb_define_singleton_method(cNokogiriXsltStylesheet, "default_security_options", rb_get_default_security_options, 0); + rb_define_singleton_method(cNokogiriXsltStylesheet, "default_security_options=", rb_set_default_security_options, 1); rb_define_method(cNokogiriXsltStylesheet, "serialize", rb_xslt_stylesheet_serialize, 1); rb_define_method(cNokogiriXsltStylesheet, "transform", rb_xslt_stylesheet_transform, -1); } diff --git a/lib/nokogiri/xslt.rb b/lib/nokogiri/xslt.rb index 7fe33f70ed..0e334499e0 100644 --- a/lib/nokogiri/xslt.rb +++ b/lib/nokogiri/xslt.rb @@ -1,6 +1,8 @@ # coding: utf-8 # frozen_string_literal: true +require_relative "xslt/security" + module Nokogiri class << self ### @@ -19,6 +21,8 @@ def XSLT(stylesheet, modules = {}) # See Nokogiri::XSLT::Stylesheet for creating and manipulating # Stylesheet object. module XSLT + include Nokogiri::XSLT::Security + class << self # :call-seq: # parse(xsl) → Nokogiri::XSLT::Stylesheet @@ -86,6 +90,20 @@ def parse(string, modules = {}) end end + ### + # Get the default security options used by libxslt + # [Returns] an object of type Nokogiri::XSLT::Security::Config + def default_security_options + Stylesheet.default_security_options + end + + ### + # Set the default security options used by libxslt + # +options+ should be an object of type Nokogiri::XSLT::Security::Config + def default_security_options=(options) + Stylesheet.default_security_options=(options) + end + # :call-seq: # quote_params(params) → Array # diff --git a/lib/nokogiri/xslt/security.rb b/lib/nokogiri/xslt/security.rb new file mode 100644 index 0000000000..4a195e6098 --- /dev/null +++ b/lib/nokogiri/xslt/security.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module Nokogiri + module XSLT + module Security + class Config + attr_accessor :allow_read_file + attr_accessor :allow_write_file + attr_accessor :allow_create_directory + attr_accessor :allow_read_network + attr_accessor :allow_write_network + + # Mirror xslt (implicit) internal defaults + def initialize + @allow_read_file = true + @allow_write_file = true + @allow_create_directory = true + @allow_read_network = true + @allow_write_network = true + end + end + end + end +end diff --git a/nokogiri.gemspec b/nokogiri.gemspec index 6a9d2b3ef6..252f3363c1 100644 --- a/nokogiri.gemspec +++ b/nokogiri.gemspec @@ -301,6 +301,7 @@ Gem::Specification.new do |spec| "lib/nokogiri/xml/xpath/syntax_error.rb", "lib/nokogiri/xml/xpath_context.rb", "lib/nokogiri/xslt.rb", + "lib/nokogiri/xslt/security.rb", "lib/nokogiri/xslt/stylesheet.rb", "lib/xsd/xmlparser/nokogiri.rb", ] diff --git a/test/files/xslt_included.xsl b/test/files/xslt_included.xsl new file mode 100644 index 0000000000..8d32d8df51 --- /dev/null +++ b/test/files/xslt_included.xsl @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/test/files/xslt_including.xsl b/test/files/xslt_including.xsl new file mode 100644 index 0000000000..57d1849663 --- /dev/null +++ b/test/files/xslt_including.xsl @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/test/helper.rb b/test/helper.rb index ed3133c6e8..52b57d2f03 100644 --- a/test/helper.rb +++ b/test/helper.rb @@ -77,6 +77,7 @@ module TestBase XML_XINCLUDE_FILE = File.join(ASSETS_DIR, "xinclude.xml") XML_ATOM_FILE = File.join(ASSETS_DIR, "atom.xml") XSLT_FILE = File.join(ASSETS_DIR, "staff.xslt") + XSLT_INCLUDING_FILE = File.join(ASSETS_DIR, "xslt_including.xsl") XPATH_FILE = File.join(ASSETS_DIR, "slow-xpath.xml") def i_am_ruby_matching(gem_version_requirement_string) diff --git a/test/test_xslt_transforms.rb b/test/test_xslt_transforms.rb index 339ec9c68e..9e4305c2fd 100644 --- a/test/test_xslt_transforms.rb +++ b/test/test_xslt_transforms.rb @@ -187,6 +187,95 @@ def test_transform_with_quote_params assert_equal("Booyah", result_doc.at_css("h1").content) end + def test_default_security_options_read_file + options = Nokogiri::XSLT::Security::Config.new + + if Nokogiri.jruby? + assert_raises(NotImplementedError) do + Nokogiri::XSLT.default_security_options = options + end + else + # Default is insecure + Nokogiri::XSLT.default_security_options = options + assert(Nokogiri::XSLT(File.open(XSLT_INCLUDING_FILE))) + + options.allow_read_file = false + Nokogiri::XSLT.default_security_options = options + assert_raises(RuntimeError) { Nokogiri::XSLT(File.open(XSLT_INCLUDING_FILE)) } + + options.allow_read_file = true + Nokogiri::XSLT.default_security_options = options + assert(Nokogiri::XSLT(File.open(XSLT_INCLUDING_FILE))) + end + end + + def test_default_security_options_roundtrip + options = Nokogiri::XSLT::Security::Config.new + + if Nokogiri.jruby? + assert_raises(NotImplementedError) do + Nokogiri::XSLT.default_security_options + end + else + # Since the getter constructs the settings from the underlying C library + # this actually tells us if the settings are configured correctly + # Check all settings once per option to make sure fields are plumbed right + # at every stage + Nokogiri::XSLT.default_security_options = options + configured_opts = Nokogiri::XSLT.default_security_options + assert(configured_opts.allow_read_file) + assert(configured_opts.allow_write_file) + assert(configured_opts.allow_create_directory) + assert(configured_opts.allow_read_network) + assert(configured_opts.allow_write_network) + + options.allow_read_file = false + Nokogiri::XSLT.default_security_options = options + configured_opts = Nokogiri::XSLT.default_security_options + refute(configured_opts.allow_read_file) + assert(configured_opts.allow_write_file) + assert(configured_opts.allow_create_directory) + assert(configured_opts.allow_read_network) + assert(configured_opts.allow_write_network) + + options.allow_write_file = false + Nokogiri::XSLT.default_security_options = options + configured_opts = Nokogiri::XSLT.default_security_options + refute(configured_opts.allow_read_file) + refute(configured_opts.allow_write_file) + assert(configured_opts.allow_create_directory) + assert(configured_opts.allow_read_network) + assert(configured_opts.allow_write_network) + + options.allow_create_directory = false + Nokogiri::XSLT.default_security_options = options + configured_opts = Nokogiri::XSLT.default_security_options + refute(configured_opts.allow_read_file) + refute(configured_opts.allow_write_file) + refute(configured_opts.allow_create_directory) + assert(configured_opts.allow_read_network) + assert(configured_opts.allow_write_network) + + options.allow_read_network = false + Nokogiri::XSLT.default_security_options = options + configured_opts = Nokogiri::XSLT.default_security_options + refute(configured_opts.allow_read_file) + refute(configured_opts.allow_write_file) + refute(configured_opts.allow_create_directory) + refute(configured_opts.allow_read_network) + assert(configured_opts.allow_write_network) + + options.allow_write_network = false + Nokogiri::XSLT.default_security_options = options + configured_opts = Nokogiri::XSLT.default_security_options + refute(configured_opts.allow_read_file) + refute(configured_opts.allow_write_file) + refute(configured_opts.allow_create_directory) + refute(configured_opts.allow_read_network) + refute(configured_opts.allow_write_network) + end + end + def test_exslt # see http://yokolet.blogspot.com/2010/10/pure-java-nokogiri-xslt-extension.html") skip_unless_libxml2("cannot get it working on JRuby")