#![allow(unused)]
#![allow(non_upper_case_globals)]
#![allow(non_snake_case)]
#![allow(non_camel_case_types)]
#[derive(Copy, Clone)]
pub enum Method {
Get,
Head,
Post,
Put,
Patch, Delete,
Connect,
Options,
Trace,
}
impl Method {
pub fn String(&self) -> &str {
match self {
Method::Get => "GET",
Method::Head => "HEAD",
Method::Post => "POST",
Method::Put => "PUT",
Method::Patch => "PATCH",
Method::Delete => "DELETE",
Method::Connect => "CONNECT",
Method::Options => "OPTHONS",
Method::Trace => "TRACE",
}
}
}
pub enum Status {
Continue = 100, SwitchingProtocols = 101, Processing = 102, EarlyHints = 103, OK = 200, Created = 201, Accepted = 202, NonAuthoritativeInfo = 203, NoContent = 204, ResetContent = 205, PartialContent = 206, MultiStatus = 207, AlreadyReported = 208, IMUsed = 226, MultipleChoices = 300, MovedPermanently = 301, Found = 302, SeeOther = 303, NotModified = 304, UseProxy = 305, TemporaryRedirect = 307, PermanentRedirect = 308, BadRequest = 400, Unauthorized = 401, PaymentRequired = 402, Forbidden = 403, NotFound = 404, MethodNotAllowed = 405, NotAcceptable = 406, ProxyAuthRequired = 407, RequestTimeout = 408, Conflict = 409, Gone = 410, LengthRequired = 411, PreconditionFailed = 412, RequestEntityTooLarge = 413, RequestURITooLong = 414, UnsupportedMediaType = 415, RequestedRangeNotSatisfiable = 416, ExpectationFailed = 417, Teapot = 418, MisdirectedRequest = 421, UnprocessableEntity = 422, Locked = 423, FailedDependency = 424, TooEarly = 425, UpgradeRequired = 426, PreconditionRequired = 428, TooManyRequests = 429, RequestHeaderFieldsTooLarge = 431, UnavailableForLegalReasons = 451, InternalServerError = 500, NotImplemented = 501, BadGateway = 502, ServiceUnavailable = 503, GatewayTimeout = 504, HTTPVersionNotSupported = 505, VariantAlsoNegotiates = 506, InsufficientStorage = 507, LoopDetected = 508, NotExtended = 510, NetworkAuthenticationRequired = 511, }
impl Status {
pub fn StatusText(&self) -> &str {
match self {
StatusContinue => "Continue",
StatusSwitchingProtocols => "Switching Protocols",
StatusProcessing => "Processing",
StatusEarlyHints => "Early Hints",
StatusOK => "OK",
StatusCreated => "Created",
StatusAccepted => "Accepted",
StatusNonAuthoritativeInfo => "Non-Authoritative Information",
StatusNoContent => "No Content",
StatusResetContent => "Reset Content",
StatusPartialContent => "Partial Content",
StatusMultiStatus => "Multi-Status",
StatusAlreadyReported => "Already Reported",
StatusIMUsed => "IM Used",
StatusMultipleChoices => "Multiple Choices",
StatusMovedPermanently => "Moved Permanently",
StatusFound => "Found",
StatusSeeOther => "See Other",
StatusNotModified => "Not Modified",
StatusUseProxy => "Use Proxy",
StatusTemporaryRedirect => "Temporary Redirect",
StatusPermanentRedirect => "Permanent Redirect",
StatusBadRequest => "Bad Request",
StatusUnauthorized => "Unauthorized",
StatusPaymentRequired => "Payment Required",
StatusForbidden => "Forbidden",
StatusNotFound => "Not Found",
StatusMethodNotAllowed => "Method Not Allowed",
StatusNotAcceptable => "Not Acceptable",
StatusProxyAuthRequired => "Proxy Authentication Required",
StatusRequestTimeout => "Request Timeout",
StatusConflict => "Conflict",
StatusGone => "Gone",
StatusLengthRequired => "Length Required",
StatusPreconditionFailed => "Precondition Failed",
StatusRequestEntityTooLarge => "Request Entity Too Large",
StatusRequestURITooLong => "Request URI Too Long",
StatusUnsupportedMediaType => "Unsupported Media Type",
StatusRequestedRangeNotSatisfiable => "Requested Range Not Satisfiable",
StatusExpectationFailed => "Expectation Failed",
StatusTeapot => "I'm a teapot",
StatusMisdirectedRequest => "Misdirected Request",
StatusUnprocessableEntity => "Unprocessable Entity",
StatusLocked => "Locked",
StatusFailedDependency => "Failed Dependency",
StatusTooEarly => "Too Early",
StatusUpgradeRequired => "Upgrade Required",
StatusPreconditionRequired => "Precondition Required",
StatusTooManyRequests => "Too Many Requests",
StatusRequestHeaderFieldsTooLarge => "Request Header Fields Too Large",
StatusUnavailableForLegalReasons => "Unavailable For Legal Reasons",
StatusInternalServerError => "Internal Server Error",
StatusNotImplemented => "Not Implemented",
StatusBadGateway => "Bad Gateway",
StatusServiceUnavailable => "Service Unavailable",
StatusGatewayTimeout => "Gateway Timeout",
StatusHTTPVersionNotSupported => "HTTP Version Not Supported",
StatusVariantAlsoNegotiates => "Variant Also Negotiates",
StatusInsufficientStorage => "Insufficient Storage",
StatusLoopDetected => "Loop Detected",
StatusNotExtended => "Not Extended",
StatusNetworkAuthenticationRequired => "Network Authentication Required",
}
}
}
use gostd_builtin::*;
mod error;
pub use error::{HTTPConnectError, HttpResult};
const DefaultMaxHeaderBytes: int32 = 1 << 20;
const DefaultMaxIdleConnsPerHost: int32 = 2;
const TimeFormat: &str = "Mon, 02 Jan 2006 15:04:05 GMT";
const TrailerPrefix: &str = "Trailer:";
use gostd_io::*;
use gostd_strings as strings;
use gostd_time as time;
use gostd_url as url;
use std::collections::HashMap;
use std::collections::HashSet;
use std::convert::TryInto;
pub fn Get(url: &str) -> HttpResult<Response> {
Client::New().Get(url)
}
pub fn Head(url: &str) -> HttpResult<Response> {
Client::New().Head(url)
}
pub fn Post(url: &str, contentType: &str, body: Option<Bytes>) -> HttpResult<Response> {
Client::New().Post(url, contentType, body)
}
pub fn PostForm(url: &str, data: url::Values) -> HttpResult<Response> {
Client::New().PostForm(url, data)
}
pub fn Patch(url: &str, body: Option<Bytes>) -> HttpResult<Response> {
Client::New().Patch(url, body)
}
pub fn Put(url: &str, body: Option<Bytes>) -> HttpResult<Response> {
Client::New().Put(url, body)
}
pub fn Delete(url: &str) -> HttpResult<Response> {
Client::New().Delete(url)
}
pub struct Client {
Transport: Box<dyn RoundTripper>,
Jar: Box<dyn CookieJar>,
Timeout: time::Duration,
}
impl Default for Client {
fn default() -> Self {
Self {
Transport: Box::new(Transport::default()),
Timeout: time::Duration::new(0),
Jar: Box::new(Cookie::default()),
}
}
}
impl Client {
pub fn New() -> Self {
Self::default()
}
pub fn Get(&mut self, url: &str) -> HttpResult<Response> {
let mut req = Request::New(Method::Get, url, None)?;
self.Do(&mut req)
}
pub fn Post(
&mut self,
url: &str,
contentType: &str,
body: Option<Bytes>,
) -> HttpResult<Response> {
let mut req = Request::New(Method::Post, url, body)?;
req.Header.Set("Content-Type", contentType);
self.Do(&mut req)
}
pub fn PostForm(&mut self, url: &str, data: url::Values) -> HttpResult<Response> {
self.Post(
url,
"application/x-www-form-urlencoded",
Some(data.Encode().into_bytes().into()),
)
}
pub fn Head(&mut self, url: &str) -> HttpResult<Response> {
let mut req = Request::New(Method::Head, url, None)?;
self.Do(&mut req)
}
pub fn Patch(&mut self, url: &str, body: Option<Bytes>) -> HttpResult<Response> {
let mut req = Request::New(Method::Patch, url, body)?;
self.Do(&mut req)
}
pub fn Put(&mut self, url: &str, body: Option<Bytes>) -> HttpResult<Response> {
let mut req = Request::New(Method::Put, url, body)?;
self.Do(&mut req)
}
pub fn Delete(&mut self, url: &str) -> HttpResult<Response> {
let mut req = Request::New(Method::Delete, url, None)?;
self.Do(&mut req)
}
pub fn Do(&mut self, req: &mut Request) -> HttpResult<Response> {
self.done(req)
}
fn send(
&mut self,
req: &mut Request,
deadline: time::Time,
) -> HttpResult<(Response, fn() -> bool)> {
let (resp, didTimeout) = send(req, self.transport(), deadline)?;
Ok((resp, didTimeout))
}
fn done(&mut self, req: &mut Request) -> HttpResult<Response> {
let deadline = self.deadline();
let (resp, didTimeout) = self.send(req, deadline)?;
Ok(resp)
}
fn deadline(&mut self) -> time::Time {
if self.Timeout > time::Duration::new(0) {
return time::Now().Add(&self.Timeout);
}
time::Time::default()
}
fn transport(&self) -> Box<dyn RoundTripper> {
Box::new(Transport::default())
}
}
fn send(
ireq: &mut Request,
mut rt: Box<dyn RoundTripper>,
deadline: time::Time,
) -> HttpResult<(Response, fn() -> bool)> {
let mut resp = Response::default();
fn didTimeout() -> bool {
return false;
};
loop {
let mut resp = rt.RoundTrip(ireq)?;
let mut loc = resp.Header.Get("Location");
let (redirectMethod, shouldRedirect, includeBody) =
redirectBehavior(ireq.Method.as_str(), &resp, ireq);
if !shouldRedirect {
return Ok((resp, didTimeout));
}
let mut u = ireq.URL.Parse(loc.as_str())?;
let urlRef = refererForURL(&ireq.URL, &u);
ireq.Method = redirectMethod.clone();
ireq.URL = u.clone();
ireq.Header.Set("Referer", urlRef.as_str());
}
}
fn redirectBehavior(reqMethod: &str, resp: &Response, ireq: &Request) -> (String, bool, bool) {
let mut shouldRedirect = false;
let mut includeBody = false;
match resp.StatusCode {
301 | 302 | 303 => return (Method::Get.String().to_string(), true, false),
307 | 308 => {
if resp.Header.Get("Location") == "" {
shouldRedirect = true;
includeBody = false;
}
if ireq.Body.is_none() && ireq.ContentLength != 0 {
shouldRedirect = false;
}
}
_ => (),
}
(reqMethod.to_string(), shouldRedirect, includeBody)
}
pub trait RoundTripper {
fn RoundTrip(&mut self, r: &Request) -> HttpResult<Response>;
}
fn refererForURL(lastReq: &url::URL, newReq: &url::URL) -> String {
if (lastReq.Scheme == "https") && (newReq.Scheme == "http") {
return "".to_string();
}
let mut referer = lastReq.String();
if let Some(user) = lastReq.User.clone() {
return referer;
}
let auth = "@";
referer = strings::Replace(referer.as_str(), auth, "", 1);
referer
}
#[derive(Default, Clone, Debug)]
pub struct Request {
Method: String,
URL: url::URL,
Proto: String,
ProtoMajor: int,
ProtoMinor: int,
pub Header: Header,
pub Body: Option<Bytes>,
ContentLength: int64,
TransferEncoding: Vec<String>,
Close: bool,
Host: String,
Form: url::Values,
PostForm: url::Values,
Trailer: Header,
RemoteAddr: String,
RequestURI: String,
isTLS: bool,
}
impl Request {
pub fn New(method: Method, url: &str, body: Option<Bytes>) -> HttpResult<Request> {
let mut u = url::Parse(url)?;
u.Host = removeEmptyPort(u.Host.as_str()).to_string();
let mut req = Request {
Method: method.String().to_owned(),
URL: u.clone(),
Proto: "HTTP/1.1".to_string(),
ProtoMajor: 1,
ProtoMinor: 1,
Header: Header::default(),
ContentLength: 0,
TransferEncoding: Vec::<String>::new(),
Close: false,
Form: url::Values::default(),
PostForm: url::Values::default(),
Trailer: Header::default(),
RemoteAddr: "".to_string(),
RequestURI: "".to_string(),
Body: None,
Host: u.Host.to_owned(),
isTLS: false,
};
if let Some(buf) = body {
req.ContentLength = len!(buf) as i64;
req.Body = Some(buf);
}
if strings::HasPrefix(url, "https://") {
req.isTLS = true
}
Ok(req)
}
pub fn Write(&self) -> HttpResult<Vec<u8>> {
self.write(false)
}
fn write(&self, usingProxy: bool) -> HttpResult<Vec<u8>> {
let mut buf = strings::Builder::new();
let host = self.Host.clone();
let ruri = self.URL.RequestURI();
let userAgent = "rust-http-client/1.1";
buf.WriteString(format!("{} {} HTTP/1.1\r\n", self.Method.as_str(), ruri).as_str());
buf.WriteString(format!("Host: {}\r\n", host).as_str());
buf.WriteString(format!("User-Agent: {}\r\n", userAgent).as_str());
buf.WriteString(self.writeHeader().as_str());
buf.WriteString("\r\n");
if let Some(body) = &self.Body {
buf.Write(body.to_vec())?;
buf.WriteString("\r\n");
}
Ok(buf.Bytes())
}
fn writeHeader(&self) -> String {
let mut buf = strings::Builder::new();
for (k, v) in &self.Header.0 {
if len!(v) > 1 {
let value = strings::Join(v.iter().map(|x| x.as_str()).collect(), ",");
buf.WriteString(format!("{}: {}\r\n", k.as_str(), value.as_str()).as_str());
} else {
buf.WriteString(format!("{}: {}\r\n", k.as_str(), v[0].as_str()).as_str());
}
}
if self.ContentLength > 0 {
buf.WriteString(format!("Content-Length: {}\r\n", self.ContentLength).as_str());
}
buf.String()
}
}
#[derive(Default, Debug, Clone)]
pub struct Response {
pub Status: String,
pub StatusCode: int,
pub Proto: String,
pub ProtoMajor: int,
pub ProtoMinor: int,
pub Header: Header,
pub ContentLength: int64,
pub TransferEncoding: Vec<String>,
pub Body: Option<BytesMut>,
pub Close: bool,
pub Uncompressed: bool,
pub Trailer: Header,
pub Request: Request,
}
impl Response {
pub fn Cookies(&self) -> Vec<Cookie> {
readSetCookies(&self.Header)
}
}
fn isCookieNameValid(raw: &str) -> bool {
if raw == "" {
return false;
}
strings::IndexFunc(raw, isNotToken) < 0
}
fn isNotToken(r: rune) -> bool {
!validHeaderFieldByte(r as u8)
}
fn validCookieValueByte(b: byte) -> bool {
return 0x20 <= b && b < 0x7f && b != b'"' && b != b';' && b != b'\\';
}
fn parseCookieValue(mut raw: &str, allowDoubleQuote: bool) -> (string, bool) {
if allowDoubleQuote
&& len!(raw) > 1
&& raw.bytes().nth(0) == Some(b'"')
&& raw.bytes().nth((len!(raw) - 1)) == Some(b'"')
{
raw = &raw[1..len!(raw) - 1]
}
for i in 0..len!(raw) {
if !validCookieValueByte(raw.as_bytes()[i as usize]) {
return ("".to_string(), false);
}
}
return (raw.to_string(), true);
}
fn readSetCookies(h: &Header) -> Vec<Cookie> {
let cookieCount = len!(h.0.get(&"Set-Cookie".to_string()).unwrap());
if cookieCount == 0 {
return vec![];
}
let mut cookies = Vec::with_capacity(cookieCount);
for line in h.0.get("Set-Cookie").unwrap() {
let mut parts = strings::Split(strings::TrimSpace(line.as_str()), ";");
if len!(parts) == 1 && parts[0] == "" {
continue;
}
parts[0] = strings::TrimSpace(parts[0]);
let j = strings::Index(parts[0], "=");
if j < 0 {
continue;
}
let mut name = &parts[0][..j as usize];
let mut value = &parts[0][j as usize + 1..];
if !isCookieNameValid(name) {
continue;
}
let cookie = parseCookieValue(value, true);
value = &cookie.0;
let ok = &cookie.1;
if !ok {
continue;
}
let mut c = Cookie::default();
c.Name = name.to_string();
c.Value = value.to_string();
c.Raw = line.to_string();
for i in 1..len!(parts) {
parts[i] = strings::TrimSpace(parts[i]);
if len!(parts[i]) == 0 {
continue;
}
let mut attr = parts[i];
let mut val = "";
let j = strings::Index(attr, "=");
if j >= 0 {
attr = &attr[..j as usize];
val = &attr[j as usize + 1..];
}
if !attr.is_ascii() {
continue;
}
let cok = parseCookieValue(val, false);
val = &cok.0;
let ok = &cok.1;
if !ok {
c.Unparsed.push(parts[i].to_string());
continue;
}
let lowerAttr = strings::ToLower(attr);
match lowerAttr.as_str() {
"sameste" => {
if !val.is_ascii() {
c.SameSite = SameSite::SameSiteDefaultMode;
continue;
}
let lowerVal = strings::ToLower(val);
match lowerVal.as_str() {
"lax" => c.SameSite = SameSite::SameSiteLaxMode,
"strict" => c.SameSite = SameSite::SameSiteStrictMode,
"none" => c.SameSite = SameSite::SameSiteNoneModepub,
_ => c.SameSite = SameSite::SameSiteDefaultMode,
}
continue;
}
"secure" => {
c.Secure = true;
continue;
}
"httponly" => {
c.HttpOnly = true;
continue;
}
"domain" => {
c.Domain = val.to_string();
continue;
}
"max-age" => {
let mut secs: int = 0;
let res = val.parse::<int>();
if res.is_err() || (secs != 0 && val.bytes().nth(0) == Some(b'0')) {
continue;
}
secs = res.unwrap();
if secs <= 0 {
secs = -1;
}
c.MaxAge = secs;
continue;
}
"expires" => {
c.RawExpires = val.to_string();
if let Ok(mut exptime) = time::Parse(time::RFC1123, val) {
c.Expires = exptime.UTC();
} else {
if let Ok(mut exptime) = time::Parse("Mon, 02-Jan-2006 15:04:05 MST", val) {
c.Expires = exptime.UTC();
} else {
c.Expires = time::Time::default();
continue;
}
}
continue;
}
"path" => {
c.Path = val.to_string();
continue;
}
_ => (),
}
c.Unparsed.push(parts[i].to_string());
}
cookies.push(c);
}
cookies
}
#[derive(PartialEq, PartialOrd, Debug, Clone)]
pub enum SameSite {
SameSiteDefaultMode,
SameSiteLaxMode,
SameSiteStrictMode,
SameSiteNoneModepub,
}
impl Default for SameSite {
fn default() -> Self {
SameSite::SameSiteDefaultMode
}
}
trait CookieJar {
fn SetCookies(&mut self, u: &url::URL, cookies: Vec<Cookie>);
fn Cookies(&self, u: &url::URL) -> Vec<Cookie>;
}
#[derive(Default, PartialEq, Debug, Clone)]
pub struct Header(HashMap<String, Vec<String>>);
impl Header {
pub fn NewWithHashMap(m: HashMap<String, Vec<String>>) -> Header {
Header(m)
}
pub fn Add(&mut self, key: &str, value: &str) {
self.0
.get_mut(&key.to_string())
.unwrap()
.push(value.to_string())
}
pub fn Set(&mut self, key: &str, value: &str) {
self.0.insert(key.to_string(), vec![value.to_string()]);
}
pub fn Get(&self, key: &str) -> String {
self.0
.get(key)
.unwrap_or(&vec!["".to_string()])
.get(0)
.unwrap()
.to_string()
}
}
#[derive(Default, PartialEq, PartialOrd, Debug, Clone)]
pub struct Cookie {
Name: String,
Value: String,
Path: String, Domain: String, Expires: time::Time, RawExpires: String,
MaxAge: int,
Secure: bool,
HttpOnly: bool,
SameSite: SameSite,
Raw: String,
Unparsed: Vec<String>, }
impl CookieJar for Cookie {
fn SetCookies(&mut self, u: &url::URL, cookies: Vec<Cookie>) {
todo!()
}
fn Cookies(&self, u: &url::URL) -> Vec<Cookie> {
todo!()
}
}
fn hasPort(s: &str) -> bool {
strings::LastIndex(s, ":") > strings::LastIndex(s, "]")
}
fn removeEmptyPort(host: &str) -> &str {
if hasPort(host) {
return strings::TrimSuffix(host, ":");
}
host
}
use std::iter::FromIterator;
use std::sync;
#[derive(Default, Clone)]
struct Transport {
closeIdle: bool,
Proxy: Option<url::URL>,
ForceAttemptHTTP2: bool,
MaxIdleConns: int,
DisableKeepAlives: bool,
DisableCompression: bool,
iMaxIdleConnsPerHost: int,
MaxConnsPerHost: int,
MaxResponseHeaderBytes: int64,
WriteBufferSize: int,
ReadBufferSize: int,
tlsNextProtoWasNil: bool,
}
use std::net;
use std::sync::mpsc;
impl RoundTripper for Transport {
fn RoundTrip(&mut self, req: &Request) -> HttpResult<Response> {
self.roundTrip(req)
}
}
impl Transport {
fn roundTrip(&mut self, req: &Request) -> HttpResult<Response> {
let treq = &mut transportRequest {
Req: req.clone(),
extra: None,
};
let cm = self.connectMethodForRequest(treq)?;
let (mut pconn, mut conn) = self.getConn(treq, cm)?;
pconn.roundTrip(treq, conn)
}
fn getConn(
&mut self,
treq: &transportRequest,
cm: connectMethod,
) -> HttpResult<(persistConn, TcpConn)> {
let conn = self.dialConn(cm)?;
let pconn = persistConn::default();
Ok((pconn, conn))
}
fn dialConn(&mut self, cm: connectMethod) -> HttpResult<TcpConn> {
self.dial("tcp", cm.addr().as_str())
}
fn dial(&mut self, network: &str, addr: &str) -> HttpResult<TcpConn> {
Ok(net::TcpStream::connect(addr)?)
}
fn connectMethodForRequest(&mut self, treq: &transportRequest) -> HttpResult<connectMethod> {
let mut cm = connectMethod::default();
cm.targetScheme = treq.Req.URL.Scheme.clone();
cm.targetAddr = canonicalAddr(&treq.Req.URL.clone());
cm.proxyURL = None;
cm.onlyH1 = true; Ok(cm)
}
fn wirteBufferSize(self) -> int {
if self.WriteBufferSize > 0 {
return self.WriteBufferSize;
}
4 << 10
}
fn readBufferSize(self) -> int {
if self.ReadBufferSize > 0 {
return self.ReadBufferSize;
}
4 << 10
}
}
fn canonicalAddr(url: &url::URL) -> String {
let portMap: HashMap<String, String> = [
("http".to_string(), "80".to_string()),
("https".to_string(), "443".to_string()),
("socks5".to_string(), "1080".to_string()),
]
.iter()
.cloned()
.collect();
let addr = url.Hostname().to_string();
let mut port = url.Port().to_string();
if port == "" {
port = portMap.get(url.Scheme.as_str()).unwrap().to_string();
}
strings::Join(vec![addr.as_str(), port.as_str()], ":")
}
#[derive(Default, Clone)]
struct transportRequest {
Req: Request,
extra: Option<Header>,
}
impl transportRequest {
fn extraHeaders(&mut self) -> Header {
if let Some(extra) = self.extra.clone() {
return extra;
}
Header::default()
}
}
#[derive(Default, PartialEq, PartialOrd, Clone)]
struct connectMethod {
proxyURL: Option<url::URL>, targetScheme: String, targetAddr: String,
onlyH1: bool, }
impl connectMethod {
fn scheme(&self) -> String {
self.targetScheme.clone()
}
fn addr(&self) -> String {
self.targetAddr.clone()
}
}
type TcpConn = TcpStream;
use std::sync::mpsc::channel;
#[derive(Default, Clone)]
struct persistConn {
t: Transport,
nwrite: int64,
isProxy: bool,
sawEOF: bool,
readLimit: int64,
numExpectedResponses: int,
broken: bool,
reused: bool,
}
use bytes::Bytes;
use rustls::ClientConnection;
use rustls::StreamOwned;
use std::convert::TryFrom;
use std::io::prelude::*;
use std::io::BufReader;
use std::net::Shutdown;
use std::net::TcpStream;
use std::rc::Rc;
use std::sync::Arc;
use webpki_roots::TLS_SERVER_ROOTS;
impl persistConn {
fn roundTrip(&mut self, req: &mut transportRequest, mut conn: TcpConn) -> HttpResult<Response> {
self.numExpectedResponses += 1;
let mut requestedGzip = false;
if !self.t.DisableCompression
&& req.Req.Header.Get("Accept-Encoding") == ""
&& req.Req.Header.Get("Range") == ""
&& req.Req.Method != "HEAD".to_string()
{
requestedGzip = true;
}
if req.Req.Close {
req.Req.Header.Set("Connection", "close");
}
let r = req.Req.Write()?;
if req.Req.isTLS {
let mut tlsConn = getTLSConn(req.Req.Host.as_str(), conn)?;
tlsConn.write(r.as_slice())?;
let mut reader = BufReader::new(tlsConn);
let resp = ReadResponse(reader, &req.Req)?;
Ok(resp)
} else {
conn.write(r.as_slice())?;
let mut reader = BufReader::new(conn);
let resp = ReadResponse(reader, &req.Req)?;
Ok(resp)
}
}
}
use bytes::{Buf, BytesMut};
use rustls::pki_types::ServerName;
use rustls::{ClientConfig, RootCertStore};
use std::io::ErrorKind;
fn get_tls_config() -> Arc<ClientConfig> {
let mut clientRootCert = RootCertStore::from_iter(TLS_SERVER_ROOTS.iter().cloned());
Arc::new(
ClientConfig::builder()
.with_root_certificates(clientRootCert)
.with_no_client_auth(),
)
}
fn getTLSConn(
dnsName: &str,
socket: TcpConn,
) -> HttpResult<StreamOwned<ClientConnection, TcpConn>> {
let tlsconfig = get_tls_config();
let serverName = ServerName::try_from(dnsName.to_owned())?;
let mut tlsClient = ClientConnection::new(tlsconfig, serverName)?;
let mut tlsConn = StreamOwned::new(tlsClient, socket);
Ok(tlsConn)
}
pub fn ReadResponse(mut r: impl BufRead, req: &Request) -> HttpResult<Response> {
let mut resp = Response::default();
resp.Request = req.clone();
let mut line = String::new();
r.read_line(&mut line)?;
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() < 3 {
return Err(HTTPConnectError::ConnectionFailure(
"malformed HTTP response".to_string(),
));
}
resp.Proto = parts[0].to_string();
resp.Status = parts[1..].join(" ");
resp.StatusCode = parts[1].parse::<isize>()?;
let vers = ParseHTTPVersion(&resp.Proto);
let ok = vers.2;
if !ok {
return Err(HTTPConnectError::ConnectionFailure(
"malformed HTTP version".to_string(),
));
}
resp.ProtoMajor = vers.0;
resp.ProtoMinor = vers.1;
let mut headPart = BytesMut::new();
let mut head_line = String::new();
while r.read_line(&mut head_line).is_ok() {
if head_line.as_bytes() == b"\r\n" {
break;
}
headPart.extend_from_slice(head_line.as_bytes());
head_line.clear()
}
resp.Header = Header::NewWithHashMap(parseHeader(&headPart)?);
fixPragmaCacheControl(&mut resp.Header);
if resp.Header.Get("Transfer-Encoding").as_str() == "chunked" {
resp.Body = Some(parseChunkedBody(r)?);
} else {
let ln: usize = resp
.Header
.Get("Content-Length")
.as_str()
.parse::<usize>()
.expect("Content-Length is not exist");
let mut buf = vec![0; ln]; r.read_exact(&mut buf)?;
resp.Body = Some(BytesMut::from(&buf[..]));
}
resp.ContentLength = resp.Body.as_ref().map_or(0, |b| b.len() as i64);
Ok(resp)
}
fn parseChunkedBody(mut r: impl BufRead) -> HttpResult<BytesMut> {
let mut body = BytesMut::new();
let mut size_buf = vec![];
while r.read_until(b'\n', &mut size_buf).is_ok() {
if size_buf.ends_with(b"\r\n") {
size_buf.truncate(size_buf.len() - 2);
let size_str = std::str::from_utf8(&size_buf)?;
if size_str == "0" {
break;
}
let chunk_size = usize::from_str_radix(size_str, 16)?;
let mut chunk_data = vec![0u8; chunk_size];
r.read_exact(&mut chunk_data)?;
body.extend_from_slice(&chunk_data);
let mut crlf = [0u8; 2];
r.read_exact(&mut crlf)?;
size_buf.clear();
}
}
Ok(body)
}
pub type MIMEHeader = HashMap<String, Vec<String>>;
fn fixPragmaCacheControl(header: &mut Header) {
if let Some(hp) = header.0.get("Pragma") {
if len!(hp) > 0 && &hp[0] == "no-cache" && header.0.get("Cache-Control").is_none() {
header.Set("Cache-Control", "no-cache");
}
}
}
fn parseHeader(headPart: &[u8]) -> HttpResult<MIMEHeader> {
let mut m: MIMEHeader = HashMap::new();
let lines = std::str::from_utf8(headPart)?;
for kv in lines.split("\r\n") {
if let Some((key, value)) = kv.split_once(':') {
let key = canonicalMIMEHeaderKey(key);
if key.is_empty() {
continue;
}
let value = value
.trim_start_matches(|c: char| c == ' ' || c == '\t')
.trim_matches('"')
.to_string();
m.entry(key).or_insert_with(Vec::new).push(value);
}
}
Ok(m)
}
fn startIndexOfBody(response: &Vec<u8>) -> Option<usize> {
let mut sep: Vec<u8> = vec![];
for (i, b) in response.iter().map(|&x| x).enumerate() {
if b == b'\r' || b == b'\n' {
sep.push(b);
} else {
sep.clear();
}
if sep.as_slice() == b"\r\n\r\n" {
return Some(i);
}
}
None
}
fn validHeaderFieldByte(b: byte) -> bool {
let isTokenTable: HashSet<char> = [
'!', '#', '$', '%', '&', '\'', '*', '+', '.', '0', '1', '2', '3', '4', '5', '6', '7', '8',
'9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q',
'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
'y', 'z', '|', '~',
]
.iter()
.cloned()
.collect();
isTokenTable.contains(&(b as char))
}
const toLower: byte = (b'a' - b'A');
fn canonicalMIMEHeaderKey(s: &str) -> String {
let mut upper = true;
let mut new = String::with_capacity(s.len());
for &byte in s.as_bytes() {
let c = if upper && byte >= b'a' && byte <= b'z' {
byte - toLower
} else if !upper && byte >= b'A' && byte <= b'Z' {
byte + toLower
} else {
byte
};
upper = c == b'-';
new.push(c as char);
}
new
}
pub fn ParseHTTPVersion(vers: &str) -> (int, int, bool) {
let big: int = 1_000_000;
if !vers.starts_with("HTTP/") {
return (0, 0, false);
}
let version_part = &vers[5..];
let parts: Vec<&str> = version_part.split('.').collect();
if parts.len() != 2 {
return (0, 0, false);
}
match (parts[0].parse::<int>(), parts[1].parse::<int>()) {
(Ok(major), Ok(minor)) if major >= 0 && major <= big && minor >= 0 && minor <= big => {
(major, minor, true)
}
_ => (0, 0, false),
}
}