Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

Expand All @@ -21,10 +22,12 @@
import javax.xml.parsers.ParserConfigurationException;

import org.apache.commons.fileupload.FileUploadBase;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpParser;
import org.apache.commons.io.IOUtils;
import org.apache.http.Header;
import org.apache.http.entity.ContentType;
import org.apache.http.impl.io.AbstractMessageParser;
import org.apache.http.impl.io.HttpTransportMetricsImpl;
import org.apache.http.impl.io.SessionInputBufferImpl;

import com.mirth.connect.connectors.http.HttpMessageConverter;
import com.mirth.connect.donkey.util.DonkeyElement.DonkeyElementException;
Expand All @@ -46,7 +49,9 @@ private HTTPUtil() {}
*/
public static Map<String, String> parseHeaders(String str) throws Exception {
Map<String, String> headersMap = new HashMap<String, String>();
Header[] headers = HttpParser.parseHeaders(new ByteArrayInputStream(str.getBytes()), "UTF-8");
SessionInputBufferImpl buffer = new SessionInputBufferImpl(new HttpTransportMetricsImpl(), 8192, 8192, null, StandardCharsets.UTF_8.newDecoder());
buffer.bind(new ByteArrayInputStream(str.getBytes()));
Header[] headers = AbstractMessageParser.parseHeaders(buffer, -1, -1, null);

for (int i = 0; i < headers.length; i++) {
headersMap.put(headers[i].getName(), headers[i].getValue());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
// SPDX-License-Identifier: MPL-2.0
// SPDX-FileCopyrightText: 2026 Nico Piel <nico.piel@hotmail.de>

package com.mirth.connect.server.userutil;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

import org.apache.http.ProtocolException;
import org.junit.Test;

/**
* Characterization tests for {@link HTTPUtil#parseHeaders(String)}, pinning the behaviour of the
* httpcore 4 AbstractMessageParser based implementation.
*/
public class HTTPUtilTest {

private static Map<String, String> map(String... keyValues) {
Map<String, String> map = new HashMap<>();
for (int i = 0; i < keyValues.length; i += 2) {
map.put(keyValues[i], keyValues[i + 1]);
}
return map;
}

@Test
public void testEmptyString() throws Exception {
assertEquals(map(), HTTPUtil.parseHeaders(""));
}

@Test
public void testCrlfLines() throws Exception {
assertEquals(map("Content-Type", "text/plain", "Content-Length", "42"), HTTPUtil.parseHeaders("Content-Type: text/plain\r\nContent-Length: 42\r\n"));
}

@Test
public void testLfLinesAndNoTrailingNewline() throws Exception {
assertEquals(map("A", "1", "B", "2"), HTTPUtil.parseHeaders("A: 1\nB: 2"));
}

@Test
public void testNameAndValueAreTrimmed() throws Exception {
assertEquals(map("Name", "some value"), HTTPUtil.parseHeaders("Name \t: \t some value \t \r\n"));
// Only spaces and tabs are trimmed, other control characters are kept
assertEquals(map("X", "\u000ba\u0001"), HTTPUtil.parseHeaders("X:\u000ba\u0001\r\n"));
}

@Test
public void testValueKeepsFurtherColons() throws Exception {
assertEquals(map("Host", "example.com:8080"), HTTPUtil.parseHeaders("Host: example.com:8080\r\n"));
}

@Test
public void testEmptyNameIsRejected() {
assertParseFailure(":v\r\nX:\r\n", ":v");
}

@Test
public void testEmptyValue() throws Exception {
assertEquals(map("X", ""), HTTPUtil.parseHeaders("X:\r\n"));
}

@Test
public void testFoldedContinuationLines() throws Exception {
// Leading whitespace of a folded line is dropped, trailing whitespace before the next fold is kept
assertEquals(map("X", "a b c", "Y", "d"), HTTPUtil.parseHeaders("X: a\r\n b \r\n\tc\r\nY: d\r\n"));
}

@Test
public void testContinuationOfEmptyValue() throws Exception {
assertEquals(map("X", "b"), HTTPUtil.parseHeaders("X:\r\n b\r\n"));
}

@Test
public void testLeadingContinuationLineIsRejected() {
assertParseFailure(" orphan\r\nX: a\r\n", " orphan");
}

@Test
public void testDuplicateNamesLastWins() throws Exception {
assertEquals(map("X", "second"), HTTPUtil.parseHeaders("X: first\r\nX: second\r\n"));
}

@Test
public void testNamesAreCaseSensitive() throws Exception {
assertEquals(map("Foo", "1", "foo", "2"), HTTPUtil.parseHeaders("Foo: 1\r\nfoo: 2\r\n"));
}

@Test
public void testStopsAtBlankLine() throws Exception {
assertEquals(map("A", "1"), HTTPUtil.parseHeaders("A: 1\r\n\r\nB: 2\r\nnot a header\r\n"));
assertEquals(map("A", "1"), HTTPUtil.parseHeaders("A: 1\n\nB: 2\n"));
}

@Test
public void testWhitespaceOnlyLineIsFolded() throws Exception {
assertEquals(map("A", "1", "B", "2"), HTTPUtil.parseHeaders("A: 1\r\n \t \r\nB: 2\r\n"));
}

@Test
public void testBareCarriageReturnLineIsRejected() {
// httpcore escapes the control character in the message
assertParseFailure("A: 1\r\n\r\r\nB: 2\r\n", "[0x10]");
}

@Test
public void testLeadingBlankLineYieldsNothing() throws Exception {
assertEquals(map(), HTTPUtil.parseHeaders("\r\nA: 1\r\n"));
}

@Test
public void testCarriageReturnHandling() throws Exception {
// Only a single CR directly before the LF is stripped; others are trimmed or kept
assertEquals(map("A", "x\ry", "B", "w", "C", "z"), HTTPUtil.parseHeaders("A: x\ry\r\nB: w\r\r\nC: z\r"));
}

@Test
public void testHttpStatusLineIsRejected() {
assertParseFailure("HTTP/1.1 200 OK\r\nA: 1\r\n", "HTTP/1.1 200 OK");
}

@Test
public void testLineWithoutColonIsRejected() {
assertParseFailure("A: 1\r\nbogus line\r\n", "bogus line");
}

@Test
public void testNonAsciiValue() throws Exception {
// The string is encoded with the platform charset and decoded as UTF-8
assumeTrue(Charset.defaultCharset().equals(StandardCharsets.UTF_8));
assertEquals(map("X-Name", "M\u00fcller \u20ac"), HTTPUtil.parseHeaders("X-Name: M\u00fcller \u20ac\r\n"));
}

@Test(expected = NullPointerException.class)
public void testNullInput() throws Exception {
HTTPUtil.parseHeaders(null);
}

private static void assertParseFailure(String input, String badLine) {
try {
HTTPUtil.parseHeaders(input);
fail("Expected a ProtocolException");
} catch (Exception e) {
assertTrue(e.getClass().getName(), e instanceof ProtocolException);
assertEquals("Invalid header: " + badLine, e.getMessage());
}
}
}
Loading