Skip to content

Commit

Permalink
Improve HTTP validation for inlining and specialized types
Browse files Browse the repository at this point in the history
  • Loading branch information
franz1981 committed Nov 16, 2024
1 parent b8d0699 commit 9101b8f
Showing 1 changed file with 157 additions and 31 deletions.
188 changes: 157 additions & 31 deletions src/main/java/io/vertx/core/http/impl/HttpUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -863,7 +863,32 @@ public static void validateHeader(CharSequence name, Iterable<? extends CharSequ
});
}

public static void validateHeaderValue(CharSequence seq) {
public static void validateHeaderValue(CharSequence value) {
if (value instanceof AsciiString) {
validateAsciiHeaderValue((AsciiString) value);
} else if (value instanceof String) {
validateStringHeaderValue((String) value);
} else {
validateSequenceHeaderValue(value);
}
}

private static void validateAsciiHeaderValue(AsciiString asciiString) {
byte[] asciiChars = asciiString.array();
int off = asciiString.arrayOffset();
int len = asciiString.length();
int state = 0;
// Start looping through each of the character
for (int index = 0; index < len; index++) {
state = validateLatinValue(asciiString, state, asciiChars[off + index]);
}

if (state != 0) {
throw new IllegalArgumentException("a header value must not end with '\\r' or '\\n':" + asciiString);
}
}

private static void validateStringHeaderValue(String seq) {

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

private static void validateSequenceHeaderValue(CharSequence seq) {
int state = 0;
// Start looping through each of the character
for (int index = 0; index < seq.length(); index++) {
state = validateValueChar(seq, state, seq.charAt(index));
}

if (state != 0) {
throw new IllegalArgumentException("a header value must not end with '\\r' or '\\n':" + seq);
}
}

private static final int HIGHEST_INVALID_VALUE_CHAR_MASK = ~0x1F;
private static final int NO_CR_LF_STATE = 0;
private static final int CR_STATE = 1;
private static final int LF_STATE = 2;

private static int validateValueChar(CharSequence seq, int state, char character) {
private static int validateLatinValue(CharSequence seq, int state, byte latinChar) {
/*
* State:
* 0: Previous character was neither CR nor LF
* 1: The previous character was CR
* 2: The previous character was LF
*/
if ((character & HIGHEST_INVALID_VALUE_CHAR_MASK) == 0 || character == 0x7F) { // 0x7F is "DEL".
// The only characters allowed in the range 0x00-0x1F are : HTAB, LF and CR
switch (character) {
case 0x09: // Horizontal tab - HTAB
case 0x0a: // Line feed - LF
case 0x0d: // Carriage return - CR
break;
default:
throw new IllegalArgumentException("a header value contains a prohibited character '" + (int) character + "': " + seq);
}
if ((latinChar & HIGHEST_INVALID_VALUE_CHAR_MASK) == 0 || latinChar == 0x7F) {
validateNonPrintableCtrlLatin(seq, latinChar);
}

// Check the CRLF (HT | SP) pattern
if (state == NO_CR_LF_STATE) {
switch (latinChar) {
case '\r':
return CR_STATE;
case '\n':
return LF_STATE;
}
return NO_CR_LF_STATE;
} else {
return validateCrLfLatin(seq, state, latinChar);
}
}
private static void validateNonPrintableCtrlLatin(CharSequence seq, byte latinChar) {
// The only characters allowed in the range 0x00-0x1F are : HTAB, LF and CR
switch (latinChar) {
case 0x09: // Horizontal tab - HTAB
case 0x0a: // Line feed - LF
case 0x0d: // Carriage return - CR
break;
default:
throw new IllegalArgumentException("a header value contains a prohibited character '" + Byte.toUnsignedInt(latinChar) + "': " + seq);
}
}


private static int validateCrLfLatin(CharSequence seq, int state, byte latinChar) {
switch (state) {
case 0:
switch (character) {
case '\r':
return 1;
case '\n':
return 2;
case CR_STATE:
if (latinChar == '\n') {
return LF_STATE;
}
break;
case 1:
switch (character) {
case '\n':
return 2;
throw new IllegalArgumentException("only '\\n' is allowed after '\\r': " + seq);
case LF_STATE:
switch (latinChar) {
case '\t':
case ' ':
// return to the normal state
return NO_CR_LF_STATE;
default:
throw new IllegalArgumentException("only '\\n' is allowed after '\\r': " + seq);
throw new IllegalArgumentException("only ' ' and '\\t' are allowed after '\\n': " + seq);
}
case 2:
default:
// this should never happen
throw new AssertionError();
}
}


private static int validateValueChar(CharSequence seq, int state, char character) {
/*
* State:
* 0: Previous character was neither CR nor LF
* 1: The previous character was CR
* 2: The previous character was LF
*/
if ((character & HIGHEST_INVALID_VALUE_CHAR_MASK) == 0 || character == 0x7F) {
validateNonPrintableCtrlChar(seq, character);
}

// Check the CRLF (HT | SP) pattern
if (state == NO_CR_LF_STATE) {
switch (character) {
case '\r':
return CR_STATE;
case '\n':
return LF_STATE;
}
return NO_CR_LF_STATE;
} else {
return validateCrLfChar(seq, state, character);
}
}

private static int validateCrLfChar(CharSequence seq, int state, char character) {
switch (state) {
case CR_STATE:
if (character == '\n') {
return LF_STATE;
}
throw new IllegalArgumentException("only '\\n' is allowed after '\\r': " + seq);
case LF_STATE:
switch (character) {
case '\t':
case ' ':
return 0;
// return to the normal state
return NO_CR_LF_STATE;
default:
throw new IllegalArgumentException("only ' ' and '\\t' are allowed after '\\n': " + seq);
}
default:
// this should never happen
throw new AssertionError();
}
}

private static void validateNonPrintableCtrlChar(CharSequence seq, char character) {
// The only characters allowed in the range 0x00-0x1F are : HTAB, LF and CR
switch (character) {
case 0x09: // Horizontal tab - HTAB
case 0x0a: // Line feed - LF
case 0x0d: // Carriage return - CR
break;
default:
throw new IllegalArgumentException("a header value contains a prohibited character '" + (int) character + "': " + seq);
}
return state;
}

private static final boolean[] VALID_H_NAME_ASCII_CHARS;
Expand Down Expand Up @@ -959,13 +1070,15 @@ private static int validateValueChar(CharSequence seq, int state, char character
public static void validateHeaderName(CharSequence value) {
if (value instanceof AsciiString) {
// no need to check for ASCII-ness anymore
validateHeaderName((AsciiString) value);
validateAsciiHeaderName((AsciiString) value);
} else if(value instanceof String) {
validateStringHeaderName((String) value);
} else {
validateHeaderName0(value);
validateSequenceHeaderName(value);
}
}

private static void validateHeaderName(AsciiString value) {
private static void validateAsciiHeaderName(AsciiString value) {
final int len = value.length();
final int off = value.arrayOffset();
final byte[] asciiChars = value.array();
Expand All @@ -981,7 +1094,20 @@ private static void validateHeaderName(AsciiString value) {
}
}

private static void validateHeaderName0(CharSequence value) {
private static void validateStringHeaderName(String value) {
for (int i = 0; i < value.length(); i++) {
final char c = value.charAt(i);
// Check to see if the character is not an ASCII character, or invalid
if (c > 0x7f) {
throw new IllegalArgumentException("a header name cannot contain non-ASCII character: " + value);
}
if (!VALID_H_NAME_ASCII_CHARS[c & 0x7F]) {
throw new IllegalArgumentException("a header name cannot contain some prohibited characters, such as : " + value);
}
}
}

private static void validateSequenceHeaderName(CharSequence value) {
for (int i = 0; i < value.length(); i++) {
final char c = value.charAt(i);
// Check to see if the character is not an ASCII character, or invalid
Expand Down

0 comments on commit 9101b8f

Please sign in to comment.