matrix_sdk/widget/settings/
url_params.rs

1// Copyright 2023 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use url::Url;
16use urlencoding::encode;
17
18pub const USER_ID: &str = "$matrix_user_id";
19pub const ROOM_ID: &str = "$matrix_room_id";
20pub const WIDGET_ID: &str = "$matrix_widget_id";
21pub const AVATAR_URL: &str = "$matrix_avatar_url";
22pub const DISPLAY_NAME: &str = "$matrix_display_name";
23pub const LANGUAGE: &str = "$org.matrix.msc2873.client_language";
24pub const CLIENT_THEME: &str = "$org.matrix.msc2873.client_theme";
25pub const CLIENT_ID: &str = "$org.matrix.msc2873.client_id";
26pub const DEVICE_ID: &str = "$org.matrix.msc2873.matrix_device_id";
27pub const HOMESERVER_URL: &str = "$org.matrix.msc4039.matrix_base_url";
28
29pub struct QueryProperties {
30    pub(crate) widget_id: String,
31    pub(crate) avatar_url: String,
32    pub(crate) display_name: String,
33    pub(crate) user_id: String,
34    pub(crate) room_id: String,
35    pub(crate) language: String,
36    pub(crate) client_theme: String,
37    pub(crate) client_id: String,
38    pub(crate) device_id: String,
39    pub(crate) homeserver_url: String,
40}
41
42pub fn replace_properties(url: &mut Url, props: QueryProperties) {
43    let replace_map: [(&str, String); 10] = [
44        (WIDGET_ID, encode(&props.widget_id).into()),
45        (AVATAR_URL, encode(&props.avatar_url).into()),
46        (DEVICE_ID, encode(&props.device_id).into()),
47        (DISPLAY_NAME, encode(&props.display_name).into()),
48        (HOMESERVER_URL, encode(&props.homeserver_url).into()),
49        (USER_ID, encode(&props.user_id).into()),
50        (ROOM_ID, encode(&props.room_id).into()),
51        (LANGUAGE, encode(&props.language).into()),
52        (CLIENT_THEME, encode(&props.client_theme).into()),
53        (CLIENT_ID, encode(&props.client_id).into()),
54    ]
55    .map(|to_replace| {
56        // It's safe to unwrap here since we know all replace strings start with `$`
57        (to_replace.0.get(1..).unwrap(), to_replace.1)
58    });
59
60    let s = url.as_str();
61    let Some(beginning) = s.split_once('$').map(|s| s.0) else {
62        // There is no '$' in the string so we don't need to do anything
63        return;
64    };
65    let mut result = String::from(beginning);
66    for section in s.split('$').skip(1) {
67        let mut section_added = false;
68        for (old, new) in &replace_map {
69            section.split_once(|c: char| !(c.is_ascii_alphanumeric() || c == '.' || c == '_'));
70            // It's safe to unwrap here since we know all replace strings start with `$`
71            if section.starts_with(old) {
72                result.push_str(new);
73                if let Some(rest) = section.get(old.len()..) {
74                    result.push_str(rest);
75                }
76                section_added = true;
77            }
78        }
79        if !section_added {
80            result.push_str(section);
81        }
82    }
83    *url = Url::parse(&result).unwrap();
84}
85
86#[cfg(test)]
87mod tests {
88    use url::Url;
89
90    use super::{replace_properties, QueryProperties};
91
92    const EXAMPLE_URL: &str = "\
93        https://my.widget.org/custom/path/using/$matrix_display_name/in/it\
94            ?widgetId=$matrix_widget_id\
95            &deviceId=$org.matrix.msc2873.matrix_device_id\
96            &avatarUrl=$matrix_avatar_url\
97            &displayName=$matrix_display_name\
98            &lang=$org.matrix.msc2873.client_language\
99            &theme=$org.matrix.msc2873.client_theme\
100            &clientId=$org.matrix.msc2873.client_id\
101            &baseUrl=$org.matrix.msc4039.matrix_base_url\
102            #andAHashWithA$org.matrix.msc2873.client_themeThemeAndTheClientId:$org.matrix.msc2873.client_id\
103    ";
104
105    fn get_example_props() -> QueryProperties {
106        QueryProperties {
107            widget_id: String::from("!@/abc_widget_id"),
108            avatar_url: "!@/abc_avatar_url".to_owned(),
109            display_name: "I_AM_THE_user".to_owned(),
110            user_id: "!@/abc_user_id".to_owned(),
111            room_id: "!@/abc_room_id".to_owned(),
112            language: "!@/abc_language".to_owned(),
113            client_theme: "light".to_owned(),
114            client_id: "12345678".to_owned(),
115            device_id: "!@/abc_device_id".to_owned(),
116            homeserver_url: "https://abc_base_url/".to_owned(),
117        }
118    }
119
120    fn get_example_url() -> Url {
121        Url::parse(EXAMPLE_URL).expect("EXAMPLE_URL is malformatted")
122    }
123
124    #[test]
125    fn replace_all_properties() {
126        let mut url = get_example_url();
127
128        const CONVERTED_URL: &str = "\
129            https://my.widget.org/custom/path/using/I_AM_THE_user/in/it\
130                ?widgetId=%21%40%2Fabc_widget_id\
131                &deviceId=%21%40%2Fabc_device_id\
132                &avatarUrl=%21%40%2Fabc_avatar_url\
133                &displayName=I_AM_THE_user\
134                &lang=%21%40%2Fabc_language\
135                &theme=light\
136                &clientId=12345678\
137                &baseUrl=https%3A%2F%2Fabc_base_url%2F\
138                #andAHashWithAlightThemeAndTheClientId:12345678\
139        ";
140
141        replace_properties(&mut url, get_example_props());
142        assert_eq!(url.as_str(), CONVERTED_URL);
143    }
144}