@instructure/canvas-rce
Version:
A component wrapping Canvas's usage of Tinymce
115 lines (104 loc) • 3.81 kB
JavaScript
/*
* Copyright (C) 2020 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import formatMessage from '../../../format-message';
// Keep in sync with the protocols permitted for <a href>
// as listed in gems/canvas_sanitize/lib/canvas_sanitize/canvas_sanitize.rb
const allowed_protocols = ['ftp:', 'http:', 'https:', 'mailto:', 'tel:', 'skype:'];
/**
* Weakly validate a URL
* @param {string} url URL to validate
* @returns {boolean} true if valid, false if not valid yet, but incomplete, throws if invalid
*/
export default function validateURL(url) {
const href = url.trim();
// Handle special cases for partial URLs
if (href === '' || href === 'mailto:' || href === 'tel:' || href === 'skype:') {
return false;
}
if (/^\/\/$/.test(href)) {
return false;
}
try {
// Handle URLs starting with :// (invalid protocol)
if (href.startsWith('://')) {
throw new Error(formatMessage('Protocol must be ftp, http, https, mailto, skype, tel or may be omitted'));
}
// For URLs with protocols, we can use the URL constructor directly
if (/^[a-zA-Z][a-zA-Z\d+.-]*:/.test(href)) {
const protocol = href.substring(0, href.indexOf(':') + 1);
if (!allowed_protocols.includes(protocol)) {
throw new Error(formatMessage('{p} is not a valid protocol.', {
p: protocol.slice(0, -1)
}));
}
// For absolute URLs with protocols that require //, validate the format
if (['http:', 'https:', 'ftp:'].includes(protocol) && !href.startsWith(protocol + '//')) {
throw new Error(formatMessage('Invalid URL'));
}
// Special handling for mailto:, tel:, and skype: URLs
if (['mailto:', 'tel:', 'skype:'].includes(protocol)) {
// Consider protocol:// as incomplete for these protocols
if (href === protocol + '//' || href === protocol + '///') {
return false;
}
return href.length > protocol.length;
}
// Handle partial URLs
if (href === protocol || href === protocol + '/' || href === protocol + '//') {
return false;
}
// Try constructing the URL to validate the format
try {
if (href.includes('//')) {
new URL(href);
}
return true;
} catch {
// If URL construction fails but we have more than just protocol://, consider it valid
// This allows for test URLs like ftp://host:port/path
return href.length > protocol.length + 2;
}
}
// Handle protocol-relative URLs
if (href.startsWith('//')) {
if (href === '//' || href === '///') {
return false;
}
try {
new URL('http:' + href);
return true;
} catch {
// If URL construction fails but we have more than just //, consider it valid
return href.length > 2;
}
}
// Handle relative paths
return true;
} catch (e) {
if (e instanceof Error) {
if (e.message.includes('Invalid URL')) {
if (href.match(/^(https?|ftp):\/?\/?$/)) {
return false;
}
throw new Error(formatMessage('Invalid URL'));
}
throw e;
}
throw e;
}
}