Index: src/OFXMLFactory.h ================================================================== --- src/OFXMLFactory.h +++ src/OFXMLFactory.h @@ -6,10 +6,12 @@ * * This file is part of libobjfw. It may be distributed under the terms of the * Q Public License 1.0, which can be found in the file LICENSE included in * the packaging of this file. */ + +#import "wchar.h" #import "OFObject.h" /** * The OFXMLFactory class provides an easy way to create XML stanzas. @@ -22,32 +24,70 @@ * \return The escaped C string. * You need to free it manually! */ + (char*)escapeCString: (const char*)s; +/** + * XML-escapes a wide C string. + * + * \param s The wide C string to escape + * \return The escaped wide C string. + * You need to free it manually! + */ ++ (wchar_t*)escapeWideCString: (const wchar_t*)s; + /** * Creates an XML stanza. * * \param name The name of the tag as a C string * \param close A boolean whether the tag should be closed - * \param data Data that should be inside the tag. + * \param data Data that should be inside the tag as a C string. * It will NOT be escaped, so you can also include other stanzas. - * \param ... Field / value pairs for the tag in the form "field", "value". + * \param ... Field / value pairs for the tag in the form "field", "value" as + * C strings. * Last element must be NULL. * Example: "field1", "value1", "field2", "value2", NULL * \return The created XML stanza as a C string. * You need to free it manually! */ + (char*)createStanza: (const char*)name withCloseTag: (BOOL)close andData: (const char*)data, ...; +/** + * Creates an XML stanza as a wide C string. + * + * \param name The name of the tag as a wide C string + * \param close A boolean whether the tag should be closed + * \param data Data that should be inside the tag as a wide C string. + * It will NOT be escaped, so you can also include other stanzas. + * \param ... Field / value pairs for the tag in the form "field", "value" as + * wide C strings. + * Last element must be NULL. + * Example: L"field1", L"value1", L"field2", L"value2", NULL + * \return The created XML stanza as a wide C string. + * You need to free it manually! + */ ++ (wchar_t*)createWideStanza: (const wchar_t*)name + withCloseTag: (BOOL)close + andData: (const wchar_t*)data, ...; + /** * Concats an array of C strings into one C string and frees the array of C * strings. * * \param strs An array of C strings * \return The concatenated C strings. * You need to free it manually! */ -+ (char*)concatAndFreeCStrings: (char **)strs; ++ (char*)concatAndFreeCStrings: (char**)strs; + +/** + * Concats an array of wide C strings into one wide C string and frees the + * array of wide C strings. + * + * \param strs An array of wide C strings + * \return The concatenated wide C strings. + * You need to free it manually! + */ ++ (wchar_t*)concatAndFreeWideCStrings: (wchar_t**)strs; @end Index: src/OFXMLFactory.m ================================================================== --- src/OFXMLFactory.m +++ src/OFXMLFactory.m @@ -24,14 +24,16 @@ * can prealloc and only resize when really necessary - OFString would always * resize when we append, which would be slow here. */ static inline BOOL -xmlfactory_resize(char **str, size_t *len, size_t add) +xf_resize_chars(char **str, size_t *len, size_t add) { char *str2; size_t len2; + + /* FIXME: Check for overflows on add */ len2 = *len + add; if ((str2 = realloc(*str, len2)) == NULL) { if (*str) @@ -45,22 +47,61 @@ return YES; } static inline BOOL -xmlfactory_add2str(char **str, size_t *len, size_t *pos, const char *add) +xf_resize_wchars(wchar_t **str, size_t *len, size_t add) +{ + wchar_t *str2; + size_t len2; + + /* FIXME: Check for overflows on add and multiply */ + + len2 = *len + add; + + if ((str2 = realloc(*str, len2 * sizeof(wchar_t))) == NULL) { + if (*str) + free(*str); + *str = NULL; + return NO; + } + + *str = str2; + *len = len2; + + return YES; +} + +static inline BOOL +xf_add2chars(char **str, size_t *len, size_t *pos, const char *add) { size_t add_len; add_len = strlen(add); - if (!xmlfactory_resize(str, len, add_len)) + if (!xf_resize_chars(str, len, add_len)) return NO; memcpy(*str + *pos, add, add_len); *pos += add_len; + return YES; +} + +static inline BOOL +xf_add2wchars(wchar_t **str, size_t *len, size_t *pos, const wchar_t *add) +{ + size_t add_len; + + add_len = wcslen(add); + + if (!xf_resize_wchars(str, len, add_len)) + return NO; + + wmemcpy(*str + *pos, add, add_len); + *pos += add_len; + return YES; } @implementation OFXMLFactory + (char*)escapeCString: (const char*)s @@ -75,39 +116,104 @@ andSize: len + 1] raise]; for (i = j = 0; i < len; i++) { switch (s[i]) { case '<': - if (!xmlfactory_add2str(&ret, &nlen, &j, "<")) + if (!xf_add2chars(&ret, &nlen, &j, "<")) [[OFNoMemException newWithObject: nil andSize: nlen + 4] raise]; break; case '>': - if (!xmlfactory_add2str(&ret, &nlen, &j, ">")) + if (!xf_add2chars(&ret, &nlen, &j, ">")) [[OFNoMemException newWithObject: nil andSize: nlen + 4] raise]; break; case '"': - if (!xmlfactory_add2str(&ret, &nlen, &j, """)) + if (!xf_add2chars(&ret, &nlen, &j, """)) [[OFNoMemException newWithObject: nil andSize: nlen + 6] raise]; break; case '\'': - if (!xmlfactory_add2str(&ret, &nlen, &j, "'")) + if (!xf_add2chars(&ret, &nlen, &j, "'")) [[OFNoMemException newWithObject: nil andSize: nlen + 6] raise]; break; case '&': - if (!xmlfactory_add2str(&ret, &nlen, &j, "&")) + if (!xf_add2chars(&ret, &nlen, &j, "&")) [[OFNoMemException newWithObject: nil andSize: nlen + 5] raise]; break; + default: + ret[j++] = s[i]; + break; + } + } + + ret[j] = 0; + return ret; +} + ++ (wchar_t*)escapeWideCString: (const wchar_t*)s +{ + wchar_t *ret; + size_t i, j, len, nlen; + + len = nlen = wcslen(s); + + /* FIXME: Check for overflow in multiply */ + if ((ret = malloc((len + 1) * sizeof(wchar_t))) == NULL) + [[OFNoMemException newWithObject: nil + andSize: (len + 1) * sizeof(wchar_t)] + raise]; + + for (i = j = 0; i < len; i++) { + switch (s[i]) { + case L'<': + if (!xf_add2wchars(&ret, &nlen, &j, L"<")) + [[OFNoMemException newWithObject: nil + andSize: (nlen + 4) * + sizeof( + wchar_t)] + raise]; + break; + case L'>': + if (!xf_add2wchars(&ret, &nlen, &j, L">")) + [[OFNoMemException newWithObject: nil + andSize: (nlen + 4) * + sizeof( + wchar_t)] + raise]; + break; + case L'"': + if (!xf_add2wchars(&ret, &nlen, &j, L""")) + [[OFNoMemException newWithObject: nil + andSize: (nlen + 6) * + sizeof( + wchar_t)] + raise]; + break; + case L'\'': + if (!xf_add2wchars(&ret, &nlen, &j, L"'")) + [[OFNoMemException newWithObject: nil + andSize: (nlen + 6) * + sizeof( + wchar_t)] + raise]; + break; + case L'&': + if (!xf_add2wchars(&ret, &nlen, &j, L"&")) + [[OFNoMemException newWithObject: nil + andSize: (nlen + 5) * + sizeof( + wchar_t)] + raise]; + break; default: ret[j++] = s[i]; break; } } @@ -148,11 +254,11 @@ */ free(xml); return NULL; } - if (!xmlfactory_resize(&xml, &len, 1 + strlen(arg) + 2 + + if (!xf_resize_chars(&xml, &len, 1 + strlen(arg) + 2 + strlen(esc_val) + 1)) { free(esc_val); [[OFNoMemException newWithObject: nil andSize: len + 1 + strlen(arg) + 2 + @@ -174,19 +280,19 @@ va_end(args); /* End of tag */ if (close) { if (data == NULL) { - if (!xmlfactory_resize(&xml, &len, 2 - 1)) + if (!xf_resize_chars(&xml, &len, 2 - 1)) [[OFNoMemException newWithObject: nil andSize: len + 2 - 1] raise]; xml[i++] = '/'; xml[i++] = '>'; } else { - if (!xmlfactory_resize(&xml, &len, 1 + strlen(data) + + if (!xf_resize_chars(&xml, &len, 1 + strlen(data) + 2 + strlen(name) + 1 - 1)) [[OFNoMemException newWithObject: nil andSize: len + 1 + strlen(data) + 2 + @@ -208,11 +314,111 @@ xml[i] = 0; return xml; } -+ (char*)concatAndFreeCStrings: (char **)strs ++ (wchar_t*)createWideStanza: (const wchar_t*)name + withCloseTag: (BOOL)close + andData: (const wchar_t*)data, ... +{ + wchar_t *arg, *val, *xml; + size_t i, len; + va_list args; + + /* Start of tag */ + len = wcslen(name) + 3; + /* TODO: Check for multiply overflow */ + if ((xml = malloc(len * sizeof(wchar_t*))) == NULL) + [[OFNoMemException newWithObject: nil + andSize: len * sizeof(wchar_t)] raise]; + + i = 0; + xml[i++] = L'<'; + wmemcpy(xml + i, name, wcslen(name)); + i += wcslen(name); + + /* Arguments */ + va_start(args, data); + while ((arg = va_arg(args, wchar_t*)) != NULL && + (val = va_arg(args, wchar_t*)) != NULL) { + wchar_t *esc_val; + + if ((esc_val = [OFXMLFactory escapeWideCString: val]) == NULL) { + /* + * escapeWideCString already throws an exception, + * no need to throw a second one here. + */ + free(xml); + return NULL; + } + + if (!xf_resize_wchars(&xml, &len, 1 + wcslen(arg) + 2 + + wcslen(esc_val) + 1)) { + free(esc_val); + [[OFNoMemException newWithObject: nil + andSize: (len + 1 + + wcslen(arg) + 2 + + wcslen(esc_val) + 1) * + sizeof(wchar_t)] + raise]; + } + + xml[i++] = L' '; + wmemcpy(xml + i, arg, wcslen(arg)); + i += wcslen(arg); + xml[i++] = L'='; + xml[i++] = L'\''; + wmemcpy(xml + i, esc_val, wcslen(esc_val)); + i += wcslen(esc_val); + xml[i++] = L'\''; + + free(esc_val); + } + va_end(args); + + /* End of tag */ + if (close) { + if (data == NULL) { + if (!xf_resize_wchars(&xml, &len, 2 - 1)) + [[OFNoMemException newWithObject: nil + andSize: (len + 2 + - 1) * sizeof( + wchar_t)] + raise]; + + xml[i++] = L'/'; + xml[i++] = L'>'; + } else { + if (!xf_resize_wchars(&xml, &len, 1 + wcslen(data) + + 2 + wcslen(name) + 1 - 1)) + [[OFNoMemException newWithObject: nil + andSize: (len + 1 + + wcslen(data) + + 2 + + wcslen(name) + + 1 - + 1) * sizeof( + wchar_t)] + raise]; + + xml[i++] = L'>'; + wmemcpy(xml + i, data, wcslen(data)); + i += wcslen(data); + xml[i++] = L'<'; + xml[i++] = L'/'; + wmemcpy(xml + i, name, wcslen(name)); + i += wcslen(name); + xml[i++] = L'>'; + } + } else + xml[i++] = L'>'; + + xml[i] = 0; + return xml; +} + ++ (char*)concatAndFreeCStrings: (char**)strs { char *ret; size_t i, len, pos; if (strs[0] == NULL) @@ -226,20 +432,56 @@ memcpy(ret, strs[0], len - 1); pos = len - 1; for (i = 1; strs[i] != NULL; i++) { - if (!xmlfactory_add2str(&ret, &len, &pos, strs[i])) { + if (!xf_add2chars(&ret, &len, &pos, strs[i])) { free(ret); [[OFNoMemException newWithObject: nil andSize: len + strlen(strs[i])] raise]; } } + + for (i = 0; strs[i] != NULL; i++) + free(strs[i]); + + ret[pos] = 0; + return ret; +} + ++ (wchar_t*)concatAndFreeWideCStrings: (wchar_t**)strs +{ + wchar_t *ret; + size_t i, len, pos; + + if (strs[0] == NULL) + return NULL; + + len = wcslen(*strs) + 1; + + /* FIXME: Check for overflow on multiply */ + if ((ret = malloc(len * sizeof(wchar_t))) == NULL) + [[OFNoMemException newWithObject: nil + andSize: len * sizeof(wchar_t)] raise]; + + wmemcpy(ret, strs[0], len - 1); + pos = len - 1; + + for (i = 1; strs[i] != NULL; i++) { + if (!xf_add2wchars(&ret, &len, &pos, strs[i])) { + free(ret); + [[OFNoMemException newWithObject: nil + andSize: (wcslen(strs[i]) + + len) * sizeof( + wchar_t)] + raise]; + } + } for (i = 0; strs[i] != NULL; i++) free(strs[i]); ret[pos] = 0; return ret; } @end Index: tests/OFXMLFactory/OFXMLFactory.m ================================================================== --- tests/OFXMLFactory/OFXMLFactory.m +++ tests/OFXMLFactory/OFXMLFactory.m @@ -10,20 +10,34 @@ */ #import #import #import +#import #import "OFXMLFactory.h" inline void check_result(char *result, const char *should) { if (!strcmp(result, should)) printf("%s is expected result\n", result); else { - printf("%s is NOT expected result!", result); + printf("%s is NOT expected result!\n", result); + exit(1); + } + + free(result); +} + +inline void +check_result_wide(wchar_t *result, const wchar_t *should) +{ + if (!wcscmp(result, should)) + wprintf(L"%ls is expected result\n", result); + else { + wprintf(L"%ls is NOT expected result!\n", result); exit(1); } free(result); } @@ -50,10 +64,35 @@ strs[3] = NULL; check_result([OFXMLFactory concatAndFreeCStrings: strs], "bar"); } + +inline void +test_concat_wide() +{ + const wchar_t *c1 = L"", *c2 = L"bar", *c3 = L""; + wchar_t *s1, *s2, *s3; + wchar_t *strs[4]; + + if ((s1 = malloc((wcslen(c1) + 1) * sizeof(wchar_t))) == NULL || + (s2 = malloc((wcslen(c2) + 1) * sizeof(wchar_t))) == NULL || + (s3 = malloc((wcslen(c3) + 1) * sizeof(wchar_t))) == NULL) + exit(1); + + wcsncpy(s1, c1, wcslen(c1) + 1); + wcsncpy(s2, c2, wcslen(c2) + 1); + wcsncpy(s3, c3, wcslen(c3) + 1); + + strs[0] = s1; + strs[1] = s2; + strs[2] = s3; + strs[3] = NULL; + + check_result_wide([OFXMLFactory concatAndFreeWideCStrings: strs], + L"bar"); +} inline void test_create_stanza() { check_result([OFXMLFactory createStanza: "foo" @@ -105,21 +144,91 @@ "x", "y", "a", "b", NULL], "bar"); } + +inline void +test_create_stanza_wide() +{ + check_result_wide([OFXMLFactory createWideStanza: L"foo" + withCloseTag: NO + andData: NULL, + NULL], + L""); + + check_result_wide([OFXMLFactory createWideStanza: L"foo" + withCloseTag: NO + andData: NULL, + L"bar", L"baz", + L"blub", L"asd", + NULL], + L""); + check_result_wide([OFXMLFactory createWideStanza: L"foo" + withCloseTag: YES + andData: NULL, + NULL], + L""); + check_result_wide([OFXMLFactory createWideStanza: L"foo" + withCloseTag: YES + andData: L"bar", + NULL], + L"bar"); + check_result_wide([OFXMLFactory createWideStanza: L"foo" + withCloseTag: YES + andData: NULL, + L"bar", L"b&az", + NULL], + L""); + check_result_wide([OFXMLFactory createWideStanza: L"foo" + withCloseTag: YES + andData: L"bar", + L"bar", L"b'az", + NULL], + L"bar"); + check_result_wide([OFXMLFactory createWideStanza: L"foo" + withCloseTag: YES + andData: NULL, + L"bar", L"b&az", + L"x", L"asd\"", + NULL], + L""); + check_result_wide([OFXMLFactory createWideStanza: L"foo" + withCloseTag: YES + andData: L"bar", + L"bar", L"b'az", + L"x", L"y", + L"a", L"b", + NULL], + L"bar"); +} inline void test_escape() { check_result([OFXMLFactory escapeCString: " &welt'\"!&"], "<hallo> &welt'"!&"); } + +inline void +test_escape_wide() +{ + check_result_wide( + [OFXMLFactory escapeWideCString: L" &welt'\"!&"], + L"<hallo> &welt'"!&"); +} + int main() { test_escape(); test_create_stanza(); test_concat(); + + puts("== Now testing with wide C strings =="); + + test_escape_wide(); + test_create_stanza_wide(); + test_concat_wide(); return 0; }