This is an automated email from the ASF dual-hosted git repository. andy pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/jena.git
commit b1eece13838b9463f37e633afc184ad4c93779ea Author: Andy Seaborne <a...@apache.org> AuthorDate: Thu May 2 11:31:36 2024 +0100 GH-2443: Update CrossOriginFilter code --- .../jena/fuseki/servlets/CrossOriginFilter.java | 292 +++++++++++++++++++-- 1 file changed, 275 insertions(+), 17 deletions(-) diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/CrossOriginFilter.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/CrossOriginFilter.java index 9b7abd08e8..305134f969 100644 --- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/CrossOriginFilter.java +++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/CrossOriginFilter.java @@ -16,17 +16,21 @@ * limitations under the License. */ -package org.apache.jena.fuseki.servlets; - -// This is a copy of Jetty's CrossOriginFilter - Fuseki needs something -// that works without Jetty on the classpath when running as a WAR file. -// Copy from Jetty 11.0.15 -// https://github.com/eclipse/jetty.project/blob/jetty-11.0.x/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CrossOriginFilter.java -// We elect to use and distribute under The Apache License v2.0. - -//Changes: +// This is a copy of Eclipse Jetty's CrossOriginFilter. Fuseki needs something +// that works without Jetty on the classpath. +// jena-fuseki-core is neutral to the servlet server implementation choice. +// +// This is a copy from Jetty 12.0.8 (where it is deprecated in favour of CrossOriginHandler) +// The license above applies only to local modifications. +// Changes: // * Package declaration -// * Functions from org.eclipse.jetty.utilStringUtil to make this class portable +// * Remove deprecation of CrossOriginFilter +// * Extract of from org.eclipse.jetty.util.StringUtil with the functions to make this file portable +// +// https://github.com/jetty/jetty.project/blob/jetty-12.0.x/jetty-ee10/jetty-ee10-servlets/src/main/java/org/eclipse/jetty/ee10/servlets/CrossOriginFilter.java +// https://github.com/jetty/jetty.project/blob/jetty-12.0.x/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java +// +// We elect to use and distribute under the Apache License v2.0. // // ======================================================================== @@ -41,7 +45,7 @@ package org.apache.jena.fuseki.servlets; // ======================================================================== // -//package org.eclipse.jetty.ee10.servlets; +package org.apache.jena.fuseki.servlets; import java.io.IOException; import java.util.ArrayList; @@ -61,10 +65,6 @@ import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import org.eclipse.jetty.http.HttpField; -import org.eclipse.jetty.http.HttpHeader; -import org.eclipse.jetty.http.PreEncodedHttpField; -import org.eclipse.jetty.util.StringUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -150,6 +150,10 @@ import org.slf4j.LoggerFactory; * </web-app> * </pre> */ +/* + * @deprecated Use {@link org.eclipse.jetty.server.handler.CrossOriginHandler} instead + */ +//@Deprecated public class CrossOriginFilter implements Filter { private static final Logger LOG = LoggerFactory.getLogger(CrossOriginFilter.class); @@ -182,7 +186,6 @@ public class CrossOriginFilter implements Filter private static final List<String> SIMPLE_HTTP_METHODS = Arrays.asList("GET", "POST", "HEAD"); private static final List<String> DEFAULT_ALLOWED_METHODS = Arrays.asList("GET", "POST", "HEAD"); private static final List<String> DEFAULT_ALLOWED_HEADERS = Arrays.asList("X-Requested-With", "Content-Type", "Accept", "Origin"); - private static final HttpField VARY_ORIGIN = new PreEncodedHttpField(HttpHeader.VARY, HttpHeader.ORIGIN.asString()); private boolean anyOriginAllowed; private boolean anyTimingOriginAllowed; @@ -303,7 +306,7 @@ public class CrossOriginFilter implements Filter private void handle(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { - response.addHeader(VARY_ORIGIN.getName(), VARY_ORIGIN.getValue()); + response.addHeader("Vary", ORIGIN_HEADER); String origin = request.getHeader(ORIGIN_HEADER); // Is it a cross origin request ? if (origin != null && isEnabled(request)) @@ -537,3 +540,258 @@ public class CrossOriginFilter implements Filter allowCredentials = false; } } + +// Extract of org.eclipse.jetty.util.StringUtil (12.0.8) in support of the CORS filter above. +class StringUtil { + + /** + * Replace chars within string. + * <p> + * Fast replacement for {@code java.lang.String#}{@link String#replace(char, char)} + * </p> + * + * @param str the input string + * @param find the char to look for + * @param with the char to replace with + * @return the now replaced string + */ + public static String replace(String str, char find, char with) + { + if (str == null) + return null; + + if (find == with) + return str; + + int c = 0; + int idx = str.indexOf(find, c); + if (idx == -1) + { + return str; + } + char[] chars = str.toCharArray(); + int len = chars.length; + for (int i = idx; i < len; i++) + { + if (chars[i] == find) + chars[i] = with; + } + return String.valueOf(chars); + } + + /** + * Replace substrings within string. + * <p> + * Fast replacement for {@code java.lang.String#}{@link String#replace(CharSequence, CharSequence)} + * </p> + * + * @param s the input string + * @param sub the string to look for + * @param with the string to replace with + * @return the now replaced string + */ + public static String replace(String s, String sub, String with) + { + if (s == null) + return null; + + int c = 0; + int i = s.indexOf(sub, c); + if (i == -1) + { + return s; + } + StringBuilder buf = new StringBuilder(s.length() + with.length()); + do + { + buf.append(s, c, i); + buf.append(with); + c = i + sub.length(); + } + while ((i = s.indexOf(sub, c)) != -1); + if (c < s.length()) + { + buf.append(s.substring(c)); + } + return buf.toString(); + } + + + /** + * Parse a CSV string using {@link #csvSplit(List, String, int, int)} + * + * @param s The string to parse + * @return An array of parsed values. + */ + public static String[] csvSplit(String s) + { + if (s == null) + return null; + return csvSplit(s, 0, s.length()); + } + + /** + * Parse a CSV string using {@link #csvSplit(List, String, int, int)} + * + * @param s The string to parse + * @param off The offset into the string to start parsing + * @param len The len in characters to parse + * @return An array of parsed values. + */ + public static String[] csvSplit(String s, int off, int len) + { + if (s == null) + return null; + if (off < 0 || len < 0 || off > s.length()) + throw new IllegalArgumentException(); + List<String> list = new ArrayList<>(); + csvSplit(list, s, off, len); + return list.toArray(new String[0]); + } + + enum CsvSplitState + { + PRE_DATA, QUOTE, SLOSH, DATA, WHITE, POST_DATA + } + + /** + * Split a quoted comma separated string to a list + * <p>Handle <a href="https://www.ietf.org/rfc/rfc4180.txt">rfc4180</a>-like + * CSV strings, with the exceptions:<ul> + * <li>quoted values may contain double quotes escaped with back-slash + * <li>Non-quoted values are trimmed of leading trailing white space + * <li>trailing commas are ignored + * <li>double commas result in a empty string value + * </ul> + * + * @param list The Collection to split to (or null to get a new list) + * @param s The string to parse + * @param off The offset into the string to start parsing + * @param len The len in characters to parse + * @return list containing the parsed list values + */ + public static List<String> csvSplit(List<String> list, String s, int off, int len) + { + if (list == null) + list = new ArrayList<>(); + CsvSplitState state = CsvSplitState.PRE_DATA; + StringBuilder out = new StringBuilder(); + int last = -1; + while (len > 0) + { + char ch = s.charAt(off++); + len--; + + switch (state) + { + case PRE_DATA -> + { + if ('"' == ch) + { + state = CsvSplitState.QUOTE; + } + else if (',' == ch) + { + list.add(""); + } + else if (!Character.isWhitespace(ch)) + { + state = CsvSplitState.DATA; + out.append(ch); + } + } + case DATA -> + { + if (Character.isWhitespace(ch)) + { + last = out.length(); + out.append(ch); + state = CsvSplitState.WHITE; + } + else if (',' == ch) + { + list.add(out.toString()); + out.setLength(0); + state = CsvSplitState.PRE_DATA; + } + else + { + out.append(ch); + } + } + case WHITE -> + { + if (Character.isWhitespace(ch)) + { + out.append(ch); + } + else if (',' == ch) + { + out.setLength(last); + list.add(out.toString()); + out.setLength(0); + state = CsvSplitState.PRE_DATA; + } + else + { + state = CsvSplitState.DATA; + out.append(ch); + last = -1; + } + } + case QUOTE -> + { + if ('\\' == ch) + { + state = CsvSplitState.SLOSH; + } + else if ('"' == ch) + { + list.add(out.toString()); + out.setLength(0); + state = CsvSplitState.POST_DATA; + } + else + { + out.append(ch); + } + } + case SLOSH -> + { + out.append(ch); + state = CsvSplitState.QUOTE; + } + case POST_DATA -> + { + if (',' == ch) + { + state = CsvSplitState.PRE_DATA; + } + } + default -> throw new IllegalStateException(state.toString()); + } + } + switch (state) + { + case PRE_DATA: + case POST_DATA: + break; + + case DATA: + case QUOTE: + case SLOSH: + list.add(out.toString()); + break; + + case WHITE: + out.setLength(last); + list.add(out.toString()); + break; + + default: + throw new IllegalStateException(state.toString()); + } + + return list; + } +}