mirror of https://github.com/fafhrd91/actix-web
Merge branch 'main' into feat-files-array-support
This commit is contained in:
commit
c1007f9fda
|
|
@ -4,8 +4,10 @@
|
|||
|
||||
- Minimum supported Rust version (MSRV) is now 1.88.
|
||||
- Support `deserialize_any` in `PathDeserializer` (enables derived `#[serde(untagged)]` enums in path segments). [#2881]
|
||||
- Fix stale path segment indices after path rewrites, preventing out-of-bounds access during extraction. [#3562]
|
||||
|
||||
[#2881]: https://github.com/actix/actix-web/pull/2881
|
||||
[#3562]: https://github.com/actix/actix-web/issues/3562
|
||||
|
||||
## 0.5.3
|
||||
|
||||
|
|
|
|||
|
|
@ -93,6 +93,45 @@ impl<T: ResourcePath> Path<T> {
|
|||
self.segments.clear();
|
||||
}
|
||||
|
||||
/// Set new path while preserving and remapping existing captured segment indices.
|
||||
///
|
||||
/// The `reindex` closure maps byte indices from the previous path to byte indices in the new
|
||||
/// path.
|
||||
#[doc(hidden)]
|
||||
pub fn update_with_reindex<F>(&mut self, path: T, mut reindex: F)
|
||||
where
|
||||
F: FnMut(u16) -> u16,
|
||||
{
|
||||
self.skip = reindex(self.skip);
|
||||
|
||||
for (_, item) in &mut self.segments {
|
||||
if let PathItem::Segment(start, end) = item {
|
||||
*start = reindex(*start);
|
||||
*end = reindex(*end);
|
||||
|
||||
if *start > *end {
|
||||
*start = *end;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.path = path;
|
||||
let path = self.path.path();
|
||||
|
||||
self.skip = clamp_to_char_boundary(path, self.skip);
|
||||
|
||||
for (_, item) in &mut self.segments {
|
||||
if let PathItem::Segment(start, end) = item {
|
||||
*start = clamp_to_char_boundary(path, *start);
|
||||
*end = clamp_to_char_boundary(path, *end);
|
||||
|
||||
if *start > *end {
|
||||
*start = *end;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Reset state.
|
||||
#[inline]
|
||||
pub fn reset(&mut self) {
|
||||
|
|
@ -179,6 +218,16 @@ impl<T: ResourcePath> Path<T> {
|
|||
}
|
||||
}
|
||||
|
||||
fn clamp_to_char_boundary(path: &str, idx: u16) -> u16 {
|
||||
let mut idx = usize::from(idx).min(path.len());
|
||||
|
||||
while idx > 0 && !path.is_char_boundary(idx) {
|
||||
idx -= 1;
|
||||
}
|
||||
|
||||
idx as u16
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PathIter<'a, T> {
|
||||
idx: usize,
|
||||
|
|
|
|||
|
|
@ -7,10 +7,12 @@
|
|||
- Ignore unparsable cookies in `Cookie` request header.
|
||||
- Add `experimental-introspection` feature to report configured routes [#3594]
|
||||
- Add config/method for `TCP_NODELAY`. [#3918]
|
||||
- Fix panic when `NormalizePath` rewrites a scoped dynamic path before extraction (e.g., `scope("{tail:.*}")` + `Path<String>`). [#3562]
|
||||
|
||||
[#3895]: https://github.com/actix/actix-web/pull/3895
|
||||
[#3594]: https://github.com/actix/actix-web/pull/3594
|
||||
[#3918]: https://github.com/actix/actix-web/pull/3918
|
||||
[#3562]: https://github.com/actix/actix-web/issues/3562
|
||||
|
||||
## 4.12.1
|
||||
|
||||
|
|
|
|||
|
|
@ -26,49 +26,49 @@ common_header! {
|
|||
/// accept-ext = OWS ";" OWS token [ "=" ( token / quoted-string ) ]
|
||||
/// ```
|
||||
///
|
||||
/// # Note
|
||||
/// This is a request header. Servers should not send `Accept` in responses; to describe the
|
||||
/// response body media type, use [`ContentType`](super::ContentType) / the `Content-Type`
|
||||
/// header instead.
|
||||
///
|
||||
/// # Example Values
|
||||
/// * `audio/*; q=0.2, audio/basic`
|
||||
/// * `text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c`
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use actix_web::HttpResponse;
|
||||
/// use actix_web::http::header::{Accept, QualityItem};
|
||||
/// use actix_web::{http::header::{Accept, QualityItem}, test};
|
||||
///
|
||||
/// let mut builder = HttpResponse::Ok();
|
||||
/// builder.insert_header(
|
||||
/// Accept(vec![
|
||||
/// QualityItem::max(mime::TEXT_HTML),
|
||||
/// ])
|
||||
/// );
|
||||
/// let req = test::TestRequest::default()
|
||||
/// .insert_header(Accept(vec![QualityItem::max(mime::TEXT_HTML)]))
|
||||
/// .to_http_request();
|
||||
/// # let _ = req;
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// use actix_web::HttpResponse;
|
||||
/// use actix_web::http::header::{Accept, QualityItem};
|
||||
/// use actix_web::{http::header::{Accept, QualityItem}, test};
|
||||
///
|
||||
/// let mut builder = HttpResponse::Ok();
|
||||
/// builder.insert_header(
|
||||
/// Accept(vec![
|
||||
/// QualityItem::max(mime::APPLICATION_JSON),
|
||||
/// ])
|
||||
/// );
|
||||
/// let req = test::TestRequest::default()
|
||||
/// .insert_header(Accept(vec![QualityItem::max(mime::APPLICATION_JSON)]))
|
||||
/// .to_http_request();
|
||||
/// # let _ = req;
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// use actix_web::HttpResponse;
|
||||
/// use actix_web::http::header::{Accept, QualityItem, q};
|
||||
/// use actix_web::{http::header::{Accept, Header as _, QualityItem, q}, test};
|
||||
///
|
||||
/// let mut builder = HttpResponse::Ok();
|
||||
/// builder.insert_header(
|
||||
/// Accept(vec![
|
||||
/// let req = test::TestRequest::default()
|
||||
/// .insert_header(Accept(vec![
|
||||
/// QualityItem::max(mime::TEXT_HTML),
|
||||
/// QualityItem::max("application/xhtml+xml".parse().unwrap()),
|
||||
/// QualityItem::new(mime::TEXT_XML, q(0.9)),
|
||||
/// QualityItem::max("image/webp".parse().unwrap()),
|
||||
/// QualityItem::new(mime::STAR_STAR, q(0.8)),
|
||||
/// ])
|
||||
/// );
|
||||
/// ]))
|
||||
/// .to_http_request();
|
||||
///
|
||||
/// let accept = Accept::parse(&req).unwrap();
|
||||
/// assert_eq!(accept.preference(), mime::TEXT_HTML);
|
||||
/// ```
|
||||
///
|
||||
/// [RFC 7231 §5.3.2]: https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.2
|
||||
|
|
|
|||
|
|
@ -10,6 +10,10 @@ common_header! {
|
|||
/// to an origin server that is capable of representing information in
|
||||
/// those charsets.
|
||||
///
|
||||
/// # Note
|
||||
/// This is a request header. Servers should not send `Accept-Charset` in responses; to
|
||||
/// describe the response body's charset, set an appropriate `Content-Type` header instead.
|
||||
///
|
||||
/// # ABNF
|
||||
/// ```plain
|
||||
/// Accept-Charset = 1#( ( charset / "*" ) [ weight ] )
|
||||
|
|
@ -20,36 +24,33 @@ common_header! {
|
|||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use actix_web::HttpResponse;
|
||||
/// use actix_web::http::header::{AcceptCharset, Charset, QualityItem};
|
||||
/// use actix_web::{http::header::{AcceptCharset, Charset, QualityItem}, test};
|
||||
///
|
||||
/// let mut builder = HttpResponse::Ok();
|
||||
/// builder.insert_header(
|
||||
/// AcceptCharset(vec![QualityItem::max(Charset::Us_Ascii)])
|
||||
/// );
|
||||
/// let req = test::TestRequest::default()
|
||||
/// .insert_header(AcceptCharset(vec![QualityItem::max(Charset::Us_Ascii)]))
|
||||
/// .to_http_request();
|
||||
/// # let _ = req;
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// use actix_web::HttpResponse;
|
||||
/// use actix_web::http::header::{AcceptCharset, Charset, q, QualityItem};
|
||||
/// use actix_web::{http::header::{AcceptCharset, Charset, q, QualityItem}, test};
|
||||
///
|
||||
/// let mut builder = HttpResponse::Ok();
|
||||
/// builder.insert_header(
|
||||
/// AcceptCharset(vec![
|
||||
/// let req = test::TestRequest::default()
|
||||
/// .insert_header(AcceptCharset(vec![
|
||||
/// QualityItem::new(Charset::Us_Ascii, q(0.9)),
|
||||
/// QualityItem::new(Charset::Iso_8859_10, q(0.2)),
|
||||
/// ])
|
||||
/// );
|
||||
/// ]))
|
||||
/// .to_http_request();
|
||||
/// # let _ = req;
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// use actix_web::HttpResponse;
|
||||
/// use actix_web::http::header::{AcceptCharset, Charset, QualityItem};
|
||||
/// use actix_web::{http::header::{AcceptCharset, Charset, QualityItem}, test};
|
||||
///
|
||||
/// let mut builder = HttpResponse::Ok();
|
||||
/// builder.insert_header(
|
||||
/// AcceptCharset(vec![QualityItem::max(Charset::Ext("utf-8".to_owned()))])
|
||||
/// );
|
||||
/// let req = test::TestRequest::default()
|
||||
/// .insert_header(AcceptCharset(vec![QualityItem::max(Charset::Ext("utf-8".to_owned()))]))
|
||||
/// .to_http_request();
|
||||
/// # let _ = req;
|
||||
/// ```
|
||||
///
|
||||
/// [RFC 7231 §5.3.3]: https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.3
|
||||
|
|
|
|||
|
|
@ -11,6 +11,11 @@ common_header! {
|
|||
/// content-codings are acceptable in the response. An `identity` token is used as a synonym
|
||||
/// for "no encoding" in order to communicate when no encoding is preferred.
|
||||
///
|
||||
/// # Note
|
||||
/// This is a request header. Servers should not send `Accept-Encoding` in responses; use the
|
||||
/// `Content-Encoding` header (or middleware like compression) to describe any content-coding
|
||||
/// applied to the response body.
|
||||
///
|
||||
/// # ABNF
|
||||
/// ```plain
|
||||
/// Accept-Encoding = #( codings [ weight ] )
|
||||
|
|
@ -26,26 +31,26 @@ common_header! {
|
|||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use actix_web::HttpResponse;
|
||||
/// use actix_web::http::header::{AcceptEncoding, Encoding, Preference, QualityItem};
|
||||
/// use actix_web::{http::header::{AcceptEncoding, Encoding, Preference, QualityItem}, test};
|
||||
///
|
||||
/// let mut builder = HttpResponse::Ok();
|
||||
/// builder.insert_header(
|
||||
/// AcceptEncoding(vec![QualityItem::max(Preference::Specific(Encoding::gzip()))])
|
||||
/// );
|
||||
/// let req = test::TestRequest::default()
|
||||
/// .insert_header(AcceptEncoding(vec![
|
||||
/// QualityItem::max(Preference::Specific(Encoding::gzip())),
|
||||
/// ]))
|
||||
/// .to_http_request();
|
||||
/// # let _ = req;
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// use actix_web::HttpResponse;
|
||||
/// use actix_web::http::header::{AcceptEncoding, Encoding, QualityItem};
|
||||
/// use actix_web::{http::header::{AcceptEncoding, QualityItem}, test};
|
||||
///
|
||||
/// let mut builder = HttpResponse::Ok();
|
||||
/// builder.insert_header(
|
||||
/// AcceptEncoding(vec![
|
||||
/// let req = test::TestRequest::default()
|
||||
/// .insert_header(AcceptEncoding(vec![
|
||||
/// "gzip".parse().unwrap(),
|
||||
/// "br".parse().unwrap(),
|
||||
/// ])
|
||||
/// );
|
||||
/// ]))
|
||||
/// .to_http_request();
|
||||
/// # let _ = req;
|
||||
/// ```
|
||||
(AcceptEncoding, header::ACCEPT_ENCODING) => (QualityItem<Preference<Encoding>>)*
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,10 @@ common_header! {
|
|||
/// [RFC 7231 §5.3.5](https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.5) using language
|
||||
/// ranges defined in [RFC 4647 §2.1](https://datatracker.ietf.org/doc/html/rfc4647#section-2.1).
|
||||
///
|
||||
/// # Note
|
||||
/// This is a request header. Servers should not send `Accept-Language` in responses; use
|
||||
/// `Content-Language` to describe the language of the response body.
|
||||
///
|
||||
/// # ABNF
|
||||
/// ```plain
|
||||
/// Accept-Language = 1#( language-range [ weight ] )
|
||||
|
|
@ -31,29 +35,25 @@ common_header! {
|
|||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use actix_web::HttpResponse;
|
||||
/// use actix_web::http::header::{AcceptLanguage, QualityItem};
|
||||
/// use actix_web::{http::header::{AcceptLanguage, QualityItem}, test};
|
||||
///
|
||||
/// let mut builder = HttpResponse::Ok();
|
||||
/// builder.insert_header(
|
||||
/// AcceptLanguage(vec![
|
||||
/// "en-US".parse().unwrap(),
|
||||
/// ])
|
||||
/// );
|
||||
/// let req = test::TestRequest::default()
|
||||
/// .insert_header(AcceptLanguage(vec!["en-US".parse().unwrap()]))
|
||||
/// .to_http_request();
|
||||
/// # let _ = req;
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// use actix_web::HttpResponse;
|
||||
/// use actix_web::http::header::{AcceptLanguage, QualityItem, q};
|
||||
/// use actix_web::{http::header::{AcceptLanguage, q, QualityItem}, test};
|
||||
///
|
||||
/// let mut builder = HttpResponse::Ok();
|
||||
/// builder.insert_header(
|
||||
/// AcceptLanguage(vec![
|
||||
/// let req = test::TestRequest::default()
|
||||
/// .insert_header(AcceptLanguage(vec![
|
||||
/// "da".parse().unwrap(),
|
||||
/// "en-GB;q=0.8".parse().unwrap(),
|
||||
/// "en;q=0.7".parse().unwrap(),
|
||||
/// ])
|
||||
/// );
|
||||
/// ]))
|
||||
/// .to_http_request();
|
||||
/// # let _ = req;
|
||||
/// ```
|
||||
(AcceptLanguage, header::ACCEPT_LANGUAGE) => (QualityItem<Preference<LanguageTag>>)*
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,11 @@ common_header! {
|
|||
/// intends this precondition to prevent the method from being applied if
|
||||
/// there have been any changes to the representation data.
|
||||
///
|
||||
/// # Note
|
||||
/// This is a request header used for conditional requests (typically to avoid lost updates).
|
||||
/// Servers should not send `If-Match` in responses; use [`ETag`](super::ETag) to describe the
|
||||
/// current representation instead.
|
||||
///
|
||||
/// # ABNF
|
||||
/// ```plain
|
||||
/// If-Match = "*" / 1#entity-tag
|
||||
|
|
@ -27,25 +32,25 @@ common_header! {
|
|||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use actix_web::HttpResponse;
|
||||
/// use actix_web::http::header::IfMatch;
|
||||
/// use actix_web::{http::header::IfMatch, test};
|
||||
///
|
||||
/// let mut builder = HttpResponse::Ok();
|
||||
/// builder.insert_header(IfMatch::Any);
|
||||
/// let req = test::TestRequest::default()
|
||||
/// .insert_header(IfMatch::Any)
|
||||
/// .to_http_request();
|
||||
/// # let _ = req;
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// use actix_web::HttpResponse;
|
||||
/// use actix_web::http::header::{IfMatch, EntityTag};
|
||||
/// use actix_web::{http::header::{EntityTag, IfMatch}, test};
|
||||
///
|
||||
/// let mut builder = HttpResponse::Ok();
|
||||
/// builder.insert_header(
|
||||
/// IfMatch::Items(vec![
|
||||
/// let req = test::TestRequest::default()
|
||||
/// .insert_header(IfMatch::Items(vec![
|
||||
/// EntityTag::new(false, "xyzzy".to_owned()),
|
||||
/// EntityTag::new(false, "foobar".to_owned()),
|
||||
/// EntityTag::new(false, "bazquux".to_owned()),
|
||||
/// ])
|
||||
/// );
|
||||
/// ]))
|
||||
/// .to_http_request();
|
||||
/// # let _ = req;
|
||||
/// ```
|
||||
(IfMatch, IF_MATCH) => {Any / (EntityTag)+}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,9 +10,14 @@ crate::http::header::common_header! {
|
|||
/// Transfer of the selected representation's data is avoided if that
|
||||
/// data has not changed.
|
||||
///
|
||||
/// # Note
|
||||
/// This is a request header used for cache validation. Servers should not send
|
||||
/// `If-Modified-Since` in responses; use [`LastModified`](super::LastModified) / the
|
||||
/// `Last-Modified` header instead.
|
||||
///
|
||||
/// # ABNF
|
||||
/// ```plain
|
||||
/// If-Unmodified-Since = HTTP-date
|
||||
/// If-Modified-Since = HTTP-date
|
||||
/// ```
|
||||
///
|
||||
/// # Example Values
|
||||
|
|
@ -22,14 +27,13 @@ crate::http::header::common_header! {
|
|||
///
|
||||
/// ```
|
||||
/// use std::time::{SystemTime, Duration};
|
||||
/// use actix_web::HttpResponse;
|
||||
/// use actix_web::http::header::IfModifiedSince;
|
||||
/// use actix_web::{http::header::IfModifiedSince, test};
|
||||
///
|
||||
/// let mut builder = HttpResponse::Ok();
|
||||
/// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24);
|
||||
/// builder.insert_header(
|
||||
/// IfModifiedSince(modified.into())
|
||||
/// );
|
||||
/// let req = test::TestRequest::default()
|
||||
/// .insert_header(IfModifiedSince(modified.into()))
|
||||
/// .to_http_request();
|
||||
/// # let _ = req;
|
||||
/// ```
|
||||
(IfModifiedSince, IF_MODIFIED_SINCE) => [HttpDate]
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,11 @@ crate::http::header::common_header! {
|
|||
/// can be used for cache validation even if there have been changes to
|
||||
/// the representation data.
|
||||
///
|
||||
/// # Note
|
||||
/// This is a request header used for cache validation (and conditional requests). Servers
|
||||
/// should not send `If-None-Match` in responses; use [`ETag`](super::ETag) to describe the
|
||||
/// current representation instead.
|
||||
///
|
||||
/// # ABNF
|
||||
/// ```plain
|
||||
/// If-None-Match = "*" / 1#entity-tag
|
||||
|
|
@ -29,25 +34,25 @@ crate::http::header::common_header! {
|
|||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use actix_web::HttpResponse;
|
||||
/// use actix_web::http::header::IfNoneMatch;
|
||||
/// use actix_web::{http::header::IfNoneMatch, test};
|
||||
///
|
||||
/// let mut builder = HttpResponse::Ok();
|
||||
/// builder.insert_header(IfNoneMatch::Any);
|
||||
/// let req = test::TestRequest::default()
|
||||
/// .insert_header(IfNoneMatch::Any)
|
||||
/// .to_http_request();
|
||||
/// # let _ = req;
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// use actix_web::HttpResponse;
|
||||
/// use actix_web::http::header::{IfNoneMatch, EntityTag};
|
||||
/// use actix_web::{http::header::{EntityTag, IfNoneMatch}, test};
|
||||
///
|
||||
/// let mut builder = HttpResponse::Ok();
|
||||
/// builder.insert_header(
|
||||
/// IfNoneMatch::Items(vec![
|
||||
/// let req = test::TestRequest::default()
|
||||
/// .insert_header(IfNoneMatch::Items(vec![
|
||||
/// EntityTag::new(false, "xyzzy".to_owned()),
|
||||
/// EntityTag::new(false, "foobar".to_owned()),
|
||||
/// EntityTag::new(false, "bazquux".to_owned()),
|
||||
/// ])
|
||||
/// );
|
||||
/// ]))
|
||||
/// .to_http_request();
|
||||
/// # let _ = req;
|
||||
/// ```
|
||||
(IfNoneMatch, IF_NONE_MATCH) => {Any / (EntityTag)+}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,9 @@ use crate::{error::ParseError, http::header, HttpMessage};
|
|||
/// representation is unchanged, send me the part(s) that I am requesting
|
||||
/// in Range; otherwise, send me the entire representation.
|
||||
///
|
||||
/// # Note
|
||||
/// This is a request header. Servers should not send `If-Range` in responses.
|
||||
///
|
||||
/// # ABNF
|
||||
/// ```plain
|
||||
/// If-Range = entity-tag / HTTP-date
|
||||
|
|
@ -34,26 +37,23 @@ use crate::{error::ParseError, http::header, HttpMessage};
|
|||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use actix_web::HttpResponse;
|
||||
/// use actix_web::http::header::{EntityTag, IfRange};
|
||||
/// use actix_web::{http::header::{EntityTag, IfRange}, test};
|
||||
///
|
||||
/// let mut builder = HttpResponse::Ok();
|
||||
/// builder.insert_header(
|
||||
/// IfRange::EntityTag(
|
||||
/// EntityTag::new(false, "abc".to_owned())
|
||||
/// )
|
||||
/// );
|
||||
/// let req = test::TestRequest::default()
|
||||
/// .insert_header(IfRange::EntityTag(EntityTag::new(false, "abc".to_owned())))
|
||||
/// .to_http_request();
|
||||
/// # let _ = req;
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// use std::time::{Duration, SystemTime};
|
||||
/// use actix_web::{http::header::IfRange, HttpResponse};
|
||||
/// use actix_web::{http::header::IfRange, test};
|
||||
///
|
||||
/// let mut builder = HttpResponse::Ok();
|
||||
/// let fetched = SystemTime::now() - Duration::from_secs(60 * 60 * 24);
|
||||
/// builder.insert_header(
|
||||
/// IfRange::Date(fetched.into())
|
||||
/// );
|
||||
/// let req = test::TestRequest::default()
|
||||
/// .insert_header(IfRange::Date(fetched.into()))
|
||||
/// .to_http_request();
|
||||
/// # let _ = req;
|
||||
/// ```
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum IfRange {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,11 @@ crate::http::header::common_header! {
|
|||
/// This field accomplishes the same purpose as If-Match for cases where
|
||||
/// the user agent does not have an entity-tag for the representation.
|
||||
///
|
||||
/// # Note
|
||||
/// This is a request header used for conditional requests. Servers should not send
|
||||
/// `If-Unmodified-Since` in responses; use [`LastModified`](super::LastModified) / the
|
||||
/// `Last-Modified` header instead.
|
||||
///
|
||||
/// # ABNF
|
||||
/// ```plain
|
||||
/// If-Unmodified-Since = HTTP-date
|
||||
|
|
@ -22,14 +27,13 @@ crate::http::header::common_header! {
|
|||
///
|
||||
/// ```
|
||||
/// use std::time::{SystemTime, Duration};
|
||||
/// use actix_web::HttpResponse;
|
||||
/// use actix_web::http::header::IfUnmodifiedSince;
|
||||
/// use actix_web::{http::header::IfUnmodifiedSince, test};
|
||||
///
|
||||
/// let mut builder = HttpResponse::Ok();
|
||||
/// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24);
|
||||
/// builder.insert_header(
|
||||
/// IfUnmodifiedSince(modified.into())
|
||||
/// );
|
||||
/// let req = test::TestRequest::default()
|
||||
/// .insert_header(IfUnmodifiedSince(modified.into()))
|
||||
/// .to_http_request();
|
||||
/// # let _ = req;
|
||||
/// ```
|
||||
(IfUnmodifiedSince, IF_UNMODIFIED_SINCE) => [HttpDate]
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,10 @@ use super::{Header, HeaderName, HeaderValue, InvalidHeaderValue, TryIntoHeaderVa
|
|||
/// only one or more sub-ranges of the selected representation data, rather than the entire selected
|
||||
/// representation data.
|
||||
///
|
||||
/// # Note
|
||||
/// This is a request header. Servers should not send `Range` in responses; use
|
||||
/// [`ContentRange`](super::ContentRange) / the `Content-Range` header for partial responses.
|
||||
///
|
||||
/// # ABNF
|
||||
/// ```plain
|
||||
/// Range = byte-ranges-specifier / other-ranges-specifier
|
||||
|
|
@ -42,16 +46,18 @@ use super::{Header, HeaderName, HeaderValue, InvalidHeaderValue, TryIntoHeaderVa
|
|||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use actix_web::http::header::{Range, ByteRangeSpec};
|
||||
/// use actix_web::HttpResponse;
|
||||
/// use actix_web::{http::header::{ByteRangeSpec, Range}, test};
|
||||
///
|
||||
/// let mut builder = HttpResponse::Ok();
|
||||
/// builder.insert_header(Range::Bytes(
|
||||
/// vec![ByteRangeSpec::FromTo(1, 100), ByteRangeSpec::From(200)]
|
||||
/// ));
|
||||
/// builder.insert_header(Range::Unregistered("letters".to_owned(), "a-f".to_owned()));
|
||||
/// builder.insert_header(Range::bytes(1, 100));
|
||||
/// builder.insert_header(Range::bytes_multi(vec![(1, 100), (200, 300)]));
|
||||
/// let req = test::TestRequest::default()
|
||||
/// .insert_header(Range::Bytes(vec![
|
||||
/// ByteRangeSpec::FromTo(1, 100),
|
||||
/// ByteRangeSpec::From(200),
|
||||
/// ]))
|
||||
/// .insert_header(Range::Unregistered("letters".to_owned(), "a-f".to_owned()))
|
||||
/// .insert_header(Range::bytes(1, 100))
|
||||
/// .insert_header(Range::bytes_multi(vec![(1, 100), (200, 300)]))
|
||||
/// .to_http_request();
|
||||
/// # let _ = req;
|
||||
/// ```
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Range {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
//! For middleware documentation, see [`NormalizePath`].
|
||||
|
||||
use actix_http::uri::{PathAndQuery, Uri};
|
||||
use actix_router::Url;
|
||||
use actix_service::{Service, Transform};
|
||||
use actix_utils::future::{ready, Ready};
|
||||
use bytes::Bytes;
|
||||
|
|
@ -14,6 +15,28 @@ use crate::{
|
|||
Error,
|
||||
};
|
||||
|
||||
fn build_byte_index_map(old_path: &str, new_path: &str) -> Vec<u16> {
|
||||
let old_path = old_path.as_bytes();
|
||||
let new_path = new_path.as_bytes();
|
||||
|
||||
let mut map = Vec::with_capacity(old_path.len() + 1);
|
||||
map.push(0);
|
||||
|
||||
let mut old_idx = 0usize;
|
||||
let mut new_idx = 0usize;
|
||||
|
||||
while old_idx < old_path.len() {
|
||||
if new_idx < new_path.len() && old_path[old_idx] == new_path[new_idx] {
|
||||
new_idx += 1;
|
||||
}
|
||||
|
||||
old_idx += 1;
|
||||
map.push(new_idx.min(u16::MAX as usize) as u16);
|
||||
}
|
||||
|
||||
map
|
||||
}
|
||||
|
||||
/// Determines the behavior of the [`NormalizePath`] middleware.
|
||||
///
|
||||
/// The default is `TrailingSlash::Trim`.
|
||||
|
|
@ -183,6 +206,7 @@ where
|
|||
// Both of the paths have the same length,
|
||||
// so the change can not be deduced from the length comparison
|
||||
if path != original_path {
|
||||
let reindex = build_byte_index_map(original_path, path);
|
||||
let mut parts = head.uri.clone().into_parts();
|
||||
let query = parts.path_and_query.as_ref().and_then(|pq| pq.query());
|
||||
|
||||
|
|
@ -193,7 +217,11 @@ where
|
|||
parts.path_and_query = Some(PathAndQuery::from_maybe_shared(path).unwrap());
|
||||
|
||||
let uri = Uri::from_parts(parts).unwrap();
|
||||
req.match_info_mut().get_mut().update(&uri);
|
||||
req.match_info_mut()
|
||||
.update_with_reindex(Url::new(uri.clone()), |idx| {
|
||||
let idx = usize::from(idx).min(reindex.len() - 1);
|
||||
reindex[idx]
|
||||
});
|
||||
req.head_mut().uri = uri;
|
||||
}
|
||||
}
|
||||
|
|
@ -209,7 +237,7 @@ mod tests {
|
|||
use super::*;
|
||||
use crate::{
|
||||
guard::fn_guard,
|
||||
test::{call_service, init_service, TestRequest},
|
||||
test::{call_service, init_service, read_body, TestRequest},
|
||||
web, App, HttpResponse,
|
||||
};
|
||||
|
||||
|
|
@ -406,6 +434,45 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn scope_dynamic_tail_path_is_reindexed() {
|
||||
async fn handler(path: web::Path<String>) -> HttpResponse {
|
||||
HttpResponse::Ok().body(path.into_inner())
|
||||
}
|
||||
|
||||
let app = init_service(
|
||||
App::new().service(
|
||||
web::scope("{tail:.*}")
|
||||
.wrap(NormalizePath::trim())
|
||||
.default_service(web::to(handler)),
|
||||
),
|
||||
)
|
||||
.await;
|
||||
|
||||
let req = TestRequest::with_uri("/uaie//iuaei").to_request();
|
||||
let res = call_service(&app, req).await;
|
||||
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
assert_eq!(read_body(res).await, Bytes::from_static(b"uaie/iuaei"));
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn scope_static_prefix_skip_is_reindexed() {
|
||||
let app = init_service(
|
||||
App::new().service(
|
||||
web::scope("/api")
|
||||
.wrap(NormalizePath::trim())
|
||||
.service(web::resource("/v1").to(HttpResponse::Ok)),
|
||||
),
|
||||
)
|
||||
.await;
|
||||
|
||||
let req = TestRequest::with_uri("/api//v1").to_request();
|
||||
let res = call_service(&app, req).await;
|
||||
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn no_path() {
|
||||
let app = init_service(
|
||||
|
|
|
|||
Loading…
Reference in New Issue