|
@@ -1,20 +1,21 @@
|
|
|
use config::Config;
|
|
|
use mime::Mime;
|
|
|
use std::io;
|
|
|
-use std::io::Read;
|
|
|
-use hyper::{Get, Post};
|
|
|
+use std::collections::HashSet;
|
|
|
+use hyper::Get;
|
|
|
use hyper::Client;
|
|
|
use hyper::client;
|
|
|
-use hyper::header::{ContentType, ETag, EntityTag, IfNoneMatch, Headers, TransferEncoding};
|
|
|
+use hyper::header::{ContentType, ETag, EntityTag, IfNoneMatch, Headers};
|
|
|
+use hyper::method::Method;
|
|
|
use hyper::server::{Handler, Request, Response};
|
|
|
use hyper::status::StatusCode::{InternalServerError, NotFound, NotModified, Unauthorized};
|
|
|
use hyper::uri::RequestUri::AbsolutePath;
|
|
|
+use hyper::Url;
|
|
|
use rustc_serialize::json;
|
|
|
use rustc_serialize::json::Json;
|
|
|
use sha1::Sha1;
|
|
|
-use url::form_urlencoded;
|
|
|
|
|
|
-const API_ENDPOINT: &'static str = "https://www.cloudflare.com/api_json.html";
|
|
|
+const API_ENDPOINT: &'static str = "https://api.cloudflare.com/client/v4";
|
|
|
|
|
|
const INDEX_HTML: &'static str = include_str!("../../index.html");
|
|
|
const BUNDLE_JS: &'static str = include_str!("../../assets/bundle.js");
|
|
@@ -27,20 +28,41 @@ struct Etags {
|
|
|
pub struct SiteHandler {
|
|
|
cfg: Config,
|
|
|
client: Client,
|
|
|
- etags: Etags
|
|
|
+ etags: Etags,
|
|
|
+ whitelist: HashSet<String>
|
|
|
}
|
|
|
|
|
|
impl SiteHandler {
|
|
|
- fn post(&self, body: &str) -> client::Response {
|
|
|
+ fn make_headers(&self) -> Headers {
|
|
|
let mut headers = Headers::new();
|
|
|
- headers.set(ContentType::form_url_encoded());
|
|
|
- headers.remove::<TransferEncoding>();
|
|
|
- self.client
|
|
|
- .post(API_ENDPOINT)
|
|
|
- .headers(headers)
|
|
|
- .body(body)
|
|
|
- .send()
|
|
|
- .unwrap()
|
|
|
+ headers.set_raw("x-auth-email", vec![self.cfg.email.as_bytes().to_vec()]);
|
|
|
+ headers.set_raw("x-auth-key", vec![self.cfg.token.as_bytes().to_vec()]);
|
|
|
+ headers.set(ContentType::json());
|
|
|
+ headers
|
|
|
+ }
|
|
|
+
|
|
|
+ fn request(&self, method: Method, url: &str, mut body: Option<Request>) -> client::Response {
|
|
|
+ let url = API_ENDPOINT.to_owned() + url;
|
|
|
+ let mut url = Url::parse(&url).unwrap();
|
|
|
+ if method == Get {
|
|
|
+ url.query_pairs_mut().append_pair("per_page", "999");
|
|
|
+ }
|
|
|
+ let request = self.client.request(method, url).headers(self.make_headers());
|
|
|
+ let request = match body.as_mut() {
|
|
|
+ Some(body) => request.body(body),
|
|
|
+ None => request
|
|
|
+ };
|
|
|
+ request.send().unwrap()
|
|
|
+ }
|
|
|
+
|
|
|
+ fn is_valid(&self, path: &str) -> bool {
|
|
|
+ let zone: String = path.chars()
|
|
|
+ .skip(1)
|
|
|
+ .skip_while(|c| *c != '/')
|
|
|
+ .skip(1)
|
|
|
+ .take_while(|c| *c != '/')
|
|
|
+ .collect();
|
|
|
+ self.whitelist.contains(&zone)
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -52,18 +74,37 @@ fn make_etag(source: &str) -> EntityTag {
|
|
|
}
|
|
|
|
|
|
pub fn new(cfg: Config) -> SiteHandler {
|
|
|
- SiteHandler {
|
|
|
+ let mut handler = SiteHandler {
|
|
|
cfg: cfg,
|
|
|
client: Client::new(),
|
|
|
etags: Etags {
|
|
|
index: make_etag(INDEX_HTML),
|
|
|
bundle: make_etag(BUNDLE_JS)
|
|
|
+ },
|
|
|
+ whitelist: HashSet::new()
|
|
|
+ };
|
|
|
+ let mut response = handler.request(Get, "/zones", None);
|
|
|
+ {
|
|
|
+ let domain_whitelist = &handler.cfg.whitelist;
|
|
|
+ let mut whitelist: HashSet<String> = HashSet::new();
|
|
|
+ match Json::from_reader(&mut response) {
|
|
|
+ Ok(body) => {
|
|
|
+ let zones = body.find("result").and_then(|result| result.as_array()).unwrap();
|
|
|
+ for zone in zones {
|
|
|
+ let id = zone.find("id").and_then(|id| id.as_string()).unwrap();
|
|
|
+ let name = zone.find("name").and_then(Json::as_string).unwrap();
|
|
|
+ if domain_whitelist.into_iter().any(|domain| domain == name) {
|
|
|
+ whitelist.insert(id.to_owned());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ Err(error) => {
|
|
|
+ println!("Error: {}", error);
|
|
|
+ }
|
|
|
}
|
|
|
+ handler.whitelist = whitelist;
|
|
|
}
|
|
|
-}
|
|
|
-
|
|
|
-fn get_param<'a>(params: &'a Vec<(String, String)>, key: &str) -> &'a str {
|
|
|
- params.into_iter().find(|tuple| tuple.0 == key).map(|tuple| tuple.1.as_ref()).unwrap_or("")
|
|
|
+ handler
|
|
|
}
|
|
|
|
|
|
fn serve(req: &Request, mut res: Response, content: &str, etag: &EntityTag, mime: Mime) {
|
|
@@ -86,54 +127,37 @@ fn serve(req: &Request, mut res: Response, content: &str, etag: &EntityTag, mime
|
|
|
}
|
|
|
|
|
|
impl Handler for SiteHandler {
|
|
|
- fn handle(&self, mut req: Request, mut res: Response) {
|
|
|
- let mut text = String::new();
|
|
|
- req.read_to_string(&mut text).ok().expect("Failed to get request body");
|
|
|
- match req.uri {
|
|
|
- AbsolutePath(ref path) => match (&req.method, &path[..]) {
|
|
|
+ fn handle(&self, req: Request, mut res: Response) {
|
|
|
+ let uri = req.uri.clone();
|
|
|
+ let method = req.method.clone();
|
|
|
+ match uri {
|
|
|
+ AbsolutePath(ref path) => match (&method, &path[..]) {
|
|
|
(&Get, "/") =>
|
|
|
serve(&req, res, INDEX_HTML, &self.etags.index, mime!(Text/Html; Charset=Utf8)),
|
|
|
(&Get, "/assets/bundle.js") =>
|
|
|
serve(&req, res, BUNDLE_JS, &self.etags.bundle, mime!(Application/Javascript; Charset=Utf8)),
|
|
|
- (&Post, "/api") => {
|
|
|
- let mut params = form_urlencoded::parse(text.as_bytes());
|
|
|
- params.push(("email".to_string(), self.cfg.email.clone()));
|
|
|
- params.push(("tkn".to_string(), self.cfg.token.clone()));
|
|
|
+ (method, url) if path.starts_with("/api") => {
|
|
|
+ let method = method.clone();
|
|
|
+ let path: String = url.chars().skip(4).collect();
|
|
|
|
|
|
- let a = get_param(¶ms, "a");
|
|
|
- let z = get_param(¶ms, "z");
|
|
|
- let whitelist = &self.cfg.whitelist;
|
|
|
- let valid = whitelist.into_iter().any(|domain| domain == z);
|
|
|
- if a == "zone_load_multi" {
|
|
|
- let form_data = form_urlencoded::serialize(¶ms);
|
|
|
- let mut proxy_res = self.post(&form_data);
|
|
|
+ let whitelist = &self.whitelist;
|
|
|
+ if path == "/zones" {
|
|
|
+ let mut proxy_res = self.request(method, &path, None);
|
|
|
match Json::from_reader(&mut proxy_res) {
|
|
|
Ok(mut body) => {
|
|
|
// filter out non-whitelisted domains
|
|
|
body.as_object_mut()
|
|
|
- .and_then(|mut root| root.get_mut("response"))
|
|
|
- .and_then(|mut obj| obj.as_object_mut())
|
|
|
- .and_then(|mut resp| resp.get_mut("zones"))
|
|
|
- .and_then(|mut obj| obj.as_object_mut())
|
|
|
+ .and_then(|mut root| root.get_mut("result"))
|
|
|
+ .and_then(|mut result| result.as_array_mut())
|
|
|
.map(|mut zones| {
|
|
|
- let count = {
|
|
|
- let objs = zones.get_mut("objs")
|
|
|
- .unwrap()
|
|
|
- .as_array_mut()
|
|
|
- .unwrap();
|
|
|
- objs.retain(|zone| {
|
|
|
- let zone_name = zone.find("zone_name").and_then(|name| name.as_string()).unwrap();
|
|
|
- whitelist.into_iter().any(|domain| domain == zone_name)
|
|
|
- });
|
|
|
- objs.len()
|
|
|
- };
|
|
|
- zones.insert("count".to_string(), Json::U64(count as u64));
|
|
|
+ zones.retain(|zone| {
|
|
|
+ let id = zone.find("id").and_then(|id| id.as_string()).unwrap();
|
|
|
+ whitelist.contains(id)
|
|
|
+ });
|
|
|
});
|
|
|
|
|
|
- res.headers_mut().extend(proxy_res.headers.iter());
|
|
|
- res.headers_mut().remove::<TransferEncoding>();
|
|
|
-
|
|
|
let json = json::encode(&body).unwrap();
|
|
|
+ res.headers_mut().set(ContentType(mime!(Application/Json; Charset=Utf8)));
|
|
|
res.send(json.as_bytes()).unwrap();
|
|
|
},
|
|
|
Err(error) => {
|
|
@@ -143,11 +167,9 @@ impl Handler for SiteHandler {
|
|
|
}
|
|
|
};
|
|
|
}
|
|
|
- else if valid {
|
|
|
- let form_data = form_urlencoded::serialize(¶ms);
|
|
|
- let mut proxy_res = self.post(&form_data);
|
|
|
- res.headers_mut().extend(proxy_res.headers.iter());
|
|
|
- res.headers_mut().remove::<TransferEncoding>();
|
|
|
+ else if self.is_valid(&path) {
|
|
|
+ let mut proxy_res = self.request(method, &path, Some(req));
|
|
|
+ res.headers_mut().set(ContentType(mime!(Application/Json; Charset=Utf8)));
|
|
|
let mut res = res.start().unwrap();
|
|
|
io::copy(&mut proxy_res, &mut res).ok().expect("Failed to proxy");
|
|
|
res.end().unwrap();
|