From f7d8e467528f04ff93701c245698a4509d62b7f7 Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Sat, 12 Nov 2016 21:10:36 +0100 Subject: [PATCH] Add the ability to return a super basic HTML page --- Cargo.lock | 7 +++ Cargo.toml | 1 + src/main.rs | 2 + src/webservice.rs | 123 +++++++++++++++++++++++++++++++++++++++++++--- 4 files changed, 125 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 40786ff..9ac6fc2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5,6 +5,7 @@ dependencies = [ "clap 2.18.0 (registry+https://github.com/rust-lang/crates.io-index)", "clippy 0.0.98 (registry+https://github.com/rust-lang/crates.io-index)", "flate2 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", + "horrorshow 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)", "hyper 0.9.12 (registry+https://github.com/rust-lang/crates.io-index)", "iron 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -142,6 +143,11 @@ dependencies = [ "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "horrorshow" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "hpack" version = "0.2.0" @@ -742,6 +748,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum flate2 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "3eeb481e957304178d2e782f2da1257f1434dfecbae883bafb61ada2a9fea3bb" "checksum gcc 0.3.38 (registry+https://github.com/rust-lang/crates.io-index)" = "553f11439bdefe755bf366b264820f1da70f3aaf3924e594b886beb9c831bcf5" "checksum gdi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0912515a8ff24ba900422ecda800b52f4016a56251922d397c576bf92c690518" +"checksum horrorshow 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)" = "234628f89d562b0ad97069e3f808cf109d0ed51b71a7825bfa6d32f317569543" "checksum hpack 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d2da7d3a34cf6406d9d700111b8eafafe9a251de41ae71d8052748259343b58" "checksum httparse 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6a8abece705b1d32c478f49447b3a575cd07f6e362ff12518f2ee2c9b9ced64e" "checksum hyper 0.9.12 (registry+https://github.com/rust-lang/crates.io-index)" = "d7da01615e9402761faab442396821b57ecb5adb12ac51958561411a82cfdf66" diff --git a/Cargo.toml b/Cargo.toml index fd37e46..e026de1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ authors = ["Frank Denis "] [dependencies] clap = "*" flate2 = "*" +horrorshow = "*" hyper = "*" iron = "*" log = "*" diff --git a/src/main.rs b/src/main.rs index cd4d08c..50fdccd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,8 @@ extern crate flate2; extern crate iron; #[macro_use] extern crate router; +#[macro_use] +extern crate horrorshow; extern crate hyper; #[macro_use] extern crate log; diff --git a/src/webservice.rs b/src/webservice.rs index 3ce2576..acb8a89 100644 --- a/src/webservice.rs +++ b/src/webservice.rs @@ -1,6 +1,7 @@ use asns::*; +use horrorshow::prelude::*; use iron::{BeforeMiddleware, typemap}; -use iron::headers::{CacheControl, CacheDirective}; +use iron::headers::{CacheControl, CacheDirective, Accept}; use iron::mime::*; use iron::modifiers::Header; use iron::prelude::*; @@ -34,6 +35,11 @@ impl BeforeMiddleware for ASNsMiddleware { } } +enum OutputType { + Json, + Html, +} + pub struct WebService; impl WebService { @@ -47,13 +53,116 @@ impl WebService { "See https://iptoasn.com"))) } + fn accept_type(req: &Request) -> OutputType { + let mut output_type = OutputType::Json; + if let Some(header_accept) = req.headers.get::() { + for header in header_accept.iter() { + match header.item { + Mime(TopLevel::Text, SubLevel::Html, _) => { + output_type = OutputType::Html; + break; + } + Mime(_, SubLevel::Json, _) => { + output_type = OutputType::Json; + break; + } + _ => {} + } + } + } + output_type + } + + fn output_json(_ip_str: &str, + map: serde_json::Map<&'static str, serde_json::value::Value>, + cache_header: Header) + -> IronResult { + let json = serde_json::to_string(&map).unwrap(); + let mime_json = Mime(TopLevel::Application, + SubLevel::Json, + vec![(Attr::Charset, Value::Utf8)]); + Ok(Response::with((status::Ok, mime_json, cache_header, json))) + } + + fn output_html(ip_str: &str, + map: serde_json::Map<&'static str, serde_json::value::Value>, + cache_header: Header) + -> IronResult { + let mime_html = Mime(TopLevel::Text, + SubLevel::Html, + vec![(Attr::Charset, Value::Utf8)]); + let html = html!{ + head { + title { : "iptoasn lookup" } + meta(name="viewport", content="width=device-widthinitial-scale=1") { } + link(rel="stylesheet", href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.5/css/bootstrap.min.css", integrity="sha384-AysaV+vQoT3kOAXZkl02PThvDr8HYKPZhNT5h/CXfBThSRXQ6jW5DO2ekP5ViFdi", crossorigin="anonymous") { } + style { + : "body { margin: 1em 4em }" + } + } + body(class="container-fluid") { + header { + h1 { + : format_args!("Information for IP address: {}", ip_str) + } + } + table { + tr { + th { : "Announced" } + td { : format_args!("{}", map.get("announced") + .unwrap().as_bool().unwrap() ) } + } + @ if map.get("announced").unwrap().as_bool().unwrap() == true { + tr { + th { : "First IP" } + td { : format_args!("{}", map.get("first_ip") + .unwrap().as_str().unwrap() )} + } + tr { + th { : "Last IP" } + td { : format_args!("{}", map.get("last_ip") + .unwrap().as_str().unwrap() )} + } + tr { + th { : "AS Number" } + td { : format_args!("{}", map.get("as_number") + .unwrap().as_u64().unwrap() )} + } + tr { + th { : "AS Country code" } + td { : format_args!("{}", map.get("as_country_code") + .unwrap().as_str().unwrap() )} + } + tr { + th { : "AS Description" } + td { : format_args!("{}", map.get("as_description") + .unwrap().as_str().unwrap() )} + } + } + } + } + } + .into_string() + .unwrap(); + let html = format!("\n\n{}", html); + Ok(Response::with((status::Ok, mime_html, cache_header, html))) + } + + fn output(output_type: OutputType, + ip_str: &str, + map: serde_json::Map<&'static str, serde_json::value::Value>, + cache_header: Header) + -> IronResult { + match output_type { + OutputType::Json => Self::output_json(ip_str, map, cache_header), + _ => Self::output_html(ip_str, map, cache_header), + } + } + fn ip_lookup(req: &mut Request) -> IronResult { let mime_text = Mime(TopLevel::Text, SubLevel::Plain, vec![(Attr::Charset, Value::Utf8)]); - let mime_json = Mime(TopLevel::Application, - SubLevel::Json, - vec![(Attr::Charset, Value::Utf8)]); let cache_header = Header(CacheControl(vec![CacheDirective::Public, CacheDirective::MaxAge(TTL)])); let ip_str = match req.extensions.get::().unwrap().find("ip") { @@ -80,8 +189,7 @@ impl WebService { None => { let mut map = serde_json::Map::new(); map.insert("announced", serde_json::value::Value::Bool(false)); - let json = serde_json::to_string(&map).unwrap(); - return Ok(Response::with((status::Ok, mime_json, cache_header, json))); + return Self::output(Self::accept_type(&req), ip_str, map, cache_header); } Some(found) => found, }; @@ -97,8 +205,7 @@ impl WebService { serde_json::value::Value::String(found.country.clone())); map.insert("as_description", serde_json::value::Value::String(found.description.clone())); - let json = serde_json::to_string(&map).unwrap(); - Ok(Response::with((status::Ok, mime_json, cache_header, json))) + Self::output(Self::accept_type(&req), ip_str, map, cache_header) } pub fn start(asns_arc: Arc>>, listen_addr: &str) {