X-Git-Url: http://git.maemo.org/git/?p=xmlrpc-c;a=blobdiff_plain;f=src%2Fxmlrpc_utf8.c;fp=src%2Fxmlrpc_utf8.c;h=daec5ce2099428cecccc18f0749c2b457bb11e28;hp=0000000000000000000000000000000000000000;hb=ce67d0cdeaa37c3e856e23ae4010480887165630;hpb=e355d4e7962400470f467b88f5568de9c8324475 diff --git a/src/xmlrpc_utf8.c b/src/xmlrpc_utf8.c new file mode 100644 index 0000000..daec5ce --- /dev/null +++ b/src/xmlrpc_utf8.c @@ -0,0 +1,386 @@ +/* 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 "xmlrpc_config.h" + +#include "xmlrpc-c/base.h" + +#if HAVE_UNICODE_WCHAR + +/*========================================================================= +** 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 . */ +static wchar_t utf8_min_char_for_length[4] = { + 0, /* Length 0: Not used (meaningless) */ + 0x0000, /* Length 1: Not used (special-cased) */ + 0x0080, /* Length 2 */ + 0x0800 /* Length 3 */ + +#if 0 + /* These are only useful on systems where wchar_t is 32-bits wide + ** and supports full UCS-4. */ + 0x00010000, /* Length 4 */ + 0x00200000, /* Length 5 */ + 0x04000000 /* Length 6 */ +#endif +}; + +/* 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) + +/* Maximum number of bytes needed to encode a supported character. */ +#define MAX_ENCODED_BYTES (3) + + +/*========================================================================= +** decode_utf8 +**========================================================================= +** Internal routine which decodes (or validates) a UTF-8 string. +** To validate, set io_buff and out_buff_len to NULL. To decode, allocate +** a sufficiently large buffer, pass it as io_buff, and pass a pointer as +** as out_buff_len. The data will be written to the buffer, and the +** length to out_buff_len. +** +** We assume that wchar_t holds a single UCS-2 character in native-endian +** byte ordering. +*/ + +static void +decode_utf8(xmlrpc_env * const env, + const char * const utf8_data, + size_t const utf8_len, + wchar_t * const io_buff, + size_t * const out_buff_len) { + + size_t i, length, out_pos; + char init, con1, con2; + wchar_t wc; + + XMLRPC_ASSERT_ENV_OK(env); + XMLRPC_ASSERT_PTR_OK(utf8_data); + XMLRPC_ASSERT((!io_buff && !out_buff_len) || + (io_buff && out_buff_len)); + + /* 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(env, XMLRPC_INVALID_UTF8_ERROR, + "Truncated UTF-8 sequence"); + + /* Decode a multibyte UTF-8 sequence. */ + switch (length) { + case 0: + XMLRPC_FAIL(env, XMLRPC_INVALID_UTF8_ERROR, + "Invalid UTF-8 initial byte"); + + case 2: + /* 110xxxxx 10xxxxxx */ + con1 = utf8_data[i+1]; + if (!IS_CONTINUATION(con1)) + XMLRPC_FAIL(env, 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(env, 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 */ + XMLRPC_FAIL(env, XMLRPC_INVALID_UTF8_ERROR, + "UCS-4 characters not supported"); + + 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(env, 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(env, XMLRPC_INVALID_UTF8_ERROR, + "UTF-16 surrogates may not appear in UTF-8 data"); + + /* Check for overlong sequences. */ + if (wc < utf8_min_char_for_length[length]) + XMLRPC_FAIL(env, XMLRPC_INVALID_UTF8_ERROR, + "Overlong UTF-8 sequence not allowed"); + } + + /* If we have a buffer, write our character to it. */ + if (io_buff) { + io_buff[out_pos++] = wc; + } + } + + /* Record the number of characters we found. */ + if (out_buff_len) + *out_buff_len = out_pos; + + cleanup: + if (env->fault_occurred) { + if (out_buff_len) + *out_buff_len = 0; + } +} + + + +/*========================================================================= +** xmlrpc_validate_utf8 +**========================================================================= +** Make sure that a UTF-8 string is valid. +*/ + +void +xmlrpc_validate_utf8 (xmlrpc_env * const env, + const char * const utf8_data, + size_t const utf8_len) { + + decode_utf8(env, utf8_data, utf8_len, NULL, NULL); +} + + + +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. +-----------------------------------------------------------------------------*/ + xmlrpc_mem_block *output; + 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. + */ + output = 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, output), + &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, output, wcs_length); + } + if (envP->fault_occurred) + xmlrpc_mem_block_free(output); + } + if (envP->fault_occurred) + output = NULL; + + return output; +} + + + +/*========================================================================= +** xmlrpc_utf8_to_wcs +**========================================================================= +** Encode a "wide character string" as UTF-8. +*/ + +xmlrpc_mem_block *xmlrpc_wcs_to_utf8 (xmlrpc_env *env, + wchar_t *wcs_data, + size_t wcs_len) +{ + size_t estimate, bytes_used, i; + xmlrpc_mem_block *output; + unsigned char *buffer; + wchar_t wc; + + XMLRPC_ASSERT_ENV_OK(env); + XMLRPC_ASSERT_PTR_OK(wcs_data); + + /* Error-handling preconditions. */ + output = NULL; + + /* Allocate a memory block large enough to hold any possible output. + ** We assume that every wchar might encode to the maximum length. */ + estimate = wcs_len * MAX_ENCODED_BYTES; + output = XMLRPC_TYPED_MEM_BLOCK_NEW(char, env, estimate); + XMLRPC_FAIL_IF_FAULT(env); + + /* Output our characters. */ + buffer = (unsigned char*) XMLRPC_TYPED_MEM_BLOCK_CONTENTS(char, output); + bytes_used = 0; + for (i = 0; i < wcs_len; i++) { + wc = wcs_data[i]; + if (wc <= 0x007F) { + buffer[bytes_used++] = wc & 0x7F; + } else if (wc <= 0x07FF) { + /* 110xxxxx 10xxxxxx */ + buffer[bytes_used++] = 0xC0 | (wc >> 6); + buffer[bytes_used++] = 0x80 | (wc & 0x3F); + } else if (wc <= 0xFFFF) { + /* 1110xxxx 10xxxxxx 10xxxxxx */ + buffer[bytes_used++] = 0xE0 | (wc >> 12); + buffer[bytes_used++] = 0x80 | ((wc >> 6) & 0x3F); + buffer[bytes_used++] = 0x80 | (wc & 0x3F); + } else { + XMLRPC_FAIL(env, XMLRPC_INTERNAL_ERROR, + "Don't know how to encode UCS-4 characters yet"); + } + } + + /* Make sure we didn't overrun our buffer. */ + XMLRPC_ASSERT(bytes_used <= estimate); + + /* Correct the length of the memory block. */ + XMLRPC_TYPED_MEM_BLOCK_RESIZE(char, env, output, bytes_used); + XMLRPC_FAIL_IF_FAULT(env); + + cleanup: + if (env->fault_occurred) { + if (output) + xmlrpc_mem_block_free(output); + return NULL; + } + return output; +} + + + +#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 */ + +