mirror of
https://github.com/tulir/gomuks.git
synced 2025-04-20 10:33:41 -05:00
web/util: move identifier validation functions to separate file
This commit is contained in:
parent
c4c5563f9a
commit
d77534c1de
3 changed files with 61 additions and 19 deletions
|
@ -13,20 +13,15 @@
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
import { parseMXC } from "@/util/validation.ts"
|
||||||
import { UserID } from "./types"
|
import { UserID } from "./types"
|
||||||
|
|
||||||
const mediaRegex = /^mxc:\/\/([a-zA-Z0-9.:-]+)\/([a-zA-Z0-9_-]+)$/
|
|
||||||
|
|
||||||
export const getMediaURL = (mxc?: string, encrypted: boolean = false): string | undefined => {
|
export const getMediaURL = (mxc?: string, encrypted: boolean = false): string | undefined => {
|
||||||
if (!mxc) {
|
const [server, mediaID] = parseMXC(mxc)
|
||||||
|
if (!mediaID) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
const match = mxc.match(mediaRegex)
|
return `_gomuks/media/${server}/${mediaID}?encrypted=${encrypted}`
|
||||||
if (!match) {
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
return `_gomuks/media/${match[1]}/${match[2]}?encrypted=${encrypted}`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getEncryptedMediaURL = (mxc?: string): string | undefined => {
|
export const getEncryptedMediaURL = (mxc?: string): string | undefined => {
|
||||||
|
@ -34,15 +29,11 @@ export const getEncryptedMediaURL = (mxc?: string): string | undefined => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getAvatarURL = (_userID: UserID, mxc?: string): string | undefined => {
|
export const getAvatarURL = (_userID: UserID, mxc?: string): string | undefined => {
|
||||||
if (!mxc) {
|
const [server, mediaID] = parseMXC(mxc)
|
||||||
|
if (!mediaID) {
|
||||||
return undefined
|
return undefined
|
||||||
// return `_gomuks/avatar/${encodeURIComponent(userID)}`
|
// return `_gomuks/avatar/${encodeURIComponent(userID)}`
|
||||||
}
|
}
|
||||||
const match = mxc.match(mediaRegex)
|
return `_gomuks/media/${server}/${mediaID}`
|
||||||
if (!match) {
|
// return `_gomuks/avatar/${encodeURIComponent(userID)}/${server}/${mediaID}`
|
||||||
return undefined
|
|
||||||
// return `_gomuks/avatar/${encodeURIComponent(userID)}`
|
|
||||||
}
|
|
||||||
return `_gomuks/media/${match[1]}/${match[2]}`
|
|
||||||
// return `_gomuks/avatar/${encodeURIComponent(userID)}/${match[1]}/${match[2]}`
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ import React, { use, useCallback } from "react"
|
||||||
import { getAvatarURL } from "@/api/media.ts"
|
import { getAvatarURL } from "@/api/media.ts"
|
||||||
import { RoomStateStore } from "@/api/statestore"
|
import { RoomStateStore } from "@/api/statestore"
|
||||||
import { MemDBEvent, MemberEventContent } from "@/api/types"
|
import { MemDBEvent, MemberEventContent } from "@/api/types"
|
||||||
|
import { isEventID } from "@/util/validation.ts"
|
||||||
import { ClientContext } from "../ClientContext.ts"
|
import { ClientContext } from "../ClientContext.ts"
|
||||||
import { LightboxContext } from "../Lightbox.tsx"
|
import { LightboxContext } from "../Lightbox.tsx"
|
||||||
import { ReplyIDBody } from "./ReplyBody.tsx"
|
import { ReplyIDBody } from "./ReplyBody.tsx"
|
||||||
|
@ -152,8 +153,7 @@ const TimelineEvent = ({ room, evt, prevEvt, setReplyTo }: TimelineEventProps) =
|
||||||
<span className="event-time" title={editTime ? `${fullTime} - ${editTime}` : fullTime}>{shortTime}</span>
|
<span className="event-time" title={editTime ? `${fullTime} - ${editTime}` : fullTime}>{shortTime}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="event-content">
|
<div className="event-content">
|
||||||
{typeof replyTo === "string" && BodyType !== HiddenEvent
|
{isEventID(replyTo) && BodyType !== HiddenEvent ? <ReplyIDBody room={room} eventID={replyTo}/> : null}
|
||||||
? <ReplyIDBody room={room} eventID={replyTo}/> : null}
|
|
||||||
<BodyType room={room} sender={memberEvt} event={evt}/>
|
<BodyType room={room} sender={memberEvt} event={evt}/>
|
||||||
{evt.reactions ? <EventReactions reactions={evt.reactions}/> : null}
|
{evt.reactions ? <EventReactions reactions={evt.reactions}/> : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
51
web/src/util/validation.ts
Normal file
51
web/src/util/validation.ts
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
// gomuks - A Matrix client written in Go.
|
||||||
|
// Copyright (C) 2024 Tulir Asokan
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
import { ContentURI, EventID, RoomAlias, RoomID, UserID } from "@/api/types"
|
||||||
|
|
||||||
|
const simpleHomeserverRegex = /^[a-zA-Z0-9.:-]+$/
|
||||||
|
const mediaRegex = /^mxc:\/\/([a-zA-Z0-9.:-]+)\/([a-zA-Z0-9_-]+)$/
|
||||||
|
|
||||||
|
function isIdentifier<T>(identifier: unknown, sigil: string, requiresServer: boolean): identifier is T {
|
||||||
|
if (typeof identifier !== "string" || !identifier.startsWith(sigil)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (requiresServer) {
|
||||||
|
const idx = identifier.indexOf(":")
|
||||||
|
return idx > 0 && simpleHomeserverRegex.test(identifier.slice(idx+1))
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
export function validated<T>(value: T | undefined, validator: (value: T) => boolean): value is T {
|
||||||
|
return value !== undefined && validator(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isEventID = (eventID: unknown) => isIdentifier<EventID>(eventID, "$", false)
|
||||||
|
export const isUserID = (userID: unknown) => isIdentifier<UserID>(userID, "@", true)
|
||||||
|
export const isRoomID = (roomID: unknown) => isIdentifier<RoomID>(roomID, "!", true)
|
||||||
|
export const isRoomAlias = (roomAlias: unknown) => isIdentifier<RoomAlias>(roomAlias, "#", true)
|
||||||
|
export const isMXC = (mxc: unknown): mxc is ContentURI => typeof mxc === "string" && mediaRegex.test(mxc)
|
||||||
|
|
||||||
|
export function parseMXC(mxc: unknown): [string, string] | [] {
|
||||||
|
if (typeof mxc !== "string") {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
const match = mxc.match(mediaRegex)
|
||||||
|
if (!match) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return [match[1], match[2]]
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue