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