Mercurial
diff markdown_converter/markdown_to_html.c @ 221:ce7f4400c2de hg-web
[hg-web] Harden forge and add changeset UI
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Sun, 02 Aug 2026 09:01:24 -0700 |
| parents | a2725419f988 |
| children |
line wrap: on
line diff
--- a/markdown_converter/markdown_to_html.c Sun Aug 02 08:34:54 2026 -0700 +++ b/markdown_converter/markdown_to_html.c Sun Aug 02 09:01:24 2026 -0700 @@ -61,14 +61,6 @@ buf->length += len; } -static void buffer_append_n(StringBuffer *buf, const char *str, size_t n) -{ - buffer_grow(buf, n); - memcpy(buf->data + buf->length, str, n); - buf->length += n; - buf->data[buf->length] = '\0'; -} - static void buffer_append_char(StringBuffer *buf, char c) { buffer_grow(buf, 1); @@ -76,6 +68,39 @@ buf->data[buf->length] = '\0'; } +static void buffer_append_html_escaped_n(StringBuffer *buf, const char *text, size_t len) +{ + for (size_t i = 0; i < len; i++) { + switch (text[i]) { + case '&': buffer_append(buf, "&"); break; + case '<': buffer_append(buf, "<"); break; + case '>': buffer_append(buf, ">"); break; + case '"': buffer_append(buf, """); break; + case '\'': buffer_append(buf, "'"); break; + default: buffer_append_char(buf, text[i]); break; + } + } +} + +static int is_safe_url(const char *url, size_t len, int is_image) +{ + if (len == 0) return 0; + + for (size_t i = 0; i < len; i++) { + unsigned char c = (unsigned char)url[i]; + if (iscntrl(c) || isspace(c)) return 0; + } + + const char *colon = memchr(url, ':', len); + if (!colon) return 1; + + size_t scheme_len = (size_t)(colon - url); + if (scheme_len == 4 && strncasecmp(url, "http", scheme_len) == 0) return 1; + if (scheme_len == 5 && strncasecmp(url, "https", scheme_len) == 0) return 1; + if (!is_image && scheme_len == 6 && strncasecmp(url, "mailto", scheme_len) == 0) return 1; + return 0; +} + static void buffer_free(StringBuffer *buf) { if (buf) { @@ -160,28 +185,6 @@ return 1; } -// Check if line starts with a specific HTML tag (e.g., "script", "style") -static int is_html_tag(const char *line, const char *tag) -{ - line = skip_whitespace(line); - if (*line != '<') return 0; - line++; - - // Skip optional / - int is_closing = 0; - if (*line == '/') { - is_closing = 1; - line++; - } - - size_t tag_len = strlen(tag); - if (strncasecmp(line, tag, tag_len) != 0) return 0; - - char next = line[tag_len]; - // Tag must be followed by space, >, or end for closing tags - return next == '>' || next == ' ' || next == '\t' || next == '\n' || next == '\0'; -} - // Check if line is ordered list item static int is_ordered_list(const char *line) { @@ -359,11 +362,16 @@ while (url_end < len && text[url_end] != ')') url_end++; if (url_end < len) { - buffer_append(buf, "<a href=\""); - buffer_append_n(buf, text + url_start, url_end - url_start); - buffer_append(buf, "\">"); - buffer_append_n(buf, text + link_start, link_end - link_start); - buffer_append(buf, "</a>"); + size_t url_len = url_end - url_start; + if (is_safe_url(text + url_start, url_len, 0)) { + buffer_append(buf, "<a href=\""); + buffer_append_html_escaped_n(buf, text + url_start, url_len); + buffer_append(buf, "\">"); + process_inline(buf, text + link_start, link_end - link_start); + buffer_append(buf, "</a>"); + } else { + process_inline(buf, text + link_start, link_end - link_start); + } i = url_end + 1; continue; } @@ -382,11 +390,16 @@ while (url_end < len && text[url_end] != ')') url_end++; if (url_end < len) { - buffer_append(buf, "<img src=\""); - buffer_append_n(buf, text + url_start, url_end - url_start); - buffer_append(buf, "\" alt=\""); - buffer_append_n(buf, text + alt_start, alt_end - alt_start); - buffer_append(buf, "\">"); + size_t url_len = url_end - url_start; + if (is_safe_url(text + url_start, url_len, 1)) { + buffer_append(buf, "<img src=\""); + buffer_append_html_escaped_n(buf, text + url_start, url_len); + buffer_append(buf, "\" alt=\""); + buffer_append_html_escaped_n(buf, text + alt_start, alt_end - alt_start); + buffer_append(buf, "\">"); + } else { + buffer_append_html_escaped_n(buf, text + alt_start, alt_end - alt_start); + } i = url_end + 1; continue; } @@ -449,25 +462,14 @@ if (end < len) { buffer_append(buf, "<code>"); - buffer_append_n(buf, text + start, end - start); + buffer_append_html_escaped_n(buf, text + start, end - start); buffer_append(buf, "</code>"); i = end + 1; continue; } } - // This might not be needed for now. - // HTML escape special characters - // if (text[i] == '<') { - // buffer_append(buf, "<"); - // } else if (text[i] == '>') { - // buffer_append(buf, ">"); - // } else if (text[i] == '&') { - // buffer_append(buf, "&"); - // } else { - // buffer_append_char(buf, text[i]); - // } - buffer_append_char(buf, text[i]); + buffer_append_html_escaped_n(buf, text + i, 1); i++; } } @@ -759,48 +761,11 @@ } } - // HTML block - pass through unchanged + // Repository markdown is untrusted. Render raw HTML as text. if (is_html_block_start(line)) { - // Check if it's a script or style tag that needs special handling - int is_script = is_html_tag(line, "script"); - int is_style = is_html_tag(line, "style"); - - if (is_script || is_style) { - const char *end_tag = is_script ? "</script>" : "</style>"; - - // Output the opening line - buffer_append(buf, line); - buffer_append_char(buf, '\n'); - - free(line); - if (*ptr == '\n') ptr++; - - // Collect content until closing tag - while (*ptr) { - line_start = ptr; - while (*ptr && *ptr != '\n') ptr++; - line_len = ptr - line_start; - - line = (char *)malloc(line_len + 1); - if (!line) break; - memcpy(line, line_start, line_len); - line[line_len] = '\0'; - - buffer_append(buf, line); - buffer_append_char(buf, '\n'); - - int found_end = (strstr(line, end_tag) != NULL); - free(line); - if (*ptr == '\n') ptr++; - - if (found_end) break; - } - continue; - } - - // Regular HTML tag - just pass through the line - buffer_append(buf, line); - buffer_append_char(buf, '\n'); + buffer_append(buf, "<p>"); + process_inline(buf, line, line_len); + buffer_append(buf, "</p>"); free(line); if (*ptr == '\n') ptr++; continue;