this repo has no description
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add comprehensive FastCGI test case corpus for parser validation

- Generated 24 binary test files covering all FastCGI record types
- Includes management records (GET_VALUES, UNKNOWN_TYPE)
- Covers all application record types for Responder/Authorizer/Filter roles
- Tests stream patterns, multiplexing, padding, and edge cases
- Added Python generators and validators for test case creation
- Comprehensive documentation of test scenarios and protocol flows

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

+1064
+123
test_cases/README.md
··· 1 + # FastCGI Test Cases 2 + 3 + This directory contains binary test case files representing various FastCGI records as defined in the FastCGI specification. These files can be used to test a FastCGI parser implementation. 4 + 5 + ## Test Case Files 6 + 7 + ### Management Records (requestId = 0) 8 + 9 + - **`get_values.bin`** - FCGI_GET_VALUES record requesting capability information 10 + - **`get_values_result.bin`** - FCGI_GET_VALUES_RESULT record with capability responses 11 + - **`unknown_type.bin`** - FCGI_UNKNOWN_TYPE record for unrecognized record types 12 + 13 + ### Application Records (requestId > 0) 14 + 15 + #### BEGIN_REQUEST Records 16 + - **`begin_request_responder.bin`** - Begin request for Responder role with KEEP_CONN flag 17 + - **`begin_request_no_keep.bin`** - Begin request for Responder role without KEEP_CONN flag 18 + - **`begin_request_authorizer.bin`** - Begin request for Authorizer role 19 + - **`begin_request_filter.bin`** - Begin request for Filter role 20 + 21 + #### Stream Records 22 + - **`params_get.bin`** - FCGI_PARAMS record with GET request environment variables 23 + - **`params_post.bin`** - FCGI_PARAMS record with POST request environment variables 24 + - **`params_empty.bin`** - Empty FCGI_PARAMS record (end of params stream) 25 + - **`stdin_form_data.bin`** - FCGI_STDIN record with form data 26 + - **`stdin_empty.bin`** - Empty FCGI_STDIN record (end of stdin stream) 27 + - **`stdout_response.bin`** - FCGI_STDOUT record with HTTP response 28 + - **`stdout_empty.bin`** - Empty FCGI_STDOUT record (end of stdout stream) 29 + - **`stderr_message.bin`** - FCGI_STDERR record with error message 30 + - **`stderr_empty.bin`** - Empty FCGI_STDERR record (end of stderr stream) 31 + - **`data_filter.bin`** - FCGI_DATA record with file content (for Filter role) 32 + - **`data_empty.bin`** - Empty FCGI_DATA record (end of data stream) 33 + 34 + #### Control Records 35 + - **`end_request_success.bin`** - FCGI_END_REQUEST record with successful completion 36 + - **`end_request_error.bin`** - FCGI_END_REQUEST record with error status 37 + - **`abort_request.bin`** - FCGI_ABORT_REQUEST record 38 + 39 + ### Complex Scenarios 40 + - **`multiplexed_requests.bin`** - Multiple concurrent requests on same connection 41 + - **`large_record.bin`** - Record with maximum content size (65KB) 42 + - **`padded_record.bin`** - Record with padding for 8-byte alignment 43 + 44 + ## Record Format 45 + 46 + All records follow the FastCGI record format: 47 + 48 + ``` 49 + typedef struct { 50 + unsigned char version; // FCGI_VERSION_1 (1) 51 + unsigned char type; // Record type (1-11) 52 + unsigned char requestIdB1; // Request ID high byte 53 + unsigned char requestIdB0; // Request ID low byte 54 + unsigned char contentLengthB1; // Content length high byte 55 + unsigned char contentLengthB0; // Content length low byte 56 + unsigned char paddingLength; // Padding length 57 + unsigned char reserved; // Reserved (always 0) 58 + // contentData[contentLength] 59 + // paddingData[paddingLength] 60 + } FCGI_Record; 61 + ``` 62 + 63 + ## Usage for Parser Testing 64 + 65 + These test cases can be used to verify: 66 + 67 + 1. **Header parsing** - Correct extraction of version, type, requestId, lengths 68 + 2. **Content parsing** - Proper handling of record body data 69 + 3. **Stream handling** - Recognition of stream start/end patterns 70 + 4. **Name-value pair parsing** - Decoding of FCGI_PARAMS format 71 + 5. **Role-specific parsing** - Different record sequences for each role 72 + 6. **Error handling** - Response to unknown types, malformed data 73 + 7. **Multiplexing** - Handling multiple concurrent request IDs 74 + 8. **Padding** - Correct skipping of padding bytes 75 + 76 + ## Typical Protocol Flows 77 + 78 + ### Simple Responder Request 79 + 1. `begin_request_responder.bin` 80 + 2. `params_get.bin` 81 + 3. `params_empty.bin` 82 + 4. `stdin_empty.bin` 83 + 5. `stdout_response.bin` 84 + 6. `stdout_empty.bin` 85 + 7. `end_request_success.bin` 86 + 87 + ### POST Request with Form Data 88 + 1. `begin_request_responder.bin` 89 + 2. `params_post.bin` 90 + 3. `params_empty.bin` 91 + 4. `stdin_form_data.bin` 92 + 5. `stdin_empty.bin` 93 + 6. `stdout_response.bin` 94 + 7. `stdout_empty.bin` 95 + 8. `end_request_success.bin` 96 + 97 + ### Authorizer Request 98 + 1. `begin_request_authorizer.bin` 99 + 2. `params_get.bin` 100 + 3. `params_empty.bin` 101 + 4. `stdout_response.bin` 102 + 5. `stdout_empty.bin` 103 + 6. `end_request_success.bin` 104 + 105 + ### Filter Request 106 + 1. `begin_request_filter.bin` 107 + 2. `params_get.bin` 108 + 3. `params_empty.bin` 109 + 4. `stdin_empty.bin` 110 + 5. `data_filter.bin` 111 + 6. `data_empty.bin` 112 + 7. `stdout_response.bin` 113 + 8. `stdout_empty.bin` 114 + 9. `end_request_success.bin` 115 + 116 + ## Binary Format Verification 117 + 118 + You can inspect the binary content using hexdump: 119 + ```bash 120 + hexdump -C begin_request_responder.bin 121 + ``` 122 + 123 + The first 8 bytes should always be the FastCGI header, followed by the record content and any padding.
test_cases/abort_request.bin

This is a binary file and will not be displayed.

test_cases/begin_request_authorizer.bin

This is a binary file and will not be displayed.

test_cases/begin_request_filter.bin

This is a binary file and will not be displayed.

test_cases/begin_request_no_keep.bin

This is a binary file and will not be displayed.

test_cases/begin_request_responder.bin

This is a binary file and will not be displayed.

test_cases/data_empty.bin

This is a binary file and will not be displayed.

test_cases/data_filter.bin

This is a binary file and will not be displayed.

test_cases/end_request_error.bin

This is a binary file and will not be displayed.

test_cases/end_request_success.bin

This is a binary file and will not be displayed.

+349
test_cases/generate_test_cases.py
··· 1 + #!/usr/bin/env python3 2 + """ 3 + Generate FastCGI test case files based on the specification. 4 + """ 5 + import struct 6 + import os 7 + 8 + # FastCGI constants from the specification 9 + FCGI_VERSION_1 = 1 10 + 11 + # Record types 12 + FCGI_BEGIN_REQUEST = 1 13 + FCGI_ABORT_REQUEST = 2 14 + FCGI_END_REQUEST = 3 15 + FCGI_PARAMS = 4 16 + FCGI_STDIN = 5 17 + FCGI_STDOUT = 6 18 + FCGI_STDERR = 7 19 + FCGI_DATA = 8 20 + FCGI_GET_VALUES = 9 21 + FCGI_GET_VALUES_RESULT = 10 22 + FCGI_UNKNOWN_TYPE = 11 23 + 24 + # Roles 25 + FCGI_RESPONDER = 1 26 + FCGI_AUTHORIZER = 2 27 + FCGI_FILTER = 3 28 + 29 + # Flags 30 + FCGI_KEEP_CONN = 1 31 + 32 + # Protocol status 33 + FCGI_REQUEST_COMPLETE = 0 34 + FCGI_CANT_MPX_CONN = 1 35 + FCGI_OVERLOADED = 2 36 + FCGI_UNKNOWN_ROLE = 3 37 + 38 + def create_record_header(version, record_type, request_id, content_length, padding_length=0): 39 + """Create a FastCGI record header.""" 40 + return struct.pack('>BBHHBB', 41 + version, 42 + record_type, 43 + request_id, 44 + content_length, 45 + padding_length, 46 + 0) # reserved 47 + 48 + def encode_name_value_pair(name, value): 49 + """Encode a name-value pair according to FastCGI spec.""" 50 + name_bytes = name.encode('utf-8') 51 + value_bytes = value.encode('utf-8') 52 + 53 + name_len = len(name_bytes) 54 + value_len = len(value_bytes) 55 + 56 + # Encode lengths 57 + if name_len < 128: 58 + name_len_encoded = struct.pack('B', name_len) 59 + else: 60 + name_len_encoded = struct.pack('>I', name_len | 0x80000000) 61 + 62 + if value_len < 128: 63 + value_len_encoded = struct.pack('B', value_len) 64 + else: 65 + value_len_encoded = struct.pack('>I', value_len | 0x80000000) 66 + 67 + return name_len_encoded + value_len_encoded + name_bytes + value_bytes 68 + 69 + def create_begin_request(): 70 + """Create FCGI_BEGIN_REQUEST record.""" 71 + # FCGI_BeginRequestBody 72 + body = struct.pack('>HB5x', FCGI_RESPONDER, FCGI_KEEP_CONN) 73 + header = create_record_header(FCGI_VERSION_1, FCGI_BEGIN_REQUEST, 1, len(body)) 74 + return header + body 75 + 76 + def create_begin_request_no_keep_conn(): 77 + """Create FCGI_BEGIN_REQUEST record without keep connection.""" 78 + body = struct.pack('>HB5x', FCGI_RESPONDER, 0) 79 + header = create_record_header(FCGI_VERSION_1, FCGI_BEGIN_REQUEST, 1, len(body)) 80 + return header + body 81 + 82 + def create_begin_request_authorizer(): 83 + """Create FCGI_BEGIN_REQUEST record for authorizer role.""" 84 + body = struct.pack('>HB5x', FCGI_AUTHORIZER, 0) 85 + header = create_record_header(FCGI_VERSION_1, FCGI_BEGIN_REQUEST, 2, len(body)) 86 + return header + body 87 + 88 + def create_begin_request_filter(): 89 + """Create FCGI_BEGIN_REQUEST record for filter role.""" 90 + body = struct.pack('>HB5x', FCGI_FILTER, FCGI_KEEP_CONN) 91 + header = create_record_header(FCGI_VERSION_1, FCGI_BEGIN_REQUEST, 3, len(body)) 92 + return header + body 93 + 94 + def create_end_request(): 95 + """Create FCGI_END_REQUEST record.""" 96 + # FCGI_EndRequestBody 97 + body = struct.pack('>IB3x', 0, FCGI_REQUEST_COMPLETE) # app_status=0, protocol_status=COMPLETE 98 + header = create_record_header(FCGI_VERSION_1, FCGI_END_REQUEST, 1, len(body)) 99 + return header + body 100 + 101 + def create_end_request_error(): 102 + """Create FCGI_END_REQUEST record with error status.""" 103 + body = struct.pack('>IB3x', 1, FCGI_REQUEST_COMPLETE) # app_status=1 (error) 104 + header = create_record_header(FCGI_VERSION_1, FCGI_END_REQUEST, 1, len(body)) 105 + return header + body 106 + 107 + def create_params_record(): 108 + """Create FCGI_PARAMS record with CGI environment variables.""" 109 + # Typical CGI parameters 110 + params = [ 111 + ("REQUEST_METHOD", "GET"), 112 + ("SCRIPT_NAME", "/test.cgi"), 113 + ("REQUEST_URI", "/test.cgi?foo=bar"), 114 + ("QUERY_STRING", "foo=bar"), 115 + ("SERVER_NAME", "localhost"), 116 + ("SERVER_PORT", "80"), 117 + ("HTTP_HOST", "localhost"), 118 + ("HTTP_USER_AGENT", "Mozilla/5.0"), 119 + ("CONTENT_TYPE", ""), 120 + ("CONTENT_LENGTH", "0") 121 + ] 122 + 123 + body = b'' 124 + for name, value in params: 125 + body += encode_name_value_pair(name, value) 126 + 127 + header = create_record_header(FCGI_VERSION_1, FCGI_PARAMS, 1, len(body)) 128 + return header + body 129 + 130 + def create_params_record_post(): 131 + """Create FCGI_PARAMS record for POST request.""" 132 + params = [ 133 + ("REQUEST_METHOD", "POST"), 134 + ("SCRIPT_NAME", "/form.cgi"), 135 + ("REQUEST_URI", "/form.cgi"), 136 + ("QUERY_STRING", ""), 137 + ("SERVER_NAME", "localhost"), 138 + ("SERVER_PORT", "443"), 139 + ("HTTPS", "on"), 140 + ("HTTP_HOST", "localhost"), 141 + ("HTTP_USER_AGENT", "curl/7.68.0"), 142 + ("CONTENT_TYPE", "application/x-www-form-urlencoded"), 143 + ("CONTENT_LENGTH", "23") 144 + ] 145 + 146 + body = b'' 147 + for name, value in params: 148 + body += encode_name_value_pair(name, value) 149 + 150 + header = create_record_header(FCGI_VERSION_1, FCGI_PARAMS, 1, len(body)) 151 + return header + body 152 + 153 + def create_empty_params(): 154 + """Create empty FCGI_PARAMS record (end of params stream).""" 155 + header = create_record_header(FCGI_VERSION_1, FCGI_PARAMS, 1, 0) 156 + return header 157 + 158 + def create_stdin_record(): 159 + """Create FCGI_STDIN record with form data.""" 160 + data = b"name=John&email=john@example.com" 161 + header = create_record_header(FCGI_VERSION_1, FCGI_STDIN, 1, len(data)) 162 + return header + data 163 + 164 + def create_empty_stdin(): 165 + """Create empty FCGI_STDIN record (end of stdin stream).""" 166 + header = create_record_header(FCGI_VERSION_1, FCGI_STDIN, 1, 0) 167 + return header 168 + 169 + def create_stdout_record(): 170 + """Create FCGI_STDOUT record with HTTP response.""" 171 + response = b"Content-Type: text/html\r\n\r\n<html><body>Hello World</body></html>" 172 + header = create_record_header(FCGI_VERSION_1, FCGI_STDOUT, 1, len(response)) 173 + return header + response 174 + 175 + def create_empty_stdout(): 176 + """Create empty FCGI_STDOUT record (end of stdout stream).""" 177 + header = create_record_header(FCGI_VERSION_1, FCGI_STDOUT, 1, 0) 178 + return header 179 + 180 + def create_stderr_record(): 181 + """Create FCGI_STDERR record with error message.""" 182 + error_msg = b"Warning: Configuration file not found\n" 183 + header = create_record_header(FCGI_VERSION_1, FCGI_STDERR, 1, len(error_msg)) 184 + return header + error_msg 185 + 186 + def create_empty_stderr(): 187 + """Create empty FCGI_STDERR record (end of stderr stream).""" 188 + header = create_record_header(FCGI_VERSION_1, FCGI_STDERR, 1, 0) 189 + return header 190 + 191 + def create_get_values(): 192 + """Create FCGI_GET_VALUES record.""" 193 + # Request standard capability variables 194 + body = (encode_name_value_pair("FCGI_MAX_CONNS", "") + 195 + encode_name_value_pair("FCGI_MAX_REQS", "") + 196 + encode_name_value_pair("FCGI_MPXS_CONNS", "")) 197 + header = create_record_header(FCGI_VERSION_1, FCGI_GET_VALUES, 0, len(body)) 198 + return header + body 199 + 200 + def create_get_values_result(): 201 + """Create FCGI_GET_VALUES_RESULT record.""" 202 + body = (encode_name_value_pair("FCGI_MAX_CONNS", "1") + 203 + encode_name_value_pair("FCGI_MAX_REQS", "1") + 204 + encode_name_value_pair("FCGI_MPXS_CONNS", "0")) 205 + header = create_record_header(FCGI_VERSION_1, FCGI_GET_VALUES_RESULT, 0, len(body)) 206 + return header + body 207 + 208 + def create_unknown_type(): 209 + """Create FCGI_UNKNOWN_TYPE record.""" 210 + # FCGI_UnknownTypeBody 211 + body = struct.pack('B7x', 99) # unknown type 99 212 + header = create_record_header(FCGI_VERSION_1, FCGI_UNKNOWN_TYPE, 0, len(body)) 213 + return header + body 214 + 215 + def create_abort_request(): 216 + """Create FCGI_ABORT_REQUEST record.""" 217 + header = create_record_header(FCGI_VERSION_1, FCGI_ABORT_REQUEST, 1, 0) 218 + return header 219 + 220 + def create_data_record(): 221 + """Create FCGI_DATA record (for Filter role).""" 222 + file_data = b"This is file content that needs to be filtered\nLine 2\nLine 3\n" 223 + header = create_record_header(FCGI_VERSION_1, FCGI_DATA, 3, len(file_data)) 224 + return header + file_data 225 + 226 + def create_empty_data(): 227 + """Create empty FCGI_DATA record (end of data stream).""" 228 + header = create_record_header(FCGI_VERSION_1, FCGI_DATA, 3, 0) 229 + return header 230 + 231 + def create_multiplexed_records(): 232 + """Create a sequence showing multiplexed requests.""" 233 + records = [] 234 + 235 + # Request 1 begins 236 + records.append(create_begin_request()) 237 + records.append(create_params_record()) 238 + records.append(create_empty_params()) 239 + 240 + # Request 2 begins (different request ID) 241 + body = struct.pack('>HB5x', FCGI_RESPONDER, FCGI_KEEP_CONN) 242 + header = create_record_header(FCGI_VERSION_1, FCGI_BEGIN_REQUEST, 2, len(body)) 243 + records.append(header + body) 244 + 245 + # Request 1 continues 246 + records.append(create_empty_stdin()) 247 + 248 + # Request 2 params 249 + params = [("REQUEST_METHOD", "POST"), ("SCRIPT_NAME", "/other.cgi")] 250 + body = b'' 251 + for name, value in params: 252 + body += encode_name_value_pair(name, value) 253 + header = create_record_header(FCGI_VERSION_1, FCGI_PARAMS, 2, len(body)) 254 + records.append(header + body) 255 + 256 + # Request 2 empty params 257 + header = create_record_header(FCGI_VERSION_1, FCGI_PARAMS, 2, 0) 258 + records.append(header) 259 + 260 + # Request 2 completes first 261 + response = b"Content-Type: text/plain\r\n\r\nRequest 2 done" 262 + header = create_record_header(FCGI_VERSION_1, FCGI_STDOUT, 2, len(response)) 263 + records.append(header + response) 264 + 265 + header = create_record_header(FCGI_VERSION_1, FCGI_STDOUT, 2, 0) 266 + records.append(header) 267 + 268 + body = struct.pack('>IB3x', 0, FCGI_REQUEST_COMPLETE) 269 + header = create_record_header(FCGI_VERSION_1, FCGI_END_REQUEST, 2, len(body)) 270 + records.append(header + body) 271 + 272 + # Request 1 completes 273 + response = b"Content-Type: text/html\r\n\r\n<html><body>Request 1 done</body></html>" 274 + header = create_record_header(FCGI_VERSION_1, FCGI_STDOUT, 1, len(response)) 275 + records.append(header + response) 276 + 277 + header = create_record_header(FCGI_VERSION_1, FCGI_STDOUT, 1, 0) 278 + records.append(header) 279 + 280 + body = struct.pack('>IB3x', 0, FCGI_REQUEST_COMPLETE) 281 + header = create_record_header(FCGI_VERSION_1, FCGI_END_REQUEST, 1, len(body)) 282 + records.append(header + body) 283 + 284 + return b''.join(records) 285 + 286 + def create_large_record(): 287 + """Create a record with maximum content size.""" 288 + # Create a large response (just under 64KB) 289 + large_content = b"x" * 65000 290 + header = create_record_header(FCGI_VERSION_1, FCGI_STDOUT, 1, len(large_content)) 291 + return header + large_content 292 + 293 + def create_padded_record(): 294 + """Create a record with padding for alignment.""" 295 + data = b"Hello" # 5 bytes 296 + padding_length = 3 # to align to 8-byte boundary (5 + 3 = 8) 297 + header = create_record_header(FCGI_VERSION_1, FCGI_STDOUT, 1, len(data), padding_length) 298 + padding = b'\x00' * padding_length 299 + return header + data + padding 300 + 301 + # Test case definitions 302 + test_cases = { 303 + "begin_request_responder.bin": create_begin_request, 304 + "begin_request_no_keep.bin": create_begin_request_no_keep_conn, 305 + "begin_request_authorizer.bin": create_begin_request_authorizer, 306 + "begin_request_filter.bin": create_begin_request_filter, 307 + "end_request_success.bin": create_end_request, 308 + "end_request_error.bin": create_end_request_error, 309 + "params_get.bin": create_params_record, 310 + "params_post.bin": create_params_record_post, 311 + "params_empty.bin": create_empty_params, 312 + "stdin_form_data.bin": create_stdin_record, 313 + "stdin_empty.bin": create_empty_stdin, 314 + "stdout_response.bin": create_stdout_record, 315 + "stdout_empty.bin": create_empty_stdout, 316 + "stderr_message.bin": create_stderr_record, 317 + "stderr_empty.bin": create_empty_stderr, 318 + "get_values.bin": create_get_values, 319 + "get_values_result.bin": create_get_values_result, 320 + "unknown_type.bin": create_unknown_type, 321 + "abort_request.bin": create_abort_request, 322 + "data_filter.bin": create_data_record, 323 + "data_empty.bin": create_empty_data, 324 + "multiplexed_requests.bin": create_multiplexed_records, 325 + "large_record.bin": create_large_record, 326 + "padded_record.bin": create_padded_record, 327 + } 328 + 329 + if __name__ == "__main__": 330 + for filename, creator in test_cases.items(): 331 + with open(filename, 'wb') as f: 332 + f.write(creator()) 333 + print(f"Created {filename}") 334 + 335 + print(f"\nGenerated {len(test_cases)} test case files") 336 + print("\nTest case descriptions:") 337 + print("- begin_request_*.bin: Various BEGIN_REQUEST records for different roles") 338 + print("- end_request_*.bin: END_REQUEST records with different status codes") 339 + print("- params_*.bin: PARAMS records with CGI environment variables") 340 + print("- stdin_*.bin: STDIN records with request body data") 341 + print("- stdout_*.bin: STDOUT records with response data") 342 + print("- stderr_*.bin: STDERR records with error messages") 343 + print("- get_values*.bin: Management records for capability negotiation") 344 + print("- unknown_type.bin: Unknown record type handling") 345 + print("- abort_request.bin: Request abortion") 346 + print("- data_*.bin: DATA records for Filter role") 347 + print("- multiplexed_requests.bin: Multiple concurrent requests") 348 + print("- large_record.bin: Maximum size record") 349 + print("- padded_record.bin: Record with padding for alignment")
test_cases/get_values.bin

This is a binary file and will not be displayed.

test_cases/get_values_result.bin

This is a binary file and will not be displayed.

test_cases/large_record.bin

This is a binary file and will not be displayed.

test_cases/multiplexed_requests.bin

This is a binary file and will not be displayed.

test_cases/padded_record.bin

This is a binary file and will not be displayed.

test_cases/params_empty.bin

This is a binary file and will not be displayed.

test_cases/params_get.bin

This is a binary file and will not be displayed.

test_cases/params_post.bin

This is a binary file and will not be displayed.

test_cases/stderr_empty.bin

This is a binary file and will not be displayed.

test_cases/stderr_message.bin

This is a binary file and will not be displayed.

test_cases/stdin_empty.bin

This is a binary file and will not be displayed.

test_cases/stdin_form_data.bin

This is a binary file and will not be displayed.

test_cases/stdout_empty.bin

This is a binary file and will not be displayed.

test_cases/stdout_response.bin

This is a binary file and will not be displayed.

+24
test_cases/test_case_sizes.txt
··· 1 + abort_request.bin 8 bytes 2 + begin_request_authorizer.bin 16 bytes 3 + begin_request_filter.bin 16 bytes 4 + begin_request_no_keep.bin 16 bytes 5 + begin_request_responder.bin 16 bytes 6 + data_empty.bin 8 bytes 7 + data_filter.bin 69 bytes 8 + end_request_error.bin 16 bytes 9 + end_request_success.bin 16 bytes 10 + get_values_result.bin 59 bytes 11 + get_values.bin 56 bytes 12 + large_record.bin 65008 bytes 13 + multiplexed_requests.bin 496 bytes 14 + padded_record.bin 16 bytes 15 + params_empty.bin 8 bytes 16 + params_get.bin 216 bytes 17 + params_post.bin 246 bytes 18 + stderr_empty.bin 8 bytes 19 + stderr_message.bin 46 bytes 20 + stdin_empty.bin 8 bytes 21 + stdin_form_data.bin 40 bytes 22 + stdout_empty.bin 8 bytes 23 + stdout_response.bin 72 bytes 24 + unknown_type.bin 16 bytes
test_cases/unknown_type.bin

This is a binary file and will not be displayed.

+130
test_cases/validate_test_cases.py
··· 1 + #!/usr/bin/env python3 2 + """ 3 + Validate that the generated FastCGI test cases are properly formatted. 4 + """ 5 + import struct 6 + import os 7 + import glob 8 + 9 + def parse_record_header(data): 10 + """Parse a FastCGI record header.""" 11 + if len(data) < 8: 12 + return None 13 + 14 + version, record_type, request_id, content_length, padding_length, reserved = struct.unpack('>BBHHBB', data[:8]) 15 + return { 16 + 'version': version, 17 + 'type': record_type, 18 + 'request_id': request_id, 19 + 'content_length': content_length, 20 + 'padding_length': padding_length, 21 + 'reserved': reserved, 22 + 'total_length': 8 + content_length + padding_length 23 + } 24 + 25 + def validate_file(filename): 26 + """Validate a single test case file.""" 27 + print(f"\nValidating {filename}:") 28 + 29 + with open(filename, 'rb') as f: 30 + data = f.read() 31 + 32 + if len(data) < 8: 33 + print(f" ❌ File too short: {len(data)} bytes") 34 + return False 35 + 36 + # Parse all records in the file 37 + offset = 0 38 + record_count = 0 39 + 40 + while offset < len(data): 41 + if offset + 8 > len(data): 42 + print(f" ❌ Incomplete header at offset {offset}") 43 + return False 44 + 45 + header = parse_record_header(data[offset:]) 46 + if not header: 47 + print(f" ❌ Failed to parse header at offset {offset}") 48 + return False 49 + 50 + record_count += 1 51 + print(f" Record {record_count}:") 52 + print(f" Version: {header['version']}") 53 + print(f" Type: {header['type']}") 54 + print(f" Request ID: {header['request_id']}") 55 + print(f" Content Length: {header['content_length']}") 56 + print(f" Padding Length: {header['padding_length']}") 57 + print(f" Reserved: {header['reserved']}") 58 + 59 + # Validate header fields 60 + if header['version'] != 1: 61 + print(f" ❌ Invalid version: {header['version']}") 62 + return False 63 + 64 + if header['type'] < 1 or header['type'] > 11: 65 + print(f" ❌ Invalid record type: {header['type']}") 66 + return False 67 + 68 + if header['reserved'] != 0: 69 + print(f" ❌ Reserved field not zero: {header['reserved']}") 70 + return False 71 + 72 + # Check if we have enough data for content and padding 73 + expected_end = offset + header['total_length'] 74 + if expected_end > len(data): 75 + print(f" ❌ Not enough data: need {header['total_length']}, have {len(data) - offset}") 76 + return False 77 + 78 + # Extract content 79 + content_start = offset + 8 80 + content_end = content_start + header['content_length'] 81 + content = data[content_start:content_end] 82 + 83 + # Extract padding 84 + padding_start = content_end 85 + padding_end = padding_start + header['padding_length'] 86 + padding = data[padding_start:padding_end] 87 + 88 + print(f" Content: {len(content)} bytes") 89 + if header['padding_length'] > 0: 90 + print(f" Padding: {len(padding)} bytes") 91 + 92 + # Show content preview for small records 93 + if len(content) <= 32: 94 + print(f" Content hex: {content.hex()}") 95 + else: 96 + print(f" Content preview: {content[:16].hex()}...") 97 + 98 + print(f" ✅ Record valid") 99 + 100 + offset = expected_end 101 + 102 + print(f" ✅ File valid: {record_count} record(s), {len(data)} total bytes") 103 + return True 104 + 105 + def main(): 106 + """Validate all test case files.""" 107 + test_files = glob.glob("*.bin") 108 + test_files.sort() 109 + 110 + print(f"Found {len(test_files)} test case files") 111 + 112 + valid_count = 0 113 + for filename in test_files: 114 + if validate_file(filename): 115 + valid_count += 1 116 + 117 + print(f"\n{'='*50}") 118 + print(f"Validation complete: {valid_count}/{len(test_files)} files valid") 119 + 120 + if valid_count == len(test_files): 121 + print("✅ All test cases are valid!") 122 + else: 123 + print("❌ Some test cases failed validation") 124 + return 1 125 + 126 + return 0 127 + 128 + if __name__ == "__main__": 129 + import sys 130 + sys.exit(main())
+438
test_cases/validation_results.txt
··· 1 + Found 24 test case files 2 + 3 + Validating abort_request.bin: 4 + Record 1: 5 + Version: 1 6 + Type: 2 7 + Request ID: 1 8 + Content Length: 0 9 + Padding Length: 0 10 + Reserved: 0 11 + Content: 0 bytes 12 + Content hex: 13 + ✅ Record valid 14 + ✅ File valid: 1 record(s), 8 total bytes 15 + 16 + Validating begin_request_authorizer.bin: 17 + Record 1: 18 + Version: 1 19 + Type: 1 20 + Request ID: 2 21 + Content Length: 8 22 + Padding Length: 0 23 + Reserved: 0 24 + Content: 8 bytes 25 + Content hex: 0002000000000000 26 + ✅ Record valid 27 + ✅ File valid: 1 record(s), 16 total bytes 28 + 29 + Validating begin_request_filter.bin: 30 + Record 1: 31 + Version: 1 32 + Type: 1 33 + Request ID: 3 34 + Content Length: 8 35 + Padding Length: 0 36 + Reserved: 0 37 + Content: 8 bytes 38 + Content hex: 0003010000000000 39 + ✅ Record valid 40 + ✅ File valid: 1 record(s), 16 total bytes 41 + 42 + Validating begin_request_no_keep.bin: 43 + Record 1: 44 + Version: 1 45 + Type: 1 46 + Request ID: 1 47 + Content Length: 8 48 + Padding Length: 0 49 + Reserved: 0 50 + Content: 8 bytes 51 + Content hex: 0001000000000000 52 + ✅ Record valid 53 + ✅ File valid: 1 record(s), 16 total bytes 54 + 55 + Validating begin_request_responder.bin: 56 + Record 1: 57 + Version: 1 58 + Type: 1 59 + Request ID: 1 60 + Content Length: 8 61 + Padding Length: 0 62 + Reserved: 0 63 + Content: 8 bytes 64 + Content hex: 0001010000000000 65 + ✅ Record valid 66 + ✅ File valid: 1 record(s), 16 total bytes 67 + 68 + Validating data_empty.bin: 69 + Record 1: 70 + Version: 1 71 + Type: 8 72 + Request ID: 3 73 + Content Length: 0 74 + Padding Length: 0 75 + Reserved: 0 76 + Content: 0 bytes 77 + Content hex: 78 + ✅ Record valid 79 + ✅ File valid: 1 record(s), 8 total bytes 80 + 81 + Validating data_filter.bin: 82 + Record 1: 83 + Version: 1 84 + Type: 8 85 + Request ID: 3 86 + Content Length: 61 87 + Padding Length: 0 88 + Reserved: 0 89 + Content: 61 bytes 90 + Content preview: 546869732069732066696c6520636f6e... 91 + ✅ Record valid 92 + ✅ File valid: 1 record(s), 69 total bytes 93 + 94 + Validating end_request_error.bin: 95 + Record 1: 96 + Version: 1 97 + Type: 3 98 + Request ID: 1 99 + Content Length: 8 100 + Padding Length: 0 101 + Reserved: 0 102 + Content: 8 bytes 103 + Content hex: 0000000100000000 104 + ✅ Record valid 105 + ✅ File valid: 1 record(s), 16 total bytes 106 + 107 + Validating end_request_success.bin: 108 + Record 1: 109 + Version: 1 110 + Type: 3 111 + Request ID: 1 112 + Content Length: 8 113 + Padding Length: 0 114 + Reserved: 0 115 + Content: 8 bytes 116 + Content hex: 0000000000000000 117 + ✅ Record valid 118 + ✅ File valid: 1 record(s), 16 total bytes 119 + 120 + Validating get_values.bin: 121 + Record 1: 122 + Version: 1 123 + Type: 9 124 + Request ID: 0 125 + Content Length: 48 126 + Padding Length: 0 127 + Reserved: 0 128 + Content: 48 bytes 129 + Content preview: 0e00464347495f4d41585f434f4e4e53... 130 + ✅ Record valid 131 + ✅ File valid: 1 record(s), 56 total bytes 132 + 133 + Validating get_values_result.bin: 134 + Record 1: 135 + Version: 1 136 + Type: 10 137 + Request ID: 0 138 + Content Length: 51 139 + Padding Length: 0 140 + Reserved: 0 141 + Content: 51 bytes 142 + Content preview: 0e01464347495f4d41585f434f4e4e53... 143 + ✅ Record valid 144 + ✅ File valid: 1 record(s), 59 total bytes 145 + 146 + Validating large_record.bin: 147 + Record 1: 148 + Version: 1 149 + Type: 6 150 + Request ID: 1 151 + Content Length: 65000 152 + Padding Length: 0 153 + Reserved: 0 154 + Content: 65000 bytes 155 + Content preview: 78787878787878787878787878787878... 156 + ✅ Record valid 157 + ✅ File valid: 1 record(s), 65008 total bytes 158 + 159 + Validating multiplexed_requests.bin: 160 + Record 1: 161 + Version: 1 162 + Type: 1 163 + Request ID: 1 164 + Content Length: 8 165 + Padding Length: 0 166 + Reserved: 0 167 + Content: 8 bytes 168 + Content hex: 0001010000000000 169 + ✅ Record valid 170 + Record 2: 171 + Version: 1 172 + Type: 4 173 + Request ID: 1 174 + Content Length: 208 175 + Padding Length: 0 176 + Reserved: 0 177 + Content: 208 bytes 178 + Content preview: 0e03524551554553545f4d4554484f44... 179 + ✅ Record valid 180 + Record 3: 181 + Version: 1 182 + Type: 4 183 + Request ID: 1 184 + Content Length: 0 185 + Padding Length: 0 186 + Reserved: 0 187 + Content: 0 bytes 188 + Content hex: 189 + ✅ Record valid 190 + Record 4: 191 + Version: 1 192 + Type: 1 193 + Request ID: 2 194 + Content Length: 8 195 + Padding Length: 0 196 + Reserved: 0 197 + Content: 8 bytes 198 + Content hex: 0001010000000000 199 + ✅ Record valid 200 + Record 5: 201 + Version: 1 202 + Type: 5 203 + Request ID: 1 204 + Content Length: 0 205 + Padding Length: 0 206 + Reserved: 0 207 + Content: 0 bytes 208 + Content hex: 209 + ✅ Record valid 210 + Record 6: 211 + Version: 1 212 + Type: 4 213 + Request ID: 2 214 + Content Length: 43 215 + Padding Length: 0 216 + Reserved: 0 217 + Content: 43 bytes 218 + Content preview: 0e04524551554553545f4d4554484f44... 219 + ✅ Record valid 220 + Record 7: 221 + Version: 1 222 + Type: 4 223 + Request ID: 2 224 + Content Length: 0 225 + Padding Length: 0 226 + Reserved: 0 227 + Content: 0 bytes 228 + Content hex: 229 + ✅ Record valid 230 + Record 8: 231 + Version: 1 232 + Type: 6 233 + Request ID: 2 234 + Content Length: 42 235 + Padding Length: 0 236 + Reserved: 0 237 + Content: 42 bytes 238 + Content preview: 436f6e74656e742d547970653a207465... 239 + ✅ Record valid 240 + Record 9: 241 + Version: 1 242 + Type: 6 243 + Request ID: 2 244 + Content Length: 0 245 + Padding Length: 0 246 + Reserved: 0 247 + Content: 0 bytes 248 + Content hex: 249 + ✅ Record valid 250 + Record 10: 251 + Version: 1 252 + Type: 3 253 + Request ID: 2 254 + Content Length: 8 255 + Padding Length: 0 256 + Reserved: 0 257 + Content: 8 bytes 258 + Content hex: 0000000000000000 259 + ✅ Record valid 260 + Record 11: 261 + Version: 1 262 + Type: 6 263 + Request ID: 1 264 + Content Length: 67 265 + Padding Length: 0 266 + Reserved: 0 267 + Content: 67 bytes 268 + Content preview: 436f6e74656e742d547970653a207465... 269 + ✅ Record valid 270 + Record 12: 271 + Version: 1 272 + Type: 6 273 + Request ID: 1 274 + Content Length: 0 275 + Padding Length: 0 276 + Reserved: 0 277 + Content: 0 bytes 278 + Content hex: 279 + ✅ Record valid 280 + Record 13: 281 + Version: 1 282 + Type: 3 283 + Request ID: 1 284 + Content Length: 8 285 + Padding Length: 0 286 + Reserved: 0 287 + Content: 8 bytes 288 + Content hex: 0000000000000000 289 + ✅ Record valid 290 + ✅ File valid: 13 record(s), 496 total bytes 291 + 292 + Validating padded_record.bin: 293 + Record 1: 294 + Version: 1 295 + Type: 6 296 + Request ID: 1 297 + Content Length: 5 298 + Padding Length: 3 299 + Reserved: 0 300 + Content: 5 bytes 301 + Padding: 3 bytes 302 + Content hex: 48656c6c6f 303 + ✅ Record valid 304 + ✅ File valid: 1 record(s), 16 total bytes 305 + 306 + Validating params_empty.bin: 307 + Record 1: 308 + Version: 1 309 + Type: 4 310 + Request ID: 1 311 + Content Length: 0 312 + Padding Length: 0 313 + Reserved: 0 314 + Content: 0 bytes 315 + Content hex: 316 + ✅ Record valid 317 + ✅ File valid: 1 record(s), 8 total bytes 318 + 319 + Validating params_get.bin: 320 + Record 1: 321 + Version: 1 322 + Type: 4 323 + Request ID: 1 324 + Content Length: 208 325 + Padding Length: 0 326 + Reserved: 0 327 + Content: 208 bytes 328 + Content preview: 0e03524551554553545f4d4554484f44... 329 + ✅ Record valid 330 + ✅ File valid: 1 record(s), 216 total bytes 331 + 332 + Validating params_post.bin: 333 + Record 1: 334 + Version: 1 335 + Type: 4 336 + Request ID: 1 337 + Content Length: 238 338 + Padding Length: 0 339 + Reserved: 0 340 + Content: 238 bytes 341 + Content preview: 0e04524551554553545f4d4554484f44... 342 + ✅ Record valid 343 + ✅ File valid: 1 record(s), 246 total bytes 344 + 345 + Validating stderr_empty.bin: 346 + Record 1: 347 + Version: 1 348 + Type: 7 349 + Request ID: 1 350 + Content Length: 0 351 + Padding Length: 0 352 + Reserved: 0 353 + Content: 0 bytes 354 + Content hex: 355 + ✅ Record valid 356 + ✅ File valid: 1 record(s), 8 total bytes 357 + 358 + Validating stderr_message.bin: 359 + Record 1: 360 + Version: 1 361 + Type: 7 362 + Request ID: 1 363 + Content Length: 38 364 + Padding Length: 0 365 + Reserved: 0 366 + Content: 38 bytes 367 + Content preview: 5761726e696e673a20436f6e66696775... 368 + ✅ Record valid 369 + ✅ File valid: 1 record(s), 46 total bytes 370 + 371 + Validating stdin_empty.bin: 372 + Record 1: 373 + Version: 1 374 + Type: 5 375 + Request ID: 1 376 + Content Length: 0 377 + Padding Length: 0 378 + Reserved: 0 379 + Content: 0 bytes 380 + Content hex: 381 + ✅ Record valid 382 + ✅ File valid: 1 record(s), 8 total bytes 383 + 384 + Validating stdin_form_data.bin: 385 + Record 1: 386 + Version: 1 387 + Type: 5 388 + Request ID: 1 389 + Content Length: 32 390 + Padding Length: 0 391 + Reserved: 0 392 + Content: 32 bytes 393 + Content hex: 6e616d653d4a6f686e26656d61696c3d6a6f686e406578616d706c652e636f6d 394 + ✅ Record valid 395 + ✅ File valid: 1 record(s), 40 total bytes 396 + 397 + Validating stdout_empty.bin: 398 + Record 1: 399 + Version: 1 400 + Type: 6 401 + Request ID: 1 402 + Content Length: 0 403 + Padding Length: 0 404 + Reserved: 0 405 + Content: 0 bytes 406 + Content hex: 407 + ✅ Record valid 408 + ✅ File valid: 1 record(s), 8 total bytes 409 + 410 + Validating stdout_response.bin: 411 + Record 1: 412 + Version: 1 413 + Type: 6 414 + Request ID: 1 415 + Content Length: 64 416 + Padding Length: 0 417 + Reserved: 0 418 + Content: 64 bytes 419 + Content preview: 436f6e74656e742d547970653a207465... 420 + ✅ Record valid 421 + ✅ File valid: 1 record(s), 72 total bytes 422 + 423 + Validating unknown_type.bin: 424 + Record 1: 425 + Version: 1 426 + Type: 11 427 + Request ID: 0 428 + Content Length: 8 429 + Padding Length: 0 430 + Reserved: 0 431 + Content: 8 bytes 432 + Content hex: 6300000000000000 433 + ✅ Record valid 434 + ✅ File valid: 1 record(s), 16 total bytes 435 + 436 + ================================================== 437 + Validation complete: 24/24 files valid 438 + ✅ All test cases are valid!