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, "&amp;"); break;
+      case '<': buffer_append(buf, "&lt;"); break;
+      case '>': buffer_append(buf, "&gt;"); break;
+      case '"': buffer_append(buf, "&quot;"); break;
+      case '\'': buffer_append(buf, "&#39;"); 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, "&lt;");
-    // } else if (text[i] == '>') {
-    //   buffer_append(buf, "&gt;");
-    // } else if (text[i] == '&') {
-    //   buffer_append(buf, "&amp;");
-    // } 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;