#ifndef _DEFAULT_SOURCE #define _DEFAULT_SOURCE #endif #include "cst_web_md.h" #include "cst_util.h" #include "sashidown.h" #include #include #include struct cst_web_md { sashidown_stream_t *st; cst_web_md_emit_fn emit; void *ud; cst_buf_t in; /* HTML from sashidown, waiting for a complete tag */ int failed; }; /* --- sanitizer ------------------------------------------------------------ */ static const char *ALLOWED_TAGS[] = { "p", "br", "hr", "h1", "h2", "h3", "h4", "h5", "h6", "strong", "em", "b", "i", "u", "s", "del", "code", "pre", "blockquote", "ul", "ol", "li", "table", "thead", "tbody", "tr", "th", "td", "a", "img", "span", "div", NULL}; static int tag_allowed(const char *name, size_t n) { for (int i = 0; ALLOWED_TAGS[i]; i++) { if (strlen(ALLOWED_TAGS[i]) == n && strncmp(name, ALLOWED_TAGS[i], n) == 0) return 1; } return 0; } /* A URL we are willing to put in href: http(s), mailto, or a same-site * path. Anything else (javascript:, data:, file:, //evil) is dropped. */ static int href_ok(const char *v, size_t n) { if (n == 0) return 0; if (!strncmp(v, "http://", 7) || !strncmp(v, "https://", 8) || !strncmp(v, "mailto:", 7)) return 1; if (v[0] == '/' && !(n > 1 && (v[1] == '/' || v[1] == '\\'))) return 1; if (v[0] == '#') return 1; return 0; } static int src_ok(const char *v, size_t n) { if (n > 11 && !strncmp(v, "data:image/", 11)) return 1; if (n > 12 && !strncmp(v, "/api/attach/", 12)) return 1; return 0; } static void attr_append(cst_buf_t *b, const char *name, size_t nlen, const char *val, size_t vlen) { cst_buf_puts(b, " "); cst_buf_append(b, name, nlen); cst_buf_puts(b, "=\""); /* a single-quoted or bare value may hold '"': it must not end ours */ for (size_t i = 0; i < vlen; i++) { if (val[i] == '"') cst_buf_puts(b, """); else if (val[i] == '<') cst_buf_puts(b, "<"); else if (val[i] == '>') cst_buf_puts(b, ">"); else cst_buf_append(b, val + i, 1); } cst_buf_puts(b, "\""); } /* Rewrite one tag (open, close or self-closing) into out; may emit nothing * when the tag is not allowed. Returns 0, or -1 on OOM. */ static int sanitize_tag(const char *tag, size_t len, cst_buf_t *out) { /* or or */ size_t i = 0; int closing = 0; if (len < 3 || tag[0] != '<') return 0; i = 1; if (i < len && tag[i] == '/') { closing = 1; i++; } size_t name_start = i; while (i < len && ((tag[i] >= 'a' && tag[i] <= 'z') || (tag[i] >= 'A' && tag[i] <= 'Z') || (tag[i] >= '0' && tag[i] <= '9'))) i++; size_t name_len = i - name_start; if (name_len == 0 || !tag_allowed(tag + name_start, name_len)) return 0; /* comments,