diff --git a/fuzzing/CMakeLists.txt b/fuzzing/CMakeLists.txt index 5ed60de..54213b8 100644 --- a/fuzzing/CMakeLists.txt +++ b/fuzzing/CMakeLists.txt @@ -8,7 +8,8 @@ set(HEADERS ../src/html_blocks.h ../src/html_entities.h ../src/markdown.h - ../src/stack.h + ../src/stack.h + ../src/renderers.h ) set(LIBRARY_SOURCES ../html/houdini_href_e.c @@ -18,7 +19,8 @@ set(LIBRARY_SOURCES ../src/autolink.c ../src/buffer.c ../src/markdown.c - ../src/stack.c + ../src/stack.c + ../src/renderers.c ${HEADERS} ) diff --git a/fuzzing/snudown-validator.c b/fuzzing/snudown-validator.c index 153e1c4..c12b022 100644 --- a/fuzzing/snudown-validator.c +++ b/fuzzing/snudown-validator.c @@ -1,6 +1,7 @@ #include "markdown.h" #include "html.h" #include "buffer.h" +#include "../src/renderers.h" #include #include @@ -19,114 +20,21 @@ #define SNUDOWN_VERSION "1.3.2" -enum snudown_renderer_mode { - RENDERER_USERTEXT = 0, - RENDERER_WIKI, - RENDERER_COUNT -}; - -struct snudown_renderopt { - struct html_renderopt html; - int nofollow; - const char *target; -}; - -struct snudown_renderer { - struct sd_markdown* main_renderer; - struct sd_markdown* toc_renderer; - struct module_state* state; - struct module_state* toc_state; -}; - -struct module_state { - struct sd_callbacks callbacks; - struct snudown_renderopt options; -}; - static struct snudown_renderer sundown[RENDERER_COUNT]; -static char* html_element_whitelist[] = {"tr", "th", "td", "table", "tbody", "thead", "tfoot", "caption", NULL}; -static char* html_attr_whitelist[] = {"colspan", "rowspan", "cellspacing", "cellpadding", "scope", NULL}; - -static struct module_state usertext_toc_state; -static struct module_state wiki_toc_state; -static struct module_state usertext_state; -static struct module_state wiki_state; - -static const unsigned int snudown_default_md_flags = - MKDEXT_NO_INTRA_EMPHASIS | - MKDEXT_SUPERSCRIPT | - MKDEXT_AUTOLINK | - MKDEXT_STRIKETHROUGH | - MKDEXT_TABLES; - -static const unsigned int snudown_default_render_flags = - HTML_SKIP_HTML | - HTML_SKIP_IMAGES | - HTML_SAFELINK | - HTML_ESCAPE | - HTML_USE_XHTML; - -static const unsigned int snudown_wiki_render_flags = - HTML_SKIP_HTML | - HTML_SAFELINK | - HTML_ALLOW_ELEMENT_WHITELIST | - HTML_ESCAPE | - HTML_USE_XHTML; - -static void -snudown_link_attr(struct buf *ob, const struct buf *link, void *opaque) -{ - struct snudown_renderopt *options = opaque; - - if (options->nofollow) - BUFPUTSL(ob, " rel=\"nofollow\""); - - if (options->target != NULL) { - BUFPUTSL(ob, " target=\""); - bufputs(ob, options->target); - bufputc(ob, '\"'); - } -} - -static struct sd_markdown* make_custom_renderer(struct module_state* state, - const unsigned int renderflags, - const unsigned int markdownflags, - int toc_renderer) { - if(toc_renderer) { - sdhtml_toc_renderer(&state->callbacks, - (struct html_renderopt *)&state->options); - } else { - sdhtml_renderer(&state->callbacks, - (struct html_renderopt *)&state->options, - renderflags); - } - - state->options.html.link_attributes = &snudown_link_attr; - state->options.html.html_element_whitelist = html_element_whitelist; - state->options.html.html_attr_whitelist = html_attr_whitelist; - - return sd_markdown_new( - markdownflags, - 16, - 64, - &state->callbacks, - &state->options - ); -} - void init_default_renderer() { - sundown[RENDERER_USERTEXT].main_renderer = make_custom_renderer(&usertext_state, snudown_default_render_flags, snudown_default_md_flags, 0); - sundown[RENDERER_USERTEXT].toc_renderer = make_custom_renderer(&usertext_toc_state, snudown_default_render_flags, snudown_default_md_flags, 1); - sundown[RENDERER_USERTEXT].state = &usertext_state; - sundown[RENDERER_USERTEXT].toc_state = &usertext_toc_state; + struct snudown_renderer *renderer = get_default_renderer(); + sundown[RENDERER_USERTEXT] = *renderer; } void init_wiki_renderer() { - sundown[RENDERER_WIKI].main_renderer = make_custom_renderer(&wiki_state, snudown_wiki_render_flags, snudown_default_md_flags, 0); - sundown[RENDERER_WIKI].toc_renderer = make_custom_renderer(&wiki_toc_state, snudown_wiki_render_flags, snudown_default_md_flags, 1); - sundown[RENDERER_WIKI].state = &wiki_state; - sundown[RENDERER_WIKI].toc_state = &wiki_toc_state; + struct snudown_renderer *renderer = get_wiki_renderer(); + sundown[RENDERER_WIKI] = *renderer; +} + +void init_default_renderer_without_links() { + struct snudown_renderer *renderer = get_default_renderer_without_links(); + sundown[RENDERER_USERTEXT_WITHOUTLINKS] = *renderer; } void @@ -172,6 +80,7 @@ main(int argc, char **argv) { init_default_renderer(); init_wiki_renderer(); + init_default_renderer_without_links(); struct buf *ib, *ob; int size_read = 0, wiki_mode = 0, i = 0, have_errors = 0; diff --git a/snudown.c b/snudown.c index e268f66..0262143 100644 --- a/snudown.c +++ b/snudown.c @@ -2,125 +2,33 @@ #include #include "markdown.h" -#include "html.h" #include "autolink.h" +#include "renderers.h" -#define SNUDOWN_VERSION "1.4.0" - -enum snudown_renderer_mode { - RENDERER_USERTEXT = 0, - RENDERER_WIKI, - RENDERER_COUNT -}; - -struct snudown_renderopt { - struct html_renderopt html; - int nofollow; - const char *target; -}; - -struct snudown_renderer { - struct sd_markdown* main_renderer; - struct sd_markdown* toc_renderer; - struct module_state* state; - struct module_state* toc_state; -}; - -struct module_state { - struct sd_callbacks callbacks; - struct snudown_renderopt options; -}; - -static struct snudown_renderer sundown[RENDERER_COUNT]; - -static char* html_element_whitelist[] = {"tr", "th", "td", "table", "tbody", "thead", "tfoot", "caption", NULL}; -static char* html_attr_whitelist[] = {"colspan", "rowspan", "cellspacing", "cellpadding", "scope", NULL}; - -static struct module_state usertext_toc_state; -static struct module_state wiki_toc_state; -static struct module_state usertext_state; -static struct module_state wiki_state; +#define SNUDOWN_VERSION "1.5.0" /* The module doc strings */ PyDoc_STRVAR(snudown_module__doc__, "When does the narwhal bacon? At Sundown."); PyDoc_STRVAR(snudown_md__doc__, "Render a Markdown document"); -static const unsigned int snudown_default_md_flags = - MKDEXT_NO_INTRA_EMPHASIS | - MKDEXT_SUPERSCRIPT | - MKDEXT_AUTOLINK | - MKDEXT_STRIKETHROUGH | - MKDEXT_TABLES; - -static const unsigned int snudown_default_render_flags = - HTML_SKIP_HTML | - HTML_SKIP_IMAGES | - HTML_SAFELINK | - HTML_ESCAPE | - HTML_USE_XHTML; - -static const unsigned int snudown_wiki_render_flags = - HTML_SKIP_HTML | - HTML_SAFELINK | - HTML_ALLOW_ELEMENT_WHITELIST | - HTML_ESCAPE | - HTML_USE_XHTML; - -static void -snudown_link_attr(struct buf *ob, const struct buf *link, void *opaque) -{ - struct snudown_renderopt *options = opaque; - - if (options->nofollow) - BUFPUTSL(ob, " rel=\"nofollow\""); - - if (options->target != NULL) { - BUFPUTSL(ob, " target=\""); - bufputs(ob, options->target); - bufputc(ob, '\"'); - } -} - -static struct sd_markdown* make_custom_renderer(struct module_state* state, - const unsigned int renderflags, - const unsigned int markdownflags, - int toc_renderer) { - if(toc_renderer) { - sdhtml_toc_renderer(&state->callbacks, - (struct html_renderopt *)&state->options); - } else { - sdhtml_renderer(&state->callbacks, - (struct html_renderopt *)&state->options, - renderflags); - } - - state->options.html.link_attributes = &snudown_link_attr; - state->options.html.html_element_whitelist = html_element_whitelist; - state->options.html.html_attr_whitelist = html_attr_whitelist; - - return sd_markdown_new( - markdownflags, - 16, - 64, - &state->callbacks, - &state->options - ); -} +static struct snudown_renderer sundown[RENDERER_COUNT]; -void init_default_renderer(PyObject *module) { +void register_default_renderer(PyObject *module) { PyModule_AddIntConstant(module, "RENDERER_USERTEXT", RENDERER_USERTEXT); - sundown[RENDERER_USERTEXT].main_renderer = make_custom_renderer(&usertext_state, snudown_default_render_flags, snudown_default_md_flags, 0); - sundown[RENDERER_USERTEXT].toc_renderer = make_custom_renderer(&usertext_toc_state, snudown_default_render_flags, snudown_default_md_flags, 1); - sundown[RENDERER_USERTEXT].state = &usertext_state; - sundown[RENDERER_USERTEXT].toc_state = &usertext_toc_state; + struct snudown_renderer *renderer = get_default_renderer(); + sundown[RENDERER_USERTEXT] = *renderer; } -void init_wiki_renderer(PyObject *module) { +void register_wiki_renderer(PyObject *module) { PyModule_AddIntConstant(module, "RENDERER_WIKI", RENDERER_WIKI); - sundown[RENDERER_WIKI].main_renderer = make_custom_renderer(&wiki_state, snudown_wiki_render_flags, snudown_default_md_flags, 0); - sundown[RENDERER_WIKI].toc_renderer = make_custom_renderer(&wiki_toc_state, snudown_wiki_render_flags, snudown_default_md_flags, 1); - sundown[RENDERER_WIKI].state = &wiki_state; - sundown[RENDERER_WIKI].toc_state = &wiki_toc_state; + struct snudown_renderer *renderer = get_wiki_renderer(); + sundown[RENDERER_WIKI] = *renderer; +} + +void register_default_renderer_without_links(PyObject *module) { + PyModule_AddIntConstant(module, "RENDERER_USERTEXT_WITHOUTLINKS", RENDERER_USERTEXT_WITHOUTLINKS); + struct snudown_renderer *renderer = get_default_renderer_without_links(); + sundown[RENDERER_USERTEXT_WITHOUTLINKS] = *renderer; } static PyObject * @@ -204,9 +112,10 @@ PyMODINIT_FUNC initsnudown(void) if (module == NULL) return; - init_default_renderer(module); - init_wiki_renderer(module); + register_default_renderer(module); + register_wiki_renderer(module); + register_default_renderer_without_links(module); /* Version */ PyModule_AddStringConstant(module, "__version__", SNUDOWN_VERSION); -} +} \ No newline at end of file diff --git a/src/renderers.c b/src/renderers.c new file mode 100644 index 0000000..6b9c735 --- /dev/null +++ b/src/renderers.c @@ -0,0 +1,100 @@ +#include "markdown.h" +#include "../html/html.h" +#include "renderers.h" + +static struct module_state usertext_toc_state; +static struct module_state wiki_toc_state; +static struct module_state usertext_state; +static struct module_state wiki_state; +static char* html_element_whitelist[] = { "tr", "th", "td", "table", "tbody", "thead", "tfoot", "caption", NULL }; +static char* html_attr_whitelist[] = { "colspan", "rowspan", "cellspacing", "cellpadding", "scope", NULL }; + +static struct sd_markdown *make_custom_renderer(struct module_state *state, + const unsigned int renderflags, + const unsigned int markdownflags, + int toc_renderer) { + if (toc_renderer) { + sdhtml_toc_renderer(&state->callbacks, + (struct html_renderopt *)&state->options); + } + else { + sdhtml_renderer(&state->callbacks, + (struct html_renderopt *)&state->options, + renderflags); + } + + state->options.html.link_attributes = &snudown_link_attr; + state->options.html.html_element_whitelist = html_element_whitelist; + state->options.html.html_attr_whitelist = html_attr_whitelist; + + return sd_markdown_new( + markdownflags, + 16, + 64, + &state->callbacks, + &state->options + ); +} + +struct snudown_renderer *get_default_renderer() { + struct snudown_renderer *sr = NULL; + + sr = calloc(1, sizeof(struct snudown_renderer)); + + if (!sr) + return NULL; + + sr->main_renderer = make_custom_renderer(&usertext_state, snudown_default_render_flags, snudown_default_md_flags, 0); + sr->toc_renderer = make_custom_renderer(&usertext_toc_state, snudown_default_render_flags, snudown_default_md_flags, 1); + sr->state = &usertext_state; + sr->toc_state = &usertext_toc_state; + + return sr; +} + +struct snudown_renderer *get_wiki_renderer() { + struct snudown_renderer *sr = NULL; + + sr = calloc(1, sizeof(struct snudown_renderer)); + + if (!sr) + return NULL; + + sr->main_renderer = make_custom_renderer(&wiki_state, snudown_wiki_render_flags, snudown_default_md_flags, 0); + sr->toc_renderer = make_custom_renderer(&wiki_toc_state, snudown_wiki_render_flags, snudown_default_md_flags, 1); + sr->state = &wiki_state; + sr->toc_state = &wiki_toc_state; + + return sr; +} + +struct snudown_renderer *get_default_renderer_without_links() { + struct snudown_renderer *sr = NULL; + + sr = calloc(1, sizeof(struct snudown_renderer)); + + if (!sr) + return NULL; + + sr->main_renderer = make_custom_renderer(&usertext_state, snudown_default_render_flags, snudown_default_md_flags_without_links, 0); + sr->toc_renderer = make_custom_renderer(&usertext_toc_state, snudown_default_render_flags, snudown_default_md_flags_without_links, 1); + sr->state = &usertext_state; + sr->toc_state = &usertext_toc_state; + + return sr; +} + +static void +snudown_link_attr(struct buf *ob, const struct buf *link, void *opaque) +{ + struct snudown_renderopt *options = opaque; + + if (options->nofollow) + BUFPUTSL(ob, " rel=\"nofollow\""); + + if (options->target != NULL) { + BUFPUTSL(ob, " target=\""); + bufputs(ob, options->target); + bufputc(ob, '\"'); + } +} \ No newline at end of file diff --git a/src/renderers.h b/src/renderers.h new file mode 100644 index 0000000..9780dcb --- /dev/null +++ b/src/renderers.h @@ -0,0 +1,82 @@ +/* renderers.h - header for renderers */ + +/* +* Copyright (c) 2017, Reddit +* +* Permission to use, copy, modify, and distribute this software for any +* purpose with or without fee is hereby granted, provided that the above +* copyright notice and this permission notice appear in all copies. +* +* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +#include "../html/html.h" + +enum snudown_renderer_mode { + RENDERER_USERTEXT = 0, + RENDERER_WIKI, + RENDERER_USERTEXT_WITHOUTLINKS, + RENDERER_COUNT +}; + +struct snudown_renderopt { + struct html_renderopt html; + int nofollow; + const char *target; +}; + +struct snudown_renderer { + struct sd_markdown* main_renderer; + struct sd_markdown* toc_renderer; + struct module_state* state; + struct module_state* toc_state; +}; + +struct module_state { + struct sd_callbacks callbacks; + struct snudown_renderopt options; +}; + +static const unsigned int snudown_default_md_flags = + MKDEXT_NO_INTRA_EMPHASIS | + MKDEXT_SUPERSCRIPT | + MKDEXT_AUTOLINK | + MKDEXT_STRIKETHROUGH | + MKDEXT_TABLES; + +static const unsigned int snudown_default_md_flags_without_links = + MKDEXT_NO_INTRA_EMPHASIS | + MKDEXT_SUPERSCRIPT | + MKDEXT_STRIKETHROUGH | + MKDEXT_TABLES; + +static const unsigned int snudown_default_render_flags = + HTML_SKIP_HTML | + HTML_SKIP_IMAGES | + HTML_SAFELINK | + HTML_ESCAPE | + HTML_USE_XHTML; + +static const unsigned int snudown_wiki_render_flags = + HTML_SKIP_HTML | + HTML_SAFELINK | + HTML_ALLOW_ELEMENT_WHITELIST | + HTML_ESCAPE | + HTML_USE_XHTML; + +static void +snudown_link_attr(struct buf *ob, const struct buf *link, void *opaque); + +extern struct snudown_renderer * +get_default_renderer(void); + +extern struct snudown_renderer * +get_wiki_renderer(void); + +extern struct snudown_renderer * +get_default_renderer_without_links(void); \ No newline at end of file diff --git a/test_snudown.py b/test_snudown.py index fa9568f..9b1316c 100644 --- a/test_snudown.py +++ b/test_snudown.py @@ -422,6 +422,70 @@ '

\n', } + +no_autolinks_cases = { + 'http://www.reddit.com': + '

http://www.reddit.com

\n', + + 'http://www.reddit.com/a\x00b': + '

http://www.reddit.com/ab

\n', + + 'ampersands http://www.google.com?test&blah': + '

ampersands http://www.google.com?test&blah

\n', + + 'www.http://example.com/': + '

www.http://example.com/

\n', + + 'foo@example.com': + '

foo@example.com

\n', + + '[foo](/u/bar)': + '

foo

\n', + + '': + '

</u/foobar>

\n', + + '/u/test/m/test test': + '

/u/test/m/test test

\n', + + '/u/test': + '

/u/test

\n', + + '/r/not.cool': + '

/r/not.cool

\n', + + 'u/test': + '

u/test

\n', + + '/r/whatever: fork': + '

/r/whatever: fork

\n', + + '/r/t:timereddit': + '

/r/t:timereddit

\n', + + '/r/reddit.com': + '

/r/reddit.com

\n', + + 'r/not.cool': + '

r/not.cool

\n', + + '/r/very+clever+multireddit+reddit.com+t:fork+yay': + '

/r/very+clever+multireddit+reddit.com+t:fork+yay

\n', + + '/r/t:heatdeathoftheuniverse': + '

/r/t:heatdeathoftheuniverse

\n', + + '/r/all-minus-something': + '

/r/all-minus-something

\n', + + r'escaped \/r/test': + '

escaped /r/test

\n', + + 'Words words /r/test words': + '

Words words /r/test words

\n', +} + + class SnudownTestCase(unittest.TestCase): def __init__(self, renderer=snudown.RENDERER_USERTEXT): self.renderer = renderer @@ -458,4 +522,10 @@ def test_snudown(): case.expected_output = expected_output suite.addTest(case) + for input, expected_output in no_autolinks_cases.iteritems(): + case = SnudownTestCase(renderer=snudown.RENDERER_USERTEXT_WITHOUTLINKS) + case.input = input + case.expected_output = expected_output + suite.addTest(case) + return suite