diff --git a/Cargo.lock b/Cargo.lock index cd1a8ee3a7b3..f2fbdbe5afa9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1072,6 +1072,12 @@ dependencies = [ "syn", ] +[[package]] +name = "data-encoding" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72aa14c04dfae8dd7d8a2b1cb7ca2152618cd01336dbfe704b8dcbf8d41dbd69" + [[package]] name = "dbus" version = "0.6.4" @@ -1972,6 +1978,7 @@ dependencies = [ "gecko_logger", "geckoservo", "gkrust_utils", + "http_sfv", "jsrust_shared", "kvstore", "lmdb-rkv-sys", @@ -2255,6 +2262,17 @@ dependencies = [ "neqo-transport", ] +[[package]] +name = "http_sfv" +version = "0.1.0" +dependencies = [ + "nserror", + "nsstring", + "sfv", + "thin-vec", + "xpcom", +] + [[package]] name = "httparse" version = "1.3.3" @@ -4233,6 +4251,16 @@ dependencies = [ "sha2", ] +[[package]] +name = "rust_decimal" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95ba36e8c41bf675947e200af432325f332f60a0aea0ef2dc456636c2f6037d7" +dependencies = [ + "num-traits", + "serde", +] + [[package]] name = "rustc-demangle" version = "0.1.8" @@ -4449,6 +4477,17 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "sfv" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83166498beeaadbb6ddf69e7ed7b2b009e2b2d4e827aae762d2d310d4f648a3b" +dependencies = [ + "data-encoding", + "indexmap", + "rust_decimal", +] + [[package]] name = "sha-1" version = "0.8.1" diff --git a/netwerk/base/http-sfv/Cargo.toml b/netwerk/base/http-sfv/Cargo.toml new file mode 100644 index 000000000000..8769e4174f30 --- /dev/null +++ b/netwerk/base/http-sfv/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "http_sfv" +version = "0.1.0" +authors = ["barabass "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +nserror = { path = "../../../xpcom/rust/nserror" } +nsstring = { path = "../../../xpcom/rust/nsstring" } +sfv = "0.4.0" +xpcom = { path = "../../../xpcom/rust/xpcom" } +thin-vec = { version = "0.1.0", features = ["gecko-ffi"] } diff --git a/netwerk/base/http-sfv/SFVService.cpp b/netwerk/base/http-sfv/SFVService.cpp new file mode 100644 index 000000000000..9759993e7f1c --- /dev/null +++ b/netwerk/base/http-sfv/SFVService.cpp @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/StaticPtr.h" +#include "nsCOMPtr.h" +#include "SFVService.h" + +// This anonymous namespace prevents outside C++ code from improperly accessing +// these implementation details. +namespace { +extern "C" { +// Implemented in Rust. +void new_sfv_service(nsISFVService** result); +} + +static mozilla::StaticRefPtr sService; +} // namespace + +namespace mozilla::net { + +already_AddRefed GetSFVService() { + nsCOMPtr service; + + if (sService) { + service = sService; + } else { + new_sfv_service(getter_AddRefs(service)); + sService = service; + mozilla::ClearOnShutdown(&sService); + } + + return service.forget(); +} + +} // namespace mozilla::net diff --git a/netwerk/base/http-sfv/SFVService.h b/netwerk/base/http-sfv/SFVService.h new file mode 100644 index 000000000000..401695160938 --- /dev/null +++ b/netwerk/base/http-sfv/SFVService.h @@ -0,0 +1,14 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIStructuredFieldValues.h" +namespace mozilla { +namespace net { + +already_AddRefed GetSFVService(); + +} // namespace net +} // namespace mozilla diff --git a/netwerk/base/http-sfv/moz.build b/netwerk/base/http-sfv/moz.build new file mode 100644 index 000000000000..027d0f092c95 --- /dev/null +++ b/netwerk/base/http-sfv/moz.build @@ -0,0 +1,17 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +XPIDL_SOURCES += [ + 'nsIStructuredFieldValues.idl', +] + +XPIDL_MODULE = 'http-sfv' + +EXPORTS.mozilla.net += ['SFVService.h'] + +SOURCES += ['SFVService.cpp'] + +FINAL_LIBRARY = 'xul' diff --git a/netwerk/base/http-sfv/nsIStructuredFieldValues.idl b/netwerk/base/http-sfv/nsIStructuredFieldValues.idl new file mode 100644 index 000000000000..5a70948757a5 --- /dev/null +++ b/netwerk/base/http-sfv/nsIStructuredFieldValues.idl @@ -0,0 +1,290 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * Conceptually, there are three types of structured field (header) values: + * + * Item - can be an Integer, Decimal, String, Token, Byte Sequence, or Boolean. + * It can have associated Parameters. + * List - array of zero or more members, each of which can be an Item or an InnerList, + * both of which can be Parameterized. + * Dictionary - ordered map of name-value pairs, where the names are short textual strings + * and the values are Items or arrays of Items (represented with InnerList), + * both of which can be Parameterized. There can be zero or more members, + * and their names are unique in the scope of the Dictionary they occur within. + * + * + * There's also a few primitive types used to construct structured field values: + * - BareItem used as Item's value or as a parameter value in Parameters. + * - Parameters are an ordered map of key-value pairs that are associated with an Item or InnerList. + * The keys are unique within the scope the Parameters they occur within, and the values are BareItem. + * - InnerList is an array of zero or more Items. Can have Parameters. + * - ListEntry represents either Item or InnerList as a member of List or as member-value in Dictionary. + */ + + + +/** + * nsISFVBareItem is a building block for Item header value (nsISFVItem) and Parameters (nsISFVParams). + * It can be of type BOOL, STRING, DECIMAL, INTEGER, TOKEN, BYTE_SEQUENCE. + * Each type is represented by its own interface which is used to create + * a bare item of that type. + */ +[scriptable, builtinclass, uuid(7072853f-215b-4a8a-92e5-9732bccc377b)] +interface nsISFVBareItem : nsISupports { + const long BOOL = 1; + const long STRING = 2; + const long DECIMAL = 3; + const long INTEGER = 4; + const long TOKEN = 5; + const long BYTE_SEQUENCE = 6; + + /** + * Returns value associated with type of bare item. + * Used to identify type of bare item without querying for interface + * (like nsISFVString, etc). + */ + readonly attribute long long type; +}; + +[scriptable, builtinclass, uuid(843eea44-990a-422c-bbf2-2aa4ba9ee4d2)] +interface nsISFVInteger : nsISFVBareItem { + attribute long long value; +}; + +[scriptable, builtinclass, uuid(df6a0787-7caa-4fef-b145-08c1104c2fde)] +interface nsISFVString : nsISFVBareItem { + attribute ACString value; +}; + +[scriptable, builtinclass, uuid(d263c6d7-4123-4c39-a121-ccf874a19012)] +interface nsISFVBool : nsISFVBareItem { + attribute boolean value; +}; + +[scriptable, builtinclass, uuid(1098da8b-b4df-4526-b985-53dbd4160ad2)] +interface nsISFVDecimal : nsISFVBareItem { + attribute double value; +}; + +[scriptable, builtinclass, uuid(8ad33d52-b9b2-4a17-8aa8-991250fc1214)] +interface nsISFVToken : nsISFVBareItem { + attribute ACString value; +}; + +[scriptable, builtinclass, uuid(887eaef0-19fe-42bc-9a42-9ff773aa8fea)] +interface nsISFVByteSeq : nsISFVBareItem { + attribute ACString value; +}; + + +/** + * nsISFVParams represents parameters, key-value pairs of ACString and nsISFVBareItem, + * which parametrize Item type header or InnerList type withing List type header. + */ +[scriptable, builtinclass, uuid(b1a397d7-3333-43e7-993a-fbe8ab90ee96)] +interface nsISFVParams : nsISupports { + /** + * Return value (nsISFVBareItem) stored for key, if it is present + * + * @throws NS_ERROR_UNEXPECTED if the key does not exist in parameters. + */ + nsISFVBareItem get(in ACString key); + + /** + * Insert a new key-value pair. + * If an equivalent key already exists: the key remains and retains in its place in the order, + * its corresponding value is updated with the new value. + * + * @throws NS_ERROR_UNEXPECTED if supplied item does not implement nsISFVBareItem interface. + */ + void set(in ACString key, in nsISFVBareItem item); + + /** + * Remove the key-value pair equivalent to key. + * + * @throws NS_ERROR_UNEXPECTED upon attempt to delete key that does not exist in parameters. + */ + void delete(in ACString key); + + /** + * Returns array of keys. + */ + Array keys(); +}; + +/** + * nsISFVParametrizable is implemented for types that + * can be parametrized with nsISFVParams + */ +[scriptable, builtinclass, uuid(6c0399f8-01de-4b25-b339-68e35e8d2e49)] +interface nsISFVParametrizable : nsISupports { + readonly attribute nsISFVParams params; +}; + +/** + * nsISFVItemOrInnerList represents member in nsISFVList + * or member-value in nsISFVDictionary. + * nsISFVItemOrInnerList is implemented for + * nsISFVItem or nsISFVInnerList, both of which are used + * to create nsISFVList and nsISFVDictionary. + */ +[scriptable, builtinclass, uuid(99ac1b56-b5b3-44e7-ad96-db7444aae4b2)] +interface nsISFVItemOrInnerList : nsISFVParametrizable { +}; + +/** + * nsISFVSerialize indicates that object can be serialized into ACString. + */ +[scriptable, builtinclass, uuid(28b9215d-c131-413c-9482-0004a371a5ec)] +interface nsISFVSerialize : nsISupports { + ACString serialize(); +}; + +/** + * nsISFVItem represents Item structured header value. + */ +[scriptable, builtinclass, uuid(abe8826b-6af7-4e54-bd2c-46ab231700ce)] +interface nsISFVItem : nsISFVItemOrInnerList { + readonly attribute nsISFVBareItem value; + ACString serialize(); +}; + +/** + * nsISFVInnerList can be used as a member of nsISFVList + * or a member-value of nsISFVDictionary. + */ +[scriptable, builtinclass, uuid(b2e52be2-8488-41b2-9ee2-3c48d92d095c)] +interface nsISFVInnerList : nsISFVItemOrInnerList { + attribute Array items; +}; + +/** + * nsISFVList represents List structured header value. + */ +[scriptable, builtinclass, uuid(02bb92a6-d1de-449c-b54f-d137f30c613d)] +interface nsISFVList : nsISFVSerialize { + /** + * Returns array of members. + * QueryInterface can be used on a member to get more specific type. + */ + attribute Array members; + + /** + * In case when header value is split across lines, it's possible + * this method parses supplied line and merges it with members of existing object. + */ + void parseMore(in ACString header); +}; + +/** + * nsISFVDictionary represents nsISFVDictionary structured header value. + */ +[scriptable, builtinclass, uuid(6642a7fe-7026-4eba-b730-05e230ee3437)] +interface nsISFVDictionary : nsISFVSerialize { + + /** + * Return value (nsISFVItemOrInnerList) stored for key, if it is present. + * QueryInterface can be used on a value to get more specific type. + * + * @throws NS_ERROR_UNEXPECTED if the key does not exist in parameters. + */ + nsISFVItemOrInnerList get(in ACString key); + + /** + * Insert a new key-value pair. + * If an equivalent key already exists: the key remains and retains in its place in the order, + * its corresponding value is updated with the new value. + * + * @throws NS_ERROR_UNEXPECTED if supplied item does not implement nsISFVItemOrInnerList interface. + */ + void set(in ACString key, in nsISFVItemOrInnerList member_value); + + /** + * Remove the key-value pair equivalent to key. + * + * @throws NS_ERROR_UNEXPECTED upon attempt to delete key that does not exist in parameters. + */ + void delete(in ACString key); + + /** + * Returns array of keys. + */ + Array keys(); + + /** + * In case when header value is split across lines, it's possible + * this method parses supplied line and merges it with members of existing object. + */ + void parseMore(in ACString header); +}; + + +/** + * nsISFVService provides a set of functions for working with HTTP header value as an object. + * It exposes functions for creating object from string containing header value, + * as well as individual components for manual structured header object creation. + */ +[scriptable, builtinclass, uuid(049f4be1-2f22-4438-a8da-518552ed390c)] +interface nsISFVService: nsISupports +{ + /** + * Parses provided string into Dictionary header value (nsISFVDictionary). + * + * @throws NS_ERROR_FAILURE if parsing fails. + */ + nsISFVDictionary parseDictionary(in ACString header); + + /** + * Parses provided string into List header value (nsISFVList). + * + * @throws NS_ERROR_FAILURE if parsing fails. + */ + nsISFVList parseList(in ACString header); + + /** + * Parses provided string into Item header value (nsISFVItem). + * + * @throws NS_ERROR_FAILURE if parsing fails. + */ + nsISFVItem parseItem(in ACString header); + + /** + * The following functions create bare item of specific type. + */ + nsISFVInteger newInteger(in long long value); + nsISFVBool newBool(in bool value); + nsISFVDecimal newDecimal(in double value); + nsISFVString newString(in ACString value); + nsISFVByteSeq newByteSequence(in ACString value); + nsISFVToken newToken(in ACString value); + + /** + * Creates nsISFVParams with no parameters. In other words, it's an empty map byt default. + */ + nsISFVParams newParameters(); + + /** + * Creates nsISFVInnerList from nsISFVItem array and nsISFVParams. + */ + nsISFVInnerList newInnerList(in Array items, in nsISFVParams params); + + /** + * Creates nsISFVItem, which represents Item header value, from nsISFVBareItem and associated nsISFVParams. + */ + nsISFVItem newItem(in nsISFVBareItem value, in nsISFVParams params); + + /** + * Creates nsISFVList, which represents List header value, from array of nsISFVItemOrInnerList. + * nsISFVItemOrInnerList represens either Item (nsISFVItem) or Inner List (nsISFVInnerList). + */ + nsISFVList newList(in Array members); + + /** + * Creates nsISFVDictionary representing Dictionary header value. It is empty by default. + */ + nsISFVDictionary newDictionary(); +}; diff --git a/netwerk/base/http-sfv/src/lib.rs b/netwerk/base/http-sfv/src/lib.rs new file mode 100644 index 000000000000..e610dfacaa43 --- /dev/null +++ b/netwerk/base/http-sfv/src/lib.rs @@ -0,0 +1,862 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use nserror::{nsresult, NS_ERROR_FAILURE, NS_ERROR_NULL_POINTER, NS_ERROR_UNEXPECTED, NS_OK}; +use nsstring::{nsACString, nsCString}; +use sfv::Parser; +use sfv::SerializeValue; +use sfv::{ + BareItem, Decimal, Dictionary, FromPrimitive, InnerList, Item, List, ListEntry, Parameters, + ParseMore, +}; +use std::cell::RefCell; +use std::ops::Deref; +use thin_vec::ThinVec; +use xpcom::interfaces::{ + nsISFVBareItem, nsISFVBool, nsISFVByteSeq, nsISFVDecimal, nsISFVDictionary, nsISFVInnerList, + nsISFVInteger, nsISFVItem, nsISFVItemOrInnerList, nsISFVList, nsISFVParams, nsISFVService, + nsISFVString, nsISFVToken, +}; +use xpcom::{xpcom, xpcom_method, RefPtr, XpCom}; + +#[no_mangle] +pub unsafe extern "C" fn new_sfv_service(result: *mut *const nsISFVService) { + let service: RefPtr = SFVService::new(); + RefPtr::new(service.coerce::()).forget(&mut *result); +} + +#[derive(xpcom)] +#[xpimplements(nsISFVService)] +#[refcnt = "atomic"] +struct InitSFVService {} + +impl SFVService { + fn new() -> RefPtr { + SFVService::allocate(InitSFVService {}) + } + + xpcom_method!(parse_dictionary => ParseDictionary(header: *const nsACString) -> *const nsISFVDictionary); + fn parse_dictionary(&self, header: &nsACString) -> Result, nsresult> { + let parsed_dict = Parser::parse_dictionary(&header).map_err(|_| NS_ERROR_FAILURE)?; + let sfv_dict = SFVDictionary::new(); + sfv_dict.value.replace(parsed_dict); + Ok(RefPtr::new(sfv_dict.coerce::())) + } + + xpcom_method!(parse_list => ParseList(field_value: *const nsACString) -> *const nsISFVList); + fn parse_list(&self, header: &nsACString) -> Result, nsresult> { + let parsed_list = Parser::parse_list(&header).map_err(|_| NS_ERROR_FAILURE)?; + + let mut nsi_members = ThinVec::new(); + for item_or_inner_list in parsed_list.iter() { + nsi_members.push(interface_from_list_entry(item_or_inner_list)?) + } + let sfv_list = SFVList::allocate(InitSFVList { + members: RefCell::new(nsi_members), + }); + Ok(RefPtr::new(sfv_list.coerce::())) + } + + xpcom_method!(parse_item => ParseItem(header: *const nsACString) -> *const nsISFVItem); + fn parse_item(&self, header: &nsACString) -> Result, nsresult> { + let parsed_item = Parser::parse_item(&header).map_err(|_| NS_ERROR_FAILURE)?; + interface_from_item(&parsed_item) + } + + xpcom_method!(new_integer => NewInteger(value: i64) -> *const nsISFVInteger); + fn new_integer(&self, value: i64) -> Result, nsresult> { + Ok(RefPtr::new( + SFVInteger::new(value).coerce::(), + )) + } + + xpcom_method!(new_decimal => NewDecimal(value: f64) -> *const nsISFVDecimal); + fn new_decimal(&self, value: f64) -> Result, nsresult> { + Ok(RefPtr::new( + SFVDecimal::new(value).coerce::(), + )) + } + + xpcom_method!(new_bool => NewBool(value: bool) -> *const nsISFVBool); + fn new_bool(&self, value: bool) -> Result, nsresult> { + Ok(RefPtr::new(SFVBool::new(value).coerce::())) + } + + xpcom_method!(new_string => NewString(value: *const nsACString) -> *const nsISFVString); + fn new_string(&self, value: &nsACString) -> Result, nsresult> { + Ok(RefPtr::new(SFVString::new(value).coerce::())) + } + + xpcom_method!(new_token => NewToken(value: *const nsACString) -> *const nsISFVToken); + fn new_token(&self, value: &nsACString) -> Result, nsresult> { + Ok(RefPtr::new(SFVToken::new(value).coerce::())) + } + + xpcom_method!(new_byte_sequence => NewByteSequence(value: *const nsACString) -> *const nsISFVByteSeq); + fn new_byte_sequence(&self, value: &nsACString) -> Result, nsresult> { + Ok(RefPtr::new( + SFVByteSeq::new(value).coerce::(), + )) + } + + xpcom_method!(new_parameters => NewParameters() -> *const nsISFVParams); + fn new_parameters(&self) -> Result, nsresult> { + Ok(RefPtr::new(SFVParams::new().coerce::())) + } + + xpcom_method!(new_item => NewItem(value: *const nsISFVBareItem, params: *const nsISFVParams) -> *const nsISFVItem); + fn new_item( + &self, + value: &nsISFVBareItem, + params: &nsISFVParams, + ) -> Result, nsresult> { + Ok(RefPtr::new( + SFVItem::new(value, params).coerce::(), + )) + } + + xpcom_method!(new_inner_list => NewInnerList(items: *const thin_vec::ThinVec>, params: *const nsISFVParams) -> *const nsISFVInnerList); + fn new_inner_list( + &self, + items: &thin_vec::ThinVec>, + params: &nsISFVParams, + ) -> Result, nsresult> { + Ok(RefPtr::new( + SFVInnerList::new(items, params).coerce::(), + )) + } + + xpcom_method!(new_list => NewList(members: *const thin_vec::ThinVec>) -> *const nsISFVList); + fn new_list( + &self, + members: &thin_vec::ThinVec>, + ) -> Result, nsresult> { + Ok(RefPtr::new(SFVList::new(members).coerce::())) + } + + xpcom_method!(new_dictionary => NewDictionary() -> *const nsISFVDictionary); + fn new_dictionary(&self) -> Result, nsresult> { + Ok(RefPtr::new( + SFVDictionary::new().coerce::(), + )) + } +} + +#[derive(xpcom)] +#[xpimplements(nsISFVInteger, nsISFVBareItem)] +#[refcnt = "nonatomic"] +struct InitSFVInteger { + value: RefCell, +} + +impl SFVInteger { + fn new(value: i64) -> RefPtr { + SFVInteger::allocate(InitSFVInteger { + value: RefCell::new(value), + }) + } + + xpcom_method!(get_value => GetValue() -> i64); + fn get_value(&self) -> Result { + Ok(*self.value.borrow()) + } + + xpcom_method!(set_value => SetValue(value: i64)); + fn set_value(&self, value: i64) -> Result<(), nsresult> { + self.value.replace(value); + Ok(()) + } + + xpcom_method!(get_type => GetType() -> i64); + fn get_type(&self) -> Result { + Ok(nsISFVBareItem::INTEGER) + } + + fn from_bare_item_interface(obj: &nsISFVBareItem) -> &Self { + unsafe { ::std::mem::transmute(obj) } + } +} + +#[derive(xpcom)] +#[xpimplements(nsISFVBool, nsISFVBareItem)] +#[refcnt = "nonatomic"] +struct InitSFVBool { + value: RefCell, +} + +impl SFVBool { + fn new(value: bool) -> RefPtr { + SFVBool::allocate(InitSFVBool { + value: RefCell::new(value), + }) + } + + xpcom_method!(get_value => GetValue() -> bool); + fn get_value(&self) -> Result { + Ok(*self.value.borrow()) + } + + xpcom_method!(set_value => SetValue(value: bool)); + fn set_value(&self, value: bool) -> Result<(), nsresult> { + self.value.replace(value); + Ok(()) + } + + xpcom_method!(get_type => GetType() -> i64); + fn get_type(&self) -> Result { + Ok(nsISFVBareItem::BOOL) + } + + fn from_bare_item_interface(obj: &nsISFVBareItem) -> &Self { + unsafe { ::std::mem::transmute(obj) } + } +} + +#[derive(xpcom)] +#[xpimplements(nsISFVString, nsISFVBareItem)] +#[refcnt = "nonatomic"] +struct InitSFVString { + value: RefCell, +} + +impl SFVString { + fn new(value: &nsACString) -> RefPtr { + SFVString::allocate(InitSFVString { + value: RefCell::new(nsCString::from(value)), + }) + } + + xpcom_method!( + get_value => GetValue( + ) -> nsACString + ); + + fn get_value(&self) -> Result { + Ok(self.value.borrow().clone()) + } + + xpcom_method!( + set_value => SetValue(value: *const nsACString) + ); + + fn set_value(&self, value: &nsACString) -> Result<(), nsresult> { + self.value.borrow_mut().assign(value); + Ok(()) + } + + xpcom_method!(get_type => GetType() -> i64); + fn get_type(&self) -> Result { + Ok(nsISFVBareItem::STRING) + } + + fn from_bare_item_interface(obj: &nsISFVBareItem) -> &Self { + unsafe { ::std::mem::transmute(obj) } + } +} + +#[derive(xpcom)] +#[xpimplements(nsISFVToken, nsISFVBareItem)] +#[refcnt = "nonatomic"] +struct InitSFVToken { + value: RefCell, +} + +impl SFVToken { + fn new(value: &nsACString) -> RefPtr { + SFVToken::allocate(InitSFVToken { + value: RefCell::new(nsCString::from(value)), + }) + } + + xpcom_method!( + get_value => GetValue( + ) -> nsACString + ); + + fn get_value(&self) -> Result { + Ok(self.value.borrow().clone()) + } + + xpcom_method!( + set_value => SetValue(value: *const nsACString) + ); + + fn set_value(&self, value: &nsACString) -> Result<(), nsresult> { + self.value.borrow_mut().assign(value); + Ok(()) + } + + xpcom_method!(get_type => GetType() -> i64); + fn get_type(&self) -> Result { + Ok(nsISFVBareItem::TOKEN) + } + + fn from_bare_item_interface(obj: &nsISFVBareItem) -> &Self { + unsafe { ::std::mem::transmute(obj) } + } +} + +#[derive(xpcom)] +#[xpimplements(nsISFVByteSeq, nsISFVBareItem)] +#[refcnt = "nonatomic"] +struct InitSFVByteSeq { + value: RefCell, +} + +impl SFVByteSeq { + fn new(value: &nsACString) -> RefPtr { + SFVByteSeq::allocate(InitSFVByteSeq { + value: RefCell::new(nsCString::from(value)), + }) + } + + xpcom_method!( + get_value => GetValue( + ) -> nsACString + ); + + fn get_value(&self) -> Result { + Ok(self.value.borrow().clone()) + } + + xpcom_method!( + set_value => SetValue(value: *const nsACString) + ); + + fn set_value(&self, value: &nsACString) -> Result<(), nsresult> { + self.value.borrow_mut().assign(value); + Ok(()) + } + + xpcom_method!(get_type => GetType() -> i64); + fn get_type(&self) -> Result { + Ok(nsISFVBareItem::BYTE_SEQUENCE) + } + + fn from_bare_item_interface(obj: &nsISFVBareItem) -> &Self { + unsafe { ::std::mem::transmute(obj) } + } +} + +#[derive(xpcom)] +#[xpimplements(nsISFVDecimal, nsISFVBareItem)] +#[refcnt = "nonatomic"] +struct InitSFVDecimal { + value: RefCell, +} + +impl SFVDecimal { + fn new(value: f64) -> RefPtr { + SFVDecimal::allocate(InitSFVDecimal { + value: RefCell::new(value), + }) + } + + xpcom_method!( + get_value => GetValue( + ) -> f64 + ); + + fn get_value(&self) -> Result { + Ok(*self.value.borrow()) + } + + xpcom_method!( + set_value => SetValue(value: f64) + ); + + fn set_value(&self, value: f64) -> Result<(), nsresult> { + self.value.replace(value); + Ok(()) + } + + xpcom_method!(get_type => GetType() -> i64); + fn get_type(&self) -> Result { + Ok(nsISFVBareItem::DECIMAL) + } + + fn from_bare_item_interface(obj: &nsISFVBareItem) -> &Self { + unsafe { ::std::mem::transmute(obj) } + } +} + +#[derive(xpcom)] +#[xpimplements(nsISFVParams)] +#[refcnt = "nonatomic"] +struct InitSFVParams { + params: RefCell, +} + +impl SFVParams { + fn new() -> RefPtr { + SFVParams::allocate(InitSFVParams { + params: RefCell::new(Parameters::new()), + }) + } + + xpcom_method!( + get => Get(key: *const nsACString) -> *const nsISFVBareItem + ); + + fn get(&self, key: &nsACString) -> Result, nsresult> { + let key = key.to_utf8(); + let params = self.params.borrow(); + let param_val = params.get(key.as_ref()); + + match param_val { + Some(val) => interface_from_bare_item(val), + None => return Err(NS_ERROR_UNEXPECTED), + } + } + + xpcom_method!( + set => Set(key: *const nsACString, item: *const nsISFVBareItem) + ); + + fn set(&self, key: &nsACString, item: &nsISFVBareItem) -> Result<(), nsresult> { + let key = key.to_utf8().into_owned(); + let bare_item = bare_item_from_interface(item)?; + self.params.borrow_mut().insert(key, bare_item); + Ok(()) + } + + xpcom_method!( + delete => Delete(key: *const nsACString) + ); + fn delete(&self, key: &nsACString) -> Result<(), nsresult> { + let key = key.to_utf8(); + self.params + .borrow_mut() + .shift_remove(key.as_ref()) + .ok_or(NS_ERROR_UNEXPECTED)?; + Ok(()) + } + + xpcom_method!( + keys => Keys() -> thin_vec::ThinVec + ); + fn keys(&self) -> Result, nsresult> { + let keys = self.params.borrow(); + let keys = keys + .keys() + .map(nsCString::from) + .collect::>(); + Ok(keys) + } + + fn from_interface(obj: &nsISFVParams) -> &Self { + unsafe { ::std::mem::transmute(obj) } + } +} + +#[derive(xpcom)] +#[xpimplements(nsISFVItem, nsISFVItemOrInnerList)] +#[refcnt = "nonatomic"] +struct InitSFVItem { + value: RefPtr, + params: RefPtr, +} + +impl SFVItem { + fn new(value: &nsISFVBareItem, params: &nsISFVParams) -> RefPtr { + SFVItem::allocate(InitSFVItem { + value: RefPtr::new(value), + params: RefPtr::new(params), + }) + } + + xpcom_method!( + get_value => GetValue( + ) -> *const nsISFVBareItem + ); + + fn get_value(&self) -> Result, nsresult> { + Ok(self.value.clone()) + } + + xpcom_method!( + get_params => GetParams( + ) -> *const nsISFVParams + ); + fn get_params(&self) -> Result, nsresult> { + Ok(self.params.clone()) + } + + xpcom_method!( + serialize => Serialize() -> nsACString + ); + fn serialize(&self) -> Result { + let bare_item = bare_item_from_interface(self.value.deref())?; + let params = params_from_interface(self.params.deref())?; + let serialized = Item::with_params(bare_item, params) + .serialize_value() + .map_err(|_| NS_ERROR_FAILURE)?; + Ok(nsCString::from(serialized)) + } + + fn from_interface(obj: &nsISFVItem) -> &Self { + unsafe { ::std::mem::transmute(obj) } + } +} + +#[derive(xpcom)] +#[xpimplements(nsISFVInnerList, nsISFVItemOrInnerList)] +#[refcnt = "nonatomic"] +struct InitSFVInnerList { + items: RefCell>>, + params: RefPtr, +} + +impl SFVInnerList { + fn new( + items: &thin_vec::ThinVec>, + params: &nsISFVParams, + ) -> RefPtr { + SFVInnerList::allocate(InitSFVInnerList { + items: RefCell::new((*items).clone()), + params: RefPtr::new(params), + }) + } + + xpcom_method!( + get_items => GetItems() -> thin_vec::ThinVec> + ); + + fn get_items(&self) -> Result>, nsresult> { + let items = self.items.borrow().clone(); + Ok(items) + } + + #[allow(non_snake_case)] + unsafe fn SetItems(&self, value: *const thin_vec::ThinVec>) -> nsresult { + if value.is_null() { + return NS_ERROR_NULL_POINTER; + } + *self.items.borrow_mut() = (*value).clone(); + NS_OK + } + + xpcom_method!( + get_params => GetParams( + ) -> *const nsISFVParams + ); + fn get_params(&self) -> Result, nsresult> { + Ok(self.params.clone()) + } + + fn from_interface(obj: &nsISFVInnerList) -> &Self { + unsafe { ::std::mem::transmute(obj) } + } +} + +#[derive(xpcom)] +#[xpimplements(nsISFVList, nsISFVSerialize)] +#[refcnt = "nonatomic"] +struct InitSFVList { + members: RefCell>>, +} + +impl SFVList { + fn new(members: &thin_vec::ThinVec>) -> RefPtr { + SFVList::allocate(InitSFVList { + members: RefCell::new((*members).clone()), + }) + } + + xpcom_method!( + get_members => GetMembers() -> thin_vec::ThinVec> + ); + + fn get_members(&self) -> Result>, nsresult> { + Ok(self.members.borrow().clone()) + } + + #[allow(non_snake_case)] + unsafe fn SetMembers( + &self, + value: *const thin_vec::ThinVec>, + ) -> nsresult { + if value.is_null() { + return NS_ERROR_NULL_POINTER; + } + *self.members.borrow_mut() = (*value).clone(); + NS_OK + } + + xpcom_method!( + parse_more => ParseMore(header: *const nsACString) + ); + fn parse_more(&self, header: &nsACString) -> Result<(), nsresult> { + // create List from SFVList to call parse_more on it + let mut list = List::new(); + for interface_entry in self.get_members()?.iter() { + let item_or_inner_list = list_entry_from_interface(interface_entry)?; + list.push(item_or_inner_list); + } + + let _ = list.parse_more(&header).map_err(|_| NS_ERROR_FAILURE)?; + + // replace SFVList's members with new_members + let mut new_members = ThinVec::new(); + for item_or_inner_list in list.iter() { + new_members.push(interface_from_list_entry(item_or_inner_list)?) + } + self.members.replace(new_members); + Ok(()) + } + + xpcom_method!( + serialize => Serialize() -> nsACString + ); + fn serialize(&self) -> Result { + let mut list = List::new(); + + for interface_entry in self.get_members()?.iter() { + let item_or_inner_list = list_entry_from_interface(interface_entry)?; + list.push(item_or_inner_list); + } + + let serialized = list.serialize_value().map_err(|_| NS_ERROR_FAILURE)?; + Ok(nsCString::from(serialized)) + } +} + +#[derive(xpcom)] +#[xpimplements(nsISFVDictionary, nsISFVSerialize)] +#[refcnt = "nonatomic"] +struct InitSFVDictionary { + value: RefCell, +} + +impl SFVDictionary { + fn new() -> RefPtr { + SFVDictionary::allocate(InitSFVDictionary { + value: RefCell::new(Dictionary::new()), + }) + } + + xpcom_method!( + get => Get(key: *const nsACString) -> *const nsISFVItemOrInnerList + ); + + fn get(&self, key: &nsACString) -> Result, nsresult> { + let key = key.to_utf8(); + let value = self.value.borrow(); + let member_value = value.get(key.as_ref()); + + match member_value { + Some(member) => interface_from_list_entry(member), + None => return Err(NS_ERROR_UNEXPECTED), + } + } + + xpcom_method!( + set => Set(key: *const nsACString, item: *const nsISFVItemOrInnerList) + ); + + fn set(&self, key: &nsACString, member_value: &nsISFVItemOrInnerList) -> Result<(), nsresult> { + let key = key.to_utf8().into_owned(); + let value = list_entry_from_interface(member_value)?; + self.value.borrow_mut().insert(key, value); + Ok(()) + } + + xpcom_method!( + delete => Delete(key: *const nsACString) + ); + + fn delete(&self, key: &nsACString) -> Result<(), nsresult> { + let key = key.to_utf8(); + self.value + .borrow_mut() + .shift_remove(key.as_ref()) + .ok_or(NS_ERROR_UNEXPECTED)?; + Ok(()) + } + + xpcom_method!( + keys => Keys() -> thin_vec::ThinVec + ); + fn keys(&self) -> Result, nsresult> { + let members = self.value.borrow(); + let keys = members + .keys() + .map(nsCString::from) + .collect::>(); + Ok(keys) + } + + xpcom_method!( + parse_more => ParseMore(header: *const nsACString) + ); + fn parse_more(&self, header: &nsACString) -> Result<(), nsresult> { + let _ = self + .value + .borrow_mut() + .parse_more(&header) + .map_err(|_| NS_ERROR_FAILURE)?; + Ok(()) + } + + xpcom_method!( + serialize => Serialize() -> nsACString + ); + fn serialize(&self) -> Result { + let serialized = self + .value + .borrow() + .serialize_value() + .map_err(|_| NS_ERROR_FAILURE)?; + Ok(nsCString::from(serialized)) + } +} + +fn bare_item_from_interface(obj: &nsISFVBareItem) -> Result { + let obj = obj + .query_interface::() + .ok_or(NS_ERROR_UNEXPECTED)?; + let mut obj_type: i64 = -1; + unsafe { + obj.deref().GetType(&mut obj_type); + } + + match obj_type { + nsISFVBareItem::BOOL => { + let item_value = SFVBool::from_bare_item_interface(obj.deref()).get_value()?; + Ok(BareItem::Boolean(item_value)) + } + nsISFVBareItem::STRING => { + let string_itm = SFVString::from_bare_item_interface(obj.deref()).get_value()?; + let item_value = (*string_itm.to_utf8()).to_string(); + Ok(BareItem::String(item_value)) + } + nsISFVBareItem::TOKEN => { + let token_itm = SFVToken::from_bare_item_interface(obj.deref()).get_value()?; + let item_value = (*token_itm.to_utf8()).to_string(); + Ok(BareItem::Token(item_value)) + } + nsISFVBareItem::INTEGER => { + let item_value = SFVInteger::from_bare_item_interface(obj.deref()).get_value()?; + Ok(BareItem::Integer(item_value)) + } + nsISFVBareItem::DECIMAL => { + let item_value = SFVDecimal::from_bare_item_interface(obj.deref()).get_value()?; + let decimal: Decimal = Decimal::from_f64(item_value).ok_or(NS_ERROR_UNEXPECTED)?; + Ok(BareItem::Decimal(decimal)) + } + nsISFVBareItem::BYTE_SEQUENCE => { + let token_itm = SFVByteSeq::from_bare_item_interface(obj.deref()).get_value()?; + let item_value: String = (*token_itm.to_utf8()).to_string(); + Ok(BareItem::ByteSeq(item_value.into_bytes())) + } + _ => return Err(NS_ERROR_UNEXPECTED), + } +} + +fn params_from_interface(obj: &nsISFVParams) -> Result { + let params = SFVParams::from_interface(obj).params.borrow(); + Ok(params.clone()) +} + +fn item_from_interface(obj: &nsISFVItem) -> Result { + let sfv_item = SFVItem::from_interface(obj); + let bare_item = bare_item_from_interface(sfv_item.value.deref())?; + let parameters = params_from_interface(sfv_item.params.deref())?; + Ok(Item::with_params(bare_item, parameters)) +} + +fn inner_list_from_interface(obj: &nsISFVInnerList) -> Result { + let sfv_inner_list = SFVInnerList::from_interface(obj); + + let mut inner_list_items: Vec = vec![]; + for item in sfv_inner_list.items.borrow().iter() { + let item = item_from_interface(item)?; + inner_list_items.push(item); + } + let inner_list_params = params_from_interface(sfv_inner_list.params.deref())?; + Ok(InnerList::with_params(inner_list_items, inner_list_params)) +} + +fn list_entry_from_interface(obj: &nsISFVItemOrInnerList) -> Result { + if let Some(nsi_item) = obj.query_interface::() { + let item = item_from_interface(nsi_item.deref())?; + Ok(ListEntry::Item(item)) + } else if let Some(nsi_inner_list) = obj.query_interface::() { + let inner_list = inner_list_from_interface(nsi_inner_list.deref())?; + Ok(ListEntry::InnerList(inner_list)) + } else { + return Err(NS_ERROR_UNEXPECTED); + } +} + +fn interface_from_bare_item(bare_item: &BareItem) -> Result, nsresult> { + let bare_item = match bare_item { + BareItem::Boolean(val) => RefPtr::new(SFVBool::new(*val).coerce::()), + BareItem::String(val) => { + RefPtr::new(SFVString::new(&nsCString::from(val)).coerce::()) + } + BareItem::Token(val) => { + RefPtr::new(SFVToken::new(&nsCString::from(val)).coerce::()) + } + BareItem::ByteSeq(val) => RefPtr::new( + SFVByteSeq::new(&nsCString::from(String::from_utf8(val.to_vec()).unwrap())) + .coerce::(), + ), + BareItem::Decimal(val) => { + let val = val + .to_string() + .parse::() + .map_err(|_| NS_ERROR_UNEXPECTED)?; + RefPtr::new(SFVDecimal::new(val).coerce::()) + } + BareItem::Integer(val) => RefPtr::new(SFVInteger::new(*val).coerce::()), + }; + + Ok(bare_item) +} + +fn interface_from_item(item: &Item) -> Result, nsresult> { + let nsi_bare_item = interface_from_bare_item(&item.bare_item)?; + let nsi_params = interface_from_params(&item.params)?; + Ok(RefPtr::new( + SFVItem::new(&nsi_bare_item, &nsi_params).coerce::(), + )) +} + +fn interface_from_params(params: &Parameters) -> Result, nsresult> { + let sfv_params = SFVParams::new(); + for (key, value) in params.iter() { + sfv_params + .params + .borrow_mut() + .insert(key.clone(), value.clone()); + } + Ok(RefPtr::new(sfv_params.coerce::())) +} + +fn interface_from_list_entry( + member: &ListEntry, +) -> Result, nsresult> { + match member { + ListEntry::Item(item) => { + let nsi_bare_item = interface_from_bare_item(&item.bare_item)?; + let nsi_params = interface_from_params(&item.params)?; + Ok(RefPtr::new( + SFVItem::new(&nsi_bare_item, &nsi_params).coerce::(), + )) + } + ListEntry::InnerList(inner_list) => { + let mut nsi_inner_list = ThinVec::new(); + for item in inner_list.items.iter() { + let nsi_item = interface_from_item(item)?; + nsi_inner_list.push(nsi_item); + } + + let nsi_params = interface_from_params(&inner_list.params)?; + Ok(RefPtr::new( + SFVInnerList::new(&nsi_inner_list, &nsi_params).coerce::(), + )) + } + } +} diff --git a/netwerk/base/moz.build b/netwerk/base/moz.build index 2b4c95083886..93017452bec0 100644 --- a/netwerk/base/moz.build +++ b/netwerk/base/moz.build @@ -312,7 +312,7 @@ EXTRA_JS_MODULES += [ 'NetUtil.jsm', ] -DIRS += [ 'mozurl', 'rust-helper' ] +DIRS += [ 'mozurl', 'rust-helper', 'http-sfv' ] include('/ipc/chromium/chromium-config.mozbuild') diff --git a/netwerk/build/components.conf b/netwerk/build/components.conf index b9111402e4bc..34e1e9e4ae01 100644 --- a/netwerk/build/components.conf +++ b/netwerk/build/components.conf @@ -596,6 +596,13 @@ Classes = [ 'constructor': 'mozilla::net::CookieService::GetXPCOMSingleton', 'headers': ['/netwerk/cookie/CookieService.h'], }, + { + 'cid': '{e1676f84-e6e5-45d0-a4bf-d9905efc5b2e}', + 'contract_ids': ['@mozilla.org/http-sfv-service;1'], + 'singleton': True, + 'constructor': 'mozilla::net::GetSFVService', + 'headers': ['mozilla/net/SFVService.h'], + }, ] if defined('NECKO_WIFI'): diff --git a/netwerk/test/unit/test_http_sfv.js b/netwerk/test/unit/test_http_sfv.js new file mode 100644 index 000000000000..4dc450a67343 --- /dev/null +++ b/netwerk/test/unit/test_http_sfv.js @@ -0,0 +1,590 @@ +"use strict"; + +var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); +var { AppConstants } = ChromeUtils.import( + "resource://gre/modules/AppConstants.jsm" +); +const gService = Cc["@mozilla.org/http-sfv-service;1"].getService( + Ci.nsISFVService +); + +add_task(async function test_sfv_bare_item() { + // tests bare item + let item_int = gService.newInteger(19); + Assert.equal(item_int.value, 19, "check bare item value"); + + let item_bool = gService.newBool(true); + Assert.equal(item_bool.value, true, "check bare item value"); + item_bool.value = false; + Assert.equal(item_bool.value, false, "check bare item value"); + + let item_float = gService.newDecimal(145.45); + Assert.equal(item_float.value, 145.45); + + let item_str = gService.newString("some_string"); + Assert.equal(item_str.value, "some_string", "check bare item value"); + + let item_byte_seq = gService.newByteSequence("aGVsbG8="); + Assert.equal(item_byte_seq.value, "aGVsbG8=", "check bare item value"); + + let item_token = gService.newToken("*a"); + Assert.equal(item_token.value, "*a", "check bare item value"); +}); + +add_task(async function test_sfv_params() { + // test params + let params = gService.newParameters(); + let bool_param = gService.newBool(false); + let int_param = gService.newInteger(15); + let decimal_param = gService.newDecimal(15.45); + + params.set("bool_param", bool_param); + params.set("int_param", int_param); + params.set("decimal_param", decimal_param); + + Assert.throws( + () => { + params.get("some_param"); + }, + /NS_ERROR_UNEXPECTED/, + "must throw exception as key does not exist in parameters" + ); + Assert.equal( + params.get("bool_param").QueryInterface(Ci.nsISFVBool).value, + false, + "get parameter by key and check its value" + ); + Assert.equal( + params.get("int_param").QueryInterface(Ci.nsISFVInteger).value, + 15, + "get parameter by key and check its value" + ); + Assert.equal( + params.get("decimal_param").QueryInterface(Ci.nsISFVDecimal).value, + 15.45, + "get parameter by key and check its value" + ); + Assert.deepEqual( + params.keys(), + ["bool_param", "int_param", "decimal_param"], + "check that parameters contain all the expected keys" + ); + + params.delete("int_param"); + Assert.deepEqual( + params.keys(), + ["bool_param", "decimal_param"], + "check that parameter has been deleted" + ); + + Assert.throws( + () => { + params.delete("some_param"); + }, + /NS_ERROR_UNEXPECTED/, + "must throw exception upon attempt to delete by non-existing key" + ); +}); + +add_task(async function test_sfv_inner_list() { + // create primitives for inner list + let item1_params = gService.newParameters(); + item1_params.set("param_1", gService.newToken("*smth")); + let item1 = gService.newItem(gService.newDecimal(172.145865), item1_params); + + let item2_params = gService.newParameters(); + item2_params.set("param_1", gService.newBool(true)); + item2_params.set("param_2", gService.newInteger(145454)); + let item2 = gService.newItem( + gService.newByteSequence("weather"), + item2_params + ); + + // create inner list + let inner_list_params = gService.newParameters(); + inner_list_params.set("inner_param", gService.newByteSequence("tests")); + let inner_list = gService.newInnerList([item1, item2], inner_list_params); + + // check inner list members & params + let inner_list_members = inner_list.QueryInterface(Ci.nsISFVInnerList).items; + let inner_list_parameters = inner_list + .QueryInterface(Ci.nsISFVInnerList) + .params.QueryInterface(Ci.nsISFVParams); + Assert.equal(inner_list_members.length, 2, "check inner list length"); + + let inner_item1 = inner_list_members[0].QueryInterface(Ci.nsISFVItem); + Assert.equal( + inner_item1.value.QueryInterface(Ci.nsISFVDecimal).value, + 172.145865, + "check inner list member value" + ); + + let inner_item2 = inner_list_members[1].QueryInterface(Ci.nsISFVItem); + Assert.equal( + inner_item2.value.QueryInterface(Ci.nsISFVByteSeq).value, + "weather", + "check inner list member value" + ); + + Assert.equal( + inner_list_parameters.get("inner_param").QueryInterface(Ci.nsISFVByteSeq) + .value, + "tests", + "get inner list parameter by key and check its value" + ); +}); + +add_task(async function test_sfv_item() { + // create parameters for item + let params = gService.newParameters(); + let param1 = gService.newBool(false); + let param2 = gService.newString("str_value"); + let param3 = gService.newBool(true); + params.set("param_1", param1); + params.set("param_2", param2); + params.set("param_3", param3); + + // create item field + let item = gService.newItem(gService.newToken("*abc"), params); + + Assert.equal( + item.value.QueryInterface(Ci.nsISFVToken).value, + "*abc", + "check items's value" + ); + Assert.equal( + item.params.get("param_1").QueryInterface(Ci.nsISFVBool).value, + false, + "get item parameter by key and check its value" + ); + Assert.equal( + item.params.get("param_2").QueryInterface(Ci.nsISFVString).value, + "str_value", + "get item parameter by key and check its value" + ); + Assert.equal( + item.params.get("param_3").QueryInterface(Ci.nsISFVBool).value, + true, + "get item parameter by key and check its value" + ); + + // check item field serialization + let serialized = item.serialize(); + Assert.equal( + serialized, + `*abc;param_1=?0;param_2="str_value";param_3`, + "serialized output must match expected one" + ); +}); + +add_task(async function test_sfv_list() { + // create primitives for List + let item1_params = gService.newParameters(); + item1_params.set("param_1", gService.newToken("*smth")); + let item1 = gService.newItem(gService.newDecimal(145454.14568), item1_params); + + let item2_params = gService.newParameters(); + item2_params.set("param_1", gService.newBool(true)); + let item2 = gService.newItem( + gService.newByteSequence("weather"), + item2_params + ); + + let inner_list = gService.newInnerList( + [item1, item2], + gService.newParameters() + ); + + // create list field + let list = gService.newList([item1, inner_list]); + + // check list's members + let list_members = list.members; + Assert.equal(list_members.length, 2, "check list length"); + + // check list's member of item type + let member1 = list_members[0].QueryInterface(Ci.nsISFVItem); + Assert.equal( + member1.value.QueryInterface(Ci.nsISFVDecimal).value, + 145454.14568, + "check list member's value" + ); + let member1_parameters = member1.params; + Assert.equal( + member1_parameters.get("param_1").QueryInterface(Ci.nsISFVToken).value, + "*smth", + "get list member's parameter by key and check its value" + ); + + // check list's member of inner list type + let inner_list_members = list_members[1].QueryInterface(Ci.nsISFVInnerList) + .items; + Assert.equal(inner_list_members.length, 2, "check inner list length"); + + let inner_item1 = inner_list_members[0].QueryInterface(Ci.nsISFVItem); + Assert.equal( + inner_item1.value.QueryInterface(Ci.nsISFVDecimal).value, + 145454.14568, + "check inner list member's value" + ); + + let inner_item2 = inner_list_members[1].QueryInterface(Ci.nsISFVItem); + Assert.equal( + inner_item2.value.QueryInterface(Ci.nsISFVByteSeq).value, + "weather", + "check inner list member's value" + ); + + // check inner list member's params + let inner_list_parameters = list_members[1] + .QueryInterface(Ci.nsISFVInnerList) + .params.QueryInterface(Ci.nsISFVParams); + + // check serialization of list field + let expected_serialized = + "145454.146;param_1=*smth, (145454.146;param_1=*smth :d2VhdGhlcg==:;param_1)"; + let actual_serialized = list.serialize(); + Assert.equal( + actual_serialized, + expected_serialized, + "serialized output must match expected one" + ); +}); + +add_task(async function test_sfv_dictionary() { + // create primitives for dictionary field + + // dict member1 + let params1 = gService.newParameters(); + params1.set("mp_1", gService.newBool(true)); + params1.set("mp_2", gService.newDecimal(68.758602)); + let member1 = gService.newItem(gService.newString("member_1"), params1); + + // dict member2 + let params2 = gService.newParameters(); + let inner_item1 = gService.newItem( + gService.newString("inner_item_1"), + gService.newParameters() + ); + let inner_item2 = gService.newItem( + gService.newToken("tok"), + gService.newParameters() + ); + let member2 = gService.newInnerList([inner_item1, inner_item2], params2); + + // dict member3 + let params_3 = gService.newParameters(); + params_3.set("mp_1", gService.newInteger(6586)); + let member3 = gService.newItem(gService.newString("member_3"), params_3); + + // create dictionary field + let dict = gService.newDictionary(); + dict.set("key_1", member1); + dict.set("key_2", member2); + dict.set("key_3", member3); + + // check dictionary keys + let expected = ["key_1", "key_2", "key_3"]; + Assert.deepEqual( + expected, + dict.keys(), + "check dictionary contains all the expected keys" + ); + + // check dictionary members + Assert.throws( + () => { + dict.get("key_4"); + }, + /NS_ERROR_UNEXPECTED/, + "must throw exception as key does not exist in dictionary" + ); + + let dict_member1 = dict.get("key_1").QueryInterface(Ci.nsISFVItem); + let dict_member2 = dict.get("key_2").QueryInterface(Ci.nsISFVInnerList); + let dict_member3 = dict.get("key_3").QueryInterface(Ci.nsISFVItem); + + // Assert.equal( + // dict_member1.value.QueryInterface(Ci.nsISFVString).value, + // "member_1", + // "check dictionary member's value" + // ); + // Assert.equal( + // dict_member1.params.get("mp_1").QueryInterface(Ci.nsISFVBool).value, + // true, + // "get dictionary member's parameter by key and check its value" + // ); + // Assert.equal( + // dict_member1.params.get("mp_2").QueryInterface(Ci.nsISFVDecimal).value, + // "68.758602", + // "get dictionary member's parameter by key and check its value" + // ); + + let dict_member2_items = dict_member2.QueryInterface(Ci.nsISFVInnerList) + .items; + let dict_member2_params = dict_member2 + .QueryInterface(Ci.nsISFVInnerList) + .params.QueryInterface(Ci.nsISFVParams); + Assert.equal( + dict_member2_items[0] + .QueryInterface(Ci.nsISFVItem) + .value.QueryInterface(Ci.nsISFVString).value, + "inner_item_1", + "get dictionary member of inner list type, and check inner list member's value" + ); + Assert.equal( + dict_member2_items[1] + .QueryInterface(Ci.nsISFVItem) + .value.QueryInterface(Ci.nsISFVToken).value, + "tok", + "get dictionary member of inner list type, and check inner list member's value" + ); + Assert.throws( + () => { + dict_member2_params.get("some_param"); + }, + /NS_ERROR_UNEXPECTED/, + "must throw exception as dict member's parameters are empty" + ); + + Assert.equal( + dict_member3.value.QueryInterface(Ci.nsISFVString).value, + "member_3", + "check dictionary member's value" + ); + Assert.equal( + dict_member3.params.get("mp_1").QueryInterface(Ci.nsISFVInteger).value, + 6586, + "get dictionary member's parameter by key and check its value" + ); + + // check serialization of Dictionary field + let expected_serialized = `key_1="member_1";mp_1;mp_2=68.759, key_2=("inner_item_1" tok), key_3="member_3";mp_1=6586`; + let actual_serialized = dict.serialize(); + Assert.equal( + actual_serialized, + expected_serialized, + "serialized output must match expected one" + ); + + // check deleting dict member + dict.delete("key_2"); + Assert.deepEqual( + dict.keys(), + ["key_1", "key_3"], + "check that dictionary member has been deleted" + ); + + Assert.throws( + () => { + dict.delete("some_key"); + }, + /NS_ERROR_UNEXPECTED/, + "must throw exception upon attempt to delete by non-existing key" + ); +}); + +add_task(async function test_sfv_item_parsing() { + Assert.ok(gService.parseItem(`"str"`), "input must be parsed into Item"); + Assert.ok(gService.parseItem("12.35;a "), "input must be parsed into Item"); + Assert.ok(gService.parseItem("12.35; a "), "input must be parsed into Item"); + Assert.ok(gService.parseItem("12.35 "), "input must be parsed into Item"); + + Assert.throws( + () => { + gService.parseItem("12.35;\ta "); + }, + /NS_ERROR_FAILURE/, + "item parsing must fail: invalid parameters delimiter" + ); + + Assert.throws( + () => { + gService.parseItem("125666.3565648855455"); + }, + /NS_ERROR_FAILURE/, + "item parsing must fail: decimal too long" + ); +}); + +add_task(async function test_sfv_list_parsing() { + Assert.ok( + gService.parseList( + "(?1;param_1=*smth :d2VhdGhlcg==:;param_1;param_2=145454);inner_param=:d2VpcmR0ZXN0cw==:" + ), + "input must be parsed into List" + ); + Assert.ok("a, (b c)", "input must be parsed into List"); + + Assert.throws(() => { + gService.parseList("?tok", "list parsing must fail"); + }, /NS_ERROR_FAILURE/); + + Assert.throws(() => { + gService.parseList( + "a, (b, c)", + "list parsing must fail: invalid delimiter within inner list" + ); + }, /NS_ERROR_FAILURE/); + + Assert.throws( + () => { + gService.parseList("a, b c"); + }, + /NS_ERROR_FAILURE/, + "list parsing must fail: invalid delimiter" + ); +}); + +add_task(async function test_sfv_dict_parsing() { + Assert.ok( + gService.parseDictionary(`abc=123;a=1;b=2, def=456, ghi=789;q=9;r="+w"`), + "input must be parsed into Dictionary" + ); + Assert.ok( + gService.parseDictionary("a=1\t,\t\t\t c=*"), + "input must be parsed into Dictionary" + ); + Assert.ok( + gService.parseDictionary("a=1\t,\tc=* \t\t"), + "input must be parsed into Dictionary" + ); + + Assert.throws( + () => { + gService.parseDictionary("a=1\t,\tc=*,"); + }, + /NS_ERROR_FAILURE/, + "dictionary parsing must fail: trailing comma" + ); + + Assert.throws( + () => { + gService.parseDictionary("a=1 c=*"); + }, + /NS_ERROR_FAILURE/, + "dictionary parsing must fail: invalid delimiter" + ); + + Assert.throws( + () => { + gService.parseDictionary("INVALID_key=1, c=*"); + }, + /NS_ERROR_FAILURE/, + "dictionary parsing must fail: invalid key format, can't be in uppercase" + ); +}); + +add_task(async function test_sfv_list_parse_serialize() { + let list_field = gService.parseList("1 , 42, (42 43)"); + Assert.equal( + list_field.serialize(), + "1, 42, (42 43)", + "serialized output must match expected one" + ); + + // create new inner list with parameters + let inner_list_params = gService.newParameters(); + inner_list_params.set("key1", gService.newString("value1")); + inner_list_params.set("key2", gService.newBool(true)); + inner_list_params.set("key3", gService.newBool(false)); + let inner_list_items = [ + gService.newItem( + gService.newDecimal(-1865.75653), + gService.newParameters() + ), + gService.newItem(gService.newToken("token"), gService.newParameters()), + gService.newItem(gService.newString(`no"yes`), gService.newParameters()), + ]; + let new_list_member = gService.newInnerList( + inner_list_items, + inner_list_params + ); + + // set one of list members to inner list and check it's serialized as expected + let members = list_field.members; + members[1] = new_list_member; + list_field.members = members; + Assert.equal( + list_field.serialize(), + `1, (-1865.757 token "no\\"yes");key1="value1";key2;key3=?0, (42 43)`, + "update list member and check list is serialized as expected" + ); +}); + +add_task(async function test_sfv_dict_parse_serialize() { + let dict_field = gService.parseDictionary( + "a=1, b; foo=*, \tc=3, \t \tabc=123;a=1;b=2\t" + ); + Assert.equal( + dict_field.serialize(), + "a=1, b;foo=*, c=3, abc=123;a=1;b=2", + "serialized output must match expected one" + ); + + // set new value for existing dict's key + dict_field.set( + "a", + gService.newItem(gService.newInteger(165), gService.newParameters()) + ); + + // add new member to dict + dict_field.set( + "key", + gService.newItem(gService.newDecimal(45.0), gService.newParameters()) + ); + + // check dict is serialized properly after the above changes + Assert.equal( + dict_field.serialize(), + "a=165, b;foo=*, c=3, abc=123;a=1;b=2, key=45.0", + "update dictionary members and dictionary list is serialized as expected" + ); +}); + +add_task(async function test_sfv_list_parse_more() { + // check parsing of multiline header of List type + let list_field = gService.parseList("(12 abc), 12.456\t\t "); + list_field.parseMore("11, 15, tok"); + Assert.equal( + list_field.serialize(), + "(12 abc), 12.456, 11, 15, tok", + "multi-line field value parsed and serialized successfully" + ); + + // should fail parsing one more line + Assert.throws( + () => { + list_field.parseMore("(tk\t1)"); + }, + /NS_ERROR_FAILURE/, + "line parsing must fail: invalid delimiter in inner list" + ); + Assert.equal( + list_field.serialize(), + "(12 abc), 12.456, 11, 15, tok", + "parsed value must not change if parsing one more line of header fails" + ); +}); + +add_task(async function test_sfv_dict_parse_more() { + // check parsing of multiline header of Dictionary type + let dict_field = gService.parseDictionary(""); + dict_field.parseMore("key2=?0, key3=?1, key4=itm"); + dict_field.parseMore("key1, key5=11, key4=45"); + + Assert.equal( + dict_field.serialize(), + "key2=?0, key3, key4=45, key1, key5=11", + "multi-line field value parsed and serialized successfully" + ); + + // should fail parsing one more line + Assert.throws( + () => { + dict_field.parseMore("c=12, _k=13"); + }, + /NS_ERROR_FAILURE/, + "line parsing must fail: invalid key format" + ); +}); diff --git a/netwerk/test/unit/xpcshell.ini b/netwerk/test/unit/xpcshell.ini index 8bdf966bacd7..623dea86f5bd 100644 --- a/netwerk/test/unit/xpcshell.ini +++ b/netwerk/test/unit/xpcshell.ini @@ -440,5 +440,6 @@ skip-if = os == "android" skip-if = os == "android" [test_trr_proxy.js] [test_trr_cname_chain.js] +[test_http_sfv.js] skip-if = os == "android" [test_blob_channelname.js] diff --git a/toolkit/library/rust/shared/Cargo.toml b/toolkit/library/rust/shared/Cargo.toml index e78a9372f803..2588ac2d69ae 100644 --- a/toolkit/library/rust/shared/Cargo.toml +++ b/toolkit/library/rust/shared/Cargo.toml @@ -50,7 +50,7 @@ mapped_hyph = { git = "https://github.com/jfkthame/mapped_hyph.git", tag = "v0.3 remote = { path = "../../../../remote", optional = true } fog_control = { path = "../../../components/glean", optional = true } app_services_logger = { path = "../../../../services/common/app_services_logger" } - +http_sfv = { path = "../../../../netwerk/base/http-sfv" } unic-langid = { version = "0.8", features = ["likelysubtags"] } unic-langid-ffi = { path = "../../../../intl/locale/rust/unic-langid-ffi" } fluent-langneg = { version = "0.12.1", features = ["cldr"] } diff --git a/toolkit/library/rust/shared/lib.rs b/toolkit/library/rust/shared/lib.rs index 9db5fcffece5..dc05e250f7df 100644 --- a/toolkit/library/rust/shared/lib.rs +++ b/toolkit/library/rust/shared/lib.rs @@ -31,6 +31,7 @@ extern crate firefox_accounts_bridge; #[cfg(feature = "glean")] extern crate fog_control; extern crate gkrust_utils; +extern crate http_sfv; extern crate jsrust_shared; extern crate kvstore; extern crate mapped_hyph;