use std::fs; use std::io::{Read, Write}; use std::net::TcpListener; use std::net::TcpStream; use std::process::Command; use std::thread; use std::time::Duration; enum ResponseType { FILE, IMAGE, ICON, } fn main() -> std::io::Result<()> { let mut thid: u32 = 1; let _r = TcpListener::bind("0.0.0.0:8888"); let listener; match _r { Ok(_r) => { listener = _r; } _ => { println!("Call to bind() failed!"); return Err(_r.unwrap_err()); } } for stream in listener.incoming() { let stream = stream.unwrap(); update_image(); let _handler = thread::spawn(move || { handle_connection(stream, thid); }); thid += 1; } Ok(()) } fn handle_connection(mut stream: TcpStream, _thid: u32) { let mut buffer = [0; 1024]; println!("Started thread {}", _thid); stream .set_read_timeout(Some(Duration::new(120, 0))) .unwrap(); stream .set_write_timeout(Some(Duration::new(120, 0))) .unwrap(); loop { buffer.fill(0); let _r = stream.read(&mut buffer); match _r { Ok(_r) => { let r = decode_message(String::from_utf8_lossy(&buffer).to_string(), &mut stream); if !r { println!("decode message failed, killing thread {}", _thid); return; } } _ => { println!("Connection closed! killing thread = {}", _thid); return; } } } } fn decode_message(_msg: String, stream: &mut TcpStream) -> bool { let msgb = _msg.as_bytes(); if !msgb.starts_with(b"GET") { return false; } let mut iter = _msg.split_whitespace(); iter.next().unwrap(); let mut fname = iter.next().unwrap(); let mut path = String::from("/home/mlombard/public_html"); if fname == "/" { fname = "/index.html"; } else if fname.contains("..") { /* Block malicious GET requests from reading files outside of the base directory */ fname = "/404.html"; } let mut resp_type = ResponseType::FILE; match fname { "/favicon.ico" => { resp_type = ResponseType::ICON; } "/cam" => { println!("Accessing the camera"); resp_type = ResponseType::IMAGE; let response = html_file_response(&"/tmp/homecam.jpg".to_string(), resp_type); if response.is_empty() { println!("Can't find the webcam data"); return false; } stream.write(&response).unwrap(); stream.flush().unwrap(); return true; } _ => {} } path.push_str(fname); println!("Reading file {}", path); let mut response = html_file_response(&path, resp_type); if response.is_empty() { println!("File {} not found... 404", path); path = "/home/mlombard/public_html/404.html".to_string(); response = html_file_response(&mut path, ResponseType::FILE); } stream.write(&response).unwrap(); stream.flush().unwrap(); return true; } fn html_file_response(_path: &String, resp_type: ResponseType) -> Vec { let _r = fs::read(_path); let mut file_content; let mut response: Vec = Vec::new(); match _r { Ok(_r) => file_content = _r, _ => return response, } let content_type = { match resp_type { ResponseType::ICON => "image/x-icon", ResponseType::IMAGE => "image/jpeg", ResponseType::FILE => "text/html", } }; let header = format!( "HTTP/1.1 200 OK\r\nContent-Length: {}\r\nContent-Type: {}\r\n\r\n", file_content.len(), content_type ); let mut header_bytes: Vec = (&header).as_bytes().to_vec(); response.append(&mut header_bytes); response.append(&mut file_content); response } fn update_image() { let meta = fs::metadata("/tmp/homecam.jpg"); match meta { Ok(meta) => { let time = meta.modified().unwrap(); let elap = time.elapsed().unwrap(); if elap > Duration::from_secs(60 * 5) { /* the image is too old, update it */ execute_rm(); execute_ffmpeg(); } } _ => { /* File not found, create it */ execute_ffmpeg(); } } } fn execute_rm() { let _status = Command::new("rm") .arg("-f") .arg("/tmp/homecam.jpg") .status(); } fn execute_ffmpeg() { let _status = Command::new("ffmpeg") .arg("-f") .arg("video4linux2") .arg("-s") .arg("640x480") .arg("-i") .arg("/dev/video0") .arg("-ss") .arg("0:0:2") .arg("-frames") .arg("1") .arg("-y") .arg("/tmp/homecam.jpg") .status() .expect("ffmpeg can't start"); }