diff --git a/src/relay/tcprelay/http.rs b/src/relay/tcprelay/http.rs index fd26978f..9caf9961 100644 --- a/src/relay/tcprelay/http.rs +++ b/src/relay/tcprelay/http.rs @@ -24,18 +24,22 @@ use std::io::{self, Read, Write}; use std::net::{SocketAddr, SocketAddrV4, SocketAddrV6}; use std::mem; +use std::str; +use std::fmt; use hyper::uri::RequestUri; -use hyper::header::Headers; +use hyper::header::{Header, HeaderFormat, Headers}; use hyper::status::StatusCode; use hyper::version::HttpVersion; use hyper::method::Method; use hyper; -use httparse::{self, Request}; +use httparse::{self, Request, Response}; use url::Host; +use ip::IpAddr; + use futures::{self, Future, BoxFuture, Poll}; use tokio_core::io::write_all; @@ -112,9 +116,14 @@ impl HttpRequest { .map(|(w, _)| w) .boxed() } + + #[inline] + pub fn get_address(&self) -> Result { + get_address(&self.request_uri) + } } -pub fn get_address(uri: &RequestUri) -> Result { +fn get_address(uri: &RequestUri) -> Result { match uri { &RequestUri::Authority(ref s) => { match s.parse::() { @@ -164,6 +173,64 @@ pub fn get_address(uri: &RequestUri) -> Result { } } +#[derive(Debug)] +pub struct HttpResponse { + pub version: HttpVersion, + pub status: StatusCode, + pub message: Option, + pub headers: Headers, +} + +impl HttpResponse { + /// Creates an empty Response + pub fn new() -> HttpResponse { + HttpResponse { + version: HttpVersion::Http11, + status: StatusCode::Ok, + message: None, + headers: Headers::new(), + } + } + + pub fn from_raw<'headers, 'buf: 'headers>(rsp: &Response<'headers, 'buf>, + headers: &'headers [httparse::Header]) + -> hyper::Result { + Ok(HttpResponse { + version: if rsp.version.unwrap() == 1 { + HttpVersion::Http11 + } else { + HttpVersion::Http10 + }, + status: StatusCode::from_u16(rsp.code.unwrap()), + message: rsp.reason.map(|s| s.to_owned()).clone(), + headers: try!(Headers::from_raw(headers)), + }) + } + + pub fn write_to(self, w: W) -> BoxFuture + where W: Write + Send + 'static + { + futures::lazy(move || { + let mut w = Vec::new(); + let msg = self.message + .as_ref() + .map(|s| &s[..]) + .or_else(|| self.status.canonical_reason()) + .unwrap_or(""); + try!(write!(w, "{} {} {}\r\n", self.version, self.status.to_u16(), msg)); + for header in self.headers.iter() { + try!(write!(w, "{}: {}\r\n", header.name(), header.value_string())); + } + + try!(write!(w, "\r\n")); + Ok(w) + }) + .and_then(|buf| write_all(w, buf)) + .map(|(w, _)| w) + .boxed() + } +} + pub fn write_response(w: W, version: HttpVersion, status: StatusCode) -> BoxFuture where W: Write + Send + 'static { @@ -171,6 +238,92 @@ pub fn write_response(w: W, version: HttpVersion, status: StatusCode) -> BoxF write_all(w, buf.into_bytes()).map(|(w, _)| w).boxed() } +#[derive(Debug, Clone)] +pub struct XForwardFor(pub Vec); + +impl Header for XForwardFor { + fn header_name() -> &'static str { + "X-Forward-For" + } + + fn parse_header(raw: &[Vec]) -> hyper::Result { + let mut ips = Vec::new(); + for raw_h in raw.iter() { + let xfor = try!(str::from_utf8(&raw_h[..])); + for xfor_str in xfor.split(',') { + let trimmed = xfor_str.trim(); + if trimmed.is_empty() { + // Ignore empty string + continue; + } + match trimmed.parse::() { + Ok(i) => ips.push(i), + Err(..) => return Err(hyper::Error::Header), + } + } + } + + Ok(XForwardFor(ips)) + } +} + +impl HeaderFormat for XForwardFor { + fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut first = true; + for ip in &self.0 { + if first { + first = false; + } else { + try!(write!(f, ", ")); + } + + try!(write!(f, "{}", ip)); + } + + Ok(()) + } +} + +#[derive(Debug, Clone)] +pub struct XRealIp(pub Option); + +impl Header for XRealIp { + fn header_name() -> &'static str { + "X-Real-IP" + } + + fn parse_header(raw: &[Vec]) -> hyper::Result { + let mut ip = None; + for raw_ip in raw.iter() { + let x_ip = try!(str::from_utf8(&raw_ip[..])); + match x_ip.trim().parse::() { + Ok(i) => { + if let Some(prev_ip) = ip.take() { + if prev_ip != i { + return Err(hyper::Error::Header); + } + } + + ip = Some(i); + } + Err(..) => return Err(hyper::Error::Header), + } + } + + Ok(XRealIp(ip)) + } +} + +impl HeaderFormat for XRealIp { + fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result { + if let Some(ref ip) = self.0 { + try!(write!(f, "{}", ip)); + } + + Ok(()) + } +} + /// HTTP Client pub enum RequestReader where R: Read diff --git a/src/relay/tcprelay/local.rs b/src/relay/tcprelay/local.rs index cc167729..83dbd9e3 100644 --- a/src/relay/tcprelay/local.rs +++ b/src/relay/tcprelay/local.rs @@ -232,13 +232,9 @@ impl HttpRelayServer { super::connect_proxy_server(&handle, svr_cfg, addr) .and_then(move |(svr_r, svr_w)| { let handshake_resp = format!("{} 200 Connection Established\r\n\r\n", http_version); + trace!("Sending HTTP tunnel handshake response"); write_all(w, handshake_resp.into_bytes()).and_then(|(w, _)| flush(w)).map(|w| (svr_r, svr_w, w)) }) - .and_then(move |(svr_r, svr_w, w)| { - req.write_to(svr_w) - .and_then(|svr_w| write_all(svr_w, remains)) - .map(move |(svr_w, _)| (svr_r, svr_w, w)) - }) .and_then(move |(svr_r, svr_w, w)| { let c2s = copy(r, svr_w); let s2c = copy(svr_r, w); @@ -263,7 +259,7 @@ impl HttpRelayServer { req.method, req.request_uri); - match http::get_address(&req.request_uri) { + match req.get_address() { Ok(..) => { req.clear_request_uri_host(); let content_length = req.headers.get::().unwrap_or(&ContentLength(0)).0; @@ -352,7 +348,7 @@ impl HttpRelayServer { req.method, req.request_uri); - match http::get_address(&req.request_uri) { + match req.get_address() { Ok(addr) => { req.clear_request_uri_host(); futures::finished((r, w, req, addr, remains)).boxed()