Skip to content

Commit 282af75

Browse files
committed
Working
1 parent a2a49f8 commit 282af75

File tree

2 files changed

+92
-24
lines changed

2 files changed

+92
-24
lines changed

src/main/java/io/fusionauth/http/util/HTTPTools.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ public static void parseEncodedData(byte[] data, int start, int length, Charset
158158
inName = false;
159159

160160
try {
161-
name = URLDecoder.decode(new String(data, start, i - start), charset);
161+
name = URLDecoder.decode(new String(data, start, i - start, charset), charset);
162162
} catch (Exception e) {
163163
name = null; // Malformed
164164
}
@@ -174,7 +174,7 @@ public static void parseEncodedData(byte[] data, int start, int length, Charset
174174
//noinspection DuplicatedCode
175175
try {
176176
if (start < i) {
177-
value = URLDecoder.decode(new String(data, start, i - start), charset);
177+
value = URLDecoder.decode(new String(data, start, i - start, charset), charset);
178178
} else {
179179
value = "";
180180
}
@@ -193,7 +193,7 @@ public static void parseEncodedData(byte[] data, int start, int length, Charset
193193
//noinspection DuplicatedCode
194194
try {
195195
if (start < length) {
196-
value = URLDecoder.decode(new String(data, start, length - start), charset);
196+
value = URLDecoder.decode(new String(data, start, length - start, charset), charset);
197197
} else {
198198
value = "";
199199
}
@@ -322,7 +322,7 @@ public static void parseRequestPreamble(PushbackInputStream inputStream, int max
322322

323323
// bytesRead will include bytes that are read past the end of the preamble. This should be ok because we will only throw an exception
324324
// for readExceeded once we have reached this state AND we are not yet in a complete state.
325-
// - So if we exceed the max header size from this read, as long as this buffer includes the entire preamble we will not throw
325+
// - If we exceed the max header size from this read, as long as this buffer includes the entire preamble we will not throw
326326
// an exception.
327327
bytesRead += read;
328328
readExceeded = maxRequestHeaderSize != -1 && bytesRead >= maxRequestHeaderSize;

src/test/java/io/fusionauth/http/util/HTTPToolsTest.java

Lines changed: 88 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2022-2023, FusionAuth, All Rights Reserved
2+
* Copyright (c) 2022-2025, FusionAuth, All Rights Reserved
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,7 +17,9 @@
1717

1818
import java.io.ByteArrayInputStream;
1919
import java.net.URLEncoder;
20+
import java.nio.charset.Charset;
2021
import java.nio.charset.StandardCharsets;
22+
import java.util.ArrayList;
2123
import java.util.HashMap;
2224
import java.util.List;
2325
import java.util.Map;
@@ -42,41 +44,69 @@
4244
public class HTTPToolsTest {
4345
@Test
4446
public void parseEncodedData() {
45-
// Test bad encoding
46-
47-
Map<String, List<String>> actual = new HashMap<>();
48-
4947
// Happy path
50-
byte[] body = "foo=bar".getBytes(StandardCharsets.UTF_8);
51-
HTTPTools.parseEncodedData(body, 0, body.length, actual);
52-
assertEquals(actual, Map.of("foo", List.of("bar")), "Actual:\n" + ToString.toString(actual));
48+
assertEncoding("bar", "bar", StandardCharsets.UTF_8);
5349

5450
// Note, there are 3 try/catches in the parseEncodedDate. This tests them in order, each hitting a specific try/catch.
5551

5652
// Bad name encoding
57-
actual.clear();
58-
byte[] badName = "foo=bar&%%%=baz".getBytes(StandardCharsets.UTF_8);
59-
HTTPTools.parseEncodedData(badName, 0, badName.length, actual);
60-
assertEquals(actual, Map.of("foo", List.of("bar")), "Actual:\n" + ToString.toString(actual));
53+
assertEncoding("bar&%%%=baz", "bar", StandardCharsets.UTF_8);
6154

6255
// Bad value encoding
63-
actual.clear();
64-
byte[] badValue1 = "foo=bar&bar=ba%å&=boom".getBytes(StandardCharsets.UTF_8);
65-
HTTPTools.parseEncodedData(badValue1, 0, badValue1.length, actual);
66-
assertEquals(actual, Map.of("foo", List.of("bar")), "Actual:\n" + ToString.toString(actual));
56+
assertEncoding("bar&bar=ba%å&=boom", "bar", StandardCharsets.UTF_8);
6757

6858
// Bad value encoding
69-
actual.clear();
70-
byte[] badValue2 = "foo=bar&bar=% % %".getBytes(StandardCharsets.UTF_8);
71-
HTTPTools.parseEncodedData(badValue2, 0, badValue2.length, actual);
72-
assertEquals(actual, Map.of("foo", List.of("bar")), "Actual:\n" + ToString.toString(actual));
59+
assertEncoding("bar&bar=% % %", "bar", StandardCharsets.UTF_8);
60+
61+
// UTF-8 encoding of characters not in the ISO-8859-1 character set
62+
assertEncoding("😎", "😎", StandardCharsets.UTF_8);
63+
assertEncoding("é", "é", StandardCharsets.UTF_8);
64+
assertEncoding("€", "€", StandardCharsets.UTF_8);
65+
assertEncoding("Héllö", "Héllö", StandardCharsets.UTF_8);
66+
67+
// Double byte values are outside ISO-88559-1, so we should expect them to not render correctly. See next test.
68+
assertHexValue("😎", "D83D DE0E");
69+
assertHexValue("€", "20AC");
70+
71+
// ISO-8559-1 encoding of characters outside the character set
72+
assertEncoding("😎", "?", StandardCharsets.ISO_8859_1);
73+
assertEncoding("€", "?", StandardCharsets.ISO_8859_1);
74+
75+
// These values are within the ISO-8559-1 charset, expect them to render correctly.
76+
assertHexValue("é", "E9");
77+
assertHexValue("Héllö", "48 E9 6C 6C F6");
78+
79+
// ISO-8559-1 encoding of non-ASCII characters inside the character set
80+
assertEncoding("é", "é", StandardCharsets.ISO_8859_1);
81+
assertEncoding("Héllö", "Héllö", StandardCharsets.ISO_8859_1);
82+
83+
// Mixing and matching. Expect some wonky behavior.
84+
// - Encoded using ISO-8559-1 and decoded as UTF-8
85+
assertEncoding("Héllö", "H�ll�", StandardCharsets.ISO_8859_1, StandardCharsets.UTF_8);
86+
assertEncoding("Hello world", "Hello world", StandardCharsets.ISO_8859_1, StandardCharsets.UTF_8);
87+
// - Reverse
88+
// The é fails here because while it does exist in both UTF-8 and ISO-8859-1, it is not the same byte. So expect the rendering to be off.
89+
assertHexValue("é", "C3 A9", StandardCharsets.UTF_8);
90+
assertHexValue("é", "E9", StandardCharsets.ISO_8859_1);
91+
assertHexValue("Ã", "C3", StandardCharsets.ISO_8859_1);
92+
assertHexValue("©", "A9", StandardCharsets.ISO_8859_1);
93+
assertEncoding("é", "é", StandardCharsets.UTF_8, StandardCharsets.ISO_8859_1);
94+
95+
// The ö fails here because while it does exist in both UTF-8 and ISO-8859-1, it is not the same byte. So expect the rendering to be off.
96+
assertHexValue("ö", "C3 B6", StandardCharsets.UTF_8);
97+
assertHexValue("ö", "F6", StandardCharsets.ISO_8859_1);
98+
assertHexValue("¶", "B6", StandardCharsets.ISO_8859_1);
99+
assertEncoding("Héllö", "Héllö", StandardCharsets.UTF_8, StandardCharsets.ISO_8859_1);
100+
assertEncoding("Hello world", "Hello world", StandardCharsets.UTF_8, StandardCharsets.ISO_8859_1);
73101
}
74102

75103
@Test
76104
public void parseHeaderValue() {
77105
String iso = "åpple";
78106
String utf = "😁";
107+
assertEquals(HTTPTools.parseHeaderValue("text/plain; charset=iso8859-1"), new HeaderValue("text/plain", Map.of("charset", "iso8859-1")));
79108
assertEquals(HTTPTools.parseHeaderValue("text/plain; charset=iso-8859-1"), new HeaderValue("text/plain", Map.of("charset", "iso-8859-1")));
109+
assertEquals(HTTPTools.parseHeaderValue("text/plain; charset=iso8859-1; boundary=FOOBAR"), new HeaderValue("text/plain", Map.of("boundary", "FOOBAR", "charset", "iso8859-1")));
80110
assertEquals(HTTPTools.parseHeaderValue("text/plain; charset=iso-8859-1; boundary=FOOBAR"), new HeaderValue("text/plain", Map.of("boundary", "FOOBAR", "charset", "iso-8859-1")));
81111
assertEquals(HTTPTools.parseHeaderValue("form-data; filename=foo.jpg"), new HeaderValue("form-data", Map.of("filename", "foo.jpg")));
82112
assertEquals(HTTPTools.parseHeaderValue("form-data; filename=\"foo.jpg\""), new HeaderValue("form-data", Map.of("filename", "foo.jpg")));
@@ -89,6 +119,7 @@ public void parseHeaderValue() {
89119
assertEquals(HTTPTools.parseHeaderValue("form-data; filename=ignore.jpg; filename*=ISO-8859-1'en'foo.jpg"), new HeaderValue("form-data", Map.of("filename", "foo.jpg")));
90120

91121
// Encoded
122+
assertEquals(HTTPTools.parseHeaderValue("form-data; filename*=ISO8859-1'en'" + URLEncoder.encode(iso, StandardCharsets.ISO_8859_1)), new HeaderValue("form-data", Map.of("filename", iso)));
92123
assertEquals(HTTPTools.parseHeaderValue("form-data; filename*=ISO-8859-1'en'" + URLEncoder.encode(iso, StandardCharsets.ISO_8859_1)), new HeaderValue("form-data", Map.of("filename", iso)));
93124
assertEquals(HTTPTools.parseHeaderValue("form-data; filename*=UTF-8'en'" + URLEncoder.encode(utf, StandardCharsets.UTF_8)), new HeaderValue("form-data", Map.of("filename", utf)));
94125

@@ -182,4 +213,41 @@ public void parsePreamble() throws Exception {
182213
int nextRequestRead = pushbackInputStream.read(buffer);
183214
assertEquals(nextRequestRead, 16);
184215
}
216+
217+
private void assertEncoding(String actualValue, String expectedValue, Charset charset) {
218+
assertEncoding(actualValue, expectedValue, charset, charset);
219+
220+
}
221+
222+
private void assertEncoding(String actualValue, String expectedValue, Charset encodingCharset, Charset decodingCharset) {
223+
Map<String, List<String>> result = new HashMap<>(1);
224+
byte[] encoded = ("foo=" + actualValue).getBytes(encodingCharset);
225+
HTTPTools.parseEncodedData(encoded, 0, encoded.length, decodingCharset, result);
226+
assertEquals(result, Map.of("foo", List.of(expectedValue)), "Actual:\n" + ToString.toString(result));
227+
}
228+
229+
private void assertHexValue(String s, String expected) {
230+
assertEquals(hex(s), expected);
231+
}
232+
233+
private void assertHexValue(String s, String expected, Charset charset) {
234+
assertEquals(hex(s.getBytes(charset)), expected);
235+
}
236+
237+
private String hex(byte[] bytes) {
238+
List<String> result = new ArrayList<>();
239+
for (byte b : bytes) {
240+
241+
result.add(Integer.toHexString(0xFF & b).toUpperCase());
242+
}
243+
return String.join(" ", result);
244+
}
245+
246+
private String hex(String s) {
247+
List<String> result = new ArrayList<>();
248+
for (char ch : s.toCharArray()) {
249+
result.add(Integer.toHexString(ch).toUpperCase());
250+
}
251+
return String.join(" ", result);
252+
}
185253
}

0 commit comments

Comments
 (0)