diff --git a/apps/meteor/app/custom-oauth/server/custom_oauth_server.js b/apps/meteor/app/custom-oauth/server/custom_oauth_server.js index b9071709f2cce..34bd0b8efe285 100644 --- a/apps/meteor/app/custom-oauth/server/custom_oauth_server.js +++ b/apps/meteor/app/custom-oauth/server/custom_oauth_server.js @@ -2,6 +2,7 @@ import { LDAP } from '@rocket.chat/core-services'; import { Logger } from '@rocket.chat/logger'; import { Users } from '@rocket.chat/models'; import { serverFetch as fetch } from '@rocket.chat/server-fetch'; +import { isAbsoluteURL } from '@rocket.chat/tools'; import { Accounts } from 'meteor/accounts-base'; import { Match, check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; @@ -10,7 +11,6 @@ import { ServiceConfiguration } from 'meteor/service-configuration'; import _ from 'underscore'; import { normalizers, fromTemplate, renameInvalidProperties } from './transform_helpers'; -import { isURL } from '../../../lib/utils/isURL'; import { client } from '../../../server/database/utils'; import { callbacks } from '../../../server/lib/callbacks'; import { saveUserIdentity } from '../../lib/server/functions/saveUserIdentity'; @@ -93,11 +93,11 @@ export class CustomOAuth { this.identityTokenSentVia = this.tokenSentVia; } - if (!isURL(this.tokenPath)) { + if (!isAbsoluteURL(this.tokenPath)) { this.tokenPath = this.serverURL + this.tokenPath; } - if (!isURL(this.identityPath)) { + if (!isAbsoluteURL(this.identityPath)) { this.identityPath = this.serverURL + this.identityPath; } diff --git a/apps/meteor/app/lib/server/functions/sendMessage.ts b/apps/meteor/app/lib/server/functions/sendMessage.ts index 036004aad5a7c..8f713511562b9 100644 --- a/apps/meteor/app/lib/server/functions/sendMessage.ts +++ b/apps/meteor/app/lib/server/functions/sendMessage.ts @@ -2,11 +2,11 @@ import { AppEvents, Apps } from '@rocket.chat/apps'; import { Message } from '@rocket.chat/core-services'; import type { IMessage, IRoom } from '@rocket.chat/core-typings'; import { Messages } from '@rocket.chat/models'; +import { isAbsoluteURL } from '@rocket.chat/tools'; import { Match, check } from 'meteor/check'; import { parseUrlsInMessage } from './parseUrlsInMessage'; import { isRelativeURL } from '../../../../lib/utils/isRelativeURL'; -import { isURL } from '../../../../lib/utils/isURL'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { FileUpload } from '../../../file-upload/server'; import { settings } from '../../../settings/server'; @@ -33,7 +33,7 @@ type SendMessageOptions = { const validFullURLParam = Match.Where((value) => { check(value, String); - if (!isURL(value) && !value.startsWith(FileUpload.getPath())) { + if (!isAbsoluteURL(value) && !value.startsWith(FileUpload.getPath())) { throw new Error('Invalid href value provided'); } @@ -47,7 +47,7 @@ const validFullURLParam = Match.Where((value) => { const validPartialURLParam = Match.Where((value) => { check(value, String); - if (!isRelativeURL(value) && !isURL(value) && !value.startsWith(FileUpload.getPath())) { + if (!isRelativeURL(value) && !isAbsoluteURL(value) && !value.startsWith(FileUpload.getPath())) { throw new Error('Invalid href value provided'); } diff --git a/apps/meteor/app/utils/lib/getURL.ts b/apps/meteor/app/utils/lib/getURL.ts index 3d757abb6c83c..ed510767311da 100644 --- a/apps/meteor/app/utils/lib/getURL.ts +++ b/apps/meteor/app/utils/lib/getURL.ts @@ -1,6 +1,6 @@ import { escapeRegExp } from '@rocket.chat/string-helpers'; +import { isAbsoluteURL } from '@rocket.chat/tools'; -import { isURL } from '../../../lib/utils/isURL'; import { ltrim, rtrim, trim } from '../../../lib/utils/stringUtils'; function getCloudUrl( @@ -41,7 +41,7 @@ export const _getURL = ( { cdn, full, cloud, cloud_route, cloud_params, _cdn_prefix, _root_url_path_prefix, _site_url }: Record, deeplinkUrl?: string, ): string => { - if (isURL(path)) { + if (isAbsoluteURL(path)) { return path; } diff --git a/apps/meteor/client/lib/customOAuth/CustomOAuth.ts b/apps/meteor/client/lib/customOAuth/CustomOAuth.ts index 8ac31adc0a5ce..69883efa401f8 100644 --- a/apps/meteor/client/lib/customOAuth/CustomOAuth.ts +++ b/apps/meteor/client/lib/customOAuth/CustomOAuth.ts @@ -1,11 +1,11 @@ import type { OAuthConfiguration, OauthConfig } from '@rocket.chat/core-typings'; import { Random } from '@rocket.chat/random'; import { capitalize } from '@rocket.chat/string-helpers'; +import { isAbsoluteURL } from '@rocket.chat/tools'; import { Accounts } from 'meteor/accounts-base'; import { Meteor } from 'meteor/meteor'; import { OAuth } from 'meteor/oauth'; -import { isURL } from '../../../lib/utils/isURL'; import type { IOAuthProvider } from '../../definitions/IOAuthProvider'; import { createOAuthTotpLoginMethod } from '../../meteor/login/oauth'; import { overrideLoginMethod, type LoginCallback } from '../2fa/overrideLoginMethod'; @@ -48,7 +48,7 @@ export class CustomOAuth implements IOAuth this.scope = options.scope ?? 'openid'; this.responseType = options.responseType || 'code'; - if (!isURL(this.authorizePath)) { + if (!isAbsoluteURL(this.authorizePath)) { this.authorizePath = this.serverURL + this.authorizePath; } } diff --git a/apps/meteor/lib/utils/isURL.ts b/apps/meteor/lib/utils/isURL.ts deleted file mode 100644 index ec245e9fdc67f..0000000000000 --- a/apps/meteor/lib/utils/isURL.ts +++ /dev/null @@ -1,4 +0,0 @@ -/** - * @todo rename it as isAbsoluteURL - */ -export const isURL = (str: string): boolean => /^(https?:\/\/|data:)/.test(str); diff --git a/apps/meteor/server/services/messages/hooks/AfterSaveOEmbed.ts b/apps/meteor/server/services/messages/hooks/AfterSaveOEmbed.ts index eba201a70a20d..2c1144d1f6904 100644 --- a/apps/meteor/server/services/messages/hooks/AfterSaveOEmbed.ts +++ b/apps/meteor/server/services/messages/hooks/AfterSaveOEmbed.ts @@ -10,6 +10,7 @@ import { isOEmbedUrlWithMetadata } from '@rocket.chat/core-typings'; import { Logger } from '@rocket.chat/logger'; import { OEmbedCache, Messages } from '@rocket.chat/models'; import { serverFetch as fetch } from '@rocket.chat/server-fetch'; +import { isAbsoluteURL } from '@rocket.chat/tools'; import he from 'he'; import iconv from 'iconv-lite'; import ipRangeCheck from 'ip-range-check'; @@ -18,7 +19,6 @@ import { camelCase } from 'lodash'; import { settings } from '../../../../app/settings/server'; import { Info } from '../../../../app/utils/rocketchat.info'; -import { isURL } from '../../../../lib/utils/isURL'; import { afterParseUrlContent, beforeGetUrlContent } from '../lib/oembed/providers'; const MAX_EXTERNAL_URL_PREVIEWS = 5; @@ -182,7 +182,7 @@ const getUrlContent = async (urlObj: URL, redirectCount = 5): Promise { const parsedUrlObject: MessageUrl = { url, meta: {} }; let foundMeta = false; - if (!isURL(url)) { + if (!isAbsoluteURL(url)) { return { urlPreview: parsedUrlObject, foundMeta }; } diff --git a/apps/meteor/tests/unit/lib/utils/isURL.spec.ts b/apps/meteor/tests/unit/lib/utils/isURL.spec.ts deleted file mode 100644 index fd314b501468c..0000000000000 --- a/apps/meteor/tests/unit/lib/utils/isURL.spec.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { expect } from 'chai'; - -import { isURL } from '../../../../lib/utils/isURL'; - -describe('isURL', () => { - const testCases = [ - ['/', false], - ['test', false], - ['test/test', false], - ['.', false], - ['./test', false], - ['https://rocket.chat', true], - ['data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==', true], - ] as const; - - testCases.forEach(([parameter, expectedResult]) => { - it(`should return ${JSON.stringify(expectedResult)} for ${JSON.stringify(parameter)}`, () => { - const result = isURL(parameter); - expect(result).to.be.equal(expectedResult); - }); - }); -}); diff --git a/packages/tools/src/index.ts b/packages/tools/src/index.ts index 7889c9caf5caa..ca7406644c65f 100644 --- a/packages/tools/src/index.ts +++ b/packages/tools/src/index.ts @@ -16,3 +16,4 @@ export * from './validateEmail'; export * from './truncateString'; export * from './isTruthy'; export * from './getHeader'; +export * from './isAbsoluteURL'; diff --git a/packages/tools/src/isAbsoluteURL.spec.ts b/packages/tools/src/isAbsoluteURL.spec.ts new file mode 100644 index 0000000000000..50b656a2433e9 --- /dev/null +++ b/packages/tools/src/isAbsoluteURL.spec.ts @@ -0,0 +1,27 @@ +import { isAbsoluteURL } from './isAbsoluteURL'; + +describe('isAbsoluteURL', () => { + test.each([ + ['/', false], + ['test', false], + ['test/test', false], + ['.', false], + ['./test', false], + ['/absolute/path', false], + ['relative/path?query=1', false], + ['ftp://example.com', false], + ])('should return false for non-absolute URL %# (%s)', (input, expected) => { + expect(isAbsoluteURL(input)).toBe(expected); + }); + + test.each([ + ['https://rocket.chat', true], + ['http://rocket.chat', true], + ['https://example.com/path?query=1#hash', true], + ['http://localhost:3000', true], + ['data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==', true], + ['data:text/plain;charset=utf-8,Hello', true], + ])('should return true for absolute URL %# (%s)', (input, expected) => { + expect(isAbsoluteURL(input)).toBe(expected); + }); +}); diff --git a/packages/tools/src/isAbsoluteURL.ts b/packages/tools/src/isAbsoluteURL.ts new file mode 100644 index 0000000000000..93fce2d0fa45b --- /dev/null +++ b/packages/tools/src/isAbsoluteURL.ts @@ -0,0 +1 @@ +export const isAbsoluteURL = (str: string): boolean => /^(https?:\/\/|data:)/.test(str);