Some checks failed
Update All Top Ranking Issues / update_top_ranking_issues (push) Has been cancelled
Triage Project Sync (#84) / Sync triage project (push) Has been cancelled
release_nightly / notify_on_failure (push) Has been cancelled
release_nightly / check_style (push) Has been cancelled
release_nightly / run_tests_windows (push) Has been cancelled
release_nightly / clippy_windows (push) Has been cancelled
release_nightly / bundle_linux_aarch64 (push) Has been cancelled
release_nightly / bundle_linux_x86_64 (push) Has been cancelled
release_nightly / bundle_mac_aarch64 (push) Has been cancelled
release_nightly / bundle_mac_x86_64 (push) Has been cancelled
release_nightly / bundle_windows_aarch64 (push) Has been cancelled
release_nightly / bundle_windows_x86_64 (push) Has been cancelled
release_nightly / build_nix_linux_x86_64 (push) Has been cancelled
release_nightly / build_nix_mac_aarch64 (push) Has been cancelled
release_nightly / update_nightly_tag (push) Has been cancelled
Hotfix Review Monitor / check-hotfix-reviews (push) Has been cancelled
Stale PR Review Reminder / check-stale-prs (push) Has been cancelled
Update Weekly Top Ranking Issues / update_top_ranking_issues (push) Has been cancelled
Bump collab-staging Tag / update-collab-staging-tag (push) Has been cancelled
compliance_check / scheduled_compliance_check (push) Has been cancelled
Single-commit orphan branch: full zed-industries/zed @ 8c74db0 source tree with a 3-file patch applied (no upstream history). Patch (crates/gpui_linux/src/linux/wayland/): - serial.rs: add SerialKind::KeyboardEnter - client.rs: store wl_keyboard.enter serial; add latest_serial_of() - window.rs: activate() uses keyboard-enter serial (Mutter focus gate) Mutter honors window activation only when the token carries the keyboard- focus serial from wl_keyboard.enter; GPUI used a stale mouse-press serial. See docs/tray-window-focus-wayland.md in logiguard. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
194 lines
5.6 KiB
Rust
194 lines
5.6 KiB
Rust
use anyhow::{Context, Result};
|
|
use base64::Engine;
|
|
use httparse::{EMPTY_HEADER, Response};
|
|
use tokio::{
|
|
io::{AsyncBufReadExt, AsyncWriteExt, BufStream},
|
|
net::TcpStream,
|
|
};
|
|
#[cfg(any(target_os = "windows", target_os = "macos"))]
|
|
use tokio_native_tls::{TlsConnector, native_tls};
|
|
#[cfg(not(any(target_os = "windows", target_os = "macos")))]
|
|
use tokio_rustls::TlsConnector;
|
|
use url::Url;
|
|
|
|
use super::AsyncReadWrite;
|
|
|
|
pub(super) enum HttpProxyType<'t> {
|
|
HTTP(Option<HttpProxyAuthorization<'t>>),
|
|
HTTPS(Option<HttpProxyAuthorization<'t>>),
|
|
}
|
|
|
|
pub(super) struct HttpProxyAuthorization<'t> {
|
|
username: &'t str,
|
|
password: &'t str,
|
|
}
|
|
|
|
pub(super) fn parse_http_proxy<'t>(scheme: &str, proxy: &'t Url) -> HttpProxyType<'t> {
|
|
let auth = proxy.password().map(|password| HttpProxyAuthorization {
|
|
username: proxy.username(),
|
|
password,
|
|
});
|
|
if scheme.starts_with("https") {
|
|
HttpProxyType::HTTPS(auth)
|
|
} else {
|
|
HttpProxyType::HTTP(auth)
|
|
}
|
|
}
|
|
|
|
pub(crate) async fn connect_http_proxy_stream(
|
|
stream: TcpStream,
|
|
http_proxy: HttpProxyType<'_>,
|
|
rpc_host: (&str, u16),
|
|
proxy_domain: &str,
|
|
) -> Result<Box<dyn AsyncReadWrite>> {
|
|
match http_proxy {
|
|
HttpProxyType::HTTP(auth) => http_connect(stream, rpc_host, auth).await,
|
|
HttpProxyType::HTTPS(auth) => https_connect(stream, rpc_host, auth, proxy_domain).await,
|
|
}
|
|
.context("error connecting to http/https proxy")
|
|
}
|
|
|
|
async fn http_connect<T>(
|
|
stream: T,
|
|
target: (&str, u16),
|
|
auth: Option<HttpProxyAuthorization<'_>>,
|
|
) -> Result<Box<dyn AsyncReadWrite>>
|
|
where
|
|
T: AsyncReadWrite,
|
|
{
|
|
let mut stream = BufStream::new(stream);
|
|
let request = make_request(target, auth);
|
|
stream.write_all(request.as_bytes()).await?;
|
|
stream.flush().await?;
|
|
check_response(&mut stream).await?;
|
|
Ok(Box::new(stream))
|
|
}
|
|
|
|
#[cfg(any(target_os = "windows", target_os = "macos"))]
|
|
async fn https_connect<T>(
|
|
stream: T,
|
|
target: (&str, u16),
|
|
auth: Option<HttpProxyAuthorization<'_>>,
|
|
proxy_domain: &str,
|
|
) -> Result<Box<dyn AsyncReadWrite>>
|
|
where
|
|
T: AsyncReadWrite,
|
|
{
|
|
let tls_connector = TlsConnector::from(native_tls::TlsConnector::new()?);
|
|
let stream = tls_connector.connect(proxy_domain, stream).await?;
|
|
http_connect(stream, target, auth).await
|
|
}
|
|
|
|
#[cfg(not(any(target_os = "windows", target_os = "macos")))]
|
|
async fn https_connect<T>(
|
|
stream: T,
|
|
target: (&str, u16),
|
|
auth: Option<HttpProxyAuthorization<'_>>,
|
|
proxy_domain: &str,
|
|
) -> Result<Box<dyn AsyncReadWrite>>
|
|
where
|
|
T: AsyncReadWrite,
|
|
{
|
|
let proxy_domain = rustls_pki_types::ServerName::try_from(proxy_domain)
|
|
.context("Address resolution failed")?
|
|
.to_owned();
|
|
let tls_connector = TlsConnector::from(std::sync::Arc::new(http_client_tls::tls_config()));
|
|
let stream = tls_connector.connect(proxy_domain, stream).await?;
|
|
http_connect(stream, target, auth).await
|
|
}
|
|
|
|
fn make_request(target: (&str, u16), auth: Option<HttpProxyAuthorization<'_>>) -> String {
|
|
let (host, port) = target;
|
|
let mut request = format!(
|
|
"CONNECT {host}:{port} HTTP/1.1\r\nHost: {host}:{port}\r\nProxy-Connection: Keep-Alive\r\n"
|
|
);
|
|
if let Some(HttpProxyAuthorization { username, password }) = auth {
|
|
let auth =
|
|
base64::prelude::BASE64_STANDARD.encode(format!("{username}:{password}").as_bytes());
|
|
let auth = format!("Proxy-Authorization: Basic {auth}\r\n");
|
|
request.push_str(&auth);
|
|
}
|
|
request.push_str("\r\n");
|
|
request
|
|
}
|
|
|
|
async fn check_response<T>(stream: &mut BufStream<T>) -> Result<()>
|
|
where
|
|
T: AsyncReadWrite,
|
|
{
|
|
let response = recv_response(stream).await?;
|
|
let mut dummy_headers = [EMPTY_HEADER; MAX_RESPONSE_HEADERS];
|
|
let mut parser = Response::new(&mut dummy_headers);
|
|
parser.parse(response.as_bytes())?;
|
|
|
|
match parser.code {
|
|
Some(code) => {
|
|
if code == 200 {
|
|
Ok(())
|
|
} else {
|
|
Err(anyhow::anyhow!(
|
|
"Proxy connection failed with HTTP code: {code}"
|
|
))
|
|
}
|
|
}
|
|
None => Err(anyhow::anyhow!(
|
|
"Proxy connection failed with no HTTP code: {}",
|
|
parser.reason.unwrap_or("Unknown reason")
|
|
)),
|
|
}
|
|
}
|
|
|
|
const MAX_RESPONSE_HEADER_LENGTH: usize = 4096;
|
|
const MAX_RESPONSE_HEADERS: usize = 16;
|
|
|
|
async fn recv_response<T>(stream: &mut BufStream<T>) -> Result<String>
|
|
where
|
|
T: AsyncReadWrite,
|
|
{
|
|
let mut response = String::new();
|
|
loop {
|
|
if stream.read_line(&mut response).await? == 0 {
|
|
return Err(anyhow::anyhow!("End of stream"));
|
|
}
|
|
|
|
if MAX_RESPONSE_HEADER_LENGTH < response.len() {
|
|
return Err(anyhow::anyhow!("Maximum response header length exceeded"));
|
|
}
|
|
|
|
if response.ends_with("\r\n\r\n") {
|
|
return Ok(response);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use url::Url;
|
|
|
|
use super::{HttpProxyAuthorization, HttpProxyType, parse_http_proxy};
|
|
|
|
#[test]
|
|
fn test_parse_http_proxy() {
|
|
let proxy = Url::parse("http://proxy.example.com:1080").unwrap();
|
|
let scheme = proxy.scheme();
|
|
|
|
let version = parse_http_proxy(scheme, &proxy);
|
|
assert!(matches!(version, HttpProxyType::HTTP(None)))
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_http_proxy_with_auth() {
|
|
let proxy = Url::parse("http://username:password@proxy.example.com:1080").unwrap();
|
|
let scheme = proxy.scheme();
|
|
|
|
let version = parse_http_proxy(scheme, &proxy);
|
|
assert!(matches!(
|
|
version,
|
|
HttpProxyType::HTTP(Some(HttpProxyAuthorization {
|
|
username: "username",
|
|
password: "password"
|
|
}))
|
|
))
|
|
}
|
|
}
|