001package com.hfg.webapp; 002 003import java.text.ParseException; 004import java.text.SimpleDateFormat; 005import java.util.ArrayList; 006import java.util.Date; 007import java.util.List; 008import java.util.regex.Matcher; 009import java.util.regex.Pattern; 010import javax.servlet.http.Cookie; 011 012import com.hfg.util.StringUtil; 013 014//------------------------------------------------------------------------------ 015/** 016 Cookie extension that handles encoding/decoding of cookie values. 017 <div> 018 @author J. Alex Taylor, hairyfatguy.com 019 </div> 020*/ 021//------------------------------------------------------------------------------ 022// com.hfg XML/HTML Coding Library 023// 024// This library is free software; you can redistribute it and/or 025// modify it under the terms of the GNU Lesser General Public 026// License as published by the Free Software Foundation; either 027// version 2.1 of the License, or (at your option) any later version. 028// 029// This library is distributed in the hope that it will be useful, 030// but WITHOUT ANY WARRANTY; without even the implied warranty of 031// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 032// Lesser General Public License for more details. 033// 034// You should have received a copy of the GNU Lesser General Public 035// License along with this library; if not, write to the Free Software 036// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 037// 038// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com 039// jataylor@hairyfatguy.com 040//------------------------------------------------------------------------------ 041// TODO: Add a toString(). 042 043public class HfgCookie extends Cookie 044{ 045 private Date mExpires; 046 private String mDecodedValue; 047 048 049 private static final Pattern COOKIE_NAME_VALUE_PATTERN = Pattern.compile("([^=]+)=(.+)"); 050 051 // Mon, 09-Dec-2002 13:46:00 GMT 052 private static final SimpleDateFormat EXPIRES_FORMAT = new SimpleDateFormat("EEE, dd-MMM-yyyy HH:mm:ss z"); 053 054 //########################################################################### 055 // CONSTRUCTORS 056 //########################################################################### 057 058 //--------------------------------------------------------------------------- 059 public HfgCookie(String inName, String inValue) 060 { 061 super(inName, inValue); 062 063 setValue(inValue); 064 } 065 066 //--------------------------------------------------------------------------- 067 public HfgCookie(Cookie inCookie) 068 { 069 this(inCookie.getName(), inCookie.getValue()); 070 071 if (inCookie.getPath() != null) 072 { 073 setPath(inCookie.getPath()); 074 } 075 076 if (inCookie.getComment() != null) 077 { 078 setComment(inCookie.getComment()); 079 } 080 081 if (inCookie.getDomain() != null) 082 { 083 setDomain(inCookie.getDomain()); 084 } 085 086 setMaxAge(inCookie.getMaxAge()); 087 setHttpOnly(inCookie.isHttpOnly()); 088 setSecure(inCookie.getSecure()); 089 setVersion(inCookie.getVersion()); 090 } 091 092 //########################################################################### 093 // PUBLIC METHODS 094 //########################################################################### 095 096 //--------------------------------------------------------------------------- 097 /** 098 * Returns one or more cookies from an http response's "Set-Cookie" or 099 * "Set-Cookie2" header field. 100 * <div> 101 * "Set-Cookie" is covered by RFC2109. 102 * RFC 2965 section 3.2.2 says that one "Set-Cookie2" header line 103 * can contain more than one cookie definition. 104 * </div> 105 * @param inSetCookieValue the value of the "Set-Cookie" or "Set-Cookie2" header 106 * @return one or more parsed cookies 107 */ 108 public static List<HfgCookie> parse(String inSetCookieValue) 109 { 110 List<HfgCookie> cookies = null; 111 112 // Ex: JSESSIONID=3AF5DD38614E80FCCC0632902E3C8E18; Path=/; Secure; HttpOnly 113 if (StringUtil.isSet(inSetCookieValue)) 114 { 115 List<String> cookieStrings = splitIntoCookieStrings(inSetCookieValue); 116 117 cookies = new ArrayList<>(cookieStrings.size()); 118 for (String cookieString : cookieStrings) 119 { 120 HfgCookie currentCookie = null; 121 122 String[] pieces = cookieString.split(";"); 123 for (String piece : pieces) 124 { 125 piece = piece.trim(); 126 127 Matcher m = COOKIE_NAME_VALUE_PATTERN.matcher(piece); 128 if (m.matches()) 129 { 130 String name = m.group(1).trim(); 131 String value = m.group(2).trim(); 132 133 if (name.equalsIgnoreCase("Path")) 134 { 135 if (currentCookie != null) 136 { 137 currentCookie.setPath(value); 138 } 139 } 140 else if (name.equalsIgnoreCase("Domain")) 141 { 142 if (currentCookie != null) 143 { 144 currentCookie.setDomain(value); 145 } 146 } 147 else if (name.equalsIgnoreCase("Version")) 148 { 149 if (currentCookie != null) 150 { 151 currentCookie.setVersion(Integer.parseInt(value)); 152 } 153 } 154 else if (name.equalsIgnoreCase("Max-Age")) // Seconds to keep the cookie 155 { 156 if (currentCookie != null) 157 { 158 currentCookie.setMaxAge(Integer.parseInt(value)); 159 } 160 } 161 else if (name.equalsIgnoreCase("Expires")) 162 { 163 if (currentCookie != null) 164 { 165 try 166 { 167 // Mon, 09-Dec-2002 13:46:00 GMT 168 currentCookie.setExpires(EXPIRES_FORMAT.parse(value)); 169 } 170 catch (ParseException e) 171 { 172 throw new RuntimeException(e); 173 } 174 } 175 } 176 else 177 { 178 currentCookie = new HfgCookie(name, value); 179 cookies.add(currentCookie); 180 } 181 } 182 else if (piece.equalsIgnoreCase("Secure")) 183 { 184 if (currentCookie != null) 185 { 186 currentCookie.setSecure(true); 187 } 188 } 189 else if (piece.equalsIgnoreCase("HttpOnly")) 190 { 191 if (currentCookie != null) 192 { 193 currentCookie.setHttpOnly(true); 194 } 195 } 196 197 } 198 } 199 200 } 201 202 return cookies; 203 } 204 205 //--------------------------------------------------------------------------- 206 @Override 207 public String toString() 208 { 209 return getName() + "=" + getValue(); 210 } 211 212 //--------------------------------------------------------------------------- 213 @Override 214 public void setValue(String inValue) 215 { 216 super.setValue(inValue); 217 // The incoming value could be encoded or decoded. 218 if (inValue != null) 219 { 220 if (WebappUtil.isCookieEncoded(this)) 221 { 222 mDecodedValue = WebappUtil.decodeCookieValue(getValue()); 223 // Sometimes we have double-encoded values. Try unwinding further... 224 while (WebappUtil.isCookieEncoded(mDecodedValue)) 225 { 226 mDecodedValue = WebappUtil.decodeCookieValue(mDecodedValue); 227 } 228 } 229 else if (WebappUtil.cookieNeedsEncoding(this)) 230 { 231 mDecodedValue = getValue(); 232 setValue(WebappUtil.encodeCookieValue(getValue())); 233 } 234 else 235 { 236 mDecodedValue = getValue(); 237 } 238 } 239 } 240 241 //--------------------------------------------------------------------------- 242 public HfgCookie setExpires(Date inValue) 243 { 244 mExpires = inValue; 245 return this; 246 } 247 248 //--------------------------------------------------------------------------- 249 public Date getExpires() 250 { 251 return mExpires; 252 } 253 254 //--------------------------------------------------------------------------- 255 public String getDecodedValue() 256 { 257 return mDecodedValue; 258 } 259 260 261 //########################################################################### 262 // PRIVATE METHODS 263 //########################################################################### 264 265 //--------------------------------------------------------------------------- 266 private static List<String> splitIntoCookieStrings(String inValue) 267 { 268 List<String> cookies; 269 270 // Only bother looking closely if the is a comma somewhere in the string 271 if (inValue.indexOf(",") > 0) 272 { 273 cookies = new ArrayList<>(2); 274 275 // Make sure the comma isn't inside of quotes 276 boolean inQuotes = false; 277 int startIndex = 0; 278 for (int i = 0; i < inValue.length(); i++) 279 { 280 char currentChar = inValue.charAt(i); 281 if (currentChar == '"') 282 { 283 inQuotes = ! inQuotes; 284 } 285 else if (currentChar == ',' 286 && ! inQuotes) 287 { 288 cookies.add(inValue.substring(startIndex, i)); 289 startIndex = i + 1; 290 } 291 } 292 293 // Add whatever is remaining 294 cookies.add(inValue.substring(startIndex)); 295 } 296 else 297 { 298 cookies = new ArrayList<>(1); 299 cookies.add(inValue); 300 } 301 302 return cookies; 303 } 304}