1 /**
  2  * Validates and invalidates designmode contents
  3  */
  4 xq.Validator = xq.Class({
  5 	initialize: function(curUrl, urlValidationMode, allowedTags, allowedAttrs) {
  6 		xq.addToFinalizeQueue(this);
  7 
  8 		this.allowedTags = (allowedTags || ['a', 'abbr', 'acronym', 'address', 'blockquote', 'br', 'caption', 'cite', 'code', 'dd', 'dfn', 'div', 'dl', 'dt', 'em', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'img', 'kbd', 'li', 'ol', 'p', 'pre', 'q', 'samp', 'span', 'sup', 'sub', 'strong', 'table', 'thead', 'tbody', 'td', 'th', 'tr', 'ul', 'var']).join(' ') + ' ';
  9 		this.allowedAttrs = (allowedAttrs || ['alt', 'cite', 'class', 'datetime', 'height', 'href', 'id', 'rel', 'rev', 'src', 'style', 'title', 'width']).join(' ') + ' ';
 10 		
 11 		this.curUrl = curUrl;
 12 		this.curUrlParts = curUrl ? curUrl.parseURL() : null;
 13 		this.urlValidationMode = urlValidationMode;
 14 	},
 15 	
 16 	/**
 17 	 * Perform validation on given element
 18 	 *
 19 	 * @param {Element} element Target element. It is not affected by validation.
 20 	 * @param {boolean} fullValidation Perform full validation. If you just want to use the result to assign innerHTML, set it false
 21 	 *
 22 	 * @returns {String} Validated HTML string
 23 	 */
 24 	validate: function(element, fullValidation) {throw "Not implemented"},
 25 	
 26 	/**
 27 	 * Perform invalidation on given element to make the designmode works well.
 28 	 *
 29 	 * @param {Element} element Target element.
 30 	 * @returns {String} Invalidated HTML string
 31 	 */
 32 	invalidate: function(element) {throw "Not implemented"},
 33 	
 34 	validateStrike: function(content) {
 35 		content = content.replace(/<strike(>|\s+[^>]*>)/ig, "<span class=\"strike\"$1");
 36 		content = content.replace(/<\/strike>/ig, "</span>");
 37 		return content;
 38 	},
 39 	
 40 	validateUnderline: function(content) {
 41 		content = content.replace(/<u(>|\s+[^>]*>)/ig, "<em class=\"underline\"$1");
 42 		content = content.replace(/<\/u>/ig, "</em>");
 43 		return content;
 44 	},
 45 	
 46 	replaceTag: function(content, from, to) {
 47 		return content.replace(new RegExp("(</?)" + from + "(>|\\s+[^>]*>)", "ig"), "$1" + to + "$2");
 48 	},
 49 	
 50 	validateSelfClosingTags: function(content) {
 51 		return content.replace(/<(br|hr|img)([^>]*?)>/img, function(str, tag, attrs) {
 52 			return "<" + tag + attrs + " />"
 53 		});
 54 	},
 55 	
 56 	removeComments: function(content) {
 57 		return content.replace(/<!--.*?-->/img, '');
 58 	},
 59 	
 60 	removeDangerousElements: function(element) {
 61 		var scripts = xq.$A(element.getElementsByTagName('SCRIPT')).reverse();
 62 		for(var i = 0; i < scripts.length; i++) {
 63 			scripts[i].parentNode.removeChild(scripts[i]);
 64 		}
 65 	},
 66 
 67 	// TODO: very slow
 68 	applyWhitelist: function(content) {
 69 		var allowedTags = this.allowedTags;
 70 		var allowedAttrs = this.allowedAttrs;
 71 		
 72 		return content.replace(new RegExp("(</?)([^>]+?)(>|\\s+([^>]*?)(\\s?/?)>)", "g"), function(str, head, tag, tail, attrs, selfClosing) {
 73 			if(allowedTags.indexOf(tag) == -1) return '';
 74 			
 75 			if(attrs) {
 76 				attrs = attrs.replace(/(^|\s")([^"=]+)(\s|$)/g, '$1$2="$2"$3'); // for IE
 77 				
 78 				var sb = [];
 79 				var m = attrs.match(/([^=]+)="[^"]*?"/g);
 80 				for(var i = 0; i < m.length; i++) {
 81 					m[i] = m[i].strip();
 82 					var name = m[i].split('=')[0];
 83 					if(allowedAttrs.indexOf(name) != -1) sb.push(m[i]);
 84 				}
 85 				attrs = sb.join(' ');
 86 				if(attrs != '') attrs = ' ' + attrs;
 87 				return head + tag + attrs + selfClosing + '>';
 88 			} else {
 89 				return str;
 90 			}
 91 		});
 92 	},
 93 	
 94 	makeUrlsRelative: function(content) {
 95 		var curUrl = this.curUrl;
 96 		var urlParts = this.curUrlParts;
 97 		
 98 		// 1. find attributes and...
 99 		return content.replace(/(<\w+\s+)(\/|([^>]+?)(\/?))>/g, function(str, head, ignored, attrs, tail) {
100 			if(attrs) {
101 				// 2. validate URL part
102 				attrs = attrs.replace(/(href|src)="([^"]+)"/g, function(str, name, url) {
103 					// 3. first, make it absolute
104 					var abs = null;
105 					if(url.charAt(0) == '#') {
106 						abs = urlParts.includeQuery + url;
107 					} else if(url.charAt(0) == '?') {
108 						abs = urlParts.includePath + url;
109 					} else if(url.charAt(0) == '/') {
110 						abs = urlParts.includeHost + url;
111 					} else if(url.match(/^\w+:\/\//)) {
112 						abs = url;
113 					} else {
114 						abs = urlParts.includeBase + url;
115 					}
116 					
117 					// 4. make it relative by removing same part
118 					var rel = abs;
119 					
120 					if(abs.indexOf(urlParts.includeQuery) == 0) {
121 						rel = abs.substring(urlParts.includeQuery.length);
122 					} else if(abs.indexOf(urlParts.includePath) == 0) {
123 						rel = abs.substring(urlParts.includePath.length);
124 					} else if(abs.indexOf(urlParts.includeBase) == 0) {
125 						rel = abs.substring(urlParts.includeBase.length);
126 					} else if(abs.indexOf(urlParts.includeHost) == 0) {
127 						rel = abs.substring(urlParts.includeHost.length);
128 					}
129 					if(rel == '') rel = '#';
130 					
131 					return name + '="' + rel + '"';
132 				});
133 				
134 				return head + attrs + tail + '>';
135 			} else {
136 				return str;
137 			}
138 		});
139 		
140 		return content;
141 	},
142 	
143 	makeUrlsHostRelative: function(content) {
144 		var curUrl = this.curUrl;
145 		var urlParts = this.curUrlParts;
146 		
147 		// 1. find attributes and...
148 		return content.replace(/(<\w+\s+)(\/|([^>]+?)(\/?))>/g, function(str, head, ignored, attrs, tail) {
149 			if(attrs) {
150 				// 2. validate URL part
151 				attrs = attrs.replace(/(href|src)="([^"]+)"/g, function(str, name, url) {
152 					// 3. first, make it absolute
153 					var abs = null;
154 					if(url.charAt(0) == '#') {
155 						abs = urlParts.includeQuery + url;
156 					} else if(url.charAt(0) == '?') {
157 						abs = urlParts.includePath + url;
158 					} else if(url.charAt(0) == '/') {
159 						abs = urlParts.includeHost + url;
160 					} else if(url.match(/^\w+:\/\//)) {
161 						abs = url;
162 					} else {
163 						abs = urlParts.includeBase + url;
164 					}
165 					
166 					// 4. make it relative by removing same part
167 					var rel = abs;
168 					if(abs.indexOf(urlParts.includeHost) == 0) {
169 						rel = abs.substring(urlParts.includeHost.length);
170 					}
171 					if(rel == '') rel = '#';
172 					
173 					return name + '="' + rel + '"';
174 				});
175 				
176 				return head + attrs + tail + '>';
177 			} else {
178 				return str;
179 			}
180 		});
181 		
182 		return content;
183 	},
184 	
185 	makeUrlsAbsolute: function(content) {
186 		var curUrl = this.curUrl;
187 		var urlParts = this.curUrlParts;
188 		
189 		// 1. find attributes and...
190 		return content.replace(/(<\w+\s+)(\/|([^>]+?)(\/?))>/g, function(str, head, ignored, attrs, tail) {
191 			if(attrs) {
192 				// 2. validate URL part
193 				attrs = attrs.replace(/(href|src)="([^"]+)"/g, function(str, name, url) {
194 					var abs = null;
195 					if(url.charAt(0) == '#') {
196 						abs = urlParts.includeQuery + url;
197 					} else if(url.charAt(0) == '?') {
198 						abs = urlParts.includePath + url;
199 					} else if(url.charAt(0) == '/') {
200 						abs = urlParts.includeHost + url;
201 					} else if(url.match(/^\w+:\/\//)) {
202 						abs = url;
203 					} else {
204 						abs = urlParts.includeBase + url;
205 					}
206 
207 					return name + '="' + abs + '"';
208 				});
209 				
210 				return head + attrs + tail + '>';
211 			} else {
212 				return str;
213 			}
214 		});
215 	}
216 });
217 
218 /**
219  * Creates and returns instance of browser specific implementation.
220  */
221 xq.Validator.createInstance = function(curUrl, urlValidationMode, allowedTags, allowedAttrs) {
222 	if(xq.Browser.isTrident) {
223 		return new xq.ValidatorTrident(curUrl, urlValidationMode, allowedTags, allowedAttrs);
224 	} else if(xq.Browser.isWebkit) {
225 		return new xq.ValidatorWebkit(curUrl, urlValidationMode, allowedTags, allowedAttrs);
226 	} else {
227 		return new xq.ValidatorGecko(curUrl, urlValidationMode, allowedTags, allowedAttrs);
228 	}
229 }
230