Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Incorrect order of response body decompression when there are multiple content encodings in undici.fetch (?) #2158

Closed
rychkog opened this issue Jun 13, 2023 · 7 comments · Fixed by #2159
Labels
bug Something isn't working

Comments

@rychkog
Copy link
Contributor

rychkog commented Jun 13, 2023

Bug Description

I might misunderstood the spec, but

According to specification the Content-Encoding
might have multiple encodings:

If one or more encodings have been applied to a representation, the sender that applied the encodings MUST generate a Content-Encoding header field that lists the content codings in the order in which they were applied

And based on the implementation it is not the case:
https://github.com/nodejs/undici/blob/main/lib/fetch/index.js#L2007

Reproducible By

Run following server

const express = require('express');
const zlib = require('zlib');
const { pipeline, Readable } = require('stream');

const app = express();

app.get('/data', (req, res) => {
  const data = {
    message: 'Hello, world!',
    date: new Date()
  };

  const jsonData = JSON.stringify(data);

  // Enable compression for both gzip and deflate
  const gzip = zlib.createGzip();
  const deflate = zlib.createDeflate();

  // Set the appropriate response headers
  res.setHeader('Content-Type', 'application/json');
  // In order the coders were applied
  res.setHeader('Content-Encoding', 'deflate, gzip');

  pipeline(Readable.from([jsonData]), deflate, gzip, res, () => {})
});

const port = 3000;
app.listen(port, () => {
  console.log(`Server is running on port ${port}`);
});

Try to fetch the data:

  const undici = require('undici');
  const res = await undici.fetch('http://localhost:3000/data')
  console.log(await res.text());

Expected Behavior

When there is a payload with following Content-Encoding: deflate, gzip we need to decode it with gzip first and then with deflate

The code above crashes right now, but should return properly decoded text.

Logs & Screenshots

N/A

Environment

Ubuntu 22.04 LTS, Node v16.20.0

Additional context

N/A

@rychkog rychkog added the bug Something isn't working label Jun 13, 2023
@ronag
Copy link
Member

ronag commented Jun 13, 2023

It seems like the codings are applied in the wrong order. PR welcome.

@KhafraDev
Copy link
Member

The specification states that the sender must set the header properly. If the body is being decompressed incorrectly, this doesn't seem to be an issue with undici?

@ronag
Copy link
Member

ronag commented Jun 13, 2023

As I'm undersanding it, the sender is sending the header correctly, undici is not decompressing correctly. It's applying the decoders in the reverse order.

@KhafraDev
Copy link
Member

that doesn't seem to match the code though

undici/lib/fetch/index.js

Lines 2007 to 2036 in 2032e89

for (const coding of codings) {
// https://www.rfc-editor.org/rfc/rfc9112.html#section-7.2
if (coding === 'x-gzip' || coding === 'gzip') {
decoders.push(zlib.createGunzip({
// Be less strict when decoding compressed responses, since sometimes
// servers send slightly invalid responses that are still accepted
// by common browsers.
// Always using Z_SYNC_FLUSH is what cURL does.
flush: zlib.constants.Z_SYNC_FLUSH,
finishFlush: zlib.constants.Z_SYNC_FLUSH
}))
} else if (coding === 'deflate') {
decoders.push(zlib.createInflate())
} else if (coding === 'br') {
decoders.push(zlib.createBrotliDecompress())
} else {
decoders.length = 0
break
}
}
}
resolve({
status,
statusText,
headersList: headers[kHeadersList],
body: decoders.length
? pipeline(this.body, ...decoders, () => { })
: this.body.on('error', () => {})
})

@rychkog
Copy link
Contributor Author

rychkog commented Jun 13, 2023

@KhafraDev As I've pointed in the issue description

When there is a payload with following Content-Encoding: deflate, gzip we need to decode it with gzip first and then with deflate

So I believe the loop from your comment should iterate over codings.reverse()

@KhafraDev
Copy link
Member

would you like to send in a PR?

@rychkog
Copy link
Contributor Author

rychkog commented Jun 13, 2023

would you like to send in a PR?

Sure, will send

KhafraDev pushed a commit that referenced this issue Jun 14, 2023
#2158) (#2159)

Co-authored-by: rychkog-ma <georgii.ry@moonactive.com>
crysmags pushed a commit to crysmags/undici that referenced this issue Feb 27, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants