ResponseUtil.java
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.tomcat.util.http;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.tomcat.util.http.parser.TokenList;
public class ResponseUtil {
private static final String VARY_HEADER = "vary";
private static final String VARY_ALL = "*";
private ResponseUtil() {
// Utility class. Hide default constructor.
}
public static void addVaryFieldName(MimeHeaders headers, String name) {
addVaryFieldName(new HeaderAdapter(headers), name);
}
public static void addVaryFieldName(HttpServletResponse response, String name) {
addVaryFieldName(new ResponseAdapter(response), name);
}
private static void addVaryFieldName(Adapter adapter, String name) {
Collection<String> varyHeaders = adapter.getHeaders(VARY_HEADER);
// Short-cut if only * has been set
if (varyHeaders.size() == 1 && varyHeaders.iterator().next().trim().equals(VARY_ALL)) {
// No need to add an additional field
return;
}
// Short-cut if no headers have been set
if (varyHeaders.size() == 0) {
adapter.addHeader(VARY_HEADER, name);
return;
}
// Short-cut if "*" is added
if (VARY_ALL.equals(name.trim())) {
adapter.setHeader(VARY_HEADER, VARY_ALL);
return;
}
// May be dealing with an application set header, or multiple headers.
// Header names overlap so can't use String.contains(). Have to parse
// the existing values, check if the new value is already present and
// then add it if not. The good news is field names are tokens which
// makes parsing simpler.
LinkedHashSet<String> fieldNames = new LinkedHashSet<>();
for (String varyHeader : varyHeaders) {
StringReader input = new StringReader(varyHeader);
try {
TokenList.parseTokenList(input, fieldNames);
} catch (IOException ioe) {
// Should never happen
}
}
if (fieldNames.contains(VARY_ALL)) {
// '*' has been added without removing other values. Optimise.
adapter.setHeader(VARY_HEADER, VARY_ALL);
return;
}
// Build single header to replace current multiple headers
// Replace existing header(s) to ensure any invalid values are removed
fieldNames.add(name);
StringBuilder varyHeader = new StringBuilder();
Iterator<String> iter = fieldNames.iterator();
// There must be at least one value as one is added just above
varyHeader.append(iter.next());
while (iter.hasNext()) {
varyHeader.append(',');
varyHeader.append(iter.next());
}
adapter.setHeader(VARY_HEADER, varyHeader.toString());
}
private interface Adapter {
Collection<String> getHeaders(String name);
void setHeader(String name, String value);
void addHeader(String name, String value);
}
private static final class HeaderAdapter implements Adapter {
private final MimeHeaders headers;
HeaderAdapter(MimeHeaders headers) {
this.headers = headers;
}
@Override
public Collection<String> getHeaders(String name) {
Enumeration<String> values = headers.values(name);
List<String> result = new ArrayList<>();
while (values.hasMoreElements()) {
result.add(values.nextElement());
}
return result;
}
@Override
public void setHeader(String name, String value) {
headers.setValue(name).setString(value);
}
@Override
public void addHeader(String name, String value) {
headers.addValue(name).setString(value);
}
}
private static final class ResponseAdapter implements Adapter {
private final HttpServletResponse response;
ResponseAdapter(HttpServletResponse response) {
this.response = response;
}
@Override
public Collection<String> getHeaders(String name) {
return response.getHeaders(name);
}
@Override
public void setHeader(String name, String value) {
response.setHeader(name, value);
}
@Override
public void addHeader(String name, String value) {
response.addHeader(name, value);
}
}
}