Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .changelog/fix-issue-3723.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
applies_to:
- server
authors:
- lee-1104
references:
- smithy-rs#3723
breaking: false
new_feature: false
bug_fix: true
---
Validate body contents when there is empty or no operation input.
For JSON protocols, an empty body or `{}` is accepted.
For CBOR, an empty body or an empty CBOR map (`0xA0`) is accepted.
For XML, only an empty body is accepted. This fixes the `AdditionalTokensEmptyStruct` malformed request protocol test for RPC v2 CBOR.
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,6 @@ class ServerProtocolTestGenerator(
AWS_JSON_10,
"AwsJson10ServerPopulatesNestedDefaultValuesWhenMissingInInResponseParams",
),
// TODO(https://github.com/smithy-lang/smithy-rs/issues/3723): This affects all protocols
FailingTest.MalformedRequestTest(RPC_V2_CBOR_EXTRAS, "AdditionalTokensEmptyStruct"),
// TODO(https://github.com/smithy-lang/smithy-rs/issues/3339)
FailingTest.ResponseTest(RPC_V2_CBOR, "RpcV2CborServerPopulatesDefaultsInResponseWhenMissingInParams"),
FailingTest.ResponseTest(REST_JSON, "RestJsonServerPopulatesDefaultsInResponseWhenMissingInParams"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,12 @@ import software.amazon.smithy.rust.codegen.server.smithy.canReachConstrainedShap
import software.amazon.smithy.rust.codegen.server.smithy.generators.ServerBuilderGenerator
import software.amazon.smithy.rust.codegen.server.smithy.generators.http.ServerRequestBindingGenerator
import software.amazon.smithy.rust.codegen.server.smithy.generators.http.ServerResponseBindingGenerator
import software.amazon.smithy.rust.codegen.server.smithy.generators.protocol.ServerAwsJsonProtocol
import software.amazon.smithy.rust.codegen.server.smithy.generators.protocol.ServerProtocol
import software.amazon.smithy.rust.codegen.server.smithy.generators.protocol.ServerProtocolGenerator
import software.amazon.smithy.rust.codegen.server.smithy.generators.protocol.ServerRestJsonProtocol
import software.amazon.smithy.rust.codegen.server.smithy.generators.protocol.ServerRestXmlProtocol
import software.amazon.smithy.rust.codegen.server.smithy.generators.protocol.ServerRpcV2CborProtocol
import software.amazon.smithy.rust.codegen.server.smithy.generators.serverBuilderSymbol
import java.util.logging.Logger

Expand Down Expand Up @@ -892,8 +896,59 @@ class ServerHttpBoundProtocolTraitImplGenerator(
)
}

// TODO(https://github.com/smithy-lang/smithy-rs/issues/3723): we should inject a check here that asserts that
// the body contents are valid when there is empty operation input or no operation input.
// Validate the body contents when there is no structured data parser (i.e. empty or no operation input).
// For JSON protocols, an empty body or `{}` is accepted; anything else is rejected.
// For CBOR, an empty body or an empty CBOR map (0xA0) is accepted.
// For XML, only an empty body is accepted.
val hasPayloadBinding = bindings.any { it.location == HttpLocation.PAYLOAD }
if (parser == null && !inputShape.hasStreamingMember(model) && !hasPayloadBinding) {
rustTemplate("let bytes = #{Hyper}::body::to_bytes(body).await?;", *codegenScope)
when (protocol) {
is ServerRestJsonProtocol, is ServerAwsJsonProtocol -> {
rustBlock("if !bytes.is_empty()") {
rustTemplate(
"""
if bytes.as_ref() != b"{}" {
return Err(#{RequestRejection}::JsonDeserialize(
#{JsonError}::custom("expected empty JSON object")
));
}
""",
*codegenScope,
"JsonError" to RuntimeType.smithyJson(runtimeConfig).resolve("deserialize::error::DeserializeError"),
)
}
}
is ServerRpcV2CborProtocol -> {
rustBlock("if !bytes.is_empty()") {
rustTemplate(
"""
if bytes.as_ref() != &[0xA0] {
return Err(#{RequestRejection}::CborDeserialize(
#{CborError}::custom("expected empty CBOR map", 0)
));
}
""",
*codegenScope,
"CborError" to RuntimeType.smithyCbor(runtimeConfig).resolve("decode::DeserializeError"),
)
Comment on lines +906 to +934
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer if we handled this without downcasting to the protocols themselves. I believe the client actually has code that is very similar to this (basically for checking / producing "empty" input for a given protocol)

}
}
is ServerRestXmlProtocol -> {
rustBlock("if !bytes.is_empty()") {
rustTemplate(
"""
return Err(#{RequestRejection}::XmlDeserialize(
#{XmlDecodeError}::custom("expected empty body for empty input")
));
""",
*codegenScope,
"XmlDecodeError" to RuntimeType.smithyXml(runtimeConfig).resolve("decode::XmlDecodeError"),
)
}
}
}
}

val err =
if (ServerBuilderGenerator.hasFallibleBuilder(
Expand Down