Skip to content

Commit 9101b8f

Browse files
committed
Improve HTTP validation for inlining and specialized types
1 parent b8d0699 commit 9101b8f

File tree

1 file changed

+157
-31
lines changed

1 file changed

+157
-31
lines changed

src/main/java/io/vertx/core/http/impl/HttpUtils.java

Lines changed: 157 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -863,7 +863,32 @@ public static void validateHeader(CharSequence name, Iterable<? extends CharSequ
863863
});
864864
}
865865

866-
public static void validateHeaderValue(CharSequence seq) {
866+
public static void validateHeaderValue(CharSequence value) {
867+
if (value instanceof AsciiString) {
868+
validateAsciiHeaderValue((AsciiString) value);
869+
} else if (value instanceof String) {
870+
validateStringHeaderValue((String) value);
871+
} else {
872+
validateSequenceHeaderValue(value);
873+
}
874+
}
875+
876+
private static void validateAsciiHeaderValue(AsciiString asciiString) {
877+
byte[] asciiChars = asciiString.array();
878+
int off = asciiString.arrayOffset();
879+
int len = asciiString.length();
880+
int state = 0;
881+
// Start looping through each of the character
882+
for (int index = 0; index < len; index++) {
883+
state = validateLatinValue(asciiString, state, asciiChars[off + index]);
884+
}
885+
886+
if (state != 0) {
887+
throw new IllegalArgumentException("a header value must not end with '\\r' or '\\n':" + asciiString);
888+
}
889+
}
890+
891+
private static void validateStringHeaderValue(String seq) {
867892

868893
int state = 0;
869894
// Start looping through each of the character
@@ -876,54 +901,140 @@ public static void validateHeaderValue(CharSequence seq) {
876901
}
877902
}
878903

904+
private static void validateSequenceHeaderValue(CharSequence seq) {
905+
int state = 0;
906+
// Start looping through each of the character
907+
for (int index = 0; index < seq.length(); index++) {
908+
state = validateValueChar(seq, state, seq.charAt(index));
909+
}
910+
911+
if (state != 0) {
912+
throw new IllegalArgumentException("a header value must not end with '\\r' or '\\n':" + seq);
913+
}
914+
}
915+
879916
private static final int HIGHEST_INVALID_VALUE_CHAR_MASK = ~0x1F;
917+
private static final int NO_CR_LF_STATE = 0;
918+
private static final int CR_STATE = 1;
919+
private static final int LF_STATE = 2;
880920

881-
private static int validateValueChar(CharSequence seq, int state, char character) {
921+
private static int validateLatinValue(CharSequence seq, int state, byte latinChar) {
882922
/*
883923
* State:
884924
* 0: Previous character was neither CR nor LF
885925
* 1: The previous character was CR
886926
* 2: The previous character was LF
887927
*/
888-
if ((character & HIGHEST_INVALID_VALUE_CHAR_MASK) == 0 || character == 0x7F) { // 0x7F is "DEL".
889-
// The only characters allowed in the range 0x00-0x1F are : HTAB, LF and CR
890-
switch (character) {
891-
case 0x09: // Horizontal tab - HTAB
892-
case 0x0a: // Line feed - LF
893-
case 0x0d: // Carriage return - CR
894-
break;
895-
default:
896-
throw new IllegalArgumentException("a header value contains a prohibited character '" + (int) character + "': " + seq);
897-
}
928+
if ((latinChar & HIGHEST_INVALID_VALUE_CHAR_MASK) == 0 || latinChar == 0x7F) {
929+
validateNonPrintableCtrlLatin(seq, latinChar);
898930
}
899931

900932
// Check the CRLF (HT | SP) pattern
933+
if (state == NO_CR_LF_STATE) {
934+
switch (latinChar) {
935+
case '\r':
936+
return CR_STATE;
937+
case '\n':
938+
return LF_STATE;
939+
}
940+
return NO_CR_LF_STATE;
941+
} else {
942+
return validateCrLfLatin(seq, state, latinChar);
943+
}
944+
}
945+
private static void validateNonPrintableCtrlLatin(CharSequence seq, byte latinChar) {
946+
// The only characters allowed in the range 0x00-0x1F are : HTAB, LF and CR
947+
switch (latinChar) {
948+
case 0x09: // Horizontal tab - HTAB
949+
case 0x0a: // Line feed - LF
950+
case 0x0d: // Carriage return - CR
951+
break;
952+
default:
953+
throw new IllegalArgumentException("a header value contains a prohibited character '" + Byte.toUnsignedInt(latinChar) + "': " + seq);
954+
}
955+
}
956+
957+
958+
private static int validateCrLfLatin(CharSequence seq, int state, byte latinChar) {
901959
switch (state) {
902-
case 0:
903-
switch (character) {
904-
case '\r':
905-
return 1;
906-
case '\n':
907-
return 2;
960+
case CR_STATE:
961+
if (latinChar == '\n') {
962+
return LF_STATE;
908963
}
909-
break;
910-
case 1:
911-
switch (character) {
912-
case '\n':
913-
return 2;
964+
throw new IllegalArgumentException("only '\\n' is allowed after '\\r': " + seq);
965+
case LF_STATE:
966+
switch (latinChar) {
967+
case '\t':
968+
case ' ':
969+
// return to the normal state
970+
return NO_CR_LF_STATE;
914971
default:
915-
throw new IllegalArgumentException("only '\\n' is allowed after '\\r': " + seq);
972+
throw new IllegalArgumentException("only ' ' and '\\t' are allowed after '\\n': " + seq);
916973
}
917-
case 2:
974+
default:
975+
// this should never happen
976+
throw new AssertionError();
977+
}
978+
}
979+
980+
981+
private static int validateValueChar(CharSequence seq, int state, char character) {
982+
/*
983+
* State:
984+
* 0: Previous character was neither CR nor LF
985+
* 1: The previous character was CR
986+
* 2: The previous character was LF
987+
*/
988+
if ((character & HIGHEST_INVALID_VALUE_CHAR_MASK) == 0 || character == 0x7F) {
989+
validateNonPrintableCtrlChar(seq, character);
990+
}
991+
992+
// Check the CRLF (HT | SP) pattern
993+
if (state == NO_CR_LF_STATE) {
994+
switch (character) {
995+
case '\r':
996+
return CR_STATE;
997+
case '\n':
998+
return LF_STATE;
999+
}
1000+
return NO_CR_LF_STATE;
1001+
} else {
1002+
return validateCrLfChar(seq, state, character);
1003+
}
1004+
}
1005+
1006+
private static int validateCrLfChar(CharSequence seq, int state, char character) {
1007+
switch (state) {
1008+
case CR_STATE:
1009+
if (character == '\n') {
1010+
return LF_STATE;
1011+
}
1012+
throw new IllegalArgumentException("only '\\n' is allowed after '\\r': " + seq);
1013+
case LF_STATE:
9181014
switch (character) {
9191015
case '\t':
9201016
case ' ':
921-
return 0;
1017+
// return to the normal state
1018+
return NO_CR_LF_STATE;
9221019
default:
9231020
throw new IllegalArgumentException("only ' ' and '\\t' are allowed after '\\n': " + seq);
9241021
}
1022+
default:
1023+
// this should never happen
1024+
throw new AssertionError();
1025+
}
1026+
}
1027+
1028+
private static void validateNonPrintableCtrlChar(CharSequence seq, char character) {
1029+
// The only characters allowed in the range 0x00-0x1F are : HTAB, LF and CR
1030+
switch (character) {
1031+
case 0x09: // Horizontal tab - HTAB
1032+
case 0x0a: // Line feed - LF
1033+
case 0x0d: // Carriage return - CR
1034+
break;
1035+
default:
1036+
throw new IllegalArgumentException("a header value contains a prohibited character '" + (int) character + "': " + seq);
9251037
}
926-
return state;
9271038
}
9281039

9291040
private static final boolean[] VALID_H_NAME_ASCII_CHARS;
@@ -959,13 +1070,15 @@ private static int validateValueChar(CharSequence seq, int state, char character
9591070
public static void validateHeaderName(CharSequence value) {
9601071
if (value instanceof AsciiString) {
9611072
// no need to check for ASCII-ness anymore
962-
validateHeaderName((AsciiString) value);
1073+
validateAsciiHeaderName((AsciiString) value);
1074+
} else if(value instanceof String) {
1075+
validateStringHeaderName((String) value);
9631076
} else {
964-
validateHeaderName0(value);
1077+
validateSequenceHeaderName(value);
9651078
}
9661079
}
9671080

968-
private static void validateHeaderName(AsciiString value) {
1081+
private static void validateAsciiHeaderName(AsciiString value) {
9691082
final int len = value.length();
9701083
final int off = value.arrayOffset();
9711084
final byte[] asciiChars = value.array();
@@ -981,7 +1094,20 @@ private static void validateHeaderName(AsciiString value) {
9811094
}
9821095
}
9831096

984-
private static void validateHeaderName0(CharSequence value) {
1097+
private static void validateStringHeaderName(String value) {
1098+
for (int i = 0; i < value.length(); i++) {
1099+
final char c = value.charAt(i);
1100+
// Check to see if the character is not an ASCII character, or invalid
1101+
if (c > 0x7f) {
1102+
throw new IllegalArgumentException("a header name cannot contain non-ASCII character: " + value);
1103+
}
1104+
if (!VALID_H_NAME_ASCII_CHARS[c & 0x7F]) {
1105+
throw new IllegalArgumentException("a header name cannot contain some prohibited characters, such as : " + value);
1106+
}
1107+
}
1108+
}
1109+
1110+
private static void validateSequenceHeaderName(CharSequence value) {
9851111
for (int i = 0; i < value.length(); i++) {
9861112
final char c = value.charAt(i);
9871113
// Check to see if the character is not an ASCII character, or invalid

0 commit comments

Comments
 (0)