Image Handling

This commit is contained in:
Stefan Hardegger
2025-10-09 14:39:55 +02:00
parent 4e02cd8eaa
commit 20d0652c85
6 changed files with 390 additions and 244 deletions

View File

@@ -20,6 +20,9 @@ public class AsyncImageProcessingService {
private final StoryService storyService;
private final ImageProcessingProgressService progressService;
@org.springframework.beans.factory.annotation.Value("${storycove.app.public-url:http://localhost:6925}")
private String publicUrl;
@Autowired
public AsyncImageProcessingService(ImageService imageService,
StoryService storyService,
@@ -103,10 +106,54 @@ public class AsyncImageProcessingService {
return count;
}
/**
* Check if a URL is external (not from this application).
* Returns true if the URL should be downloaded, false if it's already local.
*/
private boolean isExternalUrl(String url) {
return url != null &&
(url.startsWith("http://") || url.startsWith("https://")) &&
!url.contains("/api/files/images/");
if (url == null || url.trim().isEmpty()) {
return false;
}
// Skip data URLs
if (url.startsWith("data:")) {
return false;
}
// Skip relative URLs (local paths)
if (url.startsWith("/")) {
return false;
}
// Skip URLs that are already pointing to our API
if (url.contains("/api/files/images/")) {
return false;
}
// Check if URL starts with the public URL (our own domain)
if (publicUrl != null && !publicUrl.trim().isEmpty()) {
String normalizedUrl = url.trim().toLowerCase();
String normalizedPublicUrl = publicUrl.trim().toLowerCase();
// Remove trailing slash from public URL for comparison
if (normalizedPublicUrl.endsWith("/")) {
normalizedPublicUrl = normalizedPublicUrl.substring(0, normalizedPublicUrl.length() - 1);
}
if (normalizedUrl.startsWith(normalizedPublicUrl)) {
logger.debug("URL is from this application (matches publicUrl): {}", url);
return false;
}
}
// If it's an HTTP(S) URL that didn't match our filters, it's external
if (url.startsWith("http://") || url.startsWith("https://")) {
logger.debug("URL is external: {}", url);
return true;
}
// For any other format, consider it non-external (safer default)
return false;
}
private ImageService.ContentImageProcessingResult processImagesWithProgress(

View File

@@ -69,7 +69,10 @@ public class ImageService {
@Value("${storycove.images.max-file-size:5242880}") // 5MB default
private long maxFileSize;
@Value("${storycove.app.public-url:http://localhost:6925}")
private String publicUrl;
public enum ImageType {
COVER("covers"),
AVATAR("avatars"),
@@ -286,9 +289,9 @@ public class ImageService {
logger.debug("Found image #{}: {} in tag: {}", imageCount, imageUrl, fullImgTag);
try {
// Skip if it's already a local path or data URL
if (imageUrl.startsWith("/") || imageUrl.startsWith("data:")) {
logger.debug("Skipping local/data URL: {}", imageUrl);
// Skip if it's already a local path, data URL, or from this application
if (!isExternalUrl(imageUrl)) {
logger.debug("Skipping local/internal URL: {}", imageUrl);
matcher.appendReplacement(processedContent, Matcher.quoteReplacement(fullImgTag));
continue;
}
@@ -366,7 +369,7 @@ public class ImageService {
Matcher countMatcher = imgPattern.matcher(htmlContent);
while (countMatcher.find()) {
String imageUrl = countMatcher.group(1);
if (!imageUrl.startsWith("/") && !imageUrl.startsWith("data:")) {
if (isExternalUrl(imageUrl)) {
externalImages.add(imageUrl);
}
}
@@ -384,9 +387,9 @@ public class ImageService {
logger.debug("Found image: {} in tag: {}", imageUrl, fullImgTag);
try {
// Skip if it's already a local path or data URL
if (imageUrl.startsWith("/") || imageUrl.startsWith("data:")) {
logger.debug("Skipping local/data URL: {}", imageUrl);
// Skip if it's already a local path, data URL, or from this application
if (!isExternalUrl(imageUrl)) {
logger.debug("Skipping local/internal URL: {}", imageUrl);
matcher.appendReplacement(processedContent, Matcher.quoteReplacement(fullImgTag));
continue;
}
@@ -429,6 +432,56 @@ public class ImageService {
return new ContentImageProcessingResult(processedContent.toString(), warnings, downloadedImages);
}
/**
* Check if a URL is external (not from this application).
* Returns true if the URL should be downloaded, false if it's already local.
*/
private boolean isExternalUrl(String url) {
if (url == null || url.trim().isEmpty()) {
return false;
}
// Skip data URLs
if (url.startsWith("data:")) {
return false;
}
// Skip relative URLs (local paths)
if (url.startsWith("/")) {
return false;
}
// Skip URLs that are already pointing to our API
if (url.contains("/api/files/images/")) {
return false;
}
// Check if URL starts with the public URL (our own domain)
if (publicUrl != null && !publicUrl.trim().isEmpty()) {
String normalizedUrl = url.trim().toLowerCase();
String normalizedPublicUrl = publicUrl.trim().toLowerCase();
// Remove trailing slash from public URL for comparison
if (normalizedPublicUrl.endsWith("/")) {
normalizedPublicUrl = normalizedPublicUrl.substring(0, normalizedPublicUrl.length() - 1);
}
if (normalizedUrl.startsWith(normalizedPublicUrl)) {
logger.debug("URL is from this application (matches publicUrl): {}", url);
return false;
}
}
// If it's an HTTP(S) URL that didn't match our filters, it's external
if (url.startsWith("http://") || url.startsWith("https://")) {
logger.debug("URL is external: {}", url);
return true;
}
// For any other format, consider it non-external (safer default)
return false;
}
/**
* Download an image from a URL and store it locally
*/