1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
// Copyright 2019-2021 koushiro. Licensed under MIT.

#[cfg(all(not(feature = "std"), feature = "alloc"))]
use alloc::vec::Vec;
use core::str;

use nom::{
    number::streaming::{be_f64, be_i16, be_u16, be_u32, be_u8},
    IResult,
};

const SCRIPT_DATA_VALUE_STRING_TYPE: [u8; 1] = [0x02];
const OBJECT_END_MARKER: [u8; 3] = [0x00, 0x00, 0x09];

/// The tag data part of `script` FLV tag, including `name` and `value`.
/// The `name` is a `ScriptDataValue` enum whose type is `String`.
/// The `value` is a `ScriptDataValue` enum whose type is `ECMAArray`.
#[derive(Clone, Debug, PartialEq)]
pub struct ScriptTag<'a> {
    /// Method or object name.
    /// ScriptTagValue.Type = 2 (String)
    pub name: &'a str,
    /// AMF arguments or object properties.
    /// ScriptTagValue.Type = 8 (ECMAArray)
    pub value: ScriptDataValue<'a>,
}

impl<'a> ScriptTag<'a> {
    /// Parse script tag data.
    pub fn parse(input: &'a [u8], _size: usize) -> IResult<&'a [u8], ScriptTag<'a>> {
        do_parse!(
            input,
            // ScriptTagValue.Type = 2 (String)
            tag!(SCRIPT_DATA_VALUE_STRING_TYPE) >>
            // Method or object name.
            name: call!(ScriptDataValue::parse_string) >>
            // AMF arguments or object properties.
            // ScriptTagValue.Type = 8 (ECMA array)
            value: call!(ScriptDataValue::parse) >>

            (ScriptTag { name, value })
        )
    }
}

/// The `ScriptDataValue` enum.
#[derive(Debug, Clone, PartialEq)]
pub enum ScriptDataValue<'a> {
    /// 0, Number value.
    Number(f64),
    /// 1, Boolean value.
    Boolean(bool),
    /// 2, String value.
    String(&'a str),
    /// 3, Object value.
    Object(Vec<ScriptDataObjectProperty<'a>>),
    /// 4, MovieClip value.
    MovieClip,
    /// 5, Null value.
    Null,
    /// 6, Undefined value.
    Undefined,
    /// 7, Reference value.
    Reference(u16),
    /// 8, ECMA Array value.
    ECMAArray(Vec<ScriptDataObjectProperty<'a>>),
    /// 10, Strict Array value.
    StrictArray(Vec<ScriptDataValue<'a>>),
    /// 11, Date value.
    Date(ScriptDataDate),
    /// 12, Long String value.
    LongString(&'a str),
}

impl<'a> ScriptDataValue<'a> {
    /// Parse script tag data value.
    pub fn parse(input: &'a [u8]) -> IResult<&'a [u8], ScriptDataValue<'a>> {
        switch!(input,
            // parse script value type
            be_u8,
            // parse script data value
            0  => map!(Self::parse_number, ScriptDataValue::Number)               |
            1  => map!(Self::parse_boolean, |v| ScriptDataValue::Boolean(v != 0)) |
            2  => map!(Self::parse_string, ScriptDataValue::String)               |
            3  => map!(Self::parse_object, ScriptDataValue::Object)               |
            4  => value!(ScriptDataValue::MovieClip)                              |
            5  => value!(ScriptDataValue::Null)                                   |
            6  => value!(ScriptDataValue::Undefined)                              |
            7  => map!(Self::parse_reference, ScriptDataValue::Reference)         |
            8  => map!(Self::parse_ecma_array, ScriptDataValue::ECMAArray)        |
            10 => map!(Self::parse_strict_array, ScriptDataValue::StrictArray)    |
            11 => map!(Self::parse_date, ScriptDataValue::Date)                   |
            12 => map!(Self::parse_long_string, ScriptDataValue::LongString)
        )
    }

    /// Parse script tag data number value.
    pub fn parse_number(input: &[u8]) -> IResult<&[u8], f64> {
        be_f64(input)
    }

    /// Parse script tag data boolean value.
    pub fn parse_boolean(input: &[u8]) -> IResult<&[u8], u8> {
        be_u8(input)
    }

    /// Parse script tag data string value.
    pub fn parse_string(input: &[u8]) -> IResult<&[u8], &str> {
        map_res!(input, length_data!(be_u16), str::from_utf8)
    }

    /// Parse script tag data object value.
    pub fn parse_object(input: &'a [u8]) -> IResult<&'a [u8], Vec<ScriptDataObjectProperty<'a>>> {
        terminated!(
            input,
            // parse object properties
            many0!(Self::parse_object_property),
            // parse object end marker
            call!(Self::parse_object_end_marker)
        )
    }

    /// Parse script tag data object property.
    fn parse_object_property(input: &'a [u8]) -> IResult<&'a [u8], ScriptDataObjectProperty<'a>> {
        do_parse!(
            input,
            // parse object property name
            name: call!(Self::parse_string) >>
            // parse object property value
            value: call!(Self::parse) >>

            (ScriptDataObjectProperty { name, value })
        )
    }

    /// Parse script tag data object end marker.
    fn parse_object_end_marker(input: &[u8]) -> IResult<&[u8], &[u8]> {
        tag!(input, OBJECT_END_MARKER)
    }

    /// Parse script tag data reference value.
    pub fn parse_reference(input: &[u8]) -> IResult<&[u8], u16> {
        be_u16(input)
    }

    /// Parse script tag data ECMA array value.
    pub fn parse_ecma_array(
        input: &'a [u8],
    ) -> IResult<&'a [u8], Vec<ScriptDataObjectProperty<'a>>> {
        // The list contains approximately ECMA Array Length number of items.
        do_parse!(
            input,
            // parse ECMA array length
            _length: be_u32 >>
            // parse object Properties and Object End marker
            object: call!(Self::parse_object) >>

            (object)
        )
    }

    /// Parse script tag data strict array value.
    pub fn parse_strict_array(input: &'a [u8]) -> IResult<&'a [u8], Vec<ScriptDataValue<'a>>> {
        // The list shall contain Strict Array Length number of values.
        // No terminating record follows the list.
        do_parse!(
            input,
            // parse strict array length
            length: be_u32 >>
            // parse values
            value: count!(call!(Self::parse), length as usize) >>

            (value)
        )
    }

    /// Parse script tag data date value.
    pub fn parse_date(input: &[u8]) -> IResult<&[u8], ScriptDataDate> {
        do_parse!(
            input,
            // Number of milliseconds since UNIX_EPOCH.
            date_time: be_f64 >>
            // Local time offset in minutes from UTC.
            local_date_time_offset: be_i16 >>

            (ScriptDataDate { date_time, local_date_time_offset })
        )
    }

    /// Parse script tag data long string value.
    pub fn parse_long_string(input: &[u8]) -> IResult<&[u8], &str> {
        map_res!(input, length_data!(be_u32), str::from_utf8)
    }
}

/// The `ScriptDataObjectProperty` is the component of `Object` and `ECMAArray`,
/// which are a kind of `ScriptDataValue`.
#[derive(Clone, Debug, PartialEq)]
pub struct ScriptDataObjectProperty<'a> {
    /// Object property name.
    pub name: &'a str,
    /// Object property value.
    pub value: ScriptDataValue<'a>,
}

/// The `ScriptDataDate` is a kind of `ScriptDataValue`.
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct ScriptDataDate {
    /// Number of milliseconds since UNIX_EPOCH.
    // SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_millis()
    pub date_time: f64,
    /// Local time offset in minutes from UTC.
    /// For time zones located west of Greenwich, this value is a negative number.
    /// Time zones east of Greenwich are positive.
    pub local_date_time_offset: i16,
}