/**
 * Copyright 2005-2010 hdiv.org
 *
 * 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 org.hdiv.state;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hdiv.exception.HDIVException;
import org.hdiv.util.EncodingUtil;
import org.hdiv.util.HDIVErrorCodes;

/**
 * Class containing utility methods for state.
 * 
 * @author Roberto Velasco
 */
public class StateUtil {

	/**
	 * Commons Logging instance.
	 */
	private Log log = LogFactory.getLog(StateUtil.class);
	
	/**
	 * Memory strategy name 
	 */
	private static final String MEMORY_STRATEGY = "memory";
	
	/**
	 * Cipher strategy name
	 */
	private static final String CIPHER_STRATEGY = "cipher";
	
	/**
	 * Hash strategy name
	 */
	private static final String HASH_STRATEGY = "hash";
	
	/**
	 * Pattern to check if the memory strategy is being used
	 */
	private static final String MEMORY_PATTERN = "([0-9]+-){2}[0-9]+";
	
	/**
	 * Compiled MEMORY_PATTERN
	 */
	private Pattern memoryPattern = Pattern.compile(MEMORY_PATTERN);
	
	/**
	 * Parameter name included by HDIV in the requests or forms which will contain
	 * the state identifier, in the memory strategy, or the state itself, in the
	 * encoded and hash strategies.
	 */
	private String HDIVParameter;

	/**
	 * Utility methods for encoding
	 */
	private EncodingUtil encodingUtil;

	/**
	 * User defined strategy by spring factory
	 */
	private String strategy;

	/**
	 * StateUtil initialization with HDIV parameter name stored in http session.
	 */
	public void init() {
	}	

	/**
	 * Restore state data from <code>request</code>. State restore from memory can
	 * be done using an identifier or or using the serialized data received in the
	 * request.
	 * 
	 * @param requestState String that contains HDIV state received in the request
	 * @return State Restore state data from <code>request</code>.
	 * @throws HDIVException If the state doesn't exist a new HDIV exception is
	 *             thrown.
	 */ 
	public IState restoreState(String requestState) {

		IState restoredState = null;
		
		if (this.isMemoryStrategy(requestState)) {
			restoredState = this.getStateFromSession(requestState);

		} else if (this.isCipherStrategy()) {
			restoredState = (IState) encodingUtil.decode64Cipher(requestState);

		} else if (this.isHashStrategy()) {
			restoredState = this.restoreHashState(requestState);
		}

		if (restoredState == null) {
			throw new HDIVException(HDIVErrorCodes.HDIV_PARAMETER_INCORRECT_VALUE);
		}
		return restoredState;
	}

	/**
	 * Checks if the memory strategy is being used
	 *
	 * @return True if strategy is memory. False in otherwise.
	 */
	public boolean isMemoryStrategy(String value) {

		Matcher m = this.memoryPattern.matcher(value);

		return (m.matches() ? true : this.strategy.equalsIgnoreCase(MEMORY_STRATEGY));
	}

	/**
	 * Checks if the cipher (encoded) strategy is being used.
	 *
	 * @return True if strategy is cipher. False in otherwise.
	 */
	private boolean isCipherStrategy() {
		return this.strategy.equalsIgnoreCase(CIPHER_STRATEGY);
	}

	/**
	 * Checks if the hash strategy is being used.
	 *
	 * @return True if strategy is hash. False in otherwise.
	 */
	private boolean isHashStrategy() {
		return this.strategy.equalsIgnoreCase(HASH_STRATEGY);
	}

	/**
	 * Restores the state using the identifier obtained from the
	 * <code>HDIVParameter</code> of the request.
	 * 
	 * @param value <code>HDIVParameter</code> parameter value.
	 * @return State with all the page data.
	 */
	private IState getStateFromSession(String value) {

		int firstSeparator = value.indexOf("-");
		int lastSeparator = value.lastIndexOf("-");
		if ((firstSeparator == -1) || (lastSeparator == -1)) { 
			throw new HDIVException(HDIVErrorCodes.HDIV_PARAMETER_INCORRECT_VALUE);
		}
		
		String page = value.substring(0, firstSeparator);
		String stateId = value.substring(firstSeparator + 1, lastSeparator);

		IState sessionState =  this.encodingUtil.getSession().getState(page, stateId);
		
		if (sessionState == null) {
			throw new HDIVException(HDIVErrorCodes.HDIV_PARAMETER_INCORRECT_VALUE);
		}
		return sessionState;
	}
	
	/**
	 * Checks if the state hash received from the client and the hash stored in
	 * session match. If it is true, an object of type <code>IState</code> is
	 * returned. Otherwise, a HDIVException is thrown.
	 * 
	 * @param value State received in the request encoded in Base64
	 * @return Decoded state of type <code>IState</code> obtained from
	 *         <code>value</code>
	 */
	public IState restoreHashState(String value) {

		String restoredStateHash = this.encodingUtil.calculateStateHash(value);

		IState decodedState = (IState) encodingUtil.decode64(value);
		String sessionStateHash = this.encodingUtil.getSession().getStateHash(decodedState.getPageId(), decodedState.getId());

		if (restoredStateHash.equals(sessionStateHash)) {
			return decodedState;
		}
		return null;
	}

	/**
	 * @return Returns the HDIV state parameter.
	 */
	public String getHDIVParameter() {
		return HDIVParameter;
	}
	
	/**
	 * @param parameter The hDIVParameter to set.
	 */
	public void setHDIVParameter(String parameter) {
		HDIVParameter = parameter;
	}	
	
	/**
	 * @return Returns the encoding util.
	 */
	public EncodingUtil getEncodingUtil() {
		return encodingUtil;
	}

	/**
	 * @param encodingUtil The encodingUtil to set.
	 */
	public void setEncodingUtil(EncodingUtil encodingUtil) {
		this.encodingUtil = encodingUtil;
	}

	/**
	 * @param strategy The strategy to set.
	 */
	public void setStrategy(String strategy) {
		this.strategy = strategy;
	}

}

@@ -1492,16 +1492,22 @@
 %5D+-)%7B2%7D%5B
+A-Za-z
 0-9%5D+%22;%0A

correct when in-memory is used