Remove url npm dependency and use Web URL constructor (#10930)

This commit is contained in:
Michael Telatynski 2023-05-17 12:50:00 +01:00 committed by GitHub
parent 7917d973e7
commit 2da199c41d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 101 additions and 53 deletions

View file

@ -118,7 +118,6 @@
"sanitize-html": "2.10.0", "sanitize-html": "2.10.0",
"tar-js": "^0.3.0", "tar-js": "^0.3.0",
"ua-parser-js": "^1.0.2", "ua-parser-js": "^1.0.2",
"url": "^0.11.0",
"what-input": "^5.2.10", "what-input": "^5.2.10",
"zxcvbn": "^4.4.2" "zxcvbn": "^4.4.2"
}, },

View file

@ -14,7 +14,6 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import url from "url";
import { SERVICE_TYPES } from "matrix-js-sdk/src/service-types"; import { SERVICE_TYPES } from "matrix-js-sdk/src/service-types";
import { Room } from "matrix-js-sdk/src/models/room"; import { Room } from "matrix-js-sdk/src/models/room";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
@ -25,6 +24,7 @@ import { Service, startTermsFlow, TermsInteractionCallback, TermsNotSignedError
import { MatrixClientPeg } from "./MatrixClientPeg"; import { MatrixClientPeg } from "./MatrixClientPeg";
import SdkConfig from "./SdkConfig"; import SdkConfig from "./SdkConfig";
import { WidgetType } from "./widgets/WidgetType"; import { WidgetType } from "./widgets/WidgetType";
import { parseUrl } from "./utils/UrlUtils";
// The version of the integration manager API we're intending to work with // The version of the integration manager API we're intending to work with
const imApiVersion = "1.1"; const imApiVersion = "1.1";
@ -154,11 +154,10 @@ export default class ScalarAuthClient {
// Once we've fully transitioned to _matrix URLs, we can give people // Once we've fully transitioned to _matrix URLs, we can give people
// a grace period to update their configs, then use the rest url as // a grace period to update their configs, then use the rest url as
// a regular base url. // a regular base url.
const parsedImRestUrl = url.parse(this.apiUrl); const parsedImRestUrl = parseUrl(this.apiUrl);
parsedImRestUrl.path = "";
parsedImRestUrl.pathname = ""; parsedImRestUrl.pathname = "";
return startTermsFlow( return startTermsFlow(
[new Service(SERVICE_TYPES.IM, url.format(parsedImRestUrl), token)], [new Service(SERVICE_TYPES.IM, parsedImRestUrl.toString(), token)],
this.termsInteractionCallback, this.termsInteractionCallback,
).then(() => { ).then(() => {
return token; return token;

View file

@ -14,7 +14,6 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import url from "url";
import React from "react"; import React from "react";
import { SERVICE_TYPES } from "matrix-js-sdk/src/service-types"; import { SERVICE_TYPES } from "matrix-js-sdk/src/service-types";
@ -23,6 +22,7 @@ import DialogButtons from "../elements/DialogButtons";
import BaseDialog from "./BaseDialog"; import BaseDialog from "./BaseDialog";
import { ServicePolicyPair } from "../../../Terms"; import { ServicePolicyPair } from "../../../Terms";
import ExternalLink from "../elements/ExternalLink"; import ExternalLink from "../elements/ExternalLink";
import { parseUrl } from "../../../utils/UrlUtils";
interface ITermsCheckboxProps { interface ITermsCheckboxProps {
onChange: (url: string, checked: boolean) => void; onChange: (url: string, checked: boolean) => void;
@ -130,7 +130,7 @@ export default class TermsDialog extends React.PureComponent<ITermsDialogProps,
public render(): React.ReactNode { public render(): React.ReactNode {
const rows: JSX.Element[] = []; const rows: JSX.Element[] = [];
for (const policiesAndService of this.props.policiesAndServicePairs) { for (const policiesAndService of this.props.policiesAndServicePairs) {
const parsedBaseUrl = url.parse(policiesAndService.service.baseUrl); const parsedBaseUrl = parseUrl(policiesAndService.service.baseUrl);
const policyValues = Object.values(policiesAndService.policies); const policyValues = Object.values(policiesAndService.policies);
for (let i = 0; i < policyValues.length; ++i) { for (let i = 0; i < policyValues.length; ++i) {

View file

@ -17,7 +17,6 @@ limitations under the License.
*/ */
import React from "react"; import React from "react";
import url from "url";
import { RoomMember } from "matrix-js-sdk/src/models/room-member"; import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
@ -28,6 +27,7 @@ import MemberAvatar from "../avatars/MemberAvatar";
import BaseAvatar from "../avatars/BaseAvatar"; import BaseAvatar from "../avatars/BaseAvatar";
import AccessibleButton from "./AccessibleButton"; import AccessibleButton from "./AccessibleButton";
import TextWithTooltip from "./TextWithTooltip"; import TextWithTooltip from "./TextWithTooltip";
import { parseUrl } from "../../../utils/UrlUtils";
interface IProps { interface IProps {
url: string; url: string;
@ -67,13 +67,12 @@ export default class AppPermission extends React.Component<IProps, IState> {
} }
private parseWidgetUrl(): { isWrapped: boolean; widgetDomain: string | null } { private parseWidgetUrl(): { isWrapped: boolean; widgetDomain: string | null } {
const widgetUrl = url.parse(this.props.url); const widgetUrl = parseUrl(this.props.url);
const params = new URLSearchParams(widgetUrl.search ?? undefined);
// HACK: We're relying on the query params when we should be relying on the widget's `data`. // HACK: We're relying on the query params when we should be relying on the widget's `data`.
// This is a workaround for Scalar. // This is a workaround for Scalar.
if (WidgetUtils.isScalarUrl(this.props.url) && params?.get("url")) { if (WidgetUtils.isScalarUrl(this.props.url) && widgetUrl.searchParams.has("url")) {
const unwrappedUrl = url.parse(params.get("url")!); const unwrappedUrl = parseUrl(widgetUrl.searchParams.get("url")!);
return { return {
widgetDomain: unwrappedUrl.host || unwrappedUrl.hostname, widgetDomain: unwrappedUrl.host || unwrappedUrl.hostname,
isWrapped: true, isWrapped: true,

View file

@ -17,7 +17,6 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import url from "url";
import React, { ContextType, createRef, CSSProperties, MutableRefObject, ReactNode } from "react"; import React, { ContextType, createRef, CSSProperties, MutableRefObject, ReactNode } from "react";
import classNames from "classnames"; import classNames from "classnames";
import { IWidget, MatrixCapabilities } from "matrix-widget-api"; import { IWidget, MatrixCapabilities } from "matrix-widget-api";
@ -52,6 +51,7 @@ import { ElementWidgetCapabilities } from "../../../stores/widgets/ElementWidget
import { WidgetMessagingStore } from "../../../stores/widgets/WidgetMessagingStore"; import { WidgetMessagingStore } from "../../../stores/widgets/WidgetMessagingStore";
import { SdkContextClass } from "../../../contexts/SDKContext"; import { SdkContextClass } from "../../../contexts/SDKContext";
import { ModuleRunner } from "../../../modules/ModuleRunner"; import { ModuleRunner } from "../../../modules/ModuleRunner";
import { parseUrl } from "../../../utils/UrlUtils";
interface IProps { interface IProps {
app: IWidget | IApp; app: IWidget | IApp;
@ -265,7 +265,7 @@ export default class AppTile extends React.Component<IProps, IState> {
private isMixedContent(): boolean { private isMixedContent(): boolean {
const parentContentProtocol = window.location.protocol; const parentContentProtocol = window.location.protocol;
const u = url.parse(this.props.app.url); const u = parseUrl(this.props.app.url);
const childContentProtocol = u.protocol; const childContentProtocol = u.protocol;
if (parentContentProtocol === "https:" && childContentProtocol !== "https:") { if (parentContentProtocol === "https:" && childContentProtocol !== "https:") {
logger.warn( logger.warn(

View file

@ -14,7 +14,6 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import url from "url";
import React, { ReactNode } from "react"; import React, { ReactNode } from "react";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { IThreepid } from "matrix-js-sdk/src/@types/threepids"; import { IThreepid } from "matrix-js-sdk/src/@types/threepids";
@ -25,7 +24,7 @@ import Modal from "../../../Modal";
import dis from "../../../dispatcher/dispatcher"; import dis from "../../../dispatcher/dispatcher";
import { getThreepidsWithBindStatus } from "../../../boundThreepids"; import { getThreepidsWithBindStatus } from "../../../boundThreepids";
import IdentityAuthClient from "../../../IdentityAuthClient"; import IdentityAuthClient from "../../../IdentityAuthClient";
import { abbreviateUrl, unabbreviateUrl } from "../../../utils/UrlUtils"; import { abbreviateUrl, parseUrl, unabbreviateUrl } from "../../../utils/UrlUtils";
import { getDefaultIdentityServerUrl, doesIdentityServerHaveTerms } from "../../../utils/IdentityServerUtils"; import { getDefaultIdentityServerUrl, doesIdentityServerHaveTerms } from "../../../utils/IdentityServerUtils";
import { timeout } from "../../../utils/promise"; import { timeout } from "../../../utils/promise";
import { ActionPayload } from "../../../dispatcher/payloads"; import { ActionPayload } from "../../../dispatcher/payloads";
@ -44,7 +43,7 @@ const REACHABILITY_TIMEOUT = 10000; // ms
* @returns {string} null if url passes all checks, otherwise i18ned error string * @returns {string} null if url passes all checks, otherwise i18ned error string
*/ */
async function checkIdentityServerUrl(u: string): Promise<string | null> { async function checkIdentityServerUrl(u: string): Promise<string | null> {
const parsedUrl = url.parse(u); const parsedUrl = parseUrl(u);
if (parsedUrl.protocol !== "https:") return _t("Identity server URL must be HTTPS"); if (parsedUrl.protocol !== "https:") return _t("Identity server URL must be HTTPS");

View file

@ -14,7 +14,6 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import url from "url";
import { ComponentProps } from "react"; import { ComponentProps } from "react";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
@ -25,6 +24,7 @@ import Modal from "../Modal";
import SettingsStore from "../settings/SettingsStore"; import SettingsStore from "../settings/SettingsStore";
import IntegrationManager from "../components/views/settings/IntegrationManager"; import IntegrationManager from "../components/views/settings/IntegrationManager";
import { IntegrationManagers } from "./IntegrationManagers"; import { IntegrationManagers } from "./IntegrationManagers";
import { parseUrl } from "../utils/UrlUtils";
export enum Kind { export enum Kind {
Account = "account", Account = "account",
@ -42,15 +42,14 @@ export class IntegrationManagerInstance {
) {} ) {}
public get name(): string { public get name(): string {
const parsed = url.parse(this.uiUrl); const parsed = parseUrl(this.uiUrl);
return parsed.host ?? ""; return parsed.host ?? "";
} }
public get trimmedApiUrl(): string { public get trimmedApiUrl(): string {
const parsed = url.parse(this.apiUrl); const parsed = parseUrl(this.apiUrl);
parsed.pathname = ""; parsed.pathname = "";
parsed.path = ""; return parsed.toString();
return url.format(parsed);
} }
public getScalarClient(): ScalarAuthClient { public getScalarClient(): ScalarAuthClient {

View file

@ -14,7 +14,6 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import url from "url";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { ClientEvent, IClientWellKnown, MatrixClient } from "matrix-js-sdk/src/client"; import { ClientEvent, IClientWellKnown, MatrixClient } from "matrix-js-sdk/src/client";
import { compare } from "matrix-js-sdk/src/utils"; import { compare } from "matrix-js-sdk/src/utils";
@ -27,6 +26,7 @@ import IntegrationsImpossibleDialog from "../components/views/dialogs/Integratio
import IntegrationsDisabledDialog from "../components/views/dialogs/IntegrationsDisabledDialog"; import IntegrationsDisabledDialog from "../components/views/dialogs/IntegrationsDisabledDialog";
import WidgetUtils from "../utils/WidgetUtils"; import WidgetUtils from "../utils/WidgetUtils";
import { MatrixClientPeg } from "../MatrixClientPeg"; import { MatrixClientPeg } from "../MatrixClientPeg";
import { parseUrl } from "../utils/UrlUtils";
const KIND_PREFERENCE = [ const KIND_PREFERENCE = [
// Ordered: first is most preferred, last is least preferred. // Ordered: first is most preferred, last is least preferred.
@ -199,7 +199,7 @@ export class IntegrationManagers {
logger.log("Looking up integration manager via .well-known"); logger.log("Looking up integration manager via .well-known");
if (domainName.startsWith("http:") || domainName.startsWith("https:")) { if (domainName.startsWith("http:") || domainName.startsWith("https:")) {
// trim off the scheme and just use the domain // trim off the scheme and just use the domain
domainName = url.parse(domainName).host!; domainName = parseUrl(domainName).host;
} }
let wkConfig: IClientWellKnown; let wkConfig: IClientWellKnown;

View file

@ -14,8 +14,6 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import url from "url";
/** /**
* If a url has no path component, etc. abbreviate it to just the hostname * If a url has no path component, etc. abbreviate it to just the hostname
* *
@ -25,11 +23,16 @@ import url from "url";
export function abbreviateUrl(u?: string): string { export function abbreviateUrl(u?: string): string {
if (!u) return ""; if (!u) return "";
const parsedUrl = url.parse(u); let parsedUrl: URL;
try {
parsedUrl = parseUrl(u);
} catch (e) {
console.error(e);
// if it's something we can't parse as a url then just return it // if it's something we can't parse as a url then just return it
if (!parsedUrl) return u; return u;
}
if (parsedUrl.path === "/") { if (parsedUrl.pathname === "/") {
// we ignore query / hash parts: these aren't relevant for IS server URLs // we ignore query / hash parts: these aren't relevant for IS server URLs
return parsedUrl.host || ""; return parsedUrl.host || "";
} }
@ -42,8 +45,15 @@ export function unabbreviateUrl(u?: string): string {
let longUrl = u; let longUrl = u;
if (!u.startsWith("https://")) longUrl = "https://" + u; if (!u.startsWith("https://")) longUrl = "https://" + u;
const parsed = url.parse(longUrl); const parsed = parseUrl(longUrl);
if (parsed.hostname === null) return u; if (!parsed.hostname) return u;
return longUrl; return longUrl;
} }
export function parseUrl(u: string): URL {
if (!u.includes(":")) {
u = window.location.protocol + u;
}
return new URL(u);
}

View file

@ -15,7 +15,6 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import * as url from "url";
import { base32 } from "rfc4648"; import { base32 } from "rfc4648";
import { IWidget, IWidgetData } from "matrix-widget-api"; import { IWidget, IWidgetData } from "matrix-widget-api";
import { Room } from "matrix-js-sdk/src/models/room"; import { Room } from "matrix-js-sdk/src/models/room";
@ -36,6 +35,7 @@ import { Jitsi } from "../widgets/Jitsi";
import { objectClone } from "./objects"; import { objectClone } from "./objects";
import { _t } from "../languageHandler"; import { _t } from "../languageHandler";
import { IApp, isAppWidget } from "../stores/WidgetStore"; import { IApp, isAppWidget } from "../stores/WidgetStore";
import { parseUrl } from "./UrlUtils";
// How long we wait for the state event echo to come back from the server // How long we wait for the state event echo to come back from the server
// before waitFor[Room/User]Widget rejects its promise // before waitFor[Room/User]Widget rejects its promise
@ -106,7 +106,7 @@ export default class WidgetUtils {
return false; return false;
} }
const testUrl = url.parse(testUrlString); const testUrl = parseUrl(testUrlString);
let scalarUrls = SdkConfig.get().integrations_widgets_urls; let scalarUrls = SdkConfig.get().integrations_widgets_urls;
if (!scalarUrls || scalarUrls.length === 0) { if (!scalarUrls || scalarUrls.length === 0) {
const defaultManager = IntegrationManagers.sharedInstance().getPrimaryManager(); const defaultManager = IntegrationManagers.sharedInstance().getPrimaryManager();
@ -118,7 +118,7 @@ export default class WidgetUtils {
} }
for (let i = 0; i < scalarUrls.length; i++) { for (let i = 0; i < scalarUrls.length; i++) {
const scalarUrl = url.parse(scalarUrls[i]); const scalarUrl = parseUrl(scalarUrls[i]);
if (testUrl && scalarUrl) { if (testUrl && scalarUrl) {
if ( if (
testUrl.protocol === scalarUrl.protocol && testUrl.protocol === scalarUrl.protocol &&

View file

@ -52,6 +52,16 @@ import { WidgetMessagingStore } from "../../../../src/stores/widgets/WidgetMessa
import { ModuleRunner } from "../../../../src/modules/ModuleRunner"; import { ModuleRunner } from "../../../../src/modules/ModuleRunner";
import { RoomPermalinkCreator } from "../../../../src/utils/permalinks/Permalinks"; import { RoomPermalinkCreator } from "../../../../src/utils/permalinks/Permalinks";
jest.mock("../../../../src/stores/OwnProfileStore", () => ({
OwnProfileStore: {
instance: {
isProfileInfoFetched: true,
removeListener: jest.fn(),
getHttpAvatarUrl: jest.fn().mockReturnValue("http://avatar_url"),
},
},
}));
describe("AppTile", () => { describe("AppTile", () => {
let cli: MatrixClient; let cli: MatrixClient;
let r1: Room; let r1: Room;

View file

@ -0,0 +1,51 @@
/*
Copyright 2023 The Matrix.org Foundation C.I.C.
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 { abbreviateUrl, parseUrl, unabbreviateUrl } from "../../src/utils/UrlUtils";
describe("abbreviateUrl", () => {
it("should return empty string if passed falsey", () => {
expect(abbreviateUrl(undefined)).toEqual("");
});
it("should abbreviate to host if empty pathname", () => {
expect(abbreviateUrl("https://foo/")).toEqual("foo");
});
it("should not abbreviate if has path parts", () => {
expect(abbreviateUrl("https://foo/path/parts")).toEqual("https://foo/path/parts");
});
});
describe("unabbreviateUrl", () => {
it("should return empty string if passed falsey", () => {
expect(unabbreviateUrl(undefined)).toEqual("");
});
it("should prepend https to input if it lacks it", () => {
expect(unabbreviateUrl("element.io")).toEqual("https://element.io");
});
it("should not prepend https to input if it has it", () => {
expect(unabbreviateUrl("https://element.io")).toEqual("https://element.io");
});
});
describe("parseUrl", () => {
it("should not throw on no proto", () => {
expect(() => parseUrl("test")).not.toThrow();
});
});

View file

@ -7313,11 +7313,6 @@ pump@^3.0.0:
end-of-stream "^1.1.0" end-of-stream "^1.1.0"
once "^1.3.1" once "^1.3.1"
punycode@1.3.2:
version "1.3.2"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d"
integrity sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==
punycode@^2.1.0, punycode@^2.1.1: punycode@^2.1.0, punycode@^2.1.1:
version "2.3.0" version "2.3.0"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f"
@ -7352,11 +7347,6 @@ qs@~6.10.3:
dependencies: dependencies:
side-channel "^1.0.4" side-channel "^1.0.4"
querystring@0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620"
integrity sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==
querystring@^0.2.0: querystring@^0.2.0:
version "0.2.1" version "0.2.1"
resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.1.tgz#40d77615bb09d16902a85c3e38aa8b5ed761c2dd" resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.1.tgz#40d77615bb09d16902a85c3e38aa8b5ed761c2dd"
@ -8611,14 +8601,6 @@ url-parse@^1.5.3:
querystringify "^2.1.1" querystringify "^2.1.1"
requires-port "^1.0.0" requires-port "^1.0.0"
url@^0.11.0:
version "0.11.0"
resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1"
integrity sha512-kbailJa29QrtXnxgq+DdCEGlbTeYM2eJUxsz6vjZavrCYPMIFHMKQmSKYAIuUK2i7hgPm28a8piX5NTUtM/LKQ==
dependencies:
punycode "1.3.2"
querystring "0.2.0"
use-callback-ref@^1.3.0: use-callback-ref@^1.3.0:
version "1.3.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.3.0.tgz#772199899b9c9a50526fedc4993fc7fa1f7e32d5" resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.3.0.tgz#772199899b9c9a50526fedc4993fc7fa1f7e32d5"