use core::fmt; const MAX_QUERY: usize = 128; #[derive(Debug,Clone,Copy,PartialEq,Eq)] enum State { WaitG, WaitE, WaitT, WaitSpace, GetQuery, WaitCR1, WaitLF1, WaitCR2, WaitLF2, Finished } pub struct Request { state: State, query_idx: usize, query: [u8; MAX_QUERY] } impl Request { pub fn new() -> Request { Request { state: State::WaitG, query_idx: 0, query: [0; MAX_QUERY] } } pub fn reset(&mut self) { self.state = State::WaitG; self.query_idx = 0; } pub fn input_char(&mut self, c: u8) -> Result { match self.state { State::WaitG => { if c == b'G' { self.state = State::WaitE; } else { return Err("invalid character in method") } } State::WaitE => { if c == b'E' { self.state = State::WaitT; } else { return Err("invalid character in method") } } State::WaitT => { if c == b'T' { self.state = State::WaitSpace; } else { return Err("invalid character in method") } } State::WaitSpace => { if c == b' ' { self.state = State::GetQuery; } else { return Err("invalid character in method") } } State::GetQuery => { if c == b'\r' || c == b'\n' { return Err("GET ended prematurely") } else if c == b' ' { if self.query_idx == 0 { return Err("query is empty") } else { self.state = State::WaitCR1; } } else { if self.query_idx >= self.query.len() { return Err("query is too long") } else { self.query[self.query_idx] = c; self.query_idx += 1; } } } State::WaitCR1 => { if c == b'\r' { self.state = State::WaitLF1; } } State::WaitLF1 => { if c == b'\n' { self.state = State::WaitCR2; } else { self.state = State::WaitCR1; } } State::WaitCR2 => { if c == b'\r' { self.state = State::WaitLF2; } else { self.state = State::WaitCR1; } } State::WaitLF2 => { if c == b'\n' { self.state = State::Finished; return Ok(true) } else { self.state = State::WaitCR1; } } State::Finished => return Err("trailing characters") } Ok(false) } pub fn input(&mut self, buf: &[u8]) -> Result { let mut result = Ok(false); for c in buf.iter() { result = self.input_char(*c); if result.is_err() { return result; } } result } pub fn get_query<'a>(&'a self) -> Result<&'a [u8], &'static str> { if self.state != State::Finished { return Err("request is not finished") } Ok(&self.query[..self.query_idx]) } pub fn get_path<'a>(&'a self) -> Result<&'a [u8], &'static str> { let query = self.get_query()?; Ok(query.split(|b| *b == '?' as u8).next().unwrap()) } // FIXME: this yields some empty strings pub fn iter_args<'a>(&'a self) -> Result, &'static str> { let query = self.get_query()?; let mut qs = query.split(|b| *b == '?' as u8); qs.next(); let args = qs.next().unwrap_or(b""); let args_it = args.split(|b| *b == '&' as u8); Ok(args_it.map(|arg| { let mut eqs = arg.split(|b| *b == '=' as u8); (eqs.next().unwrap(), eqs.next().unwrap_or(b"")) })) } pub fn get_arg<'a>(&'a self, name: &[u8]) -> Result<&'a [u8], &'static str> { for (current_name, current_value) in self.iter_args()? { if current_name == name { return Ok(current_value) } } Err("argument not found") } } pub fn write_reply_header(output: &mut fmt::Write, status: u16, content_type: &str, gzip: bool) -> fmt::Result { let status_text = match status { 200 => "OK", 404 => "Not Found", 500 => "Internal Server Error", _ => return Err(fmt::Error) }; write!(output, "HTTP/1.1 {} {}\r\nContent-Type: {}\r\n", status, status_text, content_type)?; if gzip { write!(output, "Content-Encoding: gzip\r\n")?; } write!(output, "\r\n") }