Make logic more DRY, simplify logic, improve naming.

This commit is contained in:
David Langley 2024-11-11 19:38:58 +00:00
parent 6134cfd9c4
commit c0a313abae

View file

@ -149,10 +149,10 @@ export default class MessageEvent extends React.Component<IProps> implements IMe
/** /**
* Validates that the filename extension and advertised mimetype * Validates that the filename extension and advertised mimetype
* of the supplied image/file message content are not null, match and are actuallly video/image content. * of the supplied image/file message content match and are actuallly video/image content.
* For image/video messages with a thumbnail it also validates the mimetype is an image. * For image/video messages with a thumbnail it also validates the mimetype is an image.
* @param content The mxEvent content of the message * @param content The mxEvent content of the message
* @returns * @returns A boolean indicating whether the validation passed
*/ */
private validateImageOrVideoMimetype = (content: IContent): boolean => { private validateImageOrVideoMimetype = (content: IContent): boolean => {
// As per the spec if filename is not present the body represents the filename // As per the spec if filename is not present the body represents the filename
@ -161,32 +161,28 @@ export default class MessageEvent extends React.Component<IProps> implements IMe
logger.log("Failed to validate image/video content, filename null"); logger.log("Failed to validate image/video content, filename null");
return false; return false;
} }
// Validate mimetype of the thumbnail is valid // Check mimetype of the thumbnail
const thumbnailResult = this.validateThumbnailMimeType(content); if (!this.validateThumbnailMimeType(content)) {
if (!thumbnailResult) {
logger.log("Failed to validate file/image thumbnail"); logger.log("Failed to validate file/image thumbnail");
return false; return false;
} }
const typeFromExtension = mime.getType(filename);
const majorContentTypeFromExtension = typeFromExtension?.split("/")[0];
const allowedMajorContentTypes = ["image", "video"];
// Validate mimetype of the extension is valid // if there is no mimetype from the extesion or the mimetype is not image/video validation fails.
const result = const typeFromExtension = mime.getType(filename) ?? undefined;
!!majorContentTypeFromExtension && allowedMajorContentTypes.includes(majorContentTypeFromExtension); const extensionMajorMimeType = this.parseMajorMimeType(typeFromExtension);
if (!result) { if (!typeFromExtension || !this.validateAllowedMimetype(typeFromExtension, ["image", "video"])) {
logger.log("Failed to validate image/video content, invalid or missing extension"); logger.log("Failed to validate image/video content, invalid or missing extension");
return false;
} }
// Validate content mimetype is valid if it is set // if the content mimetype is set check it is an image/video and that it matches the extesion mimetype otherwise validation fails
const contentMimetype = content.info?.mimetype; const contentMimetype = content.info?.mimetype;
if (contentMimetype) { if (contentMimetype) {
const majorContentTypeFromContent = contentMimetype?.split("/")[0]; const contentMajorMimetype = this.parseMajorMimeType(contentMimetype);
const result = if (
!!majorContentTypeFromContent && !this.validateAllowedMimetype(contentMimetype, ["image", "video"]) ||
allowedMajorContentTypes.includes(majorContentTypeFromContent) && extensionMajorMimeType !== contentMajorMimetype
majorContentTypeFromExtension == majorContentTypeFromContent; ) {
if (!result) {
logger.log("Failed to validate image/video content, invalid or missing mimetype"); logger.log("Failed to validate image/video content, invalid or missing mimetype");
return false; return false;
} }
@ -195,50 +191,59 @@ export default class MessageEvent extends React.Component<IProps> implements IMe
}; };
/** /**
* Validates that the advertised mimetype of the supplied sticker content * Validates that the advertised mimetype of the sticker content
* is not null and is an image. * is an image.
* For stickers with a thumbnail it also validates the mimetype is an image. * For stickers with a thumbnail it also validates the mimetype is an image.
* @param content The mxEvent content of the message * @param content The mxEvent content of the message
* @returns * @returns A boolean indicating whether the validation passed
*/ */
private validateStickerMimetype = (content: IContent): boolean => { private validateStickerMimetype = (content: IContent): boolean => {
// Validate mimetype of the thumbnail is valid // Validate mimetype of the thumbnail
const thumbnailResult = this.validateThumbnailMimeType(content); const thumbnailResult = this.validateThumbnailMimeType(content);
if (!thumbnailResult) { if (!thumbnailResult) {
logger.log("Failed to validate sticker thumbnail"); logger.log("Failed to validate sticker thumbnail");
return false; return false;
} }
// Validate mimetype of the content info is valid if it is set
const contentMimetype = content.info?.mimetype; const contentMimetype = content.info?.mimetype;
if (contentMimetype) { if (contentMimetype && !this.validateAllowedMimetype(contentMimetype, ["image"])) {
// Validate mimetype of the content is valid
const majorContentTypeFromContent = contentMimetype?.split("/")[0];
const result = majorContentTypeFromContent === "image";
if (!result) {
logger.log("Failed to validate image/video content, invalid or missing mimetype/extensions"); logger.log("Failed to validate image/video content, invalid or missing mimetype/extensions");
return false; return false;
} }
}
return true; return true;
}; };
/** /**
* Validates the thumbnail assocaited with an image/video message or sticker * For image/video messages or stickers that have a thumnail mimetype specified,
* is has an image mimetype. * validates that the major mimetime is image.
* @param content The mxEvent content of the message * @param content The mxEvent content of the message
* @returns * @returns A boolean indicating whether the validation passed
*/ */
private validateThumbnailMimeType = (content: IContent): boolean => { private validateThumbnailMimeType = (content: IContent): boolean => {
const thumbnailInfo = content.info?.thumbnail_info; const thumbnailMimetype = content.info?.thumbnail_info?.mimetype;
if (thumbnailInfo) { return !thumbnailMimetype || this.validateAllowedMimetype(thumbnailMimetype, ["image"]);
const majorContentTypeFromThumbnail = thumbnailInfo.mimetype?.split("/")[0];
if (!majorContentTypeFromThumbnail || majorContentTypeFromThumbnail !== "image") {
logger.log("Failed to validate image/video content, thumbnail mimetype is not an image");
return false;
}
}
return true;
}; };
/**
* Validates that the major part of a mimetime from an allowed list.
* @param mimetype The mimetype to validate
* @param allowedMajorMimeTypes The list of allowed major mimetimes
* @returns A boolean indicating whether the validation passed
*/
private validateAllowedMimetype = (mimetype: string, allowedMajorMimeTypes: string[]): boolean => {
const majorMimetype = this.parseMajorMimeType(mimetype);
return !!majorMimetype && allowedMajorMimeTypes.includes(majorMimetype);
};
/**
* Parses and returns the the major part of a mimetype(before the "/").
* @param mimetype As optional mimetype string to parse
* @returns The major part of the mimetype string or undefined
*/
private parseMajorMimeType(mimetype?: string): string | undefined {
return mimetype?.split("/")[0];
}
public render(): React.ReactNode { public render(): React.ReactNode {
const content = this.props.mxEvent.getContent(); const content = this.props.mxEvent.getContent();
const type = this.props.mxEvent.getType(); const type = this.props.mxEvent.getType();
@ -249,20 +254,9 @@ export default class MessageEvent extends React.Component<IProps> implements IMe
if (this.props.mxEvent.isDecryptionFailure()) { if (this.props.mxEvent.isDecryptionFailure()) {
BodyType = DecryptionFailureBody; BodyType = DecryptionFailureBody;
} else if (type && this.evTypes.has(type)) { } else if (type && this.evTypes.has(type)) {
if (type == EventType.Sticker && !this.validateStickerMimetype(content)) {
BodyType = this.bodyTypes.get(MsgType.File)!;
} else {
BodyType = this.evTypes.get(type)!; BodyType = this.evTypes.get(type)!;
}
} else if (msgtype && this.bodyTypes.has(msgtype)) { } else if (msgtype && this.bodyTypes.has(msgtype)) {
if (
(msgtype == MsgType.Image || msgtype == MsgType.Video) &&
!this.validateImageOrVideoMimetype(content)
) {
BodyType = this.bodyTypes.get(MsgType.File)!;
} else {
BodyType = this.bodyTypes.get(msgtype)!; BodyType = this.bodyTypes.get(msgtype)!;
}
} else if (content.url) { } else if (content.url) {
// Fallback to MFileBody if there's a content URL // Fallback to MFileBody if there's a content URL
BodyType = this.bodyTypes.get(MsgType.File)!; BodyType = this.bodyTypes.get(MsgType.File)!;
@ -271,6 +265,13 @@ export default class MessageEvent extends React.Component<IProps> implements IMe
BodyType = UnknownBody; BodyType = UnknownBody;
} }
if (
((BodyType === MImageBody || BodyType == MVideoBody) && !this.validateImageOrVideoMimetype(content)) ||
(BodyType === MStickerBody && !this.validateStickerMimetype(content))
) {
BodyType = this.bodyTypes.get(MsgType.File)!;
}
// TODO: move to eventTypes when location sharing spec stabilises // TODO: move to eventTypes when location sharing spec stabilises
if (M_LOCATION.matches(type) || (type === EventType.RoomMessage && msgtype === MsgType.Location)) { if (M_LOCATION.matches(type) || (type === EventType.RoomMessage && msgtype === MsgType.Location)) {
BodyType = MLocationBody; BodyType = MLocationBody;