summaryrefslogtreecommitdiff
path: root/src/utils
diff options
context:
space:
mode:
Diffstat (limited to 'src/utils')
-rw-r--r--src/utils/Makefile2
-rw-r--r--src/utils/charset_errors.h19
-rw-r--r--src/utils/namespace.c2
-rw-r--r--src/utils/utf16.c239
-rw-r--r--src/utils/utf16.h38
-rw-r--r--src/utils/utf8.c368
-rw-r--r--src/utils/utf8.h38
7 files changed, 705 insertions, 1 deletions
diff --git a/src/utils/Makefile b/src/utils/Makefile
index 29369ae..ac87ded 100644
--- a/src/utils/Makefile
+++ b/src/utils/Makefile
@@ -22,7 +22,7 @@
CFLAGS += -I$(CURDIR)
# Objects
-OBJS = namespace
+OBJS = namespace utf8 utf16
.PHONY: clean debug distclean export release setup test
diff --git a/src/utils/charset_errors.h b/src/utils/charset_errors.h
new file mode 100644
index 0000000..7571c06
--- /dev/null
+++ b/src/utils/charset_errors.h
@@ -0,0 +1,19 @@
+/*
+ * This file is part of libdom.
+ * Licensed under the MIT License,
+ * http://www.opensource.org/licenses/mit-license.php
+ * Copyright 2007 John-Mark Bell <jmb@netsurf-browser.org>
+ */
+
+#ifndef dom_utils_charset_errors_h_
+#define dom_utils_charset_errors_h_
+
+typedef enum {
+ CHARSET_OK, /**< No error */
+ CHARSET_BADPARM, /**< Bad parameters to argument */
+ CHARSET_NEEDDATA, /**< Insufficient data for operation */
+ CHARSET_INVALID /**< Invalid input data */
+} charset_error;
+
+#endif
+
diff --git a/src/utils/namespace.c b/src/utils/namespace.c
index 25b56ee..8a53e45 100644
--- a/src/utils/namespace.c
+++ b/src/utils/namespace.c
@@ -29,6 +29,7 @@ dom_exception _dom_namespace_initialise(dom_alloc alloc, void *pw)
dom_exception err;
err = dom_string_create_from_ptr_no_doc(alloc, pw,
+ DOM_STRING_UTF8,
(const uint8_t *) "http://www.w3.org/XML/1998/namespace",
SLEN("http://www.w3.org/XML/1998/namespace"),
&xml);
@@ -37,6 +38,7 @@ dom_exception _dom_namespace_initialise(dom_alloc alloc, void *pw)
}
err = dom_string_create_from_ptr_no_doc(alloc, pw,
+ DOM_STRING_UTF8,
(const uint8_t *) "http://www.w3.org/2000/xmlns",
SLEN("http://www.w3.org/2000/xmlns"),
&xmlns);
diff --git a/src/utils/utf16.c b/src/utils/utf16.c
new file mode 100644
index 0000000..8917328
--- /dev/null
+++ b/src/utils/utf16.c
@@ -0,0 +1,239 @@
+/*
+ * This file is part of Hubbub.
+ * Licensed under the MIT License,
+ * http://www.opensource.org/licenses/mit-license.php
+ * Copyright 2007 John-Mark Bell <jmb@netsurf-browser.org>
+ */
+
+/** \file
+ * UTF-16 manipulation functions (implementation).
+ */
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "utils/utf16.h"
+
+/**
+ * Convert a UTF-16 sequence into a single UCS4 character
+ *
+ * \param s The sequence to process
+ * \param len Length of sequence
+ * \param ucs4 Pointer to location to receive UCS4 character (host endian)
+ * \param clen Pointer to location to receive byte length of UTF-16 sequence
+ * \return CHARSET_OK on success, appropriate error otherwise
+ */
+inline charset_error _dom_utf16_to_ucs4(const uint8_t *s, size_t len,
+ uint32_t *ucs4, size_t *clen)
+{
+ const uint16_t *ss = (const uint16_t *) (const void *) s;
+
+ if (s == NULL || ucs4 == NULL || clen == NULL)
+ return CHARSET_BADPARM;
+
+ if (len < 2)
+ return CHARSET_NEEDDATA;
+
+ if (*ss < 0xD800 || *ss > 0xDFFF) {
+ *ucs4 = *ss;
+ *clen = 2;
+ } else if (0xD800 <= *ss && *ss <= 0xBFFF) {
+ if (len < 4)
+ return CHARSET_NEEDDATA;
+
+ if (0xDC00 <= ss[1] && ss[1] <= 0xE000) {
+ *ucs4 = (((s[0] >> 6) & 0x1f) + 1) |
+ ((s[0] & 0x3f) | (s[1] & 0x3ff));
+ *clen = 4;
+ } else {
+ return CHARSET_INVALID;
+ }
+ }
+
+ return CHARSET_OK;
+}
+
+/**
+ * Convert a single UCS4 character into a UTF-16 sequence
+ *
+ * \param ucs4 The character to process (0 <= c <= 0x7FFFFFFF) (host endian)
+ * \param s Pointer to 4 byte long output buffer
+ * \param len Pointer to location to receive length of multibyte sequence
+ * \return CHARSET_OK on success, appropriate error otherwise
+ */
+inline charset_error _dom_utf16_from_ucs4(uint32_t ucs4, uint8_t *s,
+ size_t *len)
+{
+ uint16_t *ss = (uint16_t *) (void *) s;
+ uint32_t l = 0;
+
+ if (s == NULL || len == NULL)
+ return CHARSET_BADPARM;
+ else if (ucs4 < 0x10000) {
+ *ss = (uint16_t) ucs4;
+ l = 2;
+ } else if (ucs4 < 0x110000) {
+ ss[0] = 0xD800 | (((ucs4 >> 16) & 0x1f) - 1) | (ucs4 >> 10);
+ ss[1] = 0xDC00 | (ucs4 & 0x3ff);
+ l = 4;
+ } else {
+ return CHARSET_INVALID;
+ }
+
+ *len = l;
+
+ return CHARSET_OK;
+}
+
+/**
+ * Calculate the length (in characters) of a bounded UTF-16 string
+ *
+ * \param s The string
+ * \param max Maximum length
+ * \param len Pointer to location to receive length of string
+ * \return CHARSET_OK on success, appropriate error otherwise
+ */
+inline charset_error _dom_utf16_length(const uint8_t *s, size_t max,
+ size_t *len)
+{
+ const uint16_t *ss = (const uint16_t *) (const void *) s;
+ const uint16_t *end = (const uint16_t *) (const void *) (s + max);
+ int l = 0;
+
+ if (s == NULL || len == NULL)
+ return CHARSET_BADPARM;
+
+ while (ss < end) {
+ if (*ss < 0xD800 || 0xDFFF < *ss)
+ ss++;
+ else
+ ss += 2;
+
+ l++;
+ }
+
+ *len = l;
+
+ return CHARSET_OK;
+}
+
+/**
+ * Calculate the length (in bytes) of a UTF-16 character
+ *
+ * \param s Pointer to start of character
+ * \param len Pointer to location to receive length
+ * \return CHARSET_OK on success, appropriate error otherwise
+ */
+inline charset_error _dom_utf16_char_byte_length(const uint8_t *s,
+ size_t *len)
+{
+ const uint16_t *ss = (const uint16_t *) (const void *) s;
+
+ if (s == NULL || len == NULL)
+ return CHARSET_BADPARM;
+
+ if (*ss < 0xD800 || 0xDFFF < *ss)
+ *len = 2;
+ else
+ *len = 4;
+
+ return CHARSET_OK;
+}
+
+/**
+ * Find previous legal UTF-16 char in string
+ *
+ * \param s The string
+ * \param off Offset in the string to start at
+ * \param prevoff Pointer to location to receive offset of first byte of
+ * previous legal character
+ * \return CHARSET_OK on success, appropriate error otherwise
+ */
+inline charset_error _dom_utf16_prev(const uint8_t *s, uint32_t off,
+ uint32_t *prevoff)
+{
+ const uint16_t *ss = (const uint16_t *) (const void *) s;
+
+ if (s == NULL || prevoff == NULL)
+ return CHARSET_BADPARM;
+
+ if (off < 2)
+ *prevoff = 0;
+ else if (ss[-1] < 0xDC00 || ss[-1] > 0xDFFF)
+ *prevoff = off - 2;
+ else
+ *prevoff = (off < 4) ? 0 : off - 4;
+
+ return CHARSET_OK;
+}
+
+/**
+ * Find next legal UTF-16 char in string
+ *
+ * \param s The string (assumed valid)
+ * \param len Maximum offset in string
+ * \param off Offset in the string to start at
+ * \param nextoff Pointer to location to receive offset of first byte of
+ * next legal character
+ * \return CHARSET_OK on success, appropriate error otherwise
+ */
+inline charset_error _dom_utf16_next(const uint8_t *s, uint32_t len,
+ uint32_t off, uint32_t *nextoff)
+{
+ const uint16_t *ss = (const uint16_t *) (const void *) s;
+
+ if (s == NULL || off >= len || nextoff == NULL)
+ return CHARSET_BADPARM;
+
+ if (len - off < 4)
+ *nextoff = len;
+ else if (ss[1] < 0xD800 || ss[1] > 0xDBFF)
+ *nextoff = off + 2;
+ else
+ *nextoff = (len - off < 6) ? len : off + 4;
+
+ return CHARSET_OK;
+}
+
+/**
+ * Find next legal UTF-16 char in string
+ *
+ * \param s The string (assumed to be of dubious validity)
+ * \param len Maximum offset in string
+ * \param off Offset in the string to start at
+ * \param nextoff Pointer to location to receive offset of first byte of
+ * next legal character
+ * \return CHARSET_OK on success, appropriate error otherwise
+ */
+inline charset_error _dom_utf16_next_paranoid(const uint8_t *s,
+ uint32_t len, uint32_t off, uint32_t *nextoff)
+{
+ const uint16_t *ss = (const uint16_t *) (const void *) s;
+
+ if (s == NULL || off >= len || nextoff == NULL)
+ return CHARSET_BADPARM;
+
+ while (1) {
+ if (len - off < 4) {
+ return CHARSET_NEEDDATA;
+ } else if (ss[1] < 0xD800 || ss[1] > 0xDFFF) {
+ *nextoff = off + 2;
+ break;
+ } else if (ss[1] >= 0xD800 && ss[1] <= 0xDBFF) {
+ if (len - off < 6)
+ return CHARSET_NEEDDATA;
+
+ if (ss[2] >= 0xDC00 && ss[2] <= 0xDFFF) {
+ *nextoff = off + 4;
+ break;
+ } else {
+ ss++;
+ off += 2;
+ }
+ }
+ }
+
+ return CHARSET_OK;
+}
+
diff --git a/src/utils/utf16.h b/src/utils/utf16.h
new file mode 100644
index 0000000..7b9e15f
--- /dev/null
+++ b/src/utils/utf16.h
@@ -0,0 +1,38 @@
+/*
+ * This file is part of libdom.
+ * Licensed under the MIT License,
+ * http://www.opensource.org/licenses/mit-license.php
+ * Copyright 2007 John-Mark Bell <jmb@netsurf-browser.org>
+ */
+
+/** \file
+ * UTF-16 manipulation functions (interface).
+ */
+
+#ifndef dom_utils_utf16_h_
+#define dom_utils_utf16_h_
+
+#include <inttypes.h>
+
+#include "utils/charset_errors.h"
+
+inline charset_error _dom_utf16_to_ucs4(const uint8_t *s, size_t len,
+ uint32_t *ucs4, size_t *clen);
+inline charset_error _dom_utf16_from_ucs4(uint32_t ucs4, uint8_t *s,
+ size_t *len);
+
+inline charset_error _dom_utf16_length(const uint8_t *s, size_t max,
+ size_t *len);
+inline charset_error _dom_utf16_char_byte_length(const uint8_t *s,
+ size_t *len);
+
+inline charset_error _dom_utf16_prev(const uint8_t *s, uint32_t off,
+ uint32_t *prevoff);
+inline charset_error _dom_utf16_next(const uint8_t *s, uint32_t len,
+ uint32_t off, uint32_t *nextoff);
+
+inline charset_error _dom_utf16_next_paranoid(const uint8_t *s,
+ uint32_t len, uint32_t off, uint32_t *nextoff);
+
+#endif
+
diff --git a/src/utils/utf8.c b/src/utils/utf8.c
new file mode 100644
index 0000000..b80f04e
--- /dev/null
+++ b/src/utils/utf8.c
@@ -0,0 +1,368 @@
+/*
+ * This file is part of libdom.
+ * Licensed under the MIT License,
+ * http://www.opensource.org/licenses/mit-license.php
+ * Copyright 2007 John-Mark Bell <jmb@netsurf-browser.org>
+ */
+
+/** \file
+ * UTF-8 manipulation functions (implementation).
+ */
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "utils/utf8.h"
+
+/** Number of continuation bytes for a given start byte */
+static const uint8_t numContinuations[256] = {
+ 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,
+ 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,
+ 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,
+ 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,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5,
+};
+
+/**
+ * Convert a UTF-8 multibyte sequence into a single UCS4 character
+ *
+ * Encoding of UCS values outside the UTF-16 plane has been removed from
+ * RFC3629. This function conforms to RFC2279, however.
+ *
+ * \param s The sequence to process
+ * \param len Length of sequence
+ * \param ucs4 Pointer to location to receive UCS4 character (host endian)
+ * \param clen Pointer to location to receive byte length of UTF-8 sequence
+ * \return CHARSET_OK on success, appropriate error otherwise
+ */
+inline charset_error _dom_utf8_to_ucs4(const uint8_t *s, size_t len,
+ uint32_t *ucs4, size_t *clen)
+{
+ if (s == NULL || ucs4 == NULL || clen == NULL)
+ return CHARSET_BADPARM;
+
+ if (len == 0)
+ return CHARSET_NEEDDATA;
+
+ if (*s < 0x80) {
+ *ucs4 = *s;
+ *clen = 1;
+ } else if ((*s & 0xE0) == 0xC0) {
+ if (len < 2)
+ return CHARSET_NEEDDATA;
+ else if ((*(s+1) & 0xC0) != 0x80)
+ return CHARSET_INVALID;
+ else {
+ *ucs4 = ((*s & 0x1F) << 6) | (*(s+1) & 0x3F);
+ *clen = 2;
+ }
+ } else if ((*s & 0xF0) == 0xE0) {
+ if (len < 3)
+ return CHARSET_NEEDDATA;
+ else if ((*(s+1) & 0xC0) != 0x80 ||
+ (*(s+2) & 0xC0) != 0x80)
+ return CHARSET_INVALID;
+ else {
+ *ucs4 = ((*s & 0x0F) << 12) |
+ ((*(s+1) & 0x3F) << 6) |
+ (*(s+2) & 0x3F);
+ *clen = 3;
+ }
+ } else if ((*s & 0xF8) == 0xF0) {
+ if (len < 4)
+ return CHARSET_NEEDDATA;
+ else if ((*(s+1) & 0xC0) != 0x80 ||
+ (*(s+2) & 0xC0) != 0x80 ||
+ (*(s+3) & 0xC0) != 0x80)
+ return CHARSET_INVALID;
+ else {
+ *ucs4 = ((*s & 0x0F) << 18) |
+ ((*(s+1) & 0x3F) << 12) |
+ ((*(s+2) & 0x3F) << 6) |
+ (*(s+3) & 0x3F);
+ *clen = 4;
+ }
+ } else if ((*s & 0xFC) == 0xF8) {
+ if (len < 5)
+ return CHARSET_NEEDDATA;
+ else if ((*(s+1) & 0xC0) != 0x80 ||
+ (*(s+2) & 0xC0) != 0x80 ||
+ (*(s+3) & 0xC0) != 0x80 ||
+ (*(s+4) & 0xC0) != 0x80)
+ return CHARSET_INVALID;
+ else {
+ *ucs4 = ((*s & 0x0F) << 24) |
+ ((*(s+1) & 0x3F) << 18) |
+ ((*(s+2) & 0x3F) << 12) |
+ ((*(s+3) & 0x3F) << 6) |
+ (*(s+4) & 0x3F);
+ *clen = 5;
+ }
+ } else if ((*s & 0xFE) == 0xFC) {
+ if (len < 6)
+ return CHARSET_NEEDDATA;
+ else if ((*(s+1) & 0xC0) != 0x80 ||
+ (*(s+2) & 0xC0) != 0x80 ||
+ (*(s+3) & 0xC0) != 0x80 ||
+ (*(s+4) & 0xC0) != 0x80 ||
+ (*(s+5) & 0xC0) != 0x80)
+ return CHARSET_INVALID;
+ else {
+ *ucs4 = ((*s & 0x0F) << 28) |
+ ((*(s+1) & 0x3F) << 24) |
+ ((*(s+2) & 0x3F) << 18) |
+ ((*(s+3) & 0x3F) << 12) |
+ ((*(s+4) & 0x3F) << 6) |
+ (*(s+5) & 0x3F);
+ *clen = 6;
+ }
+ } else {
+ return CHARSET_INVALID;
+ }
+
+ return CHARSET_OK;
+}
+
+/**
+ * Convert a single UCS4 character into a UTF-8 multibyte sequence
+ *
+ * Encoding of UCS values outside the UTF-16 plane has been removed from
+ * RFC3629. This function conforms to RFC2279, however.
+ *
+ * \param ucs4 The character to process (0 <= c <= 0x7FFFFFFF) (host endian)
+ * \param s Pointer to 6 byte long output buffer
+ * \param len Pointer to location to receive length of multibyte sequence
+ * \return CHARSET_OK on success, appropriate error otherwise
+ */
+inline charset_error _dom_utf8_from_ucs4(uint32_t ucs4, uint8_t *s,
+ size_t *len)
+{
+ uint32_t l = 0;
+
+ if (s == NULL || len == NULL)
+ return CHARSET_BADPARM;
+ else if (ucs4 < 0x80) {
+ *s = (uint8_t) ucs4;
+ l = 1;
+ } else if (ucs4 < 0x800) {
+ *s = 0xC0 | ((ucs4 >> 6) & 0x1F);
+ *(s+1) = 0x80 | (ucs4 & 0x3F);
+ l = 2;
+ } else if (ucs4 < 0x10000) {
+ *s = 0xE0 | ((ucs4 >> 12) & 0xF);
+ *(s+1) = 0x80 | ((ucs4 >> 6) & 0x3F);
+ *(s+2) = 0x80 | (ucs4 & 0x3F);
+ l = 3;
+ } else if (ucs4 < 0x200000) {
+ *s = 0xF0 | ((ucs4 >> 18) & 0x7);
+ *(s+1) = 0x80 | ((ucs4 >> 12) & 0x3F);
+ *(s+2) = 0x80 | ((ucs4 >> 6) & 0x3F);
+ *(s+3) = 0x80 | (ucs4 & 0x3F);
+ l = 4;
+ } else if (ucs4 < 0x4000000) {
+ *s = 0xF8 | ((ucs4 >> 24) & 0x3);
+ *(s+1) = 0x80 | ((ucs4 >> 18) & 0x3F);
+ *(s+2) = 0x80 | ((ucs4 >> 12) & 0x3F);
+ *(s+3) = 0x80 | ((ucs4 >> 6) & 0x3F);
+ *(s+4) = 0x80 | (ucs4 & 0x3F);
+ l = 5;
+ } else if (ucs4 <= 0x7FFFFFFF) {
+ *s = 0xFC | ((ucs4 >> 30) & 0x1);
+ *(s+1) = 0x80 | ((ucs4 >> 24) & 0x3F);
+ *(s+2) = 0x80 | ((ucs4 >> 18) & 0x3F);
+ *(s+3) = 0x80 | ((ucs4 >> 12) & 0x3F);
+ *(s+4) = 0x80 | ((ucs4 >> 6) & 0x3F);
+ *(s+5) = 0x80 | (ucs4 & 0x3F);
+ l = 6;
+ } else {
+ return CHARSET_INVALID;
+ }
+
+ *len = l;
+
+ return CHARSET_OK;
+}
+
+/**
+ * Calculate the length (in characters) of a bounded UTF-8 string
+ *
+ * \param s The string
+ * \param max Maximum length
+ * \param len Pointer to location to receive length of string
+ * \return CHARSET_OK on success, appropriate error otherwise
+ */
+inline charset_error _dom_utf8_length(const uint8_t *s, size_t max,
+ size_t *len)
+{
+ const uint8_t *end = s + max;
+ int l = 0;
+
+ if (s == NULL || len == NULL)
+ return CHARSET_BADPARM;
+
+ while (s < end) {
+ if ((*s & 0x80) == 0x00)
+ s += 1;
+ else if ((*s & 0xE0) == 0xC0)
+ s += 2;
+ else if ((*s & 0xF0) == 0xE0)
+ s += 3;
+ else if ((*s & 0xF8) == 0xF0)
+ s += 4;
+ else if ((*s & 0xFC) == 0xF8)
+ s += 5;
+ else if ((*s & 0xFE) == 0xFC)
+ s += 6;
+ else
+ return CHARSET_INVALID;
+ l++;
+ }
+
+ *len = l;
+
+ return CHARSET_OK;
+}
+
+/**
+ * Calculate the length (in bytes) of a UTF-8 character
+ *
+ * \param s Pointer to start of character
+ * \param len Pointer to location to receive length
+ * \return CHARSET_OK on success, appropriate error otherwise
+ */
+inline charset_error _dom_utf8_char_byte_length(const uint8_t *s,
+ size_t *len)
+{
+ if (s == NULL || len == NULL)
+ return CHARSET_BADPARM;
+
+ *len = numContinuations[s[0]] + 1 /* Start byte */;
+
+ return CHARSET_OK;
+}
+
+/**
+ * Find previous legal UTF-8 char in string
+ *
+ * \param s The string
+ * \param off Offset in the string to start at
+ * \param prevoff Pointer to location to receive offset of first byte of
+ * previous legal character
+ * \return CHARSET_OK on success, appropriate error otherwise
+ */
+inline charset_error _dom_utf8_prev(const uint8_t *s, uint32_t off,
+ uint32_t *prevoff)
+{
+ if (s == NULL || prevoff == NULL)
+ return CHARSET_BADPARM;
+
+ while (off != 0 && (s[--off] & 0xC0) == 0x80)
+ /* do nothing */;
+
+ *prevoff = off;
+
+ return CHARSET_OK;
+}
+
+/**
+ * Find next legal UTF-8 char in string
+ *
+ * \param s The string (assumed valid)
+ * \param len Maximum offset in string
+ * \param off Offset in the string to start at
+ * \param nextoff Pointer to location to receive offset of first byte of
+ * next legal character
+ * \return CHARSET_OK on success, appropriate error otherwise
+ */
+inline charset_error _dom_utf8_next(const uint8_t *s, uint32_t len,
+ uint32_t off, uint32_t *nextoff)
+{
+ if (s == NULL || off >= len || nextoff == NULL)
+ return CHARSET_BADPARM;
+
+ /* Skip current start byte (if present - may be mid-sequence) */
+ if (s[off] < 0x80 || (s[off] & 0xC0) == 0xC0)
+ off++;
+
+ while (off < len && (s[off] & 0xC0) == 0x80)
+ off++;
+
+ *nextoff = off;
+
+ return CHARSET_OK;
+}
+
+/**
+ * Find next legal UTF-8 char in string
+ *
+ * \param s The string (assumed to be of dubious validity)
+ * \param len Maximum offset in string
+ * \param off Offset in the string to start at
+ * \param nextoff Pointer to location to receive offset of first byte of
+ * next legal character
+ * \return CHARSET_OK on success, appropriate error otherwise
+ */
+inline charset_error _dom_utf8_next_paranoid(const uint8_t *s, uint32_t len,
+ uint32_t off, uint32_t *nextoff)
+{
+ bool valid;
+
+ if (s == NULL || off >= len || nextoff == NULL)
+ return CHARSET_BADPARM;
+
+ /* Skip current start byte (if present - may be mid-sequence) */
+ if (s[off] < 0x80 || (s[off] & 0xC0) == 0xC0)
+ off++;
+
+ while (1) {
+ /* Find next possible start byte */
+ while (off < len && (s[off] & 0xC0) == 0x80)
+ off++;
+
+ /* Ran off end of data */
+ if (off == len || off + numContinuations[s[off]] >= len)
+ return CHARSET_NEEDDATA;
+
+ /* Found if start byte is ascii,
+ * or next n bytes are valid continuations */
+ valid = true;
+
+ switch (numContinuations[s[off]]) {
+ case 5:
+ valid &= ((s[off + 5] & 0xC0) == 0x80);
+ case 4:
+ valid &= ((s[off + 4] & 0xC0) == 0x80);
+ case 3:
+ valid &= ((s[off + 3] & 0xC0) == 0x80);
+ case 2:
+ valid &= ((s[off + 2] & 0xC0) == 0x80);
+ case 1:
+ valid &= ((s[off + 1] & 0xC0) == 0x80);
+ case 0:
+ valid &= (s[off + 0] < 0x80);
+ }
+
+ if (valid)
+ break;
+
+ /* Otherwise, skip this (invalid) start byte and try again */
+ off++;
+ }
+
+ *nextoff = off;
+
+ return CHARSET_OK;
+}
+
diff --git a/src/utils/utf8.h b/src/utils/utf8.h
new file mode 100644
index 0000000..154dbb8
--- /dev/null
+++ b/src/utils/utf8.h
@@ -0,0 +1,38 @@
+/*
+ * This file is part of libdom.
+ * Licensed under the MIT License,
+ * http://www.opensource.org/licenses/mit-license.php
+ * Copyright 2007 John-Mark Bell <jmb@netsurf-browser.org>
+ */
+
+/** \file
+ * UTF-8 manipulation functions (interface).
+ */
+
+#ifndef dom_utils_utf8_h_
+#define dom_utils_utf8_h_
+
+#include <inttypes.h>
+
+#include "utils/charset_errors.h"
+
+inline charset_error _dom_utf8_to_ucs4(const uint8_t *s, size_t len,
+ uint32_t *ucs4, size_t *clen);
+inline charset_error _dom_utf8_from_ucs4(uint32_t ucs4, uint8_t *s,
+ size_t *len);
+
+inline charset_error _dom_utf8_length(const uint8_t *s, size_t max,
+ size_t *len);
+inline charset_error _dom_utf8_char_byte_length(const uint8_t *s,
+ size_t *len);
+
+inline charset_error _dom_utf8_prev(const uint8_t *s, uint32_t off,
+ uint32_t *prevoff);
+inline charset_error _dom_utf8_next(const uint8_t *s, uint32_t len,
+ uint32_t off, uint32_t *nextoff);
+
+inline charset_error _dom_utf8_next_paranoid(const uint8_t *s, uint32_t len,
+ uint32_t off, uint32_t *nextoff);
+
+#endif
+