You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by ma...@apache.org on 2022/12/08 19:32:05 UTC

[tomcat] branch main updated (66a4bcab40 -> 6f5c624c1f)

This is an automated email from the ASF dual-hosted git repository.

markt pushed a change to branch main
in repository https://gitbox.apache.org/repos/asf/tomcat.git


    from 66a4bcab40 JEP code is now in Java 20
     new beed6c936e Add an RFC 8941 structured field parser.
     new 6f5c624c1f Add a parser for the RFC 9218 priority field

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../util/http/parser/LocalStrings.properties       |  14 +
 .../apache/tomcat/util/http/parser/Priority.java   |  89 +++
 .../tomcat/util/http/parser/StructuredField.java   | 599 +++++++++++++++++++++
 .../http/parser/TesterHttpWgStructuredField.java   | 130 +++++
 webapps/docs/changelog.xml                         |   7 +
 5 files changed, 839 insertions(+)
 create mode 100644 java/org/apache/tomcat/util/http/parser/Priority.java
 create mode 100644 java/org/apache/tomcat/util/http/parser/StructuredField.java
 create mode 100644 test/org/apache/tomcat/util/http/parser/TesterHttpWgStructuredField.java


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org


[tomcat] 01/02: Add an RFC 8941 structured field parser.

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

markt pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tomcat.git

commit beed6c936ece5d540365991d00fe35459e5c3505
Author: Mark Thomas <ma...@apache.org>
AuthorDate: Thu Dec 8 19:23:00 2022 +0000

    Add an RFC 8941 structured field parser.
---
 .../util/http/parser/LocalStrings.properties       |  14 +
 .../tomcat/util/http/parser/StructuredField.java   | 599 +++++++++++++++++++++
 .../http/parser/TesterHttpWgStructuredField.java   | 130 +++++
 webapps/docs/changelog.xml                         |   3 +
 4 files changed, 746 insertions(+)

diff --git a/java/org/apache/tomcat/util/http/parser/LocalStrings.properties b/java/org/apache/tomcat/util/http/parser/LocalStrings.properties
index ae024240d9..325e7da57f 100644
--- a/java/org/apache/tomcat/util/http/parser/LocalStrings.properties
+++ b/java/org/apache/tomcat/util/http/parser/LocalStrings.properties
@@ -44,3 +44,17 @@ http.tooFewHextets=An IPv6 address must consist of 8 hextets but this address co
 http.tooManyColons=An IPv6 address may not contain more than 2 sequential colon characters.
 http.tooManyDoubleColons=An IPv6 address may only contain a single '::' sequence.
 http.tooManyHextets=The IPv6 address contains [{0}] hextets but a valid IPv6 address may not have more than 8.
+
+sf.bareitem.invalidCharacter=The invalid character [{0}] was found parsing when start of a bare item
+sf.base64.invalidCharacter=The [{0}] character is not valid inside a base64 sequence
+sf.boolean.invalidCharacter=The [{0}] character is not a valid boolean value
+sf.invalidCharacter=The [{0}] character is not valid here
+sf.key.invalidFirstCharacter=The invalid character [{0}] was found parsing when start of a key
+sf.numeric.decimalInvalidFinal=The final character of a decimal value must be a digit
+sf.numeric.decimalPartTooLong=More than 3 digits after the decimal point
+sf.numeric.decimalTooLong=More than 16 characters found in a decimal
+sf.numeric.integerTooLong=More than 15 digits found in an integer
+sf.numeric.integralPartTooLong=More than 12 digits found in the integral part of a decimal
+sf.numeric.invalidCharacter=The invalid character [{0}] was found parsing a numeric value where a digit was expected
+sf.string.invalidCharacter=The [{0}] character is not valid inside a string
+sf.string.invalidEscape=The [{0}] character must not be escaped
diff --git a/java/org/apache/tomcat/util/http/parser/StructuredField.java b/java/org/apache/tomcat/util/http/parser/StructuredField.java
new file mode 100644
index 0000000000..5b8a695440
--- /dev/null
+++ b/java/org/apache/tomcat/util/http/parser/StructuredField.java
@@ -0,0 +1,599 @@
+/*
+ * 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.parser;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.tomcat.util.codec.binary.Base64;
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * Parsing of structured fields as per RFC 8941.
+ * <p>
+ * The parsing implementation is complete but not all elements are currently
+ * exposed via getters. Additional getters will be added as required as the
+ * use of structured fields expands.
+ * <p>
+ * The serialization of structured fields has not been implemented.
+ */
+public class StructuredField {
+
+    private static final StringManager sm = StringManager.getManager(StructuredField.class);
+
+    private static final int ARRAY_SIZE = 128;
+
+    private static final boolean[] IS_KEY_FIRST = new boolean[ARRAY_SIZE];
+    private static final boolean[] IS_KEY = new boolean[ARRAY_SIZE];
+    private static final boolean[] IS_OWS = new boolean[ARRAY_SIZE];
+    private static final boolean[] IS_BASE64 = new boolean[ARRAY_SIZE];
+    private static final boolean[] IS_TOKEN = new boolean[ARRAY_SIZE];
+
+    static {
+        for (int i = 0; i < ARRAY_SIZE; i++) {
+            if (i == '*' || i >= 'a' && i <= 'z') {
+                IS_KEY_FIRST[i] = true;
+                IS_KEY[i] = true;
+            } else if (i  >= '0' && i <= '9' || i == '_' || i == '-' || i == '.'){
+                IS_KEY[i] = true;
+            }
+        }
+
+        for (int i = 0; i < ARRAY_SIZE; i++) {
+            if (i == 9 || i == ' ') {
+                IS_OWS[i] = true;
+            }
+        }
+
+        for (int i = 0; i < ARRAY_SIZE; i++) {
+            if (i == '+' || i == '/' || i >= '0' && i <= '9' || i == '=' || i >= 'A' && i <= 'Z' ||
+                    i >= 'a' && i <= 'z') {
+                IS_BASE64[i] = true;
+            }
+        }
+
+        for (int i = 0; i < ARRAY_SIZE; i++) {
+            if (HttpParser.isToken(i) || i == ':' || i == '/') {
+                IS_TOKEN[i] = true;
+            }
+        }
+    }
+
+
+    static SfList parseSfList(Reader input) throws IOException {
+        skipSP(input);
+
+        SfList result = new SfList();
+
+        if (peek(input) != -1) {
+            while (true) {
+                SfListMember listMember = parseSfListMember(input);
+                result.addListMember(listMember);
+                skipOWS(input);
+                if (peek(input) == -1) {
+                    break;
+                }
+                requireChar(input, ',');
+                skipOWS(input);
+                requireNotChar(input, -1);
+            }
+        }
+
+        skipSP(input);
+        requireChar(input, -1);
+        return result;
+    }
+
+
+    // Item or inner list
+    static SfListMember parseSfListMember(Reader input) throws IOException {
+        SfListMember listMember;
+        if (peek(input) == '(') {
+            listMember = parseSfInnerList(input);
+        } else {
+            listMember = parseSfBareItem(input);
+        }
+        parseSfParameters(input, listMember);
+        return listMember;
+    }
+
+
+    static SfInnerList parseSfInnerList(Reader input) throws IOException {
+        requireChar(input, '(');
+
+        SfInnerList innerList = new SfInnerList();
+
+        while (true) {
+            skipSP(input);
+            if (peek(input) == ')') {
+                break;
+            }
+            SfItem<?> item = parseSfBareItem(input);
+            parseSfParameters(input, item);
+            innerList.addListItem(item);
+            input.mark(1);
+            requireChar(input, ' ', ')');
+            input.reset();
+        }
+        requireChar(input, ')');
+
+        return innerList;
+    }
+
+
+    static SfDictionary parseSfDictionary(Reader input) throws IOException {
+        skipSP(input);
+
+        SfDictionary result = new SfDictionary();
+
+        if (peek(input) != -1) {
+            while (true) {
+                String key = parseSfKey(input);
+                SfListMember listMember;
+                input.mark(1);
+                int c = input.read();
+                if (c == '=') {
+                    listMember = parseSfListMember(input);
+                } else {
+                    listMember = new SfBoolean(true);
+                    input.reset();
+                }
+                parseSfParameters(input, listMember);
+                result.addDictionaryMember(key, listMember);
+                skipOWS(input);
+                if (peek(input) == -1) {
+                    break;
+                }
+                requireChar(input, ',');
+                skipOWS(input);
+                requireNotChar(input, -1);
+            }
+        }
+
+        skipSP(input);
+        requireChar(input, -1);
+        return result;
+    }
+
+
+    static SfItem<?> parseSfItem(Reader input) throws IOException {
+        skipSP(input);
+
+        SfItem<?> item = parseSfBareItem(input);
+        parseSfParameters(input, item);
+
+        skipSP(input);
+        requireChar(input, -1);
+        return item;
+    }
+
+
+    static SfItem<?> parseSfBareItem(Reader input) throws IOException {
+        int c = input.read();
+
+        SfItem<?> item;
+        if (c == '-' || HttpParser.isNumeric(c)) {
+            item = parseSfNumeric(input, c);
+        } else if (c == '\"') {
+            item = parseSfString(input);
+        } else if (c == '*' || HttpParser.isAlpha(c)) {
+            item = parseSfToken(input, c);
+        } else if (c == ':') {
+            item = parseSfByteSequence(input);
+        } else if (c == '?') {
+            item = parseSfBoolean(input);
+        } else {
+            throw new IllegalArgumentException(
+                    sm.getString("sf.bareitem.invalidCharacter", String.format("\\u%40X", Integer.valueOf(c))));
+        }
+
+        return item;
+    }
+
+
+    static void parseSfParameters(Reader input, SfListMember listMember) throws IOException {
+        while (true) {
+            if (peek(input) != ';') {
+                break;
+            }
+            requireChar(input, ';');
+            skipSP(input);
+            String key = parseSfKey(input);
+            SfItem<?> item;
+            input.mark(1);
+            int c = input.read();
+            if (c == '=') {
+                item = parseSfBareItem(input);
+            } else {
+                item = new SfBoolean(true);
+                input.reset();
+            }
+            listMember.addParameter(key, item);
+        }
+    }
+
+
+    static String parseSfKey(Reader input) throws IOException {
+        StringBuilder result = new StringBuilder();
+
+        input.mark(1);
+        int c = input.read();
+        if (!isKeyFirst(c)) {
+            throw new IllegalArgumentException(
+                    sm.getString("sf.key.invalidFirstCharacter", String.format("\\u%40X", Integer.valueOf(c))));
+        }
+
+        while (c != -1 && isKey(c)) {
+            result.append((char) c);
+            input.mark(1);
+            c = input.read();
+        }
+        input.reset();
+        return result.toString();
+    }
+
+
+    static SfItem<?> parseSfNumeric(Reader input, int first) throws IOException {
+        int sign = 1;
+        boolean integer = true;
+        int decimalPos = 0;
+
+        StringBuilder result = new StringBuilder();
+
+        int c;
+        if (first == '-') {
+            sign = -1;
+            c = input.read();
+        } else {
+            c = first;
+        }
+
+        if (!HttpParser.isNumeric(c)) {
+            throw new IllegalArgumentException(
+                    sm.getString("sf.numeric.invalidCharacter", String.format("\\u%40X", Integer.valueOf(c))));
+        }
+        result.append((char) c);
+        input.mark(1);
+        c = input.read();
+
+        while (c != -1) {
+            if (HttpParser.isNumeric(c)) {
+                result.append((char) c);
+            } else if (integer && c == '.') {
+                if (result.length() > 12) {
+                    throw new IllegalArgumentException(sm.getString("sf.numeric.integralPartTooLong"));
+                }
+                integer = false;
+                result.append((char) c);
+                decimalPos = result.length();
+            } else {
+                input.reset();
+                break;
+            }
+            if (integer && result.length() > 15) {
+                throw new IllegalArgumentException(sm.getString("sf.numeric.integerTooLong"));
+            }
+            if (!integer && result.length() > 16) {
+                throw new IllegalArgumentException(sm.getString("sf.numeric.decimalTooLong"));
+            }
+            input.mark(1);
+            c = input.read();
+        }
+
+        if (integer) {
+            return new SfInteger(Long.parseLong(result.toString()) * sign);
+        }
+
+        if (result.charAt(result.length() - 1) == '.') {
+            throw new IllegalArgumentException(sm.getString("sf.numeric.decimalInvalidFinal"));
+        }
+
+        if (result.length() - decimalPos > 3) {
+            throw new IllegalArgumentException(sm.getString("sf.numeric.decimalPartTooLong"));
+        }
+
+        return new SfDecimal(Double.parseDouble(result.toString()) * sign);
+    }
+
+
+    static SfString parseSfString(Reader input) throws IOException {
+        // It is known first character was '"'
+        StringBuilder result = new StringBuilder();
+
+        while (true) {
+            int c = input.read();
+            if (c == '\\') {
+                requireNotChar(input, -1);
+                c = input.read();
+                if (c != '\\' && c != '\"') {
+                    throw new IllegalArgumentException(
+                            sm.getString("sf.string.invalidEscape", String.format("\\u%40X", Integer.valueOf(c))));
+                }
+            } else {
+                if (c == '\"') {
+                    break;
+                }
+                // This test also covers unexpected EOF
+                if (c < 32 || c > 126) {
+                    throw new IllegalArgumentException(
+                            sm.getString("sf.string.invalidCharacter", String.format("\\u%40X", Integer.valueOf(c))));
+                }
+            }
+            result.append((char) c);
+        }
+
+        return new SfString(result.toString());
+    }
+
+
+    static SfToken parseSfToken(Reader input, int first) throws IOException {
+        // It is known first character is valid
+        StringBuilder result = new StringBuilder();
+
+        result.append((char) first);
+        while (true) {
+            input.mark(1);
+            int c = input.read();
+            if (!isToken(c)) {
+                input.reset();
+                break;
+            }
+            result.append((char) c);
+        }
+
+        return new SfToken(result.toString());
+    }
+
+
+    static SfByteSequence parseSfByteSequence(Reader input) throws IOException {
+        // It is known first character was ':'
+        StringBuilder base64 = new StringBuilder();
+
+        while (true) {
+            int c = input.read();
+
+            if (c == ':') {
+                break;
+            } else if (isBase64(c)) {
+                base64.append((char) c);
+            } else {
+                throw new IllegalArgumentException(
+                        sm.getString("sf.base64.invalidCharacter", String.format("\\u%40X", Integer.valueOf(c))));
+            }
+        }
+
+        return new SfByteSequence(Base64.decodeBase64(base64.toString()));
+    }
+
+
+    static SfBoolean parseSfBoolean(Reader input) throws IOException {
+        // It is known first character was '?'
+        int c = input.read();
+
+        if (c == '1') {
+            return new SfBoolean(true);
+        } else if (c == '0') {
+            return new SfBoolean(false);
+        } else {
+            throw new IllegalArgumentException(
+                    sm.getString("sf.boolean.invalidCharacter", String.format("\\u%40X", Integer.valueOf(c))));
+        }
+    }
+
+
+    static void skipSP(Reader input) throws IOException {
+        input.mark(1);
+        int c = input.read();
+        while (c == 32) {
+            input.mark(1);
+            c = input.read();
+        }
+        input.reset();
+    }
+
+
+    static void skipOWS(Reader input) throws IOException {
+        input.mark(1);
+        int c = input.read();
+        while (isOws(c)) {
+            input.mark(1);
+            c = input.read();
+        }
+        input.reset();
+    }
+
+
+    static void requireChar(Reader input, int... required) throws IOException {
+        int c = input.read();
+        for (int r : required) {
+            if (c == r) {
+                return;
+            }
+        }
+        throw new IllegalArgumentException(
+                sm.getString("sf.invalidCharacter", String.format("\\u%40X", Integer.valueOf(c))));
+    }
+
+
+    static void requireNotChar(Reader input, int required) throws IOException {
+        input.mark(1);
+        int c = input.read();
+        if (c == required) {
+            throw new IllegalArgumentException(
+                    sm.getString("sf.invalidCharacter", String.format("\\u%40X", Integer.valueOf(c))));
+        }
+        input.reset();
+    }
+
+
+    static int peek(Reader input) throws IOException {
+        input.mark(1);
+        int c = input.read();
+        input.reset();
+        return c;
+    }
+
+
+    static boolean isKeyFirst(int c) {
+        try {
+            return IS_KEY_FIRST[c];
+        } catch (ArrayIndexOutOfBoundsException ex) {
+            return false;
+        }
+    }
+
+
+    static boolean isKey(int c) {
+        try {
+            return IS_KEY[c];
+        } catch (ArrayIndexOutOfBoundsException ex) {
+            return false;
+        }
+    }
+
+
+    static boolean isOws(int c) {
+        try {
+            return IS_OWS[c];
+        } catch (ArrayIndexOutOfBoundsException ex) {
+            return false;
+        }
+    }
+
+
+    static boolean isBase64(int c) {
+        try {
+            return IS_BASE64[c];
+        } catch (ArrayIndexOutOfBoundsException ex) {
+            return false;
+        }
+    }
+
+
+    static boolean isToken(int c) {
+        try {
+            return IS_TOKEN[c];
+        } catch (ArrayIndexOutOfBoundsException ex) {
+            return false;
+        }
+    }
+
+
+    private StructuredField() {
+        // Utility class. Hide default constructor.
+    }
+
+
+    static class SfDictionary {
+        private Map<String,SfListMember> dictionary = new LinkedHashMap<>();
+
+        void addDictionaryMember(String key, SfListMember value) {
+            dictionary.put(key, value);
+        }
+
+        SfListMember getDictionaryMember(String key) {
+            return dictionary.get(key);
+        }
+    }
+
+    static class SfList {
+        private List<SfListMember> listMembers = new ArrayList<>();
+
+        void addListMember(SfListMember listMember) {
+            listMembers.add(listMember);
+        }
+    }
+
+    static class SfListMember {
+        private Map<String,SfItem<?>> parameters = null;
+
+        void addParameter(String key, SfItem<?> value) {
+            if (parameters == null) {
+                parameters = new LinkedHashMap<>();
+            }
+            parameters.put(key, value);
+        }
+    }
+
+    static class SfInnerList extends SfListMember {
+        List<SfItem<?>> listItems = new ArrayList<>();
+
+        SfInnerList() {
+            // Default constructor is NO-OP
+        }
+
+        void addListItem(SfItem<?> item) {
+            listItems.add(item);
+        }
+
+        List<SfItem<?>> getListItem() {
+            return listItems;
+        }
+    }
+
+    abstract static class SfItem<T> extends SfListMember {
+        private final T value;
+
+        SfItem(T value) {
+            this.value = value;
+        }
+
+        T getVaue() {
+            return value;
+        }
+    }
+
+    static class SfInteger extends SfItem<Long> {
+        SfInteger(long value) {
+            super(Long.valueOf(value));
+        }
+    }
+
+    static class SfDecimal extends SfItem<Double> {
+        SfDecimal(double value) {
+            super(Double.valueOf(value));
+        }
+    }
+
+    static class SfString extends SfItem<String> {
+        SfString(String value) {
+            super(value);
+        }
+    }
+
+    static class SfToken extends SfItem<String> {
+        SfToken(String value) {
+            super(value);
+        }
+    }
+
+    static class SfByteSequence extends SfItem<byte[]> {
+        SfByteSequence(byte[] value) {
+            super(value);
+        }
+    }
+
+    static class SfBoolean extends SfItem<Boolean> {
+        SfBoolean(boolean value) {
+            super(Boolean.valueOf(value));
+        }
+    }
+}
diff --git a/test/org/apache/tomcat/util/http/parser/TesterHttpWgStructuredField.java b/test/org/apache/tomcat/util/http/parser/TesterHttpWgStructuredField.java
new file mode 100644
index 0000000000..794d180934
--- /dev/null
+++ b/test/org/apache/tomcat/util/http/parser/TesterHttpWgStructuredField.java
@@ -0,0 +1,130 @@
+/*
+ * 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.parser;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.Reader;
+import java.io.StringReader;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import org.apache.tomcat.util.buf.StringUtils;
+import org.apache.tomcat.util.json.JSONParser;
+
+/*
+ * Not run automatically (due to name) as if requires a local git clone of
+ * https://github.com/httpwg/structured-field-tests
+ */
+public class TesterHttpWgStructuredField {
+
+    private static final String testsPath = System.getProperty("user.home") + "/repos/httpwg-sf-tests";
+
+
+    @Test
+    public void test() throws Exception {
+        File testDir = new File(testsPath);
+        doTestDirectory(testDir);
+    }
+
+
+    private void doTestDirectory(File directory) throws Exception {
+        for (File file : directory.listFiles()) {
+            if (file.isDirectory()) {
+                if (!file.getName().equals("serialisation-tests")) {
+                    doTestDirectory(file);
+                }
+            } else if (file.isFile()) {
+                if (file.getName().endsWith(".json")) {
+                    doTestFile(file);
+                }
+            }
+        }
+    }
+
+
+    private void doTestFile(File file) throws Exception {
+        System.out.println(file.getAbsolutePath());
+
+        try (FileInputStream fis = new FileInputStream(file)) {
+            JSONParser parser = new JSONParser(fis);
+            List<Object> array = parser.parseArray();
+            for (Object obj : array) {
+                if (obj instanceof Map) {
+                    doTestMap((Map<?,?>) obj);
+                } else {
+                    Assert.fail();
+                }
+            }
+        }
+    }
+
+
+    private void doTestMap(Map<?,?> map) throws Exception {
+        String name = (String) map.get("name");
+        @SuppressWarnings("unchecked")
+        List<String> rawLines = (List<String>) map.get("raw");
+        String headerType = (String) map.get("header_type");
+        Boolean mustFail = ((Boolean) map.get("must_fail"));
+        if (mustFail == null) {
+            mustFail = Boolean.FALSE;
+        }
+        Boolean canFail = ((Boolean) map.get("can_fail"));
+        if (canFail == null) {
+            canFail = Boolean.FALSE;
+        }
+        String raw = StringUtils.join(rawLines);
+        /*
+         * The simple JSON parser may not be handling escape sequences
+         * correctly.
+         */
+        String unescaped = raw.replace("\\\"", "\"");
+        unescaped = unescaped.replace("\\b", "\u0008");
+        unescaped = unescaped.replace("\\t", "\t");
+        unescaped = unescaped.replace("\\n", "\n");
+        unescaped = unescaped.replace("\\f", "\u000c");
+        unescaped = unescaped.replace("\\r", "\r");
+        unescaped = unescaped.replace("\\\\", "\\");
+        Reader input = new StringReader(unescaped);
+
+        try {
+            switch (headerType) {
+            case "item": {
+                StructuredField.parseSfItem(input);
+                break;
+            }
+            case "list": {
+                StructuredField.parseSfList(input);
+                break;
+            }
+            case "dictionary": {
+                StructuredField.parseSfDictionary(input);
+                break;
+            }
+            default:
+                System.out.println("Type unsupported " + headerType);
+            }
+        } catch (Exception e) {
+            Assert.assertTrue(name + ": raw [" + unescaped + "]", mustFail.booleanValue() || canFail.booleanValue());
+            return;
+        }
+        Assert.assertFalse(name + ": raw [" + unescaped + "]", mustFail.booleanValue());
+    }
+}
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 2a8128d48d..7bdd1cc6dd 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -112,6 +112,9 @@
         cookie value as part of the value as required by RFC 6265 and explicitly
         clarified in RFC 6265bis. (markt)
       </fix>
+      <add>
+        Add an RFC 8941 structured field parser. (markt)
+      </add>
     </changelog>
   </subsection>
   <subsection name="Other">


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org


[tomcat] 02/02: Add a parser for the RFC 9218 priority field

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

markt pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tomcat.git

commit 6f5c624c1f2619d4b5321402b635a38c31005ce2
Author: Mark Thomas <ma...@apache.org>
AuthorDate: Thu Dec 8 19:24:33 2022 +0000

    Add a parser for the RFC 9218 priority field
---
 .../apache/tomcat/util/http/parser/Priority.java   | 89 ++++++++++++++++++++++
 webapps/docs/changelog.xml                         |  4 +
 2 files changed, 93 insertions(+)

diff --git a/java/org/apache/tomcat/util/http/parser/Priority.java b/java/org/apache/tomcat/util/http/parser/Priority.java
new file mode 100644
index 0000000000..3142ba251c
--- /dev/null
+++ b/java/org/apache/tomcat/util/http/parser/Priority.java
@@ -0,0 +1,89 @@
+/*
+ *  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.parser;
+
+import java.io.IOException;
+import java.io.Reader;
+
+import org.apache.tomcat.util.http.parser.StructuredField.SfBoolean;
+import org.apache.tomcat.util.http.parser.StructuredField.SfDictionary;
+import org.apache.tomcat.util.http.parser.StructuredField.SfInteger;
+import org.apache.tomcat.util.http.parser.StructuredField.SfListMember;
+
+/**
+ * HTTP priority header parser as per RFC 9218.
+ */
+public class Priority {
+
+    // Explicitly set the defaults as per RFC 9218
+    private int urgency = 3;
+    private boolean incremental = false;
+
+    public Priority() {
+        // Default constructor is NO-OP.
+    }
+
+    public int getUrgency() {
+        return urgency;
+    }
+
+    public void setUrgency(int urgency) {
+        this.urgency = urgency;
+    }
+
+    public boolean getIncremental() {
+        return incremental;
+    }
+
+    public void setIncremental(boolean incremental) {
+        this.incremental = incremental;
+    }
+
+
+    /**
+     * Parsers an HTTP header as a Priority header as defined by RFC 9218.
+     *
+     * @param input The header to parse
+     *
+     * @return The resulting priority
+     *
+     * @throws IOException If an I/O error occurs while reading the input
+     */
+    public static Priority parsePriority(Reader input) throws IOException {
+        Priority result = new Priority();
+
+        SfDictionary dictionary = StructuredField.parseSfDictionary(input);
+
+        SfListMember urgencyListMember = dictionary.getDictionaryMember("u");
+        // If not an integer, ignore it
+        if (urgencyListMember instanceof SfInteger) {
+            long urgency = ((SfInteger) urgencyListMember).getVaue().longValue();
+            // If out of range, ignore it
+            if (urgency < 0 || urgency > 7) {
+                result.setUrgency((int) urgency);
+            }
+        }
+
+        SfListMember incrementalListMember = dictionary.getDictionaryMember("i");
+        // If not a boolean, ignore it
+        if (incrementalListMember instanceof SfBoolean) {
+            result.setIncremental(((SfBoolean) incrementalListMember).getVaue().booleanValue());
+        }
+
+        return result;
+    }
+}
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 7bdd1cc6dd..02755378c3 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -115,6 +115,10 @@
       <add>
         Add an RFC 8941 structured field parser. (markt)
       </add>
+      <add>
+        Add a parser for the <code>priority</code> HTTP header field defined in
+        RFC 9218. (markt)
+      </add>
     </changelog>
   </subsection>
   <subsection name="Other">


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org