View Javadoc
1   /* 
2    * Licensed under the Apache License, Version 2.0 (the "License");
3    * you may not use this file except in compliance with the License.
4    * You may obtain a copy of the License at
5    *
6    * http://www.apache.org/licenses/LICENSE-2.0
7    *
8    * Unless required by applicable law or agreed to in writing, software
9    * distributed under the License is distributed on an "AS IS" BASIS,
10   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11   * See the License for the specific language governing permissions and
12   * limitations under the License.
13   *
14   */
15  
16  package org.esigate.cookie;
17  
18  import java.util.Collection;
19  import java.util.Date;
20  import java.util.List;
21  import java.util.Properties;
22  
23  import org.apache.http.cookie.Cookie;
24  import org.apache.http.impl.client.BasicCookieStore;
25  import org.apache.http.impl.cookie.BasicClientCookie;
26  import org.apache.http.impl.cookie.BasicClientCookie2;
27  import org.esigate.ConfigurationException;
28  import org.esigate.Driver;
29  import org.esigate.Parameters;
30  import org.esigate.UserContext;
31  import org.esigate.http.cookie.CookieUtil;
32  import org.esigate.impl.DriverRequest;
33  import org.esigate.util.UriUtils;
34  import org.slf4j.Logger;
35  import org.slf4j.LoggerFactory;
36  
37  /**
38   * This cookie manager supports rules for forwarding cookies to the user browser, ignore and discard cookies, or store
39   * cookies in the user session.
40   * 
41   * <p>
42   * When cookies are not stored in the session or discarded, they are forwarded to the client browser. If no cookie is
43   * stored to the session (default) EsiGate is completely stateless. For public deployment is is recommended to use
44   * cookie forwarding and discarding to prevent session creation.
45   * 
46   * @author Francois-Xavier Bonnet
47   * @author Nicolas Richeton
48   * 
49   */
50  public class DefaultCookieManager implements CookieManager {
51      private static final Logger LOG = LoggerFactory.getLogger(CookieManager.class);
52      private static final String COOKIES_LIST_SESSION_KEY = CookieManager.class.getName() + "#cookies";
53      private Collection<String> discardCookies;
54      private Collection<String> storeCookiesInSession;
55  
56      protected Collection<String> getStoredCookies() {
57          return storeCookiesInSession;
58      }
59  
60      /**
61       * Init cookie manager. Reads parameters <b>discardCookies</b> and <b>storeCookiesInSession</b>.
62       */
63      @Override
64      public void init(Driver d, Properties properties) {
65          // Cookies to store to session
66          this.storeCookiesInSession = Parameters.STORE_COOKIES_IN_SESSION.getValue(properties);
67          // Cookies to discard
68          this.discardCookies = Parameters.DISCARD_COOKIES.getValue(properties);
69  
70          // Verify configuration
71          if (this.storeCookiesInSession.contains("*") && this.storeCookiesInSession.size() > 1) {
72              throw new ConfigurationException("storeCookiesInSession must be a list of cookie names OR *");
73          }
74          if (this.discardCookies.contains("*") && this.discardCookies.size() > 1) {
75              throw new ConfigurationException("discardCookies must be a list of cookie names OR *");
76          }
77          if (this.storeCookiesInSession.contains("*") && this.discardCookies.contains("*")) {
78              throw new ConfigurationException(
79                      "cannot use * for storeCookiesInSession AND discardCookies at the same time");
80          }
81      }
82  
83      @Override
84      public void addCookie(Cookie cookie, DriverRequest originalRequest) {
85          String name = cookie.getName();
86          if (discardCookies.contains(name) || (discardCookies.contains("*") && !storeCookiesInSession.contains(name))) {
87              if (LOG.isInfoEnabled()) {
88                  LOG.info("Cookie " + toString(cookie) + " -> discarding");
89              }
90              // Ignore cookie
91          } else if (storeCookiesInSession.contains(name) || storeCookiesInSession.contains("*")) {
92              if (LOG.isInfoEnabled()) {
93                  LOG.info("Cookie " + toString(cookie) + " -> storing to session");
94              }
95              // Store cookie in session
96              UserContext userContext = originalRequest.getUserContext();
97              BasicCookieStore cookies = (BasicCookieStore) userContext.getAttribute(COOKIES_LIST_SESSION_KEY);
98              if (cookies == null) {
99                  cookies = new BasicCookieStore();
100             }
101             cookies.addCookie(cookie);
102             userContext.setAttribute(COOKIES_LIST_SESSION_KEY, cookies);
103         } else {
104             if (LOG.isInfoEnabled()) {
105                 LOG.info("Cookie " + toString(cookie) + " -> forwarding");
106             }
107             // Forward cookie to response.
108             originalRequest.getOriginalRequest().addNewCookie(rewriteForBrowser(cookie, originalRequest));
109         }
110     }
111 
112     @Override
113     public List<Cookie> getCookies(DriverRequest originalRequest) {
114         BasicCookieStore cookies = new BasicCookieStore();
115         UserContext userContext = originalRequest.getUserContext();
116 
117         // Read cookies from session
118         BasicCookieStore sessionCookies = (BasicCookieStore) userContext.getAttribute(COOKIES_LIST_SESSION_KEY);
119         if (sessionCookies != null) {
120             for (Cookie c : sessionCookies.getCookies()) {
121                 cookies.addCookie(c);
122             }
123         }
124 
125         // Read cookie from request
126         Cookie[] requestCookies = originalRequest.getOriginalRequest().getCookies();
127         if (requestCookies != null) {
128             for (Cookie cookie : requestCookies) {
129                 String name = cookie.getName();
130                 if (!storeCookiesInSession.contains(name) && !storeCookiesInSession.contains("*")
131                         && !discardCookies.contains(name) && !discardCookies.contains("*")) {
132                     cookies.addCookie(rewriteForServer(cookie, originalRequest));
133                 }
134             }
135         }
136         return cookies.getCookies();
137     }
138 
139     private static Cookie rewriteForServer(Cookie cookie, DriverRequest request) {
140         String name = cookie.getName();
141         if ("_JSESSIONID".equalsIgnoreCase(name)) {
142             name = name.substring(1);
143         }
144         BasicClientCookie2 httpClientCookie = new BasicClientCookie2(name, cookie.getValue());
145         httpClientCookie.setSecure(false);
146         String domain;
147         if (request.getDriver().getConfiguration().isPreserveHost()) {
148             domain = UriUtils.extractHostName(request.getOriginalRequest().getRequestLine().getUri());
149         } else {
150             domain = request.getBaseUrl().getHost();
151         }
152 
153         httpClientCookie.setDomain(domain);
154         httpClientCookie.setPath("/");
155         httpClientCookie.setComment(cookie.getComment());
156         httpClientCookie.setVersion(cookie.getVersion());
157         return httpClientCookie;
158     }
159 
160     protected static String rewriteDomain(String originalDomain, String providerHostName, String requestHostName) {
161         String domain = null;
162         if (!providerHostName.equals(originalDomain)) {
163             // if original domain starts with ".", remove it.
164             if (originalDomain.startsWith(".")) {
165                 originalDomain = originalDomain.substring(1);
166             }
167             String[] originalDomainParts = originalDomain.split("\\.");
168             String[] requestHostNameParts = requestHostName.split("\\.");
169             int targetLength = Math.min(originalDomainParts.length, requestHostNameParts.length);
170             if (targetLength == requestHostNameParts.length) {
171                 // The bigger domain we can use is request host name, it is like
172                 // returning null as domain name!
173                 return null;
174             }
175             domain = "";
176             for (int i = requestHostNameParts.length; i > requestHostNameParts.length - targetLength; i--) {
177                 domain = "." + requestHostNameParts[i - 1] + domain;
178             }
179         }
180         return domain;
181     }
182 
183     protected static Cookie rewriteForBrowser(Cookie cookie, DriverRequest request) {
184         String name = cookie.getName();
185         // Rewrite name if JSESSIONID because it will interfere with current
186         // server session
187         if ("JSESSIONID".equalsIgnoreCase(name)) {
188             name = "_" + name;
189         }
190 
191         // Rewrite domain
192         String domain =
193                 rewriteDomain(cookie.getDomain(), request.getBaseUrl().getHost(),
194                         UriUtils.extractHostName(request.getOriginalRequest().getRequestLine().getUri()));
195 
196         // Rewrite path
197         String originalPath = cookie.getPath();
198         String requestPath = UriUtils.getPath(request.getOriginalRequest().getRequestLine().getUri());
199         String path = originalPath;
200         if (requestPath == null || !requestPath.startsWith(originalPath)) {
201             path = "/";
202         }
203 
204         // Rewrite secure
205         boolean secure =
206                 (cookie.isSecure() && request.getOriginalRequest().getRequestLine().getUri().startsWith("https"));
207 
208         BasicClientCookie cookieToForward = new BasicClientCookie(name, cookie.getValue());
209         if (domain != null) {
210             cookieToForward.setDomain(domain);
211         }
212         cookieToForward.setPath(path);
213         cookieToForward.setSecure(secure);
214         cookieToForward.setComment(cookie.getComment());
215         cookieToForward.setVersion(cookie.getVersion());
216         cookieToForward.setExpiryDate(cookie.getExpiryDate());
217 
218         if (((BasicClientCookie) cookie).containsAttribute(CookieUtil.HTTP_ONLY_ATTR)) {
219             cookieToForward.setAttribute(CookieUtil.HTTP_ONLY_ATTR, "");
220         }
221 
222         if (LOG.isDebugEnabled()) {
223             // Ensure .toString is only called if debug enabled.
224             LOG.debug("Forwarding cookie {} -> {}", cookie.toString(), cookieToForward.toString());
225         }
226         return cookieToForward;
227     }
228 
229     private String toString(Cookie cookie) {
230         StringBuilder result = new StringBuilder(Parameters.SMALL_BUFFER_SIZE);
231         result.append(cookie.getName());
232         result.append("=");
233         result.append(cookie.getValue());
234         if (cookie.getDomain() != null) {
235             result.append(";domain=");
236             result.append(cookie.getDomain());
237         }
238         if (cookie.getPath() != null) {
239             result.append(";path=");
240             result.append(cookie.getPath());
241         }
242         if (cookie.getExpiryDate() != null) {
243             result.append(";expires=");
244             result.append(cookie.getExpiryDate());
245         }
246         if (cookie.getCommentURL() != null) {
247             result.append(";comment=");
248             result.append(cookie.getComment());
249         }
250         if (cookie.getCommentURL() != null) {
251             result.append(";comment=");
252             result.append(cookie.getCommentURL());
253         }
254         return result.toString();
255     }
256 
257     @Override
258     public boolean clearExpired(Date date, DriverRequest request) {
259         UserContext userContext = request.getUserContext();
260         BasicCookieStore cookies = (BasicCookieStore) userContext.getAttribute(COOKIES_LIST_SESSION_KEY);
261 
262         return cookies != null && cookies.clearExpired(date);
263 
264     }
265 
266     @Override
267     public void clear(DriverRequest request) {
268         UserContext userContext = request.getUserContext();
269         BasicCookieStore cookies = (BasicCookieStore) userContext.getAttribute(COOKIES_LIST_SESSION_KEY);
270         if (cookies != null) {
271             cookies.clear();
272             userContext.setAttribute(COOKIES_LIST_SESSION_KEY, cookies);
273         }
274     }
275 
276 }