-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathRelativeLinkProcessor.java
More file actions
115 lines (102 loc) · 4.08 KB
/
RelativeLinkProcessor.java
File metadata and controls
115 lines (102 loc) · 4.08 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
package run.halo.feed;
import static run.halo.feed.RssUtils.genRelativeThumbUri;
import com.google.common.base.Throwables;
import java.net.URI;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.springframework.util.Assert;
import org.springframework.web.util.UriComponentsBuilder;
import run.halo.app.core.attachment.ThumbnailSize;
import run.halo.app.infra.utils.PathUtils;
import run.halo.feed.telemetry.TelemetryEndpoint;
@Slf4j
public class RelativeLinkProcessor {
private final URI externalUri;
public RelativeLinkProcessor(String externalUrl) {
Assert.notNull(externalUrl, "External URL must not be null");
this.externalUri = URI.create(externalUrl);
}
public String processForHtml(String html) {
try {
return doProcessForHtml(html);
} catch (Throwable e) {
log.warn("Failed to process relative links for HTML", Throwables.getRootCause(e));
}
return html;
}
private String doProcessForHtml(String html) {
var document = Jsoup.parse(html);
// Process all links
var links = document.select("a[href]");
processElementAttr(links, "href", false);
// process all images
var images = document.select("img[src]");
processElementAttr(images, "src", true);
// video/audio source src
var sources = document.select("source[src]");
processElementAttr(sources, "src", false);
// video src
var videos = document.select("video[src]");
processElementAttr(videos, "src", false);
// link href
var linksHref = document.select("link[href]");
processElementAttr(linksHref, "href", false);
// script src
var scripts = document.select("script[src]");
processElementAttr(scripts, "src", false);
// iframe src
var iframes = document.select("iframe[src]");
processElementAttr(iframes, "src", false);
// frame src
var frames = document.select("frame[src]");
processElementAttr(frames, "src", false);
// embed src
var embeds = document.select("embed[src]");
processElementAttr(embeds, "src", false);
return document.body().html();
}
private void processElementAttr(Elements elements, String attrKey, boolean canThumb) {
for (Element link : elements) {
String src = link.attr(attrKey);
if (canThumb && isNotTelemetryLink(src)) {
var thumb = genThumbUrl(src, ThumbnailSize.M);
var absoluteUrl = processLink(thumb);
link.attr(attrKey, absoluteUrl);
} else {
var absoluteUrl = processLink(src);
link.attr(attrKey, absoluteUrl);
}
}
}
boolean isNotTelemetryLink(String uri) {
return uri != null && !uri.contains(TelemetryEndpoint.TELEMETRY_PATH);
}
private String genThumbUrl(String url, ThumbnailSize size) {
return processLink(genRelativeThumbUri(url, size));
}
private String processLink(String link) {
if (StringUtils.isBlank(link) || PathUtils.isAbsoluteUri(link)) {
return link;
}
var contextPath = StringUtils.defaultIfBlank(externalUri.getPath(), "/");
var linkUri = UriComponentsBuilder.fromUriString(URI.create(link).toASCIIString())
.build(true);
var builder = UriComponentsBuilder.fromUriString(externalUri.toString());
if (shouldAppendPath(contextPath, link)) {
builder.pathSegment(linkUri.getPathSegments().toArray(new String[0]));
} else {
builder.replacePath(linkUri.getPath());
}
return builder.query(linkUri.getQuery())
.fragment(linkUri.getFragment())
.build(true)
.toUri()
.toString();
}
private static boolean shouldAppendPath(String contextPath, String link) {
return !"/".equals(contextPath) && !link.startsWith(contextPath);
}
}