HttpCookie.java revision d138a32a96aef19d6ae3bd7ead3fbfef1a5f8217
1/* Licensed to the Apache Software Foundation (ASF) under one or more
2 * contributor license agreements.  See the NOTICE file distributed with
3 * this work for additional information regarding copyright ownership.
4 * The ASF licenses this file to You under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with
6 * the License.  You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package java.net;
18
19import java.util.ArrayList;
20import java.util.Date;
21import java.util.HashMap;
22import java.util.List;
23import java.util.Locale;
24import java.util.regex.Matcher;
25import java.util.regex.Pattern;
26
27import org.apache.harmony.luni.util.Msg;
28
29/**
30 * This class represents a http cookie, which indicates the status information
31 * between the client agent side and the server side. According to RFC, there
32 * are 3 http cookie specifications. This class is compatible with all the three
33 * forms. HttpCookie class can accept all these 3 forms of syntax.
34 *
35 * @since 1.6
36 */
37public final class HttpCookie implements Cloneable {
38
39    private abstract static class Setter {
40        boolean set;
41
42        Setter() {
43            set = false;
44        }
45
46        boolean isSet() {
47            return set;
48        }
49
50        void set(boolean isSet) {
51            set = isSet;
52        }
53
54        abstract void setValue(String value, HttpCookie cookie);
55
56        void validate(String value, HttpCookie cookie) {
57            if (cookie.getVersion() == 1 && value != null
58                    && value.contains(COMMA_STR)) {
59                throw new IllegalArgumentException();
60            }
61        }
62    }
63
64    private static final String DOT_STR = "."; //$NON-NLS-1$
65
66    private static final String LOCAL_STR = ".local"; //$NON-NLS-1$
67
68    private static final String QUOTE_STR = "\""; //$NON-NLS-1$
69
70    private static final String COMMA_STR = ","; //$NON-NLS-1$
71
72    private static Pattern HEAD_PATTERN = Pattern.compile("Set-Cookie2?:", //$NON-NLS-1$
73            Pattern.CASE_INSENSITIVE);
74
75    private static Pattern NAME_PATTERN = Pattern
76            .compile(
77                    "([^$=,\u0085\u2028\u2029][^,\n\t\r\r\n\u0085\u2028\u2029]*?)=([^;]*)(;)?", //$NON-NLS-1$
78                    Pattern.DOTALL | Pattern.CASE_INSENSITIVE);
79
80    private static Pattern ATTR_PATTERN0 = Pattern
81            .compile("([^;=]*)(?:=([^;]*))?"); //$NON-NLS-1$
82
83    private static Pattern ATTR_PATTERN1 = Pattern
84            .compile("(,?[^;=]*)(?:=([^;,]*))?((?=.))?"); //$NON-NLS-1$
85
86    private HashMap<String, Setter> attributeSet = new HashMap<String, Setter>();
87
88    /**
89     * A utility method used to check whether the host name is in a domain or
90     * not.
91     *
92     * @param domain
93     *            the domain to be checked against
94     * @param host
95     *            the host to be checked
96     * @return true if the host is in the domain, false otherwise
97     */
98    public static boolean domainMatches(String domain, String host) {
99        if (domain == null || host == null) {
100            return false;
101        }
102        String newDomain = domain.toLowerCase();
103        String newHost = host.toLowerCase();
104        return isValidDomain(newDomain) && effDomainMatches(newDomain, newHost)
105                && isValidHost(newDomain, newHost);
106    }
107
108    private static boolean effDomainMatches(String domain, String host) {
109        // calculate effective host name
110        String effHost = host.indexOf(DOT_STR) != -1 ? host
111                : (host + LOCAL_STR);
112
113        // Rule 2: domain and host are string-compare equal, or A = NB, B = .B'
114        // and N is a non-empty name string
115        boolean inDomain = domain.equals(effHost);
116        inDomain = inDomain
117                || (effHost.endsWith(domain)
118                        && effHost.length() > domain.length() && domain
119                        .startsWith(DOT_STR));
120        return inDomain;
121    }
122
123    private static boolean isCommaDelim(HttpCookie cookie) {
124        String value = cookie.getValue();
125        if (value.startsWith(QUOTE_STR) && value.endsWith(QUOTE_STR)) {
126            cookie.setValue(value.substring(1, value.length() - 1));
127            return false;
128        }
129        if (cookie.getVersion() == 1 && value.contains(COMMA_STR)) {
130            cookie.setValue(value.substring(0, value.indexOf(COMMA_STR)));
131            return true;
132        }
133        return false;
134    }
135
136    private static boolean isValidDomain(String domain) {
137        // Rule 1: The value for Domain contains embedded dots, or is .local
138        if (domain.length() <= 2) {
139            return false;
140        }
141        return domain.substring(1, domain.length() - 1).indexOf(DOT_STR) != -1
142                || domain.equals(LOCAL_STR);
143    }
144
145    private static boolean isValidHost(String domain, String host) {
146        // Rule 3: host does not end with domain, or the remainder does not
147        // contain "."
148        boolean matches = !host.endsWith(domain);
149        if (!matches) {
150            String hostSub = host.substring(0, host.length() - domain.length());
151            matches = hostSub.indexOf(DOT_STR) == -1;
152        }
153        return matches;
154    }
155
156    /**
157     * Constructs a cookie from a string. The string should comply with
158     * set-cookie or set-cookie2 header format as specified in RFC 2965. Since
159     * set-cookies2 syntax allows more than one cookie definitions in one
160     * header, the returned object is a list.
161     *
162     * @param header
163     *            a set-cookie or set-cookie2 header.
164     * @return a list of constructed cookies
165     * @throws IllegalArgumentException
166     *             if the string does not comply with cookie specification, or
167     *             the cookie name contains illegal characters, or reserved
168     *             tokens of cookie specification appears
169     * @throws NullPointerException
170     *             if header is null
171     */
172    public static List<HttpCookie> parse(String header) {
173        Matcher matcher = HEAD_PATTERN.matcher(header);
174        // Parse cookie name & value
175        List<HttpCookie> list = null;
176        HttpCookie cookie = null;
177        String headerString = header;
178        int version = 0;
179        // process set-cookie | set-cookie2 head
180        if (matcher.find()) {
181            String cookieHead = matcher.group();
182            if ("set-cookie2:".equalsIgnoreCase(cookieHead)) { //$NON-NLS-1$
183                version = 1;
184            }
185            headerString = header.substring(cookieHead.length());
186        }
187
188        // parse cookie name/value pair
189        matcher = NAME_PATTERN.matcher(headerString);
190        if (matcher.lookingAt()) {
191            list = new ArrayList<HttpCookie>();
192            cookie = new HttpCookie(matcher.group(1), matcher.group(2));
193            cookie.setVersion(version);
194
195            /*
196             * Comma is a delimiter in cookie spec 1.1. If find comma in version
197             * 1 cookie header, part of matched string need to be spitted out.
198             */
199            String nameGroup = matcher.group();
200            if (isCommaDelim(cookie)) {
201                headerString = headerString.substring(nameGroup
202                        .indexOf(COMMA_STR));
203            } else {
204                headerString = headerString.substring(nameGroup.length());
205            }
206            list.add(cookie);
207        } else {
208            throw new IllegalArgumentException();
209        }
210
211        // parse cookie headerString
212        while (!(headerString.length() == 0)) {
213            matcher = cookie.getVersion() == 1 ? ATTR_PATTERN1
214                    .matcher(headerString) : ATTR_PATTERN0
215                    .matcher(headerString);
216
217            if (matcher.lookingAt()) {
218                String attrName = matcher.group(1).trim();
219
220                // handle special situation like: <..>;;<..>
221                if (attrName.length() == 0) {
222                    headerString = headerString.substring(1);
223                    continue;
224                }
225
226                // If port is the attribute, then comma will not be used as a
227                // delimiter
228                if (attrName.equalsIgnoreCase("port") //$NON-NLS-1$
229                        || attrName.equalsIgnoreCase("expires")) { //$NON-NLS-1$
230                    int start = matcher.regionStart();
231                    matcher = ATTR_PATTERN0.matcher(headerString);
232                    matcher.region(start, headerString.length());
233                    matcher.lookingAt();
234                } else if (cookie.getVersion() == 1
235                        && attrName.startsWith(COMMA_STR)) {
236                    // If the last encountered token is comma, and the parsed
237                    // attribute is not port, then this attribute/value pair
238                    // ends.
239                    headerString = headerString.substring(1);
240                    matcher = NAME_PATTERN.matcher(headerString);
241                    if (matcher.lookingAt()) {
242                        cookie = new HttpCookie(matcher.group(1), matcher
243                                .group(2));
244                        list.add(cookie);
245                        headerString = headerString.substring(matcher.group()
246                                .length());
247                        continue;
248                    }
249                }
250
251                Setter setter = cookie.attributeSet.get(attrName.toLowerCase());
252                if (null == setter) {
253                    throw new IllegalArgumentException();
254                }
255                if (!setter.isSet()) {
256                    String attrValue = matcher.group(2);
257                    setter.validate(attrValue, cookie);
258                    setter.setValue(matcher.group(2), cookie);
259                }
260                headerString = headerString.substring(matcher.end());
261            }
262        }
263
264        return list;
265    }
266
267    private String comment;
268
269    private String commentURL;
270
271    private boolean discard;
272
273    private String domain;
274
275    private long maxAge = -1l;
276
277    private String name;
278
279    private String path;
280
281    private String portList;
282
283    private boolean secure;
284
285    private String value;
286
287    private int version = 1;
288
289    {
290        attributeSet.put("comment", new Setter() { //$NON-NLS-1$
291                    @Override
292                    void setValue(String value, HttpCookie cookie) {
293                        cookie.setComment(value);
294                        if (cookie.getComment() != null) {
295                            set(true);
296                        }
297                    }
298                });
299        attributeSet.put("commenturl", new Setter() { //$NON-NLS-1$
300                    @Override
301                    void setValue(String value, HttpCookie cookie) {
302                        cookie.setCommentURL(value);
303                        if (cookie.getCommentURL() != null) {
304                            set(true);
305                        }
306                    }
307                });
308        attributeSet.put("discard", new Setter() { //$NON-NLS-1$
309                    @Override
310                    void setValue(String value, HttpCookie cookie) {
311                        cookie.setDiscard(true);
312                        set(true);
313                    }
314                });
315        attributeSet.put("domain", new Setter() { //$NON-NLS-1$
316                    @Override
317                    void setValue(String value, HttpCookie cookie) {
318                        cookie.setDomain(value);
319                        if (cookie.getDomain() != null) {
320                            set(true);
321                        }
322                    }
323                });
324        attributeSet.put("max-age", new Setter() { //$NON-NLS-1$
325                    @Override
326                    void setValue(String value, HttpCookie cookie) {
327                        try {
328                            cookie.setMaxAge(Long.parseLong(value));
329                        } catch (NumberFormatException e) {
330                            throw new IllegalArgumentException(Msg.getString(
331                                    "KB001", "max-age")); //$NON-NLS-1$//$NON-NLS-2$
332                        }
333                        set(true);
334
335                        if (!attributeSet.get("version").isSet()) { //$NON-NLS-1$
336                            cookie.setVersion(1);
337                        }
338                    }
339                });
340
341        attributeSet.put("path", new Setter() { //$NON-NLS-1$
342                    @Override
343                    void setValue(String value, HttpCookie cookie) {
344                        cookie.setPath(value);
345                        if (cookie.getPath() != null) {
346                            set(true);
347                        }
348                    }
349                });
350        attributeSet.put("port", new Setter() { //$NON-NLS-1$
351                    @Override
352                    void setValue(String value, HttpCookie cookie) {
353                        cookie.setPortlist(value);
354                        if (cookie.getPortlist() != null) {
355                            set(true);
356                        }
357                    }
358
359                    @Override
360                    void validate(String v, HttpCookie cookie) {
361                        return;
362                    }
363                });
364        attributeSet.put("secure", new Setter() { //$NON-NLS-1$
365                    @Override
366                    void setValue(String value, HttpCookie cookie) {
367                        cookie.setSecure(true);
368                        set(true);
369                    }
370                });
371        attributeSet.put("version", new Setter() { //$NON-NLS-1$
372                    @Override
373                    void setValue(String value, HttpCookie cookie) {
374                        try {
375                            int v = Integer.parseInt(value);
376                            if (v > cookie.getVersion()) {
377                                cookie.setVersion(v);
378                            }
379                        } catch (NumberFormatException e) {
380                            throw new IllegalArgumentException(Msg.getString(
381                                    "KB001", "version"));//$NON-NLS-1$//$NON-NLS-2$
382                        }
383                        if (cookie.getVersion() != 0) {
384                            set(true);
385                        }
386                    }
387                });
388
389        attributeSet.put("expires", new Setter() { //$NON-NLS-1$
390                    @Override
391                    void setValue(String value, HttpCookie cookie) {
392                        cookie.setVersion(0);
393                        attributeSet.get("version").set(true); //$NON-NLS-1$
394                        if (!attributeSet.get("max-age").isSet()) { //$NON-NLS-1$
395                            attributeSet.get("max-age").set(true); //$NON-NLS-1$
396                            if (!"en".equalsIgnoreCase(Locale.getDefault() //$NON-NLS-1$
397                                    .getLanguage())) {
398                                cookie.setMaxAge(0);
399                                return;
400                            }
401                            try {
402                                cookie.setMaxAge((Date.parse(value) - System
403                                        .currentTimeMillis()) / 1000);
404                            } catch (IllegalArgumentException e) {
405                                cookie.setMaxAge(0);
406                            }
407                        }
408                    }
409
410                    @Override
411                    void validate(String v, HttpCookie cookie) {
412                        return;
413                    }
414                });
415    }
416
417    /**
418     * Initializes a cookie with the specified name and value.
419     *
420     * The name attribute can just contain ASCII characters, which is immutable
421     * after creation. Commas, white space and semicolons are not allowed. The $
422     * character is also not allowed to be the beginning of the name.
423     *
424     * The value attribute depends on what the server side is interested. The
425     * setValue method can be used to change it.
426     *
427     * RFC 2965 is the default cookie specification of this class. If one wants
428     * to change the version of the cookie, the setVersion method is available.
429     *
430     * @param name -
431     *            the specific name of the cookie
432     * @param value -
433     *            the specific value of the cookie
434     *
435     * @throws IllegalArgumentException -
436     *             if the name contains not-allowed or reserved characters
437     *
438     * @throws NullPointerException
439     *             if the value of name is null
440     */
441    public HttpCookie(String name, String value) {
442        String ntrim = name.trim(); // erase leading and trailing whitespaces
443        if (!isValidName(ntrim)) {
444            throw new IllegalArgumentException(Msg.getString("KB002")); //$NON-NLS-1$
445        }
446
447        this.name = ntrim;
448        this.value = value;
449    }
450
451    private void attrToString(StringBuilder builder, String attrName,
452            String attrValue) {
453        if (attrValue != null && builder != null) {
454            builder.append(";"); //$NON-NLS-1$
455            builder.append("$");//$NON-NLS-1$
456            builder.append(attrName);
457            builder.append("=\""); //$NON-NLS-1$
458            builder.append(attrValue);
459            builder.append(QUOTE_STR);
460        }
461    }
462
463    /**
464     * Answers a copy of this object.
465     *
466     * @return a copy of this cookie
467     */
468    @Override
469    public Object clone() {
470        try {
471            HttpCookie obj = (HttpCookie) super.clone();
472            return obj;
473        } catch (CloneNotSupportedException e) {
474            return null;
475        }
476    }
477
478    /**
479     * Answers whether two cookies are equal. Two cookies are equal if they have
480     * the same domain and name in a case-insensitive mode and path in a
481     * case-sensitive mode.
482     *
483     * @param obj
484     *            the object to be compared.
485     * @return true if two cookies equals, false otherwise
486     */
487    @Override
488    public boolean equals(Object obj) {
489        if (obj == this) {
490            return true;
491        }
492        if (obj instanceof HttpCookie) {
493            HttpCookie anotherCookie = (HttpCookie) obj;
494            if (name.equalsIgnoreCase(anotherCookie.getName())) {
495                String anotherDomain = anotherCookie.getDomain();
496                boolean equals = domain == null ? anotherDomain == null
497                        : domain.equalsIgnoreCase(anotherDomain);
498                if (equals) {
499                    String anotherPath = anotherCookie.getPath();
500                    return path == null ? anotherPath == null : path
501                            .equals(anotherPath);
502                }
503            }
504        }
505        return false;
506    }
507
508    /**
509     * Answers the value of comment attribute(specified in RFC 2965) of this
510     * cookie.
511     *
512     * @return the value of comment attribute
513     */
514    public String getComment() {
515        return comment;
516    }
517
518    /**
519     * Answers the value of commentURL attribute(specified in RFC 2965) of this
520     * cookie.
521     *
522     * @return the value of commentURL attribute
523     */
524    public String getCommentURL() {
525        return commentURL;
526    }
527
528    /**
529     * Answers the value of discard attribute(specified in RFC 2965) of this
530     * cookie.
531     *
532     * @return discard value of this cookie
533     */
534    public boolean getDiscard() {
535        return discard;
536    }
537
538    /**
539     * Answers the domain name for this cookie in the format specified in RFC
540     * 2965
541     *
542     * @return the domain value of this cookie
543     */
544    public String getDomain() {
545        return domain;
546    }
547
548    /**
549     * Returns the Max-Age value as specified in RFC 2965 of this cookie.
550     *
551     * @return the Max-Age value
552     */
553    public long getMaxAge() {
554        return maxAge;
555    }
556
557    /**
558     * Answers the name for this cookie.
559     *
560     * @return the name for this cookie
561     */
562    public String getName() {
563        return name;
564    }
565
566    /**
567     * Answers the path part of a request URL to which this cookie is returned.
568     * This cookie is visible to all subpaths.
569     *
570     * @return the path used to return the cookie
571     */
572    public String getPath() {
573        return path;
574    }
575
576    /**
577     * Answers the value of port attribute(specified in RFC 2965) of this
578     * cookie.
579     *
580     * @return port list of this cookie
581     */
582    public String getPortlist() {
583        return portList;
584    }
585
586    /**
587     * Answers true if the browser only sends cookies over a secure protocol.
588     * False if can send cookies through any protocols.
589     *
590     * @return true if sends cookies only through secure protocol, false
591     *         otherwise
592     */
593    public boolean getSecure() {
594        return secure;
595    }
596
597    /**
598     * Answers the value of this cookie.
599     *
600     * @return the value of this cookie
601     */
602    public String getValue() {
603        return value;
604    }
605
606    /**
607     * Get the version of this cookie
608     *
609     * @return 0 indicates the original Netscape cookie specification, while 1
610     *         indicates RFC 2965/2109 specification.
611     */
612    public int getVersion() {
613        return version;
614    }
615
616    /**
617     * Answers whether the cookie has expired.
618     *
619     * @return true is the cookie has expired, false otherwise
620     */
621    public boolean hasExpired() {
622        // -1 indicates the cookie will persist until browser shutdown
623        // so the cookie is not expired.
624        if (maxAge == -1l) {
625            return false;
626        }
627
628        boolean expired = false;
629        if (maxAge <= 0l) {
630            expired = true;
631        }
632        return expired;
633    }
634
635    /**
636     * Answers hash code of this http cookie. The result is calculated as below:
637     *
638     * getName().toLowerCase().hashCode() + getDomain().toLowerCase().hashCode() +
639     * getPath().hashCode()
640     *
641     * @return the hash code of this cookie
642     */
643    @Override
644    public int hashCode() {
645        int hashCode = name.toLowerCase().hashCode();
646        hashCode += domain == null ? 0 : domain.toLowerCase().hashCode();
647        hashCode += path == null ? 0 : path.hashCode();
648        return hashCode;
649    }
650
651    private boolean isValidName(String n) {
652        // name cannot be empty or begin with '$' or equals the reserved
653        // attributes (case-insensitive)
654        boolean isValid = !(n.length() == 0 || n.startsWith("$") || attributeSet.containsKey(n.toLowerCase())); //$NON-NLS-1$
655        if (isValid) {
656            for (int i = 0; i < n.length(); i++) {
657                char nameChar = n.charAt(i);
658                // name must be ASCII characters and cannot contain ';', ',' and
659                // whitespace
660                if (nameChar < 0
661                        || nameChar >= 127
662                        || nameChar == ';'
663                        || nameChar == ','
664                        || (Character.isWhitespace(nameChar) && nameChar != ' ')) {
665                    isValid = false;
666                    break;
667                }
668            }
669        }
670        return isValid;
671    }
672
673    /**
674     * Set the value of comment attribute(specified in RFC 2965) of this cookie.
675     *
676     * @param purpose
677     *            the comment value to be set
678     */
679    public void setComment(String purpose) {
680        comment = purpose;
681    }
682
683    /**
684     * Set the value of commentURL attribute(specified in RFC 2965) of this
685     * cookie.
686     *
687     * @param purpose
688     *            the value of commentURL attribute to be set
689     */
690    public void setCommentURL(String purpose) {
691        commentURL = purpose;
692    }
693
694    /**
695     * Set the value of discard attribute(specified in RFC 2965) of this cookie.
696     *
697     * @param discard
698     *            the value for discard attribute
699     */
700    public void setDiscard(boolean discard) {
701        this.discard = discard;
702    }
703
704    /**
705     * Set the domain value for this cookie. Browsers send the cookie to the
706     * domain specified by this value. The form of the domain is specified in
707     * RFC 2965.
708     *
709     * @param pattern
710     *            the domain pattern
711     */
712    public void setDomain(String pattern) {
713        domain = pattern == null ? null : pattern.toLowerCase();
714    }
715
716    /**
717     * Sets the Max-Age value as specified in RFC 2965 of this cookie to expire.
718     *
719     * @param expiry
720     *            the value used to set the Max-Age value of this cookie
721     */
722    public void setMaxAge(long expiry) {
723        maxAge = expiry;
724    }
725
726    /**
727     * Set the path to which this cookie is returned. This cookie is visible to
728     * all the pages under the path and all subpaths.
729     *
730     * @param path
731     *            the path to which this cookie is returned
732     */
733    public void setPath(String path) {
734        this.path = path;
735    }
736
737    /**
738     * Set the value of port attribute(specified in RFC 2965) of this cookie.
739     *
740     * @param ports
741     *            the value for port attribute
742     */
743    public void setPortlist(String ports) {
744        portList = ports;
745    }
746
747    /*
748     * Handle 2 special cases: 1. value is wrapped by a quotation 2. value
749     * contains comma
750     */
751
752    /**
753     * Tells the browser whether the cookies should be sent to server through
754     * secure protocols.
755     *
756     * @param flag
757     *            tells browser to send cookie to server only through secure
758     *            protocol if flag is true
759     */
760    public void setSecure(boolean flag) {
761        secure = flag;
762    }
763
764    /**
765     * Sets the value for this cookie after it has been instantiated. String
766     * newValue can be in BASE64 form. If the version of the cookie is 0,
767     * special value as: white space, brackets, parentheses, equals signs,
768     * commas, double quotes, slashes, question marks, at signs, colons, and
769     * semicolons are not recommended. Empty values may lead to different
770     * behavior on different browsers.
771     *
772     * @param newValue
773     *            the value for this cookie
774     */
775    public void setValue(String newValue) {
776        // FIXME: According to spec, version 0 cookie value does not allow many
777        // symbols. But RI does not implement it. Follow RI temporarily.
778        value = newValue;
779    }
780
781    /**
782     * Sets the version of the cookie. 0 indicates the original Netscape cookie
783     * specification, while 1 indicates RFC 2965/2109 specification.
784     *
785     * @param v
786     *            0 or 1 as stated above
787     * @throws IllegalArgumentException
788     *             if v is neither 0 nor 1
789     */
790    public void setVersion(int v) {
791        if (v != 0 && v != 1) {
792            throw new IllegalArgumentException(Msg.getString("KB003")); //$NON-NLS-1$
793        }
794        version = v;
795    }
796
797    /**
798     * Returns a string to represent the cookie. The format of string follows
799     * the cookie specification. The leading token "Cookie" is not included
800     *
801     * @return the string format of the cookie object
802     */
803    @Override
804    public String toString() {
805        StringBuilder cookieStr = new StringBuilder();
806        cookieStr.append(name);
807        cookieStr.append("="); //$NON-NLS-1$
808        if (version == 0) {
809            cookieStr.append(value);
810        } else if (version == 1) {
811            cookieStr.append(QUOTE_STR);
812            cookieStr.append(value);
813            cookieStr.append(QUOTE_STR);
814
815            attrToString(cookieStr, "Path", path); //$NON-NLS-1$
816            attrToString(cookieStr, "Domain", domain); //$NON-NLS-1$
817            attrToString(cookieStr, "Port", portList);//$NON-NLS-1$
818        }
819        return cookieStr.toString();
820    }
821}