blob: 6a34c8789fe66b67996c3ff8e3bde8fbb6b03a6f [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
use crate::Error;
use reqwest::{
StatusCode, Version,
header::{self, HeaderMap, HeaderValue},
};
use tokio_tungstenite::{
WebSocketStream,
tungstenite::{
handshake::{client::generate_key, derive_accept_key},
protocol::{Role, WebSocketConfig},
},
};
pub type Socket = WebSocketStream<reqwest::Upgraded>;
/// HTTP upgrade uses the same proxy/auth/TLS routing as ordinary requests.
/// Cancellation drops the handshake future; no response.create is sent here.
pub async fn connect_websocket(
policy: &crate::Policy,
client: reqwest::ClientBuilder,
url: &str,
mut headers: HeaderMap,
limit: usize,
) -> Result<Socket, Error> {
let key = generate_key();
headers.remove(header::CONTENT_LENGTH);
headers.remove(header::HOST);
headers.remove(header::SEC_WEBSOCKET_EXTENSIONS);
headers.remove(header::SEC_WEBSOCKET_PROTOCOL);
headers
.try_insert(header::CONNECTION, HeaderValue::from_static("Upgrade"))
.map_err(|_| Error::InvalidHeaders)?;
headers
.try_insert(header::UPGRADE, HeaderValue::from_static("websocket"))
.map_err(|_| Error::InvalidHeaders)?;
headers
.try_insert(
header::SEC_WEBSOCKET_VERSION,
HeaderValue::from_static("13"),
)
.map_err(|_| Error::InvalidHeaders)?;
headers
.try_insert(
header::SEC_WEBSOCKET_KEY,
HeaderValue::from_str(&key).map_err(|_| Error::InvalidHeaders)?,
)
.map_err(|_| Error::InvalidHeaders)?;
let target = reqwest::Url::parse(url).map_err(|_| Error::InvalidHeaders)?;
policy.check_request_headers(&target, &headers, false, false)?;
let response = client
.redirect(reqwest::redirect::Policy::none())
.http1_only()
.build()?
.get(url)
.version(Version::HTTP_11)
.headers(headers)
.send()
.await?;
let headers = response.headers();
let accept = derive_accept_key(key.as_bytes());
if response.status() != StatusCode::SWITCHING_PROTOCOLS
|| headers.get_all(header::SEC_WEBSOCKET_ACCEPT).iter().count() != 1
|| headers
.get(header::SEC_WEBSOCKET_ACCEPT)
.is_none_or(|value| value.as_bytes() != accept.as_bytes())
|| !has_token(headers, header::CONNECTION, "upgrade")
|| !has_token(headers, header::UPGRADE, "websocket")
|| headers.contains_key(header::SEC_WEBSOCKET_EXTENSIONS)
|| headers.contains_key(header::SEC_WEBSOCKET_PROTOCOL)
{
return Err(Error::InvalidUpgrade);
}
Ok(WebSocketStream::from_raw_socket(
response.upgrade().await?,
Role::Client,
Some(
WebSocketConfig::default()
.max_message_size(Some(limit))
.max_frame_size(Some(limit)),
),
)
.await)
}
fn has_token(headers: &HeaderMap, name: header::HeaderName, expected: &str) -> bool {
headers
.get_all(name)
.iter()
.filter_map(|value| value.to_str().ok())
.flat_map(|value| value.split(','))
.any(|value| value.trim().eq_ignore_ascii_case(expected))
}