Skip to content

Commit 5324d05

Browse files
committed
MessageSupport: methods for strict header and header element parsing
1 parent 598ab65 commit 5324d05

3 files changed

Lines changed: 201 additions & 0 deletions

File tree

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* ====================================================================
3+
* Licensed to the Apache Software Foundation (ASF) under one
4+
* or more contributor license agreements. See the NOTICE file
5+
* distributed with this work for additional information
6+
* regarding copyright ownership. The ASF licenses this file
7+
* to you under the Apache License, Version 2.0 (the
8+
* "License"); you may not use this file except in compliance
9+
* with the License. You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing,
14+
* software distributed under the License is distributed on an
15+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16+
* KIND, either express or implied. See the License for the
17+
* specific language governing permissions and limitations
18+
* under the License.
19+
* ====================================================================
20+
*
21+
* This software consists of voluntary contributions made by many
22+
* individuals on behalf of the Apache Software Foundation. For more
23+
* information on the Apache Software Foundation, please see
24+
* <http://www.apache.org/>.
25+
*
26+
*/
27+
package org.apache.hc.core5.http.message;
28+
29+
import org.apache.hc.core5.http.ProtocolException;
30+
31+
/**
32+
* Abstract consumer of header elements represented by a subsequence
33+
* of the given {@link CharSequence} within the given {@link ParserCursor}
34+
* bounds.
35+
*
36+
* @since 5.5
37+
*/
38+
@FunctionalInterface
39+
public interface HeaderElementConsumer {
40+
41+
void accept(CharSequence buffer, ParserCursor cursor) throws ProtocolException;
42+
43+
}

httpcore5/src/main/java/org/apache/hc/core5/http/message/MessageSupport.java

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import org.apache.hc.core5.http.MessageHeaders;
5252
import org.apache.hc.core5.http.Method;
5353
import org.apache.hc.core5.http.NameValuePair;
54+
import org.apache.hc.core5.http.ProtocolException;
5455
import org.apache.hc.core5.util.Args;
5556
import org.apache.hc.core5.util.CharArrayBuffer;
5657
import org.apache.hc.core5.util.Tokenizer;
@@ -233,6 +234,78 @@ public static void parseElementList(final CharSequence src,
233234
}
234235
}
235236

237+
/**
238+
* @since 5.5
239+
*/
240+
public static void parseElementList(final MessageHeaders headers,
241+
final String name,
242+
final BiConsumer<CharSequence, ParserCursor> consumer) {
243+
parseHeaders(headers, name, (charSequence, cursor) ->
244+
parseElementList(charSequence, cursor, consumer));
245+
}
246+
247+
/**
248+
* @since 5.5
249+
*/
250+
public static void parseHeaderStrict(final Header header,
251+
final HeaderElementConsumer consumer) throws ProtocolException {
252+
Args.notNull(header, "Header");
253+
if (header instanceof FormattedHeader) {
254+
final CharArrayBuffer buf = ((FormattedHeader) header).getBuffer();
255+
final ParserCursor cursor = new ParserCursor(0, buf.length());
256+
cursor.updatePos(((FormattedHeader) header).getValuePos());
257+
consumer.accept(buf, cursor);
258+
} else {
259+
final String value = header.getValue();
260+
final ParserCursor cursor = new ParserCursor(0, value.length());
261+
consumer.accept(value, cursor);
262+
}
263+
}
264+
265+
/**
266+
* @since 5.5
267+
*/
268+
public static void parseHeadersStrict(final MessageHeaders headers,
269+
final String name,
270+
final HeaderElementConsumer consumer) throws ProtocolException {
271+
Args.notNull(headers, "Message headers");
272+
Args.notBlank(name, "Header name");
273+
final Iterator<Header> it = headers.headerIterator(name);
274+
while (it.hasNext()) {
275+
parseHeaderStrict(it.next(), consumer);
276+
}
277+
}
278+
279+
/**
280+
* @since 5.5
281+
*/
282+
public static void parseElementListStrict(final CharSequence src,
283+
final ParserCursor cursor,
284+
final HeaderElementConsumer consumer) throws ProtocolException {
285+
Args.notNull(src, "Source");
286+
Args.notNull(cursor, "Cursor");
287+
Args.notNull(consumer, "Consumer");
288+
while (!cursor.atEnd()) {
289+
consumer.accept(src, cursor);
290+
if (!cursor.atEnd()) {
291+
final char ch = src.charAt(cursor.getPos());
292+
if (ch == ',') {
293+
cursor.updatePos(cursor.getPos() + 1);
294+
}
295+
}
296+
}
297+
}
298+
299+
/**
300+
* @since 5.5
301+
*/
302+
public static void parseElementListStrict(final MessageHeaders headers,
303+
final String name,
304+
final HeaderElementConsumer consumer) throws ProtocolException {
305+
parseHeadersStrict(headers, name, (charSequence, cursor) ->
306+
parseElementListStrict(charSequence, cursor, consumer));
307+
}
308+
236309
/**
237310
* @since 5.4
238311
*/

httpcore5/src/test/java/org/apache/hc/core5/http/message/TestMessageSupport.java

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import java.util.Arrays;
3333
import java.util.Collections;
3434
import java.util.LinkedHashSet;
35+
import java.util.LinkedList;
3536
import java.util.List;
3637
import java.util.Locale;
3738
import java.util.Set;
@@ -44,7 +45,9 @@
4445
import org.apache.hc.core5.http.HttpMessage;
4546
import org.apache.hc.core5.http.HttpResponse;
4647
import org.apache.hc.core5.http.HttpStatus;
48+
import org.apache.hc.core5.http.Method;
4749
import org.apache.hc.core5.http.NameValuePair;
50+
import org.apache.hc.core5.http.ProtocolException;
4851
import org.apache.hc.core5.http.io.entity.HttpEntities;
4952
import org.apache.hc.core5.http.support.BasicResponseBuilder;
5053
import org.apache.hc.core5.util.CharArrayBuffer;
@@ -330,6 +333,88 @@ void testParseParams() {
330333
Assertions.assertFalse(cursor.atEnd());
331334
}
332335

336+
static String copyHeader(final CharSequence charSequence, final ParserCursor cursor) {
337+
final CharArrayBuffer buf = new CharArrayBuffer(10);
338+
int pos = cursor.getPos();
339+
while (pos < cursor.getUpperBound()) {
340+
final char ch = charSequence.charAt(pos);
341+
buf.append(ch);
342+
pos++;
343+
}
344+
cursor.updatePos(pos);
345+
return buf.toString();
346+
}
347+
348+
static String copyToken(final CharSequence charSequence, final ParserCursor cursor) {
349+
final CharArrayBuffer buf = new CharArrayBuffer(10);
350+
int pos = cursor.getPos();
351+
while (pos < cursor.getUpperBound()) {
352+
final char ch = charSequence.charAt(pos);
353+
if (ch == ',') {
354+
break;
355+
}
356+
buf.append(ch);
357+
pos++;
358+
}
359+
cursor.updatePos(pos);
360+
return buf.substringTrimmed(0, buf.length());
361+
}
362+
363+
@Test
364+
void testParseHeaders() {
365+
final HttpMessage message = new BasicHttpRequest(Method.GET, "/");
366+
message.addHeader("Some-Header", "this");
367+
message.addHeader("Some-Header", "that");
368+
message.addHeader("Some-Header", " this, that, what not");
369+
370+
final List<String> headerValues = new LinkedList<>();
371+
MessageSupport.parseHeaders(message, "Some-header", (charSequence, cursor) -> {
372+
final String headerValue = copyHeader(charSequence, cursor);
373+
headerValues.add(headerValue);
374+
});
375+
Assertions.assertEquals(Arrays.asList("this", "that", " this, that, what not"), headerValues);
376+
377+
final List<String> tokens = new LinkedList<>();
378+
MessageSupport.parseElementList(message, "Some-header", (charSequence, cursor) -> {
379+
final String token = copyToken(charSequence, cursor);
380+
tokens.add(token);
381+
});
382+
Assertions.assertEquals(Arrays.asList("this", "that", "this", "that", "what not"), tokens);
383+
}
384+
385+
@Test
386+
void testParseHeadersStrict() throws Exception {
387+
final HttpMessage message = new BasicHttpRequest(Method.GET, "/");
388+
message.addHeader("Some-Header", "this");
389+
message.addHeader("Some-Header", "that");
390+
message.addHeader("Some-Header", " this, that, what not");
391+
392+
final List<String> headerValues = new LinkedList<>();
393+
MessageSupport.parseHeadersStrict(message, "Some-header", (charSequence, cursor) -> {
394+
final String headerValue = copyHeader(charSequence, cursor);
395+
headerValues.add(headerValue);
396+
});
397+
Assertions.assertEquals(Arrays.asList("this", "that", " this, that, what not"), headerValues);
398+
399+
final List<String> tokens = new LinkedList<>();
400+
MessageSupport.parseElementListStrict(message, "Some-header", (charSequence, cursor) -> {
401+
final String token = copyToken(charSequence, cursor);
402+
tokens.add(token);
403+
});
404+
Assertions.assertEquals(Arrays.asList("this", "that", "this", "that", "what not"), tokens);
405+
406+
Assertions.assertThrows(ProtocolException.class, () ->
407+
MessageSupport.parseElementListStrict(
408+
message,
409+
"Some-header",
410+
(charSequence, cursor) -> {
411+
final String token = copyToken(charSequence, cursor);
412+
if (token.equalsIgnoreCase("what not")) {
413+
throw new ProtocolException("How awful!");
414+
}
415+
}));
416+
}
417+
333418
@Test
334419
void testAddContentHeaders() {
335420
final HttpEntity entity = HttpEntities.create("some stuff with trailers", StandardCharsets.US_ASCII,

0 commit comments

Comments
 (0)