From 1fe2dcaa2a5c7f6f4864a94ffb65f8eedd8a0129 Mon Sep 17 00:00:00 2001 From: AnyISalIn Date: Sat, 22 Jul 2023 10:00:27 +0800 Subject: [bug] If txt2img/img2img raises an exception, finally call state.end() Signed-off-by: AnyISalIn --- modules/api/api.py | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) (limited to 'modules/api/api.py') diff --git a/modules/api/api.py b/modules/api/api.py index 2a4cd8a2..9d73083f 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -333,14 +333,16 @@ class Api: p.outpath_grids = opts.outdir_txt2img_grids p.outpath_samples = opts.outdir_txt2img_samples - shared.state.begin(job="scripts_txt2img") - if selectable_scripts is not None: - p.script_args = script_args - processed = scripts.scripts_txt2img.run(p, *p.script_args) # Need to pass args as list here - else: - p.script_args = tuple(script_args) # Need to pass args as tuple here - processed = process_images(p) - shared.state.end() + try: + shared.state.begin(job="scripts_txt2img") + if selectable_scripts is not None: + p.script_args = script_args + processed = scripts.scripts_txt2img.run(p, *p.script_args) # Need to pass args as list here + else: + p.script_args = tuple(script_args) # Need to pass args as tuple here + processed = process_images(p) + finally: + shared.state.end() b64images = list(map(encode_pil_to_base64, processed.images)) if send_images else [] @@ -390,14 +392,16 @@ class Api: p.outpath_grids = opts.outdir_img2img_grids p.outpath_samples = opts.outdir_img2img_samples - shared.state.begin(job="scripts_img2img") - if selectable_scripts is not None: - p.script_args = script_args - processed = scripts.scripts_img2img.run(p, *p.script_args) # Need to pass args as list here - else: - p.script_args = tuple(script_args) # Need to pass args as tuple here - processed = process_images(p) - shared.state.end() + try: + shared.state.begin(job="scripts_img2img") + if selectable_scripts is not None: + p.script_args = script_args + processed = scripts.scripts_img2img.run(p, *p.script_args) # Need to pass args as list here + else: + p.script_args = tuple(script_args) # Need to pass args as tuple here + processed = process_images(p) + finally: + shared.state.end() b64images = list(map(encode_pil_to_base64, processed.images)) if send_images else [] -- cgit v1.2.3 From 1cbfafafd2dc68c4985da2ae88d7e30f7074fed9 Mon Sep 17 00:00:00 2001 From: AnyISalIn Date: Mon, 24 Jul 2023 19:45:08 +0800 Subject: feat: add refresh vae api Signed-off-by: AnyISalIn --- modules/api/api.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'modules/api/api.py') diff --git a/modules/api/api.py b/modules/api/api.py index 9d73083f..09166df2 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -15,7 +15,7 @@ from fastapi.encoders import jsonable_encoder from secrets import compare_digest import modules.shared as shared -from modules import sd_samplers, deepbooru, sd_hijack, images, scripts, ui, postprocessing, errors, restart +from modules import sd_samplers, deepbooru, sd_hijack, images, scripts, ui, postprocessing, errors, restart, shared_items from modules.api import models from modules.shared import opts from modules.processing import StableDiffusionProcessingTxt2Img, StableDiffusionProcessingImg2Img, process_images @@ -197,6 +197,7 @@ class Api: self.add_api_route("/sdapi/v1/prompt-styles", self.get_prompt_styles, methods=["GET"], response_model=List[models.PromptStyleItem]) self.add_api_route("/sdapi/v1/embeddings", self.get_embeddings, methods=["GET"], response_model=models.EmbeddingsResponse) self.add_api_route("/sdapi/v1/refresh-checkpoints", self.refresh_checkpoints, methods=["POST"]) + self.add_api_route("/sdapi/v1/refresh-vae", self.refresh_vae, methods=["POST"]) self.add_api_route("/sdapi/v1/create/embedding", self.create_embedding, methods=["POST"], response_model=models.CreateResponse) self.add_api_route("/sdapi/v1/create/hypernetwork", self.create_hypernetwork, methods=["POST"], response_model=models.CreateResponse) self.add_api_route("/sdapi/v1/preprocess", self.preprocess, methods=["POST"], response_model=models.PreprocessResponse) @@ -608,6 +609,10 @@ class Api: with self.queue_lock: shared.refresh_checkpoints() + def refresh_vae(self): + with self.queue_lock: + shared_items.refresh_vae_list() + def create_embedding(self, args: dict): try: shared.state.begin(job="create_embedding") -- cgit v1.2.3 From 6cc5a886ae9ff30148c43a95904accd191922afc Mon Sep 17 00:00:00 2001 From: caoxipeng Date: Fri, 28 Jul 2023 11:40:10 +0800 Subject: Add total_tqdm clear in the end of txt2img & img2img api. --- modules/api/api.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'modules/api/api.py') diff --git a/modules/api/api.py b/modules/api/api.py index 606db179..909ea976 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -343,6 +343,7 @@ class Api: processed = process_images(p) finally: shared.state.end() + shared.total_tqdm.clear() b64images = list(map(encode_pil_to_base64, processed.images)) if send_images else [] @@ -402,6 +403,7 @@ class Api: processed = process_images(p) finally: shared.state.end() + shared.total_tqdm.clear() b64images = list(map(encode_pil_to_base64, processed.images)) if send_images else [] -- cgit v1.2.3 From 353c876172a48c5044130249370c9303e611dd8b Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Mon, 14 Aug 2023 10:43:18 +0300 Subject: fix API always using -1 as seed --- modules/api/api.py | 2 ++ modules/processing.py | 4 +++- modules/processing_scripts/refiner.py | 2 +- modules/processing_scripts/seed.py | 2 +- modules/scripts.py | 12 +++++++++--- 5 files changed, 16 insertions(+), 6 deletions(-) (limited to 'modules/api/api.py') diff --git a/modules/api/api.py b/modules/api/api.py index 908c4514..fb2c2ce9 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -330,6 +330,7 @@ class Api: with self.queue_lock: with closing(StableDiffusionProcessingTxt2Img(sd_model=shared.sd_model, **args)) as p: + p.is_api = True p.scripts = script_runner p.outpath_grids = opts.outdir_txt2img_grids p.outpath_samples = opts.outdir_txt2img_samples @@ -390,6 +391,7 @@ class Api: with self.queue_lock: with closing(StableDiffusionProcessingImg2Img(sd_model=shared.sd_model, **args)) as p: p.init_images = [decode_base64_to_image(x) for x in init_images] + p.is_api = True p.scripts = script_runner p.outpath_grids = opts.outdir_img2img_grids p.outpath_samples = opts.outdir_img2img_samples diff --git a/modules/processing.py b/modules/processing.py index f34ba48a..b1eac2ab 100755 --- a/modules/processing.py +++ b/modules/processing.py @@ -194,6 +194,8 @@ class StableDiffusionProcessing: sd_vae_name: str = field(default=None, init=False) sd_vae_hash: str = field(default=None, init=False) + is_api: bool = field(default=False, init=False) + def __post_init__(self): if self.sampler_index is not None: print("sampler_index argument for StableDiffusionProcessing does not do anything; use sampler_name", file=sys.stderr) @@ -258,7 +260,7 @@ class StableDiffusionProcessing: def setup_scripts(self): self.scripts_setup_complete = True - self.scripts.setup_scrips(self) + self.scripts.setup_scrips(self, is_ui=not self.is_api) def comment(self, text): self.comments[text] = 1 diff --git a/modules/processing_scripts/refiner.py b/modules/processing_scripts/refiner.py index 3c5b37d2..b389c4ef 100644 --- a/modules/processing_scripts/refiner.py +++ b/modules/processing_scripts/refiner.py @@ -5,7 +5,7 @@ from modules.ui_common import create_refresh_button from modules.ui_components import InputAccordion -class ScriptRefiner(scripts.Script): +class ScriptRefiner(scripts.ScriptBuiltinUI): section = "accordions" create_group = False diff --git a/modules/processing_scripts/seed.py b/modules/processing_scripts/seed.py index 6ce3b2fc..6b6ff987 100644 --- a/modules/processing_scripts/seed.py +++ b/modules/processing_scripts/seed.py @@ -7,7 +7,7 @@ from modules.shared import cmd_opts from modules.ui_components import ToolButton -class ScriptSeed(scripts.ScriptBuiltin): +class ScriptSeed(scripts.ScriptBuiltinUI): section = "seed" create_group = False diff --git a/modules/scripts.py b/modules/scripts.py index cbdac2b5..fcab5d3a 100644 --- a/modules/scripts.py +++ b/modules/scripts.py @@ -68,6 +68,9 @@ class Script: on_after_component_elem_id = None """list of callbacks to be called after a component with an elem_id is created""" + setup_for_ui_only = False + """If true, the script setup will only be run in Gradio UI, not in API""" + def title(self): """this function should return the title of the script. This is what will be displayed in the dropdown menu.""" @@ -258,7 +261,6 @@ class Script: self.on_after_component_elem_id.append((elem_id, callback)) - def describe(self): """unused""" return "" @@ -280,7 +282,8 @@ class Script: pass -class ScriptBuiltin(Script): +class ScriptBuiltinUI(Script): + setup_for_ui_only = True def elem_id(self, item_id): """helper function to generate id for a HTML element, constructs final id out of tab and user-supplied item_id""" @@ -728,8 +731,11 @@ class ScriptRunner: except Exception: errors.report(f"Error running before_hr: {script.filename}", exc_info=True) - def setup_scrips(self, p): + def setup_scrips(self, p, *, is_ui=True): for script in self.alwayson_scripts: + if not is_ui and script.setup_for_ui_only: + continue + try: script_args = p.script_args[script.args_from:script.args_to] script.setup(p, *script_args) -- cgit v1.2.3 From 959f8b32d5994b981845bde051ca74139a12d78b Mon Sep 17 00:00:00 2001 From: Cade Schlaefli Date: Thu, 17 Aug 2023 20:48:17 -0500 Subject: fix issues with model refresh --- modules/api/api.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'modules/api/api.py') diff --git a/modules/api/api.py b/modules/api/api.py index fb2c2ce9..51207648 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -24,7 +24,6 @@ from modules.textual_inversion.preprocess import preprocess from modules.hypernetworks.hypernetwork import create_hypernetwork, train_hypernetwork from PIL import PngImagePlugin,Image from modules.sd_models import checkpoints_list, unload_model_weights, reload_model_weights, checkpoint_aliases -from modules.sd_vae import vae_dict from modules.sd_models_config import find_checkpoint_config_near_filename from modules.realesrgan_model import get_realesrgan_models from modules import devices @@ -567,10 +566,12 @@ class Api: ] def get_sd_models(self): - return [{"title": x.title, "model_name": x.model_name, "hash": x.shorthash, "sha256": x.sha256, "filename": x.filename, "config": find_checkpoint_config_near_filename(x)} for x in checkpoints_list.values()] + import modules.sd_models as sd_models + return [{"title": x.title, "model_name": x.model_name, "hash": x.shorthash, "sha256": x.sha256, "filename": x.filename, "config": find_checkpoint_config_near_filename(x)} for x in sd_models.checkpoints_list.values()] def get_sd_vaes(self): - return [{"model_name": x, "filename": vae_dict[x]} for x in vae_dict.keys()] + import modules.sd_vae as sd_vae + return [{"model_name": x, "filename": sd_vae.vae_dict[x]} for x in sd_vae.vae_dict.keys()] def get_hypernetworks(self): return [{"name": name, "path": shared.hypernetworks[name]} for name in shared.hypernetworks] -- cgit v1.2.3 From f9c2216ffaa72e2c435e38ca221fd8707936a9d5 Mon Sep 17 00:00:00 2001 From: Cade Schlaefli Date: Thu, 17 Aug 2023 21:14:14 -0500 Subject: remove unused import --- modules/api/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'modules/api/api.py') diff --git a/modules/api/api.py b/modules/api/api.py index 51207648..da1fdbca 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -23,7 +23,7 @@ from modules.textual_inversion.textual_inversion import create_embedding, train_ from modules.textual_inversion.preprocess import preprocess from modules.hypernetworks.hypernetwork import create_hypernetwork, train_hypernetwork from PIL import PngImagePlugin,Image -from modules.sd_models import checkpoints_list, unload_model_weights, reload_model_weights, checkpoint_aliases +from modules.sd_models import unload_model_weights, reload_model_weights, checkpoint_aliases from modules.sd_models_config import find_checkpoint_config_near_filename from modules.realesrgan_model import get_realesrgan_models from modules import devices -- cgit v1.2.3 From 4760c3c0b58059e9c655a6ea8360c65a4586a71b Mon Sep 17 00:00:00 2001 From: SpenserCai Date: Sat, 19 Aug 2023 12:19:21 +0800 Subject: api support get image from url --- modules/api/api.py | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'modules/api/api.py') diff --git a/modules/api/api.py b/modules/api/api.py index fb2c2ce9..4abfd9bd 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -57,6 +57,15 @@ def setUpscalers(req: dict): def decode_base64_to_image(encoding): + if encoding.startswith("http://") or encoding.startswith("https://"): + import requests + response = requests.get(encoding, timeout=30, headers={'user-agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36'}) + try: + image = Image.open(BytesIO(response.content)) + return image + except Exception as e: + raise HTTPException(status_code=500, detail="Invalid image url") from e + if encoding.startswith("data:image/"): encoding = encoding.split(";")[1].split(",")[1] try: -- cgit v1.2.3 From 268dc9b30813a62b7a2c6f666505696ceee40c09 Mon Sep 17 00:00:00 2001 From: akiba Date: Sun, 20 Aug 2023 21:41:27 +0800 Subject: fix potential ssrf attack in #12663 --- modules/api/api.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) (limited to 'modules/api/api.py') diff --git a/modules/api/api.py b/modules/api/api.py index 6e8d21a3..fed83f8f 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -4,6 +4,8 @@ import os import time import datetime import uvicorn +import ipaddress +import requests import gradio as gr from threading import Lock from io import BytesIO @@ -56,8 +58,27 @@ def setUpscalers(req: dict): def decode_base64_to_image(encoding): + def verify_url(url): + import socket + from urllib.parse import urlparse + try: + parsed_url = urlparse(url) + domain_name = parsed_url.netloc + host = socket.gethostbyname_ex(domain_name) + for ip in host[2]: + ip_addr = ipaddress.ip_address(ip) + # https://docs.python.org/3/library/ipaddress.html#ipaddress.IPv4Address.is_global + if not ip_addr.is_global: + return False + except Exception: + return False + + return True + if encoding.startswith("http://") or encoding.startswith("https://"): - import requests + if not verify_url(encoding): + raise HTTPException(status_code=500, detail="Invalid image url") + response = requests.get(encoding, timeout=30, headers={'user-agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36'}) try: image = Image.open(BytesIO(response.content)) -- cgit v1.2.3 From 76ae1019b96c4673231a116f0b20bb85ebec5666 Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Mon, 21 Aug 2023 07:38:07 +0300 Subject: add settings for http/https URLs in source images in api --- modules/api/api.py | 46 ++++++++++++++++++++++++++-------------------- modules/shared_options.py | 6 ++++++ 2 files changed, 32 insertions(+), 20 deletions(-) (limited to 'modules/api/api.py') diff --git a/modules/api/api.py b/modules/api/api.py index fed83f8f..42fbbe3d 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -57,29 +57,35 @@ def setUpscalers(req: dict): return reqDict -def decode_base64_to_image(encoding): - def verify_url(url): - import socket - from urllib.parse import urlparse - try: - parsed_url = urlparse(url) - domain_name = parsed_url.netloc - host = socket.gethostbyname_ex(domain_name) - for ip in host[2]: - ip_addr = ipaddress.ip_address(ip) - # https://docs.python.org/3/library/ipaddress.html#ipaddress.IPv4Address.is_global - if not ip_addr.is_global: - return False - except Exception: - return False - - return True +def verify_url(url): + """Returns True if the url refers to a global resource.""" + + import socket + from urllib.parse import urlparse + try: + parsed_url = urlparse(url) + domain_name = parsed_url.netloc + host = socket.gethostbyname_ex(domain_name) + for ip in host[2]: + ip_addr = ipaddress.ip_address(ip) + if not ip_addr.is_global: + return False + except Exception: + return False + return True + + +def decode_base64_to_image(encoding): if encoding.startswith("http://") or encoding.startswith("https://"): - if not verify_url(encoding): - raise HTTPException(status_code=500, detail="Invalid image url") + if not opts.api_enable_requests: + raise HTTPException(status_code=500, detail="Requests not allowed") + + if opts.api_forbid_local_requests and not verify_url(encoding): + raise HTTPException(status_code=500, detail="Request to local resource not allowed") - response = requests.get(encoding, timeout=30, headers={'user-agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36'}) + headers = {'user-agent': opts.api_useragent} if opts.api_useragent else {} + response = requests.get(encoding, timeout=30, headers=headers) try: image = Image.open(BytesIO(response.content)) return image diff --git a/modules/shared_options.py b/modules/shared_options.py index 8630d474..5f30e8e9 100644 --- a/modules/shared_options.py +++ b/modules/shared_options.py @@ -111,6 +111,12 @@ options_templates.update(options_section(('system', "System"), { "hide_ldm_prints": OptionInfo(True, "Prevent Stability-AI's ldm/sgm modules from printing noise to console."), })) +options_templates.update(options_section(('API', "API"), { + "api_enable_requests": OptionInfo(True, "Allow http:// and https:// URLs for input images in API"), + "api_forbid_local_requests": OptionInfo(True, "Forbid URLs to local resources"), + "api_useragent": OptionInfo("", "User agent for requests"), +})) + options_templates.update(options_section(('training', "Training"), { "unload_models_when_training": OptionInfo(False, "Move VAE and CLIP to RAM when training if possible. Saves VRAM."), "pin_memory": OptionInfo(False, "Turn on pin_memory for DataLoader. Makes training slightly faster but can increase memory usage."), -- cgit v1.2.3 From b4d21e7113384bbb592bbd79bca06aeb9e4d640a Mon Sep 17 00:00:00 2001 From: AUTOMATIC1111 <16777216c@gmail.com> Date: Mon, 21 Aug 2023 07:59:57 +0300 Subject: prevent API options from being changed via API --- modules/api/api.py | 2 +- modules/options.py | 19 +++++++++++++------ modules/shared_options.py | 6 +++--- 3 files changed, 17 insertions(+), 10 deletions(-) (limited to 'modules/api/api.py') diff --git a/modules/api/api.py b/modules/api/api.py index 42fbbe3d..e6edffe7 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -570,7 +570,7 @@ class Api: raise RuntimeError(f"model {checkpoint_name!r} not found") for k, v in req.items(): - shared.opts.set(k, v) + shared.opts.set(k, v, is_api=True) shared.opts.save(shared.config_filename) return diff --git a/modules/options.py b/modules/options.py index db1fb157..41d1b672 100644 --- a/modules/options.py +++ b/modules/options.py @@ -8,7 +8,7 @@ from modules.shared_cmd_options import cmd_opts class OptionInfo: - def __init__(self, default=None, label="", component=None, component_args=None, onchange=None, section=None, refresh=None, comment_before='', comment_after='', infotext=None): + def __init__(self, default=None, label="", component=None, component_args=None, onchange=None, section=None, refresh=None, comment_before='', comment_after='', infotext=None, restrict_api=False): self.default = default self.label = label self.component = component @@ -26,6 +26,9 @@ class OptionInfo: self.infotext = infotext + self.restrict_api = restrict_api + """If True, the setting will not be accessible via API""" + def link(self, label, url): self.comment_before += f"[{label}]" return self @@ -71,7 +74,7 @@ options_builtin_fields = {"data_labels", "data", "restricted_opts", "typemap"} class Options: typemap = {int: float} - def __init__(self, data_labels, restricted_opts): + def __init__(self, data_labels: dict[str, OptionInfo], restricted_opts): self.data_labels = data_labels self.data = {k: v.default for k, v in self.data_labels.items()} self.restricted_opts = restricted_opts @@ -113,14 +116,18 @@ class Options: return super(Options, self).__getattribute__(item) - def set(self, key, value): + def set(self, key, value, is_api=False): """sets an option and calls its onchange callback, returning True if the option changed and False otherwise""" oldval = self.data.get(key, None) if oldval == value: return False - if self.data_labels[key].do_not_save: + option = self.data_labels[key] + if option.do_not_save: + return False + + if is_api and option.restrict_api: return False try: @@ -128,9 +135,9 @@ class Options: except RuntimeError: return False - if self.data_labels[key].onchange is not None: + if option.onchange is not None: try: - self.data_labels[key].onchange() + option.onchange() except Exception as e: errors.display(e, f"changing setting {key} to {value}") setattr(self, key, oldval) diff --git a/modules/shared_options.py b/modules/shared_options.py index 5f30e8e9..6f1a738d 100644 --- a/modules/shared_options.py +++ b/modules/shared_options.py @@ -112,9 +112,9 @@ options_templates.update(options_section(('system', "System"), { })) options_templates.update(options_section(('API', "API"), { - "api_enable_requests": OptionInfo(True, "Allow http:// and https:// URLs for input images in API"), - "api_forbid_local_requests": OptionInfo(True, "Forbid URLs to local resources"), - "api_useragent": OptionInfo("", "User agent for requests"), + "api_enable_requests": OptionInfo(True, "Allow http:// and https:// URLs for input images in API", restrict_api=True), + "api_forbid_local_requests": OptionInfo(True, "Forbid URLs to local resources", restrict_api=True), + "api_useragent": OptionInfo("", "User agent for requests", restrict_api=True), })) options_templates.update(options_section(('training', "Training"), { -- cgit v1.2.3