fix: reject malformed host target paths
This commit is contained in:
@@ -175,8 +175,9 @@ descriptor; generated descriptors contain neither those references nor Rust
|
|||||||
trait identity.
|
trait identity.
|
||||||
|
|
||||||
Dispatch is qualified by interface reference and operation name and targets a
|
Dispatch is qualified by interface reference and operation name and targets a
|
||||||
canonical Worldspace path. An `ObjectProvider` resolves that path exactly once
|
canonical Worldspace path. An `ObjectProvider` validates that path before
|
||||||
inside `with_object`; the host then checks the supplied object validator,
|
resolution, maps malformed paths to `InvalidRequest`, then resolves a valid path
|
||||||
|
exactly once inside `with_object`; the host checks the supplied object validator,
|
||||||
current interface membership, interface validator, descriptor operation,
|
current interface membership, interface validator, descriptor operation,
|
||||||
operation-specific object-validator policy, arguments, and authorization in
|
operation-specific object-validator policy, arguments, and authorization in
|
||||||
canonical precedence. Path and children remain provider resolution context and
|
canonical precedence. Path and children remain provider resolution context and
|
||||||
|
|||||||
@@ -540,6 +540,8 @@ pub trait ObjectProvider {
|
|||||||
|
|
||||||
/// Fetches the canonical object projection currently published at `path`.
|
/// Fetches the canonical object projection currently published at `path`.
|
||||||
fn fetch(&self, path: &str) -> Result<Object, CallError> {
|
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)
|
self.with_object(path, PublishedObject::protocol_object)
|
||||||
.ok_or_else(|| CallError::new(CallErrorKind::NotFound, "target path is not published"))
|
.ok_or_else(|| CallError::new(CallErrorKind::NotFound, "target path is not published"))
|
||||||
.and_then(|object| {
|
.and_then(|object| {
|
||||||
@@ -552,12 +554,15 @@ pub trait ObjectProvider {
|
|||||||
|
|
||||||
/// Resolves and executes a canonical path-targeted operation request.
|
/// Resolves and executes a canonical path-targeted operation request.
|
||||||
///
|
///
|
||||||
/// Failure checks follow the exact canonical precedence: path, supplied
|
/// Structural path validation precedes provider resolution. For a valid
|
||||||
/// object validator mismatch, membership, interface validator requirement
|
/// request, failure checks follow the exact canonical precedence: path,
|
||||||
/// and mismatch, operation existence, object validator requirement,
|
/// supplied object validator mismatch, membership, interface validator
|
||||||
/// arguments, then authorization. No failed precondition invokes the typed
|
/// requirement and mismatch, operation existence, object validator
|
||||||
/// implementation.
|
/// requirement, arguments, then authorization. No failed precondition
|
||||||
|
/// invokes the provider callback or typed implementation.
|
||||||
fn call_operation(&self, request: CallRequest) -> Result<CallResult, CallError> {
|
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();
|
let path = request.target.path.clone();
|
||||||
self.with_object(&path, |publication| {
|
self.with_object(&path, |publication| {
|
||||||
publication.dispatch_resolved(request, |object, interface, operation| {
|
publication.dispatch_resolved(request, |object, interface, operation| {
|
||||||
@@ -592,6 +597,8 @@ impl Error for PublicationError {}
|
|||||||
/// Stable host dispatch failure kind.
|
/// Stable host dispatch failure kind.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum CallErrorKind {
|
pub enum CallErrorKind {
|
||||||
|
/// The request is structurally invalid.
|
||||||
|
InvalidRequest,
|
||||||
/// The target path is not currently published.
|
/// The target path is not currently published.
|
||||||
NotFound,
|
NotFound,
|
||||||
/// The selected interface is not exposed by the current object.
|
/// The selected interface is not exposed by the current object.
|
||||||
@@ -671,6 +678,7 @@ impl CallError {
|
|||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn protocol_error(&self) -> ProtocolError {
|
pub fn protocol_error(&self) -> ProtocolError {
|
||||||
let code = match self.kind {
|
let code = match self.kind {
|
||||||
|
CallErrorKind::InvalidRequest => ProtocolErrorCode::InvalidRequest,
|
||||||
CallErrorKind::NotFound => ProtocolErrorCode::NotFound,
|
CallErrorKind::NotFound => ProtocolErrorCode::NotFound,
|
||||||
CallErrorKind::InterfaceMismatch => ProtocolErrorCode::InterfaceMismatch,
|
CallErrorKind::InterfaceMismatch => ProtocolErrorCode::InterfaceMismatch,
|
||||||
CallErrorKind::OperationNotFound => ProtocolErrorCode::OperationNotFound,
|
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() {
|
fn call_failures_follow_canonical_precedence_without_execution() {
|
||||||
let (_, provider, hidden) = registry_and_provider();
|
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(
|
assert_failure(
|
||||||
provider
|
provider
|
||||||
.call_operation(request(
|
.call_operation(request(
|
||||||
|
|||||||
Reference in New Issue
Block a user