Skip to content

Commit 1e0c73d

Browse files
committed
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.
1 parent 8fa4f13 commit 1e0c73d

6 files changed

Lines changed: 164 additions & 72 deletions

File tree

llex.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,7 @@ static unsigned long readutf8esc (LexState *ls) {
335335
while ((save_and_next(ls), lisxdigit(ls->current))) {
336336
i++;
337337
r = (r << 4) + luaO_hexavalue(ls->current);
338-
esccheck(ls, r <= 0x10FFFF, "UTF-8 value too large");
338+
esccheck(ls, r <= 0x7FFFFFFFu, "UTF-8 value too large");
339339
}
340340
esccheck(ls, ls->current == '}', "missing '}'");
341341
next(ls); /* skip '}' */

lobject.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,7 @@ size_t luaO_str2num (const char *s, TValue *o) {
343343

344344
int luaO_utf8esc (char *buff, unsigned long x) {
345345
int n = 1; /* number of bytes put in buffer (backwards) */
346-
lua_assert(x <= 0x10FFFF);
346+
lua_assert(x <= 0x7FFFFFFFu);
347347
if (x < 0x80) /* ascii? */
348348
buff[UTF8BUFFSZ - 1] = cast_char(x);
349349
else { /* need continuation bytes */
@@ -435,9 +435,9 @@ const char *luaO_pushvfstring (lua_State *L, const char *fmt, va_list argp) {
435435
pushstr(L, buff, l);
436436
break;
437437
}
438-
case 'U': { /* an 'int' as a UTF-8 sequence */
438+
case 'U': { /* a 'long' as a UTF-8 sequence */
439439
char buff[UTF8BUFFSZ];
440-
int l = luaO_utf8esc(buff, cast(long, va_arg(argp, long)));
440+
int l = luaO_utf8esc(buff, va_arg(argp, long));
441441
pushstr(L, buff + UTF8BUFFSZ - l, l);
442442
break;
443443
}

lutf8lib.c

Lines changed: 49 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,14 @@
2121
#include "lualib.h"
2222

2323

24-
#define MAXUNICODE 0x10FFFF
24+
#define MAXUNICODE 0x10FFFFu
25+
26+
#define MAXUTF 0x7FFFFFFFu
2527

2628
/*
27-
** Integer type for decoded UTF-8 values; MAXUNICODE needs 21 bits.
29+
** Integer type for decoded UTF-8 values; MAXUTF needs 31 bits.
2830
*/
29-
#if LUAI_BITSINT >= 21
31+
#if LUAI_BITSINT >= 31
3032
typedef unsigned int utfint;
3133
#else
3234
typedef unsigned long utfint;
@@ -46,51 +48,60 @@ static lua_Integer u_posrelat (lua_Integer pos, size_t len) {
4648

4749

4850
/*
49-
** Decode one UTF-8 sequence, returning NULL if byte sequence is invalid.
51+
** Decode one UTF-8 sequence, returning NULL if byte sequence is
52+
** invalid. The array 'limits' stores the minimum value for each
53+
** sequence length, to check for overlong representations. Its first
54+
** entry forces an error for non-ascii bytes with no continuation
55+
** bytes (count == 0).
5056
*/
51-
static const char *utf8_decode (const char *o, utfint *val) {
52-
static const unsigned int limits[] = {0xFF, 0x7F, 0x7FF, 0xFFFF};
53-
const unsigned char *s = (const unsigned char *)o;
54-
unsigned int c = s[0];
57+
static const char *utf8_decode (const char *s, utfint *val, int strict) {
58+
static const utfint limits[] =
59+
{~(utfint)0, 0x80, 0x800, 0x10000u, 0x200000u, 0x4000000u};
60+
unsigned int c = (unsigned char)s[0];
5561
utfint res = 0; /* final result */
5662
if (c < 0x80) /* ascii? */
5763
res = c;
5864
else {
5965
int count = 0; /* to count number of continuation bytes */
60-
while (c & 0x40) { /* still have continuation bytes? */
61-
int cc = s[++count]; /* read next byte */
66+
for (; c & 0x40; c <<= 1) { /* while it needs continuation bytes... */
67+
unsigned int cc = (unsigned char)s[++count]; /* read next byte */
6268
if ((cc & 0xC0) != 0x80) /* not a continuation byte? */
6369
return NULL; /* invalid byte sequence */
6470
res = (res << 6) | (cc & 0x3F); /* add lower 6 bits from cont. byte */
65-
c <<= 1; /* to test next bit */
6671
}
6772
res |= ((utfint)(c & 0x7F) << (count * 5)); /* add first byte */
68-
if (count > 3 || res > MAXUNICODE || res <= limits[count])
73+
if (count > 5 || res > MAXUTF || res < limits[count])
6974
return NULL; /* invalid byte sequence */
7075
s += count; /* skip continuation bytes read */
7176
}
77+
if (strict) {
78+
/* check for invalid code points; too large or surrogates */
79+
if (res > MAXUNICODE || (0xD800u <= res && res <= 0xDFFFu))
80+
return NULL;
81+
}
7282
if (val) *val = res;
73-
return (const char *)s + 1; /* +1 to include first byte */
83+
return s + 1; /* +1 to include first byte */
7484
}
7585

7686

7787
/*
78-
** utf8len(s [, i [, j]]) --> number of characters that start in the
79-
** range [i,j], or nil + current position if 's' is not well formed in
80-
** that interval
88+
** utf8len(s [, i [, j [, nonstrict]]]) --> number of characters that
89+
** start in the range [i,j], or nil + current position if 's' is not
90+
** well formed in that interval
8191
*/
8292
static int utflen (lua_State *L) {
8393
lua_Integer n = 0; /* counter for the number of characters */
8494
size_t len; /* string length in bytes */
8595
const char *s = luaL_checklstring(L, 1, &len);
8696
lua_Integer posi = u_posrelat(luaL_optinteger(L, 2, 1), len);
8797
lua_Integer posj = u_posrelat(luaL_optinteger(L, 3, -1), len);
98+
int nonstrict = lua_toboolean(L, 4);
8899
luaL_argcheck(L, 1 <= posi && --posi <= (lua_Integer)len, 2,
89100
"initial position out of string");
90101
luaL_argcheck(L, --posj < (lua_Integer)len, 3,
91102
"final position out of string");
92103
while (posi <= posj) {
93-
const char *s1 = utf8_decode(s + posi, NULL);
104+
const char *s1 = utf8_decode(s + posi, NULL, !nonstrict);
94105
if (s1 == NULL) { /* conversion error? */
95106
lua_pushnil(L); /* return nil ... */
96107
lua_pushinteger(L, posi + 1); /* ... and current position */
@@ -105,14 +116,15 @@ static int utflen (lua_State *L) {
105116

106117

107118
/*
108-
** codepoint(s, [i, [j]]) -> returns codepoints for all characters
109-
** that start in the range [i,j]
119+
** codepoint(s, [i, [j [, nonstrict]]]) -> returns codepoints for all
120+
** characters that start in the range [i,j]
110121
*/
111122
static int codepoint (lua_State *L) {
112123
size_t len;
113124
const char *s = luaL_checklstring(L, 1, &len);
114125
lua_Integer posi = u_posrelat(luaL_optinteger(L, 2, 1), len);
115126
lua_Integer pose = u_posrelat(luaL_optinteger(L, 3, posi), len);
127+
int nonstrict = lua_toboolean(L, 4);
116128
int n;
117129
const char *se;
118130
luaL_argcheck(L, posi >= 1, 2, "out of range");
@@ -126,7 +138,7 @@ static int codepoint (lua_State *L) {
126138
se = s + pose; /* string end */
127139
for (s += posi - 1; s < se;) {
128140
utfint code;
129-
s = utf8_decode(s, &code);
141+
s = utf8_decode(s, &code, !nonstrict);
130142
if (s == NULL)
131143
return luaL_error(L, "invalid UTF-8 code");
132144
lua_pushinteger(L, code);
@@ -137,8 +149,8 @@ static int codepoint (lua_State *L) {
137149

138150

139151
static void pushutfchar (lua_State *L, int arg) {
140-
lua_Integer code = luaL_checkinteger(L, arg);
141-
luaL_argcheck(L, 0 <= code && code <= MAXUNICODE, arg, "value out of range");
152+
lua_Unsigned code = (lua_Unsigned)luaL_checkinteger(L, arg);
153+
luaL_argcheck(L, code <= MAXUTF, arg, "value out of range");
142154
lua_pushfstring(L, "%U", (long)code);
143155
}
144156

@@ -209,7 +221,7 @@ static int byteoffset (lua_State *L) {
209221
}
210222

211223

212-
static int iter_aux (lua_State *L) {
224+
static int iter_aux (lua_State *L, int strict) {
213225
size_t len;
214226
const char *s = luaL_checklstring(L, 1, &len);
215227
lua_Integer n = lua_tointeger(L, 2) - 1;
@@ -223,8 +235,8 @@ static int iter_aux (lua_State *L) {
223235
return 0; /* no more codepoints */
224236
else {
225237
utfint code;
226-
const char *next = utf8_decode(s + n, &code);
227-
if (next == NULL || iscont(next))
238+
const char *next = utf8_decode(s + n, &code, strict);
239+
if (next == NULL)
228240
return luaL_error(L, "invalid UTF-8 code");
229241
lua_pushinteger(L, n + 1);
230242
lua_pushinteger(L, code);
@@ -233,17 +245,27 @@ static int iter_aux (lua_State *L) {
233245
}
234246

235247

248+
static int iter_auxstrict (lua_State *L) {
249+
return iter_aux(L, 1);
250+
}
251+
252+
static int iter_auxnostrict (lua_State *L) {
253+
return iter_aux(L, 0);
254+
}
255+
256+
236257
static int iter_codes (lua_State *L) {
258+
int nonstrict = lua_toboolean(L, 2);
237259
luaL_checkstring(L, 1);
238-
lua_pushcfunction(L, iter_aux);
260+
lua_pushcfunction(L, nonstrict ? iter_auxnostrict : iter_auxstrict);
239261
lua_pushvalue(L, 1);
240262
lua_pushinteger(L, 0);
241263
return 3;
242264
}
243265

244266

245267
/* pattern to match a single UTF-8 character */
246-
#define UTF8PATT "[\0-\x7F\xC2-\xF4][\x80-\xBF]*"
268+
#define UTF8PATT "[\0-\x7F\xC2-\xFD][\x80-\xBF]*"
247269

248270

249271
static const luaL_Reg funcs[] = {

manual/manual.of

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1004,6 +1004,8 @@ the escape sequence @T{\u{@rep{XXX}}}
10041004
(note the mandatory enclosing brackets),
10051005
where @rep{XXX} is a sequence of one or more hexadecimal digits
10061006
representing the character code point.
1007+
This code point can be any value smaller than @M{2@sp{31}}.
1008+
(Lua uses the original UTF-8 specification here.)
10071009

10081010
Literal strings can also be defined using a long format
10091011
enclosed by @def{long brackets}.
@@ -6899,6 +6901,7 @@ x = string.gsub("$name-$version.tar.gz", "%$(%w+)", t)
68996901
}
69006902

69016903
@LibEntry{string.len (s)|
6904+
69026905
Receives a string and returns its length.
69036906
The empty string @T{""} has length 0.
69046907
Embedded zeros are counted,
@@ -6907,6 +6910,7 @@ so @T{"a\000bc\000"} has length 5.
69076910
}
69086911

69096912
@LibEntry{string.lower (s)|
6913+
69106914
Receives a string and returns a copy of this string with all
69116915
uppercase letters changed to lowercase.
69126916
All other characters are left unchanged.
@@ -6915,6 +6919,7 @@ The definition of what an uppercase letter is depends on the current locale.
69156919
}
69166920

69176921
@LibEntry{string.match (s, pattern [, init])|
6922+
69186923
Looks for the first @emph{match} of
69196924
@id{pattern} @see{pm} in the string @id{s}.
69206925
If it finds one, then @id{match} returns
@@ -6946,6 +6951,7 @@ The format string cannot have the variable-length options
69466951
}
69476952

69486953
@LibEntry{string.rep (s, n [, sep])|
6954+
69496955
Returns a string that is the concatenation of @id{n} copies of
69506956
the string @id{s} separated by the string @id{sep}.
69516957
The default value for @id{sep} is the empty string
@@ -6958,11 +6964,13 @@ with a single call to this function.)
69586964
}
69596965

69606966
@LibEntry{string.reverse (s)|
6967+
69616968
Returns a string that is the string @id{s} reversed.
69626969

69636970
}
69646971

69656972
@LibEntry{string.sub (s, i [, j])|
6973+
69666974
Returns the substring of @id{s} that
69676975
starts at @id{i} and continues until @id{j};
69686976
@id{i} and @id{j} can be negative.
@@ -6998,6 +7006,7 @@ this function also returns the index of the first unread byte in @id{s}.
69987006
}
69997007

70007008
@LibEntry{string.upper (s)|
7009+
70017010
Receives a string and returns a copy of this string with all
70027011
lowercase letters changed to uppercase.
70037012
All other characters are left unchanged.
@@ -7318,23 +7327,40 @@ or one plus the length of the subject string.
73187327
As in the string library,
73197328
negative indices count from the end of the string.
73207329

7330+
Functions that create byte sequences
7331+
accept all values up to @T{0x7FFFFFFF},
7332+
as defined in the original UTF-8 specification;
7333+
that implies byte sequences of up to six bytes.
7334+
7335+
Functions that interpret byte sequences only accept
7336+
valid sequences (well formed and not overlong).
7337+
By default, they only accept byte sequences
7338+
that result in valid Unicode code points,
7339+
rejecting values larger than @T{10FFFF} and surrogates.
7340+
A boolean argument @id{nonstrict}, when available,
7341+
lifts these checks,
7342+
so that all values up to @T{0x7FFFFFFF} are accepted.
7343+
(Not well formed and overlong sequences are still rejected.)
7344+
73217345

73227346
@LibEntry{utf8.char (@Cdots)|
7347+
73237348
Receives zero or more integers,
73247349
converts each one to its corresponding UTF-8 byte sequence
73257350
and returns a string with the concatenation of all these sequences.
73267351

73277352
}
73287353

73297354
@LibEntry{utf8.charpattern|
7330-
The pattern (a string, not a function) @St{[\0-\x7F\xC2-\xF4][\x80-\xBF]*}
7355+
7356+
The pattern (a string, not a function) @St{[\0-\x7F\xC2-\xFD][\x80-\xBF]*}
73317357
@see{pm},
73327358
which matches exactly one UTF-8 byte sequence,
73337359
assuming that the subject is a valid UTF-8 string.
73347360

73357361
}
73367362

7337-
@LibEntry{utf8.codes (s)|
7363+
@LibEntry{utf8.codes (s [, nonstrict])|
73387364

73397365
Returns values so that the construction
73407366
@verbatim{
@@ -7347,15 +7373,17 @@ It raises an error if it meets any invalid byte sequence.
73477373

73487374
}
73497375

7350-
@LibEntry{utf8.codepoint (s [, i [, j]])|
7376+
@LibEntry{utf8.codepoint (s [, i [, j [, nonstrict]]])|
7377+
73517378
Returns the codepoints (as integers) from all characters in @id{s}
73527379
that start between byte position @id{i} and @id{j} (both included).
73537380
The default for @id{i} is 1 and for @id{j} is @id{i}.
73547381
It raises an error if it meets any invalid byte sequence.
73557382

73567383
}
73577384

7358-
@LibEntry{utf8.len (s [, i [, j]])|
7385+
@LibEntry{utf8.len (s [, i [, j [, nonstrict]]])|
7386+
73597387
Returns the number of UTF-8 characters in string @id{s}
73607388
that start between positions @id{i} and @id{j} (both inclusive).
73617389
The default for @id{i} is @num{1} and for @id{j} is @num{-1}.
@@ -7365,6 +7393,7 @@ returns a false value plus the position of the first invalid byte.
73657393
}
73667394

73677395
@LibEntry{utf8.offset (s, n [, i])|
7396+
73687397
Returns the position (in bytes) where the encoding of the
73697398
@id{n}-th character of @id{s}
73707399
(counting from position @id{i}) starts.
@@ -8755,6 +8784,12 @@ You can enclose the call in parentheses if you need to
87558784
discard these extra results.
87568785
}
87578786

8787+
@item{
8788+
By default, the decoding functions in the @Lid{utf8} library
8789+
do not accept surrogates as valid code points.
8790+
An extra parameter in these functions makes them more permissive.
8791+
}
8792+
87588793
}
87598794

87608795
}

testes/literals.lua

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,16 +56,23 @@ assert("abc\z
5656
assert("\u{0}\u{00000000}\x00\0" == string.char(0, 0, 0, 0))
5757

5858
-- limits for 1-byte sequences
59-
assert("\u{0}\u{7F}" == "\x00\z\x7F")
59+
assert("\u{0}\u{7F}" == "\x00\x7F")
6060

6161
-- limits for 2-byte sequences
62-
assert("\u{80}\u{7FF}" == "\xC2\x80\z\xDF\xBF")
62+
assert("\u{80}\u{7FF}" == "\xC2\x80\xDF\xBF")
6363

6464
-- limits for 3-byte sequences
65-
assert("\u{800}\u{FFFF}" == "\xE0\xA0\x80\z\xEF\xBF\xBF")
65+
assert("\u{800}\u{FFFF}" == "\xE0\xA0\x80\xEF\xBF\xBF")
6666

6767
-- limits for 4-byte sequences
68-
assert("\u{10000}\u{10FFFF}" == "\xF0\x90\x80\x80\z\xF4\x8F\xBF\xBF")
68+
assert("\u{10000}\u{1FFFFF}" == "\xF0\x90\x80\x80\xF7\xBF\xBF\xBF")
69+
70+
-- limits for 5-byte sequences
71+
assert("\u{200000}\u{3FFFFFF}" == "\xF8\x88\x80\x80\x80\xFB\xBF\xBF\xBF\xBF")
72+
73+
-- limits for 6-byte sequences
74+
assert("\u{4000000}\u{7FFFFFFF}" ==
75+
"\xFC\x84\x80\x80\x80\x80\xFD\xBF\xBF\xBF\xBF\xBF")
6976

7077

7178
-- Error in escape sequences
@@ -94,7 +101,7 @@ lexerror([["xyz\300"]], [[\300"]])
94101
lexerror([[" \256"]], [[\256"]])
95102

96103
-- errors in UTF-8 sequences
97-
lexerror([["abc\u{110000}"]], [[abc\u{110000]]) -- too large
104+
lexerror([["abc\u{100000000}"]], [[abc\u{100000000]]) -- too large
98105
lexerror([["abc\u11r"]], [[abc\u1]]) -- missing '{'
99106
lexerror([["abc\u"]], [[abc\u"]]) -- missing '{'
100107
lexerror([["abc\u{11r"]], [[abc\u{11r]]) -- missing '}'

0 commit comments

Comments
 (0)