0001: /*
0002: * @(#)AuthorizationInfo.java 0.3-2 18/06/1999
0003: *
0004: * This file is part of the HTTPClient package
0005: * Copyright (C) 1996-1999 Ronald Tschalär
0006: *
0007: * This library is free software; you can redistribute it and/or
0008: * modify it under the terms of the GNU Lesser General Public
0009: * License as published by the Free Software Foundation; either
0010: * version 2 of the License, or (at your option) any later version.
0011: *
0012: * This library is distributed in the hope that it will be useful,
0013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0015: * Lesser General Public License for more details.
0016: *
0017: * You should have received a copy of the GNU Lesser General Public
0018: * License along with this library; if not, write to the Free
0019: * Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
0020: * MA 02111-1307, USA
0021: *
0022: * For questions, suggestions, bug-reports, enhancement-requests etc.
0023: * I may be contacted at:
0024: *
0025: * ronald@innovation.ch
0026: *
0027: */
0028:
0029: package HTTPClient;
0030:
0031: import java.io.IOException;
0032: import java.net.ProtocolException;
0033: import java.util.Vector;
0034: import java.util.Hashtable;
0035: import java.util.Enumeration;
0036:
0037: /**
0038: * Holds the information for an authorization response.
0039: *
0040: * <P>There are 7 fields which make up this class: host, port, scheme,
0041: * realm, cookie, params, and extra_info. The host and port select which
0042: * server the info will be sent to. The realm is server specified string
0043: * which groups various URLs under a given server together and which is
0044: * used to select the correct info when a server issues an auth challenge;
0045: * for schemes which don't use a realm (such as "NTLM", "PEM", and
0046: * "Kerberos") the realm must be the empty string (""). The scheme is the
0047: * authorization scheme used (such as "Basic" or "Digest").
0048: *
0049: * <P>There are basically two formats used for the Authorization header,
0050: * the one used by the "Basic" scheme and derivatives, and the one used by
0051: * the "Digest" scheme and derivatives. The first form contains just the
0052: * the scheme and a "cookie":
0053: *
0054: * <PRE> Authorization: Basic aGVsbG86d29ybGQ=</PRE>
0055: *
0056: * The second form contains the scheme followed by a number of parameters
0057: * in the form of name=value pairs:
0058: *
0059: * <PRE> Authorization: Digest username="hello", realm="test", nonce="42", ...</PRE>
0060: *
0061: * The two fields "cookie" and "params" correspond to these two forms.
0062: * <A HREF="#toString()">toString()</A> is used by the AuthorizationModule
0063: * when generating the Authorization header and will format the info
0064: * accordingly. Note that "cookie" and "params" are mutually exclusive: if
0065: * the cookie field is non-null then toString() will generate the first
0066: * form; otherwise it will generate the second form.
0067: *
0068: * <P>In some schemes "extra" information needs to be kept which doesn't
0069: * appear directly in the Authorization header. An example of this are the
0070: * A1 and A2 strings in the Digest scheme. Since all elements in the params
0071: * field will appear in the Authorization header this field can't be used
0072: * for storing such info. This is what the extra_info field is for. It is
0073: * an arbitrary object which can be manipulated by the corresponding
0074: * setExtraInfo() and getExtraInfo() methods, but which will not be printed
0075: * by toString().
0076: *
0077: * <P>The addXXXAuthorization(), removeXXXAuthorization(), and
0078: * getAuthorization() methods manipulate and query an internal list of
0079: * AuthorizationInfo instances. There can be only one instance per host,
0080: * port, scheme, and realm combination (see <A HREF="#equals">equals()</A>).
0081: *
0082: * @version 0.3-2 18/06/1999
0083: * @author Ronald Tschalär
0084: * @since V0.1
0085: */
0086:
0087: public class AuthorizationInfo implements GlobalConstants, Cloneable {
0088: // class fields
0089:
0090: /** Holds the list of lists of authorization info structures */
0091: private static Hashtable CntxtList = new Hashtable();
0092:
0093: /** A pointer to the handler to be called when we need authorization info */
0094: private static AuthorizationHandler AuthHandler = new DefaultAuthHandler();
0095:
0096: static {
0097: CntxtList.put(HTTPConnection.getDefaultContext(),
0098: new Hashtable());
0099: }
0100:
0101: // the instance oriented stuff
0102:
0103: /** the host (lowercase) */
0104: private String host;
0105:
0106: /** the port */
0107: private int port;
0108:
0109: /** the scheme. (e.g. "Basic")
0110: * Note: don't lowercase because some buggy servers use a case-sensitive
0111: * match */
0112: private String scheme;
0113:
0114: /** the realm */
0115: private String realm;
0116:
0117: /** the string used for the "Basic", "NTLM", and other authorization
0118: * schemes which don't use parameters */
0119: private String cookie;
0120:
0121: /** any parameters */
0122: private NVPair[] auth_params = new NVPair[0];
0123:
0124: /** additional info which won't be displayed in the toString() */
0125: private Object extra_info = null;
0126:
0127: /** a list of paths where this realm has been known to be required */
0128: private String[] paths = new String[0];
0129:
0130: // Constructors
0131:
0132: /**
0133: * Creates an new info structure for the specified host and port.
0134: *
0135: * @param host the host
0136: * @param port the port
0137: */
0138: AuthorizationInfo(String host, int port) {
0139: this .host = host.trim().toLowerCase();
0140: this .port = port;
0141: }
0142:
0143: /**
0144: * Creates a new info structure for the specified host and port with the
0145: * specified scheme, realm, params. The cookie is set to null.
0146: *
0147: * @param host the host
0148: * @param port the port
0149: * @param scheme the scheme
0150: * @param realm the realm
0151: * @param params the parameters as an array of name/value pairs, or null
0152: * @param info arbitrary extra info, or null
0153: */
0154: public AuthorizationInfo(String host, int port, String scheme,
0155: String realm, NVPair params[], Object info) {
0156: this .scheme = scheme.trim();
0157: this .host = host.trim().toLowerCase();
0158: this .port = port;
0159: this .realm = realm;
0160: this .cookie = null;
0161:
0162: if (params != null)
0163: auth_params = Util.resizeArray(params, params.length);
0164:
0165: this .extra_info = info;
0166: }
0167:
0168: /**
0169: * Creates a new info structure for the specified host and port with the
0170: * specified scheme, realm and cookie. The params is set to a zero-length
0171: * array, and the extra_info is set to null.
0172: *
0173: * @param host the host
0174: * @param port the port
0175: * @param scheme the scheme
0176: * @param realm the realm
0177: * @param cookie for the "Basic" scheme this is the base64-encoded
0178: * username/password; for the "NTLM" scheme this is the
0179: * base64-encoded username/password message.
0180: */
0181: public AuthorizationInfo(String host, int port, String scheme,
0182: String realm, String cookie) {
0183: this .scheme = scheme.trim();
0184: this .host = host.trim().toLowerCase();
0185: this .port = port;
0186: this .realm = realm;
0187: if (cookie != null)
0188: this .cookie = cookie.trim();
0189: else
0190: this .cookie = null;
0191: }
0192:
0193: /**
0194: * Creates a new copy of the given AuthorizationInfo.
0195: *
0196: * @param templ the info to copy
0197: */
0198: AuthorizationInfo(AuthorizationInfo templ) {
0199: this .scheme = templ.scheme;
0200: this .host = templ.host;
0201: this .port = templ.port;
0202: this .realm = templ.realm;
0203: this .cookie = templ.cookie;
0204:
0205: this .auth_params = Util.resizeArray(templ.auth_params,
0206: templ.auth_params.length);
0207:
0208: this .extra_info = templ.extra_info;
0209: }
0210:
0211: // Class Methods
0212:
0213: /**
0214: * Set's the authorization handler. This handler is called whenever
0215: * the server requests authorization and no entry for the requested
0216: * scheme and realm can be found in the list. The handler must implement
0217: * the AuthorizationHandler interface.
0218: * <BR>If no handler is set then a default handler is used. This handler
0219: * currently only handles the "Basic" scheme and brings up a popup which
0220: * prompts for the username and password.
0221: * <BR>The default handler can be disabled by setting the auth handler
0222: * to <var>null</var>.
0223: *
0224: * @param handler the new authorization handler
0225: * @return the old authorization handler
0226: * @see AuthorizationHandler
0227: */
0228: public static AuthorizationHandler setAuthHandler(
0229: AuthorizationHandler handler) {
0230: AuthorizationHandler tmp = AuthHandler;
0231: AuthHandler = handler;
0232:
0233: return tmp;
0234: }
0235:
0236: /**
0237: * Get's the current authorization handler.
0238: *
0239: * @return the current authorization handler, or null if none is set.
0240: * @see AuthorizationHandler
0241: */
0242: public static AuthorizationHandler getAuthHandler() {
0243: return AuthHandler;
0244: }
0245:
0246: /**
0247: * Searches for the authorization info using the given host, port,
0248: * scheme and realm. The context is the default context.
0249: *
0250: * @param host the host
0251: * @param port the port
0252: * @param scheme the scheme
0253: * @param realm the realm
0254: * @return a pointer to the authorization data or null if not found
0255: */
0256: public static AuthorizationInfo getAuthorization(String host,
0257: int port, String scheme, String realm) {
0258: return getAuthorization(host, port, scheme, realm,
0259: HTTPConnection.getDefaultContext());
0260: }
0261:
0262: /**
0263: * Searches for the authorization info in the given context using the
0264: * given host, port, scheme and realm.
0265: *
0266: * @param host the host
0267: * @param port the port
0268: * @param scheme the scheme
0269: * @param realm the realm
0270: * @param context the context this info is associated with
0271: * @return a pointer to the authorization data or null if not found
0272: */
0273: public static synchronized AuthorizationInfo getAuthorization(
0274: String host, int port, String scheme, String realm,
0275: Object context) {
0276: Hashtable AuthList = Util.getList(CntxtList, context);
0277:
0278: AuthorizationInfo auth_info = new AuthorizationInfo(
0279: host.trim(), port, scheme.trim(), realm,
0280: (NVPair[]) null, null);
0281:
0282: return (AuthorizationInfo) AuthList.get(auth_info);
0283: }
0284:
0285: /**
0286: * Queries the AuthHandler for authorization info. It also adds this
0287: * info to the list.
0288: *
0289: * @param auth_info any info needed by the AuthHandler; at a minimum the
0290: * host, scheme and realm should be set.
0291: * @param req the request which initiated this query
0292: * @param resp the full response
0293: * @return a structure containing the requested info, or null if either
0294: * no AuthHandler is set or the user canceled the request.
0295: * @exception AuthSchemeNotImplException if this is thrown by
0296: * the AuthHandler.
0297: */
0298: static AuthorizationInfo queryAuthHandler(
0299: AuthorizationInfo auth_info, RoRequest req, RoResponse resp)
0300: throws AuthSchemeNotImplException {
0301: if (AuthHandler == null)
0302: return null;
0303:
0304: AuthorizationInfo new_info = AuthHandler.getAuthorization(
0305: auth_info, req, resp);
0306: if (new_info != null) {
0307: if (req != null)
0308: addAuthorization((AuthorizationInfo) new_info.clone(),
0309: req.getConnection().getContext());
0310: else
0311: addAuthorization((AuthorizationInfo) new_info.clone(),
0312: HTTPConnection.getDefaultContext());
0313: }
0314:
0315: return new_info;
0316: }
0317:
0318: /**
0319: * Searches for the authorization info using the host, port, scheme and
0320: * realm from the given info struct. If not found it queries the
0321: * AuthHandler (if set).
0322: *
0323: * @param auth_info the AuthorizationInfo
0324: * @param request the request which initiated this query
0325: * @param resp the full response
0326: * @param query_auth_h if true, query the auth-handler if no info found.
0327: * @return a pointer to the authorization data or null if not found
0328: * @exception AuthSchemeNotImplException If thrown by the AuthHandler.
0329: */
0330: static synchronized AuthorizationInfo getAuthorization(
0331: AuthorizationInfo auth_info, RoRequest req,
0332: RoResponse resp, boolean query_auth_h)
0333: throws AuthSchemeNotImplException {
0334: Hashtable AuthList;
0335: if (req != null)
0336: AuthList = Util.getList(CntxtList, req.getConnection()
0337: .getContext());
0338: else
0339: AuthList = Util.getList(CntxtList, HTTPConnection
0340: .getDefaultContext());
0341:
0342: AuthorizationInfo new_info = (AuthorizationInfo) AuthList
0343: .get(auth_info);
0344:
0345: if (new_info == null && query_auth_h)
0346: new_info = queryAuthHandler(auth_info, req, resp);
0347:
0348: return new_info;
0349: }
0350:
0351: /**
0352: * Searches for the authorization info given a host, port, scheme and
0353: * realm. Queries the AuthHandler if not found in list.
0354: *
0355: * @param host the host
0356: * @param port the port
0357: * @param scheme the scheme
0358: * @param realm the realm
0359: * @param query_auth_h if true, query the auth-handler if no info found.
0360: * @return a pointer to the authorization data or null if not found
0361: * @exception AuthSchemeNotImplException If thrown by the AuthHandler.
0362: */
0363: static AuthorizationInfo getAuthorization(String host, int port,
0364: String scheme, String realm, boolean query_auth_h)
0365: throws AuthSchemeNotImplException {
0366: return getAuthorization(new AuthorizationInfo(host.trim(),
0367: port, scheme.trim(), realm, (NVPair[]) null, null),
0368: null, null, query_auth_h);
0369: }
0370:
0371: /**
0372: * Adds an authorization entry to the list using the default context.
0373: * If an entry for the specified scheme and realm already exists then
0374: * its cookie and params are replaced with the new data.
0375: *
0376: * @param auth_info the AuthorizationInfo to add
0377: */
0378: public static void addAuthorization(AuthorizationInfo auth_info) {
0379: addAuthorization(auth_info, HTTPConnection.getDefaultContext());
0380: }
0381:
0382: /**
0383: * Adds an authorization entry to the list. If an entry for the
0384: * specified scheme and realm already exists then its cookie and
0385: * params are replaced with the new data.
0386: *
0387: * @param auth_info the AuthorizationInfo to add
0388: * @param context the context to associate this info with
0389: */
0390: public static void addAuthorization(AuthorizationInfo auth_info,
0391: Object context) {
0392: Hashtable AuthList = Util.getList(CntxtList, context);
0393:
0394: // merge path list
0395: AuthorizationInfo old_info = (AuthorizationInfo) AuthList
0396: .get(auth_info);
0397: if (old_info != null) {
0398: int ol = old_info.paths.length, al = auth_info.paths.length;
0399:
0400: if (al == 0)
0401: auth_info.paths = old_info.paths;
0402: else {
0403: auth_info.paths = Util.resizeArray(auth_info.paths, al
0404: + ol);
0405: System.arraycopy(old_info.paths, 0, auth_info.paths,
0406: al, ol);
0407: }
0408: }
0409:
0410: AuthList.put(auth_info, auth_info);
0411: }
0412:
0413: /**
0414: * Adds an authorization entry to the list using the default context.
0415: * If an entry for the specified scheme and realm already exists then
0416: * its cookie and params are replaced with the new data.
0417: *
0418: * @param host the host
0419: * @param port the port
0420: * @param scheme the scheme
0421: * @param realm the realm
0422: * @param cookie the cookie
0423: * @param params an array of name/value pairs of parameters
0424: * @param info arbitrary extra auth info
0425: */
0426: public static void addAuthorization(String host, int port,
0427: String scheme, String realm, String cookie,
0428: NVPair params[], Object info) {
0429: addAuthorization(host, port, scheme, realm, cookie, params,
0430: info, HTTPConnection.getDefaultContext());
0431: }
0432:
0433: /**
0434: * Adds an authorization entry to the list. If an entry for the
0435: * specified scheme and realm already exists then its cookie and
0436: * params are replaced with the new data.
0437: *
0438: * @param host the host
0439: * @param port the port
0440: * @param scheme the scheme
0441: * @param realm the realm
0442: * @param cookie the cookie
0443: * @param params an array of name/value pairs of parameters
0444: * @param info arbitrary extra auth info
0445: * @param context the context to associate this info with
0446: */
0447: public static void addAuthorization(String host, int port,
0448: String scheme, String realm, String cookie,
0449: NVPair params[], Object info, Object context) {
0450: AuthorizationInfo auth = new AuthorizationInfo(host, port,
0451: scheme, realm, cookie);
0452: if (params != null && params.length > 0)
0453: auth.auth_params = Util.resizeArray(params, params.length);
0454: auth.extra_info = info;
0455:
0456: addAuthorization(auth, context);
0457: }
0458:
0459: /**
0460: * Adds an authorization entry for the "Basic" authorization scheme to
0461: * the list using the default context. If an entry already exists for
0462: * the "Basic" scheme and the specified realm then it is overwritten.
0463: *
0464: * @param host the host
0465: * @param port the port
0466: * @param realm the realm
0467: * @param user the username
0468: * @param passwd the password
0469: */
0470: public static void addBasicAuthorization(String host, int port,
0471: String realm, String user, String passwd) {
0472: addAuthorization(host, port, "Basic", realm, Codecs
0473: .base64Encode(user + ":" + passwd), (NVPair[]) null,
0474: null);
0475: }
0476:
0477: /**
0478: * Adds an authorization entry for the "Basic" authorization scheme to
0479: * the list. If an entry already exists for the "Basic" scheme and the
0480: * specified realm then it is overwritten.
0481: *
0482: * @param host the host
0483: * @param port the port
0484: * @param realm the realm
0485: * @param user the username
0486: * @param passwd the password
0487: * @param context the context to associate this info with
0488: */
0489: public static void addBasicAuthorization(String host, int port,
0490: String realm, String user, String passwd, Object context) {
0491: addAuthorization(host, port, "Basic", realm, Codecs
0492: .base64Encode(user + ":" + passwd), (NVPair[]) null,
0493: null, context);
0494: }
0495:
0496: /**
0497: * Adds an authorization entry for the "Digest" authorization scheme to
0498: * the list using the default context. If an entry already exists for the
0499: * "Digest" scheme and the specified realm then it is overwritten.
0500: *
0501: * @param host the host
0502: * @param port the port
0503: * @param realm the realm
0504: * @param user the username
0505: * @param passwd the password
0506: */
0507: public static void addDigestAuthorization(String host, int port,
0508: String realm, String user, String passwd) {
0509: addDigestAuthorization(host, port, realm, user, passwd,
0510: HTTPConnection.getDefaultContext());
0511: }
0512:
0513: /**
0514: * Adds an authorization entry for the "Digest" authorization scheme to
0515: * the list. If an entry already exists for the "Digest" scheme and the
0516: * specified realm then it is overwritten.
0517: *
0518: * @param host the host
0519: * @param port the port
0520: * @param realm the realm
0521: * @param user the username
0522: * @param passwd the password
0523: * @param context the context to associate this info with
0524: */
0525: public static void addDigestAuthorization(String host, int port,
0526: String realm, String user, String passwd, Object context) {
0527: AuthorizationInfo prev = getAuthorization(host, port, "Digest",
0528: realm, context);
0529: NVPair[] params;
0530:
0531: if (prev == null) {
0532: params = new NVPair[4];
0533: params[0] = new NVPair("username", user);
0534: params[1] = new NVPair("uri", "");
0535: params[2] = new NVPair("nonce", "");
0536: params[3] = new NVPair("response", "");
0537: } else {
0538: params = prev.getParams();
0539: for (int idx = 0; idx < params.length; idx++) {
0540: if (params[idx].getName().equalsIgnoreCase("username")) {
0541: params[idx] = new NVPair("username", user);
0542: break;
0543: }
0544: }
0545: }
0546:
0547: String[] extra = {
0548: new MD5(user + ":" + realm + ":" + passwd).asHex(),
0549: null };
0550:
0551: addAuthorization(host, port, "Digest", realm, null, params,
0552: extra, context);
0553: }
0554:
0555: /**
0556: * Removes an authorization entry from the list using the default context.
0557: * If no entry for the specified host, port, scheme and realm exists then
0558: * this does nothing.
0559: *
0560: * @param auth_info the AuthorizationInfo to remove
0561: */
0562: public static void removeAuthorization(AuthorizationInfo auth_info) {
0563: removeAuthorization(auth_info, HTTPConnection
0564: .getDefaultContext());
0565: }
0566:
0567: /**
0568: * Removes an authorization entry from the list. If no entry for the
0569: * specified host, port, scheme and realm exists then this does nothing.
0570: *
0571: * @param auth_info the AuthorizationInfo to remove
0572: * @param context the context this info is associated with
0573: */
0574: public static void removeAuthorization(AuthorizationInfo auth_info,
0575: Object context) {
0576: Hashtable AuthList = Util.getList(CntxtList, context);
0577: AuthList.remove(auth_info);
0578: }
0579:
0580: /**
0581: * Removes an authorization entry from the list using the default context.
0582: * If no entry for the specified host, port, scheme and realm exists then
0583: * this does nothing.
0584: *
0585: * @param host the host
0586: * @param port the port
0587: * @param scheme the scheme
0588: * @param realm the realm
0589: */
0590: public static void removeAuthorization(String host, int port,
0591: String scheme, String realm) {
0592: removeAuthorization(new AuthorizationInfo(host, port, scheme,
0593: realm, (NVPair[]) null, null));
0594: }
0595:
0596: /**
0597: * Removes an authorization entry from the list. If no entry for the
0598: * specified host, port, scheme and realm exists then this does nothing.
0599: *
0600: * @param host the host
0601: * @param port the port
0602: * @param scheme the scheme
0603: * @param realm the realm
0604: * @param context the context this info is associated with
0605: */
0606: public static void removeAuthorization(String host, int port,
0607: String scheme, String realm, Object context) {
0608: removeAuthorization(new AuthorizationInfo(host, port, scheme,
0609: realm, (NVPair[]) null, null), context);
0610: }
0611:
0612: /**
0613: * Tries to find the candidate in the current list of auth info for the
0614: * given request. The paths associated with each auth info are examined,
0615: * and the one with either the nearest direct parent or child is chosen.
0616: * This is used for preemptively sending auth info.
0617: *
0618: * @param req the Request
0619: * @return an AuthorizationInfo containing the info for the best match,
0620: * or null if none found.
0621: */
0622: static AuthorizationInfo findBest(RoRequest req) {
0623: String path = Util.getPath(req.getRequestURI());
0624: String host = req.getConnection().getHost();
0625: int port = req.getConnection().getPort();
0626:
0627: // First search for an exact match
0628:
0629: Hashtable AuthList = Util.getList(CntxtList, req
0630: .getConnection().getContext());
0631: Enumeration list = AuthList.elements();
0632: while (list.hasMoreElements()) {
0633: AuthorizationInfo info = (AuthorizationInfo) list
0634: .nextElement();
0635:
0636: if (!info.host.equals(host) || info.port != port)
0637: continue;
0638:
0639: String[] paths = info.paths;
0640: for (int idx = 0; idx < paths.length; idx++) {
0641: if (path.equals(paths[idx]))
0642: return info;
0643: }
0644: }
0645:
0646: // Now find the closest parent or child
0647:
0648: AuthorizationInfo best = null;
0649: String base = path.substring(0, path.lastIndexOf('/') + 1);
0650: int min = Integer.MAX_VALUE;
0651:
0652: list = AuthList.elements();
0653: while (list.hasMoreElements()) {
0654: AuthorizationInfo info = (AuthorizationInfo) list
0655: .nextElement();
0656:
0657: if (!info.host.equals(host) || info.port != port)
0658: continue;
0659:
0660: String[] paths = info.paths;
0661: for (int idx = 0; idx < paths.length; idx++) {
0662: // strip the last path segment, leaving a trailing "/"
0663: String ibase = paths[idx].substring(0, paths[idx]
0664: .lastIndexOf('/') + 1);
0665:
0666: if (base.equals(ibase))
0667: return info;
0668:
0669: if (base.startsWith(ibase)) // found a parent
0670: {
0671: int num_seg = 0, pos = ibase.length() - 1;
0672: while ((pos = base.indexOf('/', pos + 1)) != -1)
0673: num_seg++;
0674:
0675: if (num_seg < min) {
0676: min = num_seg;
0677: best = info;
0678: }
0679: } else if (ibase.startsWith(base)) // found a child
0680: {
0681: int num_seg = 0, pos = base.length();
0682: while ((pos = ibase.indexOf('/', pos + 1)) != -1)
0683: num_seg++;
0684:
0685: if (num_seg < min) {
0686: min = num_seg;
0687: best = info;
0688: }
0689: }
0690: }
0691: }
0692:
0693: return best;
0694: }
0695:
0696: /**
0697: * Adds the path from the given resource to our path list. The path
0698: * list is used for deciding when to preemptively send auth info.
0699: *
0700: * @param resource the resource from which to extract the path
0701: */
0702: public synchronized void addPath(String resource) {
0703: String path = Util.getPath(resource);
0704:
0705: // First check that we don't already have this one
0706: for (int idx = 0; idx < paths.length; idx++)
0707: if (paths[idx].equals(path))
0708: return;
0709:
0710: // Ok, add it
0711: paths = Util.resizeArray(paths, paths.length + 1);
0712: paths[paths.length - 1] = path;
0713: }
0714:
0715: /**
0716: * Parses the authentication challenge(s) into an array of new info
0717: * structures for the specified host and port.
0718: *
0719: * @param challenge a string containing authentication info. This must
0720: * have the same format as value part of a
0721: * WWW-authenticate response header field, and may
0722: * contain multiple authentication challenges.
0723: * @param req the original request.
0724: * @exception ProtocolException if any error during the parsing occurs.
0725: */
0726: static AuthorizationInfo[] parseAuthString(String challenge,
0727: RoRequest req, RoResponse resp) throws ProtocolException {
0728: int beg = 0, end = 0;
0729: char[] buf = challenge.toCharArray();
0730: char ch;
0731: int len = buf.length;
0732:
0733: AuthorizationInfo auth_arr[] = new AuthorizationInfo[0], curr;
0734:
0735: while (Character.isSpace(buf[len - 1]))
0736: len--;
0737:
0738: while (true) // get all challenges
0739: {
0740: // get scheme
0741: beg = Util.skipSpace(buf, beg);
0742: if (beg == len)
0743: break;
0744:
0745: end = Util.findSpace(buf, beg + 1);
0746:
0747: int sts;
0748: try {
0749: sts = resp.getStatusCode();
0750: } catch (IOException ioe) {
0751: throw new ProtocolException(ioe.toString());
0752: }
0753: if (sts == 401)
0754: curr = new AuthorizationInfo(req.getConnection()
0755: .getHost(), req.getConnection().getPort());
0756: else
0757: curr = new AuthorizationInfo(req.getConnection()
0758: .getProxyHost(), req.getConnection()
0759: .getProxyPort());
0760: curr.scheme = challenge.substring(beg, end);
0761:
0762: // get auth-parameters
0763: boolean first = true;
0764: Vector params = new Vector();
0765: while (true) {
0766: beg = Util.skipSpace(buf, end);
0767: if (beg == len)
0768: break;
0769:
0770: if (!first) // expect ","
0771: {
0772: if (buf[beg] != ',')
0773: throw new ProtocolException(
0774: "Bad Authentication header "
0775: + "format: '"
0776: + challenge
0777: + "'\nExpected \",\" at position "
0778: + beg);
0779:
0780: beg = Util.skipSpace(buf, beg + 1); // find param name
0781: if (beg == len)
0782: break;
0783: if (buf[beg] == ',') // skip empty params
0784: {
0785: end = beg;
0786: continue;
0787: }
0788: }
0789:
0790: int pstart = beg;
0791:
0792: // extract name
0793: end = beg + 1;
0794: while (end < len && !Character.isSpace(buf[end])
0795: && buf[end] != '=' && buf[end] != ',')
0796: end++;
0797:
0798: // hack to deal with schemes which use cookies in challenge
0799: if (first
0800: && (end == len || buf[end] == '='
0801: && (end + 1 == len || (buf[end + 1] == '=' && end + 2 == len)))) {
0802: curr.cookie = challenge.substring(beg, len);
0803: beg = len;
0804: break;
0805: }
0806:
0807: String param_name = challenge.substring(beg, end), param_value;
0808:
0809: beg = Util.skipSpace(buf, end); // find "=" or ","
0810:
0811: if (beg < len && buf[beg] != '=' && buf[beg] != ',') { // It's not a param, but another challenge
0812: beg = pstart;
0813: break;
0814: }
0815:
0816: if (buf[beg] == '=') // we have a value
0817: {
0818: beg = Util.skipSpace(buf, beg + 1);
0819: if (beg == len)
0820: throw new ProtocolException(
0821: "Bad Authentication header "
0822: + "format: "
0823: + challenge
0824: + "\nUnexpected EOL after token"
0825: + " at position " + (end - 1));
0826: if (buf[beg] != '"') // it's a token
0827: {
0828: end = Util.skipToken(buf, beg);
0829: if (end == beg)
0830: throw new ProtocolException(
0831: "Bad Authentication header "
0832: + "format: " + challenge
0833: + "\nToken expected at "
0834: + "position " + beg);
0835: param_value = challenge.substring(beg, end);
0836: } else // it's a quoted-string
0837: {
0838: end = beg++;
0839: do
0840: end = challenge.indexOf('"', end + 1);
0841: while (end != -1
0842: && challenge.charAt(end - 1) == '\\');
0843: if (end == -1)
0844: throw new ProtocolException(
0845: "Bad Authentication header "
0846: + "format: "
0847: + challenge
0848: + "\nClosing <\"> for "
0849: + "quoted-string starting at position "
0850: + beg + " not found");
0851: param_value = Util.dequoteString(challenge
0852: .substring(beg, end));
0853: end++;
0854: }
0855: } else
0856: // this is not strictly allowed
0857: param_value = null;
0858:
0859: if (param_name.equalsIgnoreCase("realm"))
0860: curr.realm = param_value;
0861: else
0862: params.addElement(new NVPair(param_name,
0863: param_value));
0864:
0865: first = false;
0866: }
0867:
0868: if (!params.isEmpty()) {
0869: curr.auth_params = new NVPair[params.size()];
0870: params.copyInto(curr.auth_params);
0871: }
0872:
0873: if (curr.realm == null)
0874: /* Can't do this if we're supposed to allow for broken schemes
0875: * such as NTLM, Kerberos, and PEM.
0876: *
0877: throw new ProtocolException("Bad Authentication header "
0878: + "format: " + challenge + "\nNo realm value found");
0879: */
0880: curr.realm = "";
0881:
0882: auth_arr = Util.resizeArray(auth_arr, auth_arr.length + 1);
0883: auth_arr[auth_arr.length - 1] = curr;
0884: }
0885:
0886: return auth_arr;
0887: }
0888:
0889: // Instance Methods
0890:
0891: /**
0892: * Get the host.
0893: *
0894: * @return a string containing the host name.
0895: */
0896: public final String getHost() {
0897: return host;
0898: }
0899:
0900: /**
0901: * Get the port.
0902: *
0903: * @return an int containing the port number.
0904: */
0905: public final int getPort() {
0906: return port;
0907: }
0908:
0909: /**
0910: * Get the scheme.
0911: *
0912: * @return a string containing the scheme.
0913: */
0914: public final String getScheme() {
0915: return scheme;
0916: }
0917:
0918: /**
0919: * Get the realm.
0920: *
0921: * @return a string containing the realm.
0922: */
0923: public final String getRealm() {
0924: return realm;
0925: }
0926:
0927: /**
0928: * Get the cookie
0929: *
0930: * @return the cookie String
0931: * @since V0.3-1
0932: */
0933: public final String getCookie() {
0934: return cookie;
0935: }
0936:
0937: /**
0938: * Set the cookie
0939: *
0940: * @param cookie the new cookie
0941: * @since V0.3-1
0942: */
0943: public final void setCookie(String cookie) {
0944: this .cookie = cookie;
0945: }
0946:
0947: /**
0948: * Get the authentication parameters.
0949: *
0950: * @return an array of name/value pairs.
0951: */
0952: public final NVPair[] getParams() {
0953: return Util.resizeArray(auth_params, auth_params.length);
0954: }
0955:
0956: /**
0957: * Set the authentication parameters.
0958: *
0959: * @param an array of name/value pairs.
0960: */
0961: public final void setParams(NVPair[] params) {
0962: if (params != null)
0963: auth_params = Util.resizeArray(params, params.length);
0964: else
0965: auth_params = new NVPair[0];
0966: }
0967:
0968: /**
0969: * Get the extra info.
0970: *
0971: * @return the extra_info object
0972: */
0973: public final Object getExtraInfo() {
0974: return extra_info;
0975: }
0976:
0977: /**
0978: * Set the extra info.
0979: *
0980: * @param info the extra info
0981: */
0982: public final void setExtraInfo(Object info) {
0983: extra_info = info;
0984: }
0985:
0986: /**
0987: * Constructs a string containing the authorization info. The format
0988: * is that of the http Authorization header.
0989: *
0990: * @return a String containing all info.
0991: */
0992: public String toString() {
0993: StringBuffer field = new StringBuffer(100);
0994:
0995: field.append(scheme);
0996: field.append(" ");
0997:
0998: if (cookie != null) {
0999: field.append(cookie);
1000: } else {
1001: if (realm.length() > 0) {
1002: field.append("realm=\"");
1003: field.append(Util.quoteString(realm, "\\\""));
1004: field.append('"');
1005: }
1006:
1007: for (int idx = 0; idx < auth_params.length; idx++) {
1008: field.append(',');
1009: field.append(auth_params[idx].getName());
1010: field.append("=\"");
1011: field.append(Util.quoteString(auth_params[idx]
1012: .getValue(), "\\\""));
1013: field.append('"');
1014: }
1015: }
1016:
1017: return field.toString();
1018: }
1019:
1020: /**
1021: * Produces a hash code based on host, scheme and realm. Port is not
1022: * included for simplicity (and because it probably won't make much
1023: * difference). Used in the AuthorizationInfo.AuthList hash table.
1024: *
1025: * @return the hash code
1026: */
1027: public int hashCode() {
1028: return (host + scheme.toLowerCase() + realm).hashCode();
1029: }
1030:
1031: /**
1032: * Two AuthorizationInfos are considered equal if their host, port,
1033: * scheme and realm match. Used in the AuthorizationInfo.AuthList hash
1034: * table.
1035: *
1036: * @param obj another AuthorizationInfo against which this one is
1037: * to be compared.
1038: * @return true if they match in the above mentioned fields; false
1039: * otherwise.
1040: */
1041: public boolean equals(Object obj) {
1042: if ((obj != null) && (obj instanceof AuthorizationInfo)) {
1043: AuthorizationInfo auth = (AuthorizationInfo) obj;
1044: if (host.equals(auth.host) && (port == auth.port)
1045: && scheme.equalsIgnoreCase(auth.scheme)
1046: && realm.equals(auth.realm))
1047: return true;
1048: }
1049: return false;
1050: }
1051:
1052: /**
1053: * @return a clone of this AuthorizationInfo using a deep copy
1054: */
1055: public Object clone() {
1056: AuthorizationInfo ai;
1057: try {
1058: ai = (AuthorizationInfo) super .clone();
1059: ai.auth_params = Util.resizeArray(auth_params,
1060: auth_params.length);
1061: try {
1062: // ai.extra_info = extra_info.clone();
1063: ai.extra_info = extra_info.getClass().getMethod(
1064: "clone", null).invoke(extra_info, null);
1065: } catch (Throwable t) {
1066: }
1067: ai.paths = new String[paths.length];
1068: System.arraycopy(paths, 0, ai.paths, 0, paths.length);
1069: } catch (CloneNotSupportedException cnse) {
1070: throw new InternalError(cnse.toString()); /* shouldn't happen */
1071: }
1072:
1073: return ai;
1074: }
1075: }
|