Skip to content

Commit

Permalink
feat: allow fonts to be remote resources
Browse files Browse the repository at this point in the history
Feature changes:
- publication resources used in font declarations (in CSS documents,
  in inline CSS content of XHTML documents or SVG, or in SVG's
  deprecated `font-face-uri` element) MAY be remote resources.
- remote resource check is dependent on content parsing, so the check is
  moved to *after* the resource contents are checked, and only
  run when checking an entire publication.

Fix:
- check that the item property "remote-resource" is properly set when
  required on items representing CSS documents

Internal changes:
- Add a new `FONT` type to XRefChecker references
- Register as `XRefChecker` references of type `FONT` the paths found in
  CSS `@font-face` declaration and SVG `font-face-uri` elements.
- Add a new `getTypes(path)` public method in `XRefChecker`, to get the
  type(s) of reference(s) to a given publication resource
- Add a new `isRemote(String path)` util method to `PathUtil`
- `CSSHandler` constructor only receives a `ValidationContext`
- add tests

Fix #871, Fix #672
  • Loading branch information
rdeltour committed Mar 10, 2019
1 parent 56ebcd8 commit 4d5a5a9
Show file tree
Hide file tree
Showing 75 changed files with 972 additions and 68 deletions.
3 changes: 1 addition & 2 deletions src/main/java/com/adobe/epubcheck/css/CSSChecker.java
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,7 @@ public void runChecks()
return;
}

CSSHandler handler = new CSSHandler(path, context.xrefChecker.orNull(), report,
context.version);
CSSHandler handler = new CSSHandler(context);
if (this.mode == Mode.STRING && this.line > -1)
{
handler.setStartingLineNumber(this.line);
Expand Down
69 changes: 56 additions & 13 deletions src/main/java/com/adobe/epubcheck/css/CSSHandler.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.adobe.epubcheck.css;

import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

Expand All @@ -19,14 +21,21 @@
import com.adobe.epubcheck.messages.MessageId;
import com.adobe.epubcheck.opf.OPFChecker;
import com.adobe.epubcheck.opf.OPFChecker30;
import com.adobe.epubcheck.opf.ValidationContext;
import com.adobe.epubcheck.opf.XRefChecker;
import com.adobe.epubcheck.opf.XRefChecker.Type;
import com.adobe.epubcheck.util.EPUBVersion;
import com.adobe.epubcheck.util.FeatureEnum;
import com.adobe.epubcheck.util.PathUtil;
import com.adobe.epubcheck.vocab.PackageVocabs;
import com.adobe.epubcheck.vocab.PackageVocabs.ITEM_PROPERTIES;
import com.adobe.epubcheck.vocab.Property;
import com.google.common.base.CharMatcher;
import com.google.common.collect.Sets;

public class CSSHandler implements CssContentHandler, CssErrorHandler
{
final ValidationContext context;
final String path;
final XRefChecker xrefChecker;
final Report report;
Expand All @@ -44,14 +53,17 @@ public class CSSHandler implements CssContentHandler, CssErrorHandler
boolean hasFontFaceDeclarations = false;
boolean inKeyFrames = false;
CssAtRule atRule = null;

// properties the must be declared on the related OPF item
final Set<ITEM_PROPERTIES> detectedProperties = EnumSet.noneOf(ITEM_PROPERTIES.class);

public CSSHandler(String path, XRefChecker xrefChecker, Report report,
EPUBVersion version)
public CSSHandler(ValidationContext context)
{
this.path = path;
this.xrefChecker = xrefChecker;
this.report = report;
this.version = version;
this.context = context;
this.path = context.path;
this.xrefChecker = context.xrefChecker.orNull();
this.report = context.report;
this.version = context.version;
}

private EPUBLocation getCorrectedEPUBLocation(String fileName, int lineNumber, int columnNumber, String context)
Expand Down Expand Up @@ -104,6 +116,7 @@ public void startDocument()
@Override
public void endDocument()
{
checkProperties();
}

static final Pattern keyframesPattern = Pattern.compile("@((keyframes)|(-moz-keyframes)|(-webkit-keyframes)|(-o-keyframes))");
Expand Down Expand Up @@ -135,7 +148,7 @@ else if (uriOrString.getType() == CssConstruct.Type.STRING)
}
if (uri != null)
{
resolveAndRegister(uri, line, col, atRule.toCssString());
resolveAndRegister(uri, line, col, atRule.toCssString(), Type.GENERIC);
}
}
}
Expand Down Expand Up @@ -255,7 +268,7 @@ else if (propertyName.equals("src"))
{
fontUri = ((CssURI) construct).toUriString();
fontUri = PathUtil.resolveRelativeReference(path, fontUri);
//check font mimetypes
// check font mimetypes
String fontMimeType = xrefChecker.getMimeType(fontUri);
if (fontMimeType != null)
{
Expand Down Expand Up @@ -298,17 +311,20 @@ private void registerURIs(List<CssConstruct> constructs, int line, int col)
{
if (construct.getType() == CssConstruct.Type.URI)
{
resolveAndRegister(((CssURI) construct).toUriString(), line, col, construct.toCssString());
resolveAndRegister(((CssURI) construct).toUriString(), line, col, construct.toCssString(), inFontFace?Type.FONT:Type.GENERIC);
}
}
}

private void resolveAndRegister(String relativeRef, int line, int col, String context)
private void resolveAndRegister(String uri, int line, int col, String context, Type type)
{
if (relativeRef != null && relativeRef.trim().length() > 0)
if (uri != null && uri.trim().length() > 0)
{
String resolved = PathUtil.resolveRelativeReference(path, relativeRef);
xrefChecker.registerReference(path, line + startingLineNumber, col, resolved, XRefChecker.Type.GENERIC);
String resolved = PathUtil.resolveRelativeReference(path, uri);
xrefChecker.registerReference(path, correctedLineNumber(line), correctedColumnNumber(line, col), resolved, type);
if (PathUtil.isRemote(resolved)) {
detectedProperties.add(ITEM_PROPERTIES.REMOTE_RESOURCES);
}
}
else
{
Expand Down Expand Up @@ -339,6 +355,33 @@ private void handleFontFaceInfo()
}
}
}

protected void checkProperties() {

// Exit early if we don't have container-level info (single file validation)
if (!context.ocf.isPresent()) // single file validation
{
return;
}

Set<ITEM_PROPERTIES> declaredProperties = Property.filter(context.properties, ITEM_PROPERTIES.class);

// Check that all properties found in the doc are declared on the OPF item
for (ITEM_PROPERTIES property : Sets.difference(detectedProperties, declaredProperties))
{
report.message(MessageId.OPF_014, EPUBLocation.create(path, startingLineNumber, startingLineNumber),
PackageVocabs.ITEM_VOCAB.getName(property));
}

// Check that properties declared in the OPF item were found in the content
Set<ITEM_PROPERTIES> uncheckedProperties = Sets.difference(declaredProperties, detectedProperties)
.copyInto(EnumSet.noneOf(ITEM_PROPERTIES.class));
if (uncheckedProperties.contains(ITEM_PROPERTIES.REMOTE_RESOURCES))
{
uncheckedProperties.remove(ITEM_PROPERTIES.REMOTE_RESOURCES);
report.message(MessageId.OPF_018, EPUBLocation.create(path, startingLineNumber, startingLineNumber));
}
}

public void setStartingLineNumber(int offset)
{
Expand Down
11 changes: 10 additions & 1 deletion src/main/java/com/adobe/epubcheck/opf/OPFChecker.java
Original file line number Diff line number Diff line change
Expand Up @@ -150,20 +150,29 @@ public void runChecks()
checkGuide();
checkBindings();

// Check items content (publication resources)
for (OPFItem item : items)
{
if (!item.getPath().matches("^[^:/?#]+://.*"))
{
checkItemContent(item);
}
}

// Checks items after the content-validation pass
// This allows to run checks depending on info collected in publication resources
for (OPFItem item : items) {
checkItemAfterResourceValidation(item);
}

xrefChecker.checkReferences();
}

protected void checkItemAfterResourceValidation(OPFItem item) {
}

protected void checkBindings()
{

}

protected void checkGuide()
Expand Down
29 changes: 28 additions & 1 deletion src/main/java/com/adobe/epubcheck/opf/OPFChecker30.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

import java.net.URI;
import java.net.URISyntaxException;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;
Expand All @@ -38,6 +39,7 @@
import com.adobe.epubcheck.messages.MessageId;
import com.adobe.epubcheck.opf.MetadataSet.Metadata;
import com.adobe.epubcheck.opf.ResourceCollection.Roles;
import com.adobe.epubcheck.opf.XRefChecker.Type;
import com.adobe.epubcheck.ops.OPSCheckerFactory;
import com.adobe.epubcheck.overlay.OverlayCheckerFactory;
import com.adobe.epubcheck.util.EPUBVersion;
Expand Down Expand Up @@ -133,7 +135,25 @@ protected void checkItem(OPFItem item, OPFHandler opfHandler)

// Note: item fallback existence is checked in schematron, i.e.:
// opfHandler.getItemById(item.getFallback().get()).isPresent() == true

}

@Override
protected void checkItemAfterResourceValidation(OPFItem item)
{
XRefChecker xrefChecker = context.xrefChecker.get();

// Report remote resources when not allowed
String mediatype = item.getMimeType();
if (item.getPath().matches("^[^:/?#]+://.*")
&& !(isAudioType(mediatype)
|| isVideoType(mediatype)
|| "application/x-shockwave-flash".equals(mediatype)
|| isFontType(mediatype)
|| xrefChecker.getTypes(item.getPath()).equals(EnumSet.of(Type.FONT))))
{
report.message(MessageId.RSC_006,
EPUBLocation.create(path, item.getLineNumber(), item.getColumnNumber()), item.getPath());
}
}

@Override
Expand Down Expand Up @@ -488,6 +508,13 @@ public static boolean isCommonVideoType(String type)
return "video/h264".equals(type) || "video/webm".equals(type)
|| "video/mp4".equals(type);
}

public static boolean isFontType(String type)
{
return type.startsWith("font/")
|| type.startsWith("application/font-")
|| type.equals("application/vnd.ms-opentype");
}

public static boolean isBlessedFontType(String type)
{
Expand Down
11 changes: 0 additions & 11 deletions src/main/java/com/adobe/epubcheck/opf/OPFHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -303,16 +303,6 @@ else if (name.equals("item"))
String fallbackStyle = (context.version == EPUBVersion.VERSION_3) ? e
.getAttribute("fallback") : e.getAttribute("fallback-style");

if (context.version == EPUBVersion.VERSION_3 && href.matches("^[^:/?#]+://.*")
&& !OPFChecker30.isAudioType(mimeType)
&& !OPFChecker30.isVideoType(mimeType)
&& !"application/x-shockwave-flash".equals(mimeType))
{
report
.message(MessageId.RSC_006,
EPUBLocation.create(path, parser.getLineNumber(), parser.getColumnNumber()),
href);
}

OPFItem.Builder itemBuilder = new OPFItem.Builder(id, href, mimeType,
parser.getLineNumber(), parser.getColumnNumber()).fallback(fallback).fallbackStyle(
Expand Down Expand Up @@ -708,6 +698,5 @@ protected void reportItem(OPFItem item)
EPUBLocation.create(path, item.getLineNumber(), item.getColumnNumber()));
}
}

}
}
35 changes: 29 additions & 6 deletions src/main/java/com/adobe/epubcheck/opf/XRefChecker.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@

package com.adobe.epubcheck.opf;

import java.util.EnumSet;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.Set;
import java.util.Vector;

Expand All @@ -34,16 +36,20 @@
import com.adobe.epubcheck.ocf.OCFPackage;
import com.adobe.epubcheck.util.EPUBVersion;
import com.adobe.epubcheck.util.FeatureEnum;
import com.adobe.epubcheck.util.PathUtil;
import com.adobe.epubcheck.vocab.PackageVocabs;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;

public class XRefChecker
{

public static enum Type
{
GENERIC,
FONT,
HYPERLINK,
LINK,
IMAGE,
Expand Down Expand Up @@ -153,6 +159,23 @@ public Optional<Boolean> hasValidFallback(String path)
return resources.get(path) != null ? Optional.of(resources.get(path).hasValidItemFallback)
: Optional.<Boolean> absent();
}

/**
* Returns set (possibly multiple) types of refereences to the given resource
* @param path the path to a publication resource
* @return an immutable {@link EnumSet} containing the types of references to {@code path}.
*/
public Set<Type> getTypes(String path) {
LinkedList<Type> types = new LinkedList<>();
for (Reference reference : references)
{
if (Preconditions.checkNotNull(path).equals(reference.refResource))
{
types.add(reference.type);
}
}
return Sets.immutableEnumSet(types);
}

public Set<String> getBindingsMimeTypes()
{
Expand Down Expand Up @@ -200,7 +223,7 @@ public void registerReference(String srcResource, int srcLineNumber, int srcColu
// see http://code.google.com/p/epubcheck/issues/detail?id=190
// see http://code.google.com/p/epubcheck/issues/detail?id=261
int query = ref.indexOf('?');
if (query >= 0 && !ref.matches("^[^:/?#]+://.*"))
if (query >= 0 && !PathUtil.isRemote(ref))
{
ref = ref.substring(0, query).trim();
}
Expand Down Expand Up @@ -245,7 +268,7 @@ private void checkReference(Reference ref)
{
if (version == EPUBVersion.VERSION_3 && ref.type == Type.LINK)
{
if (ref.refResource.matches("^[^:/?#]+://.*") || ocf.hasEntry(ref.refResource))
if (PathUtil.isRemote(ref.refResource) || ocf.hasEntry(ref.refResource))
{
return;
}
Expand All @@ -256,13 +279,13 @@ private void checkReference(Reference ref)
ref.refResource);
}
}
else if (ref.refResource.matches("^[^:/?#]+://.*") && !(version == EPUBVersion.VERSION_3
&& (ref.type == Type.AUDIO || ref.type == Type.VIDEO)))
else if (PathUtil.isRemote(ref.refResource) && !(version == EPUBVersion.VERSION_3
&& (ref.type == Type.AUDIO || ref.type == Type.VIDEO || ref.type == Type.FONT)))
{
report.message(MessageId.RSC_006,
EPUBLocation.create(ref.source, ref.lineNumber, ref.columnNumber, ref.refResource));
}
else if (!ocf.hasEntry(ref.refResource) && !ref.refResource.matches("^[^:/?#]+://.*"))
else if (!ocf.hasEntry(ref.refResource) && !PathUtil.isRemote(ref.refResource))
{
report.message(MessageId.RSC_007,
EPUBLocation.create(ref.source, ref.lineNumber, ref.columnNumber, ref.refResource),
Expand Down Expand Up @@ -382,7 +405,7 @@ else if (ref.fragment.contains("=") && host != null && host.item.getProperties()
return;
}
// Fragment Identifier (by default)
else
else if (!PathUtil.isRemote(ref.refResource))
{
Anchor anchor = res.anchors.get(ref.fragment);
if (anchor == null)
Expand Down
17 changes: 17 additions & 0 deletions src/main/java/com/adobe/epubcheck/ops/OPSHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,19 @@ else if (uri.getScheme() != null)
}
processHyperlink(href);
}



protected void checkSVGFontFaceURI(XMLElement e, String attrNS, String attr)
{
String href = e.getAttributeNS(attrNS, attr);
if (xrefChecker.isPresent() && href != null)
{
href = PathUtil.resolveRelativeReference(base, href);
xrefChecker.get().registerReference(path, parser.getLineNumber(), parser.getColumnNumber(),
href, XRefChecker.Type.FONT);
}
}

protected void processHyperlink(String href)
{
Expand Down Expand Up @@ -343,6 +356,10 @@ else if (name.equals("image"))
{
checkImage(e, "http://www.w3.org/1999/xlink", "href");
}
else if (name.equals("font-face-uri"))
{
checkSVGFontFaceURI(e, "http://www.w3.org/1999/xlink", "href");
}
checkPaint(e, "fill");
checkPaint(e, "stroke");
}
Expand Down
Loading

0 comments on commit 4d5a5a9

Please sign in to comment.