LoginSignup
0
0

Rust+Hyper Shift_JIS の application/x-www-form-urlencoded リクエストを受信する

Posted at

application/x-www-form-urlencoded は原則 UTF-8 が使用されるが、古いアプリケーションなどは Shift_JIS で送信してくることがある。

shift_jis.rs
use std::borrow::Cow;
use encoding_rs::SHIFT_JIS;
use percent_encoding::percent_decode;

#[inline]
pub fn parse_form_urlencoded(input: &[u8]) -> Parse<'_> {
    Parse { input }
}

#[derive(Copy, Clone)]
pub struct Parse<'a> {
    input: &'a [u8],
}

impl<'a> Iterator for Parse<'a> {
    type Item = (Cow<'a, str>, Cow<'a, str>);

    fn next(&mut self) -> Option<Self::Item> {
        loop {
            if self.input.is_empty() {
                return None;
            }
            let mut split2 = self.input.splitn(2, |&b| b == b'&');
            let sequence = split2.next().unwrap();
            self.input = split2.next().unwrap_or(&[][..]);
            if sequence.is_empty() {
                continue;
            }
            let mut split2 = sequence.splitn(2, |&b| b == b'=');
            let name = split2.next().unwrap();
            let value = split2.next().unwrap_or(&[][..]);
            return Some((decode(name), decode(value)));
        }
    }
}

fn decode(input: &[u8]) -> Cow<'_, str> {
    let replaced = replace_plus(input);
    decode_shift_jis(match percent_decode(&replaced).into() {
        Cow::Owned(vec) => Cow::Owned(vec),
        Cow::Borrowed(_) => replaced,
    })
}

fn replace_plus(input: &[u8]) -> Cow<'_, [u8]> {
    match input.iter().position(|&b| b == b'+') {
        None => Cow::Borrowed(input),
        Some(first_position) => {
            let mut replaced = input.to_owned();
            replaced[first_position] = b' ';
            for byte in &mut replaced[first_position + 1..] {
                if *byte == b'+' {
                    *byte = b' ';
                }
            }
            Cow::Owned(replaced)
        }
    }
}

impl<'a> Parse<'a> {
    pub fn into_owned(self) -> ParseIntoOwned<'a> {
        ParseIntoOwned { inner: self }
    }
}

pub struct ParseIntoOwned<'a> {
    inner: Parse<'a>,
}

impl<'a> Iterator for ParseIntoOwned<'a> {
    type Item = (String, String);

    fn next(&mut self) -> Option<Self::Item> {
        self.inner
            .next()
            .map(|(k, v)| (k.into_owned(), v.into_owned()))
    }
}

pub(crate) fn decode_shift_jis(input: Cow<'_, [u8]>) -> Cow<'_, str> { // Shift_JIS 以外のリクエストに対応する場合、この関数を変更する
    match input {
        Cow::Borrowed(bytes) => SHIFT_JIS.decode(bytes).0, // 具体的にはこの行と
        Cow::Owned(bytes) => {
            match SHIFT_JIS.decode(&bytes).0 { // この行を変更する
                Cow::Borrowed(utf8) => Cow::Owned(String::from(utf8)),
                Cow::Owned(s) => Cow::Owned(s),
            }
        }
    }
}

以下が使い方

async fn handler(req: Request<IncomingBody>) -> Result<Response<BoxBody>> {
    let req_params = shift_jis::parse_form_urlencoded(req.collect().await?.to_bytes().as_ref()).into_owned().collect::<HashMap<String, String>>();
    // 以下はリクエストが UTF-8 の場合
    // let req_params = form_urlencoded::parse(req.collect().await?.to_bytes().as_ref()).into_owned().collect::<HashMap<String, String>>();

    let param1 = req_params.get("param1"); // Shift_JIS で POST されたパラメータ1を取得
    let param2 = req_params.get("param2"); // Shift_JIS で POST されたパラメータ2を取得

    // ...
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0