/*
* SimpleSessionHandler.java
*
* Brazil project web application toolkit,
* export version: 2.3
* Copyright (c) 2001-2008 Sun Microsystems, Inc.
*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License Version
* 1.0 (the "License"). You may not use this file except in compliance with
* the License. A copy of the License is included as the file "license.terms",
* and also available at http://www.sun.com/
*
* The Original Code is from:
* Brazil project web application toolkit release 2.3.
* The Initial Developer of the Original Code is: suhler.
* Portions created by suhler are Copyright (C) Sun Microsystems, Inc.
* All Rights Reserved.
*
* Contributor(s): suhler.
*
* Version: 2.6
* Created by suhler on 01/08/01
* Last modified by suhler on 08/11/07 11:50:52
*
* Version Histories:
*
* 2.6 08/11/07-11:50:52 (suhler)
* make query parameters available in "extract".
*
* 2.5 06/11/16-15:44:13 (suhler)
* bug fix: "value" configuration parameter wasn't being initialized properly
*
* 2.4 06/11/13-15:04:19 (suhler)
* move MatchString to package "util" from "handler"
*
* 2.3 05/06/22-13:42:42 (suhler)
* - cleaned up the documentations
* - don't subst \n's in value initially, so we don't need to \ the \'s
*
* 2.2 04/12/15-12:38:02 (suhler)
* make inner class static
*
* 2.1 02/10/01-16:36:31 (suhler)
* version change
*
* 1.10 02/06/13-17:57:56 (suhler)
* Changed the behaviour of extract= to look first in http headers, then
* in request props
*
* 1.9 02/06/11-17:47:05 (suhler)
* add option to invert the sense of the re match
*
* 1.8 02/03/05-12:42:40 (suhler)
* allow existing session id's to be modified
*
* 1.7 02/02/27-15:06:16 (suhler)
* add additional variables for session re's to match on
*
* 1.6 02/02/26-14:41:29 (suhler)
* doc fixes
*
* 1.5 02/02/26-13:37:17 (suhler)
* rewrite.
* Hopefully this one is easier to use.
* .
*
* 1.4 01/12/10-16:11:20 (suhler)
* doc lint
*
* 1.3 01/10/06-17:42:03 (suhler)
* Added options for "thin client" (Yopy) demo, to allow client-based
* authentication using http headers
*
* 1.2 01/08/14-16:38:31 (suhler)
* doc lint
*
* 1.2 01/08/01-11:07:01 (Codemgr)
* SunPro Code Manager data about conflicts, renames, etc...
* Name history : 1 0 handlers/SimpleSessionHandler.java
*
* 1.1 01/08/01-11:07:00 (suhler)
* date and time created 01/08/01 11:07:00 by suhler
*
*/
package sunlabs.brazil.handler;
import java.io.IOException;
import java.util.Vector;
import java.util.Properties;
import java.util.Hashtable;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import sunlabs.brazil.server.Handler;
import sunlabs.brazil.server.Request;
import sunlabs.brazil.server.Server;
import sunlabs.brazil.util.Base64;
import sunlabs.brazil.util.Format;
import sunlabs.brazil.util.StringMap;
import sunlabs.brazil.util.regexp.Regexp;
import sunlabs.brazil.util.MatchString;
/**
* Handler for creating browser sessions based
* on information found in the http request.
* This handler provides a single session-id that may be used by
* other handlers.
*
* The following server properties are used:
*
* prefix, suffix, glob, match
* - Specify the URL that triggers this handler
* (See {@link MatchString}).
*
-
session
* - The name of the request property that the Session ID will be stored
* in, to be passed to downstream handlers. The default value is
* "SessionID". If the property already exists, and is not empty,
* no session will be defined (unless force=true).
*
-
extract
* - If specified, a string to use as the session-id. ${...} values will
* be searched for first in special "pseudo" headers, then in
* any query parameters (either GET or POST) prefixed with "query."
* then in
* the HTTP header values, and then
* in the request properties.
*
* In addition to the actual HTTP headers,
* the pseudo http headers ipaddress, url, method, and query
* are made available for ${...} substitutions.
*
-
re
* - If specified, a regular expression that the extracted data must match.
* if it doesn't match, no session id is installed.
* The default is ".", which matches any non-empty string.
* If the first character is "!" then the sense of the match is inverted,
* But only for determining whether a match "succeeded" or not.
* no sub-matches may be used in computing the key value in this case.
*
-
value
* - The value of the session ID. May contain & or \n (n=0,1,2...)
* constructs to substitute
* matched sub-expressions of
re
. The default is "&" , which
* uses the entire string "extract" as the session
id.
* ${...} are substituted (but not \'s) for value
before
* looking
* for '\n' sequences that are part of the regular expression matches.
* -
digest
* - If set, the "value" is replaced by the base64 encoding of the
* MD5 checksum of
value
.
* -
force
* - If set (to anything), a session ID is set even if one already
* exists.
*
* If no options are provided, the client's IP address is used as the
* session ID.
*
* Examples:
*
* - Pick the session based on the browser
*
* [prefix].extract=${user-agent}
* [prefix].re=.*(Netscape|Lynx|MSIE).*
* [prefix].value=\\1
*
* - This is similar to the "old" behavior.
*
* [prefix].extract=${user-agent}${ipaddress}
* [prefix].digest=true
*
* - Look for a special authorization token, and set a request property
* to the value
*
* [prefix].extract=${Authorization}
* [prefix].re=code:([0-9]+)
* [prefix].value=id\\1
*
*
*
* @author Stephen Uhler
* @version @(#)SimpleSessionHandler.java 2.6
*/
public class SimpleSessionHandler implements Handler {
public String valueTemplate; // the resultant SessionID, if any.
public Regexp regexp; // re that the id must match
MatchString isMine; // check for matching url
String session; // property name to hold session ID
String extract; // the string to get the ID from
boolean force; // set sessioneven if exists
boolean invert=false; // invert the sense of "match"
MessageDigest digest = null;
public boolean
init(Server server, String prefix) {
isMine = new MatchString(prefix, server.props);
session = server.props.getProperty(prefix + "session", "SessionID");
extract = server.props.getProperty(prefix + "extract", "${ipaddress}");
force = (server.props.getProperty(prefix + "force") != null);
valueTemplate = server.props.getProperty(prefix + "value", "&");
if (server.props.getProperty(prefix + "digest") != null) {
try {
digest = MessageDigest.getInstance("SHA");
} catch (NoSuchAlgorithmException e) {
server.log(Server.LOG_WARNING, prefix,
"Can't find SHA implementation");
digest=null;
}
}
String match = server.props.getProperty(prefix + "re", ".+");
if (match.startsWith("!")) {
match = match.substring(1);
invert=true;
}
try {
regexp = new Regexp(match);
} catch (Exception e) {
regexp=null;
server.log(Server.LOG_WARNING, prefix, "Bad expression:" + e);
return false;
}
return true;
}
public boolean
respond(Request request) throws IOException {
if (!isMine.match(request.url)) {
return false;
}
String current = request.props.getProperty(session);
if (!force && current != null && !current.equals("")) {
request.log(Server.LOG_INFORMATIONAL, isMine.prefix(),
session + " already exists, skipping");
return false;
}
/*
* Build a properties object for Format.subst that looks for
* "special" values, then mime headers, then query
* parameters then regular properties.
*/
Props props = new Props(request.headers, "query.",
request.getQueryData(null), request.props);
props.extra("ipaddress",
request.getSocket().getInetAddress().getHostAddress());
props.extra("url",request.url);
props.extra("query",request.query);
props.extra("method",request.method);
String key = Format.subst(props, extract);
request.log(Server.LOG_DIAGNOSTIC, isMine.prefix(),
"extract: " +extract + " -> " + key);
String id; // this will be the session id
String result = Format.subst(request.props, valueTemplate, true); // ${} only, not '\'
if (invert) {
if (regexp.match(key) == null) {
id = result;
} else {
id = null;
}
} else {
id = regexp.sub(key, result);
}
if (id == null) {
request.log(Server.LOG_DIAGNOSTIC, isMine.prefix(), "(" + key +
") doesn't match re, not set");
return false;
}
if (digest != null) {
digest.reset();
id = Base64.encode(digest.digest(id.getBytes()));
}
request.props.put(session, id);
request.log(Server.LOG_DIAGNOSTIC, isMine.prefix(),
"Using (" + id + ") as session id");
return false;
}
/**
* Add a few name/value pairs in front of a dictionary so Format.subst()
* will find them when it calls get().
* Also, look in an auxiliary dictionary with a specified prefix,
* then look in the "normal" one.
* Sigh!
*/
static class Props extends StringMap {
StringMap map; // the dictionary with most of the stuff
Vector extra; // the extra name/value pairs
String pre; // prefix to "dict" lookup
Hashtable dict; // the query parameters go here
Properties defaults; // the request stuff goes here
Props(StringMap map, String pre, Hashtable dict, Properties defaults) {
this.map = map;
this.defaults = defaults;
this.dict = dict;
this.pre = pre;
extra = new Vector();
}
void extra(Object name, Object value) {
extra.addElement(name);
extra.addElement(value);
}
public String
get(String key) {
// look for "extras"
for(int i=0; i < extra.size(); i+=2) {
if (key.equals(extra.elementAt(i))) {
return (String) extra.elementAt(i+1);
}
}
// look for query parameters with prefix
String result;
if (pre != null && dict != null && key.startsWith(pre) &&
(result = (String) dict.get(key.substring(pre.length())))!= null) {
return result;
}
// look for mime headers then request props
result = map.get(key);
if (result == null && defaults != null) {
result = defaults.getProperty(key);
}
return result;
}
}
}