All Downloads are FREE. Search and download functionalities are using the official Maven repository.

dev.amp.validator.CdataMatcher Maven / Gradle / Ivy

There is a newer version: 1.0.42
Show newest version
/*
 *
 * ====================================================================
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *  ====================================================================
 */

/*
 * Changes to the original project are Copyright 2019, Yahoo Inc..
 */

package dev.amp.validator;

import dev.amp.validator.css.CssTokenUtil;
import dev.amp.validator.css.Declaration;
import dev.amp.validator.css.ParsedDocCssSpec;
import dev.amp.validator.exception.TagValidationException;
import com.steadystate.css.parser.Token;

import dev.amp.validator.css.CssParser;
import dev.amp.validator.css.ErrorToken;
import dev.amp.validator.css.CssValidationException;
import dev.amp.validator.css.Stylesheet;
import dev.amp.validator.css.ParsedCssUrl;
import dev.amp.validator.css.CssParsingConfig;

import dev.amp.validator.utils.AttributeSpecUtils;
import dev.amp.validator.utils.ByteUtils;
import dev.amp.validator.utils.CssSpecUtils;
import dev.amp.validator.utils.TagSpecUtils;
import dev.amp.validator.utils.UrlUtils;
import dev.amp.validator.visitor.InvalidDeclVisitor;
import dev.amp.validator.visitor.InvalidRuleVisitor;
import dev.amp.validator.visitor.MediaQueryVisitor;
import dev.amp.validator.visitor.SelectorSpecVisitor;
import org.xml.sax.Locator;

import javax.annotation.Nonnull;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

import static dev.amp.validator.utils.TagSpecUtils.getTagDescriptiveName;

/**
 * CdataMatcher maintains a constraint to check which an opening tag
 * introduces: a tag's cdata matches constraints set by it's cdata
 * spec. Unfortunately we need to defer such checking and can't
 * handle it while the opening tag is being processed.
 *
 * @author nhant01
 * @author GeorgeLuo
 */

public class CdataMatcher {
    /**
     * Constructor.
     *
     * @param parsedTagSpec the ParsedTagSpec.
     * @param lineCol       a line / column pair.
     */
    public CdataMatcher(@Nonnull final ParsedTagSpec parsedTagSpec, @Nonnull final Locator lineCol) {
        this.parsedTagSpec = parsedTagSpec;
        this.lineCol = lineCol;
    }

    /**
     * Matches the provided cdata against what this CdataMatcher expects.
     *
     * @param cdata            the cdata.
     * @param context          the context object.
     * @param validationResult validation result object.
     * @throws TagValidationException the TagValidationException.
     * @throws CssValidationException css validation exception.
     * @throws IOException            IO exception.
     */
    public void match(@Nonnull final String cdata, @Nonnull final Context context,
                      @Nonnull final ValidatorProtos.ValidationResult.Builder validationResult)
            throws TagValidationException, CssValidationException, IOException {
        final ValidatorProtos.CdataSpec cdataSpec = this.getTagSpec().getCdata();
        if (cdataSpec == null) {
            return;
        }

        // Max CDATA Byte Length
        if (cdataSpec.hasMaxBytes() && cdataSpec.getMaxBytes() != CDATA_MAX_BYTES
                && cdata.length() > cdataSpec.getMaxBytes()) {
            List params = new ArrayList<>();
            params.add(String.valueOf(cdata.length()));
            params.add(String.valueOf(cdataSpec.getMaxBytes()));
            context.addError(
                    ValidatorProtos.ValidationError.Code.STYLESHEET_TOO_LONG,
                    context.getLineCol(),
                    params,
                    cdataSpec.getMaxBytesSpecUrl(),
                    validationResult);
            return;
        }

        int urlBytes = 0;
        // The mandatory_cdata, cdata_regex, and css_spec fields are treated
        // like a oneof, but we're not using oneof because it's a feature
        // that was added after protobuf 2.5.0 (which our open-source
        // version uses).

        // Mandatory CDATA exact match
        List params = new ArrayList<>();
        params.add(TagSpecUtils.getTagSpecName(this.getTagSpec()));

        if (cdataSpec.hasMandatoryCdata()) {
            if (!cdataSpec.getMandatoryCdata().equals(cdata)) {
                context.addError(
                        ValidatorProtos.ValidationError.Code.MANDATORY_CDATA_MISSING_OR_INCORRECT,
                        context.getLineCol(),
                        params,
                        TagSpecUtils.getTagSpecUrl(this.getTagSpec()),
                        validationResult);
            }
            // We return early if the cdata has an exact match rule. The
            // spec shouldn't have an exact match rule that doesn't validate.
            return;
        } else if (this.getTagSpec().getCdata().hasCdataRegex()) {
            if (!context.getRules()
                    .getFullMatchRegex(this.getTagSpec().getCdata().getCdataRegex())
                    .matcher(cdata).matches()) {
                context.addError(
                        ValidatorProtos.ValidationError.Code.MANDATORY_CDATA_MISSING_OR_INCORRECT,
                        context.getLineCol(),
                        params,
                        TagSpecUtils.getTagSpecUrl(this.getTagSpec()),
                        validationResult);
                return;
            }
        } else if (cdataSpec.hasCssSpec()) {
            urlBytes = this.matchCss(cdata, cdataSpec.getCssSpec(), context, validationResult);
        } else if (cdataSpec.getWhitespaceOnly()) {
            if (!(WHITE_SPACE_CHARACTER_PATTERN.matcher(cdata).matches())) {
                context.addError(
                        ValidatorProtos.ValidationError.Code.NON_WHITESPACE_CDATA_ENCOUNTERED,
                        context.getLineCol(),
                        params,
                        TagSpecUtils.getTagSpecUrl(this.getTagSpec()),
                        validationResult);
            }
        }

        final ParsedDocCssSpec maybeDocCssSpec = context.matchingDocCssSpec();

        int adjustedCdataLength = ByteUtils.byteLength(cdata);
        if (maybeDocCssSpec != null && !maybeDocCssSpec.getSpec().getUrlBytesIncluded()) {
            adjustedCdataLength -= urlBytes;
        }

        // Record