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); +}  | 
