use turbomcp_protocol::{Error as McpError, Result as McpResult};
use url::Url;
pub fn validate_resource_uri(uri: &str) -> McpResult<String> {
let url = Url::parse(uri)
.map_err(|e| McpError::invalid_params(format!("Invalid resource URI format: {e}")))?;
match url.scheme() {
"https" => {}
"http" => {
if let Some(host) = url.host_str() {
let is_localhost = host == "localhost"
|| host == "127.0.0.1"
|| host == "0.0.0.0"
|| host == "[::1]";
if !is_localhost {
return Err(McpError::invalid_params(
"Resource URI must use https scheme (http only allowed for localhost)"
.to_string(),
));
}
}
}
scheme => {
return Err(McpError::invalid_params(format!(
"Resource URI must use http or https scheme, got: {scheme}"
)));
}
}
let host = url.host_str().ok_or_else(|| {
McpError::invalid_params("Resource URI must have a valid host".to_string())
})?;
if url.fragment().is_some() {
return Err(McpError::invalid_params(
"Resource URI must not contain fragment (#)".to_string(),
));
}
let canonical = build_canonical_uri(&url, host)?;
Ok(canonical)
}
fn build_canonical_uri(url: &Url, host: &str) -> McpResult<String> {
let scheme = url.scheme().to_lowercase();
let host_lower = host.to_lowercase();
let port_str = match url.port() {
Some(port) => {
let is_default = (scheme == "https" && port == 443) || (scheme == "http" && port == 80);
if is_default {
String::new()
} else {
format!(":{port}")
}
}
None => String::new(),
};
let path = url.path();
let normalized_path = if path == "/" {
path.to_string()
} else {
path.trim_end_matches('/').to_string()
};
Ok(format!(
"{scheme}://{host_lower}{port_str}{normalized_path}"
))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_valid_https_uri() {
let uri = "https://api.example.com/mcp";
let result = validate_resource_uri(uri).unwrap();
assert_eq!(result, "https://api.example.com/mcp");
}
#[test]
fn test_uri_normalization() {
let uri = "HTTPS://API.EXAMPLE.COM/MCP";
let result = validate_resource_uri(uri).unwrap();
assert_eq!(result, "https://api.example.com/MCP"); }
#[test]
fn test_trailing_slash_removal() {
let uri = "https://api.example.com/mcp/";
let result = validate_resource_uri(uri).unwrap();
assert_eq!(result, "https://api.example.com/mcp");
let uri2 = "https://api.example.com/";
let result2 = validate_resource_uri(uri2).unwrap();
assert_eq!(result2, "https://api.example.com/");
}
#[test]
fn test_port_handling() {
let uri = "https://api.example.com:8443/mcp";
let result = validate_resource_uri(uri).unwrap();
assert_eq!(result, "https://api.example.com:8443/mcp");
let uri2 = "https://api.example.com:443/mcp";
let result2 = validate_resource_uri(uri2).unwrap();
assert_eq!(result2, "https://api.example.com/mcp");
let uri3 = "http://localhost:80/mcp";
let result3 = validate_resource_uri(uri3).unwrap();
assert_eq!(result3, "http://localhost/mcp");
}
#[test]
fn test_localhost_http_allowed() {
let uris = vec![
"http://localhost/mcp",
"http://127.0.0.1/mcp",
"http://0.0.0.0/mcp",
"http://[::1]/mcp",
];
for uri in uris {
let result = validate_resource_uri(uri);
assert!(result.is_ok(), "Should allow http for {uri}");
}
}
#[test]
fn test_http_non_localhost_rejected() {
let uri = "http://api.example.com/mcp";
let result = validate_resource_uri(uri);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("https scheme"));
}
#[test]
fn test_fragment_rejected() {
let uri = "https://api.example.com/mcp#fragment";
let result = validate_resource_uri(uri);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("fragment"));
}
#[test]
fn test_invalid_scheme_rejected() {
let uri = "ftp://api.example.com/mcp";
let result = validate_resource_uri(uri);
assert!(result.is_err());
}
#[test]
fn test_missing_host_rejected() {
let uri = "https://";
let result = validate_resource_uri(uri);
assert!(result.is_err());
let uri2 = "/path/to/resource";
let result2 = validate_resource_uri(uri2);
assert!(result2.is_err());
}
#[test]
fn test_query_parameters_removed() {
let uri = "https://api.example.com/mcp?param=value";
let result = validate_resource_uri(uri).unwrap();
assert_eq!(result, "https://api.example.com/mcp");
}
#[test]
fn test_mcp_examples() {
let examples = vec![
("https://mcp.example.com/mcp", "https://mcp.example.com/mcp"),
("https://mcp.example.com", "https://mcp.example.com/"),
(
"https://mcp.example.com:8443",
"https://mcp.example.com:8443/",
),
(
"https://mcp.example.com/server/mcp",
"https://mcp.example.com/server/mcp",
),
];
for (input, expected) in examples {
let result = validate_resource_uri(input).unwrap();
assert_eq!(result, expected, "Failed for input: {input}");
}
}
}