Skip to content

Commit 28aea9b

Browse files
MDA2AVclaude
andcommitted
Align test validation with docs and probe STRICT expectations
- All status checks use Exact(400) instead of Range4xx to match probe - MUST-400 tests (SP-BEFORE-COLON, MISSING-HOST, DUPLICATE-HOST, OBS-FOLD, CR-ONLY) no longer allow connection close as a passing outcome - INVALID-VERSION accepts only 400/505 (not full 4xx/5xx range) - Scored smuggling tests (TE-XCHUNKED, TE-TRAILING-SPACE, CLTE/TECL-PIPELINE) now return Fail for 2xx instead of Warn - Malformed input CustomValidators accept only documented status codes - MAL-BINARY-GARBAGE now handles timeout (was a bug) - MAL-EMPTY-REQUEST sends zero bytes (was sending CRLF) - MAL-INCOMPLETE-REQUEST sends partial request with headers (was just "GET ") - Added missing RfcReferences to all smuggling tests - Fixed RfcReferences for SP-BEFORE-COLON, MISSING-HOST, DUPLICATE-HOST - Fixed duplicate Glossary breadcrumb in docs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent f87f819 commit 28aea9b

4 files changed

Lines changed: 86 additions & 83 deletions

File tree

docs/content/docs/_index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
---
22
title: Glossary
33
description: "Glossary — Http11Probe documentation"
4+
breadcrumbs: false
45
sidebar:
56
open: false
67
---

src/Http11Probe/TestCases/Suites/ComplianceSuite.cs

Lines changed: 27 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public static IEnumerable<TestCase> GetTestCases()
2828
PayloadFactory = ctx => MakeRequest($"GET / HTTP/1.1\nHost: {ctx.HostHeader}\r\n\r\n"),
2929
Expected = new ExpectedBehavior
3030
{
31-
ExpectedStatus = StatusCodeRange.Range4xx,
31+
ExpectedStatus = StatusCodeRange.Exact(400),
3232
AllowConnectionClose = true
3333
}
3434
};
@@ -42,7 +42,7 @@ public static IEnumerable<TestCase> GetTestCases()
4242
PayloadFactory = ctx => MakeRequest($"GET / HTTP/1.1\r\nHost: {ctx.HostHeader}\nX-Test: value\r\n\r\n"),
4343
Expected = new ExpectedBehavior
4444
{
45-
ExpectedStatus = StatusCodeRange.Range4xx,
45+
ExpectedStatus = StatusCodeRange.Exact(400),
4646
AllowConnectionClose = true
4747
}
4848
};
@@ -56,8 +56,7 @@ public static IEnumerable<TestCase> GetTestCases()
5656
PayloadFactory = ctx => MakeRequest($"GET / HTTP/1.1\r\nHost: {ctx.HostHeader}\r\nX-Test: value\r\n continued\r\n\r\n"),
5757
Expected = new ExpectedBehavior
5858
{
59-
ExpectedStatus = StatusCodeRange.Range4xx,
60-
AllowConnectionClose = true
59+
ExpectedStatus = StatusCodeRange.Exact(400)
6160
}
6261
};
6362

@@ -66,12 +65,11 @@ public static IEnumerable<TestCase> GetTestCases()
6665
Id = "RFC9110-5.6.2-SP-BEFORE-COLON",
6766
Description = "Whitespace between header name and colon must be rejected",
6867
Category = TestCategory.Compliance,
69-
RfcReference = "RFC 9110 §5.6.2",
68+
RfcReference = "RFC 9112 §5",
7069
PayloadFactory = ctx => MakeRequest($"GET / HTTP/1.1\r\nHost: {ctx.HostHeader}\r\nX-Test : value\r\n\r\n"),
7170
Expected = new ExpectedBehavior
7271
{
73-
ExpectedStatus = StatusCodeRange.Range4xx,
74-
AllowConnectionClose = true
72+
ExpectedStatus = StatusCodeRange.Exact(400)
7573
}
7674
};
7775

@@ -84,7 +82,7 @@ public static IEnumerable<TestCase> GetTestCases()
8482
PayloadFactory = ctx => MakeRequest($"GET / HTTP/1.1\r\nHost: {ctx.HostHeader}\r\n\r\n"),
8583
Expected = new ExpectedBehavior
8684
{
87-
ExpectedStatus = StatusCodeRange.Range4xx,
85+
ExpectedStatus = StatusCodeRange.Exact(400),
8886
AllowConnectionClose = true
8987
}
9088
};
@@ -94,12 +92,11 @@ public static IEnumerable<TestCase> GetTestCases()
9492
Id = "RFC9112-7.1-MISSING-HOST",
9593
Description = "Request without Host header must be rejected with 400",
9694
Category = TestCategory.Compliance,
97-
RfcReference = "RFC 9112 §7.1",
95+
RfcReference = "RFC 9112 §3.2",
9896
PayloadFactory = _ => MakeRequest("GET / HTTP/1.1\r\n\r\n"),
9997
Expected = new ExpectedBehavior
10098
{
101-
ExpectedStatus = StatusCodeRange.Range4xx,
102-
AllowConnectionClose = true
99+
ExpectedStatus = StatusCodeRange.Exact(400)
103100
}
104101
};
105102

@@ -112,8 +109,14 @@ public static IEnumerable<TestCase> GetTestCases()
112109
PayloadFactory = ctx => MakeRequest($"GET / HTTP/9.9\r\nHost: {ctx.HostHeader}\r\n\r\n"),
113110
Expected = new ExpectedBehavior
114111
{
115-
ExpectedStatus = StatusCodeRange.Range4xxOr5xx,
116-
AllowConnectionClose = true
112+
CustomValidator = (response, state) =>
113+
{
114+
if (response is null)
115+
return state == ConnectionState.ClosedByServer ? TestVerdict.Pass : TestVerdict.Fail;
116+
if (response.StatusCode is 400 or 505)
117+
return TestVerdict.Pass;
118+
return TestVerdict.Fail;
119+
}
117120
}
118121
};
119122

@@ -126,7 +129,7 @@ public static IEnumerable<TestCase> GetTestCases()
126129
PayloadFactory = ctx => MakeRequest($"GET / HTTP/1.1\r\nHost: {ctx.HostHeader}\r\n: empty-name\r\n\r\n"),
127130
Expected = new ExpectedBehavior
128131
{
129-
ExpectedStatus = StatusCodeRange.Range4xx,
132+
ExpectedStatus = StatusCodeRange.Exact(400),
130133
AllowConnectionClose = true
131134
}
132135
};
@@ -140,8 +143,7 @@ public static IEnumerable<TestCase> GetTestCases()
140143
PayloadFactory = ctx => MakeRequest($"GET / HTTP/1.1\rHost: {ctx.HostHeader}\r\n\r\n"),
141144
Expected = new ExpectedBehavior
142145
{
143-
ExpectedStatus = StatusCodeRange.Range4xx,
144-
AllowConnectionClose = true
146+
ExpectedStatus = StatusCodeRange.Exact(400)
145147
}
146148
};
147149

@@ -154,7 +156,7 @@ public static IEnumerable<TestCase> GetTestCases()
154156
PayloadFactory = ctx => MakeRequest($"GET HTTP/1.1\r\nHost: {ctx.HostHeader}\r\n\r\n"),
155157
Expected = new ExpectedBehavior
156158
{
157-
ExpectedStatus = StatusCodeRange.Range4xx,
159+
ExpectedStatus = StatusCodeRange.Exact(400),
158160
AllowConnectionClose = true
159161
}
160162
};
@@ -168,7 +170,7 @@ public static IEnumerable<TestCase> GetTestCases()
168170
PayloadFactory = ctx => MakeRequest($"GET /path#frag HTTP/1.1\r\nHost: {ctx.HostHeader}\r\n\r\n"),
169171
Expected = new ExpectedBehavior
170172
{
171-
ExpectedStatus = StatusCodeRange.Range4xx,
173+
ExpectedStatus = StatusCodeRange.Exact(400),
172174
AllowConnectionClose = true
173175
}
174176
};
@@ -186,7 +188,7 @@ public static IEnumerable<TestCase> GetTestCases()
186188
{
187189
if (state is ConnectionState.TimedOut or ConnectionState.ClosedByServer)
188190
return TestVerdict.Pass;
189-
if (response is not null && response.StatusCode >= 400)
191+
if (response is not null && response.StatusCode == 400)
190192
return TestVerdict.Pass;
191193
return TestVerdict.Fail;
192194
}
@@ -202,7 +204,7 @@ public static IEnumerable<TestCase> GetTestCases()
202204
PayloadFactory = ctx => MakeRequest($"GET / HTTP/1.1\r\nHost: {ctx.HostHeader}\r\nBad[Name: value\r\n\r\n"),
203205
Expected = new ExpectedBehavior
204206
{
205-
ExpectedStatus = StatusCodeRange.Range4xx,
207+
ExpectedStatus = StatusCodeRange.Exact(400),
206208
AllowConnectionClose = true
207209
}
208210
};
@@ -216,7 +218,7 @@ public static IEnumerable<TestCase> GetTestCases()
216218
PayloadFactory = ctx => MakeRequest($"GET / HTTP/1.1\r\nHost: {ctx.HostHeader}\r\nNoColonHere\r\n\r\n"),
217219
Expected = new ExpectedBehavior
218220
{
219-
ExpectedStatus = StatusCodeRange.Range4xx,
221+
ExpectedStatus = StatusCodeRange.Exact(400),
220222
AllowConnectionClose = true
221223
}
222224
};
@@ -226,12 +228,11 @@ public static IEnumerable<TestCase> GetTestCases()
226228
Id = "RFC9110-5.4-DUPLICATE-HOST",
227229
Description = "Duplicate Host headers with different values must be rejected",
228230
Category = TestCategory.Compliance,
229-
RfcReference = "RFC 9110 §5.4",
231+
RfcReference = "RFC 9112 §3.2",
230232
PayloadFactory = ctx => MakeRequest($"GET / HTTP/1.1\r\nHost: {ctx.HostHeader}\r\nHost: other.example.com\r\n\r\n"),
231233
Expected = new ExpectedBehavior
232234
{
233-
ExpectedStatus = StatusCodeRange.Range4xx,
234-
AllowConnectionClose = true
235+
ExpectedStatus = StatusCodeRange.Exact(400)
235236
}
236237
};
237238

@@ -244,7 +245,7 @@ public static IEnumerable<TestCase> GetTestCases()
244245
PayloadFactory = ctx => MakeRequest($"POST / HTTP/1.1\r\nHost: {ctx.HostHeader}\r\nContent-Length: abc\r\n\r\n"),
245246
Expected = new ExpectedBehavior
246247
{
247-
ExpectedStatus = StatusCodeRange.Range4xx,
248+
ExpectedStatus = StatusCodeRange.Exact(400),
248249
AllowConnectionClose = true
249250
}
250251
};
@@ -258,7 +259,7 @@ public static IEnumerable<TestCase> GetTestCases()
258259
PayloadFactory = ctx => MakeRequest($"POST / HTTP/1.1\r\nHost: {ctx.HostHeader}\r\nContent-Length: +5\r\n\r\nhello"),
259260
Expected = new ExpectedBehavior
260261
{
261-
ExpectedStatus = StatusCodeRange.Range4xx,
262+
ExpectedStatus = StatusCodeRange.Exact(400),
262263
AllowConnectionClose = true
263264
}
264265
};

src/Http11Probe/TestCases/Suites/MalformedInputSuite.cs

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,15 @@ public static IEnumerable<TestCase> GetTestCases()
2121
},
2222
Expected = new ExpectedBehavior
2323
{
24-
ExpectedStatus = StatusCodeRange.Range4xx,
25-
AllowConnectionClose = true
24+
CustomValidator = (response, state) =>
25+
{
26+
// Any of these is acceptable: 400, close, or timeout
27+
if (state is ConnectionState.TimedOut or ConnectionState.ClosedByServer)
28+
return TestVerdict.Pass;
29+
if (response is not null && response.StatusCode == 400)
30+
return TestVerdict.Pass;
31+
return TestVerdict.Fail;
32+
}
2633
}
2734
};
2835

@@ -42,8 +49,8 @@ public static IEnumerable<TestCase> GetTestCases()
4249
{
4350
if (response is null)
4451
return state == ConnectionState.ClosedByServer ? TestVerdict.Pass : TestVerdict.Fail;
45-
// 414 is ideal, but any 4xx is acceptable
46-
return response.StatusCode >= 400 && response.StatusCode < 600
52+
// 414 is ideal, 400 and 431 are also acceptable
53+
return response.StatusCode is 400 or 414 or 431
4754
? TestVerdict.Pass
4855
: TestVerdict.Fail;
4956
}
@@ -66,7 +73,7 @@ public static IEnumerable<TestCase> GetTestCases()
6673
{
6774
if (response is null)
6875
return state == ConnectionState.ClosedByServer ? TestVerdict.Pass : TestVerdict.Fail;
69-
return response.StatusCode >= 400 && response.StatusCode < 600
76+
return response.StatusCode is 400 or 431
7077
? TestVerdict.Pass
7178
: TestVerdict.Fail;
7279
}
@@ -93,7 +100,7 @@ public static IEnumerable<TestCase> GetTestCases()
93100
{
94101
if (response is null)
95102
return state == ConnectionState.ClosedByServer ? TestVerdict.Pass : TestVerdict.Fail;
96-
return response.StatusCode >= 400 && response.StatusCode < 600
103+
return response.StatusCode is 400 or 431
97104
? TestVerdict.Pass
98105
: TestVerdict.Fail;
99106
}
@@ -108,7 +115,7 @@ public static IEnumerable<TestCase> GetTestCases()
108115
PayloadFactory = ctx => MakeRequest($"GET /\0test HTTP/1.1\r\nHost: {ctx.HostHeader}\r\n\r\n"),
109116
Expected = new ExpectedBehavior
110117
{
111-
ExpectedStatus = StatusCodeRange.Range4xx,
118+
ExpectedStatus = StatusCodeRange.Exact(400),
112119
AllowConnectionClose = true
113120
}
114121
};
@@ -126,25 +133,25 @@ public static IEnumerable<TestCase> GetTestCases()
126133
},
127134
Expected = new ExpectedBehavior
128135
{
129-
ExpectedStatus = StatusCodeRange.Range4xx,
136+
ExpectedStatus = StatusCodeRange.Exact(400),
130137
AllowConnectionClose = true
131138
}
132139
};
133140

134141
yield return new TestCase
135142
{
136143
Id = "MAL-INCOMPLETE-REQUEST",
137-
Description = "Incomplete request line (just 'GET ') should timeout or close, not crash",
144+
Description = "Partial HTTP request — request-line and headers but no final CRLF",
138145
Category = TestCategory.MalformedInput,
139-
PayloadFactory = _ => MakeRequest("GET "),
146+
PayloadFactory = ctx => MakeRequest($"GET / HTTP/1.1\r\nHost: {ctx.HostHeader}\r\nX-Test: value"),
140147
Expected = new ExpectedBehavior
141148
{
142149
CustomValidator = (response, state) =>
143150
{
144151
// Any of these is acceptable: timeout, close, or 400
145152
if (state is ConnectionState.TimedOut or ConnectionState.ClosedByServer)
146153
return TestVerdict.Pass;
147-
if (response is not null && response.StatusCode >= 400)
154+
if (response is not null && response.StatusCode == 400)
148155
return TestVerdict.Pass;
149156
return TestVerdict.Fail;
150157
}
@@ -154,16 +161,16 @@ public static IEnumerable<TestCase> GetTestCases()
154161
yield return new TestCase
155162
{
156163
Id = "MAL-EMPTY-REQUEST",
157-
Description = "Empty request (just CRLF) should be handled gracefully",
164+
Description = "Zero bytes — TCP connection established without sending any data",
158165
Category = TestCategory.MalformedInput,
159-
PayloadFactory = _ => MakeRequest("\r\n"),
166+
PayloadFactory = _ => [],
160167
Expected = new ExpectedBehavior
161168
{
162169
CustomValidator = (response, state) =>
163170
{
164171
if (state is ConnectionState.TimedOut or ConnectionState.ClosedByServer)
165172
return TestVerdict.Pass;
166-
if (response is not null && response.StatusCode >= 400)
173+
if (response is not null && response.StatusCode == 400)
167174
return TestVerdict.Pass;
168175
return TestVerdict.Fail;
169176
}
@@ -186,7 +193,7 @@ public static IEnumerable<TestCase> GetTestCases()
186193
{
187194
if (response is null)
188195
return state == ConnectionState.ClosedByServer ? TestVerdict.Pass : TestVerdict.Fail;
189-
return response.StatusCode >= 400 && response.StatusCode < 600
196+
return response.StatusCode is 400 or 431
190197
? TestVerdict.Pass
191198
: TestVerdict.Fail;
192199
}
@@ -209,7 +216,7 @@ public static IEnumerable<TestCase> GetTestCases()
209216
{
210217
if (response is null)
211218
return state == ConnectionState.ClosedByServer ? TestVerdict.Pass : TestVerdict.Fail;
212-
return response.StatusCode >= 400 && response.StatusCode < 600
219+
return response.StatusCode == 400
213220
? TestVerdict.Pass
214221
: TestVerdict.Fail;
215222
}
@@ -235,7 +242,7 @@ public static IEnumerable<TestCase> GetTestCases()
235242
},
236243
Expected = new ExpectedBehavior
237244
{
238-
ExpectedStatus = StatusCodeRange.Range4xx,
245+
ExpectedStatus = StatusCodeRange.Exact(400),
239246
AllowConnectionClose = true
240247
}
241248
};
@@ -259,7 +266,7 @@ public static IEnumerable<TestCase> GetTestCases()
259266
},
260267
Expected = new ExpectedBehavior
261268
{
262-
ExpectedStatus = StatusCodeRange.Range4xx,
269+
ExpectedStatus = StatusCodeRange.Exact(400),
263270
AllowConnectionClose = true
264271
}
265272
};
@@ -273,7 +280,7 @@ public static IEnumerable<TestCase> GetTestCases()
273280
$"POST / HTTP/1.1\r\nHost: {ctx.HostHeader}\r\nContent-Length: 99999999999999999999\r\n\r\n"),
274281
Expected = new ExpectedBehavior
275282
{
276-
ExpectedStatus = StatusCodeRange.Range4xx,
283+
ExpectedStatus = StatusCodeRange.Exact(400),
277284
AllowConnectionClose = true
278285
}
279286
};
@@ -290,7 +297,7 @@ public static IEnumerable<TestCase> GetTestCases()
290297
{
291298
if (state is ConnectionState.TimedOut or ConnectionState.ClosedByServer)
292299
return TestVerdict.Pass;
293-
if (response is not null && response.StatusCode >= 400)
300+
if (response is not null && response.StatusCode == 400)
294301
return TestVerdict.Pass;
295302
return TestVerdict.Fail;
296303
}

0 commit comments

Comments
 (0)