summaryrefslogtreecommitdiff
path: root/utf8.c
diff options
context:
space:
mode:
authorJarkko Hietaniemi <jhi@iki.fi>2000-10-24 02:55:33 +0000
committerJarkko Hietaniemi <jhi@iki.fi>2000-10-24 02:55:33 +0000
commitba210ebec161cde003bc967e8e460c72f71fb70c (patch)
tree7eefd78e8e365cbf64ddf49314681d17b83c3025 /utf8.c
parent177b92d2814bfc842f28f277e0a2f353c652a5e3 (diff)
downloadperl-ba210ebec161cde003bc967e8e460c72f71fb70c.tar.gz
Make the UTF-8 decoding stricter and more verbose when
malformation happens. This involved adding an argument to utf8_to_uv_chk(), which involved changing its prototype, and prefer STRLEN over I32 for the UTF-8 length, which as a domino effect necessitated changing the prototypes of scan_bin(), scan_oct(), scan_hex(), and reg_uni(). The stricter UTF-8 decoding checking uses Markus Kuhn's UTF-8 Decode Stress Tester from http://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt p4raw-id: //depot/perl@7416
Diffstat (limited to 'utf8.c')
-rw-r--r--utf8.c167
1 files changed, 119 insertions, 48 deletions
diff --git a/utf8.c b/utf8.c
index a713ea198b..98236ed170 100644
--- a/utf8.c
+++ b/utf8.c
@@ -153,12 +153,12 @@ Perl_is_utf8_string(pTHX_ U8 *s, STRLEN len)
}
/*
-=for apidoc Am|U8* s|utf8_to_uv_chk|I32 *retlen|I32 checking
+=for apidoc Am|U8* s|utf8_to_uv_chk|STRLEN curlen|I32 *retlen|I32 checking
Returns the character value of the first character in the string C<s>
-which is assumed to be in UTF8 encoding; C<retlen> will be set to the
-length, in bytes, of that character, and the pointer C<s> will be
-advanced to the end of the character.
+which is assumed to be in UTF8 encoding and no longer than C<curlen>;
+C<retlen> will be set to the length, in bytes, of that character,
+and the pointer C<s> will be advanced to the end of the character.
If C<s> does not point to a well-formed UTF8 character, the behaviour
is dependent on the value of C<checking>: if this is true, it is
@@ -170,79 +170,150 @@ warning is produced.
*/
UV
-Perl_utf8_to_uv_chk(pTHX_ U8* s, I32* retlen, bool checking)
+Perl_utf8_to_uv_chk(pTHX_ U8* s, STRLEN curlen, STRLEN* retlen, bool checking)
{
- UV uv = *s;
- int len;
- if (!(uv & 0x80)) {
+ dTHR;
+ UV uv = *s, ouv;
+ STRLEN len = 1;
+ bool dowarn = ckWARN_d(WARN_UTF8);
+ STRLEN expectlen = 0;
+
+ if (uv <= 0x7f) { /* Pure ASCII. */
if (retlen)
*retlen = 1;
return *s;
}
- if (!(uv & 0x40)) {
- dTHR;
- if (checking && retlen) {
- *retlen = -1;
- return 0;
- }
- if (ckWARN_d(WARN_UTF8))
- Perl_warner(aTHX_ WARN_UTF8, "Malformed UTF-8 character");
- if (retlen)
- *retlen = 1;
- return *s;
+ if (uv >= 0x80 && uv <= 0xbf) {
+ if (dowarn)
+ Perl_warner(aTHX_ WARN_UTF8,
+ "Malformed UTF-8 character (unexpected continuation byte 0x%02x)",
+ uv);
+ goto malformed;
+ }
+
+ if (uv >= 0xc0 && uv <= 0xfd && curlen > 1 && s[1] < 0x80) {
+ if (dowarn)
+ Perl_warner(aTHX_ WARN_UTF8,
+ "Malformed UTF-8 character (unexpected non-continuation byte 0x%02x after byte 0x%02x)",
+ s[1], uv);
+ goto malformed;
+ }
+
+ if ((uv == 0xfe || uv == 0xff) && IN_UTF8){
+ if (dowarn)
+ Perl_warner(aTHX_ WARN_UTF8,
+ "Malformed UTF-8 character (impossible byte 0x%02x)",
+ uv);
+ goto malformed;
}
- if (!(uv & 0x20)) { len = 2; uv &= 0x1f; }
- else if (!(uv & 0x10)) { len = 3; uv &= 0x0f; }
- else if (!(uv & 0x08)) { len = 4; uv &= 0x07; }
- else if (!(uv & 0x04)) { len = 5; uv &= 0x03; }
- else if (!(uv & 0x02)) { len = 6; uv &= 0x01; }
- else if (!(uv & 0x01)) { len = 7; uv = 0; }
+ if (!(uv & 0x20)) { len = 2; uv &= 0x1f; }
+ else if (!(uv & 0x10)) { len = 3; uv &= 0x0f; }
+ else if (!(uv & 0x08)) { len = 4; uv &= 0x07; }
+ else if (!(uv & 0x04)) { len = 5; uv &= 0x03; }
+ else if (!(uv & 0x02)) { len = 6; uv &= 0x01; }
+ else if (!(uv & 0x01)) { len = 7; uv = 0; }
else { len = 13; uv = 0; } /* whoa! */
if (retlen)
*retlen = len;
- --len;
+
+ expectlen = len;
+
+ if (curlen < expectlen) {
+ if (dowarn)
+ Perl_warner(aTHX_ WARN_UTF8,
+ "Malformed UTF-8 character (%d byte%s, need %d)",
+ curlen, curlen > 1 ? "s" : "", expectlen);
+ goto malformed;
+ }
+
+ len--;
s++;
+ ouv = uv;
+
while (len--) {
if ((*s & 0xc0) != 0x80) {
- dTHR;
- if (checking && retlen) {
- *retlen = -1;
- return 0;
- }
-
- if (ckWARN_d(WARN_UTF8))
- Perl_warner(aTHX_ WARN_UTF8, "Malformed UTF-8 character");
- if (retlen)
- *retlen -= len + 1;
- return 0xfffd;
+ if (dowarn)
+ Perl_warner(aTHX_ WARN_UTF8,
+ "Malformed UTF-8 character (unexpected continuation byte 0x%02x)",
+ *s);
+ goto malformed;
}
else
- uv = (uv << 6) | (*s++ & 0x3f);
+ uv = (uv << 6) | (*s & 0x3f);
+ if (uv < ouv) {
+ if (dowarn)
+ Perl_warner(aTHX_ WARN_UTF8,
+ "Malformed UTF-8 character (overflow at 0x%"UVxf", byte 0x%02x)",
+ ouv, *s);
+ goto malformed;
+ }
+ s++;
+ ouv = uv;
+ }
+
+ if (uv >= 0xd800 && uv <= 0xdfff) {
+ if (dowarn)
+ Perl_warner(aTHX_ WARN_UTF8,
+ "Malformed UTF-8 character (UTF-16 surrogate 0x%04"UVxf")",
+ uv);
+ goto malformed;
+ } else if (uv == 0xfffe) {
+ if (dowarn)
+ Perl_warner(aTHX_ WARN_UTF8,
+ "Malformed UTF-8 character (byte order mark 0x%04"UVxf")",
+ uv);
+ goto malformed;
+ } else if (uv == 0xffff && IN_UTF8) {
+ if (dowarn)
+ Perl_warner(aTHX_ WARN_UTF8,
+ "Malformed UTF-8 character (impossible character 0x%04"UVxf")",
+ uv);
+ goto malformed;
+ } else if (expectlen > UTF8LEN(uv)) {
+ if (dowarn)
+ Perl_warner(aTHX_ WARN_UTF8,
+ "Malformed UTF-8 character (%d byte%s, need %d)",
+ expectlen, expectlen > 1 ? "s": "", UTF8LEN(uv));
+ goto malformed;
}
+
return uv;
+
+malformed:
+
+ if (checking) {
+ if (retlen)
+ *retlen = len;
+ return 0;
+ }
+
+ if (retlen)
+ *retlen = -1;
+
+ return UNICODE_REPLACEMENT_CHARACTER;
}
/*
-=for apidoc Am|U8* s|utf8_to_uv|I32 *retlen
+=for apidoc Am|U8* s|utf8_to_uv|STRLEN *retlen
Returns the character value of the first character in the string C<s>
which is assumed to be in UTF8 encoding; C<retlen> will be set to the
length, in bytes, of that character, and the pointer C<s> will be
advanced to the end of the character.
-If C<s> does not point to a well-formed UTF8 character, an optional UTF8
-warning is produced.
+If C<s> does not point to a well-formed UTF8 character, zero is
+returned and retlen is set, if possible, to -1.
=cut
*/
UV
-Perl_utf8_to_uv(pTHX_ U8* s, I32* retlen)
+Perl_utf8_to_uv(pTHX_ U8* s, STRLEN* retlen)
{
- return Perl_utf8_to_uv_chk(aTHX_ s, retlen, 0);
+ return Perl_utf8_to_uv_chk(aTHX_ s, (STRLEN)-1, retlen, 0);
}
/* utf8_distance(a,b) returns the number of UTF8 characters between
@@ -324,7 +395,7 @@ Perl_utf8_to_bytes(pTHX_ U8* s, STRLEN *len)
if (*s < 0x80)
*d++ = *s++;
else {
- I32 ulen;
+ STRLEN ulen;
*d++ = (U8)utf8_to_uv(s, &ulen);
s += ulen;
}
@@ -853,7 +924,7 @@ Perl_to_utf8_upper(pTHX_ U8 *p)
if (!PL_utf8_toupper)
PL_utf8_toupper = swash_init("utf8", "ToUpper", &PL_sv_undef, 4, 0);
uv = swash_fetch(PL_utf8_toupper, p);
- return uv ? uv : utf8_to_uv_chk(p,0,0);
+ return uv ? uv : utf8_to_uv_chk(p,STRLEN_MAX,0,0);
}
UV
@@ -864,7 +935,7 @@ Perl_to_utf8_title(pTHX_ U8 *p)
if (!PL_utf8_totitle)
PL_utf8_totitle = swash_init("utf8", "ToTitle", &PL_sv_undef, 4, 0);
uv = swash_fetch(PL_utf8_totitle, p);
- return uv ? uv : utf8_to_uv_chk(p,0,0);
+ return uv ? uv : utf8_to_uv_chk(p,STRLEN_MAX,0,0);
}
UV
@@ -875,7 +946,7 @@ Perl_to_utf8_lower(pTHX_ U8 *p)
if (!PL_utf8_tolower)
PL_utf8_tolower = swash_init("utf8", "ToLower", &PL_sv_undef, 4, 0);
uv = swash_fetch(PL_utf8_tolower, p);
- return uv ? uv : utf8_to_uv_chk(p,0,0);
+ return uv ? uv : utf8_to_uv_chk(p,STRLEN_MAX,0,0);
}
/* a "swash" is a swatch hash */
@@ -965,7 +1036,7 @@ Perl_swash_fetch(pTHX_ SV *sv, U8 *ptr)
PUSHMARK(SP);
EXTEND(SP,3);
PUSHs((SV*)sv);
- PUSHs(sv_2mortal(newSViv(utf8_to_uv_chk(ptr, 0, 0) & ~(needents - 1))));
+ PUSHs(sv_2mortal(newSViv(utf8_to_uv_chk(ptr, STRLEN_MAX, 0, 0) & ~(needents - 1))));
PUSHs(sv_2mortal(newSViv(needents)));
PUTBACK;
if (call_method("SWASHGET", G_SCALAR))