#![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::LogEntry;
pub use input::{SessionsManager, Session, generate_session_id};
pub use response::{Response, ResponseBody};
use std::io::Read;
use std::error;
use std::fmt;
use std::net::SocketAddr;
use std::net::ToSocketAddrs;
use std::sync::Arc;
use std::thread;
use std::ascii::AsciiExt;
pub mod cgi;
pub mod input;
mod assets;
mod find_route;
mod log;
mod response;
mod router;
#[macro_export]
macro_rules! try_or_400 {
($result:expr) => (
try!($result.map_err(|_| $crate::RouteError::WrongInput))
);
}
#[macro_export]
macro_rules! assert_or_400 {
($cond:expr) => (
if !$cond {
return Err($crate::RouteError::WrongInput);
}
);
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum RouteError {
NoRouteFound,
WrongInput,
LoginRequired,
WrongLoginPassword,
NotAuthorized,
}
impl error::Error for RouteError {
fn description(&self) -> &str {
match self {
&RouteError::NoRouteFound => {
"Couldn't find a way to handle this request."
},
&RouteError::WrongInput => {
"The body of the request is malformed or missing something."
},
&RouteError::LoginRequired => {
"The client must be logged in before this request can be answered."
},
&RouteError::WrongLoginPassword => {
"The client attempted to login but entered a wrong login or password."
},
&RouteError::NotAuthorized => {
"The client is logged in but doesn't have the permission to access this resource."
},
}
}
}
impl fmt::Display for RouteError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(fmt, "{}", error::Error::description(self))
}
}
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(handler);
for mut request in server.incoming_requests() {
let handler = handler.clone();
thread::spawn(move || {
let mut data = Vec::with_capacity(request.body_length().unwrap_or(0));
request.as_reader().read_to_end(&mut data);
let rouille_request = Request {
url: request.url().to_owned(),
method: request.method().as_str().to_owned(),
headers: request.headers().iter().map(|h| (h.field.to_string(), h.value.clone().into())).collect(),
https: false,
data: data,
remote_addr: request.remote_addr().clone(),
};
let mut rouille_response = handler(&rouille_request);
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 {
}
}
request.respond(response);
});
}
unreachable!()
}
pub struct Request {
method: String,
url: String,
headers: Vec<(String, String)>,
https: bool,
data: Vec<u8>,
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: data,
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: data,
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: data,
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: data,
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::lossy_utf8_percent_decode(url)
}
pub fn get_param(&self, param_name: &str) -> Option<String> {
let get_params = &self.raw_url()[self.raw_url().bytes().position(|c| c == b'?').unwrap_or(0) ..];
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::lossy_utf8_percent_decode(value.as_bytes()))
}
#[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) -> Vec<u8> {
self.data.clone()
}
#[inline]
pub fn remote_addr(&self) -> &SocketAddr {
&self.remote_addr
}
}
#[cfg(test)]
mod tests {
use super::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()));
}
}