All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.jetty.server.CookieCache Maven / Gradle / Ivy

There is a newer version: 12.1.0.alpha0
Show newest version
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//

package org.eclipse.jetty.server;

import java.lang.reflect.Array;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.function.Function;

import org.eclipse.jetty.http.BadMessageException;
import org.eclipse.jetty.http.ComplianceViolation;
import org.eclipse.jetty.http.CookieCompliance;
import org.eclipse.jetty.http.CookieParser;
import org.eclipse.jetty.http.HttpCookie;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Cookie parser
 * 

Optimized stateful cookie parser. * If the added fields are identical to those last added (as strings), then the * cookies are not re-parsed. * */ public class CookieCache extends AbstractList implements CookieParser.Handler, ComplianceViolation.Listener { /** * Get the core HttpCookies for a request. * Cookies may be cached as a {@link Request#getAttribute(String) request attribute}, failing that they may be * cached in the {@link Components#getCache() Component cache}, in which case they will be checked to see if they * have changed since a previous request. Otherwise, they are parsed from the request headers and both caches updated. * @param request The request to obtain cookies from * @return A list of core {@link HttpCookie}s from the request. * @see #getApiCookies(Request, Class, Function) */ public static List getCookies(Request request) { @SuppressWarnings("unchecked") List cookies = (List)request.getAttribute(Request.COOKIE_ATTRIBUTE); if (cookies != null) return cookies; CookieCache cookieCache = (CookieCache)request.getComponents().getCache().getAttribute(Request.COOKIE_ATTRIBUTE); if (cookieCache == null) { cookieCache = new CookieCache(request.getConnectionMetaData().getHttpConfiguration().getRequestCookieCompliance()); request.getComponents().getCache().setAttribute(Request.COOKIE_ATTRIBUTE, cookieCache); } cookieCache.parseCookies(request.getHeaders(), HttpChannel.from(request).getComplianceViolationListener()); request.setAttribute(Request.COOKIE_ATTRIBUTE, cookieCache); return cookieCache; } /** * Get the API specific cookies for a request. * Internally the same caching/parsing is done as by {@link #getCookies(Request)} and the core {@link HttpCookie}s are * obtained. The passed {@code convertor} function is used to covert the core {@link HttpCookie}s to API specific cookies * and the results cached along with the core {@link HttpCookie}s * @param request The request to get the cookies from. * @param cookieClass The class of the cookie API * @param convertor A function to convert from a {@link HttpCookie} to an API cookie of type {@code cookieClass}. The * function may return null if the cookie is not compliant with the API. * @param The class of the cookie API * @return An array of API specific cookies. */ public static C[] getApiCookies(Request request, Class cookieClass, Function convertor) { if (request == null) return null; CookieCache cookieCache = (CookieCache)request.getAttribute(Request.COOKIE_ATTRIBUTE); if (cookieCache == null) { cookieCache = (CookieCache)request.getComponents().getCache().getAttribute(Request.COOKIE_ATTRIBUTE); if (cookieCache == null) { cookieCache = new CookieCache(request.getConnectionMetaData().getHttpConfiguration().getRequestCookieCompliance()); request.getComponents().getCache().setAttribute(Request.COOKIE_ATTRIBUTE, cookieCache); } cookieCache.parseCookies(request.getHeaders(), HttpChannel.from(request).getComplianceViolationListener()); request.setAttribute(Request.COOKIE_ATTRIBUTE, cookieCache); } return cookieCache.getApiCookies(cookieClass, convertor); } protected static final Logger LOG = LoggerFactory.getLogger(CookieCache.class); protected final List _rawFields = new ArrayList<>(); private final CookieParser _parser; private List _httpCookies = Collections.emptyList(); private Map, Object[]> _apiCookies; private List _violations; public CookieCache() { this(CookieCompliance.RFC6265); } public CookieCache(CookieCompliance compliance) { _parser = CookieParser.newParser(this, compliance, this); } @Override public HttpCookie get(int index) { return _httpCookies.get(index); } @Override public int size() { return _httpCookies.size(); } @Override public void onComplianceViolation(ComplianceViolation.Event event) { if (_violations == null) _violations = new ArrayList<>(); _violations.add(event); } @Override public void addCookie(String cookieName, String cookieValue, int cookieVersion, String cookieDomain, String cookiePath, String cookieComment) { if (StringUtil.isEmpty(cookieDomain) && StringUtil.isEmpty(cookiePath) && cookieVersion <= 0 && StringUtil.isEmpty(cookieComment)) _httpCookies.add(HttpCookie.from(cookieName, cookieValue)); else { Map attributes = new HashMap<>(); if (!StringUtil.isEmpty(cookieDomain)) attributes.put(HttpCookie.DOMAIN_ATTRIBUTE, cookieDomain); if (!StringUtil.isEmpty(cookiePath)) attributes.put(HttpCookie.PATH_ATTRIBUTE, cookiePath); if (!StringUtil.isEmpty(cookieComment)) attributes.put(HttpCookie.COMMENT_ATTRIBUTE, cookieComment); _httpCookies.add(HttpCookie.from(cookieName, cookieValue, cookieVersion, attributes)); } } List getCookies(HttpFields headers) { parseCookies(headers, ComplianceViolation.Listener.NOOP); return _httpCookies; } public void parseCookies(HttpFields headers, ComplianceViolation.Listener complianceViolationListener) { boolean building = false; ListIterator raw = _rawFields.listIterator(); // For each of the headers for (HttpField field : headers) { // skip non cookie headers if (!HttpHeader.COOKIE.equals(field.getHeader())) continue; // skip blank cookie headers String value = field.getValue(); if (StringUtil.isBlank(value)) continue; // If we are building a new cookie list if (building) { // just add the raw string to the list to be parsed later _rawFields.add(value); continue; } // otherwise we are checking against previous cookies. // Is there a previous raw cookie to compare with? if (!raw.hasNext()) { // No, so we will flip to building state and add to the raw fields we already have. building = true; _rawFields.add(value); continue; } // If there is a previous raw cookie and it is the same, then continue checking if (value.equals(raw.next())) continue; // otherwise there is a difference in the previous raw cookie field // so switch to building mode and remove all subsequent raw fields // then add the current raw field to be built later. building = true; raw.remove(); while (raw.hasNext()) { raw.next(); raw.remove(); } _rawFields.add(value); } // If we are not building, but there are still more unmatched raw fields, then a field was deleted if (!building && raw.hasNext()) { // switch to building mode and delete the unmatched raw fields building = true; while (raw.hasNext()) { raw.next(); raw.remove(); } } // If we ended up in building mode, reparse the cookie list from the raw fields. if (building) { _httpCookies = new ArrayList<>(); _apiCookies = null; try { if (_violations != null) _violations.clear(); _parser.parseFields(_rawFields); } catch (CookieParser.InvalidCookieException invalidCookieException) { throw new BadMessageException(invalidCookieException.getMessage(), invalidCookieException); } } if (_violations != null && !_violations.isEmpty()) _violations.forEach(complianceViolationListener::onComplianceViolation); } public C[] getApiCookies(Class apiClass, Function convertor) { // No APIs if no Cookies if (_httpCookies.isEmpty()) return null; // If only the core APIs have been used, then no apiCookie map has been created if (_apiCookies == null) { // When a cookie API is ued, the most common case in only a single API, so used a cheap Map C[] apiCookies = convert(apiClass, convertor); _apiCookies = Map.of(apiClass, apiCookies); return apiCookies; } @SuppressWarnings("unchecked") C[] apiCookies = (C[])_apiCookies.get(apiClass); if (apiCookies == null) { // Only in the case of cross environment dispatch will more than 1 API be needed, so only invest in a real // map when we know it is required. if (_apiCookies.size() == 1) _apiCookies = new HashMap<>(_apiCookies); apiCookies = convert(apiClass, convertor); _apiCookies.put(apiClass, apiCookies); } return apiCookies; } private C[] convert(Class apiClass, Function convertor) { @SuppressWarnings("unchecked") C[] apiCookies = (C[])Array.newInstance(apiClass, _httpCookies.size()); int i = 0; for (HttpCookie httpCookie : _httpCookies) { C apiCookie = convertor.apply(httpCookie); // Exclude any API cookies that are not convertable to that API if (apiCookie == null) apiCookies = Arrays.copyOf(apiCookies, apiCookies.length - 1); else apiCookies[i++] = apiCookie; } return apiCookies; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy