mirror of
https://github.com/Athou/commafeed.git
synced 2026-03-21 21:37:29 +00:00
proxy images through commafeed (#231)
This commit is contained in:
@@ -21,6 +21,7 @@ import org.jsoup.nodes.Element;
|
|||||||
import org.jsoup.nodes.Entities.EscapeMode;
|
import org.jsoup.nodes.Entities.EscapeMode;
|
||||||
import org.jsoup.safety.Cleaner;
|
import org.jsoup.safety.Cleaner;
|
||||||
import org.jsoup.safety.Whitelist;
|
import org.jsoup.safety.Whitelist;
|
||||||
|
import org.jsoup.select.Elements;
|
||||||
import org.mozilla.universalchardet.UniversalDetector;
|
import org.mozilla.universalchardet.UniversalDetector;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -30,6 +31,7 @@ import org.w3c.dom.css.CSSStyleDeclaration;
|
|||||||
import com.commafeed.backend.model.Feed;
|
import com.commafeed.backend.model.Feed;
|
||||||
import com.commafeed.backend.model.FeedEntry;
|
import com.commafeed.backend.model.FeedEntry;
|
||||||
import com.commafeed.backend.model.FeedSubscription;
|
import com.commafeed.backend.model.FeedSubscription;
|
||||||
|
import com.google.api.client.util.Base64;
|
||||||
import com.google.api.client.util.Lists;
|
import com.google.api.client.util.Lists;
|
||||||
import com.google.gwt.i18n.client.HasDirection.Direction;
|
import com.google.gwt.i18n.client.HasDirection.Direction;
|
||||||
import com.google.gwt.i18n.shared.BidiUtils;
|
import com.google.gwt.i18n.shared.BidiUtils;
|
||||||
@@ -38,6 +40,7 @@ import com.steadystate.css.parser.CSSOMParser;
|
|||||||
public class FeedUtils {
|
public class FeedUtils {
|
||||||
|
|
||||||
protected static Logger log = LoggerFactory.getLogger(FeedUtils.class);
|
protected static Logger log = LoggerFactory.getLogger(FeedUtils.class);
|
||||||
|
|
||||||
private static final List<String> ALLOWED_IFRAME_CSS_RULES = Arrays.asList(
|
private static final List<String> ALLOWED_IFRAME_CSS_RULES = Arrays.asList(
|
||||||
"height", "width", "border");
|
"height", "width", "border");
|
||||||
private static final char[] DISALLOWED_IFRAME_CSS_RULE_CHARACTERS = new char[] {
|
private static final char[] DISALLOWED_IFRAME_CSS_RULE_CHARACTERS = new char[] {
|
||||||
@@ -358,4 +361,57 @@ public class FeedUtils {
|
|||||||
+ subscription.getId();
|
+ subscription.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String proxyImages(String content, String publicUrl,
|
||||||
|
boolean proxyImages) {
|
||||||
|
if (!proxyImages) {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
if (StringUtils.isBlank(content)) {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("cc");
|
||||||
|
Document doc = Jsoup.parse(content);
|
||||||
|
Elements elements = doc.select("img");
|
||||||
|
log.info("{}", elements.size());
|
||||||
|
for (Element element : elements) {
|
||||||
|
String href = element.attr("src");
|
||||||
|
log.info(href);
|
||||||
|
if (href != null) {
|
||||||
|
String proxy = removeTrailingSlash(publicUrl) + "/rest/server/proxy?u="
|
||||||
|
+ imageProxyEncoder(href);
|
||||||
|
log.info(proxy);
|
||||||
|
element.attr("src", proxy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return doc.body().html();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String rot13(String msg) {
|
||||||
|
StringBuilder message = new StringBuilder();
|
||||||
|
|
||||||
|
for (char c : msg.toCharArray()) {
|
||||||
|
if (c >= 'a' && c <= 'm')
|
||||||
|
c += 13;
|
||||||
|
else if (c >= 'n' && c <= 'z')
|
||||||
|
c -= 13;
|
||||||
|
else if (c >= 'A' && c <= 'M')
|
||||||
|
c += 13;
|
||||||
|
else if (c >= 'N' && c <= 'Z')
|
||||||
|
c -= 13;
|
||||||
|
message.append(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
return message.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String imageProxyEncoder(String url) {
|
||||||
|
return Base64.encodeBase64String(rot13(url).getBytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String imageProxyDecoder(String code) {
|
||||||
|
return rot13(new String(Base64.decodeBase64(code)));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ public class ApplicationSettings extends AbstractModel {
|
|||||||
private boolean pubsubhubbub;
|
private boolean pubsubhubbub;
|
||||||
private boolean feedbackButton = true;
|
private boolean feedbackButton = true;
|
||||||
private String logLevel = Level.INFO.toString();
|
private String logLevel = Level.INFO.toString();
|
||||||
|
private boolean imageProxyEnabled;
|
||||||
|
|
||||||
@Column(length = 255)
|
@Column(length = 255)
|
||||||
private String announcement;
|
private String announcement;
|
||||||
@@ -173,4 +174,12 @@ public class ApplicationSettings extends AbstractModel {
|
|||||||
this.logLevel = logLevel;
|
this.logLevel = logLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isImageProxyEnabled() {
|
||||||
|
return imageProxyEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setImageProxyEnabled(boolean imageProxyEnabled) {
|
||||||
|
this.imageProxyEnabled = imageProxyEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ import com.wordnik.swagger.annotations.ApiProperty;
|
|||||||
@ApiClass("Entry details")
|
@ApiClass("Entry details")
|
||||||
public class Entry implements Serializable {
|
public class Entry implements Serializable {
|
||||||
|
|
||||||
public static Entry build(FeedEntryStatus status, String publicUrl) {
|
public static Entry build(FeedEntryStatus status, String publicUrl,
|
||||||
|
boolean proxyImages) {
|
||||||
Entry entry = new Entry();
|
Entry entry = new Entry();
|
||||||
|
|
||||||
FeedEntry feedEntry = status.getEntry();
|
FeedEntry feedEntry = status.getEntry();
|
||||||
@@ -36,7 +37,8 @@ public class Entry implements Serializable {
|
|||||||
entry.setId(String.valueOf(feedEntry.getId()));
|
entry.setId(String.valueOf(feedEntry.getId()));
|
||||||
entry.setGuid(feedEntry.getGuid());
|
entry.setGuid(feedEntry.getGuid());
|
||||||
entry.setTitle(feedEntry.getContent().getTitle());
|
entry.setTitle(feedEntry.getContent().getTitle());
|
||||||
entry.setContent(feedEntry.getContent().getContent());
|
entry.setContent(FeedUtils.proxyImages(feedEntry.getContent()
|
||||||
|
.getContent(), publicUrl, proxyImages));
|
||||||
entry.setRtl(FeedUtils.isRTL(feedEntry));
|
entry.setRtl(FeedUtils.isRTL(feedEntry));
|
||||||
entry.setAuthor(feedEntry.getAuthor());
|
entry.setAuthor(feedEntry.getAuthor());
|
||||||
entry.setEnclosureUrl(feedEntry.getContent().getEnclosureUrl());
|
entry.setEnclosureUrl(feedEntry.getContent().getEnclosureUrl());
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import org.apache.wicket.request.cycle.RequestCycle;
|
|||||||
import org.apache.wicket.util.crypt.Base64;
|
import org.apache.wicket.util.crypt.Base64;
|
||||||
|
|
||||||
import com.commafeed.backend.DatabaseCleaner;
|
import com.commafeed.backend.DatabaseCleaner;
|
||||||
|
import com.commafeed.backend.HttpGetter;
|
||||||
import com.commafeed.backend.MetricsBean;
|
import com.commafeed.backend.MetricsBean;
|
||||||
import com.commafeed.backend.StartupBean;
|
import com.commafeed.backend.StartupBean;
|
||||||
import com.commafeed.backend.dao.FeedCategoryDAO;
|
import com.commafeed.backend.dao.FeedCategoryDAO;
|
||||||
@@ -128,6 +129,9 @@ public abstract class AbstractREST {
|
|||||||
@Inject
|
@Inject
|
||||||
DatabaseCleaner cleaner;
|
DatabaseCleaner cleaner;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
HttpGetter httpGetter;
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void init() {
|
public void init() {
|
||||||
CommaFeedApplication app = CommaFeedApplication.get();
|
CommaFeedApplication app = CommaFeedApplication.get();
|
||||||
|
|||||||
@@ -96,7 +96,8 @@ public class CategoryREST extends AbstractResourceREST {
|
|||||||
for (FeedEntryStatus status : list) {
|
for (FeedEntryStatus status : list) {
|
||||||
entries.getEntries().add(
|
entries.getEntries().add(
|
||||||
Entry.build(status, applicationSettingsService.get()
|
Entry.build(status, applicationSettingsService.get()
|
||||||
.getPublicUrl()));
|
.getPublicUrl(), applicationSettingsService
|
||||||
|
.get().isImageProxyEnabled()));
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (STARRED.equals(id)) {
|
} else if (STARRED.equals(id)) {
|
||||||
@@ -106,7 +107,8 @@ public class CategoryREST extends AbstractResourceREST {
|
|||||||
for (FeedEntryStatus status : starred) {
|
for (FeedEntryStatus status : starred) {
|
||||||
entries.getEntries().add(
|
entries.getEntries().add(
|
||||||
Entry.build(status, applicationSettingsService.get()
|
Entry.build(status, applicationSettingsService.get()
|
||||||
.getPublicUrl()));
|
.getPublicUrl(), applicationSettingsService
|
||||||
|
.get().isImageProxyEnabled()));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
FeedCategory feedCategory = feedCategoryDAO.findById(getUser(),
|
FeedCategory feedCategory = feedCategoryDAO.findById(getUser(),
|
||||||
@@ -127,7 +129,9 @@ public class CategoryREST extends AbstractResourceREST {
|
|||||||
for (FeedEntryStatus status : list) {
|
for (FeedEntryStatus status : list) {
|
||||||
entries.getEntries().add(
|
entries.getEntries().add(
|
||||||
Entry.build(status, applicationSettingsService
|
Entry.build(status, applicationSettingsService
|
||||||
.get().getPublicUrl()));
|
.get().getPublicUrl(),
|
||||||
|
applicationSettingsService.get()
|
||||||
|
.isImageProxyEnabled()));
|
||||||
}
|
}
|
||||||
entries.setName(feedCategory.getName());
|
entries.setName(feedCategory.getName());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,7 +75,8 @@ public class EntryREST extends AbstractResourceREST {
|
|||||||
.findByKeywords(getUser(), keywords, offset, limit);
|
.findByKeywords(getUser(), keywords, offset, limit);
|
||||||
for (FeedEntryStatus status : entriesStatus) {
|
for (FeedEntryStatus status : entriesStatus) {
|
||||||
list.add(Entry.build(status, applicationSettingsService.get()
|
list.add(Entry.build(status, applicationSettingsService.get()
|
||||||
.getPublicUrl()));
|
.getPublicUrl(), applicationSettingsService.get()
|
||||||
|
.isImageProxyEnabled()));
|
||||||
}
|
}
|
||||||
|
|
||||||
entries.setName("Search for : " + keywords);
|
entries.setName("Search for : " + keywords);
|
||||||
|
|||||||
@@ -113,7 +113,8 @@ public class FeedREST extends AbstractResourceREST {
|
|||||||
for (FeedEntryStatus status : list) {
|
for (FeedEntryStatus status : list) {
|
||||||
entries.getEntries().add(
|
entries.getEntries().add(
|
||||||
Entry.build(status, applicationSettingsService.get()
|
Entry.build(status, applicationSettingsService.get()
|
||||||
.getPublicUrl()));
|
.getPublicUrl(), applicationSettingsService
|
||||||
|
.get().isImageProxyEnabled()));
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean hasMore = entries.getEntries().size() > limit;
|
boolean hasMore = entries.getEntries().size() > limit;
|
||||||
|
|||||||
@@ -2,8 +2,13 @@ package com.commafeed.frontend.rest.resources;
|
|||||||
|
|
||||||
import javax.ws.rs.GET;
|
import javax.ws.rs.GET;
|
||||||
import javax.ws.rs.Path;
|
import javax.ws.rs.Path;
|
||||||
|
import javax.ws.rs.Produces;
|
||||||
|
import javax.ws.rs.QueryParam;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
|
import javax.ws.rs.core.Response.Status;
|
||||||
|
|
||||||
|
import com.commafeed.backend.HttpGetter.HttpResult;
|
||||||
|
import com.commafeed.backend.feeds.FeedUtils;
|
||||||
import com.commafeed.frontend.model.ServerInfo;
|
import com.commafeed.frontend.model.ServerInfo;
|
||||||
import com.wordnik.swagger.annotations.Api;
|
import com.wordnik.swagger.annotations.Api;
|
||||||
import com.wordnik.swagger.annotations.ApiOperation;
|
import com.wordnik.swagger.annotations.ApiOperation;
|
||||||
@@ -23,4 +28,19 @@ public class ServerREST extends AbstractResourceREST {
|
|||||||
startupBean.getSupportedLanguages());
|
startupBean.getSupportedLanguages());
|
||||||
return Response.ok(infos).build();
|
return Response.ok(infos).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Path("/proxy")
|
||||||
|
@GET
|
||||||
|
@ApiOperation(value = "proxy image")
|
||||||
|
@Produces("image/png")
|
||||||
|
public Response get(@QueryParam("u") String url) {
|
||||||
|
url = FeedUtils.imageProxyDecoder(url);
|
||||||
|
try {
|
||||||
|
HttpResult result = httpGetter.getBinary(url);
|
||||||
|
return Response.ok(result.getContent()).build();
|
||||||
|
} catch (Exception e) {
|
||||||
|
return Response.status(Status.SERVICE_UNAVAILABLE)
|
||||||
|
.entity(e.getMessage()).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -222,6 +222,13 @@
|
|||||||
<column name="created" type="DATETIME" />
|
<column name="created" type="DATETIME" />
|
||||||
</addColumn>
|
</addColumn>
|
||||||
<sql>update USERS set created = lastLogin</sql>
|
<sql>update USERS set created = lastLogin</sql>
|
||||||
</changeSet>
|
</changeSet>
|
||||||
|
|
||||||
|
<changeSet author="athou" id="add-proxy-setting">
|
||||||
|
<addColumn tableName="APPLICATIONSETTINGS">
|
||||||
|
<column name="imageProxyEnabled" type="BIT" />
|
||||||
|
</addColumn>
|
||||||
|
<sql>update APPLICATIONSETTINGS set imageProxyEnabled=false</sql>
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
</databaseChangeLog>
|
</databaseChangeLog>
|
||||||
|
|||||||
@@ -137,6 +137,12 @@
|
|||||||
<input type="checkbox" name="pubsubhubbub" ng-model="settings.pubsubhubbub" />
|
<input type="checkbox" name="pubsubhubbub" ng-model="settings.pubsubhubbub" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label" for="imageProxyEnabled">Proxy entry images</label>
|
||||||
|
<div class="controls">
|
||||||
|
<input type="checkbox" name="imageProxyEnabled" ng-model="settings.imageProxyEnabled" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<label class="control-label" for="logLevel">Logging level</label>
|
<label class="control-label" for="logLevel">Logging level</label>
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
|
|||||||
Reference in New Issue
Block a user