d2phap/ImageGlass · warning · InvalidOperationException

IGE: Metadata body exceeded limit: {json.Length} chars.

Error message

IGE: Metadata body exceeded limit: {json.Length} chars.

What it means

Second of two size guards in UpdateProvider's metadata fetch. After the Content-Length header passes (or is missing/zero), the response body is fully read into a string and re-checked against UpdateConstants.MaxMetadataSize (1 MiB). This catches servers that lie about or omit Content-Length while still streaming an oversized body.

Source

Thrown at source/ImageGlass.Lib/Common/ServiceProviders/UpdateProvider.cs:174

        // set per request: the value varies per check, so it cannot live on the shared client
        request.Headers.UserAgent.ParseAdd(UsageStatsAgent.Build(isScheduled));

        using var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, timeoutCts.Token)
            .ConfigureAwait(false);
        response.EnsureSuccessStatusCode();

        // enforce size limit
        var contentLength = response.Content.Headers.ContentLength ?? 0;
        if (contentLength > UpdateConstants.MaxMetadataSize)
        {
            throw new InvalidOperationException($"IGE: Metadata too large: {contentLength} bytes.");
        }

        var json = await response.Content.ReadAsStringAsync(timeoutCts.Token).ConfigureAwait(false);
        if (json.Length > UpdateConstants.MaxMetadataSize)
        {
            throw new InvalidOperationException($"IGE: Metadata body exceeded limit: {json.Length} chars.");
        }

        return json;
    }


    /// <summary>
    /// Parses the last check time from <see cref="Core.Config.AutoUpdate"/>.
    /// </summary>
    private static DateTime ParseLastCheckTime()
    {
        var value = Core.Config.AutoUpdate;
        if (string.IsNullOrEmpty(value) || string.Equals(value, "0", StringComparison.Ordinal))
        {
            return DateTime.MinValue;
        }

        return DateTime.TryParse(value, out var dt) ? dt.ToUniversalTime() : DateTime.MinValue;

View on GitHub (pinned to 4a3c4fecef)

Solutions

  1. Inspect the actual response body to see what is being served (curl the update URL with -v and -o) and confirm it is the intended JSON manifest.
  2. Ensure the upstream server sets an accurate Content-Length and serves the file uncompressed or with Content-Encoding handled at the transport layer.
  3. Catch the InvalidDataException/InvalidOperationException around the update check and degrade gracefully (skip this update check, log, retry later).
  4. If legitimate metadata exceeds 1 MiB, raise UpdateConstants.MaxMetadataSize in Update/UpdateConstants.cs:44 — but first verify nothing else (a wrong URL) is the cause.

Example fix

// before
var json = await response.Content.ReadAsStringAsync(timeoutCts.Token).ConfigureAwait(false);
if (json.Length > UpdateConstants.MaxMetadataSize) throw new InvalidOperationException(...);

// after — stream-read with a hard cap so a runaway body never fully buffers
using var fs = await response.Content.ReadAsStreamAsync(timeoutCts.Token).ConfigureAwait(false);
using var ms = new MemoryStream(checked((int)Math.Min(contentLength, UpdateConstants.MaxMetadataSize + 1)));
var buf = new byte[8192];
int n;
while ((n = await fs.ReadAsync(buf, timeoutCts.Token)) > 0) {
    ms.Write(buf, 0, n);
    if (ms.Length > UpdateConstants.MaxMetadataSize) throw new InvalidOperationException($"IGE: Metadata body exceeded limit after {ms.Length} bytes.");
}
var json = Encoding.UTF8.GetString(ms.GetBuffer(), 0, (int)ms.Length);
Defensive patterns

Strategy: validation

Validate before calling

// Read with a hard cap so a runaway body never fully buffers into RAM.
await using var stream = await response.Content.ReadAsStreamAsync(token);
using var capped = new MemoryStream();
var buf = new byte[8192]; int n;
while ((n = await stream.ReadAsync(buf, token)) > 0)
{
    capped.Write(buf, 0, n);
    if (capped.Length > UpdateConstants.MaxMetadataSize)
        return UpdateCheckResult.Skipped("Metadata stream exceeded limit");
}
var json = Encoding.UTF8.GetString(capped.GetBuffer(), 0, (int)capped.Length);

Try / catch

try { var json = await updateProvider.FetchMetadataAsync(token); }
catch (InvalidOperationException ex) when (ex.Message.Contains("body exceeded limit"))
{ _log.Warn($"Update metadata body oversize (chunked/no Content-Length), skipping: {ex.Message}"); return; }

Prevention

When it happens

Trigger: Produced when json.Length > MaxMetadataSize after ReadAsStringAsync completes. Happens when the server sends no Content-Length (chunked transfer encoding) so the header check at line 168 was skipped, or when the actual body is larger than the advertised Content-Length.

Common situations: Update endpoint serving chunked/gzip-compressed data with no Content-Length; a reverse proxy or middleware that strips Content-Length; an endpoint that streams a JSON array that grows over time past the 1 MiB mark; a man-in-the-middle injecting extra content.

Related errors


AI-assisted analysis of d2phap/ImageGlass@4a3c4fecef (2026-08-13). Data as JSON: /api/errors/b014164146472f17. Report an issue: GitHub.