feat: image integrity check

This commit is contained in:
Kieran 2023-12-14 11:50:03 +00:00
parent b38527ca1f
commit 6e5fba4f15
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
4 changed files with 18 additions and 9 deletions

View File

@ -10,7 +10,7 @@
"publicDir": "public/snort",
"httpCache": "",
"animalNamePlaceholders": false,
"defaultZapPoolFee": 0.5,
"defaultZapPoolFee": 1,
"bypassImgProxyError": false,
"features": {
"analytics": true,
@ -22,7 +22,7 @@
"moderation": true
},
"defaultPreferences": {
"checkSigs": false
"checkSigs": true
},
"noteCreatorToast": true,
"hideFromNavbar": ["/graph"],

View File

@ -47,6 +47,7 @@ const ImageElement = ({ url, meta, onMediaClick }: ImageElementProps) => {
<ProxyImg
key={url}
src={url}
sha256={meta?.sha256}
onClick={onMediaClick}
className={classNames("max-h-[80vh] w-full h-full object-contain object-center md:object-left", {
"md:max-h-[510px]": !meta,

View File

@ -5,13 +5,14 @@ import { getUrlHostname } from "@/SnortUtils";
type ProxyImgProps = HTMLProps<HTMLImageElement> & {
size?: number;
sha256?: string;
className?: string;
promptToLoadDirectly?: boolean;
missingImageElement?: ReactNode;
};
export const ProxyImg = forwardRef<HTMLImageElement, ProxyImgProps>(
({ size, className, promptToLoadDirectly, missingImageElement, ...props }: ProxyImgProps, ref) => {
({ size, className, promptToLoadDirectly, missingImageElement, sha256, ...props }: ProxyImgProps, ref) => {
const { proxy } = useImgProxy();
const [loadFailed, setLoadFailed] = useState(false);
const [bypass, setBypass] = useState(CONFIG.bypassImgProxyError);
@ -34,7 +35,7 @@ export const ProxyImg = forwardRef<HTMLImageElement, ProxyImgProps>(
</div>
);
}
const src = loadFailed && bypass ? props.src : proxy(props.src ?? "", size);
const src = loadFailed && bypass ? props.src : proxy(props.src ?? "", size, sha256);
if (!src || (loadFailed && !bypass)) return missingImageElement;
return (
<img
@ -48,7 +49,7 @@ export const ProxyImg = forwardRef<HTMLImageElement, ProxyImgProps>(
if (props.onError) {
props.onError(e);
} else {
console.error("Failed to proxy image ", props.src);
console.error("Failed to proxy image: ", props.src, e);
setLoadFailed(true);
}
}}

View File

@ -13,11 +13,11 @@ export default function useImgProxy() {
const settings = useLogin(s => s.appData.item.preferences.imgProxyConfig);
return {
proxy: (url: string, resize?: number) => proxyImg(url, settings, resize),
proxy: (url: string, resize?: number, sha256?: string) => proxyImg(url, settings, resize, sha256),
};
}
export function proxyImg(url: string, settings?: ImgProxySettings, resize?: number) {
export function proxyImg(url: string, settings?: ImgProxySettings, resize?: number, sha256?: string) {
const te = new TextEncoder();
function urlSafe(s: string) {
return s.replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
@ -33,10 +33,17 @@ export function proxyImg(url: string, settings?: ImgProxySettings, resize?: numb
}
if (!settings) return url;
if (url.startsWith("data:") || url.startsWith("blob:") || url.length == 0) return url;
const opt = resize ? `rs:fit:${resize}:${resize}/dpr:${window.devicePixelRatio}` : "";
const opts = [];
if (sha256) {
opts.push(`hs:sha256:${sha256}`);
}
if (resize) {
opts.push(`rs:fit:${resize}:${resize}`);
opts.push(`dpr:${window.devicePixelRatio}`);
}
const urlBytes = te.encode(url);
const urlEncoded = urlSafe(base64.encode(urlBytes));
const path = `/${opt}/${urlEncoded}`;
const path = `/${opts.join("/")}/${urlEncoded}`;
const sig = signUrl(path);
return `${new URL(settings.url).toString()}${sig}${path}`;
}