rocket/router/
matcher.rs

1use crate::{Route, Request, Catcher};
2use crate::router::Collide;
3use crate::http::Status;
4use crate::route::Color;
5
6impl Route {
7    /// Returns `true` if `self` matches `request`.
8    ///
9    /// A [_match_](Route#routing) occurs when:
10    ///
11    ///   * The route's method matches that of the incoming request.
12    ///   * Either the route has no format _or_:
13    ///     - If the route's method supports a payload, the request's
14    ///       `Content-Type` is [fully specified] and [collides with] the
15    ///       route's format.
16    ///     - If the route's method does not support a payload, the request
17    ///       either has no `Accept` header or it [collides with] with the
18    ///       route's format.
19    ///   * All static segments in the route's URI match the corresponding
20    ///     components in the same position in the incoming request URI.
21    ///   * The route URI has no query part _or_ all static segments in the
22    ///     route's query string are in the request query string, though in any
23    ///     position.
24    ///
25    /// [fully specified]: crate::http::MediaType::specificity()
26    /// [collides with]: Route::collides_with()
27    ///
28    /// For a request to be routed to a particular route, that route must both
29    /// `match` _and_ have the highest precedence among all matching routes for
30    /// that request. In other words, a `match` is a necessary but insufficient
31    /// condition to determine if a route will handle a particular request.
32    ///
33    /// The precedence of a route is determined by its rank. Routes with lower
34    /// ranks have higher precedence. [By default](Route#default-ranking), more
35    /// specific routes are assigned a lower ranking.
36    ///
37    /// # Example
38    ///
39    /// ```rust
40    /// use rocket::Route;
41    /// use rocket::http::Method;
42    /// # use rocket::local::blocking::Client;
43    /// # use rocket::route::dummy_handler as handler;
44    ///
45    /// // This route handles GET requests to `/<hello>`.
46    /// let a = Route::new(Method::Get, "/<hello>", handler);
47    ///
48    /// // This route handles GET requests to `/здрасти`.
49    /// let b = Route::new(Method::Get, "/здрасти", handler);
50    ///
51    /// # let client = Client::debug(rocket::build()).unwrap();
52    /// // Let's say `request` is `GET /hello`. The request matches only `a`:
53    /// let request = client.get("/hello");
54    /// # let request = request.inner();
55    /// assert!(a.matches(&request));
56    /// assert!(!b.matches(&request));
57    ///
58    /// // Now `request` is `GET /здрасти`. It matches both `a` and `b`:
59    /// let request = client.get("/здрасти");
60    /// # let request = request.inner();
61    /// assert!(a.matches(&request));
62    /// assert!(b.matches(&request));
63    ///
64    /// // But `b` is more specific, so it has lower rank (higher precedence)
65    /// // by default, so Rocket would route the request to `b`, not `a`.
66    /// assert!(b.rank < a.rank);
67    /// ```
68    #[tracing::instrument(level = "trace", name = "matching", skip_all, ret)]
69    pub fn matches(&self, request: &Request<'_>) -> bool {
70        methods_match(self, request)
71            && paths_match(self, request)
72            && queries_match(self, request)
73            && formats_match(self, request)
74    }
75}
76
77impl Catcher {
78    /// Returns `true` if `self` matches errors with `status` that occurred
79    /// during `request`.
80    ///
81    /// A [_match_](Catcher#routing) between a `Catcher` and a (`Status`,
82    /// `&Request`) pair occurs when:
83    ///
84    ///   * The catcher has the same [code](Catcher::code) as
85    ///     [`status`](Status::code) _or_ is `default`.
86    ///   * The catcher's [base](Catcher::base()) is a prefix of the `request`'s
87    ///     [normalized](crate::http::uri::Origin#normalization) URI.
88    ///
89    /// For an error arising from a request to be routed to a particular
90    /// catcher, that catcher must both `match` _and_ have higher precedence
91    /// than any other catcher that matches. In other words, a `match` is a
92    /// necessary but insufficient condition to determine if a catcher will
93    /// handle a particular error.
94    ///
95    /// The precedence of a catcher is determined by:
96    ///
97    ///   1. The number of _complete_ segments in the catcher's `base`.
98    ///   2. Whether the catcher is `default` or not.
99    ///
100    /// Non-default routes, and routes with more complete segments in their
101    /// base, have higher precedence.
102    ///
103    /// # Example
104    ///
105    /// ```rust
106    /// use rocket::Catcher;
107    /// use rocket::http::Status;
108    /// # use rocket::local::blocking::Client;
109    /// # use rocket::catcher::dummy_handler as handler;
110    ///
111    /// // This catcher handles 404 errors with a base of `/`.
112    /// let a = Catcher::new(404, handler);
113    ///
114    /// // This catcher handles 404 errors with a base of `/bar`.
115    /// let b = a.clone().map_base(|_| format!("/bar")).unwrap();
116    ///
117    /// # let client = Client::debug(rocket::build()).unwrap();
118    /// // Let's say `request` is `GET /` that 404s. The error matches only `a`:
119    /// let request = client.get("/");
120    /// # let request = request.inner();
121    /// assert!(a.matches(Status::NotFound, &request));
122    /// assert!(!b.matches(Status::NotFound, &request));
123    ///
124    /// // Now `request` is a 404 `GET /bar`. The error matches `a` and `b`:
125    /// let request = client.get("/bar");
126    /// # let request = request.inner();
127    /// assert!(a.matches(Status::NotFound, &request));
128    /// assert!(b.matches(Status::NotFound, &request));
129    ///
130    /// // Note that because `b`'s base' has more complete segments that `a's,
131    /// // Rocket would route the error to `b`, not `a`, even though both match.
132    /// let a_count = a.base().segments().filter(|s| !s.is_empty()).count();
133    /// let b_count = b.base().segments().filter(|s| !s.is_empty()).count();
134    /// assert!(b_count > a_count);
135    /// ```
136    pub fn matches(&self, status: Status, request: &Request<'_>) -> bool {
137        self.code.map_or(true, |code| code == status.code)
138            && self.base().segments().prefix_of(request.uri().path().segments())
139    }
140}
141
142fn methods_match(route: &Route, req: &Request<'_>) -> bool {
143    trace!(?route.method, request.method = %req.method());
144    route.method.map_or(true, |method| method == req.method())
145}
146
147fn paths_match(route: &Route, req: &Request<'_>) -> bool {
148    trace!(route.uri = %route.uri, request.uri = %req.uri());
149    let route_segments = &route.uri.metadata.uri_segments;
150    let req_segments = req.uri().path().segments();
151
152    // A route can never have more segments than a request. Recall that a
153    // trailing slash is considering a segment, albeit empty.
154    if route_segments.len() > req_segments.num() {
155        return false;
156    }
157
158    // requests with longer paths only match if we have dynamic trail (<a..>).
159    if req_segments.num() > route_segments.len() && !route.uri.metadata.dynamic_trail {
160        return false;
161    }
162
163    // We've checked everything beyond the zip of their lengths already.
164    for (route_seg, req_seg) in route_segments.iter().zip(req_segments.clone()) {
165        if route_seg.dynamic_trail {
166            return true;
167        }
168
169        if !route_seg.dynamic && route_seg.value != req_seg {
170            return false;
171        }
172    }
173
174    true
175}
176
177fn queries_match(route: &Route, req: &Request<'_>) -> bool {
178    trace!(
179        route.query = route.uri.query().map(display),
180        route.query.color = route.uri.metadata.query_color.map(debug),
181        request.query = req.uri().query().map(display),
182    );
183
184    if matches!(route.uri.metadata.query_color, None | Some(Color::Wild)) {
185        return true;
186    }
187
188    let route_query_fields = route.uri.metadata.static_query_fields.iter();
189    for (key, val) in route_query_fields {
190        if let Some(query) = req.uri().query() {
191            if !query.segments().any(|req_seg| req_seg == (key, val)) {
192                debug!(key, val, request.query = %query, "missing static query");
193                return false;
194            }
195        } else {
196            debug!(key, val, "missing static query (queryless request)");
197            return false;
198        }
199    }
200
201    true
202}
203
204fn formats_match(route: &Route, req: &Request<'_>) -> bool {
205    trace!(
206        route.format = route.format.as_ref().map(display),
207        request.format = req.format().map(display),
208    );
209
210    let route_format = match route.format {
211        Some(ref format) => format,
212        None => return true,
213    };
214
215    match route.method.and_then(|m| m.allows_request_body()) {
216        Some(true) => match req.format() {
217            Some(f) if f.specificity() == 2 => route_format.collides_with(f),
218            _ => false
219        },
220        _ => match req.format() {
221            Some(f) => route_format.collides_with(f),
222            None => true
223        }
224    }
225}
226
227#[cfg(test)]
228mod tests {
229    use crate::local::blocking::Client;
230    use crate::route::{Route, dummy_handler};
231    use crate::http::{Method, Method::*, MediaType, ContentType, Accept};
232
233    fn req_matches_route(a: &'static str, b: &'static str) -> bool {
234        let client = Client::debug_with(vec![]).expect("client");
235        let route = Route::ranked(0, Get, b, dummy_handler);
236        route.matches(&client.get(a))
237    }
238
239    #[test]
240    fn request_route_matching() {
241        assert!(req_matches_route("/a/b?a=b", "/a/b?<c>"));
242        assert!(req_matches_route("/a/b?a=b", "/<a>/b?<c>"));
243        assert!(req_matches_route("/a/b?a=b", "/<a>/<b>?<c>"));
244        assert!(req_matches_route("/a/b?a=b", "/a/<b>?<c>"));
245        assert!(req_matches_route("/?b=c", "/?<b>"));
246
247        assert!(req_matches_route("/a/b?a=b", "/a/b"));
248        assert!(req_matches_route("/a/b", "/a/b"));
249        assert!(req_matches_route("/a/b/c/d?", "/a/b/c/d"));
250        assert!(req_matches_route("/a/b/c/d?v=1&v=2", "/a/b/c/d"));
251
252        assert!(req_matches_route("/a/b", "/a/b?<c>"));
253        assert!(req_matches_route("/a/b", "/a/b?<c..>"));
254        assert!(req_matches_route("/a/b?c", "/a/b?c"));
255        assert!(req_matches_route("/a/b?c", "/a/b?<c>"));
256        assert!(req_matches_route("/a/b?c=foo&d=z", "/a/b?<c>"));
257        assert!(req_matches_route("/a/b?c=foo&d=z", "/a/b?<c..>"));
258        assert!(req_matches_route("/a/b?c=foo&d=z", "/a/b?c=foo&<c..>"));
259        assert!(req_matches_route("/a/b?c=foo&d=z", "/a/b?d=z&<c..>"));
260
261        assert!(req_matches_route("/", "/<foo>"));
262        assert!(req_matches_route("/a", "/<foo>"));
263        assert!(req_matches_route("/a", "/a"));
264        assert!(req_matches_route("/a/", "/a/"));
265
266        assert!(req_matches_route("//", "/"));
267        assert!(req_matches_route("/a///", "/a/"));
268        assert!(req_matches_route("/a/b", "/a/b"));
269
270        assert!(!req_matches_route("/a///", "/a"));
271        assert!(!req_matches_route("/a", "/a/"));
272        assert!(!req_matches_route("/a/", "/a"));
273        assert!(!req_matches_route("/a/b", "/a/b/"));
274
275        assert!(!req_matches_route("/a", "/<a>/"));
276        assert!(!req_matches_route("/a/", "/<a>"));
277        assert!(!req_matches_route("/a/b", "/<a>/b/"));
278        assert!(!req_matches_route("/a/b", "/<a>/<b>/"));
279
280        assert!(!req_matches_route("/a/b/c", "/a/b?<c>"));
281        assert!(!req_matches_route("/a?b=c", "/a/b?<c>"));
282        assert!(!req_matches_route("/?b=c", "/a/b?<c>"));
283        assert!(!req_matches_route("/?b=c", "/a?<c>"));
284
285        assert!(!req_matches_route("/a/", "/<a>/<b>/<c..>"));
286        assert!(!req_matches_route("/a/b", "/<a>/<b>/<c..>"));
287
288        assert!(!req_matches_route("/a/b?c=foo&d=z", "/a/b?a=b&<c..>"));
289        assert!(!req_matches_route("/a/b?c=foo&d=z", "/a/b?d=b&<c..>"));
290        assert!(!req_matches_route("/a/b", "/a/b?c"));
291        assert!(!req_matches_route("/a/b", "/a/b?foo"));
292        assert!(!req_matches_route("/a/b", "/a/b?foo&<rest..>"));
293        assert!(!req_matches_route("/a/b", "/a/b?<a>&b&<rest..>"));
294    }
295
296    fn req_matches_format<S1, S2>(m: Method, mt1: S1, mt2: S2) -> bool
297        where S1: Into<Option<&'static str>>, S2: Into<Option<&'static str>>
298    {
299        let client = Client::debug_with(vec![]).expect("client");
300        let mut req = client.req(m, "/");
301        if let Some(mt_str) = mt1.into() {
302            if m.allows_request_body() == Some(true) {
303                req.replace_header(mt_str.parse::<ContentType>().unwrap());
304            } else {
305                req.replace_header(mt_str.parse::<Accept>().unwrap());
306            }
307        }
308
309        let mut route = Route::new(m, "/", dummy_handler);
310        if let Some(mt_str) = mt2.into() {
311            route.format = Some(mt_str.parse::<MediaType>().unwrap());
312        }
313
314        route.matches(&req)
315    }
316
317    #[test]
318    fn test_req_route_mt_collisions() {
319        assert!(req_matches_format(Post, "application/json", "application/json"));
320        assert!(req_matches_format(Post, "application/json", "application/*"));
321        assert!(req_matches_format(Post, "application/json", "*/json"));
322        assert!(req_matches_format(Post, "text/html", "*/*"));
323
324        assert!(req_matches_format(Get, "application/json", "application/json"));
325        assert!(req_matches_format(Get, "text/html", "text/html"));
326        assert!(req_matches_format(Get, "text/html", "*/*"));
327        assert!(req_matches_format(Get, None, "*/*"));
328        assert!(req_matches_format(Get, None, "text/*"));
329        assert!(req_matches_format(Get, None, "text/html"));
330        assert!(req_matches_format(Get, None, "application/json"));
331
332        assert!(req_matches_format(Post, "text/html", None));
333        assert!(req_matches_format(Post, "application/json", None));
334        assert!(req_matches_format(Post, "x-custom/anything", None));
335        assert!(req_matches_format(Post, None, None));
336
337        assert!(req_matches_format(Get, "text/html", None));
338        assert!(req_matches_format(Get, "application/json", None));
339        assert!(req_matches_format(Get, "x-custom/anything", None));
340        assert!(req_matches_format(Get, None, None));
341        assert!(req_matches_format(Get, None, "text/html"));
342        assert!(req_matches_format(Get, None, "application/json"));
343
344        assert!(req_matches_format(Get, "text/html, text/plain", "text/html"));
345        assert!(req_matches_format(Get, "text/html; q=0.5, text/xml", "text/xml"));
346
347        assert!(!req_matches_format(Post, None, "text/html"));
348        assert!(!req_matches_format(Post, None, "text/*"));
349        assert!(!req_matches_format(Post, None, "*/text"));
350        assert!(!req_matches_format(Post, None, "*/*"));
351        assert!(!req_matches_format(Post, None, "text/html"));
352        assert!(!req_matches_format(Post, None, "application/json"));
353
354        assert!(!req_matches_format(Post, "application/json", "text/html"));
355        assert!(!req_matches_format(Post, "application/json", "text/*"));
356        assert!(!req_matches_format(Post, "application/json", "*/xml"));
357        assert!(!req_matches_format(Get, "application/json", "text/html"));
358        assert!(!req_matches_format(Get, "application/json", "text/*"));
359        assert!(!req_matches_format(Get, "application/json", "*/xml"));
360
361        assert!(!req_matches_format(Post, None, "text/html"));
362        assert!(!req_matches_format(Post, None, "application/json"));
363    }
364}