mirror of
https://github.com/edeproject/ede.git
synced 2023-08-10 21:13:03 +03:00
511 lines
19 KiB
C
511 lines
19 KiB
C
/* Copyright (C) 2001 by Eric Kidd. 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.
|
|
** 3. The name of the author may not be used to endorse or promote products
|
|
** derived from this software without specific prior written permission.
|
|
**
|
|
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */
|
|
|
|
|
|
/*=========================================================================
|
|
** XML-RPC UTF-8 Utilities
|
|
**=========================================================================
|
|
** Routines for validating, encoding and decoding UTF-8 data. We try to
|
|
** be very, very strict about invalid UTF-8 data.
|
|
**
|
|
** All of the code in this file assumes that your machine represents
|
|
** wchar_t as a 16-bit (or wider) character containing UCS-2 data. If this
|
|
** assumption is incorrect, you may need to replace this file.
|
|
**
|
|
** For lots of information on Unicode and UTF-8 decoding, see:
|
|
** http://www.cl.cam.ac.uk/~mgk25/unicode.html
|
|
*/
|
|
|
|
#include "int.h"
|
|
|
|
#include "xmlrpc_config.h"
|
|
#include "bool.h"
|
|
#include "xmlrpc-c/base.h"
|
|
|
|
/*=========================================================================
|
|
** Tables and Constants
|
|
**=========================================================================
|
|
** We use a variety of tables and constants to help decode and validate
|
|
** UTF-8 data.
|
|
*/
|
|
|
|
/* The number of bytes in a UTF-8 sequence starting with the character used
|
|
** as the array index. A zero entry indicates an illegal initial byte.
|
|
** This table was generated using a Perl script and information from the
|
|
** UTF-8 standard.
|
|
**
|
|
** Fredrik Lundh's UTF-8 decoder Python 2.0 uses a similar table. But
|
|
** since Python 2.0 has the icky CNRI license, I regenerated this
|
|
** table from scratch and wrote my own decoder. */
|
|
static unsigned char utf8_seq_length[256] = {
|
|
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, 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, 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, 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,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
|
|
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
|
|
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
|
4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 0, 0
|
|
};
|
|
|
|
/* The minimum legal character value for a UTF-8 sequence of the given
|
|
** length. We have to check this to avoid accepting "overlong" UTF-8
|
|
** sequences, which use more bytes than necessary to encode a given
|
|
** character. Such sequences are commonly used by evil people to bypass
|
|
** filters and security checks. This table is based on the UTF-8-test.txt
|
|
** file by Markus Kuhn <mkuhn@acm.org>. */
|
|
static uint32_t const utf8_min_char_for_length[] = {
|
|
0, /* Length 0: Not used (meaningless) */
|
|
0x0000, /* Length 1: Not used (special-cased) */
|
|
0x0080, /* Length 2 */
|
|
0x0800, /* Length 3 */
|
|
0x00010000, /* Length 4 */
|
|
0x00200000, /* Length 5 */
|
|
0x04000000 /* Length 6 */
|
|
};
|
|
|
|
/* This is the maximum legal 16-byte (UCS-2) character. Again, this
|
|
** information is based on UTF-8-test.txt. */
|
|
#define UCS2_MAX_LEGAL_CHARACTER (0xFFFD)
|
|
|
|
/* First and last UTF-16 surrogate characters. These are *not* legal UCS-2
|
|
** characters--they're used to code for UCS-4 characters when using
|
|
** UTF-16. They should never appear in decoded UTF-8 data! Again, these
|
|
** could hypothetically be used to bypass security measures on some machines.
|
|
** Based on UTF-8-test.txt. */
|
|
#define UTF16_FIRST_SURROGATE (0xD800)
|
|
#define UTF16_LAST_SURROGATE (0xDFFF)
|
|
|
|
/* Is the character 'c' a UTF-8 continuation character? */
|
|
#define IS_CONTINUATION(c) (((c) & 0xC0) == 0x80)
|
|
|
|
#define MAX_ENCODED_BYTES (3)
|
|
/* Maximum number of bytes needed to encode in UTF-8 a character
|
|
in the Basic Multilingual Plane.
|
|
*/
|
|
|
|
|
|
#if HAVE_UNICODE_WCHAR
|
|
|
|
|
|
static void
|
|
decode_utf8(xmlrpc_env * const envP,
|
|
const char * const utf8_data,
|
|
size_t const utf8_len,
|
|
wchar_t * const ioBuff,
|
|
size_t * const outBuffLenP) {
|
|
/*----------------------------------------------------------------------------
|
|
Decode to UCS-2 (or validates as UTF-8 that can be decoded to UCS-2)
|
|
a UTF-8 string. To validate, set ioBuff and outBuffLenP to NULL.
|
|
To decode, allocate a sufficiently large buffer, pass it as ioBuff,
|
|
and pass a pointer as as outBuffLenP. The data will be written to
|
|
the buffer, and the length to outBuffLenP.
|
|
|
|
We assume that wchar_t holds a single UCS-2 character in native-endian
|
|
byte ordering.
|
|
-----------------------------------------------------------------------------*/
|
|
size_t i, length, out_pos;
|
|
char init, con1, con2;
|
|
wchar_t wc;
|
|
|
|
XMLRPC_ASSERT_ENV_OK(envP);
|
|
XMLRPC_ASSERT_PTR_OK(utf8_data);
|
|
XMLRPC_ASSERT((!ioBuff && !outBuffLenP) ||
|
|
(ioBuff && outBuffLenP));
|
|
|
|
/* Suppress GCC warning about possibly undefined variable. */
|
|
wc = 0;
|
|
|
|
i = 0;
|
|
out_pos = 0;
|
|
while (i < utf8_len) {
|
|
init = utf8_data[i];
|
|
if ((init & 0x80) == 0x00) {
|
|
/* Convert ASCII character to wide character. */
|
|
wc = init;
|
|
i++;
|
|
} else {
|
|
/* Look up the length of this UTF-8 sequence. */
|
|
length = utf8_seq_length[(unsigned char) init];
|
|
|
|
/* Check to make sure we have enough bytes to convert. */
|
|
if (i + length > utf8_len)
|
|
XMLRPC_FAIL(envP, XMLRPC_INVALID_UTF8_ERROR,
|
|
"Truncated UTF-8 sequence");
|
|
|
|
/* Decode a multibyte UTF-8 sequence. */
|
|
switch (length) {
|
|
case 0:
|
|
XMLRPC_FAIL(envP, XMLRPC_INVALID_UTF8_ERROR,
|
|
"Invalid UTF-8 initial byte");
|
|
|
|
case 2:
|
|
/* 110xxxxx 10xxxxxx */
|
|
con1 = utf8_data[i+1];
|
|
if (!IS_CONTINUATION(con1))
|
|
XMLRPC_FAIL(envP, XMLRPC_INVALID_UTF8_ERROR,
|
|
"UTF-8 sequence too short");
|
|
wc = ((((wchar_t) (init & 0x1F)) << 6) |
|
|
(((wchar_t) (con1 & 0x3F))));
|
|
break;
|
|
|
|
case 3:
|
|
/* 1110xxxx 10xxxxxx 10xxxxxx */
|
|
con1 = utf8_data[i+1];
|
|
con2 = utf8_data[i+2];
|
|
if (!IS_CONTINUATION(con1) || !IS_CONTINUATION(con2))
|
|
XMLRPC_FAIL(envP, XMLRPC_INVALID_UTF8_ERROR,
|
|
"UTF-8 sequence too short");
|
|
wc = ((((wchar_t) (init & 0x0F)) << 12) |
|
|
(((wchar_t) (con1 & 0x3F)) << 6) |
|
|
(((wchar_t) (con2 & 0x3F))));
|
|
break;
|
|
|
|
case 4:
|
|
/* 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */
|
|
case 5:
|
|
/* 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx */
|
|
case 6:
|
|
/* 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx */
|
|
/* This would require more than 16 bits in UTF-16, so
|
|
it can't be represented in UCS-2, so it's beyond
|
|
our capability. Characters in the BMP fit in 16
|
|
bits.
|
|
*/
|
|
xmlrpc_env_set_fault_formatted(
|
|
envP, XMLRPC_INVALID_UTF8_ERROR,
|
|
"UTF-8 string contains a character not in the "
|
|
"Basic Multilingual Plane (first byte %08x)",
|
|
init);
|
|
goto cleanup;
|
|
|
|
default:
|
|
XMLRPC_ASSERT("Error in UTF-8 decoder tables");
|
|
}
|
|
|
|
/* Advance to the end of the sequence. */
|
|
i += length;
|
|
|
|
/* Check for illegal UCS-2 characters. */
|
|
if (wc > UCS2_MAX_LEGAL_CHARACTER)
|
|
XMLRPC_FAIL(envP, XMLRPC_INVALID_UTF8_ERROR,
|
|
"UCS-2 characters > U+FFFD are illegal");
|
|
|
|
/* Check for UTF-16 surrogates. */
|
|
if (UTF16_FIRST_SURROGATE <= wc && wc <= UTF16_LAST_SURROGATE)
|
|
XMLRPC_FAIL(envP, XMLRPC_INVALID_UTF8_ERROR,
|
|
"UTF-16 surrogates may not appear in UTF-8 data");
|
|
|
|
/* Check for overlong sequences. */
|
|
if ((uint32_t)wc < utf8_min_char_for_length[length])
|
|
XMLRPC_FAIL(envP, XMLRPC_INVALID_UTF8_ERROR,
|
|
"Overlong UTF-8 sequence not allowed");
|
|
}
|
|
|
|
/* If we have a buffer, write our character to it. */
|
|
if (ioBuff) {
|
|
ioBuff[out_pos++] = wc;
|
|
}
|
|
}
|
|
|
|
/* Record the number of characters we found. */
|
|
if (outBuffLenP)
|
|
*outBuffLenP = out_pos;
|
|
|
|
cleanup:
|
|
if (envP->fault_occurred) {
|
|
if (outBuffLenP)
|
|
*outBuffLenP = 0;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
xmlrpc_mem_block *
|
|
xmlrpc_utf8_to_wcs(xmlrpc_env * const envP,
|
|
const char * const utf8_data,
|
|
size_t const utf8_len) {
|
|
/*----------------------------------------------------------------------------
|
|
Decode UTF-8 string to a "wide character string". This function
|
|
returns an xmlrpc_mem_block with an element type of wchar_t. Don't
|
|
try to intepret the block in a bytewise fashion--it won't work in
|
|
any useful or portable fashion.
|
|
|
|
For backward compatibility, we return a meaningful value even when we
|
|
fail. We return NULL when we fail.
|
|
-----------------------------------------------------------------------------*/
|
|
xmlrpc_mem_block * wcsP;
|
|
size_t wcs_length;
|
|
|
|
/* Allocate a memory block large enough to hold any possible output.
|
|
We assume that each byte of the input may decode to a whcar_t.
|
|
*/
|
|
wcsP = XMLRPC_MEMBLOCK_NEW(wchar_t, envP, utf8_len);
|
|
if (!envP->fault_occurred) {
|
|
/* Decode the UTF-8 data. */
|
|
decode_utf8(envP, utf8_data, utf8_len,
|
|
XMLRPC_MEMBLOCK_CONTENTS(wchar_t, wcsP),
|
|
&wcs_length);
|
|
if (!envP->fault_occurred) {
|
|
/* We can't have overrun our buffer. */
|
|
XMLRPC_ASSERT(wcs_length <= utf8_len);
|
|
|
|
/* Correct the length of the memory block. */
|
|
XMLRPC_MEMBLOCK_RESIZE(wchar_t, envP, wcsP, wcs_length);
|
|
}
|
|
if (envP->fault_occurred)
|
|
XMLRPC_MEMBLOCK_FREE(wchar_t, wcsP);
|
|
}
|
|
if (envP->fault_occurred)
|
|
return NULL;
|
|
else
|
|
return wcsP;
|
|
}
|
|
|
|
|
|
|
|
xmlrpc_mem_block *
|
|
xmlrpc_wcs_to_utf8(xmlrpc_env * const envP,
|
|
const wchar_t * const wcs_data,
|
|
size_t const wcs_len) {
|
|
/*----------------------------------------------------------------------------
|
|
Encode a "wide character string" as UTF-8.
|
|
|
|
For backward compatibility, we return a meaningful value even when we
|
|
fail. We return NULL when we fail.
|
|
-----------------------------------------------------------------------------*/
|
|
size_t const estimate = wcs_len * MAX_ENCODED_BYTES;
|
|
/* Our conservative estimate of how big the output will be;
|
|
i.e. we know it won't be larger than this. For the estimate,
|
|
we assume that every wchar might encode to the maximum length.
|
|
*/
|
|
xmlrpc_mem_block * utf8P;
|
|
|
|
XMLRPC_ASSERT_ENV_OK(envP);
|
|
XMLRPC_ASSERT_PTR_OK(wcs_data);
|
|
|
|
utf8P = XMLRPC_MEMBLOCK_NEW(char, envP, estimate);
|
|
if (!envP->fault_occurred) {
|
|
unsigned char * const buffer =
|
|
XMLRPC_MEMBLOCK_CONTENTS(unsigned char, utf8P);
|
|
size_t bytesUsed;
|
|
size_t i;
|
|
|
|
bytesUsed = 0;
|
|
for (i = 0; i < wcs_len && !envP->fault_occurred; ++i) {
|
|
wchar_t const wc = wcs_data[i];
|
|
if (wc <= 0x007F)
|
|
buffer[bytesUsed++] = wc & 0x7F;
|
|
else if (wc <= 0x07FF) {
|
|
/* 110xxxxx 10xxxxxx */
|
|
buffer[bytesUsed++] = 0xC0 | (wc >> 6);
|
|
buffer[bytesUsed++] = 0x80 | (wc & 0x3F);
|
|
} else if (wc <= 0xFFFF) {
|
|
/* 1110xxxx 10xxxxxx 10xxxxxx */
|
|
buffer[bytesUsed++] = 0xE0 | (wc >> 12);
|
|
buffer[bytesUsed++] = 0x80 | ((wc >> 6) & 0x3F);
|
|
buffer[bytesUsed++] = 0x80 | (wc & 0x3F);
|
|
} else
|
|
xmlrpc_faultf(envP,
|
|
"Don't know how to encode UCS-4 characters yet");
|
|
}
|
|
if (!envP->fault_occurred) {
|
|
XMLRPC_ASSERT(bytesUsed <= estimate);
|
|
|
|
XMLRPC_MEMBLOCK_RESIZE(char, envP, utf8P, bytesUsed);
|
|
}
|
|
if (envP->fault_occurred)
|
|
XMLRPC_MEMBLOCK_FREE(char, utf8P);
|
|
}
|
|
|
|
if (envP->fault_occurred)
|
|
return NULL;
|
|
else
|
|
return utf8P;
|
|
}
|
|
|
|
|
|
|
|
#else /* HAVE_UNICODE_WCHAR */
|
|
|
|
xmlrpc_mem_block *
|
|
xmlrpc_utf8_to_wcs(xmlrpc_env * const envP,
|
|
const char * const utf8_data ATTR_UNUSED,
|
|
size_t const utf8_len ATTR_UNUSED) {
|
|
|
|
xmlrpc_faultf(envP, "INTERNAL ERROR: xmlrpc_utf8_to_wcs() called "
|
|
"on a system that doesn't do Unicode!");
|
|
|
|
return NULL;
|
|
}
|
|
#endif /* HAVE_UNICODE_WCHAR */
|
|
|
|
|
|
void
|
|
xmlrpc_force_to_utf8(char * const buffer) {
|
|
/*----------------------------------------------------------------------------
|
|
Force the contents of 'buffer' to be valid UTF-8, any way possible.
|
|
The buffer ends with a NUL character, and the mutation does not make
|
|
it longer.
|
|
|
|
The most common reason for a string that's supposed to be UTF-8 not
|
|
to be UTF-8 is that it was supposed to be ASCII but instead
|
|
includes garbage with the high bit on (ASCII characters always have
|
|
the high bit off), or maybe a primitive 8-bit ASCII extension.
|
|
Therefore, we force it to UTF-8 by replacing some bytes that have
|
|
the high bit set with DEL (0x7F). That would leave the other
|
|
characters meaningful.
|
|
-----------------------------------------------------------------------------*/
|
|
char * p;
|
|
|
|
for (p = &buffer[0]; *p;) {
|
|
unsigned int const length = utf8_seq_length[(unsigned char) *p];
|
|
|
|
bool forceDel;
|
|
uint32_t decoded;
|
|
|
|
forceDel = false;
|
|
decoded = 0; /* suppress compiler warning; valid when !forceDel */
|
|
|
|
switch (length) {
|
|
case 1:
|
|
/* One-byte UTF-8 characters are easy. */
|
|
decoded = *p;
|
|
break;
|
|
case 2:
|
|
/* 110xxxxx 10xxxxxx */
|
|
if (!*(p+1) || !(*p+2))
|
|
forceDel = true;
|
|
else if (!IS_CONTINUATION(*(p+1)))
|
|
forceDel = true;
|
|
else
|
|
decoded =
|
|
((uint32_t)(*(p+0) & 0x1F) << 6) |
|
|
((uint32_t)(*(p+1) & 0x3F) << 0);
|
|
break;
|
|
case 3:
|
|
/* 1110xxxx 10xxxxxx 10xxxxxx */
|
|
if (!*(p+1) || !(*p+2) || !(*p+3))
|
|
forceDel = true;
|
|
else if (!IS_CONTINUATION(*(p+1)) || !IS_CONTINUATION(*(p+2)))
|
|
forceDel = true;
|
|
else
|
|
decoded =
|
|
((uint32_t)(*(p+0) & 0x0F) << 12) |
|
|
((uint32_t)(*(p+1) & 0x3F) << 6) |
|
|
((uint32_t)(*(p+2) & 0x3F) << 0);
|
|
break;
|
|
default:
|
|
forceDel = true;
|
|
}
|
|
|
|
if (!forceDel) {
|
|
if (decoded > UCS2_MAX_LEGAL_CHARACTER)
|
|
forceDel = true;
|
|
else if (UTF16_FIRST_SURROGATE <= decoded &&
|
|
decoded <= UTF16_LAST_SURROGATE)
|
|
forceDel = true;
|
|
else if (decoded < utf8_min_char_for_length[length])
|
|
forceDel = true;
|
|
}
|
|
|
|
if (forceDel) {
|
|
/* Not a valid UTF-8 character, so replace the first byte
|
|
with a nice simple ASCII DEL.
|
|
*/
|
|
*p = 0x7F;
|
|
p += 1;
|
|
} else
|
|
p += length;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void
|
|
xmlrpc_force_to_xml_chars(char * const buffer) {
|
|
/*----------------------------------------------------------------------------
|
|
Modify 'buffer' so that it contains nothing but valid XML
|
|
characters. The buffer ends with a NUL character, and the mutation
|
|
does not make it longer.
|
|
|
|
Note that the valid characters in an XML document are all Unicode
|
|
codepoints except the ASCII control characters, plus CR, LF, and
|
|
Tab.
|
|
|
|
We change all non-XML characters to DEL (0x7F).
|
|
|
|
Assume input is valid UTF-8.
|
|
-----------------------------------------------------------------------------*/
|
|
char * p;
|
|
|
|
for (p = &buffer[0]; *p;) {
|
|
unsigned int const length = utf8_seq_length[(unsigned char) *p];
|
|
|
|
if (length == 1) {
|
|
if (*p < 0x20 && *p != '\r' && *p != '\n' && *p != '\t')
|
|
/* Not valid XML. Force to DEL */
|
|
*p = 0x7f;
|
|
} else {
|
|
/* We assume here that all other UTF-8 characters are
|
|
valid XML, but it's apparently not actually true.
|
|
*/
|
|
}
|
|
|
|
{
|
|
unsigned int i;
|
|
/* Advance to next UTF-8 character */
|
|
for (i = 0; i < length && *p; ++i)
|
|
++p;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void
|
|
xmlrpc_validate_utf8(xmlrpc_env * const env,
|
|
const char * const utf8_data,
|
|
size_t const utf8_len) {
|
|
/*----------------------------------------------------------------------------
|
|
Validate that a string is valid UTF-8.
|
|
-----------------------------------------------------------------------------*/
|
|
#if HAVE_UNICODE_WCHAR
|
|
decode_utf8(env, utf8_data, utf8_len, NULL, NULL);
|
|
#else
|
|
/* We don't have a convenient way to validate, so we just fake it and
|
|
call it valid.
|
|
*/
|
|
#endif
|
|
}
|