001/*
002 * ====================================================================
003 * Licensed to the Apache Software Foundation (ASF) under one
004 * or more contributor license agreements.  See the NOTICE file
005 * distributed with this work for additional information
006 * regarding copyright ownership.  The ASF licenses this file
007 * to you under the Apache License, Version 2.0 (the
008 * "License"); you may not use this file except in compliance
009 * with the License.  You may obtain a copy of the License at
010 *
011 *   http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing,
014 * software distributed under the License is distributed on an
015 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
016 * KIND, either express or implied.  See the License for the
017 * specific language governing permissions and limitations
018 * under the License.
019 * ====================================================================
020 *
021 * This software consists of voluntary contributions made by many
022 * individuals on behalf of the Apache Software Foundation.  For more
023 * information on the Apache Software Foundation, please see
024 * <http://www.apache.org/>.
025 *
026 */
027package org.apache.http.impl.cookie;
028
029import java.util.ArrayList;
030import java.util.HashMap;
031import java.util.List;
032import java.util.Locale;
033import java.util.Map;
034
035import org.apache.http.Header;
036import org.apache.http.HeaderElement;
037import org.apache.http.NameValuePair;
038import org.apache.http.annotation.Contract;
039import org.apache.http.annotation.Obsolete;
040import org.apache.http.annotation.ThreadingBehavior;
041import org.apache.http.cookie.ClientCookie;
042import org.apache.http.cookie.CommonCookieAttributeHandler;
043import org.apache.http.cookie.Cookie;
044import org.apache.http.cookie.CookieAttributeHandler;
045import org.apache.http.cookie.CookieOrigin;
046import org.apache.http.cookie.CookieRestrictionViolationException;
047import org.apache.http.cookie.MalformedCookieException;
048import org.apache.http.cookie.SM;
049import org.apache.http.message.BufferedHeader;
050import org.apache.http.util.Args;
051import org.apache.http.util.CharArrayBuffer;
052
053/**
054 * RFC 2965 compliant {@link org.apache.http.cookie.CookieSpec} implementation.
055 * <p>
056 * Rendered obsolete by {@link org.apache.http.impl.cookie.RFC6265StrictSpec}.
057 *
058 * @since 4.0
059 * @see org.apache.http.impl.cookie.RFC6265StrictSpec
060 */
061@Obsolete
062@Contract(threading = ThreadingBehavior.SAFE)
063public class RFC2965Spec extends RFC2109Spec {
064
065    /**
066     * Default constructor
067     *
068     */
069    public RFC2965Spec() {
070        this(null, false);
071    }
072
073    public RFC2965Spec(final String[] datepatterns, final boolean oneHeader) {
074        super(oneHeader,
075                new RFC2965VersionAttributeHandler(),
076                new BasicPathHandler() {
077
078                    @Override
079                    public void validate(
080                            final Cookie cookie, final CookieOrigin origin) throws MalformedCookieException {
081                        if (!match(cookie, origin)) {
082                            throw new CookieRestrictionViolationException(
083                                    "Illegal 'path' attribute \"" + cookie.getPath()
084                                            + "\". Path of origin: \"" + origin.getPath() + "\"");
085                        }
086                    }
087
088                },
089                new RFC2965DomainAttributeHandler(),
090                new RFC2965PortAttributeHandler(),
091                new BasicMaxAgeHandler(),
092                new BasicSecureHandler(),
093                new BasicCommentHandler(),
094                new BasicExpiresHandler(
095                        datepatterns != null ? datepatterns.clone() : DATE_PATTERNS),
096                new RFC2965CommentUrlAttributeHandler(),
097                new RFC2965DiscardAttributeHandler());
098    }
099
100    RFC2965Spec(final boolean oneHeader,
101                final CommonCookieAttributeHandler... handlers) {
102        super(oneHeader, handlers);
103    }
104
105    @Override
106    public List<Cookie> parse(
107            final Header header,
108            final CookieOrigin origin) throws MalformedCookieException {
109        Args.notNull(header, "Header");
110        Args.notNull(origin, "Cookie origin");
111        if (!header.getName().equalsIgnoreCase(SM.SET_COOKIE2)) {
112            throw new MalformedCookieException("Unrecognized cookie header '"
113                    + header.toString() + "'");
114        }
115        final HeaderElement[] elems = header.getElements();
116        return createCookies(elems, adjustEffectiveHost(origin));
117    }
118
119    @Override
120    protected List<Cookie> parse(
121            final HeaderElement[] elems,
122            final CookieOrigin origin) throws MalformedCookieException {
123        return createCookies(elems, adjustEffectiveHost(origin));
124    }
125
126    private List<Cookie> createCookies(
127            final HeaderElement[] elems,
128            final CookieOrigin origin) throws MalformedCookieException {
129        final List<Cookie> cookies = new ArrayList<Cookie>(elems.length);
130        for (final HeaderElement headerelement : elems) {
131            final String name = headerelement.getName();
132            final String value = headerelement.getValue();
133            if (name == null || name.isEmpty()) {
134                throw new MalformedCookieException("Cookie name may not be empty");
135            }
136
137            final BasicClientCookie2 cookie = new BasicClientCookie2(name, value);
138            cookie.setPath(getDefaultPath(origin));
139            cookie.setDomain(getDefaultDomain(origin));
140            cookie.setPorts(new int [] { origin.getPort() });
141            // cycle through the parameters
142            final NameValuePair[] attribs = headerelement.getParameters();
143
144            // Eliminate duplicate attributes. The first occurrence takes precedence
145            // See RFC2965: 3.2  Origin Server Role
146            final Map<String, NameValuePair> attribmap =
147                    new HashMap<String, NameValuePair>(attribs.length);
148            for (int j = attribs.length - 1; j >= 0; j--) {
149                final NameValuePair param = attribs[j];
150                attribmap.put(param.getName().toLowerCase(Locale.ROOT), param);
151            }
152            for (final Map.Entry<String, NameValuePair> entry : attribmap.entrySet()) {
153                final NameValuePair attrib = entry.getValue();
154                final String s = attrib.getName().toLowerCase(Locale.ROOT);
155
156                cookie.setAttribute(s, attrib.getValue());
157
158                final CookieAttributeHandler handler = findAttribHandler(s);
159                if (handler != null) {
160                    handler.parse(cookie, attrib.getValue());
161                }
162            }
163            cookies.add(cookie);
164        }
165        return cookies;
166    }
167
168    @Override
169    public void validate(
170            final Cookie cookie, final CookieOrigin origin) throws MalformedCookieException {
171        Args.notNull(cookie, "Cookie");
172        Args.notNull(origin, "Cookie origin");
173        super.validate(cookie, adjustEffectiveHost(origin));
174    }
175
176    @Override
177    public boolean match(final Cookie cookie, final CookieOrigin origin) {
178        Args.notNull(cookie, "Cookie");
179        Args.notNull(origin, "Cookie origin");
180        return super.match(cookie, adjustEffectiveHost(origin));
181    }
182
183    /**
184     * Adds valid Port attribute value, e.g. "8000,8001,8002"
185     */
186    @Override
187    protected void formatCookieAsVer(final CharArrayBuffer buffer,
188            final Cookie cookie, final int version) {
189        super.formatCookieAsVer(buffer, cookie, version);
190        // format port attribute
191        if (cookie instanceof ClientCookie) {
192            // Test if the port attribute as set by the origin server is not blank
193            final String s = ((ClientCookie) cookie).getAttribute(ClientCookie.PORT_ATTR);
194            if (s != null) {
195                buffer.append("; $Port");
196                buffer.append("=\"");
197                if (!s.trim().isEmpty()) {
198                    final int[] ports = cookie.getPorts();
199                    if (ports != null) {
200                        final int len = ports.length;
201                        for (int i = 0; i < len; i++) {
202                            if (i > 0) {
203                                buffer.append(",");
204                            }
205                            buffer.append(Integer.toString(ports[i]));
206                        }
207                    }
208                }
209                buffer.append("\"");
210            }
211        }
212    }
213
214    /**
215     * Set 'effective host name' as defined in RFC 2965.
216     * <p>
217     * If a host name contains no dots, the effective host name is
218     * that name with the string .local appended to it.  Otherwise
219     * the effective host name is the same as the host name.  Note
220     * that all effective host names contain at least one dot.
221     *
222     * @param origin origin where cookie is received from or being sent to.
223     */
224    private static CookieOrigin adjustEffectiveHost(final CookieOrigin origin) {
225        String host = origin.getHost();
226
227        // Test if the host name appears to be a fully qualified DNS name,
228        // IPv4 address or IPv6 address
229        boolean isLocalHost = true;
230        for (int i = 0; i < host.length(); i++) {
231            final char ch = host.charAt(i);
232            if (ch == '.' || ch == ':') {
233                isLocalHost = false;
234                break;
235            }
236        }
237        if (isLocalHost) {
238            host += ".local";
239            return new CookieOrigin(
240                    host,
241                    origin.getPort(),
242                    origin.getPath(),
243                    origin.isSecure());
244        } else {
245            return origin;
246        }
247    }
248
249    @Override
250    public int getVersion() {
251        return 1;
252    }
253
254    @Override
255    public Header getVersionHeader() {
256        final CharArrayBuffer buffer = new CharArrayBuffer(40);
257        buffer.append(SM.COOKIE2);
258        buffer.append(": ");
259        buffer.append("$Version=");
260        buffer.append(Integer.toString(getVersion()));
261        return new BufferedHeader(buffer);
262    }
263
264    @Override
265    public String toString() {
266        return "rfc2965";
267    }
268
269}
270