#![deny(unsafe_code)]
extern crate filetime;
extern crate multipart;
extern crate rand;
extern crate rustc_serialize;
extern crate time;
extern crate tiny_http;
extern crate url;
pub use assets::match_assets;
pub use log::log;
pub use response::{Response, ResponseBody};
use std::io::Cursor;
use std::io::Result as IoResult;
use std::io::Read;
use std::error;
use std::fmt;
use std::marker::PhantomData;
use std::net::SocketAddr;
use std::net::ToSocketAddrs;
use std::panic;
use std::panic::AssertUnwindSafe;
use std::sync::Arc;
use std::sync::Mutex;
use std::thread;
use std::ascii::AsciiExt;
pub mod cgi;
pub mod input;
pub mod session;
mod assets;
mod find_route;
mod log;
mod response;
mod router;
#[macro_export]
macro_rules! try_or_400 {
($result:expr) => (
match $result {
Ok(r) => r,
Err(_) => return $crate::Response::empty_400(),
}
);
}
#[macro_export]
macro_rules! try_or_404 {
($result:expr) => (
match $result {
Ok(r) => r,
Err(_) => return $crate::Response::empty_404(),
}
);
}
#[macro_export]
macro_rules! assert_or_400 {
($cond:expr) => (
if !$cond {
return $crate::Response::empty_400();
}
);
}
pub fn start_server<A, F>(addr: A, handler: F) -> !
where A: ToSocketAddrs,
F: Send + Sync + 'static + Fn(&Request) -> Response
{
let server = tiny_http::Server::http(addr).unwrap();
let handler = Arc::new(AssertUnwindSafe(handler));
for mut request in server.incoming_requests() {
let handler = handler.clone();
thread::spawn(move || {
struct RequestRead(Arc<Mutex<Option<tiny_http::Request>>>);
impl Read for RequestRead {
#[inline]
fn read(&mut self, buf: &mut [u8]) -> IoResult<usize> {
self.0.lock().unwrap().as_mut().unwrap().as_reader().read(buf)
}
}
let tiny_http_request;
let rouille_request = {
let url = request.url().to_owned();
let method = request.method().as_str().to_owned();
let headers = request.headers().iter().map(|h| (h.field.to_string(), h.value.clone().into())).collect();
let remote_addr = request.remote_addr().clone();
tiny_http_request = Arc::new(Mutex::new(Some(request)));
Request {
url: url,
method: method,
headers: headers,
https: false,
data: Arc::new(Mutex::new(Some(Box::new(RequestRead(tiny_http_request.clone())) as Box<_>))),
remote_addr: remote_addr,
}
};
let rouille_response = {
let rouille_request = AssertUnwindSafe(rouille_request);
let res = panic::catch_unwind(move || {
let rouille_request = rouille_request;
handler(&rouille_request)
});
match res {
Ok(r) => r,
Err(_) => {
Response::html("<h1>Internal Server Error</h1>\
<p>An internal error has occurred on the server.</p>")
.with_status_code(500)
}
}
};
let (res_data, res_len) = rouille_response.data.into_inner();
let mut response = tiny_http::Response::empty(rouille_response.status_code)
.with_data(res_data, res_len);
for (key, value) in rouille_response.headers {
if let Ok(header) = tiny_http::Header::from_bytes(key, value) {
response.add_header(header);
} else {
}
}
tiny_http_request.lock().unwrap().take().unwrap().respond(response);
});
}
unreachable!()
}
pub struct Request {
method: String,
url: String,
headers: Vec<(String, String)>,
https: bool,
data: Arc<Mutex<Option<Box<Read>>>>,
remote_addr: SocketAddr,
}
impl Request {
pub fn fake_http<U, M>(method: M, url: U, headers: Vec<(String, String)>, data: Vec<u8>)
-> Request where U: Into<String>, M: Into<String>
{
Request {
url: url.into(),
method: method.into(),
https: false,
data: Arc::new(Mutex::new(Some(Box::new(Cursor::new(data)) as Box<_>))),
headers: headers,
remote_addr: "127.0.0.1:12345".parse().unwrap(),
}
}
pub fn fake_http_from<U, M>(from: SocketAddr, method: M, url: U,
headers: Vec<(String, String)>, data: Vec<u8>)
-> Request where U: Into<String>, M: Into<String>
{
Request {
url: url.into(),
method: method.into(),
https: false,
data: Arc::new(Mutex::new(Some(Box::new(Cursor::new(data)) as Box<_>))),
headers: headers,
remote_addr: from,
}
}
pub fn fake_https<U, M>(method: M, url: U, headers: Vec<(String, String)>, data: Vec<u8>)
-> Request where U: Into<String>, M: Into<String>
{
Request {
url: url.into(),
method: method.into(),
https: true,
data: Arc::new(Mutex::new(Some(Box::new(Cursor::new(data)) as Box<_>))),
headers: headers,
remote_addr: "127.0.0.1:12345".parse().unwrap(),
}
}
pub fn fake_https_from<U, M>(from: SocketAddr, method: M, url: U,
headers: Vec<(String, String)>, data: Vec<u8>)
-> Request where U: Into<String>, M: Into<String>
{
Request {
url: url.into(),
method: method.into(),
https: true,
data: Arc::new(Mutex::new(Some(Box::new(Cursor::new(data)) as Box<_>))),
headers: headers,
remote_addr: from,
}
}
pub fn remove_prefix(&self, prefix: &str) -> Option<Request> {
if !self.url().starts_with(prefix) {
return None;
}
assert!(self.url.starts_with(prefix));
Some(Request {
method: self.method.clone(),
url: self.url[prefix.len() ..].to_owned(),
headers: self.headers.clone(), https: self.https.clone(),
data: self.data.clone(),
remote_addr: self.remote_addr.clone(),
})
}
#[inline]
pub fn secure(&self) -> bool {
self.https
}
#[inline]
pub fn method(&self) -> &str {
&self.method
}
#[inline]
pub fn raw_url(&self) -> &str {
&self.url
}
#[inline]
pub fn raw_query_string(&self) -> &str {
if let Some(pos) = self.url.bytes().position(|c| c == b'?') {
self.url.split_at(pos + 1).1
} else {
""
}
}
pub fn url(&self) -> String {
let url = self.url.as_bytes();
let url = if let Some(pos) = url.iter().position(|&c| c == b'?') {
&url[..pos]
} else {
url
};
url::percent_encoding::percent_decode(url).decode_utf8_lossy().into_owned()
}
pub fn get_param(&self, param_name: &str) -> Option<String> {
let get_params = self.raw_query_string();
let param = match get_params.rfind(&format!("{}=", param_name)) {
Some(p) => p + param_name.len() + 1,
None => return None,
};
let value = match get_params.bytes().skip(param).position(|c| c == b'&') {
None => &get_params[param..],
Some(e) => &get_params[param .. e + param],
};
Some(url::percent_encoding::percent_decode(value.replace("+", " ").as_bytes()).decode_utf8_lossy().into_owned())
}
#[inline]
pub fn header(&self, key: &str) -> Option<String> {
self.headers.iter().find(|&&(ref k, _)| k.eq_ignore_ascii_case(key)).map(|&(_, ref v)| v.clone())
}
pub fn data(&self) -> Option<RequestBody> {
let reader = self.data.lock().unwrap().take();
reader.map(|r| RequestBody { body: r, marker: PhantomData })
}
#[inline]
pub fn remote_addr(&self) -> &SocketAddr {
&self.remote_addr
}
}
pub struct RequestBody<'a> {
body: Box<Read>,
marker: PhantomData<&'a ()>,
}
impl<'a> Read for RequestBody<'a> {
#[inline]
fn read(&mut self, buf: &mut [u8]) -> IoResult<usize> {
self.body.read(buf)
}
}
#[cfg(test)]
mod tests {
use Request;
#[test]
fn header() {
let request = Request::fake_http("GET", "/", vec![("Host".to_owned(), "localhost".to_owned())], vec![]);
assert_eq!(request.header("Host"), Some("localhost".to_owned()));
assert_eq!(request.header("host"), Some("localhost".to_owned()));
}
#[test]
fn get_param() {
let request = Request::fake_http("GET", "/?p=hello", vec![], vec![]);
assert_eq!(request.get_param("p"), Some("hello".to_owned()));
}
#[test]
fn body_twice() {
let request = Request::fake_http("GET", "/", vec![], vec![62, 62, 62]);
assert!(request.data().is_some());
assert!(request.data().is_none());
}
#[test]
fn url_strips_get_query() {
let request = Request::fake_http("GET", "/?p=hello", vec![], vec![]);
assert_eq!(request.url(), "/");
}
#[test]
fn urlencode_query_string() {
let request = Request::fake_http("GET", "/?p=hello%20world", vec![], vec![]);
assert_eq!(request.get_param("p"), Some("hello world".to_owned()));
}
#[test]
fn plus_in_query_string() {
let request = Request::fake_http("GET", "/?p=hello+world", vec![], vec![]);
assert_eq!(request.get_param("p"), Some("hello world".to_owned()));
}
#[test]
fn encoded_plus_in_query_string() {
let request = Request::fake_http("GET", "/?p=hello%2Bworld", vec![], vec![]);
assert_eq!(request.get_param("p"), Some("hello+world".to_owned()));
}
#[test]
fn url_encode() {
let request = Request::fake_http("GET", "/hello%20world", vec![], vec![]);
assert_eq!(request.url(), "/hello world");
}
#[test]
fn plus_in_url() {
let request = Request::fake_http("GET", "/hello+world", vec![], vec![]);
assert_eq!(request.url(), "/hello+world");
}
}