fix: reject malformed host target paths
This commit is contained in:
@@ -540,6 +540,8 @@ pub trait ObjectProvider {
|
||||
|
||||
/// Fetches the canonical object projection currently published at `path`.
|
||||
fn fetch(&self, path: &str) -> Result<Object, CallError> {
|
||||
wip_protocol::validate_path(path)
|
||||
.map_err(|error| CallError::new(CallErrorKind::InvalidRequest, error.to_string()))?;
|
||||
self.with_object(path, PublishedObject::protocol_object)
|
||||
.ok_or_else(|| CallError::new(CallErrorKind::NotFound, "target path is not published"))
|
||||
.and_then(|object| {
|
||||
@@ -552,12 +554,15 @@ pub trait ObjectProvider {
|
||||
|
||||
/// Resolves and executes a canonical path-targeted operation request.
|
||||
///
|
||||
/// Failure checks follow the exact canonical precedence: path, supplied
|
||||
/// object validator mismatch, membership, interface validator requirement
|
||||
/// and mismatch, operation existence, object validator requirement,
|
||||
/// arguments, then authorization. No failed precondition invokes the typed
|
||||
/// implementation.
|
||||
/// Structural path validation precedes provider resolution. For a valid
|
||||
/// request, failure checks follow the exact canonical precedence: path,
|
||||
/// supplied object validator mismatch, membership, interface validator
|
||||
/// requirement and mismatch, operation existence, object validator
|
||||
/// requirement, arguments, then authorization. No failed precondition
|
||||
/// invokes the provider callback or typed implementation.
|
||||
fn call_operation(&self, request: CallRequest) -> Result<CallResult, CallError> {
|
||||
wip_protocol::validate_path(&request.target.path)
|
||||
.map_err(|error| CallError::new(CallErrorKind::InvalidRequest, error.to_string()))?;
|
||||
let path = request.target.path.clone();
|
||||
self.with_object(&path, |publication| {
|
||||
publication.dispatch_resolved(request, |object, interface, operation| {
|
||||
@@ -592,6 +597,8 @@ impl Error for PublicationError {}
|
||||
/// Stable host dispatch failure kind.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum CallErrorKind {
|
||||
/// The request is structurally invalid.
|
||||
InvalidRequest,
|
||||
/// The target path is not currently published.
|
||||
NotFound,
|
||||
/// The selected interface is not exposed by the current object.
|
||||
@@ -671,6 +678,7 @@ impl CallError {
|
||||
#[must_use]
|
||||
pub fn protocol_error(&self) -> ProtocolError {
|
||||
let code = match self.kind {
|
||||
CallErrorKind::InvalidRequest => ProtocolErrorCode::InvalidRequest,
|
||||
CallErrorKind::NotFound => ProtocolErrorCode::NotFound,
|
||||
CallErrorKind::InterfaceMismatch => ProtocolErrorCode::InterfaceMismatch,
|
||||
CallErrorKind::OperationNotFound => ProtocolErrorCode::OperationNotFound,
|
||||
|
||||
@@ -440,6 +440,37 @@ fn path_dispatch_is_interface_qualified_and_resolves_once_per_call() {
|
||||
fn call_failures_follow_canonical_precedence_without_execution() {
|
||||
let (_, provider, hidden) = registry_and_provider();
|
||||
|
||||
let resolutions = provider.resolutions.get();
|
||||
for invalid_path in [
|
||||
"relative",
|
||||
"/trailing/",
|
||||
"/double//segment",
|
||||
"/dot/./segment",
|
||||
"/parent/../segment",
|
||||
] {
|
||||
assert_failure(
|
||||
provider
|
||||
.call_operation(request(
|
||||
invalid_path,
|
||||
Some(b"stale"),
|
||||
hidden.reference(),
|
||||
Some(b"stale"),
|
||||
"missing",
|
||||
BTreeMap::new(),
|
||||
))
|
||||
.unwrap_err(),
|
||||
CallErrorKind::InvalidRequest,
|
||||
ProtocolErrorCode::InvalidRequest,
|
||||
);
|
||||
}
|
||||
assert_eq!(provider.resolutions.get(), resolutions);
|
||||
assert_failure(
|
||||
provider.fetch("/invalid/").unwrap_err(),
|
||||
CallErrorKind::InvalidRequest,
|
||||
ProtocolErrorCode::InvalidRequest,
|
||||
);
|
||||
assert_eq!(provider.resolutions.get(), resolutions);
|
||||
|
||||
assert_failure(
|
||||
provider
|
||||
.call_operation(request(
|
||||
|
||||
Reference in New Issue
Block a user