FastCGI implementation in OCaml
1(** FastCGI wire protocol parsing and serialization. 2 3 This module handles the low-level FastCGI protocol details including 4 record parsing, name-value pair encoding, and binary data serialization 5 using Eio's Buf_read and Buf_write modules. 6 7 The FastCGI protocol uses a binary record format where all data is 8 transmitted as 8-byte fixed headers followed by variable-length content 9 and optional padding. Multi-byte integers use network byte order for 10 platform independence. 11 12 The protocol supports several key features: 13 - Request multiplexing over single connections 14 - Variable-length encoding for efficient name-value pairs 15 - Stream-based data transmission with proper termination 16 - Alignment padding for optimal performance 17 18 This module provides the low-level primitives for encoding and decoding 19 these protocol elements, allowing higher-level modules to focus on 20 application logic rather than binary protocol details. *) 21 22(** Protocol parsing error. 23 24 Raised when invalid or malformed protocol data is encountered during 25 parsing operations. This includes version mismatches, invalid record 26 structures, malformed length encodings, and other protocol violations. *) 27exception Protocol_error of string 28 29(** {1 Protocol Parsing} 30 31 These functions parse incoming FastCGI records and protocol elements 32 from binary data streams. They validate protocol compliance and convert 33 binary data into OCaml types for processing. *) 34 35(** Parse a FastCGI record header from a buffer. 36 37 Reads exactly 8 bytes from the buffer to construct a record header. 38 The header contains version, record type, request ID, content length, 39 and padding length fields using network byte order. 40 41 The function validates that the version is supported (currently only 42 version 1) and that the record type is recognized. 43 44 @param buf Input buffer positioned at the start of a record header 45 @return Parsed record header with validated fields 46 @raise Protocol_error if version is unsupported or data is malformed *) 47val parse_record_header : Eio.Buf_read.t -> Fastcgi_types.record_header 48 49(** Parse a complete FastCGI record from a buffer. 50 51 Reads a record header followed by the specified amount of content data 52 and padding. This is the primary function for reading FastCGI records 53 from network streams. 54 55 The function ensures that exactly the number of content and padding 56 bytes specified in the header are read, maintaining proper stream 57 alignment for subsequent records. 58 59 @param buf Input buffer positioned at the start of a complete record 60 @return Complete parsed record with header, content, and optional padding 61 @raise Protocol_error if the record structure is invalid *) 62val parse_record : Eio.Buf_read.t -> Fastcgi_types.record 63 64(** Parse begin request body from record content. 65 66 Extracts role and connection flags from the 8-byte Begin_request record 67 body. The role indicates whether the application should act as a 68 Responder, Authorizer, or Filter. The flags control connection lifetime 69 management. 70 71 @param content Record content bytes (must be exactly 8 bytes) 72 @return Begin request body with role and flags 73 @raise Protocol_error if content length is wrong or role is unknown *) 74val parse_begin_request : bytes -> Fastcgi_types.begin_request_body 75 76(** Parse end request body from record content. 77 78 Extracts application status and protocol status from the 8-byte 79 End_request record body. The application status is similar to a 80 program exit code, while protocol status indicates completion 81 or rejection reasons. 82 83 @param content Record content bytes (must be exactly 8 bytes) 84 @return End request body with status codes 85 @raise Protocol_error if content length is wrong or status is invalid *) 86val parse_end_request : bytes -> Fastcgi_types.end_request_body 87 88(** Parse name-value pairs from record content. 89 90 Decodes the compact binary encoding used for parameter transmission. 91 Names and values can be up to 127 bytes (1-byte length) or longer 92 (4-byte length with high bit set). This encoding allows efficient 93 transmission of CGI environment variables and other parameters. 94 95 @param content Record content bytes containing encoded name-value pairs 96 @return List of decoded name-value pairs 97 @raise Protocol_error if the encoding is malformed *) 98val parse_name_value_pairs : bytes -> Fastcgi_types.name_value_pair list 99 100(** {1 Protocol Serialization} 101 102 These functions serialize OCaml types into the binary FastCGI protocol 103 format for transmission to web servers. All multi-byte integers are 104 written in network byte order for platform independence. *) 105 106(** Write a FastCGI record header to a buffer. 107 108 Serializes a record header into exactly 8 bytes using network byte order. 109 The header format is: version (1 byte), type (1 byte), request ID (2 bytes), 110 content length (2 bytes), padding length (1 byte), reserved (1 byte). 111 112 @param buf Output buffer to write the header to 113 @param header Record header to serialize *) 114val write_record_header : Eio.Buf_write.t -> Fastcgi_types.record_header -> unit 115 116(** Write a complete FastCGI record to a buffer. 117 118 Serializes a complete record including header, content data, and padding. 119 This is the primary function for sending FastCGI records over network 120 connections. The function ensures proper alignment by writing any 121 specified padding bytes. 122 123 @param buf Output buffer to write the complete record to 124 @param record Complete record with header, content, and optional padding *) 125val write_record : Eio.Buf_write.t -> Fastcgi_types.record -> unit 126 127(** Write begin request body to bytes. 128 129 Serializes role and connection flags into the 8-byte Begin_request record 130 body format. The first 2 bytes contain the role, the next byte contains 131 flags, and the remaining 5 bytes are reserved (set to zero). 132 133 @param body Begin request body with role and flags 134 @return 8-byte serialized record body *) 135val write_begin_request : Fastcgi_types.begin_request_body -> bytes 136 137(** Write end request body to bytes. 138 139 Serializes application and protocol status into the 8-byte End_request 140 record body format. The first 4 bytes contain the application status, 141 the next byte contains the protocol status, and the remaining 3 bytes 142 are reserved. 143 144 @param body End request body with status codes 145 @return 8-byte serialized record body *) 146val write_end_request : Fastcgi_types.end_request_body -> bytes 147 148(** Write name-value pairs to bytes. 149 150 Serializes name-value pairs using the compact binary encoding where 151 each pair is encoded as: name length, value length, name data, value data. 152 Lengths up to 127 bytes use 1-byte encoding; longer lengths use 4-byte 153 encoding with the high bit set. 154 155 @param pairs List of name-value pairs to encode 156 @return Serialized bytes containing all encoded pairs *) 157val write_name_value_pairs : Fastcgi_types.name_value_pair list -> bytes 158 159(** {1 Type Conversions} *) 160 161(** Convert record type to integer. *) 162val record_type_to_int : Fastcgi_types.record_type -> int 163 164(** Convert integer to record type. 165 166 @param i Integer value 167 @return Record type 168 @raise Protocol_error if unknown *) 169val record_type_of_int : int -> Fastcgi_types.record_type 170 171(** Convert role to integer. *) 172val role_to_int : Fastcgi_types.role -> int 173 174(** Convert integer to role. 175 176 @param i Integer value 177 @return Role 178 @raise Protocol_error if unknown *) 179val role_of_int : int -> Fastcgi_types.role 180 181(** Convert protocol status to integer. *) 182val protocol_status_to_int : Fastcgi_types.protocol_status -> int 183 184(** Convert integer to protocol status. 185 186 @param i Integer value 187 @return Protocol status 188 @raise Protocol_error if unknown *) 189val protocol_status_of_int : int -> Fastcgi_types.protocol_status 190 191(** {1 Length Encoding} 192 193 FastCGI uses a variable-length encoding for efficient transmission of 194 length values in name-value pairs. This encoding minimizes overhead 195 for short strings while supporting arbitrarily long values. *) 196 197(** Encode a length value using FastCGI variable-length encoding. 198 199 Short lengths (0-127) are encoded in a single byte with the high bit 200 clear. Longer lengths (128 and above) are encoded in 4 bytes with the 201 high bit of the first byte set to 1, and the remaining 31 bits containing 202 the actual length value. 203 204 This encoding provides optimal space efficiency for typical use cases 205 where most parameter names and values are relatively short. 206 207 @param length Length value to encode (must be non-negative) 208 @return 1-byte or 4-byte encoded length 209 @raise Invalid_argument if length is negative *) 210val encode_length : int -> bytes 211 212(** Decode a length value from FastCGI variable-length encoding. 213 214 Reads either 1 or 4 bytes from the buffer depending on the high bit 215 of the first byte. Returns both the decoded length and the number of 216 bytes consumed to allow proper buffer advancement. 217 218 @param buf Input buffer positioned at the start of an encoded length 219 @return Tuple of (decoded length, bytes consumed) 220 @raise Protocol_error if the encoding is invalid or buffer is too short *) 221val decode_length : Eio.Buf_read.t -> int * int 222 223(** {1 Record Construction} 224 225 These convenience functions create properly formatted FastCGI records 226 with correct headers and content. They handle the binary encoding details 227 and ensure protocol compliance. *) 228 229(** Create a begin request record. 230 231 Constructs a Begin_request record to initiate a new FastCGI request. 232 This record type is sent by web servers to start request processing 233 and specifies the role the application should play. 234 235 The record contains the application role (Responder, Authorizer, or Filter) 236 and connection flags that control whether the connection should be kept 237 alive after the request completes. 238 239 @param request_id Unique request identifier for multiplexing 240 @param role Role the application should play for this request 241 @param flags Connection management flags 242 @return Complete Begin_request record ready for transmission *) 243val make_begin_request : 244 request_id:Fastcgi_types.request_id -> 245 role:Fastcgi_types.role -> 246 flags:Fastcgi_types.connection_flags -> 247 Fastcgi_types.record 248 249(** Create an end request record. 250 251 Constructs an End_request record to complete a FastCGI request. 252 This record is sent by applications to indicate request completion 253 and provide both application-level and protocol-level status information. 254 255 The application status is similar to a program exit code, where 0 256 indicates success and non-zero values indicate errors. The protocol 257 status indicates whether the request was completed normally or rejected 258 for protocol-related reasons. 259 260 @param request_id Request ID that is being completed 261 @param app_status Application exit status (0 for success) 262 @param protocol_status Protocol completion status 263 @return Complete End_request record ready for transmission *) 264val make_end_request : 265 request_id:Fastcgi_types.request_id -> 266 app_status:Fastcgi_types.app_status -> 267 protocol_status:Fastcgi_types.protocol_status -> 268 Fastcgi_types.record 269 270(** Create a params record. 271 272 @param request_id Request ID 273 @param params Parameter name-value pairs 274 @return Params record *) 275val make_params_record : 276 request_id:Fastcgi_types.request_id -> 277 params:(string * string) list -> 278 Fastcgi_types.record 279 280(** Create a stream record (stdin, stdout, stderr, data). 281 282 @param record_type Stream record type 283 @param request_id Request ID 284 @param data Stream data 285 @return Stream record *) 286val make_stream_record : 287 record_type:Fastcgi_types.record_type -> 288 request_id:Fastcgi_types.request_id -> 289 data:bytes -> 290 Fastcgi_types.record 291 292(** Create an empty stream record (marks end of stream). 293 294 @param record_type Stream record type 295 @param request_id Request ID 296 @return Empty stream record *) 297val make_empty_stream_record : 298 record_type:Fastcgi_types.record_type -> 299 request_id:Fastcgi_types.request_id -> 300 Fastcgi_types.record 301 302(** Create a get values record. 303 304 @param variables Variable names to query 305 @return Get values record *) 306val make_get_values_record : 307 variables:string list -> 308 Fastcgi_types.record 309 310(** Create a get values result record. 311 312 @param values Variable name-value pairs 313 @return Get values result record *) 314val make_get_values_result_record : 315 values:(string * string) list -> 316 Fastcgi_types.record 317 318(** Create an unknown type record. 319 320 @param unknown_type Unknown record type value 321 @return Unknown type record *) 322val make_unknown_type_record : 323 unknown_type:int -> 324 Fastcgi_types.record 325 326(** Create an abort request record. 327 328 @param request_id Request ID to abort 329 @return Abort request record *) 330val make_abort_request_record : 331 request_id:Fastcgi_types.request_id -> 332 Fastcgi_types.record 333 334(** {1 Validation} 335 336 These functions validate protocol elements for compliance with FastCGI 337 requirements. They help detect malformed data and ensure protocol 338 correctness during parsing and construction. *) 339 340(** Validate a record header. 341 342 Checks that a record header contains valid values for all fields. 343 This includes verifying the protocol version, record type bounds, 344 content length limits, and padding length constraints. 345 346 @param header Record header to validate 347 @return True if the header is valid and protocol-compliant 348 @raise Protocol_error if any field contains invalid values *) 349val validate_record_header : Fastcgi_types.record_header -> bool 350 351(** Validate a complete record. 352 353 Performs comprehensive validation of a complete record including 354 header validation and content/padding length consistency checks. 355 Ensures the record can be safely transmitted or processed. 356 357 @param record Complete record to validate 358 @return True if the entire record is valid and well-formed 359 @raise Protocol_error if any part of the record is invalid *) 360val validate_record : Fastcgi_types.record -> bool 361 362(** Check if a record type is a management record. 363 364 Management records use request ID 0 and contain protocol-level information 365 such as capability queries (Get_values) and unknown type responses. 366 They are not associated with any specific request. 367 368 @param record_type Record type to classify 369 @return True if this is a management record type *) 370val is_management_record : Fastcgi_types.record_type -> bool 371 372(** Check if a record type is a stream record. 373 374 Stream records (Params, Stdin, Stdout, Stderr, Data) can be sent in 375 multiple parts and are terminated with an empty record of the same type. 376 This allows streaming of arbitrarily large data without buffering. 377 378 @param record_type Record type to classify 379 @return True if this is a stream record type *) 380val is_stream_record : Fastcgi_types.record_type -> bool 381 382(** {1 Protocol Constants} 383 384 Essential constants and utility functions for FastCGI protocol 385 implementation. These values are defined by the protocol specification. *) 386 387(** Null request ID for management records. 388 389 Management records always use request ID 0 to indicate they are not 390 associated with any specific request. Applications must use non-zero 391 request IDs for all application records. *) 392val null_request_id : Fastcgi_types.request_id 393 394(** Maximum content length for a single record. 395 396 FastCGI records can contain at most 65535 bytes of content data. 397 Larger data must be split across multiple records of the same type. *) 398val max_content_length : int 399 400(** Maximum padding length for a record. 401 402 Records can have at most 255 bytes of padding for alignment purposes. 403 Padding bytes are ignored by the receiver. *) 404val max_padding_length : int 405 406(** Calculate optimal padding for 8-byte alignment. 407 408 Computes the number of padding bytes needed to align the total record 409 size to an 8-byte boundary. This provides optimal performance on most 410 architectures by ensuring proper memory alignment. 411 412 @param content_length Length of the record content data 413 @return Number of padding bytes needed (0-7) *) 414val calculate_padding : int -> int