001/*
002 * (C) Copyright 2017 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Funsho David
018 *
019 */
020
021package org.nuxeo.ecm.core.filter;
022
023import com.google.common.base.CharMatcher;
024import org.apache.commons.lang.StringEscapeUtils;
025import org.nuxeo.ecm.core.api.DataModel;
026import org.nuxeo.ecm.core.api.DocumentModel;
027import org.nuxeo.ecm.core.api.impl.DataModelImpl;
028import org.nuxeo.ecm.core.api.model.DocumentPart;
029import org.nuxeo.ecm.core.api.model.Property;
030import org.nuxeo.ecm.core.api.model.impl.ArrayProperty;
031import org.nuxeo.ecm.core.api.model.impl.ComplexProperty;
032import org.nuxeo.ecm.core.api.model.impl.ListProperty;
033import org.nuxeo.ecm.core.api.model.impl.primitives.StringProperty;
034import org.nuxeo.runtime.model.ComponentInstance;
035import org.nuxeo.runtime.model.DefaultComponent;
036
037import java.io.Serializable;
038import java.util.List;
039
040/**
041 * @since 9.1
042 */
043public class CharacterFilteringServiceImpl extends DefaultComponent implements CharacterFilteringService {
044
045    public static final String FILTERING_XP = "filtering";
046
047    protected CharacterFilteringServiceDescriptor desc;
048
049    protected CharMatcher charsToRemove;
050
051    @Override
052    public void registerContribution(Object contrib, String point, ComponentInstance contributor) {
053        if (FILTERING_XP.equals(point)) {
054
055            desc = (CharacterFilteringServiceDescriptor) contrib;
056
057            CharMatcher charsToPreserve = CharMatcher.anyOf("\r\n\t");
058            CharMatcher allButPreserved = charsToPreserve.negate();
059            charsToRemove = CharMatcher.JAVA_ISO_CONTROL.and(allButPreserved);
060            charsToRemove = charsToRemove.or(CharMatcher.INVISIBLE.and(CharMatcher.WHITESPACE.negate()));
061
062            List<String> additionalChars = desc.getDisallowedChars();
063            String otherCharsToRemove = "";
064            if (additionalChars != null && !additionalChars.isEmpty()) {
065                for (String c : additionalChars) {
066                    otherCharsToRemove += StringEscapeUtils.unescapeJava(c);
067                }
068                charsToRemove = charsToRemove.or(CharMatcher.anyOf(otherCharsToRemove));
069            }
070        } else {
071            throw new RuntimeException("Unknown extension point: " + point);
072        }
073    }
074
075    @Override
076    public String filter(String value) {
077        return charsToRemove.removeFrom(value);
078    }
079
080    @Override
081    public void filter(DocumentModel docModel) {
082        if (desc.isEnabled()) {
083            // check only loaded datamodels to find the dirty ones
084            for (DataModel dm : docModel.getDataModelsCollection()) { // only loaded
085                if (!dm.isDirty()) {
086                    continue;
087                }
088                DocumentPart part = ((DataModelImpl) dm).getDocumentPart();
089                for (Property prop : part.getChildren()) {
090                    filterProperty(prop, docModel);
091                }
092            }
093        }
094    }
095
096    private void filterProperty(Property prop, DocumentModel docModel) {
097        if (!prop.isDirty()) {
098            return;
099        }
100        if (prop instanceof StringProperty) {
101            String p = (String) prop.getValue();
102            if (p != null && charsToRemove.matchesAnyOf(p)) {
103                String filteredProp = filter(p);
104                docModel.setPropertyValue(prop.getXPath(), filteredProp);
105            }
106        } else if (prop instanceof ArrayProperty) {
107            Serializable value = prop.getValue();
108            if (value instanceof Object[]) {
109                Object[] arrayProp = (Object[]) value;
110                boolean modified = false;
111                for (int i = 0; i < arrayProp.length; i++) {
112                    if (arrayProp[i] instanceof String) {
113                        String p = (String) arrayProp[i];
114                        if (charsToRemove.matchesAnyOf(p)) {
115                            arrayProp[i] = filter(p);
116                            modified = true;
117                        }
118                    }
119                }
120                if (modified) {
121                    docModel.setPropertyValue(prop.getXPath(), arrayProp);
122                }
123            }
124        } else if (prop instanceof ComplexProperty) {
125            ComplexProperty complexProp = (ComplexProperty) prop;
126            for (Property subProp : complexProp.getChildren()) {
127                filterProperty(subProp, docModel);
128            }
129        } else if (prop instanceof ListProperty) {
130            ListProperty listProp = (ListProperty) prop;
131            for (Property p : listProp) {
132                filterProperty(p, docModel);
133            }
134        }
135    }
136}