package org.jucas.actionfilter;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Iterator;
import java.util.Map;
import java.util.SortedSet;
import java.util.StringTokenizer;
import java.util.TreeMap;
import java.util.TreeSet;
import javax.servlet.http.HttpServletRequest;
import org.jucas.Conventions;
import org.jucas.JucasException;
/*
* License
*
*
* Copyright (c) 2003 Essl Christian. All rights
* reserved.
*
* This Licence is based on the Apache Software Licence Version 1.1.
*
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by Christian Essl
* and others for project Jucas (http://www.jucas.org/)."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Jucas" and "Christian Essl" must not be used to endorse or
* promote products derived from this software without prior written
* permission. For written permission, please contact essl_christian@jucas.
* org.
*
* 5. Products derived from this software may not be called "Jucas"
* or "Christian Essl", nor may "Jucas" or "Christian Essl"
* appear in their name, without prior written permission
* of Christian Essl (essl_christian@jucas.org).
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL CHRISTIAN ESSL OR
* OTHER CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
*
*/
/**
* An instanceof ActionCode is used to calculate the parameter value of the 'jucas-action' parameter
* (see {@link PeerEventInfo}). The value of this parameter contains how paramter names map to
* IPeer names and their properties. Also there is a code calculated so that it can be ensured that
* the value was actually calculated by this web-app.<br><br>
* If in an html-form values of html-form-fields should be set as properties on an IPeer the method
* {@link #getActionName(String, IPeer, String)} should be used to set the name of the html-field.<br><br>
* If an html-form-field should call a method on an IPeer the method {@link #getMethodActionName(String, IPeer, String)}
* should be used to get the name for the html-field.
* <br><br>
* After all the methods and properties have been used in the form the value of {@link #getCode()}
* should be sent as an html-hidden-field with the name jucas-action. You can use the
* convinience method {@link #getAsHtmlHiddenField()}.
*
* <br><br>
* This class may only be used under an ActionFilter.
*
* <br><br>
* @see ActionCodeURL
* @author chris
*/
final public class ActionCode
{
public static final String NAME_PREFIX = "jucas_";
private static byte[] appCode;
private TreeMap actions = new TreeMap();
/**
* creates a new ActionCode. Same as new ActionCode()
* @return ActionCode
*/
public static ActionCode create(){
return new ActionCode();
}
/**
* Constructor for ActionCode.
*/
public ActionCode()
{
super();
}
/**
* only used by ActionFilter
* @param appCode
*/
static void setAppCode(byte[] appCode){
ActionCode.appCode = appCode;
}
static byte[] getAppCode(){
return appCode;
}
/**
* like {@link #getActionName(String,IPeer, String)} where the fieldname is null;
* @param peer
* @param property
* @return String
*/
public String getActionName(IPeer peer,String property){
return this.getActionName(null,peer, property);
}
/**
* constructs an action name for the given IPeer and the property.
* Also addes the action to the internal List of actions.
* @param fieldName the name of the inputField to be returned or null if it should be created by the
* this method automaticly
* @param peer the peer the proerty should be set on
* @param property the proerty
* @return String
* @throws IllegalArgumentException if the fieldName is not null and contains an =
* @throws NullPointerException if the peer or the property is null
*/
public String getActionName(String fieldName, IPeer peer,String property){
if(fieldName != null && fieldName.indexOf('=') != -1)
throw new IllegalArgumentException("You must provide a field name which contains no ="+fieldName);
if(peer == null || property == null)
throw new NullPointerException("Peer or peropety arg is null");
//construct the name
StringBuffer stB = new StringBuffer();
URI beanName = peer.getBeanName();
stB.append(beanName.toString());
if(Conventions.isPageBeanName(beanName)){
stB.append('#');
}else{
stB.append('.');
}
stB.append(property);
String name = stB.toString();
//now get see if in the treemap there is already the action
String ret = getFieldName(fieldName, name);
return ret;
}
/**
* like {@link #getMethodActionName(String, IPeer, String)} with a fieldName null.
* @param peer
* @param method
* @return String
*/
public String getMethodActionName(IPeer peer,String method){
return this.getMethodActionName(null, peer, method);
}
/**
* constructs an html input field name wich will call the given jucasAction_XX method for the given name.
* Also addes the action to the internal List of actions
* @param fieldName the name of the html-input field. May be null than automaticly one is created.
* @param peer the peer to set the
* @param method the jucasAction_ method (jucasAction should not be included this will be prefixed.
* @return String the String which should be used if the for the html-field name
*/
public String getMethodActionName(String fieldName,IPeer peer,String method){
if(fieldName != null && fieldName.indexOf('=') != -1)
throw new IllegalArgumentException("You must provide a field name which contains no ="+fieldName);
if(peer == null || method == null)
throw new NullPointerException("Peer or method arg is null");
StringBuffer stB = new StringBuffer();
URI beanName = peer.getBeanName();
stB.append(beanName.toString());
if(Conventions.isPageBeanName(beanName)){
stB.append('#');
}
stB.append(PeerEventInfo.JUCAS_ACTION_METHOD_MARKER);
stB.append(method);
String name = stB.toString();
String ret = getFieldName(fieldName, name);
return ret;
}
private String getFieldName(String fieldName, String name)
{
String ret = (String) this.actions.get(name);
if(ret == null){
//otherwise we construct a new name and return it.
if(fieldName == null)
fieldName = NAME_PREFIX+this.actions.size();
this.actions.put(name, fieldName);
ret = fieldName;
}
return ret;
}
/**
* Returns the calculated code
* @return String
*/
public String getCode() {
//the code constists of the form:
//md5code formName=beanName formName=beanName ...
StringBuffer stB = new StringBuffer();
for (Iterator it = this.actions.entrySet().iterator(); it.hasNext();)
{
Map.Entry entry = (Map.Entry) it.next();
String beanName = (String) entry.getKey();
String formName = (String) entry.getValue();
stB.append(' ');
stB.append(formName);
stB.append('=');
stB.append(beanName);
}
//calc the code and append;
String toCode = stB.toString();
String code;
code = calcCode(toCode);
String value = code + toCode;
return value;
}
private static String calcCode(String toCode)
{
MessageDigest md;
try
{
md = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e)
{
throw new IllegalStateException("No MD5 alogaritmus found");
}
md.update(appCode);
md.update(toCode.getBytes());
String code = toHex(md.digest());
return code;
}
private static String toHex(byte[] digest){
StringBuffer buf = new StringBuffer();
for(int i=0;i<digest.length;i++){
buf.append(Integer.toHexString((int)digest[i] & 0x00ff));
}
return buf.toString();
}
static boolean isCodeOk(String code,String params){
//calc the code and compare it with the code contained
String code2 = calcCode(params);
if(code2 == null || ! code2.equals(code))
return false;
return true;
}
static boolean parseRequest(HttpServletRequest req,PeerEventInfo info) throws JucasException{
String encoded = req.getParameter(PeerEventInfo.JUCAS_ACTION_MARKER);
if(encoded == null)
return false;
//get the code token
int cut = encoded.indexOf(' ');
String code;
String params;
if(cut == -1){
code = encoded;
params = "";
}else{
code = encoded.substring(0,cut);
params = encoded.substring(cut);
}
//check the code
if(!isCodeOk(code,params))
return false;
//now parse the params
//use for now a StringTokenizer (replace because is slow)
params = params.trim();
StringTokenizer sT = new StringTokenizer(params);
while(sT.hasMoreElements()){
String token = sT.nextToken();
cut = token.indexOf('=');
if(cut == -1)
throw new IllegalArgumentException("No valid format of params no = in param:"+token);
String name = token.substring(0,cut);
String beanName = token.substring(cut+1);
//now try to get the URI
URI uri;
try
{
uri = new URI(beanName);
} catch (URISyntaxException e)
{
throw new IllegalArgumentException("The uri in the param:"+token+" is not valid");
}
//check if the URI is a BeanName
if(!Conventions.isValidName(uri))
throw new IllegalArgumentException("The uri is no BeanName:"+token);
if(uri.getFragment() == null)
throw new IllegalArgumentException("No legal uri a fragment must be present:"+token);
//get the value of the parameter from the request and set it in the Map
String[] values = req.getParameterValues(name);
info.addParameter(uri, values);
}
return true;
}
/**
* will create a hidden html field with the name jucas-action and the value the
* from {@link #getCode()}
* @return String
*/
public String getAsHtmlHiddenField(){
StringBuffer stB = new StringBuffer("<input type=\"hidden\" name=\"");
stB.append(PeerEventInfo.JUCAS_ACTION_MARKER);
stB.append("\" value=\"");
stB.append(this.getCode());
stB.append("\">");
return stB.toString();
}
public String getMethodAsHtmlHiddenField(IPeer peer,String method,String value){
StringBuffer stB = new StringBuffer("<input type=\"hidden\" name=\"");
String name = this.getMethodActionName(peer, method);
stB.append(name);
stB.append("\" value=\"");
stB.append(value);
stB.append("\">");
return stB.toString();
}
}
|