Skip to content

Commit

Permalink
add(nahsragh-552): support for @media rule
Browse files Browse the repository at this point in the history
  • Loading branch information
jonah1und1 committed Feb 4, 2025
1 parent f94866b commit 8e4f547
Show file tree
Hide file tree
Showing 10 changed files with 846 additions and 20 deletions.
75 changes: 55 additions & 20 deletions src/main/java/org/owasp/validator/css/CssHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@
import java.util.LinkedList;
import java.util.List;
import java.util.ResourceBundle;
import org.owasp.validator.css.media.CssMediaFeature;
import org.owasp.validator.css.media.CssMediaQuery;
import org.owasp.validator.css.media.CssMediaQueryList;
import org.owasp.validator.html.InternalPolicy;
import org.owasp.validator.html.Policy;
import org.owasp.validator.html.ScanException;
Expand Down Expand Up @@ -96,6 +99,10 @@ public class CssHandler implements DocumentHandler {
*/
private boolean selectorOpen = false;

private MediaState mediaState = MediaState.OUTSIDE;

private enum MediaState {INSIDE, OUTSIDE, DENIED}

/**
* Constructs a handler for stylesheets using the given policy. The List of embedded stylesheets
* produced by this constructor is now available via the getImportedStylesheetsURIList() method.
Expand Down Expand Up @@ -340,26 +347,6 @@ public void endFontFace() throws CSSException {
// CSS2 Font Face declaration - ignore this for now
}

/*
* (non-Javadoc)
*
* @see org.w3c.css.sac.DocumentHandler#startMedia(org.w3c.css.sac.SACMediaList)
*/
@Override
public void startMedia(SACMediaList media) throws CSSException {
// CSS2 Media declaration - ignore this for now
}

/*
* (non-Javadoc)
*
* @see org.w3c.css.sac.DocumentHandler#endMedia(org.w3c.css.sac.SACMediaList)
*/
@Override
public void endMedia(SACMediaList media) throws CSSException {
// CSS2 Media declaration - ignore this for now
}

/*
* (non-Javadoc)
*
Expand Down Expand Up @@ -538,4 +525,52 @@ public void property(String name, LexicalUnit value, boolean important) throws C
}));
}
}

@Override
public void startMedia(SACMediaList media) throws CSSException {
CssMediaQueryList mediaQueryList = (CssMediaQueryList) media;

boolean first = true;
for (CssMediaQuery query : mediaQueryList.getMediaQueries()) {
if (!validator.isValidMediaQuery(query)) {
continue;
}
if (first) {
styleSheet.append("@media ");
first = false;
} else {
styleSheet.append(", ");
}
if (query.getLogicalOperator() != null) {
styleSheet.append(query.getLogicalOperator()).append(' ');
}
styleSheet.append(query.getMediaType());
for (CssMediaFeature feature : query.getMediaFeatures()) {
styleSheet.append(" and (");
styleSheet.append(feature.getName());
if (feature.getExpression() != null) {
styleSheet.append(": ");
styleSheet.append(validator.lexicalValueToString(feature.getExpression()));
}
styleSheet.append(')');
}
}

if (!first) {
styleSheet.append(" {");
styleSheet.append('\n');
mediaState = MediaState.INSIDE;
} else {
mediaState = MediaState.DENIED;
}
}

@Override
public void endMedia(SACMediaList media) throws CSSException {
if (mediaState == MediaState.INSIDE) {
styleSheet.append('}');
styleSheet.append('\n');
}
mediaState = MediaState.OUTSIDE;
}
}
111 changes: 111 additions & 0 deletions src/main/java/org/owasp/validator/css/CssParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,15 @@
*/
package org.owasp.validator.css;

import static org.owasp.validator.css.media.CssMediaQueryLogicalOperator.AND;

import org.apache.batik.css.parser.CSSSACMediaList;
import org.apache.batik.css.parser.LexicalUnits;
import org.owasp.validator.css.media.CssMediaFeature;
import org.owasp.validator.css.media.CssMediaQuery;
import org.owasp.validator.css.media.CssMediaQueryList;
import org.owasp.validator.css.media.CssMediaQueryLogicalOperator;
import org.owasp.validator.css.media.CssMediaType;
import org.w3c.css.sac.CSSException;
import org.w3c.css.sac.CSSParseException;
import org.w3c.css.sac.LexicalUnit;
Expand Down Expand Up @@ -96,4 +104,107 @@ protected void parseStyleDeclaration(final boolean inSheet) throws CSSException
}
}
}

@Override
protected CSSSACMediaList parseMediaList() {
CssMediaQueryList mediaList = new CssMediaQueryList();

mediaList.append(parseMediaQuery());
while (current == LexicalUnits.COMMA) {
nextIgnoreSpaces();
mediaList.append(parseMediaQuery());
}

return mediaList;
}

protected CssMediaQuery parseMediaQuery() {
CssMediaQuery query = new CssMediaQuery();
switch (current) {
case LexicalUnits.LEFT_BRACE:
query.setMediaType(CssMediaType.ALL);
query.addMediaFeature(parseMediaFeature());
break;
case LexicalUnits.IDENTIFIER:
final CssMediaQueryLogicalOperator logicalOperator = CssMediaQueryLogicalOperator.parse(scanner.getStringValue());
if (logicalOperator != null) {
query.setLogicalOperator(logicalOperator);
if (nextIgnoreSpaces() != LexicalUnits.IDENTIFIER) {
throw createCSSParseException("identifier");
}
}
final CssMediaType mediaType = CssMediaType.parse(scanner.getStringValue());
if (mediaType == null) {
throw createCSSParseException("identifier");
}
query.setMediaType(mediaType);
nextIgnoreSpaces();
break;
default:
throw createCSSParseException("identifier");
}

while (current == LexicalUnits.IDENTIFIER && CssMediaQueryLogicalOperator.parse(scanner.getStringValue()) == AND) {
nextIgnoreSpaces();
query.addMediaFeature(parseMediaFeature());
}
return query;
}

protected CssMediaFeature parseMediaFeature() {
if (current != LexicalUnits.LEFT_BRACE) {
throw createCSSParseException("'(' expected.");
}
nextIgnoreSpaces();
String namePrefix = "";
if (current == LexicalUnits.MINUS) {
nextIgnoreSpaces();
namePrefix = "-";
}
if (current != LexicalUnits.IDENTIFIER) {
throw createCSSParseException("identifier");
}
String name = namePrefix + scanner.getStringValue();
nextIgnoreSpaces();
LexicalUnit exp = null;
if (current == LexicalUnits.COLON) {
nextIgnoreSpaces();
exp = parseTerm(null);
}
if (current != LexicalUnits.RIGHT_BRACE) {
throw createCSSParseException("right.brace");
}
nextIgnoreSpaces();

return new CssMediaFeature(name, exp);
}

@Override
protected void parseMediaRule() {
CSSSACMediaList ml = parseMediaList();
try {
documentHandler.startMedia(ml);

if (current != LexicalUnits.LEFT_CURLY_BRACE) {
reportError("left.curly.brace");
} else {
nextIgnoreSpaces();

loop:
for (; ; ) {
switch (current) {
case LexicalUnits.EOF:
case LexicalUnits.RIGHT_CURLY_BRACE:
break loop;
default:
parseRuleSet();
}
}

nextIgnoreSpaces();
}
} finally {
documentHandler.endMedia(ml);
}
}
}
41 changes: 41 additions & 0 deletions src/main/java/org/owasp/validator/css/CssValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,13 @@
import java.text.DecimalFormat;
import java.util.Iterator;
import java.util.regex.Pattern;
import org.apache.batik.css.parser.CSSLexicalUnit;
import org.owasp.validator.css.media.CssMediaFeature;
import org.owasp.validator.css.media.CssMediaQuery;
import org.owasp.validator.html.Policy;
import org.owasp.validator.html.ScanException;
import org.owasp.validator.html.model.AntiSamyPattern;
import org.owasp.validator.html.model.Attribute;
import org.owasp.validator.html.model.Property;
import org.owasp.validator.html.util.HTMLEntityEncoder;
import org.w3c.css.sac.AttributeCondition;
Expand All @@ -56,6 +60,8 @@
*/
public class CssValidator {

private static final LexicalUnit EMPTYSTRINGLEXICALUNIT = CSSLexicalUnit.createString(LexicalUnit.SAC_STRING_VALUE, "", null);

private final Policy policy;

/**
Expand Down Expand Up @@ -406,6 +412,41 @@ public String lexicalValueToString(LexicalUnit lu) {
}
}

/**
* Returns whether the given {@link CssMediaQuery} is valid
*
* @param mediaQuery mediaQuery
* @return valid mediaQuery?
*/
public boolean isValidMediaQuery(CssMediaQuery mediaQuery) {
// check mediaType against allowed media-HTML-Attribute
Attribute mediaAttribute = policy.getTagByLowercaseName("style").getAttributeByName("media");
if (mediaAttribute == null) {
return false;
}

String mediaTypeString = mediaQuery.getMediaType().toString().toLowerCase();
boolean isValidMediaType = mediaAttribute.containsAllowedValue(mediaTypeString) || mediaAttribute.matchesAllowedExpression(mediaTypeString);
if (!isValidMediaType) {
return false;
}

for (CssMediaFeature feature : mediaQuery.getMediaFeatures()) {
LexicalUnit expression = feature.getExpression();
if (expression == null) {
expression = EMPTYSTRINGLEXICALUNIT;
}
if (!isValidMediaFeature(feature.getName(), expression)) {
return false;
}
}
return true;
}

private boolean isValidMediaFeature(String name, LexicalUnit lu) {
return isValidProperty("_mediafeature_" + name, lu);
}

/**
* Returns color value as int.
* Maps percentages to values between 0 and 255.
Expand Down
28 changes: 28 additions & 0 deletions src/main/java/org/owasp/validator/css/media/CssMediaFeature.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.owasp.validator.css.media;

import org.w3c.css.sac.LexicalUnit;

public class CssMediaFeature {

private final String name;
private final LexicalUnit expression;

/**
* Constructor.
*
* @param name Feature-name
* @param expression expression, may be null
*/
public CssMediaFeature(String name, LexicalUnit expression) {
this.name = name;
this.expression = expression;
}

public String getName() {
return name;
}

public LexicalUnit getExpression() {
return expression;
}
}
36 changes: 36 additions & 0 deletions src/main/java/org/owasp/validator/css/media/CssMediaQuery.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package org.owasp.validator.css.media;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class CssMediaQuery {

private CssMediaQueryLogicalOperator logicalOperator;
private CssMediaType mediaType;
private final List<CssMediaFeature> mediaFeatures = new ArrayList<>();

public CssMediaQueryLogicalOperator getLogicalOperator() {
return logicalOperator;
}

public void setLogicalOperator(CssMediaQueryLogicalOperator logicalOperator) {
this.logicalOperator = logicalOperator;
}

public CssMediaType getMediaType() {
return mediaType;
}

public void setMediaType(CssMediaType mediaType) {
this.mediaType = mediaType;
}

public void addMediaFeature(CssMediaFeature mediaFeature) {
mediaFeatures.add(mediaFeature);
}

public List<CssMediaFeature> getMediaFeatures() {
return Collections.unmodifiableList(mediaFeatures);
}
}
Loading

0 comments on commit 8e4f547

Please sign in to comment.