package org.phms.sling.mvp.impl.presenter.serializer; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.BeanDescription; import com.fasterxml.jackson.databind.BeanProperty; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationConfig; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.ser.BeanPropertyWriter; import com.fasterxml.jackson.databind.ser.BeanSerializerFactory; import com.fasterxml.jackson.databind.ser.BeanSerializerModifier; import com.fasterxml.jackson.databind.ser.SerializerFactory; import org.apache.commons.lang3.StringUtils; import org.apache.felix.scr.annotations.Activate; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Properties; import org.apache.felix.scr.annotations.Property; import org.apache.felix.scr.annotations.Reference; import org.apache.felix.scr.annotations.Service; import org.apache.sling.xss.XSSAPI; import org.phms.sling.mvp.xss.XSSProtection; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; @Component @Service @Properties(value = { @Property(name = "service.description", value = "Handlebars: Bean to Map Serializer")} ) public class BeanToMapSerializerImpl implements BeanToMapSerializer { //TODO: Inject service private XSSAPI xssApi; private ObjectMapper objectMapper; @Activate protected void activate() { objectMapper = new ObjectMapper(); objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, true); SerializerFactory serializerFactory = BeanSerializerFactory .instance .withSerializerModifier(new XSSSerializerModifier(xssApi)); objectMapper.setSerializerFactory(serializerFactory); } private static class XSSSerializerWrapper extends JsonSerializer { @Override public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException { String safeValue = getSafeValue((String) value); //continue with default impl provider.findTypedValueSerializer(String.class, true, beanProperty).serialize(safeValue, jgen, provider); } private String getSafeValue(String originalValue) { String result; if (StringUtils.isNotBlank(originalValue) && xssApi != null) { switch (xssProtection.strategy()) { case NONE: result = originalValue; break; case HTML_FILTER: result = xssApi.filterHTML(originalValue); break; case HTML: result = xssApi.encodeForHTML(originalValue); break; case HTML_ATTR: result = xssApi.encodeForHTMLAttr(originalValue); break; case JS: result = xssApi.encodeForJSString(originalValue); break; case XML: result = xssApi.encodeForXML(originalValue); break; case XML_ATTR: result = xssApi.encodeForXMLAttr(originalValue); break; case HREF: result = xssApi.getValidHref(originalValue); break; case PLAIN_TEXT: result = stripHTML(originalValue); break; default: throw new UnsupportedOperationException(xssProtection.strategy() + " filtering is not yet supported"); } } else { result = originalValue; } return result; } private BeanProperty beanProperty; private XSSAPI xssApi; private XSSProtection xssProtection; public XSSSerializerWrapper(BeanProperty beanProperty, XSSProtection xssProtection, XSSAPI xssApi) { this.beanProperty = beanProperty; this.xssApi = xssApi; this.xssProtection = xssProtection; } private String stripHTML(String text) { if (StringUtils.isNotBlank(text)) { return text.replaceAll("<[^>]*>", " ").replaceAll("[ ]{2,}", " ").trim(); } return text; } } @Override public Map<String, Object> convertToMap(Object bean) { //only serialize objects that implement this marker interface, which excludes properties that are know to cause endless loops if(bean instanceof JacksonSerializable){ return objectMapper.convertValue(bean, Map.class); } return new HashMap<>(); } private static class XSSSerializerModifier extends BeanSerializerModifier { private XSSAPI xssApi; private static final Logger LOG = LoggerFactory.getLogger(XSSSerializerModifier.class); public XSSSerializerModifier(XSSAPI xssApi) { this.xssApi = xssApi; } @Override public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc, List<BeanPropertyWriter> beanProperties) { for (int i = 0; i < beanProperties.size(); i++) { try { BeanPropertyWriter beanPropertyWriter = beanProperties.get(i); XSSProtection xssProtection = beanPropertyWriter.getAnnotation(XSSProtection.class); if (xssProtection != null) { beanPropertyWriter.assignSerializer(new XSSSerializerWrapper(beanPropertyWriter, xssProtection, xssApi)); } } catch (Exception e) { LOG.error("" + e, e); } } return beanProperties; } } }