Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 29 additions & 6 deletions ext/json/ext/generator/generator.c
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,34 @@ static const unsigned char script_safe_escape_table[256] = {
4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 9, 9,
};

static inline bool unsafe_html_tag_p(const char *ptr, const char *end)
{
size_t remaining = end - ptr;

if (remaining < 4) return false;
if (memcmp(ptr, "<!--", 4) == 0) return true;

union { uint64_t num; char bytes[8]; } chunk;
if (remaining >= 8) {
memcpy(chunk.bytes, ptr, 8);
}
else if (remaining == 7) {
chunk.num = 0;
memcpy(chunk.bytes, ptr, 7);
}
else {
return false;
}

chunk.num |= 0x2020202020202020;

if (memcmp(chunk.bytes, "</script", 8) == 0) return true;

chunk.bytes[7] = 0;

return memcmp(chunk.bytes, "<script\0", 8) == 0;
}

static inline unsigned char search_script_safe_escape(search_state *search)
{
while (search->ptr < search->end) {
Expand All @@ -545,12 +573,7 @@ static inline unsigned char search_script_safe_escape(search_state *search)
if (RB_UNLIKELY(ch_len)) {
if (ch_len & ESCAPE_MASK) {
if (RB_UNLIKELY(ch_len == 25)) {
size_t remaining = search->end - search->ptr;
if (!(
(remaining >= 4 && memcmp(search->ptr, "<!--", 4) == 0) ||
(remaining >= 7 && memcmp(search->ptr, "<script", 7) == 0) ||
(remaining >= 8 && memcmp(search->ptr, "</script", 8) == 0)
)) {
if (!unsafe_html_tag_p(search->ptr, search->end)) {
search->ptr += 1;
continue;
}
Expand Down
32 changes: 27 additions & 5 deletions java/src/json/ext/StringEncoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,32 @@ void encodeBasic(ByteList src) throws IOException {
}
}

private boolean isUnsafeScriptTag(byte[] ptrBytes, int pos, int len) {
int remaining = len - pos;

if ((remaining >= 4 && ByteBuffer.wrap(ptrBytes, pos, 4).equals(HTML_COMMENT))) {
return true;
}

pos++;
remaining--;

if (ptrBytes[pos] == '/') {
pos++;
remaining--;
}

return (
remaining >= 6 &&
(ptrBytes[pos + 0] | 0x20) == 's' &&
(ptrBytes[pos + 1] | 0x20) == 'c' &&
(ptrBytes[pos + 2] | 0x20) == 'r' &&
(ptrBytes[pos + 3] | 0x20) == 'i' &&
(ptrBytes[pos + 4] | 0x20) == 'p' &&
(ptrBytes[pos + 5] | 0x20) == 't'
);
}

// C: convert_UTF8_to_JSON
void encode(ByteList src) throws IOException {
if (escapeTable == ESCAPE_TABLE) {
Expand Down Expand Up @@ -308,11 +334,7 @@ void encode(ByteList src) throws IOException {
int remaining = len - pos;
if (remaining > 8) remaining = 8;

if (
(remaining >= 4 && ByteBuffer.wrap(ptrBytes, pos, 4).equals(HTML_COMMENT)) ||
(remaining >= 7 && ByteBuffer.wrap(ptrBytes, pos, 7).equals(HTML_SCRIPT_OPEN)) ||
(remaining >= 8 && ByteBuffer.wrap(ptrBytes, pos, 8).equals(HTML_SCRIPT_CLOSE))
) {
if (isUnsafeScriptTag(ptrBytes, pos, len)) {
beg = pos = flushPos(pos, beg, ptrBytes, ptr, 1);
escapeAscii(ch, scratch, hexdig);
break;
Expand Down
25 changes: 6 additions & 19 deletions lib/json/truffle_ruby/generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,12 @@ module Generator
"\x1f" => '\u001f',
'"' => '\"',
'\\' => '\\\\',
'<' => '\\u003c',
}.freeze # :nodoc:

ESCAPE_PATTERN = /["\\\x0-\x1f]/

script_safe_extra = {
'<script' => '\\u003cscript',
'</script' => '\\u003c/script',
'<!--' => '\\u003c!--',
}
SCRIPT_SAFE_MAP = MAP.merge(script_safe_extra).freeze

SCRIPT_SAFE_ESCAPE_PATTERN = Regexp.union(ESCAPE_PATTERN, *script_safe_extra.keys)
SCRIPT_SAFE_ESCAPE_PATTERN = Regexp.union(ESCAPE_PATTERN, /<(?=\/?script)/i, /<(?=!--)/)

def self.native_type?(value) # :nodoc:
(false == value || true == value || nil == value || String === value || Symbol === value || Array === value || Hash === value || Integer === value || Float === value || Fragment === value)
Expand All @@ -66,18 +60,11 @@ def self.valid_encoding?(string) # :nodoc:
# Convert a UTF8 encoded Ruby string _string_ to a JSON string, encoded with
# UTF16 big endian characters as \u????, and return it.
def self.utf8_to_json(string, script_safe = false) # :nodoc:
if script_safe
if SCRIPT_SAFE_ESCAPE_PATTERN.match?(string)
string.gsub(SCRIPT_SAFE_ESCAPE_PATTERN, SCRIPT_SAFE_MAP)
else
string
end
pattern = script_safe ? SCRIPT_SAFE_ESCAPE_PATTERN : ESCAPE_PATTERN
if pattern.match?(string)
string.gsub(pattern, MAP)
else
if ESCAPE_PATTERN.match?(string)
string.gsub(ESCAPE_PATTERN, MAP)
else
string
end
string
end
end

Expand Down
4 changes: 4 additions & 0 deletions test/json/json_generator_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -761,6 +761,10 @@ def test_script_safe
data = '<ok><script><!----</script></ok>'
json = '"<ok>\\u003cscript>\\u003c!----\\u003c/script></ok>"'
assert_equal json, generate(data, script_safe: true)

data = '<ok><ScrIpT><!----</sCRiPt></ok>'
json = '"<ok>\\u003cScrIpT>\\u003c!----\\u003c/sCRiPt></ok>"'
assert_equal json, generate(data, script_safe: true)
end

def test_backslash
Expand Down
Loading