diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 803b281..236acef 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,10 +7,12 @@ jobs: name: build (${{ matrix.ruby }} / ${{ matrix.os }}) strategy: matrix: - ruby: [ '3.0', 2.7, 2.6, 2.5, head ] + ruby: [ '3.0', 2.7, 2.6, 2.5, head, jruby ] os: [ ubuntu-latest, macos-latest, windows-latest ] exclude: - { os: windows-latest, ruby: head } + - { os: macos-latest, ruby: jruby } + - { os: windows-latest, ruby: jruby } include: - { os: windows-latest, ruby: mingw } - { os: windows-latest, ruby: mswin } diff --git a/.gitignore b/.gitignore index 807ef0d..89d6c24 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ /tmp/ /ChangeLog /Gemfile.lock +*.jar *.bundle *.so *.dll diff --git a/Rakefile b/Rakefile index f97e042..77dd3cd 100644 --- a/Rakefile +++ b/Rakefile @@ -1,14 +1,30 @@ require "bundler/gem_tasks" require "rake/testtask" -require 'rake/extensiontask' -extask = Rake::ExtensionTask.new("cgi/escape") do |x| - x.lib_dir.sub!(%r[(?=/|\z)], "/#{RUBY_VERSION}/#{x.platform}") +require 'rake/javaextensiontask' +Rake::JavaExtensionTask.new("escape") do |ext| + ext.source_version = '1.8' + ext.target_version = '1.8' + ext.ext_dir = 'ext/java' + ext.lib_dir = 'lib/cgi' + + task :build => :compile +end + +unless RUBY_ENGINE == 'jruby' + require 'rake/extensiontask' + extask = Rake::ExtensionTask.new("cgi/escape") do |x| + x.lib_dir.sub!(%r[(?=/|\z)], "/#{RUBY_VERSION}/#{x.platform}") + end end Rake::TestTask.new(:test) do |t| - t.libs << "lib/#{RUBY_VERSION}/#{extask.platform}" t.libs << "test/lib" + if RUBY_ENGINE == 'jruby' + t.libs << "ext/java/org/jruby/ext/cgi/escape/lib" + else + t.libs << "lib/#{RUBY_VERSION}/#{extask.platform}" + end t.ruby_opts << "-rhelper" t.test_files = FileList['test/**/test_*.rb'] end diff --git a/cgi.gemspec b/cgi.gemspec index 3ba62b9..381c55a 100644 --- a/cgi.gemspec +++ b/cgi.gemspec @@ -22,10 +22,21 @@ Gem::Specification.new do |spec| spec.metadata["homepage_uri"] = spec.homepage spec.metadata["source_code_uri"] = spec.homepage - spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do - `git ls-files -z 2>/dev/null`.split("\x0").reject { |f| f.match(%r{\A(?:(?:test|spec|features)/|\.git)}) } - end - spec.extensions = ["ext/cgi/escape/extconf.rb"] spec.executables = [] + + spec.files = [ + "LICENSE.txt", + "README.md", + *Dir["lib{.rb,/**/*.rb}", "bin/*"] ] + spec.require_paths = ["lib"] + + if Gem::Platform === spec.platform and spec.platform =~ 'java' or RUBY_ENGINE == 'jruby' + spec.platform = 'java' + spec.require_paths << "ext/java/org/jruby/ext/cgi/escape/lib" + spec.files += Dir["ext/java/**/*.{rb}", "lib/cgi/escape.jar"] + else + spec.files += Dir["ext/cgi/**/*.{rb,c,h,sh}", "ext/cgi/escape/depend", "lib/cgi/escape.so"] + spec.extensions = ["ext/cgi/escape/extconf.rb"] + end end diff --git a/ext/java/org/jruby/ext/cgi/escape/CGIEscape.java b/ext/java/org/jruby/ext/cgi/escape/CGIEscape.java new file mode 100644 index 0000000..956eebe --- /dev/null +++ b/ext/java/org/jruby/ext/cgi/escape/CGIEscape.java @@ -0,0 +1,516 @@ +/* + **** BEGIN LICENSE BLOCK ***** + * BSD 2-Clause License + * + * Copyright (c) 2016, Charles Oliver Nutter + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ***** END LICENSE BLOCK *****/ + +package org.jruby.ext.cgi.escape; + +import org.jcodings.Encoding; +import org.jcodings.specific.ISO8859_1Encoding; +import org.jcodings.specific.UTF8Encoding; +import org.jruby.Ruby; +import org.jruby.RubyClass; +import org.jruby.RubyEncoding; +import org.jruby.RubyModule; +import org.jruby.RubyString; +import org.jruby.anno.JRubyMethod; +import org.jruby.runtime.Block; +import org.jruby.runtime.Helpers; +import org.jruby.runtime.ThreadContext; +import org.jruby.runtime.builtin.IRubyObject; +import org.jruby.runtime.load.Library; +import org.jruby.util.ByteList; +import org.jruby.util.StringSupport; +import org.jruby.util.io.EncodingUtils; + +public class CGIEscape implements Library { + public static final String ACCEPT_CHARSET = "@@accept_charset"; + public static final byte[] NO39 = "'".getBytes(RubyEncoding.UTF8); + public static final byte[] AMP = "&".getBytes(RubyEncoding.UTF8); + public static final byte[] QUOT = """.getBytes(RubyEncoding.UTF8); + public static final byte[] LT = "<".getBytes(RubyEncoding.UTF8); + public static final byte[] GT = ">".getBytes(RubyEncoding.UTF8); + public static final int UNICODE_MAX = 0x10ffff; + public static final byte[] TSEMI = "t;".getBytes(RubyEncoding.UTF8); + public static final byte[] UOTSEMI = "uot;".getBytes(RubyEncoding.UTF8); + public static final byte[] MPSEMI = "mp;".getBytes(RubyEncoding.UTF8); + public static final byte[] POSSEMI = "pos;".getBytes(RubyEncoding.UTF8); + + static void html_escaped_cat(RubyString str, byte c) { + switch (c) { + case '\'': + str.cat(NO39); + break; + case '&': + str.cat(AMP); + break; + case '"': + str.cat(QUOT); + break; + case '<': + str.cat(LT); + break; + case '>': + str.cat(GT); + break; + } + } + + static void preserve_original_state(RubyString orig, RubyString dest) { + dest.setEncoding(orig.getEncoding()); + + dest.infectBy(orig); + } + + static IRubyObject + optimized_escape_html(Ruby runtime, RubyString str) { + int i, len, beg = 0; + RubyString dest = null; + byte[] cstrBytes; + + len = str.size(); + ByteList byteList = str.getByteList(); + cstrBytes = byteList.unsafeBytes(); + int cstr = byteList.begin(); + + for (i = 0; i < len; i++) { + switch (cstrBytes[cstr + i]) { + case '\'': + case '&': + case '"': + case '<': + case '>': + if (dest == null) { + dest = RubyString.newStringLight(runtime, len); + } + + dest.cat(cstrBytes, cstr + beg, i - beg); + beg = i + 1; + + html_escaped_cat(dest, cstrBytes[cstr + i]); + break; + } + } + + if (dest != null) { + dest.cat(cstrBytes, cstr + beg, len - beg); + preserve_original_state(str, dest); + return dest; + } else { + return str.strDup(runtime); + } + } + + // Set of i has to happen outside this; see MATCH macro in MRI ext/cgi/escape/escape.c + static boolean MATCH(byte[] s, int len, int i, byte[] cstrBytes, int cstr) { + if (len - i >= s.length && ByteList.memcmp(cstrBytes, cstr + i, s, 0, s.length) == 0) { + return true; + } else { + return false; + } + } + + static IRubyObject + optimized_unescape_html(Ruby runtime, RubyString str) { + Encoding enc = str.getEncoding(); + int charlimit = (enc instanceof UTF8Encoding) ? UNICODE_MAX : + (enc instanceof ISO8859_1Encoding) ? 256 : + 128; + int i, len, beg = 0; + int clen = 0, plen; + boolean overflow = false; + byte[] cstrBytes; + int cstr; + byte[] buf = new byte[6]; + RubyString dest = null; + + len = str.size(); + ByteList byteList = str.getByteList(); + cstrBytes = byteList.getUnsafeBytes(); + cstr = byteList.begin(); + + for (i = 0; i < len; i++) { + int cc; + int c = cstrBytes[cstr + i]; + if (c != '&') continue; + plen = i - beg; + if (++i >= len) break; + c = cstrBytes[cstr + i] & 0xFF; + switch (c) { + case 'a': + ++i; + if (MATCH(POSSEMI, len, i, cstrBytes, cstr)) { + i += POSSEMI.length - 1; + c = '\''; + } else if (MATCH(MPSEMI, len, i, cstrBytes, cstr)) { + i += MPSEMI.length - 1; + c = '&'; + } else continue; + break; + case 'q': + ++i; + if (MATCH(UOTSEMI, len, i, cstrBytes, cstr)) { + i += UOTSEMI.length - 1; + c = '"'; + } else continue; + break; + case 'g': + ++i; + if (MATCH(TSEMI, len, i, cstrBytes, cstr)) { + i += TSEMI.length - 1; + c = '>'; + } else continue; + break; + case 'l': + ++i; + if (MATCH(TSEMI, len, i, cstrBytes, cstr)) { + i += TSEMI.length - 1; + c = '<'; + } else continue; + break; + case '#': + if (len - ++i >= 2 && Character.isDigit(cstrBytes[cstr + i])) { + int[] clenOverflow = {clen, overflow ? 1 : 0}; + cc = ruby_scan_digits(cstrBytes, cstr + i, len - i, 10, clenOverflow); + clen = clenOverflow[0]; + overflow = clenOverflow[1] == 1; + } else if (i < len && (cstrBytes[cstr + i] == 'x' || cstrBytes[cstr + i] == 'X') && len - ++i >= 2 && ISXDIGIT(cstrBytes, cstr + i)) { + int[] clenOverflow = {clen, overflow ? 1 : 0}; + cc = ruby_scan_digits(cstrBytes, cstr + i, len - i, 16, clenOverflow); + clen = clenOverflow[0]; + overflow = clenOverflow[1] == 1; + } else continue; + i += clen; + if (overflow || cc >= charlimit || i >= len || cstrBytes[cstr + i] != ';') continue; + if (dest == null) { + dest = RubyString.newStringLight(runtime, len); + } + dest.cat(cstrBytes, cstr + beg, plen); + if (charlimit > 256) { + dest.cat(buf, 0, enc.codeToMbc(cc, buf, 0)); + } else { + c = cc; + dest.cat(c); + } + beg = i + 1; + continue; + default: + --i; + continue; + } + if (dest == null) { + dest = RubyString.newStringLight(runtime, len); + } + dest.cat(cstrBytes, cstr + beg, plen); + dest.cat(c); + beg = i + 1; + } + + if (dest != null) { + dest.cat(cstrBytes, cstr + beg, len - beg); + preserve_original_state(str, dest); + return dest; + } else { + return str.strDup(runtime); + } + } + + static boolean ISXDIGIT(byte[] cstrBytes, int i) { + byte cstrByte = cstrBytes[i]; + return (cstrByte >= '0' && cstrByte <= '9') || (cstrByte >= 'a' && cstrByte <= 'f') || (cstrByte >= 'A' && cstrByte <= 'F'); + } + + static boolean url_unreserved_char(int c) { + switch (c) { + case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': + case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': + case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': + case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': + case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': + case '-': case '.': case '_': case '~': + return true; + default: + break; + } + return false; + } + + static final byte[] upper_hexdigits = "0123456789ABCDEF".getBytes(RubyEncoding.UTF8); + + static IRubyObject optimized_escape(Ruby runtime, RubyString str) { + int i, len, beg = 0; + RubyString dest = null; + byte[] cstrBytes; + int cstr; + byte[] buf = {'%', 0, 0}; + + len = str.size(); + ByteList byteList = str.getByteList(); + cstrBytes = byteList.unsafeBytes(); + cstr = byteList.begin(); + + for (i = 0; i < len; ++i) { + int c = cstrBytes[cstr + i] & 0xFF; + if (!url_unreserved_char(c)) { + if (dest == null) { + dest = RubyString.newStringLight(runtime, len); + } + + dest.cat(cstrBytes, cstr + beg, i - beg); + beg = i + 1; + + if (c == ' ') { + dest.cat('+'); + } else { + buf[1] = upper_hexdigits[(c >> 4) & 0xf]; + buf[2] = upper_hexdigits[c & 0xf]; + dest.cat(buf, 0, 3); + } + } + } + + if (dest != null) { + dest.cat(cstrBytes, cstr + beg, len - beg); + preserve_original_state(str, dest); + return dest; + } else { + return str.strDup(runtime); + } + } + + static IRubyObject + optimized_unescape(ThreadContext context, RubyString str, IRubyObject encoding) { + int i, len, beg = 0; + RubyString dest = null; + byte[] cstrBytes; + int cstr; + int cr; + Encoding origenc, encidx = EncodingUtils.rbToEncoding(context, encoding); + + len = str.size(); + ByteList byteList = str.getByteList(); + cstrBytes = byteList.unsafeBytes(); + cstr = byteList.begin(); + + int buf = 0; + Ruby runtime = context.runtime; + + for (i = 0; i < len; ++i) { + int c = cstrBytes[cstr + i] & 0xFF; + int clen = 0; + if (c == '%') { + if (i + 3 > len) break; + if (!ISXDIGIT(cstrBytes, cstr + i + 1)) continue; + if (!ISXDIGIT(cstrBytes, cstr + i + 2)) continue; + buf = ((char_to_number(cstrBytes[cstr + i + 1]) << 4) + | char_to_number(cstrBytes[cstr + i + 2])); + clen = 2; + } else if (c == '+') { + buf = ' '; + } else { + continue; + } + + if (dest == null) { + dest = RubyString.newStringLight(runtime, len); + } + + dest.cat(cstrBytes, cstr + beg, i - beg); + i += clen; + beg = i + 1; + + dest.cat(buf); + } + + if (dest != null) { + dest.cat(cstrBytes, cstr + beg, len - beg); + preserve_original_state(str, dest); + cr = StringSupport.CR_UNKNOWN; + } else { + dest = str.strDup(runtime); + cr = str.getCodeRange(); + } + origenc = str.getEncoding(); + if (origenc != encidx) { + dest.setEncoding(encidx); + if (StringSupport.encCoderangeClean(dest.getCodeRange()) == 0) { + dest.setEncoding(origenc); + if (cr != StringSupport.CR_UNKNOWN) + dest.setCodeRange(cr); + } + } + return dest; + } + + /* + * call-seq: + * CGI.escapeHTML(string) -> string + * + * Returns HTML-escaped string. + * + */ + @JRubyMethod(name = "escapeHTML", module = true, frame = true) + public static IRubyObject cgiesc_escape_html(ThreadContext context, IRubyObject self, IRubyObject _str) { + RubyString str = _str.convertToString(); + + if (str.getEncoding().isAsciiCompatible()) { + return optimized_escape_html(context.runtime, str); + } else { + return Helpers.invokeSuper(context, self, _str, Block.NULL_BLOCK); + } + } + + /* + * call-seq: + * CGI.unescapeHTML(string) -> string + * + * Returns HTML-unescaped string. + * + */ + @JRubyMethod(name = "unescapeHTML", module = true, frame = true) + public static IRubyObject cgiesc_unescape_html(ThreadContext context, IRubyObject self, IRubyObject _str) { + RubyString str = _str.convertToString(); + + if (str.getEncoding().isAsciiCompatible()) { + return optimized_unescape_html(context.runtime, str); + } else { + return Helpers.invokeSuper(context, self, _str, Block.NULL_BLOCK); + } + } + + /* + * call-seq: + * CGI.escape(string) -> string + * + * Returns URL-escaped string. + * + */ + @JRubyMethod(name = "escape", module = true, frame = true) + public static IRubyObject cgiesc_escape(ThreadContext context, IRubyObject self, IRubyObject _str) { + RubyString str = _str.convertToString(); + + if (str.getEncoding().isAsciiCompatible()) { + return optimized_escape(context.runtime, str); + } else { + return Helpers.invokeSuper(context, self, _str, Block.NULL_BLOCK); + } + } + + static IRubyObject accept_charset(IRubyObject[] args, int argc, int argv, IRubyObject self) { + if (argc > 0) + return args[argv]; + return self.getMetaClass().getClassVar(ACCEPT_CHARSET); + } + + /* + * call-seq: + * CGI.unescape(string, encoding=@@accept_charset) -> string + * + * Returns URL-unescaped string. + * + */ + @JRubyMethod(name = "unescape", required = 1, optional = 1, module = true, frame = true) + public static IRubyObject cgiesc_unescape(ThreadContext context, IRubyObject self, IRubyObject[] argv) { + IRubyObject _str = argv[0]; + + RubyString str = _str.convertToString(); + + if (str.getEncoding().isAsciiCompatible()) { + IRubyObject enc = accept_charset(argv, argv.length - 1, 1, self); + return optimized_unescape(context, str, enc); + } else { + return Helpers.invokeSuper(context, self, argv, Block.NULL_BLOCK); + } + } + + public void load(Ruby runtime, boolean wrap) { + RubyClass rb_cCGI = runtime.defineClass("CGI", runtime.getObject(), runtime.getObject().getAllocator()); + RubyModule rb_mEscape = rb_cCGI.defineModuleUnder("Escape"); + RubyModule rb_mUtil = rb_cCGI.defineModuleUnder("Util"); + rb_mEscape.defineAnnotatedMethods(CGIEscape.class); + rb_mUtil.prependModule(rb_mEscape); + rb_mEscape.extend_object(rb_cCGI); + } + + // PORTED FROM OTHER FILES IN MRI + + static int ruby_scan_digits(byte[] strBytes, int str, int len, int base, int[] retlenOverflow) { + int start = str; + int ret = 0, x; + int mul_overflow = Integer.MAX_VALUE / base; + + retlenOverflow[1] = 0; + + if (len == 0) { + retlenOverflow[0] = 0; + return 0; + } + + do { + int d = ruby_digit36_to_number_table[strBytes[str++]]; + if (d == -1 || base <= d) { + --str; + break; + } + if (mul_overflow < ret) + retlenOverflow[1] = 1; + ret *= base; + x = ret; + ret += d; + if (ret < x) + retlenOverflow[1] = 1; + } while (len < 0 || (--len != 0)); + retlenOverflow[0] = str - start; + return ret; + } + + static int char_to_number(int c) { + return ruby_digit36_to_number_table[c]; + } + + private static final int ruby_digit36_to_number_table[] = { + /* 0 1 2 3 4 5 6 7 8 9 a b c d e f */ + /*0*/ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + /*1*/ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + /*2*/ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + /*3*/ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, + /*4*/ -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + /*5*/ 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, + /*6*/ -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + /*7*/ 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, + /*8*/ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + /*9*/ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + /*a*/ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + /*b*/ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + /*c*/ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + /*d*/ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + /*e*/ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + /*f*/ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + }; +} diff --git a/ext/java/org/jruby/ext/cgi/escape/lib/cgi/escape.rb b/ext/java/org/jruby/ext/cgi/escape/lib/cgi/escape.rb new file mode 100644 index 0000000..f5672e2 --- /dev/null +++ b/ext/java/org/jruby/ext/cgi/escape/lib/cgi/escape.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +# Load built-in cgi/escape library +require 'cgi/escape.jar' +JRuby::Util.load_ext("org.jruby.ext.cgi.escape.CGIEscape") diff --git a/rakelib/check.rake b/rakelib/check.rake new file mode 100644 index 0000000..16b4d3b --- /dev/null +++ b/rakelib/check.rake @@ -0,0 +1,40 @@ +task :check do + Bundler.with_unbundled_env do + spec = Gem::Specification::load("cgi.gemspec") + version = spec.version.to_s + + gem = "pkg/cgi-#{version}#{"-java" if RUBY_ENGINE == "jruby"}.gem" + File.size?(gem) or abort "gem not built!" + + sh "gem", "install", gem + + require_relative "../test/lib/envutil" + + _, _, status = EnvUtil.invoke_ruby([], <<~EOS) + version = #{version.dump} + gem "cgi", version + loaded_version = Gem.loaded_specs["cgi"].version.to_s + if loaded_version == version + puts "cgi \#{loaded_version} is loaded." + else + abort "cgi \#{loaded_version} is loaded instead of \#{version}!" + end + require "cgi/escape" + + string = "&<>" + actual = CGI.escape(string) + expected = "%26%3C%3E" + puts "CGI.escape(\#{string.dump}) = \#{actual.dump}" + if actual != expected + abort "no! expected to be \#{expected.dump}!" + end + EOS + + if status.success? + puts "check succeeded!" + else + warn "check failed!" + exit status.exitstatus + end + end +end diff --git a/test/cgi/test_cgi_util.rb b/test/cgi/test_cgi_util.rb index 5a2d07b..5baf87d 100644 --- a/test/cgi/test_cgi_util.rb +++ b/test/cgi/test_cgi_util.rb @@ -105,6 +105,7 @@ def test_cgi_escape_html_dont_freeze end def test_cgi_escape_html_large + return if RUBY_ENGINE == 'jruby' ulong_max, size_max = RbConfig::LIMITS.values_at("ULONG_MAX", "SIZE_MAX") return unless ulong_max < size_max # Platforms not concerned diff --git a/test/lib/core_assertions.rb b/test/lib/core_assertions.rb index bac3856..8e0600e 100644 --- a/test/lib/core_assertions.rb +++ b/test/lib/core_assertions.rb @@ -276,6 +276,7 @@ def assert_separately(args, file = nil, line = nil, src, ignore_stderr: nil, **o eom args = args.dup args.insert((Hash === args.first ? 1 : 0), "-w", "--disable=gems", *$:.map {|l| "-I#{l}"}) + args << "--debug" if RUBY_ENGINE == 'jruby' # warning: tracing (e.g. set_trace_func) will not capture all events without --debug flag stdout, stderr, status = EnvUtil.invoke_ruby(args, src, capture_stdout, true, **opt) ensure if res_c