Skip to main content

http_msgsign/components/
derive.rs

1use std::fmt::Display;
2
3use http::{Request, Response};
4
5use crate::components::NameType;
6use crate::components::params::Serializer;
7use crate::errors::{HttpComponentError, InvalidDerivedComponent, InvalidFormat};
8
9/// See [RFC9421 HTTP Message Signatures §2.2](https://datatracker.ietf.org/doc/html/rfc9421#section-2.2)
10#[derive(Debug, Clone, Eq, PartialEq, Hash)]
11pub enum Derive {
12    Method,
13    TargetUri,
14    Authority,
15    Scheme,
16    RequestTarget,
17    Path,
18    Query,
19    QueryParam,
20    Status,
21}
22
23impl AsRef<str> for Derive {
24    fn as_ref(&self) -> &str {
25        match self {
26            Derive::Method => "@method",
27            Derive::TargetUri => "@target-uri",
28            Derive::Authority => "@authority",
29            Derive::Scheme => "@scheme",
30            Derive::RequestTarget => "@request-target",
31            Derive::Path => "@path",
32            Derive::Query => "@query",
33            Derive::QueryParam => "@query-param",
34            Derive::Status => "@status",
35        }
36    }
37}
38
39/// See [RFC9421 HTTP Message Signatures §2.2-2](https://datatracker.ietf.org/doc/html/rfc9421#section-2.2-2)
40impl Display for Derive {
41    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42        write!(f, "\"{}\"", self.as_ref())
43    }
44}
45
46impl From<Derive> for NameType {
47    fn from(derived: Derive) -> Self {
48        NameType::Derived(derived)
49    }
50}
51
52impl Derive {
53    /// Reference [RFC9421 HTTP Message Signatures §6.4.2](https://datatracker.ietf.org/doc/html/rfc9421#name-initial-contents-3)
54    pub fn seek_request<B>(
55        &self,
56        request: &Request<B>,
57        params: &Serializer,
58    ) -> Result<Option<String>, HttpComponentError> {
59        Ok(match self {
60            Derive::Method => Some(request.method().to_string()),
61            Derive::TargetUri => Some(request.uri().to_string()),
62            Derive::Authority => request
63                .uri()
64                .authority()
65                .map(|authority| authority.to_string()),
66            Derive::Scheme => request.uri().scheme().map(|scheme| scheme.to_string()),
67            Derive::RequestTarget => request
68                .uri()
69                .path_and_query()
70                .map(|req_target| req_target.to_string()),
71            Derive::Path => Some(request.uri().path().to_string()),
72            Derive::Query => request.uri().query().map(|query| query.to_string()),
73            Derive::QueryParam => extract_query_value(params, request)?.map(|val| val.to_string()),
74            _ => {
75                return Err(HttpComponentError::UnsupportedComponent(self.to_string()));
76            }
77        })
78    }
79
80    pub fn seek_response<B>(
81        &self,
82        response: &Response<B>,
83    ) -> Result<Option<String>, HttpComponentError> {
84        Ok(match self {
85            Derive::Status => Some(response.status().as_u16().to_string()),
86            _ => {
87                return Err(HttpComponentError::UnsupportedComponent(self.to_string()));
88            }
89        })
90    }
91}
92
93impl TryFrom<sfv::BareItem> for Derive {
94    type Error = InvalidFormat;
95
96    fn try_from(item: sfv::BareItem) -> Result<Self, Self::Error> {
97        let sfv::BareItem::String(ident) = item else {
98            return Err(InvalidFormat::String);
99        };
100
101        Ok(match ident.as_str() {
102            "@method" => Derive::Method,
103            "@target-uri" => Derive::TargetUri,
104            "@authority" => Derive::Authority,
105            "@scheme" => Derive::Scheme,
106            "@request-target" => Derive::RequestTarget,
107            "@path" => Derive::Path,
108            "@query" => Derive::Query,
109            "@query-param" => Derive::QueryParam,
110            "@status" => Derive::Status,
111            _ => return Err(InvalidDerivedComponent)?,
112        })
113    }
114}
115
116impl Derive {
117    pub fn contains(other: &sfv::BareItem) -> bool {
118        if let sfv::BareItem::String(ident) = other {
119            let ident = ident.as_str();
120            ident == "@method"
121                || ident == "@target-uri"
122                || ident == "@authority"
123                || ident == "@scheme"
124                || ident == "@request-target"
125                || ident == "@path"
126                || ident == "@query"
127                || ident == "@query-param"
128                || ident == "@status"
129        } else {
130            false
131        }
132    }
133}
134
135fn extract_query_value<'a, B>(
136    params: &'a Serializer,
137    req: &'a Request<B>,
138) -> Result<Option<&'a str>, HttpComponentError> {
139    let Some(require) = params.name() else {
140        return Err(HttpComponentError::UnmetRequirement {
141            reason: "`@query-param` must have a `name` parameter. https://datatracker.ietf.org/doc/html/rfc9421#section-2.2.8-1",
142        });
143    };
144
145    let Some(query) = req.uri().query().map(|params| {
146        params
147            .split("&")
148            .map(|kv| kv.split("=").collect::<Vec<_>>())
149            .collect::<Vec<_>>()
150    }) else {
151        return Ok(None);
152    };
153    let value = query.iter().find(|kv| kv[0] == require).map(|kv| kv[1]);
154    Ok(value.to_owned())
155}