Skip to content

HTTP 3.1.3.2 percent-encoding: two SDKs implement it, two do not (data for #777) #1397

Description

@filipsajdak

Summary

Two of the four SDKs I measured implement the percent-encoding rule in HTTP Protocol Binding 3.1.3.2 and two do not, so a conformant sender and a non-conformant receiver silently exchange corrupted attribute values today.

This is the data nobody came back with on #777. In that thread @duglin asked:

I'd be curious to know if those decoding libraries would just ignore non-escaped chars and pass them along. Meaning... if people cheated and only % encoded some of what they should if things would just work anyway :-)

and @jskeet replied:

I'm concerned about compatibility with existing CloudEvent providers and consumers.

The answer is that it does not just work, and the incompatibility is silent in the common direction.

Measured, 2026-09-21

SDK percent-encodes on send percent-decodes on receive
sdk-csharp (main) yes yes
sdk-cpp (cloudevents-sdk-cpp, not an official SDK) yes yes
sdk-go v2.15.2 no no
sdk-java (main) no no

sdk-go, run rather than read:

GO WRITES ce-subject = "a b"        // for subject "a b"
GO WRITES ce-pct     = "100%"       // for extension value "100%"
GO READS  "a%20b"    -> "a%20b"
GO READS  "100%25"   -> "100%25"

sdk-csharp/src/CloudNative.CloudEvents/Http/HttpUtilities.cs encodes and decodes space, double quote, percent, non-printable ASCII and non-ASCII, which is 3.1.3.2 exactly. sdk-go/v2/protocol/http/write_request.go and sdk-java/http/basic/.../HttpMessageWriter.java apply no transformation in either direction.

What that costs

Conformant sender to non-conformant receiver, silent:

event on the wire receiver's application sees
subject = "a b" ce-subject: a%20b a%20b
subject = "100%" ce-subject: 100%25 100%25

Non-conformant sender to conformant receiver:

event on the wire receiver produces
subject = "a b" ce-subject: a b a b, correct by luck: no escape present
subject = "100%" ce-subject: 100% rejected, malformed escape
subject = "100%41" ce-subject: 100%41 100A, silent corruption

Only binary mode, and only for values containing space, double quote, percent or a byte outside printable ASCII. In practice that is subject and string extensions; id, type and time are unaffected in normal use, and source tends to agree because URI serialization escapes it anyway. Structured mode is unaffected.

Why I think it went unnoticed

#793 said:

Once this is agreed, we should also document conformance tests, but it's unclear at the moment where they should live.

As far as I can find, they still do not exist. The rule is testable only against another implementation, and the interop testing I have seen between SDKs compares JSON event documents rather than binding header mappings, which is exactly the layer where this lives. My own SDK had the same gap: the format layer was well covered and the binary-mode wire shape was not covered at all, which is why I did not notice either.

What I am asking

Not for a decision from me, and I have no standing to propose one. But this seems worth resolving in one place rather than four:

  1. Is 3.1.3.2 still the intended rule? If yes, sdk-go and sdk-java are non-conformant and it is worth saying so plainly.
  2. If yes, could the conformance examples from Clarify HTTP header encoding/decoding rules #777 land somewhere the SDKs can consume? A table of (attribute value, on-the-wire header value) would be enough, and is what @jskeet asked for originally.
  3. If the rule is not going to be implemented broadly, the spec and the SDKs currently disagree in a way that corrupts data silently, which seems worse than either option on its own.

Happy to contribute the fixture table if that is useful, and to file the corresponding issues on sdk-go and sdk-java once there is a view here.

Context

Found while checking a C++ SDK for cross-SDK interoperability. That SDK stays conformant by default and offers an opt-in policy for talking to Go and Java, which is a workaround, not a fix.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions