use bstr::ByteSlice;
use percent_encoding::{self, percent_encode_byte};
#[cfg(target_family = "wasm")]
use serde::{Deserialize, Serialize};
use crate::Operation;
use crate::OperationError;
#[derive(Clone)]
#[cfg_attr(target_family = "wasm", derive(Serialize, Deserialize))]
pub struct UrlDecode {}
impl Operation for UrlDecode {
fn execute(&self, input: &[u8]) -> Result<Vec<u8>, OperationError> {
let decoded: Vec<u8> = percent_encoding::percent_decode(input).collect();
Ok(decoded)
}
}
impl UrlDecode {
pub fn new() -> Self {
UrlDecode {}
}
}
#[derive(Clone)]
#[cfg_attr(target_family = "wasm", derive(Serialize, Deserialize))]
pub struct UrlEncode {
non_ascii: bool,
charset: String,
}
fn extend_with_grapheme_encode(output: &mut Vec<u8>, grapheme: &str) {
grapheme
.as_bytes()
.iter()
.for_each(|b| output.extend_from_slice(percent_encode_byte(*b).as_bytes()));
}
impl Operation for UrlEncode {
fn execute(&self, input: &[u8]) -> Result<Vec<u8>, OperationError> {
let charset = self.charset.as_bytes().graphemes();
let input_graphemes = input.graphemes();
let mut output: Vec<u8> = vec![];
for grapheme in input_graphemes {
let mut charset_graphemes = charset.clone();
if charset_graphemes.any(|charset_grapheme| charset_grapheme == grapheme)
|| (self.non_ascii && !grapheme.is_ascii())
{
extend_with_grapheme_encode(&mut output, grapheme);
} else {
output.extend_from_slice(grapheme.as_bytes());
}
}
Ok(output)
}
}
impl UrlEncode {
pub fn new(non_ascii: bool, charset: Option<String>) -> Self {
UrlEncode {
non_ascii,
charset: charset.unwrap_or_default(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn url_decode() {
let encoder = UrlDecode::new();
let actual = encoder
.execute("caido @%C3%A9%C3%A9%F0%9F%A5%96".as_bytes())
.unwrap();
let expected = "caido @éé🥖".as_bytes().to_vec();
assert_eq!(actual, expected);
}
#[test]
fn url_encode_unicode_char() {
let encoder = UrlEncode::new(false, Some("🥖".to_string()));
let actual = encoder.execute("a🥖🥖st".as_bytes()).unwrap();
let expected = "a%F0%9F%A5%96%F0%9F%A5%96st".as_bytes().to_vec();
assert_eq!(actual, expected);
}
#[test]
fn url_encode_non_ascii() {
let encoder = UrlEncode::new(true, None);
let actual = encoder.execute("caido @éé".as_bytes()).unwrap();
let expected = "caido @%C3%A9%C3%A9".as_bytes().to_vec();
assert_eq!(actual, expected);
}
#[test]
fn url_encode_non_ascii_and_charset() {
let encoder = UrlEncode::new(true, Some("c".to_string()));
let actual = encoder.execute("caido @éé🥖".as_bytes()).unwrap();
let expected = "%63aido @%C3%A9%C3%A9%F0%9F%A5%96".as_bytes().to_vec();
assert_eq!(actual, expected);
}
#[test]
fn url_encode_charset() {
let encoder = UrlEncode::new(true, Some("@t".to_string()));
let actual = encoder.execute("a@ test".as_bytes()).unwrap();
let expected = "a%40 %74es%74".as_bytes().to_vec();
assert_eq!(actual, expected);
}
}