UNPKG

@instructure/canvas-rce

Version:

A component wrapping Canvas's usage of Tinymce

129 lines (122 loc) 4.71 kB
/* * Copyright (C) 2023 - 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 * as URI from 'uri-js'; /** * Attempts to build a URL from the given string, and returns null if it is not a valid URL, rather than * throwing an exception, as the URL constructor does. */ export function parseUrlOrNull(url, base) { if (!url) return null; try { return new URL(url, base); } catch (_e) { return null; } } export function relativizeUrl(url) { const parsed = URI.parse(url); delete parsed.scheme; delete parsed.userinfo; delete parsed.host; delete parsed.port; return URI.serialize(parsed); } export function parseUrlPath(url) { return URI.parse(url).path; } /** * Converts the given URL into a relative URL if it meets the following criteria: * - is parsable by the browser URL class * - has the HTTP or HTTPS protocol * - has the same hostname as the given origin * * Note: This will relativize URLs where the ports don't match. This is intentional, as ports really shouldn't * matter for RCE HTTP content, and it can solve issues where an extraneous port is added (e.g. :80 on an http url) * or when running locally and the port is different. There isn't a security issue because the user could just manually * put in the transformed content anyways. * * @param inputUrlStr URL to relativize * @param origin Origin to check for */ export function relativeHttpUrlForHostname(inputUrlStr, origin) { if (inputUrlStr == null || inputUrlStr === '') { return inputUrlStr; } if (!inputUrlStr?.match(/^https?:/i) || !origin?.match(/^https?:/i)) { // Already relative or not a http/https protocol url return inputUrlStr; } const url = parseUrlOrNull(inputUrlStr); if (url == null) { return inputUrlStr; } // Handle the simple case of origins matching. Note that the parsed URL will always have a lowercase origin // new URL("hTTps://CaNvAs.CoM").origin === 'https://canvas.com' if (url.origin === origin.toLowerCase()) { return relativizeUrl(inputUrlStr); } // Handle the more complex case of hostname/port matching const originUrl = parseUrlOrNull(origin); const originHostname = originUrl?.hostname; // Port checks are only needed if the port is not the default port for http or https. // If the port isn't an http port, then we don't want equivalence, especially for local origins, // since you might be running canvas on "localhost:3000" and some LTI tool on "localhost:4000" // But elsewhere, "http://canvas.com:80" and "http://canvas.com" are equivalent const urlUsesHttpPort = url.port === '80' || url.port === '443' || url.port === ''; const originUsesHttpPort = originUrl == null || originUrl?.port === '80' || originUrl?.port === '443' || originUrl?.port === ''; const portCheckNeeded = !(urlUsesHttpPort && originUsesHttpPort); if (portCheckNeeded && originUrl?.port !== url?.port) { return inputUrlStr; } if (url.hostname === originHostname?.toLowerCase()) { return relativizeUrl(inputUrlStr); } else { return inputUrlStr; } } /** * Adds a record of query parameters to a URL. null or undefined values in the record are ignored. * * - Relative URLs are supported. * - Non-parsable URLs will return null. * * @param inputUrlStr The URL string to parse * @param queryParams A record containing the query parameters to add */ export function addQueryParamsToUrl(inputUrlStr, queryParams) { var _parsedUrl$query; if (inputUrlStr == null) { return null; } const paramEntries = Object.entries(queryParams); if (paramEntries.length === 0) { return inputUrlStr; } const parsedUrl = URI.parse(inputUrlStr); if (parsedUrl == null) { return null; } const searchParams = new URLSearchParams((_parsedUrl$query = parsedUrl.query) !== null && _parsedUrl$query !== void 0 ? _parsedUrl$query : ''); for (const [paramName, paramValue] of paramEntries) { if (paramValue != null) { searchParams.set(paramName, paramValue); } } parsedUrl.query = searchParams.toString(); return URI.serialize(parsedUrl); }