diff --git a/src/main/java/com/depromeet/piki/extractor/domain/ProductSnapshot.java b/src/main/java/com/depromeet/piki/extractor/domain/ProductSnapshot.java
index 44a48ae..a629312 100644
--- a/src/main/java/com/depromeet/piki/extractor/domain/ProductSnapshot.java
+++ b/src/main/java/com/depromeet/piki/extractor/domain/ProductSnapshot.java
@@ -81,7 +81,8 @@ public String missingFieldNames() {
/**
* 구조화 파싱과 LLM 추출이 함께 통과하는 정규화·범위검증의 단일 진실 원천.
- * imageUrl 을 https 로만 좁히는 것은 클라이언트가 {@code } 로 쓸 때의 XSS 사다리를 끊기 위한 것이다.
+ * imageUrl 을 https 로 좁히는 것은 클라이언트가 {@code
} 로 쓸 때의 XSS 사다리를 끊기 위한 것이다.
+ * 다만 좁히는 방식은 스킴에 따라 다르다 — 자세한 근거는 normalizeImageUrl 참조.
*
범위를 벗어난 값은 {@link ProductSnapshotException#untrustworthyValue()} 로 막고, 그 뒤 처리는 호출부가 고른다:
* 구조화 경로는 예외를 흡수해 Miss(LLM fallback)로, LLM 경로는 그대로 흘려 확정 실패로 떨어뜨린다.
* 같은 검증, 실패 표현만 다르다.
@@ -121,9 +122,21 @@ private static String normalizeImageUrl(String imageUrl) {
if (imageUrl == null || imageUrl.isBlank()) {
return null;
}
- if (!imageUrl.regionMatches(true, 0, "https://", 0, "https://".length())) {
- return null;
+ if (imageUrl.regionMatches(true, 0, "https://", 0, "https://".length())) {
+ return imageUrl;
+ }
+ // http·프로토콜 상대(//host/path)는 버리지 않고 https 로 올린다. 스킴만 다를 뿐 같은 자원을 가리키고,
+ // 실제로 og:image 에 http 를 적어 둔 몰이 있다(실측: postarchivefaction, http 는 301→https).
+ // 올린 주소가 안 되면 이미지가 안 뜨는데, 버리면 애초에 이미지가 없으므로 더 나빠지지 않는다.
+ // 반대로 http 그대로 두는 선택지는 없다 — 클라이언트가 https 라 브라우저가 mixed content 로 막는다.
+ if (imageUrl.regionMatches(true, 0, "http://", 0, "http://".length())) {
+ return "https://" + imageUrl.substring("http://".length());
+ }
+ if (imageUrl.startsWith("//")) {
+ return "https:" + imageUrl;
}
- return imageUrl;
+ // 나머지 스킴(javascript:·data:·file: 등)은 그대로 거부한다 — 이쪽이 원래 막으려던 것이고,
+ // 스킴을 갈아끼워 살릴 수 있는 값도 아니다.
+ return null;
}
}
diff --git a/src/test/java/com/depromeet/piki/extractor/domain/ProductSnapshotTest.java b/src/test/java/com/depromeet/piki/extractor/domain/ProductSnapshotTest.java
index 254f126..a05e2bb 100644
--- a/src/test/java/com/depromeet/piki/extractor/domain/ProductSnapshotTest.java
+++ b/src/test/java/com/depromeet/piki/extractor/domain/ProductSnapshotTest.java
@@ -21,13 +21,12 @@ void blankNameToNull() {
}
@Test
- @DisplayName("imageUrl 은 https 가 아니면 null 로 정규화된다")
- void nonHttpsImageUrlToNull() {
+ @DisplayName("스킴을 갈아끼워 살릴 수 없는 imageUrl 만 null 로 정규화된다")
+ void unusableImageUrlToNull() {
List