org.apache.wicket.protocol.https.HttpsMapper.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.wicket.protocol.https.HttpsMapper.java

Source

/*
 * 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.wicket.protocol.https;

import javax.servlet.http.HttpServletRequest;

import org.apache.wicket.Session;
import org.apache.wicket.core.request.handler.IPageClassRequestHandler;
import org.apache.wicket.request.IRequestCycle;
import org.apache.wicket.request.IRequestHandler;
import org.apache.wicket.request.IRequestMapper;
import org.apache.wicket.request.Request;
import org.apache.wicket.request.Url;
import org.apache.wicket.request.component.IRequestablePage;
import org.apache.wicket.request.cycle.RequestCycle;
import org.apache.wicket.request.http.WebResponse;
import org.apache.wicket.request.mapper.IRequestMapperDelegate;
import org.apache.wicket.util.collections.ClassMetaCache;
import org.apache.wicket.util.lang.Args;

/**
 * A {@link IRequestMapper} that will issue a redirect to secured communication (over https) if the
 * page resolved by {@linkplain #delegate} is annotated with @{@link RequireHttps}
 * 
 * <p>
 * To setup it:
 * 
 * <pre>
 * public class MyApplication extends WebApplication
 * {
 *    public void init()
 *    {
 *       super.init();
 * 
 *       getRootRequestMapperAsCompound().add(new MountedMapper(&quot;secured&quot;, HttpsPage.class));
 *       mountPage(SomeOtherPage.class);
 * 
 *       // notice that in most cases this should be done as the
 *       // last mounting-related operation because it replaces the root mapper
 *       setRootRequestMapper(new HttpsMapper(getRootRequestMapper(), new HttpsConfig(80, 443)));
 *    }
 * }
 * </pre>
 * 
 * any request to <em>http://hostname:httpPort/secured</em> will be redirected to
 * <em>https://hostname:httpsPort/secured</em>
 * 
 * @author igor
 */
public class HttpsMapper implements IRequestMapperDelegate {
    private final HttpsConfig config;
    private final IRequestMapper delegate;
    private final ClassMetaCache<Scheme> cache = new ClassMetaCache<Scheme>();

    /**
     * Constructor
     * 
     * @param delegate
     * @param config
     */
    public HttpsMapper(IRequestMapper delegate, HttpsConfig config) {
        this.delegate = Args.notNull(delegate, "delegate");
        this.config = config;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IRequestMapper getDelegateMapper() {
        return delegate;
    }

    @Override
    public final int getCompatibilityScore(Request request) {
        return delegate.getCompatibilityScore(request);
    }

    @Override
    public final IRequestHandler mapRequest(Request request) {
        IRequestHandler handler = delegate.mapRequest(request);

        Scheme desired = getDesiredSchemeFor(handler);
        Scheme current = getSchemeOf(request);
        if (!desired.isCompatibleWith(current)) {
            // we are currently on the wrong scheme for this handler

            // construct a url for the handler on the correct scheme
            String url = createRedirectUrl(handler, request, desired);

            // replace handler with one that will redirect to the created url
            handler = createRedirectHandler(url);
        }
        return handler;
    }

    @Override
    public final Url mapHandler(IRequestHandler handler) {
        return mapHandler(handler, RequestCycle.get().getRequest());
    }

    /**
     * Creates the {@link IRequestHandler} that will be responsible for the redirect
     * 
     * @param url
     * @return request handler
     */
    protected IRequestHandler createRedirectHandler(String url) {
        return new RedirectHandler(url, config);
    }

    /**
     * Constructs a redirect url that should switch the user to the specified {@code scheme}
     * 
     * @param handler
     *            request handler being accessed
     * @param request
     *            current request
     * @param scheme
     *            desired scheme for the redirect url
     * @return url
     */
    protected String createRedirectUrl(IRequestHandler handler, Request request, Scheme scheme) {
        HttpServletRequest req = (HttpServletRequest) request.getContainerRequest();
        String url = scheme.urlName() + "://";
        url += req.getServerName();
        if (!scheme.usesStandardPort(config)) {
            url += ":" + scheme.getPort(config);
        }
        url += req.getRequestURI();
        if (req.getQueryString() != null) {
            url += "?" + req.getQueryString();
        }
        return url;
    }

    /**
     * Creates a url for the handler. Modifies it with the correct {@link Scheme} if necessary.
     * 
     * @param handler
     * @param request
     * @return url
     */
    final Url mapHandler(IRequestHandler handler, Request request) {
        Url url = delegate.mapHandler(handler);

        Scheme desired = getDesiredSchemeFor(handler);
        Scheme current = getSchemeOf(request);
        if (!desired.isCompatibleWith(current)) {
            // the generated url does not have the correct scheme, set it (which in turn will cause
            // the url to be rendered in its full representation)
            url.setProtocol(desired.urlName());
            url.setPort(desired.getPort(config));
        }
        return url;
    }

    /**
     * Figures out which {@link Scheme} should be used to access the request handler
     * 
     * @param handler
     *            request handler
     * @return {@link Scheme}
     */
    protected Scheme getDesiredSchemeFor(IRequestHandler handler) {
        if (handler instanceof IPageClassRequestHandler) {
            return getDesiredSchemeFor(((IPageClassRequestHandler) handler).getPageClass());
        }
        return Scheme.ANY;
    }

    /**
     * Determines the {@link Scheme} of the request
     * 
     * @param request
     * @return {@link Scheme#HTTPS} or {@link Scheme#HTTP}
     */
    protected Scheme getSchemeOf(Request request) {
        HttpServletRequest req = (HttpServletRequest) request.getContainerRequest();

        if ("https".equalsIgnoreCase(req.getScheme())) {
            return Scheme.HTTPS;
        } else if ("http".equalsIgnoreCase(req.getScheme())) {
            return Scheme.HTTP;
        } else {
            throw new IllegalStateException("Could not resolve protocol for request: " + req);
        }
    }

    /**
     * Determines which {@link Scheme} should be used to access the page
     * 
     * @param pageClass
     *            type of page
     * @return {@link Scheme}
     */
    protected Scheme getDesiredSchemeFor(Class<? extends IRequestablePage> pageClass) {
        if (pageClass == null) {
            return Scheme.ANY;
        }

        Scheme SCHEME = cache.get(pageClass);
        if (SCHEME == null) {
            if (hasSecureAnnotation(pageClass)) {
                SCHEME = Scheme.HTTPS;
            } else {
                SCHEME = Scheme.HTTP;
            }
            cache.put(pageClass, SCHEME);
        }
        return SCHEME;
    }

    /**
     * @return config with which this mapper was created
     */
    public final HttpsConfig getConfig() {
        return config;
    }

    /**
     * Checks if the specified {@code type} has the {@link RequireHttps} annotation
     * 
     * @param type
     * @return {@code true} iff {@code type} has the {@link RequireHttps} annotation
     */
    private boolean hasSecureAnnotation(Class<?> type) {
        if (type.getAnnotation(RequireHttps.class) != null) {
            return true;
        }

        for (Class<?> iface : type.getInterfaces()) {
            if (hasSecureAnnotation(iface)) {
                return true;
            }
        }

        if (type.getSuperclass() != null) {
            return hasSecureAnnotation(type.getSuperclass());
        }
        return false;
    }

    /**
     * Handler that takes care of redirecting
     * 
     * @author igor
     */
    public static class RedirectHandler implements IRequestHandler {
        private final String url;
        private final HttpsConfig config;

        /**
         * Constructor
         * 
         * @param config
         *            https config
         * @param url
         *            redirect location
         */
        public RedirectHandler(String url, HttpsConfig config) {
            this.url = Args.notNull(url, "url");
            this.config = Args.notNull(config, "config");
        }

        /**
         * @return redirect location
         */
        public String getUrl() {
            return url;
        }

        @Override
        public void respond(IRequestCycle requestCycle) {
            String location = url;

            if (location.startsWith("/")) {
                // context-absolute url
                location = requestCycle.getUrlRenderer().renderContextRelativeUrl(location);
            }

            if (config.isPreferStateful()) {
                // we need to persist the session before a redirect to https so the session lasts
                // across both http and https calls.
                Session.get().bind();
            }

            WebResponse response = (WebResponse) requestCycle.getResponse();
            response.sendRedirect(location);
        }
    }
}