Rename transport binding to WIP over HTTP
This commit is contained in:
@@ -14,7 +14,7 @@ ratatui = "0.29.0"
|
||||
serde_json = "1"
|
||||
ureq = "3.1.2"
|
||||
wip-client = { path = "../wip-client" }
|
||||
wip-https = { path = "../wip-https" }
|
||||
wip-http = { path = "../wip-http" }
|
||||
wip-protocol = { path = "../wip-protocol" }
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
@@ -8,12 +8,17 @@ owned by the client runtime.
|
||||
|
||||
## Start
|
||||
|
||||
HTTPS is the normal mode:
|
||||
Supply one `http` or `https` endpoint:
|
||||
|
||||
```console
|
||||
cargo run -p wip-tui -- --endpoint https://wip.example.test/api/wip
|
||||
```
|
||||
|
||||
The same canonical endpoint is used unchanged to construct requests, perform
|
||||
HTTP I/O, and partition the client cache. WIP over HTTP does not define separate
|
||||
HTTPS and development-HTTP profiles; URL scheme, TLS, certificate, authentication,
|
||||
and proxy policy belong to the HTTP implementation and deployment.
|
||||
|
||||
The endpoint may instead be supplied as `WIP_ENDPOINT`. A non-secret cache
|
||||
partition identity may be supplied with `--security-context NAME` or
|
||||
`WIP_SECURITY_CONTEXT`. The default request timeout is 30 seconds and can be
|
||||
@@ -31,16 +36,6 @@ Use `--authorization-env OTHER_VARIABLE` to select another variable. The value i
|
||||
redacted from `Debug` and is never rendered. Shell and process environment security
|
||||
remain the operator's responsibility.
|
||||
|
||||
Plain HTTP is rejected by default. It is available only as an explicit loopback
|
||||
development mode:
|
||||
|
||||
```console
|
||||
cargo run -p wip-tui -- http://127.0.0.1:8080/wip --allow-http
|
||||
```
|
||||
|
||||
Non-loopback HTTP remains rejected even with `--allow-http`. For production, use a
|
||||
real HTTPS endpoint and a trusted TLS deployment.
|
||||
|
||||
### Filesystem server example
|
||||
|
||||
In one terminal:
|
||||
@@ -52,14 +47,13 @@ cargo run -p wip-fs-server -- \
|
||||
--root /tmp/wip-example \
|
||||
--listen 127.0.0.1:8080 \
|
||||
--base-path /wip \
|
||||
--development-http \
|
||||
--read-write
|
||||
```
|
||||
|
||||
In another:
|
||||
|
||||
```console
|
||||
cargo run -p wip-tui -- http://127.0.0.1:8080/wip --allow-http
|
||||
cargo run -p wip-tui -- http://127.0.0.1:8080/wip
|
||||
```
|
||||
|
||||
Expand `/`, select `notes.txt`, focus the Interface/Operation pane, and choose
|
||||
|
||||
@@ -939,7 +939,7 @@ mod tests {
|
||||
// A request identity has no public constructor; derive one through the runtime.
|
||||
let mut client = wip_client::Client::new(
|
||||
wip_client::ClientLimits::new(1, 4, 4, 4, 4).unwrap(),
|
||||
wip_https::Limits::new(1024, 1024, 8, 8).unwrap(),
|
||||
wip_http::Limits::new(1024, 1024, 8, 8).unwrap(),
|
||||
);
|
||||
let session = client
|
||||
.open_session(
|
||||
|
||||
+34
-110
@@ -1,10 +1,10 @@
|
||||
//! Secure-by-default command-line and environment configuration.
|
||||
//! Command-line and environment configuration for the HTTP client.
|
||||
|
||||
use std::error::Error;
|
||||
use std::fmt::{self, Debug, Display, Formatter};
|
||||
use std::time::Duration;
|
||||
|
||||
use wip_https::url::Url;
|
||||
use wip_http::Endpoint;
|
||||
|
||||
/// Credential wrapper whose debug output is always redacted.
|
||||
#[derive(Clone)]
|
||||
@@ -25,45 +25,33 @@ impl Debug for Secret {
|
||||
/// Validated runtime configuration.
|
||||
#[derive(Clone)]
|
||||
pub struct Config {
|
||||
/// Canonical logical HTTPS endpoint used by `wip-client`.
|
||||
logical_endpoint: String,
|
||||
/// Actual request origin; differs only for explicit loopback development HTTP.
|
||||
transport_endpoint: String,
|
||||
/// Canonical endpoint used for request construction, transport, and cache partitioning.
|
||||
endpoint: String,
|
||||
/// Non-secret cache partition identity.
|
||||
security_context: String,
|
||||
/// Bounded network timeout.
|
||||
timeout: Duration,
|
||||
/// Optional complete Authorization header value, never rendered or logged.
|
||||
authorization: Option<Secret>,
|
||||
/// Whether loopback request URIs are deliberately downgraded to HTTP.
|
||||
development_http: bool,
|
||||
}
|
||||
|
||||
impl Debug for Config {
|
||||
fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
|
||||
formatter
|
||||
.debug_struct("Config")
|
||||
.field("logical_endpoint", &self.logical_endpoint)
|
||||
.field("transport_endpoint", &self.transport_endpoint)
|
||||
.field("endpoint", &self.endpoint)
|
||||
.field("security_context", &self.security_context)
|
||||
.field("timeout", &self.timeout)
|
||||
.field("authorization", &self.authorization)
|
||||
.field("development_http", &self.development_http)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
/// Returns the canonical HTTPS endpoint used for protocol request construction.
|
||||
/// Returns the canonical endpoint used by the client and HTTP transport.
|
||||
#[must_use]
|
||||
pub fn logical_endpoint(&self) -> &str {
|
||||
&self.logical_endpoint
|
||||
}
|
||||
|
||||
/// Returns the validated effective transport endpoint shown in the UI.
|
||||
#[must_use]
|
||||
pub fn transport_endpoint(&self) -> &str {
|
||||
&self.transport_endpoint
|
||||
pub fn endpoint(&self) -> &str {
|
||||
&self.endpoint
|
||||
}
|
||||
|
||||
/// Returns the non-secret session cache partition identity.
|
||||
@@ -78,12 +66,6 @@ impl Config {
|
||||
self.timeout
|
||||
}
|
||||
|
||||
/// Whether explicit loopback development HTTP is enabled.
|
||||
#[must_use]
|
||||
pub const fn development_http(&self) -> bool {
|
||||
self.development_http
|
||||
}
|
||||
|
||||
pub(crate) fn authorization(&self) -> Option<&Secret> {
|
||||
self.authorization.as_ref()
|
||||
}
|
||||
@@ -115,7 +97,6 @@ impl Config {
|
||||
F: FnMut(&str) -> Option<String>,
|
||||
{
|
||||
let mut endpoint = None;
|
||||
let mut allow_http = false;
|
||||
let mut timeout = Duration::from_secs(30);
|
||||
let mut security_context = security_context_env.unwrap_or_else(|| "anonymous".into());
|
||||
let mut authorization_env = "WIP_AUTHORIZATION".to_owned();
|
||||
@@ -123,7 +104,6 @@ impl Config {
|
||||
while let Some(argument) = iter.next() {
|
||||
match argument.as_str() {
|
||||
"--endpoint" => endpoint = Some(next_value(&mut iter, "--endpoint")?),
|
||||
"--allow-http" => allow_http = true,
|
||||
"--security-context" => {
|
||||
security_context = next_value(&mut iter, "--security-context")?;
|
||||
}
|
||||
@@ -149,52 +129,22 @@ impl Config {
|
||||
_ => return Err(ConfigError::Invalid("multiple endpoints supplied".into())),
|
||||
}
|
||||
}
|
||||
let endpoint = endpoint
|
||||
let supplied = endpoint
|
||||
.or(endpoint_env)
|
||||
.ok_or_else(|| ConfigError::Invalid("endpoint is required".into()))?;
|
||||
let mut actual = Url::parse(&endpoint)
|
||||
.map_err(|_| ConfigError::Invalid("endpoint is not a valid absolute URL".into()))?;
|
||||
if actual.query().is_some()
|
||||
|| actual.fragment().is_some()
|
||||
|| actual.host_str().is_none()
|
||||
|| !actual.username().is_empty()
|
||||
|| actual.password().is_some()
|
||||
{
|
||||
let endpoint =
|
||||
Endpoint::parse(&supplied).map_err(|error| ConfigError::Invalid(error.to_string()))?;
|
||||
if !endpoint.as_url().username().is_empty() || endpoint.as_url().password().is_some() {
|
||||
return Err(ConfigError::Invalid(
|
||||
"endpoint must be an absolute URL without userinfo, query, or fragment".into(),
|
||||
"endpoint userinfo is not accepted; supply credentials through the configured environment variable"
|
||||
.into(),
|
||||
));
|
||||
}
|
||||
if !actual.path().ends_with('/') {
|
||||
let path = format!("{}/", actual.path());
|
||||
actual.set_path(&path);
|
||||
if !matches!(endpoint.as_url().scheme(), "http" | "https") {
|
||||
return Err(ConfigError::Invalid(
|
||||
"this HTTP client supports http and https endpoint schemes".into(),
|
||||
));
|
||||
}
|
||||
let development_http = actual.scheme() == "http";
|
||||
match actual.scheme() {
|
||||
"https" => {}
|
||||
"http" if allow_http && loopback_host(&actual) => {}
|
||||
"http" if !allow_http => {
|
||||
return Err(ConfigError::Invalid(
|
||||
"plain HTTP requires the explicit --allow-http development option".into(),
|
||||
));
|
||||
}
|
||||
"http" => {
|
||||
return Err(ConfigError::Invalid(
|
||||
"development HTTP is restricted to loopback endpoints".into(),
|
||||
));
|
||||
}
|
||||
_ => return Err(ConfigError::Invalid("endpoint must use HTTPS".into())),
|
||||
}
|
||||
let transport_endpoint = actual.to_string();
|
||||
let mut logical = actual;
|
||||
if development_http {
|
||||
logical
|
||||
.set_scheme("https")
|
||||
.expect("http and https are interchangeable hierarchical schemes");
|
||||
}
|
||||
// Let the binding enforce all remaining canonical endpoint rules.
|
||||
let logical_endpoint = wip_https::Endpoint::parse(logical.as_str())
|
||||
.map_err(|error| ConfigError::Invalid(error.to_string()))?
|
||||
.to_string();
|
||||
if security_context.is_empty() {
|
||||
return Err(ConfigError::Invalid(
|
||||
"security context must not be empty".into(),
|
||||
@@ -204,20 +154,18 @@ impl Config {
|
||||
.filter(|value| !value.is_empty())
|
||||
.map(Secret);
|
||||
Ok(Self {
|
||||
logical_endpoint,
|
||||
transport_endpoint,
|
||||
endpoint: endpoint.to_string(),
|
||||
security_context,
|
||||
timeout,
|
||||
authorization,
|
||||
development_http,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns concise command usage without mentioning credential values.
|
||||
#[must_use]
|
||||
pub const fn usage() -> &'static str {
|
||||
"usage: wip-tui [--endpoint] URL [--allow-http] [--security-context NAME] [--authorization-env VAR] [--timeout SECONDS]\n\
|
||||
URL may also be supplied as WIP_ENDPOINT; HTTPS is required unless loopback HTTP is explicitly enabled."
|
||||
"usage: wip-tui [--endpoint] URL [--security-context NAME] [--authorization-env VAR] [--timeout SECONDS]\n\
|
||||
URL may also be supplied as WIP_ENDPOINT; the HTTP adapter supports http and https."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,15 +177,6 @@ fn next_value(
|
||||
.ok_or_else(|| ConfigError::Invalid(format!("{option} requires a value")))
|
||||
}
|
||||
|
||||
fn loopback_host(url: &Url) -> bool {
|
||||
match url.host() {
|
||||
Some(wip_https::url::Host::Ipv4(address)) => address.is_loopback(),
|
||||
Some(wip_https::url::Host::Ipv6(address)) => address.is_loopback(),
|
||||
Some(wip_https::url::Host::Domain(name)) => name.eq_ignore_ascii_case("localhost"),
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Startup configuration failure.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ConfigError {
|
||||
@@ -263,7 +202,7 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn https_is_default_and_secrets_are_redacted() {
|
||||
fn endpoint_is_canonical_and_secrets_are_redacted() {
|
||||
let config = Config::parse(
|
||||
["https://example.test/wip", "--authorization-env", "TOKEN"],
|
||||
None,
|
||||
@@ -271,7 +210,7 @@ mod tests {
|
||||
|name| (name == "TOKEN").then(|| "Bearer secret-value".into()),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(config.logical_endpoint(), "https://example.test/wip/");
|
||||
assert_eq!(config.endpoint(), "https://example.test/wip/");
|
||||
let debug = format!("{config:?}");
|
||||
assert!(debug.contains("[REDACTED]"));
|
||||
assert!(!debug.contains("secret-value"));
|
||||
@@ -279,37 +218,22 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn endpoint_userinfo_is_rejected_before_it_can_reach_debug_or_render_state() {
|
||||
for arguments in [
|
||||
vec!["https://user:password@example.test/wip"],
|
||||
vec!["http://user:password@localhost:3000/wip", "--allow-http"],
|
||||
for endpoint in [
|
||||
"https://user:password@example.test/wip",
|
||||
"http://user:password@localhost:3000/wip",
|
||||
] {
|
||||
let error = Config::parse(arguments, None, None, |_| None).unwrap_err();
|
||||
assert!(error.to_string().contains("without userinfo"));
|
||||
let error = Config::parse([endpoint], None, None, |_| None).unwrap_err();
|
||||
assert!(error.to_string().contains("userinfo"));
|
||||
assert!(!error.to_string().contains("password"));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn http_requires_explicit_loopback_development_mode() {
|
||||
assert!(Config::parse(["http://localhost:3000/wip"], None, None, |_| None).is_err());
|
||||
let config = Config::parse(
|
||||
["http://127.0.0.1:3000/wip", "--allow-http"],
|
||||
None,
|
||||
None,
|
||||
|_| None,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(config.logical_endpoint(), "https://127.0.0.1:3000/wip/");
|
||||
assert_eq!(config.transport_endpoint(), "http://127.0.0.1:3000/wip/");
|
||||
assert!(config.development_http());
|
||||
assert!(
|
||||
Config::parse(
|
||||
["http://example.test/wip", "--allow-http"],
|
||||
None,
|
||||
None,
|
||||
|_| None,
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
fn http_and_https_are_preserved_as_the_single_endpoint() {
|
||||
for expected in ["http://example.test:3000/wip/", "https://example.test/wip/"] {
|
||||
let supplied = expected.trim_end_matches('/');
|
||||
let config = Config::parse([supplied], None, None, |_| None).unwrap();
|
||||
assert_eq!(config.endpoint(), expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,8 +13,8 @@ use wip_client::{
|
||||
DispatchedTransportFailure, OutcomeUnknownReason, PreparedRequest, RequestId, SecurityContext,
|
||||
SessionId,
|
||||
};
|
||||
use wip_https::Limits;
|
||||
use wip_https::http::{Request, Response};
|
||||
use wip_http::Limits;
|
||||
use wip_http::http::{Request, Response};
|
||||
use wip_protocol::{InterfaceDescriptor, TypeExpr, Value};
|
||||
|
||||
use crate::app::{
|
||||
@@ -68,18 +68,17 @@ pub trait Transport: Send + Sync + 'static {
|
||||
fn execute(&self, request: Self::Prepared) -> Result<Response<Vec<u8>>, TransportError>;
|
||||
}
|
||||
|
||||
/// HTTPS transport with redirects disabled and bounded response bodies.
|
||||
/// HTTP transport with redirects disabled and bounded response bodies.
|
||||
#[derive(Clone)]
|
||||
pub struct HttpTransport {
|
||||
agent: ureq::Agent,
|
||||
logical_endpoint: wip_https::Endpoint,
|
||||
transport_endpoint: wip_https::url::Url,
|
||||
authorization: Option<wip_https::http::HeaderValue>,
|
||||
endpoint: wip_http::Endpoint,
|
||||
authorization: Option<wip_http::http::HeaderValue>,
|
||||
max_response_bytes: usize,
|
||||
}
|
||||
|
||||
impl HttpTransport {
|
||||
/// Builds the default TLS transport and revalidates its immutable origins.
|
||||
/// Builds the HTTP transport and revalidates its immutable endpoint.
|
||||
pub fn new(config: &Config, max_response_bytes: usize) -> Result<Self, TransportError> {
|
||||
if max_response_bytes == 0 {
|
||||
return Err(TransportError {
|
||||
@@ -87,29 +86,18 @@ impl HttpTransport {
|
||||
detail: "transport response limit must be nonzero".into(),
|
||||
});
|
||||
}
|
||||
let logical_endpoint =
|
||||
wip_https::Endpoint::parse(config.logical_endpoint()).map_err(|_| TransportError {
|
||||
let endpoint =
|
||||
wip_http::Endpoint::parse(config.endpoint()).map_err(|_| TransportError {
|
||||
kind: TransportErrorKind::Disconnect,
|
||||
detail: "logical endpoint failed transport validation".into(),
|
||||
detail: "endpoint failed transport validation".into(),
|
||||
})?;
|
||||
let transport_endpoint =
|
||||
wip_https::url::Url::parse(config.transport_endpoint()).map_err(|_| {
|
||||
TransportError {
|
||||
kind: TransportErrorKind::Disconnect,
|
||||
detail: "transport endpoint failed validation".into(),
|
||||
}
|
||||
})?;
|
||||
validate_transport_origin(config, &logical_endpoint, &transport_endpoint)?;
|
||||
let authorization = config
|
||||
.authorization()
|
||||
.map(|secret| {
|
||||
wip_https::http::HeaderValue::from_str(secret.expose()).map_err(|_| {
|
||||
TransportError {
|
||||
kind: TransportErrorKind::Disconnect,
|
||||
detail:
|
||||
"authorization environment variable is not a valid HTTP header value"
|
||||
.into(),
|
||||
}
|
||||
wip_http::http::HeaderValue::from_str(secret.expose()).map_err(|_| TransportError {
|
||||
kind: TransportErrorKind::Disconnect,
|
||||
detail: "authorization environment variable is not a valid HTTP header value"
|
||||
.into(),
|
||||
})
|
||||
})
|
||||
.transpose()?;
|
||||
@@ -121,67 +109,24 @@ impl HttpTransport {
|
||||
.into();
|
||||
Ok(Self {
|
||||
agent,
|
||||
logical_endpoint,
|
||||
transport_endpoint,
|
||||
endpoint,
|
||||
authorization,
|
||||
max_response_bytes,
|
||||
})
|
||||
}
|
||||
|
||||
fn effective_uri(&self, uri: &str) -> Result<String, TransportError> {
|
||||
let candidate = wip_https::url::Url::parse(uri).map_err(|_| TransportError {
|
||||
let candidate = wip_http::url::Url::parse(uri).map_err(|_| TransportError {
|
||||
kind: TransportErrorKind::Disconnect,
|
||||
detail: "refused a request with an invalid absolute URI".into(),
|
||||
})?;
|
||||
let route = self
|
||||
.logical_endpoint
|
||||
self.endpoint
|
||||
.recognize(&candidate)
|
||||
.ok_or_else(|| TransportError {
|
||||
kind: TransportErrorKind::Disconnect,
|
||||
detail: "refused a request outside the configured WIP origin and routes".into(),
|
||||
})?;
|
||||
Ok(self
|
||||
.transport_endpoint
|
||||
.join(route.relative_reference())
|
||||
.expect("validated endpoint joins a fixed route")
|
||||
.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_transport_origin(
|
||||
config: &Config,
|
||||
logical: &wip_https::Endpoint,
|
||||
transport: &wip_https::url::Url,
|
||||
) -> Result<(), TransportError> {
|
||||
let common_invalid = transport.query().is_some()
|
||||
|| transport.fragment().is_some()
|
||||
|| transport.host_str().is_none()
|
||||
|| !transport.username().is_empty()
|
||||
|| transport.password().is_some();
|
||||
let valid = if config.development_http() {
|
||||
let loopback = match transport.host() {
|
||||
Some(wip_https::url::Host::Ipv4(address)) => address.is_loopback(),
|
||||
Some(wip_https::url::Host::Ipv6(address)) => address.is_loopback(),
|
||||
Some(wip_https::url::Host::Domain(name)) => name.eq_ignore_ascii_case("localhost"),
|
||||
None => false,
|
||||
};
|
||||
let mut upgraded = transport.clone();
|
||||
let changed = upgraded.set_scheme("https").is_ok();
|
||||
!common_invalid
|
||||
&& transport.scheme() == "http"
|
||||
&& loopback
|
||||
&& changed
|
||||
&& &upgraded == logical.as_url()
|
||||
} else {
|
||||
!common_invalid && transport == logical.as_url()
|
||||
};
|
||||
if valid {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(TransportError {
|
||||
kind: TransportErrorKind::Disconnect,
|
||||
detail: "transport endpoint does not match the validated logical origin".into(),
|
||||
})
|
||||
Ok(candidate.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,10 +141,9 @@ impl Transport for HttpTransport {
|
||||
detail: "transport could not construct the configured request URI".into(),
|
||||
})?;
|
||||
if let Some(authorization) = &self.authorization {
|
||||
parts.headers.insert(
|
||||
wip_https::http::header::AUTHORIZATION,
|
||||
authorization.clone(),
|
||||
);
|
||||
parts
|
||||
.headers
|
||||
.insert(wip_http::http::header::AUTHORIZATION, authorization.clone());
|
||||
}
|
||||
Ok(Request::from_parts(parts, body))
|
||||
}
|
||||
@@ -242,7 +186,7 @@ fn classify_ureq_error(error: ureq::Error) -> TransportError {
|
||||
},
|
||||
_ => TransportError {
|
||||
kind: TransportErrorKind::Disconnect,
|
||||
detail: "HTTP/TLS transport failed".into(),
|
||||
detail: "HTTP transport failed".into(),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -274,7 +218,7 @@ impl<T: Transport> Runtime<T> {
|
||||
);
|
||||
let session = client
|
||||
.open_session(
|
||||
config.logical_endpoint(),
|
||||
config.endpoint(),
|
||||
SecurityContext::new(config.security_context()),
|
||||
)
|
||||
.map_err(|error| RuntimeError(error.to_string()))?;
|
||||
@@ -282,7 +226,7 @@ impl<T: Transport> Runtime<T> {
|
||||
Ok(Self {
|
||||
client,
|
||||
session,
|
||||
app: App::new(config.transport_endpoint()),
|
||||
app: App::new(config.endpoint()),
|
||||
transport: Arc::new(transport),
|
||||
sender,
|
||||
receiver,
|
||||
@@ -1046,6 +990,26 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transport_preserves_the_configured_endpoint_scheme_for_requests() {
|
||||
for scheme in ["http", "https"] {
|
||||
let configured = format!("{scheme}://example.test/wip");
|
||||
let config = Config::parse([configured], None, None, |_| None).unwrap();
|
||||
let transport = HttpTransport::new(&config, 1024).unwrap();
|
||||
let endpoint = wip_http::Endpoint::parse(config.endpoint()).unwrap();
|
||||
let expected = endpoint.route(wip_http::Route::Observe).to_string();
|
||||
let request = Request::builder()
|
||||
.method("POST")
|
||||
.uri(&expected)
|
||||
.body(Vec::new())
|
||||
.unwrap();
|
||||
|
||||
let prepared = transport.prepare(request).unwrap();
|
||||
|
||||
assert_eq!(prepared.uri().to_string(), expected);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn authorization_is_attached_only_after_exact_origin_and_route_validation() {
|
||||
let config = Config::parse(
|
||||
@@ -1065,15 +1029,15 @@ mod tests {
|
||||
assert!(error.detail.contains("outside"));
|
||||
assert!(!error.detail.contains("sensitive"));
|
||||
|
||||
let endpoint = wip_https::Endpoint::parse(config.logical_endpoint()).unwrap();
|
||||
let endpoint = wip_http::Endpoint::parse(config.endpoint()).unwrap();
|
||||
let valid = Request::builder()
|
||||
.method("POST")
|
||||
.uri(endpoint.route(wip_https::Route::Observe).as_str())
|
||||
.uri(endpoint.route(wip_http::Route::Observe).as_str())
|
||||
.body(Vec::new())
|
||||
.unwrap();
|
||||
let prepared = transport.prepare(valid).unwrap();
|
||||
assert_eq!(
|
||||
prepared.headers()[wip_https::http::header::AUTHORIZATION],
|
||||
prepared.headers()[wip_http::http::header::AUTHORIZATION],
|
||||
"Bearer sensitive"
|
||||
);
|
||||
}
|
||||
@@ -1081,7 +1045,7 @@ mod tests {
|
||||
#[test]
|
||||
fn transport_error_messages_do_not_echo_underlying_data() {
|
||||
let error = classify_ureq_error(ureq::Error::BadUri("secret-token".into()));
|
||||
assert_eq!(error.detail, "HTTP/TLS transport failed");
|
||||
assert_eq!(error.detail, "HTTP transport failed");
|
||||
assert!(!error.detail.contains("secret-token"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use tempfile::tempdir;
|
||||
use wip_fs_server::{AccessMode, ServerConfig, TransportSecurity, WipFsServer};
|
||||
use wip_fs_server::{AccessMode, ServerConfig, WipFsServer};
|
||||
use wip_tui::app::{Focus, HistoryKind, InputKey, Modal, ViewState};
|
||||
use wip_tui::config::Config;
|
||||
use wip_tui::runtime::{HttpTransport, Runtime, default_wire_limits};
|
||||
@@ -22,7 +22,6 @@ fn real_http_filesystem_workflow_is_lazy_validator_safe_and_symlink_confined() {
|
||||
|
||||
let mut server_config = ServerConfig::for_root(temporary.path());
|
||||
server_config.access = AccessMode::ReadWrite;
|
||||
server_config.transport_security = TransportSecurity::DevelopmentHttp;
|
||||
let bound = Arc::new(
|
||||
WipFsServer::new(server_config)
|
||||
.expect("build filesystem server")
|
||||
@@ -36,13 +35,8 @@ fn real_http_filesystem_workflow_is_lazy_validator_safe_and_symlink_confined() {
|
||||
thread::spawn(move || bound.serve_until(shutdown))
|
||||
};
|
||||
let endpoint = format!("http://127.0.0.1:{}/wip", bound.local_addr().port());
|
||||
let config = Config::parse(
|
||||
[endpoint, "--allow-http".into()],
|
||||
None,
|
||||
Some("filesystem-test".into()),
|
||||
|_| None,
|
||||
)
|
||||
.expect("development config");
|
||||
let config = Config::parse([endpoint], None, Some("filesystem-test".into()), |_| None)
|
||||
.expect("development config");
|
||||
let transport = HttpTransport::new(&config, default_wire_limits().max_response_bytes())
|
||||
.expect("HTTP transport");
|
||||
let mut runtime = Runtime::new(&config, transport).expect("runtime");
|
||||
|
||||
@@ -5,8 +5,8 @@ use std::time::{Duration, Instant};
|
||||
|
||||
use tempfile::tempdir;
|
||||
use wip_fs_server::{ServerConfig, WipFsServer};
|
||||
use wip_https::encode_protocol_error_response;
|
||||
use wip_https::http::{Request, Response};
|
||||
use wip_http::encode_protocol_error_response;
|
||||
use wip_http::http::{Request, Response};
|
||||
use wip_protocol::{ProtocolError, ProtocolErrorCode, ProtocolInteraction};
|
||||
use wip_tui::app::{ConnectionState, Focus, HistoryKind, InputKey, Modal, ViewState};
|
||||
use wip_tui::config::Config;
|
||||
|
||||
Reference in New Issue
Block a user