run cargo fmt

This commit is contained in:
geoffsee
2025-08-28 14:48:14 -04:00
parent bd5a84fe95
commit 37fa59a88a
4 changed files with 308 additions and 227 deletions

View File

@@ -1,12 +1,12 @@
use axum::{ use axum::{
Router,
extract::{Path, Query}, extract::{Path, Query},
response::Json, response::Json,
routing::{get, post, put, delete}, routing::{delete, get, post, put},
Router,
}; };
use hyper_custom_cert::HttpClient; use hyper_custom_cert::HttpClient;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::{json, Value}; use serde_json::{Value, json};
use std::collections::HashMap; use std::collections::HashMap;
use std::time::Duration; use std::time::Duration;
@@ -38,39 +38,35 @@ async fn main() {
let app = Router::new() let app = Router::new()
// Root endpoint with API overview // Root endpoint with API overview
.route("/", get(api_overview)) .route("/", get(api_overview))
// Basic HTTP client tests // Basic HTTP client tests
.route("/test/client/default", get(test_default_client)) .route("/test/client/default", get(test_default_client))
.route("/test/client/builder", get(test_builder_client)) .route("/test/client/builder", get(test_builder_client))
.route("/test/client/timeout", get(test_timeout_client)) .route("/test/client/timeout", get(test_timeout_client))
.route("/test/client/headers", get(test_headers_client)) .route("/test/client/headers", get(test_headers_client))
.route("/test/client/combined", get(test_combined_config)) .route("/test/client/combined", get(test_combined_config))
// Feature-specific tests // Feature-specific tests
.route("/test/features/native-tls", get(test_native_tls_feature)) .route("/test/features/native-tls", get(test_native_tls_feature))
.route("/test/features/rustls", get(test_rustls_feature)) .route("/test/features/rustls", get(test_rustls_feature))
.route("/test/features/insecure", get(test_insecure_feature)) .route("/test/features/insecure", get(test_insecure_feature))
// HTTP method tests // HTTP method tests
.route("/test/methods/get", get(test_get_method)) .route("/test/methods/get", get(test_get_method))
.route("/test/methods/post", post(test_post_method)) .route("/test/methods/post", post(test_post_method))
.route("/test/methods/put", put(test_put_method)) .route("/test/methods/put", put(test_put_method))
.route("/test/methods/delete", delete(test_delete_method)) .route("/test/methods/delete", delete(test_delete_method))
// Certificate and TLS tests // Certificate and TLS tests
.route("/test/tls/custom-ca", get(test_custom_ca)) .route("/test/tls/custom-ca", get(test_custom_ca))
.route("/test/tls/cert-pinning", get(test_cert_pinning)) .route("/test/tls/cert-pinning", get(test_cert_pinning))
.route("/test/tls/self-signed", get(test_self_signed)) .route("/test/tls/self-signed", get(test_self_signed))
// Configuration tests // Configuration tests
.route("/test/config/timeout/{seconds}", get(test_custom_timeout)) .route("/test/config/timeout/{seconds}", get(test_custom_timeout))
.route("/test/config/headers/{header_count}", get(test_custom_headers)) .route(
"/test/config/headers/{header_count}",
get(test_custom_headers),
)
// Error simulation tests // Error simulation tests
.route("/test/errors/timeout", get(test_timeout_error)) .route("/test/errors/timeout", get(test_timeout_error))
.route("/test/errors/invalid-url", get(test_invalid_url)) .route("/test/errors/invalid-url", get(test_invalid_url))
.route("/test/errors/connection", get(test_connection_error)) .route("/test/errors/connection", get(test_connection_error))
// Health and status endpoints // Health and status endpoints
.route("/health", get(health_check)) .route("/health", get(health_check))
.route("/status", get(status_check)); .route("/status", get(status_check));
@@ -80,7 +76,7 @@ async fn main() {
println!("📍 Listening on http://{}", SERVER_ADDRESS); println!("📍 Listening on http://{}", SERVER_ADDRESS);
println!("📖 Visit http://{} for API documentation", SERVER_ADDRESS); println!("📖 Visit http://{} for API documentation", SERVER_ADDRESS);
println!("🧪 Ready for integration testing!"); println!("🧪 Ready for integration testing!");
axum::serve(listener, app).await.unwrap(); axum::serve(listener, app).await.unwrap();
} }
@@ -130,7 +126,7 @@ async fn api_overview() -> Json<Value> {
}, },
"features_available": [ "features_available": [
"native-tls", "native-tls",
"rustls", "rustls",
"insecure-dangerous" "insecure-dangerous"
] ]
})) }))
@@ -144,7 +140,7 @@ async fn api_overview() -> Json<Value> {
async fn test_default_client() -> Json<TestResponse> { async fn test_default_client() -> Json<TestResponse> {
let client = HttpClient::new(); let client = HttpClient::new();
let result = client.request("https://httpbin.org/get").await; let result = client.request("https://httpbin.org/get").await;
Json(TestResponse { Json(TestResponse {
endpoint: "/test/client/default".to_string(), endpoint: "/test/client/default".to_string(),
status: "success".to_string(), status: "success".to_string(),
@@ -161,7 +157,7 @@ async fn test_default_client() -> Json<TestResponse> {
async fn test_builder_client() -> Json<TestResponse> { async fn test_builder_client() -> Json<TestResponse> {
let client = HttpClient::builder().build(); let client = HttpClient::builder().build();
let result = client.request("https://httpbin.org/get").await; let result = client.request("https://httpbin.org/get").await;
Json(TestResponse { Json(TestResponse {
endpoint: "/test/client/builder".to_string(), endpoint: "/test/client/builder".to_string(),
status: "success".to_string(), status: "success".to_string(),
@@ -181,11 +177,14 @@ async fn test_timeout_client(Query(params): Query<TimeoutQuery>) -> Json<TestRes
.with_timeout(Duration::from_secs(timeout_secs)) .with_timeout(Duration::from_secs(timeout_secs))
.build(); .build();
let result = client.request("https://httpbin.org/get").await; let result = client.request("https://httpbin.org/get").await;
Json(TestResponse { Json(TestResponse {
endpoint: "/test/client/timeout".to_string(), endpoint: "/test/client/timeout".to_string(),
status: "success".to_string(), status: "success".to_string(),
message: format!("HttpClient with {}s timeout configured successfully", timeout_secs), message: format!(
"HttpClient with {}s timeout configured successfully",
timeout_secs
),
features_tested: vec!["timeout-config".to_string()], features_tested: vec!["timeout-config".to_string()],
error: match result { error: match result {
Ok(_) => None, Ok(_) => None,
@@ -197,15 +196,16 @@ async fn test_timeout_client(Query(params): Query<TimeoutQuery>) -> Json<TestRes
/// Test custom headers configuration /// Test custom headers configuration
async fn test_headers_client() -> Json<TestResponse> { async fn test_headers_client() -> Json<TestResponse> {
let mut headers = HashMap::new(); let mut headers = HashMap::new();
headers.insert("User-Agent".to_string(), "hyper-custom-cert-test/1.0".to_string()); headers.insert(
"User-Agent".to_string(),
"hyper-custom-cert-test/1.0".to_string(),
);
headers.insert("X-Test-Header".to_string(), "test-value".to_string()); headers.insert("X-Test-Header".to_string(), "test-value".to_string());
headers.insert("Accept".to_string(), "application/json".to_string()); headers.insert("Accept".to_string(), "application/json".to_string());
let client = HttpClient::builder() let client = HttpClient::builder().with_default_headers(headers).build();
.with_default_headers(headers)
.build();
let result = client.request("https://httpbin.org/get").await; let result = client.request("https://httpbin.org/get").await;
Json(TestResponse { Json(TestResponse {
endpoint: "/test/client/headers".to_string(), endpoint: "/test/client/headers".to_string(),
status: "success".to_string(), status: "success".to_string(),
@@ -221,19 +221,23 @@ async fn test_headers_client() -> Json<TestResponse> {
/// Test combined configuration options /// Test combined configuration options
async fn test_combined_config() -> Json<TestResponse> { async fn test_combined_config() -> Json<TestResponse> {
let mut headers = HashMap::new(); let mut headers = HashMap::new();
headers.insert("User-Agent".to_string(), "hyper-custom-cert-combined/1.0".to_string()); headers.insert(
"User-Agent".to_string(),
"hyper-custom-cert-combined/1.0".to_string(),
);
headers.insert("X-Combined-Test".to_string(), "true".to_string()); headers.insert("X-Combined-Test".to_string(), "true".to_string());
let client = HttpClient::builder() let client = HttpClient::builder()
.with_timeout(Duration::from_secs(30)) .with_timeout(Duration::from_secs(30))
.with_default_headers(headers) .with_default_headers(headers)
.build(); .build();
let result = client.request("https://httpbin.org/get").await; let result = client.request("https://httpbin.org/get").await;
Json(TestResponse { Json(TestResponse {
endpoint: "/test/client/combined".to_string(), endpoint: "/test/client/combined".to_string(),
status: "success".to_string(), status: "success".to_string(),
message: "HttpClient with combined configuration (timeout + headers) works correctly".to_string(), message: "HttpClient with combined configuration (timeout + headers) works correctly"
.to_string(),
features_tested: vec!["timeout-config".to_string(), "custom-headers".to_string()], features_tested: vec!["timeout-config".to_string(), "custom-headers".to_string()],
error: match result { error: match result {
Ok(_) => None, Ok(_) => None,
@@ -254,7 +258,7 @@ async fn test_native_tls_feature() -> Json<TestResponse> {
.with_timeout(Duration::from_secs(10)) .with_timeout(Duration::from_secs(10))
.build(); .build();
let result = client.request("https://httpbin.org/get").await; let result = client.request("https://httpbin.org/get").await;
Json(TestResponse { Json(TestResponse {
endpoint: "/test/features/native-tls".to_string(), endpoint: "/test/features/native-tls".to_string(),
status: "success".to_string(), status: "success".to_string(),
@@ -284,13 +288,13 @@ async fn test_rustls_feature() -> Json<TestResponse> {
{ {
// Test with sample root CA PEM (this is just a demo cert) // Test with sample root CA PEM (this is just a demo cert)
let ca_pem: &[u8] = b"-----BEGIN CERTIFICATE-----\nMIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/\nMSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT\nDkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow\nPzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD\nEw5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\nAN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O\nrz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq\nOLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b\nxiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw\n7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD\naeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV\nHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG\nSIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69\nikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr\nAvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz\nR8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5\nJDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo\nOb8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ\n-----END CERTIFICATE-----\n"; let ca_pem: &[u8] = b"-----BEGIN CERTIFICATE-----\nMIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/\nMSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT\nDkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow\nPzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD\nEw5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\nAN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O\nrz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq\nOLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b\nxiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw\n7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD\naeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV\nHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG\nSIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69\nikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr\nAvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz\nR8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5\nJDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo\nOb8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ\n-----END CERTIFICATE-----\n";
let client = HttpClient::builder() let client = HttpClient::builder()
.with_timeout(Duration::from_secs(10)) .with_timeout(Duration::from_secs(10))
.with_root_ca_pem(ca_pem) .with_root_ca_pem(ca_pem)
.build(); .build();
let result = client.request("https://httpbin.org/get").await; let result = client.request("https://httpbin.org/get").await;
Json(TestResponse { Json(TestResponse {
endpoint: "/test/features/rustls".to_string(), endpoint: "/test/features/rustls".to_string(),
status: "success".to_string(), status: "success".to_string(),
@@ -321,17 +325,18 @@ async fn test_insecure_feature() -> Json<TestResponse> {
// Test shortcut method // Test shortcut method
let client = HttpClient::with_self_signed_certs(); let client = HttpClient::with_self_signed_certs();
let result = client.request("https://self-signed.badssl.com/").await; let result = client.request("https://self-signed.badssl.com/").await;
// Test builder method // Test builder method
let client2 = HttpClient::builder() let client2 = HttpClient::builder()
.insecure_accept_invalid_certs(true) .insecure_accept_invalid_certs(true)
.build(); .build();
let result2 = client2.request("https://expired.badssl.com/").await; let result2 = client2.request("https://expired.badssl.com/").await;
Json(TestResponse { Json(TestResponse {
endpoint: "/test/features/insecure".to_string(), endpoint: "/test/features/insecure".to_string(),
status: "success".to_string(), status: "success".to_string(),
message: "insecure-dangerous feature is working (DO NOT USE IN PRODUCTION!)".to_string(), message: "insecure-dangerous feature is working (DO NOT USE IN PRODUCTION!)"
.to_string(),
features_tested: vec!["insecure-dangerous".to_string()], features_tested: vec!["insecure-dangerous".to_string()],
error: match (result, result2) { error: match (result, result2) {
(Ok(_), Ok(_)) => None, (Ok(_), Ok(_)) => None,
@@ -345,7 +350,8 @@ async fn test_insecure_feature() -> Json<TestResponse> {
Json(TestResponse { Json(TestResponse {
endpoint: "/test/features/insecure".to_string(), endpoint: "/test/features/insecure".to_string(),
status: "skipped".to_string(), status: "skipped".to_string(),
message: "insecure-dangerous feature is not enabled (this is good for security!)".to_string(), message: "insecure-dangerous feature is not enabled (this is good for security!)"
.to_string(),
features_tested: vec![], features_tested: vec![],
error: Some("Feature not enabled".to_string()), error: Some("Feature not enabled".to_string()),
}) })
@@ -360,7 +366,7 @@ async fn test_insecure_feature() -> Json<TestResponse> {
async fn test_get_method() -> Json<TestResponse> { async fn test_get_method() -> Json<TestResponse> {
let client = HttpClient::new(); let client = HttpClient::new();
let result = client.request("https://httpbin.org/get").await; let result = client.request("https://httpbin.org/get").await;
Json(TestResponse { Json(TestResponse {
endpoint: "/test/methods/get".to_string(), endpoint: "/test/methods/get".to_string(),
status: "success".to_string(), status: "success".to_string(),
@@ -378,11 +384,14 @@ async fn test_post_method(Json(payload): Json<PostData>) -> Json<TestResponse> {
let client = HttpClient::new(); let client = HttpClient::new();
let body = serde_json::to_vec(&payload).unwrap_or_default(); let body = serde_json::to_vec(&payload).unwrap_or_default();
let result = client.post("https://httpbin.org/post", &body).await; let result = client.post("https://httpbin.org/post", &body).await;
Json(TestResponse { Json(TestResponse {
endpoint: "/test/methods/post".to_string(), endpoint: "/test/methods/post".to_string(),
status: "success".to_string(), status: "success".to_string(),
message: format!("HTTP POST method test completed with data: {}", payload.data), message: format!(
"HTTP POST method test completed with data: {}",
payload.data
),
features_tested: vec!["post-request".to_string()], features_tested: vec!["post-request".to_string()],
error: match result { error: match result {
Ok(_) => None, Ok(_) => None,
@@ -396,11 +405,14 @@ async fn test_put_method(Json(payload): Json<PostData>) -> Json<TestResponse> {
let client = HttpClient::new(); let client = HttpClient::new();
let body = serde_json::to_vec(&payload).unwrap_or_default(); let body = serde_json::to_vec(&payload).unwrap_or_default();
let result = client.post("https://httpbin.org/put", &body).await; let result = client.post("https://httpbin.org/put", &body).await;
Json(TestResponse { Json(TestResponse {
endpoint: "/test/methods/put".to_string(), endpoint: "/test/methods/put".to_string(),
status: "success".to_string(), status: "success".to_string(),
message: format!("HTTP PUT method test completed (simulated via POST) with data: {}", payload.data), message: format!(
"HTTP PUT method test completed (simulated via POST) with data: {}",
payload.data
),
features_tested: vec!["put-request-simulation".to_string()], features_tested: vec!["put-request-simulation".to_string()],
error: match result { error: match result {
Ok(_) => None, Ok(_) => None,
@@ -413,7 +425,7 @@ async fn test_put_method(Json(payload): Json<PostData>) -> Json<TestResponse> {
async fn test_delete_method() -> Json<TestResponse> { async fn test_delete_method() -> Json<TestResponse> {
let client = HttpClient::new(); let client = HttpClient::new();
let result = client.request("https://httpbin.org/delete").await; let result = client.request("https://httpbin.org/delete").await;
Json(TestResponse { Json(TestResponse {
endpoint: "/test/methods/delete".to_string(), endpoint: "/test/methods/delete".to_string(),
status: "success".to_string(), status: "success".to_string(),
@@ -435,7 +447,7 @@ async fn test_custom_ca() -> Json<TestResponse> {
#[cfg(feature = "rustls")] #[cfg(feature = "rustls")]
{ {
let ca_pem: &[u8] = b"-----BEGIN CERTIFICATE-----\nMIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/\nMSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT\nDkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow\nPzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD\nEw5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\nAN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O\nrz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq\nOLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b\nxiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw\n7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD\naeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV\nHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG\nSIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69\nikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr\nAvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz\nR8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5\nJDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo\nOb8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ\n-----END CERTIFICATE-----\n"; let ca_pem: &[u8] = b"-----BEGIN CERTIFICATE-----\nMIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/\nMSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT\nDkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow\nPzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD\nEw5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\nAN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O\nrz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq\nOLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b\nxiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw\n7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD\naeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV\nHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG\nSIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69\nikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr\nAvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz\nR8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5\nJDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo\nOb8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ\n-----END CERTIFICATE-----\n";
let client = HttpClient::builder() let client = HttpClient::builder()
.with_timeout(Duration::from_secs(10)) .with_timeout(Duration::from_secs(10))
.with_root_ca_pem(ca_pem) .with_root_ca_pem(ca_pem)
@@ -443,7 +455,7 @@ async fn test_custom_ca() -> Json<TestResponse> {
let result = client.request("https://httpbin.org/get"); let result = client.request("https://httpbin.org/get");
let awaited = result.await; let awaited = result.await;
Json(TestResponse { Json(TestResponse {
endpoint: "/test/tls/custom-ca".to_string(), endpoint: "/test/tls/custom-ca".to_string(),
status: "success".to_string(), status: "success".to_string(),
@@ -472,13 +484,12 @@ async fn test_cert_pinning() -> Json<TestResponse> {
#[cfg(feature = "rustls")] #[cfg(feature = "rustls")]
{ {
// Example SHA256 fingerprints (these are demo values) // Example SHA256 fingerprints (these are demo values)
let pins = vec![ let pins = vec![[
[0x1f, 0x2f, 0x3f, 0x4f, 0x5f, 0x6f, 0x7f, 0x8f, 0x1f, 0x2f, 0x3f, 0x4f, 0x5f, 0x6f, 0x7f, 0x8f, 0x9f, 0xaf, 0xbf, 0xcf, 0xdf, 0xef,
0x9f, 0xaf, 0xbf, 0xcf, 0xdf, 0xef, 0xff, 0x0f, 0xff, 0x0f, 0x1f, 0x2f, 0x3f, 0x4f, 0x5f, 0x6f, 0x7f, 0x8f, 0x9f, 0xaf, 0xbf, 0xcf,
0x1f, 0x2f, 0x3f, 0x4f, 0x5f, 0x6f, 0x7f, 0x8f, 0xdf, 0xef, 0xff, 0x0f,
0x9f, 0xaf, 0xbf, 0xcf, 0xdf, 0xef, 0xff, 0x0f], ]];
];
let client = HttpClient::builder() let client = HttpClient::builder()
.with_timeout(Duration::from_secs(10)) .with_timeout(Duration::from_secs(10))
.with_pinned_cert_sha256(pins) .with_pinned_cert_sha256(pins)
@@ -486,7 +497,7 @@ async fn test_cert_pinning() -> Json<TestResponse> {
let result = client.request("https://httpbin.org/get"); let result = client.request("https://httpbin.org/get");
let awaited = result.await; let awaited = result.await;
Json(TestResponse { Json(TestResponse {
endpoint: "/test/tls/cert-pinning".to_string(), endpoint: "/test/tls/cert-pinning".to_string(),
status: "success".to_string(), status: "success".to_string(),
@@ -518,7 +529,7 @@ async fn test_self_signed() -> Json<TestResponse> {
let result = client.request("https://self-signed.badssl.com/"); let result = client.request("https://self-signed.badssl.com/");
let awaited = result.await; let awaited = result.await;
Json(TestResponse { Json(TestResponse {
endpoint: "/test/tls/self-signed".to_string(), endpoint: "/test/tls/self-signed".to_string(),
status: "success".to_string(), status: "success".to_string(),
@@ -535,7 +546,8 @@ async fn test_self_signed() -> Json<TestResponse> {
Json(TestResponse { Json(TestResponse {
endpoint: "/test/tls/self-signed".to_string(), endpoint: "/test/tls/self-signed".to_string(),
status: "skipped".to_string(), status: "skipped".to_string(),
message: "Self-signed test requires insecure-dangerous feature (good for security!)".to_string(), message: "Self-signed test requires insecure-dangerous feature (good for security!)"
.to_string(),
features_tested: vec![], features_tested: vec![],
error: Some("insecure-dangerous feature not enabled".to_string()), error: Some("insecure-dangerous feature not enabled".to_string()),
}) })
@@ -549,11 +561,9 @@ async fn test_self_signed() -> Json<TestResponse> {
/// Test custom timeout configuration /// Test custom timeout configuration
async fn test_custom_timeout(Path(seconds): Path<u64>) -> Json<TestResponse> { async fn test_custom_timeout(Path(seconds): Path<u64>) -> Json<TestResponse> {
let timeout_duration = Duration::from_secs(seconds); let timeout_duration = Duration::from_secs(seconds);
let client = HttpClient::builder() let client = HttpClient::builder().with_timeout(timeout_duration).build();
.with_timeout(timeout_duration)
.build();
let result = client.request("https://httpbin.org/delay/1"); let result = client.request("https://httpbin.org/delay/1");
Json(TestResponse { Json(TestResponse {
endpoint: format!("/test/config/timeout/{}", seconds), endpoint: format!("/test/config/timeout/{}", seconds),
status: "success".to_string(), status: "success".to_string(),
@@ -569,28 +579,31 @@ async fn test_custom_timeout(Path(seconds): Path<u64>) -> Json<TestResponse> {
/// Test custom headers configuration /// Test custom headers configuration
async fn test_custom_headers(Path(header_count): Path<usize>) -> Json<TestResponse> { async fn test_custom_headers(Path(header_count): Path<usize>) -> Json<TestResponse> {
let mut headers = HashMap::new(); let mut headers = HashMap::new();
for i in 0..header_count { for i in 0..header_count {
headers.insert( headers.insert(format!("X-Test-Header-{}", i), format!("test-value-{}", i));
format!("X-Test-Header-{}", i),
format!("test-value-{}", i),
);
} }
// Add some standard headers // Add some standard headers
headers.insert("User-Agent".to_string(), "hyper-custom-cert-headers-test/1.0".to_string()); headers.insert(
"User-Agent".to_string(),
"hyper-custom-cert-headers-test/1.0".to_string(),
);
headers.insert("Accept".to_string(), "application/json".to_string()); headers.insert("Accept".to_string(), "application/json".to_string());
let client = HttpClient::builder() let client = HttpClient::builder()
.with_timeout(Duration::from_secs(10)) .with_timeout(Duration::from_secs(10))
.with_default_headers(headers) .with_default_headers(headers)
.build(); .build();
let result = client.request("https://httpbin.org/headers"); let result = client.request("https://httpbin.org/headers");
Json(TestResponse { Json(TestResponse {
endpoint: format!("/test/config/headers/{}", header_count), endpoint: format!("/test/config/headers/{}", header_count),
status: "success".to_string(), status: "success".to_string(),
message: format!("Custom headers test with {} headers completed", header_count + 2), message: format!(
"Custom headers test with {} headers completed",
header_count + 2
),
features_tested: vec!["custom-headers".to_string()], features_tested: vec!["custom-headers".to_string()],
error: match result.await { error: match result.await {
Ok(_) => None, Ok(_) => None,
@@ -614,7 +627,12 @@ async fn test_timeout_error() -> Json<TestResponse> {
let awaited = result.await; let awaited = result.await;
Json(TestResponse { Json(TestResponse {
endpoint: "/test/errors/timeout".to_string(), endpoint: "/test/errors/timeout".to_string(),
status: if awaited.is_err() { "success" } else { "unexpected" }.to_string(), status: if awaited.is_err() {
"success"
} else {
"unexpected"
}
.to_string(),
message: "Timeout error simulation test completed".to_string(), message: "Timeout error simulation test completed".to_string(),
features_tested: vec!["timeout-error-handling".to_string()], features_tested: vec!["timeout-error-handling".to_string()],
error: match awaited { error: match awaited {
@@ -633,7 +651,12 @@ async fn test_invalid_url() -> Json<TestResponse> {
Json(TestResponse { Json(TestResponse {
endpoint: "/test/errors/invalid-url".to_string(), endpoint: "/test/errors/invalid-url".to_string(),
status: if awaited.is_err() { "success" } else { "unexpected" }.to_string(), status: if awaited.is_err() {
"success"
} else {
"unexpected"
}
.to_string(),
message: "Invalid URL error simulation test completed".to_string(), message: "Invalid URL error simulation test completed".to_string(),
features_tested: vec!["url-validation".to_string()], features_tested: vec!["url-validation".to_string()],
error: match awaited { error: match awaited {
@@ -654,7 +677,12 @@ async fn test_connection_error() -> Json<TestResponse> {
Json(TestResponse { Json(TestResponse {
endpoint: "/test/errors/connection".to_string(), endpoint: "/test/errors/connection".to_string(),
status: if awaited.is_err() { "success" } else { "unexpected" }.to_string(), status: if awaited.is_err() {
"success"
} else {
"unexpected"
}
.to_string(),
message: "Connection error simulation test completed".to_string(), message: "Connection error simulation test completed".to_string(),
features_tested: vec!["connection-error-handling".to_string()], features_tested: vec!["connection-error-handling".to_string()],
error: match awaited { error: match awaited {
@@ -675,7 +703,7 @@ async fn health_check() -> Json<Value> {
.duration_since(UNIX_EPOCH) .duration_since(UNIX_EPOCH)
.unwrap() .unwrap()
.as_secs(); .as_secs();
Json(json!({ Json(json!({
"status": "healthy", "status": "healthy",
"timestamp": timestamp, "timestamp": timestamp,
@@ -691,13 +719,13 @@ async fn status_check() -> Json<Value> {
.duration_since(UNIX_EPOCH) .duration_since(UNIX_EPOCH)
.unwrap() .unwrap()
.as_secs(); .as_secs();
// Test basic client creation to verify library is working // Test basic client creation to verify library is working
let client_test = match HttpClient::new().request("https://httpbin.org/get").await { let client_test = match HttpClient::new().request("https://httpbin.org/get").await {
Ok(_) => "operational", Ok(_) => "operational",
Err(_) => "degraded" Err(_) => "degraded",
}; };
Json(json!({ Json(json!({
"service": "hyper-custom-cert-test-harness", "service": "hyper-custom-cert-test-harness",
"version": "1.0.0", "version": "1.0.0",
@@ -711,7 +739,7 @@ async fn status_check() -> Json<Value> {
"endpoints_available": 18, "endpoints_available": 18,
"test_categories": [ "test_categories": [
"basic_client_tests", "basic_client_tests",
"feature_specific_tests", "feature_specific_tests",
"http_method_tests", "http_method_tests",
"tls_certificate_tests", "tls_certificate_tests",
"configuration_tests", "configuration_tests",
@@ -719,4 +747,4 @@ async fn status_check() -> Json<Value> {
"utility_endpoints" "utility_endpoints"
] ]
})) }))
} }

View File

@@ -36,10 +36,10 @@ use std::path::Path;
use std::time::Duration; use std::time::Duration;
use bytes::Bytes; use bytes::Bytes;
use hyper::{body::Incoming, Request, Response, StatusCode, Uri, Method}; use http_body_util::BodyExt;
use hyper::{Method, Request, Response, StatusCode, Uri, body::Incoming};
use hyper_util::client::legacy::Client; use hyper_util::client::legacy::Client;
use hyper_util::rt::TokioExecutor; use hyper_util::rt::TokioExecutor;
use http_body_util::BodyExt;
/// Options for controlling HTTP requests. /// Options for controlling HTTP requests.
/// ///
@@ -81,13 +81,13 @@ impl RequestOptions {
pub fn new() -> Self { pub fn new() -> Self {
RequestOptions::default() RequestOptions::default()
} }
/// Add custom headers to this request. /// Add custom headers to this request.
pub fn with_headers(mut self, headers: HashMap<String, String>) -> Self { pub fn with_headers(mut self, headers: HashMap<String, String>) -> Self {
self.headers = Some(headers); self.headers = Some(headers);
self self
} }
/// Override the client's default timeout for this request. /// Override the client's default timeout for this request.
pub fn with_timeout(mut self, timeout: Duration) -> Self { pub fn with_timeout(mut self, timeout: Duration) -> Self {
self.timeout = Some(timeout); self.timeout = Some(timeout);
@@ -239,32 +239,32 @@ impl HttpClient {
/// Convenience constructor that enables acceptance of self-signed/invalid /// Convenience constructor that enables acceptance of self-signed/invalid
/// certificates. This is gated behind the `insecure-dangerous` feature and intended /// certificates. This is gated behind the `insecure-dangerous` feature and intended
/// strictly for development and testing. NEVER enable in production. /// strictly for development and testing. NEVER enable in production.
/// ///
/// # Security Warning /// # Security Warning
/// ///
/// ⚠️ CRITICAL SECURITY WARNING ⚠️ /// ⚠️ CRITICAL SECURITY WARNING ⚠️
/// ///
/// This method deliberately bypasses TLS certificate validation, creating a /// This method deliberately bypasses TLS certificate validation, creating a
/// serious security vulnerability to man-in-the-middle attacks. When used: /// serious security vulnerability to man-in-the-middle attacks. When used:
/// ///
/// - ANY certificate will be accepted, regardless of its validity /// - ANY certificate will be accepted, regardless of its validity
/// - Expired certificates will be accepted /// - Expired certificates will be accepted
/// - Certificates from untrusted issuers will be accepted /// - Certificates from untrusted issuers will be accepted
/// - Certificates for the wrong domain will be accepted /// - Certificates for the wrong domain will be accepted
/// ///
/// This is equivalent to calling `insecure_accept_invalid_certs(true)` on the builder /// This is equivalent to calling `insecure_accept_invalid_certs(true)` on the builder
/// and inherits all of its security implications. See that method's documentation /// and inherits all of its security implications. See that method's documentation
/// for more details. /// for more details.
/// ///
/// # Intended Use Cases /// # Intended Use Cases
/// ///
/// This method should ONLY be used for: /// This method should ONLY be used for:
/// - Local development with self-signed certificates /// - Local development with self-signed certificates
/// - Testing environments where security is not a concern /// - Testing environments where security is not a concern
/// - Debugging TLS connection issues /// - Debugging TLS connection issues
/// ///
/// # Implementation Details /// # Implementation Details
/// ///
/// This is a convenience wrapper that calls: /// This is a convenience wrapper that calls:
/// ```ignore /// ```ignore
/// HttpClient::builder() /// HttpClient::builder()
@@ -300,12 +300,12 @@ impl HttpClient {
/// # async { /// # async {
/// use hyper_custom_cert::{HttpClient, RequestOptions}; /// use hyper_custom_cert::{HttpClient, RequestOptions};
/// use std::collections::HashMap; /// use std::collections::HashMap;
/// ///
/// let client = HttpClient::new(); /// let client = HttpClient::new();
/// ///
/// // Basic request with no custom options /// // Basic request with no custom options
/// let response1 = client.request_with_options("https://example.com", None).await?; /// let response1 = client.request_with_options("https://example.com", None).await?;
/// ///
/// // Request with custom options /// // Request with custom options
/// let mut headers = HashMap::new(); /// let mut headers = HashMap::new();
/// headers.insert("x-request-id".into(), "abc123".into()); /// headers.insert("x-request-id".into(), "abc123".into());
@@ -318,7 +318,7 @@ impl HttpClient {
pub async fn request(&self, url: &str) -> Result<HttpResponse, ClientError> { pub async fn request(&self, url: &str) -> Result<HttpResponse, ClientError> {
self.request_with_options(url, None).await self.request_with_options(url, None).await
} }
/// Performs a GET request and returns the raw response body. /// Performs a GET request and returns the raw response body.
/// This method constructs a `hyper::Request` with the GET method and any /// This method constructs a `hyper::Request` with the GET method and any
/// default headers configured on the client, then dispatches it via `perform_request`. /// default headers configured on the client, then dispatches it via `perform_request`.
@@ -335,12 +335,12 @@ impl HttpClient {
/// # async { /// # async {
/// use hyper_custom_cert::{HttpClient, RequestOptions}; /// use hyper_custom_cert::{HttpClient, RequestOptions};
/// use std::collections::HashMap; /// use std::collections::HashMap;
/// ///
/// let client = HttpClient::new(); /// let client = HttpClient::new();
/// ///
/// // Basic request with no custom options /// // Basic request with no custom options
/// let response1 = client.request_with_options("https://example.com", None).await?; /// let response1 = client.request_with_options("https://example.com", None).await?;
/// ///
/// // Request with custom options /// // Request with custom options
/// let mut headers = HashMap::new(); /// let mut headers = HashMap::new();
/// headers.insert("x-request-id".into(), "abc123".into()); /// headers.insert("x-request-id".into(), "abc123".into());
@@ -349,13 +349,15 @@ impl HttpClient {
/// # Ok::<(), hyper_custom_cert::ClientError>(()) /// # Ok::<(), hyper_custom_cert::ClientError>(())
/// # }; /// # };
/// ``` /// ```
pub async fn request_with_options(&self, url: &str, options: Option<RequestOptions>) -> Result<HttpResponse, ClientError> { pub async fn request_with_options(
&self,
url: &str,
options: Option<RequestOptions>,
) -> Result<HttpResponse, ClientError> {
let uri: Uri = url.parse()?; let uri: Uri = url.parse()?;
let req = Request::builder() let req = Request::builder().method(Method::GET).uri(uri);
.method(Method::GET)
.uri(uri);
// Add default headers to the request. This ensures that any headers // Add default headers to the request. This ensures that any headers
// set during the client's construction (e.g., API keys, User-Agent) // set during the client's construction (e.g., API keys, User-Agent)
// are automatically included in outgoing requests. // are automatically included in outgoing requests.
@@ -363,7 +365,7 @@ impl HttpClient {
for (key, value) in &self.default_headers { for (key, value) in &self.default_headers {
req = req.header(key, value); req = req.header(key, value);
} }
// Add any request-specific headers from options // Add any request-specific headers from options
if let Some(options) = &options { if let Some(options) = &options {
if let Some(headers) = &options.headers { if let Some(headers) = &options.headers {
@@ -372,9 +374,9 @@ impl HttpClient {
} }
} }
} }
let req = req.body(http_body_util::Empty::<Bytes>::new())?; let req = req.body(http_body_util::Empty::<Bytes>::new())?;
// If options contain a timeout, temporarily modify self to use it // If options contain a timeout, temporarily modify self to use it
// This is a bit of a hack since we can't modify perform_request easily // This is a bit of a hack since we can't modify perform_request easily
let result = if let Some(opts) = &options { let result = if let Some(opts) = &options {
@@ -389,7 +391,7 @@ impl HttpClient {
#[cfg(feature = "rustls")] #[cfg(feature = "rustls")]
pinned_cert_sha256: self.pinned_cert_sha256.clone(), pinned_cert_sha256: self.pinned_cert_sha256.clone(),
}; };
// Use the modified client for this request only // Use the modified client for this request only
client_copy.perform_request(req).await client_copy.perform_request(req).await
} else { } else {
@@ -400,7 +402,7 @@ impl HttpClient {
// No options, use normal client // No options, use normal client
self.perform_request(req).await self.perform_request(req).await
}; };
result result
} }
@@ -423,12 +425,12 @@ impl HttpClient {
/// use hyper_custom_cert::{HttpClient, RequestOptions}; /// use hyper_custom_cert::{HttpClient, RequestOptions};
/// use std::collections::HashMap; /// use std::collections::HashMap;
/// use std::time::Duration; /// use std::time::Duration;
/// ///
/// let client = HttpClient::new(); /// let client = HttpClient::new();
/// ///
/// // Basic POST request with no custom options /// // Basic POST request with no custom options
/// let response1 = client.post_with_options("https://example.com/api", b"{\"key\":\"value\"}", None).await?; /// let response1 = client.post_with_options("https://example.com/api", b"{\"key\":\"value\"}", None).await?;
/// ///
/// // POST request with custom options /// // POST request with custom options
/// let mut headers = HashMap::new(); /// let mut headers = HashMap::new();
/// headers.insert("Content-Type".into(), "application/json".into()); /// headers.insert("Content-Type".into(), "application/json".into());
@@ -439,11 +441,18 @@ impl HttpClient {
/// # Ok::<(), hyper_custom_cert::ClientError>(()) /// # Ok::<(), hyper_custom_cert::ClientError>(())
/// # }; /// # };
/// ``` /// ```
#[deprecated(since = "0.4.0", note = "Use post_with_options(url, body, Some(options)) instead")] #[deprecated(
pub async fn post<B: AsRef<[u8]>>(&self, url: &str, body: B) -> Result<HttpResponse, ClientError> { since = "0.4.0",
note = "Use post_with_options(url, body, Some(options)) instead"
)]
pub async fn post<B: AsRef<[u8]>>(
&self,
url: &str,
body: B,
) -> Result<HttpResponse, ClientError> {
self.post_with_options(url, body, None).await self.post_with_options(url, body, None).await
} }
/// Performs a POST request with the given body and returns the raw response. /// Performs a POST request with the given body and returns the raw response.
/// Similar to `request`, this method builds a `hyper::Request` for a POST /// Similar to `request`, this method builds a `hyper::Request` for a POST
/// operation, handles the request body conversion to `Bytes`, and applies /// operation, handles the request body conversion to `Bytes`, and applies
@@ -463,12 +472,12 @@ impl HttpClient {
/// use hyper_custom_cert::{HttpClient, RequestOptions}; /// use hyper_custom_cert::{HttpClient, RequestOptions};
/// use std::collections::HashMap; /// use std::collections::HashMap;
/// use std::time::Duration; /// use std::time::Duration;
/// ///
/// let client = HttpClient::new(); /// let client = HttpClient::new();
/// ///
/// // Basic POST request with no custom options /// // Basic POST request with no custom options
/// let response1 = client.post_with_options("https://example.com/api", b"{\"key\":\"value\"}", None).await?; /// let response1 = client.post_with_options("https://example.com/api", b"{\"key\":\"value\"}", None).await?;
/// ///
/// // POST request with custom options /// // POST request with custom options
/// let mut headers = HashMap::new(); /// let mut headers = HashMap::new();
/// headers.insert("Content-Type".into(), "application/json".into()); /// headers.insert("Content-Type".into(), "application/json".into());
@@ -479,19 +488,22 @@ impl HttpClient {
/// # Ok::<(), hyper_custom_cert::ClientError>(()) /// # Ok::<(), hyper_custom_cert::ClientError>(())
/// # }; /// # };
/// ``` /// ```
pub async fn post_with_options<B: AsRef<[u8]>>(&self, url: &str, body: B, options: Option<RequestOptions>) -> Result<HttpResponse, ClientError> { pub async fn post_with_options<B: AsRef<[u8]>>(
&self,
url: &str,
body: B,
options: Option<RequestOptions>,
) -> Result<HttpResponse, ClientError> {
let uri: Uri = url.parse()?; let uri: Uri = url.parse()?;
let req = Request::builder() let req = Request::builder().method(Method::POST).uri(uri);
.method(Method::POST)
.uri(uri);
// Add default headers to the request for consistency across client operations. // Add default headers to the request for consistency across client operations.
let mut req = req; let mut req = req;
for (key, value) in &self.default_headers { for (key, value) in &self.default_headers {
req = req.header(key, value); req = req.header(key, value);
} }
// Add any request-specific headers from options // Add any request-specific headers from options
if let Some(options) = &options { if let Some(options) = &options {
if let Some(headers) = &options.headers { if let Some(headers) = &options.headers {
@@ -500,10 +512,10 @@ impl HttpClient {
} }
} }
} }
let body_bytes = Bytes::copy_from_slice(body.as_ref()); let body_bytes = Bytes::copy_from_slice(body.as_ref());
let req = req.body(http_body_util::Full::new(body_bytes))?; let req = req.body(http_body_util::Full::new(body_bytes))?;
// If options contain a timeout, temporarily modify self to use it // If options contain a timeout, temporarily modify self to use it
// This is a bit of a hack since we can't modify perform_request easily // This is a bit of a hack since we can't modify perform_request easily
let result = if let Some(opts) = &options { let result = if let Some(opts) = &options {
@@ -518,7 +530,7 @@ impl HttpClient {
#[cfg(feature = "rustls")] #[cfg(feature = "rustls")]
pinned_cert_sha256: self.pinned_cert_sha256.clone(), pinned_cert_sha256: self.pinned_cert_sha256.clone(),
}; };
// Use the modified client for this request only // Use the modified client for this request only
client_copy.perform_request(req).await client_copy.perform_request(req).await
} else { } else {
@@ -529,7 +541,7 @@ impl HttpClient {
// No options, use normal client // No options, use normal client
self.perform_request(req).await self.perform_request(req).await
}; };
result result
} }
@@ -537,7 +549,7 @@ impl HttpClient {
/// This centralizes the logic for dispatching `hyper::Request` objects, /// This centralizes the logic for dispatching `hyper::Request` objects,
/// handling the various TLS backends (native-tls, rustls) and ensuring /// handling the various TLS backends (native-tls, rustls) and ensuring
/// the correct `hyper` client is used based on feature flags. /// the correct `hyper` client is used based on feature flags.
async fn perform_request<B>(&self, req: Request<B>) -> Result<HttpResponse, ClientError> async fn perform_request<B>(&self, req: Request<B>) -> Result<HttpResponse, ClientError>
where where
B: hyper::body::Body + Send + 'static + Unpin, B: hyper::body::Body + Send + 'static + Unpin,
B::Data: Send, B::Data: Send,
@@ -547,46 +559,44 @@ impl HttpClient {
{ {
// When the "native-tls" feature is enabled, use `hyper-tls` for TLS // When the "native-tls" feature is enabled, use `hyper-tls` for TLS
// support, which integrates with the system's native TLS libraries. // support, which integrates with the system's native TLS libraries.
#[cfg(feature = "insecure-dangerous")] #[cfg(feature = "insecure-dangerous")]
if self.accept_invalid_certs { if self.accept_invalid_certs {
// ⚠️ SECURITY WARNING: This code path deliberately bypasses TLS certificate validation. // ⚠️ SECURITY WARNING: This code path deliberately bypasses TLS certificate validation.
// It should only be used during development/testing with self-signed certificates, // It should only be used during development/testing with self-signed certificates,
// and NEVER in production environments. This creates a vulnerability to // and NEVER in production environments. This creates a vulnerability to
// man-in-the-middle attacks and is extremely dangerous. // man-in-the-middle attacks and is extremely dangerous.
// Implementation with tokio-native-tls to accept invalid certificates // Implementation with tokio-native-tls to accept invalid certificates
let mut http_connector = hyper_util::client::legacy::connect::HttpConnector::new(); let mut http_connector = hyper_util::client::legacy::connect::HttpConnector::new();
http_connector.enforce_http(false); http_connector.enforce_http(false);
// Create a TLS connector that accepts invalid certificates // Create a TLS connector that accepts invalid certificates
let mut tls_builder = native_tls::TlsConnector::builder(); let mut tls_builder = native_tls::TlsConnector::builder();
tls_builder.danger_accept_invalid_certs(true); tls_builder.danger_accept_invalid_certs(true);
let tls_connector = tls_builder.build() let tls_connector = tls_builder.build().map_err(|e| {
.map_err(|e| ClientError::TlsError(format!("Failed to build TLS connector: {}", e)))?; ClientError::TlsError(format!("Failed to build TLS connector: {}", e))
})?;
// Create the tokio-native-tls connector // Create the tokio-native-tls connector
let tokio_connector = tokio_native_tls::TlsConnector::from(tls_connector); let tokio_connector = tokio_native_tls::TlsConnector::from(tls_connector);
// Create the HTTPS connector using the HTTP and TLS connectors // Create the HTTPS connector using the HTTP and TLS connectors
let connector = hyper_tls::HttpsConnector::from((http_connector, tokio_connector)); let connector = hyper_tls::HttpsConnector::from((http_connector, tokio_connector));
let client = Client::builder(TokioExecutor::new()) let client = Client::builder(TokioExecutor::new()).build(connector);
.build(connector);
let resp = tokio::time::timeout(self.timeout, client.request(req)) let resp = tokio::time::timeout(self.timeout, client.request(req))
.await .await
.map_err(|_| ClientError::TlsError("Request timed out".to_string()))? .map_err(|_| ClientError::TlsError("Request timed out".to_string()))??;
?;
return self.build_response(resp).await; return self.build_response(resp).await;
} }
// Standard secure TLS connection with certificate validation (default path) // Standard secure TLS connection with certificate validation (default path)
let connector = hyper_tls::HttpsConnector::new(); let connector = hyper_tls::HttpsConnector::new();
let client = Client::builder(TokioExecutor::new()).build(connector); let client = Client::builder(TokioExecutor::new()).build(connector);
let resp = tokio::time::timeout(self.timeout, client.request(req)) let resp = tokio::time::timeout(self.timeout, client.request(req))
.await .await
.map_err(|_| ClientError::TlsError("Request timed out".to_string()))? .map_err(|_| ClientError::TlsError("Request timed out".to_string()))??;
?;
self.build_response(resp).await self.build_response(resp).await
} }
#[cfg(all(feature = "rustls", not(feature = "native-tls")))] #[cfg(all(feature = "rustls", not(feature = "native-tls")))]
@@ -594,58 +604,70 @@ impl HttpClient {
// If "rustls" is enabled and "native-tls" is not, use `rustls` for TLS. // If "rustls" is enabled and "native-tls" is not, use `rustls` for TLS.
// Properly configure the rustls connector with custom CA certificates and/or // Properly configure the rustls connector with custom CA certificates and/or
// certificate validation settings based on the client configuration. // certificate validation settings based on the client configuration.
// Start with the standard rustls config with native roots // Start with the standard rustls config with native roots
let mut root_cert_store = rustls::RootCertStore::empty(); let mut root_cert_store = rustls::RootCertStore::empty();
// Load native certificates using rustls_native_certs v0.8.1 // Load native certificates using rustls_native_certs v0.8.1
// This returns a CertificateResult which has a certs field containing the certificates // This returns a CertificateResult which has a certs field containing the certificates
let native_certs = rustls_native_certs::load_native_certs(); let native_certs = rustls_native_certs::load_native_certs();
// Add each cert to the root store // Add each cert to the root store
for cert in &native_certs.certs { for cert in &native_certs.certs {
if let Err(e) = root_cert_store.add(cert.clone()) { if let Err(e) = root_cert_store.add(cert.clone()) {
return Err(ClientError::TlsError(format!("Failed to add native cert to root store: {}", e))); return Err(ClientError::TlsError(format!(
"Failed to add native cert to root store: {}",
e
)));
} }
} }
// Add custom CA certificate if provided // Add custom CA certificate if provided
if let Some(ref pem_bytes) = self.root_ca_pem { if let Some(ref pem_bytes) = self.root_ca_pem {
let mut reader = std::io::Cursor::new(pem_bytes); let mut reader = std::io::Cursor::new(pem_bytes);
for cert_result in rustls_pemfile::certs(&mut reader) { for cert_result in rustls_pemfile::certs(&mut reader) {
match cert_result { match cert_result {
Ok(cert) => { Ok(cert) => {
root_cert_store.add(cert) root_cert_store.add(cert).map_err(|e| {
.map_err(|e| ClientError::TlsError(format!("Failed to add custom cert to root store: {}", e)))?; ClientError::TlsError(format!(
}, "Failed to add custom cert to root store: {}",
Err(e) => return Err(ClientError::TlsError(format!("Failed to parse PEM cert: {}", e))), e
))
})?;
}
Err(e) => {
return Err(ClientError::TlsError(format!(
"Failed to parse PEM cert: {}",
e
)));
}
} }
} }
} }
// Configure rustls // Configure rustls
let mut config_builder = rustls::ClientConfig::builder() let mut config_builder =
.with_root_certificates(root_cert_store); rustls::ClientConfig::builder().with_root_certificates(root_cert_store);
let rustls_config = config_builder.with_no_client_auth(); let rustls_config = config_builder.with_no_client_auth();
#[cfg(feature = "insecure-dangerous")] #[cfg(feature = "insecure-dangerous")]
let rustls_config = if self.accept_invalid_certs { let rustls_config = if self.accept_invalid_certs {
// ⚠️ SECURITY WARNING: This code path deliberately bypasses TLS certificate validation. // ⚠️ SECURITY WARNING: This code path deliberately bypasses TLS certificate validation.
// It should only be used during development/testing with self-signed certificates, // It should only be used during development/testing with self-signed certificates,
// and NEVER in production environments. This creates a vulnerability to // and NEVER in production environments. This creates a vulnerability to
// man-in-the-middle attacks and is extremely dangerous. // man-in-the-middle attacks and is extremely dangerous.
use std::sync::Arc;
use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified};
use rustls::DigitallySignedStruct; use rustls::DigitallySignedStruct;
use rustls::SignatureScheme; use rustls::SignatureScheme;
use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified};
use rustls::pki_types::UnixTime; use rustls::pki_types::UnixTime;
use std::sync::Arc;
// Override the certificate verifier with a no-op verifier that accepts all certificates // Override the certificate verifier with a no-op verifier that accepts all certificates
#[derive(Debug)] #[derive(Debug)]
struct NoCertificateVerification {} struct NoCertificateVerification {}
impl rustls::client::danger::ServerCertVerifier for NoCertificateVerification { impl rustls::client::danger::ServerCertVerifier for NoCertificateVerification {
fn verify_server_cert( fn verify_server_cert(
&self, &self,
@@ -658,7 +680,7 @@ impl HttpClient {
// Accept any certificate without verification // Accept any certificate without verification
Ok(ServerCertVerified::assertion()) Ok(ServerCertVerified::assertion())
} }
fn verify_tls12_signature( fn verify_tls12_signature(
&self, &self,
_message: &[u8], _message: &[u8],
@@ -668,7 +690,7 @@ impl HttpClient {
// Accept any TLS 1.2 signature without verification // Accept any TLS 1.2 signature without verification
Ok(HandshakeSignatureValid::assertion()) Ok(HandshakeSignatureValid::assertion())
} }
fn verify_tls13_signature( fn verify_tls13_signature(
&self, &self,
_message: &[u8], _message: &[u8],
@@ -678,7 +700,7 @@ impl HttpClient {
// Accept any TLS 1.3 signature without verification // Accept any TLS 1.3 signature without verification
Ok(HandshakeSignatureValid::assertion()) Ok(HandshakeSignatureValid::assertion())
} }
fn supported_verify_schemes(&self) -> Vec<SignatureScheme> { fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
// Return a list of all supported signature schemes // Return a list of all supported signature schemes
vec![ vec![
@@ -698,31 +720,35 @@ impl HttpClient {
] ]
} }
} }
// Set up the dangerous configuration with no certificate verification // Set up the dangerous configuration with no certificate verification
let mut config = rustls_config.clone(); let mut config = rustls_config.clone();
config.dangerous().set_certificate_verifier(Arc::new(NoCertificateVerification {})); config
.dangerous()
.set_certificate_verifier(Arc::new(NoCertificateVerification {}));
config config
} else { } else {
rustls_config rustls_config
}; };
// Handle certificate pinning if configured // Handle certificate pinning if configured
#[cfg(feature = "rustls")] #[cfg(feature = "rustls")]
let rustls_config = if let Some(ref pins) = self.pinned_cert_sha256 { let rustls_config = if let Some(ref pins) = self.pinned_cert_sha256 {
// Implement certificate pinning by creating a custom certificate verifier // Implement certificate pinning by creating a custom certificate verifier
use std::sync::Arc;
use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier};
use rustls::DigitallySignedStruct; use rustls::DigitallySignedStruct;
use rustls::SignatureScheme; use rustls::SignatureScheme;
use rustls::client::danger::{
HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier,
};
use rustls::pki_types::{CertificateDer, ServerName, UnixTime}; use rustls::pki_types::{CertificateDer, ServerName, UnixTime};
use std::sync::Arc;
// Create a custom certificate verifier that checks certificate pins // Create a custom certificate verifier that checks certificate pins
struct CertificatePinner { struct CertificatePinner {
pins: Vec<[u8; 32]>, pins: Vec<[u8; 32]>,
inner: Arc<dyn ServerCertVerifier>, inner: Arc<dyn ServerCertVerifier>,
} }
impl ServerCertVerifier for CertificatePinner { impl ServerCertVerifier for CertificatePinner {
fn verify_server_cert( fn verify_server_cert(
&self, &self,
@@ -733,28 +759,36 @@ impl HttpClient {
now: UnixTime, now: UnixTime,
) -> Result<ServerCertVerified, rustls::Error> { ) -> Result<ServerCertVerified, rustls::Error> {
// First, use the inner verifier to do standard verification // First, use the inner verifier to do standard verification
self.inner.verify_server_cert(end_entity, intermediates, server_name, ocsp_response, now)?; self.inner.verify_server_cert(
end_entity,
intermediates,
server_name,
ocsp_response,
now,
)?;
// Then verify the pin // Then verify the pin
use sha2::{Sha256, Digest}; use sha2::{Digest, Sha256};
let mut hasher = Sha256::new(); let mut hasher = Sha256::new();
hasher.update(end_entity.as_ref()); hasher.update(end_entity.as_ref());
let cert_hash = hasher.finalize(); let cert_hash = hasher.finalize();
// Check if the certificate hash matches any of our pins // Check if the certificate hash matches any of our pins
for pin in &self.pins { for pin in &self.pins {
if pin[..] == cert_hash[..] { if pin[..] == cert_hash[..] {
return Ok(ServerCertVerified::assertion()); return Ok(ServerCertVerified::assertion());
} }
} }
// If we got here, none of the pins matched // If we got here, none of the pins matched
Err(rustls::Error::General("Certificate pin verification failed".into())) Err(rustls::Error::General(
"Certificate pin verification failed".into(),
))
} }
fn verify_tls12_signature( fn verify_tls12_signature(
&self, &self,
message: &[u8], message: &[u8],
cert: &CertificateDer<'_>, cert: &CertificateDer<'_>,
dss: &DigitallySignedStruct, dss: &DigitallySignedStruct,
@@ -762,7 +796,7 @@ impl HttpClient {
// Delegate to inner verifier // Delegate to inner verifier
self.inner.verify_tls12_signature(message, cert, dss) self.inner.verify_tls12_signature(message, cert, dss)
} }
fn verify_tls13_signature( fn verify_tls13_signature(
&self, &self,
message: &[u8], message: &[u8],
@@ -772,46 +806,50 @@ impl HttpClient {
// Delegate to inner verifier // Delegate to inner verifier
self.inner.verify_tls13_signature(message, cert, dss) self.inner.verify_tls13_signature(message, cert, dss)
} }
fn supported_verify_schemes(&self) -> Vec<SignatureScheme> { fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
self.inner.supported_verify_schemes() self.inner.supported_verify_schemes()
} }
} }
// Create the certificate pinner with our pins and the default verifier // Create the certificate pinner with our pins and the default verifier
let mut config = rustls_config.clone(); let mut config = rustls_config.clone();
let default_verifier = rustls::client::WebPkiServerVerifier::builder() let default_verifier = rustls::client::WebPkiServerVerifier::builder()
.with_root_certificates(root_cert_store.clone()) .with_root_certificates(root_cert_store.clone())
.build() .build()
.map_err(|e| ClientError::TlsError(format!("Failed to build certificate verifier: {}", e)))?; .map_err(|e| {
ClientError::TlsError(format!(
"Failed to build certificate verifier: {}",
e
))
})?;
let cert_pinner = Arc::new(CertificatePinner { let cert_pinner = Arc::new(CertificatePinner {
pins: pins.clone(), pins: pins.clone(),
inner: default_verifier, inner: default_verifier,
}); });
config.dangerous().set_certificate_verifier(cert_pinner); config.dangerous().set_certificate_verifier(cert_pinner);
config config
} else { } else {
rustls_config rustls_config
}; };
// Create a connector that supports HTTP and HTTPS // Create a connector that supports HTTP and HTTPS
let mut http_connector = hyper_util::client::legacy::connect::HttpConnector::new(); let mut http_connector = hyper_util::client::legacy::connect::HttpConnector::new();
http_connector.enforce_http(false); http_connector.enforce_http(false);
// Create the rustls connector using HttpsConnectorBuilder // Create the rustls connector using HttpsConnectorBuilder
let https_connector = hyper_rustls::HttpsConnectorBuilder::new() let https_connector = hyper_rustls::HttpsConnectorBuilder::new()
.with_tls_config(rustls_config) .with_tls_config(rustls_config)
.https_or_http() .https_or_http()
.enable_http1() .enable_http1()
.build(); .build();
let client = Client::builder(TokioExecutor::new()).build(https_connector); let client = Client::builder(TokioExecutor::new()).build(https_connector);
let resp = tokio::time::timeout(self.timeout, client.request(req)) let resp = tokio::time::timeout(self.timeout, client.request(req))
.await .await
.map_err(|_| ClientError::TlsError("Request timed out".to_string()))? .map_err(|_| ClientError::TlsError("Request timed out".to_string()))??;
?;
self.build_response(resp).await self.build_response(resp).await
} }
#[cfg(not(any(feature = "native-tls", feature = "rustls")))] #[cfg(not(any(feature = "native-tls", feature = "rustls")))]
@@ -824,8 +862,7 @@ impl HttpClient {
let client = Client::builder(TokioExecutor::new()).build(connector); let client = Client::builder(TokioExecutor::new()).build(connector);
let resp = tokio::time::timeout(self.timeout, client.request(req)) let resp = tokio::time::timeout(self.timeout, client.request(req))
.await .await
.map_err(|_| ClientError::TlsError("Request timed out".to_string()))? .map_err(|_| ClientError::TlsError("Request timed out".to_string()))??;
?;
self.build_response(resp).await self.build_response(resp).await
} }
} }
@@ -836,7 +873,7 @@ impl HttpClient {
/// response body into a `Bytes` buffer for easy consumption by the caller. /// response body into a `Bytes` buffer for easy consumption by the caller.
async fn build_response(&self, resp: Response<Incoming>) -> Result<HttpResponse, ClientError> { async fn build_response(&self, resp: Response<Incoming>) -> Result<HttpResponse, ClientError> {
let status = resp.status(); let status = resp.status();
// Convert hyper's `HeaderMap` to a `HashMap<String, String>` for simpler // Convert hyper's `HeaderMap` to a `HashMap<String, String>` for simpler
// public API exposure, making header access more idiomatic for consumers. // public API exposure, making header access more idiomatic for consumers.
let mut headers = HashMap::new(); let mut headers = HashMap::new();
@@ -845,12 +882,12 @@ impl HttpClient {
headers.insert(name.to_string(), value_str.to_string()); headers.insert(name.to_string(), value_str.to_string());
} }
} }
// Collect the body as raw bytes - this is the key part of the issue // Collect the body as raw bytes - this is the key part of the issue
// We expose the body as raw bytes without any permutations, ensuring // We expose the body as raw bytes without any permutations, ensuring
// the client receives the exact byte content of the response. // the client receives the exact byte content of the response.
let body_bytes = resp.into_body().collect().await?.to_bytes(); let body_bytes = resp.into_body().collect().await?.to_bytes();
Ok(HttpResponse { Ok(HttpResponse {
status, status,
headers, headers,
@@ -865,26 +902,41 @@ impl HttpClient {
/// On wasm32 targets, runtime methods are stubbed and return /// On wasm32 targets, runtime methods are stubbed and return
/// `ClientError::WasmNotImplemented` because browsers do not allow /// `ClientError::WasmNotImplemented` because browsers do not allow
/// programmatic installation/trust of custom CAs. /// programmatic installation/trust of custom CAs.
#[deprecated(since = "0.4.0", note = "Use request_with_options(url, Some(options)) instead")] #[deprecated(
since = "0.4.0",
note = "Use request_with_options(url, Some(options)) instead"
)]
pub fn request(&self, _url: &str) -> Result<(), ClientError> { pub fn request(&self, _url: &str) -> Result<(), ClientError> {
Err(ClientError::WasmNotImplemented) Err(ClientError::WasmNotImplemented)
} }
/// On wasm32 targets, runtime methods are stubbed and return /// On wasm32 targets, runtime methods are stubbed and return
/// `ClientError::WasmNotImplemented` because browsers do not allow /// `ClientError::WasmNotImplemented` because browsers do not allow
/// programmatic installation/trust of custom CAs. /// programmatic installation/trust of custom CAs.
pub fn request_with_options(&self, _url: &str, _options: Option<RequestOptions>) -> Result<(), ClientError> { pub fn request_with_options(
&self,
_url: &str,
_options: Option<RequestOptions>,
) -> Result<(), ClientError> {
Err(ClientError::WasmNotImplemented) Err(ClientError::WasmNotImplemented)
} }
/// POST is also not implemented on wasm32 targets for the same reason. /// POST is also not implemented on wasm32 targets for the same reason.
#[deprecated(since = "0.4.0", note = "Use post_with_options(url, body, Some(options)) instead")] #[deprecated(
since = "0.4.0",
note = "Use post_with_options(url, body, Some(options)) instead"
)]
pub fn post<B: AsRef<[u8]>>(&self, _url: &str, _body: B) -> Result<(), ClientError> { pub fn post<B: AsRef<[u8]>>(&self, _url: &str, _body: B) -> Result<(), ClientError> {
Err(ClientError::WasmNotImplemented) Err(ClientError::WasmNotImplemented)
} }
/// POST is also not implemented on wasm32 targets for the same reason. /// POST is also not implemented on wasm32 targets for the same reason.
pub fn post_with_options<B: AsRef<[u8]>>(&self, _url: &str, _body: B, _options: Option<RequestOptions>) -> Result<(), ClientError> { pub fn post_with_options<B: AsRef<[u8]>>(
&self,
_url: &str,
_body: B,
_options: Option<RequestOptions>,
) -> Result<(), ClientError> {
Err(ClientError::WasmNotImplemented) Err(ClientError::WasmNotImplemented)
} }
} }
@@ -928,26 +980,26 @@ impl HttpClientBuilder {
/// Dev-only: accept self-signed/invalid TLS certificates. Requires the /// Dev-only: accept self-signed/invalid TLS certificates. Requires the
/// `insecure-dangerous` feature to be enabled. NEVER enable this in production. /// `insecure-dangerous` feature to be enabled. NEVER enable this in production.
/// ///
/// # Security Warning /// # Security Warning
/// ///
/// ⚠️ CRITICAL SECURITY WARNING ⚠️ /// ⚠️ CRITICAL SECURITY WARNING ⚠️
/// ///
/// This method deliberately bypasses TLS certificate validation, which creates a /// This method deliberately bypasses TLS certificate validation, which creates a
/// serious security vulnerability to man-in-the-middle attacks. When enabled: /// serious security vulnerability to man-in-the-middle attacks. When enabled:
/// ///
/// - The client will accept ANY certificate, regardless of its validity /// - The client will accept ANY certificate, regardless of its validity
/// - The client will accept expired certificates /// - The client will accept expired certificates
/// - The client will accept certificates from untrusted issuers /// - The client will accept certificates from untrusted issuers
/// - The client will accept certificates for the wrong domain /// - The client will accept certificates for the wrong domain
/// ///
/// This method should ONLY be used for: /// This method should ONLY be used for:
/// - Local development with self-signed certificates /// - Local development with self-signed certificates
/// - Testing environments where security is not a concern /// - Testing environments where security is not a concern
/// - Debugging TLS connection issues /// - Debugging TLS connection issues
/// ///
/// # Implementation Details /// # Implementation Details
/// ///
/// When enabled, this setting: /// When enabled, this setting:
/// - For `native-tls`: Uses `danger_accept_invalid_certs(true)` on the TLS connector /// - For `native-tls`: Uses `danger_accept_invalid_certs(true)` on the TLS connector
/// - For `rustls`: Implements a custom `ServerCertVerifier` that accepts all certificates /// - For `rustls`: Implements a custom `ServerCertVerifier` that accepts all certificates
@@ -1162,7 +1214,7 @@ mod tests {
} }
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
#[tokio::test] #[tokio::test]
async fn post_returns_ok_on_native() { async fn post_returns_ok_on_native() {
let client = HttpClient::builder().build(); let client = HttpClient::builder().build();
// Just test that the method can be called - don't actually make network requests in tests // Just test that the method can be called - don't actually make network requests in tests

View File

@@ -97,7 +97,6 @@ fn default_client_static_method() {
let _client = HttpClient::default(); let _client = HttpClient::default();
} }
#[tokio::test] #[tokio::test]
async fn post_smoke_default() { async fn post_smoke_default() {
// Smoke test for POST support with default features // Smoke test for POST support with default features

View File

@@ -25,7 +25,7 @@ async fn test_default_client_against_example_endpoints() {
// let _response = client.request("http://localhost:8080/health").await.unwrap(); // let _response = client.request("http://localhost:8080/health").await.unwrap();
// let _response = client.request("http://localhost:8080/status").await.unwrap(); // let _response = client.request("http://localhost:8080/status").await.unwrap();
// let _response = client.request("http://localhost:8080/test/client/default").await.unwrap(); // let _response = client.request("http://localhost:8080/test/client/default").await.unwrap();
// For testing purposes, just verify the client exists // For testing purposes, just verify the client exists
let _ = client; let _ = client;
} }
@@ -55,13 +55,14 @@ fn test_timeout_configuration_for_example_server() {
fn test_headers_configuration_for_example_server() { fn test_headers_configuration_for_example_server() {
// Test custom headers configuration // Test custom headers configuration
let mut headers = HashMap::new(); let mut headers = HashMap::new();
headers.insert("User-Agent".to_string(), "hyper-custom-cert-integration-test/1.0".to_string()); headers.insert(
"User-Agent".to_string(),
"hyper-custom-cert-integration-test/1.0".to_string(),
);
headers.insert("X-Test-Client".to_string(), "integration".to_string()); headers.insert("X-Test-Client".to_string(), "integration".to_string());
headers.insert("Accept".to_string(), "application/json".to_string()); headers.insert("Accept".to_string(), "application/json".to_string());
let client = HttpClient::builder() let client = HttpClient::builder().with_default_headers(headers).build();
.with_default_headers(headers)
.build();
// Smoke test - verify header configuration compiles // Smoke test - verify header configuration compiles
let _ = client; let _ = client;
@@ -71,7 +72,10 @@ fn test_headers_configuration_for_example_server() {
fn test_combined_configuration_for_example_server() { fn test_combined_configuration_for_example_server() {
// Test combining multiple configuration options // Test combining multiple configuration options
let mut headers = HashMap::new(); let mut headers = HashMap::new();
headers.insert("User-Agent".to_string(), "hyper-custom-cert-combined-test/1.0".to_string()); headers.insert(
"User-Agent".to_string(),
"hyper-custom-cert-combined-test/1.0".to_string(),
);
let client = HttpClient::builder() let client = HttpClient::builder()
.with_timeout(Duration::from_secs(30)) .with_timeout(Duration::from_secs(30))
@@ -132,9 +136,7 @@ fn test_rustls_cert_pinning_configuration() {
let dummy_pin = [0u8; 32]; let dummy_pin = [0u8; 32];
let pins = vec![dummy_pin]; let pins = vec![dummy_pin];
let client = HttpClient::builder() let client = HttpClient::builder().with_pinned_cert_sha256(pins).build();
.with_pinned_cert_sha256(pins)
.build();
// Smoke test - verify cert pinning compiles // Smoke test - verify cert pinning compiles
let _ = client; let _ = client;
@@ -171,18 +173,18 @@ async fn test_request_options() {
use hyper_custom_cert::RequestOptions; use hyper_custom_cert::RequestOptions;
use std::collections::HashMap; use std::collections::HashMap;
use std::time::Duration; use std::time::Duration;
// Test RequestOptions functionality // Test RequestOptions functionality
let client = HttpClient::new(); let client = HttpClient::new();
// Create request options // Create request options
let mut headers = HashMap::new(); let mut headers = HashMap::new();
headers.insert("X-Custom-Header".to_string(), "test-value".to_string()); headers.insert("X-Custom-Header".to_string(), "test-value".to_string());
let options = RequestOptions::new() let options = RequestOptions::new()
.with_headers(headers) .with_headers(headers)
.with_timeout(Duration::from_secs(15)); .with_timeout(Duration::from_secs(15));
// Smoke test - verify request options can be used with both GET and POST // Smoke test - verify request options can be used with both GET and POST
// In real usage: // In real usage:
// let _get_resp = client.request("https://example.com", Some(options.clone())).await.unwrap(); // let _get_resp = client.request("https://example.com", Some(options.clone())).await.unwrap();
@@ -382,4 +384,4 @@ async fn test_insecure_usage_example() {
// In real usage: let _response = client.request("https://localhost:8080/self-signed").await.unwrap(); // In real usage: let _response = client.request("https://localhost:8080/self-signed").await.unwrap();
// In real usage: let _response = client2.request("https://localhost:8080/self-signed").await.unwrap(); // In real usage: let _response = client2.request("https://localhost:8080/self-signed").await.unwrap();
let _ = (client, client2); let _ = (client, client2);
} }