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

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

The 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.time.Instant;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import org.eclipse.jetty.http.ComplianceViolationException;
import org.eclipse.jetty.http.CookieCompliance;
import org.eclipse.jetty.http.HttpCookie;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.Syntax;
import org.eclipse.jetty.util.Attributes;
import org.eclipse.jetty.util.Index;
import org.eclipse.jetty.util.QuotedStringTokenizer;
import org.eclipse.jetty.util.StringUtil;

/**
 * 

Utility methods for server-side HTTP cookie handling.

*/ public final class HttpCookieUtils { /** * Name of context attribute with default SameSite cookie value */ public static final String SAME_SITE_DEFAULT_ATTRIBUTE = "org.eclipse.jetty.cookie.sameSiteDefault"; private static final Index KNOWN_ATTRIBUTES = new Index.Builder().caseSensitive(false) .with(HttpCookie.COMMENT_ATTRIBUTE) .with(HttpCookie.DOMAIN_ATTRIBUTE) .with(HttpCookie.EXPIRES_ATTRIBUTE) .with(HttpCookie.HTTP_ONLY_ATTRIBUTE) .with(HttpCookie.MAX_AGE_ATTRIBUTE) .with(HttpCookie.PATH_ATTRIBUTE) .with(HttpCookie.SAME_SITE_ATTRIBUTE) .with(HttpCookie.SECURE_ATTRIBUTE) .with(HttpCookie.PARTITIONED_ATTRIBUTE) .build(); // RFC 1123 format of epoch for the Expires attribute. private static final String EPOCH_EXPIRES = "Thu, 01 Jan 1970 00:00:00 GMT"; /** * Check that samesite is set on the cookie. If not, use a * context default value, if one has been set. * * @param cookie the cookie to check * @param attributes the context to check settings * @return either the original cookie, or a new one that has the samesit default set */ public static HttpCookie checkSameSite(HttpCookie cookie, Attributes attributes) { if (cookie == null || cookie.getSameSite() != null) return cookie; //sameSite is not set, use the default configured for the context, if one exists HttpCookie.SameSite contextDefault = getSameSiteDefault(attributes); if (contextDefault == null) return cookie; //no default set return HttpCookie.from(cookie, HttpCookie.SAME_SITE_ATTRIBUTE, contextDefault.getAttributeValue()); } /** * Get the default value for SameSite cookie attribute, if one * has been set for the given context. * * @param contextAttributes the context to check for default SameSite value * @return the default SameSite value or null if one does not exist * @throws IllegalStateException if the default value is not a permitted value */ public static HttpCookie.SameSite getSameSiteDefault(Attributes contextAttributes) { if (contextAttributes == null) return null; Object o = contextAttributes.getAttribute(SAME_SITE_DEFAULT_ATTRIBUTE); if (o == null) return null; if (o instanceof HttpCookie.SameSite) return (HttpCookie.SameSite)o; try { HttpCookie.SameSite samesite = Enum.valueOf(HttpCookie.SameSite.class, o.toString().trim().toUpperCase(Locale.ENGLISH)); contextAttributes.setAttribute(SAME_SITE_DEFAULT_ATTRIBUTE, samesite); return samesite; } catch (Exception e) { throw new IllegalStateException(e); } } public static String getSetCookie(HttpCookie httpCookie, CookieCompliance compliance) { if (compliance != null && compliance.getName().contains("RFC2965")) return getRFC2965SetCookie(httpCookie); return getRFC6265SetCookie(httpCookie); } public static String getRFC2965SetCookie(HttpCookie httpCookie) { // Check arguments String name = httpCookie.getName(); if (name == null || name.isEmpty()) throw new IllegalArgumentException("Invalid cookie name"); StringBuilder builder = new StringBuilder(); quoteIfNeededAndAppend(name, builder); builder.append('='); String value = httpCookie.getValue(); quoteIfNeededAndAppend(value, builder); // Look for domain and path fields and check if they need to be quoted. String domain = httpCookie.getDomain(); boolean hasDomain = domain != null && !domain.isEmpty(); boolean quoteDomain = hasDomain && isQuoteNeeded(domain); String path = httpCookie.getPath(); boolean hasPath = path != null && !path.isEmpty(); boolean quotePath = hasPath && isQuoteNeeded(path); // Upgrade the version if we have a comment or we need to quote value/path/domain or if they were already quoted int version = httpCookie.getVersion(); String comment = httpCookie.getComment(); if (version == 0 && (comment != null || isQuoteNeeded(name) || isQuoteNeeded(value) || quoteDomain || quotePath || QuotedStringTokenizer.isQuoted(name) || QuotedStringTokenizer.isQuoted(value) || QuotedStringTokenizer.isQuoted(path) || QuotedStringTokenizer.isQuoted(domain))) version = 1; if (version == 1) builder.append(";Version=1"); else if (version > 1) builder.append(";Version=").append(version); if (hasDomain) { builder.append(";Domain="); if (quoteDomain) HttpField.PARAMETER_TOKENIZER.quote(builder, domain); else builder.append(domain); } if (hasPath) { builder.append(";Path="); if (quotePath) HttpField.PARAMETER_TOKENIZER.quote(builder, path); else builder.append(path); } // Handle max-age and/or expires long maxAge = httpCookie.getMaxAge(); if (maxAge >= 0) { // Always add the Expires attribute too, as some // browsers do not handle max-age even with v1 cookies. builder.append(";Expires="); if (maxAge == 0) builder.append(EPOCH_EXPIRES); else builder.append(HttpCookie.formatExpires(Instant.now().plusSeconds(maxAge))); builder.append(";Max-Age="); builder.append(maxAge); } if (httpCookie.isSecure()) builder.append(";Secure"); if (httpCookie.isHttpOnly()) builder.append(";HttpOnly"); if (httpCookie.isPartitioned()) builder.append(";Partitioned"); HttpCookie.SameSite sameSite = httpCookie.getSameSite(); if (sameSite != null) builder.append(";SameSite=").append(sameSite.getAttributeValue()); if (comment != null) { builder.append(";Comment="); quoteIfNeededAndAppend(comment, builder); } return builder.toString(); } public static String getRFC6265SetCookie(HttpCookie httpCookie) { // Check arguments String name = httpCookie.getName(); if (name == null || name.isEmpty()) throw new IllegalArgumentException("Bad cookie name"); try { // Name is checked for legality by servlet spec, but can also be passed directly so check again for quoting // Per RFC6265, Cookie.name follows RFC2616 Section 2.2 token rules Syntax.requireValidRFC2616Token(name, "RFC6265 Cookie name"); } catch (IllegalArgumentException e) { throw new ComplianceViolationException(CookieCompliance.RFC6265, CookieCompliance.Violation.INVALID_COOKIES, "RFC6265 Cookie name must be a valid RFC2616 Token"); } // Ensure that Per RFC6265, Cookie.value follows syntax rules String value = httpCookie.getValue(); Syntax.requireValidRFC6265CookieValue(value); // Format value and params StringBuilder builder = new StringBuilder(); builder.append(name).append('=').append(value == null ? "" : value); // Append path String path = httpCookie.getPath(); if (path != null && !path.isEmpty()) builder.append("; Path=").append(path); // Append domain String domain = httpCookie.getDomain(); if (domain != null && !domain.isEmpty()) builder.append("; Domain=").append(domain); // Handle max-age and/or expires long maxAge = httpCookie.getMaxAge(); if (maxAge >= 0) { // Always use expires // This is required as some browser (M$ this means you!) don't handle max-age even with v1 cookies builder.append("; Expires="); if (maxAge == 0) builder.append(EPOCH_EXPIRES); else builder.append(HttpCookie.formatExpires(Instant.now().plusSeconds(maxAge))); if (maxAge > 0) { builder.append("; Max-Age="); builder.append(maxAge); } } // add the other fields if (httpCookie.isSecure()) builder.append("; Secure"); if (httpCookie.isHttpOnly()) builder.append("; HttpOnly"); if (httpCookie.isPartitioned()) builder.append("; Partitioned"); Map attributes = httpCookie.getAttributes(); String sameSiteAttr = attributes.get(HttpCookie.SAME_SITE_ATTRIBUTE); if (sameSiteAttr != null) { builder.append("; SameSite="); builder.append(sameSiteAttr); } else { HttpCookie.SameSite sameSite = httpCookie.getSameSite(); if (sameSite != null) { builder.append("; SameSite="); builder.append(sameSite.getAttributeValue()); } } //Add all other attributes for (Map.Entry e : attributes.entrySet()) { if (KNOWN_ATTRIBUTES.contains(e.getKey())) continue; builder.append("; ").append(e.getKey()); if (StringUtil.isNotBlank(e.getValue())) builder.append("=").append(e.getValue()); } return builder.toString(); } /** *

Whether a cookie name/value/attribute needs to be quoted.

* * @param text the text to check * @return whether the text needs to be quoted * @throws IllegalArgumentException if the text contains illegal characters */ private static boolean isQuoteNeeded(String text) { if (text == null || text.isEmpty()) return true; if (QuotedStringTokenizer.isQuoted(text)) return false; for (int i = 0; i < text.length(); i++) { char c = text.charAt(i); if ("\",;\\ \t".indexOf(c) >= 0) return true; if (c < 0x20 || c >= 0x7F) throw new IllegalArgumentException("Illegal character in cookie value"); } return false; } /** * Check if the Set-Cookie header represented as a string is for the name, domain and path given. * * @param setCookieHeader a Set-Cookie header * @param name the cookie name to check * @param domain the cookie domain to check * @param path the cookie path to check * @return true if all of the name, domain and path match the Set-Cookie header, false otherwise */ public static boolean match(String setCookieHeader, String name, String domain, String path) { //Parse the bare minimum List cookies = java.net.HttpCookie.parse(setCookieHeader); if (cookies.size() != 1) return false; java.net.HttpCookie cookie = cookies.get(0); return match(cookie.getName(), cookie.getDomain(), cookie.getPath(), name, domain, path); } /** * Check if the HttpCookie is for the given name, domain and path. * * @param cookie the jetty HttpCookie to check * @param name the cookie name to check * @param domain the cookie domain to check * @param path the cookie path to check * @return true if name, domain, and path, match all match the HttpCookie, false otherwise */ public static boolean match(HttpCookie cookie, String name, String domain, String path) { if (cookie == null) return false; return match(cookie.getName(), cookie.getDomain(), cookie.getPath(), name, domain, path); } /** * Check if all old parameters match the new parameters. * * @return true if old and new names match exactly and the old and new domains match case-insensitively and the paths match exactly */ private static boolean match(String oldName, String oldDomain, String oldPath, String newName, String newDomain, String newPath) { if (oldName == null) { if (newName != null) return false; } else if (!oldName.equals(newName)) return false; if (oldDomain == null) { if (newDomain != null) return false; } else if (!oldDomain.equalsIgnoreCase(newDomain)) return false; if (oldPath == null) return newPath == null; return oldPath.equals(newPath); } private static void quoteIfNeededAndAppend(String text, StringBuilder builder) { if (isQuoteNeeded(text)) HttpField.PARAMETER_TOKENIZER.quote(builder, text); else builder.append(text); } private HttpCookieUtils() { } /** * A {@link HttpField} that holds an {@link HttpHeader#SET_COOKIE} as a * {@link HttpCookie} instance, delaying any value generation until * {@link #getValue()} is called. */ public static class SetCookieHttpField extends HttpField { private final HttpCookie _cookie; private final String _value; public SetCookieHttpField(HttpCookie cookie, CookieCompliance compliance) { super(HttpHeader.SET_COOKIE, (String)null); this._cookie = cookie; // trigger compliance check this._value = getSetCookie(this._cookie, compliance); } public HttpCookie getHttpCookie() { return _cookie; } @Override public String getValue() { return this._value; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy