View Javadoc

1   /*
2    * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/cookie/CookieSpecBase.java,v 1.28 2004/11/06 19:15:42 mbecke Exp $
3    * $Revision: 7109 $
4    * $Date: 2011-03-28 23:42:58 +0000 (Mon, 28 Mar 2011) $
5    *
6    * ====================================================================
7    *
8    *  Copyright 2002-2004 The Apache Software Foundation
9    *
10   *  Licensed under the Apache License, Version 2.0 (the "License");
11   *  you may not use this file except in compliance with the License.
12   *  You may obtain a copy of the License at
13   *
14   *      http://www.apache.org/licenses/LICENSE-2.0
15   *
16   *  Unless required by applicable law or agreed to in writing, software
17   *  distributed under the License is distributed on an "AS IS" BASIS,
18   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19   *  See the License for the specific language governing permissions and
20   *  limitations under the License.
21   * ====================================================================
22   *
23   * This software consists of voluntary contributions made by many
24   * individuals on behalf of the Apache Software Foundation.  For more
25   * information on the Apache Software Foundation, please see
26   * <http://www.apache.org/>.
27   *
28   */ 
29  
30  package org.apache.commons.httpclient.cookie;
31  
32  import java.util.Collection;
33  import java.util.Date;
34  import java.util.Iterator; // <- IA/HERITRIX CHANGE
35  import java.util.LinkedList;
36  import java.util.List;
37  import java.util.SortedMap; // <- IA/HERITRIX CHANGE
38  
39  import org.apache.commons.httpclient.Cookie;
40  import org.apache.commons.httpclient.Header;
41  import org.apache.commons.httpclient.HeaderElement;
42  import org.apache.commons.httpclient.NameValuePair;
43  import org.apache.commons.httpclient.util.DateParseException;
44  import org.apache.commons.httpclient.util.DateUtil;
45  import org.apache.commons.logging.Log;
46  import org.apache.commons.logging.LogFactory;
47  
48  import com.google.common.net.InternetDomainName; // <- IA/HERITRIX CHANGE
49  import com.sleepycat.collections.StoredIterator; // <- IA/HERITRIX CHANGE
50  
51  /***
52   * 
53   * Cookie management functions shared by all specification.
54   *
55   * @author  B.C. Holmes
56   * @author <a href="mailto:jericho@thinkfree.com">Park, Sung-Gu</a>
57   * @author <a href="mailto:dsale@us.britannica.com">Doug Sale</a>
58   * @author Rod Waldhoff
59   * @author dIon Gillard
60   * @author Sean C. Sullivan
61   * @author <a href="mailto:JEvans@Cyveillance.com">John Evans</a>
62   * @author Marc A. Saegesser
63   * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
64   * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
65   * 
66   * @since 2.0 
67   */
68  @SuppressWarnings("unchecked") // <- IA/HERITRIX CHANGE
69  public class CookieSpecBase implements CookieSpec {
70      
71      /*** Log object */
72      protected static final Log LOG = LogFactory.getLog(CookieSpec.class);
73  
74      /*** Valid date patterns */
75      private Collection datepatterns = null;
76      
77      /*** Default constructor */
78      public CookieSpecBase() {
79          super();
80      }
81  
82  
83      /***
84        * Parses the Set-Cookie value into an array of <tt>Cookie</tt>s.
85        *
86        * <P>The syntax for the Set-Cookie response header is:
87        *
88        * <PRE>
89        * set-cookie      =    "Set-Cookie:" cookies
90        * cookies         =    1#cookie
91        * cookie          =    NAME "=" VALUE * (";" cookie-av)
92        * NAME            =    attr
93        * VALUE           =    value
94        * cookie-av       =    "Comment" "=" value
95        *                 |    "Domain" "=" value
96        *                 |    "Max-Age" "=" value
97        *                 |    "Path" "=" value
98        *                 |    "Secure"
99        *                 |    "Version" "=" 1*DIGIT
100       * </PRE>
101       *
102       * @param host the host from which the <tt>Set-Cookie</tt> value was
103       * received
104       * @param port the port from which the <tt>Set-Cookie</tt> value was
105       * received
106       * @param path the path from which the <tt>Set-Cookie</tt> value was
107       * received
108       * @param secure <tt>true</tt> when the <tt>Set-Cookie</tt> value was
109       * received over secure conection
110       * @param header the <tt>Set-Cookie</tt> received from the server
111       * @return an array of <tt>Cookie</tt>s parsed from the Set-Cookie value
112       * @throws MalformedCookieException if an exception occurs during parsing
113       */
114     public Cookie[] parse(String host, int port, String path, 
115         boolean secure, final String header) 
116         throws MalformedCookieException {
117             
118         LOG.trace("enter CookieSpecBase.parse(" 
119             + "String, port, path, boolean, Header)");
120 
121         if (host == null) {
122             throw new IllegalArgumentException(
123                 "Host of origin may not be null");
124         }
125         if (host.trim().equals("")) {
126             throw new IllegalArgumentException(
127                 "Host of origin may not be blank");
128         }
129         if (port < 0) {
130             throw new IllegalArgumentException("Invalid port: " + port);
131         }
132         if (path == null) {
133             throw new IllegalArgumentException(
134                 "Path of origin may not be null.");
135         }
136         if (header == null) {
137             throw new IllegalArgumentException("Header may not be null.");
138         }
139 
140         if (path.trim().equals("")) {
141             path = PATH_DELIM;
142         }
143         host = host.toLowerCase();
144 
145         String defaultPath = path;    
146         int lastSlashIndex = defaultPath.lastIndexOf(PATH_DELIM);
147         if (lastSlashIndex >= 0) {
148             if (lastSlashIndex == 0) {
149                 //Do not remove the very first slash
150                 lastSlashIndex = 1;
151             }
152             defaultPath = defaultPath.substring(0, lastSlashIndex);
153         }
154 
155         HeaderElement[] headerElements = null;
156 
157         boolean isNetscapeCookie = false; 
158         int i1 = header.toLowerCase().indexOf("expires=");
159         if (i1 != -1) {
160             i1 += "expires=".length();
161             int i2 = header.indexOf(";", i1);
162             if (i2 == -1) {
163                 i2 = header.length(); 
164             }
165             try {
166                 DateUtil.parseDate(header.substring(i1, i2), this.datepatterns);
167                 isNetscapeCookie = true; 
168             } catch (DateParseException e) {
169                 // Does not look like a valid expiry date
170             }
171         }
172         if (isNetscapeCookie) {
173             headerElements = new HeaderElement[] {
174                     new HeaderElement(header.toCharArray())
175             };
176         } else {
177             headerElements = HeaderElement.parseElements(header.toCharArray());
178         }
179         
180         Cookie[] cookies = new Cookie[headerElements.length];
181 
182         for (int i = 0; i < headerElements.length; i++) {
183 
184             HeaderElement headerelement = headerElements[i];
185             Cookie cookie = null;
186             try {
187                 cookie = new Cookie(host,
188                                     headerelement.getName(),
189                                     headerelement.getValue(),
190                                     defaultPath, 
191                                     null,
192                                     false);
193             } catch (IllegalArgumentException e) {
194                 throw new MalformedCookieException(e.getMessage()); 
195             }
196             // cycle through the parameters
197             NameValuePair[] parameters = headerelement.getParameters();
198             // could be null. In case only a header element and no parameters.
199             if (parameters != null) {
200 
201                 for (int j = 0; j < parameters.length; j++) {
202                     parseAttribute(parameters[j], cookie);
203                 }
204             }
205             cookies[i] = cookie;
206         }
207         return cookies;
208     }
209 
210 
211     /***
212       * Parse the <tt>"Set-Cookie"</tt> {@link Header} into an array of {@link
213       * Cookie}s.
214       *
215       * <P>The syntax for the Set-Cookie response header is:
216       *
217       * <PRE>
218       * set-cookie      =    "Set-Cookie:" cookies
219       * cookies         =    1#cookie
220       * cookie          =    NAME "=" VALUE * (";" cookie-av)
221       * NAME            =    attr
222       * VALUE           =    value
223       * cookie-av       =    "Comment" "=" value
224       *                 |    "Domain" "=" value
225       *                 |    "Max-Age" "=" value
226       *                 |    "Path" "=" value
227       *                 |    "Secure"
228       *                 |    "Version" "=" 1*DIGIT
229       * </PRE>
230       *
231       * @param host the host from which the <tt>Set-Cookie</tt> header was
232       * received
233       * @param port the port from which the <tt>Set-Cookie</tt> header was
234       * received
235       * @param path the path from which the <tt>Set-Cookie</tt> header was
236       * received
237       * @param secure <tt>true</tt> when the <tt>Set-Cookie</tt> header was
238       * received over secure conection
239       * @param header the <tt>Set-Cookie</tt> received from the server
240       * @return an array of <tt>Cookie</tt>s parsed from the <tt>"Set-Cookie"
241       * </tt> header
242       * @throws MalformedCookieException if an exception occurs during parsing
243       */
244     public Cookie[] parse(
245         String host, int port, String path, boolean secure, final Header header)
246         throws MalformedCookieException {
247             
248         LOG.trace("enter CookieSpecBase.parse("
249             + "String, port, path, boolean, String)");
250         if (header == null) {
251             throw new IllegalArgumentException("Header may not be null.");
252         }
253         return parse(host, port, path, secure, header.getValue());
254     }
255 
256 
257     /***
258       * Parse the cookie attribute and update the corresponsing {@link Cookie}
259       * properties.
260       *
261       * @param attribute {@link HeaderElement} cookie attribute from the
262       * <tt>Set- Cookie</tt>
263       * @param cookie {@link Cookie} to be updated
264       * @throws MalformedCookieException if an exception occurs during parsing
265       */
266 
267     public void parseAttribute(
268         final NameValuePair attribute, final Cookie cookie)
269         throws MalformedCookieException {
270             
271         if (attribute == null) {
272             throw new IllegalArgumentException("Attribute may not be null.");
273         }
274         if (cookie == null) {
275             throw new IllegalArgumentException("Cookie may not be null.");
276         }
277         final String paramName = attribute.getName().toLowerCase();
278         String paramValue = attribute.getValue();
279 
280         if (paramName.equals("path")) {
281 
282             if ((paramValue == null) || (paramValue.trim().equals(""))) {
283                 paramValue = "/";
284             }
285             cookie.setPath(paramValue);
286             cookie.setPathAttributeSpecified(true);
287 
288         } else if (paramName.equals("domain")) {
289 
290             if (paramValue == null) {
291                 throw new MalformedCookieException(
292                     "Missing value for domain attribute");
293             }
294             if (paramValue.trim().equals("")) {
295                 throw new MalformedCookieException(
296                     "Blank value for domain attribute");
297             }
298             cookie.setDomain(paramValue);
299             cookie.setDomainAttributeSpecified(true);
300 
301         } else if (paramName.equals("max-age")) {
302 
303             if (paramValue == null) {
304                 throw new MalformedCookieException(
305                     "Missing value for max-age attribute");
306             }
307             int age;
308             try {
309                 age = Integer.parseInt(paramValue);
310             } catch (NumberFormatException e) {
311                 throw new MalformedCookieException ("Invalid max-age "
312                     + "attribute: " + e.getMessage());
313             }
314             cookie.setExpiryDate(
315                 new Date(System.currentTimeMillis() + age * 1000L));
316 
317         } else if (paramName.equals("secure")) {
318 
319             cookie.setSecure(true);
320 
321         } else if (paramName.equals("comment")) {
322 
323             cookie.setComment(paramValue);
324 
325         } else if (paramName.equals("expires")) {
326 
327             if (paramValue == null) {
328                 throw new MalformedCookieException(
329                     "Missing value for expires attribute");
330             }
331 
332             try {
333                 cookie.setExpiryDate(DateUtil.parseDate(paramValue, this.datepatterns));
334             } catch (DateParseException dpe) {
335                 LOG.debug("Error parsing cookie date", dpe);
336                 throw new MalformedCookieException(
337                     "Unable to parse expiration date parameter: " 
338                     + paramValue);
339             }
340         } else {
341             if (LOG.isDebugEnabled()) {
342                 LOG.debug("Unrecognized cookie attribute: " 
343                     + attribute.toString());
344             }
345         }
346     }
347 
348     
349     public Collection getValidDateFormats() {
350         return this.datepatterns;
351     }
352 
353     public void setValidDateFormats(final Collection datepatterns) {
354         this.datepatterns = datepatterns;
355     }
356 
357     /***
358       * Performs most common {@link Cookie} validation
359       *
360       * @param host the host from which the {@link Cookie} was received
361       * @param port the port from which the {@link Cookie} was received
362       * @param path the path from which the {@link Cookie} was received
363       * @param secure <tt>true</tt> when the {@link Cookie} was received using a
364       * secure connection
365       * @param cookie The cookie to validate.
366       * @throws MalformedCookieException if an exception occurs during
367       * validation
368       */
369     
370     public void validate(String host, int port, String path, 
371         boolean secure, final Cookie cookie) 
372         throws MalformedCookieException {
373             
374         LOG.trace("enter CookieSpecBase.validate("
375             + "String, port, path, boolean, Cookie)");
376         if (host == null) {
377             throw new IllegalArgumentException(
378                 "Host of origin may not be null");
379         }
380         if (host.trim().equals("")) {
381             throw new IllegalArgumentException(
382                 "Host of origin may not be blank");
383         }
384         if (port < 0) {
385             throw new IllegalArgumentException("Invalid port: " + port);
386         }
387         if (path == null) {
388             throw new IllegalArgumentException(
389                 "Path of origin may not be null.");
390         }
391         if (path.trim().equals("")) {
392             path = PATH_DELIM;
393         }
394         host = host.toLowerCase();
395         // check version
396         if (cookie.getVersion() < 0) {
397             throw new MalformedCookieException ("Illegal version number " 
398                 + cookie.getValue());
399         }
400 
401         // security check... we musn't allow the server to give us an
402         // invalid domain scope
403 
404         // Validate the cookies domain attribute.  NOTE:  Domains without 
405         // any dots are allowed to support hosts on private LANs that don't 
406         // have DNS names.  Since they have no dots, to domain-match the 
407         // request-host and domain must be identical for the cookie to sent 
408         // back to the origin-server.
409         if (host.indexOf(".") >= 0) {
410             // Not required to have at least two dots.  RFC 2965.
411             // A Set-Cookie2 with Domain=ajax.com will be accepted.
412 
413             // domain must match host
414             if (!host.endsWith(cookie.getDomain())) {
415                 String s = cookie.getDomain();
416                 if (s.startsWith(".")) {
417                     s = s.substring(1, s.length());
418                 }
419                 if (!host.equals(s)) { 
420                     throw new MalformedCookieException(
421                         "Illegal domain attribute \"" + cookie.getDomain() 
422                         + "\". Domain of origin: \"" + host + "\"");
423                 }
424             } 
425             // BEGIN IA/HERITRIX ADDITION 
426             else {
427                 // requested domain is suffix of origin host; now make sure
428                 // it's not a public-suffix
429                 String requestedDomain = cookie.getDomain(); 
430                 if(requestedDomain.startsWith(".")) {
431                     requestedDomain = requestedDomain.substring(1);
432                 }
433                 try {
434                     if((InternetDomainName.fromLenient(requestedDomain)).isPublicSuffix()) {
435                         throw new MalformedCookieException(
436                                 "Illegal public-suffix domain attribute \"" + cookie.getDomain() 
437                                 + "\". Domain of origin: \"" + host + "\"");
438                     }  
439                 } catch (IllegalArgumentException e) {
440                     // TODO: consider if this means cookie should be invalid
441                 }
442             }
443            // END IA/HERITRIX ADDITION 
444         } else {
445             if (!host.equals(cookie.getDomain())) {
446                 throw new MalformedCookieException(
447                     "Illegal domain attribute \"" + cookie.getDomain() 
448                     + "\". Domain of origin: \"" + host + "\"");
449             }
450         }
451 
452         // another security check... we musn't allow the server to give us a
453         // cookie that doesn't match this path
454 
455         if (!path.startsWith(cookie.getPath())) {
456             throw new MalformedCookieException(
457                 "Illegal path attribute \"" + cookie.getPath() 
458                 + "\". Path of origin: \"" + path + "\"");
459         }
460     }
461 
462 
463     /***
464      * Return <tt>true</tt> if the cookie should be submitted with a request
465      * with given attributes, <tt>false</tt> otherwise.
466      * @param host the host to which the request is being submitted
467      * @param port the port to which the request is being submitted (ignored)
468      * @param path the path to which the request is being submitted
469      * @param secure <tt>true</tt> if the request is using a secure connection
470      * @param cookie {@link Cookie} to be matched
471      * @return true if the cookie matches the criterium
472      */
473 
474     public boolean match(String host, int port, String path, 
475         boolean secure, final Cookie cookie) {
476             
477         LOG.trace("enter CookieSpecBase.match("
478             + "String, int, String, boolean, Cookie");
479             
480         if (host == null) {
481             throw new IllegalArgumentException(
482                 "Host of origin may not be null");
483         }
484         if (host.trim().equals("")) {
485             throw new IllegalArgumentException(
486                 "Host of origin may not be blank");
487         }
488         if (port < 0) {
489             throw new IllegalArgumentException("Invalid port: " + port);
490         }
491         if (path == null) {
492             throw new IllegalArgumentException(
493                 "Path of origin may not be null.");
494         }
495         if (cookie == null) {
496             throw new IllegalArgumentException("Cookie may not be null");
497         }
498         if (path.trim().equals("")) {
499             path = PATH_DELIM;
500         }
501         host = host.toLowerCase();
502         if (cookie.getDomain() == null) {
503             LOG.warn("Invalid cookie state: domain not specified");
504             return false;
505         }
506         if (cookie.getPath() == null) {
507             LOG.warn("Invalid cookie state: path not specified");
508             return false;
509         }
510         
511         return
512             // only add the cookie if it hasn't yet expired 
513             (cookie.getExpiryDate() == null 
514                 || cookie.getExpiryDate().after(new Date()))
515             // and the domain pattern matches 
516             && (domainMatch(host, cookie.getDomain()))
517             // and the path is null or matching
518             && (pathMatch(path, cookie.getPath()))
519             // and if the secure flag is set, only if the request is 
520             // actually secure 
521             && (cookie.getSecure() ? secure : true);      
522     }
523 
524     /***
525      * Performs domain-match as implemented in common browsers.
526      * @param host The target host.
527      * @param domain The cookie domain attribute.
528      * @return true if the specified host matches the given domain.
529      */
530     public boolean domainMatch(final String host, String domain) {
531         if (host.equals(domain)) {
532             return true;
533         }
534         if (!domain.startsWith(".")) {
535             domain = "." + domain;
536         }
537         return host.endsWith(domain) || host.equals(domain.substring(1));
538     }
539 
540     /***
541      * Performs path-match as implemented in common browsers.
542      * @param path The target path.
543      * @param topmostPath The cookie path attribute.
544      * @return true if the paths match
545      */
546     public boolean pathMatch(final String path, final String topmostPath) {
547         boolean match = path.startsWith (topmostPath);
548         // if there is a match and these values are not exactly the same we have
549         // to make sure we're not matcing "/foobar" and "/foo"
550         if (match && path.length() != topmostPath.length()) {
551             if (!topmostPath.endsWith(PATH_DELIM)) {
552                 match = (path.charAt(topmostPath.length()) == PATH_DELIM_CHAR);
553             }
554         }
555         return match;
556     }
557 
558     /***
559      * Return an array of {@link Cookie}s that should be submitted with a
560      * request with given attributes, <tt>false</tt> otherwise.
561      * @param host the host to which the request is being submitted
562      * @param port the port to which the request is being submitted (currently
563      * ignored)
564      * @param path the path to which the request is being submitted
565      * @param secure <tt>true</tt> if the request is using a secure protocol
566      * @param cookies an array of <tt>Cookie</tt>s to be matched
567      * @return an array of <tt>Cookie</tt>s matching the criterium
568      * 
569 // BEGIN IA/HERITRIX CHANGES
570      * @deprecated use match(String, int, String, boolean, SortedMap)
571 // END IA/HERITRIX CHANGES
572      */
573 
574     public Cookie[] match(String host, int port, String path, 
575         boolean secure, final Cookie cookies[]) {
576             
577         LOG.trace("enter CookieSpecBase.match("
578             + "String, int, String, boolean, Cookie[])");
579 
580         if (cookies == null) {
581             return null;
582         }
583         List matching = new LinkedList();
584         for (int i = 0; i < cookies.length; i++) {
585             if (match(host, port, path, secure, cookies[i])) {
586                 addInPathOrder(matching, cookies[i]);
587             }
588         }
589         return (Cookie[]) matching.toArray(new Cookie[matching.size()]);
590     }
591 
592 //  BEGIN IA/HERITRIX CHANGES
593     /***
594      * Return an array of {@link Cookie}s that should be submitted with a
595      * request with given attributes, <tt>false</tt> otherwise. 
596      * 
597      * If the SortedMap comes from an HttpState and is not itself
598      * thread-safe, it may be necessary to synchronize on the HttpState
599      * instance to protect against concurrent modification. 
600      *
601      * @param host the host to which the request is being submitted
602      * @param port the port to which the request is being submitted (currently
603      * ignored)
604      * @param path the path to which the request is being submitted
605      * @param secure <tt>true</tt> if the request is using a secure protocol
606      * @param cookies SortedMap of <tt>Cookie</tt>s to be matched
607      * @return an array of <tt>Cookie</tt>s matching the criterium
608      */
609 
610     public Cookie[] match(String host, int port, String path, 
611         boolean secure, final SortedMap cookies) {
612             
613         LOG.trace("enter CookieSpecBase.match("
614            + "String, int, String, boolean, SortedMap)");
615 
616         // TODO: skip meaningless 'narrowing' when host is a numeric IP
617         // (harmless in the meantime)
618         
619         if (cookies == null) {
620             return null;
621         }
622         List matching = new LinkedList();
623         InternetDomainName domain; 
624         try {
625             domain = InternetDomainName.fromLenient(host); 
626         } catch(IllegalArgumentException e) {
627             domain = null; 
628         }
629         
630         String candidate = (domain!=null) ? domain.toString() : host;
631         while(candidate!=null) {
632             Iterator iter = cookies.subMap(candidate,
633                     candidate + Cookie.DOMAIN_OVERBOUNDS).values().iterator();
634             while (iter.hasNext()) {
635                 Cookie cookie = (Cookie) (iter.next());
636                 if (match(host, port, path, secure, cookie)) {
637                     addInPathOrder(matching, cookie);
638                 }
639             }
640             StoredIterator.close(iter);
641             if(domain!=null && domain.isUnderPublicSuffix()) {
642                 domain = domain.parent(); 
643                 candidate = domain.toString(); 
644             } else {
645                 candidate = null;
646             }
647         }
648 
649         return (Cookie[]) matching.toArray(new Cookie[matching.size()]); 
650     }
651 //  END IA/HERITRIX CHANGES
652     
653     /***
654      * Adds the given cookie into the given list in descending path order. That
655      * is, more specific path to least specific paths.  This may not be the
656      * fastest algorythm, but it'll work OK for the small number of cookies
657      * we're generally dealing with.
658      *
659      * @param list - the list to add the cookie to
660      * @param addCookie - the Cookie to add to list
661      */
662     private static void addInPathOrder(List list, Cookie addCookie) {
663         int i = 0;
664 
665         for (i = 0; i < list.size(); i++) {
666             Cookie c = (Cookie) list.get(i);
667             if (addCookie.compare(addCookie, c) > 0) {
668                 break;
669             }
670         }
671         list.add(i, addCookie);
672     }
673 
674     /***
675      * Return a string suitable for sending in a <tt>"Cookie"</tt> header
676      * @param cookie a {@link Cookie} to be formatted as string
677      * @return a string suitable for sending in a <tt>"Cookie"</tt> header.
678      */
679     public String formatCookie(Cookie cookie) {
680         LOG.trace("enter CookieSpecBase.formatCookie(Cookie)");
681         if (cookie == null) {
682             throw new IllegalArgumentException("Cookie may not be null");
683         }
684         StringBuffer buf = new StringBuffer();
685         buf.append(cookie.getName());
686         buf.append("=");
687         String s = cookie.getValue();
688         if (s != null) {
689             buf.append(s);
690         }
691         return buf.toString();
692     }
693 
694     /***
695      * Create a <tt>"Cookie"</tt> header value containing all {@link Cookie}s in
696      * <i>cookies</i> suitable for sending in a <tt>"Cookie"</tt> header
697      * @param cookies an array of {@link Cookie}s to be formatted
698      * @return a string suitable for sending in a Cookie header.
699      * @throws IllegalArgumentException if an input parameter is illegal
700      */
701 
702     public String formatCookies(Cookie[] cookies)
703       throws IllegalArgumentException {
704         LOG.trace("enter CookieSpecBase.formatCookies(Cookie[])");
705         if (cookies == null) {
706             throw new IllegalArgumentException("Cookie array may not be null");
707         }
708         if (cookies.length == 0) {
709             throw new IllegalArgumentException("Cookie array may not be empty");
710         }
711 
712         StringBuffer buffer = new StringBuffer();
713         for (int i = 0; i < cookies.length; i++) {
714             if (i > 0) {
715                 buffer.append("; ");
716             }
717             buffer.append(formatCookie(cookies[i]));
718         }
719         return buffer.toString();
720     }
721 
722 
723     /***
724      * Create a <tt>"Cookie"</tt> {@link Header} containing all {@link Cookie}s
725      * in <i>cookies</i>.
726      * @param cookies an array of {@link Cookie}s to be formatted as a <tt>"
727      * Cookie"</tt> header
728      * @return a <tt>"Cookie"</tt> {@link Header}.
729      */
730     public Header formatCookieHeader(Cookie[] cookies) {
731         LOG.trace("enter CookieSpecBase.formatCookieHeader(Cookie[])");
732         return new Header("Cookie", formatCookies(cookies));
733     }
734 
735 
736     /***
737      * Create a <tt>"Cookie"</tt> {@link Header} containing the {@link Cookie}.
738      * @param cookie <tt>Cookie</tt>s to be formatted as a <tt>Cookie</tt>
739      * header
740      * @return a Cookie header.
741      */
742     public Header formatCookieHeader(Cookie cookie) {
743         LOG.trace("enter CookieSpecBase.formatCookieHeader(Cookie)");
744         return new Header("Cookie", formatCookie(cookie));
745     }
746 
747 }