/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.
 */
package org.apache.tomcat.util.descriptor;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Map;

import org.apache.tomcat.util.res.StringManager;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.ext.EntityResolver2;

/**
 * A resolver for locally cached XML resources.
 */
public class LocalResolver implements EntityResolver2 {

    private static final StringManager sm =
            StringManager.getManager(Constants.PACKAGE_NAME);

    private static final String[] JAVA_EE_NAMESPACES = {
        XmlIdentifiers.JAVAEE_1_4_NS,
        XmlIdentifiers.JAVAEE_5_NS,
        XmlIdentifiers.JAVAEE_7_NS };


    private final Map<String,String> publicIds;
    private final Map<String,String> systemIds;
    private final boolean blockExternal;

    /**
     * Constructor providing mappings of public and system identifiers to local
     * resources. Each map contains a mapping from a well-known identifier to a
     * URL for a local resource path.
     *
     * @param publicIds mapping of well-known public identifiers to local
     *                  resources
     * @param systemIds mapping of well-known system identifiers to local
     *                  resources
     * @param blockExternal are external resources blocked that are not
     *                      well-known
     */
    public LocalResolver(Map<String,String> publicIds,
            Map<String,String> systemIds, boolean blockExternal) {
        this.publicIds = publicIds;
        this.systemIds = systemIds;
        this.blockExternal = blockExternal;
    }


    @Override
    public InputSource resolveEntity(String publicId, String systemId)
            throws SAXException, IOException {
        return resolveEntity(null, publicId, null, systemId);
    }


    @Override
    public InputSource resolveEntity(String name, String publicId,
            String base, String systemId) throws SAXException, IOException {

        // First try resolving using the publicId
        String resolved = publicIds.get(publicId);
        if (resolved != null) {
            InputSource is = new InputSource(resolved);
            is.setPublicId(publicId);
            return is;
        }

        // If there is no systemId, can't try anything else
        if (systemId == null) {
            throw new FileNotFoundException(sm.getString("localResolver.unresolvedEntity",
                    name, publicId, systemId, base));
        }

        // Try resolving with the supplied systemId
        resolved = systemIds.get(systemId);
        if (resolved != null) {
            InputSource is = new InputSource(resolved);
            is.setPublicId(publicId);
            return is;
        }

        // Work-around for XML documents that use just the file name for the
        // location to refer to a JavaEE schema
        for (String javaEENamespace : JAVA_EE_NAMESPACES) {
            String javaEESystemId = javaEENamespace + '/' + systemId;
            resolved = systemIds.get(javaEESystemId);
            if (resolved != null) {
                InputSource is = new InputSource(resolved);
                is.setPublicId(publicId);
                return is;
            }
        }

        // Resolve the supplied systemId against the base
        URI systemUri;
        try {
            if (base == null) {
                systemUri = new URI(systemId);
            } else {
                // Can't use URI.resolve() because "jar:..." URLs are not valid
                // hierarchical URIs so resolve() does not work. new URL()
                // delegates to the jar: stream handler and it manages to figure
                // it out.
                URI baseUri = new URI(base);
                systemUri = new URL(baseUri.toURL(), systemId).toURI();
            }
            systemUri = systemUri.normalize();
        } catch (URISyntaxException e) {
            // May be caused by a | being used instead of a : in an absolute
            // file URI on Windows.
            if (blockExternal) {
                // Absolute paths aren't allowed so block it
                throw new MalformedURLException(e.getMessage());
            } else {
                // See if the URLHandler can resolve it
                return new InputSource(systemId);
            }
        }
        if (systemUri.isAbsolute()) {
            // Try the resolved systemId
            resolved = systemIds.get(systemUri.toString());
            if (resolved != null) {
                InputSource is = new InputSource(resolved);
                is.setPublicId(publicId);
                return is;
            }
            if (!blockExternal) {
                InputSource is = new InputSource(systemUri.toString());
                is.setPublicId(publicId);
                return is;
            }
        }

        throw new FileNotFoundException(sm.getString("localResolver.unresolvedEntity",
                name, publicId, systemId, base));
    }


    @Override
    public InputSource getExternalSubset(String name, String baseURI)
            throws SAXException, IOException {
        return null;
    }
}
