2024-10-22 14:16:44 +08:00
|
|
|
#
|
|
|
|
|
# Copyright 2024 The InfiniFlow Authors. All Rights Reserved.
|
|
|
|
|
#
|
|
|
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
|
# you may not use this file except in compliance with the License.
|
|
|
|
|
# You may obtain a copy of the License at
|
|
|
|
|
#
|
|
|
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
#
|
|
|
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
|
# See the License for the specific language governing permissions and
|
|
|
|
|
# limitations under the License.
|
|
|
|
|
#
|
|
|
|
|
import json
|
2025-07-30 19:41:09 +08:00
|
|
|
import logging
|
|
|
|
|
import os
|
2024-10-29 13:19:01 +08:00
|
|
|
import re
|
2025-07-30 19:41:09 +08:00
|
|
|
import time
|
2024-10-22 14:16:44 +08:00
|
|
|
from abc import ABC
|
2026-04-20 14:19:42 +08:00
|
|
|
from functools import partial
|
2026-06-27 22:23:42 -07:00
|
|
|
from urllib.parse import urlparse
|
2025-10-09 16:05:58 +08:00
|
|
|
|
2024-10-22 14:16:44 +08:00
|
|
|
import requests
|
2025-07-30 19:41:09 +08:00
|
|
|
|
2025-10-09 16:05:58 +08:00
|
|
|
from agent.component.base import ComponentBase, ComponentParamBase
|
2025-11-04 11:51:12 +08:00
|
|
|
from common.connection_utils import timeout
|
Harden closed-advisory fixes (#16409)
## Summary
- harden reopened advisory fixes across REST connector, invoke, document
downloads, and markdown rendering
- add targeted regression coverage for redirect-safe SSRF handling,
invoke SSRF checks, document access control, and markdown sanitization
- verify each referenced GHSA against the original GitHub advisory text
and align the closed-advisory plan with the implemented remediation
## What changed
- add tenant access checks to document download endpoints to avoid
cross-tenant document disclosure
- add per-hop SSRF validation, DNS pinning, redirect handling, and
redirect limits to the REST API connector
- ensure invoke requests validate and pin the resolved host and never
follow redirects implicitly
- keep the generic rate-limited request path wrapped, not just GET and
POST helpers
- sanitize markdown HTML before rendering in the highlight markdown
component
## Validation
- `cd web && npm test -- --runInBand
src/components/highlight-markdown/__tests__/index.test.tsx`
- `.venv/bin/python -m pytest -q
test/unit_test/data_source/test_rest_api_connector.py`
- targeted `test/testcases/test_web_api/...` unit additions were
reviewed, but the suite cannot be executed end-to-end in this
environment because parent `test/testcases/conftest.py` requires a local
service on `127.0.0.1:9380`
## Notes
- all GHSA entries referenced by the plan were checked against the
original GitHub advisory text, not sampled
- the closed-advisory plan document was updated locally during review,
but is intentionally not included in this PR
2026-06-28 11:17:54 +08:00
|
|
|
from common.ssrf_guard import assert_url_is_safe, pin_dns
|
2024-10-29 13:19:01 +08:00
|
|
|
from deepdoc.parser import HtmlParser
|
2024-10-22 14:16:44 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class InvokeParam(ComponentParamBase):
|
|
|
|
|
"""
|
2026-04-20 14:19:42 +08:00
|
|
|
Define the Invoke component parameters.
|
2024-10-22 14:16:44 +08:00
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
|
super().__init__()
|
|
|
|
|
self.proxy = None
|
|
|
|
|
self.headers = ""
|
|
|
|
|
self.method = "get"
|
|
|
|
|
self.variables = []
|
|
|
|
|
self.url = ""
|
|
|
|
|
self.timeout = 60
|
2024-10-29 13:19:01 +08:00
|
|
|
self.clean_html = False
|
2026-04-20 14:19:42 +08:00
|
|
|
self.datatype = "json"
|
2024-10-22 14:16:44 +08:00
|
|
|
|
|
|
|
|
def check(self):
|
2025-10-09 16:05:58 +08:00
|
|
|
self.check_valid_value(self.method.lower(), "Type of content from the crawler", ["get", "post", "put"])
|
2024-10-22 14:16:44 +08:00
|
|
|
self.check_empty(self.url, "End point URL")
|
|
|
|
|
self.check_positive_integer(self.timeout, "Timeout time in second")
|
2024-10-29 13:19:01 +08:00
|
|
|
self.check_boolean(self.clean_html, "Clean HTML")
|
2025-10-09 16:05:58 +08:00
|
|
|
self.check_valid_value(self.datatype.lower(), "Data post type", ["json", "formdata"]) # Check for valid datapost value
|
2024-10-22 14:16:44 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class Invoke(ComponentBase, ABC):
|
|
|
|
|
component_name = "Invoke"
|
2026-04-20 14:19:42 +08:00
|
|
|
header_variable_ref_patt = r"\{([a-zA-Z_][a-zA-Z0-9_.@-]*)\}"
|
2024-10-22 14:16:44 +08:00
|
|
|
|
Harden closed-advisory fixes (#16409)
## Summary
- harden reopened advisory fixes across REST connector, invoke, document
downloads, and markdown rendering
- add targeted regression coverage for redirect-safe SSRF handling,
invoke SSRF checks, document access control, and markdown sanitization
- verify each referenced GHSA against the original GitHub advisory text
and align the closed-advisory plan with the implemented remediation
## What changed
- add tenant access checks to document download endpoints to avoid
cross-tenant document disclosure
- add per-hop SSRF validation, DNS pinning, redirect handling, and
redirect limits to the REST API connector
- ensure invoke requests validate and pin the resolved host and never
follow redirects implicitly
- keep the generic rate-limited request path wrapped, not just GET and
POST helpers
- sanitize markdown HTML before rendering in the highlight markdown
component
## Validation
- `cd web && npm test -- --runInBand
src/components/highlight-markdown/__tests__/index.test.tsx`
- `.venv/bin/python -m pytest -q
test/unit_test/data_source/test_rest_api_connector.py`
- targeted `test/testcases/test_web_api/...` unit additions were
reviewed, but the suite cannot be executed end-to-end in this
environment because parent `test/testcases/conftest.py` requires a local
service on `127.0.0.1:9380`
## Notes
- all GHSA entries referenced by the plan were checked against the
original GitHub advisory text, not sampled
- the closed-advisory plan document was updated locally during review,
but is intentionally not included in this PR
2026-06-28 11:17:54 +08:00
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
|
super().__init__(*args, **kwargs)
|
|
|
|
|
self._pinned_hostname: str | None = None
|
|
|
|
|
self._pinned_ip: str | None = None
|
|
|
|
|
|
2026-04-16 16:52:34 +08:00
|
|
|
@staticmethod
|
|
|
|
|
def _coerce_json_arg_if_possible(key, value):
|
|
|
|
|
raw_value = value
|
|
|
|
|
if isinstance(value, str):
|
|
|
|
|
try:
|
|
|
|
|
value = json.loads(value)
|
|
|
|
|
logging.debug(
|
|
|
|
|
"Invoke JSON arg coercion succeeded. key=%s parsed_type=%s",
|
|
|
|
|
key,
|
|
|
|
|
type(value).__name__,
|
|
|
|
|
)
|
|
|
|
|
except json.JSONDecodeError as exc:
|
|
|
|
|
logging.info(
|
|
|
|
|
"Invoke JSON arg coercion skipped; value is not valid JSON. key=%s raw=%r error=%s",
|
|
|
|
|
key,
|
|
|
|
|
raw_value,
|
|
|
|
|
exc,
|
|
|
|
|
)
|
|
|
|
|
return raw_value
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
json.dumps(value, allow_nan=False)
|
|
|
|
|
except (TypeError, ValueError) as exc:
|
|
|
|
|
logging.warning(
|
|
|
|
|
"Invoke JSON arg is not JSON-serializable. key=%s value_type=%s value=%r error=%s",
|
|
|
|
|
key,
|
|
|
|
|
type(value).__name__,
|
|
|
|
|
value,
|
|
|
|
|
exc,
|
|
|
|
|
)
|
|
|
|
|
raise ValueError(f"Invoke JSON argument '{key}' is not JSON-serializable.") from exc
|
|
|
|
|
|
|
|
|
|
return value
|
|
|
|
|
|
2026-03-18 14:22:13 +08:00
|
|
|
def get_input_form(self) -> dict[str, dict]:
|
|
|
|
|
res = {}
|
|
|
|
|
for item in self._param.variables or []:
|
|
|
|
|
if not isinstance(item, dict):
|
|
|
|
|
continue
|
|
|
|
|
ref = (item.get("ref") or "").strip()
|
|
|
|
|
if not ref or ref in res:
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
elements = self.get_input_elements_from_text("{" + ref + "}")
|
|
|
|
|
element = elements.get(ref, {})
|
|
|
|
|
res[ref] = {
|
|
|
|
|
"type": "line",
|
|
|
|
|
"name": element.get("name") or item.get("key") or ref,
|
|
|
|
|
}
|
|
|
|
|
return res
|
|
|
|
|
|
2026-04-20 14:19:42 +08:00
|
|
|
def _resolve_variable_value(self, variable_name: str, kwargs: dict | None = None):
|
|
|
|
|
kwargs = kwargs or {}
|
|
|
|
|
value = kwargs.get(variable_name, self._canvas.get_variable_value(variable_name))
|
|
|
|
|
if isinstance(value, partial):
|
|
|
|
|
value = "".join(value())
|
|
|
|
|
self.set_input_value(variable_name, value)
|
|
|
|
|
return "" if value is None else value
|
|
|
|
|
|
|
|
|
|
def _render_template(self, content: str, pattern: str, kwargs: dict | None = None, *, flags: int = 0) -> str:
|
|
|
|
|
content = content or ""
|
|
|
|
|
if not content:
|
|
|
|
|
return content
|
|
|
|
|
|
|
|
|
|
def replace_variable(match_obj):
|
|
|
|
|
return str(self._resolve_variable_value(match_obj.group(1), kwargs))
|
|
|
|
|
|
|
|
|
|
return re.sub(pattern, replace_variable, content, flags=flags)
|
2025-11-11 17:36:48 +08:00
|
|
|
|
2026-04-20 14:19:42 +08:00
|
|
|
def _resolve_template_text(self, content: str, kwargs: dict | None = None) -> str:
|
|
|
|
|
return self._render_template(content, self.variable_ref_patt, kwargs, flags=re.DOTALL)
|
|
|
|
|
|
|
|
|
|
def _resolve_header_text(self, content: str, kwargs: dict | None = None) -> str:
|
|
|
|
|
# Headers support plain {token} placeholders, so they cannot reuse the canvas variable regex.
|
|
|
|
|
return self._render_template(content, self.header_variable_ref_patt, kwargs)
|
|
|
|
|
|
|
|
|
|
def _resolve_arg_value(self, para: dict, kwargs: dict) -> object:
|
2026-04-20 16:37:33 +08:00
|
|
|
ref = (para.get("ref") or "").strip()
|
|
|
|
|
if ref and (ref in kwargs or self._canvas.get_variable_value(ref) is not None):
|
|
|
|
|
return self._resolve_variable_value(ref, kwargs)
|
|
|
|
|
|
2026-04-20 14:19:42 +08:00
|
|
|
if para.get("value") is not None:
|
|
|
|
|
value = para["value"]
|
|
|
|
|
if isinstance(value, str):
|
|
|
|
|
return self._resolve_template_text(value, kwargs)
|
|
|
|
|
return value
|
2026-04-20 16:37:33 +08:00
|
|
|
|
|
|
|
|
if ref:
|
|
|
|
|
return self._resolve_variable_value(ref, kwargs)
|
|
|
|
|
|
|
|
|
|
return ""
|
2026-04-20 14:19:42 +08:00
|
|
|
|
|
|
|
|
def _is_json_mode(self) -> bool:
|
|
|
|
|
return self._param.datatype.lower() == "json"
|
|
|
|
|
|
|
|
|
|
def _build_request_args(self, kwargs: dict) -> dict:
|
2024-10-22 14:16:44 +08:00
|
|
|
args = {}
|
|
|
|
|
for para in self._param.variables:
|
2026-04-16 16:52:34 +08:00
|
|
|
key = para["key"]
|
2026-04-20 14:19:42 +08:00
|
|
|
value = self._resolve_arg_value(para, kwargs)
|
|
|
|
|
if self._is_json_mode():
|
|
|
|
|
# JSON mode accepts stringified JSON so complex payloads can be passed through variables.
|
|
|
|
|
value = self._coerce_json_arg_if_possible(key, value)
|
|
|
|
|
args[key] = value
|
2024-10-22 14:16:44 +08:00
|
|
|
|
2026-04-16 16:52:34 +08:00
|
|
|
if para.get("ref"):
|
2026-04-20 14:19:42 +08:00
|
|
|
self.set_input_value(para["ref"], value)
|
|
|
|
|
return args
|
2025-10-09 16:05:58 +08:00
|
|
|
|
2026-04-20 14:19:42 +08:00
|
|
|
def _build_url(self, kwargs: dict) -> str:
|
|
|
|
|
url = self._resolve_template_text(self._param.url.strip(), kwargs)
|
|
|
|
|
if not url.startswith(("http://", "https://")):
|
|
|
|
|
url = "http://" + url
|
Harden closed-advisory fixes (#16409)
## Summary
- harden reopened advisory fixes across REST connector, invoke, document
downloads, and markdown rendering
- add targeted regression coverage for redirect-safe SSRF handling,
invoke SSRF checks, document access control, and markdown sanitization
- verify each referenced GHSA against the original GitHub advisory text
and align the closed-advisory plan with the implemented remediation
## What changed
- add tenant access checks to document download endpoints to avoid
cross-tenant document disclosure
- add per-hop SSRF validation, DNS pinning, redirect handling, and
redirect limits to the REST API connector
- ensure invoke requests validate and pin the resolved host and never
follow redirects implicitly
- keep the generic rate-limited request path wrapped, not just GET and
POST helpers
- sanitize markdown HTML before rendering in the highlight markdown
component
## Validation
- `cd web && npm test -- --runInBand
src/components/highlight-markdown/__tests__/index.test.tsx`
- `.venv/bin/python -m pytest -q
test/unit_test/data_source/test_rest_api_connector.py`
- targeted `test/testcases/test_web_api/...` unit additions were
reviewed, but the suite cannot be executed end-to-end in this
environment because parent `test/testcases/conftest.py` requires a local
service on `127.0.0.1:9380`
## Notes
- all GHSA entries referenced by the plan were checked against the
original GitHub advisory text, not sampled
- the closed-advisory plan document was updated locally during review,
but is intentionally not included in this PR
2026-06-28 11:17:54 +08:00
|
|
|
hostname, ip = assert_url_is_safe(url)
|
|
|
|
|
self._pinned_hostname = hostname
|
|
|
|
|
self._pinned_ip = ip
|
2026-04-20 14:19:42 +08:00
|
|
|
return url
|
2025-10-09 16:05:58 +08:00
|
|
|
|
2026-04-20 14:19:42 +08:00
|
|
|
def _build_headers(self, kwargs: dict) -> dict:
|
|
|
|
|
if not self._param.headers:
|
|
|
|
|
return {}
|
2026-03-18 17:38:20 +03:00
|
|
|
|
2026-04-20 14:19:42 +08:00
|
|
|
headers = json.loads(self._param.headers)
|
|
|
|
|
if not isinstance(headers, dict):
|
|
|
|
|
raise ValueError("Invoke headers must be a JSON object.")
|
2025-10-09 16:05:58 +08:00
|
|
|
|
2026-04-25 15:30:15 +09:00
|
|
|
return {key: self._resolve_header_text(value, kwargs) if isinstance(value, str) else value for key, value in headers.items()}
|
2024-10-22 14:16:44 +08:00
|
|
|
|
2026-06-27 22:23:42 -07:00
|
|
|
@staticmethod
|
|
|
|
|
def _ssrf_log_target(url: str) -> str:
|
|
|
|
|
parsed = urlparse(url)
|
|
|
|
|
if not parsed.scheme or not parsed.hostname:
|
|
|
|
|
return "invalid-url"
|
|
|
|
|
return f"{parsed.scheme}://{parsed.hostname}"
|
|
|
|
|
|
|
|
|
|
def _normalize_proxy_url(self) -> str | None:
|
|
|
|
|
proxy = (self._param.proxy or "").strip()
|
|
|
|
|
if not re.sub(r"https?:?/?/?", "", proxy):
|
|
|
|
|
return None
|
|
|
|
|
if not proxy.startswith(("http://", "https://")):
|
|
|
|
|
proxy = "http://" + proxy
|
|
|
|
|
return proxy
|
|
|
|
|
|
2026-04-20 14:19:42 +08:00
|
|
|
def _build_proxies(self) -> dict | None:
|
2026-06-27 22:23:42 -07:00
|
|
|
proxy_url = self._normalize_proxy_url()
|
|
|
|
|
if not proxy_url:
|
2026-04-20 14:19:42 +08:00
|
|
|
return None
|
|
|
|
|
return {"http": self._param.proxy, "https": self._param.proxy}
|
|
|
|
|
|
|
|
|
|
def _send_request(self, url: str, args: dict, headers: dict, proxies: dict | None):
|
2024-10-22 14:16:44 +08:00
|
|
|
method = self._param.method.lower()
|
2026-04-20 14:19:42 +08:00
|
|
|
request = getattr(requests, method)
|
|
|
|
|
request_kwargs = {
|
|
|
|
|
"url": url,
|
|
|
|
|
"headers": headers,
|
|
|
|
|
"proxies": proxies,
|
|
|
|
|
"timeout": self._param.timeout,
|
Harden closed-advisory fixes (#16409)
## Summary
- harden reopened advisory fixes across REST connector, invoke, document
downloads, and markdown rendering
- add targeted regression coverage for redirect-safe SSRF handling,
invoke SSRF checks, document access control, and markdown sanitization
- verify each referenced GHSA against the original GitHub advisory text
and align the closed-advisory plan with the implemented remediation
## What changed
- add tenant access checks to document download endpoints to avoid
cross-tenant document disclosure
- add per-hop SSRF validation, DNS pinning, redirect handling, and
redirect limits to the REST API connector
- ensure invoke requests validate and pin the resolved host and never
follow redirects implicitly
- keep the generic rate-limited request path wrapped, not just GET and
POST helpers
- sanitize markdown HTML before rendering in the highlight markdown
component
## Validation
- `cd web && npm test -- --runInBand
src/components/highlight-markdown/__tests__/index.test.tsx`
- `.venv/bin/python -m pytest -q
test/unit_test/data_source/test_rest_api_connector.py`
- targeted `test/testcases/test_web_api/...` unit additions were
reviewed, but the suite cannot be executed end-to-end in this
environment because parent `test/testcases/conftest.py` requires a local
service on `127.0.0.1:9380`
## Notes
- all GHSA entries referenced by the plan were checked against the
original GitHub advisory text, not sampled
- the closed-advisory plan document was updated locally during review,
but is intentionally not included in this PR
2026-06-28 11:17:54 +08:00
|
|
|
"allow_redirects": False,
|
2026-04-20 14:19:42 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# GET sends query params; POST/PUT send either JSON or form data based on datatype.
|
|
|
|
|
if method == "get":
|
|
|
|
|
request_kwargs["params"] = args
|
|
|
|
|
return request(**request_kwargs)
|
|
|
|
|
|
|
|
|
|
body_key = "json" if self._is_json_mode() else "data"
|
|
|
|
|
request_kwargs[body_key] = args
|
|
|
|
|
return request(**request_kwargs)
|
|
|
|
|
|
|
|
|
|
def _format_response(self, response) -> str:
|
|
|
|
|
if not self._param.clean_html:
|
|
|
|
|
return response.text
|
|
|
|
|
|
|
|
|
|
# HtmlParser keeps the Invoke output text-focused when the endpoint returns HTML.
|
|
|
|
|
sections = HtmlParser()(None, response.content)
|
|
|
|
|
return "\n".join(sections)
|
2026-04-25 15:30:15 +09:00
|
|
|
|
2026-04-20 14:19:42 +08:00
|
|
|
@timeout(int(os.environ.get("COMPONENT_EXEC_TIMEOUT", 3)))
|
|
|
|
|
def _invoke(self, **kwargs):
|
|
|
|
|
if self.check_if_canceled("Invoke processing"):
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
args = self._build_request_args(kwargs)
|
|
|
|
|
headers = self._build_headers(kwargs)
|
|
|
|
|
proxies = self._build_proxies()
|
2026-06-27 22:23:42 -07:00
|
|
|
proxy_hostname = proxy_ip = None
|
|
|
|
|
|
|
|
|
|
if proxies:
|
|
|
|
|
proxy_url = self._normalize_proxy_url()
|
|
|
|
|
try:
|
|
|
|
|
proxy_hostname, proxy_ip = assert_url_is_safe(proxy_url)
|
|
|
|
|
except ValueError as exc:
|
|
|
|
|
logging.warning(
|
|
|
|
|
"Invoke SSRF guard blocked proxy=%s: %s",
|
|
|
|
|
self._ssrf_log_target(proxy_url),
|
|
|
|
|
exc,
|
|
|
|
|
)
|
|
|
|
|
self.set_output("_ERROR", "URL not valid")
|
|
|
|
|
return "Http request error: URL not valid"
|
2026-04-20 14:19:42 +08:00
|
|
|
|
|
|
|
|
last_error = None
|
2025-10-09 16:05:58 +08:00
|
|
|
for _ in range(self._param.max_retries + 1):
|
2025-11-11 17:36:48 +08:00
|
|
|
if self.check_if_canceled("Invoke processing"):
|
|
|
|
|
return
|
|
|
|
|
|
2025-07-30 19:41:09 +08:00
|
|
|
try:
|
Harden closed-advisory fixes (#16409)
## Summary
- harden reopened advisory fixes across REST connector, invoke, document
downloads, and markdown rendering
- add targeted regression coverage for redirect-safe SSRF handling,
invoke SSRF checks, document access control, and markdown sanitization
- verify each referenced GHSA against the original GitHub advisory text
and align the closed-advisory plan with the implemented remediation
## What changed
- add tenant access checks to document download endpoints to avoid
cross-tenant document disclosure
- add per-hop SSRF validation, DNS pinning, redirect handling, and
redirect limits to the REST API connector
- ensure invoke requests validate and pin the resolved host and never
follow redirects implicitly
- keep the generic rate-limited request path wrapped, not just GET and
POST helpers
- sanitize markdown HTML before rendering in the highlight markdown
component
## Validation
- `cd web && npm test -- --runInBand
src/components/highlight-markdown/__tests__/index.test.tsx`
- `.venv/bin/python -m pytest -q
test/unit_test/data_source/test_rest_api_connector.py`
- targeted `test/testcases/test_web_api/...` unit additions were
reviewed, but the suite cannot be executed end-to-end in this
environment because parent `test/testcases/conftest.py` requires a local
service on `127.0.0.1:9380`
## Notes
- all GHSA entries referenced by the plan were checked against the
original GitHub advisory text, not sampled
- the closed-advisory plan document was updated locally during review,
but is intentionally not included in this PR
2026-06-28 11:17:54 +08:00
|
|
|
url = self._build_url(kwargs)
|
|
|
|
|
if not self._pinned_hostname or not self._pinned_ip:
|
|
|
|
|
raise ValueError("Invoke URL was not validated before request.")
|
|
|
|
|
with pin_dns(self._pinned_hostname, self._pinned_ip):
|
2026-06-27 22:23:42 -07:00
|
|
|
if proxy_hostname and proxy_ip:
|
|
|
|
|
with pin_dns(proxy_hostname, proxy_ip):
|
|
|
|
|
response = self._send_request(url, args, headers, proxies)
|
|
|
|
|
else:
|
|
|
|
|
response = self._send_request(url, args, headers, proxies)
|
2026-04-20 14:19:42 +08:00
|
|
|
result = self._format_response(response)
|
|
|
|
|
self.set_output("result", result)
|
|
|
|
|
return result
|
2026-06-27 22:23:42 -07:00
|
|
|
except ValueError as e:
|
|
|
|
|
logging.warning(
|
|
|
|
|
"Invoke SSRF guard blocked url=%s: %s",
|
|
|
|
|
self._ssrf_log_target(locals().get("url", self._param.url)),
|
|
|
|
|
e,
|
|
|
|
|
)
|
|
|
|
|
self.set_output("_ERROR", "URL not valid")
|
|
|
|
|
return "Http request error: URL not valid"
|
2025-07-30 19:41:09 +08:00
|
|
|
except Exception as e:
|
2025-11-11 17:36:48 +08:00
|
|
|
if self.check_if_canceled("Invoke processing"):
|
|
|
|
|
return
|
|
|
|
|
|
2026-04-20 14:19:42 +08:00
|
|
|
last_error = e
|
2025-07-30 19:41:09 +08:00
|
|
|
logging.exception(f"Http request error: {e}")
|
|
|
|
|
time.sleep(self._param.delay_after_error)
|
|
|
|
|
|
2026-04-20 14:19:42 +08:00
|
|
|
if last_error:
|
|
|
|
|
self.set_output("_ERROR", str(last_error))
|
|
|
|
|
return f"Http request error: {last_error}"
|
2025-07-31 15:13:45 +08:00
|
|
|
|
|
|
|
|
def thoughts(self) -> str:
|
2025-08-12 12:27:22 +08:00
|
|
|
return "Waiting for the server respond..."
|