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