Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: HTTPClient/AuthorizationInfo.java


1   /*
2    * @(#)AuthorizationInfo.java        0.3-2 18/06/1999
3    *
4    *  This file is part of the HTTPClient package
5    *  Copyright (C) 1996-1999  Ronald Tschalär
6    *
7    *  This library is free software; you can redistribute it and/or
8    *  modify it under the terms of the GNU Lesser General Public
9    *  License as published by the Free Software Foundation; either
10   *  version 2 of the License, or (at your option) any later version.
11   *
12   *  This library is distributed in the hope that it will be useful,
13   *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15   *  Lesser General Public License for more details.
16   *
17   *  You should have received a copy of the GNU Lesser General Public
18   *  License along with this library; if not, write to the Free
19   *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
20   *  MA 02111-1307, USA
21   *
22   *  For questions, suggestions, bug-reports, enhancement-requests etc.
23   *  I may be contacted at:
24   *
25   *  ronald@innovation.ch
26   *
27   */
28  
29  package HTTPClient;
30  
31  
32  import java.io.IOException;
33  import java.net.ProtocolException;
34  import java.util.Vector;
35  import java.util.Hashtable;
36  import java.util.Enumeration;
37  
38  
39  /**
40   * Holds the information for an authorization response.
41   *
42   * <P>There are 7 fields which make up this class: host, port, scheme,
43   * realm, cookie, params, and extra_info. The host and port select which
44   * server the info will be sent to. The realm is server specified string
45   * which groups various URLs under a given server together and which is
46   * used to select the correct info when a server issues an auth challenge;
47   * for schemes which don't use a realm (such as "NTLM", "PEM", and
48   * "Kerberos") the realm must be the empty string (""). The scheme is the
49   * authorization scheme used (such as "Basic" or "Digest").
50   *
51   * <P>There are basically two formats used for the Authorization header,
52   * the one used by the "Basic" scheme and derivatives, and the one used by
53   * the "Digest" scheme and derivatives. The first form contains just the
54   * the scheme and a "cookie":
55   * 
56   * <PRE>    Authorization: Basic aGVsbG86d29ybGQ=</PRE>
57   * 
58   * The second form contains the scheme followed by a number of parameters
59   * in the form of name=value pairs:
60   * 
61   * <PRE>    Authorization: Digest username="hello", realm="test", nonce="42", ...</PRE>
62   * 
63   * The two fields "cookie" and "params" correspond to these two forms.
64   * <A HREF="#toString()">toString()</A> is used by the AuthorizationModule
65   * when generating the Authorization header and will format the info
66   * accordingly. Note that "cookie" and "params" are mutually exclusive: if
67   * the cookie field is non-null then toString() will generate the first
68   * form; otherwise it will generate the second form.
69   *
70   * <P>In some schemes "extra" information needs to be kept which doesn't
71   * appear directly in the Authorization header. An example of this are the
72   * A1 and A2 strings in the Digest scheme. Since all elements in the params
73   * field will appear in the Authorization header this field can't be used
74   * for storing such info. This is what the extra_info field is for. It is
75   * an arbitrary object which can be manipulated by the corresponding
76   * setExtraInfo() and getExtraInfo() methods, but which will not be printed
77   * by toString().
78   *
79   * <P>The addXXXAuthorization(), removeXXXAuthorization(), and
80   * getAuthorization() methods manipulate and query an internal list of
81   * AuthorizationInfo instances. There can be only one instance per host,
82   * port, scheme, and realm combination (see <A HREF="#equals">equals()</A>).
83   *
84   * @version  0.3-2  18/06/1999
85   * @author  Ronald Tschalär
86   * @since  V0.1
87   */
88  
89  public class AuthorizationInfo implements GlobalConstants, Cloneable
90  {
91      // class fields
92  
93      /** Holds the list of lists of authorization info structures */
94      private static Hashtable     CntxtList = new Hashtable();
95  
96      /** A pointer to the handler to be called when we need authorization info */
97      private static AuthorizationHandler
98           AuthHandler = new DefaultAuthHandler();
99  
100     static
101     {
102   CntxtList.put(HTTPConnection.getDefaultContext(), new Hashtable());
103     }
104 
105 
106     // the instance oriented stuff
107 
108     /** the host (lowercase) */
109     private String host;
110 
111     /** the port */
112     private int port;
113 
114     /** the scheme. (e.g. "Basic")
115      * Note: don't lowercase because some buggy servers use a case-sensitive
116      * match */
117     private String scheme;
118 
119     /** the realm */
120     private String realm;
121 
122     /** the string used for the "Basic", "NTLM", and other authorization
123      *  schemes which don't use parameters  */
124     private String cookie;
125 
126     /** any parameters */
127     private NVPair[] auth_params = new NVPair[0];
128 
129     /** additional info which won't be displayed in the toString() */
130     private Object extra_info = null;
131 
132     /** a list of paths where this realm has been known to be required */
133     private String[] paths = new String[0];
134 
135 
136     // Constructors
137 
138     /**
139      * Creates an new info structure for the specified host and port.
140      *
141      * @param host   the host
142      * @param port   the port
143      */
144     AuthorizationInfo(String host, int port)
145     {
146   this.host = host.trim().toLowerCase();
147   this.port = port;
148     }
149 
150 
151     /**
152      * Creates a new info structure for the specified host and port with the
153      * specified scheme, realm, params. The cookie is set to null.
154      *
155      * @param host   the host
156      * @param port   the port
157      * @param scheme the scheme
158      * @param realm  the realm
159      * @param params the parameters as an array of name/value pairs, or null
160      * @param info   arbitrary extra info, or null
161      */
162     public AuthorizationInfo(String host, int port, String scheme,
163            String realm, NVPair params[], Object info)
164     {
165   this.scheme = scheme.trim();
166   this.host   = host.trim().toLowerCase();
167   this.port   = port;
168   this.realm  = realm;
169   this.cookie = null;
170 
171   if (params != null)
172       auth_params = Util.resizeArray(params, params.length);
173 
174   this.extra_info   = info;
175     }
176 
177 
178     /**
179      * Creates a new info structure for the specified host and port with the
180      * specified scheme, realm and cookie. The params is set to a zero-length
181      * array, and the extra_info is set to null.
182      *
183      * @param host   the host
184      * @param port   the port
185      * @param scheme the scheme
186      * @param realm  the realm
187      * @param cookie for the "Basic" scheme this is the base64-encoded
188      *               username/password; for the "NTLM" scheme this is the
189      *               base64-encoded username/password message.
190      */
191     public AuthorizationInfo(String host, int port, String scheme,
192            String realm, String cookie)
193     {
194   this.scheme = scheme.trim();
195   this.host   = host.trim().toLowerCase();
196   this.port   = port;
197   this.realm  = realm;
198   if (cookie != null)
199       this.cookie = cookie.trim();
200   else
201       this.cookie = null;
202     }
203 
204 
205     /**
206      * Creates a new copy of the given AuthorizationInfo.
207      *
208      * @param templ the info to copy
209      */
210     AuthorizationInfo(AuthorizationInfo templ)
211     {
212   this.scheme = templ.scheme;
213   this.host   = templ.host;
214   this.port   = templ.port;
215   this.realm  = templ.realm;
216   this.cookie = templ.cookie;
217 
218   this.auth_params =
219     Util.resizeArray(templ.auth_params, templ.auth_params.length);
220 
221   this.extra_info  = templ.extra_info;
222     }
223 
224 
225     // Class Methods
226 
227     /**
228      * Set's the authorization handler. This handler is called whenever
229      * the server requests authorization and no entry for the requested
230      * scheme and realm can be found in the list. The handler must implement
231      * the AuthorizationHandler interface.
232      * <BR>If no handler is set then a default handler is used. This handler
233      * currently only handles the "Basic" scheme and brings up a popup which
234      * prompts for the username and password.
235      * <BR>The default handler can be disabled by setting the auth handler
236      * to <var>null</var>.
237      *
238      * @param  handler the new authorization handler
239      * @return the old authorization handler
240      * @see    AuthorizationHandler
241      */
242     public static AuthorizationHandler
243         setAuthHandler(AuthorizationHandler handler)
244     {
245   AuthorizationHandler tmp = AuthHandler;
246   AuthHandler = handler;
247 
248   return tmp;
249     }
250 
251 
252     /**
253      * Get's the current authorization handler.
254      *
255      * @return the current authorization handler, or null if none is set.
256      * @see    AuthorizationHandler
257      */
258     public static AuthorizationHandler getAuthHandler()
259     {
260   return AuthHandler;
261     }
262 
263 
264     /**
265      * Searches for the authorization info using the given host, port,
266      * scheme and realm. The context is the default context.
267      *
268      * @param  host         the host
269      * @param  port         the port
270      * @param  scheme       the scheme
271      * @param  realm        the realm
272      * @return a pointer to the authorization data or null if not found
273      */
274     public static AuthorizationInfo getAuthorization(
275             String host, int port,
276             String scheme, String realm)
277     {
278   return getAuthorization(host, port, scheme, realm,
279         HTTPConnection.getDefaultContext());
280     }
281 
282 
283     /**
284      * Searches for the authorization info in the given context using the
285      * given host, port, scheme and realm.
286      *
287      * @param  host         the host
288      * @param  port         the port
289      * @param  scheme       the scheme
290      * @param  realm        the realm
291      * @param  context      the context this info is associated with
292      * @return a pointer to the authorization data or null if not found
293      */
294     public static synchronized AuthorizationInfo getAuthorization(
295             String host, int port,
296             String scheme, String realm,
297             Object context)
298     {
299   Hashtable AuthList = Util.getList(CntxtList, context);
300 
301   AuthorizationInfo auth_info =
302       new AuthorizationInfo(host.trim(), port, scheme.trim(),
303           realm, (NVPair[]) null, null);
304 
305   return (AuthorizationInfo) AuthList.get(auth_info);
306     }
307 
308 
309     /**
310      * Queries the AuthHandler for authorization info. It also adds this
311      * info to the list.
312      *
313      * @param  auth_info  any info needed by the AuthHandler; at a minimum the
314      *                    host, scheme and realm should be set.
315      * @param  req        the request which initiated this query
316      * @param  resp       the full response
317      * @return a structure containing the requested info, or null if either
318      *         no AuthHandler is set or the user canceled the request.
319      * @exception AuthSchemeNotImplException if this is thrown by
320      *                                            the AuthHandler.
321      */
322     static AuthorizationInfo queryAuthHandler(AuthorizationInfo auth_info,
323                 RoRequest req, RoResponse resp)
324   throws AuthSchemeNotImplException
325     {
326   if (AuthHandler == null)
327       return null;
328 
329   AuthorizationInfo new_info =
330         AuthHandler.getAuthorization(auth_info, req, resp);
331   if (new_info != null)
332   {
333       if (req != null)
334     addAuthorization((AuthorizationInfo) new_info.clone(),
335          req.getConnection().getContext());
336       else
337     addAuthorization((AuthorizationInfo) new_info.clone(),
338          HTTPConnection.getDefaultContext());
339   }
340 
341   return new_info;
342     }
343 
344 
345     /**
346      * Searches for the authorization info using the host, port, scheme and
347      * realm from the given info struct. If not found it queries the
348      * AuthHandler (if set).
349      *
350      * @param  auth_info    the AuthorizationInfo
351      * @param  request      the request which initiated this query
352      * @param  resp         the full response
353      * @param  query_auth_h if true, query the auth-handler if no info found.
354      * @return a pointer to the authorization data or null if not found
355      * @exception AuthSchemeNotImplException If thrown by the AuthHandler.
356      */
357     static synchronized AuthorizationInfo getAuthorization(
358             AuthorizationInfo auth_info, RoRequest req,
359             RoResponse resp, boolean query_auth_h)
360   throws AuthSchemeNotImplException
361     {
362   Hashtable AuthList;
363   if (req != null)
364       AuthList = Util.getList(CntxtList, req.getConnection().getContext());
365   else
366       AuthList = Util.getList(CntxtList, HTTPConnection.getDefaultContext());
367 
368   AuthorizationInfo new_info =
369       (AuthorizationInfo) AuthList.get(auth_info);
370 
371   if (new_info == null  &&  query_auth_h)
372       new_info = queryAuthHandler(auth_info, req, resp);
373 
374   return new_info;
375     }
376 
377 
378     /**
379      * Searches for the authorization info given a host, port, scheme and
380      * realm. Queries the AuthHandler if not found in list.
381      *
382      * @param  host         the host
383      * @param  port         the port
384      * @param  scheme       the scheme
385      * @param  realm        the realm
386      * @param  query_auth_h if true, query the auth-handler if no info found.
387      * @return a pointer to the authorization data or null if not found
388      * @exception AuthSchemeNotImplException If thrown by the AuthHandler.
389      */
390     static AuthorizationInfo getAuthorization(String host, int port,
391                 String scheme, String realm,
392                 boolean query_auth_h)
393   throws AuthSchemeNotImplException
394     {
395   return getAuthorization(new AuthorizationInfo(host.trim(), port,
396         scheme.trim(), realm, (NVPair[]) null, null),
397         null, null, query_auth_h);
398     }
399 
400 
401     /**
402      * Adds an authorization entry to the list using the default context.
403      * If an entry for the specified scheme and realm already exists then
404      * its cookie and params are replaced with the new data.
405      *
406      * @param auth_info the AuthorizationInfo to add
407      */
408     public static void addAuthorization(AuthorizationInfo auth_info)
409     {
410   addAuthorization(auth_info, HTTPConnection.getDefaultContext());
411     }
412 
413 
414     /**
415      * Adds an authorization entry to the list. If an entry for the
416      * specified scheme and realm already exists then its cookie and
417      * params are replaced with the new data.
418      *
419      * @param auth_info the AuthorizationInfo to add
420      * @param context   the context to associate this info with
421      */
422     public static void addAuthorization(AuthorizationInfo auth_info,
423           Object context)
424     {
425   Hashtable AuthList = Util.getList(CntxtList, context);
426 
427   // merge path list
428   AuthorizationInfo old_info =
429           (AuthorizationInfo) AuthList.get(auth_info);
430   if (old_info != null)
431   {
432       int ol = old_info.paths.length,
433     al = auth_info.paths.length;
434 
435       if (al == 0)
436     auth_info.paths = old_info.paths;
437       else
438       {
439     auth_info.paths = Util.resizeArray(auth_info.paths, al+ol);
440     System.arraycopy(old_info.paths, 0, auth_info.paths, al, ol);
441       }
442   }
443 
444   AuthList.put(auth_info, auth_info);
445     }
446 
447 
448     /**
449      * Adds an authorization entry to the list using the default context.
450      * If an entry for the specified scheme and realm already exists then
451      * its cookie and params are replaced with the new data.
452      *
453      * @param host   the host
454      * @param port   the port
455      * @param scheme the scheme
456      * @param realm  the realm
457      * @param cookie the cookie
458      * @param params an array of name/value pairs of parameters
459      * @param info   arbitrary extra auth info
460      */
461     public static void addAuthorization(String host, int port, String scheme,
462           String realm, String cookie,
463           NVPair params[], Object info)
464     {
465   addAuthorization(host, port, scheme, realm, cookie, params, info,
466        HTTPConnection.getDefaultContext());
467     }
468 
469 
470     /**
471      * Adds an authorization entry to the list. If an entry for the
472      * specified scheme and realm already exists then its cookie and
473      * params are replaced with the new data.
474      *
475      * @param host    the host
476      * @param port    the port
477      * @param scheme  the scheme
478      * @param realm   the realm
479      * @param cookie  the cookie
480      * @param params  an array of name/value pairs of parameters
481      * @param info    arbitrary extra auth info
482      * @param context the context to associate this info with
483      */
484     public static void addAuthorization(String host, int port, String scheme,
485           String realm, String cookie,
486           NVPair params[], Object info,
487           Object context)
488     {
489   AuthorizationInfo auth =
490       new AuthorizationInfo(host, port, scheme, realm, cookie);
491   if (params != null  &&  params.length > 0)
492       auth.auth_params = Util.resizeArray(params, params.length);
493   auth.extra_info = info;
494 
495   addAuthorization(auth, context);
496     }
497 
498 
499     /**
500      * Adds an authorization entry for the "Basic" authorization scheme to
501      * the list using the default context. If an entry already exists for
502      * the "Basic" scheme and the specified realm then it is overwritten.
503      *
504      * @param host   the host
505      * @param port   the port
506      * @param realm  the realm
507      * @param user   the username
508      * @param passwd the password
509      */
510     public static void addBasicAuthorization(String host, int port,
511                String realm, String user,
512                String passwd)
513     {
514   addAuthorization(host, port, "Basic", realm,
515        Codecs.base64Encode(user + ":" + passwd),
516        (NVPair[]) null, null);
517     }
518 
519 
520     /**
521      * Adds an authorization entry for the "Basic" authorization scheme to
522      * the list. If an entry already exists for the "Basic" scheme and the
523      * specified realm then it is overwritten.
524      *
525      * @param host    the host
526      * @param port    the port
527      * @param realm   the realm
528      * @param user    the username
529      * @param passwd  the password
530      * @param context the context to associate this info with
531      */
532     public static void addBasicAuthorization(String host, int port,
533                String realm, String user,
534                String passwd, Object context)
535     {
536   addAuthorization(host, port, "Basic", realm,
537        Codecs.base64Encode(user + ":" + passwd),
538        (NVPair[]) null, null, context);
539     }
540 
541 
542     /**
543      * Adds an authorization entry for the "Digest" authorization scheme to
544      * the list using the default context. If an entry already exists for the
545      * "Digest" scheme and the specified realm then it is overwritten.
546      *
547      * @param host   the host
548      * @param port   the port
549      * @param realm  the realm
550      * @param user   the username
551      * @param passwd the password
552      */
553     public static void addDigestAuthorization(String host, int port,
554                 String realm, String user,
555                 String passwd)
556     {
557   addDigestAuthorization(host, port, realm, user, passwd,
558              HTTPConnection.getDefaultContext());
559     }
560 
561 
562     /**
563      * Adds an authorization entry for the "Digest" authorization scheme to
564      * the list. If an entry already exists for the "Digest" scheme and the
565      * specified realm then it is overwritten.
566      *
567      * @param host    the host
568      * @param port    the port
569      * @param realm   the realm
570      * @param user    the username
571      * @param passwd  the password
572      * @param context the context to associate this info with
573      */
574     public static void addDigestAuthorization(String host, int port,
575                 String realm, String user,
576                 String passwd, Object context)
577     {
578   AuthorizationInfo prev =
579       getAuthorization(host, port, "Digest", realm, context);
580   NVPair[] params;
581 
582   if (prev == null)
583   {
584       params = new NVPair[4];
585       params[0] = new NVPair("username", user);
586       params[1] = new NVPair("uri", "");
587       params[2] = new NVPair("nonce", "");
588       params[3] = new NVPair("response", "");
589   }
590   else
591   {
592       params = prev.getParams();
593       for (int idx=0; idx<params.length; idx++)
594       {
595     if (params[idx].getName().equalsIgnoreCase("username"))
596     {
597         params[idx] = new NVPair("username", user);
598         break;
599     }
600       }
601   }
602 
603   String[] extra = { new MD5(user + ":" + realm + ":" + passwd).asHex(),
604          null };
605 
606   addAuthorization(host, port, "Digest", realm, null, params, extra,
607        context);
608     }
609 
610 
611     /**
612      * Removes an authorization entry from the list using the default context.
613      * If no entry for the specified host, port, scheme and realm exists then
614      * this does nothing.
615      *
616      * @param auth_info the AuthorizationInfo to remove
617      */
618     public static void removeAuthorization(AuthorizationInfo auth_info)
619     {
620   removeAuthorization(auth_info, HTTPConnection.getDefaultContext());
621     }
622 
623 
624     /**
625      * Removes an authorization entry from the list. If no entry for the
626      * specified host, port, scheme and realm exists then this does nothing.
627      *
628      * @param auth_info the AuthorizationInfo to remove
629      * @param context   the context this info is associated with
630      */
631     public static void removeAuthorization(AuthorizationInfo auth_info,
632              Object context)
633     {
634   Hashtable AuthList = Util.getList(CntxtList, context);
635   AuthList.remove(auth_info);
636     }
637 
638 
639     /**
640      * Removes an authorization entry from the list using the default context.
641      * If no entry for the specified host, port, scheme and realm exists then
642      * this does nothing.
643      *
644      * @param host   the host
645      * @param port   the port
646      * @param scheme the scheme
647      * @param realm  the realm
648      */
649     public static void removeAuthorization(String host, int port, String scheme,
650              String realm)
651     {
652   removeAuthorization(
653       new AuthorizationInfo(host, port, scheme, realm, (NVPair[]) null,
654           null));
655     }
656 
657 
658     /**
659      * Removes an authorization entry from the list. If no entry for the
660      * specified host, port, scheme and realm exists then this does nothing.
661      *
662      * @param host    the host
663      * @param port    the port
664      * @param scheme  the scheme
665      * @param realm   the realm
666      * @param context the context this info is associated with
667      */
668     public static void removeAuthorization(String host, int port, String scheme,
669              String realm, Object context)
670     {
671   removeAuthorization(
672       new AuthorizationInfo(host, port, scheme, realm, (NVPair[]) null,
673           null), context);
674     }
675 
676 
677     /**
678      * Tries to find the candidate in the current list of auth info for the
679      * given request. The paths associated with each auth info are examined,
680      * and the one with either the nearest direct parent or child is chosen.
681      * This is used for preemptively sending auth info.
682      *
683      * @param  req  the Request
684      * @return an AuthorizationInfo containing the info for the best match,
685      *         or null if none found.
686      */
687     static AuthorizationInfo findBest(RoRequest req)
688     {
689   String path = Util.getPath(req.getRequestURI());
690   String host = req.getConnection().getHost();
691   int    port = req.getConnection().getPort();
692 
693 
694   // First search for an exact match
695 
696   Hashtable AuthList =
697         Util.getList(CntxtList, req.getConnection().getContext());
698   Enumeration list = AuthList.elements();
699   while (list.hasMoreElements())
700   {
701       AuthorizationInfo info = (AuthorizationInfo) list.nextElement();
702 
703       if (!info.host.equals(host)  ||  info.port != port)
704     continue;
705 
706       String[] paths = info.paths;
707       for (int idx=0; idx<paths.length; idx++)
708       {
709     if (path.equals(paths[idx]))
710         return info;
711       }
712   }
713 
714 
715   // Now find the closest parent or child
716 
717   AuthorizationInfo best = null;
718   String base = path.substring(0, path.lastIndexOf('/')+1);
719   int    min  = Integer.MAX_VALUE;
720 
721   list = AuthList.elements();
722   while (list.hasMoreElements())
723   {
724       AuthorizationInfo info = (AuthorizationInfo) list.nextElement();
725 
726       if (!info.host.equals(host)  ||  info.port != port)
727     continue;
728 
729       String[] paths = info.paths;
730       for (int idx=0; idx<paths.length; idx++)
731       {
732     // strip the last path segment, leaving a trailing "/"
733     String ibase =
734       paths[idx].substring(0, paths[idx].lastIndexOf('/')+1);
735 
736     if (base.equals(ibase))
737         return info;
738 
739     if (base.startsWith(ibase))    // found a parent
740     {
741         int num_seg = 0, pos = ibase.length()-1;
742         while ((pos = base.indexOf('/', pos+1)) != -1)  num_seg++;
743 
744         if (num_seg < min)
745         {
746       min  = num_seg;
747       best = info;
748         }
749     }
750     else if (ibase.startsWith(base))  // found a child
751     {
752         int num_seg = 0, pos = base.length();
753         while ((pos = ibase.indexOf('/', pos+1)) != -1)  num_seg++;
754 
755         if (num_seg < min)
756         {
757       min  = num_seg;
758       best = info;
759         }
760     }
761       }
762   }
763 
764   return best;
765     }
766 
767 
768     /**
769      * Adds the path from the given resource to our path list. The path
770      * list is used for deciding when to preemptively send auth info.
771      *
772      * @param resource the resource from which to extract the path
773      */
774     public synchronized void addPath(String resource)
775     {
776   String path = Util.getPath(resource);
777 
778   // First check that we don't already have this one
779   for (int idx=0; idx<paths.length; idx++)
780       if (paths[idx].equals(path)) return;
781 
782   // Ok, add it
783   paths = Util.resizeArray(paths, paths.length+1);
784   paths[paths.length-1] = path;
785     }
786 
787 
788     /**
789      * Parses the authentication challenge(s) into an array of new info
790      * structures for the specified host and port.
791      *
792      * @param challenge a string containing authentication info. This must
793      *                  have the same format as value part of a
794      *                  WWW-authenticate response header field, and may
795      *                  contain multiple authentication challenges.
796      * @param req       the original request.
797      * @exception ProtocolException if any error during the parsing occurs.
798      */
799     static AuthorizationInfo[] parseAuthString(String challenge, RoRequest req,
800                  RoResponse resp)
801       throws ProtocolException
802     {
803   int    beg = 0,
804          end = 0;
805   char[] buf = challenge.toCharArray();
806   char   ch;
807   int    len = buf.length;
808 
809   AuthorizationInfo auth_arr[] = new AuthorizationInfo[0],
810         curr;
811 
812   while (Character.isSpace(buf[len-1]))  len--;
813 
814   while (true)      // get all challenges
815   {
816       // get scheme
817       beg = Util.skipSpace(buf, beg);
818       if (beg == len)  break;
819 
820       end = Util.findSpace(buf, beg+1);
821 
822       int sts;
823       try
824     { sts = resp.getStatusCode(); }
825       catch (IOException ioe)
826     { throw new ProtocolException(ioe.toString()); }
827       if (sts == 401)
828     curr = new AuthorizationInfo(req.getConnection().getHost(),
829                req.getConnection().getPort());
830       else
831     curr = new AuthorizationInfo(req.getConnection().getProxyHost(),
832               req.getConnection().getProxyPort());
833       curr.scheme = challenge.substring(beg, end);
834 
835       // get auth-parameters
836       boolean first = true;
837       Vector params = new Vector();
838       while (true)
839       {
840     beg = Util.skipSpace(buf, end);
841     if (beg == len)  break;
842 
843     if (!first)        // expect ","
844     {
845         if (buf[beg] != ',')
846       throw new ProtocolException("Bad Authentication header "
847                 + "format: '" + challenge +
848                 "'\nExpected \",\" at position "+
849                 beg);
850 
851         beg = Util.skipSpace(buf, beg+1);  // find param name
852         if (beg == len)  break;
853         if (buf[beg] == ',')  // skip empty params
854         {
855       end = beg;
856       continue;
857         }
858     }
859 
860     int pstart = beg;
861 
862     // extract name
863     end = beg + 1;
864     while (end < len  &&  !Character.isSpace(buf[end]) &&
865            buf[end] != '='  &&  buf[end] != ',')
866         end++;
867 
868     // hack to deal with schemes which use cookies in challenge
869     if (first  &&
870         (end == len   ||  buf[end] == '='  &&
871         (end+1 == len  ||  (buf[end+1] == '='  &&  end+2 == len))))
872     {
873         curr.cookie = challenge.substring(beg, len);
874         beg = len;
875         break;
876     }
877 
878     String param_name = challenge.substring(beg, end),
879            param_value;
880 
881     beg = Util.skipSpace(buf, end);  // find "=" or ","
882 
883     if (beg < len  &&  buf[beg] != '='  &&  buf[beg] != ',')
884     {      // It's not a param, but another challenge
885         beg = pstart;
886         break;
887     }
888 
889 
890     if (buf[beg] == '=')    // we have a value
891     {
892         beg = Util.skipSpace(buf, beg+1);
893         if (beg == len)
894       throw new ProtocolException("Bad Authentication header "
895                 + "format: " + challenge +
896                 "\nUnexpected EOL after token" +
897                 " at position " + (end-1));
898         if (buf[beg] != '"')  // it's a token
899         {
900       end = Util.skipToken(buf, beg);
901       if (end == beg)
902           throw new ProtocolException("Bad Authentication header "
903         + "format: " + challenge + "\nToken expected at " +
904         "position " + beg);
905       param_value = challenge.substring(beg, end);
906         }
907         else      // it's a quoted-string
908         {
909       end = beg++;
910       do
911           end = challenge.indexOf('"', end+1);
912       while (end != -1  &&  challenge.charAt(end-1) == '\\');
913       if (end == -1)
914           throw new ProtocolException("Bad Authentication header "
915         + "format: " + challenge + "\nClosing <\"> for "
916         + "quoted-string starting at position " + beg
917         + " not found");
918       param_value =
919           Util.dequoteString(challenge.substring(beg, end));
920       end++;
921         }
922     }
923     else        // this is not strictly allowed
924         param_value = null;
925 
926     if (param_name.equalsIgnoreCase("realm"))
927         curr.realm = param_value;
928     else
929         params.addElement(new NVPair(param_name, param_value));
930 
931     first = false;
932       }
933 
934       if (!params.isEmpty())
935       {
936     curr.auth_params = new NVPair[params.size()];
937     params.copyInto(curr.auth_params);
938       }
939 
940       if (curr.realm == null)
941     /* Can't do this if we're supposed to allow for broken schemes
942      * such as NTLM, Kerberos, and PEM.
943      *
944     throw new ProtocolException("Bad Authentication header "
945         + "format: " + challenge + "\nNo realm value found");
946      */
947     curr.realm = "";
948 
949       auth_arr = Util.resizeArray(auth_arr, auth_arr.length+1);
950       auth_arr[auth_arr.length-1] = curr;
951   }
952 
953   return auth_arr;
954     }
955 
956 
957     // Instance Methods
958 
959     /**
960      * Get the host.
961      *
962      * @return a string containing the host name.
963      */
964     public final String getHost()
965     {
966   return host;
967     }
968 
969 
970     /**
971      * Get the port.
972      *
973      * @return an int containing the port number.
974      */
975     public final int getPort()
976     {
977   return port;
978     }
979 
980 
981     /**
982      * Get the scheme.
983      *
984      * @return a string containing the scheme.
985      */
986     public final String getScheme()
987     {
988   return scheme;
989     }
990 
991 
992     /**
993      * Get the realm.
994      *
995      * @return a string containing the realm.
996      */
997     public final String getRealm()
998     {
999   return realm;
1000    }
1001
1002
1003    /**
1004     * Get the cookie
1005     *
1006     * @return the cookie String
1007     * @since V0.3-1
1008     */
1009    public final String getCookie()
1010    {
1011  return cookie;
1012    }
1013
1014
1015    /**
1016     * Set the cookie
1017     *
1018     * @param cookie the new cookie
1019     * @since V0.3-1
1020     */
1021    public final void setCookie(String cookie)
1022    {
1023  this.cookie = cookie;
1024    }
1025
1026
1027    /**
1028     * Get the authentication parameters.
1029     *
1030     * @return an array of name/value pairs.
1031     */
1032    public final NVPair[] getParams()
1033    {
1034  return Util.resizeArray(auth_params, auth_params.length);
1035    }
1036
1037
1038    /**
1039     * Set the authentication parameters.
1040     *
1041     * @param an array of name/value pairs.
1042     */
1043    public final void setParams(NVPair[] params)
1044    {
1045  if (params != null)
1046      auth_params = Util.resizeArray(params, params.length);
1047  else
1048      auth_params = new NVPair[0];
1049    }
1050
1051
1052    /**
1053     * Get the extra info.
1054     *
1055     * @return the extra_info object
1056     */
1057    public final Object getExtraInfo()
1058    {
1059  return extra_info;
1060    }
1061
1062
1063    /**
1064     * Set the extra info.
1065     *
1066     * @param info the extra info
1067     */
1068    public final void setExtraInfo(Object info)
1069    {
1070  extra_info = info;
1071    }
1072
1073
1074    /**
1075     * Constructs a string containing the authorization info. The format
1076     * is that of the http Authorization header.
1077     *
1078     * @return a String containing all info.
1079     */
1080    public String toString()
1081    {
1082  StringBuffer field = new StringBuffer(100);
1083
1084  field.append(scheme);
1085  field.append(" ");
1086
1087  if (cookie != null)
1088  {
1089      field.append(cookie);
1090  }
1091  else
1092  {
1093      if (realm.length() > 0)
1094      {
1095    field.append("realm=\"");
1096    field.append(Util.quoteString(realm, "\\\""));
1097    field.append('"');
1098      }
1099
1100      for (int idx=0; idx<auth_params.length; idx++)
1101      {
1102    field.append(',');
1103    field.append(auth_params[idx].getName());
1104    field.append("=\"");
1105    field.append(
1106        Util.quoteString(auth_params[idx].getValue(), "\\\""));
1107    field.append('"');
1108      }
1109  }
1110
1111  return field.toString();
1112    }
1113
1114
1115    /**
1116     * Produces a hash code based on host, scheme and realm. Port is not
1117     * included for simplicity (and because it probably won't make much
1118     * difference). Used in the AuthorizationInfo.AuthList hash table.
1119     *
1120     * @return the hash code
1121     */
1122    public int hashCode()
1123    {
1124  return (host+scheme.toLowerCase()+realm).hashCode();
1125    }
1126
1127    /**
1128     * Two AuthorizationInfos are considered equal if their host, port,
1129     * scheme and realm match. Used in the AuthorizationInfo.AuthList hash
1130     * table.
1131     *
1132     * @param obj another AuthorizationInfo against which this one is
1133     *            to be compared.
1134     * @return true if they match in the above mentioned fields; false
1135     *              otherwise.
1136     */
1137    public boolean equals(Object obj)
1138    {
1139  if ((obj != null)  &&  (obj instanceof AuthorizationInfo))
1140  {
1141      AuthorizationInfo auth = (AuthorizationInfo) obj;
1142      if (host.equals(auth.host)  &&
1143    (port == auth.port)  &&
1144    scheme.equalsIgnoreCase(auth.scheme)  &&
1145    realm.equals(auth.realm))
1146        return true;
1147  }
1148  return false;
1149    }
1150
1151
1152    /**
1153     * @return a clone of this AuthorizationInfo using a deep copy
1154     */
1155    public Object clone()
1156    {
1157  AuthorizationInfo ai;
1158  try
1159  {
1160      ai = (AuthorizationInfo) super.clone();
1161      ai.auth_params = Util.resizeArray(auth_params, auth_params.length);
1162      try
1163      {
1164    // ai.extra_info  = extra_info.clone();
1165    ai.extra_info = extra_info.getClass().getMethod("clone", null).
1166        invoke(extra_info, null);
1167      }
1168      catch (Throwable t)
1169    { }
1170      ai.paths = new String[paths.length];
1171      System.arraycopy(paths, 0, ai.paths, 0, paths.length);
1172  }
1173  catch (CloneNotSupportedException cnse)
1174      { throw new InternalError(cnse.toString()); /* shouldn't happen */ }
1175
1176  return ai;
1177    }
1178}
1179