pjfanning opened a new pull request, #869:
URL: https://github.com/apache/pekko-grpc/pull/869

   ### Motivation
   
   `grpc-message` travels UTF-8 percent-encoded on the wire. The server side
   encodes it, and `Status-Message.parse` exists to decode it, but the 
pekko-http
   client's `mapToStatusException` read the header value raw:
   
   ```scala
   val description = allHeaders.find(_.name == "grpc-message").map(_.value)
   ```
   
   So an error description containing anything outside the unescaped set reached
   the caller as `caf%C3%A9` rather than `café`. The netty backend decodes it 
inside
   grpc-java, so this affected the pekko-http backend only.
   
   Fixing that exposed a second bug underneath. `PercentEncoding.Decoder.decode`
   converted its input with `getBytes(US_ASCII)`, which replaces every non-ASCII
   character with `?`. A conforming `grpc-message` is pure ASCII so this never 
bit
   on valid input, but the protocol is explicit that an invalid value must not 
be
   errored on or thrown away, and this silently destroyed it. Only values
   containing a `%` reach the slow path, which is why the existing round-trip 
tests
   missed it — they always encode first, so any non-ASCII is already escaped by 
the
   time `parse` sees it.
   
   **grpc-java does not have this bug.** Its `parseAsciiString` takes the wire
   `byte[]` directly and uses `US_ASCII` only for the two hex digits following a
   `%`, which are ASCII by definition; payload bytes are copied through 
untouched
   and decoded once as UTF-8 at the end. The pekko port takes a `String`, 
because
   `ModeledCustomHeaderCompanion.parse` hands it one, so it needs a 
String-to-bytes
   step that grpc-java simply does not have — and that step picked `US_ASCII`,
   following the name `parseAsciiString` rather than its actual data flow.
   
   ### Modification
   
   - `mapToStatusException` decodes the header through 
`PercentEncoding.Decoder`.
   - `Decoder.TransferEncoding` is now `UTF_8`. It is byte-identical to 
US-ASCII for
     conforming input and preserves the characters of a non-conforming one.
     `decodeSlow` already copies payload bytes through one at a time and decodes
     once as UTF-8 at the end, so multi-byte sequences reassemble correctly.
   
   ### Result
   
   A percent-encoded `grpc-message` reaches the caller as text on both 
backends, and
   a non-conforming one is no longer mangled.
   
   ### Tests
   
   - 3 cases in `PekkoHttpClientUtilsSpec`: an encoded message, a plain one 
that must
     pass through untouched, and a broken `%ZZ` escape that must not be thrown 
away
   - 1 case in `HeadersSpec` for the decoder, covering non-ASCII alongside a 
percent —
     the shape the existing round-trip tests cannot reach
   
   Confirmed both guards bite, by reverting each fix in turn:
   
   - client-side decode removed → the encoded-message case fails
   - `TransferEncoding` back to `US_ASCII` → the decoder case fails
   
   - `sbt "runtime/testOnly ...PekkoHttpClientUtilsSpec ...HeadersSpec"` — 14 
passed
   - `sbt "runtime/mimaReportBinaryIssues"` — passed
   - `sbt scalafmtAll scalafmtSbt` — applied
   - `sbt "runtime/test"` (full suite) — not run locally, left to CI
   
   ### Notes
   
   One divergence from grpc-java left as is: its fast-path guard requires
   `i + 2 < value.length` before taking the slow path, so a trailing `%`
   short-circuits, whereas `decode` only checks `indexOf('%') > -1`. Both 
produce
   the same output, just via different paths.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to