httpsig/message_component/
component_param.rs

1use crate::error::{HttpSigError, HttpSigResult};
2use sfv::{Parser, SerializeValue};
3
4type IndexSet<K> = indexmap::IndexSet<K, fxhash::FxBuildHasher>;
5
6/* ---------------------------------------------------------------- */
7#[derive(PartialEq, Eq, Hash, Debug, Clone)]
8/// Http message component parameters that appends with `;` in the signature input
9/// https://datatracker.ietf.org/doc/html/rfc9421#secion-2.1
10pub enum HttpMessageComponentParam {
11  /// sf: https://datatracker.ietf.org/doc/html/rfc9421#section-2.1.1
12  Sf,
13  /// key: https://datatracker.ietf.org/doc/html/rfc9421#section-2.1.2
14  /// This will be encoded to `;key="..."` in the signature input
15  Key(String),
16  /// bs: https://datatracker.ietf.org/doc/html/rfc9421#section-2.1.3
17  Bs,
18  // tr: https://datatracker.ietf.org/doc/html/rfc9421#section-2.1.4
19  Tr,
20  // req: https://datatracker.ietf.org/doc/html/rfc9421#section-2.4
21  Req,
22  // name: https://datatracker.ietf.org/doc/html/rfc9421#name-query-parameters
23  /// This will be encoded to `;name="..."` in the signature input
24  Name(String),
25}
26
27impl From<HttpMessageComponentParam> for String {
28  fn from(val: HttpMessageComponentParam) -> Self {
29    match val {
30      HttpMessageComponentParam::Sf => "sf".to_string(),
31      HttpMessageComponentParam::Key(val) => format!("key=\"{val}\""),
32      HttpMessageComponentParam::Bs => "bs".to_string(),
33      HttpMessageComponentParam::Tr => "tr".to_string(),
34      HttpMessageComponentParam::Req => "req".to_string(),
35      HttpMessageComponentParam::Name(v) => format!("name=\"{v}\""),
36    }
37  }
38}
39
40impl TryFrom<(&str, &sfv::BareItem)> for HttpMessageComponentParam {
41  type Error = HttpSigError;
42  fn try_from((key, val): (&str, &sfv::BareItem)) -> Result<Self, Self::Error> {
43    match key {
44      "sf" => Ok(Self::Sf),
45      "bs" => Ok(Self::Bs),
46      "tr" => Ok(Self::Tr),
47      "req" => Ok(Self::Req),
48      "name" => {
49        let name = val.as_str().ok_or(HttpSigError::InvalidComponentParam(
50          "Invalid http field param: name".to_string(),
51        ))?;
52        Ok(Self::Name(name.to_string()))
53      }
54      "key" => {
55        let key = val.as_str().ok_or(HttpSigError::InvalidComponentParam(
56          "Invalid http field param: key".to_string(),
57        ))?;
58        Ok(Self::Key(key.to_string()))
59      }
60      _ => Err(HttpSigError::InvalidComponentParam(format!(
61        "Invalid http field param: {key}"
62      ))),
63    }
64  }
65}
66
67#[derive(PartialEq, Eq, Debug, Clone)]
68/// Http message component parameters
69pub struct HttpMessageComponentParams(pub IndexSet<HttpMessageComponentParam>);
70
71impl std::hash::Hash for HttpMessageComponentParams {
72  fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
73    let mut params = self.0.iter().map(|v| v.clone().into()).collect::<Vec<String>>();
74    params.sort();
75    params.hash(state);
76  }
77}
78
79impl TryFrom<&sfv::Parameters> for HttpMessageComponentParams {
80  type Error = HttpSigError;
81  fn try_from(val: &sfv::Parameters) -> Result<Self, Self::Error> {
82    let hs = val
83      .iter()
84      .map(|(k, v)| HttpMessageComponentParam::try_from((k.as_str(), v)))
85      .collect::<Result<IndexSet<_>, _>>()?;
86    Ok(Self(hs))
87  }
88}
89impl std::fmt::Display for HttpMessageComponentParams {
90  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91    if !self.0.is_empty() {
92      write!(
93        f,
94        ";{}",
95        self.0.iter().map(|v| v.clone().into()).collect::<Vec<String>>().join(";")
96      )
97    } else {
98      Ok(())
99    }
100  }
101}
102
103/* ---------------------------------------------------------------- */
104/// Handle `sf` parameter
105pub(super) fn handle_params_sf(field_values: &mut [String]) -> HttpSigResult<()> {
106  let parsed_list = field_values
107    .iter()
108    .map(|v| {
109      if let Ok(list) = Parser::parse_list(v.as_bytes()) {
110        list.serialize_value()
111      } else if let Ok(dict) = Parser::parse_dictionary(v.as_bytes()) {
112        dict.serialize_value()
113      } else {
114        Err("invalid structured field value for sf")
115      }
116    })
117    .collect::<Result<Vec<_>, _>>()
118    .map_err(|e| HttpSigError::InvalidComponentParam(format!("Failed to parse structured field value: {e}")))?;
119
120  field_values.iter_mut().zip(parsed_list).for_each(|(v, p)| {
121    *v = p;
122  });
123
124  Ok(())
125}
126
127/* ---------------------------------------------------------------- */
128/// Handle `key` parameter, returns new field values
129pub(super) fn handle_params_key_into(field_values: &[String], key: &str) -> HttpSigResult<Vec<String>> {
130  let dicts = field_values
131    .iter()
132    .map(|v| Parser::parse_dictionary(v.as_bytes()))
133    .collect::<Result<Vec<_>, _>>()
134    .map_err(|e| HttpSigError::InvalidComponentParam(format!("Failed to parse structured field value: {e}")))?;
135
136  let found_entries = dicts
137    .into_iter()
138    .filter_map(|dict| {
139      dict.get(key).map(|v| {
140        let sfvalue: sfv::List = vec![v.clone()];
141        sfvalue.serialize_value()
142      })
143    })
144    .collect::<Result<Vec<_>, _>>()
145    .map_err(|e| HttpSigError::InvalidComponentParam(format!("Failed to serialize structured field value: {e}")))?;
146
147  Ok(found_entries)
148}
149
150/* ---------------------------------------------------------------- */
151
152mod tests {
153  #[allow(unused)]
154  use super::*;
155
156  #[test]
157  fn parser_test() {
158    // Parsing structured field value of Item type.
159    let item_header_input = "12.445;foo=bar";
160    let item = Parser::parse_item(item_header_input.as_bytes()).unwrap();
161    assert_eq!(item.serialize_value().unwrap(), item_header_input);
162
163    // Parsing structured field value of List type.
164    let list_header_input = "  1; a=tok, (\"foo\"   \"bar\" );baz, (  )";
165    let list = Parser::parse_list(list_header_input.as_bytes()).unwrap();
166    assert_eq!(list.serialize_value().unwrap(), "1;a=tok, (\"foo\" \"bar\");baz, ()");
167
168    // Parsing structured field value of Dictionary type.
169    let dict_header_input = "a=?0, b, c; foo=bar, rating=1.5, fruits=(apple pear), d";
170    let dict = Parser::parse_dictionary(dict_header_input.as_bytes()).unwrap();
171    assert_eq!(
172      dict.serialize_value().unwrap(),
173      "a=?0, b, c;foo=bar, rating=1.5, fruits=(apple pear), d"
174    );
175  }
176}