From 90e3aaaf8a6585d17423f71ac70ba4476ab57c5b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 28 Mar 2018 16:10:58 -0700 Subject: [PATCH] fix router cannot parse Non-ASCII characters in URL #137 --- CHANGES.md | 2 ++ src/client/request.rs | 22 +++++++++++++++------- src/httprequest.rs | 10 +++++++++- src/router.rs | 4 +++- tests/test_handlers.rs | 17 +++++++++++++++++ 5 files changed, 46 insertions(+), 9 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 549525547..2790c88e2 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,8 @@ * Added `HttpReuqest::extract_xxx()`, type safe path/query information extractor +* Router cannot parse Non-ASCII characters in URL #137 + * Fix long client urls #129 * Fix panic on invalid URL characters #130 diff --git a/src/client/request.rs b/src/client/request.rs index fdff515ab..49206b0bc 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -11,6 +11,7 @@ use http::header::{self, HeaderName, HeaderValue}; use futures::Stream; use serde_json; use serde::Serialize; +use url::Url; use percent_encoding::{USERINFO_ENCODE_SET, percent_encode}; use body::Body; @@ -66,35 +67,35 @@ impl Default for ClientRequest { impl ClientRequest { /// Create request builder for `GET` request - pub fn get(uri: U) -> ClientRequestBuilder where Uri: HttpTryFrom { + pub fn get>(uri: U) -> ClientRequestBuilder { let mut builder = ClientRequest::build(); builder.method(Method::GET).uri(uri); builder } /// Create request builder for `HEAD` request - pub fn head(uri: U) -> ClientRequestBuilder where Uri: HttpTryFrom { + pub fn head>(uri: U) -> ClientRequestBuilder { let mut builder = ClientRequest::build(); builder.method(Method::HEAD).uri(uri); builder } /// Create request builder for `POST` request - pub fn post(uri: U) -> ClientRequestBuilder where Uri: HttpTryFrom { + pub fn post>(uri: U) -> ClientRequestBuilder { let mut builder = ClientRequest::build(); builder.method(Method::POST).uri(uri); builder } /// Create request builder for `PUT` request - pub fn put(uri: U) -> ClientRequestBuilder where Uri: HttpTryFrom { + pub fn put>(uri: U) -> ClientRequestBuilder { let mut builder = ClientRequest::build(); builder.method(Method::PUT).uri(uri); builder } /// Create request builder for `DELETE` request - pub fn delete(uri: U) -> ClientRequestBuilder where Uri: HttpTryFrom { + pub fn delete>(uri: U) -> ClientRequestBuilder { let mut builder = ClientRequest::build(); builder.method(Method::DELETE).uri(uri); builder @@ -255,8 +256,15 @@ pub struct ClientRequestBuilder { impl ClientRequestBuilder { /// Set HTTP uri of request. #[inline] - pub fn uri(&mut self, uri: U) -> &mut Self where Uri: HttpTryFrom { - match Uri::try_from(uri) { + pub fn uri>(&mut self, uri: U) -> &mut Self { + match Url::parse(uri.as_ref()) { + Ok(url) => self._uri(url.as_str()), + Err(_) => self._uri(uri.as_ref()), + } + } + + fn _uri(&mut self, url: &str) -> &mut Self { + match Uri::try_from(url) { Ok(uri) => { // set request host header if let Some(host) = uri.host() { diff --git a/src/httprequest.rs b/src/httprequest.rs index 20fef5a2c..2954cd5b6 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -2,6 +2,7 @@ use std::{io, cmp, str, fmt, mem}; use std::rc::Rc; use std::net::SocketAddr; +use std::borrow::Cow; use bytes::Bytes; use cookie::Cookie; use futures::{Async, Future, Stream, Poll}; @@ -11,6 +12,7 @@ use url::{Url, form_urlencoded}; use http::{header, Uri, Method, Version, HeaderMap, Extensions, StatusCode}; use tokio_io::AsyncRead; use serde::de; +use percent_encoding::percent_decode; use body::Body; use info::ConnectionInfo; @@ -257,6 +259,12 @@ impl HttpRequest { self.uri().path() } + /// Percent decoded path of this Request. + #[inline] + pub fn path_decoded(&self) -> Cow { + percent_decode(self.uri().path().as_bytes()).decode_utf8().unwrap() + } + /// Get *ConnectionInfo* for correct request. pub fn connection_info(&self) -> &ConnectionInfo { if self.as_ref().info.is_none() { @@ -598,7 +606,7 @@ impl AsyncRead for HttpRequest {} impl fmt::Debug for HttpRequest { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let res = write!(f, "\nHttpRequest {:?} {}:{}\n", - self.as_ref().version, self.as_ref().method, self.as_ref().uri); + self.as_ref().version, self.as_ref().method, self.path_decoded()); if !self.query_string().is_empty() { let _ = write!(f, " query: ?{:?}\n", self.query_string()); } diff --git a/src/router.rs b/src/router.rs index fc01bd3be..57b4d0a12 100644 --- a/src/router.rs +++ b/src/router.rs @@ -4,6 +4,7 @@ use std::hash::{Hash, Hasher}; use std::collections::HashMap; use regex::{Regex, escape}; +use percent_encoding::percent_decode; use error::UrlGenerationError; use param::Params; @@ -70,9 +71,10 @@ impl Router { } let path: &str = unsafe{mem::transmute(&req.path()[self.0.prefix_len..])}; let route_path = if path.is_empty() { "/" } else { path }; + let p = percent_decode(route_path.as_bytes()).decode_utf8().unwrap(); for (idx, pattern) in self.0.patterns.iter().enumerate() { - if pattern.match_with_params(route_path, req.match_info_mut()) { + if pattern.match_with_params(p.as_ref(), req.match_info_mut()) { return Some(idx) } } diff --git a/tests/test_handlers.rs b/tests/test_handlers.rs index b38fdfbd0..d7a8bc45d 100644 --- a/tests/test_handlers.rs +++ b/tests/test_handlers.rs @@ -61,3 +61,20 @@ fn test_query_extractor() { let response = srv.execute(request.send()).unwrap(); assert_eq!(response.status(), StatusCode::BAD_REQUEST); } + +#[test] +fn test_non_ascii_route() { + let mut srv = test::TestServer::new(|app| { + app.resource("/中文/index.html", |r| r.f(|_| "success")); + }); + + // client request + let request = srv.get().uri(srv.url("/中文/index.html")) + .finish().unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.execute(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from_static(b"success")); +}