fix: port safe image and link stuff to new markdown renderer (closes #1112)

This commit is contained in:
MiniDigger | Martin 2023-03-18 10:14:34 +01:00
parent 4a8291438d
commit 07754c9ee1
8 changed files with 61 additions and 46 deletions

View File

@ -1,9 +1,6 @@
package io.papermc.hangar.config.hangar;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.List;
@ -35,44 +32,4 @@ public record HangarSecurityConfig(
public boolean checkSafe(final String url) {
return this.safeDownloadHosts.contains(URI.create(url).getHost());
}
public boolean isSafeHost(final String host) {
for (final String safeHost : this.safeDownloadHosts) {
// Make sure it's the full host or a subdomain
if (host.equals(safeHost) || host.endsWith("." + safeHost)) {
return true;
}
}
return false;
}
private boolean isSafe(final String urlString) {
try {
final URI uri = new URI(urlString);
final String host = uri.getHost();
if (uri.getScheme() != null && host == null) {
if (uri.getScheme().equals("mailto")) {
return true;
}
} else if (host == null || this.isSafeHost(host)) {
return true;
}
} catch (final URISyntaxException ignored) {
}
return false;
}
public String makeSafe(final String urlString) {
if (this.isSafe(urlString)) {
return urlString;
}
return "/linkout?remoteUrl=" + URLEncoder.encode(urlString, StandardCharsets.UTF_8);
}
public String proxyImage(final String urlString) {
if (this.isSafe(urlString)) {
return urlString;
}
return String.format(this.imageProxyUrl(), urlString);
}
}

View File

@ -6,6 +6,7 @@ import com.fasterxml.jackson.databind.introspect.Annotated;
import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.papermc.hangar.HangarComponent;
import io.papermc.hangar.config.CacheConfig;
import io.papermc.hangar.config.hangar.HangarConfig;
import io.papermc.hangar.db.customtypes.RoleCategory;
@ -19,6 +20,7 @@ import io.papermc.hangar.model.common.projects.Category;
import io.papermc.hangar.model.common.projects.FlagReason;
import io.papermc.hangar.model.common.projects.Visibility;
import io.papermc.hangar.model.common.roles.RoleData;
import io.papermc.hangar.model.internal.api.responses.Security;
import io.papermc.hangar.model.internal.api.responses.Validations;
import io.papermc.hangar.model.internal.logs.LogAction;
import io.papermc.hangar.security.annotations.Anyone;
@ -44,7 +46,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
@Anyone
@RateLimit(path = "backenddata")
@RequestMapping(path = "/api/internal/data", produces = MediaType.APPLICATION_JSON_VALUE)
public class BackendDataController {
public class BackendDataController extends HangarComponent {
private final ObjectMapper objectMapper; // ignores JsonValue annotations
private final HangarConfig config;
@ -226,5 +228,10 @@ public class BackendDataController {
public List<String> getLoggedActions() {
return LogAction.LOG_REGISTRY.keySet().stream().sorted().toList();
}
}
@GetMapping("/security")
@ResponseBody
public Security getSecurity() {
return new Security(this.config.security.safeDownloadHosts(), this.config.security.imageProxyUrl());
}
}

View File

@ -0,0 +1,6 @@
package io.papermc.hangar.model.internal.api.responses;
import java.util.List;
public record Security(List<String> safeDownloadHosts, String imageProxyUrl) {
}

View File

@ -185,6 +185,7 @@ hangar:
timeout: 10000
safe-download-hosts:
- "github.com"
- "githubusercontent.com"
- "gitlab.com"
- "youtu.be"
- "youtube.com"

View File

@ -1,6 +1,35 @@
import { marked } from "marked";
import markedLinkifyIt from "marked-linkify-it";
import markedExtendedTables from "marked-extended-tables";
import { useBackendData } from "~/store/backendData";
const isSafeHost = (host: string) => {
for (const safeHost of useBackendData.security.safeDownloadHosts) {
// Make sure it's the full host or a subdomain
if (host === safeHost || host.endsWith("." + safeHost)) {
return true;
}
}
return false;
};
const isSafe = (urlString: string) => {
try {
const url = new URL(urlString);
const host = url.hostname;
if (url.protocol?.startsWith("mailto")) {
return true;
} else if (!host || isSafeHost(host)) {
return true;
}
} catch {}
return false;
};
const makeSafe = (urlString: string) => (isSafe(urlString) ? urlString : "/linkout?remoteUrl=" + encodeURIComponent(urlString));
const proxyImage = (urlString: string) => (isSafe(urlString) ? urlString : useBackendData.security.imageProxyUrl.replace("%s", urlString));
const renderer = {
heading(text: string, level: number) {
@ -14,6 +43,12 @@ const renderer = {
</a>
</h${level}>`;
},
image(href: string, title: string, alt: string) {
return `<img src="${proxyImage(href)}" alt="${alt}"` + (title ? ` title="${title}">` : ">");
},
link(href: string, title: string, text: string) {
return `<a href="${makeSafe(href)}"` + (title ? ` title="${title}">` : ">") + text + "</a>";
},
};
marked.use({ renderer });
marked.use(markedExtendedTables());

@ -1 +1 @@
Subproject commit 2b58fc7797330a359acbefb1c675915bda7e958e
Subproject commit ef0b37feb4c956497caad9ad2eb9c1ebc081dd51

View File

@ -132,6 +132,7 @@ async function loadData(state: BackendData, axiosInstance: AxiosInstance) {
channelColors,
flagReasons,
loggedActions,
security,
] = (
await Promise.all([
axiosInstance.get<typeof state.projectCategories>("/categories"),
@ -148,6 +149,7 @@ async function loadData(state: BackendData, axiosInstance: AxiosInstance) {
axiosInstance.get<typeof state.channelColors>("/channelColors"),
axiosInstance.get<typeof state.flagReasons>("/flagReasons"),
axiosInstance.get<typeof state.loggedActions>("/loggedActions"),
axiosInstance.get<typeof state.security>("/security"),
])
).map((it) => it?.data || it);
@ -169,4 +171,5 @@ async function loadData(state: BackendData, axiosInstance: AxiosInstance) {
state.channelColors = channelColors as typeof state.channelColors;
state.flagReasons = flagReasons as typeof state.flagReasons;
state.loggedActions = loggedActions as typeof state.loggedActions;
state.security = security as typeof state.security;
}

View File

@ -68,6 +68,11 @@ declare module "hangar-api" {
urlRegex: string;
}
interface Security {
safeDownloadHosts: string[];
imageProxyUrl: string;
}
interface BackendData {
meta: {
lastGenerated: string;
@ -88,5 +93,6 @@ declare module "hangar-api" {
channelColors: Color[];
flagReasons: FlagReason[];
loggedActions: string[];
security: Security;
}
}