mirror of
https://github.com/actix/actix-extras.git
synced 2025-02-12 22:55:32 +01:00
Release 0.3.0
This commit is contained in:
parent
aad60744ed
commit
0ebfd790fc
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-web-httpauth"
|
name = "actix-web-httpauth"
|
||||||
version = "0.3.0-alpha.2"
|
version = "0.3.0"
|
||||||
authors = ["svartalf <self@svartalf.info>"]
|
authors = ["svartalf <self@svartalf.info>"]
|
||||||
description = "HTTP authentication schemes for actix-web"
|
description = "HTTP authentication schemes for actix-web"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
@ -14,7 +14,7 @@ exclude = [".travis.yml", ".gitignore"]
|
|||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-web = { version = "1.0.0-beta.5", default_features = false }
|
actix-web = { version = "^1.0", default_features = false }
|
||||||
futures = "0.1"
|
futures = "0.1"
|
||||||
actix-service = "0.4.0"
|
actix-service = "0.4.0"
|
||||||
bytes = "0.4"
|
bytes = "0.4"
|
||||||
|
10
README.md
10
README.md
@ -2,13 +2,19 @@
|
|||||||
|
|
||||||
[![Latest Version](https://img.shields.io/crates/v/actix-web-httpauth.svg)](https://crates.io/crates/actix-web-httpauth)
|
[![Latest Version](https://img.shields.io/crates/v/actix-web-httpauth.svg)](https://crates.io/crates/actix-web-httpauth)
|
||||||
[![Latest Version](https://docs.rs/actix-web-httpauth/badge.svg)](https://docs.rs/actix-web-httpauth)
|
[![Latest Version](https://docs.rs/actix-web-httpauth/badge.svg)](https://docs.rs/actix-web-httpauth)
|
||||||
|
[![dependency status](https://deps.rs/crate/actix-web-httpauth/0.3.0/status.svg)](https://deps.rs/crate/actix-web-httpauth/0.3.0)
|
||||||
![Build Status](https://travis-ci.org/svartalf/actix-web-httpauth.svg?branch=master)
|
![Build Status](https://travis-ci.org/svartalf/actix-web-httpauth.svg?branch=master)
|
||||||
![Apache 2.0 OR MIT licensed](https://img.shields.io/badge/license-Apache2.0%2FMIT-blue.svg)
|
![Apache 2.0 OR MIT licensed](https://img.shields.io/badge/license-Apache2.0%2FMIT-blue.svg)
|
||||||
|
|
||||||
HTTP authentication schemes for [actix-web](https://github.com/actix/actix-web) framework.
|
HTTP authentication schemes for [actix-web](https://github.com/actix/actix-web) framework.
|
||||||
|
|
||||||
All supported schemas are actix [Extractors](https://docs.rs/actix-web/0.6.7/actix_web/trait.FromRequest.html),
|
Provides:
|
||||||
and can be used both in middlewares and request handlers, check the `examples/` folder.
|
* typed [Authorization] and [WWW-Authenticate] headers
|
||||||
|
* [extractors] for an [Authorization] header
|
||||||
|
* [middleware] for easier authorization checking
|
||||||
|
|
||||||
|
All supported schemas are actix [Extractors](https://docs.rs/actix-web/1.0.0/actix_web/trait.FromRequest.html),
|
||||||
|
and can be used both in the middlewares and request handlers.
|
||||||
|
|
||||||
## Supported schemes
|
## Supported schemes
|
||||||
|
|
||||||
|
@ -10,7 +10,6 @@ use bytes::{BufMut, BytesMut};
|
|||||||
|
|
||||||
use crate::headers::authorization::errors::ParseError;
|
use crate::headers::authorization::errors::ParseError;
|
||||||
use crate::headers::authorization::Scheme;
|
use crate::headers::authorization::Scheme;
|
||||||
use crate::utils;
|
|
||||||
|
|
||||||
/// Credentials for `Basic` authentication scheme, defined in [RFC 7617](https://tools.ietf.org/html/rfc7617)
|
/// Credentials for `Basic` authentication scheme, defined in [RFC 7617](https://tools.ietf.org/html/rfc7617)
|
||||||
#[derive(Clone, Eq, Ord, PartialEq, PartialOrd)]
|
#[derive(Clone, Eq, Ord, PartialEq, PartialOrd)]
|
||||||
@ -107,13 +106,14 @@ impl IntoHeaderValue for Basic {
|
|||||||
fn try_into(self) -> Result<HeaderValue, <Self as IntoHeaderValue>::Error> {
|
fn try_into(self) -> Result<HeaderValue, <Self as IntoHeaderValue>::Error> {
|
||||||
let mut credentials = BytesMut::with_capacity(
|
let mut credentials = BytesMut::with_capacity(
|
||||||
self.user_id.len()
|
self.user_id.len()
|
||||||
+ 1
|
+ 1 // ':'
|
||||||
+ self.password.as_ref().map_or(0, |pwd| pwd.len()),
|
+ self.password.as_ref().map_or(0, |pwd| pwd.len()),
|
||||||
);
|
);
|
||||||
utils::put_cow(&mut credentials, &self.user_id);
|
|
||||||
|
credentials.extend_from_slice(self.user_id.as_bytes());
|
||||||
credentials.put_u8(b':');
|
credentials.put_u8(b':');
|
||||||
if let Some(ref password) = self.password {
|
if let Some(ref password) = self.password {
|
||||||
utils::put_cow(&mut credentials, password);
|
credentials.extend_from_slice(password.as_bytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: It would be nice not to allocate new `String` here but write
|
// TODO: It would be nice not to allocate new `String` here but write
|
||||||
|
@ -8,7 +8,6 @@ use bytes::{BufMut, BytesMut};
|
|||||||
|
|
||||||
use crate::headers::authorization::errors::ParseError;
|
use crate::headers::authorization::errors::ParseError;
|
||||||
use crate::headers::authorization::scheme::Scheme;
|
use crate::headers::authorization::scheme::Scheme;
|
||||||
use crate::utils;
|
|
||||||
|
|
||||||
/// Credentials for `Bearer` authentication scheme, defined in [RFC6750](https://tools.ietf.org/html/rfc6750)
|
/// Credentials for `Bearer` authentication scheme, defined in [RFC6750](https://tools.ietf.org/html/rfc6750)
|
||||||
///
|
///
|
||||||
@ -82,7 +81,7 @@ impl IntoHeaderValue for Bearer {
|
|||||||
fn try_into(self) -> Result<HeaderValue, <Self as IntoHeaderValue>::Error> {
|
fn try_into(self) -> Result<HeaderValue, <Self as IntoHeaderValue>::Error> {
|
||||||
let mut buffer = BytesMut::with_capacity(7 + self.token.len());
|
let mut buffer = BytesMut::with_capacity(7 + self.token.len());
|
||||||
buffer.put("Bearer ");
|
buffer.put("Bearer ");
|
||||||
utils::put_cow(&mut buffer, &self.token);
|
buffer.extend_from_slice(self.token.as_bytes());
|
||||||
|
|
||||||
HeaderValue::from_shared(buffer.freeze())
|
HeaderValue::from_shared(buffer.freeze())
|
||||||
}
|
}
|
||||||
|
@ -85,7 +85,7 @@ impl Challenge for Basic {
|
|||||||
buffer.put("Basic");
|
buffer.put("Basic");
|
||||||
if let Some(ref realm) = self.realm {
|
if let Some(ref realm) = self.realm {
|
||||||
buffer.put(" realm=\"");
|
buffer.put(" realm=\"");
|
||||||
utils::put_cow(&mut buffer, realm);
|
utils::put_quoted(&mut buffer, realm);
|
||||||
buffer.put("\"");
|
buffer.put("\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,13 +82,13 @@ impl Challenge for Bearer {
|
|||||||
|
|
||||||
if let Some(ref realm) = self.realm {
|
if let Some(ref realm) = self.realm {
|
||||||
buffer.put(" realm=\"");
|
buffer.put(" realm=\"");
|
||||||
utils::put_cow(&mut buffer, realm);
|
utils::put_quoted(&mut buffer, realm);
|
||||||
buffer.put("\"");
|
buffer.put("\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(ref scope) = self.scope {
|
if let Some(ref scope) = self.scope {
|
||||||
buffer.put(" scope=\"");
|
buffer.put(" scope=\"");
|
||||||
utils::put_cow(&mut buffer, scope);
|
utils::put_quoted(&mut buffer, scope);
|
||||||
buffer.put("\"");
|
buffer.put("\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,19 +100,19 @@ impl Challenge for Bearer {
|
|||||||
buffer.reserve(required);
|
buffer.reserve(required);
|
||||||
}
|
}
|
||||||
buffer.put(" error=\"");
|
buffer.put(" error=\"");
|
||||||
buffer.put(error_repr);
|
utils::put_quoted(&mut buffer, error_repr);
|
||||||
buffer.put("\"")
|
buffer.put("\"")
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(ref error_description) = self.error_description {
|
if let Some(ref error_description) = self.error_description {
|
||||||
buffer.put(" error_description=\"");
|
buffer.put(" error_description=\"");
|
||||||
utils::put_cow(&mut buffer, error_description);
|
utils::put_quoted(&mut buffer, error_description);
|
||||||
buffer.put("\"");
|
buffer.put("\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(ref error_uri) = self.error_uri {
|
if let Some(ref error_uri) = self.error_uri {
|
||||||
buffer.put(" error_uri=\"");
|
buffer.put(" error_uri=\"");
|
||||||
utils::put_cow(&mut buffer, error_uri);
|
utils::put_quoted(&mut buffer, error_uri);
|
||||||
buffer.put("\"");
|
buffer.put("\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,27 +16,31 @@ use crate::extractors::{basic, bearer, AuthExtractor};
|
|||||||
/// If there is no `Authorization` header in the request,
|
/// If there is no `Authorization` header in the request,
|
||||||
/// this middleware returns an error immediately,
|
/// this middleware returns an error immediately,
|
||||||
/// without calling the `F` callback.
|
/// without calling the `F` callback.
|
||||||
/// Otherwise, it will pass parsed credentials into it.
|
///
|
||||||
|
/// Otherwise, it will pass both the request and
|
||||||
|
/// the parsed credentials into it.
|
||||||
|
/// In case of successful validation `F` callback
|
||||||
|
/// is required to return the `ServiceRequest` back.
|
||||||
pub struct HttpAuthentication<T, F>
|
pub struct HttpAuthentication<T, F>
|
||||||
where
|
where
|
||||||
T: AuthExtractor,
|
T: AuthExtractor,
|
||||||
{
|
{
|
||||||
validator_fn: Rc<F>,
|
process_fn: Rc<F>,
|
||||||
_extractor: PhantomData<T>,
|
_extractor: PhantomData<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, F, O> HttpAuthentication<T, F>
|
impl<T, F, O> HttpAuthentication<T, F>
|
||||||
where
|
where
|
||||||
T: AuthExtractor,
|
T: AuthExtractor,
|
||||||
F: FnMut(&mut ServiceRequest, T) -> O,
|
F: FnMut(ServiceRequest, T) -> O,
|
||||||
O: IntoFuture<Item = (), Error = Error>,
|
O: IntoFuture<Item = ServiceRequest, Error = Error>,
|
||||||
{
|
{
|
||||||
/// Construct `HttpAuthentication` middleware
|
/// Construct `HttpAuthentication` middleware
|
||||||
/// with the provided auth extractor `T` and
|
/// with the provided auth extractor `T` and
|
||||||
/// validation callback `F`.
|
/// validation callback `F`.
|
||||||
pub fn with_fn(validator_fn: F) -> HttpAuthentication<T, F> {
|
pub fn with_fn(process_fn: F) -> HttpAuthentication<T, F> {
|
||||||
HttpAuthentication {
|
HttpAuthentication {
|
||||||
validator_fn: Rc::new(validator_fn),
|
process_fn: Rc::new(process_fn),
|
||||||
_extractor: PhantomData,
|
_extractor: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -44,15 +48,15 @@ where
|
|||||||
|
|
||||||
impl<F, O> HttpAuthentication<basic::BasicAuth, F>
|
impl<F, O> HttpAuthentication<basic::BasicAuth, F>
|
||||||
where
|
where
|
||||||
F: FnMut(&mut ServiceRequest, basic::BasicAuth) -> O,
|
F: FnMut(ServiceRequest, basic::BasicAuth) -> O,
|
||||||
O: IntoFuture<Item = (), Error = Error>,
|
O: IntoFuture<Item = ServiceRequest, Error = Error>,
|
||||||
{
|
{
|
||||||
/// Construct `HttpAuthentication` middleware for the HTTP "Basic"
|
/// Construct `HttpAuthentication` middleware for the HTTP "Basic"
|
||||||
/// authentication scheme.
|
/// authentication scheme.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```rust
|
||||||
/// # use actix_web::Error;
|
/// # use actix_web::Error;
|
||||||
/// # use actix_web::dev::ServiceRequest;
|
/// # use actix_web::dev::ServiceRequest;
|
||||||
/// # use futures::future::{self, FutureResult};
|
/// # use futures::future::{self, FutureResult};
|
||||||
@ -64,39 +68,40 @@ where
|
|||||||
/// // it can be extended to query database
|
/// // it can be extended to query database
|
||||||
/// // or to do something else in a async manner.
|
/// // or to do something else in a async manner.
|
||||||
/// fn validator(
|
/// fn validator(
|
||||||
/// req: &mut ServiceRequest,
|
/// req: ServiceRequest,
|
||||||
/// credentials: BasicAuth,
|
/// credentials: BasicAuth,
|
||||||
/// ) -> FutureResult<(), Error> {
|
/// ) -> FutureResult<ServiceRequest, Error> {
|
||||||
/// // All users are great and more than welcome!
|
/// // All users are great and more than welcome!
|
||||||
/// future::ok(())
|
/// future::ok(req)
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// let middleware = HttpAuthentication::basic(validator);
|
/// let middleware = HttpAuthentication::basic(validator);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn basic(validator_fn: F) -> Self {
|
pub fn basic(process_fn: F) -> Self {
|
||||||
Self::with_fn(validator_fn)
|
Self::with_fn(process_fn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<F, O> HttpAuthentication<bearer::BearerAuth, F>
|
impl<F, O> HttpAuthentication<bearer::BearerAuth, F>
|
||||||
where
|
where
|
||||||
F: FnMut(&mut ServiceRequest, bearer::BearerAuth) -> O,
|
F: FnMut(ServiceRequest, bearer::BearerAuth) -> O,
|
||||||
O: IntoFuture<Item = (), Error = Error>,
|
O: IntoFuture<Item = ServiceRequest, Error = Error>,
|
||||||
{
|
{
|
||||||
/// Construct `HttpAuthentication` middleware for the HTTP "Bearer"
|
/// Construct `HttpAuthentication` middleware for the HTTP "Bearer"
|
||||||
/// authentication scheme.
|
/// authentication scheme.
|
||||||
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```rust
|
||||||
/// # use actix_web::Error;
|
/// # use actix_web::Error;
|
||||||
/// # use actix_web::dev::ServiceRequest;
|
/// # use actix_web::dev::ServiceRequest;
|
||||||
/// # use futures::future::{self, FutureResult};
|
/// # use futures::future::{self, FutureResult};
|
||||||
/// # use actix_web_httpauth::middleware::HttpAuthentication;
|
/// # use actix_web_httpauth::middleware::HttpAuthentication;
|
||||||
/// # use actix_web_httpauth::extractors::bearer::{Config, BearerAuth};
|
/// # use actix_web_httpauth::extractors::bearer::{Config, BearerAuth};
|
||||||
/// # use actix_web_httpauth::extractors::{AuthenticationError, AuthExtractorConfig};
|
/// # use actix_web_httpauth::extractors::{AuthenticationError, AuthExtractorConfig};
|
||||||
/// fn validator(req: &mut ServiceRequest, credentials: BearerAuth) -> FutureResult<(), Error> {
|
/// fn validator(req: ServiceRequest, credentials: BearerAuth) -> FutureResult<ServiceRequest, Error> {
|
||||||
/// if credentials.token() == "mF_9.B5f-4.1JqM" {
|
/// if credentials.token() == "mF_9.B5f-4.1JqM" {
|
||||||
/// future::ok(())
|
/// future::ok(req)
|
||||||
/// } else {
|
/// } else {
|
||||||
/// let config = req.app_data::<Config>()
|
/// let config = req.app_data::<Config>()
|
||||||
/// .map(|data| data.get_ref().clone())
|
/// .map(|data| data.get_ref().clone())
|
||||||
@ -109,8 +114,8 @@ where
|
|||||||
///
|
///
|
||||||
/// let middleware = HttpAuthentication::bearer(validator);
|
/// let middleware = HttpAuthentication::bearer(validator);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn bearer(validator_fn: F) -> Self {
|
pub fn bearer(process_fn: F) -> Self {
|
||||||
Self::with_fn(validator_fn)
|
Self::with_fn(process_fn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,8 +127,8 @@ where
|
|||||||
Error = Error,
|
Error = Error,
|
||||||
> + 'static,
|
> + 'static,
|
||||||
S::Future: 'static,
|
S::Future: 'static,
|
||||||
F: Fn(&mut ServiceRequest, T) -> O + 'static,
|
F: Fn(ServiceRequest, T) -> O + 'static,
|
||||||
O: IntoFuture<Item = (), Error = Error> + 'static,
|
O: IntoFuture<Item = ServiceRequest, Error = Error> + 'static,
|
||||||
T: AuthExtractor + 'static,
|
T: AuthExtractor + 'static,
|
||||||
{
|
{
|
||||||
type Request = ServiceRequest;
|
type Request = ServiceRequest;
|
||||||
@ -136,7 +141,7 @@ where
|
|||||||
fn new_transform(&self, service: S) -> Self::Future {
|
fn new_transform(&self, service: S) -> Self::Future {
|
||||||
future::ok(AuthenticationMiddleware {
|
future::ok(AuthenticationMiddleware {
|
||||||
service: Some(service),
|
service: Some(service),
|
||||||
validator_fn: self.validator_fn.clone(),
|
process_fn: self.process_fn.clone(),
|
||||||
_extractor: PhantomData,
|
_extractor: PhantomData,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -148,7 +153,7 @@ where
|
|||||||
T: AuthExtractor,
|
T: AuthExtractor,
|
||||||
{
|
{
|
||||||
service: Option<S>,
|
service: Option<S>,
|
||||||
validator_fn: Rc<F>,
|
process_fn: Rc<F>,
|
||||||
_extractor: PhantomData<T>,
|
_extractor: PhantomData<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,8 +165,8 @@ where
|
|||||||
Error = Error,
|
Error = Error,
|
||||||
> + 'static,
|
> + 'static,
|
||||||
S::Future: 'static,
|
S::Future: 'static,
|
||||||
F: Fn(&mut ServiceRequest, T) -> O + 'static,
|
F: Fn(ServiceRequest, T) -> O + 'static,
|
||||||
O: IntoFuture<Item = (), Error = Error> + 'static,
|
O: IntoFuture<Item = ServiceRequest, Error = Error> + 'static,
|
||||||
T: AuthExtractor + 'static,
|
T: AuthExtractor + 'static,
|
||||||
{
|
{
|
||||||
type Request = ServiceRequest;
|
type Request = ServiceRequest;
|
||||||
@ -177,7 +182,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn call(&mut self, req: Self::Request) -> Self::Future {
|
fn call(&mut self, req: Self::Request) -> Self::Future {
|
||||||
let validator_fn = self.validator_fn.clone();
|
let process_fn = self.process_fn.clone();
|
||||||
let mut service = self
|
let mut service = self
|
||||||
.service
|
.service
|
||||||
.take()
|
.take()
|
||||||
@ -185,7 +190,7 @@ where
|
|||||||
|
|
||||||
let f = Extract::new(req)
|
let f = Extract::new(req)
|
||||||
.and_then(move |(req, credentials)| {
|
.and_then(move |(req, credentials)| {
|
||||||
Validate::new(req, validator_fn, credentials)
|
(process_fn)(req, credentials)
|
||||||
})
|
})
|
||||||
.and_then(move |req| service.call(req));
|
.and_then(move |req| service.call(req));
|
||||||
|
|
||||||
@ -238,60 +243,3 @@ where
|
|||||||
Ok(Async::Ready((req, credentials)))
|
Ok(Async::Ready((req, credentials)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Validate<F, T> {
|
|
||||||
req: Option<ServiceRequest>,
|
|
||||||
validation_f: Option<Box<dyn Future<Item = (), Error = Error>>>,
|
|
||||||
validator_fn: Rc<F>,
|
|
||||||
credentials: Option<T>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<F, T> Validate<F, T> {
|
|
||||||
pub fn new(
|
|
||||||
req: ServiceRequest,
|
|
||||||
validator_fn: Rc<F>,
|
|
||||||
credentials: T,
|
|
||||||
) -> Self {
|
|
||||||
Validate {
|
|
||||||
req: Some(req),
|
|
||||||
credentials: Some(credentials),
|
|
||||||
validator_fn,
|
|
||||||
validation_f: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<F, T, O> Future for Validate<F, T>
|
|
||||||
where
|
|
||||||
F: Fn(&mut ServiceRequest, T) -> O,
|
|
||||||
O: IntoFuture<Item = (), Error = Error> + 'static,
|
|
||||||
{
|
|
||||||
type Item = ServiceRequest;
|
|
||||||
type Error = Error;
|
|
||||||
|
|
||||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
|
||||||
if self.validation_f.is_none() {
|
|
||||||
let req = self
|
|
||||||
.req
|
|
||||||
.as_mut()
|
|
||||||
.expect("Unable to get the mutable access to the request");
|
|
||||||
let credentials = self
|
|
||||||
.credentials
|
|
||||||
.take()
|
|
||||||
.expect("Validate future was polled in some weird manner");
|
|
||||||
let f = (self.validator_fn)(req, credentials).into_future();
|
|
||||||
|
|
||||||
self.validation_f = Some(Box::new(f));
|
|
||||||
}
|
|
||||||
|
|
||||||
let f = self
|
|
||||||
.validation_f
|
|
||||||
.as_mut()
|
|
||||||
.expect("Validation future should exist at this moment");
|
|
||||||
// We do not care about returned `Ok(())`
|
|
||||||
futures::try_ready!(f.poll());
|
|
||||||
let req = self.req.take().expect("Validate future was polled already");
|
|
||||||
|
|
||||||
Ok(Async::Ready(req))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
121
src/utils.rs
121
src/utils.rs
@ -1,14 +1,115 @@
|
|||||||
use std::borrow::Cow;
|
use std::str;
|
||||||
|
|
||||||
use bytes::{BufMut, BytesMut};
|
use bytes::BytesMut;
|
||||||
|
|
||||||
// `bytes::Buf` is not implemented for `Cow<'static, str>`, implementing it by
|
enum State {
|
||||||
// ourselves.
|
YieldStr,
|
||||||
#[inline]
|
YieldQuote,
|
||||||
#[allow(clippy::ptr_arg)] // Totally okay to accept the reference to Cow here
|
}
|
||||||
pub fn put_cow(buf: &mut BytesMut, value: &Cow<'static, str>) {
|
|
||||||
match value {
|
struct Quoted<'a> {
|
||||||
Cow::Borrowed(str) => buf.put(str),
|
inner: ::std::iter::Peekable<str::Split<'a, char>>,
|
||||||
Cow::Owned(ref string) => buf.put(string),
|
state: State,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Quoted<'a> {
|
||||||
|
pub fn new(s: &'a str) -> Quoted {
|
||||||
|
Quoted {
|
||||||
|
inner: s.split('"').peekable(),
|
||||||
|
state: State::YieldStr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Iterator for Quoted<'a> {
|
||||||
|
type Item = &'a str;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
match self.state {
|
||||||
|
State::YieldStr => {
|
||||||
|
match self.inner.next() {
|
||||||
|
Some(s) => {
|
||||||
|
self.state = State::YieldQuote;
|
||||||
|
Some(s)
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
State::YieldQuote => {
|
||||||
|
match self.inner.peek() {
|
||||||
|
Some(_) => {
|
||||||
|
self.state = State::YieldStr;
|
||||||
|
Some("\\\"")
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tries to quote the quotes in the passed `value`
|
||||||
|
pub fn put_quoted(buf: &mut BytesMut, value: &str) {
|
||||||
|
for part in Quoted::new(value) {
|
||||||
|
buf.extend_from_slice(part.as_bytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::str;
|
||||||
|
|
||||||
|
use bytes::BytesMut;
|
||||||
|
|
||||||
|
use super::put_quoted;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_quote_str() {
|
||||||
|
let input = "a \"quoted\" string";
|
||||||
|
let mut output = BytesMut::new();
|
||||||
|
put_quoted(&mut output, input);
|
||||||
|
let result = str::from_utf8(&output).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(result, "a \\\"quoted\\\" string");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_without_quotes() {
|
||||||
|
let input = "non-quoted string";
|
||||||
|
let mut output = BytesMut::new();
|
||||||
|
put_quoted(&mut output, input);
|
||||||
|
let result = str::from_utf8(&output).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(result, "non-quoted string");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_starts_with_quote() {
|
||||||
|
let input = "\"first-quoted string";
|
||||||
|
let mut output = BytesMut::new();
|
||||||
|
put_quoted(&mut output, input);
|
||||||
|
let result = str::from_utf8(&output).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(result, "\\\"first-quoted string");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ends_with_quote() {
|
||||||
|
let input = "last-quoted string\"";
|
||||||
|
let mut output = BytesMut::new();
|
||||||
|
put_quoted(&mut output, input);
|
||||||
|
let result = str::from_utf8(&output).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(result, "last-quoted string\\\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_double_quote() {
|
||||||
|
let input = "quote\"\"string";
|
||||||
|
let mut output = BytesMut::new();
|
||||||
|
put_quoted(&mut output, input);
|
||||||
|
let result = str::from_utf8(&output).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(result, "quote\\\"\\\"string");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user