mirror of
https://github.com/HangarMC/Hangar.git
synced 2024-12-27 07:03:26 +08:00
fix: port safe image and link stuff to new markdown renderer (closes #1112)
This commit is contained in:
parent
4a8291438d
commit
07754c9ee1
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,6 @@
|
||||
package io.papermc.hangar.model.internal.api.responses;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record Security(List<String> safeDownloadHosts, String imageProxyUrl) {
|
||||
}
|
@ -185,6 +185,7 @@ hangar:
|
||||
timeout: 10000
|
||||
safe-download-hosts:
|
||||
- "github.com"
|
||||
- "githubusercontent.com"
|
||||
- "gitlab.com"
|
||||
- "youtu.be"
|
||||
- "youtube.com"
|
||||
|
@ -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
|
@ -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;
|
||||
}
|
||||
|
6
frontend/src/types/api.d.ts
vendored
6
frontend/src/types/api.d.ts
vendored
@ -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;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user