Skip to main content

httpsig/message_component/
component_id.rs

1use super::{
2  component_name::{DerivedComponentName, HttpMessageComponentName},
3  component_param::{HttpMessageComponentParam, HttpMessageComponentParams},
4};
5use crate::error::{HttpSigError, HttpSigResult};
6use sfv::Parser;
7
8/* ---------------------------------------------------------------- */
9#[derive(Debug, Clone, PartialEq, Eq, Hash)]
10/// Http message component id
11pub struct HttpMessageComponentId {
12  /// Http message component name
13  pub name: HttpMessageComponentName,
14  /// Http message component params
15  pub params: HttpMessageComponentParams,
16}
17
18impl HttpMessageComponentId {
19  /// Add `req` field param to the component, which is used to generate signature input for response from its corresponding request.
20  pub fn add_req_param(&mut self) {
21    self.params.0.insert(HttpMessageComponentParam::Req);
22  }
23}
24
25impl std::fmt::Display for HttpMessageComponentId {
26  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27    write!(f, "{}{}", self.name, self.params)
28  }
29}
30
31impl TryFrom<&str> for HttpMessageComponentId {
32  type Error = HttpSigError;
33  /// Parse http message component id from string
34  /// Accept `"<name>";<params>` or `"<name>"` (with double quotations).
35  /// But accept string in the form of `<name>` (without double quotations) when no param is given
36  fn try_from(val: &str) -> HttpSigResult<Self> {
37    let val = val.trim();
38    let item: sfv::Item = if !val.starts_with('"') && !val.ends_with('"') && !val.is_empty() && !val.contains('"') {
39      // maybe insufficient, but it's enough for now
40      Parser::new(format!("\"{val}\"").as_str())
41        .parse()
42        .map_err(|e| HttpSigError::ParseSFVError(e.to_string()))?
43      // Parser::parse_item(format!("\"{val}\"").as_bytes()).map_err(|e| HttpSigError::ParseSFVError(e.to_string()))?
44    } else {
45      Parser::new(val)
46        .parse()
47        .map_err(|e| HttpSigError::ParseSFVError(e.to_string()))?
48      // Parser::parse_item(val.as_bytes()).map_err(|e| HttpSigError::ParseSFVError(e.to_string()))?
49    };
50
51    let res = Self {
52      name: HttpMessageComponentName::try_from(&item.bare_item)?,
53      params: HttpMessageComponentParams::try_from(&item.params)?,
54    };
55
56    // assert for query param
57    if res.params.0.iter().any(|v| matches!(v, &HttpMessageComponentParam::Name(_)))
58      && !matches!(res.name, HttpMessageComponentName::Derived(DerivedComponentName::QueryParam))
59    {
60      return Err(HttpSigError::InvalidComponentId(format!(
61        "Invalid http message component id: {res}"
62      )));
63    }
64
65    // assert for http field components
66    // only req field param is allowed
67    if res.params.0.iter().any(|v| {
68      matches!(v, &HttpMessageComponentParam::Bs)
69        || matches!(v, &HttpMessageComponentParam::Sf)
70        || matches!(v, &HttpMessageComponentParam::Tr)
71        || matches!(v, &HttpMessageComponentParam::Key(_))
72    }) && !matches!(res.name, HttpMessageComponentName::HttpField(_))
73    {
74      return Err(HttpSigError::InvalidComponentId(format!(
75        "Invalid http message component id: {res}"
76      )));
77    }
78
79    Ok(res)
80  }
81}