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