1/*
2 * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/RFC2965Spec.java $
3 * $Revision: 653041 $
4 * $Date: 2008-05-03 03:39:28 -0700 (Sat, 03 May 2008) $
5 *
6 * ====================================================================
7 *
8 *  Licensed to the Apache Software Foundation (ASF) under one or more
9 *  contributor license agreements.  See the NOTICE file distributed with
10 *  this work for additional information regarding copyright ownership.
11 *  The ASF licenses this file to You under the Apache License, Version 2.0
12 *  (the "License"); you may not use this file except in compliance with
13 *  the License.  You may obtain a copy of the License at
14 *
15 *      http://www.apache.org/licenses/LICENSE-2.0
16 *
17 *  Unless required by applicable law or agreed to in writing, software
18 *  distributed under the License is distributed on an "AS IS" BASIS,
19 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 *  See the License for the specific language governing permissions and
21 *  limitations under the License.
22 * ====================================================================
23 *
24 * This software consists of voluntary contributions made by many
25 * individuals on behalf of the Apache Software Foundation.  For more
26 * information on the Apache Software Foundation, please see
27 * <http://www.apache.org/>.
28 *
29 */
30
31package org.apache.http.impl.cookie;
32
33import java.util.ArrayList;
34import java.util.HashMap;
35import java.util.List;
36import java.util.Locale;
37import java.util.Map;
38
39import org.apache.http.Header;
40import org.apache.http.HeaderElement;
41import org.apache.http.NameValuePair;
42import org.apache.http.cookie.ClientCookie;
43import org.apache.http.cookie.Cookie;
44import org.apache.http.cookie.CookieAttributeHandler;
45import org.apache.http.cookie.CookieOrigin;
46import org.apache.http.cookie.MalformedCookieException;
47import org.apache.http.cookie.SM;
48import org.apache.http.message.BufferedHeader;
49import org.apache.http.util.CharArrayBuffer;
50
51/**
52 * <p>RFC 2965 specific cookie management functions.</p>
53 *
54 * @author jain.samit@gmail.com (Samit Jain)
55 * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a>
56 *
57 * @since 3.1
58 */
59public class RFC2965Spec extends RFC2109Spec {
60
61    /**
62     * Default constructor
63     *
64     */
65    public RFC2965Spec() {
66        this(null, false);
67    }
68
69    public RFC2965Spec(final String[] datepatterns, boolean oneHeader) {
70        super(datepatterns, oneHeader);
71        registerAttribHandler(ClientCookie.DOMAIN_ATTR, new RFC2965DomainAttributeHandler());
72        registerAttribHandler(ClientCookie.PORT_ATTR, new RFC2965PortAttributeHandler());
73        registerAttribHandler(ClientCookie.COMMENTURL_ATTR, new RFC2965CommentUrlAttributeHandler());
74        registerAttribHandler(ClientCookie.DISCARD_ATTR, new RFC2965DiscardAttributeHandler());
75        registerAttribHandler(ClientCookie.VERSION_ATTR, new RFC2965VersionAttributeHandler());
76    }
77
78    private BasicClientCookie createCookie(
79            final String name, final String value, final CookieOrigin origin) {
80        BasicClientCookie cookie = new BasicClientCookie(name, value);
81        cookie.setPath(getDefaultPath(origin));
82        cookie.setDomain(getDefaultDomain(origin));
83        return cookie;
84    }
85
86    private BasicClientCookie createCookie2(
87            final String name, final String value, final CookieOrigin origin) {
88        BasicClientCookie2 cookie = new BasicClientCookie2(name, value);
89        cookie.setPath(getDefaultPath(origin));
90        cookie.setDomain(getDefaultDomain(origin));
91        cookie.setPorts(new int [] { origin.getPort() });
92        return cookie;
93    }
94
95    @Override
96    public List<Cookie> parse(
97            final Header header,
98            CookieOrigin origin) throws MalformedCookieException {
99        if (header == null) {
100            throw new IllegalArgumentException("Header may not be null");
101        }
102        if (origin == null) {
103            throw new IllegalArgumentException("Cookie origin may not be null");
104        }
105
106        origin = adjustEffectiveHost(origin);
107
108        HeaderElement[] elems = header.getElements();
109
110        List<Cookie> cookies = new ArrayList<Cookie>(elems.length);
111        for (HeaderElement headerelement : elems) {
112            String name = headerelement.getName();
113            String value = headerelement.getValue();
114            if (name == null || name.length() == 0) {
115                throw new MalformedCookieException("Cookie name may not be empty");
116            }
117
118            BasicClientCookie cookie;
119            if (header.getName().equals(SM.SET_COOKIE2)) {
120                cookie = createCookie2(name, value, origin);
121            } else {
122                cookie = createCookie(name, value, origin);
123            }
124
125            // cycle through the parameters
126            NameValuePair[] attribs = headerelement.getParameters();
127
128            // Eliminate duplicate attributes. The first occurrence takes precedence
129            // See RFC2965: 3.2  Origin Server Role
130            Map<String, NameValuePair> attribmap =
131                    new HashMap<String, NameValuePair>(attribs.length);
132            for (int j = attribs.length - 1; j >= 0; j--) {
133                NameValuePair param = attribs[j];
134                attribmap.put(param.getName().toLowerCase(Locale.ENGLISH), param);
135            }
136            for (Map.Entry<String, NameValuePair> entry : attribmap.entrySet()) {
137                NameValuePair attrib = entry.getValue();
138                String s = attrib.getName().toLowerCase(Locale.ENGLISH);
139
140                cookie.setAttribute(s, attrib.getValue());
141
142                CookieAttributeHandler handler = findAttribHandler(s);
143                if (handler != null) {
144                    handler.parse(cookie, attrib.getValue());
145                }
146            }
147            cookies.add(cookie);
148        }
149        return cookies;
150    }
151
152    @Override
153    public void validate(final Cookie cookie, CookieOrigin origin)
154            throws MalformedCookieException {
155        if (cookie == null) {
156            throw new IllegalArgumentException("Cookie may not be null");
157        }
158        if (origin == null) {
159            throw new IllegalArgumentException("Cookie origin may not be null");
160        }
161        origin = adjustEffectiveHost(origin);
162        super.validate(cookie, origin);
163    }
164
165    @Override
166    public boolean match(final Cookie cookie, CookieOrigin origin) {
167        if (cookie == null) {
168            throw new IllegalArgumentException("Cookie may not be null");
169        }
170        if (origin == null) {
171            throw new IllegalArgumentException("Cookie origin may not be null");
172        }
173        origin = adjustEffectiveHost(origin);
174        return super.match(cookie, origin);
175    }
176
177    /**
178     * Adds valid Port attribute value, e.g. "8000,8001,8002"
179     */
180    @Override
181    protected void formatCookieAsVer(final CharArrayBuffer buffer,
182            final Cookie cookie, int version) {
183        super.formatCookieAsVer(buffer, cookie, version);
184        // format port attribute
185        if (cookie instanceof ClientCookie) {
186            // Test if the port attribute as set by the origin server is not blank
187            String s = ((ClientCookie) cookie).getAttribute(ClientCookie.PORT_ATTR);
188            if (s != null) {
189                buffer.append("; $Port");
190                buffer.append("=\"");
191                if (s.trim().length() > 0) {
192                    int[] ports = cookie.getPorts();
193                    if (ports != null) {
194                        for (int i = 0, len = ports.length; i < len; i++) {
195                            if (i > 0) {
196                                buffer.append(",");
197                            }
198                            buffer.append(Integer.toString(ports[i]));
199                        }
200                    }
201                }
202                buffer.append("\"");
203            }
204        }
205    }
206
207    /**
208     * Set 'effective host name' as defined in RFC 2965.
209     * <p>
210     * If a host name contains no dots, the effective host name is
211     * that name with the string .local appended to it.  Otherwise
212     * the effective host name is the same as the host name.  Note
213     * that all effective host names contain at least one dot.
214     *
215     * @param origin origin where cookie is received from or being sent to.
216     * @return
217     */
218    private static CookieOrigin adjustEffectiveHost(final CookieOrigin origin) {
219        String host = origin.getHost();
220
221        // Test if the host name appears to be a fully qualified DNS name,
222        // IPv4 address or IPv6 address
223        boolean isLocalHost = true;
224        for (int i = 0; i < host.length(); i++) {
225            char ch = host.charAt(i);
226            if (ch == '.' || ch == ':') {
227                isLocalHost = false;
228                break;
229            }
230        }
231        if (isLocalHost) {
232            host += ".local";
233            return new CookieOrigin(
234                    host,
235                    origin.getPort(),
236                    origin.getPath(),
237                    origin.isSecure());
238        } else {
239            return origin;
240        }
241    }
242
243    @Override
244    public int getVersion() {
245        return 1;
246    }
247
248    @Override
249    public Header getVersionHeader() {
250        CharArrayBuffer buffer = new CharArrayBuffer(40);
251        buffer.append(SM.COOKIE2);
252        buffer.append(": ");
253        buffer.append("$Version=");
254        buffer.append(Integer.toString(getVersion()));
255        return new BufferedHeader(buffer);
256    }
257
258}
259
260