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}