File: std-https-v1_0-rustls.rs

package info (click to toggle)
rust-io-stream 0.0.2%2Bds-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 268 kB
  • sloc: makefile: 2; sh: 1
file content (116 lines) | stat: -rw-r--r-- 3,232 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
#![cfg(feature = "std")]

use std::{
    env,
    io::{stdin, stdout, Write as _},
    net::TcpStream,
    sync::Arc,
};

use io_stream::{
    coroutines::{
        read::{ReadStream, ReadStreamResult},
        write::{WriteStream, WriteStreamResult},
    },
    runtimes::std::handle,
};
use memchr::memmem;
use rustls::{ClientConfig, ClientConnection, StreamOwned};
use rustls_platform_verifier::ConfigVerifierExt;
use url::Url;

fn main() {
    env_logger::init();

    let url: Url = match env::var("URL") {
        Ok(url) => url.parse().unwrap(),
        Err(_) => read_line("URL?").parse().unwrap(),
    };

    let mut stream = connect(&url);

    let request = format!(
        "GET {} HTTP/1.0\r\nHost: {}:{}\r\n\r\n",
        url.path(),
        url.host_str().unwrap(),
        url.port_or_known_default().unwrap(),
    );

    println!("request: {request:?}");

    let mut arg = None;
    let mut write = WriteStream::new(request.into_bytes());

    loop {
        match write.resume(arg) {
            WriteStreamResult::Ok(_) => break,
            WriteStreamResult::Err(err) => panic!("{err}"),
            WriteStreamResult::Eof => panic!("reached unexpected EOF"),
            WriteStreamResult::Io(io) => arg = Some(handle(&mut stream, io).unwrap()),
        }
    }

    let mut response = Vec::new();

    loop {
        let mut arg = None;
        let mut read = ReadStream::new();

        let output = loop {
            match read.resume(arg) {
                ReadStreamResult::Ok(output) => break output,
                ReadStreamResult::Err(err) => panic!("{err}"),
                ReadStreamResult::Eof => panic!("reached unexpected EOF"),
                ReadStreamResult::Io(io) => arg = Some(handle(&mut stream, io).unwrap()),
            }
        };

        let bytes = output.bytes();

        match memmem::find(bytes, &[b'\r', b'\n', b'\r', b'\n']) {
            None => {
                response.extend(bytes);
                continue;
            }
            Some(n) => {
                response.extend(&bytes[..n]);
                break;
            }
        }
    }

    println!("----------------");
    println!("{}", String::from_utf8_lossy(&response));
    println!("----------------");
}

fn read_line(prompt: &str) -> String {
    print!("{prompt} ");
    stdout().flush().unwrap();

    let mut line = String::new();
    stdin().read_line(&mut line).unwrap();

    line.trim().to_owned()
}

trait StreamExt: std::io::Read + std::io::Write {}
impl<T: std::io::Read + std::io::Write> StreamExt for T {}

fn connect(url: &Url) -> Box<dyn StreamExt> {
    let domain = url.domain().unwrap();

    if url.scheme().eq_ignore_ascii_case("https") {
        let config = ClientConfig::with_platform_verifier()
            .expect("Failed to load TLS certificates")
            .into();
        let server_name = domain.to_string().try_into().unwrap();
        let conn = ClientConnection::new(config, server_name).unwrap();
        let tcp = TcpStream::connect((domain.to_string(), 443)).unwrap();
        let tls = StreamOwned::new(conn, tcp);
        Box::new(tls)
    } else {
        let tcp = TcpStream::connect((domain.to_string(), 80)).unwrap();
        Box::new(tcp)
    }
}