iOfficeAI/OfficeCLI · error · InvalidDataException

body too large

Error message

body too large

What it means

Thrown by WatchServer.ReadPostBodyAsync when a POST selection request's Content-Length is negative or exceeds MaxSelectionBodyBytes (InvalidDataException). It is a slow-loris / memory-exhaustion guard on the watch server's HTTP surface (FUZZER-001). A prefix that already overshoots Content-Length is trimmed rather than rejected, but an oversized declared length is refused outright.

Solutions

  1. Keep POST bodies under MaxSelectionBodyBytes.
  2. Send selections in smaller batches.
  3. Fix the client's Content-Length computation.
Defensive patterns

Strategy: validation

Validate before calling

// Client side: cap payload before posting
if (Encoding.UTF8.GetByteCount(bodyJson) > MaxSelectionBodyBytes)
    throw new InvalidOperationException("selection payload too large; split into batches");

Try / catch

// Server side this is a hard refuse; client side avoid it:
// ensure Content-Length == actual body length and stays under the cap.

Prevention

When it happens

Trigger: A client posts a selection/mark body with a Content-Length greater than MaxSelectionBodyBytes or a negative Content-Length to the watch server's selection endpoint.

Common situations: A buggy/automated client sending an oversized payload; a fuzzed/malicious request; a client computing Content-Length incorrectly.

Related errors


AI-assisted analysis of iOfficeAI/OfficeCLI@1ced45e900 (2026-08-13). Data as JSON: /api/errors/ed63879f76918092. Report an issue: GitHub.

Appendix: source

Thrown at src/officecli/Core/Watch/WatchServer.cs:2219

    /// straddles a read boundary into two independent fragments, and each
    /// fragment decodes to U+FFFD — a large CJK payload came back from a
    /// 200 OK with characters silently replaced, and the replacements went
    /// straight into the document. Every /api POST shares this path so the
    /// bound checks stay identical across endpoints.
    ///
    /// Bounded by MaxSelectionBodyBytes (FUZZER-001 slow-loris) and
    /// PostBodyReadTimeout. A prefix overshooting Content-Length is trimmed:
    /// otherwise extra bytes could be smuggled in the header segment
    /// (FUZZER-002). A request with no Content-Length keeps the prefix as-is.
    /// </summary>
    private static async Task<string> ReadPostBodyAsync(
        NetworkStream stream, Dictionary<string, string> headers, byte[] bodyPrefix, CancellationToken token)
    {
        int contentLength = -1;
        if (headers.TryGetValue("Content-Length", out var clStr) && int.TryParse(clStr, out var parsedCl))
        {
            if (parsedCl < 0 || parsedCl > MaxSelectionBodyBytes)
                throw new InvalidDataException("body too large");
            contentLength = parsedCl;
        }

        if (contentLength < 0) return Encoding.UTF8.GetString(bodyPrefix);
        if (bodyPrefix.Length >= contentLength) return Encoding.UTF8.GetString(bodyPrefix, 0, contentLength);

        var body = new byte[contentLength];
        bodyPrefix.CopyTo(body, 0);
        int have = bodyPrefix.Length;
        using var readCts = CancellationTokenSource.CreateLinkedTokenSource(token);
        readCts.CancelAfter(PostBodyReadTimeout);
        try
        {
            while (have < contentLength)
            {
                var n = await stream.ReadAsync(body.AsMemory(have, contentLength - have), readCts.Token);
                if (n == 0) break;
                have += n;

View on GitHub (pinned to 1ced45e900)