prevent XXS in third party apps

CommaFeed React client already strips malicious "javascript:" urls but we might as well not store those in the database
This commit is contained in:
Athou
2026-08-03 08:22:43 +02:00
parent b9eceaae73
commit bc6e767953
3 changed files with 58 additions and 7 deletions

View File

@@ -8,6 +8,7 @@ import org.netpreserve.urlcanon.Canonicalizer;
import org.netpreserve.urlcanon.ParsedUrl;
import java.net.URI;
import java.util.Locale;
import java.util.regex.Pattern;
@UtilityClass
@@ -17,17 +18,38 @@ public class Urls {
private static final Pattern QUESTION_MARK = Pattern.compile(Pattern.quote("?"));
public static boolean isHttp(String url) {
return url.startsWith("http://");
if (url == null) {
return false;
}
return url.toLowerCase(Locale.ROOT).startsWith("http://");
}
public static boolean isHttps(String url) {
return url.startsWith("https://");
if (url == null) {
return false;
}
return url.toLowerCase(Locale.ROOT).startsWith("https://");
}
public static boolean isAbsolute(String url) {
return isHttp(url) || isHttps(url);
}
/** remove malicious 'javascript: 'URLs * */
public static String sanitize(String url) {
if (url == null) {
return null;
}
if (!isHttp(url) && !isHttps(url)) {
return null;
}
return url;
}
/**
* @param relativeUrl the url of the entry
* @param feedLink the url of the feed as described in the feed

View File

@@ -80,8 +80,8 @@ public class FeedParser {
handleForeignMarkup(feed);
String title = feed.getTitle();
String link = feed.getLink();
String iconUrl = feed.getIcon() != null ? feed.getIcon().getUrl() : null;
String link = Urls.sanitize(feed.getLink());
String iconUrl = Urls.sanitize(feed.getIcon() != null ? feed.getIcon().getUrl() : null);
List<Entry> entries = buildEntries(feed, feedUrl);
Instant lastEntryDate = entries.stream().findFirst().map(Entry::published).orElse(null);
Instant lastPublishedDate = toValidInstant(feed.getPublishedDate(), false);
@@ -145,7 +145,7 @@ public class FeedParser {
Instant publishedDate = buildEntryPublishedDate(item);
Content content = buildContent(item);
entries.add(new Entry(guid, url, publishedDate, content));
entries.add(new Entry(guid, Urls.sanitize(url), publishedDate, content));
}
entries.sort(ENTRY_COMPARATOR);
@@ -173,7 +173,7 @@ public class FeedParser {
return null;
}
return new Enclosure(enclosure.getUrl(), enclosure.getType());
return new Enclosure(Urls.sanitize(enclosure.getUrl()), enclosure.getType());
}
private Instant buildEntryPublishedDate(SyndEntry item) {
@@ -277,7 +277,7 @@ public class FeedParser {
return null;
}
return new Media(description, thumbnailUrl, thumbnailWidth, thumbnailHeight);
return new Media(description, Urls.sanitize(thumbnailUrl), thumbnailWidth, thumbnailHeight);
}
private Long averageTimeBetweenEntries(List<Entry> entries) {