forked from M-Labs/ionpak-thermostat
180 lines
5.1 KiB
Rust
180 lines
5.1 KiB
Rust
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<bool, &'static str> {
|
|
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<bool, &'static str> {
|
|
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<impl Iterator<Item=(&'a [u8], &'a [u8])>, &'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")
|
|
}
|