Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 55 additions & 10 deletions crates/codex-plus-core/src/launcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -713,6 +713,7 @@ async fn handle_helper_connection(
let path = raw_path.split('?').next().unwrap_or(raw_path);
let request_body = http_request_body(&request);
let request_user_agent = header_value_from_request(&request, "user-agent");
let allowed_origin = cors_allowed_origin(&request);
let remote_addr_text = remote_addr.map(|addr| addr.to_string());

let _ = crate::diagnostic_log::append_diagnostic_log(
Expand All @@ -734,6 +735,7 @@ async fn handle_helper_connection(
method,
path,
remote_addr_text,
&allowed_origin,
)
.await;
}
Expand All @@ -745,6 +747,7 @@ async fn handle_helper_connection(
method,
path,
remote_addr_text,
&allowed_origin,
)
.await;
}
Expand All @@ -755,6 +758,7 @@ async fn handle_helper_connection(
method,
path,
remote_addr_text,
&allowed_origin,
)
.await;
}
Expand Down Expand Up @@ -837,13 +841,14 @@ async fn handle_helper_connection(
"remote_addr": remote_addr_text
}),
);
let cors = cors_headers_for_origin(&allowed_origin);
let response = if method == "OPTIONS" {
format!(
"HTTP/1.1 204 No Content\r\nAccess-Control-Allow-Origin: *\r\nAccess-Control-Allow-Methods: GET, POST, OPTIONS\r\nAccess-Control-Allow-Headers: Content-Type, Authorization\r\nContent-Length: 0\r\nConnection: close\r\n\r\n"
"HTTP/1.1 204 No Content\r\n{cors}Content-Length: 0\r\nConnection: close\r\n\r\n"
)
} else {
format!(
"HTTP/1.1 {status}\r\nContent-Type: {content_type}\r\nAccess-Control-Allow-Origin: *\r\nAccess-Control-Allow-Methods: GET, POST, OPTIONS\r\nAccess-Control-Allow-Headers: Content-Type, Authorization\r\nContent-Length: {}\r\nConnection: close\r\n\r\n",
"HTTP/1.1 {status}\r\nContent-Type: {content_type}\r\n{cors}Content-Length: {}\r\nConnection: close\r\n\r\n",
body.len()
)
};
Expand Down Expand Up @@ -912,13 +917,15 @@ async fn handle_models_proxy_connection(
method: &str,
path: &str,
remote_addr_text: Option<String>,
allowed_origin: &str,
) -> anyhow::Result<()> {
if method == "OPTIONS" {
write_http_response(
stream,
"204 No Content",
"application/json; charset=utf-8",
&[],
allowed_origin,
)
.await?;
stream.shutdown().await?;
Expand All @@ -938,6 +945,7 @@ async fn handle_models_proxy_connection(
"502 Bad Gateway",
"application/json; charset=utf-8",
&body,
allowed_origin,
)
.await?;
log_helper_response(
Expand All @@ -960,7 +968,7 @@ async fn handle_models_proxy_connection(
upstream.content_type.clone()
};
let body = upstream.response.bytes().await?.to_vec();
write_http_response(stream, &status, &content_type, &body).await?;
write_http_response(stream, &status, &content_type, &body, allowed_origin).await?;
log_helper_response(
if is_success {
"helper.models_proxy_ok"
Expand All @@ -983,6 +991,7 @@ async fn handle_protocol_proxy_connection(
method: &str,
path: &str,
remote_addr_text: Option<String>,
allowed_origin: &str,
) -> anyhow::Result<()> {
let request_json = serde_json::from_str::<serde_json::Value>(request_body).ok();
let upstream =
Expand All @@ -1000,6 +1009,7 @@ async fn handle_protocol_proxy_connection(
"502 Bad Gateway",
"application/json; charset=utf-8",
&body,
allowed_origin,
)
.await?;
log_helper_response(
Expand All @@ -1024,7 +1034,7 @@ async fn handle_protocol_proxy_connection(
&upstream_body,
);
let body = serde_json::to_vec(&error)?;
write_http_response(stream, &status, "application/json; charset=utf-8", &body).await?;
write_http_response(stream, &status, "application/json; charset=utf-8", &body, allowed_origin).await?;
log_helper_response(
"helper.protocol_proxy_upstream_error",
method,
Expand All @@ -1037,7 +1047,7 @@ async fn handle_protocol_proxy_connection(
}

if upstream.is_stream {
write_http_stream_headers(stream, "200 OK", "text/event-stream; charset=utf-8").await?;
write_http_stream_headers(stream, "200 OK", "text/event-stream; charset=utf-8", allowed_origin).await?;
if upstream.wire_api == crate::protocol_proxy::UpstreamWireApi::Responses {
let mut bytes_stream = upstream.response.bytes_stream();
while let Some(chunk) = bytes_stream.next().await {
Expand Down Expand Up @@ -1115,6 +1125,7 @@ async fn handle_protocol_proxy_connection(
&upstream.content_type
},
&upstream_body,
allowed_origin,
)
.await?;
log_helper_response(
Expand All @@ -1135,7 +1146,7 @@ async fn handle_protocol_proxy_connection(
crate::protocol_proxy::chat_completion_to_response(chat_json)?
};
let body = serde_json::to_vec(&response_json)?;
write_http_response(stream, "200 OK", "application/json; charset=utf-8", &body).await?;
write_http_response(stream, "200 OK", "application/json; charset=utf-8", &body, allowed_origin).await?;
log_helper_response(
"helper.protocol_proxy_ok",
method,
Expand All @@ -1154,6 +1165,7 @@ async fn handle_chat_completions_proxy_connection(
method: &str,
path: &str,
remote_addr_text: Option<String>,
allowed_origin: &str,
) -> anyhow::Result<()> {
let upstream = match crate::protocol_proxy::open_chat_completions_proxy_request(
request_body,
Expand All @@ -1172,6 +1184,7 @@ async fn handle_chat_completions_proxy_connection(
"502 Bad Gateway",
"application/json; charset=utf-8",
&body,
allowed_origin,
)
.await?;
log_helper_response(
Expand All @@ -1195,7 +1208,7 @@ async fn handle_chat_completions_proxy_connection(
};

if upstream.is_stream && is_success {
write_http_stream_headers(stream, &status, &content_type).await?;
write_http_stream_headers(stream, &status, &content_type, allowed_origin).await?;
let mut bytes_stream = upstream.response.bytes_stream();
while let Some(chunk) = bytes_stream.next().await {
stream.write_all(&chunk?).await?;
Expand All @@ -1212,7 +1225,7 @@ async fn handle_chat_completions_proxy_connection(
}

let body = upstream.response.bytes().await?.to_vec();
write_http_response(stream, &status, &content_type, &body).await?;
write_http_response(stream, &status, &content_type, &body, allowed_origin).await?;
log_helper_response(
if is_success {
"helper.chat_completions_proxy_ok"
Expand All @@ -1233,9 +1246,11 @@ async fn write_http_response(
status: &str,
content_type: &str,
body: &[u8],
allowed_origin: &str,
) -> anyhow::Result<()> {
let cors = cors_headers_for_origin(allowed_origin);
let response = format!(
"HTTP/1.1 {status}\r\nContent-Type: {content_type}\r\nAccess-Control-Allow-Origin: *\r\nAccess-Control-Allow-Methods: GET, POST, OPTIONS\r\nAccess-Control-Allow-Headers: Content-Type, Authorization\r\nContent-Length: {}\r\nConnection: close\r\n\r\n",
"HTTP/1.1 {status}\r\nContent-Type: {content_type}\r\n{cors}Content-Length: {}\r\nConnection: close\r\n\r\n",
body.len()
);
stream.write_all(response.as_bytes()).await?;
Expand All @@ -1247,9 +1262,11 @@ async fn write_http_stream_headers(
stream: &mut tokio::net::TcpStream,
status: &str,
content_type: &str,
allowed_origin: &str,
) -> anyhow::Result<()> {
let cors = cors_headers_for_origin(allowed_origin);
let response = format!(
"HTTP/1.1 {status}\r\nContent-Type: {content_type}\r\nCache-Control: no-cache\r\nAccess-Control-Allow-Origin: *\r\nAccess-Control-Allow-Methods: GET, POST, OPTIONS\r\nAccess-Control-Allow-Headers: Content-Type, Authorization\r\nConnection: close\r\n\r\n"
"HTTP/1.1 {status}\r\nContent-Type: {content_type}\r\nCache-Control: no-cache\r\n{cors}Connection: close\r\n\r\n"
);
stream.write_all(response.as_bytes()).await?;
Ok(())
Expand Down Expand Up @@ -1394,6 +1411,34 @@ fn sanitize_diagnostic_event(event: &str) -> String {
}
}

fn cors_allowed_origin(request: &str) -> String {
match header_value_from_request(request, "origin") {
Some(origin) if is_local_origin(&origin) => origin,
None => "http://127.0.0.1".to_string(),
Some(_) => String::new(),
}
}

fn is_local_origin(origin: &str) -> bool {
if origin == "null" {
return true;
}
let authority = match origin.find("://") {
Some(pos) => &origin[pos + 3..],
None => return false,
};
let host = authority.split(':').next().unwrap_or(authority);
matches!(host, "127.0.0.1" | "localhost" | "[::1]" | "::1")
}

fn cors_headers_for_origin(allowed_origin: &str) -> String {
if allowed_origin.is_empty() {
String::new()
} else {
format!("Access-Control-Allow-Origin: {allowed_origin}\r\nAccess-Control-Allow-Methods: GET, POST, OPTIONS\r\nAccess-Control-Allow-Headers: Content-Type, Authorization\r\nVary: Origin\r\n")
}
}

pub fn build_codex_arguments(debug_port: u16, extra_args: &[String]) -> Vec<String> {
let mut args = vec![
format!("--remote-debugging-port={debug_port}"),
Expand Down