diff --git a/WebContent/WEB-INF/jsp/include/highlight.jsp b/WebContent/WEB-INF/jsp/include/highlight.jsp
index 1f2d229dc4..41ad467f82 100644
--- a/WebContent/WEB-INF/jsp/include/highlight.jsp
+++ b/WebContent/WEB-INF/jsp/include/highlight.jsp
@@ -15,9 +15,6 @@ function updateCodeText(text, destination) {
function updateCodeTextEscaped(text, destination) {
let result_element = document.querySelector(destination);
- if(text[text.length-1] == "\n") {
- text += " ";
- }
result_element.innerHTML = text;
hljs.highlightElement(result_element);
}
diff --git a/WebContent/WEB-INF/jsp/systemSettings.jsp b/WebContent/WEB-INF/jsp/systemSettings.jsp
index 35899f2afa..c8e12ff945 100644
--- a/WebContent/WEB-INF/jsp/systemSettings.jsp
+++ b/WebContent/WEB-INF/jsp/systemSettings.jsp
@@ -33,7 +33,7 @@
@@ -1142,13 +1163,18 @@
+
diff --git a/WebContent/WEB-INF/lib/classmate-1.3.4.jar b/WebContent/WEB-INF/lib/classmate-1.3.4.jar
new file mode 100644
index 0000000000..5be6d9961f
Binary files /dev/null and b/WebContent/WEB-INF/lib/classmate-1.3.4.jar differ
diff --git a/WebContent/WEB-INF/lib/cssparser-0.9.30.jar b/WebContent/WEB-INF/lib/cssparser-0.9.30.jar
new file mode 100644
index 0000000000..1ee9271c84
Binary files /dev/null and b/WebContent/WEB-INF/lib/cssparser-0.9.30.jar differ
diff --git a/WebContent/WEB-INF/lib/hibernate-validator-6.0.23.Final.jar b/WebContent/WEB-INF/lib/hibernate-validator-6.0.23.Final.jar
new file mode 100644
index 0000000000..a6bed2af81
Binary files /dev/null and b/WebContent/WEB-INF/lib/hibernate-validator-6.0.23.Final.jar differ
diff --git a/WebContent/WEB-INF/lib/jboss-logging-3.3.2.Final.jar b/WebContent/WEB-INF/lib/jboss-logging-3.3.2.Final.jar
new file mode 100644
index 0000000000..67cde710fc
Binary files /dev/null and b/WebContent/WEB-INF/lib/jboss-logging-3.3.2.Final.jar differ
diff --git a/WebContent/WEB-INF/lib/sac-1.3.jar b/WebContent/WEB-INF/lib/sac-1.3.jar
new file mode 100644
index 0000000000..39b92b1d89
Binary files /dev/null and b/WebContent/WEB-INF/lib/sac-1.3.jar differ
diff --git a/WebContent/WEB-INF/lib/validation-api-2.0.1.Final.jar b/WebContent/WEB-INF/lib/validation-api-2.0.1.Final.jar
new file mode 100644
index 0000000000..2368e10a53
Binary files /dev/null and b/WebContent/WEB-INF/lib/validation-api-2.0.1.Final.jar differ
diff --git a/build.gradle b/build.gradle
index d4ef81117f..2962dd4b1e 100644
--- a/build.gradle
+++ b/build.gradle
@@ -242,6 +242,7 @@ test {
includeTestsMatching "org.scada_lts.utils.BlockingQueuesUtilsTest"
includeTestsMatching "org.scada_lts.web.security.XssProtectHtmlEscapeUtilsTest"
includeTestsMatching "org.scada_lts.web.security.XssUtilsTest"
+ includeTestsMatching "org.scada_lts.web.mvc.api.css.validation.utils.CssValidatorTestsSuite"
}
failFast = true
diff --git a/src/org/scada_lts/config/ScadaConfig.java b/src/org/scada_lts/config/ScadaConfig.java
index a509ff4726..379154faa7 100644
--- a/src/org/scada_lts/config/ScadaConfig.java
+++ b/src/org/scada_lts/config/ScadaConfig.java
@@ -148,6 +148,7 @@ public class ScadaConfig {
private static final String FILE_NAME_PROPERTIES="env.properties";
private static final String FILE_NAME_CUSTOM_CSS="common.css";
private static final String DIR_NAME_CUSTOM_CONFIG="assets";
+ private static final String FILE_NAME_USER_STYLESHEET="user_styles.css";
private static ScadaConfig instance = null;
@@ -424,6 +425,5 @@ private static String getPathCustomConfig() {
return path;
}
-
}
diff --git a/src/org/scada_lts/dao/SystemSettingsDAO.java b/src/org/scada_lts/dao/SystemSettingsDAO.java
index 12aa9edcce..f469be10e4 100644
--- a/src/org/scada_lts/dao/SystemSettingsDAO.java
+++ b/src/org/scada_lts/dao/SystemSettingsDAO.java
@@ -166,6 +166,7 @@ public class SystemSettingsDAO {
public static final String EVENT_ASSIGN_ENABLED = "eventAssignEnabled";
public static final String TOP_DESCRIPTION_PREFIX = "topDescriptionPrefix";
public static final String TOP_DESCRIPTION = "topDescription";
+ public static final String CUSTOM_CSS_CONTENT = "customCssContent";
// @formatter:off
private static final String SELECT_SETTING_VALUE_WHERE = ""
@@ -418,7 +419,8 @@ public String getDatabaseSchemaVersion(String key, String defaultValue) {
DEFAULT_VALUES.put(EVENT_ASSIGN_ENABLED, SystemSettingsUtils.isEventAssignEnabled());
DEFAULT_VALUES.put(TOP_DESCRIPTION, "");
DEFAULT_VALUES.put(TOP_DESCRIPTION_PREFIX, "");
- }
+ DEFAULT_VALUES.put(CUSTOM_CSS_CONTENT, SystemSettingsUtils.getCustomCssContent());
+ }
@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED, rollbackFor = SQLException.class)
public void resetDataBase() {
diff --git a/src/org/scada_lts/mango/adapter/MangoScadaConfig.java b/src/org/scada_lts/mango/adapter/MangoScadaConfig.java
index 4781584670..27f05773fd 100644
--- a/src/org/scada_lts/mango/adapter/MangoScadaConfig.java
+++ b/src/org/scada_lts/mango/adapter/MangoScadaConfig.java
@@ -21,6 +21,9 @@
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.scada_lts.config.ScadaConfig;
+import org.scada_lts.mango.service.SystemSettingsService;
+import org.scada_lts.web.mvc.api.css.CssStyle;
+import org.scada_lts.web.mvc.api.css.CustomCssUtils;
/**
@@ -55,6 +58,11 @@ public void init() {
LOG.info("copy env.properties");
ScadaConfig.copyConfig();
}
+
+ LOG.info("loading user_styles.css form database");
+ SystemSettingsService service = new SystemSettingsService();
+ CssStyle cssStyle = service.getCustomCss();
+ CustomCssUtils.saveToFile(cssStyle);
}
diff --git a/src/org/scada_lts/mango/service/SystemSettingsService.java b/src/org/scada_lts/mango/service/SystemSettingsService.java
index a1c1e889d9..049c75a970 100644
--- a/src/org/scada_lts/mango/service/SystemSettingsService.java
+++ b/src/org/scada_lts/mango/service/SystemSettingsService.java
@@ -24,6 +24,7 @@
import org.scada_lts.utils.SystemSettingsUtils;
import org.scada_lts.web.beans.ApplicationBeans;
import org.scada_lts.web.mvc.api.AggregateSettings;
+import org.scada_lts.web.mvc.api.css.CssStyle;
import org.scada_lts.web.mvc.api.json.*;
import org.springframework.stereotype.Service;
@@ -519,4 +520,13 @@ private static Map deserializeMap(String json) {
return Collections.emptyMap();
}
}
+
+ public CssStyle getCustomCss() {
+ String content = SystemSettingsDAO.getValue(SystemSettingsDAO.CUSTOM_CSS_CONTENT);
+ return new CssStyle(content);
+ }
+
+ public void saveCustomCss(CssStyle cssStyle) {
+ systemSettingsDAO.setValue(SystemSettingsDAO.CUSTOM_CSS_CONTENT, cssStyle.getContent());
+ }
}
diff --git a/src/org/scada_lts/utils/SystemSettingsUtils.java b/src/org/scada_lts/utils/SystemSettingsUtils.java
index c0dab97fff..39c630d476 100644
--- a/src/org/scada_lts/utils/SystemSettingsUtils.java
+++ b/src/org/scada_lts/utils/SystemSettingsUtils.java
@@ -73,6 +73,8 @@ private SystemSettingsUtils() {}
private static final String SECURITY_HTTP_QUERY_LIMIT_KEY = "scadalts.security.http.query.limit";
private static final String SECURITY_HTTP_QUERY_PROTECT_ENABLED_KEY = "scadalts.security.http.query.protect.enabled";
+ private static final String CUSTOM_CSS_CONTENT_KEY = "systemsettings.custom.css.content";
+
private static final org.apache.commons.logging.Log LOG = LogFactory.getLog(SystemSettingsUtils.class);
public static DataPointSyncMode getDataPointSynchronizedMode() {
@@ -594,4 +596,13 @@ public static boolean isSecurityHttpQueryProtectEnabled() {
return false;
}
}
+
+ public static String getCustomCssContent() {
+ try {
+ return ScadaConfig.getInstance().getConf().getProperty(CUSTOM_CSS_CONTENT_KEY, "");
+ } catch (Exception e) {
+ LOG.error(e.getMessage());
+ return "";
+ }
+ }
}
diff --git a/src/org/scada_lts/web/mvc/api/CustomCssAPI.java b/src/org/scada_lts/web/mvc/api/CustomCssAPI.java
index 0fa9823069..2c063da8e1 100644
--- a/src/org/scada_lts/web/mvc/api/CustomCssAPI.java
+++ b/src/org/scada_lts/web/mvc/api/CustomCssAPI.java
@@ -1,80 +1,71 @@
package org.scada_lts.web.mvc.api;
-import com.serotonin.mango.Common;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
+import org.scada_lts.mango.service.SystemSettingsService;
+import org.scada_lts.serorepl.utils.StringUtils;
import org.scada_lts.web.mvc.api.css.CssStyle;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
-import org.springframework.stereotype.Controller;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.validation.BindingResult;
+import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
-import java.io.*;
-import java.nio.file.Files;
+import javax.validation.Valid;
-@Controller
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static org.scada_lts.web.mvc.api.css.CustomCssUtils.saveToFile;
+
+@RestController
@RequestMapping("/api/customcss")
public class CustomCssAPI {
private static final Log LOG = LogFactory.getLog(CustomCssAPI.class);
- private static final String CSS_FILENAME = "/assets/user_styles.css";
- private static final String REQ_RESP_ERROR = "Couldn't create a *.css file.";
+ private final SystemSettingsService systemSettingsService;
+
+ public CustomCssAPI(SystemSettingsService systemSettingsService) {
+ this.systemSettingsService = systemSettingsService;
+ }
- @GetMapping("/")
- public ResponseEntity> getCustomCssFile(HttpServletRequest request) {
+ @GetMapping(value = "", produces = {"application/json;charset=UTF-8"})
+ public ResponseEntity getCustomCss(HttpServletRequest request) {
LOG.info("GET: /api/customcss");
try {
- File cssFile = getCustomCssFileFromPath();
- if(cssFile != null) {
- String content = Files.readString(cssFile.toPath());
- return new ResponseEntity<>(new CssStyle(content), HttpStatus.OK);
- } else {
- return new ResponseEntity<>(REQ_RESP_ERROR,HttpStatus.INTERNAL_SERVER_ERROR);
+ CssStyle customCss = systemSettingsService.getCustomCss();
+ if (!StringUtils.isEmpty(customCss.getContent())) {
+ return new ResponseEntity<>(customCss.clearedOfTabs(), HttpStatus.OK);
}
+ return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
} catch (Exception e) {
LOG.error(e);
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
- @PostMapping("/")
- public ResponseEntity saveCustomCssFile(HttpServletRequest request, @RequestBody String fileContent) {
+ @PostMapping(value = "", consumes = {"application/json;charset=UTF-8"})
+ public ResponseEntity> saveCustomCss(HttpServletRequest request, @Valid @RequestBody(required = true) CssStyle cssStyle, BindingResult bindingResult) {
LOG.info("POST: /api/customcss");
+ if (bindingResult.hasErrors()) {
+ List errors = bindingResult.getFieldErrors().stream()
+ .map(error -> error.getDefaultMessage())
+ .collect(Collectors.toList());
+
+ return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
+ }
+
try {
- File cssFile = getCustomCssFileFromPath();
- if(cssFile != null) {
- try (BufferedWriter writer = new BufferedWriter(new FileWriter(cssFile.getAbsolutePath()))) {
- writer.write(fileContent);
- }
+ if (!StringUtils.isEmpty(cssStyle.getContent())) {
+ systemSettingsService.saveCustomCss(cssStyle);
+ saveToFile(cssStyle);
return new ResponseEntity<>(HttpStatus.OK);
- } else {
- return new ResponseEntity<>(REQ_RESP_ERROR, HttpStatus.INTERNAL_SERVER_ERROR);
}
+ return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
} catch (Exception e) {
LOG.error(e);
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
-
- }
-
- private File getCustomCssFileFromPath() {
- try {
- File cssFile = new File(Common.ctx.getCtx().getRealPath(CSS_FILENAME));
- if(!cssFile.exists()) {
- boolean created = cssFile.createNewFile();
- if(created) {
- LOG.info("Created custom CSS stylesheet file: " + CSS_FILENAME);
- }
- }
- return cssFile;
- } catch (IOException e) {
- LOG.error("Could not create a custom CSS file: " + CSS_FILENAME);
- }
- return null;
}
}
diff --git a/src/org/scada_lts/web/mvc/api/css/CssStyle.java b/src/org/scada_lts/web/mvc/api/css/CssStyle.java
index a969130d35..a152ea0a39 100644
--- a/src/org/scada_lts/web/mvc/api/css/CssStyle.java
+++ b/src/org/scada_lts/web/mvc/api/css/CssStyle.java
@@ -1,13 +1,25 @@
package org.scada_lts.web.mvc.api.css;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.scada_lts.web.mvc.api.css.validation.CssValid;
+
public class CssStyle {
+
+ @CssValid
private final String content;
- public CssStyle(String content) {
- this.content = content;
+ @JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
+ public CssStyle(@JsonProperty("content") String content) {
+ this.content = CustomCssUtils.replaceToTab(content);
}
public String getContent() {
return content;
}
+
+ public CssStyle clearedOfTabs() {
+ return new CssStyle(getContent().replace("\t", " "));
+ }
}
diff --git a/src/org/scada_lts/web/mvc/api/css/CustomCssUtils.java b/src/org/scada_lts/web/mvc/api/css/CustomCssUtils.java
new file mode 100644
index 0000000000..feb7c0d9c4
--- /dev/null
+++ b/src/org/scada_lts/web/mvc/api/css/CustomCssUtils.java
@@ -0,0 +1,61 @@
+package org.scada_lts.web.mvc.api.css;
+
+import com.serotonin.mango.util.LoggingUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.scada_lts.utils.PathSecureUtils;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+public class CustomCssUtils {
+
+ private static final Log LOG = LogFactory.getLog(CustomCssUtils.class);
+ private static final String CSS_FILENAME = "/assets/user_styles.css";
+ private static final String TAB = new String(new byte[]{-30, -128, -125}, StandardCharsets.UTF_8);
+
+ public static boolean saveToFile(CssStyle cssStyle) {
+ try {
+ File customCssFile = getCustomCssFileFromPath();
+ if (customCssFile != null) {
+ try (BufferedWriter writer = new BufferedWriter(new FileWriter(customCssFile, StandardCharsets.UTF_8))) {
+ writer.write(cssStyle.getContent());
+ }
+ }
+ return true;
+ } catch (Exception ex) {
+ LOG.warn(LoggingUtils.exceptionInfo(ex));
+ return false;
+ }
+ }
+
+ private static File getCustomCssFileFromPath() {
+ try {
+ Path path = PathSecureUtils.getAppContextSystemFilePath(CSS_FILENAME);
+ File cssFile = path.toFile();
+ if(Files.exists(path) == Files.notExists(path)) {
+ LOG.warn("Missing file permissions!: " + CSS_FILENAME);
+ return null;
+ }
+ if(Files.notExists(path)) {
+ boolean created = cssFile.createNewFile();
+ if(created) {
+ LOG.info("Created custom CSS stylesheet file: " + CSS_FILENAME);
+ }
+ }
+ return cssFile;
+ } catch (IOException e) {
+ LOG.warn("Could not create a custom CSS file: " + CSS_FILENAME);
+ }
+ return null;
+ }
+
+ public static String replaceToTab(String content) {
+ return content.replace(TAB, "\t");
+ }
+}
diff --git a/src/org/scada_lts/web/mvc/api/css/validation/CssConstraintValidator.java b/src/org/scada_lts/web/mvc/api/css/validation/CssConstraintValidator.java
new file mode 100644
index 0000000000..a57b3af538
--- /dev/null
+++ b/src/org/scada_lts/web/mvc/api/css/validation/CssConstraintValidator.java
@@ -0,0 +1,39 @@
+package org.scada_lts.web.mvc.api.css.validation;
+
+
+import com.serotonin.mango.util.LoggingUtils;
+import org.scada_lts.serorepl.utils.StringUtils;
+import org.scada_lts.web.mvc.api.css.validation.utils.CssValidator;
+import org.scada_lts.web.mvc.api.css.validation.utils.SacCssValidator;
+
+import javax.validation.ConstraintValidator;
+import javax.validation.ConstraintValidatorContext;
+
+
+public class CssConstraintValidator implements ConstraintValidator {
+
+ @Override
+ public void initialize(CssValid constraintAnnotation) {
+ }
+
+ @Override
+ public boolean isValid(String value, ConstraintValidatorContext context) {
+ if (StringUtils.isEmpty(value)) {
+ addErrors(context, "Empty");
+ return false;
+ }
+ try {
+ CssValidator validator = new SacCssValidator();
+ validator.validate(value);
+ return true;
+ }catch (Exception e) {
+ addErrors(context, LoggingUtils.causeInfo(e));
+ return false;
+ }
+ }
+
+ private static void addErrors(ConstraintValidatorContext context, String msg) {
+ context.disableDefaultConstraintViolation();
+ context.buildConstraintViolationWithTemplate("Unexpected error: " + msg).addConstraintViolation();
+ }
+}
diff --git a/src/org/scada_lts/web/mvc/api/css/validation/CssValid.java b/src/org/scada_lts/web/mvc/api/css/validation/CssValid.java
new file mode 100644
index 0000000000..f48f46fc1f
--- /dev/null
+++ b/src/org/scada_lts/web/mvc/api/css/validation/CssValid.java
@@ -0,0 +1,16 @@
+package org.scada_lts.web.mvc.api.css.validation;
+
+
+import javax.validation.Constraint;
+import javax.validation.Payload;
+import java.lang.annotation.*;
+
+@Documented
+@Constraint(validatedBy = CssConstraintValidator.class)
+@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.PARAMETER})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface CssValid {
+ String message() default "Wrong Css format detected";
+ Class>[] groups() default {};
+ Class extends Payload>[] payload() default {};
+}
diff --git a/src/org/scada_lts/web/mvc/api/css/validation/utils/CssValidator.java b/src/org/scada_lts/web/mvc/api/css/validation/utils/CssValidator.java
new file mode 100644
index 0000000000..8ed98f7ab3
--- /dev/null
+++ b/src/org/scada_lts/web/mvc/api/css/validation/utils/CssValidator.java
@@ -0,0 +1,6 @@
+package org.scada_lts.web.mvc.api.css.validation.utils;
+
+public interface CssValidator {
+
+ void validate(String style) throws CustomCssException;
+}
diff --git a/src/org/scada_lts/web/mvc/api/css/validation/utils/CustomCssException.java b/src/org/scada_lts/web/mvc/api/css/validation/utils/CustomCssException.java
new file mode 100644
index 0000000000..c9561bc583
--- /dev/null
+++ b/src/org/scada_lts/web/mvc/api/css/validation/utils/CustomCssException.java
@@ -0,0 +1,23 @@
+package org.scada_lts.web.mvc.api.css.validation.utils;
+
+public class CustomCssException extends Exception {
+
+ public CustomCssException() {
+ }
+
+ public CustomCssException(String message) {
+ super(message);
+ }
+
+ public CustomCssException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public CustomCssException(Throwable cause) {
+ super(cause);
+ }
+
+ public CustomCssException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ }
+}
diff --git a/src/org/scada_lts/web/mvc/api/css/validation/utils/SacCssValidator.java b/src/org/scada_lts/web/mvc/api/css/validation/utils/SacCssValidator.java
new file mode 100644
index 0000000000..ebefad4d16
--- /dev/null
+++ b/src/org/scada_lts/web/mvc/api/css/validation/utils/SacCssValidator.java
@@ -0,0 +1,53 @@
+package org.scada_lts.web.mvc.api.css.validation.utils;
+
+import com.steadystate.css.parser.SACParserCSS3;
+import org.w3c.css.sac.*;
+
+import java.io.IOException;
+import java.io.StringReader;
+
+public final class SacCssValidator implements CssValidator {
+
+ private final Parser parser;
+
+ public SacCssValidator() {
+ this.parser = createParser();
+ }
+
+ @Override
+ public void validate(String style) throws CustomCssException {
+ try {
+ validateStyle(parser, style);
+ } catch (Exception e) {
+ throw new CustomCssException(e.getMessage(), e);
+ }
+ }
+
+ private static void validateStyle(Parser parser, String value) throws CSSParseException, IOException {
+ try (StringReader stringReader = new StringReader(value)) {
+ InputSource inputSource = new InputSource(stringReader);
+ parser.parseStyleSheet(inputSource);
+ }
+ }
+
+ private static Parser createParser() {
+ Parser parser = new SACParserCSS3();
+ parser.setErrorHandler(new ErrorHandler() {
+ @Override
+ public void warning(CSSParseException e) throws CSSException {
+ throw e;
+ }
+
+ @Override
+ public void error(CSSParseException e) throws CSSException {
+ throw e;
+ }
+
+ @Override
+ public void fatalError(CSSParseException e) throws CSSException {
+ throw e;
+ }
+ });
+ return parser;
+ }
+}
diff --git a/test/org/scada_lts/web/mvc/api/css/validation/utils/CssValidatorExceptionTest.java b/test/org/scada_lts/web/mvc/api/css/validation/utils/CssValidatorExceptionTest.java
new file mode 100644
index 0000000000..c15f26dab8
--- /dev/null
+++ b/test/org/scada_lts/web/mvc/api/css/validation/utils/CssValidatorExceptionTest.java
@@ -0,0 +1,37 @@
+package org.scada_lts.web.mvc.api.css.validation.utils;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+@RunWith(Parameterized.class)
+public class CssValidatorExceptionTest {
+
+ @Parameterized.Parameters(name = "{index}: CSS: {0}")
+ public static Collection