LinkCheckerTransformer.java 16.2 KB
/*
 * Decompiled with CFR 0_118.
 * 
 * Could not load the following classes:
 *  com.day.text.Text
 *  javax.servlet.http.HttpServletRequest
 *  org.apache.cocoon.xml.sax.AbstractSAXPipe
 *  org.apache.cocoon.xml.sax.AttributesImpl
 *  org.apache.commons.lang.StringEscapeUtils
 *  org.apache.sling.api.SlingHttpServletRequest
 *  org.apache.sling.api.SlingHttpServletResponse
 *  org.apache.sling.api.resource.ResourceResolver
 *  org.apache.sling.api.resource.ValueMap
 *  org.apache.sling.api.wrappers.ValueMapDecorator
 *  org.apache.sling.rewriter.ProcessingComponentConfiguration
 *  org.apache.sling.rewriter.ProcessingContext
 *  org.apache.sling.rewriter.Transformer
 *  org.osgi.service.component.ComponentInstance
 *  org.slf4j.Logger
 *  org.slf4j.LoggerFactory
 */
package com.day.cq.rewriter.linkchecker.impl;

import com.day.cq.rewriter.htmlparser.HtmlParser;
import com.day.cq.rewriter.htmlparser.SAXWriter;
import com.day.cq.rewriter.linkchecker.Link;
import com.day.cq.rewriter.linkchecker.LinkChecker;
import com.day.cq.rewriter.linkchecker.LinkCheckerSettings;
import com.day.cq.rewriter.linkchecker.LinkRewriteConfig;
import com.day.cq.rewriter.linkchecker.LinkValidity;
import com.day.cq.rewriter.linkchecker.impl.LinkCheckerTransformerConfig;
import com.day.cq.rewriter.pipeline.Generator;
import com.day.cq.rewriter.pipeline.RequestLinkChecker;
import com.day.cq.rewriter.pipeline.RequestRewriter;
import com.day.cq.rewriter.pipeline.Serializer;
import com.day.cq.rewriter.processor.impl.GeneratorWrapper;
import com.day.cq.rewriter.processor.impl.SerializerWrapper;
import com.day.text.Text;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.net.URI;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import org.apache.cocoon.xml.sax.AbstractSAXPipe;
import org.apache.cocoon.xml.sax.AttributesImpl;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.api.wrappers.ValueMapDecorator;
import org.apache.sling.rewriter.ProcessingComponentConfiguration;
import org.apache.sling.rewriter.ProcessingContext;
import org.apache.sling.rewriter.Transformer;
import org.osgi.service.component.ComponentInstance;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;

public class LinkCheckerTransformer
extends AbstractSAXPipe
implements Transformer {
    private static final String HINT_ATTRIBUTE = "x-cq-linkchecker";
    private static final String SKIP_HINT = "skip";
    private static final String VALID_HINT = "valid";
    private static final ProcessingComponentConfiguration HTMLPARSER_CONFIG = new ProcessingComponentConfigurationImpl("htmparser");
    private static final ProcessingComponentConfiguration SAXWRITER_CONFIG = new ProcessingComponentConfigurationImpl("saxwriter");
    private static final Set<String> LINK_NAMES = new HashSet<String>(Arrays.asList("href", "src", "action"));
    private static final Set<String> CHECK_ELEMENT_NAMES = new HashSet<String>(Arrays.asList("a", "area"));
    private static final Logger log = LoggerFactory.getLogger((String)LinkCheckerTransformer.class.getName());
    private LinkChecker linkChecker;
    private ProcessingContext pipelineContext;
    private LinkCheckerSettings settings;
    private final ResourceResolver serviceResourceResolver;
    private LinkRewriteConfig rwConfig;
    private String linkElemName;
    private boolean isTryingToFix;
    private final RequestLinkChecker requestLinkChecker;
    private final RequestRewriter requestRewriter;
    private final LinkCheckerTransformerConfig config;

    public LinkCheckerTransformer(LinkChecker checker, RequestLinkChecker requestLinkChecker, RequestRewriter requestRewriter, LinkCheckerTransformerConfig config, ResourceResolver serviceResourceResolver) {
        this.linkChecker = checker;
        this.isTryingToFix = false;
        this.requestLinkChecker = requestLinkChecker;
        this.requestRewriter = requestRewriter;
        this.config = config;
        this.serviceResourceResolver = serviceResourceResolver;
    }

    public void init(ProcessingContext pipelineContext, ProcessingComponentConfiguration config) {
        this.settings = this.linkChecker.createSettings(pipelineContext.getRequest());
        this.pipelineContext = pipelineContext;
    }

    public LinkCheckerTransformer(LinkCheckerSettings settings, LinkChecker linkChecker, ProcessingContext pipelineContext, boolean isTryingToFix, RequestLinkChecker requestLinkChecker, RequestRewriter requestRewriter, LinkCheckerTransformerConfig config, ResourceResolver serviceResourceResolver) {
        if (settings == null) {
            throw new IllegalArgumentException("Settings must not be null.");
        }
        if (linkChecker == null) {
            throw new IllegalArgumentException("LinkChecker must not be null.");
        }
        this.linkChecker = linkChecker;
        this.settings = settings;
        this.isTryingToFix = isTryingToFix;
        this.pipelineContext = pipelineContext;
        this.requestLinkChecker = requestLinkChecker;
        this.requestRewriter = requestRewriter;
        this.config = config;
        this.serviceResourceResolver = serviceResourceResolver;
    }

    public void startElement(String nsUri, String name, String raw, Attributes attrs) throws SAXException {
        String linkCheckerHint;
        String attrName;
        Attributes rewritten = this.requestRewriter == null ? null : this.requestRewriter.rewrite(name, attrs, this.settings);
        AttributesImpl attributes = new AttributesImpl(rewritten != null ? rewritten : attrs);
        if (!this.config.getDisableRewriting()) {
            this.externalizeLinks(attributes);
        }
        if ((linkCheckerHint = attributes.getValue("x-cq-linkchecker")) != null) {
            attributes.removeAttribute("x-cq-linkchecker");
            if (!"skip".equals(linkCheckerHint) && !"valid".equals(linkCheckerHint)) {
                linkCheckerHint = null;
            }
        }
        if ((attrName = this.config.getRewriteAttributeName(name)) != null) {
            String href = attributes.getValue(attrName);
            if (href != null) {
                href = StringEscapeUtils.unescapeHtml((String)href);
            }
            if (name.equalsIgnoreCase("base") && href != null) {
                this.settings.setBaseRef(href);
            }
            if (!(this.linkChecker.isSpecial(href) || this.isTryingToFix || "skip".equals(linkCheckerHint))) {
                String dst;
                String relPath;
                String oriRelPath;
                boolean oldExternal;
                boolean oldInternal;
                Link link = null;
                LinkValidity validity = null;
                if ("valid".equals(linkCheckerHint)) {
                    oldExternal = this.settings.setIgnoreExternals(true);
                    oldInternal = this.settings.setIgnoreInternals(true);
                    link = this.linkChecker.getLink(href, this.settings);
                    this.settings.setIgnoreExternals(oldExternal);
                    this.settings.setIgnoreInternals(oldInternal);
                }
                if (link == null && this.requestLinkChecker != null) {
                    link = this.requestLinkChecker.getLink(href, this.settings);
                }
                if (link == null) {
                    if (this.config.getDisableChecking()) {
                        oldExternal = this.settings.setIgnoreExternals(true);
                        oldInternal = this.settings.setIgnoreInternals(true);
                        link = this.linkChecker.getLink(href, this.settings);
                        this.settings.setIgnoreExternals(oldExternal);
                        this.settings.setIgnoreInternals(oldInternal);
                    } else {
                        link = this.linkChecker.getLink(href, this.settings);
                    }
                }
                if (validity == null) {
                    validity = link.getValidity();
                }
                this.rwConfig = validity == LinkValidity.INVALID ? this.settings.getInvalidConfig() : (validity == LinkValidity.EXPIRED ? this.settings.getExpiredConfig() : (validity == LinkValidity.PREDATED ? this.settings.getPredatedConfig() : null));
                if (this.rwConfig != null && CHECK_ELEMENT_NAMES.contains(name.toLowerCase())) {
                    this.linkElemName = name;
                    this.writeFixTag(href, this.rwConfig.getPrefix());
                    if (this.rwConfig.remove()) {
                        return;
                    }
                }
                String rewriteValue = null;
                if (this.requestRewriter != null) {
                    rewriteValue = this.requestRewriter.rewriteLink(link, this.settings);
                }
                if (rewriteValue == null && !this.config.getDisableRewriting() && !this.settings.isIgnoreInternals() && link.isContextRelative() && (relPath = this.getHtmlLink(oriRelPath = link.getRelUri().getPath())) != null && !(dst = this.config.map(this.settings.getResourceResolver(), this.serviceResourceResolver, (HttpServletRequest)this.settings.getRequest(), "/" + relPath)).equals("/" + oriRelPath)) {
                    log.debug("LinkCheckerTransformerConfig mapped {} to {}", (Object)relPath, (Object)dst);
                    if (link.getRelUri().getRawQuery() != null) {
                        dst = dst + "?" + link.getRelUri().getRawQuery();
                    }
                    if (link.getRelUri().getRawFragment() != null) {
                        dst = dst + "#" + link.getRelUri().getRawFragment();
                    }
                    rewriteValue = dst;
                }
                if (rewriteValue != null && !rewriteValue.equals(href)) {
                    for (int i = 0; i < attributes.getLength(); ++i) {
                        if (!attributes.getQName(i).equalsIgnoreCase(attrName)) continue;
                        if (log.isDebugEnabled()) {
                            log.debug("{} attribute rewritten from {} to {}", new Object[]{attrName, attributes.getValue(i), rewriteValue});
                        }
                        attributes.setValue(i, rewriteValue);
                        break;
                    }
                }
            }
        }
        super.startElement(nsUri, name, raw, (Attributes)attributes);
    }

    private String getHtmlLink(String path) {
        int firstSlash;
        int lastDot = path.lastIndexOf(".");
        if (lastDot == -1) {
            return this.config.isStripHtmlExtension() ? path : null;
        }
        String extPlusSuffix = path.substring(lastDot + 1);
        String ext = this.config.isStrictExtensionCheck() ? extPlusSuffix : ((firstSlash = extPlusSuffix.indexOf("/")) >= 0 ? extPlusSuffix.substring(0, firstSlash) : extPlusSuffix);
        if (ext.equals("html") || ext.equals("htm")) {
            return this.config.isStripHtmlExtension() ? path.substring(0, lastDot) : path;
        }
        return null;
    }

    public void endElement(String uri, String name, String raw) throws SAXException {
        if (name.equalsIgnoreCase(this.linkElemName) && !this.isTryingToFix) {
            this.linkElemName = null;
            if (this.rwConfig != null) {
                if (!this.rwConfig.remove()) {
                    super.endElement(uri, name, raw);
                }
                this.writeFixTag("", this.rwConfig.getSuffix());
            }
        } else {
            super.endElement(uri, name, raw);
        }
    }

    private void externalizeLinks(AttributesImpl attributes) {
        for (int i = 0; i < attributes.getLength(); ++i) {
            String value;
            String name = attributes.getLocalName(i);
            if (!LINK_NAMES.contains(name) || (value = attributes.getValue(i)) == null || this.linkChecker.isSpecial(value = value.trim())) continue;
            if (!(!value.startsWith("/") || value.startsWith("//") || "/".equals(this.settings.getContextPath()) || value.startsWith(this.settings.getContextPath() + "/") || value.startsWith("//"))) {
                value = this.settings.getContextPath() + value;
            }
            if (value.indexOf(":/") < 1) {
                value = this.replaceNamespacePrefixes(value);
            }
            attributes.setValue(i, value);
        }
    }

    private String replaceNamespacePrefixes(String href) {
        int queryPos = href.indexOf(63);
        int pos = href.indexOf(":");
        while (pos != -1 && (queryPos == -1 || pos < queryPos)) {
            int slashPos = href.lastIndexOf(47, pos);
            href = href.substring(0, slashPos + 1) + '_' + href.substring(slashPos + 1, pos) + '_' + href.substring(pos + 1);
            pos = href.indexOf(":");
        }
        while ((pos = href.indexOf("/jcr%3a")) != -1) {
            href = href.substring(0, pos + 1) + "_jcr_" + href.substring(pos + 7);
        }
        return href;
    }

    private void writeFixTag(String href, String str) throws SAXException {
        try {
            str = Text.replace((String)str, (String)"%s", (String)Text.escapeXml((String)href));
            char[] characters = str.toCharArray();
            BufferingContext buffer = new BufferingContext(this.pipelineContext);
            SerializerWrapper serializer = new SerializerWrapper(new SAXWriter(), null);
            serializer.init((ProcessingContext)buffer, SAXWRITER_CONFIG);
            LinkCheckerTransformer transformer = new LinkCheckerTransformer(this.settings, this.linkChecker, this.pipelineContext, true, this.requestLinkChecker, this.requestRewriter, this.config, this.serviceResourceResolver);
            transformer.setContentHandler((ContentHandler)((Object)serializer));
            GeneratorWrapper parser = new GeneratorWrapper(new HtmlParser(), null);
            parser.init((ProcessingContext)buffer, HTMLPARSER_CONFIG);
            parser.setContentHandler((ContentHandler)((Object)transformer));
            parser.getWriter().write(characters, 0, characters.length);
            parser.finished();
            characters = buffer.toString().toCharArray();
            this.characters(characters, 0, characters.length);
        }
        catch (IOException ioe) {
            throw new SAXException(ioe);
        }
    }

    public void dispose() {
        if (this.serviceResourceResolver != null) {
            this.serviceResourceResolver.close();
        }
    }

    public static class ProcessingComponentConfigurationImpl
    implements ProcessingComponentConfiguration {
        public static final ValueMap EMPTY_CONFIG = new ValueMapDecorator(new HashMap());
        private final String type;

        public ProcessingComponentConfigurationImpl(String type) {
            this.type = type;
        }

        public ValueMap getConfiguration() {
            return EMPTY_CONFIG;
        }

        public String getType() {
            return this.type;
        }
    }

    private static final class BufferingContext
    implements ProcessingContext {
        private final ProcessingContext delegatee;
        private final StringWriter buffer;
        private final PrintWriter writer;

        public BufferingContext(ProcessingContext delegatee) {
            this.delegatee = delegatee;
            this.buffer = new StringWriter();
            this.writer = new PrintWriter(this.buffer);
        }

        public String getContentType() {
            return this.delegatee.getContentType();
        }

        public SlingHttpServletRequest getRequest() {
            return this.delegatee.getRequest();
        }

        public SlingHttpServletResponse getResponse() {
            return this.delegatee.getResponse();
        }

        public PrintWriter getWriter() {
            return this.writer;
        }

        public OutputStream getOutputStream() throws IOException {
            return null;
        }

        public String toString() {
            return this.buffer.toString();
        }
    }

}