From 1e0c73d5b643707335b06abd2546a83d9439d14c Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Fri, 15 Mar 2019 13:14:17 -0300 Subject: Changes in the validation of UTF-8 All UTF-8 encoding functionality (including the escape sequence '\u') accepts all values from the original UTF-8 specification (with sequences of up to six bytes). By default, the decoding functions in the UTF-8 library do not accept invalid Unicode code points, such as surrogates. A new parameter 'nonstrict' makes them accept all code points up to (2^31)-1, as in the original UTF-8 specification. --- testes/literals.lua | 17 +++++++--- testes/utf8.lua | 92 ++++++++++++++++++++++++++++++++++------------------- 2 files changed, 72 insertions(+), 37 deletions(-) (limited to 'testes') diff --git a/testes/literals.lua b/testes/literals.lua index 76c08f12..fc45d4ad 100644 --- a/testes/literals.lua +++ b/testes/literals.lua @@ -56,16 +56,23 @@ assert("abc\z assert("\u{0}\u{00000000}\x00\0" == string.char(0, 0, 0, 0)) -- limits for 1-byte sequences -assert("\u{0}\u{7F}" == "\x00\z\x7F") +assert("\u{0}\u{7F}" == "\x00\x7F") -- limits for 2-byte sequences -assert("\u{80}\u{7FF}" == "\xC2\x80\z\xDF\xBF") +assert("\u{80}\u{7FF}" == "\xC2\x80\xDF\xBF") -- limits for 3-byte sequences -assert("\u{800}\u{FFFF}" == "\xE0\xA0\x80\z\xEF\xBF\xBF") +assert("\u{800}\u{FFFF}" == "\xE0\xA0\x80\xEF\xBF\xBF") -- limits for 4-byte sequences -assert("\u{10000}\u{10FFFF}" == "\xF0\x90\x80\x80\z\xF4\x8F\xBF\xBF") +assert("\u{10000}\u{1FFFFF}" == "\xF0\x90\x80\x80\xF7\xBF\xBF\xBF") + +-- limits for 5-byte sequences +assert("\u{200000}\u{3FFFFFF}" == "\xF8\x88\x80\x80\x80\xFB\xBF\xBF\xBF\xBF") + +-- limits for 6-byte sequences +assert("\u{4000000}\u{7FFFFFFF}" == + "\xFC\x84\x80\x80\x80\x80\xFD\xBF\xBF\xBF\xBF\xBF") -- Error in escape sequences @@ -94,7 +101,7 @@ lexerror([["xyz\300"]], [[\300"]]) lexerror([[" \256"]], [[\256"]]) -- errors in UTF-8 sequences -lexerror([["abc\u{110000}"]], [[abc\u{110000]]) -- too large +lexerror([["abc\u{100000000}"]], [[abc\u{100000000]]) -- too large lexerror([["abc\u11r"]], [[abc\u1]]) -- missing '{' lexerror([["abc\u"]], [[abc\u"]]) -- missing '{' lexerror([["abc\u{11r"]], [[abc\u{11r]]) -- missing '}' diff --git a/testes/utf8.lua b/testes/utf8.lua index 4b6a57fd..86ec1b00 100644 --- a/testes/utf8.lua +++ b/testes/utf8.lua @@ -21,62 +21,59 @@ local justone = "^" .. utf8.charpattern .. "$" -- 't' is the list of codepoints of 's' local function checksyntax (s, t) + -- creates a string "return '\u{t[1]}...\u{t[n]}'" local ts = {"return '"} for i = 1, #t do ts[i + 1] = string.format("\\u{%x}", t[i]) end ts[#t + 2] = "'" ts = table.concat(ts) + -- its execution should result in 's' assert(assert(load(ts))() == s) end assert(utf8.offset("alo", 5) == nil) assert(utf8.offset("alo", -4) == nil) --- 't' is the list of codepoints of 's' -local function check (s, t) - local l = utf8.len(s) +-- 'check' makes several tests over the validity of string 's'. +-- 't' is the list of codepoints of 's'. +local function check (s, t, nonstrict) + local l = utf8.len(s, 1, -1, nonstrict) assert(#t == l and len(s) == l) - assert(utf8.char(table.unpack(t)) == s) + assert(utf8.char(table.unpack(t)) == s) -- 't' and 's' are equivalent assert(utf8.offset(s, 0) == 1) checksyntax(s, t) - local t1 = {utf8.codepoint(s, 1, -1)} + -- creates new table with all codepoints of 's' + local t1 = {utf8.codepoint(s, 1, -1, nonstrict)} assert(#t == #t1) - for i = 1, #t do assert(t[i] == t1[i]) end + for i = 1, #t do assert(t[i] == t1[i]) end -- 't' is equal to 't1' - for i = 1, l do + for i = 1, l do -- for all codepoints local pi = utf8.offset(s, i) -- position of i-th char local pi1 = utf8.offset(s, 2, pi) -- position of next char assert(string.find(string.sub(s, pi, pi1 - 1), justone)) assert(utf8.offset(s, -1, pi1) == pi) assert(utf8.offset(s, i - l - 1) == pi) - assert(pi1 - pi == #utf8.char(utf8.codepoint(s, pi))) - for j = pi, pi1 - 1 do + assert(pi1 - pi == #utf8.char(utf8.codepoint(s, pi, pi, nonstrict))) + for j = pi, pi1 - 1 do assert(utf8.offset(s, 0, j) == pi) end for j = pi + 1, pi1 - 1 do assert(not utf8.len(s, j)) end - assert(utf8.len(s, pi, pi) == 1) - assert(utf8.len(s, pi, pi1 - 1) == 1) - assert(utf8.len(s, pi) == l - i + 1) - assert(utf8.len(s, pi1) == l - i) - assert(utf8.len(s, 1, pi) == i) + assert(utf8.len(s, pi, pi, nonstrict) == 1) + assert(utf8.len(s, pi, pi1 - 1, nonstrict) == 1) + assert(utf8.len(s, pi, -1, nonstrict) == l - i + 1) + assert(utf8.len(s, pi1, -1, nonstrict) == l - i) + assert(utf8.len(s, 1, pi, -1, nonstrict) == i) end local i = 0 - for p, c in utf8.codes(s) do + for p, c in utf8.codes(s, nonstrict) do i = i + 1 assert(c == t[i] and p == utf8.offset(s, i)) - assert(utf8.codepoint(s, p) == c) - end - assert(i == #t) - - i = 0 - for p, c in utf8.codes(s) do - i = i + 1 - assert(c == t[i] and p == utf8.offset(s, i)) + assert(utf8.codepoint(s, p, p, nonstrict) == c) end assert(i == #t) @@ -105,13 +102,17 @@ do -- error indication in utf8.len check("\xF4\x9F\xBF\xBF", 1) end --- error in utf8.codes -checkerror("invalid UTF%-8 code", - function () - local s = "ab\xff" - for c in utf8.codes(s) do assert(c) end - end) - +-- errors in utf8.codes +do + local function errorcodes (s) + checkerror("invalid UTF%-8 code", + function () + for c in utf8.codes(s) do assert(c) end + end) + end + errorcodes("ab\xff") + errorcodes("\u{110000}") +end -- error in initial position for offset checkerror("position out of range", utf8.offset, "abc", 1, 5) @@ -141,14 +142,22 @@ do assert(#t == 0) checkerror("out of range", utf8.codepoint, s, -(#s + 1), 1) checkerror("out of range", utf8.codepoint, s, 1, #s + 1) + -- surrogates + assert(utf8.codepoint("\u{D7FF}") == 0xD800 - 1) + assert(utf8.codepoint("\u{E000}") == 0xDFFF + 1) + assert(utf8.codepoint("\u{D800}", 1, 1, true) == 0xD800) + assert(utf8.codepoint("\u{DFFF}", 1, 1, true) == 0xDFFF) + assert(utf8.codepoint("\u{7FFFFFFF}", 1, 1, true) == 0x7FFFFFFF) end assert(utf8.char() == "") -assert(utf8.char(97, 98, 99) == "abc") +assert(utf8.char(0, 97, 98, 99, 1) == "\0abc\1") assert(utf8.codepoint(utf8.char(0x10FFFF)) == 0x10FFFF) +assert(utf8.codepoint(utf8.char(0x7FFFFFFF), 1, 1, true) == (1<<31) - 1) -checkerror("value out of range", utf8.char, 0x10FFFF + 1) +checkerror("value out of range", utf8.char, 0x7FFFFFFF + 1) +checkerror("value out of range", utf8.char, -1) local function invalid (s) checkerror("invalid UTF%-8 code", utf8.codepoint, s) @@ -158,6 +167,10 @@ end -- UTF-8 representation for 0x11ffff (value out of valid range) invalid("\xF4\x9F\xBF\xBF") +-- surrogates +invalid("\u{D800}") +invalid("\u{DFFF}") + -- overlong sequences invalid("\xC0\x80") -- zero invalid("\xC1\xBF") -- 0x7F (should be coded in 1 byte) @@ -183,6 +196,21 @@ s = "\0 \x7F\z s = string.gsub(s, " ", "") check(s, {0,0x7F, 0x80,0x7FF, 0x800,0xFFFF, 0x10000,0x10FFFF}) +do + -- original UTF-8 values + local s = "\u{4000000}\u{7FFFFFFF}" + assert(#s == 12) + check(s, {0x4000000, 0x7FFFFFFF}, true) + + s = "\u{200000}\u{3FFFFFF}" + assert(#s == 10) + check(s, {0x200000, 0x3FFFFFF}, true) + + s = "\u{10000}\u{1fffff}" + assert(#s == 8) + check(s, {0x10000, 0x1FFFFF}, true) +end + x = "日本語a-4\0éó" check(x, {26085, 26412, 35486, 97, 45, 52, 0, 233, 243}) -- cgit v1.2.3-55-g6feb