]> git.lizzy.rs Git - rust.git/blob - src/rt/sundown/html/html.c
Ignore tests broken by failing on ICE
[rust.git] / src / rt / sundown / html / html.c
1 /*
2  * Copyright (c) 2009, Natacha Porté
3  * Copyright (c) 2011, Vicent Marti
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17
18 #include "markdown.h"
19 #include "html.h"
20
21 #include <string.h>
22 #include <stdlib.h>
23 #include <stdio.h>
24 #include <ctype.h>
25
26 #include "houdini.h"
27
28 #define USE_XHTML(opt) (opt->flags & HTML_USE_XHTML)
29
30 int
31 sdhtml_is_tag(const uint8_t *tag_data, size_t tag_size, const char *tagname)
32 {
33         size_t i;
34         int closed = 0;
35
36         if (tag_size < 3 || tag_data[0] != '<')
37                 return HTML_TAG_NONE;
38
39         i = 1;
40
41         if (tag_data[i] == '/') {
42                 closed = 1;
43                 i++;
44         }
45
46         for (; i < tag_size; ++i, ++tagname) {
47                 if (*tagname == 0)
48                         break;
49
50                 if (tag_data[i] != *tagname)
51                         return HTML_TAG_NONE;
52         }
53
54         if (i == tag_size)
55                 return HTML_TAG_NONE;
56
57         if (isspace(tag_data[i]) || tag_data[i] == '>')
58                 return closed ? HTML_TAG_CLOSE : HTML_TAG_OPEN;
59
60         return HTML_TAG_NONE;
61 }
62
63 static inline void escape_html(struct buf *ob, const uint8_t *source, size_t length)
64 {
65         houdini_escape_html0(ob, source, length, 0);
66 }
67
68 static inline void escape_href(struct buf *ob, const uint8_t *source, size_t length)
69 {
70         houdini_escape_href(ob, source, length);
71 }
72
73 /********************
74  * GENERIC RENDERER *
75  ********************/
76 static int
77 rndr_autolink(struct buf *ob, const struct buf *link, enum mkd_autolink type, void *opaque)
78 {
79         struct html_renderopt *options = opaque;
80
81         if (!link || !link->size)
82                 return 0;
83
84         if ((options->flags & HTML_SAFELINK) != 0 &&
85                 !sd_autolink_issafe(link->data, link->size) &&
86                 type != MKDA_EMAIL)
87                 return 0;
88
89         BUFPUTSL(ob, "<a href=\"");
90         if (type == MKDA_EMAIL)
91                 BUFPUTSL(ob, "mailto:");
92         escape_href(ob, link->data, link->size);
93
94         if (options->link_attributes) {
95                 bufputc(ob, '\"');
96                 options->link_attributes(ob, link, opaque);
97                 bufputc(ob, '>');
98         } else {
99                 BUFPUTSL(ob, "\">");
100         }
101
102         /*
103          * Pretty printing: if we get an email address as
104          * an actual URI, e.g. `mailto:foo@bar.com`, we don't
105          * want to print the `mailto:` prefix
106          */
107         if (bufprefix(link, "mailto:") == 0) {
108                 escape_html(ob, link->data + 7, link->size - 7);
109         } else {
110                 escape_html(ob, link->data, link->size);
111         }
112
113         BUFPUTSL(ob, "</a>");
114
115         return 1;
116 }
117
118 static void
119 rndr_blockcode(struct buf *ob, const struct buf *text, const struct buf *lang, void *opaque)
120 {
121         if (ob->size) bufputc(ob, '\n');
122
123         if (lang && lang->size) {
124                 size_t i, cls;
125                 BUFPUTSL(ob, "<pre><code class=\"");
126
127                 for (i = 0, cls = 0; i < lang->size; ++i, ++cls) {
128                         while (i < lang->size && isspace(lang->data[i]))
129                                 i++;
130
131                         if (i < lang->size) {
132                                 size_t org = i;
133                                 while (i < lang->size && !isspace(lang->data[i]))
134                                         i++;
135
136                                 if (lang->data[org] == '.')
137                                         org++;
138
139                                 if (cls) bufputc(ob, ' ');
140                                 escape_html(ob, lang->data + org, i - org);
141                         }
142                 }
143
144                 BUFPUTSL(ob, "\">");
145         } else
146                 BUFPUTSL(ob, "<pre><code>");
147
148         if (text)
149                 escape_html(ob, text->data, text->size);
150
151         BUFPUTSL(ob, "</code></pre>\n");
152 }
153
154 static void
155 rndr_blockquote(struct buf *ob, const struct buf *text, void *opaque)
156 {
157         if (ob->size) bufputc(ob, '\n');
158         BUFPUTSL(ob, "<blockquote>\n");
159         if (text) bufput(ob, text->data, text->size);
160         BUFPUTSL(ob, "</blockquote>\n");
161 }
162
163 static int
164 rndr_codespan(struct buf *ob, const struct buf *text, void *opaque)
165 {
166         BUFPUTSL(ob, "<code>");
167         if (text) escape_html(ob, text->data, text->size);
168         BUFPUTSL(ob, "</code>");
169         return 1;
170 }
171
172 static int
173 rndr_strikethrough(struct buf *ob, const struct buf *text, void *opaque)
174 {
175         if (!text || !text->size)
176                 return 0;
177
178         BUFPUTSL(ob, "<del>");
179         bufput(ob, text->data, text->size);
180         BUFPUTSL(ob, "</del>");
181         return 1;
182 }
183
184 static int
185 rndr_double_emphasis(struct buf *ob, const struct buf *text, void *opaque)
186 {
187         if (!text || !text->size)
188                 return 0;
189
190         BUFPUTSL(ob, "<strong>");
191         bufput(ob, text->data, text->size);
192         BUFPUTSL(ob, "</strong>");
193
194         return 1;
195 }
196
197 static int
198 rndr_emphasis(struct buf *ob, const struct buf *text, void *opaque)
199 {
200         if (!text || !text->size) return 0;
201         BUFPUTSL(ob, "<em>");
202         if (text) bufput(ob, text->data, text->size);
203         BUFPUTSL(ob, "</em>");
204         return 1;
205 }
206
207 static int
208 rndr_linebreak(struct buf *ob, void *opaque)
209 {
210         struct html_renderopt *options = opaque;
211         bufputs(ob, USE_XHTML(options) ? "<br/>\n" : "<br>\n");
212         return 1;
213 }
214
215 static void
216 rndr_header(struct buf *ob, const struct buf *text, int level, void *opaque)
217 {
218         struct html_renderopt *options = opaque;
219
220         if (ob->size)
221                 bufputc(ob, '\n');
222
223         if (options->flags & HTML_TOC)
224                 bufprintf(ob, "<h%d id=\"toc_%d\">", level, options->toc_data.header_count++);
225         else
226                 bufprintf(ob, "<h%d>", level);
227
228         if (text) bufput(ob, text->data, text->size);
229         bufprintf(ob, "</h%d>\n", level);
230 }
231
232 static int
233 rndr_link(struct buf *ob, const struct buf *link, const struct buf *title, const struct buf *content, void *opaque)
234 {
235         struct html_renderopt *options = opaque;
236
237         if (link != NULL && (options->flags & HTML_SAFELINK) != 0 && !sd_autolink_issafe(link->data, link->size))
238                 return 0;
239
240         BUFPUTSL(ob, "<a href=\"");
241
242         if (link && link->size)
243                 escape_href(ob, link->data, link->size);
244
245         if (title && title->size) {
246                 BUFPUTSL(ob, "\" title=\"");
247                 escape_html(ob, title->data, title->size);
248         }
249
250         if (options->link_attributes) {
251                 bufputc(ob, '\"');
252                 options->link_attributes(ob, link, opaque);
253                 bufputc(ob, '>');
254         } else {
255                 BUFPUTSL(ob, "\">");
256         }
257
258         if (content && content->size) bufput(ob, content->data, content->size);
259         BUFPUTSL(ob, "</a>");
260         return 1;
261 }
262
263 static void
264 rndr_list(struct buf *ob, const struct buf *text, int flags, void *opaque)
265 {
266         if (ob->size) bufputc(ob, '\n');
267         bufput(ob, flags & MKD_LIST_ORDERED ? "<ol>\n" : "<ul>\n", 5);
268         if (text) bufput(ob, text->data, text->size);
269         bufput(ob, flags & MKD_LIST_ORDERED ? "</ol>\n" : "</ul>\n", 6);
270 }
271
272 static void
273 rndr_listitem(struct buf *ob, const struct buf *text, int flags, void *opaque)
274 {
275         BUFPUTSL(ob, "<li>");
276         if (text) {
277                 size_t size = text->size;
278                 while (size && text->data[size - 1] == '\n')
279                         size--;
280
281                 bufput(ob, text->data, size);
282         }
283         BUFPUTSL(ob, "</li>\n");
284 }
285
286 static void
287 rndr_paragraph(struct buf *ob, const struct buf *text, void *opaque)
288 {
289         struct html_renderopt *options = opaque;
290         size_t i = 0;
291
292         if (ob->size) bufputc(ob, '\n');
293
294         if (!text || !text->size)
295                 return;
296
297         while (i < text->size && isspace(text->data[i])) i++;
298
299         if (i == text->size)
300                 return;
301
302         BUFPUTSL(ob, "<p>");
303         if (options->flags & HTML_HARD_WRAP) {
304                 size_t org;
305                 while (i < text->size) {
306                         org = i;
307                         while (i < text->size && text->data[i] != '\n')
308                                 i++;
309
310                         if (i > org)
311                                 bufput(ob, text->data + org, i - org);
312
313                         /*
314                          * do not insert a line break if this newline
315                          * is the last character on the paragraph
316                          */
317                         if (i >= text->size - 1)
318                                 break;
319
320                         rndr_linebreak(ob, opaque);
321                         i++;
322                 }
323         } else {
324                 bufput(ob, &text->data[i], text->size - i);
325         }
326         BUFPUTSL(ob, "</p>\n");
327 }
328
329 static void
330 rndr_raw_block(struct buf *ob, const struct buf *text, void *opaque)
331 {
332         size_t org, sz;
333         if (!text) return;
334         sz = text->size;
335         while (sz > 0 && text->data[sz - 1] == '\n') sz--;
336         org = 0;
337         while (org < sz && text->data[org] == '\n') org++;
338         if (org >= sz) return;
339         if (ob->size) bufputc(ob, '\n');
340         bufput(ob, text->data + org, sz - org);
341         bufputc(ob, '\n');
342 }
343
344 static int
345 rndr_triple_emphasis(struct buf *ob, const struct buf *text, void *opaque)
346 {
347         if (!text || !text->size) return 0;
348         BUFPUTSL(ob, "<strong><em>");
349         bufput(ob, text->data, text->size);
350         BUFPUTSL(ob, "</em></strong>");
351         return 1;
352 }
353
354 static void
355 rndr_hrule(struct buf *ob, void *opaque)
356 {
357         struct html_renderopt *options = opaque;
358         if (ob->size) bufputc(ob, '\n');
359         bufputs(ob, USE_XHTML(options) ? "<hr/>\n" : "<hr>\n");
360 }
361
362 static int
363 rndr_image(struct buf *ob, const struct buf *link, const struct buf *title, const struct buf *alt, void *opaque)
364 {
365         struct html_renderopt *options = opaque;
366         if (!link || !link->size) return 0;
367
368         BUFPUTSL(ob, "<img src=\"");
369         escape_href(ob, link->data, link->size);
370         BUFPUTSL(ob, "\" alt=\"");
371
372         if (alt && alt->size)
373                 escape_html(ob, alt->data, alt->size);
374
375         if (title && title->size) {
376                 BUFPUTSL(ob, "\" title=\"");
377                 escape_html(ob, title->data, title->size); }
378
379         bufputs(ob, USE_XHTML(options) ? "\"/>" : "\">");
380         return 1;
381 }
382
383 static int
384 rndr_raw_html(struct buf *ob, const struct buf *text, void *opaque)
385 {
386         struct html_renderopt *options = opaque;
387
388         /* HTML_ESCAPE overrides SKIP_HTML, SKIP_STYLE, SKIP_LINKS and SKIP_IMAGES
389         * It doens't see if there are any valid tags, just escape all of them. */
390         if((options->flags & HTML_ESCAPE) != 0) {
391                 escape_html(ob, text->data, text->size);
392                 return 1;
393         }
394
395         if ((options->flags & HTML_SKIP_HTML) != 0)
396                 return 1;
397
398         if ((options->flags & HTML_SKIP_STYLE) != 0 &&
399                 sdhtml_is_tag(text->data, text->size, "style"))
400                 return 1;
401
402         if ((options->flags & HTML_SKIP_LINKS) != 0 &&
403                 sdhtml_is_tag(text->data, text->size, "a"))
404                 return 1;
405
406         if ((options->flags & HTML_SKIP_IMAGES) != 0 &&
407                 sdhtml_is_tag(text->data, text->size, "img"))
408                 return 1;
409
410         bufput(ob, text->data, text->size);
411         return 1;
412 }
413
414 static void
415 rndr_table(struct buf *ob, const struct buf *header, const struct buf *body, void *opaque)
416 {
417         if (ob->size) bufputc(ob, '\n');
418         BUFPUTSL(ob, "<table><thead>\n");
419         if (header)
420                 bufput(ob, header->data, header->size);
421         BUFPUTSL(ob, "</thead><tbody>\n");
422         if (body)
423                 bufput(ob, body->data, body->size);
424         BUFPUTSL(ob, "</tbody></table>\n");
425 }
426
427 static void
428 rndr_tablerow(struct buf *ob, const struct buf *text, void *opaque)
429 {
430         BUFPUTSL(ob, "<tr>\n");
431         if (text)
432                 bufput(ob, text->data, text->size);
433         BUFPUTSL(ob, "</tr>\n");
434 }
435
436 static void
437 rndr_tablecell(struct buf *ob, const struct buf *text, int flags, void *opaque)
438 {
439         if (flags & MKD_TABLE_HEADER) {
440                 BUFPUTSL(ob, "<th");
441         } else {
442                 BUFPUTSL(ob, "<td");
443         }
444
445         switch (flags & MKD_TABLE_ALIGNMASK) {
446         case MKD_TABLE_ALIGN_CENTER:
447                 BUFPUTSL(ob, " align=\"center\">");
448                 break;
449
450         case MKD_TABLE_ALIGN_L:
451                 BUFPUTSL(ob, " align=\"left\">");
452                 break;
453
454         case MKD_TABLE_ALIGN_R:
455                 BUFPUTSL(ob, " align=\"right\">");
456                 break;
457
458         default:
459                 BUFPUTSL(ob, ">");
460         }
461
462         if (text)
463                 bufput(ob, text->data, text->size);
464
465         if (flags & MKD_TABLE_HEADER) {
466                 BUFPUTSL(ob, "</th>\n");
467         } else {
468                 BUFPUTSL(ob, "</td>\n");
469         }
470 }
471
472 static int
473 rndr_superscript(struct buf *ob, const struct buf *text, void *opaque)
474 {
475         if (!text || !text->size) return 0;
476         BUFPUTSL(ob, "<sup>");
477         bufput(ob, text->data, text->size);
478         BUFPUTSL(ob, "</sup>");
479         return 1;
480 }
481
482 static void
483 rndr_normal_text(struct buf *ob, const struct buf *text, void *opaque)
484 {
485         if (text)
486                 escape_html(ob, text->data, text->size);
487 }
488
489 static void
490 toc_header(struct buf *ob, const struct buf *text, int level, void *opaque)
491 {
492         struct html_renderopt *options = opaque;
493
494         /* set the level offset if this is the first header
495          * we're parsing for the document */
496         if (options->toc_data.current_level == 0) {
497                 options->toc_data.level_offset = level - 1;
498         }
499         level -= options->toc_data.level_offset;
500
501         if (level > options->toc_data.current_level) {
502                 while (level > options->toc_data.current_level) {
503                         BUFPUTSL(ob, "<ul>\n<li>\n");
504                         options->toc_data.current_level++;
505                 }
506         } else if (level < options->toc_data.current_level) {
507                 BUFPUTSL(ob, "</li>\n");
508                 while (level < options->toc_data.current_level) {
509                         BUFPUTSL(ob, "</ul>\n</li>\n");
510                         options->toc_data.current_level--;
511                 }
512                 BUFPUTSL(ob,"<li>\n");
513         } else {
514                 BUFPUTSL(ob,"</li>\n<li>\n");
515         }
516
517         bufprintf(ob, "<a href=\"#toc_%d\">", options->toc_data.header_count++);
518         if (text)
519                 escape_html(ob, text->data, text->size);
520         BUFPUTSL(ob, "</a>\n");
521 }
522
523 static int
524 toc_link(struct buf *ob, const struct buf *link, const struct buf *title, const struct buf *content, void *opaque)
525 {
526         if (content && content->size)
527                 bufput(ob, content->data, content->size);
528         return 1;
529 }
530
531 static void
532 toc_finalize(struct buf *ob, void *opaque)
533 {
534         struct html_renderopt *options = opaque;
535
536         while (options->toc_data.current_level > 0) {
537                 BUFPUTSL(ob, "</li>\n</ul>\n");
538                 options->toc_data.current_level--;
539         }
540 }
541
542 void
543 sdhtml_toc_renderer(struct sd_callbacks *callbacks, struct html_renderopt *options)
544 {
545         static const struct sd_callbacks cb_default = {
546                 NULL,
547                 NULL,
548                 NULL,
549                 toc_header,
550                 NULL,
551                 NULL,
552                 NULL,
553                 NULL,
554                 NULL,
555                 NULL,
556                 NULL,
557
558                 NULL,
559                 rndr_codespan,
560                 rndr_double_emphasis,
561                 rndr_emphasis,
562                 NULL,
563                 NULL,
564                 toc_link,
565                 NULL,
566                 rndr_triple_emphasis,
567                 rndr_strikethrough,
568                 rndr_superscript,
569
570                 NULL,
571                 NULL,
572
573                 NULL,
574                 toc_finalize,
575         };
576
577         memset(options, 0x0, sizeof(struct html_renderopt));
578         options->flags = HTML_TOC;
579
580         memcpy(callbacks, &cb_default, sizeof(struct sd_callbacks));
581 }
582
583 void
584 sdhtml_renderer(struct sd_callbacks *callbacks, struct html_renderopt *options, unsigned int render_flags)
585 {
586         static const struct sd_callbacks cb_default = {
587                 rndr_blockcode,
588                 rndr_blockquote,
589                 rndr_raw_block,
590                 rndr_header,
591                 rndr_hrule,
592                 rndr_list,
593                 rndr_listitem,
594                 rndr_paragraph,
595                 rndr_table,
596                 rndr_tablerow,
597                 rndr_tablecell,
598
599                 rndr_autolink,
600                 rndr_codespan,
601                 rndr_double_emphasis,
602                 rndr_emphasis,
603                 rndr_image,
604                 rndr_linebreak,
605                 rndr_link,
606                 rndr_raw_html,
607                 rndr_triple_emphasis,
608                 rndr_strikethrough,
609                 rndr_superscript,
610
611                 NULL,
612                 rndr_normal_text,
613
614                 NULL,
615                 NULL,
616         };
617
618         /* Prepare the options pointer */
619         memset(options, 0x0, sizeof(struct html_renderopt));
620         options->flags = render_flags;
621
622         /* Prepare the callbacks */
623         memcpy(callbacks, &cb_default, sizeof(struct sd_callbacks));
624
625         if (render_flags & HTML_SKIP_IMAGES)
626                 callbacks->image = NULL;
627
628         if (render_flags & HTML_SKIP_LINKS) {
629                 callbacks->link = NULL;
630                 callbacks->autolink = NULL;
631         }
632
633         if (render_flags & HTML_SKIP_HTML || render_flags & HTML_ESCAPE)
634                 callbacks->blockhtml = NULL;
635 }