001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2015 ForgeRock AS. 015 */ 016 017package org.forgerock.http.header; 018 019import static java.util.Collections.*; 020 021import java.text.ParseException; 022import java.text.SimpleDateFormat; 023import java.util.ArrayList; 024import java.util.Arrays; 025import java.util.Date; 026import java.util.List; 027import java.util.Locale; 028import java.util.TimeZone; 029 030import org.forgerock.http.protocol.Cookie; 031import org.forgerock.http.protocol.Header; 032import org.forgerock.http.protocol.Response; 033 034/** 035 * Processes the <strong>{@code Set-Cookie}</strong> request message header. For 036 * more information, see the Http State Management Mechanism specification <a 037 * href="http://tools.ietf.org/html/rfc6265">RFC 6265</a>. 038 * <p> 039 * Note: This implementation is designed to be forgiving when parsing malformed 040 * cookies. 041 */ 042public class SetCookieHeader extends Header { 043 044 /** The name of this header. */ 045 public static final String NAME = "Set-Cookie"; 046 047 /** 048 * Constructs a new header, initialized from the specified string value. 049 * 050 * @param value 051 * The value to initialize the header from. 052 * @return The parsed header. 053 */ 054 public static SetCookieHeader valueOf(String value) { 055 return new SetCookieHeader(singletonList(parseCookie(value))); 056 } 057 058 private static Cookie parseCookie(String value) { 059 List<String> parts = Arrays.asList(value.split(";")); 060 Cookie cookie = new Cookie(); 061 for (String part : parts) { 062 String[] nvp = part.split("="); 063 if ("Expires".equalsIgnoreCase(nvp[0].trim())) { 064 cookie.setExpires(parseDate(nvp[1].trim())); 065 } else if ("Max-Age".equalsIgnoreCase(nvp[0].trim())) { 066 cookie.setMaxAge(parseInteger(nvp[1].trim())); 067 } else if ("Path".equalsIgnoreCase(nvp[0].trim())) { 068 cookie.setPath(nvp[1]); 069 } else if ("Domain".equalsIgnoreCase(nvp[0].trim())) { 070 cookie.setDomain(nvp[1]); 071 } else if ("Secure".equalsIgnoreCase(nvp[0].trim())) { 072 cookie.setSecure(true); 073 } else if ("HttpOnly".equalsIgnoreCase(nvp[0].trim())) { 074 cookie.setHttpOnly(true); 075 } else if (cookie.getName() == null || cookie.getName().isEmpty()) { 076 cookie.setName(nvp[0].trim()); 077 cookie.setValue(nvp[1].trim()); 078 } 079 } 080 if (cookie.getName() == null || cookie.getName().isEmpty()) { 081 cookie = new Cookie(); 082 } 083 return cookie; 084 } 085 086 /** 087 * Constructs a new header, initialized from the specified response message. 088 * 089 * @param response 090 * The response message to initialize the header from. 091 * @return The parsed header. 092 */ 093 public static SetCookieHeader valueOf(Response response) { 094 if (response == null || !response.getHeaders().containsKey(NAME)) { 095 return null; 096 } 097 return valueOf(response.getHeaders().get(NAME).getValues()); 098 } 099 100 /** 101 * Constructs a new header, initialized from the specified list of Set-Cookie values. 102 * 103 * @param values 104 * The values to initialize the header from. 105 * @return The parsed header. 106 */ 107 public static SetCookieHeader valueOf(List<String> values) { 108 if (values == null) { 109 return null; 110 } 111 List<Cookie> cookies = new ArrayList<>(); 112 for (String headerValue : values) { 113 cookies.add(parseCookie(headerValue)); 114 } 115 return new SetCookieHeader(unmodifiableList(cookies)); 116 } 117 118 private static Integer parseInteger(String s) { 119 try { 120 return Integer.valueOf(s); 121 } catch (NumberFormatException nfe) { 122 return null; 123 } 124 } 125 126 private static final String EXPIRES_DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss z"; 127 128 private static SimpleDateFormat getDateFormatter() { 129 SimpleDateFormat formatter = new SimpleDateFormat(EXPIRES_DATE_FORMAT, Locale.ROOT); 130 formatter.setTimeZone(TimeZone.getTimeZone("GMT")); 131 return formatter; 132 } 133 134 private static Date parseDate(String s) { 135 try { 136 return getDateFormatter().parse(s); 137 } catch (ParseException e) { 138 return null; 139 } 140 } 141 142 private final List<Cookie> cookies; 143 private final List<String> values; 144 145 /** 146 * Constructs a new header with the provided cookies. 147 * 148 * @param cookies The cookies. 149 */ 150 public SetCookieHeader(List<Cookie> cookies) { 151 this.cookies = cookies; 152 if (cookies != null) { 153 this.values = new ArrayList<>(); 154 for (Cookie cookie : cookies) { 155 values.add(toString(cookie)); 156 } 157 } else { 158 values = null; 159 } 160 } 161 162 @Override 163 public String getName() { 164 return NAME; 165 } 166 167 @Override 168 public List<String> getValues() { 169 return values; 170 } 171 172 /** 173 * Returns the cookies. 174 * 175 * @return The cookies. 176 */ 177 public List<Cookie> getCookies() { 178 return cookies; 179 } 180 181 private String toString(Cookie cookie) { 182 StringBuilder sb = new StringBuilder(); 183 if (cookie.getName() != null) { 184 sb.append(cookie.getName()).append("=").append(cookie.getValue()); 185 if (cookie.getExpires() != null) { 186 sb.append("; ").append("Expires").append("=").append(getDateFormatter().format(cookie.getExpires())); 187 } 188 if (cookie.getMaxAge() != null && cookie.getMaxAge() > 0) { 189 sb.append("; ").append("Max-Age").append("=").append(cookie.getMaxAge()); 190 } 191 if (cookie.getPath() != null) { 192 sb.append("; ").append("Path").append("=").append(cookie.getPath()); 193 } 194 if (cookie.getDomain() != null) { 195 sb.append("; ").append("Domain").append("=").append(cookie.getDomain()); 196 } 197 if (cookie.isSecure() != null && cookie.isSecure()) { 198 sb.append("; ").append("Secure"); 199 } 200 if (cookie.isHttpOnly() != null && cookie.isHttpOnly()) { 201 sb.append("; ").append("HttpOnly"); 202 } 203 } 204 return sb.toString(); 205 } 206 207 static class Factory extends HeaderFactory<SetCookieHeader> { 208 209 @Override 210 public SetCookieHeader parse(String value) { 211 return valueOf(value); 212 } 213 214 @Override 215 public SetCookieHeader parse(List<String> values) { 216 return valueOf(values); 217 } 218 } 219}