async_fcgi/
fastcgi.rs

1/*! Contains constants and models for fcgi data records.
2```
3    use bytes::{Bytes, BytesMut};
4    use async_fcgi::fastcgi::*;
5    let mut b = BytesMut::with_capacity(96);
6    BeginRequestBody::new(FastCGIRole::Responder,0,1).append(&mut b);
7    let mut nv = NVBody::new();
8    nv.add(NameValuePair::new(Bytes::from(&b"SCRIPT_FILENAME"[..]),Bytes::from(&b"/home/daniel/Public/test.php"[..]))).expect("record full");
9    nv.to_record(RecordType::Params, 1).append(&mut b);
10    NVBody::new().to_record(RecordType::Params, 1).append(&mut b);
11```
12*/
13use crate::bufvec::BufList;
14use bytes::{Buf, BufMut, Bytes, BytesMut};
15#[cfg(feature = "web_server")]
16use log::{debug, trace};
17use std::{iter::{Extend, FromIterator, IntoIterator}, fmt::Display};
18
19/// FCGI record header
20#[derive(Debug, Clone)]
21pub(crate) struct Header {
22    version: u8,
23    rtype: RecordType,
24    request_id: u16, // A request ID R becomes active when the application receives a record {FCGI_BEGIN_REQUEST, R, …} and becomes inactive when the application sends a record {FCGI_END_REQUEST, R, …} to the Web server. Management records have a requestId value of zero
25    content_length: u16,
26    padding_length: u8, // align by 8
27                        //    reserved: [u8; 1],
28}
29pub struct BeginRequestBody {
30    role: FastCGIRole,
31    flags: u8,
32    //    reserved: [u8; 5],
33}
34pub struct EndRequestBody {
35    pub app_status: u32,
36    pub protocol_status: ProtoStatus,
37    //    pub reserved: [u8; 3],
38}
39pub struct UnknownTypeBody {
40    pub rtype: u8,
41    //    pub reserved: [u8; 7],
42}
43
44pub struct NameValuePair {
45    pub name_data: Bytes,
46    pub value_data: Bytes,
47}
48/// Body type for Params and GetValues.
49/// Holds multiple NameValuePair's
50pub struct NVBody {
51    pairs: BufList<Bytes>,
52    len: u16,
53}
54/// Helper type to generate one or more NVBody Records,
55/// depending on the space needed
56pub struct NVBodyList {
57    bodies: Vec<NVBody>,
58}
59pub struct STDINBody {}
60pub enum Body {
61    BeginRequest(BeginRequestBody),
62    EndRequest(EndRequestBody),
63    UnknownType(UnknownTypeBody),
64    Params(NVBody),
65    StdIn(Bytes),
66    StdErr(Bytes),
67    StdOut(Bytes),
68    Abort,
69    GetValues(NVBody),
70    GetValuesResult(NVBody),
71}
72/// FCGI record
73pub struct Record {
74    header: Header,
75    pub body: Body,
76}
77
78/// Listening socket file number
79pub const LISTENSOCK_FILENO: u8 = 0;
80
81impl Body {
82    /// Maximum length per record
83    const MAX_LENGTH: usize = 0xffff;
84}
85
86impl Header {
87    /// Number of bytes in a Header.
88    ///
89    /// Future versions of the protocol will not reduce this number.
90    #[cfg(feature = "codec")]
91    pub const HEADER_LEN: usize = 8;
92
93    /// version component of Header
94    const VERSION_1: u8 = 1;
95}
96
97impl Record {
98    /// Default request id component of Header
99    pub const MGMT_REQUEST_ID: u16 = 0;
100}
101/// rtype of Header
102#[derive(Debug, Clone, Copy)]
103#[repr(u8)]
104pub enum RecordType {
105    /// The Web server sends a FCGI_BEGIN_REQUEST record to start a request
106    BeginRequest = 1,
107    /// A Web server aborts a FastCGI request when an HTTP client closes its transport connection while the FastCGI request is running on behalf of that client
108    AbortRequest = 2,
109    /// The application sends a FCGI_END_REQUEST record to terminate a request
110    EndRequest = 3,
111    /// Receive name-value pairs from the Web server to the application
112    Params = 4,
113    /// Byte Stream
114    StdIn = 5,
115    /// Byte Stream
116    StdOut = 6,
117    /// Byte Stream
118    StdErr = 7,
119    /// Byte Stream
120    Data = 8,
121    /// The Web server can query specific variables within the application
122    /// The application receives.
123    GetValues = 9,
124    /// The Web server can query specific variables within the application.
125    /// The application responds.
126    GetValuesResult = 10,
127    /// Unrecognized management record
128    #[cfg(feature = "web_server")]
129    UnknownType = 11,
130}
131impl TryFrom<u8> for RecordType {
132    type Error = u8;
133    fn try_from(value: u8) -> Result<Self, Self::Error> {
134        match value {
135            1 => Ok(RecordType::BeginRequest),
136            2 => Ok(RecordType::AbortRequest),
137            3 => Ok(RecordType::EndRequest),
138            4 => Ok(RecordType::Params),
139            5 => Ok(RecordType::StdIn),
140            6 => Ok(RecordType::StdOut),
141            7 => Ok(RecordType::StdErr),
142            8 => Ok(RecordType::Data),
143            9 => Ok(RecordType::GetValues),
144            10 => Ok(RecordType::GetValuesResult),
145            #[cfg(feature = "web_server")]
146            11 => Ok(RecordType::UnknownType),
147            o => Err(o),
148        }
149    }
150}
151impl Display for RecordType {
152    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
153        match self {
154            //RecordType::Other(o) => f.write_fmt(format_args!("Unknown Status: {}", o)),
155            RecordType::BeginRequest => f.write_str("BeginRequest"),
156            RecordType::AbortRequest => f.write_str("AbortRequest"),
157            RecordType::EndRequest => f.write_str("EndRequest"),
158            RecordType::Params => f.write_str("Params"),
159            RecordType::StdIn => f.write_str("StdIn"),
160            RecordType::StdOut => f.write_str("StdOut"),
161            RecordType::StdErr => f.write_str("StdErr"),
162            RecordType::Data => f.write_str("Data"),
163            RecordType::GetValues => f.write_str("GetValues"),
164            RecordType::GetValuesResult => f.write_str("GetValuesResult"),
165            #[cfg(feature = "web_server")]
166            RecordType::UnknownType => f.write_str("UnknownType"),
167        }
168    }
169}
170impl BeginRequestBody {
171    /// Mask for flags component of BeginRequestBody
172    pub const KEEP_CONN: u8 = 1;
173}
174/// FastCGI role
175#[repr(u16)]
176pub enum FastCGIRole {
177    /// emulated CGI/1.1 program
178    Responder = 1,
179    /// authorized/unauthorized decision
180    Authorizer = 2,    
181    Filter = 3
182}
183/// protocol_status component of EndRequestBody
184#[repr(u8)]
185pub enum ProtoStatus {
186    /// Normal end of request
187    Complete = 0,
188    /// Application is designed to process one request at a time per connection
189    CantMpxCon = 1,
190    /// The application runs out of some resource, e.g. database connections
191    Overloaded = 2,
192    /// Web server has specified a role that is unknown to the application
193    UnknownRole = 3,
194}
195impl TryFrom<u8> for ProtoStatus {
196    type Error = u8;
197    fn try_from(value: u8) -> Result<Self, Self::Error> {
198        match value {
199            0 => Ok(ProtoStatus::Complete),
200            1 => Ok(ProtoStatus::CantMpxCon),
201            2 => Ok(ProtoStatus::Overloaded),
202            3 => Ok(ProtoStatus::UnknownRole),
203            o => Err(o),
204        }
205    }
206}
207impl Display for ProtoStatus {
208    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
209        match self {
210            ProtoStatus::Complete => f.write_str("Complete"),
211            ProtoStatus::CantMpxCon => f.write_str("CantMpxCon"),
212            ProtoStatus::Overloaded => f.write_str("Overloaded"),
213            ProtoStatus::UnknownRole => f.write_str("UnknownRole"),
214        }
215    }
216}
217/// The maximum number of concurrent transport connections this application will accept
218/// 
219/// e.g. "1" or "10".
220/// Used with GET_VALUES / GET_VALUES_RESULT records.
221pub const MAX_CONNS: &[u8] = b"MAX_CONNS";
222
223/// The maximum number of concurrent requests this application will accept
224/// 
225/// e.g. "1" or "50".
226/// Used with GET_VALUES / GET_VALUES_RESULT records.
227pub const MAX_REQS: &[u8] = b"MAX_REQS";
228
229/// If this application do multiplex connections
230/// 
231/// i.e. handle concurrent requests over each connection ("1": Yes, "0": No).
232/// Used with GET_VALUES / GET_VALUES_RESULT records.
233pub const MPXS_CONNS: &[u8] = b"MPXS_CONNS";
234
235// ----------------- implementation -----------------
236
237impl NameValuePair {
238    //pub name_data: Vec<u8>;
239    //pub value_data: Vec<u8>;
240    pub fn parse(data: &mut Bytes) -> NameValuePair {
241        let mut pos: usize = 0;
242        let key_length = NameValuePair::param_length(data, &mut pos);
243        let value_length = NameValuePair::param_length(data, &mut pos);
244        let key = data.slice(pos..pos + key_length);
245        pos += key_length;
246        let value = data.slice(pos..pos + value_length);
247        pos += value_length;
248        data.advance(pos);
249
250        NameValuePair {
251            name_data: key,
252            value_data: value,
253        }
254    }
255    pub fn new(name_data: Bytes, value_data: Bytes) -> NameValuePair {
256        NameValuePair {
257            name_data,
258            value_data,
259        }
260    }
261
262    fn param_length(data: &Bytes, pos: &mut usize) -> usize {
263        let mut length: usize = data[*pos] as usize;
264
265        if (length >> 7) == 1 {
266            length = (data.slice(*pos + 1..(*pos + 4)).get_u32() & 0x7FFFFFFF) as usize;
267
268            *pos += 4;
269        } else {
270            *pos += 1;
271        }
272
273        length
274    }
275    pub fn len(&self) -> usize {
276        let ln = self.name_data.len();
277        let lv = self.value_data.len();
278        let mut lf: usize = ln + lv + 2;
279        if ln > 0x7f {
280            lf += 3;
281        }
282        if lv > 0x7f {
283            lf += 3;
284        }
285        lf
286    }
287}
288
289impl STDINBody {
290    /// create a single STDIN record from Bytes
291    /// remaining bytes might be left if the record is full
292    #[allow(clippy::new_ret_no_self)]
293    pub fn new(request_id: u16, b: &mut dyn Buf) -> Record {
294        let mut rec_data = b.take(Body::MAX_LENGTH);
295        let len = rec_data.remaining();
296
297        Record {
298            header: Header::new(RecordType::StdIn, request_id, len as u16),
299            body: Body::StdIn(rec_data.copy_to_bytes(len)),
300        }
301    }
302}
303
304impl Header {
305    pub fn new(rtype: RecordType, request_id: u16, len: u16) -> Header {
306        let mut pad: u8 = (len % 8) as u8;
307        if pad != 0 {
308            pad = 8 - pad;
309        }
310        Header {
311            version: Header::VERSION_1,
312            rtype,
313            request_id,
314            content_length: len,
315            padding_length: pad,
316        }
317    }
318    pub fn write_into<BM: BufMut>(self, data: &mut BM) {
319        data.put_u8(self.version);
320        data.put_u8(self.rtype as u8);
321        data.put_u16(self.request_id);
322        data.put_u16(self.content_length);
323        data.put_u8(self.padding_length);
324        data.put_u8(0); // reserved
325                        //debug!("h {} {} -> {:?}",self.request_id, self.content_length, &data);
326    }
327    #[cfg(feature = "web_server")]
328    fn parse(data: &mut Bytes) -> Header {
329        let h = Header {
330            version: data.get_u8(),
331            rtype: data.get_u8().try_into().expect("not a fcgi type"),
332            request_id: data.get_u16(),
333            content_length: data.get_u16(),
334            padding_length: data.get_u8(),
335        };
336        data.advance(1); // reserved
337        h
338    }
339    #[inline]
340    #[cfg(feature = "codec")]
341    pub fn get_padding(&self) -> u8 {
342        self.padding_length
343    }
344}
345
346impl BeginRequestBody {
347    /// create a record of type BeginRequest
348    #[allow(clippy::new_ret_no_self)]
349    pub fn new(role: FastCGIRole, flags: u8, request_id: u16) -> Record {
350        Record {
351            header: Header {
352                version: Header::VERSION_1,
353                rtype: RecordType::BeginRequest,
354                request_id,
355                content_length: 8,
356                padding_length: 0,
357            },
358            body: Body::BeginRequest(BeginRequestBody { role, flags }),
359        }
360    }
361}
362/// to get a sream of records from a stream of bytes
363/// a record does not need to be completely available
364#[cfg(feature = "web_server")]
365pub(crate) struct RecordReader {
366    current: Option<Header>,
367}
368#[cfg(feature = "web_server")]
369impl RecordReader {
370    pub(crate) fn new() -> RecordReader {
371        RecordReader { current: None }
372    }
373    pub(crate) fn read(&mut self, data: &mut Bytes) -> Option<Record> {
374        let mut full_header = match self.current.take() {
375            Some(h) => h,
376            None => {
377                //dont alter data until we have a whole header to read
378                if data.remaining() < 8 {
379                    return None;
380                }
381                debug!("new header");
382                Header::parse(data)
383            }
384        };
385        let mut body_len = full_header.content_length as usize;
386        let header = if data.remaining() < body_len {
387            let mut nh = full_header.clone();
388            body_len = data.remaining();
389            nh.content_length = body_len as u16;
390            nh.padding_length = 0;
391
392            //store rest for next time
393            full_header.content_length -= body_len as u16;
394            self.current = Some(full_header);
395            //we need to enforce a read if we are out of data
396            if body_len < 1 {
397                return None;
398            }
399
400            debug!("more later, now:");
401            nh
402        } else {
403            full_header
404        };
405
406        debug!("read type {}", header.rtype);
407        trace!("payload: {:?}", &data.slice(..body_len));
408        let body = data.slice(0..body_len);
409        data.advance(body_len);
410
411        if data.remaining() < header.padding_length as usize {
412            //only possible if last/only fragment -> self.current is None
413            if body_len < 1 {
414                //no data at all
415                self.current = Some(header);
416                return None;
417            }
418            let mut nh = header.clone();
419            nh.content_length = 0;
420            self.current = Some(nh);
421            debug!("padding {} is still missing", header.padding_length);
422        } else {
423            data.advance(header.padding_length as usize);
424        }
425
426        let body = Record::parse_body(body, header.rtype);
427        Some(Record { header, body })
428    }
429}
430impl Record {
431    #[cfg(feature = "web_server")]
432    pub(crate) fn get_request_id(&self) -> u16 {
433        self.header.request_id
434    }
435    /// parse bytes to a single record
436    /// returns `Ǹone` and leaves data untouched if not enough data is available
437    #[cfg(feature = "con_pool")]
438    pub(crate) fn read(data: &mut Bytes) -> Option<Record> {
439        //dont alter data until we have a whole packet to read
440        if data.remaining() < 8 {
441            return None;
442        }
443        let header = Header::parse(&mut data.slice(..));
444        let len = header.content_length as usize + header.padding_length as usize;
445        if data.remaining() < len + 8 {
446            return None;
447        }
448        data.advance(8);
449        debug!("read type {}", header.rtype);
450        trace!(
451            "payload: {:?}",
452            &data.slice(..header.content_length as usize)
453        );
454        let body = data.slice(0..header.content_length as usize);
455        data.advance(len);
456        let body = Record::parse_body(body, header.rtype);
457        Some(Record { header, body })
458    }
459    #[cfg(feature = "web_server")]
460    fn parse_body(mut payload: Bytes, ptype: RecordType) -> Body {
461        match ptype {
462            RecordType::StdOut => Body::StdOut(payload),
463            RecordType::StdErr => Body::StdErr(payload),
464            RecordType::EndRequest => Body::EndRequest(EndRequestBody::parse(payload)),
465            RecordType::UnknownType => {
466                let rtype = payload.get_u8();
467                payload.advance(7);
468                Body::UnknownType(UnknownTypeBody { rtype })
469            }
470            RecordType::GetValuesResult => Body::GetValuesResult(NVBody::from_bytes(payload)),
471            RecordType::GetValues => Body::GetValues(NVBody::from_bytes(payload)),
472            RecordType::Params => Body::Params(NVBody::from_bytes(payload)),
473            _ => panic!("not impl"),
474        }
475    }
476    ///serialize this record and append it to buf - used by tests
477    pub fn append<BM: BufMut>(self, buf: &mut BM) {
478        match self.body {
479            Body::BeginRequest(brb) => {
480                self.header.write_into(buf);
481                brb.write_into(buf);
482            }
483            Body::Params(nvs) | Body::GetValues(nvs) | Body::GetValuesResult(nvs) => {
484                let pad = self.header.padding_length as usize;
485                self.header.write_into(buf);
486                let kvp = nvs.drain().0;
487                buf.put(kvp);
488                unsafe {
489                    buf.advance_mut(pad);
490                } // padding, value does not matter
491            }
492            Body::StdIn(b) => {
493                let pad = self.header.padding_length as usize;
494                self.header.write_into(buf);
495                if !b.has_remaining() {
496                    //end stream has no data or padding
497                    debug_assert!(pad == 0);
498                    return;
499                }
500                buf.put(b);
501                unsafe {
502                    buf.advance_mut(pad);
503                } // padding, value does not matter
504            }
505            Body::Abort => {
506                self.header.write_into(buf);
507            }
508            _ => panic!("not impl"),
509        }
510    }
511}
512impl NVBody {
513    pub fn new() -> NVBody {
514        NVBody {
515            pairs: BufList::new(),
516            len: 0,
517        }
518    }
519    pub fn to_record(self, rtype: RecordType, request_id: u16) -> Record {
520        let mut pad: u8 = (self.len % 8) as u8;
521        if pad != 0 {
522            pad = 8 - pad;
523        }
524        Record {
525            header: Header {
526                version: Header::VERSION_1,
527                rtype,
528                request_id,
529                content_length: self.len,
530                padding_length: pad,
531            },
532            body: match rtype {
533                RecordType::Params => Body::Params(self),
534                RecordType::GetValues => Body::GetValues(self),
535                RecordType::GetValuesResult => Body::GetValuesResult(self),
536                _ => panic!("No valid type"),
537            },
538        }
539    }
540    pub fn fits(&self, pair: &NameValuePair) -> bool {
541        let l = pair.len() + self.len as usize;
542        l <= Body::MAX_LENGTH
543    }
544    pub fn add(&mut self, pair: NameValuePair) -> Result<(), ()> {
545        let l = pair.len() + self.len as usize;
546        if l > Body::MAX_LENGTH {
547            return Err(()); // this body is full
548        }
549        self.len = l as u16;
550
551        let mut ln = pair.name_data.len();
552        let mut lv = pair.value_data.len();
553        if ln + lv > Body::MAX_LENGTH {
554            return Err(()); // could be handled
555        }
556        let mut lf: usize = 2;
557        if ln > 0x7f {
558            if ln > 0x7fffffff {
559                return Err(());
560            }
561            lf += 3;
562            ln |= 0x80000000;
563        }
564        if lv > 0x7f {
565            if lv > 0x7fffffff {
566                return Err(());
567            }
568            lf += 3;
569            lv |= 0x80000000;
570        }
571        let mut data: BytesMut = BytesMut::with_capacity(lf);
572        if ln > 0x7f {
573            data.put_u32(ln as u32);
574        } else {
575            data.put_u8(ln as u8);
576        }
577        if lv > 0x7f {
578            data.put_u32(lv as u32);
579        } else {
580            data.put_u8(lv as u8);
581        }
582        //self.pairs.push(pair.write());
583        self.pairs.push(data.freeze());
584        self.pairs.push(pair.name_data);
585        if lv > 0 {
586            self.pairs.push(pair.value_data);
587        }
588        Ok(())
589    }
590    #[cfg(feature = "web_server")]
591    pub(crate) fn from_bytes(buf: Bytes) -> NVBody {
592        let mut b = NVBody::new();
593        b.len = buf.remaining() as u16;
594        if b.len > 0 {
595            b.pairs.push(buf);
596        }
597        b
598    }
599    pub fn drain(mut self) -> NVDrain {
600        NVDrain(self.pairs.copy_to_bytes(self.len as usize))
601    }
602}
603pub struct NVDrain(Bytes);
604
605impl Iterator for NVDrain {
606    type Item = NameValuePair;
607
608    /// might panic
609    fn next(&mut self) -> Option<NameValuePair> {
610        if !self.0.has_remaining() {
611            return None;
612        }
613        Some(NameValuePair::parse(&mut self.0))
614    }
615
616    fn size_hint(&self) -> (usize, Option<usize>) {
617        (1, None)
618    }
619}
620
621impl NVBodyList {
622    pub fn new() -> NVBodyList {
623        NVBodyList {
624            bodies: vec![NVBody::new()],
625        }
626    }
627    /// # Panics
628    /// If a KV-Pair is bigger than one Record
629    pub fn add(&mut self, pair: NameValuePair) {
630        let mut nv = self.bodies.last_mut().unwrap(); //never empty
631        if !nv.fits(&pair) {
632            let new = NVBody::new();
633            self.bodies.push(new);
634            nv = self.bodies.last_mut().unwrap(); //never empty
635        }
636        nv.add(pair).expect("KVPair bigger that 0xFFFF");
637    }
638}
639
640impl FromIterator<(Bytes, Bytes)> for NVBodyList {
641    fn from_iter<T: IntoIterator<Item = (Bytes, Bytes)>>(iter: T) -> NVBodyList {
642        let mut nv = NVBodyList::new();
643        nv.extend(iter);
644        nv
645    }
646}
647
648impl Extend<(Bytes, Bytes)> for NVBodyList {
649    #[inline]
650    fn extend<T: IntoIterator<Item = (Bytes, Bytes)>>(&mut self, iter: T) {
651        for (k, v) in iter {
652            self.add(NameValuePair::new(k, v));
653        }
654    }
655}
656
657impl BeginRequestBody {
658    pub fn write_into<BM: BufMut>(self, data: &mut BM) {
659        data.put_u16(self.role as u16);
660        data.put_u8(self.flags);
661        data.put_slice(&[0; 5]); // reserved
662    }
663}
664
665impl EndRequestBody {
666    #[cfg(feature = "web_server")]
667    fn parse(mut data: Bytes) -> EndRequestBody {
668        let b = EndRequestBody {
669            app_status: data.get_u32(),
670            protocol_status: data.get_u8().try_into().expect("not a fcgi status"),
671        };
672        data.advance(3); // reserved
673        b
674    }
675}
676
677impl std::fmt::Debug for NameValuePair {
678    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
679        write!(f, "{:?} = {:?}", self.name_data, self.value_data)
680    }
681}
682
683#[test]
684fn encode_simple_get() {
685    let mut b = BytesMut::with_capacity(80);
686    BeginRequestBody::new(FastCGIRole::Responder, 0, 1).append(&mut b);
687    let mut nv = NVBody::new();
688    nv.add(NameValuePair::new(
689        Bytes::from(&b"SCRIPT_FILENAME"[..]),
690        Bytes::from(&b"/home/daniel/Public/test.php"[..]),
691    ))
692    .expect("record full");
693    nv.to_record(RecordType::Params, 1).append(&mut b);
694    NVBody::new().to_record(RecordType::Params, 1).append(&mut b);
695
696    let mut dst = [0; 80];
697    b.copy_to_slice(&mut dst);
698
699    let expected = b"\x01\x01\0\x01\0\x08\0\0\0\x01\0\0\0\0\0\0\x01\x04\0\x01\0-\x03\0\x0f\x1cSCRIPT_FILENAME/home/daniel/Public/test.php\x01\x04\0\x01\x04\0\x01\0\0\0\0";
700
701    assert_eq!(dst[..69], expected[..69]);
702    //padding is uninit
703    assert_eq!(dst[72..], expected[72..]);
704}
705#[test]
706fn encode_post() {
707    let mut b = BytesMut::with_capacity(104);
708    BeginRequestBody::new(FastCGIRole::Responder, 0, 1).append(&mut b);
709    let mut nv = NVBody::new();
710    nv.add(NameValuePair::new(
711        Bytes::from(&b"SCRIPT_FILENAME"[..]),
712        Bytes::from(&b"/home/daniel/Public/test.php"[..]),
713    ))
714    .expect("record full");
715    nv.to_record(RecordType::Params, 1).append(&mut b);
716    NVBody::new().to_record(RecordType::Params, 1).append(&mut b);
717    STDINBody::new(1, &mut Bytes::from(&b"a=b"[..])).append(&mut b);
718    STDINBody::new(1, &mut Bytes::new()).append(&mut b);
719
720    let mut dst = [0; 104];
721    b.copy_to_slice(&mut dst);
722    //padding is uninit
723    dst[69] = 0xFF;
724    dst[70] = 0xFF;
725    dst[71] = 0xFF;
726    dst[91] = 0xFF;
727    dst[92] = 0xFF;
728    dst[93] = 0xFF;
729    dst[94] = 0xFF;
730    dst[95] = 0xFF;
731    let expected = b"\x01\x01\0\x01\0\x08\0\0\0\x01\0\0\0\0\0\0\x01\x04\0\x01\0-\x03\0\x0f\x1cSCRIPT_FILENAME/home/daniel/Public/test.php\xff\xff\xff\x01\x04\0\x01\0\0\0\0\x01\x05\0\x01\0\x03\x05\0a=b\xff\xff\xff\xff\xff\x01\x05\0\x01\0\0\0\0";
732
733    assert_eq!(dst, &expected[..]);
734}
735
736#[test]
737fn encode_long_param() {
738    let mut b = BytesMut::with_capacity(190);
739    BeginRequestBody::new(FastCGIRole::Responder, 0, 1).append(&mut b);
740    let mut nv = NVBody::new();
741    nv.add(NameValuePair::new(
742        Bytes::from(&b"HTTP_ACCEPT"[..]),
743        Bytes::from(&b"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"[..]),
744    ))
745    .expect("record full");
746    nv.to_record(RecordType::Params, 1).append(&mut b);
747    NVBody::new().to_record(RecordType::Params, 1).append(&mut b);
748
749    let mut dst = [0; 184];
750    b.copy_to_slice(&mut dst);
751
752    //padding is uninit
753    dst[175]=1;
754
755    let expected = b"\x01\x01\0\x01\0\x08\0\0\0\x01\0\0\0\0\0\0\x01\x04\0\x01\0\x97\x01\0\x0b\x80\0\0\x87HTTP_ACCEPTtext/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\x01\x01\x04\0\x01\0\0\0\0";
756
757    assert_eq!(dst, expected[..]);
758}