feat(url): 支持docreader不上传替换白名单url的图片
This commit is contained in:
@@ -493,6 +493,11 @@ DOCREADER_TRANSPORT=grpc
|
|||||||
# Weaviate gRPC 地址(Docker 内:weaviate:50051;宿主机访问:localhost:50052)
|
# Weaviate gRPC 地址(Docker 内:weaviate:50051;宿主机访问:localhost:50052)
|
||||||
# WEAVIATE_GRPC_ADDRESS=weaviate:50051
|
# WEAVIATE_GRPC_ADDRESS=weaviate:50051
|
||||||
|
|
||||||
|
# 保留原始 URL 的图片域名白名单(可选,逗号分隔)
|
||||||
|
# 配置后,这些域名的图片仍会被下载和分析(OCR/字幕),但 markdown 中保留原始 URL,
|
||||||
|
# 不会被替换为对象存储的 provider:// URL。适用于内部稳定服务(如自建 MinerU)。
|
||||||
|
# IMAGE_HOST_KEEP_URL=mineru.internal.example.com
|
||||||
|
|
||||||
# Weaviate 架构模式
|
# Weaviate 架构模式
|
||||||
# WEAVIATE_SCHEME=http
|
# WEAVIATE_SCHEME=http
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,8 @@ NEO4J_ENABLE=false
|
|||||||
WEKNORA_SANDBOX_MODE=disabled
|
WEKNORA_SANDBOX_MODE=disabled
|
||||||
ENABLE_GRAPH_RAG=false
|
ENABLE_GRAPH_RAG=false
|
||||||
DISABLE_REGISTRATION=false
|
DISABLE_REGISTRATION=false
|
||||||
|
# 保留原始 URL 的图片域名白名单(可选)
|
||||||
|
# IMAGE_HOST_KEEP_URL=mineru.internal.example.com
|
||||||
|
|
||||||
# === Langfuse 可观测性(可选) ===
|
# === Langfuse 可观测性(可选) ===
|
||||||
# 追踪 chat / embedding / rerank / VLM / ASR 的 prompt、响应与 token 消耗。
|
# 追踪 chat / embedding / rerank / VLM / ASR 的 prompt、响应与 token 消耗。
|
||||||
|
|||||||
@@ -149,6 +149,8 @@ services:
|
|||||||
- TENANT_AES_KEY=${TENANT_AES_KEY:-}
|
- TENANT_AES_KEY=${TENANT_AES_KEY:-}
|
||||||
- SYSTEM_AES_KEY=${SYSTEM_AES_KEY:-}
|
- SYSTEM_AES_KEY=${SYSTEM_AES_KEY:-}
|
||||||
- SSRF_WHITELIST=${SSRF_WHITELIST:-}
|
- SSRF_WHITELIST=${SSRF_WHITELIST:-}
|
||||||
|
# 保留原始 URL 的图片域名白名单(逗号分隔,不替换为 provider://)
|
||||||
|
- IMAGE_HOST_KEEP_URL=${IMAGE_HOST_KEEP_URL:-}
|
||||||
# Always allow the optional searxng sidecar (compose service hostname);
|
# Always allow the optional searxng sidecar (compose service hostname);
|
||||||
# merged on top of SSRF_WHITELIST so user overrides don't clobber it.
|
# merged on top of SSRF_WHITELIST so user overrides don't clobber it.
|
||||||
- SSRF_WHITELIST_EXTRA=${SSRF_WHITELIST_EXTRA:-searxng}
|
- SSRF_WHITELIST_EXTRA=${SSRF_WHITELIST_EXTRA:-searxng}
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"mime"
|
"mime"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
@@ -195,6 +197,35 @@ func isProviderScheme(p string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isWhitelistedImageHost checks if the image URL's host is in the whitelist.
|
||||||
|
// Whitelisted hosts are trusted (e.g. internal MinerU service) — images are
|
||||||
|
// still downloaded for validation and OCR/caption analysis, but not uploaded
|
||||||
|
// to object storage. The markdown keeps the original URL.
|
||||||
|
// Configure via IMAGE_HOST_KEEP_URL env var (comma-separated hosts).
|
||||||
|
func isWhitelistedImageHost(rawURL string) bool {
|
||||||
|
whitelist := strings.TrimSpace(os.Getenv("IMAGE_HOST_KEEP_URL"))
|
||||||
|
if whitelist == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
u, err := url.Parse(rawURL)
|
||||||
|
if err != nil || u.Host == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
host := strings.ToLower(u.Host)
|
||||||
|
hostname := strings.ToLower(u.Hostname())
|
||||||
|
for _, h := range strings.Split(whitelist, ",") {
|
||||||
|
h = strings.ToLower(strings.TrimSpace(h))
|
||||||
|
if h == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Exact host match (includes port) or hostname match (any port)
|
||||||
|
if host == h || hostname == h {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Helper functions for base64 image handling
|
// Helper functions for base64 image handling
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -670,10 +701,18 @@ func (r *ImageResolver) ResolveRemoteImages(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- SSRF check (centralised entry-point with whitelist support) ---
|
// For whitelisted hosts: download to validate (mime type, icon check),
|
||||||
if err := secutils.ValidateURLForSSRF(imgURL); err != nil {
|
// create StoredImage for downstream OCR/caption analysis, but do NOT
|
||||||
log.Printf("WARN: remote image blocked by SSRF check (%v): %s", err, imgURL)
|
// upload to storage and keep the original URL in markdown.
|
||||||
continue
|
// The multimodal service will download from the original URL later.
|
||||||
|
whitelisted := isWhitelistedImageHost(imgURL)
|
||||||
|
|
||||||
|
// --- SSRF check (skip for whitelisted) ---
|
||||||
|
if !whitelisted {
|
||||||
|
if err := secutils.ValidateURLForSSRF(imgURL); err != nil {
|
||||||
|
log.Printf("WARN: remote image blocked by SSRF check (%v): %s", err, imgURL)
|
||||||
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Download ---
|
// --- Download ---
|
||||||
@@ -697,12 +736,20 @@ func (r *ImageResolver) ResolveRemoteImages(
|
|||||||
ext = ".png" // safe default
|
ext = ".png" // safe default
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Upload to storage ---
|
var servingURL string
|
||||||
fileName := uuid.New().String() + ext
|
if whitelisted {
|
||||||
servingURL, saveErr := fileSvc.SaveBytes(ctx, data, tenantID, fileName, false)
|
// Keep the original URL — ImageMultimodalService will download it
|
||||||
if saveErr != nil {
|
// directly for OCR/caption analysis.
|
||||||
log.Printf("WARN: failed to save remote image %s: %v", imgURL, saveErr)
|
servingURL = imgURL
|
||||||
continue
|
} else {
|
||||||
|
// --- Upload to storage ---
|
||||||
|
fileName := uuid.New().String() + ext
|
||||||
|
var saveErr error
|
||||||
|
servingURL, saveErr = fileSvc.SaveBytes(ctx, data, tenantID, fileName, false)
|
||||||
|
if saveErr != nil {
|
||||||
|
log.Printf("WARN: failed to save remote image %s: %v", imgURL, saveErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
images = append(images, StoredImage{
|
images = append(images, StoredImage{
|
||||||
@@ -711,8 +758,10 @@ func (r *ImageResolver) ResolveRemoteImages(
|
|||||||
MimeType: mimeType,
|
MimeType: mimeType,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Replace URL in markdown.
|
if !whitelisted {
|
||||||
markdown = markdown[:m[4]] + servingURL + markdown[m[5]:]
|
// Replace URL in markdown.
|
||||||
|
markdown = markdown[:m[4]] + servingURL + markdown[m[5]:]
|
||||||
|
}
|
||||||
processed++
|
processed++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user