1
//! Store XML element attributes and their values.
2

            
3
use std::slice;
4
use std::str;
5

            
6
use markup5ever::{
7
    expanded_name, local_name, namespace_url, ns, LocalName, Namespace, Prefix, QualName,
8
};
9
use string_cache::DefaultAtom;
10

            
11
use crate::error::{ImplementationLimit, LoadingError};
12
use crate::limits;
13
use crate::util::{opt_utf8_cstr, utf8_cstr, utf8_cstr_bounds};
14

            
15
/// Type used to store attribute values.
16
///
17
/// Attribute values are often repeated in an SVG file, so we intern them using the
18
/// string_cache crate.
19
pub type AttributeValue = DefaultAtom;
20

            
21
/// Iterable wrapper for libxml2's representation of attribute/value.
22
///
23
/// See the [`new_from_xml2_attributes`] function for information.
24
///
25
/// [`new_from_xml2_attributes`]: #method.new_from_xml2_attributes
26
#[derive(Clone)]
27
pub struct Attributes {
28
    attrs: Box<[(QualName, AttributeValue)]>,
29
    id_idx: Option<u16>,
30
    class_idx: Option<u16>,
31
}
32

            
33
/// Iterator from `Attributes.iter`.
34
pub struct AttributesIter<'a>(slice::Iter<'a, (QualName, AttributeValue)>);
35

            
36
#[cfg(test)]
37
impl Default for Attributes {
38
    fn default() -> Self {
39
        Self::new()
40
    }
41
}
42

            
43
impl Attributes {
44
    #[cfg(test)]
45
3
    pub fn new() -> Attributes {
46
3
        Attributes {
47
3
            attrs: [].into(),
48
3
            id_idx: None,
49
3
            class_idx: None,
50
3
        }
51
3
    }
52

            
53
    /// Creates an iterable `Attributes` from the C array of borrowed C strings.
54
    ///
55
    /// With libxml2's SAX parser, the caller's startElementNsSAX2Func
56
    /// callback gets passed a `xmlChar **` for attributes, which
57
    /// comes in groups of (localname/prefix/URI/value_start/value_end).
58
    /// In those, localname/prefix/URI are NUL-terminated strings;
59
    /// value_start and value_end point to the start-inclusive and
60
    /// end-exclusive bytes in the attribute's value.
61
    ///
62
    /// # Safety
63
    ///
64
    /// This function is unsafe because the caller must guarantee the following:
65
    ///
66
    /// * `attrs` is a valid pointer, with (n_attributes * 5) elements.
67
    ///
68
    /// * All strings are valid UTF-8.
69
19461557
    pub unsafe fn new_from_xml2_attributes(
70
19461557
        n_attributes: usize,
71
19461557
        attrs: *const *const libc::c_char,
72
19461557
    ) -> Result<Attributes, LoadingError> {
73
19461557
        let mut array = Vec::with_capacity(n_attributes);
74
19461557
        let mut id_idx = None;
75
19461557
        let mut class_idx = None;
76
19461557

            
77
19461557
        if n_attributes > limits::MAX_LOADED_ATTRIBUTES {
78
            return Err(LoadingError::LimitExceeded(
79
                ImplementationLimit::TooManyAttributes,
80
            ));
81
19461557
        }
82
19461557

            
83
19461557
        if n_attributes > 0 && !attrs.is_null() {
84
1709287
            for attr in slice::from_raw_parts(attrs, n_attributes * 5).chunks_exact(5) {
85
1709287
                let localname = attr[0];
86
1709287
                let prefix = attr[1];
87
1709287
                let uri = attr[2];
88
1709287
                let value_start = attr[3];
89
1709287
                let value_end = attr[4];
90
1709287

            
91
1709287
                assert!(!localname.is_null());
92

            
93
1709287
                let localname = utf8_cstr(localname);
94
1709287

            
95
1709287
                let prefix = opt_utf8_cstr(prefix);
96
1709287
                let uri = opt_utf8_cstr(uri);
97
1709287
                let qual_name = QualName::new(
98
1709287
                    prefix.map(Prefix::from),
99
1709287
                    uri.map(Namespace::from)
100
1709287
                        .unwrap_or_else(|| namespace_url!("")),
101
1709287
                    LocalName::from(localname),
102
1709287
                );
103
1709287

            
104
1709287
                if !value_start.is_null() && !value_end.is_null() {
105
1709287
                    assert!(value_end >= value_start);
106

            
107
1709287
                    let value_str = utf8_cstr_bounds(value_start, value_end);
108
1709287
                    let value_atom = DefaultAtom::from(value_str);
109
1709287

            
110
1709287
                    let idx = array.len() as u16;
111
1709287
                    match qual_name.expanded() {
112
221356
                        expanded_name!("", "id") => id_idx = Some(idx),
113
1863
                        expanded_name!("", "class") => class_idx = Some(idx),
114
1486068
                        _ => (),
115
                    }
116

            
117
1709287
                    array.push((qual_name, value_atom));
118
                }
119
            }
120
19086780
        }
121

            
122
19461557
        Ok(Attributes {
123
19461557
            attrs: array.into(),
124
19461557
            id_idx,
125
19461557
            class_idx,
126
19461557
        })
127
19461557
    }
128

            
129
    /// Returns the number of attributes.
130
1
    pub fn len(&self) -> usize {
131
1
        self.attrs.len()
132
1
    }
133

            
134
    /// Creates an iterator that yields `(QualName, &'a str)` tuples.
135
39543940
    pub fn iter(&self) -> AttributesIter<'_> {
136
39543940
        AttributesIter(self.attrs.iter())
137
39543940
    }
138

            
139
57628713
    pub fn get_id(&self) -> Option<&str> {
140
57628713
        self.id_idx.and_then(|idx| {
141
10842875
            self.attrs
142
10842875
                .get(usize::from(idx))
143
10842875
                .map(|(_name, value)| &value[..])
144
57628713
        })
145
57628713
    }
146

            
147
17483
    pub fn get_class(&self) -> Option<&str> {
148
17483
        self.class_idx.and_then(|idx| {
149
6253
            self.attrs
150
6253
                .get(usize::from(idx))
151
6253
                .map(|(_name, value)| &value[..])
152
17483
        })
153
17483
    }
154

            
155
3602
    pub fn clear_class(&mut self) {
156
3602
        self.class_idx = None;
157
3602
    }
158
}
159

            
160
impl<'a> Iterator for AttributesIter<'a> {
161
    type Item = (QualName, &'a str);
162

            
163
45059873
    fn next(&mut self) -> Option<Self::Item> {
164
45059873
        self.0.next().map(|(a, v)| (a.clone(), v.as_ref()))
165
45059873
    }
166
}
167

            
168
#[cfg(test)]
169
mod tests {
170
    use super::*;
171
    use markup5ever::{expanded_name, local_name, ns};
172
    use std::ffi::CString;
173
    use std::ptr;
174

            
175
    #[test]
176
1
    fn empty_attributes() {
177
1
        let map = unsafe { Attributes::new_from_xml2_attributes(0, ptr::null()).unwrap() };
178
1
        assert_eq!(map.len(), 0);
179
1
    }
180

            
181
    #[test]
182
1
    fn attributes_with_namespaces() {
183
1
        let attrs = [
184
1
            (
185
1
                CString::new("href").unwrap(),
186
1
                Some(CString::new("xlink").unwrap()),
187
1
                Some(CString::new("http://www.w3.org/1999/xlink").unwrap()),
188
1
                CString::new("1").unwrap(),
189
1
            ),
190
1
            (
191
1
                CString::new("ry").unwrap(),
192
1
                None,
193
1
                None,
194
1
                CString::new("2").unwrap(),
195
1
            ),
196
1
            (
197
1
                CString::new("d").unwrap(),
198
1
                None,
199
1
                None,
200
1
                CString::new("").unwrap(),
201
1
            ),
202
1
        ];
203
1

            
204
1
        let mut v: Vec<*const libc::c_char> = Vec::new();
205

            
206
4
        for (localname, prefix, uri, val) in &attrs {
207
3
            v.push(localname.as_ptr());
208
3
            v.push(
209
3
                prefix
210
3
                    .as_ref()
211
3
                    .map(|p: &CString| p.as_ptr())
212
3
                    .unwrap_or_else(ptr::null),
213
3
            );
214
3
            v.push(
215
3
                uri.as_ref()
216
3
                    .map(|p: &CString| p.as_ptr())
217
3
                    .unwrap_or_else(ptr::null),
218
3
            );
219
3

            
220
3
            let val_start = val.as_ptr();
221
3
            let val_end = unsafe { val_start.add(val.as_bytes().len()) };
222
3
            v.push(val_start); // value_start
223
3
            v.push(val_end); // value_end
224
3
        }
225

            
226
1
        let attrs = unsafe { Attributes::new_from_xml2_attributes(3, v.as_ptr()).unwrap() };
227
1

            
228
1
        let mut had_href: bool = false;
229
1
        let mut had_ry: bool = false;
230
1
        let mut had_d: bool = false;
231

            
232
3
        for (a, v) in attrs.iter() {
233
3
            match a.expanded() {
234
                expanded_name!(xlink "href") => {
235
1
                    assert!(v == "1");
236
1
                    had_href = true;
237
                }
238

            
239
                expanded_name!("", "ry") => {
240
1
                    assert!(v == "2");
241
1
                    had_ry = true;
242
                }
243

            
244
                expanded_name!("", "d") => {
245
1
                    assert!(v.is_empty());
246
1
                    had_d = true;
247
                }
248

            
249
                _ => unreachable!(),
250
            }
251
        }
252

            
253
1
        assert!(had_href);
254
1
        assert!(had_ry);
255
1
        assert!(had_d);
256
1
    }
257
}