comparison 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
comparison
equal deleted inserted replaced
219:8c9bb0b0759e 221:ce7f4400c2de
59 buffer_grow(buf, len); 59 buffer_grow(buf, len);
60 memcpy(buf->data + buf->length, str, len + 1); 60 memcpy(buf->data + buf->length, str, len + 1);
61 buf->length += len; 61 buf->length += len;
62 } 62 }
63 63
64 static void buffer_append_n(StringBuffer *buf, const char *str, size_t n)
65 {
66 buffer_grow(buf, n);
67 memcpy(buf->data + buf->length, str, n);
68 buf->length += n;
69 buf->data[buf->length] = '\0';
70 }
71
72 static void buffer_append_char(StringBuffer *buf, char c) 64 static void buffer_append_char(StringBuffer *buf, char c)
73 { 65 {
74 buffer_grow(buf, 1); 66 buffer_grow(buf, 1);
75 buf->data[buf->length++] = c; 67 buf->data[buf->length++] = c;
76 buf->data[buf->length] = '\0'; 68 buf->data[buf->length] = '\0';
69 }
70
71 static void buffer_append_html_escaped_n(StringBuffer *buf, const char *text, size_t len)
72 {
73 for (size_t i = 0; i < len; i++) {
74 switch (text[i]) {
75 case '&': buffer_append(buf, "&amp;"); break;
76 case '<': buffer_append(buf, "&lt;"); break;
77 case '>': buffer_append(buf, "&gt;"); break;
78 case '"': buffer_append(buf, "&quot;"); break;
79 case '\'': buffer_append(buf, "&#39;"); break;
80 default: buffer_append_char(buf, text[i]); break;
81 }
82 }
83 }
84
85 static int is_safe_url(const char *url, size_t len, int is_image)
86 {
87 if (len == 0) return 0;
88
89 for (size_t i = 0; i < len; i++) {
90 unsigned char c = (unsigned char)url[i];
91 if (iscntrl(c) || isspace(c)) return 0;
92 }
93
94 const char *colon = memchr(url, ':', len);
95 if (!colon) return 1;
96
97 size_t scheme_len = (size_t)(colon - url);
98 if (scheme_len == 4 && strncasecmp(url, "http", scheme_len) == 0) return 1;
99 if (scheme_len == 5 && strncasecmp(url, "https", scheme_len) == 0) return 1;
100 if (!is_image && scheme_len == 6 && strncasecmp(url, "mailto", scheme_len) == 0) return 1;
101 return 0;
77 } 102 }
78 103
79 static void buffer_free(StringBuffer *buf) 104 static void buffer_free(StringBuffer *buf)
80 { 105 {
81 if (buf) { 106 if (buf) {
156 181
157 // Check for valid tag name (letter followed by alphanumeric) 182 // Check for valid tag name (letter followed by alphanumeric)
158 if (!isalpha((unsigned char)*line)) return 0; 183 if (!isalpha((unsigned char)*line)) return 0;
159 184
160 return 1; 185 return 1;
161 }
162
163 // Check if line starts with a specific HTML tag (e.g., "script", "style")
164 static int is_html_tag(const char *line, const char *tag)
165 {
166 line = skip_whitespace(line);
167 if (*line != '<') return 0;
168 line++;
169
170 // Skip optional /
171 int is_closing = 0;
172 if (*line == '/') {
173 is_closing = 1;
174 line++;
175 }
176
177 size_t tag_len = strlen(tag);
178 if (strncasecmp(line, tag, tag_len) != 0) return 0;
179
180 char next = line[tag_len];
181 // Tag must be followed by space, >, or end for closing tags
182 return next == '>' || next == ' ' || next == '\t' || next == '\n' || next == '\0';
183 } 186 }
184 187
185 // Check if line is ordered list item 188 // Check if line is ordered list item
186 static int is_ordered_list(const char *line) 189 static int is_ordered_list(const char *line)
187 { 190 {
357 size_t url_start = link_end + 2; 360 size_t url_start = link_end + 2;
358 size_t url_end = url_start; 361 size_t url_end = url_start;
359 while (url_end < len && text[url_end] != ')') url_end++; 362 while (url_end < len && text[url_end] != ')') url_end++;
360 363
361 if (url_end < len) { 364 if (url_end < len) {
362 buffer_append(buf, "<a href=\""); 365 size_t url_len = url_end - url_start;
363 buffer_append_n(buf, text + url_start, url_end - url_start); 366 if (is_safe_url(text + url_start, url_len, 0)) {
364 buffer_append(buf, "\">"); 367 buffer_append(buf, "<a href=\"");
365 buffer_append_n(buf, text + link_start, link_end - link_start); 368 buffer_append_html_escaped_n(buf, text + url_start, url_len);
366 buffer_append(buf, "</a>"); 369 buffer_append(buf, "\">");
370 process_inline(buf, text + link_start, link_end - link_start);
371 buffer_append(buf, "</a>");
372 } else {
373 process_inline(buf, text + link_start, link_end - link_start);
374 }
367 i = url_end + 1; 375 i = url_end + 1;
368 continue; 376 continue;
369 } 377 }
370 } 378 }
371 } 379 }
380 size_t url_start = alt_end + 2; 388 size_t url_start = alt_end + 2;
381 size_t url_end = url_start; 389 size_t url_end = url_start;
382 while (url_end < len && text[url_end] != ')') url_end++; 390 while (url_end < len && text[url_end] != ')') url_end++;
383 391
384 if (url_end < len) { 392 if (url_end < len) {
385 buffer_append(buf, "<img src=\""); 393 size_t url_len = url_end - url_start;
386 buffer_append_n(buf, text + url_start, url_end - url_start); 394 if (is_safe_url(text + url_start, url_len, 1)) {
387 buffer_append(buf, "\" alt=\""); 395 buffer_append(buf, "<img src=\"");
388 buffer_append_n(buf, text + alt_start, alt_end - alt_start); 396 buffer_append_html_escaped_n(buf, text + url_start, url_len);
389 buffer_append(buf, "\">"); 397 buffer_append(buf, "\" alt=\"");
398 buffer_append_html_escaped_n(buf, text + alt_start, alt_end - alt_start);
399 buffer_append(buf, "\">");
400 } else {
401 buffer_append_html_escaped_n(buf, text + alt_start, alt_end - alt_start);
402 }
390 i = url_end + 1; 403 i = url_end + 1;
391 continue; 404 continue;
392 } 405 }
393 } 406 }
394 } 407 }
447 size_t end = start; 460 size_t end = start;
448 while (end < len && text[end] != '`') end++; 461 while (end < len && text[end] != '`') end++;
449 462
450 if (end < len) { 463 if (end < len) {
451 buffer_append(buf, "<code>"); 464 buffer_append(buf, "<code>");
452 buffer_append_n(buf, text + start, end - start); 465 buffer_append_html_escaped_n(buf, text + start, end - start);
453 buffer_append(buf, "</code>"); 466 buffer_append(buf, "</code>");
454 i = end + 1; 467 i = end + 1;
455 continue; 468 continue;
456 } 469 }
457 } 470 }
458 471
459 // This might not be needed for now. 472 buffer_append_html_escaped_n(buf, text + i, 1);
460 // HTML escape special characters
461 // if (text[i] == '<') {
462 // buffer_append(buf, "&lt;");
463 // } else if (text[i] == '>') {
464 // buffer_append(buf, "&gt;");
465 // } else if (text[i] == '&') {
466 // buffer_append(buf, "&amp;");
467 // } else {
468 // buffer_append_char(buf, text[i]);
469 // }
470 buffer_append_char(buf, text[i]);
471 i++; 473 i++;
472 } 474 }
473 } 475 }
474 476
475 // Convert markdown to HTML 477 // Convert markdown to HTML
757 } 759 }
758 free(next_line); 760 free(next_line);
759 } 761 }
760 } 762 }
761 763
762 // HTML block - pass through unchanged 764 // Repository markdown is untrusted. Render raw HTML as text.
763 if (is_html_block_start(line)) { 765 if (is_html_block_start(line)) {
764 // Check if it's a script or style tag that needs special handling 766 buffer_append(buf, "<p>");
765 int is_script = is_html_tag(line, "script"); 767 process_inline(buf, line, line_len);
766 int is_style = is_html_tag(line, "style"); 768 buffer_append(buf, "</p>");
767
768 if (is_script || is_style) {
769 const char *end_tag = is_script ? "</script>" : "</style>";
770
771 // Output the opening line
772 buffer_append(buf, line);
773 buffer_append_char(buf, '\n');
774
775 free(line);
776 if (*ptr == '\n') ptr++;
777
778 // Collect content until closing tag
779 while (*ptr) {
780 line_start = ptr;
781 while (*ptr && *ptr != '\n') ptr++;
782 line_len = ptr - line_start;
783
784 line = (char *)malloc(line_len + 1);
785 if (!line) break;
786 memcpy(line, line_start, line_len);
787 line[line_len] = '\0';
788
789 buffer_append(buf, line);
790 buffer_append_char(buf, '\n');
791
792 int found_end = (strstr(line, end_tag) != NULL);
793 free(line);
794 if (*ptr == '\n') ptr++;
795
796 if (found_end) break;
797 }
798 continue;
799 }
800
801 // Regular HTML tag - just pass through the line
802 buffer_append(buf, line);
803 buffer_append_char(buf, '\n');
804 free(line); 769 free(line);
805 if (*ptr == '\n') ptr++; 770 if (*ptr == '\n') ptr++;
806 continue; 771 continue;
807 } 772 }
808 773