streamline the state caching for IE mousedown a bit; also remember the text area...
[pagedown] / Markdown.Sanitizer.js
1 (function () {
2     var output, Converter;
3     if (typeof exports === "object" && typeof require === "function") { // we're in a CommonJS (e.g. Node.js) module
4         output = exports;
5         Converter = require("./Markdown.Converter").Converter;
6     } else {
7         output = window.Markdown;
8         Converter = output.Converter;
9     }
10         
11     output.getSanitizingConverter = function () {
12         var converter = new Converter();
13         converter.hooks.chain("postConversion", sanitizeHtml);
14         converter.hooks.chain("postConversion", balanceTags);
15         return converter;
16     }
17
18     function sanitizeHtml(html) {
19         return html.replace(/<[^>]*>?/gi, sanitizeTag);
20     }
21
22     // (tags that can be opened/closed) | (tags that stand alone)
23     var basic_tag_whitelist = /^(<\/?(b|blockquote|code|del|dd|dl|dt|em|h1|h2|h3|i|kbd|li|ol|p|pre|s|sup|sub|strong|strike|ul)>|<(br|hr)\s?\/?>)$/i;
24     // <a href="url..." optional title>|</a>
25     var a_white = /^(<a\shref="((https?|ftp):\/\/|\/)[-A-Za-z0-9+&@#\/%?=~_|!:,.;\(\)]+"(\stitle="[^"<>]+")?\s?>|<\/a>)$/i;
26
27     // <img src="url..." optional width  optional height  optional alt  optional title
28     var img_white = /^(<img\ssrc="(https?:\/\/|\/)[-A-Za-z0-9+&@#\/%?=~_|!:,.;\(\)]+"(\swidth="\d{1,3}")?(\sheight="\d{1,3}")?(\salt="[^"<>]*")?(\stitle="[^"<>]*")?\s?\/?>)$/i;
29
30     function sanitizeTag(tag) {
31         if (tag.match(basic_tag_whitelist) || tag.match(a_white) || tag.match(img_white))
32             return tag;
33         else
34             return "";
35     }
36
37     /// <summary>
38     /// attempt to balance HTML tags in the html string
39     /// by removing any unmatched opening or closing tags
40     /// IMPORTANT: we *assume* HTML has *already* been 
41     /// sanitized and is safe/sane before balancing!
42     /// 
43     /// adapted from CODESNIPPET: A8591DBA-D1D3-11DE-947C-BA5556D89593
44     /// </summary>
45     function balanceTags(html) {
46
47         if (html == "")
48             return "";
49
50         var re = /<\/?\w+[^>]*(\s|$|>)/g;
51         // convert everything to lower case; this makes
52         // our case insensitive comparisons easier
53         var tags = html.toLowerCase().match(re);
54
55         // no HTML tags present? nothing to do; exit now
56         var tagcount = (tags || []).length;
57         if (tagcount == 0)
58             return html;
59
60         var tagname, tag;
61         var ignoredtags = "<p><img><br><li><hr>";
62         var match;
63         var tagpaired = [];
64         var tagremove = [];
65         var needsRemoval = false;
66
67         // loop through matched tags in forward order
68         for (var ctag = 0; ctag < tagcount; ctag++) {
69             tagname = tags[ctag].replace(/<\/?(\w+).*/, "$1");
70             // skip any already paired tags
71             // and skip tags in our ignore list; assume they're self-closed
72             if (tagpaired[ctag] || ignoredtags.search("<" + tagname + ">") > -1)
73                 continue;
74
75             tag = tags[ctag];
76             match = -1;
77
78             if (!/^<\//.test(tag)) {
79                 // this is an opening tag
80                 // search forwards (next tags), look for closing tags
81                 for (var ntag = ctag + 1; ntag < tagcount; ntag++) {
82                     if (!tagpaired[ntag] && tags[ntag] == "</" + tagname + ">") {
83                         match = ntag;
84                         break;
85                     }
86                 }
87             }
88
89             if (match == -1)
90                 needsRemoval = tagremove[ctag] = true; // mark for removal
91             else
92                 tagpaired[match] = true; // mark paired
93         }
94
95         if (!needsRemoval)
96             return html;
97
98         // delete all orphaned tags from the string
99
100         var ctag = 0;
101         html = html.replace(re, function (match) {
102             var res = tagremove[ctag] ? "" : match;
103             ctag++;
104             return res;
105         });
106         return html;
107     }
108 })()