| 
					
				 | 
			
			
				@@ -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(); 
			 |