<commit_before>//! The HTTP Client that perform query

use std::io::{self, Write};
use std::fmt::Arguments;

use super::request::Request;
use super::http;
use super::response::{Response, ResponseBuilder};
use super::results::CabotResult;

/// Perform the http query
pub struct Client {
    verbose: bool
}


impl Client {

    /// Construct a new `Client`
    pub fn new() -> Self {
        Client{verbose: false}
    }

    /// Execute the query [Request](../request/struct.Request.html) and
    /// return the associate [Response](../response/struct.Response.html).
    pub fn execute(&self, request: &Request) -> CabotResult<Response> {
        let mut out = CabotLibWrite::new();
        http::http_query(&request, &mut out, self.verbose)?;
        out.response()
    }
}


struct CabotLibWrite {
    response_builder: ResponseBuilder
}


impl CabotLibWrite {

    pub fn new() -> Self {
        CabotLibWrite{response_builder: ResponseBuilder::new()}
    }

    pub fn response(&self) -> CabotResult<Response> {
        self.response_builder.build()
    }
}

impl Write for CabotLibWrite {


    fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
        info!("Parsing http response");
        let mut response = Vec::with_capacity(buf.len());
        response.extend_from_slice(buf);
        let response = unsafe { String::from_utf8_unchecked(response) };
        let response: Vec<&str> = response.splitn(2, "\r\n\r\n").collect();
        let header_len = response.get(0).unwrap().len();
        let mut headers: Vec<&str> = response.get(0).unwrap().split("\r\n").collect();
        let mut builder = ResponseBuilder::new();
        let status_line = headers.remove(0);
        debug!("Adding status line {}", status_line);
        builder = builder.set_status_line(status_line);
        let mut iter_header = headers.iter().peekable();
        let header = iter_header.next();
        if header.is_some() {
            let buf = header.unwrap();
            let mut header = String::with_capacity(buf.len() * 2);
            header.push_str(buf);
            loop {
                {
                    let buf = iter_header.peek();
                    if buf.is_none() {
                        builder = builder.add_header(header.as_str());
                        break;
                    }
                    let buf = buf.unwrap();
                    if buf.starts_with(" ") || buf.starts_with("\t") {
                        debug!("Obsolete line folded header reveived in {}",
                               header);
                        header.push_str(" ");
                        header.push_str(buf.trim_left());
                    }
                    else {
                        debug!("Adding header {}", header);
                        builder = builder.add_header(header.as_str());
                        header.clear();
                        header.push_str(buf);
                    }
                }
                let _  = iter_header.next();
            }
        }
        let body = if (header_len + 4) < buf.len() {
            &buf[(header_len + 4)..buf.len()]
        }
        else {
            &buf[..]
        }; 
        //debug!("Adding body {:?}", body);
        builder = builder.set_body(body);
        self.response_builder = builder;
        // debug!("Response Builder - {:?}", self.response_builder);
        Ok(())
    }

    fn flush(&mut self) -> io::Result<()> {
        Ok(())
    }

    // Don't implemented unused method

    fn write(&mut self, _: &[u8]) -> io::Result<usize> {
        Err(io::Error::new(io::ErrorKind::Other, "Not Implemented"))
    }


    fn write_fmt(&mut self, _: Arguments) -> io::Result<()> {
        Err(io::Error::new(io::ErrorKind::Other, "Not Implemented"))
    }

}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_build_http_response_from_string() {
        let response = vec!["HTTP/1.1 200 Ok",
                            "Content-Type: text/plain",
                            "Content-Length: 12",
                            "",
                            "Hello World!"];
        let response = response.join("\r\n");

        let mut out = CabotLibWrite::new();
        out.write_all(response.as_bytes()).unwrap();
        let response = out.response().unwrap();
        assert_eq!(response.http_version(), "HTTP/1.1");
        assert_eq!(response.status_code(), 200);
        assert_eq!(response.status_line(), "200 Ok");
        let headers: &[&str] = &["Content-Type: text/plain", "Content-Length: 12"];
        assert_eq!(response.headers(), headers);
        assert_eq!(response.body_as_string().unwrap(), "Hello World!".to_owned());

    }

    #[test]
    fn test_build_http_header_obsolete_line_folding() {
        let response = vec!["HTTP/1.1 200 Ok",
                            "ows: https://tools.ietf.org/html/rfc7230",
                            "  #section-3.2.4",
                            "Content-Length: 12",
                            "",
                            "Hello World!"];
        let response = response.join("\r\n");

        let mut out = CabotLibWrite::new();
        out.write_all(response.as_bytes()).unwrap();
        let response = out.response().unwrap();
        assert_eq!(response.http_version(), "HTTP/1.1");
        assert_eq!(response.status_code(), 200);
        assert_eq!(response.status_line(), "200 Ok");
        let headers: &[&str] = &[
            "ows: https://tools.ietf.org/html/rfc7230 #section-3.2.4",
            "Content-Length: 12"];
        assert_eq!(response.headers(), headers);
        assert_eq!(response.body_as_string().unwrap(), "Hello World!".to_owned());

    }

    #[test]
    fn test_build_http_header_obsolete_line_folding_tab() {
        let response = vec!["HTTP/1.1 200 Ok",
                            "ows: https://tools.ietf.org/html/rfc7230",
                            "\t#section-3.2.4",
                            "Content-Length: 12",
                            "",
                            "Hello World!"];
        let response = response.join("\r\n");

        let mut out = CabotLibWrite::new();
        out.write_all(response.as_bytes()).unwrap();
        let response = out.response().unwrap();
        assert_eq!(response.http_version(), "HTTP/1.1");
        assert_eq!(response.status_code(), 200);
        assert_eq!(response.status_line(), "200 Ok");
        let headers: &[&str] = &[
            "ows: https://tools.ietf.org/html/rfc7230 #section-3.2.4",
            "Content-Length: 12"];
        assert_eq!(response.headers(), headers);
        assert_eq!(response.body_as_string().unwrap(), "Hello World!".to_owned());

    }

}<commit_msg>Fix response body in case of no content<commit_after>//! The HTTP Client that perform query

use std::io::{self, Write};
use std::fmt::Arguments;

use super::request::Request;
use super::http;
use super::response::{Response, ResponseBuilder};
use super::results::CabotResult;

/// Perform the http query
pub struct Client {
    verbose: bool
}


impl Client {

    /// Construct a new `Client`
    pub fn new() -> Self {
        Client{verbose: false}
    }

    /// Execute the query [Request](../request/struct.Request.html) and
    /// return the associate [Response](../response/struct.Response.html).
    pub fn execute(&self, request: &Request) -> CabotResult<Response> {
        let mut out = CabotLibWrite::new();
        http::http_query(&request, &mut out, self.verbose)?;
        out.response()
    }
}


struct CabotLibWrite {
    response_builder: ResponseBuilder
}


impl CabotLibWrite {

    pub fn new() -> Self {
        CabotLibWrite{response_builder: ResponseBuilder::new()}
    }

    pub fn response(&self) -> CabotResult<Response> {
        self.response_builder.build()
    }
}

impl Write for CabotLibWrite {


    fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
        info!("Parsing http response");
        let mut response = Vec::with_capacity(buf.len());
        response.extend_from_slice(buf);
        let response = unsafe { String::from_utf8_unchecked(response) };
        let response: Vec<&str> = response.splitn(2, "\r\n\r\n").collect();
        let header_len = response.get(0).unwrap().len();
        let mut headers: Vec<&str> = response.get(0).unwrap().split("\r\n").collect();
        let mut builder = ResponseBuilder::new();
        let status_line = headers.remove(0);
        debug!("Adding status line {}", status_line);
        builder = builder.set_status_line(status_line);
        let mut iter_header = headers.iter().peekable();
        let header = iter_header.next();
        if header.is_some() {
            let buf = header.unwrap();
            let mut header = String::with_capacity(buf.len() * 2);
            header.push_str(buf);
            loop {
                {
                    let buf = iter_header.peek();
                    if buf.is_none() {
                        builder = builder.add_header(header.as_str());
                        break;
                    }
                    let buf = buf.unwrap();
                    if buf.starts_with(" ") || buf.starts_with("\t") {
                        debug!("Obsolete line folded header reveived in {}",
                               header);
                        header.push_str(" ");
                        header.push_str(buf.trim_left());
                    }
                    else {
                        debug!("Adding header {}", header);
                        builder = builder.add_header(header.as_str());
                        header.clear();
                        header.push_str(buf);
                    }
                }
                let _  = iter_header.next();
            }
        }
        let body = if (header_len + 4) < buf.len() {
            &buf[(header_len + 4)..buf.len()]
        }
        else {
            &[]
        }; 
        //debug!("Adding body {:?}", body);
        builder = builder.set_body(body);
        self.response_builder = builder;
        // debug!("Response Builder - {:?}", self.response_builder);
        Ok(())
    }

    fn flush(&mut self) -> io::Result<()> {
        Ok(())
    }

    // Don't implemented unused method

    fn write(&mut self, _: &[u8]) -> io::Result<usize> {
        Err(io::Error::new(io::ErrorKind::Other, "Not Implemented"))
    }


    fn write_fmt(&mut self, _: Arguments) -> io::Result<()> {
        Err(io::Error::new(io::ErrorKind::Other, "Not Implemented"))
    }

}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_build_http_response_from_string() {
        let response = vec!["HTTP/1.1 200 Ok",
                            "Content-Type: text/plain",
                            "Content-Length: 12",
                            "",
                            "Hello World!"];
        let response = response.join("\r\n");

        let mut out = CabotLibWrite::new();
        out.write_all(response.as_bytes()).unwrap();
        let response = out.response().unwrap();
        assert_eq!(response.http_version(), "HTTP/1.1");
        assert_eq!(response.status_code(), 200);
        assert_eq!(response.status_line(), "200 Ok");
        let headers: &[&str] = &["Content-Type: text/plain", "Content-Length: 12"];
        assert_eq!(response.headers(), headers);
        assert_eq!(response.body_as_string().unwrap(), "Hello World!".to_owned());

    }

    #[test]
    fn test_build_http_header_obsolete_line_folding() {
        let response = vec!["HTTP/1.1 200 Ok",
                            "ows: https://tools.ietf.org/html/rfc7230",
                            "  #section-3.2.4",
                            "Content-Length: 12",
                            "",
                            "Hello World!"];
        let response = response.join("\r\n");

        let mut out = CabotLibWrite::new();
        out.write_all(response.as_bytes()).unwrap();
        let response = out.response().unwrap();
        assert_eq!(response.http_version(), "HTTP/1.1");
        assert_eq!(response.status_code(), 200);
        assert_eq!(response.status_line(), "200 Ok");
        let headers: &[&str] = &[
            "ows: https://tools.ietf.org/html/rfc7230 #section-3.2.4",
            "Content-Length: 12"];
        assert_eq!(response.headers(), headers);
        assert_eq!(response.body_as_string().unwrap(), "Hello World!".to_owned());

    }

    #[test]
    fn test_build_http_header_obsolete_line_folding_tab() {
        let response = vec!["HTTP/1.1 200 Ok",
                            "ows: https://tools.ietf.org/html/rfc7230",
                            "\t#section-3.2.4",
                            "Content-Length: 12",
                            "",
                            "Hello World!"];
        let response = response.join("\r\n");

        let mut out = CabotLibWrite::new();
        out.write_all(response.as_bytes()).unwrap();
        let response = out.response().unwrap();
        assert_eq!(response.http_version(), "HTTP/1.1");
        assert_eq!(response.status_code(), 200);
        assert_eq!(response.status_line(), "200 Ok");
        let headers: &[&str] = &[
            "ows: https://tools.ietf.org/html/rfc7230 #section-3.2.4",
            "Content-Length: 12"];
        assert_eq!(response.headers(), headers);
        assert_eq!(response.body_as_string().unwrap(), "Hello World!".to_owned());

    }

    #[test]
    fn test_build_http_no_response_body() {
        let response = vec!["HTTP/1.1 302 Moved",
                            "Location: https://tools.ietf.org/html/rfc7230#section-3.3",
                            ];
        let response = response.join("\r\n");

        let mut out = CabotLibWrite::new();
        out.write_all(response.as_bytes()).unwrap();
        let response = out.response().unwrap();
        assert_eq!(response.http_version(), "HTTP/1.1");
        assert_eq!(response.status_code(), 302);
        assert_eq!(response.status_line(), "302 Moved");
        let headers: &[&str] = &[
            "Location: https://tools.ietf.org/html/rfc7230#section-3.3",
            ];
        assert_eq!(response.headers(), headers);
        assert_eq!(response.body_as_string().unwrap(), "");
    }
}
{'fraction_non_alphanumeric': 0.15583046320376748, 'fraction_numerical': 0.014268644127363464, 'mean_word_length': 1.8467386954781913, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 35, 'https://': 10, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4174318', 'n_tokens_mistral': 4244, 'n_tokens_neox': 4105, 'n_words': 782}