Bug 1871963: Implement zstd content-encoding support r=necko-reviewers,valentin

Differential Revision: https://phabricator.services.mozilla.com/D205109
This commit is contained in:
Randell Jesup 2024-04-09 12:31:56 +00:00
parent 764b8fb01e
commit b1ad40b8ee
9 changed files with 142 additions and 4 deletions

View File

@ -1203,7 +1203,7 @@ pref("network.http.redirection-limit", 20);
// NOTE: support for "compress" has been disabled per bug 196406.
// NOTE: separate values with comma+space (", "): see bug 576033
pref("network.http.accept-encoding", "gzip, deflate");
pref("network.http.accept-encoding.secure", "gzip, deflate, br");
pref("network.http.accept-encoding.secure", "gzip, deflate, br, zstd");
// Prompt for redirects resulting in unsafe HTTP requests
pref("network.http.prompt-temp-redirect", false);

View File

@ -31,6 +31,7 @@
#define APPLICATION_GZIP2 "application/gzip"
#define APPLICATION_GZIP3 "application/x-gunzip"
#define APPLICATION_BROTLI "application/brotli"
#define APPLICATION_ZSTD "application/zstd"
#define APPLICATION_ZIP "application/zip"
#define APPLICATION_HTTP_INDEX_FORMAT "application/http-index-format"
#define APPLICATION_ECMASCRIPT "application/ecmascript"
@ -245,6 +246,7 @@
#define ENCODING_UUENCODE3 "uuencode"
#define ENCODING_UUENCODE4 "uue"
#define ENCODING_YENCODE "x-yencode"
#define ENCODING_ZSTD "zstd"
/* Some names of parameters that various MIME headers include.
*/

View File

@ -1539,6 +1539,8 @@ HttpBaseChannel::DoApplyContentConversions(nsIStreamListener* aNextListener,
mode = 2;
} else if (from.EqualsLiteral("br")) {
mode = 3;
} else if (from.EqualsLiteral("zstd")) {
mode = 4;
}
Telemetry::Accumulate(Telemetry::HTTP_CONTENT_ENCODING, mode);
}
@ -1643,6 +1645,14 @@ HttpBaseChannel::nsContentEncodings::GetNext(nsACString& aNextEncoding) {
}
}
if (!haveType) {
encoding.BeginReading(start);
if (CaseInsensitiveFindInReadable("zstd"_ns, start, end)) {
aNextEncoding.AssignLiteral(APPLICATION_ZSTD);
haveType = true;
}
}
// Prepare to fetch the next encoding
mCurEnd = mCurStart;
mReady = false;

View File

@ -7690,6 +7690,7 @@ static nsLiteralCString ContentTypeToTelemetryLabel(nsHttpChannel* aChannel) {
return "proxy"_ns;
}
if (contentType.EqualsLiteral(APPLICATION_BROTLI) ||
contentType.EqualsLiteral(APPLICATION_ZSTD) ||
contentType.Find("zip") != kNotFound ||
contentType.Find("compress") != kNotFound) {
return "compressed"_ns;

View File

@ -29,3 +29,7 @@ LOCAL_INCLUDES += [
"/modules/brotli/dec",
"/netwerk/base",
]
DIRS += [
"/third_party/zstd",
]

View File

@ -29,6 +29,8 @@
#include "state.h"
#include "brotli/decode.h"
#include "zstd/zstd.h"
namespace mozilla {
namespace net {
@ -53,6 +55,26 @@ class BrotliWrapper {
uint64_t mSourceOffset{0};
};
class ZstdWrapper {
public:
ZstdWrapper() {
mDStream = ZSTD_createDStream();
ZSTD_DCtx_setParameter(mDStream, ZSTD_d_windowLogMax, 23 /*8*1024*1024*/);
}
~ZstdWrapper() {
if (mDStream) {
ZSTD_freeDStream(mDStream);
}
}
UniquePtr<uint8_t[]> mOutBuffer;
nsresult mStatus = NS_OK;
nsIRequest* mRequest{nullptr};
nsISupports* mContext{nullptr};
uint64_t mSourceOffset{0};
ZSTD_DStream* mDStream{nullptr};
};
// nsISupports implementation
NS_IMPL_ISUPPORTS(nsHTTPCompressConv, nsIStreamConverter, nsIStreamListener,
nsIRequestObserver, nsICompressConvStats,
@ -112,6 +134,12 @@ nsHTTPCompressConv::AsyncConvertData(const char* aFromType, const char* aToType,
} else if (!nsCRT::strncasecmp(aFromType, HTTP_BROTLI_TYPE,
sizeof(HTTP_BROTLI_TYPE) - 1)) {
mMode = HTTP_COMPRESS_BROTLI;
} else if (!nsCRT::strncasecmp(aFromType, HTTP_ZSTD_TYPE,
sizeof(HTTP_ZSTD_TYPE) - 1)) {
mMode = HTTP_COMPRESS_ZSTD;
} else if (!nsCRT::strncasecmp(aFromType, HTTP_ZST_TYPE,
sizeof(HTTP_ZST_TYPE) - 1)) {
mMode = HTTP_COMPRESS_ZSTD;
}
LOG(("nsHttpCompresssConv %p AsyncConvertData %s %s mode %d\n", this,
aFromType, aToType, (CompressMode)mMode));
@ -363,6 +391,71 @@ nsresult nsHTTPCompressConv::BrotliHandler(nsIInputStream* stream,
return self->mBrotli->mStatus;
}
/* static */
nsresult nsHTTPCompressConv::ZstdHandler(nsIInputStream* stream, void* closure,
const char* dataIn, uint32_t,
uint32_t aAvail, uint32_t* countRead) {
MOZ_ASSERT(stream);
nsHTTPCompressConv* self = static_cast<nsHTTPCompressConv*>(closure);
*countRead = 0;
const size_t kOutSize = ZSTD_DStreamOutSize(); // normally 128K
uint8_t* outPtr;
size_t avail = aAvail;
// Stop decompressing after an error
if (self->mZstd->mStatus != NS_OK) {
*countRead = aAvail;
return NS_OK;
}
if (!self->mZstd->mOutBuffer) {
self->mZstd->mOutBuffer = MakeUniqueFallible<uint8_t[]>(kOutSize);
if (!self->mZstd->mOutBuffer) {
self->mZstd->mStatus = NS_ERROR_OUT_OF_MEMORY;
return self->mZstd->mStatus;
}
}
ZSTD_inBuffer inBuffer = {.src = dataIn, .size = aAvail, .pos = 0};
uint32_t last_pos = 0;
while (inBuffer.pos < inBuffer.size) {
outPtr = self->mZstd->mOutBuffer.get();
LOG(("nsHttpCompresssConv %p zstdhandler decompress %zu\n", self, avail));
// Use ZSTD_(de)compressStream to (de)compress the input buffer into the
// output buffer, and fill aReadCount with the number of bytes consumed.
ZSTD_outBuffer outBuffer{.dst = outPtr, .size = kOutSize};
size_t result;
bool output_full;
do {
outBuffer.pos = 0;
result =
ZSTD_decompressStream(self->mZstd->mDStream, &outBuffer, &inBuffer);
// If we errored when writing, flag this and abort writing.
if (ZSTD_isError(result)) {
self->mZstd->mStatus = NS_ERROR_INVALID_CONTENT_ENCODING;
return self->mZstd->mStatus;
}
nsresult rv = self->do_OnDataAvailable(
self->mZstd->mRequest, self->mZstd->mSourceOffset,
reinterpret_cast<const char*>(outPtr), outBuffer.pos);
if (NS_FAILED(rv)) {
self->mZstd->mStatus = rv;
return rv;
}
self->mZstd->mSourceOffset += inBuffer.pos - last_pos;
last_pos = inBuffer.pos;
output_full = outBuffer.pos == outBuffer.size;
// in the unlikely case that the output buffer was full, loop to
// drain it before processing more input
} while (output_full);
}
*countRead = inBuffer.pos;
return NS_OK;
}
NS_IMETHODIMP
nsHTTPCompressConv::OnDataAvailable(nsIRequest* request, nsIInputStream* iStr,
uint64_t aSourceOffset, uint32_t aCount) {
@ -596,6 +689,25 @@ nsHTTPCompressConv::OnDataAvailable(nsIRequest* request, nsIInputStream* iStr,
}
} break;
case HTTP_COMPRESS_ZSTD: {
if (!mZstd) {
mZstd = MakeUnique<ZstdWrapper>();
}
mZstd->mRequest = request;
mZstd->mContext = nullptr;
mZstd->mSourceOffset = aSourceOffset;
uint32_t countRead;
rv = iStr->ReadSegments(ZstdHandler, this, streamLen, &countRead);
if (NS_SUCCEEDED(rv)) {
rv = mZstd->mStatus;
}
if (NS_FAILED(rv)) {
return rv;
}
} break;
default:
nsCOMPtr<nsIStreamListener> listener;
{

View File

@ -34,11 +34,14 @@ class nsIStringInputStream;
# define HTTP_BROTLI_TYPE "br"
# define HTTP_IDENTITY_TYPE "identity"
# define HTTP_UNCOMPRESSED_TYPE "uncompressed"
# define HTTP_ZSTD_TYPE "zstd"
# define HTTP_ZST_TYPE "zst"
namespace mozilla {
namespace net {
class BrotliWrapper;
class ZstdWrapper;
class nsHTTPCompressConv : public nsIStreamConverter,
public nsICompressConvStats {
@ -60,7 +63,8 @@ class nsHTTPCompressConv : public nsIStreamConverter,
HTTP_COMPRESS_DEFLATE,
HTTP_COMPRESS_COMPRESS,
HTTP_COMPRESS_BROTLI,
HTTP_COMPRESS_IDENTITY
HTTP_COMPRESS_IDENTITY,
HTTP_COMPRESS_ZSTD,
};
private:
@ -77,6 +81,7 @@ class nsHTTPCompressConv : public nsIStreamConverter,
uint32_t mInpBufferLen{0};
UniquePtr<BrotliWrapper> mBrotli;
UniquePtr<ZstdWrapper> mZstd;
nsCOMPtr<nsIStringInputStream> mStream;
@ -84,6 +89,10 @@ class nsHTTPCompressConv : public nsIStreamConverter,
const char* dataIn, uint32_t, uint32_t avail,
uint32_t* countRead);
static nsresult ZstdHandler(nsIInputStream* stream, void* closure,
const char* dataIn, uint32_t, uint32_t avail,
uint32_t* countRead);
nsresult do_OnDataAvailable(nsIRequest* request, uint64_t aSourceOffset,
const char* buffer, uint32_t aCount);

View File

@ -100,7 +100,7 @@ add_task(
});
equal(
Services.prefs.getCharPref("network.http.accept-encoding.secure"),
"gzip, deflate, br"
"gzip, deflate, br, zstd"
);
let { req, buff } = await new Promise(resolve => {
let chan = NetUtil.newChannel({

View File

@ -4815,7 +4815,7 @@
"expires_in_version": "never",
"kind": "enumerated",
"n_values": 6,
"description": "encoding removed: 0=unknown, 1=gzip, 2=deflate, 3=brotli"
"description": "encoding removed: 0=unknown, 1=gzip, 2=deflate, 3=brotli, 4=zstd"
},
"CACHE_LM_INCONSISTENT": {
"record_in_processes": ["main", "content"],