fix url_for parser

This commit is contained in:
Ali MJ Al-Nasrawy 2021-11-01 01:59:16 +03:00
parent ec6d284a8e
commit d397900e1a
3 changed files with 74 additions and 15 deletions

View File

@ -33,8 +33,8 @@ pub enum UrlGenerationError {
#[display(fmt = "Resource not found")] #[display(fmt = "Resource not found")]
ResourceNotFound, ResourceNotFound,
/// Not all path pattern covered /// Not all url paramters covered
#[display(fmt = "Not all path pattern covered")] #[display(fmt = "Not all url parameters covered")]
NotEnoughElements, NotEnoughElements,
/// URL parse error /// URL parse error

View File

@ -114,12 +114,20 @@ impl HttpRequest {
self.uri().query().unwrap_or_default() self.uri().query().unwrap_or_default()
} }
/// Get a reference to the Path parameters. /// Get a reference to url parameters container
/// ///
/// Params is a container for url parameters. /// A url parameter is specified in the form `{identifier}`,
/// A variable segment is specified in the form `{identifier}`,
/// where the identifier can be used later in a request handler to /// where the identifier can be used later in a request handler to
/// access the matched value for that segment. /// access the matched value for that parameter.
///
///
/// # Percent Enconding and Url Paramters
///
/// Because each url paramter is able to capture multiple path segments,
/// both `["%2F", "%25"]` found in the request uri are not decoded into `["/", "%"]`
/// in order to preserve path segment boundaries.
/// If a url paramter is expected to contain these characters, then it is on the user to decode them.
///
#[inline] #[inline]
pub fn match_info(&self) -> &Path<Url> { pub fn match_info(&self) -> &Path<Url> {
&self.inner.path &self.inner.path
@ -163,6 +171,14 @@ impl HttpRequest {
/// Generate url for named resource /// Generate url for named resource
/// ///
/// This substitutes in sequence all url parameters that appear in the resource itself
/// and in parent [scopes](crate::web::scope), if any.
///
/// It is worth noting that the characters `['/', '%']` are not escaped and therefore a single
/// url parameter may expand into multiple path segments and `elements`
/// can be percent-encoded beforehand without worrying about double encoding.
/// Any other character that is not valid in url ath context is escaped using percent-encoding.
///
/// ``` /// ```
/// # use actix_web::{web, App, HttpRequest, HttpResponse}; /// # use actix_web::{web, App, HttpRequest, HttpResponse};
/// # /// #

View File

@ -1,3 +1,4 @@
use std::borrow::Cow;
use std::cell::RefCell; use std::cell::RefCell;
use std::rc::{Rc, Weak}; use std::rc::{Rc, Weak};
@ -102,17 +103,23 @@ impl ResourceMap {
}) })
.ok_or(UrlGenerationError::NotEnoughElements)?; .ok_or(UrlGenerationError::NotEnoughElements)?;
if path.starts_with('/') { let (base, path): (Cow<'_, _>, _) = if path.starts_with('/') {
let conn = req.connection_info(); let conn = req.connection_info();
Ok(Url::parse(&format!( let base = format!("{}://{}", conn.scheme(), conn.host(),).into();
"{}://{}{}", (base, path.as_str())
conn.scheme(),
conn.host(),
path
))?)
} else { } else {
Ok(Url::parse(&path)?) // external resource
} let third_slash_index = path
.char_indices()
.filter_map(|(i, c)| (c == '/').then(|| i))
.nth(2)
.unwrap_or(path.len());
(path[..third_slash_index].into(), &path[third_slash_index..])
};
let mut url = Url::parse(&base)?;
url.set_path(path);
Ok(url)
} }
pub fn has_resource(&self, path: &str) -> bool { pub fn has_resource(&self, path: &str) -> bool {
@ -406,6 +413,42 @@ mod tests {
assert!(rmap.url_for(&req, "missing", &["u123"]).is_err()); assert!(rmap.url_for(&req, "missing", &["u123"]).is_err());
} }
#[test]
fn url_for_parser() {
let mut root = ResourceMap::new(ResourceDef::prefix(""));
let mut rdef_1 = ResourceDef::new("/{var}");
rdef_1.set_name("internal");
let mut rdef_2 = ResourceDef::new("http://host.dom/{var}");
rdef_2.set_name("external.1");
let mut rdef_3 = ResourceDef::new("{var}");
rdef_3.set_name("external.2");
root.add(&mut rdef_1, None);
root.add(&mut rdef_2, None);
root.add(&mut rdef_3, None);
let rmap = Rc::new(root);
ResourceMap::finish(&rmap);
let mut req = crate::test::TestRequest::default();
req.set_server_hostname("localhost:8888");
let req = req.to_http_request();
const INPUT: &[&str] = &["a/../quick brown%20fox/%nan?query#frag"];
const OUTPUT: &str = "/quick%20brown%20fox/%nan%3Fquery%23frag";
let url = rmap.url_for(&req, "internal", INPUT).unwrap();
assert_eq!(url.path(), OUTPUT);
let url = rmap.url_for(&req, "external.1", INPUT).unwrap();
assert_eq!(url.path(), OUTPUT);
assert!(rmap.url_for(&req, "external.2", INPUT).is_err());
assert!(rmap.url_for(&req, "external.2", &[""]).is_err());
}
#[test] #[test]
fn external_resource_with_no_name() { fn external_resource_with_no_name() {
let mut root = ResourceMap::new(ResourceDef::prefix("")); let mut root = ResourceMap::new(ResourceDef::prefix(""));