website-icon-extract 0.4.2

extract favicon link paths from a html page from different standards
Documentation
use native_tls::TlsConnector;

use std::fmt;

use std::net::TcpStream;
use std::net::SocketAddr;
use std::io::{Read, Write};

use std::error::Error;
use url::Url;
use std::collections::HashMap;

type BoxResult<T> = Result<T, Box<dyn Error>>;

#[derive(Debug)]
struct RequestError {
    details: String,
}

impl RequestError {
    fn new(msg: &str) -> RequestError {
        RequestError {
            details: msg.to_string(),
        }
    }
}

impl fmt::Display for RequestError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.details)
    }
}

impl Error for RequestError {
    fn description(&self) -> &str {
        &self.details
    }
}

#[derive(Debug)]
pub struct HttpHeaders {
    pub code: u32,
    pub message: String,
    pub version: String,
    pub headers: HashMap<String, String>,
}

pub struct Request {
    info: HttpHeaders,
    readable: Box<dyn Read>,
    content_read_done: bool,
    content: String,
}

use std::time::Duration;
use std::net::ToSocketAddrs;
use std::vec::IntoIter;

fn connect(addrs: Box<IntoIter<SocketAddr>>, timeout: u32) -> BoxResult<TcpStream> {
    for addr in addrs {
        let stream = TcpStream::connect_timeout(
            &addr,
            Duration::from_secs(timeout as u64),
        );
        if let Ok(stream) = stream {
            return Ok(stream);
        }
    }
    return Err(Box::new(RequestError::new("connection was not possible")));
}

fn normalize_url(base_url_str: &str, url: &str) -> Result<String, Box<dyn Error>> {
    let base_url = Url::parse(base_url_str)?;
    let abs_url = base_url.join(&url)?;
    return Ok(abs_url.to_string());
}

impl Request {
    pub fn get_header(&self, name: &str) -> Option<String> {
        let value: Option<&String> = self.info.headers.get(name);
        if let Some(value) = value {
            return Some(value.clone());
        }
        None
    }

    pub fn new_recursive(url_str: &str, agent: &str, timeout: u32) -> BoxResult<Request> {
        let mut depth = 0;
        let mut r = Request::new(url_str, agent, timeout);
        loop{
            if let Ok(r_unwrapped) = r {
                let code = r_unwrapped.get_code();
                if code == 301 || code == 302 {
                    if depth < 5{
                        depth = depth + 1;
                        let l = r_unwrapped.get_header("location");
                        if let Some(l) = l {
                            let l_unwrapped = normalize_url(url_str, &l)?;
                            r = Request::new(&l_unwrapped, agent, timeout);
                            continue;
                        }
                    }
                }
                return Ok(r_unwrapped);
            }
            return r;
        }
    }

    pub fn new(url_str: &str, agent: &str, timeout: u32) -> BoxResult<Request> {
        let url = Url::parse(url_str)?;

        let host = url.host_str()
            .ok_or(RequestError::new("illegal host name"))?;
        let port = url.port_or_known_default()
            .ok_or(RequestError::new("port unknown"))?;

        let connect_str = format!("{}:{}", host, port);
        let addrs_iter = connect_str.to_socket_addrs()?;
        let mut stream: TcpStream = connect(Box::new(addrs_iter),timeout)?;
        stream.set_read_timeout(Some(Duration::from_secs(timeout as u64)))?;

        if url.scheme() == "https" {
            let connector = TlsConnector::builder().build()?;
            let mut stream = connector.connect(host, stream)?;
            let mut host_str = String::from(host);
            if port != 443{
                host_str = format!("{}:{}",host,port);
            }
            let query = url.query();
            if let Some(query) = query {
                let full_path = format!("{}?{}",url.path(),query);
                Request::send_request(agent, &mut stream, &host_str, &full_path)?;
            }else{
                Request::send_request(agent, &mut stream, &host_str, url.path())?;
            }
            let header = Request::read_request(&mut stream)?;
            Ok(Request {
                info: header,
                readable: Box::new(stream),
                content_read_done: false,
                content: String::from(""),
            })
        } else if url.scheme() == "http" {
            let mut host_str = String::from(host);
            if port != 80{
                host_str = format!("{}:{}",host,port);
            }
            let query = url.query();
            if let Some(query) = query {
                let full_path = format!("{}?{}",url.path(),query);
                Request::send_request(agent, &mut stream, &host_str, &full_path)?;
            }else{
                Request::send_request(agent, &mut stream, &host_str, url.path())?;
            }
            let header = Request::read_request(&mut stream)?;
            Ok(Request {
                info: header,
                readable: Box::new(stream),
                content_read_done: false,
                content: String::from(""),
            })
        } else {
            Err(Box::new(RequestError::new("unknown scheme")))
        }
    }

    pub fn read_content(&mut self) -> BoxResult<()> {
        if self.content_read_done {
            return Ok(());
        }
        self.content_read_done = true;

        let content_length = self.info
            .headers
            .get("content-length")
            .unwrap_or(&String::from(""))
            .parse();
        match content_length {
            Ok(content_length) => {
                let mut buffer = vec![0; content_length];
                self.readable.read_exact(&mut buffer)?;
                //let out = String::from_utf8(buffer)?;
                let out = String::from_utf8_lossy(&buffer);
                self.content = out.to_string();
                return Ok(());
            }
            Err(_) => {
                let mut result_buffer = vec![];
                loop {
                    let mut buffer = vec![0; 10000];
                    let result = self.readable.read(&mut buffer);

                    match result {
                        Ok(bytes) => {
                            if bytes == 0 {
                                break;
                            } else {
                                result_buffer.extend(buffer[0..bytes].iter());
                            }
                        }
                        Err(err) => {
                            println!("err {}", err);
                        }
                    }

                    if result_buffer.len() > 10000 {
                        break;
                    }
                }
                //let out = String::from_utf8(result_buffer)?;
                let out = String::from_utf8_lossy(&result_buffer);
                self.content = out.to_string();
                return Ok(());
            }
        }
    }

    pub fn get_content<'a>(&'a self) -> &'a str {
        &self.content
    }

    pub fn get_code(&self) -> u32{
        return self.info.code;
    }

    fn read_stream_until(stream: &mut dyn Read, condition: &'static [u8]) -> BoxResult<String> {
        let mut buffer = vec![0; 1];
        let mut bytes = Vec::new();
        loop {
            let result_recv = stream.read(&mut buffer);
            match result_recv {
                Ok(a) => {
                    if a == 0 {
                        break;
                    } else {
                        bytes.push(buffer[0]);
                        if bytes.len() >= condition.len() {
                            let (_, right) = bytes.split_at(bytes.len() - condition.len());
                            if right == condition {
                                break;
                            }
                        }
                    }
                }
                _ => {
                    break;
                }
            }
            if bytes.len() > 10000 {
                break;
            }
        }
        let out = String::from_utf8_lossy(&bytes);
        Ok(out.to_string())
    }

    fn send_request(agent: &str, stream: &mut dyn Write, host: &str, path: &str) -> BoxResult<()> {
        let request_str = format!(
            "GET {} HTTP/1.0\r\nHost: {}\r\nAccept: */*\r\nUser-Agent: {}\r\nConnection: close\r\n\r\n",
            path, host, agent
        );
        stream.write(request_str.as_bytes())?;
        stream.flush()?;
        Ok(())
    }

    fn decode_first_line(line: &str) -> BoxResult<HttpHeaders> {
        if line.starts_with("HTTP/") {
            if line.len() < 14 {
                return Err(Box::new(RequestError::new("HTTP status line too short")));
            }
            Ok(HttpHeaders {
                code: line[9..12].parse()?,
                message: String::from(&line[13..]),
                version: String::from(&line[5..8]),
                headers: HashMap::new(),
            })
        } else if line.starts_with("ICY") {
            Ok(HttpHeaders {
                code: line[4..7].parse()?,
                message: String::from(&line[8..]),
                version: String::from(""),
                headers: HashMap::new(),
            })
        } else {
            return Err(Box::new(RequestError::new("HTTP header missing")));
        }
    }

    fn read_request(stream: &mut dyn Read) -> BoxResult<HttpHeaders> {
        let out = Request::read_stream_until(stream, b"\r\n")?;
        let mut httpinfo = Request::decode_first_line(&out)?;

        let out = Request::read_stream_until(stream, b"\r\n\r\n")?;
        let lines = out.lines();

        for line in lines {
            match line.find(':') {
                Some(index) => {
                    let (key, value) = line.split_at(index);
                    httpinfo.headers.insert(
                        String::from(key).to_lowercase(),
                        String::from(value[1..].trim()),
                    );
                }
                _ => {}
            }
        }
        Ok(httpinfo)
    }
}