use std::io::Write;
use url::Url;
const CHUNK_SIZE: usize = 1024 * 1024;
#[derive(Debug, Default, Clone)]
pub struct ConnectionPool {}
impl ConnectionPool {
fn new() -> Self {
ConnectionPool {}
}
fn connect(
&mut self,
request: &Request,
method: &str,
url: &Url,
redirects: u32,
mut jar: Option<&mut CookieJar>,
body: SizedReader,
) -> Result<Response, Error> {
let do_chunk = request.header("transfer-encoding")
.map(|enc| enc.len() > 0)
.unwrap_or(false);
let hostname = url.host_str().unwrap_or("localhost");
let query_string = match (url.query(), request.query.len() > 0) {
(Some(urlq), true) => format!("?{}{}", urlq, request.query),
(Some(urlq), false) => format!("?{}", urlq),
(None, true) => format!("?{}", request.query),
(None, false) => "".to_string(),
};
let is_secure = url.scheme().eq_ignore_ascii_case("https");
let cookie_headers: Vec<_> = {
match jar.as_ref() {
None => vec![],
Some(jar) => match_cookies(jar, hostname, url.path(), is_secure),
}
};
let extra_headers = {
let mut extra = vec![];
if !do_chunk && !request.has("content-length") {
if let Some(size) = body.size {
extra.push(format!("Content-Length: {}\r\n", size).parse::<Header>()?);
}
}
extra
};
let headers = request
.headers
.iter()
.chain(cookie_headers.iter())
.chain(extra_headers.iter());
let mut stream = match url.scheme() {
"http" => connect_http(request, &url),
"https" => connect_https(request, &url),
"test" => connect_test(request, &url),
_ => Err(Error::UnknownScheme(url.scheme().to_string())),
}?;
let mut prelude: Vec<u8> = vec![];
write!(prelude, "{} {}{} HTTP/1.1\r\n", method, url.path(), query_string)?;
if !request.has("host") {
write!(prelude, "Host: {}\r\n", url.host().unwrap())?;
}
for header in headers {
write!(prelude, "{}: {}\r\n", header.name(), header.value())?;
}
write!(prelude, "\r\n")?;
stream.write_all(&mut prelude[..])?;
let mut resp = Response::from_read(&mut stream);
if let Some(add_jar) = jar.as_mut() {
for raw_cookie in resp.all("set-cookie").iter() {
let to_parse = if raw_cookie.to_lowercase().contains("domain=") {
raw_cookie.to_string()
} else {
format!("{}; Domain={}", raw_cookie, hostname)
};
match Cookie::parse_encoded(&to_parse[..]) {
Err(_) => (), Ok(mut cookie) => {
let cookie = cookie.into_owned();
add_jar.add(cookie)
}
}
}
}
if resp.redirect() {
if redirects == 0 {
return Err(Error::TooManyRedirects);
}
let location = resp.header("location");
if let Some(location) = location {
let new_url = url.join(location)
.map_err(|_| Error::BadUrl(format!("Bad redirection: {}", location)))?;
return match resp.status {
301 | 302 | 303 => {
send_body(body, do_chunk, &mut stream)?;
let empty = Payload::Empty.into_read();
self.connect(request, "GET", &new_url, redirects - 1, jar, empty)
}
307 | 308 | _ => {
self.connect(request, method, &new_url, redirects - 1, jar, body)
}
};
}
}
send_body(body, do_chunk, &mut stream)?;
resp.set_stream(stream);
Ok(resp)
}
}
fn send_body(body: SizedReader, do_chunk: bool, stream: &mut Stream) -> IoResult<()> {
if do_chunk {
pipe(body.reader, chunked_transfer::Encoder::new(stream))?;
} else {
pipe(body.reader, stream)?;
}
Ok(())
}
fn pipe<R, W>(mut reader: R, mut writer: W) -> IoResult<()>
where
R: Read,
W: Write,
{
let mut buf = [0_u8; CHUNK_SIZE];
loop {
let len = reader.read(&mut buf)?;
if len == 0 {
break;
}
writer.write_all(&buf[0..len])?;
}
Ok(())
}
fn match_cookies<'a>(jar: &'a CookieJar, domain: &str, path: &str, is_secure: bool) -> Vec<Header> {
jar.iter()
.filter(|c| {
let domain_ok = c.domain()
.map(|cdom| domain.contains(cdom))
.unwrap_or(false);
let path_ok = c.path()
.map(|cpath| path.find(cpath).map(|pos| pos == 0).unwrap_or(false))
.unwrap_or(true);
let secure_ok = !c.secure() || is_secure;
domain_ok && path_ok && secure_ok
})
.map(|c| {
let name = c.name().to_string();
let value = c.value().to_string();
let nameval = Cookie::new(name, value).encoded().to_string();
let head = format!("Cookie: {}", nameval);
head.parse::<Header>().ok()
})
.filter(|o| o.is_some())
.map(|o| o.unwrap())
.collect()
}