1// Client-side Validation for XSS Vulnerabilities 2document.addEventListener('DOMContentLoaded', function () { 3 // Wait for Umbraco Forms scripts to initialize 4 setTimeout(function () { 5 // Find all Umbraco forms on the page 6 var umbracoForms = document.querySelectorAll('form[id^="form"]'); 7 8 umbracoForms.forEach(function (form) { 9 // Add submission validation as a fallback security measure 10 form.addEventListener('submit', function (event) { 11 // Only validate if not navigating between pages 12 var isNavigating = event.submitter && (event.submitter.getAttribute('data-form-navigate') === 'prev'); 13 if (!isNavigating && !validateFormAgainstXSS(form)) { 14 event.preventDefault(); 15 showXSSWarning(form); 16 } 17 }); 18 19 // Find all text inputs and textareas in the form 20 var textInputs = form.querySelectorAll('input[type="text"], input[type="email"], input[type="url"], input[type="search"], input[type="tel"], textarea'); 21 22 textInputs.forEach(function (input) { 23 // Prevent pasting of HTML content 24 input.addEventListener('paste', function (e) { 25 // Get pasted content 26 var pastedText = (e.clipboardData || window.clipboardData).getData('text'); 27 28 // Sanitize the pasted content 29 var sanitizedText = sanitizeInput(pastedText); 30 31 // If the content was changed (contained HTML), prevent default and insert sanitized version 32 if (sanitizedText !== pastedText) { 33 e.preventDefault(); 34 35 // Insert at cursor position or replace selection 36 var startPos = this.selectionStart; 37 var endPos = this.selectionEnd; 38 39 this.value = this.value.substring(0, startPos) + 40 sanitizedText + 41 this.value.substring(endPos); 42 43 // Set cursor position after inserted text 44 this.selectionStart = this.selectionEnd = startPos + sanitizedText.length; 45 } 46 }); 47 48 // Block HTML characters as they are typed 49 input.addEventListener('keypress', function (e) { 50 // Block < and > characters completely 51 if (e.key === '<' || e.key === '>') { 52 e.preventDefault(); 53 showInputWarning(this); 54 return false; 55 } 56 }); 57 58 // Check input after any change (for cases not caught by keypress) 59 input.addEventListener('input', function () { 60 // If HTML-like content is detected, immediately sanitize it 61 if (containsHTMLTags(this.value)) { 62 var cursorPos = this.selectionStart; 63 var oldLength = this.value.length; 64 65 // Sanitize the input 66 this.value = sanitizeInput(this.value); 67 68 // Adjust cursor position if content length changed 69 var newLength = this.value.length; 70 if (oldLength !== newLength) { 71 this.selectionStart = this.selectionEnd = Math.max(0, cursorPos - (oldLength - newLength)); 72 } 73 74 showInputWarning(this); 75 } 76 }); 77 }); 78 }); 79 }, 500); // Small delay to ensure Umbraco Forms scripts have loaded 80}); 81 82 // Check if a string contains HTML tags 83 function containsHTMLTags(text) { 84 // Basic pattern to detect HTML tags 85 var htmlPattern = /<\/?[a-z][\s\S]*>/i; 86 87 // Additional patterns to detect other potentially harmful content 88 var otherSuspiciousPatterns = [ 89 /javascript\s*:/i, 90 /on\w+\s*=/i, 91 /data\s*:/i 92 ]; 93 94 if (htmlPattern.test(text)) { 95 return true; 96 } 97 98 for (var i = 0; i < otherSuspiciousPatterns.length; i++) { 99 if (otherSuspiciousPatterns[i].test(text)) { 100 return true; 101 } 102 } 103 104 return false;
105} 106 107 // Function to sanitize a string by removing HTML tags and potentially harmful content 108 function sanitizeInput(text) { 109 if (typeof text !== 'string') { 110 return ''; 111 } 112 113 // Remove < and > characters to prevent any HTML 114 text = text.replace(/</g, '').replace(/>/g, ''); 115 116 // Remove script tags and their content (just in case) 117 text = text.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, ''); 118 119 // Remove event handlers 120 text = text.replace(/on\w+\s*=\s*["']?[^"']*["']?/gi, ''); 121 122 // Remove javascript: protocol 123 text = text.replace(/javascript\s*:/gi, ''); 124 125 // Remove data: URIs 126 text = text.replace(/data\s*:[^\s]*/gi, ''); 127 128 return text; 129} 130 131 // Show temporary warning tooltip near input 132 function showInputWarning(input) { 133 var warningId = input.id + '_warning'; 134 var existingWarning = document.getElementById(warningId); 135 136 if (!existingWarning) { 137 var warningElem = document.createElement('div'); 138 warningElem.id = warningId; 139 warningElem.className = 'xss-warning-tooltip'; 140 warningElem.textContent = 'HTML tags are not allowed'; 141 warningElem.style.position = 'absolute'; 142 warningElem.style.zIndex = '1000'; 143 warningElem.style.backgroundColor = '#dc3545'; 144 warningElem.style.color = 'white'; 145 warningElem.style.padding = '5px 10px'; 146 warningElem.style.borderRadius = '3px'; 147 warningElem.style.fontSize = '15px'; 148 149 // Position tooltip near the input 150 var rect = input.getBoundingClientRect(); 151 var scrollTop = window.pageYOffset || document.documentElement.scrollTop; 152 var scrollLeft = window.pageXOffset || document.documentElement.scrollLeft; 153 154 warningElem.style.top = (rect.top + scrollTop - 30) + 'px'; 155 warningElem.style.left = (rect.left + scrollLeft) + 'px'; 156 157 document.body.appendChild(warningElem); 158 159 // Remove after 2 seconds 160 setTimeout(function() { 161 if (warningElem.parentNode) { 162 warningElem.parentNode.removeChild(warningElem); 163 } 164 }, 2000); 165 } 166} 167 168 // Fallback validation for the entire form 169 function validateFormAgainstXSS(form) { 170 var textInputs = form.querySelectorAll('input[type="text"], input[type="email"], input[type="url"], input[type="search"], input[type="tel"], textarea'); 171 var isValid = true; 172 173 textInputs.forEach(function(input) { 174 // Skip validation for hidden fields or fields in hidden containers 175 if (input.closest('.umbraco-forms-hidden') || 176 input.type === 'hidden' || 177 input.style.display === 'none') { 178 return; 179 } 180 181 if (containsHTMLTags(input.value)) { 182 // Auto-sanitize on submission attempt 183 input.value = sanitizeInput(input.value); 184 isValid = false; 185 } 186 }); 187 188 return isValid; 189} 190 191 // Show a warning message at the top of the form 192 function showXSSWarning(form) { 193 // Create validation summary if it doesn't exist 194 var summaryId = 'xss_validation_summary'; 195 var existingSummary = document.getElementById(summaryId); 196 197 if (!existingSummary) { 198 var validationSummary = document.createElement('div'); 199 validationSummary.id = summaryId; 200 validationSummary.className = 'validation-summary-errors alert alert-danger'; 201 validationSummary.setAttribute('role', 'alert'); 202 203 var message = document.createElement('p'); 204 message.textContent = 'HTML tags and potentially unsafe content have been removed from your form inputs.'; 205 206 validationSummary.appendChild(message); 207 208 // Insert at the top of the form 209 form.insertBefore(validationSummary, form.firstChild); 210 211 // Scroll to the validation summary 212 validationSummary.scrollIntoView({behavior: 'smooth', block: 'start' }); 213 214 // Remove after 4 seconds 215 setTimeout(function() { 216 if (validationSummary.parentNode) { 217 validationSummary.parentNode.removeChild(validationSummary); 218 } 219 }, 4000); 220 } 221} 222 223//Remove 'form-control' from all Rich Text Editors 224document.addEventListener("DOMContentLoaded", function() { 225 // 1. Only run on an Umbraco Forms page 226 if (!document.querySelector('.umbraco-forms-page')) return; 227 228 // 2. Grab all richtext wrappers (may be zero) 229 var rteWrappers = document.querySelectorAll('.richtext'); 230 if (rteWrappers.length === 0) return; // nothing to do 231
232 // 3. For each wrapper, remove 'form-control' from any descendants 233 rteWrappers.forEach(function(wrapper) { 234 wrapper.querySelectorAll('.form-control').forEach(function (el) { 235 el.classList.remove('form-control'); 236 }); 237 }); 238}); 239 240document.addEventListener('DOMContentLoaded', function () { 241 // Find all select elements within custom-select containers 242 const selects = document.querySelectorAll('.custom-select .form-control'); 243 244 // Only proceed if there are matching elements 245 if (selects.length > 0) { 246 selects.forEach(function (select) { 247 // Function to check if the select has a non-empty value 248 function checkSelectValue(select) { 249 if (select.value === '') { 250 select.classList.remove('has-value'); 251 } else { 252 select.classList.add('has-value'); 253 } 254 } 255 // Check initial state when page loads 256 checkSelectValue(select); 257 // Add change event listener 258 select.addEventListener('change', function () { 259 checkSelectValue(this); 260 }); 261 }); 262 } 263});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.