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
- 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.
- Ensure the upstream server sets an accurate Content-Length and serves the file uncompressed or with Content-Encoding handled at the transport layer.
- Catch the InvalidDataException/InvalidOperationException around the update check and degrade gracefully (skip this update check, log, retry later).
- 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
- Stream the body with a size cap instead of buffering all of it before checking.
- Distrust missing Content-Length: always enforce the cap on bytes read, not just on the header.
- Verify the upstream server sets accurate Content-Length and serves the manifest directly (no wrapping HTML).
- Log the actual content type and first bytes when this fires so the wrong-endpoint case is obvious.
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.