/* snac - A simple, minimalistic ActivityPub instance */ /* copyright (c) 2022 grunfink - MIT license */ #include "xs.h" #include "xs_regex.h" #include "snac.h" /* emoticons, people laughing and such */ struct { const char *key; const char *value; } smileys[] = { { ":-)", "🙂" }, { ":-D", "😀" }, { "X-D", "😆" }, { ";-)", "😉" }, { "B-)", "😎" }, { ":-(", "😞" }, { ":-*", "😘" }, { ":-/", "😕" }, { "8-o", "😲" }, { "%-)", "🤪" }, { ":_(", "😢" }, { ":-|", "😐" }, { "<3", "💓" }, { ":facepalm:", "🤦" }, { ":shrug:", "🤷" }, { ":eyeroll:", "🙄" }, { ":beer:", "🍺" }, { ":beers:", "🍻" }, { ":munch:", "😱" }, { ":thumb:", "👍" }, { NULL, NULL } }; static d_char *format_line(const char *line) /* formats a line */ { d_char *s = xs_str_new(NULL); char *p, *v; /* split by markup */ xs *sm = xs_regex_split(line, "(`[^`]+`|\\*\\*?[^\\*]+\\*?\\*|https?:/" "/[^[:space:]]+)"); int n = 0; p = sm; while (xs_list_iter(&p, &v)) { if ((n & 0x1)) { /* markup */ if (xs_startswith(v, "`")) { xs *s1 = xs_crop(xs_dup(v), 1, -1); xs *s2 = xs_fmt("%s", s1); s = xs_str_cat(s, s2); } else if (xs_startswith(v, "**")) { xs *s1 = xs_crop(xs_dup(v), 2, -2); xs *s2 = xs_fmt("%s", s1); s = xs_str_cat(s, s2); } else if (xs_startswith(v, "*")) { xs *s1 = xs_crop(xs_dup(v), 1, -1); xs *s2 = xs_fmt("%s", s1); s = xs_str_cat(s, s2); } else if (xs_startswith(v, "http")) { xs *s1 = xs_fmt("%s", v, v); s = xs_str_cat(s, s1); } else s = xs_str_cat(s, v); } else /* surrounded text, copy directly */ s = xs_str_cat(s, v); n++; } return s; } d_char *not_really_markdown(const char *content) /* formats a content using some Markdown rules */ { d_char *s = xs_str_new(NULL); int in_pre = 0; int in_blq = 0; xs *list; char *p, *v; /* work by lines */ p = list = xs_split(content, "\n"); while (xs_list_iter(&p, &v)) { xs *ss = NULL; if (strcmp(v, "```") == 0) { if (!in_pre) s = xs_str_cat(s, "
");
            else
                s = xs_str_cat(s, "
"); in_pre = !in_pre; continue; } if (in_pre) ss = xs_dup(v); else ss = xs_strip(format_line(v)); if (xs_startswith(ss, ">")) { /* delete the > and subsequent spaces */ ss = xs_strip(xs_crop(ss, 1, 0)); if (!in_blq) { s = xs_str_cat(s, "
"); in_blq = 1; } s = xs_str_cat(s, ss); s = xs_str_cat(s, "
"); continue; } if (in_blq) { s = xs_str_cat(s, "
"); in_blq = 0; } s = xs_str_cat(s, ss); s = xs_str_cat(s, "
"); } if (in_blq) s = xs_str_cat(s, ""); if (in_pre) s = xs_str_cat(s, ""); /* some beauty fixes */ s = xs_replace_i(s, "

", "
"); s = xs_replace_i(s, "

", "
"); s = xs_replace_i(s, "
", ""); { /* traditional emoticons */ int n; for (n = 0; smileys[n].key; n++) s = xs_replace_i(s, smileys[n].key, smileys[n].value); } return s; } const char *valid_tags[] = { "a", "p", "br", "br/", "blockquote", "ul", "li", "span", "i", "b", "pre", "code", "em", "strong", NULL }; d_char *sanitize(const char *content) /* cleans dangerous HTML output */ { d_char *s = xs_str_new(NULL); xs *sl; int n = 0; char *p, *v; sl = xs_regex_split(content, "]+>"); p = sl; while (xs_list_iter(&p, &v)) { if (n & 0x1) { xs *s1 = xs_strip(xs_crop(xs_dup(v), v[1] == '/' ? 2 : 1, -1)); xs *l1 = xs_split_n(s1, " ", 1); xs *tag = xs_tolower(xs_dup(xs_list_get(l1, 0))); xs *s2 = NULL; int i; /* check if it's one of the valid tags */ for (i = 0; valid_tags[i]; i++) { if (strcmp(tag, valid_tags[i]) == 0) break; } if (valid_tags[i]) { /* accepted tag: rebuild it with only the accepted elements */ xs *el = xs_regex_match(v, "(href|rel|class|target)=\"[^\"]*\""); xs *s3 = xs_join(el, " "); s2 = xs_fmt("<%s%s%s%s>", v[1] == '/' ? "/" : "", tag, xs_list_len(s3) ? " " : "", s3); } else { /* bad tag: escape it */ s2 = xs_replace(v, "<", "<"); } s = xs_str_cat(s, s2); } else { /* non-tag */ s = xs_str_cat(s, v); } n++; } return s; }