Skip to content

Commit

Permalink
#467 (related) Prevent CSS import loop
Browse files Browse the repository at this point in the history
+ With test.
+ Cleanup of CSS code.
+ Removed stylesheet cache.
  • Loading branch information
danfickle committed Dec 19, 2020
1 parent 2d8edb7 commit 34aaf6a
Show file tree
Hide file tree
Showing 14 changed files with 74 additions and 146 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
import com.openhtmltopdf.css.extend.lib.DOMTreeResolver;
import com.openhtmltopdf.css.newmatch.CascadedStyle;
import com.openhtmltopdf.css.newmatch.PageInfo;
import com.openhtmltopdf.css.newmatch.Selector;
import com.openhtmltopdf.css.parser.CSSPrimitiveValue;
import com.openhtmltopdf.css.sheet.PropertyDeclaration;
import com.openhtmltopdf.css.sheet.Stylesheet;
Expand Down Expand Up @@ -211,29 +210,6 @@ public PageInfo getPageStyle(String pageName, String pseudoPage) {
return _matcher.getPageCascadedStyle(pageName, pseudoPage);
}

/**
* Flushes any stylesheet associated with this stylereference (based on the user agent callback) that are in cache.
* Deprecated for now, until we fix caching, use a new <code>StylesheetFactory</code> each run.
*/
@Deprecated
public void flushStyleSheets() {
String uri = _uac.getBaseURL();
StylesheetInfo info = new StylesheetInfo();
info.setUri(uri);
info.setOrigin(StylesheetInfo.AUTHOR);
if (_stylesheetFactory.containsStylesheet(uri)) {
_stylesheetFactory.removeCachedStylesheet(uri);
XRLog.log(Level.INFO, LogMessageId.LogMessageId1Param.CSS_PARSE_REMOVING_STYLESHEET_URI_FROM_CACHE_BY_REQUEST, uri);
} else {
XRLog.log(Level.INFO, LogMessageId.LogMessageId1Param.CSS_PARSE_REQUESTED_REMOVING_STYLESHEET_URI_NOT_IN_CACHE, uri);
}
}

@Deprecated
public void flushAllStyleSheets() {
_stylesheetFactory.flushCachedStylesheets();
}

/**
* Gets StylesheetInfos for all stylesheets and inline styles associated
* with the current document. Default (user agent) stylesheet and the inline
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@

import java.io.IOException;
import java.io.Reader;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;

import com.openhtmltopdf.css.extend.StylesheetFactory;
Expand All @@ -46,21 +48,19 @@ public class StylesheetFactoryImpl implements StylesheetFactory {
*/
private UserAgentCallback _userAgentCallback;

private final int _cacheCapacity = 16;
/**
* This may avoid @import loops, ie. one.css includes two.css
* which then includes one.css.
*/
private final Map<String, Integer> _seenStylesheetUris = new HashMap<>();

/**
* an LRU cache
* The maximum number of times a stylesheet uri can be link or
* imported before we give up and conclude there is a loop.
*/
private final java.util.LinkedHashMap<String, Stylesheet> _cache =
new java.util.LinkedHashMap<String, Stylesheet>(_cacheCapacity, 0.75f, true) {
private static final long serialVersionUID = 1L;
private static final int MAX_STYLESHEET_INCLUDES = 10;

protected boolean removeEldestEntry(java.util.Map.Entry<String, Stylesheet> eldest) {
return size() > _cacheCapacity;
}
};

private CSSParser _cssParser;
private final CSSParser _cssParser;

public StylesheetFactoryImpl(UserAgentCallback userAgentCallback) {
_userAgentCallback = userAgentCallback;
Expand Down Expand Up @@ -108,74 +108,27 @@ public Ruleset parseStyleDeclaration(int origin, String styleDeclaration) {
}

/**
* Adds a stylesheet to the factory cache. Will overwrite older entry for
* same key.
*
* @param key Key to use to reference sheet later; must be unique in
* factory.
* @param sheet The sheet to cache.
*/
@Deprecated
public void putStylesheet(String key, Stylesheet sheet) {
_cache.put(key, sheet);
}

/**
* @param key
* @return true if a Stylesheet with this key has been put in the cache.
* Note that the Stylesheet may be null.
*/
//TODO: work out how to handle caching properly, with cache invalidation
@Deprecated
public boolean containsStylesheet(String key) {
return _cache.containsKey(key);
}

/**
* Returns a cached sheet by its key; null if no entry for that key.
*
* @param key The key for this sheet; same as key passed to
* putStylesheet();
* @return The stylesheet
*/
@Deprecated
public Stylesheet getCachedStylesheet(String key) {
return _cache.get(key);
}

/**
* Removes a cached sheet by its key.
*
* @param key The key for this sheet; same as key passed to
* putStylesheet();
*/
@Deprecated
public Object removeCachedStylesheet(String key) {
return _cache.remove(key);
}

@Deprecated
public void flushCachedStylesheets() {
_cache.clear();
}

/**
* Returns a cached sheet by its key; loads and caches it if not in cache;
* Returns a sheet by its key
* null if not able to load
*
*
* @param info The StylesheetInfo for this sheet
* @return The stylesheet
*/
//TODO: this looks a bit odd
public Stylesheet getStylesheet(StylesheetInfo info) {
XRLog.log(Level.INFO, LogMessageId.LogMessageId1Param.LOAD_REQUESTING_STYLESHEET_AT_URI, info.getUri());

Stylesheet s = getCachedStylesheet(info.getUri());
if (s == null && !containsStylesheet(info.getUri())) {
s = parse(info);
putStylesheet(info.getUri(), s);
Integer includeCount = _seenStylesheetUris.get(info.getUri());

if (includeCount != null && includeCount >= MAX_STYLESHEET_INCLUDES) {
// Probably an import loop.
XRLog.log(Level.SEVERE, LogMessageId.LogMessageId2Param.CSS_PARSE_TOO_MANY_STYLESHEET_IMPORTS, includeCount, info.getUri());
return null;
}
return s;

_seenStylesheetUris.merge(info.getUri(), 1, (oldV, newV) -> oldV + 1);

return parse(info);
}

public void setUserAgentCallback(UserAgentCallback userAgent) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ private void import_rule(Stylesheet stylesheet) throws IOException {
t, new Token[] { Token.TK_STRING, Token.TK_URI }, getCurrentLine());
}

if (info.getMedia().size() == 0) {
if (info.getMedia().isEmpty()) {
info.addMedium("all");
}
stylesheet.addImportRule(info);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
package com.openhtmltopdf.css.sheet;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;

Expand All @@ -38,12 +39,12 @@
*/
public class StylesheetInfo {

private Stylesheet stylesheet = null;//just to be able to attach "dummy" stylesheets. Also might save a lookup if it's already looked up
private Stylesheet stylesheet = null;
private String title;
private String uri;
private int origin = USER_AGENT;
private String type;
private List<String> mediaTypes = new ArrayList<>();
private final List<String> mediaTypes = new ArrayList<>();
private String content;

/** Origin of stylesheet - user agent */
Expand All @@ -54,15 +55,20 @@ public class StylesheetInfo {

/** Origin of stylesheet - author */
public final static int AUTHOR = 2;


/**
* @param m a single media identifier
* @return true if the stylesheet referenced applies to the medium
*/
public boolean appliesToMedia(String m) {
return m.toLowerCase(Locale.US).equals("all") ||
mediaTypes.contains("all") || mediaTypes.contains(m.toLowerCase(Locale.US));
if (mediaTypes.contains("all")) {
return true;
}

String media = m.toLowerCase(Locale.US);

return media.equals("all") ||
mediaTypes.contains(media);
}

/**
Expand All @@ -80,18 +86,18 @@ public void setUri(String uri) {
* @param media The new media value
*/
public void setMedia(String media) {
String[] mediaTypes = media.split(",");
this.mediaTypes.clear();

for (int i = 0; i < mediaTypes.length; i++) {
this.mediaTypes.add(mediaTypes[i].trim().toLowerCase(Locale.US));

if (media == null || media.isEmpty()) {
// Common case, no media attribute, applies to all.
this.addMedium("all");
} else {
Arrays.stream(media.split(","))
.map(mediaType -> mediaType.trim().toLowerCase(Locale.US))
.forEach(this::addMedium);
}
}

public void setMedia(List<String> mediaTypes) {
this.mediaTypes = mediaTypes;
}


public void addMedium(String medium) {
mediaTypes.add(medium);
}
Expand Down Expand Up @@ -193,7 +199,7 @@ public String getContent() {
public void setContent(String content) {
this.content = content;
}

public boolean isInline() {
return this.content != null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,24 +129,6 @@ public class SharedContext {
public SharedContext() {
}


/**
* Constructor for the Context object
* @deprecated This stuff should go in the renderers of a specific device.
*/
@Deprecated
public SharedContext(UserAgentCallback uac, FontResolver fr, ReplacedElementFactory ref, TextRenderer tr, float dpi) {
fontResolver = fr;
replacedElementFactory = ref;
setMedia("screen");
this.uac = uac;
setCss(new StyleReference(uac));
XRLog.log(Level.INFO, LogMessageId.LogMessageId1Param.RENDER_USING_CSS_IMPLEMENTATION_FROM, getCss().getClass().getName());
setTextRenderer(tr);
setDPI(dpi);
}


public LayoutContext newLayoutContextInstance() {
LayoutContext c = new LayoutContext(this);
return c;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -255,11 +255,9 @@ private Element findFirstChild(Element parent, String targetName) {
}

protected StylesheetInfo readStyleElement(Element style) {
String media = style.getAttribute("media");
if ("".equals(media)) {
media = "all";
}//default for HTML is "screen", but that is silly and firefox seems to assume "all"
StylesheetInfo info = new StylesheetInfo();
String media = style.getAttribute("media");

info.setMedia(media);
info.setType(style.getAttribute("type"));
info.setTitle(style.getAttribute("title"));
Expand Down Expand Up @@ -306,12 +304,9 @@ protected StylesheetInfo readLinkElement(Element link) {
info.setType(type);

info.setOrigin(StylesheetInfo.AUTHOR);

info.setUri(link.getAttribute("href"));

String media = link.getAttribute("media");
if ("".equals(media)) {
media = "all";
}
info.setMedia(media);

String title = link.getAttribute("title");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ public String formatMessage(Object[] args) {
}

enum LogMessageId2Param implements LogMessageId {
CSS_PARSE_TOO_MANY_STYLESHEET_IMPORTS(XRLog.CSS_PARSE, "Gave up after {} attempts to load stlyesheet at {} to avoid possible loop"),
CSS_PARSE_COULDNT_PARSE_STYLESHEET_AT_URI(XRLog.CSS_PARSE, "Couldn't parse stylesheet at URI {}: {}"),
CSS_PARSE_GENERIC_MESSAGE(XRLog.CSS_PARSE, "({}) {}"),

Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<html>
<head>
<style>
@page {
size: 200px 200px;
}
</style>
<link rel="stylesheet" href="stylesheets/loop-one.css" />
</head>
<body>
<div id="one">RED</div>
<div id="two">BLUE</div>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@import "loop-two.css";

div#one { color: red; }
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@import "loop-one.css";

div#two { color: blue; }
Original file line number Diff line number Diff line change
Expand Up @@ -1343,6 +1343,11 @@ public void testMoreRobustDataUriBase64() throws IOException {
assertTrue(vt.runTest("more-robust-data-uri-base64"));
}

@Test
public void testCSSImportLoop() throws IOException {
assertTrue(vt.runTest("css-import-loop"));
}

// TODO:
// + Elements that appear just on generated overflow pages.
// + content property (page counters, etc)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,11 +233,6 @@ private void setDocument(Document doc, String url, NamespaceHandler nsh) {

//TODOgetFontResolver().flushFontFaceFonts();

if (Configuration.isTrue("xr.cache.stylesheets", true)) {
_sharedContext.getCss().flushStyleSheets();
} else {
_sharedContext.getCss().flushAllStyleSheets();
}
_sharedContext.setBaseURL(url);
_sharedContext.setNamespaceHandler(nsh);
_sharedContext.getCss().setDocumentContext(_sharedContext, _sharedContext.getNamespaceHandler(), doc, new NullUserInterface());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -314,11 +314,6 @@ private void setDocumentP(Document doc, String url, NamespaceHandler nsh) {

getFontResolver().flushFontFaceFonts();

if (Configuration.isTrue("xr.cache.stylesheets", true)) {
_sharedContext.getCss().flushStyleSheets();
} else {
_sharedContext.getCss().flushAllStyleSheets();
}
_sharedContext.setBaseURL(url);
_sharedContext.setNamespaceHandler(nsh);
_sharedContext.getCss().setDocumentContext(_sharedContext, _sharedContext.getNamespaceHandler(), doc, new NullUserInterface());
Expand Down

0 comments on commit 34aaf6a

Please sign in to comment.