// (C) Copyright 2015 Moodle Pty Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import { CoreSite } from '@classes/sites/site'; import { CorePath } from './path'; import { CoreText } from './text'; import { CorePlatform } from '@services/platform'; import { CoreConstants } from '../constants'; import { CoreMedia } from './media'; import { CoreLang, CoreLangFormat } from '@services/lang'; import { DomSanitizer } from '@singletons'; import { SafeUrl } from '@angular/platform-browser'; /** * Parts contained within a url. */ interface UrlParts { /** * Url protocol. */ protocol?: string; /** * Url domain. */ domain?: string; /** * Url port. */ port?: string; /** * Url credentials: username and password (if any). */ credentials?: string; /** * Url's username. */ username?: string; /** * Url's password. */ password?: string; /** * Url path. */ path?: string; /** * Url query. */ query?: string; /** * Url fragment. */ fragment?: string; } export const enum CoreUrlPartNames { Protocol = 'protocol', WWWInDomain = 'www', // Will remove starting www from domain. Query = 'query', Fragment = 'fragment', } /** * Singleton with helper functions for urls. */ export class CoreUrl { // Avoid creating singleton instances. private constructor() { // Nothing to do. } /** * Given an address as a string, return a URL to open the address in maps. * * @param address The address. * @returns URL to view the address. */ static buildAddressURL(address: string): SafeUrl { const parsedUrl = CoreUrl.parse(address); if (parsedUrl?.protocol) { // It's already a URL, don't convert it. return DomSanitizer.bypassSecurityTrustUrl(address); } return DomSanitizer.bypassSecurityTrustUrl((CorePlatform.isAndroid() ? 'geo:0,0?q=' : 'http://maps.google.com?q=') + encodeURIComponent(address)); } /** * Parse parts of a url, using an implicit protocol if it is missing from the url. * * @param url Url. * @returns Url parts. */ static parse(url: string): UrlParts | null { url = url.trim(); // Parse url with regular expression taken from RFC 3986: https://tools.ietf.org/html/rfc3986#appendix-B. const match = url.match(/^(([^:/?#]+):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/); if (!match) { return null; } const host = match[4] || ''; // Get the credentials and the port from the host. const [domainAndPort, credentials]: string[] = host.split('@').reverse(); const [domain, port]: string[] = domainAndPort.split(':'); const [username, password]: string[] = credentials ? credentials.split(':') : []; // Prepare parts replacing empty strings with undefined. return { protocol: match[2] || undefined, domain: domain || undefined, port: port || undefined, credentials: credentials || undefined, username: username || undefined, password: password || undefined, path: match[5] || undefined, query: match[7] || undefined, fragment: match[9] || undefined, }; } /** * Given some parts of a URL, returns the URL as a string. * * @param parts Parts. * @returns Assembled URL. */ static assemble(parts: UrlParts): string { const protocol = parts.protocol; const credentials = parts.credentials || (parts.password ? `${parts.username}:${parts.password}` : parts.username); return (protocol ? `${protocol}://` : '') + (credentials ? `${credentials}@` : '') + (parts.domain ?? '') + (parts.port ? `:${parts.port}` : '') + (parts.path ?? '') + (parts.query ? `?${parts.query}` : '') + (parts.fragment ? `#${parts.fragment}` : ''); } /** * Guess the Moodle domain from a site url. * * @param url Site url. * @returns Guessed Moodle domain. */ static guessMoodleDomain(url: string): string | null { // Add protocol if it was missing. Moodle can only be served through http or https, so this is a fair assumption to make. if (!url.match(/^https?:\/\//)) { url = `https://${url}`; } // Match using common suffixes. const knownSuffixes = [ '/my/?', '/\\?redirect=0', '/index\\.php', '/course/view\\.php', '\\/login/index\\.php', '/mod/page/view\\.php', ]; const match = url.match(new RegExp(`^https?://(.*?)(${knownSuffixes.join('|')})`)); if (match) { return match[1]; } // If nothing else worked, parse the domain. const urlParts = CoreUrl.parse(url); return urlParts?.domain ? urlParts.domain : null; } /** * Returns the pattern to check if the URL is a valid Moodle Url. * * @returns Desired RegExp. */ static getValidMoodleUrlPattern(): RegExp { // Regular expression based on RFC 3986: https://tools.ietf.org/html/rfc3986#appendix-B. // Improved to not admit spaces. return new RegExp(/^(([^:/?# ]+):)?(\/\/([^/?# ]*))?([^?# ]*)(\?([^#]*))?(#(.*))?$/); } /** * Check if the given url is valid for the app to connect. * * @param url Url to check. * @returns True if valid, false otherwise. */ static isValidMoodleUrl(url: string): boolean { const patt = CoreUrl.getValidMoodleUrlPattern(); return patt.test(url.trim()); } /** * Removes protocol from the url. * * @param url Site url. * @returns Url without protocol. * @deprecated since 4.5. Use CoreUrl.removeUrlParts(url, CoreUrlPartNames.Protocol) instead. */ static removeProtocol(url: string): string { return CoreUrl.removeUrlParts(url, CoreUrlPartNames.Protocol); } /** * Check if two URLs have the same domain and path. * * @param urlA First URL. * @param urlB Second URL. * @returns Whether they have same domain and path. */ static sameDomainAndPath(urlA: string, urlB: string): boolean { // Add protocol if missing, the parse function requires it. if (!urlA.match(/^[^/:.?]*:\/\//)) { urlA = `https://${urlA}`; } if (!urlB.match(/^[^/:.?]*:\/\//)) { urlB = `https://${urlB}`; } const partsA = CoreUrl.parse(urlA); const partsB = CoreUrl.parse(urlB); partsA && Object.entries(partsA).forEach(([part, value]) => partsA[part] = value?.toLowerCase()); partsB && Object.entries(partsB).forEach(([part, value]) => partsB[part] = value?.toLowerCase()); return partsA?.domain === partsB?.domain && CoreText.removeEndingSlash(partsA?.path) === CoreText.removeEndingSlash(partsB?.path); } /** * Get the anchor of a URL. If there's more than one they'll all be returned, separated by #. * E.g. myurl.com#foo=1#bar=2 will return #foo=1#bar=2. * * @param url URL. * @returns Anchor, undefined if no anchor. */ static getUrlAnchor(url: string): string | undefined { const urlParts = CoreUrl.parse(url); return urlParts?.fragment ? `#${urlParts.fragment}` : undefined; } /** * Remove the anchor from a URL. * * @param url URL. * @returns URL without anchor if any. * * @deprecated since 4.5. Use CoreUrl.removeUrlParts(url, CoreUrlPartNames.Fragment) instead. */ static removeUrlAnchor(url: string): string { return CoreUrl.removeUrlParts(url, CoreUrlPartNames.Fragment); } /** * Convert a URL to an absolute URL (if it isn't already). * * @param parentUrl The parent URL. * @param url The url to convert. * @returns Absolute URL. */ static toAbsoluteURL(parentUrl: string, url: string): string { const parsedUrl = CoreUrl.parse(url); if (parsedUrl?.protocol) { return url; // Already absolute URL. } const parsedParentUrl = CoreUrl.parse(parentUrl); if (url.startsWith('//')) { // It only lacks the protocol, add it. return (parsedParentUrl?.protocol || 'https') + ':' + url; } // The URL should be added after the domain (if starts with /) or after the parent path. const treatedParentUrl = CoreUrl.assemble({ protocol: parsedParentUrl?.protocol || 'https', domain: parsedParentUrl?.domain, port: parsedParentUrl?.port, credentials: parsedParentUrl?.credentials, path: url.startsWith('/') ? undefined : parsedParentUrl?.path, }); return CorePath.concatenatePaths(treatedParentUrl, url); } /** * Convert a URL to a relative URL (if it isn't already). * * @param parentUrl The parent URL. * @param url The url to convert. * @returns Relative URL. */ static toRelativeURL(parentUrl: string, url: string): string { parentUrl = CoreUrl.removeUrlParts(parentUrl, CoreUrlPartNames.Protocol); if (!url.includes(parentUrl)) { return url; // Already relative URL. } return CoreText.removeStartingSlash(CoreUrl.removeUrlParts(url, CoreUrlPartNames.Protocol).replace(parentUrl, '')); } /** * Returns if URL is a Vimeo video URL. * * @param url URL. * @returns Whether is a Vimeo video URL. */ static isVimeoVideoUrl(url: string): boolean { return !!url.match(/https?:\/\/player\.vimeo\.com\/video\/[0-9]+/); } /** * Get the URL to use to play a Vimeo video if the URL supplied is a Vimeo video URL. * If it's a Vimeo video, the app will use the site's wsplayer script instead to make restricted videos work. * * @param url URL to treat. * @param site Site that contains the URL. * @returns URL, undefined if not a Vimeo video. */ static getVimeoPlayerUrl( url: string, site: CoreSite, ): string | undefined { const matches = url.match(/https?:\/\/player\.vimeo\.com\/video\/([0-9]+)([?&]+h=([a-zA-Z0-9]*))?/); if (!matches || !matches[1]) { // Not a Vimeo video. return; } let newUrl = CorePath.concatenatePaths(site.getURL(), '/media/player/vimeo/wsplayer.php?video=') + matches[1] + '&token=' + site.getToken(); let privacyHash: string | undefined | null = matches[3]; if (!privacyHash) { // No privacy hash using the new format. Check the legacy format. const matches = url.match(/https?:\/\/player\.vimeo\.com\/video\/([0-9]+)(\/([a-zA-Z0-9]+))?/); privacyHash = matches && matches[3]; } if (privacyHash) { newUrl += `&h=${privacyHash}`; } return newUrl; } /** * Add or remove 'www' from a URL. The url needs to have http or https protocol. * * @param url URL to modify. * @returns Modified URL. */ static addOrRemoveWWW(url: string): string { if (url) { if (url.match(/http(s)?:\/\/www\./)) { // Already has www. Remove it. url = url.replace('www.', ''); } else { url = url.replace('https://', 'https://www.'); url = url.replace('http://', 'http://www.'); } } return url; } /** * Add params to a URL. * * @param url URL to add the params to. * @param params Object with the params to add. * @param anchor Anchor text if needed. * @param boolToNumber Whether to convert bools to 1 or 0. * @returns URL with params. */ static addParamsToUrl(url: string, params?: Record, anchor?: string, boolToNumber?: boolean): string { // Remove any existing anchor to add the params before it. const urlAndAnchor = url.split('#'); url = urlAndAnchor[0]; let separator = url.indexOf('?') !== -1 ? '&' : '?'; for (const key in params) { let value = params[key]; if (boolToNumber && typeof value === 'boolean') { // Convert booleans to 1 or 0. value = value ? '1' : '0'; } // Ignore objects and undefined. if (typeof value !== 'object' && value !== undefined) { url += separator + key + '=' + value; separator = '&'; } } // Re-add the anchor if any. if (urlAndAnchor.length > 1) { // Remove the URL from the array. urlAndAnchor.shift(); // Use a join in case there is more than one #. url += '#' + urlAndAnchor.join('#'); } if (anchor) { url += '#' + anchor; } return url; } /** * Given a URL and a text, return an HTML link. * * @param url URL. * @param text Text of the link. * @returns Link. */ static buildLink(url: string, text: string): string { return '' + text + ''; } /** * Check whether we can use tokenpluginfile.php endpoint for a certain URL. * * @param url URL to check. * @param siteUrl The URL of the site the URL belongs to. * @param accessKey User access key for tokenpluginfile. * @returns Whether tokenpluginfile.php can be used. */ static canUseTokenPluginFile(url: string, siteUrl: string, accessKey?: string): boolean { // Do not use tokenpluginfile if site doesn't use slash params, the URL doesn't work. // Also, only use it for "core" pluginfile endpoints. Some plugins can implement their own endpoint (like customcert). return !CoreConstants.CONFIG.disableTokenFile && !!accessKey && !url.match(/[&?]file=/) && ( url.indexOf(CorePath.concatenatePaths(siteUrl, 'pluginfile.php')) === 0 || url.indexOf(CorePath.concatenatePaths(siteUrl, 'webservice/pluginfile.php')) === 0) && !CoreMedia.sourceUsesJavascriptPlayer({ src: url }); } /** * Same as Javascript's decodeURI, but if an exception is thrown it will return the original URI. * * @param uri URI to decode. * @returns Decoded URI, or original URI if an exception is thrown. */ static decodeURI(uri: string): string { try { return decodeURI(uri); } catch { // Error, use the original URI. } return uri; } /** * Same as Javascript's decodeURIComponent, but if an exception is thrown it will return the original URI. * * @param uri URI to decode. * @returns Decoded URI, or original URI if an exception is thrown. */ static decodeURIComponent(uri: string): string { try { return decodeURIComponent(uri); } catch { // Error, use the original URI. } return uri; } /** * Extracts the parameters from a URL and stores them in an object. * * @param url URL to treat. * @returns Object with the params. */ static extractUrlParams(url: string): CoreUrlParams { const regex = /[?&]+([^=&]+)=?([^&]*)?/gi; const subParamsPlaceholder = '@@@SUBPARAMS@@@'; const params: CoreUrlParams = {}; const urlAndHash = url.split('#'); const questionMarkSplit = urlAndHash[0].split('?'); let subParams: string; if (questionMarkSplit.length > 2) { // There is more than one question mark in the URL. This can happen if any of the params is a URL with params. // We only want to treat the first level of params, so we'll remove this second list of params and restore it later. questionMarkSplit.splice(0, 2); subParams = '?' + questionMarkSplit.join('?'); urlAndHash[0] = urlAndHash[0].replace(subParams, subParamsPlaceholder); } urlAndHash[0].replace(regex, (match: string, key: string, value: string): string => { params[key] = value !== undefined ? CoreUrl.decodeURIComponent(value) : ''; if (subParams) { params[key] = params[key].replace(subParamsPlaceholder, subParams); } return match; }); if (urlAndHash.length > 1) { // Remove the URL from the array. urlAndHash.shift(); // Add the hash as a param with a special name. Use a join in case there is more than one #. params.urlHash = urlAndHash.join('#'); } return params; } /** * Generic function for adding the wstoken to Moodle urls and for pointing to the correct script. * For download remote files from Moodle we need to use the special /webservice/pluginfile passing * the ws token as a get parameter. * * @param url The url to be fixed. * @param token Token to use. * @param siteUrl The URL of the site the URL belongs to. * @param accessKey User access key for tokenpluginfile. * @returns Fixed URL. */ static fixPluginfileURL(url: string, token: string, siteUrl: string, accessKey?: string): string { if (!url) { return ''; } url = url.replace(/&/g, '&'); const canUseTokenPluginFile = accessKey && CoreUrl.canUseTokenPluginFile(url, siteUrl, accessKey); // First check if we need to fix this url or is already fixed. if (!canUseTokenPluginFile && url.indexOf('token=') != -1) { return url; } // Check if is a valid URL (contains the pluginfile endpoint) and belongs to the site. if (!CoreUrl.isPluginFileUrl(url) || url.indexOf(CoreText.addEndingSlash(siteUrl)) !== 0) { return url; } if (canUseTokenPluginFile) { // Use tokenpluginfile.php. url = url.replace(/(\/webservice)?\/pluginfile\.php/, '/tokenpluginfile.php/' + accessKey); } else { // Use pluginfile.php. Some webservices returns directly the correct download url, others not. if (url.indexOf(CorePath.concatenatePaths(siteUrl, 'pluginfile.php')) === 0) { url = url.replace('/pluginfile', '/webservice/pluginfile'); } url = CoreUrl.addParamsToUrl(url, { token }); } // Always send offline=1 (it's for external repositories). return CoreUrl.addParamsToUrl(url, { offline: '1', lang: CoreLang.getCurrentLanguageSync(CoreLangFormat.LMS) }); } /** * Formats a URL, trim, lowercase, etc... * * @param url The url to be formatted. * @returns Fromatted url. */ static formatURL(url: string): string { url = url.trim(); // Check if the URL starts by http or https. if (! /^http(s)?:\/\/.*/i.test(url)) { // Test first allways https. url = 'https://' + url; } // http always in lowercase. url = url.replace(/^http/i, 'http'); url = url.replace(/^https/i, 'https'); // Replace last slash. url = url.replace(/\/$/, ''); return url; } /** * Returns the Youtube Embed Video URL or undefined if not found. * * @param url URL * @returns Youtube Embed Video URL or undefined if not found. */ static getYoutubeEmbedUrl(url?: string): string | void { if (!url) { return; } let videoId = ''; const params: CoreUrlParams = {}; url = CoreText.decodeHTML(url); // Get the video ID. let match = url.match(/^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=)([^#&?]*).*/); if (match && match[2].length === 11) { videoId = match[2]; } // No videoId, do not continue. if (!videoId) { return; } // Now get the playlist (if any). match = url.match(/[?&]list=([^#&?]+)/); if (match && match[1]) { params.list = match[1]; } // Now get the start time (if any). match = url.match(/[?&]start=(\d+)/); if (match && match[1]) { params.start = parseInt(match[1], 10).toString(); } else { // No start param, but it could have a time param. match = url.match(/[?&]t=(\d+h)?(\d+m)?(\d+s)?/); if (match) { const start = (match[1] ? parseInt(match[1], 10) * 3600 : 0) + (match[2] ? parseInt(match[2], 10) * 60 : 0) + (match[3] ? parseInt(match[3], 10) : 0); params.start = start.toString(); } } return CoreUrl.addParamsToUrl('https://www.youtube.com/embed/' + videoId, params); } /** * Given a URL, returns what's after the last '/' without params. * Example: * http://mysite.com/a/course.html?id=1 -> course.html * * @param url URL to treat. * @returns Last file without params. */ static getLastFileWithoutParams(url: string): string { const parsedUrl = CoreUrl.parse(url); if (!parsedUrl) { return ''; } const path = parsedUrl.path ?? ''; return path.split('/').pop() ?? ''; } /** * Return the array of arguments of the pluginfile or tokenpluginfile url. * * @param url URL to get the args. * @returns The args found, undefined if not a pluginfile. */ static getPluginFileArgs(url: string): string[] | undefined { let args: string[] = []; if (CoreUrl.isPluginFileUrl(url)) { const relativePath = url.substring(url.indexOf('/pluginfile.php') + 16); args = relativePath.split('/'); } else if (CoreUrl.isTokenPluginFileUrl(url)) { const relativePath = url.substring(url.indexOf('/tokenpluginfile.php') + 21); args = relativePath.split('/'); args.shift(); // Remove the token. } if (args.length < 3) { // To be a plugin file it should have at least contextId, Component and Filearea. return; } return args; } /** * Get the protocol from a URL. * E.g. http://www.google.com returns 'http'. * * @param url URL to treat. * @returns Protocol, undefined if no protocol found. */ static getUrlProtocol(url: string): string | void { return CoreUrl.parse(url)?.protocol; } /** * Gets a username from a URL like: user@mysite.com. * * @param url URL to treat. * @returns Username. Undefined if no username found. * @todo Use CoreUrl.parse. It cannot use it right now because it won't detect username on custom URL with double protocol. */ static getUsernameFromUrl(url: string): string | undefined { if (url.indexOf('@') < 0) { return; } // Get URL without protocol. const withoutProtocol = url.replace(/^[^?@/]*:\/\//, ''); const matches = withoutProtocol.match(/[^@]*/); // Make sure that @ is at the start of the URL, not in a param at the end. if (matches && matches.length && !matches[0].match(/[/|?]/)) { const credentials = matches[0]; return credentials.split(':')[0]; } } /** * Returns if a URL has any protocol (not a relative URL). * * @param url The url to test against the pattern. * @returns Whether the url is absolute. */ static isAbsoluteURL(url: string): boolean { return /^[^:]{2,}:\/\//i.test(url) || /^(tel:|mailto:|geo:)/.test(url); } /** * Returns if a URL is downloadable: plugin file OR theme/image.php OR gravatar. * * @param url The URL to test. * @returns Whether the URL is downloadable. */ static isDownloadableUrl(url: string): boolean { return CoreUrl.isPluginFileUrl(url) || CoreUrl.isTokenPluginFileUrl(url) || CoreUrl.isThemeImageUrl(url) || CoreUrl.isGravatarUrl(url); } /** * Returns if a URL is a gravatar URL. * * @param url The URL to test. * @returns Whether the URL is a gravatar URL. */ static isGravatarUrl(url: string): boolean { return url?.indexOf('gravatar.com/avatar') !== -1; } /** * Check if a URL uses http or https protocol. * * @param url The url to test. * @returns Whether the url uses http or https protocol. * @todo Use CoreUrl.parse */ static isHttpURL(url: string): boolean { return /^https?:\/\/.+/i.test(url); } /** * Check whether an URL belongs to a local file. * * @param url URL to check. * @returns Whether the URL belongs to a local file. */ static isLocalFileUrl(url: string): boolean { const urlParts = CoreUrl.parse(url); return CoreUrl.isLocalFileUrlScheme(urlParts?.protocol || '', urlParts?.domain || ''); } /** * Check whether a URL scheme belongs to a local file. * * @param scheme Scheme to check. * @returns Whether the scheme belongs to a local file. */ static isLocalFileUrlScheme(scheme: string, domain: string): boolean { if (!scheme) { return false; } scheme = scheme.toLowerCase(); return scheme === 'cdvfile' || scheme === 'file' || scheme === 'filesystem' || scheme === CoreConstants.CONFIG.ioswebviewscheme || (CorePlatform.isMobile() && scheme === 'http' && domain === 'localhost'); // @todo Get served domain from ENV. } /** * Returns if a URL is a pluginfile URL. * * @param url The URL to test. * @returns Whether the URL is a pluginfile URL. */ static isPluginFileUrl(url: string): boolean { return url.indexOf('/pluginfile.php') !== -1; } /** * Returns if a URL is a tokenpluginfile URL. * * @param url The URL to test. * @returns Whether the URL is a tokenpluginfile URL. */ static isTokenPluginFileUrl(url: string): boolean { return url.indexOf('/tokenpluginfile.php') !== -1; } /** * Returns if a URL is a theme image URL. * * @param imageUrl The URL to test. * @param siteUrl The Site Url. * @returns Whether the URL is a theme image URL. */ static isThemeImageUrl(imageUrl: string, siteUrl?: string): boolean { if (siteUrl) { return imageUrl.startsWith(`${siteUrl}/theme/image.php`); } return imageUrl?.indexOf('/theme/image.php') !== -1; } /** * Returns an specific param from an image URL. * * @param imageUrl Image Url * @param param Param to get from the URL. * @param siteUrl Site URL. * @returns Param from the URL. */ static getThemeImageUrlParam(imageUrl: string, param: string, siteUrl?: string): string { if (!CoreUrl.isThemeImageUrl(imageUrl, siteUrl)) { // Cannot be guessed. return ''; } const matches = imageUrl.match('/theme/image.php/(.*)'); if (matches?.[1]) { // Slash arguments found. const slasharguments = matches[1].split('/'); if (slasharguments.length < 4) { // Image not found, malformed URL. return ''; } // Join from the third element to the end. const image = slasharguments.slice(3).join('/'); switch (param) { case 'theme': return slasharguments[0]; case 'component': return slasharguments[1]; case 'rev': return slasharguments[2]; case 'image': // Remove possible url params. return CoreUrl.removeUrlParts(image, [CoreUrlPartNames.Query, CoreUrlPartNames.Fragment]); default: return CoreUrl.extractUrlParams(image)[param] || ''; } } // URL arguments found. const iconParams = CoreUrl.extractUrlParams(imageUrl); switch (param) { case 'theme': return iconParams[param] || 'standard'; case 'component': return iconParams[param] || 'core'; case 'rev': return iconParams[param] || '-1'; case 'svg': return iconParams[param] || '1'; case 'image': default: return iconParams[param] || ''; } } /** * Returns the URL without the desired parts. * * @param url URL to treat. * @param parts Parts to remove. * @returns URL without the parts. */ static removeUrlParts(url: string, parts: CoreUrlPartNames | CoreUrlPartNames[]): string { if (!url) { return url; } if (!Array.isArray(parts)) { parts = [parts]; } parts.forEach((part) => { switch (part) { case CoreUrlPartNames.WWWInDomain: // Remove www, no protocol. url = url.replace(/^www./, ''); // Remove www, with protocol. url = url.replace(/\/\/www./, '//'); break; case CoreUrlPartNames.Protocol: // Remove the protocol from url url = url.replace(/^.*?:\/\//, ''); break; case CoreUrlPartNames.Query: url = url.match(/^[^?]+/)?.[0] || ''; break; case CoreUrlPartNames.Fragment: url = url.split('#')[0]; break; } }); return url; } /** * Modifies a pluginfile URL to use the default pluginfile script instead of the webservice one. * * @param url The url to be fixed. * @param siteUrl The URL of the site the URL belongs to. * @returns Modified URL. */ static unfixPluginfileURL(url: string, siteUrl?: string): string { if (!url) { return ''; } url = url.replace(/&/g, '&'); // It site URL is supplied, check if the URL belongs to the site. if (siteUrl && url.indexOf(CoreText.addEndingSlash(siteUrl)) !== 0) { return url; } // Check tokenpluginfile first. url = url.replace(/\/tokenpluginfile\.php\/[^/]+\//, '/pluginfile.php/'); // Treat webservice/pluginfile case. url = url.replace(/\/webservice\/pluginfile\.php\//, '/pluginfile.php/'); // Make sure the URL doesn't contain the token. url = url.replace(/([?&])token=[^&]*&?/, '$1'); return url; } } export type CoreUrlParams = {[key: string]: string};