Skip to content

Commit a0fa552

Browse files
authored
refactor: share invalid header handling (#221)
handle_invalid_char! duplicates the invalid char recovery loop at all macro expansions. Share it with a labeled block. This reduces code size by about 1.7-3KiB.
1 parent e48b248 commit a0fa552

1 file changed

Lines changed: 138 additions & 143 deletions

File tree

src/lib.rs

Lines changed: 138 additions & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -1072,178 +1072,173 @@ fn parse_headers_iter_uninit<'a>(
10721072
}
10731073

10741074
'headers: loop {
1075-
// Return the error `$err` if `ignore_invalid_headers_in_responses`
1076-
// is false, otherwise find the end of the current line and resume
1077-
// parsing on the next one.
1078-
macro_rules! handle_invalid_char {
1079-
($bytes:ident, $b:ident, $err:ident) => {
1080-
if !config.ignore_invalid_headers {
1081-
return Err(Error::$err);
1082-
}
1083-
1084-
let mut b = $b;
1075+
let mut b;
10851076

1086-
loop {
1087-
if b == b'\r' {
1088-
expect!(bytes.next() == b'\n' => Err(Error::$err));
1089-
break;
1090-
}
1091-
if b == b'\n' {
1092-
break;
1093-
}
1094-
if b == b'\0' {
1095-
return Err(Error::$err);
1077+
#[allow(clippy::never_loop)]
1078+
let invalid_header_err = 'header: loop {
1079+
// a newline here means the head is over!
1080+
b = next!(bytes);
1081+
if b == b'\r' {
1082+
expect!(bytes.next() == b'\n' => Err(Error::NewLine));
1083+
let end = bytes.as_ref().as_ptr() as usize;
1084+
result = Ok(Status::Complete(end - start));
1085+
break 'headers;
1086+
}
1087+
if b == b'\n' {
1088+
let end = bytes.as_ref().as_ptr() as usize;
1089+
result = Ok(Status::Complete(end - start));
1090+
break 'headers;
1091+
}
1092+
if !is_header_name_token(b) {
1093+
if config.allow_space_before_first_header_name
1094+
&& autoshrink.num_headers == 0
1095+
&& (b == b' ' || b == b'\t')
1096+
{
1097+
//advance past white space and then try parsing header again
1098+
while let Some(peek) = bytes.peek() {
1099+
if peek == b' ' || peek == b'\t' {
1100+
next!(bytes);
1101+
} else {
1102+
break;
1103+
}
10961104
}
1097-
b = next!($bytes);
1105+
bytes.slice();
1106+
continue 'headers;
1107+
} else {
1108+
break 'header Error::HeaderName;
10981109
}
1110+
}
10991111

1100-
$bytes.slice();
1112+
#[allow(clippy::never_loop)]
1113+
// parse header name until colon
1114+
let header_name: &str = 'name: loop {
1115+
simd::match_header_name_vectored(bytes);
1116+
b = next!(bytes);
11011117

1102-
continue 'headers;
1103-
};
1104-
}
1118+
// SAFETY: previously bumped by 1 with next! -> always safe.
1119+
let bslice = unsafe { bytes.slice_skip(1) };
1120+
// SAFETY: previous call to match_header_name_vectored ensured all bytes are valid
1121+
// header name chars, and as such also valid utf-8.
1122+
let name = unsafe { str::from_utf8_unchecked(bslice) };
11051123

1106-
// a newline here means the head is over!
1107-
let b = next!(bytes);
1108-
if b == b'\r' {
1109-
expect!(bytes.next() == b'\n' => Err(Error::NewLine));
1110-
let end = bytes.as_ref().as_ptr() as usize;
1111-
result = Ok(Status::Complete(end - start));
1112-
break;
1113-
}
1114-
if b == b'\n' {
1115-
let end = bytes.as_ref().as_ptr() as usize;
1116-
result = Ok(Status::Complete(end - start));
1117-
break;
1118-
}
1119-
if !is_header_name_token(b) {
1120-
if config.allow_space_before_first_header_name
1121-
&& autoshrink.num_headers == 0
1122-
&& (b == b' ' || b == b'\t')
1123-
{
1124-
//advance past white space and then try parsing header again
1125-
while let Some(peek) = bytes.peek() {
1126-
if peek == b' ' || peek == b'\t' {
1127-
next!(bytes);
1128-
} else {
1129-
break;
1124+
if b == b':' {
1125+
break 'name name;
1126+
}
1127+
1128+
if config.allow_spaces_after_header_name {
1129+
while b == b' ' || b == b'\t' {
1130+
b = next!(bytes);
1131+
1132+
if b == b':' {
1133+
bytes.slice();
1134+
break 'name name;
1135+
}
11301136
}
11311137
}
1132-
bytes.slice();
1133-
continue 'headers;
1134-
} else {
1135-
handle_invalid_char!(bytes, b, HeaderName);
1136-
}
1137-
}
11381138

1139-
#[allow(clippy::never_loop)]
1140-
// parse header name until colon
1141-
let header_name: &str = 'name: loop {
1142-
simd::match_header_name_vectored(bytes);
1143-
let mut b = next!(bytes);
1144-
1145-
// SAFETY: previously bumped by 1 with next! -> always safe.
1146-
let bslice = unsafe { bytes.slice_skip(1) };
1147-
// SAFETY: previous call to match_header_name_vectored ensured all bytes are valid
1148-
// header name chars, and as such also valid utf-8.
1149-
let name = unsafe { str::from_utf8_unchecked(bslice) };
1150-
1151-
if b == b':' {
1152-
break 'name name;
1153-
}
1139+
break 'header Error::HeaderName;
1140+
};
11541141

1155-
if config.allow_spaces_after_header_name {
1156-
while b == b' ' || b == b'\t' {
1142+
#[allow(clippy::never_loop)]
1143+
let value_slice = 'value: loop {
1144+
// eat white space between colon and value
1145+
'whitespace_after_colon: loop {
11571146
b = next!(bytes);
1158-
1159-
if b == b':' {
1147+
if b == b' ' || b == b'\t' {
11601148
bytes.slice();
1161-
break 'name name;
1149+
continue 'whitespace_after_colon;
1150+
}
1151+
if is_header_value_token(b) {
1152+
break 'whitespace_after_colon;
11621153
}
1163-
}
1164-
}
11651154

1166-
handle_invalid_char!(bytes, b, HeaderName);
1167-
};
1155+
if b == b'\r' {
1156+
expect!(bytes.next() == b'\n' => Err(Error::HeaderValue));
1157+
} else if b != b'\n' {
1158+
break 'header Error::HeaderValue;
1159+
}
11681160

1169-
let mut b;
1161+
maybe_continue_after_obsolete_line_folding!(bytes, 'whitespace_after_colon);
11701162

1171-
#[allow(clippy::never_loop)]
1172-
let value_slice = 'value: loop {
1173-
// eat white space between colon and value
1174-
'whitespace_after_colon: loop {
1175-
b = next!(bytes);
1176-
if b == b' ' || b == b'\t' {
1177-
bytes.slice();
1178-
continue 'whitespace_after_colon;
1179-
}
1180-
if is_header_value_token(b) {
1181-
break 'whitespace_after_colon;
1182-
}
1163+
let whitespace_slice = bytes.slice();
11831164

1184-
if b == b'\r' {
1185-
expect!(bytes.next() == b'\n' => Err(Error::HeaderValue));
1186-
} else if b != b'\n' {
1187-
handle_invalid_char!(bytes, b, HeaderValue);
1165+
// This produces an empty slice that points to the beginning
1166+
// of the whitespace.
1167+
break 'value &whitespace_slice[0..0];
11881168
}
11891169

1190-
maybe_continue_after_obsolete_line_folding!(bytes, 'whitespace_after_colon);
1191-
1192-
let whitespace_slice = bytes.slice();
1170+
'value_lines: loop {
1171+
// parse value till EOL
11931172

1194-
// This produces an empty slice that points to the beginning
1195-
// of the whitespace.
1196-
break 'value &whitespace_slice[0..0];
1197-
}
1173+
simd::match_header_value_vectored(bytes);
1174+
b = next!(bytes);
11981175

1199-
'value_lines: loop {
1200-
// parse value till EOL
1176+
//found_ctl
1177+
let skip = if b == b'\r' {
1178+
expect!(bytes.next() == b'\n' => Err(Error::HeaderValue));
1179+
2
1180+
} else if b == b'\n' {
1181+
1
1182+
} else {
1183+
break 'header Error::HeaderValue;
1184+
};
12011185

1202-
simd::match_header_value_vectored(bytes);
1203-
let b = next!(bytes);
1186+
maybe_continue_after_obsolete_line_folding!(bytes, 'value_lines);
12041187

1205-
//found_ctl
1206-
let skip = if b == b'\r' {
1207-
expect!(bytes.next() == b'\n' => Err(Error::HeaderValue));
1208-
2
1209-
} else if b == b'\n' {
1210-
1
1211-
} else {
1212-
handle_invalid_char!(bytes, b, HeaderValue);
1213-
};
1188+
// SAFETY: having just checked that a newline exists, it's safe to skip it.
1189+
unsafe {
1190+
break 'value bytes.slice_skip(skip);
1191+
}
1192+
}
1193+
};
12141194

1215-
maybe_continue_after_obsolete_line_folding!(bytes, 'value_lines);
1195+
let uninit_header = match iter.next() {
1196+
Some(header) => header,
1197+
None => break 'headers
1198+
};
12161199

1217-
// SAFETY: having just checked that a newline exists, it's safe to skip it.
1218-
unsafe {
1219-
break 'value bytes.slice_skip(skip);
1220-
}
1221-
}
1222-
};
1200+
// trim trailing whitespace in the header
1201+
let header_value = if let Some(last_visible) = value_slice
1202+
.iter()
1203+
.rposition(|b| *b != b' ' && *b != b'\t' && *b != b'\r' && *b != b'\n')
1204+
{
1205+
// There is at least one non-whitespace character.
1206+
&value_slice[0..last_visible+1]
1207+
} else {
1208+
// There is no non-whitespace character. This can only happen when value_slice is
1209+
// empty.
1210+
value_slice
1211+
};
12231212

1224-
let uninit_header = match iter.next() {
1225-
Some(header) => header,
1226-
None => break 'headers
1213+
*uninit_header = MaybeUninit::new(Header {
1214+
name: header_name,
1215+
value: header_value,
1216+
});
1217+
autoshrink.num_headers += 1;
1218+
continue 'headers;
12271219
};
12281220

1229-
// trim trailing whitespace in the header
1230-
let header_value = if let Some(last_visible) = value_slice
1231-
.iter()
1232-
.rposition(|b| *b != b' ' && *b != b'\t' && *b != b'\r' && *b != b'\n')
1233-
{
1234-
// There is at least one non-whitespace character.
1235-
&value_slice[0..last_visible+1]
1236-
} else {
1237-
// There is no non-whitespace character. This can only happen when value_slice is
1238-
// empty.
1239-
value_slice
1240-
};
1221+
// The header contains an invalid character. Reject the header if
1222+
// `ignore_invalid_headers_in_responses` is false; otherwise find the
1223+
// end of the current line and resume parsing on the next one.
1224+
if !config.ignore_invalid_headers {
1225+
return Err(invalid_header_err);
1226+
}
12411227

1242-
*uninit_header = MaybeUninit::new(Header {
1243-
name: header_name,
1244-
value: header_value,
1245-
});
1246-
autoshrink.num_headers += 1;
1228+
loop {
1229+
if b == b'\r' {
1230+
expect!(bytes.next() == b'\n' => Err(invalid_header_err));
1231+
break;
1232+
}
1233+
if b == b'\n' {
1234+
break;
1235+
}
1236+
if b == b'\0' {
1237+
return Err(invalid_header_err);
1238+
}
1239+
b = next!(bytes);
1240+
}
1241+
bytes.slice();
12471242
}
12481243

12491244
result

0 commit comments

Comments
 (0)