[MM-21835] Use URL instead of the url library (#1384)
Additionally, migrate all of the URL related helper functions from `src/utils/utils.js` to the new `src/utils/url.js` file and migrate tests. Issue MM-21835 Fixes #1206
This commit is contained in:
parent
ad1871ad95
commit
5d0a937bb9
|
@ -6,7 +6,6 @@
|
|||
/* eslint-disable react/no-set-state */
|
||||
|
||||
import os from 'os';
|
||||
import url from 'url';
|
||||
|
||||
import React, {Fragment} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
@ -17,6 +16,7 @@ import DotsVerticalIcon from 'mdi-react/DotsVerticalIcon';
|
|||
import {ipcRenderer, remote, shell} from 'electron';
|
||||
|
||||
import Utils from '../../utils/util';
|
||||
import urlUtils from '../../utils/url';
|
||||
import contextmenu from '../js/contextMenu';
|
||||
|
||||
import restoreButton from '../../assets/titlebar/chrome-restore.svg';
|
||||
|
@ -75,16 +75,16 @@ export default class MainPage extends React.Component {
|
|||
|
||||
parseDeeplinkURL(deeplink, teams = this.props.teams) {
|
||||
if (deeplink && Array.isArray(teams) && teams.length) {
|
||||
const deeplinkURL = url.parse(deeplink);
|
||||
const deeplinkURL = urlUtils.parseURL(deeplink);
|
||||
let parsedDeeplink = null;
|
||||
teams.forEach((team, index) => {
|
||||
const teamURL = url.parse(team.url);
|
||||
const teamURL = urlUtils.parseURL(team.url);
|
||||
if (deeplinkURL.host === teamURL.host) {
|
||||
parsedDeeplink = {
|
||||
teamURL,
|
||||
teamIndex: index,
|
||||
originalURL: deeplinkURL,
|
||||
url: `${teamURL.protocol}//${teamURL.host}${deeplinkURL.pathname || '/'}`,
|
||||
url: `${teamURL.origin}${deeplinkURL.pathname || '/'}`,
|
||||
path: deeplinkURL.pathname || '/',
|
||||
};
|
||||
}
|
||||
|
@ -389,18 +389,18 @@ export default class MainPage extends React.Component {
|
|||
|
||||
switchToTabForCertificateRequest = (origin) => {
|
||||
// origin is server name + port, if the port doesn't match the protocol, it is kept by URL
|
||||
const originURL = new URL(`http://${origin.split(':')[0]}`);
|
||||
const secureOriginURL = new URL(`https://${origin.split(':')[0]}`);
|
||||
const originURL = urlUtils.parseURL(`http://${origin.split(':')[0]}`);
|
||||
const secureOriginURL = urlUtils.parseURL(`https://${origin.split(':')[0]}`);
|
||||
|
||||
const key = this.props.teams.findIndex((team) => {
|
||||
const parsedURL = new URL(team.url);
|
||||
const parsedURL = urlUtils.parseURL(team.url);
|
||||
return (parsedURL.origin === originURL.origin) || (parsedURL.origin === secureOriginURL.origin);
|
||||
});
|
||||
this.handleSelect(key);
|
||||
};
|
||||
|
||||
handleInterTeamLink = (linkUrl) => {
|
||||
const selectedTeam = Utils.getServer(linkUrl, this.props.teams);
|
||||
const selectedTeam = urlUtils.getServer(linkUrl, this.props.teams);
|
||||
if (!selectedTeam) {
|
||||
return;
|
||||
}
|
||||
|
@ -649,7 +649,7 @@ export default class MainPage extends React.Component {
|
|||
showExtraBar = () => {
|
||||
const ref = this.refs[`mattermostView${this.state.key}`];
|
||||
if (typeof ref !== 'undefined') {
|
||||
return !Utils.isTeamUrl(this.props.teams[this.state.key].url, ref.getSrc());
|
||||
return !urlUtils.isTeamUrl(this.props.teams[this.state.key].url, ref.getSrc());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -814,8 +814,8 @@ export default class MainPage extends React.Component {
|
|||
let authInfo = null;
|
||||
if (this.state.loginQueue.length !== 0) {
|
||||
request = this.state.loginQueue[0].request;
|
||||
const tmpURL = url.parse(this.state.loginQueue[0].request.url);
|
||||
authServerURL = `${tmpURL.protocol}//${tmpURL.host}`;
|
||||
const tmpURL = urlUtils.parseURL(this.state.loginQueue[0].request.url);
|
||||
authServerURL = tmpURL.origin;
|
||||
authInfo = this.state.loginQueue[0].authInfo;
|
||||
}
|
||||
const modal = (
|
||||
|
|
|
@ -5,14 +5,13 @@
|
|||
// This file uses setState().
|
||||
/* eslint-disable react/no-set-state */
|
||||
|
||||
import url from 'url';
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {ipcRenderer, remote, shell} from 'electron';
|
||||
|
||||
import contextMenu from '../js/contextMenu';
|
||||
import Utils from '../../utils/util';
|
||||
import urlUtils from '../../utils/url';
|
||||
import {protocols} from '../../../electron-builder.json';
|
||||
const scheme = protocols[0].schemes[0];
|
||||
|
||||
|
@ -78,38 +77,38 @@ export default class MattermostView extends React.Component {
|
|||
|
||||
// Open link in browserWindow. for example, attached files.
|
||||
webview.addEventListener('new-window', (e) => {
|
||||
if (!Utils.isValidURI(e.url)) {
|
||||
if (!urlUtils.isValidURI(e.url)) {
|
||||
return;
|
||||
}
|
||||
const currentURL = url.parse(webview.getURL());
|
||||
const destURL = url.parse(e.url);
|
||||
const currentURL = urlUtils.parseURL(webview.getURL());
|
||||
const destURL = urlUtils.parseURL(e.url);
|
||||
if (destURL.protocol !== 'http:' && destURL.protocol !== 'https:' && destURL.protocol !== `${scheme}:`) {
|
||||
ipcRenderer.send('confirm-protocol', destURL.protocol, e.url);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Utils.isInternalURL(destURL, currentURL, this.state.basename)) {
|
||||
if (urlUtils.isInternalURL(destURL, currentURL, this.state.basename)) {
|
||||
if (destURL.path.match(/^\/api\/v[3-4]\/public\/files\//)) {
|
||||
ipcRenderer.send('download-url', e.url);
|
||||
} else if (destURL.path.match(/^\/help\//)) {
|
||||
// continue to open special case internal urls in default browser
|
||||
shell.openExternal(e.url);
|
||||
} else if (Utils.isTeamUrl(this.props.src, e.url, true) || Utils.isAdminUrl(this.props.src, e.url)) {
|
||||
} else if (urlUtils.isTeamUrl(this.props.src, e.url, true) || urlUtils.isAdminUrl(this.props.src, e.url)) {
|
||||
e.preventDefault();
|
||||
this.webviewRef.current.loadURL(e.url);
|
||||
} else if (Utils.isPluginUrl(this.props.src, e.url)) {
|
||||
} else if (urlUtils.isPluginUrl(this.props.src, e.url)) {
|
||||
// New window should disable nodeIntegration.
|
||||
window.open(e.url, remote.app.name, 'nodeIntegration=no, contextIsolation=yes, show=yes');
|
||||
} else if (Utils.isManagedResource(this.props.src, e.url)) {
|
||||
} else if (urlUtils.isManagedResource(this.props.src, e.url)) {
|
||||
e.preventDefault();
|
||||
} else {
|
||||
e.preventDefault();
|
||||
shell.openExternal(e.url);
|
||||
}
|
||||
} else {
|
||||
const parsedURL = Utils.parseURL(e.url);
|
||||
const serverURL = Utils.getServer(parsedURL, this.props.teams);
|
||||
if (serverURL !== null && Utils.isTeamUrl(serverURL.url, parsedURL)) {
|
||||
const parsedURL = urlUtils.parseURL(e.url);
|
||||
const serverURL = urlUtils.getServer(parsedURL, this.props.teams);
|
||||
if (serverURL !== null && urlUtils.isTeamUrl(serverURL.url, parsedURL)) {
|
||||
this.props.handleInterTeamLink(parsedURL);
|
||||
} else {
|
||||
// if the link is external, use default os' application.
|
||||
|
|
|
@ -6,7 +6,7 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import {Modal, Button, FormGroup, FormControl, ControlLabel, HelpBlock} from 'react-bootstrap';
|
||||
|
||||
import Utils from '../../utils/util';
|
||||
import urlUtils from '../../utils/url';
|
||||
|
||||
export default class NewTeamModal extends React.Component {
|
||||
static defaultProps = {
|
||||
|
@ -62,7 +62,7 @@ export default class NewTeamModal extends React.Component {
|
|||
if (!(/^https?:\/\/.*/).test(this.state.teamUrl.trim())) {
|
||||
return 'URL should start with http:// or https://.';
|
||||
}
|
||||
if (!Utils.isValidURL(this.state.teamUrl.trim())) {
|
||||
if (!urlUtils.isValidURL(this.state.teamUrl.trim())) {
|
||||
return 'URL is not formatted correctly.';
|
||||
}
|
||||
return null;
|
||||
|
|
|
@ -5,6 +5,8 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import {ipcRenderer} from 'electron';
|
||||
|
||||
import urlUtils from '../../utils/url';
|
||||
|
||||
// this component is used to override some checks from the UI, leaving only to trust the protocol in case it wasn't http/s
|
||||
// it is used the same as an `a` JSX tag
|
||||
export default function ExternalLink(props) {
|
||||
|
@ -12,7 +14,7 @@ export default function ExternalLink(props) {
|
|||
e.preventDefault();
|
||||
let parseUrl;
|
||||
try {
|
||||
parseUrl = new URL(props.href);
|
||||
parseUrl = urlUtils.parseURL(props.href);
|
||||
ipcRenderer.send('confirm-protocol', parseUrl.protocol, props.href);
|
||||
} catch (err) {
|
||||
console.error(`invalid url ${props.href} supplied to externallink: ${err}`);
|
||||
|
@ -29,4 +31,4 @@ export default function ExternalLink(props) {
|
|||
|
||||
ExternalLink.propTypes = {
|
||||
href: PropTypes.string.isRequired,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -8,12 +8,12 @@ window.eval = global.eval = () => { // eslint-disable-line no-multi-assign, no-e
|
|||
throw new Error('Sorry, Mattermost does not support window.eval() for security reasons.');
|
||||
};
|
||||
|
||||
import url from 'url';
|
||||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import {remote, ipcRenderer} from 'electron';
|
||||
|
||||
import urlUtils from '../utils/url';
|
||||
|
||||
import Config from '../common/config';
|
||||
|
||||
import EnhancedNotification from './js/notification';
|
||||
|
@ -32,11 +32,12 @@ if (teams.length === 0) {
|
|||
remote.getCurrentWindow().loadFile('browser/settings.html');
|
||||
}
|
||||
|
||||
const parsedURL = url.parse(window.location.href, true);
|
||||
const initialIndex = parsedURL.query.index ? parseInt(parsedURL.query.index, 10) : getInitialIndex();
|
||||
const parsedURLSearchParams = urlUtils.parseURL(window.location.href).searchParams;
|
||||
const parsedURLHasIndex = parsedURLSearchParams.has('index');
|
||||
const initialIndex = parsedURLHasIndex ? parseInt(parsedURLSearchParams.get('index'), 10) : getInitialIndex();
|
||||
|
||||
let deeplinkingUrl = null;
|
||||
if (!parsedURL.query.index || parsedURL.query.index === null) {
|
||||
if (!parsedURLHasIndex) {
|
||||
deeplinkingUrl = remote.getCurrentWindow().deeplinkingUrl;
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
import {ipcRenderer, remote} from 'electron';
|
||||
import electronContextMenu from 'electron-context-menu';
|
||||
|
||||
import urlUtils from '../../utils/url';
|
||||
|
||||
function getSuggestionsMenus(webcontents, suggestions) {
|
||||
if (suggestions.length === 0) {
|
||||
return [{
|
||||
|
@ -57,7 +59,7 @@ export default {
|
|||
const isInternalLink = p.linkURL.endsWith('#') && p.linkURL.slice(0, -1) === p.pageURL;
|
||||
let isInternalSrc;
|
||||
try {
|
||||
const srcurl = new URL(p.srcURL);
|
||||
const srcurl = urlUtils.parseURL(p.srcURL);
|
||||
isInternalSrc = srcurl.protocol === 'file:';
|
||||
console.log(`srcrurl protocol: ${srcurl.protocol}`);
|
||||
} catch (err) {
|
||||
|
|
|
@ -1,17 +1,18 @@
|
|||
// Copyright (c) 2015-2016 Yuya Ochiai
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import url from 'url';
|
||||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import propTypes from 'prop-types';
|
||||
import {ipcRenderer, remote} from 'electron';
|
||||
|
||||
import urlUtils from '../utils/url';
|
||||
|
||||
import UpdaterPage from './components/UpdaterPage.jsx';
|
||||
|
||||
const thisURL = url.parse(location.href, true);
|
||||
const notifyOnly = thisURL.query.notifyOnly === 'true';
|
||||
const thisURL = urlUtils.parseURL(location.href);
|
||||
const notifyOnly = thisURL.searchParams.get('notifyOnly') === 'true';
|
||||
|
||||
class UpdaterPageContainer extends React.Component {
|
||||
constructor(props) {
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
|
@ -13,4 +12,4 @@ export const BASIC_AUTH_PERMISSION = 'canBasicAuth';
|
|||
// Permission descriptions
|
||||
export const PERMISSION_DESCRIPTION = {
|
||||
[BASIC_AUTH_PERMISSION]: 'Web Authentication',
|
||||
};
|
||||
};
|
||||
|
|
59
src/main.js
59
src/main.js
|
@ -31,8 +31,14 @@ import initCookieManager from './main/cookieManager';
|
|||
import SpellChecker from './main/SpellChecker';
|
||||
import UserActivityMonitor from './main/UserActivityMonitor';
|
||||
import Utils from './utils/util';
|
||||
import urlUtils from './utils/url';
|
||||
import parseArgs from './main/ParseArgs';
|
||||
import {REQUEST_PERMISSION_CHANNEL, GRANT_PERMISSION_CHANNEL, DENY_PERMISSION_CHANNEL, BASIC_AUTH_PERMISSION} from './common/permissions';
|
||||
import {
|
||||
REQUEST_PERMISSION_CHANNEL,
|
||||
GRANT_PERMISSION_CHANNEL,
|
||||
DENY_PERMISSION_CHANNEL,
|
||||
BASIC_AUTH_PERMISSION
|
||||
} from './common/permissions';
|
||||
|
||||
// pull out required electron components like this
|
||||
// as not all components can be referenced before the app is ready
|
||||
|
@ -357,7 +363,7 @@ function handleSelectedCertificate(event, server, cert) {
|
|||
}
|
||||
|
||||
function handleAppCertificateError(event, webContents, url, error, certificate, callback) {
|
||||
const parsedURL = new URL(url);
|
||||
const parsedURL = urlUtils.parseURL(url);
|
||||
if (!parsedURL) {
|
||||
return;
|
||||
}
|
||||
|
@ -425,8 +431,8 @@ function handleAppGPUProcessCrashed(event, killed) {
|
|||
|
||||
function handleAppLogin(event, webContents, request, authInfo, callback) {
|
||||
event.preventDefault();
|
||||
const parsedURL = new URL(request.url);
|
||||
const server = Utils.getServer(parsedURL, config.teams);
|
||||
const parsedURL = urlUtils.parseURL(request.url);
|
||||
const server = urlUtils.getServer(parsedURL, config.teams);
|
||||
|
||||
loginCallbackMap.set(request.url, typeof callback === 'undefined' ? null : callback); // if callback is undefined set it to null instead so we know we have set it up with no value
|
||||
if (isTrustedURL(request.url) || isCustomLoginURL(parsedURL, server) || trustedOriginsStore.checkPermission(request.url, BASIC_AUTH_PERMISSION)) {
|
||||
|
@ -461,6 +467,7 @@ function handleAppWillFinishLaunching() {
|
|||
setTimeout(openDeepLink, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
openDeepLink();
|
||||
}
|
||||
});
|
||||
|
@ -479,10 +486,10 @@ function handleAppWebContentsCreated(dc, contents) {
|
|||
|
||||
contents.on('will-navigate', (event, url) => {
|
||||
const contentID = event.sender.id;
|
||||
const parsedURL = Utils.parseURL(url);
|
||||
const server = Utils.getServer(parsedURL, config.teams);
|
||||
const parsedURL = urlUtils.parseURL(url);
|
||||
const server = urlUtils.getServer(parsedURL, config.teams);
|
||||
|
||||
if ((server !== null && (Utils.isTeamUrl(server.url, parsedURL) || Utils.isAdminUrl(server.url, parsedURL))) ||
|
||||
if ((server !== null && (urlUtils.isTeamUrl(server.url, parsedURL) || urlUtils.isAdminUrl(server.url, parsedURL))) ||
|
||||
isTrustedPopupWindow(event.sender)) {
|
||||
return;
|
||||
}
|
||||
|
@ -508,8 +515,8 @@ function handleAppWebContentsCreated(dc, contents) {
|
|||
// - indicate custom login is NOT in progress
|
||||
contents.on('did-start-navigation', (event, url) => {
|
||||
const contentID = event.sender.id;
|
||||
const parsedURL = Utils.parseURL(url);
|
||||
const server = Utils.getServer(parsedURL, config.teams);
|
||||
const parsedURL = urlUtils.parseURL(url);
|
||||
const server = urlUtils.getServer(parsedURL, config.teams);
|
||||
|
||||
if (!isTrustedURL(parsedURL)) {
|
||||
return;
|
||||
|
@ -525,18 +532,18 @@ function handleAppWebContentsCreated(dc, contents) {
|
|||
contents.on('new-window', (event, url) => {
|
||||
event.preventDefault();
|
||||
|
||||
const parsedURL = Utils.parseURL(url);
|
||||
const server = Utils.getServer(parsedURL, config.teams);
|
||||
const parsedURL = urlUtils.parseURL(url);
|
||||
const server = urlUtils.getServer(parsedURL, config.teams);
|
||||
|
||||
if (!server) {
|
||||
log.info(`Untrusted popup window blocked: ${url}`);
|
||||
return;
|
||||
}
|
||||
if (Utils.isTeamUrl(server.url, parsedURL, true)) {
|
||||
if (urlUtils.isTeamUrl(server.url, parsedURL, true)) {
|
||||
log.info(`${url} is a known team, preventing to open a new window`);
|
||||
return;
|
||||
}
|
||||
if (Utils.isAdminUrl(server.url, parsedURL)) {
|
||||
if (urlUtils.isAdminUrl(server.url, parsedURL)) {
|
||||
log.info(`${url} is an admin console page, preventing to open a new window`);
|
||||
return;
|
||||
}
|
||||
|
@ -544,7 +551,7 @@ function handleAppWebContentsCreated(dc, contents) {
|
|||
log.info(`Popup window already open at provided url: ${url}`);
|
||||
return;
|
||||
}
|
||||
if (Utils.isPluginUrl(server.url, parsedURL) || Utils.isManagedResource(server.url, parsedURL)) {
|
||||
if (urlUtils.isPluginUrl(server.url, parsedURL) || urlUtils.isManagedResource(server.url, parsedURL)) {
|
||||
if (!popupWindow || popupWindow.closed) {
|
||||
popupWindow = new BrowserWindow({
|
||||
backgroundColor: '#fff', // prevents blurry text: https://electronjs.org/docs/faq#the-font-looks-blurry-what-is-this-and-what-can-i-do
|
||||
|
@ -564,7 +571,7 @@ function handleAppWebContentsCreated(dc, contents) {
|
|||
});
|
||||
}
|
||||
|
||||
if (Utils.isManagedResource(server.url, parsedURL)) {
|
||||
if (urlUtils.isManagedResource(server.url, parsedURL)) {
|
||||
popupWindow.loadURL(url);
|
||||
} else {
|
||||
// currently changing the userAgent for popup windows to allow plugins to go through google's oAuth
|
||||
|
@ -621,8 +628,8 @@ function handleAppWebContentsCreated(dc, contents) {
|
|||
mainWindow.webContents.send('zoom-reset');
|
||||
break;
|
||||
|
||||
// Manually handle undo/redo keyboard shortcuts
|
||||
// - temporary fix for https://mattermost.atlassian.net/browse/MM-19198
|
||||
// Manually handle undo/redo keyboard shortcuts
|
||||
// - temporary fix for https://mattermost.atlassian.net/browse/MM-19198
|
||||
case 'z':
|
||||
if (input.shift) {
|
||||
mainWindow.webContents.send('redo');
|
||||
|
@ -631,7 +638,7 @@ function handleAppWebContentsCreated(dc, contents) {
|
|||
}
|
||||
break;
|
||||
|
||||
// Manually handle copy/cut/paste keyboard shortcuts
|
||||
// Manually handle copy/cut/paste keyboard shortcuts
|
||||
case 'c':
|
||||
mainWindow.webContents.send('copy');
|
||||
break;
|
||||
|
@ -791,7 +798,7 @@ function initializeAfterAppReady() {
|
|||
mainWindow.webContents.send('download-complete', {
|
||||
fileName: filename,
|
||||
path: item.savePath,
|
||||
serverInfo: Utils.getServer(webContents.getURL(), config.teams),
|
||||
serverInfo: urlUtils.getServer(webContents.getURL(), config.teams),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -1026,11 +1033,11 @@ function handleMainWindowWebContentsCrashed() {
|
|||
//
|
||||
|
||||
function isTrustedURL(url) {
|
||||
const parsedURL = Utils.parseURL(url);
|
||||
const parsedURL = urlUtils.parseURL(url);
|
||||
if (!parsedURL) {
|
||||
return false;
|
||||
}
|
||||
return Utils.getServer(parsedURL, config.teams) !== null;
|
||||
return urlUtils.getServer(parsedURL, config.teams) !== null;
|
||||
}
|
||||
|
||||
function isTrustedPopupWindow(webContents) {
|
||||
|
@ -1045,7 +1052,7 @@ function isTrustedPopupWindow(webContents) {
|
|||
|
||||
function isCustomLoginURL(url, server) {
|
||||
const subpath = (server === null || typeof server === 'undefined') ? '' : server.url.pathname;
|
||||
const parsedURL = Utils.parseURL(url);
|
||||
const parsedURL = urlUtils.parseURL(url);
|
||||
if (!parsedURL) {
|
||||
return false;
|
||||
}
|
||||
|
@ -1080,8 +1087,7 @@ function getTrayImages() {
|
|||
unread: nativeImage.createFromPath(path.resolve(assetsDir, 'windows/tray_unread.ico')),
|
||||
mention: nativeImage.createFromPath(path.resolve(assetsDir, 'windows/tray_mention.ico')),
|
||||
};
|
||||
case 'darwin':
|
||||
{
|
||||
case 'darwin': {
|
||||
const icons = {
|
||||
light: {
|
||||
normal: nativeImage.createFromPath(path.resolve(assetsDir, 'osx/MenuIcon.png')),
|
||||
|
@ -1097,8 +1103,7 @@ function getTrayImages() {
|
|||
switchMenuIconImages(icons, nativeTheme.shouldUseDarkColors);
|
||||
return icons;
|
||||
}
|
||||
case 'linux':
|
||||
{
|
||||
case 'linux': {
|
||||
const theme = config.trayIconTheme;
|
||||
try {
|
||||
return {
|
||||
|
@ -1136,7 +1141,7 @@ function getDeeplinkingURL(args) {
|
|||
if (Array.isArray(args) && args.length) {
|
||||
// deeplink urls should always be the last argument, but may not be the first (i.e. Windows with the app already running)
|
||||
const url = args[args.length - 1];
|
||||
if (url && scheme && url.startsWith(scheme) && Utils.isValidURI(url)) {
|
||||
if (url && scheme && url.startsWith(scheme) && urlUtils.isValidURI(url)) {
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
// See LICENSE.txt for license information.
|
||||
import Joi from '@hapi/joi';
|
||||
|
||||
import Utils from '../utils/util';
|
||||
import urlUtils from '../utils/url';
|
||||
|
||||
const defaultOptions = {
|
||||
stripUnknown: true,
|
||||
|
@ -138,7 +138,7 @@ export function validateV1ConfigData(data) {
|
|||
});
|
||||
|
||||
// next filter out urls that are still invalid so all is not lost
|
||||
teams = teams.filter(({url}) => Utils.isValidURL(url));
|
||||
teams = teams.filter(({url}) => urlUtils.isValidURL(url));
|
||||
|
||||
// replace original teams
|
||||
data.teams = teams;
|
||||
|
@ -158,7 +158,7 @@ export function validateV2ConfigData(data) {
|
|||
});
|
||||
|
||||
// next filter out urls that are still invalid so all is not lost
|
||||
teams = teams.filter(({url}) => Utils.isValidURL(url));
|
||||
teams = teams.filter(({url}) => urlUtils.isValidURL(url));
|
||||
|
||||
// replace original teams
|
||||
data.teams = teams;
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
|
||||
import fs from 'fs';
|
||||
|
||||
import urlUtils from '../utils/url';
|
||||
|
||||
import * as Validator from './Validator';
|
||||
|
||||
function comparableCertificate(certificate) {
|
||||
|
@ -24,11 +26,6 @@ function areEqual(certificate0, certificate1) {
|
|||
return true;
|
||||
}
|
||||
|
||||
function getHost(targetURL) {
|
||||
const parsedURL = new URL(targetURL);
|
||||
return parsedURL.origin;
|
||||
}
|
||||
|
||||
function CertificateStore(storeFile) {
|
||||
this.storeFile = storeFile;
|
||||
let storeStr;
|
||||
|
@ -49,15 +46,15 @@ CertificateStore.prototype.save = function save() {
|
|||
};
|
||||
|
||||
CertificateStore.prototype.add = function add(targetURL, certificate) {
|
||||
this.data[getHost(targetURL)] = comparableCertificate(certificate);
|
||||
this.data[urlUtils.getHost(targetURL)] = comparableCertificate(certificate);
|
||||
};
|
||||
|
||||
CertificateStore.prototype.isExisting = function isExisting(targetURL) {
|
||||
return this.data.hasOwnProperty(getHost(targetURL));
|
||||
return this.data.hasOwnProperty(urlUtils.getHost(targetURL));
|
||||
};
|
||||
|
||||
CertificateStore.prototype.isTrusted = function isTrusted(targetURL, certificate) {
|
||||
const host = getHost(targetURL);
|
||||
const host = urlUtils.getHost(targetURL);
|
||||
if (!this.isExisting(targetURL)) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import fs from 'fs';
|
|||
|
||||
import log from 'electron-log';
|
||||
|
||||
import Utils from '../utils/util.js';
|
||||
import urlUtils from '../utils/url';
|
||||
|
||||
import * as Validator from './Validator';
|
||||
|
||||
|
@ -56,12 +56,12 @@ export default class TrustedOriginsStore {
|
|||
if (!validPermissions) {
|
||||
throw new Error(`Invalid permissions set for trusting ${targetURL}`);
|
||||
}
|
||||
this.data.set(Utils.getHost(targetURL), validPermissions);
|
||||
this.data.set(urlUtils.getHost(targetURL), validPermissions);
|
||||
};
|
||||
|
||||
// enables usage of `targetURL` for `permission`
|
||||
addPermission = (targetURL, permission) => {
|
||||
const origin = Utils.getHost(targetURL);
|
||||
const origin = urlUtils.getHost(targetURL);
|
||||
const currentPermissions = this.data.get(origin) || {};
|
||||
currentPermissions[permission] = true;
|
||||
this.set(origin, currentPermissions);
|
||||
|
@ -70,7 +70,7 @@ export default class TrustedOriginsStore {
|
|||
delete = (targetURL) => {
|
||||
let host;
|
||||
try {
|
||||
host = Utils.getHost(targetURL);
|
||||
host = urlUtils.getHost(targetURL);
|
||||
this.data.delete(host);
|
||||
} catch {
|
||||
return false;
|
||||
|
@ -79,7 +79,7 @@ export default class TrustedOriginsStore {
|
|||
}
|
||||
|
||||
isExisting = (targetURL) => {
|
||||
return (typeof this.data.get(Utils.getHost(targetURL)) !== 'undefined');
|
||||
return (typeof this.data.get(urlUtils.getHost(targetURL)) !== 'undefined');
|
||||
};
|
||||
|
||||
// if user hasn't set his preferences, it will return null (falsy)
|
||||
|
@ -90,7 +90,7 @@ export default class TrustedOriginsStore {
|
|||
}
|
||||
let origin;
|
||||
try {
|
||||
origin = Utils.getHost(targetURL);
|
||||
origin = urlUtils.getHost(targetURL);
|
||||
} catch (e) {
|
||||
log.error(`invalid host to retrieve permissions: ${targetURL}: ${e}`);
|
||||
return null;
|
||||
|
|
190
src/utils/url.js
Normal file
190
src/utils/url.js
Normal file
|
@ -0,0 +1,190 @@
|
|||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {isHttpsUri, isHttpUri, isUri} from 'valid-url';
|
||||
|
||||
import buildConfig from '../common/config/buildConfig';
|
||||
|
||||
function getDomain(inputURL) {
|
||||
const parsedURL = parseURL(inputURL);
|
||||
return parsedURL.origin;
|
||||
}
|
||||
|
||||
function isValidURL(testURL) {
|
||||
return Boolean(isHttpUri(testURL) || isHttpsUri(testURL)) && parseURL(testURL) !== null;
|
||||
}
|
||||
|
||||
function isValidURI(testURL) {
|
||||
return Boolean(isUri(testURL));
|
||||
}
|
||||
|
||||
function parseURL(inputURL) {
|
||||
if (!inputURL) {
|
||||
return null;
|
||||
}
|
||||
if (inputURL instanceof URL) {
|
||||
return inputURL;
|
||||
}
|
||||
try {
|
||||
return new URL(inputURL);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function getHost(inputURL) {
|
||||
const parsedURL = parseURL(inputURL);
|
||||
if (parsedURL) {
|
||||
return parsedURL.origin;
|
||||
}
|
||||
throw new Error(`Couldn't parse url: ${inputURL}`);
|
||||
}
|
||||
|
||||
// isInternalURL determines if the target url is internal to the application.
|
||||
// - currentURL is the current url inside the webview
|
||||
// - basename is the global export from the Mattermost application defining the subpath, if any
|
||||
function isInternalURL(targetURL, currentURL, basename = '/') {
|
||||
if (targetURL.host !== currentURL.host) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(targetURL.pathname || '/').startsWith(basename)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function getServerInfo(serverUrl) {
|
||||
const parsedServer = parseURL(serverUrl);
|
||||
if (!parsedServer) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// does the server have a subpath?
|
||||
const pn = parsedServer.pathname.toLowerCase();
|
||||
const subpath = pn.endsWith('/') ? pn.toLowerCase() : `${pn}/`;
|
||||
return {origin: parsedServer.origin, subpath, url: parsedServer};
|
||||
}
|
||||
|
||||
function getManagedResources() {
|
||||
if (!buildConfig) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return buildConfig.managedResources || [];
|
||||
}
|
||||
|
||||
function isAdminUrl(serverUrl, inputUrl) {
|
||||
const parsedURL = parseURL(inputUrl);
|
||||
const server = getServerInfo(serverUrl);
|
||||
if (!parsedURL || !server || (!equalUrlsIgnoringSubpath(server, parsedURL))) {
|
||||
return null;
|
||||
}
|
||||
return (parsedURL.pathname.toLowerCase().startsWith(`${server.subpath}/admin_console/`) ||
|
||||
parsedURL.pathname.toLowerCase().startsWith('/admin_console/'));
|
||||
}
|
||||
|
||||
function isTeamUrl(serverUrl, inputUrl, withApi) {
|
||||
const parsedURL = parseURL(inputUrl);
|
||||
const server = getServerInfo(serverUrl);
|
||||
if (!parsedURL || !server || (!equalUrlsIgnoringSubpath(server, parsedURL))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// pre process nonTeamUrlPaths
|
||||
let nonTeamUrlPaths = [
|
||||
'plugins',
|
||||
'signup',
|
||||
'login',
|
||||
'admin',
|
||||
'channel',
|
||||
'post',
|
||||
'oauth',
|
||||
'admin_console',
|
||||
];
|
||||
const managedResources = getManagedResources();
|
||||
nonTeamUrlPaths = nonTeamUrlPaths.concat(managedResources);
|
||||
|
||||
if (withApi) {
|
||||
nonTeamUrlPaths.push('api');
|
||||
}
|
||||
return !(nonTeamUrlPaths.some((testPath) => (
|
||||
parsedURL.pathname.toLowerCase().startsWith(`${server.subpath}${testPath}/`) ||
|
||||
parsedURL.pathname.toLowerCase().startsWith(`/${testPath}/`))));
|
||||
}
|
||||
|
||||
function isPluginUrl(serverUrl, inputURL) {
|
||||
const server = getServerInfo(serverUrl);
|
||||
const parsedURL = parseURL(inputURL);
|
||||
if (!parsedURL || !server) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
equalUrlsIgnoringSubpath(server, parsedURL) &&
|
||||
(parsedURL.pathname.toLowerCase().startsWith(`${server.subpath}plugins/`) ||
|
||||
parsedURL.pathname.toLowerCase().startsWith('/plugins/')));
|
||||
}
|
||||
|
||||
function isManagedResource(serverUrl, inputURL) {
|
||||
const server = getServerInfo(serverUrl);
|
||||
const parsedURL = parseURL(inputURL);
|
||||
if (!parsedURL || !server) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const managedResources = getManagedResources();
|
||||
|
||||
return (
|
||||
equalUrlsIgnoringSubpath(server, parsedURL) && managedResources && managedResources.length &&
|
||||
managedResources.some((managedResource) => (parsedURL.pathname.toLowerCase().startsWith(`${server.subpath}${managedResource}/`) || parsedURL.pathname.toLowerCase().startsWith(`/${managedResource}/`))));
|
||||
}
|
||||
|
||||
function getServer(inputURL, teams) {
|
||||
const parsedURL = parseURL(inputURL);
|
||||
if (!parsedURL) {
|
||||
return null;
|
||||
}
|
||||
let parsedServerUrl;
|
||||
let secondOption = null;
|
||||
for (let i = 0; i < teams.length; i++) {
|
||||
parsedServerUrl = parseURL(teams[i].url);
|
||||
|
||||
// check server and subpath matches (without subpath pathname is \ so it always matches)
|
||||
if (equalUrlsWithSubpath(parsedServerUrl, parsedURL)) {
|
||||
return {name: teams[i].name, url: parsedServerUrl, index: i};
|
||||
}
|
||||
if (equalUrlsIgnoringSubpath(parsedServerUrl, parsedURL)) {
|
||||
// in case the user added something on the path that doesn't really belong to the server
|
||||
// there might be more than one that matches, but we can't differentiate, so last one
|
||||
// is as good as any other in case there is no better match (e.g.: two subpath servers with the same origin)
|
||||
// e.g.: https://community.mattermost.com/core
|
||||
secondOption = {name: teams[i].name, url: parsedServerUrl, index: i};
|
||||
}
|
||||
}
|
||||
return secondOption;
|
||||
}
|
||||
|
||||
// next two functions are defined to clarify intent
|
||||
function equalUrlsWithSubpath(url1, url2) {
|
||||
return url1.origin === url2.origin && url2.pathname.toLowerCase().startsWith(url1.pathname.toLowerCase());
|
||||
}
|
||||
|
||||
function equalUrlsIgnoringSubpath(url1, url2) {
|
||||
return url1.origin.toLowerCase() === url2.origin.toLowerCase();
|
||||
}
|
||||
|
||||
export default {
|
||||
getDomain,
|
||||
isValidURL,
|
||||
isValidURI,
|
||||
isInternalURL,
|
||||
parseURL,
|
||||
getServer,
|
||||
getServerInfo,
|
||||
isAdminUrl,
|
||||
isTeamUrl,
|
||||
isPluginUrl,
|
||||
isManagedResource,
|
||||
getHost,
|
||||
};
|
|
@ -1,173 +1,9 @@
|
|||
// Copyright (c) 2015-2016 Yuya Ochiai
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import url from 'url';
|
||||
|
||||
import electron, {remote} from 'electron';
|
||||
import log from 'electron-log';
|
||||
import {isUri, isHttpUri, isHttpsUri} from 'valid-url';
|
||||
|
||||
import buildConfig from '../common/config/buildConfig';
|
||||
|
||||
function getDomain(inputURL) {
|
||||
const parsedURL = url.parse(inputURL);
|
||||
return `${parsedURL.protocol}//${parsedURL.host}`;
|
||||
}
|
||||
|
||||
function isValidURL(testURL) {
|
||||
return Boolean(isHttpUri(testURL) || isHttpsUri(testURL)) && parseURL(testURL) !== null;
|
||||
}
|
||||
|
||||
function isValidURI(testURL) {
|
||||
return Boolean(isUri(testURL));
|
||||
}
|
||||
|
||||
function parseURL(inputURL) {
|
||||
if (!inputURL) {
|
||||
return null;
|
||||
}
|
||||
if (inputURL instanceof URL) {
|
||||
return inputURL;
|
||||
}
|
||||
try {
|
||||
return new URL(inputURL);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function getHost(inputURL) {
|
||||
const parsedURL = parseURL(inputURL);
|
||||
if (parsedURL) {
|
||||
return parsedURL.origin;
|
||||
}
|
||||
throw new Error(`Couldn't parse url: ${inputURL}`);
|
||||
}
|
||||
|
||||
// isInternalURL determines if the target url is internal to the application.
|
||||
// - currentURL is the current url inside the webview
|
||||
// - basename is the global export from the Mattermost application defining the subpath, if any
|
||||
function isInternalURL(targetURL, currentURL, basename = '/') {
|
||||
if (targetURL.host !== currentURL.host) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(targetURL.pathname || '/').startsWith(basename)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function getServerInfo(serverUrl) {
|
||||
const parsedServer = parseURL(serverUrl);
|
||||
if (!parsedServer) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// does the server have a subpath?
|
||||
const pn = parsedServer.pathname.toLowerCase();
|
||||
const subpath = pn.endsWith('/') ? pn.toLowerCase() : `${pn}/`;
|
||||
return {origin: parsedServer.origin, subpath, url: parsedServer};
|
||||
}
|
||||
|
||||
function getManagedResources() {
|
||||
if (!buildConfig) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return buildConfig.managedResources || [];
|
||||
}
|
||||
|
||||
function isAdminUrl(serverUrl, inputUrl) {
|
||||
const parsedURL = parseURL(inputUrl);
|
||||
const server = getServerInfo(serverUrl);
|
||||
if (!parsedURL || !server || (!equalUrlsIgnoringSubpath(server, parsedURL))) {
|
||||
return null;
|
||||
}
|
||||
return (parsedURL.pathname.toLowerCase().startsWith(`${server.subpath}/admin_console/`) ||
|
||||
parsedURL.pathname.toLowerCase().startsWith('/admin_console/'));
|
||||
}
|
||||
|
||||
function isTeamUrl(serverUrl, inputUrl, withApi) {
|
||||
const parsedURL = parseURL(inputUrl);
|
||||
const server = getServerInfo(serverUrl);
|
||||
if (!parsedURL || !server || (!equalUrlsIgnoringSubpath(server, parsedURL))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// pre process nonTeamUrlPaths
|
||||
let nonTeamUrlPaths = [
|
||||
'plugins',
|
||||
'signup',
|
||||
'login',
|
||||
'admin',
|
||||
'channel',
|
||||
'post',
|
||||
'oauth',
|
||||
'admin_console',
|
||||
];
|
||||
const managedResources = getManagedResources();
|
||||
nonTeamUrlPaths = nonTeamUrlPaths.concat(managedResources);
|
||||
|
||||
if (withApi) {
|
||||
nonTeamUrlPaths.push('api');
|
||||
}
|
||||
return !(nonTeamUrlPaths.some((testPath) => (
|
||||
parsedURL.pathname.toLowerCase().startsWith(`${server.subpath}${testPath}/`) ||
|
||||
parsedURL.pathname.toLowerCase().startsWith(`/${testPath}/`))));
|
||||
}
|
||||
|
||||
function isPluginUrl(serverUrl, inputURL) {
|
||||
const server = getServerInfo(serverUrl);
|
||||
const parsedURL = parseURL(inputURL);
|
||||
if (!parsedURL || !server) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
equalUrlsIgnoringSubpath(server, parsedURL) &&
|
||||
(parsedURL.pathname.toLowerCase().startsWith(`${server.subpath}plugins/`) ||
|
||||
parsedURL.pathname.toLowerCase().startsWith('/plugins/')));
|
||||
}
|
||||
|
||||
function isManagedResource(serverUrl, inputURL) {
|
||||
const server = getServerInfo(serverUrl);
|
||||
const parsedURL = parseURL(inputURL);
|
||||
if (!parsedURL || !server) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const managedResources = getManagedResources();
|
||||
|
||||
return (
|
||||
equalUrlsIgnoringSubpath(server, parsedURL) && managedResources && managedResources.length &&
|
||||
managedResources.some((managedResource) => (parsedURL.pathname.toLowerCase().startsWith(`${server.subpath}${managedResource}/`) || parsedURL.pathname.toLowerCase().startsWith(`/${managedResource}/`))));
|
||||
}
|
||||
|
||||
function getServer(inputURL, teams) {
|
||||
const parsedURL = parseURL(inputURL);
|
||||
if (!parsedURL) {
|
||||
return null;
|
||||
}
|
||||
let parsedServerUrl;
|
||||
let secondOption = null;
|
||||
for (let i = 0; i < teams.length; i++) {
|
||||
parsedServerUrl = parseURL(teams[i].url);
|
||||
|
||||
// check server and subpath matches (without subpath pathname is \ so it always matches)
|
||||
if (equalUrlsWithSubpath(parsedServerUrl, parsedURL)) {
|
||||
return {name: teams[i].name, url: parsedServerUrl, index: i};
|
||||
}
|
||||
if (equalUrlsIgnoringSubpath(parsedServerUrl, parsedURL)) {
|
||||
// in case the user added something on the path that doesn't really belong to the server
|
||||
// there might be more than one that matches, but we can't differentiate, so last one
|
||||
// is as good as any other in case there is no better match (e.g.: two subpath servers with the same origin)
|
||||
// e.g.: https://community.mattermost.com/core
|
||||
secondOption = {name: teams[i].name, url: parsedServerUrl, index: i};
|
||||
}
|
||||
}
|
||||
return secondOption;
|
||||
}
|
||||
|
||||
function getDisplayBoundaries() {
|
||||
const {screen} = electron;
|
||||
|
@ -186,15 +22,6 @@ function getDisplayBoundaries() {
|
|||
});
|
||||
}
|
||||
|
||||
// next two functions are defined to clarify intent
|
||||
function equalUrlsWithSubpath(url1, url2) {
|
||||
return url1.origin === url2.origin && url2.pathname.toLowerCase().startsWith(url1.pathname.toLowerCase());
|
||||
}
|
||||
|
||||
function equalUrlsIgnoringSubpath(url1, url2) {
|
||||
return url1.origin.toLowerCase() === url2.origin.toLowerCase();
|
||||
}
|
||||
|
||||
const dispatchNotification = async (title, body, silent, data, handleClick) => {
|
||||
let permission;
|
||||
const appIconURL = `file:///${remote.app.getAppPath()}/assets/appicon_48.png`;
|
||||
|
@ -228,18 +55,6 @@ const dispatchNotification = async (title, body, silent, data, handleClick) => {
|
|||
};
|
||||
|
||||
export default {
|
||||
getDomain,
|
||||
isValidURL,
|
||||
isValidURI,
|
||||
isInternalURL,
|
||||
parseURL,
|
||||
getServer,
|
||||
getServerInfo,
|
||||
isAdminUrl,
|
||||
isTeamUrl,
|
||||
isPluginUrl,
|
||||
isManagedResource,
|
||||
getDisplayBoundaries,
|
||||
dispatchNotification,
|
||||
getHost,
|
||||
};
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
// Copyright (c) 2015-2016 Yuya Ochiai
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
|
|
@ -2,106 +2,105 @@
|
|||
// See LICENSE.txt for license information.
|
||||
'use strict';
|
||||
|
||||
import url from 'url';
|
||||
import assert from 'assert';
|
||||
|
||||
import Utils from '../../../src/utils/util';
|
||||
import urlUtils from '../../../src/utils/url';
|
||||
|
||||
describe('Utils', () => {
|
||||
describe('url', () => {
|
||||
describe('isValidURL', () => {
|
||||
it('should be true for a valid web url', () => {
|
||||
const testURL = 'https://developers.mattermost.com/';
|
||||
assert.equal(Utils.isValidURL(testURL), true);
|
||||
assert.equal(urlUtils.isValidURL(testURL), true);
|
||||
});
|
||||
it('should be true for a valid, non-https web url', () => {
|
||||
const testURL = 'http://developers.mattermost.com/';
|
||||
assert.equal(Utils.isValidURL(testURL), true);
|
||||
assert.equal(urlUtils.isValidURL(testURL), true);
|
||||
});
|
||||
it('should be true for an invalid, self-defined, top-level domain', () => {
|
||||
const testURL = 'https://www.example.x';
|
||||
assert.equal(Utils.isValidURL(testURL), true);
|
||||
assert.equal(urlUtils.isValidURL(testURL), true);
|
||||
});
|
||||
it('should be true for a file download url', () => {
|
||||
const testURL = 'https://community.mattermost.com/api/v4/files/ka3xbfmb3ffnmgdmww8otkidfw?download=1';
|
||||
assert.equal(Utils.isValidURL(testURL), true);
|
||||
assert.equal(urlUtils.isValidURL(testURL), true);
|
||||
});
|
||||
it('should be true for a permalink url', () => {
|
||||
const testURL = 'https://community.mattermost.com/test-channel/pl/pdqowkij47rmbyk78m5hwc7r6r';
|
||||
assert.equal(Utils.isValidURL(testURL), true);
|
||||
assert.equal(urlUtils.isValidURL(testURL), true);
|
||||
});
|
||||
it('should be true for a valid, internal domain', () => {
|
||||
const testURL = 'https://mattermost.company-internal';
|
||||
assert.equal(Utils.isValidURL(testURL), true);
|
||||
assert.equal(urlUtils.isValidURL(testURL), true);
|
||||
});
|
||||
it('should be true for a second, valid internal domain', () => {
|
||||
const testURL = 'https://serverXY/mattermost';
|
||||
assert.equal(Utils.isValidURL(testURL), true);
|
||||
assert.equal(urlUtils.isValidURL(testURL), true);
|
||||
});
|
||||
it('should be true for a valid, non-https internal domain', () => {
|
||||
const testURL = 'http://mattermost.local';
|
||||
assert.equal(Utils.isValidURL(testURL), true);
|
||||
assert.equal(urlUtils.isValidURL(testURL), true);
|
||||
});
|
||||
it('should be true for a valid, non-https, ip address with port number', () => {
|
||||
const testURL = 'http://localhost:8065';
|
||||
assert.equal(Utils.isValidURL(testURL), true);
|
||||
assert.equal(urlUtils.isValidURL(testURL), true);
|
||||
});
|
||||
});
|
||||
describe('isValidURI', () => {
|
||||
it('should be true for a deeplink url', () => {
|
||||
const testURL = 'mattermost://community-release.mattermost.com/core/channels/developers';
|
||||
assert.equal(Utils.isValidURI(testURL), true);
|
||||
assert.equal(urlUtils.isValidURI(testURL), true);
|
||||
});
|
||||
it('should be false for a malicious url', () => {
|
||||
const testURL = String.raw`mattermost:///" --data-dir "\\deans-mbp\mattermost`;
|
||||
assert.equal(Utils.isValidURI(testURL), false);
|
||||
assert.equal(urlUtils.isValidURI(testURL), false);
|
||||
});
|
||||
});
|
||||
describe('isInternalURL', () => {
|
||||
it('should be false for different hosts', () => {
|
||||
const currentURL = url.parse('http://localhost/team/channel1');
|
||||
const targetURL = url.parse('http://example.com/team/channel2');
|
||||
const currentURL = new URL('http://localhost/team/channel1');
|
||||
const targetURL = new URL('http://example.com/team/channel2');
|
||||
const basename = '/';
|
||||
assert.equal(Utils.isInternalURL(targetURL, currentURL, basename), false);
|
||||
assert.equal(urlUtils.isInternalURL(targetURL, currentURL, basename), false);
|
||||
});
|
||||
|
||||
it('should be false for same hosts, non-matching basename', () => {
|
||||
const currentURL = url.parse('http://localhost/subpath/team/channel1');
|
||||
const targetURL = url.parse('http://localhost/team/channel2');
|
||||
const currentURL = new URL('http://localhost/subpath/team/channel1');
|
||||
const targetURL = new URL('http://localhost/team/channel2');
|
||||
const basename = '/subpath';
|
||||
assert.equal(Utils.isInternalURL(targetURL, currentURL, basename), false);
|
||||
assert.equal(urlUtils.isInternalURL(targetURL, currentURL, basename), false);
|
||||
});
|
||||
|
||||
it('should be true for same hosts, matching basename', () => {
|
||||
const currentURL = url.parse('http://localhost/subpath/team/channel1');
|
||||
const targetURL = url.parse('http://localhost/subpath/team/channel2');
|
||||
const currentURL = new URL('http://localhost/subpath/team/channel1');
|
||||
const targetURL = new URL('http://localhost/subpath/team/channel2');
|
||||
const basename = '/subpath';
|
||||
assert.equal(Utils.isInternalURL(targetURL, currentURL, basename), true);
|
||||
assert.equal(urlUtils.isInternalURL(targetURL, currentURL, basename), true);
|
||||
});
|
||||
|
||||
it('should be true for same hosts, default basename', () => {
|
||||
const currentURL = url.parse('http://localhost/team/channel1');
|
||||
const targetURL = url.parse('http://localhost/team/channel2');
|
||||
const currentURL = new URL('http://localhost/team/channel1');
|
||||
const targetURL = new URL('http://localhost/team/channel2');
|
||||
const basename = '/';
|
||||
assert.equal(Utils.isInternalURL(targetURL, currentURL, basename), true);
|
||||
assert.equal(urlUtils.isInternalURL(targetURL, currentURL, basename), true);
|
||||
});
|
||||
|
||||
it('should be true for same hosts, default basename, empty target path', () => {
|
||||
const currentURL = url.parse('http://localhost/team/channel1');
|
||||
const targetURL = url.parse('http://localhost/');
|
||||
const currentURL = new URL('http://localhost/team/channel1');
|
||||
const targetURL = new URL('http://localhost/');
|
||||
const basename = '/';
|
||||
assert.equal(Utils.isInternalURL(targetURL, currentURL, basename), true);
|
||||
assert.equal(urlUtils.isInternalURL(targetURL, currentURL, basename), true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getHost', () => {
|
||||
it('should return the origin of a well formed url', () => {
|
||||
const myurl = 'https://mattermost.com/download';
|
||||
assert.equal(Utils.getHost(myurl), 'https://mattermost.com');
|
||||
assert.equal(urlUtils.getHost(myurl), 'https://mattermost.com');
|
||||
});
|
||||
|
||||
it('shoud raise an error on malformed urls', () => {
|
||||
const myurl = 'http://example.com:-80/';
|
||||
assert.throws(() => Utils.getHost(myurl), Error);
|
||||
assert.throws(() => urlUtils.getHost(myurl), Error);
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in a new issue