url/
slicing.rs

1// Copyright 2016 The rust-url developers.
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9use core::ops::{Index, Range, RangeFrom, RangeFull, RangeTo};
10
11use crate::Url;
12
13impl Index<RangeFull> for Url {
14    type Output = str;
15    fn index(&self, _: RangeFull) -> &str {
16        &self.serialization
17    }
18}
19
20impl Index<RangeFrom<Position>> for Url {
21    type Output = str;
22    fn index(&self, range: RangeFrom<Position>) -> &str {
23        &self.serialization[self.index(range.start)..]
24    }
25}
26
27impl Index<RangeTo<Position>> for Url {
28    type Output = str;
29    fn index(&self, range: RangeTo<Position>) -> &str {
30        &self.serialization[..self.index(range.end)]
31    }
32}
33
34impl Index<Range<Position>> for Url {
35    type Output = str;
36    fn index(&self, range: Range<Position>) -> &str {
37        &self.serialization[self.index(range.start)..self.index(range.end)]
38    }
39}
40
41// Counts how many base-10 digits are required to represent n in the given base
42fn count_digits(n: u16) -> usize {
43    match n {
44        0..=9 => 1,
45        10..=99 => 2,
46        100..=999 => 3,
47        1000..=9999 => 4,
48        10000..=65535 => 5,
49    }
50}
51
52#[test]
53fn test_count_digits() {
54    assert_eq!(count_digits(0), 1);
55    assert_eq!(count_digits(1), 1);
56    assert_eq!(count_digits(9), 1);
57    assert_eq!(count_digits(10), 2);
58    assert_eq!(count_digits(99), 2);
59    assert_eq!(count_digits(100), 3);
60    assert_eq!(count_digits(9999), 4);
61    assert_eq!(count_digits(65535), 5);
62}
63
64/// Indicates a position within a URL based on its components.
65///
66/// A range of positions can be used for slicing `Url`:
67///
68/// ```rust
69/// # use url::{Url, Position};
70/// # fn something(some_url: Url) {
71/// let serialization: &str = &some_url[..];
72/// let serialization_without_fragment: &str = &some_url[..Position::AfterQuery];
73/// let authority: &str = &some_url[Position::BeforeUsername..Position::AfterPort];
74/// let data_url_payload: &str = &some_url[Position::BeforePath..Position::AfterQuery];
75/// let scheme_relative: &str = &some_url[Position::BeforeUsername..];
76/// # }
77/// ```
78///
79/// In a pseudo-grammar (where `[`…`]?` makes a sub-sequence optional),
80/// URL components and delimiters that separate them are:
81///
82/// ```notrust
83/// url =
84///     scheme ":"
85///     [ "//" [ username [ ":" password ]? "@" ]? host [ ":" port ]? ]?
86///     path [ "?" query ]? [ "#" fragment ]?
87/// ```
88///
89/// When a given component is not present,
90/// its "before" and "after" position are the same
91/// (so that `&some_url[BeforeFoo..AfterFoo]` is the empty string)
92/// and component ordering is preserved
93/// (so that a missing query "is between" a path and a fragment).
94///
95/// The end of a component and the start of the next are either the same or separate
96/// by a delimiter.
97/// (Note that the initial `/` of a path is considered part of the path here, not a delimiter.)
98/// For example, `&url[..BeforeFragment]` would include a `#` delimiter (if present in `url`),
99/// so `&url[..AfterQuery]` might be desired instead.
100///
101/// `BeforeScheme` and `AfterFragment` are always the start and end of the entire URL,
102/// so `&url[BeforeScheme..X]` is the same as `&url[..X]`
103/// and `&url[X..AfterFragment]` is the same as `&url[X..]`.
104#[derive(Copy, Clone, Debug)]
105pub enum Position {
106    BeforeScheme,
107    AfterScheme,
108    BeforeUsername,
109    AfterUsername,
110    BeforePassword,
111    AfterPassword,
112    BeforeHost,
113    AfterHost,
114    BeforePort,
115    AfterPort,
116    BeforePath,
117    AfterPath,
118    BeforeQuery,
119    AfterQuery,
120    BeforeFragment,
121    AfterFragment,
122}
123
124impl Url {
125    #[inline]
126    fn index(&self, position: Position) -> usize {
127        match position {
128            Position::BeforeScheme => 0,
129
130            Position::AfterScheme => self.scheme_end as usize,
131
132            Position::BeforeUsername => {
133                if self.has_authority() {
134                    self.scheme_end as usize + "://".len()
135                } else {
136                    debug_assert!(self.byte_at(self.scheme_end) == b':');
137                    debug_assert!(self.scheme_end + ":".len() as u32 == self.username_end);
138                    self.scheme_end as usize + ":".len()
139                }
140            }
141
142            Position::AfterUsername => self.username_end as usize,
143
144            Position::BeforePassword => {
145                if self.has_authority() && self.byte_at(self.username_end) == b':' {
146                    self.username_end as usize + ":".len()
147                } else {
148                    debug_assert!(self.username_end == self.host_start);
149                    self.username_end as usize
150                }
151            }
152
153            Position::AfterPassword => {
154                if self.has_authority() && self.byte_at(self.username_end) == b':' {
155                    debug_assert!(self.byte_at(self.host_start - "@".len() as u32) == b'@');
156                    self.host_start as usize - "@".len()
157                } else {
158                    debug_assert!(self.username_end == self.host_start);
159                    self.host_start as usize
160                }
161            }
162
163            Position::BeforeHost => self.host_start as usize,
164
165            Position::AfterHost => self.host_end as usize,
166
167            Position::BeforePort => {
168                if self.port.is_some() {
169                    debug_assert!(self.byte_at(self.host_end) == b':');
170                    self.host_end as usize + ":".len()
171                } else {
172                    self.host_end as usize
173                }
174            }
175
176            Position::AfterPort => {
177                if let Some(port) = self.port {
178                    debug_assert!(self.byte_at(self.host_end) == b':');
179                    self.host_end as usize + ":".len() + count_digits(port)
180                } else {
181                    self.host_end as usize
182                }
183            }
184
185            Position::BeforePath => self.path_start as usize,
186
187            Position::AfterPath => match (self.query_start, self.fragment_start) {
188                (Some(q), _) => q as usize,
189                (None, Some(f)) => f as usize,
190                (None, None) => self.serialization.len(),
191            },
192
193            Position::BeforeQuery => match (self.query_start, self.fragment_start) {
194                (Some(q), _) => {
195                    debug_assert!(self.byte_at(q) == b'?');
196                    q as usize + "?".len()
197                }
198                (None, Some(f)) => f as usize,
199                (None, None) => self.serialization.len(),
200            },
201
202            Position::AfterQuery => match self.fragment_start {
203                None => self.serialization.len(),
204                Some(f) => f as usize,
205            },
206
207            Position::BeforeFragment => match self.fragment_start {
208                Some(f) => {
209                    debug_assert!(self.byte_at(f) == b'#');
210                    f as usize + "#".len()
211                }
212                None => self.serialization.len(),
213            },
214
215            Position::AfterFragment => self.serialization.len(),
216        }
217    }
218}