muesli/
ser.rs

1use std::io::Write;
2
3use crate::{
4    value::{ArrayKey, SessionEntry, Value},
5    ObjectPropertyVisibility,
6};
7
8/// Encode data to PHP's `serialize` format
9///
10/// # Errors
11///
12/// Will return `Err` if write fail
13pub fn serialize<W: Write>(w: &mut W, value: &Value) -> std::result::Result<usize, std::io::Error> {
14    match value {
15        Value::Null => w.write(b"N;"),
16        Value::Boolean(false) => w.write(b"b:0;"),
17        Value::Boolean(true) => w.write(b"b:1;"),
18        Value::Integer(n) => w.write(format!("i:{n};").as_bytes()),
19        Value::Decimal(d) => {
20            if d.is_nan() {
21                w.write(b"d:NAN;")
22            } else if d.is_infinite() {
23                if d.is_sign_positive() {
24                    w.write(b"d:INF;")
25                } else {
26                    w.write(b"d:-INF;")
27                }
28            } else {
29                w.write(format!("d:{d};").as_bytes())
30            }
31        }
32        Value::String(string) => {
33            let mut count = 0;
34            count += w.write(format!("s:{}:\"", string.len()).as_bytes())?;
35            count += w.write(string)?;
36            count += w.write(b"\";")?;
37            Ok(count)
38        }
39        Value::Array(items) => {
40            let mut count = 0;
41            count += w.write(format!("a:{}:{{", items.len()).as_bytes())?;
42            for (key, value) in items {
43                match key {
44                    ArrayKey::Integer(key) => {
45                        count += w.write(format!("i:{key};").as_bytes())?;
46                    }
47                    ArrayKey::String(key) => {
48                        count += w.write(format!("s:{}:\"", key.len()).as_bytes())?;
49                        count += w.write(key)?;
50                        count += w.write(b"\";")?;
51                    }
52                }
53                count += serialize(w, value)?;
54            }
55            count += w.write(b"}")?;
56            Ok(count)
57        }
58        Value::ValueReference(idx) => w.write(format!("R:{idx};").as_bytes()),
59        Value::ObjectReference(idx) => w.write(format!("r:{idx};").as_bytes()),
60        Value::Object {
61            class_name,
62            properties,
63        } => {
64            let mut count = 0;
65            count += w.write(format!("O:{}:\"", class_name.len()).as_bytes())?;
66            count += w.write(class_name)?;
67            count += w.write(format!("\":{}:{{", properties.len()).as_bytes())?;
68            for property in properties {
69                use ObjectPropertyVisibility::{Private, Protected, Public};
70
71                match property.visibility {
72                    Public => {
73                        count += w.write(format!("s:{}:\"", property.name.len()).as_bytes())?;
74                    }
75                    Protected => {
76                        count +=
77                            w.write(format!("s:{}:\"\0*\0", property.name.len() + 3).as_bytes())?;
78                    }
79                    Private => {
80                        count += w.write(
81                            format!("s:{}:\"\0", property.name.len() + 2 + class_name.len())
82                                .as_bytes(),
83                        )?;
84                        count += w.write(class_name)?;
85                        count += w.write(b"\0")?;
86                    }
87                }
88                count += w.write(property.name)?;
89                count += w.write(b"\";")?;
90                count += serialize(w, &property.value)?;
91            }
92            count += w.write(b"}")?;
93            Ok(count)
94        }
95        Value::CustomObject { class_name, data } => {
96            let mut count = 0;
97            count += w.write(format!("C:{}:\"", class_name.len()).as_bytes())?;
98            count += w.write(class_name)?;
99            count += w.write(format!("\":{}:{{", data.len()).as_bytes())?;
100            count += w.write(data)?;
101            count += w.write(b"}")?;
102            Ok(count)
103        }
104    }
105}
106
107/// Encode data to PHP's session format, compatible with `session_decode()`.
108///
109/// # Errors
110///
111/// Will return `Err` if write fail
112pub fn session_encode<W: Write>(
113    w: &mut W,
114    session: &[SessionEntry],
115) -> std::result::Result<usize, std::io::Error> {
116    let mut count = 0;
117    for entry in session {
118        count += w.write(entry.key)?;
119        count += w.write(b"|")?;
120        count += serialize(w, &entry.value)?;
121    }
122    Ok(count)
123}
124
125#[cfg(test)]
126mod tests {
127    use std::num::NonZeroUsize;
128
129    use crate::ObjectProperty;
130
131    use super::*;
132
133    fn run_encode_cases(cases: &[(Value, &[u8])]) {
134        let mut buffer = Vec::<u8>::new();
135        for (input, expected) in cases {
136            buffer.clear();
137            let count = serialize(&mut buffer, input).unwrap();
138            assert_eq!(&buffer.as_slice(), expected);
139            assert_eq!(count, expected.len());
140        }
141    }
142
143    fn run_session_encode_cases(cases: &[(Vec<SessionEntry>, &[u8])]) {
144        let mut buffer = Vec::<u8>::new();
145        for (input, expected) in cases {
146            buffer.clear();
147            let count = session_encode(&mut buffer, input).unwrap();
148            assert_eq!(&buffer.as_slice(), expected);
149            assert_eq!(count, expected.len());
150        }
151    }
152
153    #[test]
154    fn encode_value_null() {
155        let mut buffer = Vec::<u8>::new();
156        serialize(&mut buffer, &Value::Null).unwrap();
157        assert_eq!(buffer.as_slice(), b"N;");
158    }
159
160    #[test]
161    fn encode_value_boolean() {
162        let cases = [
163            (Value::Boolean(false), b"b:0;".as_slice()),
164            (Value::Boolean(true), b"b:1;".as_slice()),
165        ];
166        run_encode_cases(&cases);
167    }
168
169    #[test]
170    fn encode_value_integer() {
171        let cases = [
172            (Value::Integer(0), b"i:0;".as_slice()),
173            (Value::Integer(123), b"i:123;".as_slice()),
174            (Value::Integer(-23), b"i:-23;".as_slice()),
175            (Value::Integer(-23_432_123), b"i:-23432123;".as_slice()),
176        ];
177        run_encode_cases(&cases);
178    }
179
180    #[test]
181    fn encode_value_decimal() {
182        let cases = [
183            (Value::Decimal(0.0), b"d:0;".as_slice()),
184            (Value::Decimal(0.2), b"d:0.2;".as_slice()),
185            (Value::Decimal(-0.2), b"d:-0.2;".as_slice()),
186            (Value::Decimal(f64::NAN), b"d:NAN;".as_slice()),
187            (Value::Decimal(f64::INFINITY), b"d:INF;".as_slice()),
188            (Value::Decimal(f64::NEG_INFINITY), b"d:-INF;".as_slice()),
189        ];
190        run_encode_cases(&cases);
191    }
192
193    #[test]
194    fn encode_value_string() {
195        let cases = [
196            (Value::String(b"".as_slice()), b"s:0:\"\";".as_slice()),
197            (Value::String(b"foo".as_slice()), b"s:3:\"foo\";".as_slice()),
198        ];
199        run_encode_cases(&cases);
200    }
201
202    #[test]
203    fn encode_value_references() {
204        let cases = [
205            (
206                Value::ValueReference(NonZeroUsize::new(10).unwrap()),
207                b"R:10;".as_slice(),
208            ),
209            (
210                Value::ObjectReference(NonZeroUsize::new(42).unwrap()),
211                b"r:42;".as_slice(),
212            ),
213        ];
214        run_encode_cases(&cases);
215    }
216
217    #[test]
218    fn encode_object() {
219        let cases = [
220            (
221                Value::Object {
222                    class_name: b"Test".as_slice(),
223                    properties: vec![
224                        ObjectProperty {
225                            name: b"public".as_slice(),
226                            visibility: ObjectPropertyVisibility::Public,
227                            value: Value::Integer(1),
228                        },
229                        ObjectProperty {
230                            name: b"protected".as_slice(),
231                            visibility: ObjectPropertyVisibility::Protected,
232                            value: Value::Integer(2),
233                        },
234                        ObjectProperty {
235                            name: b"private".as_slice(),
236                            visibility: ObjectPropertyVisibility::Private,
237                            value: Value::Integer(3),
238                        },
239                    ],
240                },
241                b"O:4:\"Test\":3:{s:6:\"public\";i:1;s:12:\"\0*\0protected\";i:2;s:13:\"\0Test\0private\";i:3;}".as_slice(),
242            ),
243            (
244                Value::ObjectReference(NonZeroUsize::new(42).unwrap()),
245                b"r:42;".as_slice(),
246            ),
247        ];
248        run_encode_cases(&cases);
249    }
250
251    #[test]
252    fn encode_custom_object() {
253        let cases = [(
254            Value::CustomObject {
255                class_name: b"CustomSerializableClass".as_slice(),
256                data: b"foobar".as_slice(),
257            },
258            b"C:23:\"CustomSerializableClass\":6:{foobar}".as_slice(),
259        )];
260        run_encode_cases(&cases);
261    }
262
263    #[test]
264    fn encode_value_array() {
265        let cases = [
266            (Value::Array(vec![]), b"a:0:{}".as_slice()),
267            (
268                Value::Array(vec![(ArrayKey::String(b"foo"), Value::String(b"bar"))]),
269                b"a:1:{s:3:\"foo\";s:3:\"bar\";}".as_slice(),
270            ),
271            (
272                Value::Array(vec![(
273                    ArrayKey::Integer(0),
274                    Value::Array(vec![(
275                        ArrayKey::Integer(0),
276                        Value::Array(vec![(ArrayKey::Integer(0), Value::Array(vec![]))]),
277                    )]),
278                )]),
279                b"a:1:{i:0;a:1:{i:0;a:1:{i:0;a:0:{}}}}".as_slice(),
280            ),
281            (
282                Value::Array(vec![
283                    (ArrayKey::Integer(0), Value::Integer(1)),
284                    (ArrayKey::Integer(1), Value::Integer(1)),
285                    (ArrayKey::Integer(2), Value::Integer(2)),
286                    (ArrayKey::Integer(3), Value::Integer(3)),
287                    (ArrayKey::Integer(4), Value::Integer(5)),
288                ]),
289                b"a:5:{i:0;i:1;i:1;i:1;i:2;i:2;i:3;i:3;i:4;i:5;}".as_slice(),
290            ),
291        ];
292        run_encode_cases(&cases);
293    }
294
295    #[test]
296    fn encode_session() {
297        let cases = [
298            (vec![], b"".as_slice()),
299            (
300                vec![SessionEntry {
301                    key: b"foo",
302                    value: Value::Integer(42),
303                }],
304                b"foo|i:42;".as_slice(),
305            ),
306            (
307                vec![
308                    SessionEntry {
309                        key: b"foo",
310                        value: Value::Integer(42),
311                    },
312                    SessionEntry {
313                        key: b"bar",
314                        value: Value::String(b"baz".as_slice()),
315                    },
316                ],
317                b"foo|i:42;bar|s:3:\"baz\";".as_slice(),
318            ),
319            (
320                vec![
321                    SessionEntry {
322                        key: b"foo",
323                        value: Value::Integer(42),
324                    },
325                    SessionEntry {
326                        key: b"bar",
327                        value: Value::String(b"baz|qux".as_slice()),
328                    },
329                    SessionEntry {
330                        key: b"pub",
331                        value: Value::Integer(1337),
332                    },
333                ],
334                b"foo|i:42;bar|s:7:\"baz|qux\";pub|i:1337;".as_slice(),
335            ),
336        ];
337        run_session_encode_cases(&cases);
338    }
339}