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