actix_http/header/shared/
extended.rs1use std::{fmt, str::FromStr};
4
5use language_tags::LanguageTag;
6
7use crate::header::{Charset, HTTP_VALUE};
8
9#[derive(Clone, Debug, PartialEq, Eq)]
16pub struct ExtendedValue {
17 pub charset: Charset,
19
20 pub language_tag: Option<LanguageTag>,
22
23 pub value: Vec<u8>,
25}
26
27pub fn parse_extended_value(val: &str) -> Result<ExtendedValue, crate::error::ParseError> {
67 let mut parts = val.splitn(3, '\'');
69
70 let charset: Charset = match parts.next() {
72 None => return Err(crate::error::ParseError::Header),
73 Some(n) => FromStr::from_str(n).map_err(|_| crate::error::ParseError::Header)?,
74 };
75
76 let language_tag: Option<LanguageTag> = match parts.next() {
78 None => return Err(crate::error::ParseError::Header),
79 Some("") => None,
80 Some(s) => match s.parse() {
81 Ok(lt) => Some(lt),
82 Err(_) => return Err(crate::error::ParseError::Header),
83 },
84 };
85
86 let value: Vec<u8> = match parts.next() {
88 None => return Err(crate::error::ParseError::Header),
89 Some(v) => percent_encoding::percent_decode(v.as_bytes()).collect(),
90 };
91
92 Ok(ExtendedValue {
93 charset,
94 language_tag,
95 value,
96 })
97}
98
99impl fmt::Display for ExtendedValue {
100 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
101 let encoded_value = percent_encoding::percent_encode(&self.value[..], HTTP_VALUE);
102
103 if let Some(ref lang) = self.language_tag {
104 write!(f, "{}'{}'{}", self.charset, lang, encoded_value)
105 } else {
106 write!(f, "{}''{}", self.charset, encoded_value)
107 }
108 }
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114
115 #[test]
116 fn test_parse_extended_value_with_encoding_and_language_tag() {
117 let expected_language_tag = "en".parse::<LanguageTag>().unwrap();
118 let result = parse_extended_value("iso-8859-1'en'%A3%20rates");
121 assert!(result.is_ok());
122 let extended_value = result.unwrap();
123 assert_eq!(Charset::Iso_8859_1, extended_value.charset);
124 assert!(extended_value.language_tag.is_some());
125 assert_eq!(expected_language_tag, extended_value.language_tag.unwrap());
126 assert_eq!(
127 vec![163, b' ', b'r', b'a', b't', b'e', b's'],
128 extended_value.value
129 );
130 }
131
132 #[test]
133 fn test_parse_extended_value_with_encoding() {
134 let result = parse_extended_value("UTF-8''%c2%a3%20and%20%e2%82%ac%20rates");
138 assert!(result.is_ok());
139 let extended_value = result.unwrap();
140 assert_eq!(Charset::Ext("UTF-8".to_string()), extended_value.charset);
141 assert!(extended_value.language_tag.is_none());
142 assert_eq!(
143 vec![
144 194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a', b't',
145 b'e', b's',
146 ],
147 extended_value.value
148 );
149 }
150
151 #[test]
152 fn test_parse_extended_value_missing_language_tag_and_encoding() {
153 let result = parse_extended_value("foo%20bar.html");
155 assert!(result.is_err());
156 }
157
158 #[test]
159 fn test_parse_extended_value_partially_formatted() {
160 let result = parse_extended_value("UTF-8'missing third part");
161 assert!(result.is_err());
162 }
163
164 #[test]
165 fn test_parse_extended_value_partially_formatted_blank() {
166 let result = parse_extended_value("blank second part'");
167 assert!(result.is_err());
168 }
169
170 #[test]
171 fn test_fmt_extended_value_with_encoding_and_language_tag() {
172 let extended_value = ExtendedValue {
173 charset: Charset::Iso_8859_1,
174 language_tag: Some("en".parse().expect("Could not parse language tag")),
175 value: vec![163, b' ', b'r', b'a', b't', b'e', b's'],
176 };
177 assert_eq!("ISO-8859-1'en'%A3%20rates", format!("{}", extended_value));
178 }
179
180 #[test]
181 fn test_fmt_extended_value_with_encoding() {
182 let extended_value = ExtendedValue {
183 charset: Charset::Ext("UTF-8".to_string()),
184 language_tag: None,
185 value: vec![
186 194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a', b't',
187 b'e', b's',
188 ],
189 };
190 assert_eq!(
191 "UTF-8''%C2%A3%20and%20%E2%82%AC%20rates",
192 format!("{}", extended_value)
193 );
194 }
195}