rust_rcs_core/internet/syntax/
mod.rs

1// Copyright 2023 宋昊文
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 std::borrow::Cow;
16
17/// Adding quote to a string if neccessary so that it can be correctly used in a internet message header parameter field
18///
19/// # Examples
20///
21/// ```
22/// let a = "sip:bj.ims.mnc000.mcc460.3gppnetwork.org";
23/// let b = "\"sip:bj.ims.mnc000.mcc460.3gppnetwork.org\"";
24///
25/// let slice_a = rust_rcs_core::internet::syntax::quote(a);
26/// let slice_b = rust_rcs_core::internet::syntax::quote(b);
27///
28/// assert_eq!(slice_a, b);
29/// assert_eq!(slice_b, b);
30///
31/// let c = "AKAv1-MD5";
32/// let d = "\"AKAv1-MD5\"";
33///
34/// let slice_c = rust_rcs_core::internet::syntax::quote(c);
35/// let slice_d = rust_rcs_core::internet::syntax::quote(d);
36///
37/// assert_eq!(slice_c, c);
38/// assert_eq!(slice_d, d);
39/// ```
40pub fn quote(s: &str) -> Cow<str> {
41    // to-do: &[u8] version needed
42    if s.len() == 0 {
43        return Cow::Borrowed(&"\"\"");
44    }
45
46    if s.len() >= 2 && s.starts_with("\"") && s.ends_with("\"") {
47        return Cow::Borrowed(s);
48    }
49
50    if s.contains("(")
51        || s.contains(")")
52        || s.contains("<")
53        || s.contains(">")
54        || s.contains("[")
55        || s.contains("]")
56        || s.contains(":")
57        || s.contains(";")
58        || s.contains("@")
59        || s.contains("\\")
60        || s.contains(",")
61        || s.contains(".")
62        || s.contains("\"")
63        || s.contains("=")
64    {
65        let s = format!("\"{}\"", s);
66
67        return Cow::Owned(s);
68    }
69
70    Cow::Borrowed(s)
71}
72
73/// Unquote a string to retrieve its literal value
74///
75/// # Examples
76///
77/// ```
78/// let a = b"\"urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.session\"";
79///
80/// let slice = rust_rcs_core::internet::syntax::unquote(a);
81///
82/// assert_eq!(slice, b"urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.session");
83/// ```
84pub fn unquote(s: &[u8]) -> &[u8] {
85    if s.len() >= 2 && s.starts_with(b"\"") && s.ends_with(b"\"") {
86        return &s[1..s.len() - 1];
87    }
88
89    s
90}
91
92/// Remove wrapping brackets from a string to retrieve its literal value
93///
94/// # Examples
95///
96/// ```
97/// let a = b"<urn:ietf:params:imdn>";
98///
99/// let slice = rust_rcs_core::internet::syntax::undo_bracket(a);
100///
101/// assert_eq!(slice, b"urn:ietf:params:imdn");
102/// ```
103pub fn undo_bracket(s: &[u8]) -> &[u8] {
104    if s.len() >= 2 && s.starts_with(b"<") && s.ends_with(b">") {
105        return &s[1..s.len() - 1];
106    }
107
108    s
109}
110
111/// Trim a string from both starting and ending white spaces
112///
113/// # Examples
114///
115/// ```
116/// let a = b" Hello, World! ";
117///
118/// let slice = rust_rcs_core::internet::syntax::trim(a);
119///
120/// assert_eq!(slice, b"Hello, World!");
121/// ```
122///
123/// Trimming a string full of white spaces should yield an empty slice
124///
125/// ```
126/// let a = b"    \t   ";
127///
128/// let slice = rust_rcs_core::internet::syntax::trim(a);
129///
130/// assert_eq!(slice, b"");
131/// ```
132pub fn trim(s: &[u8]) -> &[u8] {
133    let mut i = 0;
134    while i < s.len() {
135        let c = s[i];
136        if c == b' ' || c == b'\t' {
137            i = i + 1;
138            continue;
139        }
140        break;
141    }
142    let mut j = s.len();
143    while j > i {
144        let c = s[j - 1];
145        if c == b' ' || c == b'\t' {
146            j = j - 1;
147            continue;
148        }
149        break;
150    }
151    &s[i..j]
152}
153
154/// Find index of a ascii character that forms a language construct,
155/// quoted characters are opaque tokens by definition, therefore are not taken into account
156///
157/// # Examples
158///
159/// ```
160/// let a = b"<urn:ietf:params:imdn>";
161///
162/// let b = &a[1..]; // "urn:ietf:params:imdn>"
163///
164/// if let Some(idx) = rust_rcs_core::internet::syntax::index_with_character_escaping(b, b'>') {
165///     assert_eq!(idx, 20);
166///     assert_eq!(&a[1..1+idx], b"urn:ietf:params:imdn");
167/// } else {
168///     panic!("not found!\n");
169/// }
170/// ```
171pub fn index_with_character_escaping(s: &[u8], b: u8) -> Option<usize> {
172    let mut i = 0;
173
174    while i < s.len() {
175        let c = s[i];
176
177        if c == b'\\' {
178            i = i + 2;
179            continue;
180        }
181
182        if c == b {
183            return Some(i);
184        }
185
186        i = i + 1;
187    }
188
189    None
190}
191
192/// Find index of a ascii character that forms a language construct,
193/// bypass all tokens that are bracket or double quoted
194///
195/// # Examples
196///
197/// ```
198/// let a = b"<sip:anonymous@anonymous.invalid;transport=tcp>;+sip.instance=\"<urn:uuid:e283b49b-8999-475d-c708-0805e279b4d1>\";+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.msg\"";
199///
200/// if let Some(idx) = rust_rcs_core::internet::syntax::index_with_token_escaping(a, b';') {
201///     assert_eq!(idx, 47);
202///     assert_eq!(&a[..idx], b"<sip:anonymous@anonymous.invalid;transport=tcp>");
203///
204///     let b = &a[48..]; // "+sip.instance=\"<urn:uuid:e283b49b-8999-475d-c708-0805e279b4d1>\";+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.msg\""
205///
206///     if let Some(idx) = rust_rcs_core::internet::syntax::index_with_token_escaping(b, b';') {
207///         assert_eq!(idx, 63);
208///         assert_eq!(&b[..idx], b"+sip.instance=\"<urn:uuid:e283b49b-8999-475d-c708-0805e279b4d1>\"");
209///     } else {
210///         panic!("not found @2!\n");
211///     }
212///
213/// } else {
214///     panic!("not found @1!\n");
215/// }
216/// ```
217pub fn index_with_token_escaping(s: &[u8], b: u8) -> Option<usize> {
218    let mut i = 0;
219
220    while i < s.len() {
221        let c = s[i];
222
223        if c == b {
224            return Some(i);
225        }
226
227        if c == b'\"' {
228            match index_with_character_escaping(&s[i + 1..], b'\"') {
229                Some(idx) => i = i + 1 + idx + 1,
230                None => return None,
231            }
232        } else if c == b'<' {
233            match index_with_character_escaping(&s[i + 1..], b'>') {
234                Some(idx) => i = i + 1 + idx + 1,
235                None => return None,
236            }
237        } else {
238            i = i + 1;
239        }
240    }
241
242    None
243}
244
245/// Find index of first charater after OWS(optional white spaces) or Obsolete line folding
246///
247/// # Examples
248///
249/// ```
250/// let a = b"Newauth realm=\"apps\", type=1,\n                       title=\"Login to \\\"apps\\\"\", Basic realm=\"simple\"\n";
251///
252/// let b = &a[29..]; // "\n                       title=\"Login to \\\"apps\\\"\", Basic realm=\"simple\"\n"
253///
254/// let idx = rust_rcs_core::internet::syntax::index_skipping_ows_and_obs_fold(b);
255///
256/// assert_eq!(idx, 24);
257/// assert_eq!(&b[idx..], b"title=\"Login to \\\"apps\\\"\", Basic realm=\"simple\"");
258/// ```
259pub fn index_skipping_ows_and_obs_fold(s: &[u8]) -> usize {
260    let mut i = 0;
261
262    while i < s.len() {
263        let c = s[i];
264
265        if c == b'\n' {
266            if i + 1 < s.len() {
267                let ws = s[i + 1];
268                if ws == b' ' || ws == b'\t' {
269                    i = i + 2;
270                    continue;
271                } else {
272                    return i;
273                }
274            } else {
275                return i;
276            }
277        } else if c != b' ' && c != b'\t' {
278            return i;
279        }
280
281        i = i + 1;
282    }
283
284    i
285}
286
287/// Find index of first charater after padding white spaces and CRLFs
288///
289/// # Examples
290///
291/// ```
292/// let a = b"Content-Type: multipart/mixed; boundary=next\r\n    \r\n    \r\n--next\r\n";
293///
294/// let b = &a[46..]; // "    \r\n    \r\n--next\r\n"
295///
296/// let idx = rust_rcs_core::internet::syntax::index_skipping_transport_padding_and_crlf(b);
297///
298/// assert_eq!(idx, 12);
299/// assert_eq!(&b[idx..], b"--next\r\n");
300/// ```
301pub fn index_skipping_transport_padding_and_crlf(s: &[u8]) -> usize {
302    let mut i = 0;
303
304    while i < s.len() {
305        let cr = s[i];
306
307        if cr != b' ' && cr != b'\t' {
308            if cr == b'\r' {
309                if i + 1 < s.len() {
310                    let lf = s[i + 1];
311
312                    if lf == b'\n' {
313                        if i + 2 < s.len() {
314                            let wsp = s[i + 2];
315
316                            if wsp == b' ' || wsp == b'\t' {
317                                i = i + 3;
318                                continue;
319                            }
320                        }
321
322                        return i + 2;
323                    }
324                }
325            }
326
327            return i;
328        }
329
330        i = i + 1;
331    }
332
333    i
334}