Skip to main content

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::ToOwned;
7use std::default::Default;
8use std::hash::{Hash, Hasher};
9use std::ops::Deref;
10use std::ptr::NonNull;
11use std::str::FromStr;
12use std::{fmt, ops, slice, str};
13
14use js::context::JSContext;
15use js::conversions::latin1_to_string;
16use js::gc::{HandleObject, HandleValue};
17use js::jsapi::{JS_DeprecatedStringHasLatin1Chars, JSString};
18use js::rust::wrappers2::{JS_GetTwoByteStringCharsAndLength, ToJSON};
19use js::rust::{
20    HandleValue as SafeHandleValue, MutableHandleString as SafeMutableHandleString, ToString,
21};
22
23pub use crate::domstring::DOMString;
24use crate::error::Error;
25
26/// Encapsulates the IDL `ByteString` type.
27#[derive(Clone, Debug, Default, Eq, JSTraceable, MallocSizeOf, PartialEq)]
28pub struct ByteString(Vec<u8>);
29
30impl ByteString {
31    /// Creates a new `ByteString`.
32    pub fn new(value: Vec<u8>) -> ByteString {
33        ByteString(value)
34    }
35
36    /// Returns `self` as a string, if it encodes valid UTF-8, and `None`
37    /// otherwise.
38    pub fn as_str(&self) -> Option<&str> {
39        str::from_utf8(&self.0).ok()
40    }
41
42    /// Returns the length.
43    pub fn len(&self) -> usize {
44        self.0.len()
45    }
46
47    /// Checks if the ByteString is empty.
48    pub fn is_empty(&self) -> bool {
49        self.0.is_empty()
50    }
51
52    /// Returns `self` with A–Z replaced by a–z.
53    pub fn to_lower(&self) -> ByteString {
54        ByteString::new(self.0.to_ascii_lowercase())
55    }
56}
57
58impl From<ByteString> for Vec<u8> {
59    fn from(byte_string: ByteString) -> Vec<u8> {
60        byte_string.0
61    }
62}
63
64impl Hash for ByteString {
65    fn hash<H: Hasher>(&self, state: &mut H) {
66        self.0.hash(state);
67    }
68}
69
70impl FromStr for ByteString {
71    type Err = ();
72    fn from_str(s: &str) -> Result<ByteString, ()> {
73        Ok(ByteString::new(s.to_owned().into_bytes()))
74    }
75}
76
77impl ops::Deref for ByteString {
78    type Target = [u8];
79    fn deref(&self) -> &[u8] {
80        &self.0
81    }
82}
83
84/// A string that is constructed from a UCS-2 buffer by replacing invalid code
85/// points with the replacement character.
86#[derive(Clone, Debug, Default, Eq, Hash, MallocSizeOf, Ord, PartialEq, PartialOrd)]
87pub struct USVString(pub String);
88
89impl USVString {
90    /// Creates a new `USVString`.
91    pub fn new() -> USVString {
92        USVString(String::new())
93    }
94}
95
96impl Deref for USVString {
97    type Target = str;
98
99    #[inline]
100    fn deref(&self) -> &str {
101        &self.0
102    }
103}
104
105impl AsRef<str> for USVString {
106    fn as_ref(&self) -> &str {
107        &self.0
108    }
109}
110
111impl fmt::Display for USVString {
112    #[inline]
113    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
114        fmt::Display::fmt(&self.0, f)
115    }
116}
117
118impl PartialEq<str> for USVString {
119    fn eq(&self, other: &str) -> bool {
120        self.0 == other
121    }
122}
123
124impl<'a> PartialEq<&'a str> for USVString {
125    fn eq(&self, other: &&'a str) -> bool {
126        self.0 == *other
127    }
128}
129
130impl From<String> for USVString {
131    fn from(contents: String) -> USVString {
132        USVString(contents)
133    }
134}
135
136impl From<USVString> for String {
137    fn from(value: USVString) -> Self {
138        value.0
139    }
140}
141
142impl From<USVString> for DOMString {
143    fn from(value: USVString) -> Self {
144        value.0.into()
145    }
146}
147
148/// Returns whether `s` is a `token`, as defined by
149/// [RFC 2616](http://tools.ietf.org/html/rfc2616#page-17).
150pub fn is_token(s: &[u8]) -> bool {
151    if s.is_empty() {
152        return false; // A token must be at least a single character
153    }
154    s.iter().all(|&x| {
155        // http://tools.ietf.org/html/rfc2616#section-2.2
156        match x {
157            0..=31 | 127 => false, // CTLs
158            40 | 41 | 60 | 62 | 64 | 44 | 59 | 58 | 92 | 34 | 47 | 91 | 93 | 63 | 61 | 123 |
159            125 | 32 => false, // separators
160            x if x > 127 => false, // non-CHARs
161            _ => true,
162        }
163    })
164}
165
166/// Because this converts to a DOMString it becomes UTF-8 encoded which is closer to
167/// the spec definition of <https://infra.spec.whatwg.org/#serialize-a-javascript-value-to-json-bytes>
168/// but we generally do not operate on anything that is truly a WTF-16 string.
169///
170/// <https://infra.spec.whatwg.org/#serialize-a-javascript-value-to-a-json-string>
171pub fn serialize_jsval_to_json_utf8(
172    cx: &mut JSContext,
173    data: HandleValue,
174) -> Result<DOMString, Error> {
175    #[repr(C)]
176    struct ToJSONCallbackData {
177        string: Option<String>,
178    }
179
180    let mut out_str = ToJSONCallbackData { string: None };
181
182    #[expect(unsafe_code)]
183    unsafe extern "C" fn write_callback(
184        string: *const u16,
185        len: u32,
186        data: *mut std::ffi::c_void,
187    ) -> bool {
188        let data = data as *mut ToJSONCallbackData;
189        let string_chars = unsafe { slice::from_raw_parts(string, len as usize) };
190        unsafe { &mut *data }
191            .string
192            .get_or_insert_with(Default::default)
193            .push_str(&String::from_utf16_lossy(string_chars));
194        true
195    }
196
197    // 1. Let result be ? Call(%JSON.stringify%, undefined, « value »).
198    unsafe {
199        let stringify_result = ToJSON(
200            cx,
201            data,
202            HandleObject::null(),
203            HandleValue::null(),
204            Some(write_callback),
205            &mut out_str as *mut ToJSONCallbackData as *mut _,
206        );
207        // Note: ToJSON returns false when a JS error is thrown, so we need to return
208        // JSFailed to propagate the raised exception
209        if !stringify_result {
210            return Err(Error::JSFailed);
211        }
212    }
213
214    // 2. If result is undefined, then throw a TypeError.
215    // Note: ToJSON will not call the callback if the data cannot be serialized.
216    // 3. Assert: result is a string.
217    // 4. Return result.
218    out_str
219        .string
220        .map(Into::into)
221        .ok_or_else(|| Error::Type(c"unable to serialize JSON".to_owned()))
222}
223
224fn has_latin1_chars(jsstr: *mut JSString) -> bool {
225    unsafe { JS_DeprecatedStringHasLatin1Chars(jsstr) }
226}
227
228/// Safe wrapper around <https://tc39.es/ecma262/multipage/abstract-operations.html#sec-tostring>
229pub fn to_js_string(
230    cx: &mut JSContext,
231    source: SafeHandleValue,
232    target: &mut SafeMutableHandleString,
233) -> Result<(), Error> {
234    rooted!(&in(cx) let jsstr = unsafe { ToString(cx, source) });
235    if jsstr.is_null() {
236        return Err(Error::JSFailed);
237    }
238    target.set(*jsstr);
239    Ok(())
240}
241
242/// <https://infra.spec.whatwg.org/#code-unit>
243pub struct CodeUnits(pub Vec<u16>);
244
245/// A ad-hoc replacement for <https://webidl.spec.whatwg.org/#idl-DOMString>,
246/// used by `component/script/dom/encoding`.
247pub enum ConversionResult {
248    CodeUnits(CodeUnits),
249    String(String),
250}
251
252/// <https://webidl.spec.whatwg.org/#js-DOMString>
253/// Implements the js to DOMstring conversion, but using an ad-hoc data structure,
254/// because current DOMString implementation does not preserve the exact sequence of code units.
255pub fn js_string_to_code_units(
256    cx: &mut JSContext,
257    data: SafeHandleValue,
258    mut target: SafeMutableHandleString,
259) -> Result<ConversionResult, Error> {
260    // Step 1: If V is null
261    // and the conversion is to an IDL type associated with the [LegacyNullToEmptyString] extended attribute,
262    // then return the DOMString value that represents the empty string.
263    // Note: skipping this step to keep a PASS on
264    // /encoding/streams/encode-bad-chunks.any.html,
265    // which seems to require the js error returned below.
266
267    // Step 2: Let x be ? ToString(V).
268    to_js_string(cx, data, &mut target)?;
269
270    // Step 3: Return the IDL DOMString value that represents
271    // the same sequence of code units as the one the JavaScript String value x represents.
272    // Note: not using DOMString because it does not preserve the same sequence of code units.
273    if has_latin1_chars(*target) {
274        let string =
275            unsafe { latin1_to_string(cx, NonNull::new(*target).expect("jsstr cannot be null")) };
276        Ok(ConversionResult::String(string))
277    } else {
278        let maybe_ill_formed_code_units = unsafe {
279            let mut len = 0;
280            let data = JS_GetTwoByteStringCharsAndLength(cx, *target, &mut len);
281
282            // Note: defensive copy to avoid having the js string data move underneath us.
283            // For an optimal pattern,
284            // see <https://searchfox.org/firefox-main/rev/a4d4f7ecfae304e10b0f6724595f9c931d7c919b/js/src/vm/StringType.cpp#1738>
285            std::slice::from_raw_parts(data, len).to_vec()
286        };
287        Ok(ConversionResult::CodeUnits(CodeUnits(
288            maybe_ill_formed_code_units,
289        )))
290    }
291}