diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | Makefile.am | 20 | ||||
-rw-r--r-- | src/blogc.c | 6 | ||||
-rw-r--r-- | src/file.c | 9 | ||||
-rw-r--r-- | src/utf8.c | 88 | ||||
-rw-r--r-- | src/utf8.h | 19 | ||||
-rw-r--r-- | tests/check_utf8.c | 83 |
7 files changed, 226 insertions, 0 deletions
@@ -57,6 +57,7 @@ blogc*.html /tests/check_renderer /tests/check_source_parser /tests/check_template_parser +/tests/check_utf8 /tests/check_utils # tarballs diff --git a/Makefile.am b/Makefile.am index 11dde62..2582881 100644 --- a/Makefile.am +++ b/Makefile.am @@ -41,6 +41,7 @@ noinst_HEADERS = \ src/renderer.h \ src/source-parser.h \ src/template-parser.h \ + src/utf8.h \ src/utils.h \ $(NULL) @@ -94,6 +95,7 @@ libblogc_la_LIBADD = \ libblogc_utils_la_SOURCES = \ + src/utf8.c \ src/utils.c \ $(NULL) @@ -277,6 +279,7 @@ check_PROGRAMS += \ tests/check_renderer \ tests/check_source_parser \ tests/check_template_parser \ + tests/check_utf8 \ tests/check_utils \ $(NULL) @@ -408,6 +411,23 @@ tests_check_template_parser_LDADD = \ libblogc_utils.la \ $(NULL) +tests_check_utf8_SOURCES = \ + tests/check_utf8.c \ + $(NULL) + +tests_check_utf8_CFLAGS = \ + $(CMOCKA_CFLAGS) \ + $(NULL) + +tests_check_utf8_LDFLAGS = \ + -no-install \ + $(NULL) + +tests_check_utf8_LDADD = \ + $(CMOCKA_LIBS) \ + libblogc_utils.la \ + $(NULL) + tests_check_utils_SOURCES = \ tests/check_utils.c \ $(NULL) diff --git a/src/blogc.c b/src/blogc.c index 40a5918..4606740 100644 --- a/src/blogc.c +++ b/src/blogc.c @@ -30,6 +30,7 @@ #include "loader.h" #include "renderer.h" #include "error.h" +#include "utf8.h" #include "utils.h" #ifndef PACKAGE_VERSION @@ -125,6 +126,11 @@ main(int argc, char **argv) sb_trie_insert(config, "BLOGC_VERSION", sb_strdup(PACKAGE_VERSION)); for (unsigned int i = 1; i < argc; i++) { + if (!blogc_utf8_validate((uint8_t*) argv[i], strlen(argv[i]))) { + fprintf(stderr, "blogc: error: command-line argument is not utf8-" + "encoded: %s\n", argv[i]); + goto cleanup; + } tmp = NULL; if (argv[i][0] == '-') { switch (argv[i][1]) { @@ -16,6 +16,7 @@ #include <string.h> #include "file.h" #include "error.h" +#include "utf8.h" #include "utils.h" // this would belong to loader.c, but we need it in a separated file to be @@ -47,6 +48,14 @@ blogc_file_get_contents(const char *path, size_t *len, blogc_error_t **err) sb_string_append_len(str, buffer, read_len); } fclose(fp); + + if (!blogc_utf8_validate_str(str)) { + *err = blogc_error_new_printf(BLOGC_ERROR_LOADER, + "File content is not valid UTF-8: %s", path); + sb_string_free(str, true); + return NULL; + } + return sb_string_free(str, false); } diff --git a/src/utf8.c b/src/utf8.c new file mode 100644 index 0000000..deea46d --- /dev/null +++ b/src/utf8.c @@ -0,0 +1,88 @@ +/* + * blogc: A blog compiler. + * Copyright (c) 2008-2010 Bjoern Hoehrmann <bjoern@hoehrmann.de> + * Copyright (c) 2016 Rafael G. Martins <rafael@rafaelmartins.eng.br> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +// Based on Bjoern Hoehrmann's algorithm. +// See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. + +#include <stdbool.h> +#include <inttypes.h> +#include "utils.h" + +#define UTF8_ACCEPT 0 +#define UTF8_REJECT 12 + + +static const uint8_t utf8d[] = { + // The first part of the table maps bytes to character classes that + // to reduce the size of the transition table and create bitmasks. + 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, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 8,8,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, + 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8, + + // The second part is a transition table that maps a combination + // of a state of the automaton and a character class to a state. + 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12, + 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12, + 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12, + 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12, + 12,36,12,12,12,12,12,12,12,12,12,12, +}; + + +static uint32_t inline +decode(uint32_t* state, uint32_t* codep, uint32_t byte) { + uint32_t type = utf8d[byte]; + + *codep = (*state != UTF8_ACCEPT) ? + (byte & 0x3fu) | (*codep << 6) : + (0xff >> type) & (byte); + + *state = utf8d[256 + *state + type]; + return *state; +} + + +bool +blogc_utf8_validate(const uint8_t *str, size_t len) +{ + uint32_t codepoint; + uint32_t state = 0; + + for (size_t i = 0; i < len; i++) + decode(&state, &codepoint, str[i]); + + return state == UTF8_ACCEPT; +} + + +bool +blogc_utf8_validate_str(sb_string_t *str) +{ + return blogc_utf8_validate((uint8_t*) str->str, str->len); +} diff --git a/src/utf8.h b/src/utf8.h new file mode 100644 index 0000000..837d02f --- /dev/null +++ b/src/utf8.h @@ -0,0 +1,19 @@ +/* + * blogc: A blog compiler. + * Copyright (C) 2015-2016 Rafael G. Martins <rafael@rafaelmartins.eng.br> + * + * This program can be distributed under the terms of the BSD License. + * See the file LICENSE. + */ + +#ifndef _UTF_8_H +#define _UTF_8_H + +#include <inttypes.h> +#include <stdbool.h> +#include "utils.h" + +bool blogc_utf8_validate(const uint8_t *str, size_t len); +bool blogc_utf8_validate_str(sb_string_t *str); + +#endif /* _UTF_8_H */ diff --git a/tests/check_utf8.c b/tests/check_utf8.c new file mode 100644 index 0000000..b0dec4e --- /dev/null +++ b/tests/check_utf8.c @@ -0,0 +1,83 @@ +/* + * blogc: A blog compiler. + * Copyright (C) 2015-2016 Rafael G. Martins <rafael@rafaelmartins.eng.br> + * + * This program can be distributed under the terms of the BSD License. + * See the file LICENSE. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <stdarg.h> +#include <stddef.h> +#include <setjmp.h> +#include <cmocka.h> +#include <string.h> +#include "../src/utf8.h" +#include "../src/utils.h" + + +static void +test_utf8_valid(void **state) +{ + const char *c = "<a href=\"{{ BASE_URL }}/page/{{ PREVIOUS_PAGE }}/\">" + "\xc2\xab Newer posts</a>"; + assert_true(blogc_utf8_validate((uint8_t*) c, strlen(c))); + const uint8_t d[3] = {0xe2, 0x82, 0xac}; + assert_true(blogc_utf8_validate(d, 3)); +} + + +static void +test_utf8_invalid(void **state) +{ + const uint8_t c[4] = {0xff, 0xfe, 0xac, 0x20}; // utf-16 + assert_false(blogc_utf8_validate(c, 4)); + const uint8_t d[8] = {0xff, 0xfe, 0x00, 0x00, 0xac, 0x20, 0x00, 0x00}; // utf-32 + assert_false(blogc_utf8_validate(d, 8)); +} + + +static void +test_utf8_valid_str(void **state) +{ + sb_string_t *s = sb_string_new(); + sb_string_append(s, + "<a href=\"{{ BASE_URL }}/page/{{ PREVIOUS_PAGE }}/\">\xc2\xab Newer " + "posts</a>"); + assert_true(blogc_utf8_validate_str(s)); + sb_string_free(s, true); + s = sb_string_new(); + sb_string_append(s, "\xe2\x82\xac"); + assert_true(blogc_utf8_validate_str(s)); + sb_string_free(s, true); +} + + +static void +test_utf8_invalid_str(void **state) +{ + sb_string_t *s = sb_string_new(); + sb_string_append(s, "\xff\xfe\xac\x20"); // utf-16 + assert_false(blogc_utf8_validate_str(s)); + sb_string_free(s, true); + s = sb_string_new(); + sb_string_append(s, "\xff\xfe\x00\x00\xac\x20\x00\x00"); // utf-32 + assert_false(blogc_utf8_validate_str(s)); + sb_string_free(s, true); +} + + +int +main(void) +{ + const UnitTest tests[] = { + unit_test(test_utf8_valid), + unit_test(test_utf8_invalid), + unit_test(test_utf8_valid_str), + unit_test(test_utf8_invalid_str), + }; + return run_tests(tests); +} |