wicket.protocol.http.request.CryptedUrlWebRequestCodingStrategy.java Source code

Java tutorial

Introduction

Here is the source code for wicket.protocol.http.request.CryptedUrlWebRequestCodingStrategy.java

Source

/*
 * $Id: WebRequestCodingStrategy.java,v 1.24 2006/02/15 02:00:30 jonathanlocke
 * Exp $ $Revision: 7015 $ $Date: 2006-04-25 15:03:59 +0200 (Di, 25 Apr 2006) $
 * 
 * ==============================================================================
 * 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.
 */
package wicket.protocol.http.request;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import wicket.Application;
import wicket.IRequestTarget;
import wicket.PageParameters;
import wicket.Request;
import wicket.RequestCycle;
import wicket.WicketRuntimeException;
import wicket.request.IRequestCodingStrategy;
import wicket.request.RequestParameters;
import wicket.request.target.coding.IRequestTargetUrlCodingStrategy;
import wicket.util.crypt.ICrypt;
import wicket.util.string.AppendingStringBuffer;
import wicket.util.string.Strings;

/**
 * This is a request coding strategy which encrypts the URL and hence makes it
 * impossible for users to guess what is in the url and rebuild it manually. It
 * uses the CryptFactory registered with the application to encode and decode
 * the URL. Hence, the coding algorithm must be a two-way one (reversable).
 * Because the algrithm is reversible, URLs which were bookmarkable before will
 * remain bookmarkable.
 * <p>
 * To register the request coding strategy to need to do the following:
 * 
 * <pre>
 * protected IRequestCycleProcessor newRequestCycleProcessor()
 * {
 *    return new CompoundRequestCycleProcessor(new CryptedUrlWebRequestCodingStrategy(
 *          new WebRequestCodingStrategy()), null, null, null, null);
 * }
 * </pre>
 * 
 * @author Juergen Donnerstag
 */
public class CryptedUrlWebRequestCodingStrategy implements IRequestCodingStrategy {
    /** log. */
    private static final Log log = LogFactory.getLog(CryptedUrlWebRequestCodingStrategy.class);

    /** The default request coding strategy most of the methods are delegated to */
    private final IRequestCodingStrategy defaultStrategy;

    /**
     * Construct.
     * 
     * @param defaultStrategy
     *            The default strategy most requests are forwarded to
     */
    public CryptedUrlWebRequestCodingStrategy(final IRequestCodingStrategy defaultStrategy) {
        this.defaultStrategy = defaultStrategy;
    }

    /**
     * Decode the querystring of the URL
     * 
     * @see wicket.request.IRequestCodingStrategy#decode(wicket.Request)
     */
    public RequestParameters decode(final Request request) {
        String url = request.decodeURL(request.getURL());
        String decodedQueryParams = decodeURL(url);
        if (decodedQueryParams != null) {
            // The difficulty now is that this.defaultStrategy.decode(request)
            // doesn't know the just decoded url which is why must create
            // a fake Request for.
            Request fakeRequest = new DecodedUrlRequest(request, url, decodedQueryParams);
            return this.defaultStrategy.decode(fakeRequest);
        }

        return this.defaultStrategy.decode(request);
    }

    /**
     * Encode the querystring of the URL
     * 
     * @see wicket.request.IRequestCodingStrategy#encode(wicket.RequestCycle,
     *      wicket.IRequestTarget)
     */
    public CharSequence encode(final RequestCycle requestCycle, final IRequestTarget requestTarget) {
        CharSequence url = this.defaultStrategy.encode(requestCycle, requestTarget);
        url = encodeURL(url);
        return url;
    }

    /**
     * @see wicket.request.IRequestTargetMounter#mount(
     *      wicket.request.target.coding.IRequestTargetUrlCodingStrategy)
     */
    public void mount(IRequestTargetUrlCodingStrategy urlCodingStrategy) {
        this.defaultStrategy.mount(urlCodingStrategy);
    }

    /**
     * @see wicket.request.IRequestTargetMounter#unmount(java.lang.String)
     */
    public void unmount(String path) {
        this.defaultStrategy.unmount(path);
    }

    /**
     * @see wicket.request.IRequestTargetMounter#urlCodingStrategyForPath(java.lang.String)
     */
    public IRequestTargetUrlCodingStrategy urlCodingStrategyForPath(String path) {
        return this.defaultStrategy.urlCodingStrategyForPath(path);
    }

    /**
     * @see wicket.request.IRequestTargetMounter#pathForTarget(wicket.IRequestTarget)
     */
    public CharSequence pathForTarget(IRequestTarget requestTarget) {
        return this.defaultStrategy.pathForTarget(requestTarget);
    }

    /**
     * @see wicket.request.IRequestTargetMounter#targetForRequest(wicket.request.RequestParameters)
     */
    public IRequestTarget targetForRequest(RequestParameters requestParameters) {
        return this.defaultStrategy.targetForRequest(requestParameters);
    }

    /**
     * Returns the given url encoded.
     * 
     * @param url
     *            The URL to encode
     * @return The encoded url
     */
    protected CharSequence encodeURL(final CharSequence url) {
        // Get the crypt implementation from the application
        ICrypt urlCrypt = Application.get().getSecuritySettings().getCryptFactory().newCrypt();
        if (urlCrypt != null) {
            // The url must have a query string, otherwise keep the url
            // unchanged
            final int pos = url.toString().indexOf('?');
            if (pos > 0) {
                // The url's path
                CharSequence urlPrefix = url.subSequence(0, pos);

                // Extract the querystring
                String queryString = url.subSequence(pos + 1, url.length()).toString();

                // if the querystring starts with a parameter like
                // "x=", than don't change the querystring as it
                // has been encoded already
                if (!queryString.startsWith("x=")) {
                    // The length of the encrypted string depends on the
                    // length of the original querystring. Let's try to
                    // make the querystring shorter first without loosing
                    // information.
                    queryString = shortenUrl(queryString).toString();

                    // encrypt the query string
                    String encryptedQueryString = urlCrypt.encryptUrlSafe(queryString);

                    try {
                        encryptedQueryString = URLEncoder.encode(encryptedQueryString,
                                Application.get().getRequestCycleSettings().getResponseRequestEncoding());
                    } catch (UnsupportedEncodingException ex) {
                        throw new WicketRuntimeException(ex);
                    }

                    // build the new complete url
                    return new AppendingStringBuffer(urlPrefix).append("?x=").append(encryptedQueryString);
                }
            }
        }

        // we didn't change anything
        return url;
    }

    /**
     * Decode the "x" parameter of the querystring
     * 
     * @param url
     *            The encoded URL
     * @return The decoded 'x' parameter of the querystring
     */
    protected String decodeURL(final String url) {
        int startIndex = url.indexOf("?x=");
        if (startIndex != -1) {
            startIndex = startIndex + 3;
            final int endIndex = url.indexOf("&", startIndex);
            String secureParam;
            if (endIndex == -1) {
                secureParam = url.substring(startIndex);
            } else {
                secureParam = url.substring(startIndex, endIndex);
            }

            try {
                secureParam = URLDecoder.decode(secureParam,
                        Application.get().getRequestCycleSettings().getResponseRequestEncoding());
            } catch (UnsupportedEncodingException ex) {
                throw new WicketRuntimeException(ex);
            }

            // Get the crypt implementation from the application
            final ICrypt urlCrypt = Application.get().getSecuritySettings().getCryptFactory().newCrypt();

            // Decrypt the query string
            String queryString = urlCrypt.decryptUrlSafe(secureParam);

            // The querystring might have been shortened (length reduced).
            // In that case, lengthen the query string again.
            queryString = rebuildUrl(queryString);
            return queryString;
        }
        return null;
    }

    /**
     * Try to shorten the querystring without loosing information. Note:
     * WebRequestWithCryptedUrl must implement exactly the opposite logic.
     * 
     * @param queryString
     *            The original query string
     * @return The shortened querystring
     */
    protected CharSequence shortenUrl(CharSequence queryString) {
        queryString = Strings.replaceAll(queryString, WebRequestCodingStrategy.BEHAVIOR_ID_PARAMETER_NAME + "=",
                "1-");
        queryString = Strings.replaceAll(queryString,
                WebRequestCodingStrategy.INTERFACE_PARAMETER_NAME + "=IRedirectListener", "2-");
        queryString = Strings.replaceAll(queryString,
                WebRequestCodingStrategy.INTERFACE_PARAMETER_NAME + "=IFormSubmitListener", "3-");
        queryString = Strings.replaceAll(queryString,
                WebRequestCodingStrategy.INTERFACE_PARAMETER_NAME + "=IOnChangeListener", "4-");
        queryString = Strings.replaceAll(queryString,
                WebRequestCodingStrategy.INTERFACE_PARAMETER_NAME + "=ILinkListener", "5-");
        queryString = Strings.replaceAll(queryString, WebRequestCodingStrategy.INTERFACE_PARAMETER_NAME + "=",
                "6-");
        queryString = Strings.replaceAll(queryString,
                WebRequestCodingStrategy.BOOKMARKABLE_PAGE_PARAMETER_NAME + "=", "7-");

        // For debugging only: determine possibilities to further shorten
        // the query string
        if (log.isDebugEnabled()) {
            // Every word with at least 3 letters
            Pattern words = Pattern.compile("\\w\\w\\w+");
            Matcher matcher = words.matcher(queryString);
            while (matcher.find()) {
                CharSequence word = queryString.subSequence(matcher.start(), matcher.end());
                log.debug("URL pattern NOT shortened: '" + word + "' - '" + queryString + "'");
            }
        }

        return queryString;
    }

    /**
     * In case the query string has been shortened prior to encryption, than
     * rebuild (lengthen) the query string now. Note: This implementation must
     * exactly match the reverse one implemented in WebResponseWithCryptedUrl.
     * 
     * @param queryString
     *            The URL's query string
     * @return The lengthened query string
     */
    protected String rebuildUrl(CharSequence queryString) {
        queryString = Strings.replaceAll(queryString, "1-",
                WebRequestCodingStrategy.BEHAVIOR_ID_PARAMETER_NAME + "=");
        queryString = Strings.replaceAll(queryString, "2-",
                WebRequestCodingStrategy.INTERFACE_PARAMETER_NAME + "=IRedirectListener");
        queryString = Strings.replaceAll(queryString, "3-",
                WebRequestCodingStrategy.INTERFACE_PARAMETER_NAME + "=IFormSubmitListener");
        queryString = Strings.replaceAll(queryString, "4-",
                WebRequestCodingStrategy.INTERFACE_PARAMETER_NAME + "=IOnChangeListener");
        queryString = Strings.replaceAll(queryString, "5-",
                WebRequestCodingStrategy.INTERFACE_PARAMETER_NAME + "=ILinkListener");
        queryString = Strings.replaceAll(queryString, "6-",
                WebRequestCodingStrategy.INTERFACE_PARAMETER_NAME + "=");
        queryString = Strings.replaceAll(queryString, "7-",
                WebRequestCodingStrategy.BOOKMARKABLE_PAGE_PARAMETER_NAME + "=");

        return queryString.toString();
    }

    /**
     * IRequestCodingStrategy.decode(Request) requires a Request parameter and
     * not a URL. Hence, based on the original URL and the decoded 'x' parameter
     * a new Request object must be created to serve the default coding strategy
     * as input for analyzing the URL.
     */
    private static class DecodedUrlRequest extends Request {
        /** The original request */
        private final Request request;

        /** The new URL with the 'x' param decoded */
        private final String url;

        /**
         * The new parameter map with the 'x' param removed and the 'new' one
         * included
         */
        private final Map<String, Object> parameterMap;

        /** The start index to the new relative URL */
        private final int startRelativeUrl;

        /**
         * Construct.
         * 
         * @param request
         * @param url
         * @param encodedParamReplacement
         */
        @SuppressWarnings("unchecked")
        public DecodedUrlRequest(final Request request, final String url, final String encodedParamReplacement) {
            this.request = request;

            // Create a copy of the original parameter map
            this.parameterMap = this.request.getParameterMap();

            // Remove the 'x' parameter which contains ALL the encoded params
            this.parameterMap.remove("x");
            String decodedParamReplacement = encodedParamReplacement;
            try {
                decodedParamReplacement = URLDecoder.decode(encodedParamReplacement,
                        Application.get().getRequestCycleSettings().getResponseRequestEncoding());
            } catch (UnsupportedEncodingException ex) {
                log.error("error decoding url: " + encodedParamReplacement, ex);
            }

            // Add ALL of the params from the decoded 'x' param
            PageParameters params = new PageParameters(decodedParamReplacement, "&");
            this.parameterMap.putAll(params);

            // Rebuild the URL with the 'x' param removed
            int pos1 = url.indexOf("?x=");
            if (pos1 == -1) {
                throw new WicketRuntimeException("Programming error: we should come here");
            }
            int pos2 = url.indexOf("&");

            AppendingStringBuffer urlBuf = new AppendingStringBuffer(
                    url.length() + encodedParamReplacement.length());
            urlBuf.append(url.subSequence(0, pos1 + 1));
            urlBuf.append(encodedParamReplacement);
            if (pos2 != -1) {
                urlBuf.append(url.substring(pos2));
            }
            this.url = urlBuf.toString();

            // Determine the index for the relative path.
            this.startRelativeUrl = url.indexOf(request.getRelativeURL());
        }

        /**
         * Delegate to the original request
         * 
         * @see wicket.Request#getLocale()
         */
        @Override
        public Locale getLocale() {
            return this.request.getLocale();
        }

        /**
         * @see wicket.Request#getParameter(java.lang.String)
         */
        @Override
        public String getParameter(final String key) {
            if (key == null) {
                return null;
            }
            return (String) this.parameterMap.get(key);
        }

        /**
         * @see wicket.Request#getParameterMap()
         */
        @Override
        public Map<String, Object> getParameterMap() {
            return this.parameterMap;
        }

        /**
         * @see wicket.Request#getParameters(java.lang.String)
         */
        @Override
        public String[] getParameters(final String key) {
            if (key == null) {
                return null;
            }
            return (String[]) this.parameterMap.get(key);
        }

        /**
         * @see wicket.Request#getPath()
         */
        @Override
        public String getPath() {
            // Hasn't changed. We only encoded the querystring
            return this.request.getPath();
        }

        /**
         * @see wicket.Request#getRelativeURL()
         */
        @Override
        public String getRelativeURL() {
            return this.url.substring(this.startRelativeUrl);
        }

        /**
         * @see wicket.Request#getURL()
         */
        @Override
        public String getURL() {
            return this.url;
        }
    }
}