Wed, 31 Dec 2014 07:22:50 +0100
Correct previous dual key logic pending first delivery installment.
michael@0 | 1 | /* |
michael@0 | 2 | * ==================================================================== |
michael@0 | 3 | * Licensed to the Apache Software Foundation (ASF) under one |
michael@0 | 4 | * or more contributor license agreements. See the NOTICE file |
michael@0 | 5 | * distributed with this work for additional information |
michael@0 | 6 | * regarding copyright ownership. The ASF licenses this file |
michael@0 | 7 | * to you under the Apache License, Version 2.0 (the |
michael@0 | 8 | * "License"); you may not use this file except in compliance |
michael@0 | 9 | * with the License. You may obtain a copy of the License at |
michael@0 | 10 | * |
michael@0 | 11 | * http://www.apache.org/licenses/LICENSE-2.0 |
michael@0 | 12 | * |
michael@0 | 13 | * Unless required by applicable law or agreed to in writing, |
michael@0 | 14 | * software distributed under the License is distributed on an |
michael@0 | 15 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
michael@0 | 16 | * KIND, either express or implied. See the License for the |
michael@0 | 17 | * specific language governing permissions and limitations |
michael@0 | 18 | * under the License. |
michael@0 | 19 | * ==================================================================== |
michael@0 | 20 | * |
michael@0 | 21 | * This software consists of voluntary contributions made by many |
michael@0 | 22 | * individuals on behalf of the Apache Software Foundation. For more |
michael@0 | 23 | * information on the Apache Software Foundation, please see |
michael@0 | 24 | * <http://www.apache.org/>. |
michael@0 | 25 | * |
michael@0 | 26 | */ |
michael@0 | 27 | |
michael@0 | 28 | package ch.boye.httpclientandroidlib.impl.cookie; |
michael@0 | 29 | |
michael@0 | 30 | import java.util.ArrayList; |
michael@0 | 31 | import java.util.Collections; |
michael@0 | 32 | import java.util.List; |
michael@0 | 33 | |
michael@0 | 34 | import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; |
michael@0 | 35 | |
michael@0 | 36 | import ch.boye.httpclientandroidlib.Header; |
michael@0 | 37 | import ch.boye.httpclientandroidlib.HeaderElement; |
michael@0 | 38 | import ch.boye.httpclientandroidlib.cookie.ClientCookie; |
michael@0 | 39 | import ch.boye.httpclientandroidlib.cookie.Cookie; |
michael@0 | 40 | import ch.boye.httpclientandroidlib.cookie.CookieOrigin; |
michael@0 | 41 | import ch.boye.httpclientandroidlib.cookie.CookiePathComparator; |
michael@0 | 42 | import ch.boye.httpclientandroidlib.cookie.CookieRestrictionViolationException; |
michael@0 | 43 | import ch.boye.httpclientandroidlib.cookie.CookieSpec; |
michael@0 | 44 | import ch.boye.httpclientandroidlib.cookie.MalformedCookieException; |
michael@0 | 45 | import ch.boye.httpclientandroidlib.cookie.SM; |
michael@0 | 46 | import ch.boye.httpclientandroidlib.message.BufferedHeader; |
michael@0 | 47 | import ch.boye.httpclientandroidlib.util.CharArrayBuffer; |
michael@0 | 48 | |
michael@0 | 49 | /** |
michael@0 | 50 | * RFC 2109 compliant {@link CookieSpec} implementation. This is an older |
michael@0 | 51 | * version of the official HTTP state management specification superseded |
michael@0 | 52 | * by RFC 2965. |
michael@0 | 53 | * |
michael@0 | 54 | * @see RFC2965Spec |
michael@0 | 55 | * |
michael@0 | 56 | * @since 4.0 |
michael@0 | 57 | */ |
michael@0 | 58 | @NotThreadSafe // superclass is @NotThreadSafe |
michael@0 | 59 | public class RFC2109Spec extends CookieSpecBase { |
michael@0 | 60 | |
michael@0 | 61 | private final static CookiePathComparator PATH_COMPARATOR = new CookiePathComparator(); |
michael@0 | 62 | |
michael@0 | 63 | private final static String[] DATE_PATTERNS = { |
michael@0 | 64 | DateUtils.PATTERN_RFC1123, |
michael@0 | 65 | DateUtils.PATTERN_RFC1036, |
michael@0 | 66 | DateUtils.PATTERN_ASCTIME |
michael@0 | 67 | }; |
michael@0 | 68 | |
michael@0 | 69 | private final String[] datepatterns; |
michael@0 | 70 | private final boolean oneHeader; |
michael@0 | 71 | |
michael@0 | 72 | /** Default constructor */ |
michael@0 | 73 | public RFC2109Spec(final String[] datepatterns, boolean oneHeader) { |
michael@0 | 74 | super(); |
michael@0 | 75 | if (datepatterns != null) { |
michael@0 | 76 | this.datepatterns = datepatterns.clone(); |
michael@0 | 77 | } else { |
michael@0 | 78 | this.datepatterns = DATE_PATTERNS; |
michael@0 | 79 | } |
michael@0 | 80 | this.oneHeader = oneHeader; |
michael@0 | 81 | registerAttribHandler(ClientCookie.VERSION_ATTR, new RFC2109VersionHandler()); |
michael@0 | 82 | registerAttribHandler(ClientCookie.PATH_ATTR, new BasicPathHandler()); |
michael@0 | 83 | registerAttribHandler(ClientCookie.DOMAIN_ATTR, new RFC2109DomainHandler()); |
michael@0 | 84 | registerAttribHandler(ClientCookie.MAX_AGE_ATTR, new BasicMaxAgeHandler()); |
michael@0 | 85 | registerAttribHandler(ClientCookie.SECURE_ATTR, new BasicSecureHandler()); |
michael@0 | 86 | registerAttribHandler(ClientCookie.COMMENT_ATTR, new BasicCommentHandler()); |
michael@0 | 87 | registerAttribHandler(ClientCookie.EXPIRES_ATTR, new BasicExpiresHandler( |
michael@0 | 88 | this.datepatterns)); |
michael@0 | 89 | } |
michael@0 | 90 | |
michael@0 | 91 | /** Default constructor */ |
michael@0 | 92 | public RFC2109Spec() { |
michael@0 | 93 | this(null, false); |
michael@0 | 94 | } |
michael@0 | 95 | |
michael@0 | 96 | public List<Cookie> parse(final Header header, final CookieOrigin origin) |
michael@0 | 97 | throws MalformedCookieException { |
michael@0 | 98 | if (header == null) { |
michael@0 | 99 | throw new IllegalArgumentException("Header may not be null"); |
michael@0 | 100 | } |
michael@0 | 101 | if (origin == null) { |
michael@0 | 102 | throw new IllegalArgumentException("Cookie origin may not be null"); |
michael@0 | 103 | } |
michael@0 | 104 | if (!header.getName().equalsIgnoreCase(SM.SET_COOKIE)) { |
michael@0 | 105 | throw new MalformedCookieException("Unrecognized cookie header '" |
michael@0 | 106 | + header.toString() + "'"); |
michael@0 | 107 | } |
michael@0 | 108 | HeaderElement[] elems = header.getElements(); |
michael@0 | 109 | return parse(elems, origin); |
michael@0 | 110 | } |
michael@0 | 111 | |
michael@0 | 112 | @Override |
michael@0 | 113 | public void validate(final Cookie cookie, final CookieOrigin origin) |
michael@0 | 114 | throws MalformedCookieException { |
michael@0 | 115 | if (cookie == null) { |
michael@0 | 116 | throw new IllegalArgumentException("Cookie may not be null"); |
michael@0 | 117 | } |
michael@0 | 118 | String name = cookie.getName(); |
michael@0 | 119 | if (name.indexOf(' ') != -1) { |
michael@0 | 120 | throw new CookieRestrictionViolationException("Cookie name may not contain blanks"); |
michael@0 | 121 | } |
michael@0 | 122 | if (name.startsWith("$")) { |
michael@0 | 123 | throw new CookieRestrictionViolationException("Cookie name may not start with $"); |
michael@0 | 124 | } |
michael@0 | 125 | super.validate(cookie, origin); |
michael@0 | 126 | } |
michael@0 | 127 | |
michael@0 | 128 | public List<Header> formatCookies(List<Cookie> cookies) { |
michael@0 | 129 | if (cookies == null) { |
michael@0 | 130 | throw new IllegalArgumentException("List of cookies may not be null"); |
michael@0 | 131 | } |
michael@0 | 132 | if (cookies.isEmpty()) { |
michael@0 | 133 | throw new IllegalArgumentException("List of cookies may not be empty"); |
michael@0 | 134 | } |
michael@0 | 135 | if (cookies.size() > 1) { |
michael@0 | 136 | // Create a mutable copy and sort the copy. |
michael@0 | 137 | cookies = new ArrayList<Cookie>(cookies); |
michael@0 | 138 | Collections.sort(cookies, PATH_COMPARATOR); |
michael@0 | 139 | } |
michael@0 | 140 | if (this.oneHeader) { |
michael@0 | 141 | return doFormatOneHeader(cookies); |
michael@0 | 142 | } else { |
michael@0 | 143 | return doFormatManyHeaders(cookies); |
michael@0 | 144 | } |
michael@0 | 145 | } |
michael@0 | 146 | |
michael@0 | 147 | private List<Header> doFormatOneHeader(final List<Cookie> cookies) { |
michael@0 | 148 | int version = Integer.MAX_VALUE; |
michael@0 | 149 | // Pick the lowest common denominator |
michael@0 | 150 | for (Cookie cookie : cookies) { |
michael@0 | 151 | if (cookie.getVersion() < version) { |
michael@0 | 152 | version = cookie.getVersion(); |
michael@0 | 153 | } |
michael@0 | 154 | } |
michael@0 | 155 | CharArrayBuffer buffer = new CharArrayBuffer(40 * cookies.size()); |
michael@0 | 156 | buffer.append(SM.COOKIE); |
michael@0 | 157 | buffer.append(": "); |
michael@0 | 158 | buffer.append("$Version="); |
michael@0 | 159 | buffer.append(Integer.toString(version)); |
michael@0 | 160 | for (Cookie cooky : cookies) { |
michael@0 | 161 | buffer.append("; "); |
michael@0 | 162 | Cookie cookie = cooky; |
michael@0 | 163 | formatCookieAsVer(buffer, cookie, version); |
michael@0 | 164 | } |
michael@0 | 165 | List<Header> headers = new ArrayList<Header>(1); |
michael@0 | 166 | headers.add(new BufferedHeader(buffer)); |
michael@0 | 167 | return headers; |
michael@0 | 168 | } |
michael@0 | 169 | |
michael@0 | 170 | private List<Header> doFormatManyHeaders(final List<Cookie> cookies) { |
michael@0 | 171 | List<Header> headers = new ArrayList<Header>(cookies.size()); |
michael@0 | 172 | for (Cookie cookie : cookies) { |
michael@0 | 173 | int version = cookie.getVersion(); |
michael@0 | 174 | CharArrayBuffer buffer = new CharArrayBuffer(40); |
michael@0 | 175 | buffer.append("Cookie: "); |
michael@0 | 176 | buffer.append("$Version="); |
michael@0 | 177 | buffer.append(Integer.toString(version)); |
michael@0 | 178 | buffer.append("; "); |
michael@0 | 179 | formatCookieAsVer(buffer, cookie, version); |
michael@0 | 180 | headers.add(new BufferedHeader(buffer)); |
michael@0 | 181 | } |
michael@0 | 182 | return headers; |
michael@0 | 183 | } |
michael@0 | 184 | |
michael@0 | 185 | /** |
michael@0 | 186 | * Return a name/value string suitable for sending in a <tt>"Cookie"</tt> |
michael@0 | 187 | * header as defined in RFC 2109 for backward compatibility with cookie |
michael@0 | 188 | * version 0 |
michael@0 | 189 | * @param buffer The char array buffer to use for output |
michael@0 | 190 | * @param name The cookie name |
michael@0 | 191 | * @param value The cookie value |
michael@0 | 192 | * @param version The cookie version |
michael@0 | 193 | */ |
michael@0 | 194 | protected void formatParamAsVer(final CharArrayBuffer buffer, |
michael@0 | 195 | final String name, final String value, int version) { |
michael@0 | 196 | buffer.append(name); |
michael@0 | 197 | buffer.append("="); |
michael@0 | 198 | if (value != null) { |
michael@0 | 199 | if (version > 0) { |
michael@0 | 200 | buffer.append('\"'); |
michael@0 | 201 | buffer.append(value); |
michael@0 | 202 | buffer.append('\"'); |
michael@0 | 203 | } else { |
michael@0 | 204 | buffer.append(value); |
michael@0 | 205 | } |
michael@0 | 206 | } |
michael@0 | 207 | } |
michael@0 | 208 | |
michael@0 | 209 | /** |
michael@0 | 210 | * Return a string suitable for sending in a <tt>"Cookie"</tt> header |
michael@0 | 211 | * as defined in RFC 2109 for backward compatibility with cookie version 0 |
michael@0 | 212 | * @param buffer The char array buffer to use for output |
michael@0 | 213 | * @param cookie The {@link Cookie} to be formatted as string |
michael@0 | 214 | * @param version The version to use. |
michael@0 | 215 | */ |
michael@0 | 216 | protected void formatCookieAsVer(final CharArrayBuffer buffer, |
michael@0 | 217 | final Cookie cookie, int version) { |
michael@0 | 218 | formatParamAsVer(buffer, cookie.getName(), cookie.getValue(), version); |
michael@0 | 219 | if (cookie.getPath() != null) { |
michael@0 | 220 | if (cookie instanceof ClientCookie |
michael@0 | 221 | && ((ClientCookie) cookie).containsAttribute(ClientCookie.PATH_ATTR)) { |
michael@0 | 222 | buffer.append("; "); |
michael@0 | 223 | formatParamAsVer(buffer, "$Path", cookie.getPath(), version); |
michael@0 | 224 | } |
michael@0 | 225 | } |
michael@0 | 226 | if (cookie.getDomain() != null) { |
michael@0 | 227 | if (cookie instanceof ClientCookie |
michael@0 | 228 | && ((ClientCookie) cookie).containsAttribute(ClientCookie.DOMAIN_ATTR)) { |
michael@0 | 229 | buffer.append("; "); |
michael@0 | 230 | formatParamAsVer(buffer, "$Domain", cookie.getDomain(), version); |
michael@0 | 231 | } |
michael@0 | 232 | } |
michael@0 | 233 | } |
michael@0 | 234 | |
michael@0 | 235 | public int getVersion() { |
michael@0 | 236 | return 1; |
michael@0 | 237 | } |
michael@0 | 238 | |
michael@0 | 239 | public Header getVersionHeader() { |
michael@0 | 240 | return null; |
michael@0 | 241 | } |
michael@0 | 242 | |
michael@0 | 243 | @Override |
michael@0 | 244 | public String toString() { |
michael@0 | 245 | return "rfc2109"; |
michael@0 | 246 | } |
michael@0 | 247 | |
michael@0 | 248 | } |