script_bindings/
str.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! The `ByteString` struct.
6use std::borrow::{Borrow, ToOwned};
7use std::default::Default;
8use std::hash::{Hash, Hasher};
9use std::ops::{Deref, DerefMut};
10use std::str::FromStr;
11use std::{fmt, ops, slice, str};
12
13use js::gc::{HandleObject, HandleValue};
14use js::rust::wrappers::ToJSON;
15
16pub use crate::domstring::DOMString;
17use crate::error::Error;
18use crate::script_runtime::JSContext;
19
20/// Encapsulates the IDL `ByteString` type.
21#[derive(Clone, Debug, Default, Eq, JSTraceable, MallocSizeOf, PartialEq)]
22pub struct ByteString(Vec<u8>);
23
24impl ByteString {
25    /// Creates a new `ByteString`.
26    pub fn new(value: Vec<u8>) -> ByteString {
27        ByteString(value)
28    }
29
30    /// Returns `self` as a string, if it encodes valid UTF-8, and `None`
31    /// otherwise.
32    pub fn as_str(&self) -> Option<&str> {
33        str::from_utf8(&self.0).ok()
34    }
35
36    /// Returns the length.
37    pub fn len(&self) -> usize {
38        self.0.len()
39    }
40
41    /// Checks if the ByteString is empty.
42    pub fn is_empty(&self) -> bool {
43        self.0.is_empty()
44    }
45
46    /// Returns `self` with A–Z replaced by a–z.
47    pub fn to_lower(&self) -> ByteString {
48        ByteString::new(self.0.to_ascii_lowercase())
49    }
50}
51
52impl From<ByteString> for Vec<u8> {
53    fn from(byte_string: ByteString) -> Vec<u8> {
54        byte_string.0
55    }
56}
57
58impl Hash for ByteString {
59    fn hash<H: Hasher>(&self, state: &mut H) {
60        self.0.hash(state);
61    }
62}
63
64impl FromStr for ByteString {
65    type Err = ();
66    fn from_str(s: &str) -> Result<ByteString, ()> {
67        Ok(ByteString::new(s.to_owned().into_bytes()))
68    }
69}
70
71impl ops::Deref for ByteString {
72    type Target = [u8];
73    fn deref(&self) -> &[u8] {
74        &self.0
75    }
76}
77
78/// A string that is constructed from a UCS-2 buffer by replacing invalid code
79/// points with the replacement character.
80#[derive(Clone, Debug, Default, Eq, Hash, MallocSizeOf, Ord, PartialEq, PartialOrd)]
81pub struct USVString(pub String);
82
83impl Borrow<str> for USVString {
84    #[inline]
85    fn borrow(&self) -> &str {
86        &self.0
87    }
88}
89
90impl Deref for USVString {
91    type Target = str;
92
93    #[inline]
94    fn deref(&self) -> &str {
95        &self.0
96    }
97}
98
99impl DerefMut for USVString {
100    #[inline]
101    fn deref_mut(&mut self) -> &mut str {
102        &mut self.0
103    }
104}
105
106impl AsRef<str> for USVString {
107    fn as_ref(&self) -> &str {
108        &self.0
109    }
110}
111
112impl fmt::Display for USVString {
113    #[inline]
114    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
115        fmt::Display::fmt(&**self, f)
116    }
117}
118
119impl PartialEq<str> for USVString {
120    fn eq(&self, other: &str) -> bool {
121        &**self == other
122    }
123}
124
125impl<'a> PartialEq<&'a str> for USVString {
126    fn eq(&self, other: &&'a str) -> bool {
127        &**self == *other
128    }
129}
130
131impl From<String> for USVString {
132    fn from(contents: String) -> USVString {
133        USVString(contents)
134    }
135}
136
137/// Returns whether `s` is a `token`, as defined by
138/// [RFC 2616](http://tools.ietf.org/html/rfc2616#page-17).
139pub fn is_token(s: &[u8]) -> bool {
140    if s.is_empty() {
141        return false; // A token must be at least a single character
142    }
143    s.iter().all(|&x| {
144        // http://tools.ietf.org/html/rfc2616#section-2.2
145        match x {
146            0..=31 | 127 => false, // CTLs
147            40 | 41 | 60 | 62 | 64 | 44 | 59 | 58 | 92 | 34 | 47 | 91 | 93 | 63 | 61 | 123 |
148            125 | 32 => false, // separators
149            x if x > 127 => false, // non-CHARs
150            _ => true,
151        }
152    })
153}
154
155/// Because this converts to a DOMString it becomes UTF-8 encoded which is closer to
156/// the spec definition of <https://infra.spec.whatwg.org/#serialize-a-javascript-value-to-json-bytes>
157/// but we generally do not operate on anything that is truly a WTF-16 string.
158///
159/// <https://infra.spec.whatwg.org/#serialize-a-javascript-value-to-a-json-string>
160pub fn serialize_jsval_to_json_utf8(cx: JSContext, data: HandleValue) -> Result<DOMString, Error> {
161    #[repr(C)]
162    struct ToJSONCallbackData {
163        string: Option<String>,
164    }
165
166    let mut out_str = ToJSONCallbackData { string: None };
167
168    #[allow(unsafe_code)]
169    unsafe extern "C" fn write_callback(
170        string: *const u16,
171        len: u32,
172        data: *mut std::ffi::c_void,
173    ) -> bool {
174        let data = data as *mut ToJSONCallbackData;
175        let string_chars = unsafe { slice::from_raw_parts(string, len as usize) };
176        unsafe { &mut *data }
177            .string
178            .get_or_insert_with(Default::default)
179            .push_str(&String::from_utf16_lossy(string_chars));
180        true
181    }
182
183    // 1. Let result be ? Call(%JSON.stringify%, undefined, « value »).
184    unsafe {
185        let stringify_result = ToJSON(
186            *cx,
187            data,
188            HandleObject::null(),
189            HandleValue::null(),
190            Some(write_callback),
191            &mut out_str as *mut ToJSONCallbackData as *mut _,
192        );
193        // Note: ToJSON returns false when a JS error is thrown, so we need to return
194        // JSFailed to propagate the raised exception
195        if !stringify_result {
196            return Err(Error::JSFailed);
197        }
198    }
199
200    // 2. If result is undefined, then throw a TypeError.
201    // Note: ToJSON will not call the callback if the data cannot be serialized.
202    // 3. Assert: result is a string.
203    // 4. Return result.
204    out_str
205        .string
206        .map(Into::into)
207        .ok_or_else(|| Error::Type("unable to serialize JSON".to_owned()))
208}