You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hc.apache.org by ol...@apache.org on 2026/04/04 08:33:27 UTC
(httpcomponents-core) 01/01: MessageSupport: methods for strict header and header element parsing
This is an automated email from the ASF dual-hosted git repository.
olegk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/httpcomponents-core.git
commit 5324d057d7ff42e88719a99bbca08e65e2756fbd
Author: Oleg Kalnichevski <ol...@apache.org>
AuthorDate: Sat Apr 4 09:53:31 2026 +0200
MessageSupport: methods for strict header and header element parsing
---
.../core5/http/message/HeaderElementConsumer.java | 43 +++++++++++
.../hc/core5/http/message/MessageSupport.java | 73 +++++++++++++++++++
.../hc/core5/http/message/TestMessageSupport.java | 85 ++++++++++++++++++++++
3 files changed, 201 insertions(+)
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/message/HeaderElementConsumer.java b/httpcore5/src/main/java/org/apache/hc/core5/http/message/HeaderElementConsumer.java
new file mode 100644
index 000000000..2e47c4dd5
--- /dev/null
+++ b/httpcore5/src/main/java/org/apache/hc/core5/http/message/HeaderElementConsumer.java
@@ -0,0 +1,43 @@
+/*
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.hc.core5.http.message;
+
+import org.apache.hc.core5.http.ProtocolException;
+
+/**
+ * Abstract consumer of header elements represented by a subsequence
+ * of the given {@link CharSequence} within the given {@link ParserCursor}
+ * bounds.
+ *
+ * @since 5.5
+ */
+@FunctionalInterface
+public interface HeaderElementConsumer {
+
+ void accept(CharSequence buffer, ParserCursor cursor) throws ProtocolException;
+
+}
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/message/MessageSupport.java b/httpcore5/src/main/java/org/apache/hc/core5/http/message/MessageSupport.java
index b17743fa5..4c130ddf7 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/http/message/MessageSupport.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/http/message/MessageSupport.java
@@ -51,6 +51,7 @@
import org.apache.hc.core5.http.MessageHeaders;
import org.apache.hc.core5.http.Method;
import org.apache.hc.core5.http.NameValuePair;
+import org.apache.hc.core5.http.ProtocolException;
import org.apache.hc.core5.util.Args;
import org.apache.hc.core5.util.CharArrayBuffer;
import org.apache.hc.core5.util.Tokenizer;
@@ -233,6 +234,78 @@ public static void parseElementList(final CharSequence src,
}
}
+ /**
+ * @since 5.5
+ */
+ public static void parseElementList(final MessageHeaders headers,
+ final String name,
+ final BiConsumer<CharSequence, ParserCursor> consumer) {
+ parseHeaders(headers, name, (charSequence, cursor) ->
+ parseElementList(charSequence, cursor, consumer));
+ }
+
+ /**
+ * @since 5.5
+ */
+ public static void parseHeaderStrict(final Header header,
+ final HeaderElementConsumer consumer) throws ProtocolException {
+ Args.notNull(header, "Header");
+ if (header instanceof FormattedHeader) {
+ final CharArrayBuffer buf = ((FormattedHeader) header).getBuffer();
+ final ParserCursor cursor = new ParserCursor(0, buf.length());
+ cursor.updatePos(((FormattedHeader) header).getValuePos());
+ consumer.accept(buf, cursor);
+ } else {
+ final String value = header.getValue();
+ final ParserCursor cursor = new ParserCursor(0, value.length());
+ consumer.accept(value, cursor);
+ }
+ }
+
+ /**
+ * @since 5.5
+ */
+ public static void parseHeadersStrict(final MessageHeaders headers,
+ final String name,
+ final HeaderElementConsumer consumer) throws ProtocolException {
+ Args.notNull(headers, "Message headers");
+ Args.notBlank(name, "Header name");
+ final Iterator<Header> it = headers.headerIterator(name);
+ while (it.hasNext()) {
+ parseHeaderStrict(it.next(), consumer);
+ }
+ }
+
+ /**
+ * @since 5.5
+ */
+ public static void parseElementListStrict(final CharSequence src,
+ final ParserCursor cursor,
+ final HeaderElementConsumer consumer) throws ProtocolException {
+ Args.notNull(src, "Source");
+ Args.notNull(cursor, "Cursor");
+ Args.notNull(consumer, "Consumer");
+ while (!cursor.atEnd()) {
+ consumer.accept(src, cursor);
+ if (!cursor.atEnd()) {
+ final char ch = src.charAt(cursor.getPos());
+ if (ch == ',') {
+ cursor.updatePos(cursor.getPos() + 1);
+ }
+ }
+ }
+ }
+
+ /**
+ * @since 5.5
+ */
+ public static void parseElementListStrict(final MessageHeaders headers,
+ final String name,
+ final HeaderElementConsumer consumer) throws ProtocolException {
+ parseHeadersStrict(headers, name, (charSequence, cursor) ->
+ parseElementListStrict(charSequence, cursor, consumer));
+ }
+
/**
* @since 5.4
*/
diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestMessageSupport.java b/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestMessageSupport.java
index 709821ee7..ea69a0e14 100644
--- a/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestMessageSupport.java
+++ b/httpcore5/src/test/java/org/apache/hc/core5/http/message/TestMessageSupport.java
@@ -32,6 +32,7 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
+import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Set;
@@ -44,7 +45,9 @@
import org.apache.hc.core5.http.HttpMessage;
import org.apache.hc.core5.http.HttpResponse;
import org.apache.hc.core5.http.HttpStatus;
+import org.apache.hc.core5.http.Method;
import org.apache.hc.core5.http.NameValuePair;
+import org.apache.hc.core5.http.ProtocolException;
import org.apache.hc.core5.http.io.entity.HttpEntities;
import org.apache.hc.core5.http.support.BasicResponseBuilder;
import org.apache.hc.core5.util.CharArrayBuffer;
@@ -330,6 +333,88 @@ void testParseParams() {
Assertions.assertFalse(cursor.atEnd());
}
+ static String copyHeader(final CharSequence charSequence, final ParserCursor cursor) {
+ final CharArrayBuffer buf = new CharArrayBuffer(10);
+ int pos = cursor.getPos();
+ while (pos < cursor.getUpperBound()) {
+ final char ch = charSequence.charAt(pos);
+ buf.append(ch);
+ pos++;
+ }
+ cursor.updatePos(pos);
+ return buf.toString();
+ }
+
+ static String copyToken(final CharSequence charSequence, final ParserCursor cursor) {
+ final CharArrayBuffer buf = new CharArrayBuffer(10);
+ int pos = cursor.getPos();
+ while (pos < cursor.getUpperBound()) {
+ final char ch = charSequence.charAt(pos);
+ if (ch == ',') {
+ break;
+ }
+ buf.append(ch);
+ pos++;
+ }
+ cursor.updatePos(pos);
+ return buf.substringTrimmed(0, buf.length());
+ }
+
+ @Test
+ void testParseHeaders() {
+ final HttpMessage message = new BasicHttpRequest(Method.GET, "/");
+ message.addHeader("Some-Header", "this");
+ message.addHeader("Some-Header", "that");
+ message.addHeader("Some-Header", " this, that, what not");
+
+ final List<String> headerValues = new LinkedList<>();
+ MessageSupport.parseHeaders(message, "Some-header", (charSequence, cursor) -> {
+ final String headerValue = copyHeader(charSequence, cursor);
+ headerValues.add(headerValue);
+ });
+ Assertions.assertEquals(Arrays.asList("this", "that", " this, that, what not"), headerValues);
+
+ final List<String> tokens = new LinkedList<>();
+ MessageSupport.parseElementList(message, "Some-header", (charSequence, cursor) -> {
+ final String token = copyToken(charSequence, cursor);
+ tokens.add(token);
+ });
+ Assertions.assertEquals(Arrays.asList("this", "that", "this", "that", "what not"), tokens);
+ }
+
+ @Test
+ void testParseHeadersStrict() throws Exception {
+ final HttpMessage message = new BasicHttpRequest(Method.GET, "/");
+ message.addHeader("Some-Header", "this");
+ message.addHeader("Some-Header", "that");
+ message.addHeader("Some-Header", " this, that, what not");
+
+ final List<String> headerValues = new LinkedList<>();
+ MessageSupport.parseHeadersStrict(message, "Some-header", (charSequence, cursor) -> {
+ final String headerValue = copyHeader(charSequence, cursor);
+ headerValues.add(headerValue);
+ });
+ Assertions.assertEquals(Arrays.asList("this", "that", " this, that, what not"), headerValues);
+
+ final List<String> tokens = new LinkedList<>();
+ MessageSupport.parseElementListStrict(message, "Some-header", (charSequence, cursor) -> {
+ final String token = copyToken(charSequence, cursor);
+ tokens.add(token);
+ });
+ Assertions.assertEquals(Arrays.asList("this", "that", "this", "that", "what not"), tokens);
+
+ Assertions.assertThrows(ProtocolException.class, () ->
+ MessageSupport.parseElementListStrict(
+ message,
+ "Some-header",
+ (charSequence, cursor) -> {
+ final String token = copyToken(charSequence, cursor);
+ if (token.equalsIgnoreCase("what not")) {
+ throw new ProtocolException("How awful!");
+ }
+ }));
+ }
+
@Test
void testAddContentHeaders() {
final HttpEntity entity = HttpEntities.create("some stuff with trailers", StandardCharsets.US_ASCII,