Skip to content

Commit

Permalink
Fix authenticated media download (#1947)
Browse files Browse the repository at this point in the history
* remove dead function

* fix media download in room timeline

* authenticate remaining media endpoints
  • Loading branch information
ajbura authored Sep 11, 2024
1 parent f2c31d2 commit 03cc25e
Show file tree
Hide file tree
Showing 13 changed files with 286 additions and 209 deletions.
6 changes: 4 additions & 2 deletions src/app/components/image-viewer/ImageViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Box, Chip, Header, Icon, IconButton, Icons, Text, as } from 'folds';
import * as css from './ImageViewer.css';
import { useZoom } from '../../hooks/useZoom';
import { usePan } from '../../hooks/usePan';
import { downloadMedia } from '../../utils/matrix';

export type ImageViewerProps = {
alt: string;
Expand All @@ -18,8 +19,9 @@ export const ImageViewer = as<'div', ImageViewerProps>(
const { zoom, zoomIn, zoomOut, setZoom } = useZoom(0.2);
const { pan, cursor, onMouseDown } = usePan(zoom !== 1);

const handleDownload = () => {
FileSaver.saveAs(src, alt);
const handleDownload = async () => {
const fileContent = await downloadMedia(src);
FileSaver.saveAs(fileContent, alt);
};

return (
Expand Down
19 changes: 13 additions & 6 deletions src/app/components/message/content/AudioContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { EncryptedAttachmentInfo } from 'browser-encrypt-attachment';
import { Range } from 'react-range';
import { useMatrixClient } from '../../../hooks/useMatrixClient';
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
import { getFileSrcUrl } from './util';
import { IAudioInfo } from '../../../../types/matrix/common';
import {
PlayTimeCallback,
Expand All @@ -17,7 +16,12 @@ import {
} from '../../../hooks/media';
import { useThrottle } from '../../../hooks/useThrottle';
import { secondsToMinutesAndSeconds } from '../../../utils/common';
import { mxcUrlToHttp } from '../../../utils/matrix';
import {
decryptFile,
downloadEncryptedMedia,
downloadMedia,
mxcUrlToHttp,
} from '../../../utils/matrix';
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';

const PLAY_TIME_THROTTLE_OPS = {
Expand Down Expand Up @@ -49,10 +53,13 @@ export function AudioContent({
const useAuthentication = useMediaAuthentication();

const [srcState, loadSrc] = useAsyncCallback(
useCallback(
() => getFileSrcUrl(mxcUrlToHttp(mx, url, useAuthentication) ?? '', mimeType, encInfo, true),
[mx, url, useAuthentication, mimeType, encInfo]
)
useCallback(async () => {
const mediaUrl = mxcUrlToHttp(mx, url, useAuthentication) ?? url;
const fileContent = encInfo
? await downloadEncryptedMedia(mediaUrl, (encBuf) => decryptFile(encBuf, mimeType, encInfo))
: await downloadMedia(mediaUrl);
return URL.createObjectURL(fileContent);
}, [mx, url, useAuthentication, mimeType, encInfo])
);

const audioRef = useRef<HTMLAudioElement | null>(null);
Expand Down
42 changes: 26 additions & 16 deletions src/app/components/message/content/FileContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import FocusTrap from 'focus-trap-react';
import { IFileInfo } from '../../../../types/matrix/common';
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
import { useMatrixClient } from '../../../hooks/useMatrixClient';
import { getFileSrcUrl, getSrcFile } from './util';
import { bytesToSize } from '../../../utils/common';
import {
READABLE_EXT_TO_MIME_TYPE,
Expand All @@ -30,7 +29,12 @@ import {
} from '../../../utils/mimeTypes';
import * as css from './style.css';
import { stopPropagation } from '../../../utils/keyboard';
import { mxcUrlToHttp } from '../../../utils/matrix';
import {
decryptFile,
downloadEncryptedMedia,
downloadMedia,
mxcUrlToHttp,
} from '../../../utils/matrix';
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';

const renderErrorButton = (retry: () => void, text: string) => (
Expand Down Expand Up @@ -80,19 +84,17 @@ export function ReadTextFile({ body, mimeType, url, encInfo, renderViewer }: Rea
const useAuthentication = useMediaAuthentication();
const [textViewer, setTextViewer] = useState(false);

const loadSrc = useCallback(
() => getFileSrcUrl(mxcUrlToHttp(mx, url, useAuthentication) ?? '', mimeType, encInfo),
[mx, url, useAuthentication, mimeType, encInfo]
);

const [textState, loadText] = useAsyncCallback(
useCallback(async () => {
const src = await loadSrc();
const blob = await getSrcFile(src);
const text = blob.text();
const mediaUrl = mxcUrlToHttp(mx, url, useAuthentication) ?? url;
const fileContent = encInfo
? await downloadEncryptedMedia(mediaUrl, (encBuf) => decryptFile(encBuf, mimeType, encInfo))
: await downloadMedia(mediaUrl);

const text = fileContent.text();
setTextViewer(true);
return text;
}, [loadSrc])
}, [mx, useAuthentication, mimeType, encInfo, url])
);

return (
Expand Down Expand Up @@ -174,9 +176,12 @@ export function ReadPdfFile({ body, mimeType, url, encInfo, renderViewer }: Read

const [pdfState, loadPdf] = useAsyncCallback(
useCallback(async () => {
const httpUrl = await getFileSrcUrl(mxcUrlToHttp(mx, url, useAuthentication) ?? '', mimeType, encInfo);
const mediaUrl = mxcUrlToHttp(mx, url, useAuthentication) ?? url;
const fileContent = encInfo
? await downloadEncryptedMedia(mediaUrl, (encBuf) => decryptFile(encBuf, mimeType, encInfo))
: await downloadMedia(mediaUrl);
setPdfViewer(true);
return httpUrl;
return URL.createObjectURL(fileContent);
}, [mx, url, useAuthentication, mimeType, encInfo])
);

Expand Down Expand Up @@ -248,9 +253,14 @@ export function DownloadFile({ body, mimeType, url, info, encInfo }: DownloadFil

const [downloadState, download] = useAsyncCallback(
useCallback(async () => {
const httpUrl = await getFileSrcUrl(mxcUrlToHttp(mx, url, useAuthentication) ?? '', mimeType, encInfo);
FileSaver.saveAs(httpUrl, body);
return httpUrl;
const mediaUrl = mxcUrlToHttp(mx, url, useAuthentication) ?? url;
const fileContent = encInfo
? await downloadEncryptedMedia(mediaUrl, (encBuf) => decryptFile(encBuf, mimeType, encInfo))
: await downloadMedia(mediaUrl);

const fileURL = URL.createObjectURL(fileContent);
FileSaver.saveAs(fileURL, body);
return fileURL;
}, [mx, url, useAuthentication, mimeType, encInfo, body])
);

Expand Down
17 changes: 11 additions & 6 deletions src/app/components/message/content/ImageContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,11 @@ import { EncryptedAttachmentInfo } from 'browser-encrypt-attachment';
import { IImageInfo, MATRIX_BLUR_HASH_PROPERTY_NAME } from '../../../../types/matrix/common';
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
import { useMatrixClient } from '../../../hooks/useMatrixClient';
import { getFileSrcUrl } from './util';
import * as css from './style.css';
import { bytesToSize } from '../../../utils/common';
import { FALLBACK_MIMETYPE } from '../../../utils/mimeTypes';
import { stopPropagation } from '../../../utils/keyboard';
import { mxcUrlToHttp } from '../../../utils/matrix';
import { decryptFile, downloadEncryptedMedia, mxcUrlToHttp } from '../../../utils/matrix';
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';

type RenderViewerProps = {
Expand Down Expand Up @@ -79,10 +78,16 @@ export const ImageContent = as<'div', ImageContentProps>(
const [viewer, setViewer] = useState(false);

const [srcState, loadSrc] = useAsyncCallback(
useCallback(
() => getFileSrcUrl(mxcUrlToHttp(mx, url, useAuthentication) ?? '', mimeType || FALLBACK_MIMETYPE, encInfo),
[mx, url, useAuthentication, mimeType, encInfo]
)
useCallback(async () => {
const mediaUrl = mxcUrlToHttp(mx, url, useAuthentication) ?? url;
if (encInfo) {
const fileContent = await downloadEncryptedMedia(mediaUrl, (encBuf) =>
decryptFile(encBuf, mimeType ?? FALLBACK_MIMETYPE, encInfo)
);
return URL.createObjectURL(fileContent);
}
return mediaUrl;
}, [mx, url, useAuthentication, mimeType, encInfo])
);

const handleLoad = () => {
Expand Down
22 changes: 14 additions & 8 deletions src/app/components/message/content/ThumbnailContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { ReactNode, useCallback, useEffect } from 'react';
import { IThumbnailContent } from '../../../../types/matrix/common';
import { useMatrixClient } from '../../../hooks/useMatrixClient';
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
import { getFileSrcUrl } from './util';
import { mxcUrlToHttp } from '../../../utils/matrix';
import { decryptFile, downloadEncryptedMedia, mxcUrlToHttp } from '../../../utils/matrix';
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
import { FALLBACK_MIMETYPE } from '../../../utils/mimeTypes';

export type ThumbnailContentProps = {
info: IThumbnailContent;
Expand All @@ -15,17 +15,23 @@ export function ThumbnailContent({ info, renderImage }: ThumbnailContentProps) {
const useAuthentication = useMediaAuthentication();

const [thumbSrcState, loadThumbSrc] = useAsyncCallback(
useCallback(() => {
useCallback(async () => {
const thumbInfo = info.thumbnail_info;
const thumbMxcUrl = info.thumbnail_file?.url ?? info.thumbnail_url;
const encInfo = info.thumbnail_file;
if (typeof thumbMxcUrl !== 'string' || typeof thumbInfo?.mimetype !== 'string') {
throw new Error('Failed to load thumbnail');
}
return getFileSrcUrl(
mxcUrlToHttp(mx, thumbMxcUrl, useAuthentication) ?? '',
thumbInfo.mimetype,
info.thumbnail_file
);

const mediaUrl = mxcUrlToHttp(mx, thumbMxcUrl, useAuthentication) ?? thumbMxcUrl;
if (encInfo) {
const fileContent = await downloadEncryptedMedia(mediaUrl, (encBuf) =>
decryptFile(encBuf, thumbInfo.mimetype ?? FALLBACK_MIMETYPE, encInfo)
);
return URL.createObjectURL(fileContent);
}

return mediaUrl;
}, [mx, info, useAuthentication])
);

Expand Down
21 changes: 15 additions & 6 deletions src/app/components/message/content/VideoContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,14 @@ import {
import * as css from './style.css';
import { useMatrixClient } from '../../../hooks/useMatrixClient';
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
import { getFileSrcUrl } from './util';
import { bytesToSize } from '../../../../util/common';
import { millisecondsToMinutesAndSeconds } from '../../../utils/common';
import { mxcUrlToHttp } from '../../../utils/matrix';
import {
decryptFile,
downloadEncryptedMedia,
downloadMedia,
mxcUrlToHttp,
} from '../../../utils/matrix';
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';

type RenderVideoProps = {
Expand Down Expand Up @@ -70,10 +74,15 @@ export const VideoContent = as<'div', VideoContentProps>(
const [error, setError] = useState(false);

const [srcState, loadSrc] = useAsyncCallback(
useCallback(
() => getFileSrcUrl(mxcUrlToHttp(mx, url, useAuthentication) ?? '', mimeType, encInfo, true),
[mx, url, useAuthentication, mimeType, encInfo]
)
useCallback(async () => {
const mediaUrl = mxcUrlToHttp(mx, url, useAuthentication) ?? url;
const fileContent = encInfo
? await downloadEncryptedMedia(mediaUrl, (encBuf) =>
decryptFile(encBuf, mimeType, encInfo)
)
: await downloadMedia(mediaUrl);
return URL.createObjectURL(fileContent);
}, [mx, url, useAuthentication, mimeType, encInfo])
);

const handleLoad = () => {
Expand Down
30 changes: 0 additions & 30 deletions src/app/components/message/content/util.ts

This file was deleted.

Loading

0 comments on commit 03cc25e

Please sign in to comment.