/* * @(#)AuthorizationInfo.java 0.3-3 06/05/2001 * * This file is part of the HTTPClient package * Copyright (C) 1996-2001 Ronald Tschalär * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, * MA 02111-1307, USA * * For questions, suggestions, bug-reports, enhancement-requests etc. * I may be contacted at: * * ronald@innovation.ch * * The HTTPClient's home page is located at: * * http://www.innovation.ch/java/HTTPClient/ * */ package HTTPClient; import java.io.IOException; import java.net.ProtocolException; import java.util.Vector; import java.util.Hashtable; import java.util.Enumeration; /** * Holds the information for an authorization response. * *

There are 7 fields which make up this class: host, port, scheme, * realm, cookie, params, and extra_info. The host and port select which * server the info will be sent to. The realm is server specified string * which groups various URLs under a given server together and which is * used to select the correct info when a server issues an auth challenge; * for schemes which don't use a realm (such as "NTLM", "PEM", and * "Kerberos") the realm must be the empty string (""). The scheme is the * authorization scheme used (such as "Basic" or "Digest"). * *

There are basically two formats used for the Authorization header, * the one used by the "Basic" scheme and derivatives, and the one used by * the "Digest" scheme and derivatives. The first form contains just the * the scheme and a "cookie": * *

    Authorization: Basic aGVsbG86d29ybGQ=
* * The second form contains the scheme followed by a number of parameters * in the form of name=value pairs: * *
    Authorization: Digest username="hello", realm="test", nonce="42", ...
* * The two fields "cookie" and "params" correspond to these two forms. * toString() is used by the AuthorizationModule * when generating the Authorization header and will format the info * accordingly. Note that "cookie" and "params" are mutually exclusive: if * the cookie field is non-null then toString() will generate the first * form; otherwise it will generate the second form. * *

In some schemes "extra" information needs to be kept which doesn't * appear directly in the Authorization header. An example of this are the * A1 and A2 strings in the Digest scheme. Since all elements in the params * field will appear in the Authorization header this field can't be used * for storing such info. This is what the extra_info field is for. It is * an arbitrary object which can be manipulated by the corresponding * setExtraInfo() and getExtraInfo() methods, but which will not be printed * by toString(). * *

The addXXXAuthorization(), removeXXXAuthorization(), and * getAuthorization() methods manipulate and query an internal list of * AuthorizationInfo instances. There can be only one instance per host, * port, scheme, and realm combination (see equals()). * * @version 0.3-3 06/05/2001 * @author Ronald Tschalär * @since V0.1 */ public class AuthorizationInfo implements Cloneable { // class fields /** Holds the list of lists of authorization info structures */ private static Hashtable CntxtList = new Hashtable(); /** A pointer to the handler to be called when we need authorization info */ private static AuthorizationHandler AuthHandler = new DefaultAuthHandler(); static { CntxtList.put(HTTPConnection.getDefaultContext(), new Hashtable()); } // the instance oriented stuff /** the host (lowercase) */ private String host; /** the port */ private int port; /** the scheme. (e.g. "Basic") * Note: don't lowercase because some buggy servers use a case-sensitive * match */ private String scheme; /** the realm */ private String realm; /** the string used for the "Basic", "NTLM", and other authorization * schemes which don't use parameters */ private String cookie; /** any parameters */ private NVPair[] auth_params = new NVPair[0]; /** additional info which won't be displayed in the toString() */ private Object extra_info = null; /** a list of paths where this realm has been known to be required */ private String[] paths = new String[0]; // Constructors /** * Creates an new info structure for the specified host and port. * * @param host the host * @param port the port */ AuthorizationInfo(String host, int port) { this.host = host.trim().toLowerCase(); this.port = port; } /** * Creates a new info structure for the specified host and port with the * specified scheme, realm, params. The cookie is set to null. * * @param host the host * @param port the port * @param scheme the scheme * @param realm the realm * @param params the parameters as an array of name/value pairs, or null * @param info arbitrary extra info, or null */ public AuthorizationInfo(String host, int port, String scheme, String realm, NVPair params[], Object info) { this.scheme = scheme.trim(); this.host = host.trim().toLowerCase(); this.port = port; this.realm = realm; this.cookie = null; if (params != null) auth_params = Util.resizeArray(params, params.length); this.extra_info = info; } /** * Creates a new info structure for the specified host and port with the * specified scheme, realm and cookie. The params is set to a zero-length * array, and the extra_info is set to null. * * @param host the host * @param port the port * @param scheme the scheme * @param realm the realm * @param cookie for the "Basic" scheme this is the base64-encoded * username/password; for the "NTLM" scheme this is the * base64-encoded username/password message. */ public AuthorizationInfo(String host, int port, String scheme, String realm, String cookie) { this.scheme = scheme.trim(); this.host = host.trim().toLowerCase(); this.port = port; this.realm = realm; if (cookie != null) this.cookie = cookie.trim(); else this.cookie = null; } /** * Creates a new copy of the given AuthorizationInfo. * * @param templ the info to copy */ AuthorizationInfo(AuthorizationInfo templ) { this.scheme = templ.scheme; this.host = templ.host; this.port = templ.port; this.realm = templ.realm; this.cookie = templ.cookie; this.auth_params = Util.resizeArray(templ.auth_params, templ.auth_params.length); this.extra_info = templ.extra_info; } // Class Methods /** * Set's the authorization handler. This handler is called whenever * the server requests authorization and no entry for the requested * scheme and realm can be found in the list. The handler must implement * the AuthorizationHandler interface. * *

If no handler is set then a {@link DefaultAuthHandler default * handler} is used. This handler currently only handles the "Basic" and * "Digest" schemes and brings up a popup which prompts for the username * and password. * *

The default handler can be disabled by setting the auth handler to * null. * * @param handler the new authorization handler * @return the old authorization handler * @see AuthorizationHandler */ public static AuthorizationHandler setAuthHandler(AuthorizationHandler handler) { AuthorizationHandler tmp = AuthHandler; AuthHandler = handler; return tmp; } /** * Get's the current authorization handler. * * @return the current authorization handler, or null if none is set. * @see AuthorizationHandler */ public static AuthorizationHandler getAuthHandler() { return AuthHandler; } /** * Searches for the authorization info using the given host, port, * scheme and realm. The context is the default context. * * @param host the host * @param port the port * @param scheme the scheme * @param realm the realm * @return a pointer to the authorization data or null if not found */ public static AuthorizationInfo getAuthorization( String host, int port, String scheme, String realm) { return getAuthorization(host, port, scheme, realm, HTTPConnection.getDefaultContext()); } /** * Searches for the authorization info in the given context using the * given host, port, scheme and realm. * * @param host the host * @param port the port * @param scheme the scheme * @param realm the realm * @param context the context this info is associated with * @return a pointer to the authorization data or null if not found */ public static synchronized AuthorizationInfo getAuthorization( String host, int port, String scheme, String realm, Object context) { Hashtable AuthList = Util.getList(CntxtList, context); AuthorizationInfo auth_info = new AuthorizationInfo(host, port, scheme, realm, (NVPair[]) null, null); return (AuthorizationInfo) AuthList.get(auth_info); } /** * Queries the AuthHandler for authorization info. It also adds this * info to the list. * * @param auth_info any info needed by the AuthHandler; at a minimum the * host, scheme and realm should be set. * @param req the request which initiated this query * @param resp the full response * @return a structure containing the requested info, or null if either * no AuthHandler is set or the user canceled the request. * @exception AuthSchemeNotImplException if this is thrown by * the AuthHandler. */ static AuthorizationInfo queryAuthHandler(AuthorizationInfo auth_info, RoRequest req, RoResponse resp) throws AuthSchemeNotImplException, IOException { if (AuthHandler == null) return null; AuthorizationInfo new_info = AuthHandler.getAuthorization(auth_info, req, resp); if (new_info != null) { if (req != null) addAuthorization((AuthorizationInfo) new_info.clone(), req.getConnection().getContext()); else addAuthorization((AuthorizationInfo) new_info.clone(), HTTPConnection.getDefaultContext()); } return new_info; } /** * Searches for the authorization info using the host, port, scheme and * realm from the given info struct. If not found it queries the * AuthHandler (if set). * * @param auth_info the AuthorizationInfo * @param req the request which initiated this query * @param resp the full response * @param query_auth_h if true, query the auth-handler if no info found. * @return a pointer to the authorization data or null if not found * @exception AuthSchemeNotImplException If thrown by the AuthHandler. */ static synchronized AuthorizationInfo getAuthorization( AuthorizationInfo auth_info, RoRequest req, RoResponse resp, boolean query_auth_h) throws AuthSchemeNotImplException, IOException { Hashtable AuthList; if (req != null) AuthList = Util.getList(CntxtList, req.getConnection().getContext()); else AuthList = Util.getList(CntxtList, HTTPConnection.getDefaultContext()); AuthorizationInfo new_info = (AuthorizationInfo) AuthList.get(auth_info); if (new_info == null && query_auth_h) new_info = queryAuthHandler(auth_info, req, resp); return new_info; } /** * Searches for the authorization info given a host, port, scheme and * realm. Queries the AuthHandler if not found in list. * * @param host the host * @param port the port * @param scheme the scheme * @param realm the realm * @param req the request which initiated this query * @param resp the full response * @param query_auth_h if true, query the auth-handler if no info found. * @return a pointer to the authorization data or null if not found * @exception AuthSchemeNotImplException If thrown by the AuthHandler. */ static AuthorizationInfo getAuthorization(String host, int port, String scheme, String realm, RoRequest req, RoResponse resp, boolean query_auth_h) throws AuthSchemeNotImplException, IOException { return getAuthorization(new AuthorizationInfo(host, port, scheme, realm, (NVPair[]) null, null), req, resp, query_auth_h); } /** * Adds an authorization entry to the list using the default context. * If an entry for the specified scheme and realm already exists then * its cookie and params are replaced with the new data. * * @param auth_info the AuthorizationInfo to add */ public static void addAuthorization(AuthorizationInfo auth_info) { addAuthorization(auth_info, HTTPConnection.getDefaultContext()); } /** * Adds an authorization entry to the list. If an entry for the * specified scheme and realm already exists then its cookie and * params are replaced with the new data. * * @param auth_info the AuthorizationInfo to add * @param context the context to associate this info with */ public static void addAuthorization(AuthorizationInfo auth_info, Object context) { Hashtable AuthList = Util.getList(CntxtList, context); // merge path list AuthorizationInfo old_info = (AuthorizationInfo) AuthList.get(auth_info); if (old_info != null) { int ol = old_info.paths.length, al = auth_info.paths.length; if (al == 0) auth_info.paths = old_info.paths; else { auth_info.paths = Util.resizeArray(auth_info.paths, al+ol); System.arraycopy(old_info.paths, 0, auth_info.paths, al, ol); } } AuthList.put(auth_info, auth_info); } /** * Adds an authorization entry to the list using the default context. * If an entry for the specified scheme and realm already exists then * its cookie and params are replaced with the new data. * * @param host the host * @param port the port * @param scheme the scheme * @param realm the realm * @param cookie the cookie * @param params an array of name/value pairs of parameters * @param info arbitrary extra auth info */ public static void addAuthorization(String host, int port, String scheme, String realm, String cookie, NVPair params[], Object info) { addAuthorization(host, port, scheme, realm, cookie, params, info, HTTPConnection.getDefaultContext()); } /** * Adds an authorization entry to the list. If an entry for the * specified scheme and realm already exists then its cookie and * params are replaced with the new data. * * @param host the host * @param port the port * @param scheme the scheme * @param realm the realm * @param cookie the cookie * @param params an array of name/value pairs of parameters * @param info arbitrary extra auth info * @param context the context to associate this info with */ public static void addAuthorization(String host, int port, String scheme, String realm, String cookie, NVPair params[], Object info, Object context) { AuthorizationInfo auth = new AuthorizationInfo(host, port, scheme, realm, cookie); if (params != null && params.length > 0) auth.auth_params = Util.resizeArray(params, params.length); auth.extra_info = info; addAuthorization(auth, context); } /** * Adds an authorization entry for the "Basic" authorization scheme to * the list using the default context. If an entry already exists for * the "Basic" scheme and the specified realm then it is overwritten. * * @param host the host * @param port the port * @param realm the realm * @param user the username * @param passwd the password */ public static void addBasicAuthorization(String host, int port, String realm, String user, String passwd) { addAuthorization(host, port, "Basic", realm, Codecs.base64Encode(user + ":" + passwd), (NVPair[]) null, null); } /** * Adds an authorization entry for the "Basic" authorization scheme to * the list. If an entry already exists for the "Basic" scheme and the * specified realm then it is overwritten. * * @param host the host * @param port the port * @param realm the realm * @param user the username * @param passwd the password * @param context the context to associate this info with */ public static void addBasicAuthorization(String host, int port, String realm, String user, String passwd, Object context) { addAuthorization(host, port, "Basic", realm, Codecs.base64Encode(user + ":" + passwd), (NVPair[]) null, null, context); } /** * Adds an authorization entry for the "Digest" authorization scheme to * the list using the default context. If an entry already exists for the * "Digest" scheme and the specified realm then it is overwritten. * * @param host the host * @param port the port * @param realm the realm * @param user the username * @param passwd the password */ public static void addDigestAuthorization(String host, int port, String realm, String user, String passwd) { addDigestAuthorization(host, port, realm, user, passwd, HTTPConnection.getDefaultContext()); } /** * Adds an authorization entry for the "Digest" authorization scheme to * the list. If an entry already exists for the "Digest" scheme and the * specified realm then it is overwritten. * * @param host the host * @param port the port * @param realm the realm * @param user the username * @param passwd the password * @param context the context to associate this info with */ public static void addDigestAuthorization(String host, int port, String realm, String user, String passwd, Object context) { AuthorizationInfo prev = getAuthorization(host, port, "Digest", realm, context); NVPair[] params; if (prev == null) { params = new NVPair[4]; params[0] = new NVPair("username", user); params[1] = new NVPair("uri", ""); params[2] = new NVPair("nonce", ""); params[3] = new NVPair("response", ""); } else { params = prev.getParams(); for (int idx=0; idx for " + "quoted-string starting at position " + beg + " not found"); param_value = Util.dequoteString(challenge.substring(beg, end)); end++; } } else // this is not strictly allowed param_value = null; if (param_name.equalsIgnoreCase("realm")) curr.realm = param_value; else params.addElement(new NVPair(param_name, param_value)); first = false; } pos_ref[0] = beg; pos_ref[1] = end; return params; } // Instance Methods /** * Get the host. * * @return a string containing the host name. */ public final String getHost() { return host; } /** * Get the port. * * @return an int containing the port number. */ public final int getPort() { return port; } /** * Get the scheme. * * @return a string containing the scheme. */ public final String getScheme() { return scheme; } /** * Get the realm. * * @return a string containing the realm. */ public final String getRealm() { return realm; } /** * Get the cookie * * @return the cookie String * @since V0.3-1 */ public final String getCookie() { return cookie; } /** * Set the cookie * * @param cookie the new cookie * @since V0.3-1 */ public final void setCookie(String cookie) { this.cookie = cookie; } /** * Get the authentication parameters. * * @return an array of name/value pairs. */ public final NVPair[] getParams() { return Util.resizeArray(auth_params, auth_params.length); } /** * Set the authentication parameters. * * @param an array of name/value pairs. */ public final void setParams(NVPair[] params) { if (params != null) auth_params = Util.resizeArray(params, params.length); else auth_params = new NVPair[0]; } /** * Get the extra info. * * @return the extra_info object */ public final Object getExtraInfo() { return extra_info; } /** * Set the extra info. * * @param info the extra info */ public final void setExtraInfo(Object info) { extra_info = info; } /** * Constructs a string containing the authorization info. The format * is that of the http Authorization header. * * @return a String containing all info. */ public String toString() { StringBuffer field = new StringBuffer(100); field.append(scheme); field.append(" "); if (cookie != null) { field.append(cookie); } else { if (realm.length() > 0) { field.append("realm=\""); field.append(Util.quoteString(realm, "\\\"")); field.append('"'); } for (int idx=0; idx