From b33e4d0a6accd403f9f38fc725edcdbd47d7ec0a Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sun, 24 Mar 2024 09:40:13 -0400 Subject: [PATCH] gallery urlencode/decode filenames --- javascript/gallery.js | 61 ++++++++++++++++++++++++++---------------- modules/api/gallery.py | 48 +++++++++++++++++++-------------- 2 files changed, 66 insertions(+), 43 deletions(-) diff --git a/javascript/gallery.js b/javascript/gallery.js index 3a9605e82..172023615 100644 --- a/javascript/gallery.js +++ b/javascript/gallery.js @@ -18,7 +18,7 @@ const el = { class GalleryFolder extends HTMLElement { constructor(name) { super(); - this.name = name; + this.name = decodeURI(name); this.shadow = this.attachShadow({ mode: 'open' }); } @@ -69,19 +69,28 @@ async function createThumb(img) { } async function delayFetchThumb(fn) { - while (outstanding > 10) await new Promise((resolve) => setTimeout(resolve, 50)); // eslint-disable-line no-promise-executor-return + while (outstanding > 16) await new Promise((resolve) => setTimeout(resolve, 50)); // eslint-disable-line no-promise-executor-return outstanding++; - const res = await fetch(`/sdapi/v1/browser/thumb?file=${fn}`, { priority: 'low' }); + const res = await fetch(`/sdapi/v1/browser/thumb?file=${encodeURI(fn)}`, { priority: 'low' }); + if (!res.ok) { + console.error(res.statusText); + outstanding--; + return undefined; + } const json = await res.json(); outstanding--; + if (!res || !json || json.error) { + console.error(json.error); + return undefined; + } return json; } class GalleryFile extends HTMLElement { constructor({ folder, file, size, mtime }) { super(); - this.folder = folder; - this.name = file; + this.folder = decodeURI(folder); + this.name = decodeURI(file); this.size = size; this.mtime = new Date(1000 * mtime); this.hash = undefined; @@ -126,6 +135,7 @@ class GalleryFile extends HTMLElement { } } }; + let ok = true; if (cache) { img.src = cache.img; this.exif = cache.exif; @@ -134,28 +144,33 @@ class GalleryFile extends HTMLElement { } else { try { const json = await delayFetchThumb(this.src); - img.src = json.data; - this.exif = json.exif; - this.width = json.width; - this.height = json.height; - await idbAdd({ - hash: this.hash, - folder: this.folder, - file: this.name, - size: this.size, - mtime: this.mtime, - width: this.width, - height: this.height, - src: this.src, - exif: this.exif, - img: img.src, - // exif: await getExif(img), // alternative client-side exif - // img: await createThumb(img), // alternative client-side thumb - }); + if (!json) { + ok = false; + } else { + img.src = json.data; + this.exif = json.exif; + this.width = json.width; + this.height = json.height; + await idbAdd({ + hash: this.hash, + folder: this.folder, + file: this.name, + size: this.size, + mtime: this.mtime, + width: this.width, + height: this.height, + src: this.src, + exif: this.exif, + img: img.src, + // exif: await getExif(img), // alternative client-side exif + // img: await createThumb(img), // alternative client-side thumb + }); + } } catch (err) { // thumb fetch failed so assign actual image img.src = `file=${this.src}`; } } + if (!ok) return; img.onclick = () => { currentImage = this.src; el.btnSend.click(); diff --git a/modules/api/gallery.py b/modules/api/gallery.py index ce717ddb8..bb60194f6 100644 --- a/modules/api/gallery.py +++ b/modules/api/gallery.py @@ -3,6 +3,7 @@ import os import time import base64 from typing import List +from urllib.parse import quote, unquote from fastapi import FastAPI from fastapi.responses import JSONResponse from starlette.websockets import WebSocket, WebSocketState, WebSocketDisconnect @@ -83,29 +84,36 @@ def register_api(app: FastAPI): # register api for f in folders: if os.path.isabs(f) and f not in shared.demo.allowed_paths: debug(f'Browser folders allow: {f}') - shared.demo.allowed_paths.append(f) + shared.demo.allowed_paths.append(quote(f)) debug(f'Browser folders: {folders}') return JSONResponse(content=folders) @app.get("/sdapi/v1/browser/thumb", response_model=dict) async def get_thumb(file: str): - image = Image.open(file) - geninfo, _items = images.read_info_from_image(image) - h = shared.opts.extra_networks_card_size - w = shared.opts.extra_networks_card_size if shared.opts.browser_fixed_width else image.width * h // image.height - width, height = image.width, image.height - image.thumbnail((w, h), Image.Resampling.HAMMING) - buffered = io.BytesIO() - image.save(buffered, format='jpeg') - data_url = f'data:image/jpeg;base64,{base64.b64encode(buffered.getvalue()).decode("ascii")}' - image.close() - content = { - 'exif': geninfo, - 'data': data_url, - 'width': width, - 'height': height, - } - return JSONResponse(content=content) + try: + decoded = unquote(file) + image = Image.open(decoded) + geninfo, _items = images.read_info_from_image(image) + h = shared.opts.extra_networks_card_size + w = shared.opts.extra_networks_card_size if shared.opts.browser_fixed_width else image.width * h // image.height + width, height = image.width, image.height + image = image.convert('RGB') + image.thumbnail((w, h), Image.Resampling.HAMMING) + buffered = io.BytesIO() + image.save(buffered, format='jpeg') + data_url = f'data:image/jpeg;base64,{base64.b64encode(buffered.getvalue()).decode("ascii")}' + image.close() + content = { + 'exif': geninfo, + 'data': data_url, + 'width': width, + 'height': height, + } + return JSONResponse(content=content) + except Exception as e: + shared.log.error(f'Gallery: {file} {e}') + content = { 'error': str(e) } + return JSONResponse(content=content) @app.websocket("/sdapi/v1/browser/files") async def ws_files(ws: WebSocket): @@ -119,8 +127,8 @@ def register_api(app: FastAPI): # register api file = os.path.relpath(f, folder) stat = os.stat(f) dct = { - 'folder': folder, - 'file': file, + 'folder': quote(folder), + 'file': quote(file), 'size': stat.st_size, 'mtime': stat.st_mtime, }